From c1ab9e7afd5072c11d879db913e1b553c7fe0dbe Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Fri, 26 Apr 2019 17:07:41 -0700 Subject: Bundle literals implementation (#1057) --- .../src/main/scala/chisel3/core/Aggregate.scala | 94 +++++++++++++- .../src/main/scala/chisel3/core/Data.scala | 2 - src/main/scala/chisel3/package.scala | 8 ++ src/test/scala/chiselTests/BundleLiteralSpec.scala | 136 ++++++++++++++++----- src/test/scala/chiselTests/DataPrint.scala | 18 +-- .../scala/chiselTests/LiteralExtractorSpec.scala | 30 +---- 6 files changed, 212 insertions(+), 76 deletions(-) diff --git a/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala b/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala index 64148170..35f53013 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala @@ -43,10 +43,6 @@ sealed abstract class Aggregate extends Data { override def litOption: Option[BigInt] = ??? // TODO implement me - // Returns the LitArg of a Bits object. - // Internal API for Bundle literals, to copy the LitArg of argument literals into the top map. - protected def litArgOfBits(elt: Bits): LitArg = elt.litArgOption.get - /** Returns a Seq of the immediate contents of this Aggregate, in order. */ def getElements: Seq[Data] @@ -446,6 +442,95 @@ abstract class Record(private[chisel3] implicit val compileOptions: CompileOptio } } + /** Creates a Bundle literal of this type with specified values. this must be a chisel type. + * + * @param elems literal values, specified as a pair of the Bundle field to the literal value. + * The Bundle field is specified as a function from an object of this type to the field. + * Fields that aren't initialized to DontCare, and assignment to a wire will overwrite any + * existing value with DontCare. + * @return a Bundle literal of this type with subelement values specified + * + * @example {{{ + * class MyBundle extends Bundle { + * val a = UInt(8.W) + * val b = Bool() + * } + * + * (mew MyBundle).Lit( + * _.a -> 42.U, + * _.b -> true.B + * ) + * }}} + */ + private[chisel3] def _makeLit(elems: (this.type => (Data, Data))*): this.type = { // scalastyle:ignore line.size.limit method.length method.name cyclomatic.complexity + // Returns pairs of all fields, element-level and containers, in a Record and their path names + def getRecursiveFields(data: Data, path: String): Seq[(Data, String)] = data match { + case data: Record => data.elements.map { case (fieldName, fieldData) => + getRecursiveFields(fieldData, s"$path.$fieldName") + }.fold(Seq(data -> path)) { _ ++ _ } + case data => Seq(data -> path) // we don't support or recurse into other Aggregate types here + } + + // Returns pairs of corresponding fields between two Records of the same type + def getMatchedFields(x: Data, y: Data): Seq[(Data, Data)] = (x, y) match { + case (x: Element, y: Element) => + require(x typeEquivalent y) + Seq(x -> y) + case (x: Record, y: Record) => + (x.elements zip y.elements).map { case ((xName, xElt), (yName, yElt)) => + require(xName == yName) // assume fields returned in same, deterministic order + getMatchedFields(xElt, yElt) + }.fold(Seq(x -> y)) { _ ++ _ } + } + + requireIsChiselType(this, "bundle literal constructor model") + val clone = cloneType + val cloneFields = getRecursiveFields(clone, "(bundle root)").toMap + + // Create the Bundle literal binding from litargs of arguments + val bundleLitMap = elems.map { fn => fn(clone) }.flatMap { case (field, value) => + val fieldName = cloneFields.getOrElse(field, + throw new BundleLiteralException(s"field $field (with value $value) is not a field," + + s" ensure the field is specified as a function returning a field on an object of class ${this.getClass}," + + s" eg '_.a' to select hypothetical bundle field 'a'") + ) + val valueBinding = value.topBindingOpt match { + case Some(litBinding: LitBinding) => litBinding + case _ => throw new BundleLiteralException(s"field $fieldName specified with non-literal value $value") + } + + field match { // Get the litArg(s) for this field + case field: Bits => + if (field.getClass != value.getClass) { // TODO typeEquivalent is too strict because it checks width + throw new BundleLiteralException(s"Field $fieldName $field specified with non-type-equivalent value $value") + } + val litArg = valueBinding match { + case ElementLitBinding(litArg) => litArg + case BundleLitBinding(litMap) => litMap.getOrElse(value, + throw new BundleLiteralException(s"Field $fieldName specified with unspecified value")) + } + Seq(field -> litArg) + case field: Record => + if (!(field typeEquivalent value)) { + throw new BundleLiteralException(s"field $fieldName $field specified with non-type-equivalent value $value") + } + // Copy the source BundleLitBinding with fields (keys) remapped to the clone + val remap = getMatchedFields(value, field).toMap + value.topBinding.asInstanceOf[BundleLitBinding].litMap.map { case (valueField, valueValue) => + remap(valueField) -> valueValue + } + case _ => throw new BundleLiteralException(s"unsupported field $fieldName of type $field") + } + } // don't convert to a Map yet to preserve duplicate keys + val duplicates = bundleLitMap.map(_._1).groupBy(identity).collect { case (x, elts) if elts.size > 1 => x } + if (!duplicates.isEmpty) { + val duplicateNames = duplicates.map(cloneFields(_)).mkString(", ") + throw new BundleLiteralException(s"duplicate fields $duplicateNames in Bundle literal constructor") + } + clone.bind(BundleLitBinding(bundleLitMap.toMap)) + clone + } + /** The collection of [[Data]] * * This underlying datastructure is a ListMap because the elements must @@ -535,6 +620,7 @@ trait IgnoreSeqInBundle { } class AutoClonetypeException(message: String) extends ChiselException(message) +class BundleLiteralException(message: String) extends ChiselException(message) /** Base class for data types defined as a bundle of other data types. * diff --git a/chiselFrontend/src/main/scala/chisel3/core/Data.scala b/chiselFrontend/src/main/scala/chisel3/core/Data.scala index 03c71af5..abb5675c 100644 --- a/chiselFrontend/src/main/scala/chisel3/core/Data.scala +++ b/chiselFrontend/src/main/scala/chisel3/core/Data.scala @@ -332,8 +332,6 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { // sc * binding and direction are valid after this call completes. */ private[chisel3] def bind(target: Binding, parentDirection: SpecifiedDirection = SpecifiedDirection.Unspecified) - // Variant of bind that can be called from subclasses, used for bundle literals - protected def selfBind(target: Binding) = bind(target) // Both _direction and _resolvedUserDirection are saved versions of computed variables (for // efficiency, avoid expensive recomputation of frequent operations). diff --git a/src/main/scala/chisel3/package.scala b/src/main/scala/chisel3/package.scala index 02c4ecf5..7587f211 100644 --- a/src/main/scala/chisel3/package.scala +++ b/src/main/scala/chisel3/package.scala @@ -511,5 +511,13 @@ package object chisel3 { // scalastyle:ignore package.object.name class dump extends chisel3.internal.naming.dump // scalastyle:ignore class.name class treedump extends chisel3.internal.naming.treedump // scalastyle:ignore class.name class chiselName extends chisel3.internal.naming.chiselName // scalastyle:ignore class.name + + object BundleLiterals { + implicit class AddBundleLiteralConstructor[T <: Bundle](x: T) { + def Lit(elems: (T => (Data, Data))*): T = { + x._makeLit(elems: _*) + } + } + } } } diff --git a/src/test/scala/chiselTests/BundleLiteralSpec.scala b/src/test/scala/chiselTests/BundleLiteralSpec.scala index 9db602ee..2a6c53d5 100644 --- a/src/test/scala/chiselTests/BundleLiteralSpec.scala +++ b/src/test/scala/chiselTests/BundleLiteralSpec.scala @@ -3,47 +3,25 @@ package chiselTests import chisel3._ -import chisel3.core.FixedPoint -import chisel3.experimental.RawModule import chisel3.testers.BasicTester -import org.scalatest._ +import chisel3.experimental.RawModule +import chisel3.experimental.BundleLiterals._ +import chisel3.core.BundleLiteralException class BundleLiteralSpec extends ChiselFlatSpec { class MyBundle extends Bundle { val a = UInt(8.W) val b = Bool() - - // Bundle literal constructor code, which will be auto-generated using macro annotations in - // the future. - import chisel3.core.BundleLitBinding - import chisel3.internal.firrtl.{ULit, Width} - // Full bundle literal constructor - def Lit(aVal: UInt, bVal: Bool): MyBundle = { // scalastyle:ignore method.name - val clone = cloneType - clone.selfBind(BundleLitBinding(Map( - clone.a -> litArgOfBits(aVal), - clone.b -> litArgOfBits(bVal) - ))) - clone - } - // Partial bundle literal constructor - def Lit(aVal: UInt): MyBundle = { // scalastyle:ignore method.name - val clone = cloneType - clone.selfBind(BundleLitBinding(Map( - clone.a -> litArgOfBits(aVal) - ))) - clone - } } "bundle literals" should "work in RTL" in { - val outsideBundleLit = (new MyBundle).Lit(42.U, true.B) + val outsideBundleLit = (new MyBundle).Lit(_.a -> 42.U, _.b -> true.B) assertTesterPasses{ new BasicTester{ // TODO: add direct bundle compare operations, when that feature is added chisel3.assert(outsideBundleLit.a === 42.U) chisel3.assert(outsideBundleLit.b === true.B) - val bundleLit = (new MyBundle).Lit(42.U, true.B) + val bundleLit = (new MyBundle).Lit(_.a -> 42.U, _.b -> true.B) chisel3.assert(bundleLit.a === 42.U) chisel3.assert(bundleLit.b === true.B) @@ -62,7 +40,7 @@ class BundleLiteralSpec extends ChiselFlatSpec { "partial bundle literals" should "work in RTL" in { assertTesterPasses{ new BasicTester{ - val bundleLit = (new MyBundle).Lit(42.U) + val bundleLit = (new MyBundle).Lit(_.a -> 42.U) chisel3.assert(bundleLit.a === 42.U) val bundleWire = Wire(new MyBundle) @@ -73,4 +51,106 @@ class BundleLiteralSpec extends ChiselFlatSpec { stop() } } } + + class MyOuterBundle extends Bundle { + val a = new MyBundle + val b = new Bundle { + val c = Bool() + val d = UInt(8.W) + } + } + + "contained bundles" should "work" in { + assertTesterPasses{ new BasicTester{ + // Specify the inner Bundle value as a Bundle literal + val explicitBundleLit = (new MyOuterBundle).Lit( + _.a -> (new MyBundle).Lit(_.a -> 42.U, _.b -> true.B) + ) + chisel3.assert(explicitBundleLit.a.a === 42.U) + chisel3.assert(explicitBundleLit.a.b === true.B) + + // Specify the inner Bundle fields directly + val expandedBundleLit = (new MyOuterBundle).Lit( + _.a.a -> 42.U, _.a.b -> true.B, + _.b.c -> false.B, _.b.d -> 255.U + ) + chisel3.assert(expandedBundleLit.a.a === 42.U) + chisel3.assert(expandedBundleLit.a.b === true.B) + chisel3.assert(expandedBundleLit.b.c === false.B) + chisel3.assert(expandedBundleLit.b.d === 255.U) + + // Anonymously contruct the inner Bundle literal + // A bit of weird syntax that depends on implementation details of the Bundle literal constructor + val childBundleLit = (new MyOuterBundle).Lit( + b => b.b -> b.b.Lit(_.c -> false.B, _.d -> 255.U) + ) + chisel3.assert(childBundleLit.b.c === false.B) + chisel3.assert(childBundleLit.b.d === 255.U) + + stop() + } } + } + + "Bundle literals" should "assign" in { + assertTesterPasses{ new BasicTester{ + val bundleWire = Wire(Output(new MyBundle)) + val bundleLit = (new MyBundle).Lit(_.a -> 42.U, _.b -> true.B) + bundleWire := bundleLit + + chisel3.assert(bundleWire.a === 42.U) + chisel3.assert(bundleWire.b === true.B) + stop() + } } + } + + "partially initialized Bundle literals" should "assign" in { + assertTesterPasses{ new BasicTester{ + val bundleWire = Wire(Output(new MyBundle)) + val bundleLit = (new MyBundle).Lit(_.a -> 42.U) + bundleWire := bundleLit + + chisel3.assert(bundleWire.a === 42.U) + stop() + } } + } + + "bundle literals with bad field specifiers" should "fail" in { + val exc = intercept[BundleLiteralException] { elaborate { new RawModule { + val bundle = new MyBundle + bundle.Lit(x => bundle.a -> 0.U) // DONT DO THIS, this gets past a syntax error to exercise the failure + }}} + exc.getMessage should include ("not a field") + } + + "bundle literals with duplicate fields" should "fail" in { + val exc = intercept[BundleLiteralException] { elaborate { new RawModule { + (new MyBundle).Lit(_.a -> 0.U, _.a -> 0.U) + }}} + exc.getMessage should include ("duplicate") + exc.getMessage should include (".a") + } + + "bundle literals with non-literal values" should "fail" in { + val exc = intercept[BundleLiteralException] { elaborate { new RawModule { + (new MyBundle).Lit(_.a -> UInt()) + }}} + exc.getMessage should include ("non-literal value") + exc.getMessage should include (".a") + } + + "bundle literals with non-type-equivalent element fields" should "fail" in { + val exc = intercept[BundleLiteralException] { elaborate { new RawModule { + (new MyBundle).Lit(_.a -> true.B) + }}} + exc.getMessage should include ("non-type-equivalent value") + exc.getMessage should include (".a") + } + + "bundle literals with non-type-equivalent sub-bundles" should "fail" in { + val exc = intercept[BundleLiteralException] { elaborate { new RawModule { + (new MyOuterBundle).Lit(_.b -> (new MyBundle).Lit(_.a -> 0.U)) + }}} + exc.getMessage should include ("non-type-equivalent value") + exc.getMessage should include (".b") + } } diff --git a/src/test/scala/chiselTests/DataPrint.scala b/src/test/scala/chiselTests/DataPrint.scala index bec722f3..57e44c36 100644 --- a/src/test/scala/chiselTests/DataPrint.scala +++ b/src/test/scala/chiselTests/DataPrint.scala @@ -6,30 +6,16 @@ import org.scalatest._ import chisel3._ import chisel3.experimental.{ChiselEnum, FixedPoint, RawModule, MultiIOModule} +import chisel3.experimental.BundleLiterals._ class DataPrintSpec extends ChiselFlatSpec with Matchers { object EnumTest extends ChiselEnum { val sNone, sOne, sTwo = Value } - // TODO: dedup w/ BundleLiteralSpec class BundleTest extends Bundle { val a = UInt(8.W) val b = Bool() - - // Bundle literal constructor code, which will be auto-generated using macro annotations in - // the future. - import chisel3.core.BundleLitBinding - import chisel3.internal.firrtl.{ULit, Width} - // Full bundle literal constructor - def Lit(aVal: UInt, bVal: Bool): BundleTest = { // scalastyle:ignore method.name - val clone = cloneType - clone.selfBind(BundleLitBinding(Map( - clone.a -> litArgOfBits(aVal), - clone.b -> litArgOfBits(bVal) - ))) - clone - } } "Data types" should "have a meaningful string representation" in { @@ -82,7 +68,7 @@ class DataPrintSpec extends ChiselFlatSpec with Matchers { EnumTest.sNone.toString should be ("EnumTest(0=sNone)") EnumTest.sTwo.toString should be ("EnumTest(2=sTwo)") EnumTest(1.U).toString should be ("EnumTest(1=sOne)") - (new BundleTest).Lit(2.U, false.B).toString should be ("BundleTest(a=UInt<8>(2), b=Bool(false))") + (new BundleTest).Lit(_.a -> 2.U, _.b -> false.B).toString should be ("BundleTest(a=UInt<8>(2), b=Bool(false))") new Bundle { val a = UInt(8.W) }.toString should be ("AnonymousBundle") diff --git a/src/test/scala/chiselTests/LiteralExtractorSpec.scala b/src/test/scala/chiselTests/LiteralExtractorSpec.scala index 25979488..c2d84831 100644 --- a/src/test/scala/chiselTests/LiteralExtractorSpec.scala +++ b/src/test/scala/chiselTests/LiteralExtractorSpec.scala @@ -5,6 +5,7 @@ package chiselTests import chisel3._ import chisel3.core.FixedPoint import chisel3.experimental.RawModule +import chisel3.experimental.BundleLiterals._ import chisel3.testers.BasicTester import org.scalatest._ @@ -61,20 +62,10 @@ class LiteralExtractorSpec extends ChiselFlatSpec { class InsideBundle extends Bundle { val x = SInt(8.W) val y = FixedPoint(8.W, 4.BP) - - import chisel3.core.BundleLitBinding - def Lit(aVal: SInt, bVal: FixedPoint): InsideBundle = { // scalastyle:ignore method.name - val clone = cloneType - clone.selfBind(BundleLitBinding(Map( - clone.x -> litArgOfBits(aVal), - clone.y -> litArgOfBits(bVal) - ))) - clone - } } class LitInsideOutsideTester(outsideLiteral: InsideBundle) extends BasicTester { - val insideLiteral = (new InsideBundle).Lit(7.S, 6.125.F(4.BP)) + val insideLiteral = (new InsideBundle).Lit(_.x -> 7.S, _.y -> 6.125.F(4.BP)) // the following errors with "assertion failed" @@ -92,7 +83,7 @@ class LiteralExtractorSpec extends ChiselFlatSpec { stop() } - val outsideLiteral = (new InsideBundle).Lit(7.S, 6.125.F(4.BP)) + val outsideLiteral = (new InsideBundle).Lit(_.x -> 7.S, _.y -> 6.125.F(4.BP)) assertTesterPasses{ new LitInsideOutsideTester(outsideLiteral) } } @@ -102,21 +93,8 @@ class LiteralExtractorSpec extends ChiselFlatSpec { class MyBundle extends Bundle { val a = UInt(8.W) val b = Bool() - - // Bundle literal constructor code, which will be auto-generated using macro annotations in - // the future. - import chisel3.core.BundleLitBinding - import chisel3.internal.firrtl.{ULit, Width} - def Lit(aVal: UInt, bVal: Bool): MyBundle = { // scalastyle:ignore method.name - val clone = cloneType - clone.selfBind(BundleLitBinding(Map( - clone.a -> litArgOfBits(aVal), - clone.b -> litArgOfBits(bVal) - ))) - clone - } } - val myBundleLiteral = (new MyBundle).Lit(42.U, true.B) + val myBundleLiteral = (new MyBundle).Lit(_.a -> 42.U, _.b -> true.B) assert(myBundleLiteral.a.litValue == 42) assert(myBundleLiteral.b.litValue == 1) assert(myBundleLiteral.b.litToBoolean == true) -- cgit v1.2.3