diff --git a/core/src/main/scala/chisel3/FormalTest.scala b/core/src/main/scala/chisel3/FormalTest.scala deleted file mode 100644 index e76cd2cc790..00000000000 --- a/core/src/main/scala/chisel3/FormalTest.scala +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chisel3 - -import chisel3.experimental.{BaseModule, Param} -import chisel3.internal.Builder -import chisel3.internal.firrtl.ir._ -import chisel3.internal.throwException -import chisel3.experimental.{SourceInfo, UnlocatableSourceInfo} - -object FormalTest { - def apply( - module: BaseModule, - params: MapTestParam = MapTestParam(Map.empty), - name: String = "" - )(implicit sourceInfo: SourceInfo): Unit = { - val proposedName = if (name != "") { - name - } else { - module._proposedName - } - val sanitizedName = Builder.globalNamespace.name(proposedName) - Builder.components += DefFormalTest(sanitizedName, module, params, sourceInfo) - } -} - -/** Parameters for test declarations. */ -sealed abstract class TestParam -case class IntTestParam(value: BigInt) extends TestParam -case class DoubleTestParam(value: Double) extends TestParam -case class StringTestParam(value: String) extends TestParam -case class ArrayTestParam(value: Seq[TestParam]) extends TestParam -case class MapTestParam(value: Map[String, TestParam]) extends TestParam diff --git a/core/src/main/scala/chisel3/TestMarker.scala b/core/src/main/scala/chisel3/TestMarker.scala new file mode 100644 index 00000000000..8936188d9b0 --- /dev/null +++ b/core/src/main/scala/chisel3/TestMarker.scala @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3 + +import chisel3.experimental.{BaseModule, Param} +import chisel3.internal.Builder +import chisel3.internal.firrtl.ir._ +import chisel3.internal.throwException +import chisel3.experimental.{SourceInfo, UnlocatableSourceInfo} + +object FormalTest { + + /** Mark a module as a formal test. + * + * Other tools can use this information to, for example, collect all modules + * marked as formal tests and run formal verification on them. This is + * particularly useful in combination with the `UnitTest` trait. + * + * @param module The module to be marked. + * @param params Optional user-defined test parameters. + * @param name Optional name for the test. Uses the module name by default. + * + * @example + * The following creates a module marked as a formal test: + * + * {{{ + * class TestHarness extends RawModule { + * FormalTest(this) + * } + * }}} + * + * Additional parameters may be passed to the test, which other tools may use + * to control how the test is interpreted or executed: + * + * {{{ + * class TestHarness extends RawModule { + * FormalTest( + * this, + * MapTestParam(Map( + * "a" -> IntTestParam(9001), + * "b" -> DoubleTestParam(13.37), + * "c" -> StringTestParam("hello"), + * "d" -> ArrayTestParam(Seq( + * IntTestParam(9001), + * StringTestParam("hello") + * )), + * "e" -> MapTestParam(Map( + * "x" -> IntTestParam(9001), + * "y" -> StringTestParam("hello"), + * )) + * )) + * ) + * } + * }}} + */ + def apply( + module: BaseModule, + params: MapTestParam = MapTestParam(Map.empty), + name: String = "" + )(implicit sourceInfo: SourceInfo): Unit = { + val proposedName = if (name != "") { + name + } else { + module._proposedName + } + val sanitizedName = Builder.globalNamespace.name(proposedName) + Builder.components += DefTestMarker("formal", sanitizedName, module, params, sourceInfo) + } +} + +object SimulationTest { + + /** Mark a module as a simulation test. + * + * Other tools can use this information to, for example, collect all modules + * marked as simulation tests and run them in a simulator. This is + * particularly useful in combination with the `UnitTest` trait. + * + * @param module The module to be marked. + * @param params Optional user-defined test parameters. + * @param name Optional name for the test. Uses the module name by default. + * + * @example + * The following creates a module marked as a simulation test: + * + * {{{ + * class TestHarness extends RawModule { + * SimulationTest(this) + * } + * }}} + * + * Additional parameters may be passed to the test, which other tools may use + * to control how the test is interpreted or executed: + * + * {{{ + * class TestHarness extends RawModule { + * SimulationTest( + * this, + * MapTestParam(Map( + * "a" -> IntTestParam(9001), + * "b" -> DoubleTestParam(13.37), + * "c" -> StringTestParam("hello"), + * "d" -> ArrayTestParam(Seq( + * IntTestParam(9001), + * StringTestParam("hello") + * )), + * "e" -> MapTestParam(Map( + * "x" -> IntTestParam(9001), + * "y" -> StringTestParam("hello"), + * )) + * )) + * ) + * } + * }}} + */ + def apply( + module: BaseModule, + params: MapTestParam = MapTestParam(Map.empty), + name: String = "" + )(implicit sourceInfo: SourceInfo): Unit = { + val proposedName = if (name != "") { + name + } else { + module._proposedName + } + val sanitizedName = Builder.globalNamespace.name(proposedName) + Builder.components += DefTestMarker("simulation", sanitizedName, module, params, sourceInfo) + } +} + +/** Parameters for test declarations. */ +sealed abstract class TestParam +case class IntTestParam(value: BigInt) extends TestParam +case class DoubleTestParam(value: Double) extends TestParam +case class StringTestParam(value: String) extends TestParam +case class ArrayTestParam(value: Seq[TestParam]) extends TestParam +case class MapTestParam(value: Map[String, TestParam]) extends TestParam diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index a3db9220e89..0256ff3c876 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -468,8 +468,9 @@ private[chisel3] object Converter { (ports ++ ctx.secretPorts).map(p => convert(p, typeAliases)), convert(block, ctx, typeAliases) ) - case ctx @ DefFormalTest(name, module, params, sourceInfo) => - fir.FormalTest( + case ctx @ DefTestMarker(kind, name, module, params, sourceInfo) => + fir.TestMarker( + kind, convert(sourceInfo), name, module.name, diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 2117d08d608..7135a8bacab 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -541,12 +541,14 @@ private[chisel3] object ir { params: Map[String, Param] ) extends Component - case class DefFormalTest( + case class DefTestMarker( + kind: String, name: String, module: BaseModule, params: MapTestParam, sourceInfo: SourceInfo ) extends Component { + require(kind == "formal" || kind == "simulation") def id = module val ports: Seq[Port] = Seq.empty override val secretPorts = mutable.ArrayBuffer[Port]() diff --git a/core/src/main/scala/chisel3/internal/firrtl/Serializer.scala b/core/src/main/scala/chisel3/internal/firrtl/Serializer.scala index 589b786e950..c3e5fcd88c1 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Serializer.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Serializer.scala @@ -587,9 +587,9 @@ private[chisel3] object Serializer { } Iterator(start) ++ serialize(block, ctx, typeAliases)(indent + 1) - case ctx @ DefFormalTest(name, module, params, sourceInfo) => + case ctx @ DefTestMarker(kind, name, module, params, sourceInfo) => implicit val b = new StringBuilder - doIndent(0); b ++= "formal "; b ++= legalize(name); b ++= " of "; b ++= legalize(module.name); b ++= " :"; + doIndent(0); b ++= kind; b ++= " "; b ++= legalize(name); b ++= " of "; b ++= legalize(module.name); b ++= " :"; serialize(sourceInfo) params.value.keys.toSeq.sorted.foreach { case name => newLineAndIndent(1); b ++= name; b ++= " = "; serialize(params.value(name)) diff --git a/firrtl/src/main/scala/firrtl/ir/IR.scala b/firrtl/src/main/scala/firrtl/ir/IR.scala index 4bc88519169..bfd620df013 100644 --- a/firrtl/src/main/scala/firrtl/ir/IR.scala +++ b/firrtl/src/main/scala/firrtl/ir/IR.scala @@ -656,11 +656,12 @@ case class StringTestParam(value: String) extends TestParam with UseSerializer case class ArrayTestParam(value: Seq[TestParam]) extends TestParam with UseSerializer case class MapTestParam(value: Map[String, TestParam]) extends TestParam with UseSerializer -/** Formal Test +/** Formal or Simulation Test */ -case class FormalTest(info: Info, name: String, moduleName: String, params: MapTestParam) +case class TestMarker(kind: String, info: Info, name: String, moduleName: String, params: MapTestParam) extends DefModule with UseSerializer { + require(kind == "formal" || kind == "simulation") val ports: Seq[Port] = Seq.empty } diff --git a/firrtl/src/main/scala/firrtl/ir/Serializer.scala b/firrtl/src/main/scala/firrtl/ir/Serializer.scala index 136780e8ab0..80db141b0ac 100644 --- a/firrtl/src/main/scala/firrtl/ir/Serializer.scala +++ b/firrtl/src/main/scala/firrtl/ir/Serializer.scala @@ -547,9 +547,10 @@ object Serializer { b.toString } Iterator(start) ++ sIt(body)(indent + 1) - case FormalTest(info, name, moduleName, params) => + case TestMarker(kind, info, name, moduleName, params) => implicit val b = new StringBuilder - doIndent(0); b ++= "formal "; b ++= legalize(name); b ++= " of "; b ++= legalize(moduleName); b ++= " :"; s(info) + doIndent(0); b ++= kind; b ++= " "; b ++= legalize(name); b ++= " of "; b ++= legalize(moduleName); b ++= " :"; + s(info) params.value.keys.toSeq.sorted.foreach { case name => newLineAndIndent(1); b ++= name; b ++= " = "; s(params.value(name)) } diff --git a/src/test/scala-2/chiselTests/RawModuleSpec.scala b/src/test/scala-2/chiselTests/RawModuleSpec.scala index af724179ae0..4368df1846f 100644 --- a/src/test/scala-2/chiselTests/RawModuleSpec.scala +++ b/src/test/scala-2/chiselTests/RawModuleSpec.scala @@ -271,4 +271,52 @@ class RawModuleSpec extends AnyFlatSpec with Matchers with ChiselSim with FileCh |""".stripMargin ) } + + "RawModule marked as simulation test" should "emit a simulation test declaration" in { + class Foo extends RawModule { + val clock = IO(Input(Clock())) + val init = IO(Input(Bool())) + val done = IO(Output(Bool())) + val success = IO(Output(Bool())) + done := true.B + success := true.B + + SimulationTest(this) + SimulationTest(this, MapTestParam(Map("hello" -> StringTestParam("world")))) + SimulationTest( + this, + MapTestParam( + Map( + "a_int" -> IntTestParam(42), + "b_double" -> DoubleTestParam(13.37), + "c_string" -> StringTestParam("hello"), + "d_array" -> ArrayTestParam(Seq(IntTestParam(42), StringTestParam("hello"))), + "e_map" -> MapTestParam( + Map( + "x" -> IntTestParam(42), + "y" -> StringTestParam("hello") + ) + ) + ) + ), + "thisBetterWork" + ) + } + + ChiselStage + .emitCHIRRTL(new Foo) + .fileCheck()( + """|CHECK: simulation Foo of [[FOO:Foo_.*]] : + |CHECK: simulation Foo_1 of [[FOO]] : + |CHECK: hello = "world" + |CHECK: simulation thisBetterWork of [[FOO]] : + |CHECK: a_int = 42 + |CHECK: b_double = 13.37 + |CHECK: c_string = "hello" + |CHECK: d_array = [42, "hello"] + |CHECK: e_map = {x = 42, y = "hello"} + |CHECK: module [[FOO]] : + |""".stripMargin + ) + } }