diff options
| author | mergify[bot] | 2022-02-01 19:56:13 +0000 |
|---|---|---|
| committer | GitHub | 2022-02-01 19:56:13 +0000 |
| commit | ea1ced34b5c9e42412cc0ac3e7431cd3194ccbc3 (patch) | |
| tree | 2c51bccf3c954b85746e127bf9f7dccca9863a16 /src/test/scala | |
| parent | 91ad2312cab72442fcfec9c28576b5464669f8fd (diff) | |
Chisel plugin bundle elements handler (#2306) (#2380)
Adds generation of `Bundle.elements` method to the chores done by the compiler plugin
For each `Bundle` find the relevant visible Chisel field members and construct a
hard-coded list of the elements and their names implemented as `_elementsImpl`
For more details: See plugins/README.md
- Should be no change in API
- Handles inheritance and mixins
- Handles Seq[Data]
- Tests in BundleElementSpec
Co-authored-by: chick <chick.markley@sifive.com>
Co-authored-by: Jack Koenig <koenig@sifive.com>
(cherry picked from commit 237200a420581519f29149cbae9b3e968c0d01fc)
Co-authored-by: Chick Markley <chick@qrhino.com>
Diffstat (limited to 'src/test/scala')
| -rw-r--r-- | src/test/scala/chiselTests/BundleElementsSpec.scala | 564 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/BundleSpec.scala | 10 |
2 files changed, 571 insertions, 3 deletions
diff --git a/src/test/scala/chiselTests/BundleElementsSpec.scala b/src/test/scala/chiselTests/BundleElementsSpec.scala new file mode 100644 index 00000000..fab2e733 --- /dev/null +++ b/src/test/scala/chiselTests/BundleElementsSpec.scala @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests + +import chisel3._ +import chisel3.experimental.{ChiselEnum, FixedPoint} +import chisel3.stage.ChiselStage +import chisel3.util.Decoupled +import org.scalatest.exceptions.TestFailedException +import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.matchers.should.Matchers + +import scala.language.reflectiveCalls + +/* Rich and complicated bundle examples + */ +trait BpipSuperTraitWithField { + val bpipSuperTraitGood = SInt(17.W) + def bpipSuperTraitBad = SInt(22.W) +} + +trait BpipTraitWithField extends BpipSuperTraitWithField { + val bpipTraitGood = SInt(17.W) + def bpipTraitBad = SInt(22.W) +} + +class BpipOneField extends Bundle with BpipTraitWithField { + val bpipOneFieldOne = SInt(8.W) + val bpipOneFieldTwo = SInt(8.W) +} + +class BpipTwoField extends BpipOneField { + val bpipTwoFieldOne = SInt(8.W) + val bpipTwoFieldTwo = Vec(4, UInt(12.W)) + val myInt = 7 + val baz = Decoupled(UInt(16.W)) +} + +class BpipDecoupled extends BpipOneField { + val bpipDecoupledSInt = SInt(8.W) + val bpipDecoupledVec = Vec(4, UInt(12.W)) + val bpipDecoupledDecoupled = Decoupled(UInt(16.W)) +} + +class HasDecoupledBundleByInheritance extends Module { + val out1 = IO(Output(new BpipDecoupled)) + assertElementsMatchExpected(out1)( + "bpipDecoupledDecoupled" -> _.bpipDecoupledDecoupled, + "bpipDecoupledVec" -> _.bpipDecoupledVec, + "bpipDecoupledSInt" -> _.bpipDecoupledSInt, + "bpipOneFieldTwo" -> _.bpipOneFieldTwo, + "bpipOneFieldOne" -> _.bpipOneFieldOne, + "bpipTraitGood" -> _.bpipTraitGood, + "bpipSuperTraitGood" -> _.bpipSuperTraitGood + ) +} + +/* plugin should not affect the seq detection */ +class DebugProblem3 extends Module { + val out1 = IO(Output(new BpipTwoField)) + assertElementsMatchExpected(out1)( + "baz" -> _.baz, + "bpipTwoFieldTwo" -> _.bpipTwoFieldTwo, + "bpipTwoFieldOne" -> _.bpipTwoFieldOne, + "bpipOneFieldTwo" -> _.bpipOneFieldTwo, + "bpipOneFieldOne" -> _.bpipOneFieldOne, + "bpipTraitGood" -> _.bpipTraitGood, + "bpipSuperTraitGood" -> _.bpipSuperTraitGood + ) +} + +class BpipBadSeqBundle extends Bundle { + val bpipBadSeqBundleGood = UInt(999.W) + val bpipBadSeqBundleBad = Seq(UInt(16.W), UInt(8.W), UInt(4.W)) +} + +class HasBadSeqBundle extends Module { + val out1 = IO(Output(new BpipBadSeqBundle)) +} + +class BpipBadSeqBundleWithIgnore extends Bundle with IgnoreSeqInBundle { + val goodFieldWithIgnore = UInt(999.W) + val badSeqFieldWithIgnore = Seq(UInt(16.W), UInt(8.W), UInt(4.W)) +} + +class UsesIgnoreSeqInBundle extends Module { + val out1 = IO(Output(new BpipBadSeqBundleWithIgnore)) +} + +/* This is mostly a test of the field order */ +class BpipP8_1 extends Bundle { + val field_1_1 = UInt(11.W) + val field_1_2 = UInt(12.W) +} + +class BpipP8_2 extends BpipP8_1 { + val field_2_1 = UInt(11.W) + val field_2_2 = UInt(12.W) +} + +class BpipP8_3 extends BpipP8_2 { + val field_3_1 = UInt(11.W) + val field_3_2 = UInt(12.W) +} + +/* plugin should not affect the seq detection */ +class ForFieldOrderingTest extends Module { + val out1 = IO(Output(new BpipP8_3)) + out1 := DontCare + assertElementsMatchExpected(out1)( + "field_3_2" -> _.field_3_2, + "field_3_1" -> _.field_3_1, + "field_2_2" -> _.field_2_2, + "field_2_1" -> _.field_2_1, + "field_1_2" -> _.field_1_2, + "field_1_1" -> _.field_1_1 + ) +} + +/* plugin should allow parameter var fields */ +class HasValParamsToBundle extends Module { + // The following block does not work, suggesting that ParamIsField is not a case we need to solve + class BpipParamIsField0(val paramField0: UInt) extends Bundle + class BpipParamIsField1(val paramField1: UInt) extends BpipParamIsField0(UInt(66.W)) + + val out3 = IO(Output(new BpipParamIsField1(UInt(10.W)))) + val out4 = IO(Output(new BpipParamIsField1(UInt(10.W)))) + out3 := DontCare + assertElementsMatchExpected(out3)("paramField0" -> _.paramField0, "paramField1" -> _.paramField1) + assertElementsMatchExpected(out4)("paramField0" -> _.paramField0, "paramField1" -> _.paramField1) +} + +class HasGenParamsPassedToSuperclasses extends Module { + + class OtherBundle extends Bundle { + val otherField = UInt(55.W) + } + + class BpipWithGen[T <: Data, TT <: Data](gen: T, gen2: => TT) extends Bundle { + val superFoo = gen + val superQux = gen2 + } + +// class BpipDemoBundle[T <: Data](gen: T, gen2: => T) extends BpipTwoField with BpipVarmint { + class BpipUsesWithGen[T <: Data](gen: T, gen2: => T) extends BpipWithGen(gen, gen2) { + // val foo = gen + val bar = Bool() + val qux = gen2 + val bad = 444 + val baz = Decoupled(UInt(16.W)) + } + + val out1 = IO(Output(new BpipUsesWithGen(UInt(4.W), new OtherBundle))) + val out2 = IO(Output(new BpipUsesWithGen(UInt(4.W), FixedPoint(10.W, 4.BP)))) + + out1 := DontCare + + assertElementsMatchExpected(out1)( + "baz" -> _.baz, + "qux" -> _.qux, + "bar" -> _.bar, + "superQux" -> _.superQux, + "superFoo" -> _.superFoo + ) + assertElementsMatchExpected(out2)( + "baz" -> _.baz, + "qux" -> _.qux, + "bar" -> _.bar, + "superQux" -> _.superQux, + "superFoo" -> _.superFoo + ) +} + +/* Testing whether gen fields superFoo and superQux can be found when they are + * declared in a superclass + * + */ +class UsesGenFiedldsInSuperClass extends Module { + class BpipWithGen[T <: Data](gen: T) extends Bundle { + val superFoo = gen + val superQux = gen + } + + class BpipUsesWithGen[T <: Data](gen: T) extends BpipWithGen(gen) {} + + val out = IO(Output(new BpipUsesWithGen(UInt(4.W)))) + + out := DontCare + + assertElementsMatchExpected(out)() +} + +/* Testing whether gen fields superFoo and superQux can be found when they are + * declared in a superclass + * + */ +class BpipBadBundleWithHardware extends Bundle { + val bpipWithHardwareGood = UInt(8.W) + val bpipWithHardwareBad = 244.U(16.W) +} + +class HasHardwareFieldsInBundle extends Module { + val out = IO(Output(new BpipBadBundleWithHardware)) + assertElementsMatchExpected(out)() +} + +/* This is legal because of => + */ +class UsesBundleWithGeneratorField extends Module { + class BpipWithGen[T <: Data](gen: => T) extends Bundle { + val superFoo = gen + val superQux = gen + } + + class BpipUsesWithGen[T <: Data](gen: => T) extends BpipWithGen(gen) + + val out = IO(Output(new BpipUsesWithGen(UInt(4.W)))) + + out := DontCare + + assertElementsMatchExpected(out)("superQux" -> _.superQux, "superFoo" -> _.superFoo) +} + +/* Testing whether gen fields superFoo and superQux can be found when they are + * declared in a superclass + * + */ +class BundleElementsSpec extends AnyFreeSpec with Matchers { + + /** Tests a whole bunch of different Bundle constructions + */ + class BpipIsComplexBundle extends Module { + + trait BpipVarmint { + val varmint = Bool() + + def vermin = Bool() + + private val puppy = Bool() + } + + abstract class BpipAbstractBundle extends Bundle { + def doNothing: Unit + + val fromAbstractBundle = UInt(22.W) + } + + class BpipOneField extends Bundle { + val fieldOne = SInt(8.W) + } + + class BpipTwoField extends BpipOneField { + val fieldTwo = SInt(8.W) + val fieldThree = Vec(4, UInt(12.W)) + } + + class BpipAnimalBundle(w1: Int, w2: Int) extends Bundle { + val dog = SInt(w1.W) + val fox = UInt(w2.W) + } + + class BpipDemoBundle[T <: Data](gen: T, gen2: => T) extends BpipTwoField with BpipVarmint { + val foo = gen + val bar = Bool() + val qux = gen2 + val bad = 44 + val baz = Decoupled(UInt(16.W)) + val animals = new BpipAnimalBundle(4, 8) + } + + val out = IO(Output(new BpipDemoBundle(UInt(4.W), FixedPoint(10.W, 4.BP)))) + + val out2 = IO(Output(new BpipAbstractBundle { + override def doNothing: Unit = () + + val notAbstract: Bool = Bool() + })) + + val out4 = IO(Output(new BpipAnimalBundle(99, 100))) + val out5 = IO(Output(new BpipTwoField)) + + out := DontCare + out5 := DontCare + + assertElementsMatchExpected(out)( + "animals" -> _.animals, + "baz" -> _.baz, + "qux" -> _.qux, + "bar" -> _.bar, + "varmint" -> _.varmint, + "fieldThree" -> _.fieldThree, + "fieldTwo" -> _.fieldTwo, + "fieldOne" -> _.fieldOne, + "foo" -> _.foo + ) + assertElementsMatchExpected(out5)("fieldThree" -> _.fieldThree, "fieldTwo" -> _.fieldTwo, "fieldOne" -> _.fieldOne) + assertElementsMatchExpected(out2)("notAbstract" -> _.notAbstract, "fromAbstractBundle" -> _.fromAbstractBundle) + assertElementsMatchExpected(out4)("fox" -> _.fox, "dog" -> _.dog) + } + + "Complex Bundle with inheritance, traits and params. DebugProblem1" in { + ChiselStage.emitFirrtl(new BpipIsComplexBundle) + } + + "Decoupled Bundle with inheritance" in { + ChiselStage.emitFirrtl(new HasDecoupledBundleByInheritance) + } + + "Simple bundle inheritance. DebugProblem3" in { + ChiselStage.emitFirrtl(new DebugProblem3) + } + + "Bundles containing Seq[Data] should be compile erorr. HasBadSeqBundle" in { + intercept[ChiselException] { + ChiselStage.emitFirrtl(new HasBadSeqBundle) + } + } + + "IgnoreSeqInBundle allows Seq[Data] in bundle" in { + ChiselStage.emitFirrtl(new UsesIgnoreSeqInBundle) + } + + "Simple field ordering test." in { + ChiselStage.emitFirrtl(new ForFieldOrderingTest) + } + + "Val params to Bundle should be an Exception." in { + ChiselStage.emitFirrtl(new HasValParamsToBundle) + } + + "Should handle gen params passed to superclasses" in { + ChiselStage.emitFirrtl(new HasGenParamsPassedToSuperclasses) + } + + "Aliased fields should be detected and throw an exception, because gen: Data, with no =>" in { + intercept[AliasedAggregateFieldException] { + ChiselStage.emitFirrtl(new UsesGenFiedldsInSuperClass) + } + } + + "Error when bundle fields are hardware, such as literal values. HasHardwareFieldsInBundle" in { + val e = intercept[ExpectedChiselTypeException] { + ChiselStage.emitFirrtl(new HasHardwareFieldsInBundle) + } + e.getMessage should include( + "Bundle: BpipBadBundleWithHardware contains hardware fields: bpipWithHardwareBad: UInt<16>(244)" + ) + } + + "Aliased fields not created when using gen: => Data" in { + ChiselStage.emitFirrtl(new UsesBundleWithGeneratorField) + } + + class OptionBundle(val hasIn: Boolean) extends Bundle { + val in = if (hasIn) { + Some(Input(Bool())) + } else { + None + } + val out = Output(Bool()) + } + + class UsesBundleWithOptionFields extends Module { + val outTrue = IO(Output(new OptionBundle(hasIn = true))) + val outFalse = IO(Output(new OptionBundle(hasIn = false))) + //NOTE: The _.in.get _.in is an optional field + assertElementsMatchExpected(outTrue)("out" -> _.out, "in" -> _.in.get) + assertElementsMatchExpected(outFalse)("out" -> _.out) + } + + "plugin should work with Bundles with Option fields" in { + ChiselStage.emitFirrtl(new UsesBundleWithOptionFields) + } + + "plugin should work with enums in bundles" in { + object Enum0 extends ChiselEnum { + val s0, s1, s2 = Value + } + + ChiselStage.emitFirrtl(new Module { + val out = IO(Output(new Bundle { + val a = UInt(8.W) + val b = Bool() + val c = Enum0.Type + })) + assertElementsMatchExpected(out)("b" -> _.b, "a" -> _.a) + }) + } + + "plugin will NOT see fields that are Data but declared in some way as Any" in { + //This is not incompatible with chisel not using the plugin, but this code is considered bad practice + + ChiselStage.emitFirrtl(new Module { + val out = IO(Output(new Bundle { + val a = UInt(8.W) + val b: Any = Bool() + })) + + intercept[TestFailedException] { + assert(out.elements.keys.exists(_ == "b")) + } + }) + } + + "plugin tests should fail properly in the following cases" - { + + class BundleAB extends Bundle { + val a = Output(UInt(8.W)) + val b = Output(Bool()) + } + + def checkAssertion(checks: (BundleAB => (String, Data))*)(expectedMessage: String): Unit = { + intercept[AssertionError] { + ChiselStage.emitFirrtl(new Module { + val out = IO(new BundleAB) + assertElementsMatchExpected(out)(checks: _*) + }) + }.getMessage should include(expectedMessage) + } + + "one of the expected data values is wrong" in { + checkAssertion("b" -> _.b, "a" -> _.b)("field 'a' data field BundleElementsSpec_Anon.out.a") + } + + "one of the expected field names in wrong" in { + checkAssertion("b" -> _.b, "z" -> _.a)("field: 'a' did not match expected 'z'") + } + + "fields that are expected are not returned by the elements method" in { + checkAssertion("b" -> _.b, "a" -> _.a, "c" -> _.a)("#elements is missing the 'c' field") + } + + "fields returned by the element are not specified in the expected fields" in { + checkAssertion("b" -> _.b)("expected fields did not include 'a' field found in #elements") + } + + "multiple errors between elements method and expected fields are shown in the assertion error message" in { + checkAssertion()( + "expected fields did not include 'b' field found in #elements," + + " expected fields did not include 'a' field found in #elements" + ) + } + } + + "plugin should error correctly when bundles contain only a Option field" in { + ChiselStage.emitFirrtl(new Module { + val io = IO(new Bundle { + val foo = Input(UInt(8.W)) + val x = new Bundle { + val y = if (false) Some(Input(UInt(8.W))) else None + } + }) + assertElementsMatchExpected(io)("x" -> _.x, "foo" -> _.foo) + assertElementsMatchExpected(io.x)() + }) + } + + "plugin should handle fields using the boolean to option construct" in { + case class ALUConfig( + xLen: Int, + mul: Boolean, + b: Boolean) + + class OptionalBundle extends Bundle { + val optionBundleA = Input(UInt(3.W)) + val optionBundleB = Input(UInt(7.W)) + } + + class ALU(c: ALUConfig) extends Module { + + class BpipOptionBundle extends Bundle with IgnoreSeqInBundle { + val bpipUIntVal = Input(UInt(8.W)) + lazy val bpipUIntLazyVal = Input(UInt(8.W)) + var bpipUIntVar = Input(UInt(8.W)) + + def bpipUIntDef = Input(UInt(8.W)) + + val bpipOptionUInt = Some(Input(UInt(16.W))) + val bpipOptionOfBundle = if (c.b) Some(new OptionalBundle) else None + val bpipSeqData = Seq(UInt(8.W), UInt(8.W)) + } + + val io = IO(new BpipOptionBundle) + assertElementsMatchExpected(io)( + "bpipUIntLazyVal" -> _.bpipUIntLazyVal, + "bpipOptionUInt" -> _.bpipOptionUInt.get, + "bpipUIntVar" -> _.bpipUIntVar, + "bpipUIntVal" -> _.bpipUIntVal + ) + } + + ChiselStage.emitFirrtl(new ALU(ALUConfig(10, mul = true, b = false))) + } + + "TraceSpec test, different version found in TraceSpec.scala" in { + class Bundle0 extends Bundle { + val a = UInt(8.W) + val b = Bool() + val c = Enum0.Type + } + + class Bundle1 extends Bundle { + val a = new Bundle0 + val b = Vec(4, Vec(4, Bool())) + } + + class Module0 extends Module { + val i = IO(Input(new Bundle1)) + val o = IO(Output(new Bundle1)) + val r = Reg(new Bundle1) + o := r + r := i + + assertElementsMatchExpected(i)("b" -> _.b, "a" -> _.a) + assertElementsMatchExpected(o)("b" -> _.b, "a" -> _.a) + assertElementsMatchExpected(r)("b" -> _.b, "a" -> _.a) + } + + class Module1 extends Module { + val i = IO(Input(new Bundle1)) + val m0 = Module(new Module0) + m0.i := i + m0.o := DontCare + assertElementsMatchExpected(i)("b" -> _.b, "a" -> _.a) + } + + object Enum0 extends ChiselEnum { + val s0, s1, s2 = Value + } + + ChiselStage.emitFirrtl(new Module1) + } +} +/* Checks that the elements method of a bundle matches the testers idea of what the bundle field names and their + * associated data match and that they have the same number of fields in the same order + */ +object assertElementsMatchExpected { + def apply[T <: Bundle](bun: T)(checks: (T => (String, Data))*): Unit = { + val expected = checks.map { fn => fn(bun) } + val elements = bun.elements + val missingMsg = "missing field in #elements" + val extraMsg = "extra field in #elements" + val paired = elements.toSeq.zipAll(expected, missingMsg -> UInt(1.W), extraMsg -> UInt(1.W)) + val errorsStrings = paired.flatMap { + case (element, expected) => + val (elementName, elementData) = element + val (expectedName, expectedData) = expected + if (elementName == missingMsg) { + Some(s"#elements is missing the '$expectedName' field") + } else if (expectedName == extraMsg) { + Some(s"expected fields did not include '$elementName' field found in #elements") + } else if (elementName != expectedName) { + Some(s"field: '$elementName' did not match expected '$expectedName'") + } else if (elementData != expectedData) { + Some( + s"field '$elementName' data field ${elementData}(${elementData.hashCode}) did not match expected $expectedData(${expectedData.hashCode})" + ) + } else { + None + } + } + assert(errorsStrings.isEmpty, s"Bundle: ${bun.getClass.getName}: " + errorsStrings.mkString(", ")) + } +} diff --git a/src/test/scala/chiselTests/BundleSpec.scala b/src/test/scala/chiselTests/BundleSpec.scala index 720f877f..5dcbbefa 100644 --- a/src/test/scala/chiselTests/BundleSpec.scala +++ b/src/test/scala/chiselTests/BundleSpec.scala @@ -26,6 +26,10 @@ trait BundleSpecUtils { val bar = Seq(UInt(16.W), UInt(8.W), UInt(4.W)) } + class BadSeqBundleWithIgnoreSeqInBundle extends Bundle with IgnoreSeqInBundle { + val bar = Seq(UInt(16.W), UInt(8.W), UInt(4.W)) + } + class MyModule(output: Bundle, input: Bundle) extends Module { val io = IO(new Bundle { val in = Input(input) @@ -87,7 +91,7 @@ class BundleSpec extends ChiselFlatSpec with BundleSpecUtils with Utils { new BasicTester { val m = Module(new Module { val io = IO(new Bundle { - val b = new BadSeqBundle with IgnoreSeqInBundle + val b = new BadSeqBundleWithIgnoreSeqInBundle }) }) stop() @@ -141,7 +145,7 @@ class BundleSpec extends ChiselFlatSpec with BundleSpecUtils with Utils { out := in } } - }).getMessage should include("must be a Chisel type, not hardware") + }).getMessage should include("MyBundle contains hardware fields: foo: UInt<7>(123)") } "Bundles" should "not recursively contain aggregates with bound hardware" in { (the[ChiselException] thrownBy extractCause[ChiselException] { @@ -153,7 +157,7 @@ class BundleSpec extends ChiselFlatSpec with BundleSpecUtils with Utils { out := in } } - }).getMessage should include("must be a Chisel type, not hardware") + }).getMessage should include("Bundle: MyBundle contains hardware fields: foo: BundleSpec_Anon.out") } "Unbound bundles sharing a field" should "not error" in { ChiselStage.elaborate { |
