// 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(", ")) } }