Add SvGenerationUtils. Useful functions for interfacing Chisel with SV. Change-Id: I6f08996d4017f38864eada9a34f6169525a58ecc
diff --git a/hdl/chisel/src/common/BUILD b/hdl/chisel/src/common/BUILD index df98801..40605d1 100644 --- a/hdl/chisel/src/common/BUILD +++ b/hdl/chisel/src/common/BUILD
@@ -123,6 +123,15 @@ ], ) +chisel_library( + name = "sv_generation_utils", + srcs = [ + "SvGenerationUtils.scala", + ], + visibility = ["//visibility:public"], + deps = [], +) + chisel_test( name = "common_test", srcs = [ @@ -208,6 +217,17 @@ ], ) +chisel_test( + name = "sv_generation_utils_test", + srcs = [ + "SvGenerationUtilsTest.scala", + ], + visibility = ["//visibility:public"], + deps = [ + ":sv_generation_utils", + ], +) + chisel_library( name = "testing", srcs = [
diff --git a/hdl/chisel/src/common/SvGenerationUtils.scala b/hdl/chisel/src/common/SvGenerationUtils.scala new file mode 100644 index 0000000..9d304a7 --- /dev/null +++ b/hdl/chisel/src/common/SvGenerationUtils.scala
@@ -0,0 +1,62 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import chisel3._ +import chisel3.reflect.DataMirror + +object GenerateInterface { + def apply(interface: Data, baseName: String = ""): String = { + def getLeafPortNames(name: String, data: Data): Seq[(String, Data)] = { + data match { + case _: Element => Seq((name, data)) + case r: Record => { + r.elements.toList.reverse.map({ case (n, d) => { + getLeafPortNames(s"${name}_${n}", d) + }}).reduce(_ ++ _) + } + case v: Vec[_] => v.zipWithIndex.flatMap { case (d, i) => + getLeafPortNames(s"${name}_${i}", d) + } + } + } + + val leafs = getLeafPortNames(baseName, interface) + var ios: Seq[String] = Seq() + for ((leafName, leafData) <- leafs) { + val direction = DataMirror.directionOf(leafData) match { + case chisel3.ActualDirection.Input => "input " + case chisel3.ActualDirection.Output => "output" + case _ => "unknown" + } + + val ioLine = leafData match { + case b: Bool => Some(s"$direction logic $leafName") + case u: UInt => Some(s"$direction logic [${u.getWidth-1}:0] $leafName") + case s: SInt => Some(s"$direction logic [${s.getWidth-1}:0] $leafName") + case c: Clock => Some(s"$direction logic $leafName") + case c: Reset => Some(s"$direction logic $leafName") + // Assume remaining element is a ChiselEnum + case e: Element => + Some(s"$direction logic [${e.getWidth-1}:0] $leafName") + case _ => None + } + + ios = ios ++ ioLine + } + + ios.map(" " ++ _).mkString(",\n") + } +}
diff --git a/hdl/chisel/src/common/SvGenerationUtilsTest.scala b/hdl/chisel/src/common/SvGenerationUtilsTest.scala new file mode 100644 index 0000000..30114b9 --- /dev/null +++ b/hdl/chisel/src/common/SvGenerationUtilsTest.scala
@@ -0,0 +1,169 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import chisel3._ +import chisel3.util._ +import chisel3.simulator.scalatest.ChiselSim +import org.scalatest.freespec.AnyFreeSpec + +class BasicModule extends Module { + val io = IO(new Bundle { + val in = Input(UInt(32.W)) + val out = Output(UInt(32.W)) + }) + + io.out := io.in + 1.U +} + +class ValidModule extends Module { + val io = IO(new Bundle { + val in = Input(Valid(UInt(32.W))) + val out = Output(Valid(UInt(32.W))) + }) + + io.out := io.in.map(_ + 1.U) +} + +object TrafficLight extends ChiselEnum { + val RED = Value + val YELLOW = Value + val GREEN = Value +} + +class ChiselEnumModule extends Module { + val io = IO(new Bundle { + val in = Input(TrafficLight()) + val out = Output(TrafficLight()) + }) + io.out := MuxLookup(io.in, TrafficLight.RED)(Seq( + TrafficLight.RED -> TrafficLight.GREEN, + TrafficLight.GREEN -> TrafficLight.YELLOW, + TrafficLight.YELLOW -> TrafficLight.RED, + )) +} + +class VectorModule extends Module { + val io = IO(new Bundle { + val in = Input(Vec(4, UInt(8.W))) + val out = Output(Vec(4, UInt(8.W))) + }) + io.out := io.in +} + +class KitchenSync extends Bundle { + val b = Bool() + val u = UInt(12.W) + val s = SInt(7.W) + val e = TrafficLight() + val v = Vec(3, new Bundle { + val nu = UInt(3.W) + val ns = SInt(2.W) + }) +} + +class KitchenSyncModule extends Module { + val io = IO(new Bundle { + val in = Flipped(Decoupled(new KitchenSync())) + val out = Decoupled(new KitchenSync()) + }) + io.out <> Queue(io.in, 2) +} + +class GenerateInterfaceSpec extends AnyFreeSpec with ChiselSim { + "BasicModule" in { + val expectedInterface = + """ input logic [31:0] io_in, + | output logic [31:0] io_out""".stripMargin + + simulate(new BasicModule()) { dut => + val interface = GenerateInterface(dut.io, "io") + assert(interface === expectedInterface) + } + } + + "ValidModule" in { + val expectedInterface = + """ input logic io_in_valid, + | input logic [31:0] io_in_bits, + | output logic io_out_valid, + | output logic [31:0] io_out_bits""".stripMargin + + simulate(new ValidModule()) { dut => + val interface = GenerateInterface(dut.io, "io") + assert(interface === expectedInterface) + } + } + + "ChiselEnumModule" in { + val expectedInterface = + """ input logic [1:0] io_in, + | output logic [1:0] io_out""".stripMargin + simulate(new ChiselEnumModule()) { dut => + val interface = GenerateInterface(dut.io, "io") + assert(interface === expectedInterface) + } + } + + "VectorModule" in { + val expectedInterface = + """ input logic [7:0] io_in_0, + | input logic [7:0] io_in_1, + | input logic [7:0] io_in_2, + | input logic [7:0] io_in_3, + | output logic [7:0] io_out_0, + | output logic [7:0] io_out_1, + | output logic [7:0] io_out_2, + | output logic [7:0] io_out_3""".stripMargin + + simulate(new VectorModule()) { dut => + val interface = GenerateInterface(dut.io, "io") + assert(interface === expectedInterface) + } + } + + "KitchenSyncModule" in { + val expectedInterface = + """ output logic io_in_ready, + | input logic io_in_valid, + | input logic io_in_bits_b, + | input logic [11:0] io_in_bits_u, + | input logic [6:0] io_in_bits_s, + | input logic [1:0] io_in_bits_e, + | input logic [2:0] io_in_bits_v_0_nu, + | input logic [1:0] io_in_bits_v_0_ns, + | input logic [2:0] io_in_bits_v_1_nu, + | input logic [1:0] io_in_bits_v_1_ns, + | input logic [2:0] io_in_bits_v_2_nu, + | input logic [1:0] io_in_bits_v_2_ns, + | input logic io_out_ready, + | output logic io_out_valid, + | output logic io_out_bits_b, + | output logic [11:0] io_out_bits_u, + | output logic [6:0] io_out_bits_s, + | output logic [1:0] io_out_bits_e, + | output logic [2:0] io_out_bits_v_0_nu, + | output logic [1:0] io_out_bits_v_0_ns, + | output logic [2:0] io_out_bits_v_1_nu, + | output logic [1:0] io_out_bits_v_1_ns, + | output logic [2:0] io_out_bits_v_2_nu, + | output logic [1:0] io_out_bits_v_2_ns""".stripMargin + + simulate(new KitchenSyncModule()) { dut => + val interface = GenerateInterface(dut.io, "io") + assert(interface === expectedInterface) + } + } +} \ No newline at end of file