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