summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Lin2019-04-26 17:07:41 -0700
committerGitHub2019-04-26 17:07:41 -0700
commitc1ab9e7afd5072c11d879db913e1b553c7fe0dbe (patch)
tree6e9dce17e2e9556dac48d461b224c6dddf934f0c
parent5e4c3be862bf53ad34315e635d89816729550c01 (diff)
Bundle literals implementation (#1057)
-rw-r--r--chiselFrontend/src/main/scala/chisel3/core/Aggregate.scala94
-rw-r--r--chiselFrontend/src/main/scala/chisel3/core/Data.scala2
-rw-r--r--src/main/scala/chisel3/package.scala8
-rw-r--r--src/test/scala/chiselTests/BundleLiteralSpec.scala136
-rw-r--r--src/test/scala/chiselTests/DataPrint.scala18
-rw-r--r--src/test/scala/chiselTests/LiteralExtractorSpec.scala30
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)