diff options
Diffstat (limited to 'src/test')
54 files changed, 5012 insertions, 572 deletions
diff --git a/src/test/scala/chisel3/stage/phases/DriverCompatibilitySpec.scala b/src/test/scala/chisel3/stage/phases/DriverCompatibilitySpec.scala deleted file mode 100644 index b80d5298..00000000 --- a/src/test/scala/chisel3/stage/phases/DriverCompatibilitySpec.scala +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chisel3.stage.phases - - -import chisel3.stage.{NoRunFirrtlCompilerAnnotation, ChiselOutputFileAnnotation} - -import firrtl.options.{OutputAnnotationFileAnnotation, StageOptions} -import firrtl.options.Viewer.view -import firrtl.stage.phases.DriverCompatibility.TopNameAnnotation -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -class DriverCompatibilitySpec extends AnyFlatSpec with Matchers { - - behavior of classOf[DriverCompatibility.AddImplicitOutputFile].toString - - it should "do nothing if a ChiselOutputFileAnnotation is present" in { - val annotations = Seq( - ChiselOutputFileAnnotation("Foo"), - TopNameAnnotation("Bar") ) - (new DriverCompatibility.AddImplicitOutputFile).transform(annotations).toSeq should be (annotations) - } - - it should "add a ChiselOutputFileAnnotation derived from a TopNameAnnotation" in { - val annotations = Seq( TopNameAnnotation("Bar") ) - val expected = ChiselOutputFileAnnotation("Bar") +: annotations - (new DriverCompatibility.AddImplicitOutputFile).transform(annotations).toSeq should be (expected) - } - - behavior of classOf[DriverCompatibility.AddImplicitOutputAnnotationFile].toString - - it should "do nothing if an OutputAnnotationFileAnnotation is present" in { - val annotations = Seq( - OutputAnnotationFileAnnotation("Foo"), - TopNameAnnotation("Bar") ) - (new DriverCompatibility.AddImplicitOutputAnnotationFile).transform(annotations).toSeq should be (annotations) - } - - it should "add an OutputAnnotationFileAnnotation derived from a TopNameAnnotation" in { - val annotations = Seq( TopNameAnnotation("Bar") ) - val expected = OutputAnnotationFileAnnotation("Bar") +: annotations - (new DriverCompatibility.AddImplicitOutputAnnotationFile).transform(annotations).toSeq should be (expected) - } - - behavior of classOf[DriverCompatibility.DisableFirrtlStage].toString - - it should "add a NoRunFirrtlCompilerAnnotation if one does not exist" in { - val annos = Seq(NoRunFirrtlCompilerAnnotation) - val expected = DriverCompatibility.RunFirrtlCompilerAnnotation +: annos - (new DriverCompatibility.DisableFirrtlStage).transform(Seq.empty).toSeq should be (expected) - } - - it should "NOT add a NoRunFirrtlCompilerAnnotation if one already exists" in { - val annos = Seq(NoRunFirrtlCompilerAnnotation) - (new DriverCompatibility.DisableFirrtlStage).transform(annos).toSeq should be (annos) - } - - behavior of classOf[DriverCompatibility.ReEnableFirrtlStage].toString - - it should "NOT strip a NoRunFirrtlCompilerAnnotation if NO RunFirrtlCompilerAnnotation is present" in { - val annos = Seq(NoRunFirrtlCompilerAnnotation, DriverCompatibility.RunFirrtlCompilerAnnotation) - (new DriverCompatibility.ReEnableFirrtlStage).transform(annos).toSeq should be (Seq.empty) - } - - it should "strip a NoRunFirrtlCompilerAnnotation if a RunFirrtlCompilerAnnotation is present" in { - val annos = Seq(NoRunFirrtlCompilerAnnotation) - (new DriverCompatibility.ReEnableFirrtlStage).transform(annos).toSeq should be (annos) - } - -} diff --git a/src/test/scala/chisel3/testers/TestUtils.scala b/src/test/scala/chisel3/testers/TestUtils.scala index 12712bf7..c72c779a 100644 --- a/src/test/scala/chisel3/testers/TestUtils.scala +++ b/src/test/scala/chisel3/testers/TestUtils.scala @@ -3,10 +3,22 @@ package chisel3.testers import TesterDriver.Backend +import chisel3.{Bundle, RawModule} +import chisel3.internal.firrtl.Circuit +import chisel3.stage.ChiselStage import firrtl.AnnotationSeq object TestUtils { // Useful because TesterDriver.Backend is chisel3 package private def containsBackend(annos: AnnotationSeq): Boolean = annos.collectFirst { case b: Backend => b }.isDefined + + // Allows us to check that the compiler plugin cloneType is actually working + val usingPlugin: Boolean = (new Bundle { def check = _usingPlugin }).check + def elaborateNoReflectiveAutoCloneType(f: => RawModule): Circuit = { + ChiselStage.elaborate { + chisel3.internal.Builder.allowReflectiveAutoCloneType = !usingPlugin + f + } + } } diff --git a/src/test/scala/chiselTests/AsyncResetSpec.scala b/src/test/scala/chiselTests/AsyncResetSpec.scala index a8e62fe8..d49f390c 100644 --- a/src/test/scala/chiselTests/AsyncResetSpec.scala +++ b/src/test/scala/chiselTests/AsyncResetSpec.scala @@ -209,7 +209,6 @@ class AsyncResetSpec extends ChiselFlatSpec with Utils { } it should "support Fixed regs" in { - import chisel3.experimental.{withReset => _, _} assertTesterPasses(new BasicTester { val reg = withReset(reset.asAsyncReset)(RegNext(-6.0.F(2.BP), 3.F(2.BP))) val (count, done) = Counter(true.B, 4) @@ -223,7 +222,7 @@ class AsyncResetSpec extends ChiselFlatSpec with Utils { } it should "support Interval regs" in { - import chisel3.experimental.{withReset => _, _} + import chisel3.experimental._ assertTesterPasses(new BasicTester { val reg = withReset(reset.asAsyncReset) { val x = RegInit(Interval(range"[0,13]"), 13.I) diff --git a/src/test/scala/chiselTests/AutoClonetypeSpec.scala b/src/test/scala/chiselTests/AutoClonetypeSpec.scala index b791297d..fcbc4785 100644 --- a/src/test/scala/chiselTests/AutoClonetypeSpec.scala +++ b/src/test/scala/chiselTests/AutoClonetypeSpec.scala @@ -3,7 +3,8 @@ package chiselTests import chisel3._ -import chisel3.stage.ChiselStage +import chisel3.testers.TestUtils +import chisel3.util.QueueIO class BundleWithIntArg(val i: Int) extends Bundle { val out = UInt(i.W) @@ -65,10 +66,19 @@ class NestedAnonymousBundle extends Bundle { // Not necessarily good style (and not necessarily recommended), but allowed to preserve compatibility. class BundleWithArgumentField(val x: Data, val y: Data) extends Bundle +// Needs to be top-level so that reflective autoclonetype works +class InheritingBundle extends QueueIO(UInt(8.W), 8) { + val error = Output(Bool()) +} + +// TODO all `.suggestNames` are due to https://github.com/chipsalliance/chisel3/issues/1802 class AutoClonetypeSpec extends ChiselFlatSpec with Utils { + val usingPlugin: Boolean = TestUtils.usingPlugin + val elaborate = TestUtils.elaborateNoReflectiveAutoCloneType _ + "Bundles with Scala args" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") val myWire = Wire(new BundleWithIntArg(8)) assert(myWire.i == 8) @@ -76,8 +86,8 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { } "Bundles with Scala implicit args" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") implicit val implicitInt: Int = 4 val myWire = Wire(new BundleWithImplicit()) @@ -87,8 +97,8 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { } "Bundles with Scala explicit and impicit args" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") implicit val implicitInt: Int = 4 val myWire = Wire(new BundleWithArgAndImplicit(8)) @@ -99,16 +109,16 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { } "Subtyped Bundles" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") val myWire = Wire(new SubBundle(8, 4)) assert(myWire.i == 8) assert(myWire.i2 == 4) } } - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") val myWire = Wire(new SubBundleVal(8, 4)) @@ -117,68 +127,88 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { } } } - "Subtyped Bundles that don't clone well" should "be caught" in { - a [ChiselException] should be thrownBy extractCause[ChiselException] { - ChiselStage.elaborate { new Module { - val io = IO(new Bundle{}) - val myWire = Wire(new SubBundleInvalid(8, 4)) - } } + "Autoclonetype" should "work outside of a builder context" in { + new BundleWithIntArg(8).cloneType + } + + def checkSubBundleInvalid() = { + elaborate { new Module { + val io = IO(new Bundle{}).suggestName("io") + val myWire = Wire(new SubBundleInvalid(8, 4)) + } } + } + if (usingPlugin) { + "Subtyped Bundles that don't clone well" should "be now be supported!" in { + checkSubBundleInvalid() + } + } else { + "Subtyped Bundles that don't clone well" should "be caught" in { + a [ChiselException] should be thrownBy extractCause[ChiselException] { + checkSubBundleInvalid() + } } } "Inner bundles with Scala args" should "not need clonetype" in { - ChiselStage.elaborate { new ModuleWithInner } + elaborate { new ModuleWithInner } } "Bundles with arguments as fields" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(Output(new BundleWithArgumentField(UInt(8.W), UInt(8.W)))) + elaborate { new Module { + val io = IO(Output(new BundleWithArgumentField(UInt(8.W), UInt(8.W)))).suggestName("io") io.x := 1.U io.y := 1.U } } } + it should "also work when giving directions to the fields" in { + elaborate { new Module { + val io = IO(new BundleWithArgumentField(Input(UInt(8.W)), Output(UInt(8.W)))).suggestName("io") + io.y := io.x + } } + } + "Bundles inside companion objects" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(Output(new CompanionObjectWithBundle.Inner)) + elaborate { new Module { + val io = IO(Output(new CompanionObjectWithBundle.Inner)).suggestName("io") io.data := 1.U } } } "Parameterized bundles inside companion objects" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(Output(new CompanionObjectWithBundle.ParameterizedInner(8))) + elaborate { new Module { + val io = IO(Output(new CompanionObjectWithBundle.ParameterizedInner(8))).suggestName("io") io.data := 1.U } } } "Nested directioned anonymous Bundles" should "not need clonetype" in { - ChiselStage.elaborate { new Module { - val io = IO(new NestedAnonymousBundle) + elaborate { new Module { + val io = IO(new NestedAnonymousBundle).suggestName("io") val a = WireDefault(io) io.a.a := 1.U } } } "3.0 null compatibility" should "not need clonetype" in { - ChiselStage.elaborate { new Module { + elaborate { new Module { class InnerClassThing { def createBundle: Bundle = new Bundle { val a = Output(UInt(8.W)) } } - val io = IO((new InnerClassThing).createBundle) + val io = IO((new InnerClassThing).createBundle).suggestName("io") val a = WireDefault(io) } } } "Aliased fields" should "be caught" in { a [ChiselException] should be thrownBy extractCause[ChiselException] { - ChiselStage.elaborate { new Module { + elaborate { new Module { val bundleFieldType = UInt(8.W) val io = IO(Output(new Bundle { val a = bundleFieldType - })) + })).suggestName("io") io.a := 0.U } } } @@ -190,8 +220,8 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { val a = typeTuple._1 } - ChiselStage.elaborate { new Module { - val io = IO(Output(new BadBundle(UInt(8.W), 1))) + elaborate { new Module { + val io = IO(Output(new BadBundle(UInt(8.W), 1))).suggestName("io") io.a := 0.U } } } @@ -204,7 +234,7 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { val x = Output(UInt(3.W)) })) } - ChiselStage.elaborate { new TestModule } + elaborate { new TestModule } } "Wrapped IO construction with parent references" should "not fail for autoclonetype" in { @@ -216,6 +246,120 @@ class AutoClonetypeSpec extends ChiselFlatSpec with Utils { val x = Output(UInt(blah.W)) })) } - ChiselStage.elaborate { new TestModule(3) } + elaborate { new TestModule(3) } + } + + "Autoclonetype" should "support Bundles with if-blocks" in { + class MyModule(n: Int) extends MultiIOModule { + val io = IO(new Bundle { + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + if (n > 4) { + println("Here we are!") + } + }) + io.out := io.in + } + elaborate(new MyModule(3)) + } + + behavior of "Compiler Plugin Autoclonetype" + + // New tests from the plugin + if (usingPlugin) { + it should "NOT break code that extends chisel3.util Bundles if they use the plugin" in { + class MyModule extends MultiIOModule { + val io = IO(new InheritingBundle) + io.deq <> io.enq + io.count := 0.U + io.error := true.B + } + elaborate(new MyModule) + } + + it should "support Bundles with non-val parameters" in { + class MyBundle(i: Int) extends Bundle { + val foo = UInt(i.W) + } + elaborate { new MultiIOModule { + val in = IO(Input(new MyBundle(8))) + val out = IO(Output(new MyBundle(8))) + out := in + }} + } + + it should "support type-parameterized Bundles" in { + class MyBundle[T <: Data](gen: T) extends Bundle { + val foo = gen + } + elaborate { new MultiIOModule { + val in = IO(Input(new MyBundle(UInt(8.W)))) + val out = IO(Output(new MyBundle(UInt(8.W)))) + out := in + }} + } + + it should "support Bundles with non-val implicit parameters" in { + class MyBundle(implicit i: Int) extends Bundle { + val foo = UInt(i.W) + } + elaborate { new MultiIOModule { + implicit val x = 8 + val in = IO(Input(new MyBundle)) + val out = IO(Output(new MyBundle)) + out := in + }} + } + + it should "support Bundles with multiple parameter lists" in { + class MyBundle(i: Int)(j: Int, jj: Int)(k: UInt) extends Bundle { + val foo = UInt((i + j + jj + k.getWidth).W) + } + elaborate { + new MultiIOModule { + val in = IO(Input(new MyBundle(8)(8, 8)(UInt(8.W)))) + val out = IO(Output(new MyBundle(8)(8, 8)(UInt(8.W)))) + out := in + } + } + } + + it should "support Bundles that implement their own cloneType" in { + class MyBundle(i: Int) extends Bundle { + val foo = UInt(i.W) + override def cloneType = new MyBundle(i).asInstanceOf[this.type] + } + elaborate { new MultiIOModule { + val in = IO(Input(new MyBundle(8))) + val out = IO(Output(new MyBundle(8))) + out := in + }} + } + + it should "support Bundles that capture type parameters from their parent scope" in { + class MyModule[T <: Data](gen: T) extends MultiIOModule { + class MyBundle(n: Int) extends Bundle { + val foo = Vec(n, gen) + } + val in = IO(Input(new MyBundle(4))) + val out = IO(Output(new MyBundle(4))) + out := in + } + elaborate(new MyModule(UInt(8.W))) + } + + it should "work for higher-kinded types" in { + class DataGen[T <: Data](gen: T) { + def newType: T = gen.cloneType + } + class MyBundle[A <: Data, B <: DataGen[A]](gen: B) extends Bundle { + val foo = gen.newType + } + class MyModule extends MultiIOModule { + val io = IO(Output(new MyBundle[UInt, DataGen[UInt]](new DataGen(UInt(3.W))))) + io.foo := 0.U + } + elaborate(new MyModule) + } } } diff --git a/src/test/scala/chiselTests/AutoNestedCloneSpec.scala b/src/test/scala/chiselTests/AutoNestedCloneSpec.scala index 8e40ad20..401766e2 100644 --- a/src/test/scala/chiselTests/AutoNestedCloneSpec.scala +++ b/src/test/scala/chiselTests/AutoNestedCloneSpec.scala @@ -1,10 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 package chiselTests -import Chisel.ChiselException -import org.scalatest._ import chisel3._ -import chisel3.stage.ChiselStage +import chisel3.testers.TestUtils import org.scalatest.matchers.should.Matchers class BundleWithAnonymousInner(val w: Int) extends Bundle { @@ -13,11 +11,15 @@ class BundleWithAnonymousInner(val w: Int) extends Bundle { } } +// TODO all `.suggestNames` are due to https://github.com/chipsalliance/chisel3/issues/1802 class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { + val usingPlugin: Boolean = TestUtils.usingPlugin + val elaborate = TestUtils.elaborateNoReflectiveAutoCloneType _ + behavior of "autoCloneType of inner Bundle in Chisel3" it should "clone a doubly-nested inner bundle successfully" in { - ChiselStage.elaborate { + elaborate { class Outer(val w: Int) extends Module { class Middle(val w: Int) { class InnerIOType extends Bundle { @@ -25,7 +27,7 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } def getIO: InnerIOType = new InnerIOType } - val io = IO(new Bundle {}) + val io = IO(new Bundle {}).suggestName("io") val myWire = Wire((new Middle(w)).getIO) } new Outer(2) @@ -33,9 +35,9 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "clone an anonymous inner bundle successfully" in { - ChiselStage.elaborate { + elaborate { class TestTop(val w: Int) extends Module { - val io = IO(new Bundle {}) + val io = IO(new Bundle {}).suggestName("io") val myWire = Wire(new Bundle{ val a = UInt(w.W) }) } new TestTop(2) @@ -43,18 +45,18 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "pick the correct $outer instance for an anonymous inner bundle" in { - ChiselStage.elaborate { + elaborate { class Inner(val w: Int) extends Module { val io = IO(new Bundle{ val in = Input(UInt(w.W)) val out = Output(UInt(w.W)) - }) + }).suggestName("io") } class Outer(val w: Int) extends Module { val io = IO(new Bundle{ val in = Input(UInt(w.W)) val out = Output(UInt(w.W)) - }) + }).suggestName("io") val i = Module(new Inner(w)) val iw = Wire(chiselTypeOf(i.io)) iw <> io @@ -65,9 +67,9 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "clone an anonymous, bound, inner bundle of another bundle successfully" in { - ChiselStage.elaborate { + elaborate { class TestModule(w: Int) extends Module { - val io = IO(new BundleWithAnonymousInner(w) ) + val io = IO(new BundleWithAnonymousInner(w) ).suggestName("io") val w0 = WireDefault(io) val w1 = WireDefault(io.inner) } @@ -76,14 +78,14 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "clone an anonymous, inner bundle of a Module, bound to another bundle successfully" in { - ChiselStage.elaborate { + elaborate { class TestModule(w: Int) extends Module { val bun = new Bundle { val foo = UInt(w.W) } val io = IO(new Bundle { val inner = Input(bun) - }) + }).suggestName("io") val w0 = WireDefault(io) val w1 = WireDefault(io.inner) } @@ -92,31 +94,48 @@ class AutoNestedCloneSpec extends ChiselFlatSpec with Matchers with Utils { } it should "clone a double-nested anonymous Bundle" in { - ChiselStage.elaborate { + elaborate { class TestModule() extends Module { val io = IO(new Bundle { val inner = Input(new Bundle { val x = UInt(8.W) }) - }) + }).suggestName("io") } new TestModule() } } - // Test ignored because the compatibility null-inserting autoclonetype doesn't trip this - ignore should "fail on anonymous doubly-nested inner bundle with clear error" in { - intercept[ChiselException] { extractCause[ChiselException] { ChiselStage.elaborate { - class Outer(val w: Int) extends Module { - class Middle(val w: Int) { - def getIO: Bundle = new Bundle { - val in = Input(UInt(w.W)) + if (usingPlugin) { + // This works with the plugin, but is a null pointer exception when using reflective autoclonetype + it should "support an anonymous doubly-nested inner bundle" in { + elaborate { + class Outer(val w: Int) extends Module { + class Middle(val w: Int) { + def getIO: Bundle = new Bundle { + val in = Input(UInt(w.W)) + } } + val io = IO(new Bundle {}).suggestName("io") + val myWire = Wire((new Middle(w)).getIO) } - val io = IO(new Bundle {}) - val myWire = Wire((new Middle(w)).getIO) + new Outer(2) } - new Outer(2) - }}}.getMessage should include("Unable to determine instance") + } + + it should "support anonymous Inner bundles that capture type parameters from outer Bundles" in { + elaborate(new MultiIOModule { + class MyBundle[T <: Data](n: Int, gen: T) extends Bundle { + val foo = new Bundle { + val x = Input(Vec(n, gen)) + } + val bar = Output(Option(new { def mkBundle = new Bundle { val x = Vec(n, gen) }}).get.mkBundle) + } + val io = IO(new MyBundle(4, UInt(8.W))) + val myWire = WireInit(io.foo) + val myWire2 = WireInit(io.bar) + io.bar.x := io.foo.x + }) + } } } diff --git a/src/test/scala/chiselTests/BlackBoxImpl.scala b/src/test/scala/chiselTests/BlackBoxImpl.scala index f8e16ad7..a9a6fa29 100644 --- a/src/test/scala/chiselTests/BlackBoxImpl.scala +++ b/src/test/scala/chiselTests/BlackBoxImpl.scala @@ -8,6 +8,7 @@ import chisel3._ import chisel3.util.{HasBlackBoxInline, HasBlackBoxResource, HasBlackBoxPath} import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} import firrtl.FirrtlExecutionSuccess +import firrtl.transforms.BlackBoxNotFoundException import org.scalacheck.Test.Failed import org.scalatest.Succeeded import org.scalatest.freespec.AnyFreeSpec @@ -88,6 +89,15 @@ class UsesBlackBoxMinusViaPath extends Module { io.out := mod0.io.out } +class BlackBoxResourceNotFound extends HasBlackBoxResource { + val io = IO(new Bundle{}) + addResource("/missing.resource") +} + +class UsesMissingBlackBoxResource extends RawModule { + val foo = Module(new BlackBoxResourceNotFound) +} + class BlackBoxImplSpec extends AnyFreeSpec with Matchers { val targetDir = "test_run_dir" val stage = new ChiselStage @@ -114,5 +124,10 @@ class BlackBoxImplSpec extends AnyFreeSpec with Matchers { verilogOutput.delete() Succeeded } + "Resource files that do not exist produce Chisel errors" in { + assertThrows[BlackBoxNotFoundException]{ + ChiselStage.emitChirrtl(new UsesMissingBlackBoxResource) + } + } } } diff --git a/src/test/scala/chiselTests/BundleLiteralSpec.scala b/src/test/scala/chiselTests/BundleLiteralSpec.scala index 2a3ce2c9..b4adde4a 100644 --- a/src/test/scala/chiselTests/BundleLiteralSpec.scala +++ b/src/test/scala/chiselTests/BundleLiteralSpec.scala @@ -6,9 +6,8 @@ import chisel3._ import chisel3.stage.ChiselStage import chisel3.testers.BasicTester import chisel3.experimental.BundleLiterals._ -import chisel3.experimental.BundleLiteralException -import chisel3.experimental.ChiselEnum -import chisel3.experimental.FixedPoint +import chisel3.experimental.VecLiterals.AddVecLiteralConstructor +import chisel3.experimental.{BundleLiteralException, ChiselEnum, ChiselRange, FixedPoint, Interval} class BundleLiteralSpec extends ChiselFlatSpec with Utils { object MyEnum extends ChiselEnum { @@ -76,6 +75,24 @@ class BundleLiteralSpec extends ChiselFlatSpec with Utils { } } } + "bundle literals of vec literals" should "work" in { + assertTesterPasses(new BasicTester { + val range = range"[0,4].2" + val bundleWithVecs = new Bundle { + val a = Vec(2, UInt(4.W)) + val b = Vec(2, Interval(range)) + }.Lit( + _.a -> Vec(2, UInt(4.W)).Lit(0 -> 0xA.U, 1 -> 0xB.U), + _.b -> Vec(2, Interval(range)).Lit(0 -> (1.5).I(range), 1 -> (0.25).I(range)) + ) + chisel3.assert(bundleWithVecs.a(0) === 0xA.U) + chisel3.assert(bundleWithVecs.a(1) === 0xB.U) + chisel3.assert(bundleWithVecs.b(0) === (1.5).I(range)) + chisel3.assert(bundleWithVecs.b(1) === (0.25).I(range)) + stop() + }) + } + "partial bundle literals" should "work in RTL" in { assertTesterPasses{ new BasicTester{ val bundleLit = (new MyBundle).Lit(_.a -> 42.U) diff --git a/src/test/scala/chiselTests/BundleSpec.scala b/src/test/scala/chiselTests/BundleSpec.scala index 5d3f23ec..1d392f5c 100644 --- a/src/test/scala/chiselTests/BundleSpec.scala +++ b/src/test/scala/chiselTests/BundleSpec.scala @@ -129,6 +129,27 @@ class BundleSpec extends ChiselFlatSpec with BundleSpecUtils with Utils { }).getMessage should include("aliased fields") } + "Bundles" should "not have bound hardware" in { + (the[ChiselException] thrownBy extractCause[ChiselException] { + ChiselStage.elaborate { new Module { + class MyBundle(val foo: UInt) extends Bundle + val in = IO(Input(new MyBundle(123.U))) // This should error: value passed in instead of type + val out = IO(Output(new MyBundle(UInt(8.W)))) + + out := in + } } + }).getMessage should include("must be a Chisel type, not hardware") + } + "Bundles" should "not recursively contain aggregates with bound hardware" in { + (the[ChiselException] thrownBy extractCause[ChiselException] { + ChiselStage.elaborate { new Module { + class MyBundle(val foo: UInt) extends Bundle + val out = IO(Output(Vec(2, UInt(8.W)))) + val in = IO(Input(new MyBundle(out(0)))) // This should error: Bound aggregate passed + out := in + } } + }).getMessage should include("must be a Chisel type, not hardware") + } "Unbound bundles sharing a field" should "not error" in { ChiselStage.elaborate { new RawModule { @@ -141,17 +162,4 @@ class BundleSpec extends ChiselFlatSpec with BundleSpecUtils with Utils { } } } - - "Bound Data" should "have priority in setting ref over unbound Data" in { - class MyModule extends RawModule { - val foo = IO(new Bundle { - val x = Output(UInt(8.W)) - }) - foo.x := 0.U // getRef on foo.x is None.get without fix - val bar = new Bundle { - val y = foo.x - } - } - ChiselStage.emitChirrtl(new MyModule) - } } diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala index 843b3192..8647d903 100644 --- a/src/test/scala/chiselTests/ChiselSpec.scala +++ b/src/test/scala/chiselTests/ChiselSpec.scala @@ -2,22 +2,27 @@ package chiselTests -import org.scalatest._ -import org.scalatest.prop._ -import org.scalatest.flatspec.AnyFlatSpec -import org.scalacheck._ import chisel3._ -import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} -import chisel3.testers._ -import firrtl.{AnnotationSeq, CommonOptions, EmittedVerilogCircuitAnnotation, ExecutionOptionsManager, FirrtlExecutionFailure, FirrtlExecutionSuccess, HasFirrtlOptions} -import firrtl.annotations.{Annotation, DeletedAnnotation} -import firrtl.util.BackendCompilationUtilities -import java.io.ByteArrayOutputStream -import java.security.Permission - import chisel3.aop.Aspect import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage, NoRunFirrtlCompilerAnnotation, PrintFullStackTraceAnnotation} +import chisel3.testers._ +import firrtl.annotations.Annotation +import firrtl.ir.Circuit +import firrtl.util.BackendCompilationUtilities +import firrtl.{AnnotationSeq, EmittedVerilogCircuitAnnotation} +import _root_.logger.Logger +import firrtl.stage.FirrtlCircuitAnnotation +import org.scalacheck._ +import org.scalatest._ +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.propspec.AnyPropSpec +import org.scalatest.matchers.should.Matchers import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks + +import java.io.{ByteArrayOutputStream, PrintStream} +import java.security.Permission import scala.reflect.ClassTag /** Common utility functions for Chisel unit tests. */ @@ -85,67 +90,47 @@ trait ChiselRunners extends Assertions with BackendCompilationUtilities { case EmittedVerilogCircuitAnnotation(a) => a.value }.getOrElse(fail("No Verilog circuit was emitted by the FIRRTL compiler!")) } -} - -/** Spec base class for BDD-style testers. */ -abstract class ChiselFlatSpec extends AnyFlatSpec with ChiselRunners with Matchers - -class ChiselTestUtilitiesSpec extends ChiselFlatSpec { - import org.scalatest.exceptions.TestFailedException - // Who tests the testers? - "assertKnownWidth" should "error when the expected width is wrong" in { - val caught = intercept[ChiselException] { - assertKnownWidth(7) { - Wire(UInt(8.W)) - } - } - assert(caught.getCause.isInstanceOf[TestFailedException]) - } - it should "error when the width is unknown" in { - a [ChiselException] shouldBe thrownBy { - assertKnownWidth(7) { - Wire(UInt()) - } + def elaborateAndGetModule[A <: RawModule](t: => A): A = { + var res: Any = null + ChiselStage.elaborate { + res = t + res.asInstanceOf[A] } + res.asInstanceOf[A] } - it should "work if the width is correct" in { - assertKnownWidth(8) { - Wire(UInt(8.W)) - } + /** Compiles a Chisel Module to FIRRTL + * NOTE: This uses the "test_run_dir" as the default directory for generated code. + * @param t the generator for the module + * @return The FIRRTL Circuit and Annotations _before_ FIRRTL compilation + */ + def getFirrtlAndAnnos(t: => RawModule, providedAnnotations: Seq[Annotation] = Nil): (Circuit, Seq[Annotation]) = { + val args = Array( + "--target-dir", + createTestDirectory(this.getClass.getSimpleName).toString, + "--no-run-firrtl", + "--full-stacktrace" + ) + val annos = (new ChiselStage).execute(args, Seq(ChiselGeneratorAnnotation(() => t)) ++ providedAnnotations) + val circuit = annos.collectFirst { + case FirrtlCircuitAnnotation(c) => c + }.getOrElse(fail("No FIRRTL Circuit found!!")) + (circuit, annos) } +} - "assertInferredWidth" should "error if the width is known" in { - val caught = intercept[ChiselException] { - assertInferredWidth(8) { - Wire(UInt(8.W)) - } - } - assert(caught.getCause.isInstanceOf[TestFailedException]) - } +/** Spec base class for BDD-style testers. */ +abstract class ChiselFlatSpec extends AnyFlatSpec with ChiselRunners with Matchers - it should "error if the expected width is wrong" in { - a [TestFailedException] shouldBe thrownBy { - assertInferredWidth(8) { - val w = Wire(UInt()) - w := 2.U(2.W) - w - } - } - } +/** Spec base class for BDD-style testers. */ +abstract class ChiselFreeSpec extends AnyFreeSpec with ChiselRunners with Matchers - it should "pass if the width is correct" in { - assertInferredWidth(4) { - val w = Wire(UInt()) - w := 2.U(4.W) - w - } - } -} +/** Spec base class for BDD-style testers. */ +abstract class ChiselFunSpec extends AnyFunSpec with ChiselRunners with Matchers /** Spec base class for property-based testers. */ -class ChiselPropSpec extends PropSpec with ChiselRunners with ScalaCheckPropertyChecks with Matchers { +abstract class ChiselPropSpec extends AnyPropSpec with ChiselRunners with ScalaCheckPropertyChecks with Matchers { // Constrain the default number of instances generated for every use of forAll. implicit override val generatorDrivenConfig: PropertyCheckConfiguration = @@ -222,6 +207,20 @@ trait Utils { (stdout.toString, stderr.toString, ret) } + /** Run some Scala thunk and return all logged messages as Strings + * @param thunk some Scala code + * @return a tuple containing LOGGED, and what the thunk returns + */ + def grabLog[T](thunk: => T): (String, T) = { + val baos = new ByteArrayOutputStream() + val stream = new PrintStream(baos, true, "utf-8") + val ret = Logger.makeScope(Nil) { + Logger.setOutput(stream) + thunk + } + (baos.toString, ret) + } + /** Encodes a System.exit exit code * @param status the exit code */ @@ -343,7 +342,7 @@ trait Utils { exceptions.collectFirst{ case a: A => a } match { case Some(a) => throw a case None => exceptions match { - case Nil => Unit + case Nil => () case h :: t => throw h } } diff --git a/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala b/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala new file mode 100644 index 00000000..40358d11 --- /dev/null +++ b/src/test/scala/chiselTests/ChiselTestUtilitiesSpec.scala @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests + +import chisel3._ +import org.scalatest.exceptions.TestFailedException + +class ChiselTestUtilitiesSpec extends ChiselFlatSpec { + // Who tests the testers? + "assertKnownWidth" should "error when the expected width is wrong" in { + intercept[TestFailedException] { + assertKnownWidth(7) { + Wire(UInt(8.W)) + } + } + } + + it should "error when the width is unknown" in { + intercept[ChiselException] { + assertKnownWidth(7) { + Wire(UInt()) + } + } + } + + it should "work if the width is correct" in { + assertKnownWidth(8) { + Wire(UInt(8.W)) + } + } + + "assertInferredWidth" should "error if the width is known" in { + intercept[TestFailedException] { + assertInferredWidth(8) { + Wire(UInt(8.W)) + } + } + } + + it should "error if the expected width is wrong" in { + a [TestFailedException] shouldBe thrownBy { + assertInferredWidth(8) { + val w = Wire(UInt()) + w := 2.U(2.W) + w + } + } + } + + it should "pass if the width is correct" in { + assertInferredWidth(4) { + val w = Wire(UInt()) + w := 2.U(4.W) + w + } + } +} diff --git a/src/test/scala/chiselTests/CloneModuleSpec.scala b/src/test/scala/chiselTests/CloneModuleSpec.scala index e54ef1c2..8359bc28 100644 --- a/src/test/scala/chiselTests/CloneModuleSpec.scala +++ b/src/test/scala/chiselTests/CloneModuleSpec.scala @@ -4,7 +4,7 @@ package chiselTests import chisel3._ import chisel3.stage.ChiselStage -import chisel3.util.{Queue, EnqIO, DeqIO, QueueIO, log2Ceil} +import chisel3.util.{Decoupled, Queue, EnqIO, DeqIO, QueueIO, log2Ceil} import chisel3.experimental.{CloneModuleAsRecord, IO} import chisel3.testers.BasicTester @@ -57,6 +57,26 @@ class QueueCloneTester(x: Int, multiIO: Boolean = false) extends BasicTester { } } +class CloneModuleAsRecordAnnotate extends Module { + override def desiredName = "Top" + val in = IO(Flipped(Decoupled(UInt(8.W)))) + val out = IO(Decoupled(UInt(8.W))) + + val q1 = Module(new Queue(UInt(8.W), 4)) + val q2 = CloneModuleAsRecord(q1) + val q2_io = q2("io").asInstanceOf[q1.io.type] + // Also make a wire to check that cloning works, can be connected to, and annotated + val q2_wire = { + val w = Wire(chiselTypeOf(q2)) + w <> q2 + w + } + // But connect to the original (using last connect semantics to override connects to wire + q1.io.enq <> in + q2_io.enq <> q1.io.deq + out <> q2_io.deq +} + class CloneModuleSpec extends ChiselPropSpec { val xVals = Table( @@ -87,4 +107,48 @@ class CloneModuleSpec extends ChiselPropSpec { assert(c.modules.length == 3) } + property("Cloned Modules should annotate correctly") { + // Hackily get the actually Module object out + var mod: CloneModuleAsRecordAnnotate = null + val res = ChiselStage.convert { + mod = new CloneModuleAsRecordAnnotate + mod + } + // ********** Checking the output of CloneModuleAsRecord ********** + // Note that we overrode desiredName so that Top is named "Top" + mod.q1.io.enq.toTarget.serialize should be ("~Top|Queue>io.enq") + mod.q2_io.deq.toTarget.serialize should be ("~Top|Queue>io.deq") + mod.q1.io.enq.toAbsoluteTarget.serialize should be ("~Top|Top/q1:Queue>io.enq") + mod.q2_io.deq.toAbsoluteTarget.serialize should be ("~Top|Top/q2:Queue>io.deq") + // Legacy APIs that nevertheless were tricky to get right + mod.q1.io.enq.toNamed.serialize should be ("Top.Queue.io.enq") + mod.q2_io.deq.toNamed.serialize should be ("Top.Queue.io.deq") + mod.q1.io.enq.instanceName should be ("io.enq") + mod.q2_io.deq.instanceName should be ("io.deq") + mod.q1.io.enq.pathName should be ("Top.q1.io.enq") + mod.q2_io.deq.pathName should be ("Top.q2.io.deq") + mod.q1.io.enq.parentPathName should be ("Top.q1") + mod.q2_io.deq.parentPathName should be ("Top.q2") + mod.q1.io.enq.parentModName should be ("Queue") + mod.q2_io.deq.parentModName should be ("Queue") + + // ********** Checking the wire cloned from the output of CloneModuleAsRecord ********** + val wire_io = mod.q2_wire("io").asInstanceOf[QueueIO[UInt]] + mod.q2_wire.toTarget.serialize should be ("~Top|Top>q2_wire") + wire_io.enq.toTarget.serialize should be ("~Top|Top>q2_wire.io.enq") + mod.q2_wire.toAbsoluteTarget.serialize should be ("~Top|Top>q2_wire") + wire_io.enq.toAbsoluteTarget.serialize should be ("~Top|Top>q2_wire.io.enq") + // Legacy APIs + mod.q2_wire.toNamed.serialize should be ("Top.Top.q2_wire") + wire_io.enq.toNamed.serialize should be ("Top.Top.q2_wire.io.enq") + mod.q2_wire.instanceName should be ("q2_wire") + wire_io.enq.instanceName should be ("q2_wire.io.enq") + mod.q2_wire.pathName should be ("Top.q2_wire") + wire_io.enq.pathName should be ("Top.q2_wire.io.enq") + mod.q2_wire.parentPathName should be ("Top") + wire_io.enq.parentPathName should be ("Top") + mod.q2_wire.parentModName should be ("Top") + wire_io.enq.parentModName should be ("Top") + } + } diff --git a/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala b/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala index cfcc4608..1795cc1f 100644 --- a/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala +++ b/src/test/scala/chiselTests/CompatibilityInteroperabilitySpec.scala @@ -289,5 +289,75 @@ class CompatibiltyInteroperabilitySpec extends ChiselFlatSpec { } } } + + "A chisel3 Bundle that instantiates a Chisel Bundle" should "bulk connect correctly" in { + compile { + object Compat { + import Chisel._ + class BiDir extends Bundle { + val a = Input(UInt(8.W)) + val b = Output(UInt(8.W)) + } + class Struct extends Bundle { + val a = UInt(8.W) + } + } + import chisel3._ + import Compat._ + class Bar extends Bundle { + val bidir1 = new BiDir + val bidir2 = Flipped(new BiDir) + val struct1 = Output(new Struct) + val struct2 = Input(new Struct) + } + // Check every connection both ways to see that chisel3 <>'s commutativity holds + class Child extends RawModule { + val deq = IO(new Bar) + val enq = IO(Flipped(new Bar)) + enq <> deq + deq <> enq + } + new RawModule { + val deq = IO(new Bar) + val enq = IO(Flipped(new Bar)) + // Also important to check connections to child ports + val c1 = Module(new Child) + val c2 = Module(new Child) + c1.enq <> enq + enq <> c1.enq + c2.enq <> c1.deq + c1.deq <> c2.enq + deq <> c2.deq + c2.deq <> deq + } + } + } + + "A unidirectional but flipped Bundle" should "bulk connect in import chisel3._ code correctly" in { + object Compat { + import Chisel._ + class MyBundle(extraFlip: Boolean) extends Bundle { + private def maybeFlip[T <: Data](t: T): T = if (extraFlip) t.flip else t + val foo = maybeFlip(new Bundle { + val bar = UInt(INPUT, width = 8) + }) + override def cloneType = (new MyBundle(extraFlip)).asInstanceOf[this.type] + } + } + import chisel3._ + import Compat._ + class Top(extraFlip: Boolean) extends RawModule { + val port = IO(new MyBundle(extraFlip)) + val wire = Wire(new MyBundle(extraFlip)) + port <> DontCare + wire <> DontCare + port <> wire + wire <> port + port.foo <> wire.foo + wire.foo <> port.foo + } + compile(new Top(true)) + compile(new Top(false)) + } } diff --git a/src/test/scala/chiselTests/CompatibilitySpec.scala b/src/test/scala/chiselTests/CompatibilitySpec.scala index 6a77c821..2d4ad517 100644 --- a/src/test/scala/chiselTests/CompatibilitySpec.scala +++ b/src/test/scala/chiselTests/CompatibilitySpec.scala @@ -103,7 +103,6 @@ class CompatibiltySpec extends ChiselFlatSpec with ScalaCheckDrivenPropertyCheck Reverse(wire) shouldBe a [UInt] Cat(wire, wire) shouldBe a [UInt] Log2(wire) shouldBe a [UInt] - unless(Bool(false)) {} // 'switch' and 'is' are tested below in Risc Counter(2) shouldBe a [Counter] DecoupledIO(wire) shouldBe a [DecoupledIO[UInt]] @@ -353,13 +352,6 @@ class CompatibiltySpec extends ChiselFlatSpec with ScalaCheckDrivenPropertyCheck info("Deprecated method DC hasn't been removed") val bp = BitPat.DC(4) - - info("BitPat != UInt is a Bool") - (bp != UInt(4)) shouldBe a [Bool] - - /* This test does not work, but I'm not sure it's supposed to? It does *not* work on chisel3. */ - // info("UInt != BitPat is a Bool") - // (UInt(4) != bp) shouldBe a [Bool] } ChiselStage.elaborate(new Foo) @@ -459,6 +451,18 @@ class CompatibiltySpec extends ChiselFlatSpec with ScalaCheckDrivenPropertyCheck ChiselStage.elaborate(new Foo) } + it should "support data-types of mixed directionality" in { + class Foo extends Module { + val io = IO(new Bundle {}) + val tpe = new Bundle { val foo = UInt(OUTPUT, width = 4); val bar = UInt(width = 4) } + // NOTE for some reason, the old bug this hit did not occur when `tpe` is inlined + val mem = SeqMem(tpe, 8) + mem(3.U) + + } + ChiselStage.elaborate((new Foo)) + } + behavior of "debug" it should "still exist" in { @@ -474,22 +478,6 @@ class CompatibiltySpec extends ChiselFlatSpec with ScalaCheckDrivenPropertyCheck behavior of "Data methods" - it should "support legacy methods" in { - class Foo extends Module { - val io = IO(new Bundle{}) - - info("litArg works") - UInt(width=3).litArg() should be (None) - UInt(0, width=3).litArg() should be (Some(chisel3.internal.firrtl.ULit(0, 3.W))) - - info("toBits works") - val wire = Wire(UInt(width=4)) - Vec.fill(4)(wire).toBits.getWidth should be (wire.getWidth * 4) - } - - ChiselStage.elaborate(new Foo) - } - behavior of "Wire" it should "support legacy methods" in { @@ -542,9 +530,6 @@ class CompatibiltySpec extends ChiselFlatSpec with ScalaCheckDrivenPropertyCheck val u = UInt(8) val s = SInt(-4) - info("toBools works") - u.toBools shouldBe a [Seq[Bool]] - info("asBits works") s.asBits shouldBe a [Bits] @@ -553,35 +538,6 @@ class CompatibiltySpec extends ChiselFlatSpec with ScalaCheckDrivenPropertyCheck info("toUInt works") s.toUInt shouldBe a [UInt] - - info("toBool works") - UInt(1).toBool shouldBe a [Bool] - } - - ChiselStage.elaborate(new Foo) - } - - behavior of "UInt" - - it should "support legacy methods" in { - class Foo extends Module { - val io = new Bundle{} - - info("!= works") - (UInt(1) != UInt(1)) shouldBe a [Bool] - } - - ChiselStage.elaborate(new Foo) - } - - behavior of "SInt" - - it should "support legacy methods" in { - class Foo extends Module { - val io = new Bundle{} - - info("!= works") - (SInt(-1) != SInt(-1)) shouldBe a [Bool] } ChiselStage.elaborate(new Foo) diff --git a/src/test/scala/chiselTests/CustomBundle.scala b/src/test/scala/chiselTests/CustomBundle.scala new file mode 100644 index 00000000..b04dcc59 --- /dev/null +++ b/src/test/scala/chiselTests/CustomBundle.scala @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests + +import chisel3._ +import chisel3.experimental.{DataMirror, requireIsChiselType} +import scala.collection.immutable.ListMap + +// An example of how Record might be extended +// In this case, CustomBundle is a Record constructed from a Tuple of (String, Data) +// it is a possible implementation of a programmatic "Bundle" +// (and can by connected to MyBundle below) +final class CustomBundle(elts: (String, Data)*) extends Record { + val elements = ListMap(elts map { case (field, elt) => + requireIsChiselType(elt) + field -> elt + }: _*) + def apply(elt: String): Data = elements(elt) + override def cloneType: this.type = { + val cloned = elts.map { case (n, d) => n -> DataMirror.internal.chiselTypeClone(d) } + (new CustomBundle(cloned: _*)).asInstanceOf[this.type] + } +} + diff --git a/src/test/scala/chiselTests/DriverSpec.scala b/src/test/scala/chiselTests/DriverSpec.scala deleted file mode 100644 index 3a78683b..00000000 --- a/src/test/scala/chiselTests/DriverSpec.scala +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chiselTests - -import java.io.File - -import chisel3._ -import firrtl.FirrtlExecutionSuccess -import org.scalacheck.Test.Failed -import org.scalatest.Succeeded -import org.scalatest.freespec.AnyFreeSpec -import org.scalatest.matchers.should.Matchers - -class DummyModule extends Module { - val io = IO(new Bundle { - val in = Input(UInt(1.W)) - val out = Output(UInt(1.W)) - }) - io.out := io.in -} - -class TypeErrorModule extends chisel3.Module { - val in = IO(Input(UInt(1.W))) - val out = IO(Output(SInt(1.W))) - out := in -} - -class DriverSpec extends AnyFreeSpec with Matchers with chiselTests.Utils { - "Driver's execute methods are used to run chisel and firrtl" - { - "options can be picked up from comand line with no args" in { - // NOTE: Since we don't provide any arguments (notably, "--target-dir"), - // the generated files will be created in the current directory. - val targetDir = "." - Driver.execute(Array.empty[String], () => new DummyModule) match { - case ChiselExecutionSuccess(_, _, Some(_: FirrtlExecutionSuccess)) => - val exts = List("anno.json", "fir", "v") - for (ext <- exts) { - val dummyOutput = new File(targetDir, "DummyModule" + "." + ext) - info(s"${dummyOutput.toString} exists") - dummyOutput.exists() should be(true) - dummyOutput.delete() - } - Succeeded - case _ => - Failed - } - } - - "options can be picked up from comand line setting top name" in { - val targetDir = "local-build" - Driver.execute(Array("-tn", "dm", "-td", targetDir), () => new DummyModule) match { - case ChiselExecutionSuccess(_, _, Some(_: FirrtlExecutionSuccess)) => - val exts = List("anno.json", "fir", "v") - for (ext <- exts) { - val dummyOutput = new File(targetDir, "dm" + "." + ext) - info(s"${dummyOutput.toString} exists") - dummyOutput.exists() should be(true) - dummyOutput.delete() - } - Succeeded - case _ => - Failed - } - - } - - "execute returns a chisel execution result" in { - val targetDir = "test_run_dir" - val args = Array("--compiler", "low", "--target-dir", targetDir) - - info("Driver returned a ChiselExecutionSuccess") - val result = Driver.execute(args, () => new DummyModule) - result shouldBe a[ChiselExecutionSuccess] - - info("emitted circuit included 'circuit DummyModule'") - val successResult = result.asInstanceOf[ChiselExecutionSuccess] - successResult.emitted should include ("circuit DummyModule") - - val dummyOutput = new File(targetDir, "DummyModule.lo.fir") - info(s"${dummyOutput.toString} exists") - dummyOutput.exists() should be(true) - dummyOutput.delete() - } - - "user errors show a trimmed stack trace" in { - val targetDir = "test_run_dir" - val args = Array("--compiler", "low", "--target-dir", targetDir) - - val (stdout, stderr, result) = grabStdOutErr { Driver.execute(args, () => new TypeErrorModule) } - - info("stdout shows a trimmed stack trace") - stdout should include ("Stack trace trimmed to user code only") - - info("stdout does not include FIRRTL information") - stdout should not include ("firrtl.") - - info("Driver returned a ChiselExecutionFailure") - result shouldBe a [ChiselExecutionFailure] - } - } -} diff --git a/src/test/scala/chiselTests/ExtModule.scala b/src/test/scala/chiselTests/ExtModule.scala index 0c3a0633..161b6f5f 100644 --- a/src/test/scala/chiselTests/ExtModule.scala +++ b/src/test/scala/chiselTests/ExtModule.scala @@ -9,7 +9,7 @@ import chisel3.testers.{BasicTester, TesterDriver} // Avoid collisions with regular BlackBox tests by putting ExtModule blackboxes // in their own scope. -package ExtModule { +package extmoduletests { import chisel3.experimental.ExtModule @@ -25,8 +25,8 @@ package ExtModule { } class ExtModuleTester extends BasicTester { - val blackBoxPos = Module(new ExtModule.BlackBoxInverter) - val blackBoxNeg = Module(new ExtModule.BlackBoxInverter) + val blackBoxPos = Module(new extmoduletests.BlackBoxInverter) + val blackBoxNeg = Module(new extmoduletests.BlackBoxInverter) blackBoxPos.in := 1.U blackBoxNeg.in := 0.U @@ -42,10 +42,10 @@ class ExtModuleTester extends BasicTester { */ class MultiExtModuleTester extends BasicTester { - val blackBoxInvPos = Module(new ExtModule.BlackBoxInverter) - val blackBoxInvNeg = Module(new ExtModule.BlackBoxInverter) - val blackBoxPassPos = Module(new ExtModule.BlackBoxPassthrough) - val blackBoxPassNeg = Module(new ExtModule.BlackBoxPassthrough) + val blackBoxInvPos = Module(new extmoduletests.BlackBoxInverter) + val blackBoxInvNeg = Module(new extmoduletests.BlackBoxInverter) + val blackBoxPassPos = Module(new extmoduletests.BlackBoxPassthrough) + val blackBoxPassNeg = Module(new extmoduletests.BlackBoxPassthrough) blackBoxInvPos.in := 1.U blackBoxInvNeg.in := 0.U @@ -71,7 +71,7 @@ class ExtModuleSpec extends ChiselFlatSpec { "DataMirror.modulePorts" should "work with ExtModule" in { ChiselStage.elaborate(new Module { val io = IO(new Bundle { }) - val m = Module(new ExtModule.BlackBoxPassthrough) + val m = Module(new extmoduletests.BlackBoxPassthrough) assert(DataMirror.modulePorts(m) == Seq( "in" -> m.in, "out" -> m.out)) }) diff --git a/src/test/scala/chiselTests/ExtModuleImpl.scala b/src/test/scala/chiselTests/ExtModuleImpl.scala index f71a1335..c6cd4a9f 100644 --- a/src/test/scala/chiselTests/ExtModuleImpl.scala +++ b/src/test/scala/chiselTests/ExtModuleImpl.scala @@ -8,11 +8,11 @@ import chisel3._ import chisel3.experimental.ExtModule import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} import chisel3.util.{HasExtModuleInline, HasExtModulePath, HasExtModuleResource} -import firrtl.FirrtlExecutionSuccess import firrtl.options.TargetDirAnnotation import firrtl.stage.FirrtlCircuitAnnotation -import org.scalacheck.Test.Failed -import org.scalatest.{FreeSpec, Matchers, Succeeded} +import firrtl.transforms.BlackBoxNotFoundException +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should.Matchers //scalastyle:off magic.number @@ -92,7 +92,16 @@ class UsesExtModuleMinusViaPath extends Module { io.out := mod0.io.out } -class ExtModuleImplSpec extends FreeSpec with Matchers { +class ExtModuleResourceNotFound extends HasExtModuleResource { + val io = IO(new Bundle{}) + addResource("/missing.resource") +} + +class UsesMissingExtModuleResource extends RawModule { + val foo = Module(new ExtModuleResourceNotFound) +} + +class ExtModuleImplSpec extends AnyFreeSpec with Matchers { "ExtModule can have verilator source implementation" - { "Implementations can be contained in-line" in { @@ -137,5 +146,11 @@ class ExtModuleImplSpec extends FreeSpec with Matchers { verilogOutput.exists() should be(true) verilogOutput.delete() } + + "Resource files that do not exist produce Chisel errors" in { + assertThrows[BlackBoxNotFoundException]{ + ChiselStage.emitChirrtl(new UsesMissingExtModuleResource) + } + } } } diff --git a/src/test/scala/chiselTests/LoadMemoryFromFileSpec.scala b/src/test/scala/chiselTests/LoadMemoryFromFileSpec.scala index 3be649e1..8a998496 100644 --- a/src/test/scala/chiselTests/LoadMemoryFromFileSpec.scala +++ b/src/test/scala/chiselTests/LoadMemoryFromFileSpec.scala @@ -6,7 +6,7 @@ import java.io.File import chisel3._ import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} -import chisel3.util.experimental.loadMemoryFromFile +import chisel3.util.experimental.{loadMemoryFromFile,loadMemoryFromFileInline} import chisel3.util.log2Ceil import firrtl.FirrtlExecutionSuccess import firrtl.annotations.MemoryLoadFileType @@ -33,6 +33,26 @@ class UsesThreeMems(memoryDepth: Int, memoryType: Data) extends Module { io.value3 := memory3(io.address) } +class UsesThreeMemsInline(memoryDepth: Int, memoryType: Data, memoryFile: String, hexOrBinary: MemoryLoadFileType.FileType) extends Module { + val io = IO(new Bundle { + val address = Input(UInt(memoryType.getWidth.W)) + val value1 = Output(memoryType) + val value2 = Output(memoryType) + val value3 = Output(memoryType) + }) + + val memory1 = Mem(memoryDepth, memoryType) + val memory2 = Mem(memoryDepth, memoryType) + val memory3 = Mem(memoryDepth, memoryType) + loadMemoryFromFileInline(memory1, memoryFile, hexOrBinary) + loadMemoryFromFileInline(memory2, memoryFile, hexOrBinary) + loadMemoryFromFileInline(memory3, memoryFile, hexOrBinary) + + io.value1 := memory1(io.address) + io.value2 := memory2(io.address) + io.value3 := memory3(io.address) +} + class UsesMem(memoryDepth: Int, memoryType: Data) extends Module { val io = IO(new Bundle { val address = Input(UInt(memoryType.getWidth.W)) @@ -205,4 +225,35 @@ class LoadMemoryFromFileSpec extends AnyFreeSpec with Matchers { file.delete() } + "Module with more than one hex memory inline should work" in { + val testDirName = "test_run_dir/load_three_memory_spec_inline" + + val result = (new ChiselStage).execute( + args = Array("-X", "verilog", "--target-dir", testDirName), + annotations = Seq(ChiselGeneratorAnnotation(() => new UsesThreeMemsInline(memoryDepth = 8, memoryType = UInt(16.W), "./testmem.h", MemoryLoadFileType.Hex))) + ) + val dir = new File(testDirName) + val file = new File(dir, s"UsesThreeMemsInline.v") + file.exists() should be (true) + val fileText = io.Source.fromFile(file).getLines().mkString("\n") + fileText should include (s"""$$readmemh("./testmem.h", memory1);""") + fileText should include (s"""$$readmemh("./testmem.h", memory2);""") + fileText should include (s"""$$readmemh("./testmem.h", memory3);""") + } + + "Module with more than one bin memory inline should work" in { + val testDirName = "test_run_dir/load_three_memory_spec_inline" + + val result = (new ChiselStage).execute( + args = Array("-X", "verilog", "--target-dir", testDirName), + annotations = Seq(ChiselGeneratorAnnotation(() => new UsesThreeMemsInline(memoryDepth = 8, memoryType = UInt(16.W), "testmem.bin", MemoryLoadFileType.Binary))) + ) + val dir = new File(testDirName) + val file = new File(dir, s"UsesThreeMemsInline.v") + file.exists() should be (true) + val fileText = io.Source.fromFile(file).getLines().mkString("\n") + fileText should include (s"""$$readmemb("testmem.bin", memory1);""") + fileText should include (s"""$$readmemb("testmem.bin", memory2);""") + fileText should include (s"""$$readmemb("testmem.bin", memory3);""") + } } diff --git a/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala b/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala deleted file mode 100644 index 28673495..00000000 --- a/src/test/scala/chiselTests/MissingCloneBindingExceptionSpec.scala +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chiselTests -import Chisel.ChiselException -import chisel3.stage.ChiselStage -import org.scalatest._ -import org.scalatest.matchers.should.Matchers - -class MissingCloneBindingExceptionSpec extends ChiselFlatSpec with Matchers with Utils { - behavior of "missing cloneType in Chisel3" - ( the [ChiselException] thrownBy extractCause[ChiselException] { - import chisel3._ - - class Test extends Module { - class TestIO(w: Int) extends Bundle { - val a = Input(Vec(4, UInt(w.W))) - } - - val io = IO(new TestIO(32)) - } - - class TestTop extends Module { - val io = IO(new Bundle {}) - - val subs = VecInit(Seq.fill(2) { - Module(new Test).io - }) - } - - ChiselStage.elaborate(new TestTop) - }).getMessage should include("make all parameters immutable") - - behavior of "missing cloneType in Chisel2" - ( the [ChiselException] thrownBy extractCause[ChiselException] { - import Chisel._ - - class Test extends Module { - class TestIO(w: Int) extends Bundle { - val a = Vec(4, UInt(width = w)).asInput - } - - val io = IO(new TestIO(32)) - } - - class TestTop extends Module { - val io = IO(new Bundle {}) - - val subs = Vec.fill(2) { - Module(new Test).io - } - } - - ChiselStage.elaborate(new TestTop) - }).getMessage should include("make all parameters immutable") -} diff --git a/src/test/scala/chiselTests/Module.scala b/src/test/scala/chiselTests/Module.scala index 932c94a5..7703e876 100644 --- a/src/test/scala/chiselTests/Module.scala +++ b/src/test/scala/chiselTests/Module.scala @@ -3,8 +3,12 @@ package chiselTests import chisel3._ -import chisel3.stage.ChiselStage import chisel3.experimental.DataMirror +import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage, NoRunFirrtlCompilerAnnotation} +import firrtl.annotations.NoTargetAnnotation +import firrtl.options.Unserializable + +import scala.io.Source class SimpleIO extends Bundle { val in = Input(UInt(32.W)) @@ -140,6 +144,17 @@ class ModuleSpec extends ChiselPropSpec with Utils { assert(checkModule(this)) }) } + + property("object chisel3.util.experimental.getAnnotations should return current annotations.") { + case class DummyAnnotation() extends NoTargetAnnotation with Unserializable + (new ChiselStage).transform(Seq( + ChiselGeneratorAnnotation(() => new RawModule { + assert(chisel3.util.experimental.getAnnotations().contains(DummyAnnotation())) + }), + DummyAnnotation(), + NoRunFirrtlCompilerAnnotation)) + } + property("DataMirror.modulePorts should work") { ChiselStage.elaborate(new Module { val io = IO(new Bundle { }) @@ -174,4 +189,15 @@ class ModuleSpec extends ChiselPropSpec with Utils { } ChiselStage.elaborate(new RawModule with Foo) } + + property("getVerilogString(new PlusOne() should produce a valid Verilog string") { + val s = getVerilogString(new PlusOne()) + assert(s.contains("assign io_out = io_in + 32'h1")) + } + + property("emitVerilog((new PlusOne()..) shall produce a valid Verilog file in a subfolder") { + emitVerilog(new PlusOne(), Array("--target-dir", "generated")) + val s = Source.fromFile("generated/PlusOne.v").mkString("") + assert(s.contains("assign io_out = io_in + 32'h1")) + } } diff --git a/src/test/scala/chiselTests/OneHotMuxSpec.scala b/src/test/scala/chiselTests/OneHotMuxSpec.scala index 887843d4..7608a3e7 100644 --- a/src/test/scala/chiselTests/OneHotMuxSpec.scala +++ b/src/test/scala/chiselTests/OneHotMuxSpec.scala @@ -37,26 +37,18 @@ class OneHotMuxSpec extends AnyFreeSpec with Matchers with ChiselRunners { } } "simple one hot mux with all fixed width bundles but with different bundles should Not work" in { - try { + intercept[IllegalArgumentException] { assertTesterPasses(new DifferentBundleOneHotTester) - } catch { - case a: ChiselException => a.getCause match { - case _: IllegalArgumentException => - } } } "UIntToOH with output width greater than 2^(input width)" in { assertTesterPasses(new UIntToOHTester) } "UIntToOH should not accept width of zero (until zero-width wires are fixed" in { - try { + intercept[IllegalArgumentException] { assertTesterPasses(new BasicTester { val out = UIntToOH(0.U, 0) }) - } catch { - case a: ChiselException => a.getCause match { - case _: IllegalArgumentException => - } } } diff --git a/src/test/scala/chiselTests/PrintableSpec.scala b/src/test/scala/chiselTests/PrintableSpec.scala index c76b26de..25b54966 100644 --- a/src/test/scala/chiselTests/PrintableSpec.scala +++ b/src/test/scala/chiselTests/PrintableSpec.scala @@ -3,11 +3,33 @@ package chiselTests import chisel3._ +import chisel3.experimental.{BaseSim, ChiselAnnotation} import chisel3.stage.ChiselStage import chisel3.testers.BasicTester +import firrtl.annotations.{ReferenceTarget, SingleTargetAnnotation} import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers +import java.io.File + +/** Dummy [[printf]] annotation. + * @param target target of component to be annotated + */ +case class PrintfAnnotation(target: ReferenceTarget) extends SingleTargetAnnotation[ReferenceTarget] { + def duplicate(n: ReferenceTarget): PrintfAnnotation = this.copy(target = n) +} + +object PrintfAnnotation { + /** Create annotation for a given [[printf]]. + * @param c component to be annotated + */ + def annotate(c: BaseSim): Unit = { + chisel3.experimental.annotate(new ChiselAnnotation { + def toFirrtl: PrintfAnnotation = PrintfAnnotation(c.toTarget) + }) + } +} + /* Printable Tests */ class PrintableSpec extends AnyFlatSpec with Matchers { // This regex is brittle, it specifically finds the clock and enable signals followed by commas @@ -128,7 +150,6 @@ class PrintableSpec extends AnyFlatSpec with Matchers { printf(p"${FullName(myInst.io.fizz)}") } val firrtl = ChiselStage.emitChirrtl(new MyModule) - println(firrtl) getPrintfs(firrtl) match { case Seq(Printf("foo", Seq()), Printf("myWire.foo", Seq()), @@ -194,4 +215,48 @@ class PrintableSpec extends AnyFlatSpec with Matchers { case e => fail() } } + it should "get emitted with a name and annotated" in { + + /** Test circuit containing annotated and renamed [[printf]]s. */ + class PrintfAnnotationTest extends Module { + val myBun = Wire(new Bundle { + val foo = UInt(32.W) + val bar = UInt(32.W) + }) + myBun.foo := 0.U + myBun.bar := 0.U + val howdy = printf(p"hello ${myBun}") + PrintfAnnotation.annotate(howdy) + PrintfAnnotation.annotate(printf(p"goodbye $myBun")) + PrintfAnnotation.annotate(printf(p"adieu $myBun").suggestName("farewell")) + } + + // compile circuit + val testDir = new File("test_run_dir", "PrintfAnnotationTest") + (new ChiselStage).emitSystemVerilog( + gen = new PrintfAnnotationTest, + args = Array("-td", testDir.getPath) + ) + + // read in annotation file + val annoFile = new File(testDir, "PrintfAnnotationTest.anno.json") + annoFile should exist + val annoLines = scala.io.Source.fromFile(annoFile).getLines.toList + + // check for expected annotations + exactly(3, annoLines) should include ("chiselTests.PrintfAnnotation") + exactly(1, annoLines) should include ("~PrintfAnnotationTest|PrintfAnnotationTest>farewell") + exactly(1, annoLines) should include ("~PrintfAnnotationTest|PrintfAnnotationTest>SIM") + exactly(1, annoLines) should include ("~PrintfAnnotationTest|PrintfAnnotationTest>howdy") + + // read in FIRRTL file + val firFile = new File(testDir, "PrintfAnnotationTest.fir") + firFile should exist + val firLines = scala.io.Source.fromFile(firFile).getLines.toList + + // check that verification components have expected names + exactly(1, firLines) should include ("""printf(clock, UInt<1>("h1"), "hello AnonymousBundle(foo -> %d, bar -> %d)", myBun.foo, myBun.bar) : howdy""") + exactly(1, firLines) should include ("""printf(clock, UInt<1>("h1"), "goodbye AnonymousBundle(foo -> %d, bar -> %d)", myBun.foo, myBun.bar) : SIM""") + exactly(1, firLines) should include ("""printf(clock, UInt<1>("h1"), "adieu AnonymousBundle(foo -> %d, bar -> %d)", myBun.foo, myBun.bar) : farewell""") + } } diff --git a/src/test/scala/chiselTests/QueueFlushSpec.scala b/src/test/scala/chiselTests/QueueFlushSpec.scala new file mode 100644 index 00000000..11a411a8 --- /dev/null +++ b/src/test/scala/chiselTests/QueueFlushSpec.scala @@ -0,0 +1,259 @@ +package chiselTests + +import org.scalacheck._ + +import chisel3._ +import chisel3.testers.{BasicTester, TesterDriver} +import chisel3.util._ +import chisel3.util.random.LFSR +import treadle.WriteVcdAnnotation + +/** Test elements can be enqueued and dequeued when flush is tied to false + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +class ThingsPassThroughFlushQueueTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends ThingsPassThroughTester(elements, queueDepth, bitWidth, tap, useSyncReadMem, hasFlush = true) + +/** Generic flush queue tester base class + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +abstract class FlushQueueTesterBase(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), queueDepth, hasFlush = true)) + val elems = VecInit(elements.map(_.U)) + val inCnt = Counter(elements.length + 1) + val outCnt = RegInit(0.U(log2Ceil(elements.length).W)) + val currQCnt = RegInit(0.U(log2Ceil(5).W)) + + val flush: Bool = WireInit(false.B) + val flushRegister = RegNext(flush, init = false.B) + q.io.flush.get := flush + q.io.enq.valid := (inCnt.value < elements.length.U) + q.io.deq.ready := LFSR(16)(tap) + + q.io.enq.bits := elems(inCnt.value) + when(q.io.enq.fire()) { + inCnt.inc() + currQCnt := currQCnt + 1.U //counts how many items have been enqueued + } + when(q.io.deq.fire()) { + assert(flushRegister === false.B) //check queue isn't flushed (can't dequeue an empty queue) + } + when(flushRegister) { //Internal signal maybe_full is a register so some signals update on the next cycle + //check that queue gets flushed when queue is full + assert(q.io.count === 0.U) + assert(!q.io.deq.valid, "Expected to not be able to dequeue when flush is asserted the previous cycle") + assert(q.io.enq.ready, "Expected enqueue to be ready when flush was asserted the previous cycle because queue should be empty") + } + when(inCnt.value === elements.length.U) { //stop when all entries are enqueued + stop() + } +} + +/** Test queue can flush at random times + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +class QueueGetsFlushedTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends FlushQueueTesterBase(elements, queueDepth, bitWidth, tap, useSyncReadMem) { + flush := LFSR(16)((tap + 3) % 16) //testing a flush when flush is called randomly + val halfCnt = (queueDepth + 1)/2 + + when(q.io.deq.fire()) { + //ensure that what comes out is what comes in + assert(currQCnt <= queueDepth.U) + assert(elems(outCnt) === q.io.deq.bits) + outCnt := outCnt + 1.U + when (currQCnt > 0.U) { + currQCnt := Mux(q.io.enq.fire(), currQCnt, (currQCnt - 1.U)) + } + } + when(flush) { + assert(currQCnt === 0.U || q.io.deq.valid) + outCnt := outCnt + Mux(q.io.enq.fire(), (currQCnt + 1.U), currQCnt) + currQCnt := 0.U //resets the number of items currently inside queue + } +} + +/** Test queue can flush when empty + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +class EmptyFlushEdgecaseTester (elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends FlushQueueTesterBase(elements, queueDepth, bitWidth, tap, useSyncReadMem) { + val cycleCounter = Counter(elements.length + 1) + cycleCounter.inc() //counts every cycle + + //testing a flush when queue is empty + flush := (cycleCounter.value === 0.U && inCnt.value === 0.U) //flushed only before anything is enqueued + q.io.enq.valid := (inCnt.value < elements.length.U) && !flush + + when(q.io.deq.fire()) { + assert(elems(outCnt) === q.io.deq.bits) + outCnt := outCnt + 1.U + } +} + +/** Test queue can enqueue during a flush + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +class EnqueueEmptyFlushEdgecaseTester (elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends FlushQueueTesterBase(elements, queueDepth, bitWidth, tap, useSyncReadMem) { + val cycleCounter = Counter(elements.length + 1) + val outCounter = Counter(elements.length + 1) + + //testing an enqueue during a flush + flush := (cycleCounter.value === 0.U && inCnt.value === 0.U) //flushed only before anything is enqueued + cycleCounter.inc() //counts every cycle + + when(q.io.deq.fire()) { + //flush and enqueue were both active on the first cycle, + //so that element is flushed immediately which makes outCnt off by one + assert(elems(outCounter.value + 1.U) === q.io.deq.bits) //ensure that what comes out is what comes in + outCounter.inc() + } +} + +/** Test queue can flush when full + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +class FullQueueFlushEdgecaseTester (elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends FlushQueueTesterBase(elements, queueDepth, bitWidth, tap, useSyncReadMem) { + + //testing a flush when queue is full + flush := (currQCnt === queueDepth.U) + + when(q.io.deq.fire()) { + //ensure that what comes out is what comes in + assert(currQCnt <= queueDepth.U) + assert(elems(outCnt) === q.io.deq.bits) + outCnt := outCnt + 1.U + when (currQCnt > 0.U) { + currQCnt := currQCnt - 1.U + } + } + when(flush) { + outCnt := outCnt + currQCnt + currQCnt := 0.U //resets the number of items currently inside queue + assert(currQCnt === 0.U || q.io.deq.valid) + } +} + +/** Test queue can dequeue on the same cycle as a flush + * + * @param elements The sequence of elements used in the queue + * @param queueDepth The max number of entries in the queue + * @param bitWidth Integer size of the data type used in the queue + * @param tap Integer tap('seed') for the LFSR + * @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal memory element + */ +class DequeueFullQueueEdgecaseTester (elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends FlushQueueTesterBase(elements, queueDepth, bitWidth, tap, useSyncReadMem) { + //Queue should be able to dequeue when queue is not empty and flush is high + + //testing a flush when dequeue is called + flush := currQCnt === (queueDepth/2).U + q.io.enq.valid := !flushRegister + q.io.deq.ready := flush + + when(q.io.deq.fire()) { + //ensure that what comes out is what comes in + assert(currQCnt <= queueDepth.U) + assert(elems(outCnt) === q.io.deq.bits) + assert(currQCnt > 0.U) + } + when(flush) { + //The outcount register is one count behind because the dequeue happens at the same time as the flush + outCnt := outCnt + currQCnt + 1.U + currQCnt := 0.U //resets the number of items currently inside queue + assert(currQCnt === 0.U || q.io.deq.valid) + } + when(flushRegister) { + //check that queue gets flushed when queue is full + assert(q.io.deq.fire() === false.B) + } + +} + +class QueueFlushSpec extends ChiselPropSpec { + // Disable shrinking on error. + implicit val noShrinkListVal = Shrink[List[Int]](_ => Stream.empty) + implicit val noShrinkInt = Shrink[Int](_ => Stream.empty) + + property("Queue should have things pass through") { + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => + whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { + assertTesterPasses { + new ThingsPassThroughFlushQueueTester(se._2, depth, se._1, tap, isSync) + } + } + } + } + property("Queue should flush when requested") { + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => + whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { + assertTesterPasses { + new QueueGetsFlushedTester(se._2, depth, se._1, tap, isSync) + } + } + } + } + property("Queue flush when queue is empty") { + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => + whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { + assertTesterPasses { + new EmptyFlushEdgecaseTester(se._2, depth, se._1, tap, isSync) + } + } + } + } + property("Test queue can enqueue during a flush") { + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => + whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { + assertTesterPasses { + new EnqueueEmptyFlushEdgecaseTester(se._2, depth, se._1, tap, isSync) + } + } + } + } + property("Queue flush when queue is full") { + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => + whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { + assertTesterPasses { + new FullQueueFlushEdgecaseTester(se._2, depth, se._1, tap, isSync) + } + } + } + } + property("Queue should be able to dequeue when flush is high") { + forAll(Gen.choose(3, 5), safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => + whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { + assertTesterPasses ( + new DequeueFullQueueEdgecaseTester(se._2, depth, se._1, tap, isSync), + annotations = Seq(WriteVcdAnnotation) + ) + } + } + } +} diff --git a/src/test/scala/chiselTests/QueueSpec.scala b/src/test/scala/chiselTests/QueueSpec.scala index 9dc7f120..51b899cb 100644 --- a/src/test/scala/chiselTests/QueueSpec.scala +++ b/src/test/scala/chiselTests/QueueSpec.scala @@ -9,8 +9,8 @@ import chisel3.testers.BasicTester import chisel3.util._ import chisel3.util.random.LFSR -class ThingsPassThroughTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int) extends BasicTester { - val q = Module(new Queue(UInt(bitWidth.W), queueDepth)) +class ThingsPassThroughTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean, hasFlush: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), queueDepth, useSyncReadMem = useSyncReadMem, hasFlush = hasFlush)) val elems = VecInit(elements.map { _.asUInt() }) @@ -19,7 +19,7 @@ class ThingsPassThroughTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int q.io.enq.valid := (inCnt.value < elements.length.U) q.io.deq.ready := LFSR(16)(tap) - + q.io.flush.foreach { _ := false.B } //Flush behavior is tested in QueueFlushSpec q.io.enq.bits := elems(inCnt.value) when(q.io.enq.fire()) { inCnt.inc() @@ -34,8 +34,8 @@ class ThingsPassThroughTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int } } -class QueueReasonableReadyValid(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int) extends BasicTester { - val q = Module(new Queue(UInt(bitWidth.W), queueDepth)) +class QueueReasonableReadyValid(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), queueDepth, useSyncReadMem = useSyncReadMem)) val elems = VecInit(elements.map { _.asUInt() }) @@ -62,8 +62,8 @@ class QueueReasonableReadyValid(elements: Seq[Int], queueDepth: Int, bitWidth: I } } -class CountIsCorrectTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int) extends BasicTester { - val q = Module(new Queue(UInt(bitWidth.W), queueDepth)) +class CountIsCorrectTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), queueDepth, useSyncReadMem = useSyncReadMem)) val elems = VecInit(elements.map { _.asUInt(bitWidth.W) }) @@ -89,8 +89,8 @@ class CountIsCorrectTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, t } } -class QueueSinglePipeTester(elements: Seq[Int], bitWidth: Int, tap: Int) extends BasicTester { - val q = Module(new Queue(UInt(bitWidth.W), 1, pipe = true)) +class QueueSinglePipeTester(elements: Seq[Int], bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), 1, pipe = true, useSyncReadMem = useSyncReadMem)) val elems = VecInit(elements.map { _.asUInt(bitWidth.W) }) @@ -115,8 +115,8 @@ class QueueSinglePipeTester(elements: Seq[Int], bitWidth: Int, tap: Int) extends } } -class QueuePipeTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int) extends BasicTester { - val q = Module(new Queue(UInt(bitWidth.W), queueDepth, pipe = true)) +class QueuePipeTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), queueDepth, pipe = true, useSyncReadMem = useSyncReadMem)) val elems = VecInit(elements.map { _.asUInt(bitWidth.W) }) @@ -141,8 +141,8 @@ class QueuePipeTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: I } } -class QueueFlowTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int) extends BasicTester { - val q = Module(new Queue(UInt(bitWidth.W), queueDepth, flow = true)) +class QueueFlowTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { + val q = Module(new Queue(UInt(bitWidth.W), queueDepth, flow = true, useSyncReadMem = useSyncReadMem)) val elems = VecInit(elements.map { _.asUInt() }) @@ -169,9 +169,9 @@ class QueueFlowTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: I } } -class QueueFactoryTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int) extends BasicTester { +class QueueFactoryTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester { val enq = Wire(Decoupled(UInt(bitWidth.W))) - val deq = Queue(enq, queueDepth) + val deq = Queue(enq, queueDepth, useSyncReadMem = useSyncReadMem) val elems = VecInit(elements.map { _.asUInt() @@ -202,70 +202,70 @@ class QueueSpec extends ChiselPropSpec { implicit val noShrinkInt = Shrink[Int](_ => Stream.empty) property("Queue should have things pass through") { - forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15)) { (depth, se, tap) => + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { assertTesterPasses { - new ThingsPassThroughTester(se._2, depth, se._1, tap) + new ThingsPassThroughTester(se._2, depth, se._1, tap, isSync, false) } } } } property("Queue should have reasonable ready/valid") { - forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15)) { (depth, se, tap) => + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { assertTesterPasses { - new QueueReasonableReadyValid(se._2, depth, se._1, tap) + new QueueReasonableReadyValid(se._2, depth, se._1, tap, isSync) } } } } property("Queue should have correct count") { - forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15)) { (depth, se, tap) => + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { assertTesterPasses { - new CountIsCorrectTester(se._2, depth, se._1, tap) + new CountIsCorrectTester(se._2, depth, se._1, tap, isSync) } } } } property("Queue pipe should work for 1-element queues") { - forAll(safeUIntN(20), Gen.choose(0, 15)) { (se, tap) => + forAll(safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (se, tap, isSync) => whenever(se._1 >= 1 && se._2.nonEmpty) { assertTesterPasses { - new QueueSinglePipeTester(se._2, se._1, tap) + new QueueSinglePipeTester(se._2, se._1, tap, isSync) } } } } property("Queue pipe should work for more general queues") { - forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15)) { (depth, se, tap) => + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { assertTesterPasses { - new QueuePipeTester(se._2, depth, se._1, tap) + new QueuePipeTester(se._2, depth, se._1, tap, isSync) } } } } property("Queue flow should work") { - forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15)) { (depth, se, tap) => + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => whenever(se._1 >= 1 && depth >= 1 && se._2.nonEmpty) { assertTesterPasses { - new QueueFlowTester(se._2, depth, se._1, tap) + new QueueFlowTester(se._2, depth, se._1, tap, isSync) } } } } property("Queue companion object factory method should work") { - forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15)) { (depth, se, tap) => + forAll(vecSizes, safeUIntN(20), Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, se, tap, isSync) => whenever(se._1 >= 1 && se._2.nonEmpty) { assertTesterPasses { - new QueueFactoryTester(se._2, depth, se._1, tap) + new QueueFactoryTester(se._2, depth, se._1, tap, isSync) } } } diff --git a/src/test/scala/chiselTests/RecordSpec.scala b/src/test/scala/chiselTests/RecordSpec.scala index c34c2bf4..c21d455c 100644 --- a/src/test/scala/chiselTests/RecordSpec.scala +++ b/src/test/scala/chiselTests/RecordSpec.scala @@ -6,24 +6,7 @@ import chisel3._ import chisel3.stage.ChiselStage import chisel3.testers.BasicTester import chisel3.util.{Counter, Queue} -import chisel3.experimental.{DataMirror, requireIsChiselType} -import scala.collection.immutable.ListMap - -// An example of how Record might be extended -// In this case, CustomBundle is a Record constructed from a Tuple of (String, Data) -// it is a possible implementation of a programmatic "Bundle" -// (and can by connected to MyBundle below) -final class CustomBundle(elts: (String, Data)*) extends Record { - val elements = ListMap(elts map { case (field, elt) => - requireIsChiselType(elt) - field -> elt - }: _*) - def apply(elt: String): Data = elements(elt) - override def cloneType: this.type = { - val cloned = elts.map { case (n, d) => n -> DataMirror.internal.chiselTypeClone(d) } - (new CustomBundle(cloned: _*)).asInstanceOf[this.type] - } -} +import chisel3.experimental.DataMirror trait RecordSpecUtils { class MyBundle extends Bundle { diff --git a/src/test/scala/chiselTests/Reg.scala b/src/test/scala/chiselTests/Reg.scala index d86fe8b4..a02e6fa5 100644 --- a/src/test/scala/chiselTests/Reg.scala +++ b/src/test/scala/chiselTests/Reg.scala @@ -7,6 +7,7 @@ import chisel3.util._ import chisel3.experimental.DataMirror import chisel3.stage.ChiselStage import chisel3.testers.BasicTester +import org.scalacheck.Gen class RegSpec extends ChiselFlatSpec { "Reg" should "be of the same type and width as t" in { @@ -55,17 +56,35 @@ class ShiftResetTester(n: Int) extends BasicTester { val start = 23.U val sr = ShiftRegister(cntVal + 23.U, n, 1.U, true.B) when(done) { - assert(sr === 1.U) + assert(sr === (if(n == 0) cntVal + 23.U else 1.U)) stop() } } class ShiftRegisterSpec extends ChiselPropSpec { property("ShiftRegister should shift") { - forAll(smallPosInts) { (shift: Int) => assertTesterPasses{ new ShiftTester(shift) } } + forAll(Gen.choose(0, 4)) { (shift: Int) => assertTesterPasses{ new ShiftTester(shift) } } } property("ShiftRegister should reset all values inside") { - forAll(smallPosInts) { (shift: Int) => assertTesterPasses{ new ShiftResetTester(shift) } } + forAll(Gen.choose(0, 4)) { (shift: Int) => assertTesterPasses{ new ShiftResetTester(shift) } } + } +} + +class ShiftsTester(n: Int) extends BasicTester { + val (cntVal, done) = Counter(true.B, n) + val start = 23.U + val srs = ShiftRegisters(cntVal + start, n) + when(RegNext(done)) { + srs.zipWithIndex.foreach{ case (data, index) => + assert(data === (23 + n - 1 - index).U) + } + stop() + } +} + +class ShiftRegistersSpec extends ChiselPropSpec { + property("ShiftRegisters should shift") { + forAll(Gen.choose(0, 4)) { (shift: Int) => assertTesterPasses{ new ShiftsTester(shift) } } } } diff --git a/src/test/scala/chiselTests/ResetSpec.scala b/src/test/scala/chiselTests/ResetSpec.scala index 0e535964..7a5d444d 100644 --- a/src/test/scala/chiselTests/ResetSpec.scala +++ b/src/test/scala/chiselTests/ResetSpec.scala @@ -44,6 +44,26 @@ class ResetSpec extends ChiselFlatSpec with Utils { ChiselStage.elaborate(new AbstractResetDontCareModule) } + it should "be able to drive Bool" in { + ChiselStage.emitVerilog(new RawModule { + val in = IO(Input(Bool())) + val out = IO(Output(Bool())) + val w = Wire(Reset()) + w := in + out := w + }) + } + + it should "be able to drive AsyncReset" in { + ChiselStage.emitVerilog(new RawModule { + val in = IO(Input(AsyncReset())) + val out = IO(Output(AsyncReset())) + val w = Wire(Reset()) + w := in + out := w + }) + } + it should "allow writing modules that are reset agnostic" in { val sync = compile(new Module { val io = IO(new Bundle { diff --git a/src/test/scala/chiselTests/SIntOps.scala b/src/test/scala/chiselTests/SIntOps.scala index 9aacc378..f2e238e9 100644 --- a/src/test/scala/chiselTests/SIntOps.scala +++ b/src/test/scala/chiselTests/SIntOps.scala @@ -116,4 +116,36 @@ class SIntOpsSpec extends ChiselPropSpec with Utils { assertTesterPasses(new SIntLitExtractTester) } + // We use WireDefault with 2 arguments because of + // https://www.chisel-lang.org/api/3.4.1/chisel3/WireDefault$.html + // Single Argument case 2 + property("modulo divide should give min width of arguments") { + assertKnownWidth(4) { + val x = WireDefault(SInt(8.W), DontCare) + val y = WireDefault(SInt(4.W), DontCare) + val op = x % y + WireDefault(chiselTypeOf(op), op) + } + assertKnownWidth(4) { + val x = WireDefault(SInt(4.W), DontCare) + val y = WireDefault(SInt(8.W), DontCare) + val op = x % y + WireDefault(chiselTypeOf(op), op) + } + } + + property("division should give the width of the numerator + 1") { + assertKnownWidth(9) { + val x = WireDefault(SInt(8.W), DontCare) + val y = WireDefault(SInt(4.W), DontCare) + val op = x / y + WireDefault(chiselTypeOf(op), op) + } + assertKnownWidth(5) { + val x = WireDefault(SInt(4.W), DontCare) + val y = WireDefault(SInt(8.W), DontCare) + val op = x / y + WireDefault(chiselTypeOf(op), op) + } + } } diff --git a/src/test/scala/chiselTests/StrongEnum.scala b/src/test/scala/chiselTests/StrongEnum.scala index bf0eb2fe..d7dea571 100644 --- a/src/test/scala/chiselTests/StrongEnum.scala +++ b/src/test/scala/chiselTests/StrongEnum.scala @@ -74,6 +74,18 @@ class CastFromNonLit extends Module { io.valid := io.out.isValid } +class SafeCastFromNonLit extends Module { + val io = IO(new Bundle { + val in = Input(UInt(EnumExample.getWidth.W)) + val out = Output(EnumExample()) + val valid = Output(Bool()) + }) + + val (enum, valid) = EnumExample.safe(io.in) + io.out := enum + io.valid := valid +} + class CastFromNonLitWidth(w: Option[Int] = None) extends Module { val width = if (w.isDefined) w.get.W else UnknownWidth() @@ -191,6 +203,28 @@ class CastFromNonLitTester extends BasicTester { stop() } +class SafeCastFromNonLitTester extends BasicTester { + for ((enum,lit) <- EnumExample.all zip EnumExample.litValues) { + val mod = Module(new SafeCastFromNonLit) + mod.io.in := lit + assert(mod.io.out === enum) + assert(mod.io.valid === true.B) + } + + val invalid_values = (1 until (1 << EnumExample.getWidth)). + filter(!EnumExample.litValues.map(_.litValue).contains(_)). + map(_.U) + + for (invalid_val <- invalid_values) { + val mod = Module(new SafeCastFromNonLit) + mod.io.in := invalid_val + + assert(mod.io.valid === false.B) + } + + stop() +} + class CastToInvalidEnumTester extends BasicTester { val invalid_value: UInt = EnumExample.litValues.last + 1.U Module(new CastFromLit(invalid_value)) @@ -270,6 +304,41 @@ class StrongEnumFSMTester extends BasicTester { } } +class IsOneOfTester extends BasicTester { + import EnumExample._ + + // is one of itself + assert(e0.isOneOf(e0)) + + // is one of Seq of itself + assert(e0.isOneOf(Seq(e0))) + assert(e0.isOneOf(Seq(e0, e0, e0, e0))) + assert(e0.isOneOf(e0, e0, e0, e0)) + + // is one of Seq of multiple elements + val subset = Seq(e0, e1, e2) + assert(e0.isOneOf(subset)) + assert(e1.isOneOf(subset)) + assert(e2.isOneOf(subset)) + + // is not element not in subset + assert(!e100.isOneOf(subset)) + assert(!e101.isOneOf(subset)) + + // test multiple elements with variable number of arguments + assert(e0.isOneOf(e0, e1, e2)) + assert(e1.isOneOf(e0, e1, e2)) + assert(e2.isOneOf(e0, e1, e2)) + assert(!e100.isOneOf(e0, e1, e2)) + assert(!e101.isOneOf(e0, e1, e2)) + + // is not another value + assert(!e0.isOneOf(e1)) + assert(!e2.isOneOf(e101)) + + stop() +} + class StrongEnumSpec extends ChiselFlatSpec with Utils { import chisel3.internal.ChiselException @@ -320,6 +389,10 @@ class StrongEnumSpec extends ChiselFlatSpec with Utils { assertTesterPasses(new CastFromNonLitTester) } + it should "safely cast non-literal UInts to enums correctly and detect illegal casts" in { + assertTesterPasses(new SafeCastFromNonLitTester) + } + it should "prevent illegal literal casts to enums" in { a [ChiselException] should be thrownBy extractCause[ChiselException] { ChiselStage.elaborate(new CastToInvalidEnumTester) @@ -377,6 +450,69 @@ class StrongEnumSpec extends ChiselFlatSpec with Utils { "StrongEnum FSM" should "work" in { assertTesterPasses(new StrongEnumFSMTester) } + + "Casting a UInt to an Enum" should "warn if the UInt can express illegal states" in { + object MyEnum extends ChiselEnum { + val e0, e1, e2 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out = IO(Output(MyEnum())) + out := MyEnum(in) + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + log should include ("warn") + log should include ("Casting non-literal UInt") + } + + it should "NOT warn if the Enum is total" in { + object TotalEnum extends ChiselEnum { + val e0, e1, e2, e3 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out = IO(Output(TotalEnum())) + out := TotalEnum(in) + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + log should not include ("warn") + } + + "Casting a UInt to an Enum with .safe" should "NOT warn" in { + object MyEnum extends ChiselEnum { + val e0, e1, e2 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out = IO(Output(MyEnum())) + out := MyEnum.safe(in)._1 + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + log should not include ("warn") + } + + it should "NOT generate any validity logic if the Enum is total" in { + object TotalEnum extends ChiselEnum { + val e0, e1, e2, e3 = Value + } + + class MyModule extends Module { + val in = IO(Input(UInt(2.W))) + val out = IO(Output(TotalEnum())) + val (res, valid) = TotalEnum.safe(in) + assert(valid.litToBoolean, "It should be true.B") + out := res + } + val (log, _) = grabLog(ChiselStage.elaborate(new MyModule)) + log should not include ("warn") + } + + it should "correctly check if the enumeration is one of the values in a given sequence" in { + assertTesterPasses(new IsOneOfTester) + } } class StrongEnumAnnotator extends Module { diff --git a/src/test/scala/chiselTests/UIntOps.scala b/src/test/scala/chiselTests/UIntOps.scala index bba06d11..62d00de2 100644 --- a/src/test/scala/chiselTests/UIntOps.scala +++ b/src/test/scala/chiselTests/UIntOps.scala @@ -153,4 +153,37 @@ class UIntOpsSpec extends ChiselPropSpec with Matchers with Utils { io.out := io.in.asBools()(2) }) } + + // We use WireDefault with 2 arguments because of + // https://www.chisel-lang.org/api/3.4.1/chisel3/WireDefault$.html + // Single Argument case 2 + property("modulo divide should give min width of arguments") { + assertKnownWidth(4) { + val x = WireDefault(UInt(8.W), DontCare) + val y = WireDefault(UInt(4.W), DontCare) + val op = x % y + WireDefault(chiselTypeOf(op), op) + } + assertKnownWidth(4) { + val x = WireDefault(UInt(4.W), DontCare) + val y = WireDefault(UInt(8.W), DontCare) + val op = x % y + WireDefault(chiselTypeOf(op), op) + } + } + + property("division should give the width of the numerator") { + assertKnownWidth(8) { + val x = WireDefault(UInt(8.W), DontCare) + val y = WireDefault(UInt(4.W), DontCare) + val op = x / y + WireDefault(chiselTypeOf(op), op) + } + assertKnownWidth(4) { + val x = WireDefault(UInt(4.W), DontCare) + val y = WireDefault(UInt(8.W), DontCare) + val op = x / y + WireDefault(chiselTypeOf(op), op) + } + } } diff --git a/src/test/scala/chiselTests/Vec.scala b/src/test/scala/chiselTests/Vec.scala index 1327913d..97aea909 100644 --- a/src/test/scala/chiselTests/Vec.scala +++ b/src/test/scala/chiselTests/Vec.scala @@ -2,11 +2,14 @@ package chiselTests +import org.scalacheck._ + import chisel3._ import chisel3.stage.ChiselStage import chisel3.testers.BasicTester import chisel3.util._ import org.scalacheck.Shrink +import scala.annotation.tailrec class LitTesterMod(vecSize: Int) extends Module { val io = IO(new Bundle { @@ -104,6 +107,136 @@ class TabulateTester(n: Int) extends BasicTester { stop() } +class FillTester(n: Int, value: Int) extends BasicTester { + val x = VecInit(Array.fill(n)(value.U)) + val u = VecInit.fill(n)(value.U) + + assert(x.asUInt() === u.asUInt(), s"Expected Vec to be filled like $x, instead VecInit.fill created $u") + stop() +} + +object VecMultiDimTester { + + @tailrec + private def assert2DIsCorrect(n: Int, arr: Vec[Vec[UInt]], compArr: Seq[Seq[Int]]): Unit = { + val compareRow = arr(n) zip compArr(n) + compareRow.foreach (x => assert(x._1 === x._2.U)) + if (n != 0) assert2DIsCorrect(n-1, arr, compArr) + } + + @tailrec + private def assert3DIsCorrect(n: Int, m: Int, arr: Vec[Vec[Vec[UInt]]], compArr: Seq[Seq[Seq[Int]]]): Unit = { + assert2DIsCorrect(m-1, arr(n), compArr(n)) + if (n != 0) assert3DIsCorrect(n-1, m, arr, compArr) + } + + class TabulateTester2D(n: Int, m: Int) extends BasicTester { + def gen(x: Int, y: Int): UInt = (x+y).asUInt + def genCompVec(x: Int, y:Int): Int = x+y + val vec = VecInit.tabulate(n, m){ gen } + val compArr = Seq.tabulate(n,m){ genCompVec } + + assert2DIsCorrect(n-1, vec, compArr) + stop() + } + + class TabulateTester3D(n: Int, m: Int, p: Int) extends BasicTester { + def gen(x: Int, y: Int, z: Int): UInt = (x+y+z).asUInt + def genCompVec(x: Int, y:Int, z: Int): Int = x+y+z + val vec = VecInit.tabulate(n, m, p){ gen } + val compArr = Seq.tabulate(n, m, p){ genCompVec } + + assert3DIsCorrect(n-1, m, vec, compArr) + stop() + } + + class Fill2DTester(n: Int, m: Int, value: Int) extends BasicTester { + val u = VecInit.fill(n,m)(value.U) + val compareArr = Seq.fill(n,m)(value) + + assert2DIsCorrect(n-1, u, compareArr) + stop() + } + + class Fill3DTester(n: Int, m: Int, p: Int, value: Int) extends BasicTester { + val u = VecInit.fill(n,m,p)(value.U) + val compareArr = Seq.fill(n,m,p)(value) + + assert3DIsCorrect(n-1, m, u, compareArr) + stop() + } + + class BidirectionalTester2DFill(n: Int, m: Int) extends BasicTester { + val mod = Module(new PassthroughModule) + val vec2D = VecInit.fill(n, m)(mod.io) + for { + vec1D <- vec2D + module <- vec1D + } yield { + module <> Module(new PassthroughModuleTester).io + } + stop() + } + + class BidirectionalTester3DFill(n: Int, m: Int, p: Int) extends BasicTester { + val mod = Module(new PassthroughModule) + val vec3D = VecInit.fill(n, m, p)(mod.io) + + for { + vec2D <- vec3D + vec1D <- vec2D + module <- vec1D + } yield { + module <> (Module(new PassthroughModuleTester).io) + } + stop() + } + + class TabulateModuleTester(value: Int) extends Module { + val io = IO(Flipped(new PassthroughModuleIO)) + // This drives the input of a PassthroughModule + io.in := value.U + } + + class BidirectionalTester2DTabulate(n: Int, m: Int) extends BasicTester { + val vec2D = VecInit.tabulate(n, m) { (x, y) => Module(new TabulateModuleTester(x + y + 1)).io} + + for { + x <- 0 until n + y <- 0 until m + } yield { + val value = x + y + 1 + val receiveMod = Module(new PassthroughModule).io + vec2D(x)(y) <> receiveMod + assert(receiveMod.out === value.U) + } + stop() + } + + class BidirectionalTester3DTabulate(n: Int, m: Int, p: Int) extends BasicTester { + val vec3D = VecInit.tabulate(n, m, p) { (x, y, z) => Module(new TabulateModuleTester(x + y + z + 1)).io } + + for { + x <- 0 until n + y <- 0 until m + z <- 0 until p + } yield { + val value = x + y + z + 1 + val receiveMod = Module(new PassthroughModule).io + vec3D(x)(y)(z) <> receiveMod + assert(receiveMod.out === value.U) + } + stop() + } +} + +class IterateTester(start: Int, len: Int)(f: UInt => UInt) extends BasicTester { + val controlVec = VecInit(Seq.iterate(start.U, len)(f)) + val testVec = VecInit.iterate(start.U, len)(f) + assert(controlVec.asUInt() === testVec.asUInt(), s"Expected Vec to be filled like $controlVec, instead creaeted $testVec\n") + stop() +} + class ShiftRegisterTester(n: Int) extends BasicTester { val (cnt, wrap) = Counter(true.B, n*2) val shifter = Reg(Vec(n, UInt((log2Ceil(n) max 1).W))) @@ -160,9 +293,8 @@ class PassthroughModuleTester extends Module { assert(io.out === 123.U) } - class ModuleIODynamicIndexTester(n: Int) extends BasicTester { - val duts = VecInit(Seq.fill(n)(Module(new PassthroughModule).io)) + val duts = VecInit.fill(n)(Module(new PassthroughModule).io) val tester = Module(new PassthroughModuleTester) val (cycle, done) = Counter(true.B, n) @@ -219,10 +351,50 @@ class VecSpec extends ChiselPropSpec with Utils { } } - property("Vecs should tabulate correctly") { + property("VecInit should tabulate correctly") { forAll(smallPosInts) { (n: Int) => assertTesterPasses{ new TabulateTester(n) } } } + property("VecInit should tabulate 2D vec correctly") { + forAll(smallPosInts, smallPosInts) { (n: Int, m: Int) => assertTesterPasses { new VecMultiDimTester.TabulateTester2D(n, m) } } + } + + property("VecInit should tabulate 3D vec correctly") { + forAll(smallPosInts, smallPosInts, smallPosInts) { (n: Int, m: Int, p: Int) => assertTesterPasses{ new VecMultiDimTester.TabulateTester3D(n, m, p) } } + } + + property("VecInit should fill correctly") { + forAll(smallPosInts, Gen.choose(0, 50)) { (n: Int, value: Int) => assertTesterPasses{ new FillTester(n, value) } } + } + + property("VecInit should fill 2D vec correctly") { + forAll(smallPosInts, smallPosInts, Gen.choose(0, 50)) { (n: Int, m: Int, value: Int) => assertTesterPasses{ new VecMultiDimTester.Fill2DTester(n, m, value) } } + } + + property("VecInit should fill 3D vec correctly") { + forAll(smallPosInts, smallPosInts, smallPosInts, Gen.choose(0, 50)) { (n: Int, m: Int, p: Int, value: Int) => assertTesterPasses{ new VecMultiDimTester.Fill3DTester(n, m, p, value) } } + } + + property("VecInit should support 2D fill bidirectional wire connection") { + forAll(smallPosInts, smallPosInts) { (n: Int, m: Int) => assertTesterPasses{ new VecMultiDimTester.BidirectionalTester2DFill(n, m) }} + } + + property("VecInit should support 3D fill bidirectional wire connection") { + forAll(smallPosInts, smallPosInts, smallPosInts) { (n: Int, m: Int, p: Int) => assertTesterPasses{ new VecMultiDimTester.BidirectionalTester3DFill(n, m, p) }} + } + + property("VecInit should support 2D tabulate bidirectional wire connection") { + forAll(smallPosInts, smallPosInts) { (n: Int, m: Int) => assertTesterPasses{ new VecMultiDimTester.BidirectionalTester2DTabulate(n, m) }} + } + + property("VecInit should support 3D tabulate bidirectional wire connection") { + forAll(smallPosInts, smallPosInts, smallPosInts) { (n: Int, m: Int, p: Int) => assertTesterPasses{ new VecMultiDimTester.BidirectionalTester3DTabulate(n, m, p) }} + } + + property("VecInit should iterate correctly") { + forAll(Gen.choose(1, 10), smallPosInts) { (start: Int, len: Int) => assertTesterPasses{ new IterateTester(start, len)(x => x + 50.U)}} + } + property("Regs of vecs should be usable as shift registers") { forAll(smallPosInts) { (n: Int) => assertTesterPasses{ new ShiftRegisterTester(n) } } } diff --git a/src/test/scala/chiselTests/VecLiteralSpec.scala b/src/test/scala/chiselTests/VecLiteralSpec.scala new file mode 100644 index 00000000..d91cd2f4 --- /dev/null +++ b/src/test/scala/chiselTests/VecLiteralSpec.scala @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests + +import chisel3._ +import chisel3.experimental.BundleLiterals.AddBundleLiteralConstructor +import chisel3.experimental.VecLiterals._ +import chisel3.experimental.{ChiselEnum, FixedPoint, VecLiteralException} +import chisel3.stage.ChiselStage +import chisel3.testers.BasicTester +import chisel3.util.Counter +import scala.language.reflectiveCalls + +class VecLiteralSpec extends ChiselFreeSpec with Utils { + object MyEnum extends ChiselEnum { + val sA, sB, sC = Value + } + object MyEnumB extends ChiselEnum { + val sA, sB = Value + } + + "Vec literals should work with chisel Enums" in { + val enumVec = Vec(3, MyEnum()).Lit(0 -> MyEnum.sA, 1 -> MyEnum.sB, 2-> MyEnum.sC) + enumVec(0).toString should include (MyEnum.sA.toString) + enumVec(1).toString should include (MyEnum.sB.toString) + enumVec(2).toString should include (MyEnum.sC.toString) + } + + "improperly constructed vec literals should be detected" - { + "indices in vec literal muse be greater than zero and less than length" in { + val e = intercept[VecLiteralException] { + Vec(2, UInt(4.W)).Lit(0 -> 1.U, 1 -> 2.U, 2 -> 3.U, 3 -> 4.U, -2 -> 7.U) + } + e.getMessage should include ( + "VecLiteral: The following indices (2,3,-2) are less than zero or greater or equal to than Vec length" + ) + } + + "indices in vec literals must not be repeated" in { + val e = intercept[VecLiteralException] { + Vec(2, UInt(4.W)).Lit(0 -> 1.U, 1 -> 2.U, 2 -> 3.U, 2 -> 3.U, 2 -> 3.U, 3 -> 4.U) + } + e.getMessage should include("VecLiteral: has duplicated indices 2(3 times)") + } + "lits must fit in vec element width" in { + val e = intercept[VecLiteralException] { + Vec(2, SInt(4.W)).Lit(0 -> 0xab.S, 1 -> 0xbc.S) + } + e.getMessage should include( + "VecLiteral: Vec[SInt<4>] has the following incorrectly typed or sized initializers: " + + "0 -> SInt<9>(171),1 -> SInt<9>(188)" + ) + } + + "all lits must be the same type but width can be equal or smaller than the Vec's element width" in { + val v = Vec(2, SInt(4.W)).Lit(0 -> 1.S, 1 -> -2.S) + v(0).toString should include(1.S(4.W).toString) + v(1).toString should include((-2).S(4.W).toString) + v.toString should include ("SInt<4>[2](0=SLit(1,<4>), 1=SLit(-2,<4>)") + } + + "all lits must be the same type but width cannot be greater than Vec's element width" in { + val e = intercept[VecLiteralException] { + val v = Vec(2, SInt(4.W)).Lit(0 -> 11.S, 1 -> -0xffff.S) + } + e.getMessage should include( + "VecLiteral: Vec[SInt<4>] has the following incorrectly typed or sized initializers: 0 -> SInt<5>(11),1 -> SInt<17>(-65535)" + ) + } + } + + //NOTE: I had problems where this would not work if this class declaration was inside test scope + class HasVecInit extends Module { + val initValue = Vec(4, UInt(8.W)).Lit(0 -> 0xAB.U(8.W), 1 -> 0xCD.U(8.W), 2 -> 0xEF.U(8.W), 3 -> 0xFF.U(8.W)) + val y = RegInit(initValue) + } + + "Vec literals should work when used to initialize a reg of vec" in { + val firrtl = (new ChiselStage).emitFirrtl(new HasVecInit, args = Array("--full-stacktrace")) + firrtl should include("""_y_WIRE[0] <= UInt<8>("hab")""") + firrtl should include("""_y_WIRE[1] <= UInt<8>("hcd")""") + firrtl should include("""_y_WIRE[2] <= UInt<8>("hef")""") + firrtl should include("""_y_WIRE[3] <= UInt<8>("hff")""") + firrtl should include(""" reset => (reset, _y_WIRE)""".stripMargin) + } + + //NOTE: I had problems where this would not work if this class declaration was inside test scope + class HasPartialVecInit extends Module { + val initValue = Vec(4, UInt(8.W)).Lit(0 -> 0xAB.U(8.W), 2 -> 0xEF.U(8.W), 3 -> 0xFF.U(8.W)) + val y = RegInit(initValue) + } + + "Vec literals should work when used to partially initialize a reg of vec" in { + val firrtl = (new ChiselStage).emitFirrtl(new HasPartialVecInit, args = Array("--full-stacktrace")) + firrtl should include("""_y_WIRE[0] <= UInt<8>("hab")""") + firrtl should include("""_y_WIRE[1] is invalid""") + firrtl should include("""_y_WIRE[2] <= UInt<8>("hef")""") + firrtl should include("""_y_WIRE[3] <= UInt<8>("hff")""") + firrtl should include(""" reset => (reset, _y_WIRE)""".stripMargin) + } + + class ResetRegWithPartialVecLiteral extends Module { + val in = IO(Input(Vec(4, UInt(8.W)))) + val out = IO(Output(Vec(4, UInt(8.W)))) + val initValue = Vec(4, UInt(8.W)).Lit(0 -> 0xAB.U(8.W), 2 -> 0xEF.U(8.W), 3 -> 0xFF.U(8.W)) + val y = RegInit(initValue) + when(in(1) > 0.U) { + y(1) := in(1) + } + when(in(2) > 0.U) { + y(2) := in(2) + } + out := y + } + + "Vec literals should only init specified fields when used to partially initialize a reg of vec" in { + println(ChiselStage.emitFirrtl(new ResetRegWithPartialVecLiteral)) + assertTesterPasses(new BasicTester { + val m = Module(new ResetRegWithPartialVecLiteral) + val (counter, wrapped) = Counter(true.B, 8) + m.in := DontCare + when(counter < 2.U) { + m.in(1) := 0xff.U + m.in(2) := 0xff.U + }.elsewhen(counter === 2.U) { + chisel3.assert(m.out(1) === 0xff.U) + chisel3.assert(m.out(2) === 0xff.U) + }.elsewhen(counter === 3.U) { + m.in(1) := 0.U + m.in(2) := 0.U + m.reset := true.B + }.elsewhen(counter > 2.U) { + // m.out(1) should not be reset, m.out(2) should be reset + chisel3.assert(m.out(1) === 0xff.U) + chisel3.assert(m.out(2) === 0xEF.U) + } + when(wrapped) { + stop() + } + }) + } + + "lowest of vec literal contains least significant bits and " in { + val y = Vec(4, UInt(8.W)).Lit(0 -> 0xAB.U(8.W), 1 -> 0xCD.U(8.W), 2 -> 0xEF.U(8.W), 3 -> 0xFF.U(8.W)) + y.litValue() should be(BigInt("FFEFCDAB", 16)) + } + + "the order lits are specified does not matter" in { + val y = Vec(4, UInt(8.W)).Lit(3 -> 0xFF.U(8.W), 2 -> 0xEF.U(8.W), 1 -> 0xCD.U(8.W), 0 -> 0xAB.U(8.W)) + y.litValue() should be(BigInt("FFEFCDAB", 16)) + } + + "regardless of the literals widths, packing should be done based on the width of the Vec's gen" in { + val z = Vec(4, UInt(8.W)).Lit(0 -> 0x2.U, 1 -> 0x2.U, 2 -> 0x2.U, 3 -> 0x3.U) + z.litValue() should be(BigInt("03020202", 16)) + } + + "packing sparse vec lits should not pack, litOption returns None" in { + // missing sub-listeral for index 2 + val z = Vec(4, UInt(8.W)).Lit(0 -> 0x2.U, 1 -> 0x2.U, 3 -> 0x3.U) + + z.litOption should be(None) + } + + "registers can be initialized with a Vec literal" in { + assertTesterPasses(new BasicTester { + val y = RegInit(Vec(4, UInt(8.W)).Lit(0 -> 0xAB.U(8.W), 1 -> 0xCD.U(8.W), 2 -> 0xEF.U(8.W), 3 -> 0xFF.U(8.W))) + chisel3.assert(y.asUInt === BigInt("FFEFCDAB", 16).U) + stop() + }) + } + + "how does asUInt work" in { + assertTesterPasses(new BasicTester { + val vec1 = Vec(4, UInt(16.W)).Lit(0 -> 0xDD.U, 1 -> 0xCC.U, 2 -> 0xBB.U, 3 -> 0xAA.U) + + val vec2 = VecInit(Seq(0xDD.U, 0xCC.U, 0xBB.U, 0xAA.U)) + printf("vec1 %x\n", vec1.asUInt()) + printf("vec2 %x\n", vec2.asUInt()) + stop() + }) + } + + "Vec literals uint conversion" in { + class M1 extends Module { + val out1 = IO(Output(UInt(64.W))) + val out2 = IO(Output(UInt(64.W))) + + val v1 = Vec(4, UInt(16.W)).Lit(0 -> 0xDD.U, 1 -> 0xCC.U, 2 -> 0xBB.U, 3 -> 0xAA.U) + out1 := v1.asUInt + + val v2 = VecInit(0xDD.U(16.W), 0xCC.U, 0xBB.U, 0xAA.U) + out2 := v2.asUInt + } + + assertTesterPasses(new BasicTester { + val m = Module(new M1) + chisel3.assert(m.out1 === m.out2) + stop() + }) + } + + "VecLits should work properly with .asUInt" in { + val outsideVecLit = Vec(4, UInt(16.W)).Lit(0 -> 0xDD.U, 1 -> 0xCC.U, 2 -> 0xBB.U, 3 -> 0xAA.U) + + assertTesterPasses { + new BasicTester { + chisel3.assert(outsideVecLit(0) === 0xDD.U, s"v(0)") + stop() + } + } + } + + "bundle literals should work in RTL" in { + val outsideVecLit = Vec(4, UInt(16.W)).Lit(0 -> 0xDD.U, 1 -> 0xCC.U, 2 -> 0xBB.U, 3 -> 0xAA.U) + + assertTesterPasses { + new BasicTester { + chisel3.assert(outsideVecLit(0) === 0xDD.U, s"v(0)") + chisel3.assert(outsideVecLit(1) === 0xCC.U) + chisel3.assert(outsideVecLit(2) === 0xBB.U) + chisel3.assert(outsideVecLit(3) === 0xAA.U) + + chisel3.assert(outsideVecLit.litValue().U === outsideVecLit.asUInt()) + + val insideVecLit = Vec(4, UInt(16.W)).Lit(0 -> 0xDD.U, 1 -> 0xCC.U, 2 -> 0xBB.U, 3 -> 0xAA.U) + chisel3.assert(insideVecLit(0) === 0xDD.U) + chisel3.assert(insideVecLit(1) === 0xCC.U) + chisel3.assert(insideVecLit(2) === 0xBB.U) + chisel3.assert(insideVecLit(3) === 0xAA.U) + + chisel3.assert(insideVecLit(0) === outsideVecLit(0)) + chisel3.assert(insideVecLit(1) === outsideVecLit(1)) + chisel3.assert(insideVecLit(2) === outsideVecLit(2)) + chisel3.assert(insideVecLit(3) === outsideVecLit(3)) + + val vecWire1 = Wire(Vec(4, UInt(16.W))) + vecWire1 := outsideVecLit + + chisel3.assert(vecWire1(0) === 0xDD.U) + chisel3.assert(vecWire1(1) === 0xCC.U) + chisel3.assert(vecWire1(2) === 0xBB.U) + chisel3.assert(vecWire1(3) === 0xAA.U) + + val vecWire2 = Wire(Vec(4, UInt(16.W))) + vecWire2 := insideVecLit + + chisel3.assert(vecWire2(0) === 0xDD.U) + chisel3.assert(vecWire2(1) === 0xCC.U) + chisel3.assert(vecWire2(2) === 0xBB.U) + chisel3.assert(vecWire2(3) === 0xAA.U) + + stop() + } + } + } + + "partial vec literals should work in RTL" in { + assertTesterPasses{ new BasicTester{ + val vecLit = Vec(4, UInt(8.W)).Lit(0 -> 42.U, 2 -> 5.U) + chisel3.assert(vecLit(0) === 42.U) + chisel3.assert(vecLit(2) === 5.U) + + val vecWire = Wire(Vec(4, UInt(8.W))) + vecWire := vecLit + + chisel3.assert(vecWire(0) === 42.U) + chisel3.assert(vecWire(2) === 5.U) + + stop() + }} + } + + "nested vec literals should be constructable" in { + val outerVec = Vec(2, Vec(3, UInt(4.W))).Lit( + 0 -> Vec(3, UInt(4.W)).Lit(0 -> 1.U, 1 -> 2.U, 2 -> 3.U), + 1 -> Vec(3, UInt(4.W)).Lit(0 -> 4.U, 1 -> 5.U, 2 -> 6.U) + ) + + outerVec.litValue() should be (BigInt("654321", 16)) + outerVec(0).litValue() should be (BigInt("321", 16)) + outerVec(1).litValue() should be (BigInt("654", 16)) + outerVec(0)(0).litValue() should be (BigInt(1)) + outerVec(0)(1).litValue() should be (BigInt(2)) + outerVec(0)(2).litValue() should be (BigInt(3)) + outerVec(1)(0).litValue() should be (BigInt(4)) + outerVec(1)(1).litValue() should be (BigInt(5)) + outerVec(1)(2).litValue() should be (BigInt(6)) + } + + "contained vecs should work" in { + assertTesterPasses{ new BasicTester { + val outerVec = Vec(2, Vec(3, UInt(4.W))).Lit( + 0 -> Vec(3, UInt(4.W)).Lit(0 -> 1.U, 1 -> 2.U, 2 -> 3.U), + 1 -> Vec(3, UInt(4.W)).Lit(0 -> 4.U, 1 -> 5.U, 2 -> 6.U) + ) + + chisel3.assert(outerVec(0)(0) === 1.U) + chisel3.assert(outerVec(0)(1) === 2.U) + chisel3.assert(outerVec(0)(2) === 3.U) + chisel3.assert(outerVec(1)(0) === 4.U) + chisel3.assert(outerVec(1)(1) === 5.U) + chisel3.assert(outerVec(1)(2) === 6.U) + + val v0 = outerVec(0) + val v1 = outerVec(1) + chisel3.assert(v0(0) === 1.U) + chisel3.assert(v0(1) === 2.U) + chisel3.assert(v0(2) === 3.U) + chisel3.assert(v1(0) === 4.U) + chisel3.assert(v1(1) === 5.U) + chisel3.assert(v1(2) === 6.U) + + stop() + }} + } + + //TODO: decide what behavior here should be + "This doesn't work should it" ignore { + assertTesterPasses { + new BasicTester { + def vecFactory = Vec(2, FixedPoint(8.W, 4.BP)) + + val vecWire1 = Wire(Output(vecFactory)) + val vecLit1 = vecFactory.Lit(0 -> (1.5).F(8.W, 4.BP)) + val vecLit2 = vecFactory.Lit(1 -> (3.25).F(8.W, 4.BP)) + + vecWire1 := vecLit1 + vecWire1 := vecLit2 + printf("vw1(0) %x vw1(1) %x\n", vecWire1(0).asUInt(), vecWire1(1).asUInt()) + chisel3.assert(vecWire1(0) === (1.5).F(8.W, 4.BP)) + chisel3.assert(vecWire1(1) === (3.25).F(8.W, 4.BP)) + stop() + } + } + } + + "partially initialized Vec literals should assign" in { + assertTesterPasses { + new BasicTester { + def vecFactory = Vec(2, FixedPoint(8.W, 4.BP)) + + val vecWire1 = Wire(Output(vecFactory)) + val vecWire2 = Wire(Output(vecFactory)) + val vecLit1 = vecFactory.Lit(0 -> (1.5).F(8.W, 4.BP)) + val vecLit2 = vecFactory.Lit(1 -> (3.25).F(8.W, 4.BP)) + + vecWire1 := vecLit1 + vecWire2 := vecLit2 + vecWire1(1) := (0.5).F(8.W, 4.BP) + printf("vw1(0) %x vw1(1) %x\n", vecWire1(0).asUInt(), vecWire1(1).asUInt()) + chisel3.assert(vecWire1(0) === (1.5).F(8.W, 4.BP)) + chisel3.assert(vecWire1(1) === (0.5).F(8.W, 4.BP)) // Last connect won + chisel3.assert(vecWire2(1) === (3.25).F(8.W, 4.BP)) + stop() + } + } + } + + "Vec literals should work as register reset values" in { + assertTesterPasses { + new BasicTester { + val r = RegInit(Vec(3, UInt(11.W)).Lit(0 -> 0xA.U, 1 -> 0xB.U, 2 -> 0xC.U)) + r := (r.asUInt + 1.U).asTypeOf(Vec(3, UInt(11.W))) // prevent constprop + + // check reset values on first cycle out of reset + chisel3.assert(r(0) === 0xA.U) + chisel3.assert(r(1) === 0xB.U) + chisel3.assert(r(2) === 0xC.U) + stop() + } + } + } + + "partially initialized Vec literals should work as register reset values" in { + assertTesterPasses { + new BasicTester { + val r = RegInit(Vec(3, UInt(11.W)).Lit(0 -> 0xA.U, 2 -> 0xC.U)) + r := (r.asUInt + 1.U).asTypeOf(Vec(3, UInt(11.W))) // prevent constprop + // check reset values on first cycle out of reset + chisel3.assert(r(0) === 0xA.U) + chisel3.assert(r(2) === 0xC.U) + stop() + } + } + } + + "Fields extracted from Vec Literals should work as register reset values" in { + assertTesterPasses { + new BasicTester { + val r = RegInit(Vec(3, UInt(11.W)).Lit(0 -> 0xA.U, 2 -> 0xC.U).apply(0)) + r := r + 1.U // prevent const prop + chisel3.assert(r === 0xA.U) // coming out of reset + stop() + } + } + } + + "DontCare fields extracted from Vec Literals should work as register reset values" in { + assertTesterPasses { + new BasicTester { + val r = RegInit(Vec(3, Bool()).Lit(0 -> true.B).apply(2)) + r := reset.asBool + printf(p"r = $r\n") // Can't assert because reset value is DontCare + stop() + } + } + } + + "DontCare fields extracted from Vec Literals should work in other Expressions" in { + assertTesterPasses { + new BasicTester { + val x = Vec(3, Bool()).Lit(0 -> true.B).apply(2) || true.B + chisel3.assert(x === true.B) + stop() + } + } + } + + "vec literals with non-literal values should fail" in { + val exc = intercept[VecLiteralException] { + extractCause[VecLiteralException] { + ChiselStage.elaborate { + new RawModule { + (Vec(3, UInt(11.W)).Lit(0 -> UInt())) + } + } + } + } + exc.getMessage should include("field 0 specified with non-literal value UInt") + } + + "vec literals are instantiated on connect" in { + class VecExample5 extends RawModule { + val out = IO(Output(Vec(2, UInt(4.W)))) + val bundle = Vec(2, UInt(4.W)).Lit( + 0 -> 0xa.U, + 1 -> 0xb.U + ) + out := bundle + } + + val firrtl = (new chisel3.stage.ChiselStage).emitFirrtl(new VecExample5, args = Array("--full-stacktrace")) + firrtl should include("""out[0] <= UInt<4>("ha")""") + firrtl should include("""out[1] <= UInt<4>("hb")""") + } + + class SubBundle extends Bundle { + val foo = UInt(8.W) + val bar = UInt(4.W) + } + + class VecExample extends RawModule { + val out = IO(Output(Vec(2, new SubBundle))) + val bundle = Vec(2, new SubBundle).Lit( + 0 -> (new SubBundle).Lit(_.foo -> 42.U, _.bar -> 22.U), + 1 -> (new SubBundle).Lit(_.foo -> 7.U, _.bar -> 3.U) + ) + out := bundle + } + + "vec literals can contain bundles" in { + val chirrtl = (new chisel3.stage.ChiselStage).emitChirrtl(new VecExample, args = Array("--full-stacktrace")) + chirrtl should include("""out[0].bar <= UInt<5>("h16")""") + chirrtl should include("""out[0].foo <= UInt<6>("h2a")""") + chirrtl should include("""out[1].bar <= UInt<2>("h3")""") + chirrtl should include("""out[1].foo <= UInt<3>("h7")""") + + } + + "vec literals can have bundle children" in { + val vec = Vec(2, new SubBundle).Lit( + 0 -> (new SubBundle).Lit(_.foo -> 0xab.U, _.bar -> 0xc.U), + 1 -> (new SubBundle).Lit(_.foo -> 0xde.U, _.bar -> 0xf.U) + ) + vec.litValue().toString(16) should be("defabc") + } + + "vec literals can have bundle children assembled incrementally" in { + val bundle1 = (new SubBundle).Lit(_.foo -> 0xab.U, _.bar -> 0xc.U) + val bundle2 = (new SubBundle).Lit(_.foo -> 0xde.U, _.bar -> 0xf.U) + + bundle1.litValue().toString(16) should be("abc") + bundle2.litValue().toString(16) should be("def") + + val vec = Vec(2, new SubBundle).Lit(0 -> bundle1, 1 -> bundle2) + + vec.litValue().toString(16) should be("defabc") + } + + "bundles can contain vec lits" in { + val vec1 = Vec(3, UInt(4.W)).Lit(0 -> 0xa.U, 1 -> 0xb.U, 2 -> 0xc.U) + val vec2 = Vec(2, UInt(4.W)).Lit(0 -> 0xd.U, 1 -> 0xe.U) + val bundle = (new Bundle { + val foo = Vec(3, UInt(4.W)) + val bar = Vec(2, UInt(4.W)) + }).Lit(_.foo -> vec1, _.bar -> vec2) + bundle.litValue().toString(16) should be("cbaed") + } + + "bundles can contain vec lits in-line" in { + val bundle = (new Bundle { + val foo = Vec(3, UInt(4.W)) + val bar = Vec(2, UInt(4.W)) + }).Lit( + _.foo -> Vec(3, UInt(4.W)).Lit(0 -> 0xa.U, 1 -> 0xb.U, 2 -> 0xc.U), + _.bar -> Vec(2, UInt(4.W)).Lit(0 -> 0xd.U, 1 -> 0xe.U) + ) + bundle.litValue().toString(16) should be("cbaed") + } + + "Vec.Lit is a trivial Vec literal factory" in { + val vec = Vec.Lit(0xa.U, 0xb.U) + vec(0).litValue() should be(0xa) + vec(1).litValue() should be(0xb) + } + + "Vec.Lit bases it's element width on the widest literal supplied" in { + val vec = Vec.Lit(0xa.U, 0xbbbb.U) + vec(0).litValue() should be(0xa) + vec(1).litValue() should be(0xbbbb) + vec.length should be(2) + vec.getWidth should be(16 * 2) + vec.litValue() should be(BigInt("bbbb000a", 16)) + } +} diff --git a/src/test/scala/chiselTests/WidthSpec.scala b/src/test/scala/chiselTests/WidthSpec.scala index 2a33c1d6..34159214 100644 --- a/src/test/scala/chiselTests/WidthSpec.scala +++ b/src/test/scala/chiselTests/WidthSpec.scala @@ -186,3 +186,62 @@ class RegInitWidthSpec extends WireDefaultRegInitSpecImpl { def builder2[T <: Data](x: T, y: T): T = RegInit(x, y) } +class OpWidthSpec extends ChiselFlatSpec { + import firrtl._ + import firrtl.ir._ + + val maxWidth = 5 + val uIntOps: Seq[((UInt, UInt) => UInt, PrimOp)] = + Seq( + (_ +& _, PrimOps.Add), + (_ -& _, PrimOps.Sub), + (_ * _, PrimOps.Mul), + (_ / _, PrimOps.Div), + (_ % _, PrimOps.Rem), + (_ << _, PrimOps.Dshl), + (_ >> _, PrimOps.Dshr) + ) + + assertTesterPasses(new chisel3.testers.BasicTester { + for (i <- 0 to maxWidth) { + for (j <- 0 to maxWidth) { + for ((cOp, fOp) <- uIntOps) { + val args = Seq(i, j).map(w => Reference("", UIntType(IntWidth(w)))) + fOp.propagateType(DoPrim(fOp, args, Nil, UnknownType)) match { + case UIntType(IntWidth(w)) => + val x = 0.U(maxWidth.W).head(i) + val y = 0.U(maxWidth.W).head(j) + assert(w == cOp(x, y).getWidth) + } + } + } + } + stop() + }) + + val sIntOps: Seq[((SInt, SInt) => SInt, PrimOp)] = + Seq( + (_ +& _, PrimOps.Add), + (_ -& _, PrimOps.Sub), + (_ * _, PrimOps.Mul), + (_ / _, PrimOps.Div), + (_ % _, PrimOps.Rem) + ) + + assertTesterPasses(new chisel3.testers.BasicTester { + for (i <- 0 to maxWidth) { + for (j <- 0 to maxWidth) { + for ((cOp, fOp) <- sIntOps) { + val args = Seq(i, j).map(w => Reference("", SIntType(IntWidth(w)))) + fOp.propagateType(DoPrim(fOp, args, Nil, UnknownType)) match { + case SIntType(IntWidth(w)) => + val x = 0.U(maxWidth.W).head(i).asSInt + val y = 0.U(maxWidth.W).head(j).asSInt + assert(w == cOp(x, y).getWidth) + } + } + } + } + stop() + }) +} diff --git a/src/test/scala/chiselTests/aop/InjectionSpec.scala b/src/test/scala/chiselTests/aop/InjectionSpec.scala index c9fa2e5e..a28501a5 100644 --- a/src/test/scala/chiselTests/aop/InjectionSpec.scala +++ b/src/test/scala/chiselTests/aop/InjectionSpec.scala @@ -5,6 +5,7 @@ package chiselTests.aop import chisel3.testers.{BasicTester, TesterDriver} import chiselTests.{ChiselFlatSpec, Utils} import chisel3._ +import chisel3.aop.Select import chisel3.aop.injecting.InjectingAspect import logger.{LogLevel, LogLevelAnnotation} @@ -14,6 +15,11 @@ object InjectionHierarchy { val moduleSubmoduleA = Module(new SubmoduleA) } + class MultiModuleInjectionTester extends BasicTester { + val subA0 = Module(new SubmoduleA) + val subA1 = Module(new SubmoduleA) + } + class SubmoduleA extends Module { val io = IO(new Bundle { val out = Output(Bool()) @@ -104,6 +110,17 @@ class InjectionSpec extends ChiselFlatSpec with Utils { } ) + val multiModuleInjectionAspect = InjectingAspect( + { top: MultiModuleInjectionTester => + Select.collectDeep(top) { case m: SubmoduleA => m } + }, + { m: Module => + val wire = Wire(Bool()) + wire := m.reset.asBool() + dontTouch(wire) + stop() + } + ) "Test" should "pass if inserted the correct values" in { assertTesterPasses{ new AspectTester(Seq(0, 1, 2)) } @@ -142,4 +159,12 @@ class InjectionSpec extends ChiselFlatSpec with Utils { Seq(addingExternalModules) ++ TesterDriver.verilatorOnly ) } + + "Injection into multiple submodules of the same class" should "work" in { + assertTesterPasses( + {new MultiModuleInjectionTester}, + Nil, + Seq(multiModuleInjectionAspect) ++ TesterDriver.verilatorOnly + ) + } } diff --git a/src/test/scala/chiselTests/aop/SelectSpec.scala b/src/test/scala/chiselTests/aop/SelectSpec.scala index 91353f5a..e09e78c8 100644 --- a/src/test/scala/chiselTests/aop/SelectSpec.scala +++ b/src/test/scala/chiselTests/aop/SelectSpec.scala @@ -22,14 +22,16 @@ class SelectTester(results: Seq[Int]) extends BasicTester { val nreset = reset.asBool() === false.B val selected = values(counter) val zero = 0.U + 0.U + var p: printf.Printf = null when(overflow) { counter := zero stop() }.otherwise { when(nreset) { assert(counter === values(counter)) - printf("values(%d) = %d\n", counter, selected) + p = printf("values(%d) = %d\n", counter, selected) } + } } @@ -81,17 +83,18 @@ class SelectSpec extends ChiselFlatSpec { "Test" should "pass if selecting correct printfs" in { execute( () => new SelectTester(Seq(0, 1, 2)), - { dut: SelectTester => Seq(Select.printfs(dut).last) }, + { dut: SelectTester => Seq(Select.printfs(dut).last.toString) }, { dut: SelectTester => Seq(Select.Printf( + dut.p, Seq( When(Select.ops("eq")(dut).last.asInstanceOf[Bool]), When(dut.nreset), WhenNot(dut.overflow) ), - Printable.pack("values(%d) = %d\n", dut.counter, dut.selected), + dut.p.pable, dut.clock - )) + ).toString) } ) } @@ -153,5 +156,60 @@ class SelectSpec extends ChiselFlatSpec { assert(bbs.size == 1) } + "CloneModuleAsRecord" should "NOT show up in Select aspects" in { + import chisel3.experimental.CloneModuleAsRecord + class Child extends RawModule { + val in = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + out := in + } + class Top extends Module { + val in = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + val inst0 = Module(new Child) + val inst1 = CloneModuleAsRecord(inst0) + inst0.in := in + inst1("in") := inst0.out + out := inst1("out") + } + val top = ChiselGeneratorAnnotation(() => { + new Top() + }).elaborate + .collectFirst { case DesignAnnotation(design: Top) => design } + .get + Select.collectDeep(top) { case x => x } should equal (Seq(top, top.inst0)) + Select.getDeep(top)(x => Seq(x)) should equal (Seq(top, top.inst0)) + Select.instances(top) should equal (Seq(top.inst0)) + } + + "Using Definition/Instance with Injecting Aspects" should "throw an error" in { + import chisel3.experimental.CloneModuleAsRecord + import chisel3.experimental.hierarchy._ + @instantiable + class Child extends RawModule { + @public val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + out := in + } + class Top extends Module { + val in = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + val definition = Definition(new Child) + val inst0 = Instance(definition) + val inst1 = Instance(definition) + inst0.in := in + inst1.in := inst0.out + out := inst1.out + } + val top = ChiselGeneratorAnnotation(() => { + new Top() + }).elaborate + .collectFirst { case DesignAnnotation(design: Top) => design } + .get + intercept[Exception] { Select.collectDeep(top) { case x => x } } + intercept[Exception] { Select.getDeep(top)(x => Seq(x)) } + intercept[Exception] { Select.instances(top) } + } + } diff --git a/src/test/scala/chiselTests/experimental/DataView.scala b/src/test/scala/chiselTests/experimental/DataView.scala new file mode 100644 index 00000000..d1620e88 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/DataView.scala @@ -0,0 +1,546 @@ +// See LICENSE for license details. + +package chiselTests.experimental + +import chiselTests.ChiselFlatSpec +import chisel3._ +import chisel3.experimental.dataview._ +import chisel3.experimental.DataMirror.internal.chiselTypeClone +import chisel3.stage.ChiselStage +import chisel3.util.{Decoupled, DecoupledIO} + +object SimpleBundleDataView { + class BundleA(val w: Int) extends Bundle { + val foo = UInt(w.W) + } + class BundleB(val w: Int) extends Bundle { + val bar = UInt(w.W) + } + implicit val v1 = DataView[BundleA, BundleB](a => new BundleB(a.w), _.foo -> _.bar) + implicit val v2 = v1.invert(b => new BundleA(b.w)) +} + +object VecBundleDataView { + class MyBundle extends Bundle { + val foo = UInt(8.W) + val bar = UInt(8.W) + } + implicit val v1: DataView[MyBundle, Vec[UInt]] = DataView(_ => Vec(2, UInt(8.W)), _.foo -> _(1), _.bar -> _(0)) + implicit val v2 = v1.invert(_ => new MyBundle) +} + +object FlatDecoupledDataView { + class FizzBuzz extends Bundle { + val fizz = UInt(8.W) + val buzz = UInt(8.W) + } + class FlatDecoupled extends Bundle { + val valid = Output(Bool()) + val ready = Input(Bool()) + val fizz = Output(UInt(8.W)) + val buzz = Output(UInt(8.W)) + } + implicit val view = DataView[FlatDecoupled, DecoupledIO[FizzBuzz]]( + _ => Decoupled(new FizzBuzz), + _.valid -> _.valid, + _.ready -> _.ready, + _.fizz -> _.bits.fizz, + _.buzz -> _.bits.buzz + ) + implicit val view2 = view.invert(_ => new FlatDecoupled) +} + +// This should become part of Chisel in a later PR +object Tuple2DataProduct { + implicit def tuple2DataProduct[A : DataProduct, B : DataProduct] = new DataProduct[(A, B)] { + def dataIterator(tup: (A, B), path: String): Iterator[(Data, String)] = { + val dpa = implicitly[DataProduct[A]] + val dpb = implicitly[DataProduct[B]] + val (a, b) = tup + dpa.dataIterator(a, s"$path._1") ++ dpb.dataIterator(b, s"$path._2") + } + } +} + +// This should become part of Chisel in a later PR +object HWTuple { + import Tuple2DataProduct._ + + class HWTuple2[A <: Data, B <: Data](val _1: A, val _2: B) extends Bundle + + implicit def view[T1 : DataProduct, T2 : DataProduct, V1 <: Data, V2 <: Data]( + implicit v1: DataView[T1, V1], v2: DataView[T2, V2] + ): DataView[(T1, T2), HWTuple2[V1, V2]] = + DataView.mapping( + { case (a, b) => new HWTuple2(a.viewAs[V1].cloneType, b.viewAs[V2].cloneType)}, + { case ((a, b), hwt) => + Seq(a.viewAs[V1] -> hwt._1, + b.viewAs[V2] -> hwt._2) + } + ) + + implicit def tuple2hwtuple[T1 : DataProduct, T2 : DataProduct, V1 <: Data, V2 <: Data]( + tup: (T1, T2))(implicit v1: DataView[T1, V1], v2: DataView[T2, V2] + ): HWTuple2[V1, V2] = tup.viewAs[HWTuple2[V1, V2]] +} + +// This should become part of Chisel in a later PR +object SeqDataProduct { + // Should we special case Seq[Data]? + implicit def seqDataProduct[A : DataProduct]: DataProduct[Seq[A]] = new DataProduct[Seq[A]] { + def dataIterator(a: Seq[A], path: String): Iterator[(Data, String)] = { + val dpa = implicitly[DataProduct[A]] + a.iterator + .zipWithIndex + .flatMap { case (elt, idx) => + dpa.dataIterator(elt, s"$path[$idx]") + } + } + } +} + +object SeqToVec { + import SeqDataProduct._ + + // TODO this would need a better way to determine the prototype for the Vec + implicit def seqVec[A : DataProduct, B <: Data](implicit dv: DataView[A, B]): DataView[Seq[A], Vec[B]] = + DataView.mapping[Seq[A], Vec[B]]( + xs => Vec(xs.size, chiselTypeClone(xs.head.viewAs[B])), // xs.head is not correct in general + { case (s, v) => s.zip(v).map { case (a, b) => a.viewAs[B] -> b } } + ) + + implicit def seq2Vec[A : DataProduct, B <: Data](xs: Seq[A])(implicit dv: DataView[A, B]): Vec[B] = + xs.viewAs[Vec[B]] +} + +class DataViewSpec extends ChiselFlatSpec { + + behavior of "DataView" + + it should "support simple Bundle viewing" in { + import SimpleBundleDataView._ + class MyModule extends Module { + val in = IO(Input(new BundleA(8))) + val out = IO(Output(new BundleB(8))) + out := in.viewAs[BundleB] + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("out.bar <= in.foo") + } + + it should "be a bidirectional mapping" in { + import SimpleBundleDataView._ + class MyModule extends Module { + val in = IO(Input(new BundleA(8))) + val out = IO(Output(new BundleB(8))) + out.viewAs[BundleA] := in + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("out.bar <= in.foo") + } + + it should "handle viewing UInts as UInts" in { + class MyModule extends Module { + val in = IO(Input(UInt(8.W))) + val foo = IO(Output(UInt(8.W))) + val bar = IO(Output(UInt(8.W))) + foo := in.viewAs[UInt] + bar.viewAs[UInt] := in + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("foo <= in") + chirrtl should include("bar <= in") + } + + it should "handle viewing Bundles as their same concrete type" in { + class MyBundle extends Bundle { + val foo = UInt(8.W) + } + class MyModule extends Module { + val in = IO(Input(new MyBundle)) + val fizz = IO(Output(new MyBundle)) + val buzz = IO(Output(new MyBundle)) + fizz := in.viewAs[MyBundle] + buzz.viewAs[MyBundle] := in + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("fizz.foo <= in.foo") + chirrtl should include("buzz.foo <= in.foo") + } + + it should "handle viewing Vecs as their same concrete type" in { + class MyModule extends Module { + val in = IO(Input(Vec(1, UInt(8.W)))) + val fizz = IO(Output(Vec(1, UInt(8.W)))) + val buzz = IO(Output(Vec(1, UInt(8.W)))) + fizz := in.viewAs[Vec[UInt]] + buzz.viewAs[Vec[UInt]] := in + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("fizz[0] <= in[0]") + chirrtl should include("buzz[0] <= in[0]") + } + + it should "handle viewing Vecs as Bundles and vice versa" in { + import VecBundleDataView._ + class MyModule extends Module { + val in = IO(Input(new MyBundle)) + val out = IO(Output(Vec(2, UInt(8.W)))) + val out2 = IO(Output(Vec(2, UInt(8.W)))) + out := in.viewAs[Vec[UInt]] + out2.viewAs[MyBundle] := in + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("out[0] <= in.bar") + chirrtl should include("out[1] <= in.foo") + chirrtl should include("out2[0] <= in.bar") + chirrtl should include("out2[1] <= in.foo") + } + + it should "work with bidirectional connections for nested types" in { + import FlatDecoupledDataView._ + class MyModule extends Module { + val enq = IO(Flipped(Decoupled(new FizzBuzz))) + val deq = IO(new FlatDecoupled) + val deq2 = IO(new FlatDecoupled) + deq <> enq.viewAs[FlatDecoupled] + deq2.viewAs[DecoupledIO[FizzBuzz]] <> enq + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("deq.valid <= enq.valid") + chirrtl should include("enq.ready <= deq.ready") + chirrtl should include("deq.fizz <= enq.bits.fizz") + chirrtl should include("deq.buzz <= enq.bits.buzz") + chirrtl should include("deq2.valid <= enq.valid") + chirrtl should include("enq.ready <= deq2.ready") + chirrtl should include("deq2.fizz <= enq.bits.fizz") + chirrtl should include("deq2.buzz <= enq.bits.buzz") + } + + it should "support viewing a Bundle as a Parent Bundle type" in { + class Foo extends Bundle { + val foo = UInt(8.W) + } + class Bar extends Foo { + val bar = UInt(8.W) + } + class MyModule extends Module { + val fooIn = IO(Input(new Foo)) + val barOut = IO(Output(new Bar)) + barOut.viewAsSupertype(new Foo) := fooIn + + val barIn = IO(Input(new Bar)) + val fooOut = IO(Output(new Foo)) + fooOut := barIn.viewAsSupertype(new Foo) + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include("barOut.foo <= fooIn.foo") + chirrtl should include("fooOut.foo <= barIn.foo") + } + + it should "error if viewing a parent Bundle as a child Bundle type" in { + assertTypeError(""" + class Foo extends Bundle { + val foo = UInt(8.W) + } + class Bar extends Foo { + val bar = UInt(8.W) + } + class MyModule extends Module { + val barIn = IO(Input(new Bar)) + val fooOut = IO(Output(new Foo)) + fooOut.viewAs(new Bar) := barIn + } + """) + } + + it should "work in UInt operations" in { + class MyBundle extends Bundle { + val value = UInt(8.W) + } + class MyModule extends Module { + val a = IO(Input(UInt(8.W))) + val b = IO(Input(new MyBundle)) + val cond = IO(Input(Bool())) + val and, mux, bitsCat = IO(Output(UInt(8.W))) + // Chisel unconditionally emits a node, so name it at least + val x = a.viewAs[UInt] & b.viewAs[MyBundle].value + and.viewAs[UInt] := x + + val y = Mux(cond.viewAs[Bool], a.viewAs[UInt], b.value.viewAs[UInt]) + mux.viewAs[UInt] := y + + // TODO should we have a macro so that we don't need .apply? + val aBits = a.viewAs[UInt].apply(3, 0) + val bBits = b.viewAs[MyBundle].value(3, 0) + val abCat = aBits.viewAs[UInt] ## bBits.viewAs[UInt] + bitsCat := abCat + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + val expected = List( + "node x = and(a, b.value)", + "and <= x", + "node y = mux(cond, a, b.value)", + "mux <= y", + "node aBits = bits(a, 3, 0)", + "node bBits = bits(b.value, 3, 0)", + "node abCat = cat(aBits, bBits)", + "bitsCat <= abCat" + ) + for (line <- expected) { + chirrtl should include(line) + } + } + + it should "support .asUInt of Views" in { + import VecBundleDataView._ + class MyModule extends Module { + val barIn = IO(Input(new MyBundle)) + val fooOut = IO(Output(UInt())) + val cat = barIn.viewAs[Vec[UInt]].asUInt + fooOut := cat + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include ("node cat = cat(barIn.foo, barIn.bar)") + chirrtl should include ("fooOut <= cat") + } + + it should "be composable" in { + // Given DataView[A, B] and DataView[B, C], derive DataView[A, C] + class Foo(val foo: UInt) extends Bundle + class Bar(val bar: UInt) extends Bundle + class Fizz(val fizz: UInt) extends Bundle + + implicit val foo2bar = DataView[Foo, Bar](f => new Bar(chiselTypeClone(f.foo)), _.foo -> _.bar) + implicit val bar2fizz = DataView[Bar, Fizz](b => new Fizz(chiselTypeClone(b.bar)), _.bar -> _.fizz) + + implicit val foo2fizz: DataView[Foo, Fizz] = foo2bar.andThen(bar2fizz) + + class MyModule extends Module { + val a, b = IO(Input(new Foo(UInt(8.W)))) + val y, z = IO(Output(new Fizz(UInt(8.W)))) + y := a.viewAs[Fizz] + z := b.viewAs[Bar].viewAs[Fizz] + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include ("y.fizz <= a.foo") + chirrtl should include ("z.fizz <= b.foo") + } + + // This example should be turned into a built-in feature + it should "enable implementing \"HardwareTuple\"" in { + import HWTuple._ + + class MyModule extends Module { + val a, b, c, d = IO(Input(UInt(8.W))) + val sel = IO(Input(Bool())) + val y, z = IO(Output(UInt(8.W))) + (y, z) := Mux(sel, (a, b), (c, d)) + } + // Verilog instead of CHIRRTL because the optimizations make it much prettier + val verilog = ChiselStage.emitVerilog(new MyModule) + verilog should include ("assign y = sel ? a : c;") + verilog should include ("assign z = sel ? b : d;") + } + + it should "support nesting of tuples" in { + import Tuple2DataProduct._ + import HWTuple._ + + class MyModule extends Module { + val a, b, c, d = IO(Input(UInt(8.W))) + val w, x, y, z = IO(Output(UInt(8.W))) + ((w, x), (y, z)) := ((a, b), (c, d)) + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include ("w <= a") + chirrtl should include ("x <= b") + chirrtl should include ("y <= c") + chirrtl should include ("z <= d") + } + + // This example should be turned into a built-in feature + it should "enable viewing Seqs as Vecs" in { + import SeqToVec._ + + class MyModule extends Module { + val a, b, c = IO(Input(UInt(8.W))) + val x, y, z = IO(Output(UInt(8.W))) + Seq(x, y, z) := VecInit(a, b, c) + } + // Verilog instead of CHIRRTL because the optimizations make it much prettier + val verilog = ChiselStage.emitVerilog(new MyModule) + verilog should include ("assign x = a;") + verilog should include ("assign y = b;") + verilog should include ("assign z = c;") + } + + it should "support recursive composition of views" in { + import Tuple2DataProduct._ + import SeqDataProduct._ + import SeqToVec._ + import HWTuple._ + + class MyModule extends Module { + val a, b, c, d = IO(Input(UInt(8.W))) + val w, x, y, z = IO(Output(UInt(8.W))) + + // A little annoying that we need the type annotation on VecInit to get the implicit conversion to work + // Note that one can just use the Seq on the RHS so there is an alternative (may lack discoverability) + // We could also overload `VecInit` instead of relying on the implicit conversion + Seq((w, x), (y, z)) := VecInit[HWTuple2[UInt, UInt]]((a, b), (c, d)) + } + val verilog = ChiselStage.emitVerilog(new MyModule) + verilog should include ("assign w = a;") + verilog should include ("assign x = b;") + verilog should include ("assign y = c;") + verilog should include ("assign z = d;") + } + + it should "error if you try to dynamically index a Vec view" in { + import SeqDataProduct._ + import SeqToVec._ + import Tuple2DataProduct._ + import HWTuple._ + + class MyModule extends Module { + val inA, inB = IO(Input(UInt(8.W))) + val outA, outB = IO(Output(UInt(8.W))) + val idx = IO(Input(UInt(1.W))) + + val a, b, c, d = RegInit(0.U) + + // Dynamic indexing is more of a "generator" in Chisel3 than an individual node + val selected = Seq((a, b), (c, d)).apply(idx) + selected := (inA, inB) + (outA, outB) := selected + } + (the [InvalidViewException] thrownBy { + ChiselStage.emitChirrtl(new MyModule) + }).getMessage should include ("Dynamic indexing of Views is not yet supported") + } + + it should "error if the mapping is non-total in the view" in { + class MyBundle(val foo: UInt, val bar: UInt) extends Bundle + implicit val dv = DataView[UInt, MyBundle](_ => new MyBundle(UInt(), UInt()), _ -> _.bar) + class MyModule extends Module { + val tpe = new MyBundle(UInt(8.W), UInt(8.W)) + val in = IO(Input(UInt(8.W))) + val out = IO(Output(tpe)) + out := in.viewAs[MyBundle] + } + val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule)) + err.toString should include ("View field '_.foo' is missing") + } + + it should "error if the mapping is non-total in the target" in { + import Tuple2DataProduct._ + implicit val dv = DataView[(UInt, UInt), UInt](_ => UInt(), _._1 -> _) + class MyModule extends Module { + val a, b = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + out := (a, b).viewAs[UInt] + } + val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule)) + err.toString should include ("Target field '_._2' is missing") + } + + it should "error if the mapping contains Data that are not part of the Target" in { + class BundleA extends Bundle { + val foo = UInt(8.W) + } + class BundleB extends Bundle { + val fizz = UInt(8.W) + val buzz = UInt(8.W) + } + implicit val dv = DataView[BundleA, BundleB](_ => new BundleB, _.foo -> _.fizz, (_, b) => (3.U, b.buzz)) + class MyModule extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + out := in.viewAs[BundleB] + } + val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule)) + err.toString should include ("View mapping must only contain Elements within the Target") + } + + it should "error if the mapping contains Data that are not part of the View" in { + class BundleA extends Bundle { + val foo = UInt(8.W) + } + class BundleB extends Bundle { + val fizz = UInt(8.W) + val buzz = UInt(8.W) + } + implicit val dv = DataView[BundleA, BundleB](_ => new BundleB, _.foo -> _.fizz, (_, b) => (3.U, b.buzz)) + implicit val dv2 = dv.invert(_ => new BundleA) + class MyModule extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + out.viewAs[BundleA] := in + } + val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule)) + err.toString should include ("View mapping must only contain Elements within the View") + } + + it should "error if a view has a width that does not match the target" in { + class BundleA extends Bundle { + val foo = UInt(8.W) + } + class BundleB extends Bundle { + val bar = UInt(4.W) + } + implicit val dv = DataView[BundleA, BundleB](_ => new BundleB, _.foo -> _.bar) + class MyModule extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + out := in.viewAs[BundleB] + } + val err = the [InvalidViewException] thrownBy ChiselStage.emitChirrtl(new MyModule) + val expected = """View field _\.bar UInt<4> has width <4> that is incompatible with target value .+'s width <8>""".r + err.getMessage should fullyMatch regex expected + } + + it should "error if a view has a known width when the target width is unknown" in { + class BundleA extends Bundle { + val foo = UInt() + } + class BundleB extends Bundle { + val bar = UInt(4.W) + } + implicit val dv = DataView[BundleA, BundleB](_ => new BundleB, _.foo -> _.bar) + class MyModule extends Module { + val in = IO(Input(new BundleA)) + val out = IO(Output(new BundleB)) + out := in.viewAs[BundleB] + } + val err = the [InvalidViewException] thrownBy ChiselStage.emitChirrtl(new MyModule) + val expected = """View field _\.bar UInt<4> has width <4> that is incompatible with target value .+'s width <unknown>""".r + err.getMessage should fullyMatch regex expected + } + + behavior of "PartialDataView" + + it should "still error if the mapping is non-total in the view" in { + class MyBundle(val foo: UInt, val bar: UInt) extends Bundle + implicit val dv = PartialDataView[UInt, MyBundle](_ => new MyBundle(UInt(), UInt()), _ -> _.bar) + class MyModule extends Module { + val in = IO(Input(UInt(8.W))) + val out = IO(Output(new MyBundle(UInt(8.W), UInt(8.W)))) + out := in.viewAs[MyBundle] + } + val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule)) + err.toString should include ("View field '_.foo' is missing") + } + + it should "NOT error if the mapping is non-total in the target" in { + import Tuple2DataProduct._ + implicit val dv = PartialDataView[(UInt, UInt), UInt](_ => UInt(), _._2 -> _) + class MyModule extends Module { + val a, b = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + out := (a, b).viewAs[UInt] + } + val verilog = ChiselStage.emitVerilog(new MyModule) + verilog should include ("assign out = b;") + } +} diff --git a/src/test/scala/chiselTests/experimental/DataViewIntegrationSpec.scala b/src/test/scala/chiselTests/experimental/DataViewIntegrationSpec.scala new file mode 100644 index 00000000..3f149f75 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/DataViewIntegrationSpec.scala @@ -0,0 +1,57 @@ +// See LICENSE for license details. + +package chiselTests.experimental + +import chisel3._ +import chisel3.experimental.{BaseModule, ExtModule} +import chisel3.experimental.dataview._ +import chisel3.util.{Decoupled, DecoupledIO, Queue, QueueIO, log2Ceil} +import chiselTests.ChiselFlatSpec +import firrtl.transforms.DontTouchAnnotation + +// Let's put it all together! +object DataViewIntegrationSpec { + + class QueueIntf[T <: Data](gen: T, entries: Int) extends Bundle { + val ports = new QueueIO(gen, entries) + // Let's grab a reference to something internal too + // Output because can't have directioned and undirectioned stuff + val enq_ptr = Output(UInt(log2Ceil(entries).W)) + } + + // It's not clear if a view of a Module ever _can_ be total since internal nodes are part of the Module + implicit def queueView[T <: Data] = PartialDataView[Queue[T], QueueIntf[T]]( + q => new QueueIntf(q.gen, q.entries), + _.io -> _.ports, + // Some token internal signal + _.enq_ptr.value -> _.enq_ptr + ) + + object MyQueue { + def apply[T <: Data](enq: DecoupledIO[T], n: Int): QueueIntf[T] = { + val queue = Module(new Queue[T](enq.bits.cloneType, n)) + val view = queue.viewAs[QueueIntf[T]] + view.ports.enq <> enq + view + } + } + + class MyModule extends Module { + val enq = IO(Flipped(Decoupled(UInt(8.W)))) + val deq = IO(Decoupled(UInt(8.W))) + + val queue = MyQueue(enq, 4) + deq <> queue.ports.deq + dontTouch(queue.enq_ptr) + } +} + +class DataViewIntegrationSpec extends ChiselFlatSpec { + import DataViewIntegrationSpec.MyModule + + "Users" should "be able to view and annotate Modules" in { + val (_, annos) = getFirrtlAndAnnos(new MyModule) + val ts = annos.collect { case DontTouchAnnotation(t) => t.serialize } + ts should equal (Seq("~MyModule|Queue>enq_ptr_value")) + } +} diff --git a/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala b/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala new file mode 100644 index 00000000..92091631 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.experimental + +import chisel3._ +import chisel3.experimental.dataview._ +import chisel3.experimental.{ChiselAnnotation, annotate} +import chisel3.stage.ChiselStage +import chiselTests.ChiselFlatSpec + +object DataViewTargetSpec { + import firrtl.annotations._ + private case class DummyAnno(target: ReferenceTarget, id: Int) extends SingleTargetAnnotation[ReferenceTarget] { + override def duplicate(n: ReferenceTarget) = this.copy(target = n) + } + private def mark(d: Data, id: Int) = annotate(new ChiselAnnotation { + override def toFirrtl: Annotation = DummyAnno(d.toTarget, id) + }) + private def markAbs(d: Data, id: Int) = annotate(new ChiselAnnotation { + override def toFirrtl: Annotation = DummyAnno(d.toAbsoluteTarget, id) + }) +} + +class DataViewTargetSpec extends ChiselFlatSpec { + import DataViewTargetSpec._ + private val checks: Seq[Data => String] = Seq( + _.toTarget.toString, + _.toAbsoluteTarget.toString, + _.instanceName, + _.pathName, + _.parentPathName, + _.parentModName, + ) + + // Check helpers + private def checkAll(impl: Data, refs: String*): Unit = { + refs.size should be (checks.size) + for ((check, value) <- checks.zip(refs)) { + check(impl) should be (value) + } + } + private def checkSameAs(impl: Data, refs: Data*): Unit = + for (ref <- refs) { + checkAll(impl, checks.map(_(ref)):_*) + } + + behavior of "DataView Naming" + + it should "support views of Elements" in { + class MyChild extends Module { + val out = IO(Output(UInt(8.W))) + val insideView = out.viewAs[UInt] + out := 0.U + } + class MyParent extends Module { + val out = IO(Output(UInt(8.W))) + val inst = Module(new MyChild) + out := inst.out + } + val m = elaborateAndGetModule(new MyParent) + val outsideView = m.inst.out.viewAs[UInt] + checkSameAs(m.inst.out, m.inst.insideView, outsideView) + } + + it should "support 1:1 mappings of Aggregates and their children" in { + class MyBundle extends Bundle { + val foo = UInt(8.W) + val bars = Vec(2, UInt(8.W)) + } + implicit val dv = DataView[MyBundle, Vec[UInt]](_ => Vec(3, UInt(8.W)), _.foo -> _(0), _.bars(0) -> _(1), _.bars(1) -> _(2)) + class MyChild extends Module { + val out = IO(Output(new MyBundle)) + val outView = out.viewAs[Vec[UInt]] // Note different type + val outFooView = out.foo.viewAs[UInt] + val outBarsView = out.bars.viewAs[Vec[UInt]] + val outBars0View = out.bars(0).viewAs[UInt] + out := 0.U.asTypeOf(new MyBundle) + } + class MyParent extends Module { + val out = IO(Output(new MyBundle)) + val inst = Module(new MyChild) + out := inst.out + } + val m = elaborateAndGetModule(new MyParent) + val outView = m.inst.out.viewAs[Vec[UInt]]// Note different type + val outFooView = m.inst.out.foo.viewAs[UInt] + val outBarsView = m.inst.out.bars.viewAs[Vec[UInt]] + val outBars0View = m.inst.out.bars(0).viewAs[UInt] + + checkSameAs(m.inst.out, m.inst.outView, outView) + checkSameAs(m.inst.out.foo, m.inst.outFooView, m.inst.outView(0), outFooView, outView(0)) + checkSameAs(m.inst.out.bars, m.inst.outBarsView, outBarsView) + checkSameAs(m.inst.out.bars(0), m.inst.outBars0View, outBars0View, m.inst.outView(1), outView(1), + m.inst.outBarsView(0), outBarsView(0)) + } + + // Ideally this would work 1:1 but that requires changing the binding + it should "support annotation renaming of Aggregate children of Aggregate views" in { + class MyBundle extends Bundle { + val foo = Vec(2, UInt(8.W)) + } + class MyChild extends Module { + val out = IO(Output(new MyBundle)) + val outView = out.viewAs[MyBundle] + mark(out.foo, 0) + mark(outView.foo, 1) + markAbs(out.foo, 2) + markAbs(outView, 3) + out := 0.U.asTypeOf(new MyBundle) + } + class MyParent extends Module { + val out = IO(Output(new MyBundle)) + val inst = Module(new MyChild) + out := inst.out + } + val (_, annos) = getFirrtlAndAnnos(new MyParent) + val pairs = annos.collect { case DummyAnno(t, idx) => (idx, t.toString) }.sortBy(_._1) + val expected = Seq( + 0 -> "~MyParent|MyChild>out.foo", + // The child of the view that was itself an Aggregate got split because 1:1 is lacking here + 1 -> "~MyParent|MyChild>out.foo[0]", + 1 -> "~MyParent|MyChild>out.foo[1]", + 2 -> "~MyParent|MyParent/inst:MyChild>out.foo", + 3 -> "~MyParent|MyParent/inst:MyChild>out" + ) + pairs should equal (expected) + } + + it should "support annotating views that cannot be mapped to a single ReferenceTarget" in { + import HWTuple._ + class MyBundle extends Bundle { + val a, b = Input(UInt(8.W)) + val c, d = Output(UInt(8.W)) + } + // Note that each use of a Tuple as Data causes an implicit conversion creating a View + class MyChild extends Module { + val io = IO(new MyBundle) + (io.c, io.d) := (io.a, io.b) + // The type annotations create the views via the implicit conversion + val view1: Data = (io.a, io.b) + val view2: Data = (io.c, io.d) + mark(view1, 0) + mark(view2, 1) + markAbs(view1, 2) + markAbs(view2, 3) + mark((io.b, io.d), 4) // Mix it up for fun + } + class MyParent extends Module { + val io = IO(new MyBundle) + val inst = Module(new MyChild) + io <> inst.io + } + val (_, annos) = getFirrtlAndAnnos(new MyParent) + val pairs = annos.collect { case DummyAnno(t, idx) => (idx, t.toString) }.sorted + val expected = Seq( + 0 -> "~MyParent|MyChild>io.a", + 0 -> "~MyParent|MyChild>io.b", + 1 -> "~MyParent|MyChild>io.c", + 1 -> "~MyParent|MyChild>io.d", + 2 -> "~MyParent|MyParent/inst:MyChild>io.a", + 2 -> "~MyParent|MyParent/inst:MyChild>io.b", + 3 -> "~MyParent|MyParent/inst:MyChild>io.c", + 3 -> "~MyParent|MyParent/inst:MyChild>io.d", + 4 -> "~MyParent|MyChild>io.b", + 4 -> "~MyParent|MyChild>io.d", + ) + pairs should equal (expected) + } + + // TODO check these properties when using @instance API (especially preservation of totality) +} diff --git a/src/test/scala/chiselTests/experimental/ForceNames.scala b/src/test/scala/chiselTests/experimental/ForceNames.scala index b3534f11..06f911e6 100644 --- a/src/test/scala/chiselTests/experimental/ForceNames.scala +++ b/src/test/scala/chiselTests/experimental/ForceNames.scala @@ -4,7 +4,7 @@ package chiselTests import firrtl._ import chisel3._ -import chisel3.core.annotate +import chisel3.experimental.annotate import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage} import chisel3.util.experimental.{ForceNameAnnotation, ForceNamesTransform, InlineInstance, forceName} import firrtl.annotations.{Annotation, ReferenceTarget} diff --git a/src/test/scala/chiselTests/experimental/ModuleDataProductSpec.scala b/src/test/scala/chiselTests/experimental/ModuleDataProductSpec.scala new file mode 100644 index 00000000..78986517 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/ModuleDataProductSpec.scala @@ -0,0 +1,91 @@ +// See LICENSE for license details. + +package chiselTests.experimental + +import chisel3._ +import chisel3.experimental.{BaseModule, ExtModule} +import chisel3.experimental.dataview.DataProduct +import chiselTests.ChiselFlatSpec + +object ModuleDataProductSpec { + class MyBundle extends Bundle { + val foo = UInt(8.W) + val bar = UInt(8.W) + } + trait MyIntf extends BaseModule { + val in = IO(Input(new MyBundle)) + val out = IO(Output(new MyBundle)) + } + class Passthrough extends RawModule { + val in = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + out := in + } + class MyUserModule extends Module with MyIntf { + val inst = Module(new Passthrough) + inst.in := in.foo + val r = RegNext(in) + out := r + } + + class MyExtModule extends ExtModule with MyIntf + class MyExtModuleWrapper extends RawModule with MyIntf { + val inst = Module(new MyExtModule) + inst.in := in + out := inst.out + } +} + +class ModuleDataProductSpec extends ChiselFlatSpec { + import ModuleDataProductSpec._ + + behavior of "DataProduct" + + it should "work for UserModules (recursively)" in { + val m = elaborateAndGetModule(new MyUserModule) + val expected = Seq( + m.clock -> "m.clock", + m.reset -> "m.reset", + m.in -> "m.in", + m.in.foo -> "m.in.foo", + m.in.bar -> "m.in.bar", + m.out -> "m.out", + m.out.foo -> "m.out.foo", + m.out.bar -> "m.out.bar", + m.r -> "m.r", + m.r.foo -> "m.r.foo", + m.r.bar -> "m.r.bar", + m.inst.in -> "m.inst.in", + m.inst.out -> "m.inst.out" + ) + + val impl = implicitly[DataProduct[MyUserModule]] + val set = impl.dataSet(m) + for ((d, _) <- expected) { + set(d) should be (true) + } + val it = impl.dataIterator(m, "m") + it.toList should contain theSameElementsAs (expected) + } + + it should "work for (wrapped) ExtModules" in { + val m = elaborateAndGetModule(new MyExtModuleWrapper).inst + val expected = Seq( + m.in -> "m.in", + m.in.foo -> "m.in.foo", + m.in.bar -> "m.in.bar", + m.out -> "m.out", + m.out.foo -> "m.out.foo", + m.out.bar -> "m.out.bar" + ) + + val impl = implicitly[DataProduct[MyExtModule]] + val set = impl.dataSet(m) + for ((d, _) <- expected) { + set(d) should be (true) + } + val it = impl.dataIterator(m, "m") + it.toList should contain theSameElementsAs (expected) + } + +} diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala b/src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala new file mode 100644 index 00000000..43111fdd --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.experimental.hierarchy + +import _root_.firrtl.annotations._ +import chisel3.experimental.{annotate, BaseModule} +import chisel3.Data +import chisel3.experimental.hierarchy.{Instance, Definition} + +object Annotations { + case class MarkAnnotation(target: IsMember, tag: String) extends SingleTargetAnnotation[IsMember] { + def duplicate(n: IsMember): Annotation = this.copy(target = n) + } + case class MarkChiselInstanceAnnotation[B <: BaseModule](d: Instance[B], tag: String, isAbsolute: Boolean) extends chisel3.experimental.ChiselAnnotation { + def toFirrtl = MarkAnnotation(d.toTarget, tag) + } + case class MarkChiselDefinitionAnnotation[B <: BaseModule](d: Definition[B], tag: String, isAbsolute: Boolean) extends chisel3.experimental.ChiselAnnotation { + def toFirrtl = MarkAnnotation(d.toTarget, tag) + } + case class MarkChiselAnnotation(d: Data, tag: String, isAbsolute: Boolean) extends chisel3.experimental.ChiselAnnotation { + def toFirrtl = if(isAbsolute) MarkAnnotation(d.toAbsoluteTarget, tag) else MarkAnnotation(d.toTarget, tag) + } + def mark(d: Data, tag: String): Unit = annotate(MarkChiselAnnotation(d, tag, false)) + def mark[B <: BaseModule](d: Instance[B], tag: String): Unit = annotate(MarkChiselInstanceAnnotation(d, tag, false)) + def mark[B <: BaseModule](d: Definition[B], tag: String): Unit = annotate(MarkChiselDefinitionAnnotation(d, tag, false)) + def amark(d: Data, tag: String): Unit = annotate(MarkChiselAnnotation(d, tag, true)) + def amark[B <: BaseModule](d: Instance[B], tag: String): Unit = annotate(MarkChiselInstanceAnnotation(d, tag, true)) +} diff --git a/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala b/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala new file mode 100644 index 00000000..19261c36 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests +package experimental.hierarchy + +import chisel3._ +import chisel3.experimental.BaseModule +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, public} + +// TODO/Notes +// - In backport, clock/reset are not automatically assigned. I think this is fixed in 3.5 +// - CircuitTarget for annotations on the definition are wrong - needs to be fixed. +class DefinitionSpec extends ChiselFunSpec with Utils { + import Annotations._ + import Examples._ + describe("0: Definition instantiation") { + it("0.0: module name of a definition should be correct") { + class Top extends Module { + val definition = Definition(new AddOne) + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("module AddOne :") + } + it("0.2: accessing internal fields through non-generated means is hard to do") { + class Top extends Module { + val definition = Definition(new AddOne) + //definition.lookup(_.in) // Uncommenting this line will give the following error: + //"You are trying to access a macro-only API. Please use the @public annotation instead." + definition.in + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("module AddOne :") + } + it("0.2: reset inference is not defaulted to Bool for definitions") { + class Top extends Module with RequireAsyncReset { + val definition = Definition(new HasUninferredReset) + val i0 = Instance(definition) + i0.in := 0.U + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("inst i0 of HasUninferredReset") + } + } + describe("1: Annotations on definitions in same chisel compilation") { + it("1.0: should work on a single definition, annotating the definition") { + class Top extends Module { + val definition: Definition[AddOne] = Definition(new AddOne) + mark(definition, "mark") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne".mt, "mark")) + } + it("1.1: should work on a single definition, annotating an inner wire") { + class Top extends Module { + val definition: Definition[AddOne] = Definition(new AddOne) + mark(definition.innerWire, "i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne>innerWire".rt, "i0.innerWire")) + } + it("1.2: should work on a two nested definitions, annotating the definition") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + mark(definition.definition, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne".mt, "i0.i0")) + } + it("1.2: should work on an instance in a definition, annotating the instance") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + mark(definition.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne".it, "i0.i0")) + } + it("1.2: should work on a definition in an instance, annotating the definition") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + val i0 = Instance(definition) + mark(i0.definition, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne".mt, "i0.i0")) + } + it("1.3: should work on a wire in an instance in a definition") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + mark(definition.i0.innerWire, "i0.i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "i0.i0.innerWire")) + } + it("1.4: should work on a nested module in a definition, annotating the module") { + class Top extends Module { + val definition: Definition[AddTwoMixedModules] = Definition(new AddTwoMixedModules) + mark(definition.i1, "i0.i1") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwoMixedModules/i1:AddOne_2".it, "i0.i1")) + } + // Can you define an instantiable container? I think not. + // Instead, we can test the instantiable container in a definition + it("1.5: should work on an instantiable container, annotating a wire in the defintion") { + class Top extends Module { + val definition: Definition[AddOneWithInstantiableWire] = Definition(new AddOneWithInstantiableWire) + mark(definition.wireContainer.innerWire, "i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableWire>innerWire".rt, "i0.innerWire")) + } + it("1.6: should work on an instantiable container, annotating a module") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableModule) + mark(definition.moduleContainer.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableModule/i0:AddOne".it, "i0.i0")) + } + it("1.7: should work on an instantiable container, annotating an instance") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstance) + mark(definition.instanceContainer.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableInstance/i0:AddOne".it, "i0.i0")) + } + it("1.8: should work on an instantiable container, annotating an instantiable container's module") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstantiable) + mark(definition.containerContainer.container.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableInstantiable/i0:AddOne".it, "i0.i0")) + } + it("1.9: should work on public member which references public member of another instance") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstantiable) + mark(definition.containerContainer.container.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableInstantiable/i0:AddOne".it, "i0.i0")) + } + it("1.10: should work for targets on definition to have correct circuit name"){ + class Top extends Module { + val definition = Definition(new AddOneWithAnnotation) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithAnnotation>innerWire".rt, "innerWire")) + } + } + describe("2: Annotations on designs not in the same chisel compilation") { + it("2.0: should work on an innerWire, marked in a different compilation") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Definition(new ViewerParent(x, false, true)) + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain(MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "first")) + } + it("2.1: should work on an innerWire, marked in a different compilation, in instanced instantiable") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Definition(new ViewerParent(x, true, false)) + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain(MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "second")) + } + it("2.2: should work on an innerWire, marked in a different compilation, in instanced module") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Definition(new ViewerParent(x, false, false)) + mark(parent.viewer.x.i0.innerWire, "third") + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain(MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "third")) + } + } + describe("3: @public") { + it("3.0: should work on multi-vals") { + class Top() extends Module { + val mv = Definition(new MultiVal()) + mark(mv.x, "mv.x") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|MultiVal>x".rt, "mv.x")) + } + it("3.1: should work on lazy vals") { + class Top() extends Module { + val lv = Definition(new LazyVal()) + mark(lv.x, lv.y) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|LazyVal>x".rt, "Hi")) + } + it("3.2: should work on islookupables") { + class Top() extends Module { + val p = Parameters("hi", 0) + val up = Definition(new UsesParameters(p)) + mark(up.x, up.y.string + up.y.int) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|UsesParameters>x".rt, "hi0")) + } + it("3.3: should work on lists") { + class Top() extends Module { + val i = Definition(new HasList()) + mark(i.x(1), i.y(1).toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasList>x_1".rt, "2")) + } + it("3.4: should work on seqs") { + class Top() extends Module { + val i = Definition(new HasSeq()) + mark(i.x(1), i.y(1).toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasSeq>x_1".rt, "2")) + } + it("3.5: should work on options") { + class Top() extends Module { + val i = Definition(new HasOption()) + i.x.map(x => mark(x, "x")) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasOption>x".rt, "x")) + } + it("3.6: should work on vecs") { + class Top() extends Module { + val i = Definition(new HasVec()) + mark(i.x, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasVec>x".rt, "blah")) + } + it("3.7: should work on statically indexed vectors external to module") { + class Top() extends Module { + val i = Definition(new HasVec()) + mark(i.x(1), "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasVec>x[1]".rt, "blah")) + } + it("3.8: should work on statically indexed vectors internal to module") { + class Top() extends Module { + val i = Definition(new HasIndexedVec()) + mark(i.y, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasIndexedVec>x[1]".rt, "blah")) + } + ignore("3.9: should work on vals in constructor arguments") { + class Top() extends Module { + val i = Definition(new HasPublicConstructorArgs(10)) + //mark(i.x, i.int.toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasPublicConstructorArgs>x".rt, "10")) + } + } + describe("4: toDefinition") { + it("4.0: should work on modules") { + class Top() extends Module { + val i = Module(new AddOne()) + f(i.toDefinition) + } + def f(i: Definition[AddOne]): Unit = mark(i.innerWire, "blah") + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne>innerWire".rt, "blah")) + } + it("4.2: should work on seqs of modules") { + class Top() extends Module { + val is = Seq(Module(new AddTwo()), Module(new AddTwo())).map(_.toDefinition) + mark(f(is), "blah") + } + def f(i: Seq[Definition[AddTwo]]): Data = i.head.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("4.2: should work on options of modules") { + class Top() extends Module { + val is: Option[Definition[AddTwo]] = Some(Module(new AddTwo())).map(_.toDefinition) + mark(f(is), "blah") + } + def f(i: Option[Definition[AddTwo]]): Data = i.get.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + } + describe("5: Absolute Targets should work as expected") { + it("5.0: toAbsoluteTarget on a port of a definition") { + class Top() extends Module { + val i = Definition(new AddTwo()) + amark(i.in, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo>in".rt, "blah")) + } + it("5.1: toAbsoluteTarget on a subinstance's data within a definition") { + class Top() extends Module { + val i = Definition(new AddTwo()) + amark(i.i0.innerWire, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("5.2: toAbsoluteTarget on a submodule's data within a definition") { + class Top() extends Module { + val i = Definition(new AddTwoMixedModules()) + amark(i.i1.in, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwoMixedModules/i1:AddOne_2>in".rt, "blah")) + } + it("5.3: toAbsoluteTarget on a submodule's data, in an aggregate, within a definition") { + class Top() extends Module { + val i = Definition(new InstantiatesHasVec()) + amark(i.i1.x.head, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|InstantiatesHasVec/i1:HasVec_2>x[0]".rt, "blah")) + } + } + describe("6: @instantiable traits should work as expected") { + class MyBundle extends Bundle { + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + } + @instantiable + trait ModuleIntf extends BaseModule { + @public val io = IO(new MyBundle) + } + @instantiable + class ModuleWithCommonIntf(suffix: String = "") extends Module with ModuleIntf { + override def desiredName: String = super.desiredName + suffix + @public val sum = io.in + 1.U + + io.out := sum + } + class BlackBoxWithCommonIntf extends BlackBox with ModuleIntf + + it("6.0: A Module that implements an @instantiable trait should be definable as that trait") { + class Top extends Module { + val i: Definition[ModuleIntf] = Definition(new ModuleWithCommonIntf) + mark(i.io.in, "gotcha") + mark(i, "inst") + } + val expected = List( + "~Top|ModuleWithCommonIntf>io.in".rt -> "gotcha", + "~Top|ModuleWithCommonIntf".mt -> "inst" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.1 An @instantiable Module that implements an @instantiable trait should be able to use extension methods from both") { + class Top extends Module { + val i: Definition[ModuleWithCommonIntf] = Definition(new ModuleWithCommonIntf) + mark(i.io.in, "gotcha") + mark(i.sum, "also this") + mark(i, "inst") + } + val expected = List( + "~Top|ModuleWithCommonIntf>io.in".rt -> "gotcha", + "~Top|ModuleWithCommonIntf>sum".rt -> "also this", + "~Top|ModuleWithCommonIntf".mt -> "inst" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.2 A BlackBox that implements an @instantiable trait should be instantiable as that trait") { + class Top extends Module { + val m: ModuleIntf = Module(new BlackBoxWithCommonIntf) + val d: Definition[ModuleIntf] = m.toDefinition + mark(d.io.in, "gotcha") + mark(d, "module") + } + val expected = List( + "~Top|BlackBoxWithCommonIntf>in".rt -> "gotcha", + "~Top|BlackBoxWithCommonIntf".mt -> "module" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.3 It should be possible to have Vectors of @instantiable traits mixing concrete subclasses") { + class Top extends Module { + val definition = Definition(new ModuleWithCommonIntf("X")) + val insts: Seq[Definition[ModuleIntf]] = Vector( + Module(new ModuleWithCommonIntf("Y")).toDefinition, + Module(new BlackBoxWithCommonIntf).toDefinition, + definition + ) + mark(insts(0).io.in, "foo") + mark(insts(1).io.in, "bar") + mark(insts(2).io.in, "fizz") + } + val expected = List( + "~Top|ModuleWithCommonIntfY>io.in".rt -> "foo", + "~Top|BlackBoxWithCommonIntf>in".rt -> "bar", + "~Top|ModuleWithCommonIntfX>io.in".rt -> "fizz" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } + describe("7: @instantiable and @public should compose with DataView") { + import chisel3.experimental.dataview._ + ignore("7.0: should work on simple Views") { + @instantiable + class MyModule extends RawModule { + val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + val sum = in + 1.U + out := sum + 1.U + @public val foo = in.viewAs[UInt] + @public val bar = sum.viewAs[UInt] + } + class Top extends RawModule { + val foo = IO(Input(UInt(8.W))) + val bar = IO(Output(UInt(8.W))) + val d = Definition(new MyModule) + val i = Instance(d) + i.foo := foo + bar := i.out + mark(d.out, "out") + mark(d.foo, "foo") + mark(d.bar, "bar") + } + val expectedAnnos = List( + "~Top|MyModule>out".rt -> "out", + "~Top|MyModule>in".rt -> "foo", + "~Top|MyModule>sum".rt -> "bar" + ) + val expectedLines = List( + "i.in <= foo", + "bar <= i.out" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- expectedLines) { + text should include (line) + } + for (e <- expectedAnnos.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + ignore("7.1: should work on Aggregate Views that are mapped 1:1") { + import chiselTests.experimental.SimpleBundleDataView._ + @instantiable + class MyModule extends RawModule { + private val a = IO(Input(new BundleA(8))) + private val b = IO(Output(new BundleA(8))) + @public val in = a.viewAs[BundleB] + @public val out = b.viewAs[BundleB] + out := in + } + class Top extends RawModule { + val foo = IO(Input(new BundleB(8))) + val bar = IO(Output(new BundleB(8))) + val d = Definition(new MyModule) + val i = Instance(d) + i.in := foo + bar.bar := i.out.bar + mark(d.in, "in") + mark(d.in.bar, "in_bar") + } + val expectedAnnos = List( + "~Top|MyModule>a".rt -> "in", + "~Top|MyModule>a.foo".rt -> "in_bar", + ) + val expectedLines = List( + "i.a <= foo", + "bar <= i.b.foo" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- expectedLines) { + text should include (line) + } + for (e <- expectedAnnos.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } +} diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala new file mode 100644 index 00000000..23b8c9c0 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.experimental.hierarchy + +import chisel3._ +import chisel3.util.Valid +import chisel3.experimental.hierarchy._ +import chisel3.experimental.BaseModule + +object Examples { + import Annotations._ + @instantiable + class AddOne extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val innerWire = Wire(UInt(32.W)) + innerWire := in + 1.U + out := innerWire + } + @instantiable + class AddOneWithAnnotation extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val innerWire = Wire(UInt(32.W)) + mark(innerWire, "innerWire") + innerWire := in + 1.U + out := innerWire + } + @instantiable + class AddOneWithAbsoluteAnnotation extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val innerWire = Wire(UInt(32.W)) + amark(innerWire, "innerWire") + innerWire := in + 1.U + out := innerWire + } + @instantiable + class AddTwo extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val definition = Definition(new AddOne) + @public val i0: Instance[AddOne] = Instance(definition) + @public val i1: Instance[AddOne] = Instance(definition) + i0.in := in + i1.in := i0.out + out := i1.out + } + @instantiable + class AddTwoMixedModules extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + val definition = Definition(new AddOne) + @public val i0: Instance[AddOne] = Instance(definition) + @public val i1 = Module(new AddOne) + i0.in := in + i1.in := i0.out + out := i1.out + } + @instantiable + class AggregatePortModule extends Module { + @public val io = IO(new Bundle { + val in = Input(UInt(32.W)) + val out = Output(UInt(32.W)) + }) + io.out := io.in + } + @instantiable + class WireContainer { + @public val innerWire = Wire(UInt(32.W)) + } + @instantiable + class AddOneWithInstantiableWire extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val wireContainer = new WireContainer() + wireContainer.innerWire := in + 1.U + out := wireContainer.innerWire + } + @instantiable + class AddOneContainer { + @public val i0 = Module(new AddOne) + } + @instantiable + class AddOneWithInstantiableModule extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val moduleContainer = new AddOneContainer() + moduleContainer.i0.in := in + out := moduleContainer.i0.out + } + @instantiable + class AddOneInstanceContainer { + val definition = Definition(new AddOne) + @public val i0 = Instance(definition) + } + @instantiable + class AddOneWithInstantiableInstance extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val instanceContainer = new AddOneInstanceContainer() + instanceContainer.i0.in := in + out := instanceContainer.i0.out + } + @instantiable + class AddOneContainerContainer { + @public val container = new AddOneContainer + } + @instantiable + class AddOneWithInstantiableInstantiable extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val containerContainer = new AddOneContainerContainer() + containerContainer.container.i0.in := in + out := containerContainer.container.i0.out + } + @instantiable + class Viewer(val y: AddTwo, markPlease: Boolean) { + @public val x = y + if(markPlease) mark(x.i0.innerWire, "first") + } + @instantiable + class ViewerParent(val x: AddTwo, markHere: Boolean, markThere: Boolean) extends Module { + @public val viewer = new Viewer(x, markThere) + if(markHere) mark(viewer.x.i0.innerWire, "second") + } + @instantiable + class MultiVal() extends Module { + @public val (x, y) = (Wire(UInt(3.W)), Wire(UInt(3.W))) + } + @instantiable + class LazyVal() extends Module { + @public val x = Wire(UInt(3.W)) + @public lazy val y = "Hi" + } + case class Parameters(string: String, int: Int) extends IsLookupable + @instantiable + class UsesParameters(p: Parameters) extends Module { + @public val y = p + @public val x = Wire(UInt(3.W)) + } + @instantiable + class HasList() extends Module { + @public val y = List(1, 2, 3) + @public val x = List.fill(3)(Wire(UInt(3.W))) + } + @instantiable + class HasSeq() extends Module { + @public val y = Seq(1, 2, 3) + @public val x = Seq.fill(3)(Wire(UInt(3.W))) + } + @instantiable + class HasOption() extends Module { + @public val x: Option[UInt] = Some(Wire(UInt(3.W))) + } + @instantiable + class HasVec() extends Module { + @public val x = VecInit(1.U, 2.U, 3.U) + } + @instantiable + class HasIndexedVec() extends Module { + val x = VecInit(1.U, 2.U, 3.U) + @public val y = x(1) + } + @instantiable + class HasSubFieldAccess extends Module { + val in = IO(Input(Valid(UInt(8.W)))) + @public val valid = in.valid + @public val bits = in.bits + } + @instantiable + class HasPublicConstructorArgs(@public val int: Int) extends Module { + @public val x = Wire(UInt(3.W)) + } + @instantiable + class InstantiatesHasVec() extends Module { + @public val i0 = Instance(Definition(new HasVec())) + @public val i1 = Module(new HasVec()) + } + @instantiable + class HasUninferredReset() extends Module { + @public val in = IO(Input(UInt(3.W))) + @public val out = IO(Output(UInt(3.W))) + out := RegNext(in) + } +} diff --git a/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala new file mode 100644 index 00000000..3866bf87 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala @@ -0,0 +1,709 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests +package experimental.hierarchy + +import chisel3._ +import chisel3.experimental.BaseModule +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, public} +import chisel3.util.{DecoupledIO, Valid} + + +// TODO/Notes +// - In backport, clock/reset are not automatically assigned. I think this is fixed in 3.5 +// - CircuitTarget for annotations on the definition are wrong - needs to be fixed. +class InstanceSpec extends ChiselFunSpec with Utils { + import Annotations._ + import Examples._ + describe("0: Instance instantiation") { + it("0.0: name of an instance should be correct") { + class Top extends Module { + val definition = Definition(new AddOne) + val i0 = Instance(definition) + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("inst i0 of AddOne") + } + it("0.1: name of an instanceclone should not error") { + class Top extends Module { + val definition = Definition(new AddTwo) + val i0 = Instance(definition) + val i = i0.i0 // This should not error + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("inst i0 of AddTwo") + } + it("0.2: accessing internal fields through non-generated means is hard to do") { + class Top extends Module { + val definition = Definition(new AddOne) + val i0 = Instance(definition) + //i0.lookup(_.in) // Uncommenting this line will give the following error: + //"You are trying to access a macro-only API. Please use the @public annotation instead." + i0.in + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("inst i0 of AddOne") + } + } + describe("1: Annotations on instances in same chisel compilation") { + it("1.0: should work on a single instance, annotating the instance") { + class Top extends Module { + val definition: Definition[AddOne] = Definition(new AddOne) + val i0: Instance[AddOne] = Instance(definition) + mark(i0, "i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOne".it, "i0")) + } + it("1.1: should work on a single instance, annotating an inner wire") { + class Top extends Module { + val definition: Definition[AddOne] = Definition(new AddOne) + val i0: Instance[AddOne] = Instance(definition) + mark(i0.innerWire, "i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOne>innerWire".rt, "i0.innerWire")) + } + it("1.2: should work on a two nested instances, annotating the instance") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + val i0: Instance[AddTwo] = Instance(definition) + mark(i0.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddTwo/i0:AddOne".it, "i0.i0")) + } + it("1.3: should work on a two nested instances, annotating the inner wire") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + val i0: Instance[AddTwo] = Instance(definition) + mark(i0.i0.innerWire, "i0.i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddTwo/i0:AddOne>innerWire".rt, "i0.i0.innerWire")) + } + it("1.4: should work on a nested module in an instance, annotating the module") { + class Top extends Module { + val definition: Definition[AddTwoMixedModules] = Definition(new AddTwoMixedModules) + val i0: Instance[AddTwoMixedModules] = Instance(definition) + mark(i0.i1, "i0.i1") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddTwoMixedModules/i1:AddOne_2".it, "i0.i1")) + } + it("1.5: should work on an instantiable container, annotating a wire") { + class Top extends Module { + val definition: Definition[AddOneWithInstantiableWire] = Definition(new AddOneWithInstantiableWire) + val i0: Instance[AddOneWithInstantiableWire] = Instance(definition) + mark(i0.wireContainer.innerWire, "i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableWire>innerWire".rt, "i0.innerWire")) + } + it("1.6: should work on an instantiable container, annotating a module") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableModule) + val i0 = Instance(definition) + mark(i0.moduleContainer.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableModule/i0:AddOne".it, "i0.i0")) + } + it("1.7: should work on an instantiable container, annotating an instance") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstance) + val i0 = Instance(definition) + mark(i0.instanceContainer.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableInstance/i0:AddOne".it, "i0.i0")) + } + it("1.8: should work on an instantiable container, annotating an instantiable container's module") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstantiable) + val i0 = Instance(definition) + mark(i0.containerContainer.container.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableInstantiable/i0:AddOne".it, "i0.i0")) + } + it("1.9: should work on public member which references public member of another instance") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstantiable) + val i0 = Instance(definition) + mark(i0.containerContainer.container.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableInstantiable/i0:AddOne".it, "i0.i0")) + } + it("1.10: should work for targets on definition to have correct circuit name"){ + class Top extends Module { + val definition = Definition(new AddOneWithAnnotation) + val i0 = Instance(definition) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|AddOneWithAnnotation>innerWire".rt, "innerWire")) + } + } + describe("2: Annotations on designs not in the same chisel compilation") { + it("2.0: should work on an innerWire, marked in a different compilation") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Instance(Definition(new ViewerParent(x, false, true))) + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain (MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "first")) + } + it("2.1: should work on an innerWire, marked in a different compilation, in instanced instantiable") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Instance(Definition(new ViewerParent(x, true, false))) + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain (MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "second")) + } + it("2.2: should work on an innerWire, marked in a different compilation, in instanced module") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Instance(Definition(new ViewerParent(x, false, false))) + mark(parent.viewer.x.i0.innerWire, "third") + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain (MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "third")) + } + } + describe("3: @public") { + it("3.0: should work on multi-vals") { + class Top() extends Module { + val mv = Instance(Definition(new MultiVal())) + mark(mv.x, "mv.x") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/mv:MultiVal>x".rt, "mv.x")) + } + it("3.1: should work on lazy vals") { + class Top() extends Module { + val lv = Instance(Definition(new LazyVal())) + mark(lv.x, lv.y) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/lv:LazyVal>x".rt, "Hi")) + } + it("3.2: should work on islookupables") { + class Top() extends Module { + val p = Parameters("hi", 0) + val up = Instance(Definition(new UsesParameters(p))) + mark(up.x, up.y.string + up.y.int) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/up:UsesParameters>x".rt, "hi0")) + } + it("3.3: should work on lists") { + class Top() extends Module { + val i = Instance(Definition(new HasList())) + mark(i.x(1), i.y(1).toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasList>x_1".rt, "2")) + } + it("3.4: should work on seqs") { + class Top() extends Module { + val i = Instance(Definition(new HasSeq())) + mark(i.x(1), i.y(1).toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasSeq>x_1".rt, "2")) + } + it("3.5: should work on options") { + class Top() extends Module { + val i = Instance(Definition(new HasOption())) + i.x.map(x => mark(x, "x")) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasOption>x".rt, "x")) + } + it("3.6: should work on vecs") { + class Top() extends Module { + val i = Instance(Definition(new HasVec())) + mark(i.x, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasVec>x".rt, "blah")) + } + it("3.7: should work on statically indexed vectors external to module") { + class Top() extends Module { + val i = Instance(Definition(new HasVec())) + mark(i.x(1), "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasVec>x[1]".rt, "blah")) + } + it("3.8: should work on statically indexed vectors internal to module") { + class Top() extends Module { + val i = Instance(Definition(new HasIndexedVec())) + mark(i.y, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasIndexedVec>x[1]".rt, "blah")) + } + it("3.9: should work on accessed subfields of aggregate ports") { + class Top extends Module { + val input = IO(Input(Valid(UInt(8.W)))) + val i = Instance(Definition(new HasSubFieldAccess)) + i.valid := input.valid + i.bits := input.bits + mark(i.valid, "valid") + mark(i.bits, "bits") + } + val expected = List( + "~Top|Top/i:HasSubFieldAccess>in.valid".rt -> "valid", + "~Top|Top/i:HasSubFieldAccess>in.bits".rt -> "bits" + ) + val lines = List( + "i.in.valid <= input.valid", + "i.in.bits <= input.bits" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- lines) { + text should include (line) + } + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + ignore("3.10: should work on vals in constructor arguments") { + class Top() extends Module { + val i = Instance(Definition(new HasPublicConstructorArgs(10))) + //mark(i.x, i.int.toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasPublicConstructorArgs>x".rt, "10")) + } + } + describe("4: toInstance") { + it("4.0: should work on modules") { + class Top() extends Module { + val i = Module(new AddOne()) + f(i.toInstance) + } + def f(i: Instance[AddOne]): Unit = mark(i.innerWire, "blah") + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne>innerWire".rt, "blah")) + } + it("4.1: should work on isinstantiables") { + class Top() extends Module { + val i = Module(new AddTwo()) + val v = new Viewer(i, false) + mark(f(v.toInstance), "blah") + } + def f(i: Instance[Viewer]): Data = i.x.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("4.2: should work on seqs of modules") { + class Top() extends Module { + val is = Seq(Module(new AddTwo()), Module(new AddTwo())).map(_.toInstance) + mark(f(is), "blah") + } + def f(i: Seq[Instance[AddTwo]]): Data = i.head.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("4.3: should work on seqs of isInstantiables") { + class Top() extends Module { + val i = Module(new AddTwo()) + val vs = Seq(new Viewer(i, false), new Viewer(i, false)).map(_.toInstance) + mark(f(vs), "blah") + } + def f(i: Seq[Instance[Viewer]]): Data = i.head.x.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("4.2: should work on options of modules") { + class Top() extends Module { + val is: Option[Instance[AddTwo]] = Some(Module(new AddTwo())).map(_.toInstance) + mark(f(is), "blah") + } + def f(i: Option[Instance[AddTwo]]): Data = i.get.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + } + describe("5: Absolute Targets should work as expected") { + it("5.0: toAbsoluteTarget on a port of an instance") { + class Top() extends Module { + val i = Instance(Definition(new AddTwo())) + amark(i.in, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:AddTwo>in".rt, "blah")) + } + it("5.1: toAbsoluteTarget on a subinstance's data within an instance") { + class Top() extends Module { + val i = Instance(Definition(new AddTwo())) + amark(i.i0.innerWire, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("5.2: toAbsoluteTarget on a submodule's data within an instance") { + class Top() extends Module { + val i = Instance(Definition(new AddTwoMixedModules())) + amark(i.i1.in, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:AddTwoMixedModules/i1:AddOne_2>in".rt, "blah")) + } + it("5.3: toAbsoluteTarget on a submodule's data, in an aggregate, within an instance") { + class Top() extends Module { + val i = Instance(Definition(new InstantiatesHasVec())) + amark(i.i1.x.head, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:InstantiatesHasVec/i1:HasVec_2>x[0]".rt, "blah")) + } + it("5.4: toAbsoluteTarget on a submodule's data, in an aggregate, within an instance, ILit") { + class MyBundle extends Bundle { val x = UInt(3.W) } + @instantiable + class HasVec() extends Module { + @public val x = Wire(Vec(3, new MyBundle())) + } + @instantiable + class InstantiatesHasVec() extends Module { + @public val i0 = Instance(Definition(new HasVec())) + @public val i1 = Module(new HasVec()) + } + class Top() extends Module { + val i = Instance(Definition(new InstantiatesHasVec())) + amark(i.i1.x.head.x, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:InstantiatesHasVec/i1:HasVec_2>x[0].x".rt, "blah")) + } + it("5.5: toAbsoluteTarget on a subinstance") { + class Top() extends Module { + val i = Instance(Definition(new AddTwo())) + amark(i.i1, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:AddTwo/i1:AddOne".it, "blah")) + } + it("5.6: should work for absolute targets on definition to have correct circuit name"){ + class Top extends Module { + val definition = Definition(new AddOneWithAbsoluteAnnotation) + val i0 = Instance(definition) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithAbsoluteAnnotation>innerWire".rt, "innerWire")) + } + } + describe("6: @instantiable traits should work as expected") { + class MyBundle extends Bundle { + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + } + @instantiable + trait ModuleIntf extends BaseModule { + @public val io = IO(new MyBundle) + } + @instantiable + class ModuleWithCommonIntf(suffix: String = "") extends Module with ModuleIntf { + override def desiredName: String = super.desiredName + suffix + @public val sum = io.in + 1.U + + io.out := sum + } + class BlackBoxWithCommonIntf extends BlackBox with ModuleIntf + + it("6.0: A Module that implements an @instantiable trait should be instantiable as that trait") { + class Top extends Module { + val i: Instance[ModuleIntf] = Instance(Definition(new ModuleWithCommonIntf)) + mark(i.io.in, "gotcha") + mark(i, "inst") + } + val expected = List( + "~Top|Top/i:ModuleWithCommonIntf>io.in".rt -> "gotcha", + "~Top|Top/i:ModuleWithCommonIntf".it -> "inst" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.1 An @instantiable Module that implements an @instantiable trait should be able to use extension methods from both") { + class Top extends Module { + val i: Instance[ModuleWithCommonIntf] = Instance(Definition(new ModuleWithCommonIntf)) + mark(i.io.in, "gotcha") + mark(i.sum, "also this") + mark(i, "inst") + } + val expected = List( + "~Top|Top/i:ModuleWithCommonIntf>io.in".rt -> "gotcha", + "~Top|Top/i:ModuleWithCommonIntf>sum".rt -> "also this", + "~Top|Top/i:ModuleWithCommonIntf".it -> "inst" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.2 A BlackBox that implements an @instantiable trait should be instantiable as that trait") { + class Top extends Module { + val i: Instance[ModuleIntf] = Module(new BlackBoxWithCommonIntf).toInstance + mark(i.io.in, "gotcha") + mark(i, "module") + } + val expected = List( + "~Top|BlackBoxWithCommonIntf>in".rt -> "gotcha", + "~Top|BlackBoxWithCommonIntf".mt -> "module" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.3 It should be possible to have Vectors of @instantiable traits mixing concrete subclasses") { + class Top extends Module { + val proto = Definition(new ModuleWithCommonIntf("X")) + val insts: Seq[Instance[ModuleIntf]] = Vector( + Module(new ModuleWithCommonIntf("Y")).toInstance, + Module(new BlackBoxWithCommonIntf).toInstance, + Instance(proto) + ) + mark(insts(0).io.in, "foo") + mark(insts(1).io.in, "bar") + mark(insts(2).io.in, "fizz") + } + val expected = List( + "~Top|ModuleWithCommonIntfY>io.in".rt -> "foo", + "~Top|BlackBoxWithCommonIntf>in".rt -> "bar", + "~Top|Top/insts_2:ModuleWithCommonIntfX>io.in".rt -> "fizz" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } + // TODO don't forget to test this with heterogeneous Views (eg. viewing a tuple of a port and non-port as a single Bundle) + describe("7: @instantiable and @public should compose with DataView") { + import chisel3.experimental.dataview._ + it("7.0: should work on simple Views") { + @instantiable + class MyModule extends RawModule { + val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + val sum = in + 1.U + out := sum + 1.U + @public val foo = in.viewAs[UInt] + @public val bar = sum.viewAs[UInt] + } + class Top extends RawModule { + val foo = IO(Input(UInt(8.W))) + val bar = IO(Output(UInt(8.W))) + val i = Instance(Definition(new MyModule)) + i.foo := foo + bar := i.out + mark(i.out, "out") + mark(i.foo, "foo") + mark(i.bar, "bar") + } + val expectedAnnos = List( + "~Top|Top/i:MyModule>out".rt -> "out", + "~Top|Top/i:MyModule>in".rt -> "foo", + "~Top|Top/i:MyModule>sum".rt -> "bar" + ) + val expectedLines = List( + "i.in <= foo", + "bar <= i.out" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- expectedLines) { + text should include (line) + } + for (e <- expectedAnnos.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + + ignore("7.1: should work on Aggregate Views") { + import chiselTests.experimental.FlatDecoupledDataView._ + type RegDecoupled = DecoupledIO[FizzBuzz] + @instantiable + class MyModule extends RawModule { + private val a = IO(Flipped(new FlatDecoupled)) + private val b = IO(new FlatDecoupled) + @public val enq = a.viewAs[RegDecoupled] + @public val deq = b.viewAs[RegDecoupled] + @public val enq_valid = enq.valid // Also return a subset of the view + deq <> enq + } + class Top extends RawModule { + val foo = IO(Flipped(new RegDecoupled(new FizzBuzz))) + val bar = IO(new RegDecoupled(new FizzBuzz)) + val i = Instance(Definition(new MyModule)) + i.enq <> foo + i.enq_valid := foo.valid // Make sure connections also work for @public on elements of a larger Aggregate + i.deq.ready := bar.ready + bar.valid := i.deq.valid + bar.bits := i.deq.bits + mark(i.enq, "enq") + mark(i.enq.bits, "enq.bits") + mark(i.deq.bits.fizz, "deq.bits.fizz") + mark(i.enq_valid, "enq_valid") + } + val expectedAnnos = List( + "~Top|Top/i:MyModule>a".rt -> "enq", // Not split, checks 1:1 + "~Top|Top/i:MyModule>a.fizz".rt -> "enq.bits", // Split, checks non-1:1 inner Aggregate + "~Top|Top/i:MyModule>a.buzz".rt -> "enq.bits", + "~Top|Top/i:MyModule>b.fizz".rt -> "deq.bits.fizz", // Checks 1 inner Element + "~Top|Top/i:MyModule>a.valid".rt -> "enq_valid" + ) + val expectedLines = List( + "i.a.valid <= foo.valid", + "foo.ready <= i.a.ready", + "i.a.fizz <= foo.bits.fizz", + "i.a.buzz <= foo.bits.buzz", + "bar.valid <= i.b.valid", + "i.b.ready <= bar.ready", + "bar.bits.fizz <= i.b.fizz", + "bar.bits.buzz <= i.b.buzz", + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- expectedLines) { + text should include (line) + } + for (e <- expectedAnnos.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + + it("7.2: should work on views of views") { + import chiselTests.experimental.SimpleBundleDataView._ + @instantiable + class MyModule extends RawModule { + private val a = IO(Input(UInt(8.W))) + private val b = IO(Output(new BundleA(8))) + @public val in = a.viewAs[UInt].viewAs[UInt] + @public val out = b.viewAs[BundleB].viewAs[BundleA].viewAs[BundleB] + out.bar := in + } + class Top extends RawModule { + val foo = IO(Input(UInt(8.W))) + val bar = IO(Output(new BundleB(8))) + val i = Instance(Definition(new MyModule)) + i.in := foo + bar := i.out + bar.bar := i.out.bar + mark(i.in, "in") + mark(i.out.bar, "out_bar") + } + val expected = List( + "~Top|Top/i:MyModule>a".rt -> "in", + "~Top|Top/i:MyModule>b.foo".rt -> "out_bar", + ) + val lines = List( + "i.a <= foo", + "bar.bar <= i.b.foo" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- lines) { + text should include (line) + } + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + + it("7.3: should work with DataView + implicit conversion") { + import chiselTests.experimental.SeqToVec._ + @instantiable + class MyModule extends RawModule { + private val a = IO(Input(UInt(8.W))) + private val b = IO(Output(UInt(8.W))) + @public val ports = Seq(a, b) + b := a + } + class Top extends RawModule { + val foo = IO(Input(UInt(8.W))) + val bar = IO(Output(UInt(8.W))) + val i = Instance(Definition(new MyModule)) + i.ports <> Seq(foo, bar) + mark(i.ports, "i.ports") + } + val expected = List( + // Not 1:1 so will get split out + "~Top|Top/i:MyModule>a".rt -> "i.ports", + "~Top|Top/i:MyModule>b".rt -> "i.ports", + ) + val lines = List( + "i.a <= foo", + "bar <= i.b" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- lines) { + text should include (line) + } + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } + + describe("8: @instantiable and @public should compose with CloneModuleAsRecord") { + it("8.0: it should support @public on a CMAR Record in Definitions") { + @instantiable + class HasCMAR extends Module { + @public val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + @public val m = Module(new AggregatePortModule) + @public val c = experimental.CloneModuleAsRecord(m) + } + class Top extends Module { + val d = Definition(new HasCMAR) + mark(d.c("io"), "c.io") + val bun = d.c("io").asInstanceOf[Record] + mark(bun.elements("out"), "c.io.out") + } + val expected = List( + "~Top|HasCMAR/c:AggregatePortModule>io".rt -> "c.io", + "~Top|HasCMAR/c:AggregatePortModule>io.out".rt -> "c.io.out" + + ) + val (_, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("8.1: it should support @public on a CMAR Record in Instances") { + @instantiable + class HasCMAR extends Module { + @public val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + @public val m = Module(new AggregatePortModule) + @public val c = experimental.CloneModuleAsRecord(m) + } + class Top extends Module { + val i = Instance(Definition(new HasCMAR)) + mark(i.c("io"), "i.c.io") + val bun = i.c("io").asInstanceOf[Record] + mark(bun.elements("out"), "i.c.io.out") + } + val expected = List( + "~Top|Top/i:HasCMAR/c:AggregatePortModule>io".rt -> "i.c.io", + "~Top|Top/i:HasCMAR/c:AggregatePortModule>io.out".rt -> "i.c.io.out" + + ) + val (_, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } +} + diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Utils.scala b/src/test/scala/chiselTests/experimental/hierarchy/Utils.scala new file mode 100644 index 00000000..a2e51765 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/Utils.scala @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.experimental.hierarchy + +import chisel3._ +import _root_.firrtl.annotations._ +import chisel3.stage.{ChiselCircuitAnnotation, CircuitSerializationAnnotation, DesignAnnotation} +import chiselTests.ChiselRunners +import firrtl.stage.FirrtlCircuitAnnotation +import org.scalatest.matchers.should.Matchers + +trait Utils extends ChiselRunners with chiselTests.Utils with Matchers { + import Annotations._ + // TODO promote to standard API (in FIRRTL) and perhaps even implement with a macro + implicit class Str2RefTarget(str: String) { + def rt: ReferenceTarget = Target.deserialize(str).asInstanceOf[ReferenceTarget] + def it: InstanceTarget = Target.deserialize(str).asInstanceOf[InstanceTarget] + def mt: ModuleTarget = Target.deserialize(str).asInstanceOf[ModuleTarget] + def ct: CircuitTarget = Target.deserialize(str).asInstanceOf[CircuitTarget] + } +} diff --git a/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala b/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala index 52293abb..1e080739 100644 --- a/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala +++ b/src/test/scala/chiselTests/experimental/verification/VerificationSpec.scala @@ -3,11 +3,15 @@ package chiselTests.experimental.verification import chisel3._ -import chisel3.experimental.{verification => formal} +import chisel3.experimental.{ChiselAnnotation, verification => formal} import chisel3.stage.ChiselStage import chiselTests.ChiselPropSpec +import firrtl.annotations.{ReferenceTarget, SingleTargetAnnotation} -class VerificationModule extends Module { +import java.io.File +import org.scalatest.matchers.should.Matchers + +class SimpleTest extends Module { val io = IO(new Bundle{ val in = Input(UInt(8.W)) val out = Output(UInt(8.W)) @@ -20,18 +24,131 @@ class VerificationModule extends Module { } } -class VerificationSpec extends ChiselPropSpec { +/** Dummy verification annotation. + * @param target target of component to be annotated + */ +case class VerifAnnotation(target: ReferenceTarget) extends SingleTargetAnnotation[ReferenceTarget] { + def duplicate(n: ReferenceTarget): VerifAnnotation = this.copy(target = n) +} - def assertContains[T](s: Seq[T], x: T): Unit = { - val containsLine = s.map(_ == x).reduce(_ || _) +object VerifAnnotation { + /** Create annotation for a given verification component. + * @param c component to be annotated + */ + def annotate(c: experimental.BaseSim): Unit = { + chisel3.experimental.annotate(new ChiselAnnotation { + def toFirrtl: VerifAnnotation = VerifAnnotation(c.toTarget) + }) + } +} + +class VerificationSpec extends ChiselPropSpec with Matchers { + + def assertContains(s: Seq[String], x: String): Unit = { + val containsLine = s.map(_.contains(x)).reduce(_ || _) assert(containsLine, s"\n $x\nwas not found in`\n ${s.mkString("\n ")}``") } property("basic equality check should work") { - val fir = ChiselStage.emitFirrtl(new VerificationModule) + val fir = ChiselStage.emitChirrtl(new SimpleTest) val lines = fir.split("\n").map(_.trim) - assertContains(lines, "cover(clock, _T, UInt<1>(\"h1\"), \"\") @[VerificationSpec.scala 16:15]") - assertContains(lines, "assume(clock, _T_2, UInt<1>(\"h1\"), \"\") @[VerificationSpec.scala 18:18]") - assertContains(lines, "assert(clock, _T_3, UInt<1>(\"h1\"), \"\") @[VerificationSpec.scala 19:18]") + + // reset guard around the verification statement + assertContains(lines, "when _T_2 : @[VerificationSpec.scala") + assertContains(lines, "cover(clock, _T, UInt<1>(\"h1\"), \"\")") + + assertContains(lines, "when _T_6 : @[VerificationSpec.scala") + assertContains(lines, "assume(clock, _T_4, UInt<1>(\"h1\"), \"\")") + + assertContains(lines, "when _T_9 : @[VerificationSpec.scala") + assertContains(lines, "assert(clock, _T_7, UInt<1>(\"h1\"), \"\")") + } + + property("annotation of verification constructs should work") { + /** Circuit that contains and annotates verification nodes. */ + class AnnotationTest extends Module { + val io = IO(new Bundle{ + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + }) + io.out := io.in + val cov = formal.cover(io.in === 3.U) + val assm = formal.assume(io.in =/= 2.U) + val asst = formal.assert(io.out === io.in) + VerifAnnotation.annotate(cov) + VerifAnnotation.annotate(assm) + VerifAnnotation.annotate(asst) + } + + // compile circuit + val testDir = new File("test_run_dir", "VerificationAnnotationTests") + (new ChiselStage).emitSystemVerilog( + gen = new AnnotationTest, + args = Array("-td", testDir.getPath) + ) + + // read in annotation file + val annoFile = new File(testDir, "AnnotationTest.anno.json") + annoFile should exist + val annoLines = scala.io.Source.fromFile(annoFile).getLines.toList + + // check for expected verification annotations + exactly(3, annoLines) should include ("chiselTests.experimental.verification.VerifAnnotation") + exactly(1, annoLines) should include ("~AnnotationTest|AnnotationTest>asst") + exactly(1, annoLines) should include ("~AnnotationTest|AnnotationTest>assm") + exactly(1, annoLines) should include ("~AnnotationTest|AnnotationTest>cov") + + // read in FIRRTL file + val firFile = new File(testDir, "AnnotationTest.fir") + firFile should exist + val firLines = scala.io.Source.fromFile(firFile).getLines.toList + + // check that verification components have expected names + exactly(1, firLines) should include ("cover(clock, _T, UInt<1>(\"h1\"), \"\") : cov") + exactly(1, firLines) should include ("assume(clock, _T_3, UInt<1>(\"h1\"), \"\") : assm") + exactly(1, firLines) should include ("assert(clock, _T_6, UInt<1>(\"h1\"), \"\") : asst") + } + + property("annotation of verification constructs with suggested name should work") { + /** Circuit that annotates a renamed verification nodes. */ + class AnnotationRenameTest extends Module { + val io = IO(new Bundle{ + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + }) + io.out := io.in + + val goodbye = formal.assert(io.in === 1.U) + goodbye.suggestName("hello") + VerifAnnotation.annotate(goodbye) + + VerifAnnotation.annotate(formal.assume(io.in =/= 2.U).suggestName("howdy")) + } + + // compile circuit + val testDir = new File("test_run_dir", "VerificationAnnotationRenameTests") + (new ChiselStage).emitSystemVerilog( + gen = new AnnotationRenameTest, + args = Array("-td", testDir.getPath) + ) + + // read in annotation file + val annoFile = new File(testDir, "AnnotationRenameTest.anno.json") + annoFile should exist + val annoLines = scala.io.Source.fromFile(annoFile).getLines.toList + + // check for expected verification annotations + exactly(2, annoLines) should include ("chiselTests.experimental.verification.VerifAnnotation") + exactly(1, annoLines) should include ("~AnnotationRenameTest|AnnotationRenameTest>hello") + exactly(1, annoLines) should include ("~AnnotationRenameTest|AnnotationRenameTest>howdy") + + // read in FIRRTL file + val firFile = new File(testDir, "AnnotationRenameTest.fir") + firFile should exist + val firLines = scala.io.Source.fromFile(firFile).getLines.toList + + // check that verification components have expected names + exactly(1, firLines) should include ("assert(clock, _T, UInt<1>(\"h1\"), \"\") : hello") + exactly(1, firLines) should include ("assume(clock, _T_3, UInt<1>(\"h1\"), \"\") : howdy") } } diff --git a/src/test/scala/chiselTests/stage/ChiselMainSpec.scala b/src/test/scala/chiselTests/stage/ChiselMainSpec.scala index 0fc42fc6..1634e765 100644 --- a/src/test/scala/chiselTests/stage/ChiselMainSpec.scala +++ b/src/test/scala/chiselTests/stage/ChiselMainSpec.scala @@ -10,6 +10,8 @@ import chisel3.aop.inspecting.{InspectingAspect, InspectorAspect} import org.scalatest.GivenWhenThen import org.scalatest.featurespec.AnyFeatureSpec import org.scalatest.matchers.should.Matchers +import org.scalatest.Inside._ +import org.scalatest.EitherValues._ import scala.io.Source import firrtl.Parser @@ -32,7 +34,7 @@ object ChiselMainSpec { /** A module that fails a requirement */ class FailingRequirementModule extends RawModule { - require(false) + require(false, "the user wrote a failing requirement") } /** A module that triggers a Builder.error (as opposed to exception) */ @@ -69,14 +71,35 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit args: Array[String], generator: Option[Class[_ <: RawModule]] = None, files: Seq[String] = Seq.empty, - stdout: Option[String] = None, - stderr: Option[String] = None, + stdout: Seq[Either[String, String]] = Seq.empty, + stderr: Seq[Either[String, String]] = Seq.empty, result: Int = 0, fileChecks: Map[String, File => Unit] = Map.empty) { def testName: String = "args" + args.mkString("_") def argsString: String = args.mkString(" ") } + /** A test of ChiselMain that is going to involve catching an exception. + * @param args command line arguments (excluding --module) to pass in + * @param generator the module to build (used to generate --module) + * @param message snippets of text that should appear (Right) or not appear (Left) in the exception message + * @param stdout snippets of text that should appear (Right) or not appear (Left) in STDOUT + * @param stderr snippets of text that should appear (Right) or not appear (Left) in STDERR + * @param stackTrace snippets of text that should appear (Right) or not appear (Left) in the stack trace + * @tparam the type of exception that should occur + */ + case class ChiselMainExceptionTest[A <: Throwable]( + args: Array[String], + generator: Option[Class[_ <: RawModule]] = None, + message: Seq[Either[String, String]] = Seq.empty, + stdout: Seq[Either[String, String]] = Seq.empty, + stderr: Seq[Either[String, String]] = Seq.empty, + stackTrace: Seq[Either[String, String]] = Seq.empty + ) { + def testName: String = "args" + args.mkString("_") + def argsString: String = args.mkString(" ") + } + def runStageExpectFiles(p: ChiselMainTest): Unit = { Scenario(s"""User runs Chisel Stage with '${p.argsString}'""") { val f = new ChiselMainFixture @@ -85,32 +108,33 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit p.files.foreach( f => new File(td.buildDir + s"/$f").delete() ) When(s"""the user tries to compile with '${p.argsString}'""") + val module: Array[String] = + (if (p.generator.nonEmpty) { Array("--module", p.generator.get.getName) } + else { Array.empty[String] }) + f.stage.main(Array("-td", td.buildDir.toString) ++ module ++ p.args) val (stdout, stderr, result) = grabStdOutErr { catchStatus { - val module: Array[String] = Array("foo") ++ - (if (p.generator.nonEmpty) { Array("--module", p.generator.get.getName) } - else { Array.empty[String] }) f.stage.main(Array("-td", td.buildDir.toString) ++ module ++ p.args) } } - p.stdout match { - case Some(a) => + p.stdout.foreach { + case Right(a) => Then(s"""STDOUT should include "$a"""") stdout should include (a) - case None => - Then(s"nothing should print to STDOUT") - stdout should be (empty) + case Left(a) => + Then(s"""STDOUT should not include "$a"""") + stdout should not include (a) } - p.stderr match { - case Some(a) => - And(s"""STDERR should include "$a"""") + p.stderr.foreach { + case Right(a) => + Then(s"""STDERR should include "$a"""") stderr should include (a) - case None => - And(s"nothing should print to STDERR") - stderr should be (empty) + case Left(a) => + Then(s"""STDERR should not include "$a"""") + stderr should not include (a) } p.result match { @@ -131,56 +155,128 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit } } + /** Run a ChiselMainExceptionTest and verify that all the properties it spells out hold. + * @param p the test to run + * @tparam the type of the exception to catch (you shouldn't have to explicitly provide this) + */ + def runStageExpectException[A <: Throwable: scala.reflect.ClassTag](p: ChiselMainExceptionTest[A]): Unit = { + Scenario(s"""User runs Chisel Stage with '${p.argsString}'""") { + val f = new ChiselMainFixture + val td = new TargetDirectoryFixture(p.testName) + + When(s"""the user tries to compile with '${p.argsString}'""") + val module: Array[String] = + (if (p.generator.nonEmpty) { Array("--module", p.generator.get.getName) } + else { Array.empty[String] }) + val (stdout, stderr, result) = + grabStdOutErr { + catchStatus { + intercept[A] { + f.stage.main(Array("-td", td.buildDir.toString) ++ module ++ p.args) + } + } + } + + Then("the expected exception was thrown") + result should be a ('right) + val exception = result.right.get + info(s""" - Exception was a "${exception.getClass.getName}"""") + + val message = exception.getMessage + p.message.foreach { + case Right(a) => + Then(s"""STDOUT should include "$a"""") + message should include (a) + case Left(a) => + Then(s"""STDOUT should not include "$a"""") + message should not include (a) + } + + p.stdout.foreach { + case Right(a) => + Then(s"""STDOUT should include "$a"""") + stdout should include (a) + case Left(a) => + Then(s"""STDOUT should not include "$a"""") + stdout should not include (a) + } + + p.stderr.foreach { + case Right(a) => + Then(s"""STDERR should include "$a"""") + stderr should include (a) + case Left(a) => + Then(s"""STDERR should not include "$a"""") + stderr should not include (a) + } + + val stackTraceString = exception.getStackTrace.mkString("\n") + p.stackTrace.foreach { + case Left(a) => + And(s"""the stack does not include "$a"""") + stackTraceString should not include (a) + case Right(a) => + And(s"""the stack trace includes "$a"""") + stackTraceString should include (a) + } + + } + } + info("As a Chisel user") info("I compile a design") Feature("show elaborating message") { runStageExpectFiles( ChiselMainTest(args = Array("-X", "high"), - generator = Some(classOf[SameTypesModule]), - stdout = Some("Done elaborating.") + generator = Some(classOf[SameTypesModule]) ) ) } info("I screw up and compile some bad code") - Feature("Stack trace trimming") { + Feature("Stack trace trimming of ChiselException") { Seq( - ChiselMainTest(args = Array("-X", "low"), - generator = Some(classOf[DifferentTypesModule]), - stdout = Some("Stack trace trimmed to user code only"), - result = 1), - ChiselMainTest(args = Array("-X", "high", "--full-stacktrace"), - generator = Some(classOf[DifferentTypesModule]), - stdout = Some("org.scalatest"), - result = 1) - ).foreach(runStageExpectFiles) + ChiselMainExceptionTest[chisel3.internal.ChiselException]( + args = Array("-X", "low"), + generator = Some(classOf[DifferentTypesModule]), + stackTrace = Seq(Left("java"), Right(classOf[DifferentTypesModule].getName)) + ), + ChiselMainExceptionTest[chisel3.internal.ChiselException]( + args = Array("-X", "low", "--full-stacktrace"), + generator = Some(classOf[DifferentTypesModule]), + stackTrace = Seq(Right("java"), Right(classOf[DifferentTypesModule].getName)) + ) + ).foreach(runStageExpectException) } - Feature("Report properly trimmed stack traces") { + Feature("Stack trace trimming of user exceptions") { Seq( - ChiselMainTest(args = Array("-X", "low"), - generator = Some(classOf[FailingRequirementModule]), - stdout = Some("requirement failed"), - result = 1), - ChiselMainTest(args = Array("-X", "low", "--full-stacktrace"), - generator = Some(classOf[FailingRequirementModule]), - stdout = Some("chisel3.internal.ChiselException"), - result = 1) - ).foreach(runStageExpectFiles) + ChiselMainExceptionTest[java.lang.IllegalArgumentException]( + args = Array("-X", "low"), + generator = Some(classOf[FailingRequirementModule]), + stackTrace = Seq(Right(classOf[FailingRequirementModule].getName), Left("java")) + ), + ChiselMainExceptionTest[java.lang.IllegalArgumentException]( + args = Array("-X", "low", "--full-stacktrace"), + generator = Some(classOf[FailingRequirementModule]), + stackTrace = Seq(Right(classOf[FailingRequirementModule].getName), Right("java")) + ) + ).foreach(runStageExpectException) } - Feature("Builder.error source locator") { + Feature("Stack trace trimming and Builder.error errors") { Seq( - ChiselMainTest(args = Array("-X", "none"), + ChiselMainExceptionTest[chisel3.internal.ChiselException]( + args = Array("-X", "low"), generator = Some(classOf[BuilderErrorModule]), - stdout = Some("ChiselMainSpec.scala:41: Invalid bit range (3,-1) in class chiselTests.stage.ChiselMainSpec$BuilderErrorModule"), - result = 1) - ).foreach(runStageExpectFiles) + message = Seq(Right("Fatal errors during hardware elaboration")), + stdout = Seq(Right("ChiselMainSpec.scala:43: Invalid bit range (3,-1) in class chiselTests.stage.ChiselMainSpec$BuilderErrorModule")) + ) + ).foreach(runStageExpectException) } Feature("Specifying a custom output file") { runStageExpectFiles(ChiselMainTest( args = Array("--chisel-output-file", "Foo", "--no-run-firrtl"), generator = Some(classOf[SameTypesModule]), - stdout = Some(""), files = Seq("Foo.fir"), fileChecks = Map( "Foo.fir" -> { file => @@ -192,7 +288,6 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit runStageExpectFiles(ChiselMainTest( args = Array("--chisel-output-file", "Foo.pb", "--no-run-firrtl"), generator = Some(classOf[SameTypesModule]), - stdout = Some(""), files = Seq("Foo.pb"), fileChecks = Map( "Foo.pb" -> { file => @@ -209,10 +304,10 @@ class ChiselMainSpec extends AnyFeatureSpec with GivenWhenThen with Matchers wit Seq( ChiselMainTest(args = Array( "-X", "high", "--with-aspect", "chiselTests.stage.TestClassAspect" ), generator = Some(classOf[SameTypesModule]), - stdout = Some("Ran inspectingAspect")), + stdout = Seq(Right("Ran inspectingAspect"))), ChiselMainTest(args = Array( "-X", "high", "--with-aspect", "chiselTests.stage.TestObjectAspect" ), generator = Some(classOf[SameTypesModule]), - stdout = Some("Ran inspectingAspect")) + stdout = Seq(Right("Ran inspectingAspect"))) ).foreach(runStageExpectFiles) } diff --git a/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala b/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala index 35e354a6..99c0f7c0 100644 --- a/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala +++ b/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala @@ -4,6 +4,7 @@ package chiselTests.stage import firrtl.options.Viewer.view +import firrtl.RenameMap import chisel3.stage._ import chisel3.internal.firrtl.Circuit @@ -15,7 +16,7 @@ class ChiselOptionsViewSpec extends AnyFlatSpec with Matchers { behavior of ChiselOptionsView.getClass.getName it should "construct a view from an AnnotationSeq" in { - val bar = Circuit("bar", Seq.empty, Seq.empty) + val bar = Circuit("bar", Seq.empty, Seq.empty, RenameMap()) val annotations = Seq( NoRunFirrtlCompilerAnnotation, PrintFullStackTraceAnnotation, diff --git a/src/test/scala/chiselTests/stage/ChiselStageSpec.scala b/src/test/scala/chiselTests/stage/ChiselStageSpec.scala index 98bbb2ea..7b6a2d39 100644 --- a/src/test/scala/chiselTests/stage/ChiselStageSpec.scala +++ b/src/test/scala/chiselTests/stage/ChiselStageSpec.scala @@ -30,6 +30,10 @@ object ChiselStageSpec { out := memory(bar.out) } + class UserExceptionModule extends RawModule { + assert(false, "User threw an exception") + } + } class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { @@ -40,13 +44,13 @@ class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { val stage = new ChiselStage } - behavior of "ChiselStage.emitChirrtl" + behavior of "ChiselStage$.emitChirrtl" it should "return a CHIRRTL string" in { ChiselStage.emitChirrtl(new Foo) should include ("infer mport") } - behavior of "ChiselStage.emitFirrtl" + behavior of "ChiselStage$.emitFirrtl" it should "return a High FIRRTL string" in { ChiselStage.emitFirrtl(new Foo) should include ("mem memory") @@ -58,7 +62,7 @@ class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { .emitFirrtl(new Foo, args) should include ("module Bar") } - behavior of "ChiselStage.emitVerilog" + behavior of "ChiselStage$.emitVerilog" it should "return a Verilog string" in { ChiselStage.emitVerilog(new Foo) should include ("endmodule") @@ -84,6 +88,13 @@ class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { catchWrites { ChiselStage.convert(new Foo) } shouldBe a[Right[_, _]] } + ignore should "generate a FIRRTL circuit from a CHIRRTL circuit" in { + info("no files were written") + catchWrites { + ChiselStage.convert(ChiselStage.elaborate(new Foo)) + } shouldBe a[Right[_, _]] + } + behavior of "ChiselStage$.emitChirrtl" ignore should "generate a CHIRRTL string from a Chisel module" in { @@ -142,4 +153,58 @@ class ChiselStageSpec extends AnyFlatSpec with Matchers with Utils { exactly (1, order) should be (Dependency[chisel3.stage.phases.Elaborate]) } + behavior of "ChiselStage$ exception handling" + + it should "truncate a user exception" in { + info("The user's java.lang.AssertionError was thrown") + val exception = intercept[java.lang.AssertionError] { + ChiselStage.emitChirrtl(new UserExceptionModule) + } + + val message = exception.getMessage + info("The exception includes the user's message") + message should include ("User threw an exception") + + info("The stack trace is trimmed") + exception.getStackTrace.mkString("\n") should not include ("java") + } + + behavior of "ChiselStage exception handling" + + it should "truncate a user exception" in { + info("The user's java.lang.AssertionError was thrown") + val exception = intercept[java.lang.AssertionError] { + (new ChiselStage).emitChirrtl(new UserExceptionModule) + } + + info(s""" - Exception was a ${exception.getClass.getName}""") + + val message = exception.getMessage + info("The exception includes the user's message") + message should include ("User threw an exception") + + val stackTrace = exception.getStackTrace.mkString("\n") + info("The stack trace is trimmed") + stackTrace should not include ("java") + + info("The stack trace include information about running --full-stacktrace") + stackTrace should include ("--full-stacktrace") + } + + it should """not truncate a user exception with "--full-stacktrace"""" in { + info("The user's java.lang.AssertionError was thrown") + val exception = intercept[java.lang.AssertionError] { + (new ChiselStage).emitChirrtl(new UserExceptionModule, Array("--full-stacktrace")) + } + + info(s""" - Exception was a ${exception.getClass.getName}""") + + val message = exception.getMessage + info("The exception includes the user's message") + message should include ("User threw an exception") + + info("The stack trace is not trimmed") + exception.getStackTrace.mkString("\n") should include ("java") + } + } diff --git a/src/test/scala/chiselTests/util/BitPatSpec.scala b/src/test/scala/chiselTests/util/BitPatSpec.scala new file mode 100644 index 00000000..0c83493f --- /dev/null +++ b/src/test/scala/chiselTests/util/BitPatSpec.scala @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.util + +import chisel3.util.BitPat +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + + +class BitPatSpec extends AnyFlatSpec with Matchers { + behavior of classOf[BitPat].toString + + it should "convert a BitPat to readable form" in { + val testPattern = "0" * 32 + "1" * 32 + "?" * 32 + "?01" * 32 + BitPat("b" + testPattern).toString should be (s"BitPat($testPattern)") + } + + it should "convert a BitPat to raw form" in { + val testPattern = "0" * 32 + "1" * 32 + "?" * 32 + "?01" * 32 + BitPat("b" + testPattern).rawString should be(testPattern) + } + + it should "not fail if BitPat width is 0" in { + intercept[IllegalArgumentException]{BitPat("b")} + } + + it should "contact BitPat via ##" in { + (BitPat.Y(4) ## BitPat.dontCare(3) ## BitPat.N(2)).toString should be (s"BitPat(1111???00)") + } + + it should "index and return new BitPat" in { + val b = BitPat("b1001???") + b(0) should be(BitPat.dontCare(1)) + b(6) should be(BitPat.Y()) + b(5) should be(BitPat.N()) + } + + it should "slice and return new BitPat" in { + val b = BitPat("b1001???") + b(2, 0) should be(BitPat("b???")) + b(4, 3) should be(BitPat("b01")) + b(6, 6) should be(BitPat("b1")) + } +} diff --git a/src/test/scala/chiselTests/util/CatSpec.scala b/src/test/scala/chiselTests/util/CatSpec.scala index 5565ca51..79d2c027 100644 --- a/src/test/scala/chiselTests/util/CatSpec.scala +++ b/src/test/scala/chiselTests/util/CatSpec.scala @@ -5,6 +5,7 @@ package chiselTests.util import chisel3._ import chisel3.stage.ChiselStage import chisel3.util.Cat +import chisel3.experimental.noPrefix import chiselTests.ChiselFlatSpec @@ -31,4 +32,33 @@ class CatSpec extends ChiselFlatSpec { } + it should "not override the names of its arguments" in { + class MyModule extends RawModule { + val a, b, c, d = IO(Input(UInt(8.W))) + val out = IO(Output(UInt())) + + out := Cat(a, b, c, d) + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + for (name <- Seq("a", "b", "c", "d")) { + chirrtl should include (s"input $name : UInt<8>") + } + } + + it should "have prefixed naming" in { + class MyModule extends RawModule { + val in = IO(Input(Vec(8, UInt(8.W)))) + val out = IO(Output(UInt())) + + // noPrefix to avoid `out` as prefix + out := noPrefix(Cat(in)) + } + val chirrtl = ChiselStage.emitChirrtl(new MyModule) + chirrtl should include ("node lo_lo = cat(in[6], in[7])") + chirrtl should include ("node lo_hi = cat(in[4], in[5])") + chirrtl should include ("node hi_lo = cat(in[2], in[3])") + chirrtl should include ("node hi_hi = cat(in[0], in[1])") + } + + } diff --git a/src/test/scala/chiselTests/util/experimental/PlaSpec.scala b/src/test/scala/chiselTests/util/experimental/PlaSpec.scala new file mode 100644 index 00000000..8af5c936 --- /dev/null +++ b/src/test/scala/chiselTests/util/experimental/PlaSpec.scala @@ -0,0 +1,95 @@ +package chiselTests.util.experimental + +import chisel3._ +import chisel3.stage.PrintFullStackTraceAnnotation +import chisel3.testers.BasicTester +import chisel3.util.{BitPat, pla} +import chiselTests.ChiselFlatSpec + +class PlaSpec extends ChiselFlatSpec { + "A 1-of-8 decoder (eg. 74xx138 without enables)" should "be generated correctly" in { + assertTesterPasses(new BasicTester { + val table = Seq( + (BitPat("b000"), BitPat("b00000001")), + (BitPat("b001"), BitPat("b00000010")), + (BitPat("b010"), BitPat("b00000100")), + (BitPat("b011"), BitPat("b00001000")), + (BitPat("b100"), BitPat("b00010000")), + (BitPat("b101"), BitPat("b00100000")), + (BitPat("b110"), BitPat("b01000000")), + (BitPat("b111"), BitPat("b10000000")), + ) + table.foreach { case (i, o) => + val (plaIn, plaOut) = pla(table) + plaIn := WireDefault(i.value.U(3.W)) + chisel3.assert(plaOut === o.value.U(8.W), "Input " + i.toString + " produced incorrect output BitPat(%b)", plaOut) + } + stop() + }) + } + + "An active-low 1-of-8 decoder (eg. inverted 74xx138 without enables)" should "be generated correctly" in { + assertTesterPasses(new BasicTester { + val table = Seq( + (BitPat("b000"), BitPat("b00000001")), + (BitPat("b001"), BitPat("b00000010")), + (BitPat("b010"), BitPat("b00000100")), + (BitPat("b011"), BitPat("b00001000")), + (BitPat("b100"), BitPat("b00010000")), + (BitPat("b101"), BitPat("b00100000")), + (BitPat("b110"), BitPat("b01000000")), + (BitPat("b111"), BitPat("b10000000")), + ) + table.foreach { case (i, o) => + val (plaIn, plaOut) = pla(table, BitPat("b11111111")) + plaIn := WireDefault(i.value.U(3.W)) + chisel3.assert(plaOut === ~o.value.U(8.W), "Input " + i.toString + " produced incorrect output BitPat(%b)", plaOut) + } + stop() + }) + } + + "#2112" should "be generated correctly" in { + assertTesterPasses(new BasicTester { + val table = Seq( + (BitPat("b000"), BitPat("b?01")), + (BitPat("b111"), BitPat("b?01")), + ) + table.foreach { case (i, o) => + val (plaIn, plaOut) = pla(table) + plaIn := WireDefault(i.value.U(3.W)) + chisel3.assert(o === plaOut, "Input " + i.toString + " produced incorrect output BitPat(%b)", plaOut) + } + stop() + }) + } + + "A simple PLA" should "be generated correctly" in { + assertTesterPasses(new BasicTester { + val table = Seq( + (BitPat("b0000"), BitPat("b1")), + (BitPat("b0001"), BitPat("b1")), + (BitPat("b0010"), BitPat("b0")), + (BitPat("b0011"), BitPat("b1")), + (BitPat("b0100"), BitPat("b1")), + (BitPat("b0101"), BitPat("b0")), + (BitPat("b0110"), BitPat("b0")), + (BitPat("b0111"), BitPat("b0")), + (BitPat("b1000"), BitPat("b0")), + (BitPat("b1001"), BitPat("b0")), + (BitPat("b1010"), BitPat("b1")), + (BitPat("b1011"), BitPat("b0")), + (BitPat("b1100"), BitPat("b0")), + (BitPat("b1101"), BitPat("b1")), + (BitPat("b1110"), BitPat("b1")), + (BitPat("b1111"), BitPat("b1")), + ) + table.foreach { case (i, o) => + val (plaIn, plaOut) = pla(table) + plaIn := WireDefault(i.value.U(4.W)) + chisel3.assert(plaOut === o.value.U(1.W), "Input " + i.toString + " produced incorrect output BitPat(%b)", plaOut) + } + stop() + }) + } +} diff --git a/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala b/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala new file mode 100644 index 00000000..743a3cd8 --- /dev/null +++ b/src/test/scala/chiselTests/util/experimental/TruthTableSpec.scala @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.util.experimental + +import chisel3.util.BitPat +import chisel3.util.experimental.decode.TruthTable +import org.scalatest.flatspec.AnyFlatSpec + +class TruthTableSpec extends AnyFlatSpec { + val table = TruthTable( + Map( + // BitPat("b000") -> BitPat("b0"), + BitPat("b001") -> BitPat("b?"), + BitPat("b010") -> BitPat("b?"), + // BitPat("b011") -> BitPat("b0"), + BitPat("b100") -> BitPat("b1"), + BitPat("b101") -> BitPat("b1"), + // BitPat("b110") -> BitPat("b0"), + BitPat("b111") -> BitPat("b1") + ), + BitPat("b0") + ) + val str = """001->? + |010->? + |100->1 + |101->1 + |111->1 + |0""".stripMargin + "TruthTable" should "serialize" in { + assert(table.toString contains "001->?") + assert(table.toString contains "010->?") + assert(table.toString contains "100->1") + assert(table.toString contains "111->1") + assert(table.toString contains " 0") + } + "TruthTable" should "deserialize" in { + assert(TruthTable(str) == table) + } + "TruthTable" should "merge same key" in { + assert( + TruthTable( + """001100->??1 + |001100->1?? + |??? + |""".stripMargin + ) == TruthTable( + """001100->1?1 + |??? + |""".stripMargin + ) + ) + } + "TruthTable" should "crash when merging 0 and 1" in { + intercept[IllegalArgumentException] { + TruthTable( + """0->0 + |0->1 + |??? + |""".stripMargin + ) + } + } +} diff --git a/src/test/scala/examples/VendingMachineUtils.scala b/src/test/scala/examples/VendingMachineUtils.scala index 131256f8..6847768a 100644 --- a/src/test/scala/examples/VendingMachineUtils.scala +++ b/src/test/scala/examples/VendingMachineUtils.scala @@ -34,6 +34,6 @@ object VendingMachineUtils { value += incValue } } - outputs + outputs.toSeq } } |
