diff options
Diffstat (limited to 'core/src/main')
39 files changed, 2554 insertions, 643 deletions
diff --git a/core/src/main/scala-2.12/scala/collection/immutable/package.scala b/core/src/main/scala-2.12/scala/collection/immutable/package.scala new file mode 100644 index 00000000..c06935d0 --- /dev/null +++ b/core/src/main/scala-2.12/scala/collection/immutable/package.scala @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 + +package scala.collection + +import scala.collection.immutable.ListMap + +package object immutable { + val SeqMap = ListMap + type SeqMap[K, +V] = ListMap[K, V] + + val VectorMap = ListMap + type VectorMap[K, +V] = ListMap[K, V] + + val LazyList = Stream + type LazyList[+A] = Stream[A] +} diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index 403fcdba..17e46cb3 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -2,25 +2,28 @@ package chisel3 -import scala.collection.immutable.ListMap +import chisel3.experimental.VecLiterals.AddVecLiteralConstructor +import chisel3.experimental.dataview.{InvalidViewException, isView} + +import scala.collection.immutable.{SeqMap, VectorMap} import scala.collection.mutable.{HashSet, LinkedHashMap} import scala.language.experimental.macros - -import chisel3.experimental.BaseModule -import chisel3.experimental.BundleLiteralException -import chisel3.experimental.EnumType +import chisel3.experimental.{BaseModule, BundleLiteralException, ChiselEnum, EnumType, VecLiteralException} import chisel3.internal._ import chisel3.internal.Builder.pushCommand import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo._ +import scala.collection.mutable + class AliasedAggregateFieldException(message: String) extends ChiselException(message) /** An abstract class for data types that solely consist of (are an aggregate * of) other Data objects. */ sealed abstract class Aggregate extends Data { - private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection) { + private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection): Unit = { + _parent.foreach(_.addId(this)) binding = target val resolvedDirection = SpecifiedDirection.fromParent(parentDirection, specifiedDirection) @@ -50,16 +53,19 @@ sealed abstract class Aggregate extends Data { */ override def litOption: Option[BigInt] = { // Shift the accumulated value by our width and add in our component, masked by our width. - def shiftAdd(accumulator: Option[BigInt], elt: Data): Option[BigInt] = (accumulator, elt.litOption()) match { - case (Some(accumulator), Some(eltLit)) => - val width = elt.width.get - val masked = ((BigInt(1) << width) - 1) & eltLit // also handles the negative case with two's complement - Some((accumulator << width) + masked) - case (None, _) => None - case (_, None) => None + def shiftAdd(accumulator: Option[BigInt], elt: Data): Option[BigInt] = { + (accumulator, elt.litOption()) match { + case (Some(accumulator), Some(eltLit)) => + val width = elt.width.get + val masked = ((BigInt(1) << width) - 1) & eltLit // also handles the negative case with two's complement + Some((accumulator << width) + masked) + case (None, _) => None + case (_, None) => None + } } + topBindingOpt match { - case Some(BundleLitBinding(_)) => + case Some(BundleLitBinding(_)) | Some(VecLitBinding(_)) => getElements .reverse .foldLeft[Option[BigInt]](Some(BigInt(0)))(shiftAdd) @@ -72,6 +78,7 @@ sealed abstract class Aggregate extends Data { def getElements: Seq[Data] private[chisel3] def width: Width = getElements.map(_.width).foldLeft(0.W)(_ + _) + private[chisel3] def legacyConnect(that: Data)(implicit sourceInfo: SourceInfo): Unit = { // If the source is a DontCare, generate a DefInvalid for the sink, // otherwise, issue a Connect. @@ -85,8 +92,9 @@ sealed abstract class Aggregate extends Data { override def do_asUInt(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = { SeqUtils.do_asUInt(flatten.map(_.asUInt())) } + private[chisel3] override def connectFromBits(that: Bits)(implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions): Unit = { + compileOptions: CompileOptions): Unit = { var i = 0 val bits = if (that.isLit) that else WireDefault(UInt(this.width), that) // handles width padding for (x <- flatten) { @@ -154,9 +162,18 @@ trait VecFactory extends SourceInfoDoc { */ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int) extends Aggregate with VecLike[T] { + override def toString: String = { + val bindingString = topBindingOpt match { + case Some(VecLitBinding(vecLitBinding)) => + val contents = vecLitBinding.zipWithIndex.map { case ((data, lit), index) => + s"$index=$lit" + }.mkString(", ") + s"($contents)" + case _ => bindingToString + } val elementType = sample_element.cloneType - s"$elementType[$length]$bindingToString" + s"$elementType[$length]$bindingString" } private[chisel3] override def typeEquivalent(that: Data): Boolean = that match { @@ -166,7 +183,8 @@ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int) case _ => false } - private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection) { + private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection): Unit = { + _parent.foreach(_.addId(this)) binding = target val resolvedDirection = SpecifiedDirection.fromParent(parentDirection, specifiedDirection) @@ -242,6 +260,9 @@ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int) def do_apply(p: UInt)(implicit compileOptions: CompileOptions): T = { requireIsHardware(this, "vec") requireIsHardware(p, "vec index") + if (isView(this)) { + throw InvalidViewException("Dynamic indexing of Views is not yet supported") + } val port = gen // Reconstruct the resolvedDirection (in Aggregate.bind), since it's not stored. @@ -316,9 +337,172 @@ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int) } curLayer(0) } + + /** Creates a Vec literal of this type with specified values. this must be a chisel type. + * + * @param elementInitializers literal values, specified as a pair of the Vec field to the literal value. + * The Vec 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 Vec literal of this type with subelement values specified + * + * Vec(2, UInt(8.W)).Lit( + * 1 -> 0x0A.U, + * 2 -> 0x0B.U + * ) + * }}} + */ + private[chisel3] def _makeLit(elementInitializers: (Int, T)*)(implicit sourceInfo: SourceInfo, + compileOptions: CompileOptions): this.type = { + + def checkLiteralConstruction(): Unit = { + val dupKeys = elementInitializers.map { x => x._1 }.groupBy(x => x).flatMap { case (k, v) => + if (v.length > 1) { + Some(k, v.length) + } else { + None + } + } + if (dupKeys.nonEmpty) { + throw new VecLiteralException( + s"VecLiteral: has duplicated indices ${dupKeys.map { case (k, n) => s"$k($n times)" }.mkString(",")}" + ) + } + + val outOfRangeIndices = elementInitializers.map(_._1).filter { case index => index < 0 || index >= length } + if (outOfRangeIndices.nonEmpty) { + throw new VecLiteralException( + s"VecLiteral: The following indices (${outOfRangeIndices.mkString(",")}) " + + s"are less than zero or greater or equal to than Vec length" + ) + } + cloneSupertype(elementInitializers.map(_._2), s"Vec.Lit(...)") + + // look for literals of this vec that are wider than the vec's type + val badLits = elementInitializers.flatMap { + case (index, lit) => + (sample_element.width, lit.width) match { + case (KnownWidth(m), KnownWidth(n)) => + if (m < n) Some(index -> lit) else None + case (KnownWidth(_), _) => + None + case (UnknownWidth(), _) => + None + case _ => + Some(index -> lit) + } + case _ => None + } + if (badLits.nonEmpty) { + throw new VecLiteralException( + s"VecLiteral: Vec[$gen] has the following incorrectly typed or sized initializers: " + + badLits.map { case (a, b) => s"$a -> $b" }.mkString(",") + ) + } + + } + + requireIsChiselType(this, "vec literal constructor model") + checkLiteralConstruction() + + val clone = cloneType + val cloneFields = getRecursiveFields(clone, "(vec root)").toMap + + // Create the Vec literal binding from litArgs of arguments + val vecLitLinkedMap = new mutable.LinkedHashMap[Data, LitArg]() + elementInitializers.sortBy { case (a, _) => a }.foreach { case (fieldIndex, value) => + val field = clone.apply(fieldIndex) + val fieldName = cloneFields.getOrElse(field, + throw new VecLiteralException(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 VecLiteralException(s"field $fieldIndex specified with non-literal value $value") + } + + field match { // Get the litArg(s) for this field + case bitField: Bits => + if (!field.typeEquivalent(bitField)) { + throw new VecLiteralException( + s"VecLit: Literal specified at index $fieldIndex ($value) does not match Vec type $sample_element" + ) + } + if (bitField.getWidth > field.getWidth) { + throw new VecLiteralException( + s"VecLit: Literal specified at index $fieldIndex ($value) is too wide for Vec type $sample_element" + ) + } + val litArg = valueBinding match { + case ElementLitBinding(litArg) => litArg + case BundleLitBinding(litMap) => litMap.getOrElse(value, + throw new BundleLiteralException(s"Field $fieldName specified with unspecified value") + ) + case VecLitBinding(litMap) => litMap.getOrElse(value, + throw new VecLiteralException(s"Field $fieldIndex specified with unspecified value")) + } + val adjustedLitArg = litArg.cloneWithWidth(sample_element.width) + vecLitLinkedMap(bitField) = adjustedLitArg + + case recordField: Record => + if (!(recordField.typeEquivalent(value))) { + throw new VecLiteralException(s"field $fieldIndex $recordField specified with non-type-equivalent value $value") + } + // Copy the source BundleLitBinding with fields (keys) remapped to the clone + val remap = getMatchedFields(value, recordField).toMap + valueBinding.asInstanceOf[BundleLitBinding].litMap.map { case (valueField, valueValue) => + vecLitLinkedMap(remap(valueField)) = valueValue + } + + case vecField: Vec[_] => + if (!(vecField typeEquivalent value)) { + throw new VecLiteralException(s"field $fieldIndex $vecField specified with non-type-equivalent value $value") + } + // Copy the source VecLitBinding with vecFields (keys) remapped to the clone + val remap = getMatchedFields(value, vecField).toMap + value.topBinding.asInstanceOf[VecLitBinding].litMap.map { case (valueField, valueValue) => + vecLitLinkedMap(remap(valueField)) = valueValue + } + + case enumField: EnumType => { + if (!(enumField typeEquivalent value)) { + throw new VecLiteralException(s"field $fieldIndex $enumField specified with non-type-equivalent enum value $value") + } + val litArg = valueBinding match { + case ElementLitBinding(litArg) => litArg + case _ => + throw new VecLiteralException(s"field $fieldIndex $enumField could not bematched with $valueBinding") + } + vecLitLinkedMap(field) = litArg + } + + case _ => throw new VecLiteralException(s"unsupported field $fieldIndex of type $field") + } + } + + clone.bind(VecLitBinding(VectorMap(vecLitLinkedMap.toSeq:_*))) + clone + } } object VecInit extends SourceInfoDoc { + + /** Gets the correct connect operation (directed hardware assign or bulk connect) for element in Vec. + */ + private def getConnectOpFromDirectionality[T <: Data](proto: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): (T, T) => Unit = proto.direction match { + case ActualDirection.Input | ActualDirection.Output | ActualDirection.Unspecified => + // When internal wires are involved, driver / sink must be specified explicitly, otherwise + // the system is unable to infer which is driver / sink + (x, y) => x := y + case ActualDirection.Bidirectional(_) => + // For bidirectional, must issue a bulk connect so subelements are resolved correctly. + // Bulk connecting two wires may not succeed because Chisel frontend does not infer + // directions. + (x, y) => x <> y + } + /** Creates a new [[Vec]] composed of elements of the input Seq of [[Data]] * nodes. * @@ -340,22 +524,14 @@ object VecInit extends SourceInfoDoc { // DummyImplicit or additional type parameter will break some code. // Check that types are homogeneous. Width mismatch for Elements is safe. - require(!elts.isEmpty) + require(elts.nonEmpty, "Vec hardware values are not allowed to be empty") elts.foreach(requireIsHardware(_, "vec element")) val vec = Wire(Vec(elts.length, cloneSupertype(elts, "Vec"))) - - // TODO: try to remove the logic for this mess - elts.head.direction match { - case ActualDirection.Input | ActualDirection.Output | ActualDirection.Unspecified => - // When internal wires are involved, driver / sink must be specified explicitly, otherwise - // the system is unable to infer which is driver / sink - (vec zip elts).foreach(x => x._1 := x._2) - case ActualDirection.Bidirectional(_) => - // For bidirectional, must issue a bulk connect so subelements are resolved correctly. - // Bulk connecting two wires may not succeed because Chisel frontend does not infer - // directions. - (vec zip elts).foreach(x => x._1 <> x._2) + val op = getConnectOpFromDirectionality(vec.head) + + (vec zip elts).foreach{ x => + op(x._1, x._2) } vec } @@ -387,12 +563,137 @@ object VecInit extends SourceInfoDoc { /** @group SourceInfoTransformMacro */ def do_tabulate[T <: Data](n: Int)(gen: (Int) => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = apply((0 until n).map(i => gen(i))) + + /** Creates a new 2D [[Vec]] of length `n by m` composed of the results of the given + * function applied over a range of integer values starting from 0. + * + * @param n number of 1D vectors inside outer vector + * @param m number of elements in each 1D vector (the function is applied from + * 0 to `n-1`) + * @param gen function that takes in an Int (the index) and returns a + * [[Data]] that becomes the output element + */ + def tabulate[T <: Data](n: Int, m: Int)(gen: (Int, Int) => T): Vec[Vec[T]] = macro VecTransform.tabulate2D + + /** @group SourceInfoTransformMacro */ + def do_tabulate[T <: Data](n: Int, m: Int)(gen: (Int, Int) => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[T]] = { + // TODO make this lazy (requires LazyList and cross compilation, beyond the scope of this PR) + val elts = Seq.tabulate(n, m)(gen) + val flatElts = elts.flatten + + require(flatElts.nonEmpty, "Vec hardware values are not allowed to be empty") + flatElts.foreach(requireIsHardware(_, "vec element")) + + val tpe = cloneSupertype(flatElts, "Vec.tabulate") + val myVec = Wire(Vec(n, Vec(m, tpe))) + val op = getConnectOpFromDirectionality(myVec.head.head) + for ( + (xs1D, ys1D) <- myVec zip elts; + (x, y) <- xs1D zip ys1D + ) { + op(x, y) + } + myVec + } + + /** Creates a new 3D [[Vec]] of length `n by m by p` composed of the results of the given + * function applied over a range of integer values starting from 0. + * + * @param n number of 2D vectors inside outer vector + * @param m number of 1D vectors in each 2D vector + * @param p number of elements in each 1D vector + * @param gen function that takes in an Int (the index) and returns a + * [[Data]] that becomes the output element + */ + def tabulate[T <: Data](n: Int, m: Int, p: Int)(gen: (Int, Int, Int) => T): Vec[Vec[Vec[T]]] = macro VecTransform.tabulate3D + + /** @group SourceInfoTransformMacro */ + def do_tabulate[T <: Data](n: Int, m: Int, p: Int)(gen: (Int, Int, Int) => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[Vec[T]]] = { + // TODO make this lazy (requires LazyList and cross compilation, beyond the scope of this PR) + val elts = Seq.tabulate(n, m, p)(gen) + val flatElts = elts.flatten.flatten + + require(flatElts.nonEmpty, "Vec hardware values are not allowed to be empty") + flatElts.foreach(requireIsHardware(_, "vec element")) + + val tpe = cloneSupertype(flatElts, "Vec.tabulate") + val myVec = Wire(Vec(n, Vec(m, Vec(p, tpe)))) + val op = getConnectOpFromDirectionality(myVec.head.head.head) + + for ( + (xs2D, ys2D) <- myVec zip elts; + (xs1D, ys1D) <- xs2D zip ys2D; + (x, y) <- xs1D zip ys1D + ) { + op(x, y) + } + + myVec + } + + /** Creates a new [[Vec]] of length `n` composed of the result of the given + * function applied to an element of data type T. + * + * @param n number of elements in the vector + * @param gen function that takes in an element T and returns an output + * element of the same type + */ + def fill[T <: Data](n: Int)(gen: => T): Vec[T] = macro VecTransform.fill + + /** @group SourceInfoTransformMacro */ + def do_fill[T <: Data](n: Int)(gen: => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = + apply(Seq.fill(n)(gen)) + + /** Creates a new 2D [[Vec]] of length `n by m` composed of the result of the given + * function applied to an element of data type T. + * + * @param n number of inner vectors (rows) in the outer vector + * @param m number of elements in each inner vector (column) + * @param gen function that takes in an element T and returns an output + * element of the same type + */ + def fill[T <: Data](n: Int, m: Int)(gen: => T): Vec[Vec[T]] = macro VecTransform.fill2D + + /** @group SourceInfoTransformMacro */ + def do_fill[T <: Data](n: Int, m: Int)(gen: => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[T]] = { + do_tabulate(n, m)((_, _) => gen) + } + + /** Creates a new 3D [[Vec]] of length `n by m by p` composed of the result of the given + * function applied to an element of data type T. + * + * @param n number of 2D vectors inside outer vector + * @param m number of 1D vectors in each 2D vector + * @param p number of elements in each 1D vector + * @param gen function that takes in an element T and returns an output + * element of the same type + */ + def fill[T <: Data](n: Int, m: Int, p: Int)(gen: => T): Vec[Vec[Vec[T]]] = macro VecTransform.fill3D + + /** @group SourceInfoTransformMacro */ + def do_fill[T <: Data](n: Int, m: Int, p: Int)(gen: => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[Vec[Vec[T]]] = { + do_tabulate(n, m, p)((_, _, _) => gen) + } + + /** Creates a new [[Vec]] of length `n` composed of the result of the given + * function applied to an element of data type T. + * + * @param start First element in the Vec + * @param len Lenth of elements in the Vec + * @param f Function that applies the element T from previous index and returns the output + * element to the next index + */ + def iterate[T <: Data](start: T, len: Int)(f: (T) => T): Vec[T] = macro VecTransform.iterate + + /** @group SourceInfoTransformMacro */ + def do_iterate[T <: Data](start: T, len: Int)(f: (T) => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = + apply(Seq.iterate(start, len)(f)) } /** A trait for [[Vec]]s containing common hardware generators for collection * operations. */ -trait VecLike[T <: Data] extends collection.IndexedSeq[T] with HasId with SourceInfoDoc { +trait VecLike[T <: Data] extends IndexedSeq[T] with HasId with SourceInfoDoc { def apply(p: UInt): T = macro CompileOptionsTransform.pArg /** @group SourceInfoTransformMacro */ @@ -519,32 +820,13 @@ abstract class Record(private[chisel3] implicit val compileOptions: CompileOptio * val b = Bool() * } * - * (mew MyBundle).Lit( + * (new MyBundle).Lit( * _.a -> 42.U, * _.b -> true.B * ) * }}} */ private[chisel3] def _makeLit(elems: (this.type => (Data, Data))*): this.type = { - // 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 @@ -570,9 +852,15 @@ abstract class Record(private[chisel3] implicit val compileOptions: CompileOptio val litArg = valueBinding match { case ElementLitBinding(litArg) => litArg case BundleLitBinding(litMap) => litMap.getOrElse(value, - throw new BundleLiteralException(s"Field $fieldName specified with unspecified value")) + throw new BundleLiteralException(s"Field $fieldName specified with unspecified value") + ) + case VecLitBinding(litMap) => litMap.getOrElse(value, + throw new VecLiteralException(s"Vec literal $fieldName specified with out literal values") + ) + } Seq(field -> litArg) + case field: Record => if (!(field typeEquivalent value)) { throw new BundleLiteralException(s"field $fieldName $field specified with non-type-equivalent value $value") @@ -582,18 +870,33 @@ abstract class Record(private[chisel3] implicit val compileOptions: CompileOptio value.topBinding.asInstanceOf[BundleLitBinding].litMap.map { case (valueField, valueValue) => remap(valueField) -> valueValue } + + case vecField: Vec[_] => + if (!(vecField typeEquivalent value)) { + throw new BundleLiteralException(s"field $fieldName $vecField specified with non-type-equivalent value $value") + } + // Copy the source BundleLitBinding with fields (keys) remapped to the clone + val remap = getMatchedFields(value, vecField).toMap + value.topBinding.asInstanceOf[VecLitBinding].litMap.map { case (valueField, valueValue) => + remap(valueField) -> valueValue + } + case field: EnumType => { if (!(field typeEquivalent value)) { throw new BundleLiteralException(s"field $fieldName $field specified with non-type-equivalent enum value $value") } val litArg = valueBinding match { case ElementLitBinding(litArg) => litArg + case _ => + throw new BundleLiteralException(s"field $fieldName $field could not be matched with $valueBinding") } Seq(field -> litArg) } case _ => throw new BundleLiteralException(s"unsupported field $fieldName of type $field") } - } // don't convert to a Map yet to preserve duplicate keys + } + + // 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(", ") @@ -630,7 +933,7 @@ abstract class Record(private[chisel3] implicit val compileOptions: CompileOptio s"$className$bindingString" } - val elements: ListMap[String, Data] + def elements: SeqMap[String, Data] /** Name for Pretty Printing */ def className: String = this.getClass.getSimpleName @@ -692,6 +995,7 @@ class AutoClonetypeException(message: String) extends ChiselException(message) package experimental { class BundleLiteralException(message: String) extends ChiselException(message) + class VecLiteralException(message: String) extends ChiselException(message) } @@ -752,11 +1056,13 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { * assert(uint === "h12345678".U) // This will pass * }}} */ - final lazy val elements: ListMap[String, Data] = { + final lazy val elements: SeqMap[String, Data] = { val nameMap = LinkedHashMap[String, Data]() for (m <- getPublicFields(classOf[Bundle])) { getBundleField(m) match { case Some(d: Data) => + requireIsChiselType(d) + if (nameMap contains m.getName) { require(nameMap(m.getName) eq d) } else { @@ -779,7 +1085,7 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { } } } - ListMap(nameMap.toSeq sortWith { case ((an, a), (bn, b)) => (a._id > b._id) || ((a eq b) && (an > bn)) }: _*) + VectorMap(nameMap.toSeq sortWith { case ((an, a), (bn, b)) => (a._id > b._id) || ((a eq b) && (an > bn)) }: _*) } /** @@ -796,17 +1102,50 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { case _ => None } + /** Indicates if a concrete Bundle class was compiled using the compiler plugin + * + * Used for optimizing Chisel's performance and testing Chisel itself + * @note This should not be used in user code! + */ + protected def _usingPlugin: Boolean = false + // Memoize the outer instance for autoclonetype, especially where this is context-dependent // (like the outer module or enclosing Bundles). private var _outerInst: Option[Object] = None - // For autoclonetype, record possible candidates for outer instance. + // For reflective autoclonetype, record possible candidates for outer instance. // _outerInst should always take precedence, since it should be propagated from the original // object which has the most accurate context. - private val _containingModule: Option[BaseModule] = Builder.currentModule - private val _containingBundles: Seq[Bundle] = Builder.updateBundleStack(this) + private val _containingModule: Option[BaseModule] = if (_usingPlugin) None else Builder.currentModule + private val _containingBundles: Seq[Bundle] = if (_usingPlugin) Nil else Builder.updateBundleStack(this) + + private def checkClone(clone: Bundle): Unit = { + for ((name, field) <- elements) { + if (clone.elements(name) eq field) { + throw new AutoClonetypeException( + s"Automatically cloned $clone has field '$name' aliased with base $this." + + " In the future, this will be solved automatically by the compiler plugin." + + " For now, ensure Chisel types used in the Bundle definition are passed through constructor arguments," + + " or wrapped in Input(...), Output(...), or Flipped(...) if appropriate." + + " As a last resort, you can override cloneType manually." + ) + } + } + + } + + override def cloneType: this.type = { + val clone = _cloneTypeImpl.asInstanceOf[this.type] + checkClone(clone) + clone + } - override def cloneType : this.type = { + /** Implementation of cloneType using runtime reflection. This should _never_ be overridden or called in user-code + * + * @note This is overridden by the compiler plugin (it is never called when using the plugin) + */ + protected def _cloneTypeImpl: Bundle = { + assert(Builder.allowReflectiveAutoCloneType, "reflective autoclonetype is disallowed, this should only happen in testing") // This attempts to infer constructor and arguments to clone this Bundle subtype without // requiring the user explicitly overriding cloneType. import scala.language.existentials @@ -814,24 +1153,19 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { val clazz = this.getClass - def autoClonetypeError(desc: String): Nothing = { - throw new AutoClonetypeException(s"Unable to automatically infer cloneType on $clazz: $desc") - } + def autoClonetypeError(desc: String): Nothing = + throw new AutoClonetypeException( + s"Unable to automatically infer cloneType on $clazz. " + + "cloneType is now implemented by the Chisel compiler plugin so please ensure you are using it in your build. " + + "If you cannot use the compiler plugin or you are using it and you still see this message, please file an issue and let us know. " + + s"For those not using the plugin, here is the 'runtime reflection' cloneType error message: $desc" + ) def validateClone(clone: Bundle, equivDiagnostic: String): Unit = { if (!clone.typeEquivalent(this)) { autoClonetypeError(s"Automatically cloned $clone not type-equivalent to base $this. " + equivDiagnostic) } - - for ((name, field) <- elements) { - if (clone.elements(name) eq field) { - autoClonetypeError(s"Automatically cloned $clone has field $name aliased with base $this." + - " In the future, this can be solved by wrapping the field in Field(...)," + - " see https://github.com/freechipsproject/chisel3/pull/909." + - " For now, ensure Chisel types used in the Bundle definition are passed through constructor arguments," + - " or wrapped in Input(...), Output(...), or Flipped(...) if appropriate.") - } - } + checkClone(clone) } val mirror = runtimeMirror(clazz.getClassLoader) @@ -1032,3 +1366,4 @@ abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { */ override def toPrintable: Printable = toPrintableHelper(elements.toList.reverse) } + diff --git a/core/src/main/scala/chisel3/Bits.scala b/core/src/main/scala/chisel3/Bits.scala index 6bd5a07c..670f6e7a 100644 --- a/core/src/main/scala/chisel3/Bits.scala +++ b/core/src/main/scala/chisel3/Bits.scala @@ -29,15 +29,6 @@ private[chisel3] sealed trait ToBoolable extends Element { /** @group SourceInfoTransformMacro */ def do_asBool(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool - - /** Casts this $coll to a [[Bool]] - * - * @note The width must be known and equal to 1 - */ - final def toBool(): Bool = macro SourceInfoWhiteboxTransform.noArg - - /** @group SourceInfoTransformMacro */ - def do_toBool(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool } /** A data type for values represented by a single bitvector. This provides basic bitwise operations. @@ -315,11 +306,6 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi /** Returns the contents of this wire as a [[scala.collection.Seq]] of [[Bool]]. */ final def toBools(): Seq[Bool] = macro SourceInfoTransform.noArg - /** @group SourceInfoTransformMacro */ - @chiselRuntimeDeprecated - @deprecated("Use asBools instead", "3.2") - def do_toBools(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Seq[Bool] = do_asBools - /** Returns the contents of this wire as a [[scala.collection.Seq]] of [[Bool]]. */ final def asBools(): Seq[Bool] = macro SourceInfoTransform.noArg @@ -369,10 +355,6 @@ sealed abstract class Bits(private[chisel3] val width: Width) extends Element wi } } - @chiselRuntimeDeprecated - @deprecated("Use asBool instead", "3.2") - final def do_toBool(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = do_asBool - /** Concatenation operator * * @param that a hardware component @@ -448,7 +430,7 @@ sealed class UInt private[chisel3] (width: Width) extends Bits(width) with Num[U override def do_/ (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = binop(sourceInfo, UInt(this.width), DivideOp, that) override def do_% (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = - binop(sourceInfo, UInt(this.width), RemOp, that) + binop(sourceInfo, UInt(this.width min that.width), RemOp, that) override def do_* (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = binop(sourceInfo, UInt(this.width + that.width), TimesOp, that) @@ -591,10 +573,6 @@ sealed class UInt private[chisel3] (width: Width) extends Bits(width) with Num[U override def do_<= (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = compop(sourceInfo, LessEqOp, that) override def do_>= (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = compop(sourceInfo, GreaterEqOp, that) - @chiselRuntimeDeprecated - @deprecated("Use '=/=', which avoids potential precedence problems", "3.0") - final def != (that: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = this =/= that - /** Dynamic not equals operator * * @param that a hardware $coll @@ -762,9 +740,9 @@ sealed class SInt private[chisel3] (width: Width) extends Bits(width) with Num[S override def do_* (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): SInt = binop(sourceInfo, SInt(this.width + that.width), TimesOp, that) override def do_/ (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): SInt = - binop(sourceInfo, SInt(this.width), DivideOp, that) + binop(sourceInfo, SInt(this.width + 1), DivideOp, that) override def do_% (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): SInt = - binop(sourceInfo, SInt(this.width), RemOp, that) + binop(sourceInfo, SInt(this.width min that.width), RemOp, that) /** Multiplication operator * @@ -877,10 +855,6 @@ sealed class SInt private[chisel3] (width: Width) extends Bits(width) with Num[S override def do_<= (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = compop(sourceInfo, LessEqOp, that) override def do_>= (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = compop(sourceInfo, GreaterEqOp, that) - @chiselRuntimeDeprecated - @deprecated("Use '=/=', which avoids potential precedence problems", "3.0") - final def != (that: SInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = this =/= that - /** Dynamic not equals operator * * @param that a hardware $coll @@ -987,11 +961,6 @@ final class ResetType(private[chisel3] val width: Width = Width(1)) extends Elem private[chisel3] def typeEquivalent(that: Data): Boolean = this.getClass == that.getClass - override def connect(that: Data)(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): Unit = that match { - case _: Reset | DontCare => super.connect(that)(sourceInfo, connectCompileOptions) - case _ => super.badConnect(that)(sourceInfo) - } - override def litOption = None /** Not really supported */ @@ -1034,11 +1003,6 @@ sealed class AsyncReset(private[chisel3] val width: Width = Width(1)) extends El private[chisel3] def typeEquivalent(that: Data): Boolean = this.getClass == that.getClass - override def connect(that: Data)(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions): Unit = that match { - case _: AsyncReset | DontCare => super.connect(that)(sourceInfo, connectCompileOptions) - case _ => super.badConnect(that)(sourceInfo) - } - override def litOption = None /** Not really supported */ @@ -1173,6 +1137,7 @@ sealed class Bool() extends UInt(1.W) with Reset { package experimental { import chisel3.internal.firrtl.BinaryPoint + import chisel3.internal.requireIsHardware // Fix ambiguous import /** Chisel types that have binary points support retrieving * literal values as `Double` or `BigDecimal` diff --git a/core/src/main/scala/chisel3/BlackBox.scala b/core/src/main/scala/chisel3/BlackBox.scala index 03543790..38b08193 100644 --- a/core/src/main/scala/chisel3/BlackBox.scala +++ b/core/src/main/scala/chisel3/BlackBox.scala @@ -62,7 +62,7 @@ package experimental { * @note The parameters API is experimental and may change */ abstract class ExtModule(val params: Map[String, Param] = Map.empty[String, Param]) extends BaseBlackBox { - private[chisel3] override def generateComponent(): Component = { + private[chisel3] override def generateComponent(): Option[Component] = { require(!_closed, "Can't generate module more than once") _closed = true @@ -81,10 +81,12 @@ package experimental { id._onModuleClose } + closeUnboundIds(names) + val firrtlPorts = getModulePorts map {port => Port(port, port.specifiedDirection)} val component = DefBlackBox(this, name, firrtlPorts, SpecifiedDirection.Unspecified, params) _component = Some(component) - component + _component } private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = { @@ -143,7 +145,7 @@ abstract class BlackBox(val params: Map[String, Param] = Map.empty[String, Param // Allow access to bindings from the compatibility package protected def _compatIoPortBound() = portsContains(_io) - private[chisel3] override def generateComponent(): Component = { + private[chisel3] override def generateComponent(): Option[Component] = { _compatAutoWrapPorts() // pre-IO(...) compatibility hack // Restrict IO to just io, clock, and reset @@ -156,11 +158,12 @@ abstract class BlackBox(val params: Map[String, Param] = Map.empty[String, Param val namedPorts = _io.elements.toSeq.reverse // ListMaps are stored in reverse order - // setRef is not called on the actual io. // There is a risk of user improperly attempting to connect directly with io // Long term solution will be to define BlackBox IO differently as part of // it not descending from the (current) Module for ((name, port) <- namedPorts) { + // We are setting a 'fake' ref for io, so that cloneType works but if a user connects to io, it still fails. + this.findPort("io").get.setRef(ModuleIO(internal.ViewParent, ""), force = true) // We have to force override the _ref because it was set during IO binding port.setRef(ModuleIO(this, _namespace.name(name)), force = true) } @@ -176,7 +179,7 @@ abstract class BlackBox(val params: Map[String, Param] = Map.empty[String, Param val firrtlPorts = namedPorts map {namedPort => Port(namedPort._2, namedPort._2.specifiedDirection)} val component = DefBlackBox(this, name, firrtlPorts, _io.specifiedDirection, params) _component = Some(component) - component + _component } private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = { diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index a1f6abf8..32d83008 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -2,13 +2,16 @@ package chisel3 +import chisel3.experimental.dataview.reify + import scala.language.experimental.macros -import chisel3.experimental.{Analog, DataMirror, FixedPoint, Interval} +import chisel3.experimental.{Analog, BaseModule, DataMirror, FixedPoint, Interval} import chisel3.internal.Builder.pushCommand import chisel3.internal._ import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.{DeprecatedSourceInfo, SourceInfo, SourceInfoTransform, UnlocatableSourceInfo} +import scala.collection.immutable.LazyList // Needed for 2.12 alias import scala.util.Try /** User-specified directions. @@ -122,6 +125,7 @@ object ActualDirection { } package experimental { + import chisel3.internal.requireIsHardware // Fix ambiguous import /** Experimental hardware construction reflection API */ @@ -155,7 +159,9 @@ package experimental { // with compiled artifacts (vs. elaboration-time reflection)? def modulePorts(target: BaseModule): Seq[(String, Data)] = target.getChiselPorts - // Returns all module ports with underscore-qualified names + /** Returns all module ports with underscore-qualified names + * return includes [[Module.clock]] and [[Module.reset]] + */ def fullModulePorts(target: BaseModule): Seq[(String, Data)] = { def getPortNames(name: String, data: Data): Seq[(String, Data)] = Seq(name -> data) ++ (data match { case _: Element => Seq() @@ -233,6 +239,62 @@ private[chisel3] object cloneSupertype { } } +// Returns pairs of all fields, element-level and containers, in a Record and their path names +private[chisel3] object getRecursiveFields { + def apply(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: Vec[_] => + data.getElements.zipWithIndex.map { case (fieldData, fieldIndex) => + getRecursiveFields(fieldData, path = s"$path($fieldIndex)") + }.fold(Seq(data -> path)) { + _ ++ _ + } + case data: Element => Seq(data -> path) + } + + def lazily(data: Data, path: String): Seq[(Data, String)] = data match { + case data: Record => + LazyList(data -> path) ++ + data.elements.view.flatMap { case (fieldName, fieldData) => + getRecursiveFields(fieldData, s"$path.$fieldName") + } + case data: Vec[_] => + LazyList(data -> path) ++ + data.getElements.view.zipWithIndex.flatMap { case (fieldData, fieldIndex) => + getRecursiveFields(fieldData, path = s"$path($fieldIndex)") + } + case data: Element => LazyList(data -> path) + } +} + +// Returns pairs of corresponding fields between two Records of the same type +// TODO it seems wrong that Elements are checked for typeEquivalence in Bundle and Vec lit creation +private[chisel3] object getMatchedFields { + def apply(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)) { + _ ++ _ + } + case (x: Vec[_], y: Vec[_]) => + (x.getElements zip y.getElements).map { case (xElt, yElt) => + getMatchedFields(xElt, yElt) + }.fold(Seq(x -> y)) { + _ ++ _ + } + } +} + /** Returns the chisel type of a hardware object, allowing other hardware to be constructed from it. */ object chiselTypeOf { @@ -339,13 +401,14 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { private[chisel3] final def isSynthesizable: Boolean = _binding.map { case ChildBinding(parent) => parent.isSynthesizable case _: TopBinding => true - case _: SampleElementBinding[_] => false + case (_: SampleElementBinding[_] | _: MemTypeBinding[_]) => false }.getOrElse(false) private[chisel3] def topBindingOpt: Option[TopBinding] = _binding.flatMap { case ChildBinding(parent) => parent.topBindingOpt case bindingVal: TopBinding => Some(bindingVal) case SampleElementBinding(parent) => parent.topBindingOpt + case _: MemTypeBinding[_] => None } private[chisel3] def topBinding: TopBinding = topBindingOpt.get @@ -355,7 +418,7 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { * node is the top-level. * binding and direction are valid after this call completes. */ - private[chisel3] def bind(target: Binding, parentDirection: SpecifiedDirection = SpecifiedDirection.Unspecified) + private[chisel3] def bind(target: Binding, parentDirection: SpecifiedDirection = SpecifiedDirection.Unspecified): Unit // Both _direction and _resolvedUserDirection are saved versions of computed variables (for // efficiency, avoid expensive recomputation of frequent operations). @@ -391,6 +454,7 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { case Some(DontCareBinding()) => s"(DontCare)" case Some(ElementLitBinding(litArg)) => s"(unhandled literal)" case Some(BundleLitBinding(litMap)) => s"(unhandled bundle literal)" + case Some(VecLitBinding(litMap)) => s"(unhandled vec literal)" }).getOrElse("") // Return ALL elements at root of this type. @@ -483,6 +547,14 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { } requireIsHardware(this) topBindingOpt match { + // DataView + case Some(ViewBinding(target)) => reify(target).ref + case Some(AggregateViewBinding(viewMap, _)) => + viewMap.get(this) match { + case None => materializeWire() // FIXME FIRRTL doesn't have Aggregate Init expressions + // This should not be possible because Element does the lookup in .topBindingOpt + case x: Some[_] => throwException(s"Internal Error: In .ref for $this got '$topBindingOpt' and '$x'") + } // Literals case Some(ElementLitBinding(litArg)) => litArg case Some(BundleLitBinding(litMap)) => @@ -490,6 +562,11 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { case Some(litArg) => litArg case _ => materializeWire() // FIXME FIRRTL doesn't have Bundle literal expressions } + case Some(VecLitBinding(litMap)) => + litMap.get(this) match { + case Some(litArg) => litArg + case _ => materializeWire() // FIXME FIRRTL doesn't have Vec literal expressions + } case Some(DontCareBinding()) => materializeWire() // FIXME FIRRTL doesn't have a DontCare expression so materialize a Wire // Non-literals @@ -504,6 +581,20 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { } } + + // Recursively set the parent of the start Data and any children (eg. in an Aggregate) + private[chisel3] def setAllParents(parent: Option[BaseModule]): Unit = { + def rec(data: Data): Unit = { + data._parent = parent + data match { + case _: Element => + case agg: Aggregate => + agg.getElements.foreach(rec) + } + } + rec(this) + } + private[chisel3] def width: Width private[chisel3] def legacyConnect(that: Data)(implicit sourceInfo: SourceInfo): Unit @@ -554,14 +645,6 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { } } - @chiselRuntimeDeprecated - @deprecated("litArg is deprecated, use litOption or litTo*Option", "3.2") - def litArg(): Option[LitArg] = topBindingOpt match { - case Some(ElementLitBinding(litArg)) => Some(litArg) - case Some(BundleLitBinding(litMap)) => None // this API does not support Bundle literals - case _ => None - } - def isLit(): Boolean = litOption.isDefined /** @@ -764,35 +847,33 @@ object WireDefault { } } -package internal { - /** RHS (source) for Invalidate API. - * Causes connection logic to emit a DefInvalid when connected to an output port (or wire). - */ - private[chisel3] object InternalDontCare extends Element { - // This object should be initialized before we execute any user code that refers to it, - // otherwise this "Chisel" object will end up on the UserModule's id list. - // We make it private to chisel3 so it has to be accessed through the package object. +/** RHS (source) for Invalidate API. + * Causes connection logic to emit a DefInvalid when connected to an output port (or wire). + */ +final case object DontCare extends Element { + // This object should be initialized before we execute any user code that refers to it, + // otherwise this "Chisel" object will end up on the UserModule's id list. + // We make it private to chisel3 so it has to be accessed through the package object. - private[chisel3] override val width: Width = UnknownWidth() + private[chisel3] override val width: Width = UnknownWidth() - bind(DontCareBinding(), SpecifiedDirection.Output) - override def cloneType: this.type = DontCare + bind(DontCareBinding(), SpecifiedDirection.Output) + override def cloneType: this.type = DontCare - override def toString: String = "DontCare()" + override def toString: String = "DontCare()" - override def litOption: Option[BigInt] = None + override def litOption: Option[BigInt] = None - def toPrintable: Printable = PString("DONTCARE") + def toPrintable: Printable = PString("DONTCARE") - private[chisel3] def connectFromBits(that: Bits)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = { - Builder.error("connectFromBits: DontCare cannot be a connection sink (LHS)") - } + private[chisel3] def connectFromBits(that: Bits)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = { + Builder.error("connectFromBits: DontCare cannot be a connection sink (LHS)") + } - def do_asUInt(implicit sourceInfo: chisel3.internal.sourceinfo.SourceInfo, compileOptions: CompileOptions): UInt = { - Builder.error("DontCare does not have a UInt representation") - 0.U - } - // DontCare's only match themselves. - private[chisel3] def typeEquivalent(that: Data): Boolean = that == DontCare + def do_asUInt(implicit sourceInfo: chisel3.internal.sourceinfo.SourceInfo, compileOptions: CompileOptions): UInt = { + Builder.error("DontCare does not have a UInt representation") + 0.U } + // DontCare's only match themselves. + private[chisel3] def typeEquivalent(that: Data): Boolean = that == DontCare } diff --git a/core/src/main/scala/chisel3/Element.scala b/core/src/main/scala/chisel3/Element.scala index 55415f3d..bc006922 100644 --- a/core/src/main/scala/chisel3/Element.scala +++ b/core/src/main/scala/chisel3/Element.scala @@ -17,7 +17,8 @@ abstract class Element extends Data { def widthKnown: Boolean = width.known def name: String = getRef.name - private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection) { + private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection): Unit = { + _parent.foreach(_.addId(this)) binding = target val resolvedDirection = SpecifiedDirection.fromParent(parentDirection, specifiedDirection) direction = ActualDirection.fromSpecified(resolvedDirection) @@ -29,6 +30,14 @@ abstract class Element extends Data { case Some(litArg) => Some(ElementLitBinding(litArg)) case _ => Some(DontCareBinding()) } + case Some(VecLitBinding(litMap)) => litMap.get(this) match { + case Some(litArg) => Some(ElementLitBinding(litArg)) + case _ => Some(DontCareBinding()) + } + case Some(b @ AggregateViewBinding(viewMap, _)) => viewMap.get(this) match { + case Some(elt) => Some(ViewBinding(elt)) + case _ => throwException(s"Internal Error! $this missing from topBinding $b") + } case topBindingOpt => topBindingOpt } diff --git a/core/src/main/scala/chisel3/Mem.scala b/core/src/main/scala/chisel3/Mem.scala index a60b31ac..183620b6 100644 --- a/core/src/main/scala/chisel3/Mem.scala +++ b/core/src/main/scala/chisel3/Mem.scala @@ -35,6 +35,7 @@ object Mem { } val mt = t.cloneTypeFull val mem = new Mem(mt, size) + mt.bind(MemTypeBinding(mem)) pushCommand(DefMemory(sourceInfo, mem, mt, size)) mem } @@ -45,6 +46,8 @@ object Mem { } sealed abstract class MemBase[T <: Data](val t: T, val length: BigInt) extends HasId with NamedComponent with SourceInfoDoc { + _parent.foreach(_.addId(this)) + // REVIEW TODO: make accessors (static/dynamic, read/write) combinations consistent. /** Creates a read accessor into the memory with static addressing. See the @@ -174,6 +177,7 @@ object SyncReadMem { } val mt = t.cloneTypeFull val mem = new SyncReadMem(mt, size, ruw) + mt.bind(MemTypeBinding(mem)) pushCommand(DefSeqMemory(sourceInfo, mem, mt, size, ruw)) mem } @@ -209,8 +213,14 @@ sealed class SyncReadMem[T <: Data] private (t: T, n: BigInt, val readUnderWrite var port: Option[T] = None when (enable) { a := addr - port = Some(read(a)) + port = Some(super.do_read(a)) } port.get } + + /** @group SourceInfoTransformMacro*/ + override def do_read(idx: UInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = + do_read(addr = idx, enable = true.B) + // note: we implement do_read(addr) for SyncReadMem in terms of do_read(addr, en) in order to ensure that + // `mem.read(addr)` will always behave the same as `mem.read(addr, true.B)` } diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index d34211f1..3ae48821 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -4,9 +4,7 @@ package chisel3 import scala.collection.immutable.ListMap import scala.collection.mutable.{ArrayBuffer, HashMap} -import scala.collection.JavaConversions._ import scala.language.experimental.macros -import java.util.IdentityHashMap import chisel3.internal._ import chisel3.internal.Builder._ @@ -14,6 +12,7 @@ import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.{InstTransform, SourceInfo, UnlocatableSourceInfo} import chisel3.experimental.BaseModule import _root_.firrtl.annotations.{IsModule, ModuleName, ModuleTarget} +import _root_.firrtl.AnnotationSeq object Module extends SourceInfoDoc { /** A wrapper method that all Module instantiations must be wrapped in @@ -65,13 +64,18 @@ object Module extends SourceInfoDoc { Builder.currentClock = saveClock // Back to clock and reset scope Builder.currentReset = saveReset - val component = module.generateComponent() - Builder.components += component + // Only add the component if the module generates one + val componentOpt = module.generateComponent() + for (component <- componentOpt) { + Builder.components += component + } Builder.setPrefix(savePrefix) // Handle connections at enclosing scope - if(!Builder.currentModule.isEmpty) { + // We use _component because Modules that don't generate them may still have one + if (Builder.currentModule.isDefined && module._component.isDefined) { + val component = module._component.get pushCommand(DefInstance(sourceInfo, module, component.ports)) module.initializeInParent(compileOptions) } @@ -117,7 +121,8 @@ abstract class Module(implicit moduleCompileOptions: CompileOptions) extends Raw private[chisel3] def mkReset: Reset = { // Top module and compatibility mode use Bool for reset - val inferReset = _parent.isDefined && moduleCompileOptions.inferModuleReset + // Note that a Definition elaboration will lack a parent, but still not be a Top module + val inferReset = (_parent.isDefined || Builder.inDefinition) && moduleCompileOptions.inferModuleReset if (inferReset) Reset() else Bool() } @@ -138,6 +143,8 @@ abstract class Module(implicit moduleCompileOptions: CompileOptions) extends Raw package experimental { + import chisel3.internal.requireIsChiselType // Fix ambiguous import + object IO { /** Constructs a port for the current Module * @@ -175,8 +182,116 @@ package experimental { package internal { import chisel3.experimental.BaseModule + import chisel3.experimental.hierarchy.IsInstantiable object BaseModule { + /** Represents a clone of an underlying object. This is used to support CloneModuleAsRecord and Instance/Definition. + * + * @note We don't actually "clone" anything in the traditional sense but is a placeholder so we lazily clone internal state + */ + private [chisel3] trait IsClone[+T] { + // Underlying object of which this is a clone of + val _proto: T + def getProto: T = _proto + def isACloneOf(a: Any): Boolean = this == a || _proto == a + } + + // Private internal class to serve as a _parent for Data in cloned ports + private[chisel3] class ModuleClone[T <: BaseModule] (val _proto: T) extends PseudoModule with IsClone[T] { + override def toString = s"ModuleClone(${_proto})" + def getPorts = _portsRecord + // ClonePorts that hold the bound ports for this module + // Used for setting the refs of both this module and the Record + private[BaseModule] var _portsRecord: Record = _ + // This is necessary for correctly supporting .toTarget on a Module Clone. If it is made from the + // Instance/Definition API, it should return an instanceTarget. If made from CMAR, it should return a + // ModuleTarget. + private[chisel3] var _madeFromDefinition: Boolean = false + // Don't generate a component, but point to the one for the cloned Module + private[chisel3] def generateComponent(): Option[Component] = { + require(!_closed, "Can't generate module more than once") + _closed = true + _component = _proto._component + None + } + // Maps proto ports to module clone's ports + private[chisel3] lazy val ioMap: Map[Data, Data] = { + val name2Port = getPorts.elements + _proto.getChiselPorts.map { case (name, data) => data -> name2Port(name) }.toMap + } + // This module doesn't actually exist in the FIRRTL so no initialization to do + private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () + + // Name of this instance's module is the same as the proto's name + override def desiredName: String = _proto.name + + private[chisel3] def setRefAndPortsRef(namespace: Namespace): Unit = { + val record = _portsRecord + // Use .forceName to re-use default name resolving behavior + record.forceName(None, default=this.desiredName, namespace) + // Now take the Ref that forceName set and convert it to the correct Arg + val instName = record.getRef match { + case Ref(name) => name + case bad => throwException(s"Internal Error! Cloned-module Record $record has unexpected ref $bad") + } + // Set both the record and the module to have the same instance name + record.setRef(ModuleCloneIO(_proto, instName), force=true) // force because we did .forceName first + this.setRef(Ref(instName)) + } + } + + /** Represents a module viewed from a different instance context. + * + * @note Why do we need both ModuleClone and InstanceClone? If we are annotating a reference in a module-clone, + * all submodules must be also be 'cloned' so the toTarget can be computed properly. However, we don't need separate + * connectable ports for this instance; all that's different from the proto is the parent. + * + * @note In addition, the instance name of an InstanceClone is going to be the SAME as the proto, but this is not true + * for ModuleClone. + */ + private[chisel3] final class InstanceClone[T <: BaseModule] (val _proto: T, val instName: () => String) extends PseudoModule with IsClone[T] { + override def toString = s"InstanceClone(${_proto})" + // No addition components are generated + private[chisel3] def generateComponent(): Option[Component] = None + // Necessary for toTarget to work + private[chisel3] def setAsInstanceRef(): Unit = { this.setRef(Ref(instName())) } + // This module doesn't acutally exist in the FIRRTL so no initialization to do + private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () + // Instance name is the same as proto's instance name + override def instanceName = instName() + // Module name is the same as proto's module name + override def desiredName: String = _proto.name + } + + /** Represents a Definition root module, when accessing something from a definition + * + * @note This is necessary to distinguish between the toTarget behavior for a Module returned from a Definition, + * versus a normal Module. A normal Module.toTarget will always return a local target. If calling toTarget + * on a Module returned from a Definition (and thus wrapped in an Instance), we need to return the non-local + * target whose root is the Definition. This DefinitionClone is used to represent the root parent of the + * InstanceClone (which represents the returned module). + */ + private[chisel3] class DefinitionClone[T <: BaseModule] (val _proto: T) extends PseudoModule with IsClone[T] { + override def toString = s"DefinitionClone(${_proto})" + // No addition components are generated + private[chisel3] def generateComponent(): Option[Component] = None + // Necessary for toTarget to work + private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () + // Module name is the same as proto's module name + override def desiredName: String = _proto.name + } + + /** @note If we are cloning a non-module, we need another object which has the proper _parent set! + */ + private[chisel3] final class InstantiableClone[T <: IsInstantiable] (val _proto: T) extends IsClone[T] { + private[chisel3] var _parent: Option[BaseModule] = internal.Builder.currentModule + } + + /** Record type returned by CloneModuleAsRecord + * + * @note These are not true Data (the Record doesn't correspond to anything in the emitted + * FIRRTL yet its elements *do*) so have some very specialized behavior. + */ private[chisel3] class ClonePorts (elts: Data*)(implicit compileOptions: CompileOptions) extends Record { val elements = ListMap(elts.map(d => d.instanceName -> d.cloneTypeFull): _*) def apply(field: String) = elements(field) @@ -185,12 +300,20 @@ package internal { private[chisel3] def cloneIORecord(proto: BaseModule)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): ClonePorts = { require(proto.isClosed, "Can't clone a module before module close") + // Fake Module to serve as the _parent of the cloned ports + // We make this before clonePorts because we want it to come up first in naming in + // currentModule + val cloneParent = Module(new ModuleClone(proto)) + require(proto.isClosed, "Can't clone a module before module close") + require(cloneParent.getOptionRef.isEmpty, "Can't have ref set already!") + // Fake Module to serve as the _parent of the cloned ports + // We don't create this inside the ModuleClone because we need the ref to be set by the + // currentModule (and not clonePorts) val clonePorts = new ClonePorts(proto.getModulePorts: _*) - clonePorts.bind(WireBinding(Builder.forcedUserModule, Builder.currentWhen())) - val cloneInstance = new DefInstance(sourceInfo, proto, proto._component.get.ports) { - override def name = clonePorts.getRef.name - } - pushCommand(cloneInstance) + clonePorts.bind(PortBinding(cloneParent)) + clonePorts.setAllParents(Some(cloneParent)) + cloneParent._portsRecord = clonePorts + // Normally handled during Module construction but ClonePorts really lives in its parent's parent if (!compileOptions.explicitInvalidate) { pushCommand(DefInvalid(sourceInfo, clonePorts.ref)) } @@ -205,10 +328,21 @@ package internal { package experimental { + import chisel3.experimental.hierarchy.IsInstantiable + + object BaseModule { + implicit class BaseModuleExtensions[T <: BaseModule](b: T) { + import chisel3.experimental.hierarchy.{Instance, Definition} + def toInstance: Instance[T] = new Instance(Left(b)) + def toDefinition: Definition[T] = new Definition(Left(b)) + } + } /** Abstract base class for Modules, an instantiable organizational unit for RTL. */ // TODO: seal this? - abstract class BaseModule extends HasId { + abstract class BaseModule extends HasId with IsInstantiable { + _parent.foreach(_.addId(this)) + // // Builder Internals - this tracks which Module RTL construction belongs to. // @@ -240,7 +374,15 @@ package experimental { } } - protected def getIds = { + // Returns the last id contained within a Module + private[chisel3] def _lastId: Long = _ids.last match { + case mod: BaseModule => mod._lastId + case _ => + // Ideally we could just take last._id, but Records store and thus bind their Data in reverse order + _ids.maxBy(_._id)._id + } + + private[chisel3] def getIds = { require(_closed, "Can't get ids before module close") _ids.toSeq } @@ -267,7 +409,7 @@ package experimental { /** Generates the FIRRTL Component (Module or Blackbox) of this Module. * Also closes the module so no more construction can happen inside. */ - private[chisel3] def generateComponent(): Component + private[chisel3] def generateComponent(): Option[Component] /** Sets up this module in the parent context */ @@ -305,9 +447,12 @@ package experimental { /** Legalized name of this module. */ final lazy val name = try { - // If this is a module aspect, it should share the same name as the original module - // Thus, the desired name should be returned without uniquification - if(this.isInstanceOf[ModuleAspect]) desiredName else Builder.globalNamespace.name(desiredName) + // PseudoModules are not "true modules" and thus should share + // their original modules names without uniquification + this match { + case _: PseudoModule => desiredName + case _ => Builder.globalNamespace.name(desiredName) + } } catch { case e: NullPointerException => throwException( s"Error: desiredName of ${this.getClass.getName} is null. Did you evaluate 'name' before all values needed by desiredName were available?", e) @@ -318,13 +463,36 @@ package experimental { * * @note Should not be called until circuit elaboration is complete */ - final def toNamed: ModuleName = toTarget.toNamed + final def toNamed: ModuleName = ModuleTarget(this.circuitName, this.name).toNamed /** Returns a FIRRTL ModuleTarget that references this object * * @note Should not be called until circuit elaboration is complete */ - final def toTarget: ModuleTarget = ModuleTarget(this.circuitName, this.name) + final def toTarget: ModuleTarget = this match { + case m: internal.BaseModule.InstanceClone[_] => throwException(s"Internal Error! It's not legal to call .toTarget on an InstanceClone. $m") + case m: internal.BaseModule.DefinitionClone[_] => throwException(s"Internal Error! It's not legal to call .toTarget on an DefinitionClone. $m") + case _ => ModuleTarget(this.circuitName, this.name) + } + + /** Returns the real target of a Module which may be an [[InstanceTarget]] + * + * BaseModule.toTarget returns a ModuleTarget because the classic Module(new MyModule) API elaborates + * Modules in a way that there is a 1:1 relationship between instances and elaborated definitions + * + * Instance/Definition introduced special internal modules [[InstanceClone]] and [[ModuleClone]] that + * do not have this 1:1 relationship so need the ability to return [[InstanceTarget]]s. + * Because users can never actually get references to these underlying objects, we can maintain + * BaseModule.toTarget's API returning [[ModuleTarget]] while providing an internal API for getting + * the correct [[InstanceTarget]]s whenever using the Definition/Instance API. + */ + private[chisel3] def getTarget: IsModule = this match { + case m: internal.BaseModule.InstanceClone[_] if m._parent.nonEmpty => m._parent.get.getTarget.instOf(instanceName, name) + case m: internal.BaseModule.ModuleClone[_] if m._madeFromDefinition => m._parent.get.getTarget.instOf(instanceName, name) + // Without this, we get the wrong CircuitName for the Definition + case m: internal.BaseModule.DefinitionClone[_] if m._circuit.nonEmpty => ModuleTarget(this._circuit.get.circuitName, this.name) + case _ => this.toTarget + } /** Returns a FIRRTL ModuleTarget that references this object * @@ -332,8 +500,15 @@ package experimental { */ final def toAbsoluteTarget: IsModule = { _parent match { - case Some(parent) => parent.toAbsoluteTarget.instOf(this.instanceName, toTarget.module) - case None => toTarget + case Some(parent) => parent.toAbsoluteTarget.instOf(this.instanceName, name) + case None => + // FIXME Special handling for Views - evidence of "weirdness" of .toAbsoluteTarget + // In theory, .toAbsoluteTarget should not be necessary, .toTarget combined with the + // target disambiguation in FIRRTL's deduplication transform should ensure that .toTarget + // is always unambigous. However, legacy workarounds for Chisel's lack of an instance API + // have lead some to use .toAbsoluteTarget as a workaround. A proper instance API will make + // it possible to deprecate and remove .toAbsoluteTarget + if (this == ViewParent) ViewParent.absoluteTarget else getTarget } } @@ -382,6 +557,17 @@ package experimental { names } + /** Invokes _onModuleClose on HasIds found via reflection but not bound to hardware + * (thus not part of _ids) + * This maintains old naming behavior for non-hardware Data + */ + private[chisel3] def closeUnboundIds(names: HashMap[HasId, String]): Unit = { + val idLookup = _ids.toSet + for ((id, _) <- names if !idLookup(id)) { + id._onModuleClose + } + } + /** Compatibility function. Allows Chisel2 code which had ports without the IO wrapper to * compile under Bindings checks. Does nothing in non-compatibility mode. * diff --git a/core/src/main/scala/chisel3/ModuleAspect.scala b/core/src/main/scala/chisel3/ModuleAspect.scala index 4e37ab35..a528fa72 100644 --- a/core/src/main/scala/chisel3/ModuleAspect.scala +++ b/core/src/main/scala/chisel3/ModuleAspect.scala @@ -2,7 +2,7 @@ package chisel3 -import chisel3.internal.Builder +import chisel3.internal.{Builder, PseudoModule} /** Used by Chisel Aspects to inject Chisel code into modules, after they have been elaborated. * This is an internal API - don't use! @@ -13,7 +13,7 @@ import chisel3.internal.Builder * @param moduleCompileOptions */ abstract class ModuleAspect private[chisel3] (module: RawModule) - (implicit moduleCompileOptions: CompileOptions) extends RawModule { + (implicit moduleCompileOptions: CompileOptions) extends RawModule with PseudoModule { Builder.addAspect(module, this) diff --git a/core/src/main/scala/chisel3/Num.scala b/core/src/main/scala/chisel3/Num.scala index e1af9bdb..6dd299f4 100644 --- a/core/src/main/scala/chisel3/Num.scala +++ b/core/src/main/scala/chisel3/Num.scala @@ -156,7 +156,7 @@ trait Num[T <: Data] { /** Minimum operator * * @param that a hardware $coll - * @return a $numType with a value equal to the mimimum value of this $coll and `that` + * @return a $numType with a value equal to the minimum value of this $coll and `that` * $maxWidth * @group Arithmetic */ @@ -169,7 +169,7 @@ trait Num[T <: Data] { /** Maximum operator * * @param that a $numType - * @return a $numType with a value equal to the mimimum value of this $coll and `that` + * @return a $numType with a value equal to the minimum value of this $coll and `that` * $maxWidth * @group Arithmetic */ @@ -226,7 +226,7 @@ trait NumObject { */ def toBigInt(x: BigDecimal, binaryPoint: Int): BigInt = { val multiplier = math.pow(2, binaryPoint) - val result = (x * multiplier).rounded.toBigInt() + val result = (x * multiplier).rounded.toBigInt result } @@ -304,4 +304,4 @@ trait NumObject { throw new ChiselException(s"Error converting BigDecimal $value to BigInt, binary point must be known, not $x") } } -}
\ No newline at end of file +} diff --git a/core/src/main/scala/chisel3/Printf.scala b/core/src/main/scala/chisel3/Printf.scala index 7cbd1918..cf7821b8 100644 --- a/core/src/main/scala/chisel3/Printf.scala +++ b/core/src/main/scala/chisel3/Printf.scala @@ -3,11 +3,10 @@ package chisel3 import scala.language.experimental.macros - import chisel3.internal._ import chisel3.internal.Builder.pushCommand -import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.SourceInfo +import chisel3.experimental.BaseSim /** Prints a message in simulation * @@ -34,6 +33,9 @@ object printf { formatIn map escaped mkString "" } + /** Named class for [[printf]]s. */ + final class Printf(val pable: Printable) extends BaseSim + /** Prints a message in simulation * * Prints a message every cycle. If defined within the scope of a [[when]] block, the message @@ -71,7 +73,7 @@ object printf { * @param fmt printf format string * @param data format string varargs containing data to print */ - def apply(fmt: String, data: Bits*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = + def apply(fmt: String, data: Bits*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Printf = apply(Printable.pack(fmt, data:_*)) /** Prints a message in simulation * @@ -87,16 +89,20 @@ object printf { * @see [[Printable]] documentation * @param pable [[Printable]] to print */ - def apply(pable: Printable)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = { + def apply(pable: Printable)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Printf = { + var printfId: Printf = null when (!Module.reset.asBool) { - printfWithoutReset(pable) + printfId = printfWithoutReset(pable) } + printfId } - private[chisel3] def printfWithoutReset(pable: Printable)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = { + private[chisel3] def printfWithoutReset(pable: Printable)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Printf = { val clock = Builder.forcedClock - pushCommand(Printf(sourceInfo, clock.ref, pable)) + val printfId = new Printf(pable) + pushCommand(chisel3.internal.firrtl.Printf(printfId, sourceInfo, clock.ref, pable)) + printfId } - private[chisel3] def printfWithoutReset(fmt: String, data: Bits*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Unit = + private[chisel3] def printfWithoutReset(fmt: String, data: Bits*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Printf = printfWithoutReset(Printable.pack(fmt, data:_*)) } diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index 0adacedb..27f16ad4 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -4,14 +4,14 @@ package chisel3 import scala.collection.mutable.{ArrayBuffer, HashMap} import scala.util.Try -import scala.collection.JavaConversions._ import scala.language.experimental.macros - -import chisel3.experimental.BaseModule +import chisel3.experimental.{BaseModule, BaseSim} import chisel3.internal._ +import chisel3.internal.BaseModule.{ModuleClone, InstanceClone} import chisel3.internal.Builder._ import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.UnlocatableSourceInfo +import _root_.firrtl.annotations.{IsModule, ModuleTarget} /** Abstract base class for Modules that contain Chisel RTL. * This abstract base class is a user-defined module which does not include implicit clock and reset and supports @@ -37,6 +37,7 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) // // For debuggers/testers, TODO: refactor out into proper public API private var _firrtlPorts: Option[Seq[firrtl.Port]] = None + @deprecated("Use DataMirror.fullModulePorts instead. this API will be removed in Chisel 3.6", "Chisel 3.5") lazy val getPorts = _firrtlPorts.get val compileOptions = moduleCompileOptions @@ -59,7 +60,7 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) } - private[chisel3] override def generateComponent(): Component = { + private[chisel3] override def generateComponent(): Option[Component] = { require(!_closed, "Can't generate module more than once") _closed = true @@ -76,8 +77,11 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) // All suggestions are in, force names to every node. for (id <- getIds) { id match { + case id: ModuleClone[_] => id.setRefAndPortsRef(_namespace) // special handling + case id: InstanceClone[_] => id.setAsInstanceRef() case id: BaseModule => id.forceName(None, default=id.desiredName, _namespace) case id: MemBase[_] => id.forceName(None, default="MEM", _namespace) + case id: BaseSim => id.forceName(None, default="SIM", _namespace) case id: Data => if (id.isSynthesizable) { id.topBinding match { @@ -98,6 +102,8 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) id._onModuleClose } + closeUnboundIds(names) + val firrtlPorts = getModulePorts map { port: Data => // Special case Vec to make FIRRTL emit the direction of its // element. @@ -129,7 +135,7 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) } val component = DefModule(this, name, firrtlPorts, invalidateCommands ++ getCommands) _component = Some(component) - component + _component } private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = { @@ -153,6 +159,13 @@ trait RequireSyncReset extends Module { package object internal { + import scala.annotation.implicitNotFound + @implicitNotFound("You are trying to access a macro-only API. Please use the @public annotation instead.") + trait MacroGenerated + + /** Marker trait for modules that are not true modules */ + private[chisel3] trait PseudoModule extends BaseModule + // Private reflective version of "val io" to maintain Chisel.Module semantics without having // io as a virtual method. See https://github.com/freechipsproject/chisel3/pull/1550 for more // information about the removal of "val io" @@ -220,7 +233,7 @@ package object internal { // Allow access to bindings from the compatibility package protected def _compatIoPortBound() = portsContains(_io) - private[chisel3] override def generateComponent(): Component = { + private[chisel3] override def generateComponent(): Option[Component] = { _compatAutoWrapPorts() // pre-IO(...) compatibility hack // Restrict IO to just io, clock, and reset @@ -260,4 +273,31 @@ package object internal { } } } + + /** Internal API for [[ViewParent]] */ + sealed private[chisel3] class ViewParentAPI extends RawModule()(ExplicitCompileOptions.Strict) with PseudoModule { + // We must provide `absoluteTarget` but not `toTarget` because otherwise they would be exactly + // the same and we'd have no way to distinguish the kind of target when renaming view targets in + // the Converter + // Note that this is not overriding .toAbsoluteTarget, that is a final def in BaseModule that delegates + // to this method + private[chisel3] val absoluteTarget: IsModule = ModuleTarget(this.circuitName, "_$$AbsoluteView$$_") + + // This module is not instantiable + override private[chisel3] def generateComponent(): Option[Component] = None + override private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () + // This module is not really part of the circuit + _parent = None + + // Sigil to mark views, starts with '_' to make it a legal FIRRTL target + override def desiredName = "_$$View$$_" + + private[chisel3] val fakeComponent: Component = DefModule(this, desiredName, Nil, Nil) + } + + /** Special internal object representing the parent of all views + * + * @note this is a val instead of an object because of the need to wrap in Module(...) + */ + private[chisel3] val ViewParent = Module.do_apply(new ViewParentAPI)(UnlocatableSourceInfo, ExplicitCompileOptions.Strict) } diff --git a/core/src/main/scala/chisel3/Reg.scala b/core/src/main/scala/chisel3/Reg.scala index b2b99cc1..bd9e5311 100644 --- a/core/src/main/scala/chisel3/Reg.scala +++ b/core/src/main/scala/chisel3/Reg.scala @@ -127,7 +127,7 @@ object RegNext { * val x = Wire(UInt()) * val y = Wire(UInt(8.W)) * val r1 = RegInit(x) // width will be inferred - * val r2 = RegInit(y) // width is set to 8 + * val r2 = RegInit(y) // width will be inferred * }}} * * 3. [[Aggregate]] initializer - width will be set to match the aggregate diff --git a/core/src/main/scala/chisel3/SeqUtils.scala b/core/src/main/scala/chisel3/SeqUtils.scala index f6642bcb..da6fc802 100644 --- a/core/src/main/scala/chisel3/SeqUtils.scala +++ b/core/src/main/scala/chisel3/SeqUtils.scala @@ -7,7 +7,7 @@ import chisel3.internal.{prefix, throwException} import scala.language.experimental.macros import chisel3.internal.sourceinfo._ - +import chisel3.internal.plugin.autoNameRecursively private[chisel3] object SeqUtils { /** Concatenates the data elements of the input sequence, in sequence order, together. @@ -26,12 +26,12 @@ private[chisel3] object SeqUtils { } else if (in.tail.isEmpty) { in.head.asUInt } else { - val lo = prefix("lo") { + val lo = autoNameRecursively("lo")(prefix("lo") { asUInt(in.slice(0, in.length/2)) - }.autoSeed("lo") - val hi = prefix("hi") { + }) + val hi = autoNameRecursively("hi")(prefix("hi") { asUInt(in.slice(in.length/2, in.length)) - }.autoSeed("hi") + }) hi ## lo } } diff --git a/core/src/main/scala/chisel3/StrongEnum.scala b/core/src/main/scala/chisel3/StrongEnum.scala index 2d372a94..b3d7cf7d 100644 --- a/core/src/main/scala/chisel3/StrongEnum.scala +++ b/core/src/main/scala/chisel3/StrongEnum.scala @@ -18,7 +18,7 @@ object EnumAnnotations { /** An annotation for strong enum instances that are ''not'' inside of Vecs * * @param target the enum instance being annotated - * @param typeName the name of the enum's type (e.g. ''"mypackage.MyEnum"'') + * @param enumTypeName the name of the enum's type (e.g. ''"mypackage.MyEnum"'') */ case class EnumComponentAnnotation(target: Named, enumTypeName: String) extends SingleTargetAnnotation[Named] { def duplicate(n: Named): EnumComponentAnnotation = this.copy(target = n) @@ -50,11 +50,11 @@ object EnumAnnotations { * */ case class EnumVecAnnotation(target: Named, typeName: String, fields: Seq[Seq[String]]) extends SingleTargetAnnotation[Named] { - def duplicate(n: Named) = this.copy(target = n) + def duplicate(n: Named): EnumVecAnnotation = this.copy(target = n) } case class EnumVecChiselAnnotation(target: InstanceId, typeName: String, fields: Seq[Seq[String]]) extends ChiselAnnotation { - override def toFirrtl = EnumVecAnnotation(target.toNamed, typeName, fields) + override def toFirrtl: EnumVecAnnotation = EnumVecAnnotation(target.toNamed, typeName, fields) } /** An annotation for enum types (rather than enum ''instances''). @@ -131,10 +131,28 @@ abstract class EnumType(private val factory: EnumFactory, selfAnnotating: Boolea if (litOption.isDefined) { true.B } else { - factory.all.map(this === _).reduce(_ || _) + if (factory.isTotal) true.B else factory.all.map(this === _).reduce(_ || _) } } + /** Test if this enumeration is equal to any of the values in a given sequence + * + * @param s a [[scala.collection.Seq$ Seq]] of enumeration values to look for + * @return a hardware [[Bool]] that indicates if this value matches any of the given values + */ + final def isOneOf(s: Seq[EnumType])(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = { + VecInit(s.map(this === _)).asUInt().orR() + } + + /** Test if this enumeration is equal to any of the values given as arguments + * + * @param u1 the first value to look for + * @param u2 zero or more additional values to look for + * @return a hardware [[Bool]] that indicates if this value matches any of the given values + */ + final def isOneOf(u1: EnumType, u2: EnumType*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool + = isOneOf(u1 +: u2.toSeq) + def next(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): this.type = { if (litOption.isDefined) { val index = factory.all.indexOf(this) @@ -225,14 +243,20 @@ abstract class EnumFactory { private[chisel3] var width: Width = 0.W private case class EnumRecord(inst: Type, name: String) - private val enum_records = mutable.ArrayBuffer.empty[EnumRecord] + private val enumRecords = mutable.ArrayBuffer.empty[EnumRecord] - private def enumNames = enum_records.map(_.name).toSeq - private def enumValues = enum_records.map(_.inst.litValue()).toSeq - private def enumInstances = enum_records.map(_.inst).toSeq + private def enumNames = enumRecords.map(_.name).toSeq + private def enumValues = enumRecords.map(_.inst.litValue()).toSeq + private def enumInstances = enumRecords.map(_.inst).toSeq private[chisel3] val enumTypeName = getClass.getName.init + // Do all bitvectors of this Enum's width represent legal states? + private[chisel3] def isTotal: Boolean = { + (this.getWidth < 31) && // guard against Integer overflow + (enumRecords.size == (1 << this.getWidth)) + } + private[chisel3] def globalAnnotation: EnumDefChiselAnnotation = EnumDefChiselAnnotation(enumTypeName, (enumNames, enumValues).zipped.toMap) @@ -241,7 +265,7 @@ abstract class EnumFactory { def all: Seq[Type] = enumInstances private[chisel3] def nameOfValue(id: BigInt): Option[String] = { - enum_records.find(_.inst.litValue() == id).map(_.name) + enumRecords.find(_.inst.litValue() == id).map(_.name) } protected def Value: Type = macro EnumMacros.ValImpl @@ -253,7 +277,7 @@ abstract class EnumFactory { // We have to use UnknownWidth here, because we don't actually know what the final width will be result.bindToLiteral(id, UnknownWidth()) - enum_records.append(EnumRecord(result, name)) + enumRecords.append(EnumRecord(result, name)) width = (1 max id.bitLength).W id += 1 @@ -277,7 +301,7 @@ abstract class EnumFactory { def apply(): Type = new Type - def apply(n: UInt)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Type = { + private def castImpl(n: UInt, warn: Boolean)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Type = { if (n.litOption.isDefined) { enumInstances.find(_.litValue == n.litValue) match { case Some(result) => result @@ -288,8 +312,9 @@ abstract class EnumFactory { } else if (n.getWidth > this.getWidth) { throwException(s"The UInt being cast to $enumTypeName is wider than $enumTypeName's width ($getWidth)") } else { - Builder.warning(s"Casting non-literal UInt to $enumTypeName. You can check that its value is legal by calling isValid") - + if (warn && !this.isTotal) { + Builder.warning(s"Casting non-literal UInt to $enumTypeName. You can use $enumTypeName.safe to cast without this warning.") + } val glue = Wire(new UnsafeEnum(width)) glue := n val result = Wire(new Type) @@ -297,6 +322,25 @@ abstract class EnumFactory { result } } + + /** Cast an [[UInt]] to the type of this Enum + * + * @note will give a Chisel elaboration time warning if the argument could hit invalid states + * @param n the UInt to cast + * @return the equivalent Enum to the value of the cast UInt + */ + def apply(n: UInt)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Type = castImpl(n, warn = true) + + /** Safely cast an [[UInt]] to the type of this Enum + * + * @param n the UInt to cast + * @return the equivalent Enum to the value of the cast UInt and a Bool indicating if the + * Enum is valid + */ + def safe(n: UInt)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): (Type, Bool) = { + val t = castImpl(n, warn = false) + (t, t.isValid) + } } diff --git a/core/src/main/scala/chisel3/aop/Aspect.scala b/core/src/main/scala/chisel3/aop/Aspect.scala index 59add417..be9b8975 100644 --- a/core/src/main/scala/chisel3/aop/Aspect.scala +++ b/core/src/main/scala/chisel3/aop/Aspect.scala @@ -12,6 +12,10 @@ import firrtl.AnnotationSeq * @tparam T Type of top-level module */ abstract class Aspect[T <: RawModule] extends Annotation with Unserializable with NoTargetAnnotation { + /** variable to save [[AnnotationSeq]] from [[chisel3.stage.phases.AspectPhase]] + * to be used at [[chisel3.aop.injecting.InjectorAspect]], exposes annotations to [[chisel3.internal.DynamicContext]] + */ + private[aop] var annotationsInAspect: AnnotationSeq = Seq() /** Convert this Aspect to a seq of FIRRTL annotation * @param top * @return @@ -22,7 +26,8 @@ abstract class Aspect[T <: RawModule] extends Annotation with Unserializable wit * @param top * @return */ - private[chisel3] def resolveAspect(top: RawModule): AnnotationSeq = { + private[chisel3] def resolveAspect(top: RawModule, remainingAnnotations: AnnotationSeq): AnnotationSeq = { + annotationsInAspect = remainingAnnotations toAnnotation(top.asInstanceOf[T]) } } diff --git a/core/src/main/scala/chisel3/core/package.scala b/core/src/main/scala/chisel3/core/package.scala deleted file mode 100644 index fa29f244..00000000 --- a/core/src/main/scala/chisel3/core/package.scala +++ /dev/null @@ -1,288 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -package chisel3 - -/** - * These definitions exist to deal with those clients that relied on chisel3.core. - * They are deprecated and will be removed in the future. - */ -package object core { - - @deprecated("Use the version in chisel3._", "3.2") - val CompileOptions = chisel3.CompileOptions - - @deprecated("Use the version in chisel3._", "3.2") - val Input = chisel3.Input - @deprecated("Use the version in chisel3._", "3.2") - val Output = chisel3.Output - @deprecated("Use the version in chisel3._", "3.2") - val Flipped = chisel3.Flipped - @deprecated("Use the version in chisel3._", "3.2") - val chiselTypeOf = chisel3.chiselTypeOf - - @deprecated("Use the version in chisel3._", "3.2") - type Data = chisel3.Data - - @deprecated("Use the version in chisel3._", "3.2") - val WireDefault = chisel3.WireDefault - - @deprecated("Use the version in chisel3._", "3.2") - val Clock = chisel3.Clock - @deprecated("Use the version in chisel3._", "3.2") - type Clock = chisel3.Clock - - @deprecated("Use the version in chisel3._", "3.2") - type Reset = chisel3.Reset - - @deprecated("Use the version in chisel3._", "3.2") - type Aggregate = chisel3.Aggregate - - @deprecated("Use the version in chisel3._", "3.2") - val Vec = chisel3.Vec - @deprecated("Use the version in chisel3._", "3.2") - val VecInit = chisel3.VecInit - @deprecated("Use the version in chisel3._", "3.2") - type Vec[T <: Data] = chisel3.Vec[T] - @deprecated("Use the version in chisel3._", "3.2") - type VecLike[T <: Data] = chisel3.VecLike[T] - @deprecated("Use the version in chisel3._", "3.2") - type Bundle = chisel3.Bundle - @deprecated("Use the version in chisel3._", "3.2") - type IgnoreSeqInBundle = chisel3.IgnoreSeqInBundle - @deprecated("Use the version in chisel3._", "3.2") - type Record = chisel3.Record - - @deprecated("Use the version in chisel3._", "3.2") - val assert = chisel3.assert - - @deprecated("Use the version in chisel3._", "3.2") - type Element = chisel3.Element - @deprecated("Use the version in chisel3._", "3.2") - type Bits = chisel3.Bits - - // These provide temporary compatibility for those who foolishly imported from chisel3.core - @deprecated("Avoid importing from chisel3.core, these are not public APIs and may change at any time. " + - " Use chisel3.RawModule instead. This alias will be removed in 3.4.", "since the beginning of time") - type RawModule = chisel3.RawModule - @deprecated("Avoid importing from chisel3.core, these are not public APIs and may change at any time. " + - "Use chisel3.MultiIOModule instead. This alias will be removed in 3.4.", "since the beginning of time") - type MultiIOModule = chisel3.Module - @deprecated("Avoid importing from chisel3.core, these are not public APIs and may change at any time. " + - " Use chisel3.RawModule instead. This alias will be removed in 3.4.", "since the beginning of time") - type UserModule = chisel3.RawModule - @deprecated("Avoid importing from chisel3.core, these are not public APIs and may change at any time. " + - "Use chisel3.MultiIOModule instead. This alias will be removed in 3.4.", "since the beginning of time") - type ImplicitModule = chisel3.Module - - @deprecated("Use the version in chisel3._", "3.2") - val Bits = chisel3.Bits - @deprecated("Use the version in chisel3._", "3.2") - type Num[T <: chisel3.Data] = chisel3.Num[T] - @deprecated("Use the version in chisel3._", "3.2") - type UInt = chisel3.UInt - @deprecated("Use the version in chisel3._", "3.2") - val UInt = chisel3.UInt - @deprecated("Use the version in chisel3._", "3.2") - type SInt = chisel3.SInt - @deprecated("Use the version in chisel3._", "3.2") - val SInt = chisel3.SInt - @deprecated("Use the version in chisel3._", "3.2") - type Bool = chisel3.Bool - @deprecated("Use the version in chisel3._", "3.2") - val Bool = chisel3.Bool - @deprecated("Use the version in chisel3._", "3.2") - val Mux = chisel3.Mux - - @deprecated("Use the version in chisel3._", "3.2") - type BlackBox = chisel3.BlackBox - - @deprecated("Use the version in chisel3._", "3.2") - val Mem = chisel3.Mem - @deprecated("Use the version in chisel3._", "3.2") - type MemBase[T <: chisel3.Data] = chisel3.MemBase[T] - @deprecated("Use the version in chisel3._", "3.2") - type Mem[T <: chisel3.Data] = chisel3.Mem[T] - @deprecated("Use the version in chisel3._", "3.2") - val SyncReadMem = chisel3.SyncReadMem - @deprecated("Use the version in chisel3._", "3.2") - type SyncReadMem[T <: chisel3.Data] = chisel3.SyncReadMem[T] - - @deprecated("Use the version in chisel3._", "3.2") - val Module = chisel3.Module - @deprecated("Use the version in chisel3._", "3.2") - type Module = chisel3.Module - - @deprecated("Use the version in chisel3._", "3.2") - val printf = chisel3.printf - - @deprecated("Use the version in chisel3._", "3.2") - val RegNext = chisel3.RegNext - @deprecated("Use the version in chisel3._", "3.2") - val RegInit = chisel3.RegInit - @deprecated("Use the version in chisel3._", "3.2") - val Reg = chisel3.Reg - - @deprecated("Use the version in chisel3._", "3.2") - val when = chisel3.when - @deprecated("Use the version in chisel3._", "3.2") - type WhenContext = chisel3.WhenContext - - @deprecated("Use the version in chisel3._", "3.2") - type Printable = chisel3.Printable - @deprecated("Use the version in chisel3._", "3.2") - val Printable = chisel3.Printable - @deprecated("Use the version in chisel3._", "3.2") - type Printables = chisel3.Printables - @deprecated("Use the version in chisel3._", "3.2") - val Printables = chisel3.Printables - @deprecated("Use the version in chisel3._", "3.2") - type PString = chisel3.PString - @deprecated("Use the version in chisel3._", "3.2") - val PString = chisel3.PString - @deprecated("Use the version in chisel3._", "3.2") - type FirrtlFormat = chisel3.FirrtlFormat - @deprecated("Use the version in chisel3._", "3.2") - val FirrtlFormat = chisel3.FirrtlFormat - @deprecated("Use the version in chisel3._", "3.2") - type Decimal = chisel3.Decimal - @deprecated("Use the version in chisel3._", "3.2") - val Decimal = chisel3.Decimal - @deprecated("Use the version in chisel3._", "3.2") - type Hexadecimal = chisel3.Hexadecimal - val Hexadecimal = chisel3.Hexadecimal - @deprecated("Use the version in chisel3._", "3.2") - type Binary = chisel3.Binary - @deprecated("Use the version in chisel3._", "3.2") - val Binary = chisel3.Binary - @deprecated("Use the version in chisel3._", "3.2") - type Character = chisel3.Character - @deprecated("Use the version in chisel3._", "3.2") - val Character = chisel3.Character - @deprecated("Use the version in chisel3._", "3.2") - type Name = chisel3.Name - @deprecated("Use the version in chisel3._", "3.2") - val Name = chisel3.Name - @deprecated("Use the version in chisel3._", "3.2") - type FullName = chisel3.FullName - @deprecated("Use the version in chisel3._", "3.2") - val FullName = chisel3.FullName - @deprecated("Use the version in chisel3._", "3.2") - val Percent = chisel3.Percent - - @deprecated("Use the version in chisel3.experimental._", "3.2") - type Param = chisel3.experimental.Param - @deprecated("Use the version in chisel3.experimental._", "3.2") - type IntParam = chisel3.experimental.IntParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - val IntParam = chisel3.experimental.IntParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - type DoubleParam = chisel3.experimental.DoubleParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - val DoubleParam = chisel3.experimental.DoubleParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - type StringParam = chisel3.experimental.StringParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - val StringParam = chisel3.experimental.StringParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - type RawParam = chisel3.experimental.RawParam - @deprecated("Use the version in chisel3.experimental._", "3.2") - val RawParam = chisel3.experimental.RawParam - - @deprecated("Use the version in chisel3.experimental._", "3.2") - type Analog = chisel3.experimental.Analog - @deprecated("Use the version in chisel3.experimental._", "3.2") - val Analog = chisel3.experimental.Analog - - @deprecated("Use the version in chisel3._", "3.2") - implicit class fromIntToWidth(int: Int) extends chisel3.fromIntToWidth(int) - - @deprecated("Use the version in chisel3.experimental._", "3.2") - val attach = chisel3.experimental.attach - - @deprecated("Use the version in chisel3.experimental._", "3.2") - type EnumType = chisel3.experimental.EnumType - @deprecated("Use the version in chisel3.experimental._", "3.2") - type EnumFactory = chisel3.experimental.EnumFactory - @deprecated("Use the version in chisel3.experimental._", "3.2") - val EnumAnnotations = chisel3.experimental.EnumAnnotations - - @deprecated("Use the version in chisel3._", "3.2") - val withClockAndReset = chisel3.withClockAndReset - @deprecated("Use the version in chisel3._", "3.2") - val withClock = chisel3.withClock - @deprecated("Use the version in chisel3._", "3.2") - val withReset = chisel3.withReset - - @deprecated("Use the version in chisel3._", "3.2") - val dontTouch = chisel3.dontTouch - - @deprecated("Use the version in chisel3.experimental._", "3.2") - type BaseModule = chisel3.experimental.BaseModule - @deprecated("Use the version in chisel3.experimental._", "3.2") - type ExtModule = chisel3.experimental.ExtModule - - @deprecated("Use the version in chisel3.experimental._", "3.2") - val IO = chisel3.experimental.IO - - @deprecated("Use the version in chisel3.experimental._", "3.2") - type FixedPoint = chisel3.experimental.FixedPoint - @deprecated("Use the version in chisel3.experimental._", "3.2") - val FixedPoint = chisel3.experimental.FixedPoint - @deprecated("Use the version in chisel3.experimental._", "3.2") - implicit class fromDoubleToLiteral(double: Double) extends experimental.FixedPoint.Implicits.fromDoubleToLiteral(double) - @deprecated("Use the version in chisel3.experimental._", "3.2") - implicit class fromIntToBinaryPoint(int: Int) extends chisel3.fromIntToBinaryPoint(int) - @deprecated("Use the version in chisel3.experimental._", "3.2") - type RunFirrtlTransform = chisel3.experimental.RunFirrtlTransform - - @deprecated("Use the version in chisel3.experimental._", "3.2") - val annotate = chisel3.experimental.annotate - - @deprecated("Use the version in chisel3.experimental._", "3.2") - val DataMirror = chisel3.experimental.DataMirror - @deprecated("Use the version in chisel3._", "3.2") - type ActualDirection = chisel3.ActualDirection - @deprecated("Use the version in chisel3._", "3.2") - val ActualDirection = chisel3.ActualDirection - - @deprecated("Use the version in chisel3.internal._", "3.2") - val requireIsHardware = chisel3.internal.requireIsHardware - @deprecated("Use the version in chisel3.internal._", "3.2") - val requireIsChiselType = chisel3.internal.requireIsChiselType - @deprecated("Use the version in chisel3.internal._", "3.2") - val BiConnect = chisel3.internal.BiConnect - @deprecated("Use the version in chisel3.internal._", "3.2") - val MonoConnect = chisel3.internal.MonoConnect - @deprecated("Use the version in chisel3.internal._", "3.2") - val BindingDirection = chisel3.internal.BindingDirection - @deprecated("Use the version in chisel3.internal._", "3.2") - type Binding = chisel3.internal.Binding - @deprecated("Use the version in chisel3.internal._", "3.2") - type TopBinding = chisel3.internal.TopBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type UnconstrainedBinding = chisel3.internal.UnconstrainedBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type ConstrainedBinding = chisel3.internal.ConstrainedBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type ReadOnlyBinding = chisel3.internal.ReadOnlyBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type OpBinding = chisel3.internal.OpBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type MemoryPortBinding = chisel3.internal.MemoryPortBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type PortBinding = chisel3.internal.PortBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type RegBinding = chisel3.internal.RegBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type WireBinding = chisel3.internal.WireBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type ChildBinding = chisel3.internal.ChildBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type DontCareBinding = chisel3.internal.DontCareBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type LitBinding = chisel3.internal.LitBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type ElementLitBinding = chisel3.internal.ElementLitBinding - @deprecated("Use the version in chisel3.internal._", "3.2") - type BundleLitBinding = chisel3.internal.BundleLitBinding -} diff --git a/core/src/main/scala/chisel3/dontTouch.scala b/core/src/main/scala/chisel3/dontTouch.scala index 0487a567..e679f1a4 100644 --- a/core/src/main/scala/chisel3/dontTouch.scala +++ b/core/src/main/scala/chisel3/dontTouch.scala @@ -5,7 +5,8 @@ package chisel3 import chisel3.experimental.{ChiselAnnotation, annotate, requireIsHardware} import firrtl.transforms.DontTouchAnnotation -/** Marks that a signal should not be removed by Chisel and Firrtl optimization passes +/** Marks that a signal is an optimization barrier to Chisel and the FIRRTL compiler. This has the effect of + * guaranteeing that a signal will not be removed. * * @example {{{ * class MyModule extends Module { @@ -21,9 +22,11 @@ import firrtl.transforms.DontTouchAnnotation * @note Calling this on [[Data]] creates an annotation that Chisel emits to a separate annotations * file. This file must be passed to FIRRTL independently of the `.fir` file. The execute methods * in [[chisel3.Driver]] will pass the annotations to FIRRTL automatically. + * @note Because this is an optimization barrier, constants will not be propagated through a signal marked as + * dontTouch. */ object dontTouch { - /** Marks a signal to be preserved in Chisel and Firrtl + /** Mark a signal as an optimization barrier to Chisel and FIRRTL. * * @note Requires the argument to be bound to hardware * @param data The signal to be marked diff --git a/core/src/main/scala/chisel3/experimental/Analog.scala b/core/src/main/scala/chisel3/experimental/Analog.scala index df76fd70..6cca81f5 100644 --- a/core/src/main/scala/chisel3/experimental/Analog.scala +++ b/core/src/main/scala/chisel3/experimental/Analog.scala @@ -45,7 +45,8 @@ final class Analog private (private[chisel3] val width: Width) extends Element { // Define setter/getter pairing // Analog can only be bound to Ports and Wires (and Unbound) - private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection) { + private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection): Unit = { + _parent.foreach(_.addId(this)) SpecifiedDirection.fromParent(parentDirection, specifiedDirection) match { case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => case x => throwException(s"Analog may not have explicit direction, got '$x'") @@ -55,6 +56,7 @@ final class Analog private (private[chisel3] val width: Width) extends Element { case ChildBinding(parent) => parent.topBinding // See https://github.com/freechipsproject/chisel3/pull/946 case SampleElementBinding(parent) => parent.topBinding + case a: MemTypeBinding[_] => a } targetTopBinding match { @@ -82,4 +84,3 @@ final class Analog private (private[chisel3] val width: Width) extends Element { object Analog { def apply(width: Width): Analog = new Analog(width) } - diff --git a/core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala b/core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala new file mode 100644 index 00000000..55dd8505 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.dataview + +import chisel3.experimental.BaseModule +import chisel3.{Data, getRecursiveFields} + +import scala.annotation.implicitNotFound + +/** Typeclass interface for getting elements of type [[Data]] + * + * This is needed for validating [[DataView]]s targeting type `A`. + * Can be thought of as "can be the Target of a DataView". + * + * Chisel provides some implementations in [[DataProduct$ object DataProduct]] that are available + * by default in the implicit scope. + * + * @tparam A Type that has elements of type [[Data]] + * @see [[https://www.chisel-lang.org/chisel3/docs/explanations/dataview#dataproduct Detailed Documentation]] + */ +@implicitNotFound("Could not find implicit value for DataProduct[${A}].\n" + + "Please see https://www.chisel-lang.org/chisel3/docs/explanations/dataview#dataproduct") +trait DataProduct[-A] { + /** Provides [[Data]] elements within some containing object + * + * @param a Containing object + * @param path Hierarchical path to current signal (for error reporting) + * @return Data elements and associated String paths (Strings for error reporting only!) + */ + def dataIterator(a: A, path: String): Iterator[(Data, String)] + + /** Returns a checker to test if the containing object contains a `Data` object + * @note Implementers may want to override if iterating on all `Data` is expensive for `A` and `A` + * will primarily be used in `PartialDataViews` + * @note The returned value is a function, not a true Set, but is describing the functionality of + * Set containment + * @param a Containing object + * @return A checker that itself returns True if a given `Data` is contained in `a` + * as determined by an `==` test + */ + def dataSet(a: A): Data => Boolean = dataIterator(a, "").map(_._1).toSet +} + +/** Encapsulating object for automatically provided implementations of [[DataProduct]] + * + * @note DataProduct implementations provided in this object are available in the implicit scope + */ +object DataProduct { + /** [[DataProduct]] implementation for [[Data]] */ + implicit val dataDataProduct: DataProduct[Data] = new DataProduct[Data] { + def dataIterator(a: Data, path: String): Iterator[(Data, String)] = + getRecursiveFields.lazily(a, path).iterator + } + + /** [[DataProduct]] implementation for [[BaseModule]] */ + implicit val userModuleDataProduct: DataProduct[BaseModule] = new DataProduct[BaseModule] { + def dataIterator(a: BaseModule, path: String): Iterator[(Data, String)] = { + a.getIds.iterator.flatMap { + case d: Data if d.getOptionRef.isDefined => // Using ref to decide if it's truly hardware in the module + Seq(d -> s"${path}.${d.instanceName}") + case b: BaseModule => dataIterator(b, s"$path.${b.instanceName}") + case _ => Seq.empty + } + } + // Overridden for performance + override def dataSet(a: BaseModule): Data => Boolean = { + val lastId = a._lastId // Not cheap to compute + // Return a function + e => e._id > a._id && e._id <= lastId + } + } +} diff --git a/core/src/main/scala/chisel3/experimental/dataview/DataView.scala b/core/src/main/scala/chisel3/experimental/dataview/DataView.scala new file mode 100644 index 00000000..caf004c2 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/dataview/DataView.scala @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.dataview + +import chisel3._ +import chisel3.internal.sourceinfo.SourceInfo +import scala.reflect.runtime.universe.WeakTypeTag + +import annotation.implicitNotFound + + +/** Mapping between a target type `T` and a view type `V` + * + * Enables calling `.viewAs[T]` on instances of the target type. + * + * ==Detailed documentation== + * - [[https://www.chisel-lang.org/chisel3/docs/explanations/dataview Explanation]] + * - [[https://www.chisel-lang.org/chisel3/docs/cookbooks/dataview Cookbook]] + * + * @example {{{ + * class Foo(val w: Int) extends Bundle { + * val a = UInt(w.W) + * } + * class Bar(val w: Int) extends Bundle { + * val b = UInt(w.W) + * } + * // DataViews are created using factory methods in the companion object + * implicit val view = DataView[Foo, Bar]( + * // The first argument is a function constructing a Foo from a Bar + * foo => new Bar(foo.w) + * // The remaining arguments are a variable number of field pairings + * _.a -> _.b + * ) + * }}} + * + * @tparam T Target type (must have an implementation of [[DataProduct]]) + * @tparam V View type + * @see [[DataView$ object DataView]] for factory methods + * @see [[PartialDataView object PartialDataView]] for defining non-total `DataViews` + */ +@implicitNotFound("Could not find implicit value for DataView[${T}, ${V}].\n" + + "Please see https://www.chisel-lang.org/chisel3/docs/explanations/dataview") +sealed class DataView[T : DataProduct, V <: Data] private[chisel3] ( + /** Function constructing an object of the View type from an object of the Target type */ + private[chisel3] val mkView: T => V, + /** Function that returns corresponding fields of the target and view */ + private[chisel3] val mapping: (T, V) => Iterable[(Data, Data)], + // Aliasing this with a def below to make the ScalaDoc show up for the field + _total: Boolean +)( + implicit private[chisel3] val sourceInfo: SourceInfo +) { + /** Indicates if the mapping contains every field of the target */ + def total: Boolean = _total + + override def toString: String = { + val base = sourceInfo.makeMessage(x => x) + val loc = if (base.nonEmpty) base else "@unknown" + val name = if (total) "DataView" else "PartialDataView" + s"$name(defined $loc)" + } + + /** Compose two `DataViews` together to construct a view from the target of this `DataView` to the + * view type of the second `DataView` + * + * @param g a DataView from `V` to new view-type `V2` + * @tparam V2 View type of `DataView` `g` + * @return a new `DataView` from the original `T` to new view-type `V2` + */ + def andThen[V2 <: Data](g: DataView[V, V2])(implicit sourceInfo: SourceInfo): DataView[T, V2] = { + val self = this + // We have to pass the DataProducts and DataViews manually to .viewAs below + val tdp = implicitly[DataProduct[T]] + val vdp = implicitly[DataProduct[V]] + new DataView[T, V2]( + t => g.mkView(mkView(t)), + { case (t, v2) => List(t.viewAs[V](tdp, self).viewAs[V2](vdp, g) -> v2) }, + this.total && g.total + ) { + override def toString: String = s"$self andThen $g" + } + } +} + +/** Factory methods for constructing [[DataView]]s, see class for example use */ +object DataView { + + /** Default factory method, alias for [[pairs]] */ + def apply[T : DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] = + DataView.pairs(mkView, pairs: _*) + + /** Construct [[DataView]]s with pairs of functions from the target and view to corresponding fields */ + def pairs[T : DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] = + mapping(mkView: T => V, swizzle(pairs)) + + /** More general factory method for complex mappings */ + def mapping[T : DataProduct, V <: Data](mkView: T => V, mapping: (T, V) => Iterable[(Data, Data)])(implicit sourceInfo: SourceInfo): DataView[T, V] = + new DataView[T, V](mkView, mapping, _total = true) + + /** Provides `invert` for invertible [[DataView]]s + * + * This must be done as an extension method because it applies an addition constraint on the `Target` + * type parameter, namely that it must be a subtype of [[Data]]. + * + * @note [[PartialDataView]]s are **not** invertible and will result in an elaboration time exception + */ + implicit class InvertibleDataView[T <: Data : WeakTypeTag, V <: Data : WeakTypeTag](view: DataView[T, V]) { + def invert(mkView: V => T): DataView[V, T] = { + // It would've been nice to make this a compiler error, but it's unclear how to make that work. + // We tried having separate TotalDataView and PartialDataView and only defining inversion for + // TotalDataView. For some reason, implicit resolution wouldn't invert TotalDataViews. This is + // probably because it was looking for the super-type DataView and since invertDataView was + // only defined on TotalDataView, it wasn't included in implicit resolution. Thus we end up + // with a runtime check. + if (!view.total) { + val tt = implicitly[WeakTypeTag[T]].tpe + val vv = implicitly[WeakTypeTag[V]].tpe + val msg = s"Cannot invert '$view' as it is non-total.\n Try providing a DataView[$vv, $tt]." + + s"\n Please see https://www.chisel-lang.org/chisel3/docs/explanations/dataview." + throw InvalidViewException(msg) + } + implicit val sourceInfo = view.sourceInfo + new DataView[V, T](mkView, swapArgs(view.mapping), view.total) + } + } + + private[dataview] def swizzle[A, B, C, D](fs: Iterable[(A, B) => (C, D)]): (A, B) => Iterable[(C, D)] = { + case (a, b) => fs.map(f => f(a, b)) + } + + private def swapArgs[A, B, C, D](f: (A, B) => Iterable[(C, D)]): (B, A) => Iterable[(D, C)] = { + case (b, a) => f(a, b).map(_.swap) + } + + /** All Chisel Data are viewable as their own type */ + implicit def identityView[A <: Data](implicit sourceInfo: SourceInfo): DataView[A, A] = + DataView[A, A](chiselTypeOf.apply, { case (x, y) => (x, y) }) +} + +/** Factory methods for constructing non-total [[DataView]]s */ +object PartialDataView { + + /** Default factory method, alias for [[pairs]] */ + def apply[T: DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] = + PartialDataView.pairs(mkView, pairs: _*) + + /** Construct [[DataView]]s with pairs of functions from the target and view to corresponding fields */ + def pairs[T: DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] = + mapping(mkView, DataView.swizzle(pairs)) + + /** More general factory method for complex mappings */ + def mapping[T: DataProduct, V <: Data](mkView: T => V, mapping: (T, V) => Iterable[(Data, Data)])(implicit sourceInfo: SourceInfo): DataView[T, V] = + new DataView[T, V](mkView, mapping, _total = false) +} diff --git a/core/src/main/scala/chisel3/experimental/dataview/package.scala b/core/src/main/scala/chisel3/experimental/dataview/package.scala new file mode 100644 index 00000000..1acf43e1 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/dataview/package.scala @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental + +import chisel3._ +import chisel3.internal._ +import chisel3.internal.sourceinfo.SourceInfo + +import scala.annotation.{implicitNotFound, tailrec} +import scala.collection.mutable +import scala.collection.immutable.LazyList // Needed for 2.12 alias + +package object dataview { + case class InvalidViewException(message: String) extends ChiselException(message) + + /** Provides `viewAs` for types that have an implementation of [[DataProduct]] + * + * Calling `viewAs` also requires an implementation of [[DataView]] for the target type + */ + implicit class DataViewable[T](target: T) { + def viewAs[V <: Data](implicit dataproduct: DataProduct[T], dataView: DataView[T, V]): V = { + // TODO put a try catch here for ExpectedHardwareException and perhaps others + // It's likely users will accidentally use chiselTypeOf or something that may error, + // The right thing to use is DataMirror...chiselTypeClone because of composition with DataView.andThen + // Another option is that .andThen could give a fake binding making chiselTypeOfs in the user code safe + val result: V = dataView.mkView(target) + requireIsChiselType(result, "viewAs") + + doBind(target, result, dataView) + + // Setting the parent marks these Data as Views + result.setAllParents(Some(ViewParent)) + // The names of views do not matter except for when a view is annotated. For Views that correspond + // To a single Data, we just forward the name of the Target. For Views that correspond to more + // than one Data, we return this assigned name but rename it in the Convert stage + result.forceName(None, "view", Builder.viewNamespace) + result + } + } + + // This private type alias lets us provide a custom error message for misuing the .viewAs for upcasting Bundles + @implicitNotFound("${A} is not a subtype of ${B}! Did you mean .viewAs[${B}]? " + + "Please see https://www.chisel-lang.org/chisel3/docs/cookbooks/dataview") + private type SubTypeOf[A, B] = A <:< B + + /** Provides `viewAsSupertype` for subclasses of [[Bundle]] */ + implicit class BundleUpcastable[T <: Bundle](target: T) { + /** View a [[Bundle]] or [[Record]] as a parent type (upcast) */ + def viewAsSupertype[V <: Bundle](proto: V)(implicit ev: SubTypeOf[T, V], sourceInfo: SourceInfo): V = { + implicit val dataView = PartialDataView.mapping[T, V](_ => proto, { + case (a, b) => + val aElts = a.elements + val bElts = b.elements + val bKeys = bElts.keySet + val keys = aElts.keysIterator.filter(bKeys.contains) + keys.map(k => aElts(k) -> bElts(k)).toSeq + }) + target.viewAs[V] + } + } + + private def nonTotalViewException(dataView: DataView[_, _], target: Any, view: Data, targetFields: Seq[String], viewFields: Seq[String]) = { + def missingMsg(name: String, fields: Seq[String]): Option[String] = { + val str = fields.mkString(", ") + fields.size match { + case 0 => None + case 1 => Some(s"$name field '$str' is missing") + case _ => Some(s"$name fields '$str' are missing") + } + } + val vs = missingMsg("view", viewFields) + val ts = missingMsg("target", targetFields) + val reasons = (ts ++ vs).mkString(" and ").capitalize + val suggestion = if (ts.nonEmpty) "\n If the view *should* be non-total, try a 'PartialDataView'." else "" + val msg = s"Viewing $target as $view is non-Total!\n $reasons.\n DataView used is $dataView.$suggestion" + throw InvalidViewException(msg) + } + + // TODO should this be moved to class Aggregate / can it be unified with Aggregate.bind? + private def doBind[T : DataProduct, V <: Data](target: T, view: V, dataView: DataView[T, V]): Unit = { + val mapping = dataView.mapping(target, view) + val total = dataView.total + // Lookups to check the mapping results + val viewFieldLookup: Map[Data, String] = getRecursiveFields(view, "_").toMap + val targetContains: Data => Boolean = implicitly[DataProduct[T]].dataSet(target) + + // Resulting bindings for each Element of the View + val childBindings = + new mutable.HashMap[Data, mutable.ListBuffer[Element]] ++ + viewFieldLookup.view + .collect { case (elt: Element, _) => elt } + .map(_ -> new mutable.ListBuffer[Element]) + + def viewFieldName(d: Data): String = + viewFieldLookup.get(d).map(_ + " ").getOrElse("") + d.toString + + // Helper for recording the binding of each + def onElt(te: Element, ve: Element): Unit = { + // TODO can/should we aggregate these errors? + def err(name: String, arg: Data) = + throw InvalidViewException(s"View mapping must only contain Elements within the $name, got $arg") + + // The elements may themselves be views, look through the potential chain of views for the Elements + // that are actually members of the target or view + val tex = unfoldView(te).find(targetContains).getOrElse(err("Target", te)) + val vex = unfoldView(ve).find(viewFieldLookup.contains).getOrElse(err("View", ve)) + + if (tex.getClass != vex.getClass) { + val fieldName = viewFieldName(vex) + throw InvalidViewException(s"Field $fieldName specified as view of non-type-equivalent value $tex") + } + // View width must be unknown or match target width + if (vex.widthKnown && vex.width != tex.width) { + def widthAsString(x: Element) = x.widthOption.map("<" + _ + ">").getOrElse("<unknown>") + val fieldName = viewFieldName(vex) + val vwidth = widthAsString(vex) + val twidth = widthAsString(tex) + throw InvalidViewException(s"View field $fieldName has width ${vwidth} that is incompatible with target value $tex's width ${twidth}") + } + childBindings(vex) += tex + } + + mapping.foreach { + // Special cased because getMatchedFields checks typeEquivalence on Elements (and is used in Aggregate path) + // Also saves object allocations on common case of Elements + case (ae: Element, be: Element) => onElt(ae, be) + + case (aa: Aggregate, ba: Aggregate) => + if (!ba.typeEquivalent(aa)) { + val fieldName = viewFieldLookup(ba) + throw InvalidViewException(s"field $fieldName specified as view of non-type-equivalent value $aa") + } + getMatchedFields(aa, ba).foreach { + case (aelt: Element, belt: Element) => onElt(aelt, belt) + case _ => // Ignore matching of Aggregates + } + } + + // Errors in totality of the View, use var List to keep fast path cheap (no allocation) + var viewNonTotalErrors: List[Data] = Nil + var targetNonTotalErrors: List[String] = Nil + + val targetSeen: Option[mutable.Set[Data]] = if (total) Some(mutable.Set.empty[Data]) else None + + val resultBindings = childBindings.map { case (data, targets) => + val targetsx = targets match { + case collection.Seq(target: Element) => target + case collection.Seq() => + viewNonTotalErrors = data :: viewNonTotalErrors + data.asInstanceOf[Element] // Return the Data itself, will error after this map, cast is safe + case x => + throw InvalidViewException(s"Got $x, expected Seq(_: Direct)") + } + // TODO record and report aliasing errors + targetSeen.foreach(_ += targetsx) + data -> targetsx + }.toMap + + // Check for totality of Target + targetSeen.foreach { seen => + val lookup = implicitly[DataProduct[T]].dataIterator(target, "_") + for (missed <- lookup.collect { case (d: Element, name) if !seen(d) => name }) { + targetNonTotalErrors = missed :: targetNonTotalErrors + } + } + if (viewNonTotalErrors != Nil || targetNonTotalErrors != Nil) { + val viewErrors = viewNonTotalErrors.map(f => viewFieldLookup.getOrElse(f, f.toString)) + nonTotalViewException(dataView, target, view, targetNonTotalErrors, viewErrors) + } + + view match { + case elt: Element => view.bind(ViewBinding(resultBindings(elt))) + case agg: Aggregate => + // We record total Data mappings to provide a better .toTarget + val topt = target match { + case d: Data if total => Some(d) + case _ => + // Record views that don't have the simpler .toTarget for later renaming + Builder.unnamedViews += view + None + } + // TODO We must also record children as unnamed, some could be namable but this requires changes to the Binding + getRecursiveFields.lazily(view, "_").foreach { + case (agg: Aggregate, _) if agg != view => + Builder.unnamedViews += agg + case _ => // Do nothing + } + agg.bind(AggregateViewBinding(resultBindings, topt)) + } + } + + // Traces an Element that may (or may not) be a view until it no longer maps + // Inclusive of the argument + private def unfoldView(elt: Element): LazyList[Element] = { + def rec(e: Element): LazyList[Element] = e.topBindingOpt match { + case Some(ViewBinding(target)) => target #:: rec(target) + case Some(AggregateViewBinding(mapping, _)) => + val target = mapping(e) + target #:: rec(target) + case Some(_) | None => LazyList.empty + } + elt #:: rec(elt) + } + + // Safe for all Data + private[chisel3] def isView(d: Data): Boolean = d._parent.contains(ViewParent) + + /** Turn any [[Element]] that could be a View into a concrete Element + * + * This is the fundamental "unwrapping" or "tracing" primitive operation for handling Views within + * Chisel. + */ + private[chisel3] def reify(elt: Element): Element = + reify(elt, elt.topBinding) + + /** Turn any [[Element]] that could be a View into a concrete Element + * + * This is the fundamental "unwrapping" or "tracing" primitive operation for handling Views within + * Chisel. + */ + @tailrec private[chisel3] def reify(elt: Element, topBinding: TopBinding): Element = + topBinding match { + case ViewBinding(target) => reify(target, elt.topBinding) + case _ => elt + } + + /** Determine the target of a View if it is a single Target + * + * @note An Aggregate may be a view of unrelated [[Data]] (eg. like a Seq or tuple) and thus this + * there is no single Data representing the Target and this function will return None + * @return The single Data target of this view or None if a single Data doesn't exist + */ + private[chisel3] def reifySingleData(data: Data): Option[Data] = { + val candidate: Option[Data] = + data.binding.collect { // First check if this is a total mapping of an Aggregate + case AggregateViewBinding(_, Some(t)) => t + }.orElse { // Otherwise look via top binding + data.topBindingOpt match { + case None => None + case Some(ViewBinding(target)) => Some(target) + case Some(AggregateViewBinding(lookup, _)) => lookup.get(data) + case Some(_) => None + } + } + candidate.flatMap { d => + // Candidate may itself be a view, keep tracing in those cases + if (isView(d)) reifySingleData(d) else Some(d) + } + } + +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala new file mode 100644 index 00000000..0cc3d131 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import scala.language.experimental.macros +import chisel3._ + +import scala.collection.mutable.HashMap +import chisel3.internal.{Builder, DynamicContext} +import chisel3.internal.sourceinfo.{DefinitionTransform, DefinitionWrapTransform, SourceInfo} +import chisel3.experimental.BaseModule +import chisel3.internal.BaseModule.IsClone +import firrtl.annotations.{IsModule, ModuleTarget} + +/** User-facing Definition type. + * Represents a definition of an object of type [[A]] which are marked as @instantiable + * Can be created using Definition.apply method. + * + * These definitions are then used to create multiple [[Instance]]s. + * + * @param cloned The internal representation of the instance, which may be either be directly the object, or a clone of an object + */ +case class Definition[+A] private[chisel3] (private[chisel3] cloned: Either[A, IsClone[A]]) extends IsLookupable { + private[chisel3] def proto: A = cloned match { + case Left(value: A) => value + case Right(i: IsClone[A]) => i._proto + } + /** Used by Chisel's internal macros. DO NOT USE in your normal Chisel code!!! + * Instead, mark the field you are accessing with [[@public]] + * + * Given a selector function (that) which selects a member from the original, return the + * corresponding member from the instance. + * + * Our @instantiable and @public macros generate the calls to this apply method + * + * By calling this function, we summon the proper Lookupable typeclass from our implicit scope. + * + * @param that a user-specified lookup function + * @param lookup typeclass which contains the correct lookup function, based on the types of A and B + * @param macroGenerated a value created in the macro, to make it harder for users to use this API + */ + def _lookup[B, C](that: A => B)(implicit lookup: Lookupable[B], macroGenerated: chisel3.internal.MacroGenerated): lookup.C = { + lookup.definitionLookup(that, this) + } + + /** Updated by calls to [[apply]], to avoid recloning returned Data's */ + private [chisel3] val cache = HashMap[Data, Data]() + + + /** @return the context of any Data's return from inside the instance */ + private[chisel3] def getInnerDataContext: Option[BaseModule] = proto match { + case value: BaseModule => + val newChild = Module.do_apply(new internal.BaseModule.DefinitionClone(value))(chisel3.internal.sourceinfo.UnlocatableSourceInfo, chisel3.ExplicitCompileOptions.Strict) + newChild._circuit = value._circuit.orElse(Some(value)) + newChild._parent = None + Some(newChild) + case value: IsInstantiable => None + } + +} + +/** Factory methods for constructing [[Definition]]s */ +object Definition extends SourceInfoDoc { + implicit class DefinitionBaseModuleExtensions[T <: BaseModule](d: Definition[T]) { + /** If this is an instance of a Module, returns the toTarget of this instance + * @return target of this instance + */ + def toTarget: ModuleTarget = d.proto.toTarget + + /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance + * @return absoluteTarget of this instance + */ + def toAbsoluteTarget: IsModule = d.proto.toAbsoluteTarget + } + /** A construction method to build a Definition of a Module + * + * @param proto the Module being defined + * + * @return the input module as a Definition + */ + def apply[T <: BaseModule with IsInstantiable](proto: => T): Definition[T] = macro DefinitionTransform.apply[T] + + /** A construction method to build a Definition of a Module + * + * @param bc the Module being defined + * + * @return the input module as a Definition + */ + def do_apply[T <: BaseModule with IsInstantiable](proto: => T) (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Definition[T] = { + val dynamicContext = new DynamicContext(Nil) + Builder.globalNamespace.copyTo(dynamicContext.globalNamespace) + dynamicContext.inDefinition = true + val (ir, module) = Builder.build(Module(proto), dynamicContext) + Builder.components ++= ir.components + Builder.annotations ++= ir.annotations + module._circuit = Builder.currentModule + dynamicContext.globalNamespace.copyTo(Builder.globalNamespace) + new Definition(Left(module)) + } +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala new file mode 100644 index 00000000..9b17bfce --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import scala.collection.mutable.{ArrayBuffer, HashMap} +import scala.language.experimental.macros +import chisel3._ +import chisel3.internal.BaseModule.{InstantiableClone, IsClone, ModuleClone} +import chisel3.internal.sourceinfo.{InstanceTransform, SourceInfo} +import chisel3.experimental.BaseModule +import firrtl.annotations.IsModule + +/** User-facing Instance type. + * Represents a unique instance of type [[A]] which are marked as @instantiable + * Can be created using Instance.apply method. + * + * @param cloned The internal representation of the instance, which may be either be directly the object, or a clone of an object + */ +case class Instance[+A] private [chisel3] (private[chisel3] cloned: Either[A, IsClone[A]]) { + + /** Returns the original object which is instantiated here. + * If this is an instance of a clone, return that clone's original proto + * + * @return the original object which was instantiated + */ + private[chisel3] def proto: A = cloned match { + case Left(value: A) => value + case Right(i: IsClone[A]) => i._proto + } + + /** @return the context of any Data's return from inside the instance */ + private[chisel3] def getInnerDataContext: Option[BaseModule] = cloned match { + case Left(value: BaseModule) => Some(value) + case Left(value: IsInstantiable) => None + case Right(i: BaseModule) => Some(i) + case Right(i: InstantiableClone[_]) => i._parent + } + + /** @return the context this instance. Note that for non-module clones, getInnerDataContext will be the same as getClonedParent */ + private[chisel3] def getClonedParent: Option[BaseModule] = cloned match { + case Left(value: BaseModule) => value._parent + case Right(i: BaseModule) => i._parent + case Right(i: InstantiableClone[_]) => i._parent + } + + /** Updated by calls to [[apply]], to avoid recloning returned Data's */ + private [chisel3] val cache = HashMap[Data, Data]() + + /** Used by Chisel's internal macros. DO NOT USE in your normal Chisel code!!! + * Instead, mark the field you are accessing with [[@public]] + * + * Given a selector function (that) which selects a member from the original, return the + * corresponding member from the instance. + * + * Our @instantiable and @public macros generate the calls to this apply method + * + * By calling this function, we summon the proper Lookupable typeclass from our implicit scope. + * + * @param that a user-specified lookup function + * @param lookup typeclass which contains the correct lookup function, based on the types of A and B + * @param macroGenerated a value created in the macro, to make it harder for users to use this API + */ + def _lookup[B, C](that: A => B)(implicit lookup: Lookupable[B], macroGenerated: chisel3.internal.MacroGenerated): lookup.C = { + lookup.instanceLookup(that, this) + } + + /** Returns the definition of this Instance */ + def toDefinition: Definition[A] = new Definition(Left(proto)) + +} + +/** Factory methods for constructing [[Instance]]s */ +object Instance extends SourceInfoDoc { + implicit class InstanceBaseModuleExtensions[T <: BaseModule](i: Instance[T]) { + /** If this is an instance of a Module, returns the toTarget of this instance + * @return target of this instance + */ + def toTarget: IsModule = i.cloned match { + case Left(x: BaseModule) => x.getTarget + case Right(x: IsClone[_] with BaseModule) => x.getTarget + } + + /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance + * @return absoluteTarget of this instance + */ + def toAbsoluteTarget: IsModule = i.cloned match { + case Left(x) => x.toAbsoluteTarget + case Right(x: IsClone[_] with BaseModule) => x.toAbsoluteTarget + } + + } + /** A constructs an [[Instance]] from a [[Definition]] + * + * @param definition the Module being created + * @return an instance of the module definition + */ + def apply[T <: BaseModule with IsInstantiable](definition: Definition[T]): Instance[T] = macro InstanceTransform.apply[T] + + /** A constructs an [[Instance]] from a [[Definition]] + * + * @param definition the Module being created + * @return an instance of the module definition + */ + def do_apply[T <: BaseModule with IsInstantiable](definition: Definition[T])(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Instance[T] = { + val ports = experimental.CloneModuleAsRecord(definition.proto) + val clone = ports._parent.get.asInstanceOf[ModuleClone[T]] + clone._madeFromDefinition = true + new Instance(Right(clone)) + } + +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala new file mode 100644 index 00000000..26ba0286 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +/** While this is public, it is not recommended for users to extend directly. + * Instead, use the [[@instantiable]] annotation on your trait or class. + * + * This trait indicates whether a class can be returned from an Instance. + * + */ +trait IsInstantiable + +object IsInstantiable { + implicit class IsInstantiableExtensions[T <: IsInstantiable](i: T) { + def toInstance: Instance[T] = new Instance(Left(i)) + } +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala new file mode 100644 index 00000000..37d29a43 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +/** A User-extendable trait to mark metadata-containers, e.g. parameter case classes, as valid to return unchanged + * from an instance. + * + * This should only be true of the metadata returned is identical for ALL instances! + * + * @example For instances of the same proto, metadata or other construction parameters + * may be useful to access outside of the instance construction. For parameters that are + * the same for all instances, we should mark it as IsLookupable + * {{{ + * case class Params(debugMessage: String) extends IsLookupable + * class MyModule(p: Params) extends MultiIOModule { + * printf(p.debugMessage) + * } + * val myParams = Params("Hello World") + * val definition = Definition(new MyModule(myParams)) + * val i0 = Instance(definition) + * val i1 = Instance(definition) + * require(i0.p == i1.p) // p is only accessable because it extends IsLookupable + * }}} + */ +trait IsLookupable diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala new file mode 100644 index 00000000..b9617723 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import chisel3.experimental.BaseModule +import chisel3.internal.sourceinfo.SourceInfo +import chisel3.internal.BaseModule.{InstanceClone, InstantiableClone, IsClone, ModuleClone} + +import scala.annotation.implicitNotFound +import scala.collection.mutable.HashMap +import chisel3._ +import chisel3.experimental.dataview.{isView, reify, reifySingleData} +import chisel3.internal.firrtl.{Arg, ILit, Index, Slot, ULit} +import chisel3.internal.{AggregateViewBinding, Builder, ChildBinding, ViewBinding, ViewParent, throwException} + +/** Represents lookup typeclass to determine how a value accessed from an original IsInstantiable + * should be tweaked to return the Instance's version + * Sealed. + */ +@implicitNotFound("@public is only legal within a class marked @instantiable and only on vals of type" + + " Data, BaseModule, IsInstantiable, IsLookupable, or Instance[_], or in an Iterable or Option") +sealed trait Lookupable[-B] { + type C // Return type of the lookup + /** Function called to modify the returned value of type B from A, into C + * + * @param that function that selects B from A + * @param instance Instance of A, used to determine C's context + * @return + */ + def instanceLookup[A](that: A => B, instance: Instance[A]): C + + /** Function called to modify the returned value of type B from A, into C + * + * @param that function that selects B from A + * @param definition Definition of A, used to determine C's context + * @return + */ + def definitionLookup[A](that: A => B, definition: Definition[A]): C +} + +private[chisel3] object Lookupable { + + /** Clones a data and sets its internal references to its parent module to be in a new context. + * + * @param data data to be cloned + * @param context new context + * @return + */ + private[chisel3] def cloneDataToContext[T <: Data](data: T, context: BaseModule) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { + internal.requireIsHardware(data, "cross module reference type") + data._parent match { + case None => data + case Some(parent) => + val newParent = cloneModuleToContext(Left(parent), context) + newParent match { + case Left(p) if p == parent => data + case Right(m: BaseModule) => + val newChild = data.cloneTypeFull + newChild.setRef(data.getRef, true) + newChild.bind(internal.CrossModuleBinding) + newChild.setAllParents(Some(m)) + newChild + } + } + } + // The business logic of lookupData + // Also called by cloneViewToContext which potentially needs to lookup stuff from ioMap or the cache + private[chisel3] def doLookupData[A, B <: Data](data: B, cache: HashMap[Data, Data], ioMap: Option[Map[Data, Data]], context: Option[BaseModule]) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): B = { + def impl[C <: Data](d: C): C = d match { + case x: Data if ioMap.nonEmpty && ioMap.get.contains(x) => ioMap.get(x).asInstanceOf[C] + case x: Data if cache.contains(x) => cache(x).asInstanceOf[C] + case _ => + assert(context.nonEmpty) // TODO is this even possible? Better error message here + val ret = cloneDataToContext(d, context.get) + cache(d) = ret + ret + } + data.binding match { + case Some(_: ChildBinding) => mapRootAndExtractSubField(data, impl) + case _ => impl(data) + } + } + + // Helper for co-iterating on Elements of aggregates, they must be the same type but that is unchecked + private def coiterate(a: Data, b: Data): Iterable[(Element, Element)] = { + val as = getRecursiveFields.lazily(a, "_") + val bs = getRecursiveFields.lazily(b, "_") + as.zip(bs).collect { case ((ae: Element, _), (be: Element, _)) => (ae, be) } + } + + /** Given a Data, find the root of its binding, apply a function to the root to get a "new root", + * and find the equivalent child Data in the "new root" + * + * @example {{{ + * Given `arg = a.b[2].c` and some `f`: + * 1. a = root(arg) = root(a.b[2].c) + * 2. newRoot = f(root(arg)) = f(a) + * 3. return newRoot.b[2].c + * }}} + * + * Invariants that elt is a Child of something of the type of data is dynamically checked as we traverse + */ + private def mapRootAndExtractSubField[A <: Data](arg: A, f: Data => Data): A = { + def err(msg: String) = throwException(s"Internal Error! $msg") + def unrollCoordinates(res: List[Arg], d: Data): (List[Arg], Data) = d.binding.get match { + case ChildBinding(parent) => d.getRef match { + case arg @ (_: Slot | _: Index) => unrollCoordinates(arg :: res, parent) + case other => err(s"Unroll coordinates failed for '$arg'! Unexpected arg '$other'") + } + case _ => (res, d) + } + def applyCoordinates(fullCoor: List[Arg], start: Data): Data = { + def rec(coor: List[Arg], d: Data): Data = { + if (coor.isEmpty) d + else { + val next = (coor.head, d) match { + case (Slot(_, name), rec: Record) => rec.elements(name) + case (Index(_, ILit(n)), vec: Vec[_]) => vec.apply(n.toInt) + case (arg, _) => err(s"Unexpected Arg '$arg' applied to '$d'! Root was '$start'.") + } + applyCoordinates(coor.tail, next) + } + } + rec(fullCoor, start) + } + val (coor, root) = unrollCoordinates(Nil, arg) + val newRoot = f(root) + val result = applyCoordinates(coor, newRoot) + try { + result.asInstanceOf[A] + } catch { + case _: ClassCastException => err(s"Applying '$coor' to '$newRoot' somehow resulted in '$result'") + } + } + + // TODO this logic is complicated, can any of it be unified with viewAs? + // If `.viewAs` would capture its arguments, we could potentially use it + // TODO Describe what this is doing at a high level + private[chisel3] def cloneViewToContext[A, B <: Data](data: B, cache: HashMap[Data, Data], ioMap: Option[Map[Data, Data]], context: Option[BaseModule]) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): B = { + // alias to shorten lookups + def lookupData[C <: Data](d: C) = doLookupData(d, cache, ioMap, context) + + val result = data.cloneTypeFull + + // We have to lookup the target(s) of the view since they may need to be cloned into the current context + val newBinding = data.topBinding match { + case ViewBinding(target) => ViewBinding(lookupData(reify(target))) + case avb @ AggregateViewBinding(map, targetOpt) => data match { + case _: Element => ViewBinding(lookupData(reify(map(data)))) + case _: Aggregate => + // Provide a 1:1 mapping if possible + val singleTargetOpt = targetOpt.filter(_ => avb == data.binding.get).flatMap(reifySingleData) + singleTargetOpt match { + case Some(singleTarget) => // It is 1:1! + // This is a little tricky because the values in newMap need to point to Elements of newTarget + val newTarget = lookupData(singleTarget) + val newMap = coiterate(result, data).map { case (res, from) => + (res: Data) -> mapRootAndExtractSubField(map(from), _ => newTarget) + }.toMap + AggregateViewBinding(newMap, Some(newTarget)) + + case None => // No 1:1 mapping so we have to do a flat binding + // Just remap each Element of this aggregate + val newMap = coiterate(result, data).map { + // Upcast res to Data since Maps are invariant in the Key type parameter + case (res, from) => (res: Data) -> lookupData(reify(map(from))) + }.toMap + AggregateViewBinding(newMap, None) + } + } + } + + // TODO Unify the following with `.viewAs` + // We must also mark non-1:1 and child Aggregates in the view for renaming + newBinding match { + case _: ViewBinding => // Do nothing + case AggregateViewBinding(_, target) => + if (target.isEmpty) { + Builder.unnamedViews += result + } + // Binding does not capture 1:1 for child aggregates views + getRecursiveFields.lazily(result, "_").foreach { + case (agg: Aggregate, _) if agg != result => + Builder.unnamedViews += agg + case _ => // Do nothing + } + } + + result.bind(newBinding) + result.setAllParents(Some(ViewParent)) + result.forceName(None, "view", Builder.viewNamespace) + result + } + /** Given a module (either original or a clone), clone it to a new context + * + * This function effectively recurses up the parents of module to find whether: + * (1) A parent is already in the context; then we do nothing and return module + * (2) A parent is in a different clone of the context; then we clone all the parents up + * to that parent and set their parents to be in this cloned context + * (3) A parent has no root; in that case, we do nothing and return the module. + * + * @param module original or clone to be cloned into a new context + * @param context new context + * @return original or clone in the new context + */ + private[chisel3] def cloneModuleToContext[T <: BaseModule](module: Either[T, IsClone[T]], context: BaseModule) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Either[T, IsClone[T]] = { + // Recursive call + def rec[A <: BaseModule](m: A): Either[A, IsClone[A]] = { + def clone(x: A, p: Option[BaseModule], name: () => String): Either[A, IsClone[A]] = { + val newChild = Module.do_apply(new internal.BaseModule.InstanceClone(x, name)) + newChild._parent = p + Right(newChild) + } + (m, context) match { + case (c, ctx) if ctx == c => Left(c) + case (c, ctx: IsClone[_]) if ctx.isACloneOf(c) => Right(ctx.asInstanceOf[IsClone[A]]) + case (c, ctx) if c._parent.isEmpty => Left(c) + case (_, _) => + cloneModuleToContext(Left(m._parent.get), context) match { + case Left(p) => Left(m) + case Right(p: BaseModule) => + clone(m, Some(p), () => m.instanceName) + } + } + } + module match { + case Left(m) => rec(m) + case Right(m: ModuleClone[_]) => + rec(m) match { + case Left(mx) => Right(mx) + case Right(i: InstanceClone[_]) => + val newChild = Module.do_apply(new InstanceClone(m._proto, () => m.instanceName)) + newChild._parent = i._parent + Right(newChild) + } + case Right(m: InstanceClone[_]) => + rec(m) match { + case Left(mx) => Right(mx) + case Right(i: InstanceClone[_]) => + val newChild = Module.do_apply(new InstanceClone(m._proto, () => m.instanceName)) + newChild._parent = i._parent + Right(newChild) + } + } + } + + class SimpleLookupable[X] extends Lookupable[X] { + type B = X + type C = X + def definitionLookup[A](that: A => B, definition: Definition[A]): C = that(definition.proto) + def instanceLookup[A](that: A => B, instance: Instance[A]): C = that(instance.proto) + } + + implicit def lookupInstance[B <: BaseModule](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[Instance[B]] { + type C = Instance[B] + def definitionLookup[A](that: A => Instance[B], definition: Definition[A]): C = { + val ret = that(definition.proto) + new Instance(cloneModuleToContext(ret.cloned, definition.getInnerDataContext.get)) + } + def instanceLookup[A](that: A => Instance[B], instance: Instance[A]): C = { + val ret = that(instance.proto) + instance.cloned match { + // If instance is just a normal module, no changing of context is necessary + case Left(_) => new Instance(ret.cloned) + case Right(_) => new Instance(cloneModuleToContext(ret.cloned, instance.getInnerDataContext.get)) + } + } + } + + implicit def lookupModule[B <: BaseModule](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[B] { + type C = Instance[B] + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + val ret = that(definition.proto) + new Instance(cloneModuleToContext(Left(ret), definition.getInnerDataContext.get)) + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + val ret = that(instance.proto) + instance.cloned match { + // If instance is just a normal module, no changing of context is necessary + case Left(_) => new Instance(Left(ret)) + case Right(_) => new Instance(cloneModuleToContext(Left(ret), instance.getInnerDataContext.get)) + } + } + } + + implicit def lookupData[B <: Data](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[B] { + type C = B + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + val ret = that(definition.proto) + if (isView(ret)) { + ??? // TODO!!!!!! cloneViewToContext(ret, instance, ioMap, instance.getInnerDataContext) + } else { + doLookupData(ret, definition.cache, None, definition.getInnerDataContext) + } + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + val ret = that(instance.proto) + val ioMap: Option[Map[Data, Data]] = instance.cloned match { + case Right(x: ModuleClone[_]) => Some(x.ioMap) + case Left(x: BaseModule) => Some(x.getChiselPorts.map { case (_, data) => data -> data }.toMap) + case _ => None + } + if (isView(ret)) { + cloneViewToContext(ret, instance.cache, ioMap, instance.getInnerDataContext) + } else { + doLookupData(ret, instance.cache, ioMap, instance.getInnerDataContext) + } + + } + } + + import scala.language.higherKinds // Required to avoid warning for lookupIterable type parameter + implicit def lookupIterable[B, F[_] <: Iterable[_]](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions, lookupable: Lookupable[B]) = new Lookupable[F[B]] { + type C = F[lookupable.C] + def definitionLookup[A](that: A => F[B], definition: Definition[A]): C = { + val ret = that(definition.proto).asInstanceOf[Iterable[B]] + ret.map{ x: B => lookupable.definitionLookup[A](_ => x, definition) }.asInstanceOf[C] + } + def instanceLookup[A](that: A => F[B], instance: Instance[A]): C = { + import instance._ + val ret = that(proto).asInstanceOf[Iterable[B]] + ret.map{ x: B => lookupable.instanceLookup[A](_ => x, instance) }.asInstanceOf[C] + } + } + implicit def lookupOption[B](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions, lookupable: Lookupable[B]) = new Lookupable[Option[B]] { + type C = Option[lookupable.C] + def definitionLookup[A](that: A => Option[B], definition: Definition[A]): C = { + val ret = that(definition.proto) + ret.map{ x: B => lookupable.definitionLookup[A](_ => x, definition) } + } + def instanceLookup[A](that: A => Option[B], instance: Instance[A]): C = { + import instance._ + val ret = that(proto) + ret.map{ x: B => lookupable.instanceLookup[A](_ => x, instance) } + } + } + implicit def lookupIsInstantiable[B <: IsInstantiable](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[B] { + type C = Instance[B] + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + val ret = that(definition.proto) + val cloned = new InstantiableClone(ret) + cloned._parent = definition.getInnerDataContext + new Instance(Right(cloned)) + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + val ret = that(instance.proto) + val cloned = new InstantiableClone(ret) + cloned._parent = instance.getInnerDataContext + new Instance(Right(cloned)) + } + } + + implicit def lookupIsLookupable[B <: IsLookupable](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new SimpleLookupable[B]() + + implicit val lookupInt = new SimpleLookupable[Int]() + implicit val lookupByte = new SimpleLookupable[Byte]() + implicit val lookupShort = new SimpleLookupable[Short]() + implicit val lookupLong = new SimpleLookupable[Long]() + implicit val lookupFloat = new SimpleLookupable[Float]() + implicit val lookupChar = new SimpleLookupable[Char]() + implicit val lookupString = new SimpleLookupable[String]() + implicit val lookupBoolean = new SimpleLookupable[Boolean]() + implicit val lookupBigInt = new SimpleLookupable[BigInt]() +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/package.scala b/core/src/main/scala/chisel3/experimental/hierarchy/package.scala new file mode 100644 index 00000000..c309ab52 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/package.scala @@ -0,0 +1,48 @@ +package chisel3.experimental + +package object hierarchy { + + /** Classes or traits which will be used with the [[Definition]] + [[Instance]] api should be marked + * with the [[@instantiable]] annotation at the class/trait definition. + * + * @example {{{ + * @instantiable + * class MyModule extends Module { + * ... + * } + * + * val d = Definition(new MyModule) + * val i0 = Instance(d) + * val i1 = Instance(d) + * }}} + */ + class instantiable extends chisel3.internal.instantiable + + /** Classes marked with [[@instantiable]] can have their vals marked with the [[@public]] annotation to + * enable accessing these values from a [[Definition]] or [[Instance]] of the class. + * + * Only vals of the the following types can be marked [[@public]]: + * 1. IsInstantiable + * 2. IsLookupable + * 3. Data + * 4. BaseModule + * 5. Iterable/Option containing a type that meets these requirements + * 6. Basic type like String, Int, BigInt etc. + * + * @example {{{ + * @instantiable + * class MyModule extends Module { + * @public val in = IO(Input(UInt(3.W))) + * @public val out = IO(Output(UInt(3.W))) + * .. + * } + * + * val d = Definition(new MyModule) + * val i0 = Instance(d) + * val i1 = Instance(d) + * + * i1.in := i0.out + * }}} + */ + class public extends chisel3.internal.public +} diff --git a/core/src/main/scala/chisel3/experimental/package.scala b/core/src/main/scala/chisel3/experimental/package.scala index ec3f2a79..8018159f 100644 --- a/core/src/main/scala/chisel3/experimental/package.scala +++ b/core/src/main/scala/chisel3/experimental/package.scala @@ -2,6 +2,9 @@ package chisel3 +import chisel3.internal.NamedComponent +import chisel3.internal.sourceinfo.SourceInfo + /** Package for experimental features, which may have their API changed, be removed, etc. * * Because its contents won't necessarily have the same level of stability and support as @@ -20,13 +23,6 @@ package object experimental { type ChiselEnum = EnumFactory - @deprecated("Use the version in chisel3._", "3.2") - val withClockAndReset = chisel3.withClockAndReset - @deprecated("Use the version in chisel3._", "3.2") - val withClock = chisel3.withClock - @deprecated("Use the version in chisel3._", "3.2") - val withReset = chisel3.withReset - // Rocket Chip-style clonemodule /** A record containing the results of CloneModuleAsRecord @@ -131,14 +127,48 @@ package object experimental { object BundleLiterals { implicit class AddBundleLiteralConstructor[T <: Record](x: T) { - def Lit(elems: (T => (Data, Data))*): T = { + def Lit(elems: (T => (Data, Data))*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { x._makeLit(elems: _*) } } } + /** This class provides the `Lit` method needed to define a `Vec` literal + */ + object VecLiterals { + implicit class AddVecLiteralConstructor[T <: Data](x: Vec[T]) { + /** Given a generator of a list tuples of the form [Int, Data] + * constructs a Vec literal, parallel concept to `BundleLiteral` + * + * @param elems tuples of an index and a literal value + * @return + */ + def Lit(elems: (Int, T)*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = { + x._makeLit(elems: _*) + } + } + + implicit class AddObjectLiteralConstructor(x: Vec.type) { + /** This provides an literal construction method for cases using + * object `Vec` as in `Vec.Lit(1.U, 2.U)` + */ + def Lit[T <: Data](elems: T*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = { + require(elems.nonEmpty, s"Lit.Vec(...) must have at least one element") + val indexElements = elems.zipWithIndex.map { case (element, index) => (index, element)} + val widestElement = elems.maxBy(_.getWidth) + val vec: Vec[T] = Vec.apply(indexElements.length, chiselTypeOf(widestElement)) + vec.Lit(indexElements:_*) + } + } + } + // Use to add a prefix to any component generated in input scope val prefix = chisel3.internal.prefix // Use to remove prefixes not in provided scope val noPrefix = chisel3.internal.noPrefix + + /** Base simulation-only component. */ + abstract class BaseSim extends NamedComponent { + _parent.foreach(_.addId(this)) + } } diff --git a/core/src/main/scala/chisel3/experimental/verification/package.scala b/core/src/main/scala/chisel3/experimental/verification/package.scala index 5c71bd5f..190083fd 100644 --- a/core/src/main/scala/chisel3/experimental/verification/package.scala +++ b/core/src/main/scala/chisel3/experimental/verification/package.scala @@ -2,40 +2,59 @@ package chisel3.experimental -import chisel3.{Bool, CompileOptions} +import chisel3._ import chisel3.internal.Builder -import chisel3.internal.Builder.pushCommand import chisel3.internal.firrtl.{Formal, Verification} import chisel3.internal.sourceinfo.SourceInfo package object verification { + object assert { + /** Named class for assertions. */ + final class Assert(private[chisel3] val predicate: Bool) extends BaseSim + + def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions): Unit = { - val clock = Builder.forcedClock - pushCommand(Verification(Formal.Assert, sourceInfo, clock.ref, - predicate.ref, msg)) + compileOptions: CompileOptions): Assert = { + val a = new Assert(predicate) + when (!Module.reset.asBool) { + val clock = Module.clock + Builder.pushCommand(Verification(a, Formal.Assert, sourceInfo, clock.ref, predicate.ref, msg)) + } + a } } object assume { + /** Named class for assumes. */ + final class Assume(private[chisel3] val predicate: Bool) extends BaseSim + def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions): Unit = { - val clock = Builder.forcedClock - pushCommand(Verification(Formal.Assume, sourceInfo, clock.ref, - predicate.ref, msg)) + compileOptions: CompileOptions): Assume = { + val a = new Assume(predicate) + when (!Module.reset.asBool) { + val clock = Module.clock + Builder.pushCommand(Verification(a, Formal.Assume, sourceInfo, clock.ref, predicate.ref, msg)) + } + a } } object cover { + /** Named class for covers. */ + final class Cover(private[chisel3] val predicate: Bool) extends BaseSim + def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions): Unit = { - val clock = Builder.forcedClock - pushCommand(Verification(Formal.Cover, sourceInfo, clock.ref, - predicate.ref, msg)) + compileOptions: CompileOptions): Cover = { + val clock = Module.clock + val c = new Cover(predicate) + when (!Module.reset.asBool) { + Builder.pushCommand(Verification(c, Formal.Cover, sourceInfo, clock.ref, predicate.ref, msg)) + } + c } } } diff --git a/core/src/main/scala/chisel3/internal/BiConnect.scala b/core/src/main/scala/chisel3/internal/BiConnect.scala index 1ee149ee..aa58cb95 100644 --- a/core/src/main/scala/chisel3/internal/BiConnect.scala +++ b/core/src/main/scala/chisel3/internal/BiConnect.scala @@ -3,12 +3,16 @@ package chisel3.internal import chisel3._ +import chisel3.experimental.dataview.reify import chisel3.experimental.{Analog, BaseModule, attach} import chisel3.internal.Builder.pushCommand import chisel3.internal.firrtl.{Connect, DefInvalid} + import scala.language.experimental.macros import chisel3.internal.sourceinfo._ +import scala.annotation.tailrec + /** * BiConnect.connect executes a bidirectional connection element-wise. * @@ -113,14 +117,33 @@ private[chisel3] object BiConnect { } } } - // Handle Records defined in Chisel._ code (change to NotStrict) - case (left_r: Record, right_r: Record) => (left_r.compileOptions, right_r.compileOptions) match { - case (ExplicitCompileOptions.NotStrict, _) => - left_r.bulkConnect(right_r)(sourceInfo, ExplicitCompileOptions.NotStrict) - case (_, ExplicitCompileOptions.NotStrict) => - left_r.bulkConnect(right_r)(sourceInfo, ExplicitCompileOptions.NotStrict) - case _ => recordConnect(sourceInfo, connectCompileOptions, left_r, right_r, context_mod) - } + // Handle Records defined in Chisel._ code by emitting a FIRRTL partial connect + case pair @ (left_r: Record, right_r: Record) => + val notStrict = + Seq(left_r.compileOptions, right_r.compileOptions).contains(ExplicitCompileOptions.NotStrict) + if (notStrict) { + // Traces flow from a child Data to its parent + @tailrec def traceFlow(currentlyFlipped: Boolean, data: Data): Boolean = { + import SpecifiedDirection.{Input => SInput, Flip => SFlip} + val sdir = data.specifiedDirection + val flipped = sdir == SInput || sdir == SFlip + data.binding.get match { + case ChildBinding(parent) => traceFlow(flipped ^ currentlyFlipped, parent) + case PortBinding(enclosure) => + val childPort = enclosure != context_mod + childPort ^ flipped ^ currentlyFlipped + case _ => true + } + } + def canBeSink(data: Data): Boolean = traceFlow(true, data) + def canBeSource(data: Data): Boolean = traceFlow(false, data) + // chisel3 <> is commutative but FIRRTL <- is not + val flipConnection = !canBeSink(left_r) || !canBeSource(right_r) + val (newLeft, newRight) = if (flipConnection) pair.swap else pair + newLeft.bulkConnect(newRight)(sourceInfo, ExplicitCompileOptions.NotStrict) + } else { + recordConnect(sourceInfo, connectCompileOptions, left_r, right_r, context_mod) + } // Handle Records connected to DontCare (change to NotStrict) case (left_r: Record, DontCare) => @@ -215,8 +238,10 @@ private[chisel3] object BiConnect { // This function checks if element-level connection operation allowed. // Then it either issues it or throws the appropriate exception. - def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, left: Element, right: Element, context_mod: RawModule): Unit = { + def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, _left: Element, _right: Element, context_mod: RawModule): Unit = { import BindingDirection.{Internal, Input, Output} // Using extensively so import these + val left = reify(_left) + val right = reify(_right) // If left or right have no location, assume in context module // This can occur if one of them is a literal, unbound will error previously val left_mod: BaseModule = left.topBinding.location.getOrElse(context_mod) diff --git a/core/src/main/scala/chisel3/internal/Binding.scala b/core/src/main/scala/chisel3/internal/Binding.scala index 4442c62e..a0dcc20c 100644 --- a/core/src/main/scala/chisel3/internal/Binding.scala +++ b/core/src/main/scala/chisel3/internal/Binding.scala @@ -6,6 +6,8 @@ import chisel3._ import chisel3.experimental.BaseModule import chisel3.internal.firrtl.LitArg +import scala.collection.immutable.VectorMap + /** Requires that a node is hardware ("bound") */ object requireIsHardware { @@ -110,12 +112,32 @@ case class ChildBinding(parent: Data) extends Binding { case class SampleElementBinding[T <: Data](parent: Vec[T]) extends Binding { def location = parent.topBinding.location } +/** Special binding for Mem types */ +case class MemTypeBinding[T <: Data](parent: MemBase[T]) extends Binding { + def location: Option[BaseModule] = parent._parent +} // A DontCare element has a specific Binding, somewhat like a literal. // It is a source (RHS). It may only be connected/applied to sinks. case class DontCareBinding() extends UnconstrainedBinding +// Views currently only support 1:1 Element-level mappings +private[chisel3] case class ViewBinding(target: Element) extends UnconstrainedBinding +/** Binding for Aggregate Views + * @param childMap Mapping from children of this view to each child's target + * @param target Optional Data this Aggregate views if the view is total and the target is a Data + */ +private[chisel3] case class AggregateViewBinding(childMap: Map[Data, Element], target: Option[Data]) extends UnconstrainedBinding + + +/** Binding for Data's returned from accessing an Instance/Definition members, if not readable/writable port */ +private[chisel3] case object CrossModuleBinding extends TopBinding { + def location = None +} + sealed trait LitBinding extends UnconstrainedBinding with ReadOnlyBinding // Literal binding attached to a element that is not part of a Bundle. case class ElementLitBinding(litArg: LitArg) extends LitBinding // Literal binding attached to the root of a Bundle, containing literal values of its children. case class BundleLitBinding(litMap: Map[Data, LitArg]) extends LitBinding +// Literal binding attached to the root of a Vec, containing literal values of its children. +case class VecLitBinding(litMap: VectorMap[Data, LitArg]) extends LitBinding diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index b7772aea..441abc92 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -6,10 +6,13 @@ import scala.util.DynamicVariable import scala.collection.mutable.ArrayBuffer import chisel3._ import chisel3.experimental._ +import chisel3.experimental.hierarchy.Instance import chisel3.internal.firrtl._ import chisel3.internal.naming._ import _root_.firrtl.annotations.{CircuitName, ComponentName, IsMember, ModuleName, Named, ReferenceTarget} -import _root_.firrtl.annotations.AnnotationUtils.{validComponentName} +import _root_.firrtl.annotations.AnnotationUtils.validComponentName +import _root_.firrtl.{AnnotationSeq, RenameMap} +import chisel3.experimental.dataview.{reify, reifySingleData} import chisel3.internal.Builder.Prefix import logger.LazyLogging @@ -17,6 +20,7 @@ import scala.collection.mutable private[chisel3] class Namespace(keywords: Set[String]) { private val names = collection.mutable.HashMap[String, Long]() + def copyTo(other: Namespace): Unit = names.foreach { case (s: String, l: Long) => other.names(s) = l } for (keyword <- keywords) names(keyword) = 1 @@ -83,8 +87,10 @@ trait InstanceId { private[chisel3] trait HasId extends InstanceId { private[chisel3] def _onModuleClose: Unit = {} - private[chisel3] val _parent: Option[BaseModule] = Builder.currentModule - _parent.foreach(_.addId(this)) + private[chisel3] var _parent: Option[BaseModule] = Builder.currentModule + + // Set if the returned top-level module of a nested call to the Chisel Builder, see Definition.apply + private[chisel3] var _circuit: Option[BaseModule] = None private[chisel3] val _id: Long = Builder.idGen.next @@ -215,32 +221,53 @@ private[chisel3] trait HasId extends InstanceId { private[chisel3] def getRef: Arg = _ref.get private[chisel3] def getOptionRef: Option[Arg] = _ref + private def refName(c: Component): String = _ref match { + case Some(arg) => arg fullName c + case None => computeName(None, None).get + } + + // Helper for reifying views if they map to a single Target + private[chisel3] def reifyTarget: Option[Data] = this match { + case d: Data => reifySingleData(d) // Only Data can be views + case bad => throwException(s"This shouldn't be possible - got $bad with ${_parent}") + } + + // Helper for reifying the parent of a view if the view maps to a single Target + private[chisel3] def reifyParent: BaseModule = reifyTarget.flatMap(_._parent).getOrElse(ViewParent) + // Implementation of public methods. def instanceName: String = _parent match { - case Some(p) => p._component match { - case Some(c) => _ref match { - case Some(arg) => arg fullName c - case None => computeName(None, None).get + case Some(ViewParent) => reifyTarget.map(_.instanceName).getOrElse(this.refName(ViewParent.fakeComponent)) + case Some(p) => + (p._component, this) match { + case (Some(c), _) => refName(c) + case (None, d: Data) if d.topBindingOpt == Some(CrossModuleBinding) => _ref.get.localName + case (None, _) => throwException(s"signalName/pathName should be called after circuit elaboration: $this, ${_parent}") } - case None => throwException("signalName/pathName should be called after circuit elaboration") - } case None => throwException("this cannot happen") } def pathName: String = _parent match { case None => instanceName + case Some(ViewParent) => s"${reifyParent.pathName}.$instanceName" case Some(p) => s"${p.pathName}.$instanceName" } def parentPathName: String = _parent match { + case Some(ViewParent) => reifyParent.pathName case Some(p) => p.pathName case None => throwException(s"$instanceName doesn't have a parent") } def parentModName: String = _parent match { + case Some(ViewParent) => reifyParent.name case Some(p) => p.name case None => throwException(s"$instanceName doesn't have a parent") } // TODO Should this be public? protected def circuitName: String = _parent match { - case None => instanceName + case None => _circuit match { + case None => instanceName + case Some(o) => o.circuitName + } + case Some(ViewParent) => reifyParent.circuitName case Some(p) => p.circuitName } @@ -279,8 +306,12 @@ private[chisel3] trait NamedComponent extends HasId { val name = this.instanceName if (!validComponentName(name)) throwException(s"Illegal component name: $name (note: literals are illegal)") import _root_.firrtl.annotations.{Target, TargetToken} + val root = _parent.map { + case ViewParent => reifyParent + case other => other + }.get.getTarget // All NamedComponents will have a parent, only the top module can have None here Target.toTargetTokens(name).toList match { - case TargetToken.Ref(r) :: components => ReferenceTarget(this.circuitName, this.parentModName, Nil, r, components) + case TargetToken.Ref(r) :: components => root.ref(r).copy(component = components) case other => throw _root_.firrtl.annotations.Target.NamedException(s"Cannot convert $name into [[ReferenceTarget]]: $other") } @@ -288,8 +319,10 @@ private[chisel3] trait NamedComponent extends HasId { final def toAbsoluteTarget: ReferenceTarget = { val localTarget = toTarget + def makeTarget(p: BaseModule) = p.toAbsoluteTarget.ref(localTarget.ref).copy(component = localTarget.component) _parent match { - case Some(parent) => parent.toAbsoluteTarget.ref(localTarget.ref).copy(component = localTarget.component) + case Some(ViewParent) => makeTarget(reifyParent) + case Some(parent) => makeTarget(parent) case None => localTarget } } @@ -304,19 +337,29 @@ private[chisel3] class ChiselContext() { // Records the different prefixes which have been scoped at this point in time var prefixStack: Prefix = Nil + + // Views belong to a separate namespace (for renaming) + // The namespace outside of Builder context is useless, but it ensures that views can still be created + // and the resulting .toTarget is very clearly useless (_$$View$$_...) + val viewNamespace = Namespace.empty } -private[chisel3] class DynamicContext() { +private[chisel3] class DynamicContext(val annotationSeq: AnnotationSeq) { val globalNamespace = Namespace.empty val components = ArrayBuffer[Component]() val annotations = ArrayBuffer[ChiselAnnotation]() var currentModule: Option[BaseModule] = None + // This is only used for testing, it can be removed if the plugin becomes mandatory + var allowReflectiveAutoCloneType = true /** Contains a mapping from a elaborated module to their aspect * Set by [[ModuleAspect]] */ val aspectModule: mutable.HashMap[BaseModule, BaseModule] = mutable.HashMap.empty[BaseModule, BaseModule] + // Views that do not correspond to a single ReferenceTarget and thus require renaming + val unnamedViews: ArrayBuffer[Data] = ArrayBuffer.empty + // Set by object Module.apply before calling class Module constructor // Used to distinguish between no Module() wrapping, multiple wrappings, and rewrapping var readyForModuleConstr: Boolean = false @@ -325,6 +368,8 @@ private[chisel3] class DynamicContext() { var currentReset: Option[Reset] = None val errors = new ErrorLog val namingStack = new NamingStack + // Used to indicate if this is the top-level module of full elaboration, or from a Definition + var inDefinition: Boolean = false } private[chisel3] object Builder extends LazyLogging { @@ -339,6 +384,11 @@ private[chisel3] object Builder extends LazyLogging { dynamicContextVar.value.get } + // Returns the current dynamic context + def captureContext(): DynamicContext = dynamicContext + // Sets the current dynamic contents + def restoreContext(dc: DynamicContext) = dynamicContextVar.value = Some(dc) + // Ensure we have a thread-specific ChiselContext private val chiselContext = new ThreadLocal[ChiselContext]{ override def initialValue: ChiselContext = { @@ -365,8 +415,12 @@ private[chisel3] object Builder extends LazyLogging { def globalNamespace: Namespace = dynamicContext.globalNamespace def components: ArrayBuffer[Component] = dynamicContext.components def annotations: ArrayBuffer[ChiselAnnotation] = dynamicContext.annotations + def annotationSeq: AnnotationSeq = dynamicContext.annotationSeq def namingStack: NamingStack = dynamicContext.namingStack + def unnamedViews: ArrayBuffer[Data] = dynamicContext.unnamedViews + def viewNamespace: Namespace = chiselContext.get.viewNamespace + // Puts a prefix string onto the prefix stack def pushPrefix(d: String): Unit = { val context = chiselContext.get() @@ -400,6 +454,7 @@ private[chisel3] object Builder extends LazyLogging { case PortBinding(mod) if Builder.currentModule.contains(mod) => data.seedOpt case PortBinding(mod) => map2(mod.seedOpt, data.seedOpt)(_ + "_" + _) case (_: LitBinding | _: DontCareBinding) => None + case _ => Some("view_") // TODO implement } id match { case d: Data => recData(d) @@ -529,6 +584,22 @@ private[chisel3] object Builder extends LazyLogging { dynamicContext.currentReset = newReset } + def inDefinition: Boolean = { + dynamicContextVar.value + .map(_.inDefinition) + .getOrElse(false) + } + + // This should only be used for testing, must be true outside of Builder context + def allowReflectiveAutoCloneType: Boolean = { + dynamicContextVar.value + .map(_.allowReflectiveAutoCloneType) + .getOrElse(true) + } + def allowReflectiveAutoCloneType_=(value: Boolean): Unit = { + dynamicContext.allowReflectiveAutoCloneType = value + } + def forcedClock: Clock = currentClock.getOrElse( throwException("Error: No implicit clock.") ) @@ -588,6 +659,10 @@ private[chisel3] object Builder extends LazyLogging { * (Note: Map is Iterable[Tuple2[_,_]] and thus excluded) */ def nameRecursively(prefix: String, nameMe: Any, namer: (HasId, String) => Unit): Unit = nameMe match { + case (id: Instance[_]) => id.cloned match { + case Right(m: internal.BaseModule.ModuleClone[_]) => namer(m.getPorts, prefix) + case _ => + } case (id: HasId) => namer(id, prefix) case Some(elt) => nameRecursively(prefix, elt, namer) case (iter: Iterable[_]) if iter.hasDefiniteSize => @@ -633,21 +708,37 @@ private[chisel3] object Builder extends LazyLogging { } } - - def build[T <: RawModule](f: => T): (Circuit, T) = { - build(f, new DynamicContext()) + // Builds a RenameMap for all Views that do not correspond to a single Data + // These Data give a fake ReferenceTarget for .toTarget and .toReferenceTarget that the returned + // RenameMap can split into the constituent parts + private[chisel3] def makeViewRenameMap: RenameMap = { + val renames = RenameMap() + for (view <- unnamedViews) { + val localTarget = view.toTarget + val absTarget = view.toAbsoluteTarget + val elts = getRecursiveFields.lazily(view, "") + .collect { case (elt: Element, _) => elt } + for (elt <- elts) { + val targetOfView = reify(elt) + renames.record(localTarget, targetOfView.toTarget) + renames.record(absTarget, targetOfView.toAbsoluteTarget) + } + } + renames } - private [chisel3] def build[T <: RawModule](f: => T, dynamicContext: DynamicContext): (Circuit, T) = { + private [chisel3] def build[T <: BaseModule](f: => T, dynamicContext: DynamicContext): (Circuit, T) = { dynamicContextVar.withValue(Some(dynamicContext)) { + ViewParent // Must initialize the singleton in a Builder context or weird things can happen + // in tiny designs/testcases that never access anything in chisel3.internal checkScalaVersion() - logger.warn("Elaborating design...") + logger.info("Elaborating design...") val mod = f mod.forceName(None, mod.name, globalNamespace) - errors.checkpoint() - logger.warn("Done elaborating.") + errors.checkpoint(logger) + logger.info("Done elaborating.") - (Circuit(components.last.name, components, annotations), mod) + (Circuit(components.last.name, components.toSeq, annotations.toSeq, makeViewRenameMap), mod) } } initializeSingletons() diff --git a/core/src/main/scala/chisel3/internal/Error.scala b/core/src/main/scala/chisel3/internal/Error.scala index 6a1794ce..d6e0c0e6 100644 --- a/core/src/main/scala/chisel3/internal/Error.scala +++ b/core/src/main/scala/chisel3/internal/Error.scala @@ -4,13 +4,84 @@ package chisel3.internal import scala.annotation.tailrec import scala.collection.mutable.{ArrayBuffer, LinkedHashMap} +import _root_.logger.Logger -class ChiselException(message: String, cause: Throwable = null) extends Exception(message, cause) { +object ExceptionHelpers { + + /** Root packages that are not typically relevant to Chisel user code. */ + final val packageTrimlist: Set[String] = Set("chisel3", "scala", "java", "jdk", "sun", "sbt") + + /** The object name of Chisel's internal `Builder`. */ + final val builderName: String = chisel3.internal.Builder.getClass.getName + + /** Return a stack trace element that looks like `... (someMessage)`. + * @param message an optional message to include + */ + def ellipsis(message: Option[String] = None): StackTraceElement = + new StackTraceElement("..", " ", message.getOrElse(""), -1) + + /** Utility methods that can be added to exceptions. + */ + implicit class ThrowableHelpers(throwable: Throwable) { + + /** For an exception, mutably trim a stack trace to user code only. + * + * This does the following actions to the stack trace: + * + * 1. From the top, remove elements while the (root) package matches the packageTrimlist + * 2. Optionally, from the bottom, remove elements until the class matches an anchor + * 3. From the anchor (or the bottom), remove elements while the (root) package matches the packageTrimlist + * + * @param packageTrimlist packages that should be removed from the stack trace + * @param anchor an optional class name at which user execution might begin, e.g., a main object + * @return nothing as this mutates the exception directly + */ + def trimStackTraceToUserCode( + packageTrimlist: Set[String] = packageTrimlist, + anchor: Option[String] = Some(builderName) + ): Unit = { + def inTrimlist(ste: StackTraceElement) = { + val packageName = ste.getClassName().takeWhile(_ != '.') + packageTrimlist.contains(packageName) + } + + // Step 1: Remove elements from the top in the package trimlist + ((a: Array[StackTraceElement]) => a.dropWhile(inTrimlist)) + // Step 2: Optionally remove elements from the bottom until the anchor + .andThen(_.reverse) + .andThen( a => + anchor match { + case Some(b) => a.dropWhile(ste => !ste.getClassName.startsWith(b)) + case None => a + } + ) + // Step 3: Remove elements from the bottom in the package trimlist + .andThen(_.dropWhile(inTrimlist)) + // Step 4: Reverse back to the original order + .andThen(_.reverse.toArray) + // Step 5: Add ellipsis stack trace elements and "--full-stacktrace" info + .andThen(a => + ellipsis() +: + a :+ + ellipsis() :+ + ellipsis(Some("Stack trace trimmed to user code only. Rerun with --full-stacktrace to see the full stack trace"))) + // Step 5: Mutate the stack trace in this exception + .andThen(throwable.setStackTrace(_)) + .apply(throwable.getStackTrace) + } + + } + +} + +class ChiselException(message: String, cause: Throwable = null) extends Exception(message, cause, true, true) { /** Package names whose stack trace elements should be trimmed when generating a trimmed stack trace */ + @deprecated("Use ExceptionHelpers.packageTrimlist. This will be removed in Chisel 3.6", "3.5") val blacklistPackages: Set[String] = Set("chisel3", "scala", "java", "sun", "sbt") /** The object name of Chisel's internal `Builder`. Everything stack trace element after this will be trimmed. */ + @deprecated("Use ExceptionHelpers.builderName. This will be removed in Chisel 3.6", "3.5") val builderName: String = chisel3.internal.Builder.getClass.getName /** Examine a [[Throwable]], to extract all its causes. Innermost cause is first. @@ -27,7 +98,7 @@ class ChiselException(message: String, cause: Throwable = null) extends Exceptio /** Returns true if an exception contains */ private def containsBuilder(throwable: Throwable): Boolean = throwable.getStackTrace().collectFirst { - case ste if ste.getClassName().startsWith(builderName) => throwable + case ste if ste.getClassName().startsWith(ExceptionHelpers.builderName) => throwable }.isDefined /** Examine this [[ChiselException]] and it's causes for the first [[Throwable]] that contains a stack trace including @@ -55,19 +126,12 @@ class ChiselException(message: String, cause: Throwable = null) extends Exceptio } val trimmedLeft = throwable.getStackTrace().view.dropWhile(isBlacklisted) - val trimmedReverse = trimmedLeft.reverse + val trimmedReverse = trimmedLeft.toIndexedSeq.reverse.view .dropWhile(ste => !ste.getClassName.startsWith(builderName)) .dropWhile(isBlacklisted) - trimmedReverse.reverse.toArray + trimmedReverse.toIndexedSeq.reverse.toArray } - /** trims the top of the stack of elements belonging to [[blacklistPackages]] - * then trims the bottom elements until it reaches [[builderName]] - * then continues trimming elements belonging to [[blacklistPackages]] - */ - @deprecated("This method will be removed in 3.4", "3.3") - def trimmedStackTrace: Array[StackTraceElement] = trimmedStackTrace(this) - def chiselStackTrace: String = { val trimmed = trimmedStackTrace(likelyCause) @@ -121,35 +185,36 @@ private[chisel3] class ErrorLog { } /** Throw an exception if any errors have yet occurred. */ - def checkpoint(): Unit = { + def checkpoint(logger: Logger): Unit = { deprecations.foreach { case ((message, sourceLoc), count) => - println(s"${ErrorLog.depTag} $sourceLoc ($count calls): $message") + logger.warn(s"${ErrorLog.depTag} $sourceLoc ($count calls): $message") } - errors foreach println + errors.foreach(e => logger.error(e.toString)) if (!deprecations.isEmpty) { - println(s"${ErrorLog.warnTag} ${Console.YELLOW}There were ${deprecations.size} deprecated function(s) used." + + logger.warn(s"${ErrorLog.warnTag} ${Console.YELLOW}There were ${deprecations.size} deprecated function(s) used." + s" These may stop compiling in a future release - you are encouraged to fix these issues.${Console.RESET}") - println(s"${ErrorLog.warnTag} Line numbers for deprecations reported by Chisel may be inaccurate; enable scalac compiler deprecation warnings via either of the following methods:") - println(s"${ErrorLog.warnTag} In the sbt interactive console, enter:") - println(s"""${ErrorLog.warnTag} set scalacOptions in ThisBuild ++= Seq("-unchecked", "-deprecation")""") - println(s"${ErrorLog.warnTag} or, in your build.sbt, add the line:") - println(s"""${ErrorLog.warnTag} scalacOptions := Seq("-unchecked", "-deprecation")""") + logger.warn(s"${ErrorLog.warnTag} Line numbers for deprecations reported by Chisel may be inaccurate; enable scalac compiler deprecation warnings via either of the following methods:") + logger.warn(s"${ErrorLog.warnTag} In the sbt interactive console, enter:") + logger.warn(s"""${ErrorLog.warnTag} set scalacOptions in ThisBuild ++= Seq("-unchecked", "-deprecation")""") + logger.warn(s"${ErrorLog.warnTag} or, in your build.sbt, add the line:") + logger.warn(s"""${ErrorLog.warnTag} scalacOptions := Seq("-unchecked", "-deprecation")""") } val allErrors = errors.filter(_.isFatal) val allWarnings = errors.filter(!_.isFatal) if (!allWarnings.isEmpty && !allErrors.isEmpty) { - println(s"${ErrorLog.errTag} There were ${Console.RED}${allErrors.size} error(s)${Console.RESET} and ${Console.YELLOW}${allWarnings.size} warning(s)${Console.RESET} during hardware elaboration.") + logger.warn(s"${ErrorLog.errTag} There were ${Console.RED}${allErrors.size} error(s)${Console.RESET} and ${Console.YELLOW}${allWarnings.size} warning(s)${Console.RESET} during hardware elaboration.") } else if (!allWarnings.isEmpty) { - println(s"${ErrorLog.warnTag} There were ${Console.YELLOW}${allWarnings.size} warning(s)${Console.RESET} during hardware elaboration.") + logger.warn(s"${ErrorLog.warnTag} There were ${Console.YELLOW}${allWarnings.size} warning(s)${Console.RESET} during hardware elaboration.") } else if (!allErrors.isEmpty) { - println(s"${ErrorLog.errTag} There were ${Console.RED}${allErrors.size} error(s)${Console.RESET} during hardware elaboration.") + logger.warn(s"${ErrorLog.errTag} There were ${Console.RED}${allErrors.size} error(s)${Console.RESET} during hardware elaboration.") } if (!allErrors.isEmpty) { - throwException("Fatal errors during hardware elaboration") + throw new ChiselException("Fatal errors during hardware elaboration. Look above for error list.") + with scala.util.control.NoStackTrace } else { // No fatal errors, clear accumulated warnings since they've been reported errors.clear() diff --git a/core/src/main/scala/chisel3/internal/MonoConnect.scala b/core/src/main/scala/chisel3/internal/MonoConnect.scala index 2155894a..5cbab329 100644 --- a/core/src/main/scala/chisel3/internal/MonoConnect.scala +++ b/core/src/main/scala/chisel3/internal/MonoConnect.scala @@ -5,7 +5,9 @@ package chisel3.internal import chisel3._ import chisel3.experimental.{Analog, BaseModule, EnumType, FixedPoint, Interval, UnsafeEnum} import chisel3.internal.Builder.pushCommand +import chisel3.experimental.dataview.reify import chisel3.internal.firrtl.{Connect, DefInvalid} + import scala.language.experimental.macros import chisel3.internal.sourceinfo.SourceInfo @@ -103,6 +105,8 @@ private[chisel3] object MonoConnect { elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) case (sink_e: ResetType, source_e: Reset) => elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) + case (sink_e: Reset, source_e: ResetType) => + elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) case (sink_e: EnumType, source_e: UnsafeEnum) => elemConnect(sourceInfo, connectCompileOptions, sink_e, source_e, context_mod) case (sink_e: EnumType, source_e: EnumType) if sink_e.typeEquivalent(source_e) => @@ -186,8 +190,10 @@ private[chisel3] object MonoConnect { // This function checks if element-level connection operation allowed. // Then it either issues it or throws the appropriate exception. - def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, sink: Element, source: Element, context_mod: RawModule): Unit = { + def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, _sink: Element, _source: Element, context_mod: RawModule): Unit = { import BindingDirection.{Internal, Input, Output} // Using extensively so import these + val sink = reify(_sink) + val source = reify(_source) // If source has no location, assume in context module // This can occur if is a literal, unbound will error previously val sink_mod: BaseModule = sink.topBinding.location.getOrElse(throw UnwritableSinkException) diff --git a/core/src/main/scala/chisel3/internal/Namer.scala b/core/src/main/scala/chisel3/internal/Namer.scala index 1694d71d..c6e36cb6 100644 --- a/core/src/main/scala/chisel3/internal/Namer.scala +++ b/core/src/main/scala/chisel3/internal/Namer.scala @@ -8,9 +8,8 @@ import chisel3.experimental.NoChiselNamePrefix import scala.collection.mutable.Stack import scala.collection.mutable.ListBuffer -import scala.collection.JavaConversions._ - import java.util.IdentityHashMap +import scala.collection.JavaConverters._ /** Recursive Function Namer overview * @@ -81,7 +80,14 @@ class NamingContext extends NamingContextInterface { def addDescendant(ref: Any, descendant: NamingContext) { ref match { case ref: AnyRef => - descendants.getOrElseUpdate(ref, ListBuffer[NamingContext]()) += descendant + // getOrElseUpdate + val l = descendants.get(ref) + val buf = if (l != null) l else { + val value = ListBuffer[NamingContext]() + descendants.put(ref, value) + value + } + buf += descendant case _ => anonymousDescendants += descendant } } @@ -111,7 +117,7 @@ class NamingContext extends NamingContextInterface { } } - for (descendant <- descendants.values().flatten) { + for (descendant <- descendants.values.asScala.flatten) { // Where we have a broken naming link, just ignore the missing parts descendant.namePrefix(prefix) } diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index aefbf8ab..f56c3b15 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -3,12 +3,13 @@ package chisel3.internal.firrtl import chisel3._ import chisel3.experimental._ -import chisel3.internal.sourceinfo.{NoSourceInfo, SourceLine, SourceInfo} +import chisel3.internal.sourceinfo.{NoSourceInfo, SourceInfo, SourceLine, UnlocatableSourceInfo} import firrtl.{ir => fir} -import chisel3.internal.{castToInt, throwException} +import chisel3.internal.{HasId, castToInt, throwException} import scala.annotation.tailrec import scala.collection.immutable.Queue +import scala.collection.immutable.LazyList // Needed for 2.12 alias private[chisel3] object Converter { // TODO modeled on unpack method on Printable, refactor? @@ -24,6 +25,24 @@ private[chisel3] object Converter { case Percent => ("%%", List.empty) } + private def reportInternalError(msg: String): Nothing = { + val link = "https://github.com/chipsalliance/chisel3/issues/new" + val fullMsg = s"Internal Error! $msg This is a bug in Chisel, please file an issue at '$link'" + throwException(fullMsg) + } + + def getRef(id: HasId, sourceInfo: SourceInfo): Arg = + id.getOptionRef.getOrElse { + val module = id._parent.map(m => s" '$id' was defined in module '$m'.").getOrElse("") + val loc = sourceInfo.makeMessage(" " + _) + reportInternalError(s"Could not get ref for '$id'$loc!$module") + } + + private def clonedModuleIOError(mod: BaseModule, name: String, sourceInfo: SourceInfo): Nothing = { + val loc = sourceInfo.makeMessage(" " + _) + reportInternalError(s"Trying to convert a cloned IO of $mod inside of $mod itself$loc!") + } + def convert(info: SourceInfo): fir.Info = info match { case _: NoSourceInfo => fir.NoInfo case SourceLine(fn, line, col) => fir.FileInfo(fir.StringLit(s"$fn $line:$col")) @@ -41,37 +60,40 @@ private[chisel3] object Converter { // TODO // * Memoize? // * Move into the Chisel IR? - def convert(arg: Arg, ctx: Component): fir.Expression = arg match { + def convert(arg: Arg, ctx: Component, info: SourceInfo): fir.Expression = arg match { case Node(id) => - convert(id.getRef, ctx) + convert(getRef(id, info), ctx, info) case Ref(name) => fir.Reference(name, fir.UnknownType) case Slot(imm, name) => - fir.SubField(convert(imm, ctx), name, fir.UnknownType) + fir.SubField(convert(imm, ctx, info), name, fir.UnknownType) case Index(imm, ILit(idx)) => - fir.SubIndex(convert(imm, ctx), castToInt(idx, "Index"), fir.UnknownType) + fir.SubIndex(convert(imm, ctx, info), castToInt(idx, "Index"), fir.UnknownType) case Index(imm, value) => - fir.SubAccess(convert(imm, ctx), convert(value, ctx), fir.UnknownType) + fir.SubAccess(convert(imm, ctx, info), convert(value, ctx, info), fir.UnknownType) case ModuleIO(mod, name) => if (mod eq ctx.id) fir.Reference(name, fir.UnknownType) - else fir.SubField(fir.Reference(mod.getRef.name, fir.UnknownType), name, fir.UnknownType) + else fir.SubField(fir.Reference(getRef(mod, info).name, fir.UnknownType), name, fir.UnknownType) + case ModuleCloneIO(mod, name) => + if (mod eq ctx.id) clonedModuleIOError(mod, name, info) + else fir.Reference(name) case u @ ULit(n, UnknownWidth()) => fir.UIntLiteral(n, fir.IntWidth(u.minWidth)) case ULit(n, w) => fir.UIntLiteral(n, convert(w)) case slit @ SLit(n, w) => fir.SIntLiteral(n, convert(w)) val unsigned = if (n < 0) (BigInt(1) << slit.width.get) + n else n - val uint = convert(ULit(unsigned, slit.width), ctx) + val uint = convert(ULit(unsigned, slit.width), ctx, info) fir.DoPrim(firrtl.PrimOps.AsSInt, Seq(uint), Seq.empty, fir.UnknownType) // TODO Simplify case fplit @ FPLit(n, w, bp) => val unsigned = if (n < 0) (BigInt(1) << fplit.width.get) + n else n - val uint = convert(ULit(unsigned, fplit.width), ctx) + val uint = convert(ULit(unsigned, fplit.width), ctx, info) val lit = bp.asInstanceOf[KnownBinaryPoint].value fir.DoPrim(firrtl.PrimOps.AsFixedPoint, Seq(uint), Seq(lit), fir.UnknownType) case intervalLit @ IntervalLit(n, w, bp) => val unsigned = if (n < 0) (BigInt(1) << intervalLit.width.get) + n else n - val uint = convert(ULit(unsigned, intervalLit.width), ctx) + val uint = convert(ULit(unsigned, intervalLit.width), ctx, info) val lit = bp.asInstanceOf[KnownBinaryPoint].value fir.DoPrim(firrtl.PrimOps.AsInterval, Seq(uint), Seq(n, n, lit), fir.UnknownType) case lit: ILit => @@ -84,7 +106,7 @@ private[chisel3] object Converter { val consts = e.args.collect { case ILit(i) => i } val args = e.args.flatMap { case _: ILit => None - case other => Some(convert(other, ctx)) + case other => Some(convert(other, ctx, e.sourceInfo)) } val expr = e.op.name match { case "mux" => @@ -95,44 +117,45 @@ private[chisel3] object Converter { } Some(fir.DefNode(convert(e.sourceInfo), e.name, expr)) case e @ DefWire(info, id) => - Some(fir.DefWire(convert(info), e.name, extractType(id))) + Some(fir.DefWire(convert(info), e.name, extractType(id, info))) case e @ DefReg(info, id, clock) => - Some(fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx), - firrtl.Utils.zero, convert(id.getRef, ctx))) + Some(fir.DefRegister(convert(info), e.name, extractType(id, info), convert(clock, ctx, info), + firrtl.Utils.zero, convert(getRef(id, info), ctx, info))) case e @ DefRegInit(info, id, clock, reset, init) => - Some(fir.DefRegister(convert(info), e.name, extractType(id), convert(clock, ctx), - convert(reset, ctx), convert(init, ctx))) + Some(fir.DefRegister(convert(info), e.name, extractType(id, info), convert(clock, ctx, info), + convert(reset, ctx, info), convert(init, ctx, info))) case e @ DefMemory(info, id, t, size) => - Some(firrtl.CDefMemory(convert(info), e.name, extractType(t), size, false)) + Some(firrtl.CDefMemory(convert(info), e.name, extractType(t, info), size, false)) case e @ DefSeqMemory(info, id, t, size, ruw) => - Some(firrtl.CDefMemory(convert(info), e.name, extractType(t), size, true, ruw)) + Some(firrtl.CDefMemory(convert(info), e.name, extractType(t, info), size, true, ruw)) case e: DefMemPort[_] => + val info = e.sourceInfo Some(firrtl.CDefMPort(convert(e.sourceInfo), e.name, fir.UnknownType, - e.source.fullName(ctx), Seq(convert(e.index, ctx), convert(e.clock, ctx)), convert(e.dir))) + e.source.fullName(ctx), Seq(convert(e.index, ctx, info), convert(e.clock, ctx, info)), convert(e.dir))) case Connect(info, loc, exp) => - Some(fir.Connect(convert(info), convert(loc, ctx), convert(exp, ctx))) + Some(fir.Connect(convert(info), convert(loc, ctx, info), convert(exp, ctx, info))) case BulkConnect(info, loc, exp) => - Some(fir.PartialConnect(convert(info), convert(loc, ctx), convert(exp, ctx))) + Some(fir.PartialConnect(convert(info), convert(loc, ctx, info), convert(exp, ctx, info))) case Attach(info, locs) => - Some(fir.Attach(convert(info), locs.map(l => convert(l, ctx)))) + Some(fir.Attach(convert(info), locs.map(l => convert(l, ctx, info)))) case DefInvalid(info, arg) => - Some(fir.IsInvalid(convert(info), convert(arg, ctx))) + Some(fir.IsInvalid(convert(info), convert(arg, ctx, info))) case e @ DefInstance(info, id, _) => Some(fir.DefInstance(convert(info), e.name, id.name)) case Stop(info, clock, ret) => - Some(fir.Stop(convert(info), ret, convert(clock, ctx), firrtl.Utils.one)) - case Printf(info, clock, pable) => + Some(fir.Stop(convert(info), ret, convert(clock, ctx, info), firrtl.Utils.one)) + case e @ Printf(_, info, clock, pable) => val (fmt, args) = unpack(pable, ctx) Some(fir.Print(convert(info), fir.StringLit(fmt), - args.map(a => convert(a, ctx)), convert(clock, ctx), firrtl.Utils.one)) - case Verification(op, info, clk, pred, msg) => + args.map(a => convert(a, ctx, info)), convert(clock, ctx, info), firrtl.Utils.one, e.name)) + case e @ Verification(_, op, info, clk, pred, msg) => val firOp = op match { case Formal.Assert => fir.Formal.Assert case Formal.Assume => fir.Formal.Assume case Formal.Cover => fir.Formal.Cover } - Some(fir.Verification(firOp, convert(info), convert(clk, ctx), - convert(pred, ctx), firrtl.Utils.one, fir.StringLit(msg))) + Some(fir.Verification(firOp, convert(info), convert(clk, ctx, info), + convert(pred, ctx, info), firrtl.Utils.one, fir.StringLit(msg), e.name)) case _ => None } @@ -173,7 +196,7 @@ private[chisel3] object Converter { // Please see WhenFrame for more details case None => cmds.head match { case WhenBegin(info, pred) => - val when = fir.Conditionally(convert(info), convert(pred, ctx), fir.EmptyStmt, fir.EmptyStmt) + val when = fir.Conditionally(convert(info), convert(pred, ctx, info), fir.EmptyStmt, fir.EmptyStmt) val frame = WhenFrame(when, acc, false) rec(Queue.empty, frame +: scope)(cmds.tail) case WhenEnd(info, depth, _) => @@ -221,7 +244,9 @@ private[chisel3] object Converter { case d => d.specifiedDirection } - def extractType(data: Data, clearDir: Boolean = false): fir.Type = data match { + def extractType(data: Data, info: SourceInfo): fir.Type = extractType(data, false, info) + + def extractType(data: Data, clearDir: Boolean, info: SourceInfo): fir.Type = data match { case _: Clock => fir.ClockType case _: AsyncReset => fir.AsyncResetType case _: ResetType => fir.ResetType @@ -231,16 +256,16 @@ private[chisel3] object Converter { case d: FixedPoint => fir.FixedType(convert(d.width), convert(d.binaryPoint)) case d: Interval => fir.IntervalType(d.range.lowerBound, d.range.upperBound, d.range.firrtlBinaryPoint) case d: Analog => fir.AnalogType(convert(d.width)) - case d: Vec[_] => fir.VectorType(extractType(d.sample_element, clearDir), d.length) + case d: Vec[_] => fir.VectorType(extractType(d.sample_element, clearDir, info), d.length) case d: Record => val childClearDir = clearDir || d.specifiedDirection == SpecifiedDirection.Input || d.specifiedDirection == SpecifiedDirection.Output def eltField(elt: Data): fir.Field = (childClearDir, firrtlUserDirOf(elt)) match { - case (true, _) => fir.Field(elt.getRef.name, fir.Default, extractType(elt, true)) + case (true, _) => fir.Field(getRef(elt, info).name, fir.Default, extractType(elt, true, info)) case (false, SpecifiedDirection.Unspecified | SpecifiedDirection.Output) => - fir.Field(elt.getRef.name, fir.Default, extractType(elt, false)) + fir.Field(getRef(elt, info).name, fir.Default, extractType(elt, false, info)) case (false, SpecifiedDirection.Flip | SpecifiedDirection.Input) => - fir.Field(elt.getRef.name, fir.Flip, extractType(elt, false)) + fir.Field(getRef(elt, info).name, fir.Flip, extractType(elt, false, info)) } fir.BundleType(d.elements.toIndexedSeq.reverse.map { case (_, e) => eltField(e) }) } @@ -251,6 +276,7 @@ private[chisel3] object Converter { case StringParam(value) => fir.StringParam(name, fir.StringLit(value)) case RawParam(value) => fir.RawStringParam(name, value) } + def convert(port: Port, topDir: SpecifiedDirection = SpecifiedDirection.Unspecified): fir.Port = { val resolvedDir = SpecifiedDirection.fromParent(topDir, port.dir) val dir = resolvedDir match { @@ -261,8 +287,9 @@ private[chisel3] object Converter { case SpecifiedDirection.Input | SpecifiedDirection.Output => true case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => false } - val tpe = extractType(port.id, clearDir) - fir.Port(fir.NoInfo, port.id.getRef.name, dir, tpe) + val info = UnlocatableSourceInfo // Unfortunately there is no source locator for ports ATM + val tpe = extractType(port.id, clearDir, info) + fir.Port(fir.NoInfo, getRef(port.id, info).name, dir, tpe) } def convert(component: Component): fir.DefModule = component match { @@ -275,5 +302,11 @@ private[chisel3] object Converter { def convert(circuit: Circuit): fir.Circuit = fir.Circuit(fir.NoInfo, circuit.components.map(convert), circuit.name) + + // TODO Unclear if this should just be the default + def convertLazily(circuit: Circuit): fir.Circuit = { + val lazyModules = LazyList() ++ circuit.components + fir.Circuit(fir.NoInfo, lazyModules.map(convert), circuit.name) + } } diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 095c8a05..0b568548 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -3,13 +3,13 @@ package chisel3.internal.firrtl import firrtl.{ir => fir} - import chisel3._ import chisel3.internal._ import chisel3.internal.sourceinfo.SourceInfo import chisel3.experimental._ import _root_.firrtl.{ir => firrtlir} -import _root_.firrtl.PrimOps +import _root_.firrtl.{PrimOps, RenameMap} +import _root_.firrtl.annotations.Annotation import scala.collection.immutable.NumericRange import scala.math.BigDecimal.RoundingMode @@ -65,13 +65,19 @@ object PrimOp { } abstract class Arg { - def fullName(ctx: Component): String = name + def localName: String = name + def contextualName(ctx: Component): String = name + def fullName(ctx: Component): String = contextualName(ctx) def name: String } case class Node(id: HasId) extends Arg { - override def fullName(ctx: Component): String = id.getOptionRef match { - case Some(arg) => arg.fullName(ctx) + override def contextualName(ctx: Component): String = id.getOptionRef match { + case Some(arg) => arg.contextualName(ctx) + case None => id.instanceName + } + override def localName: String = id.getOptionRef match { + case Some(arg) => arg.localName case None => id.instanceName } def name: String = id.getOptionRef match { @@ -83,7 +89,7 @@ case class Node(id: HasId) extends Arg { abstract class LitArg(val num: BigInt, widthArg: Width) extends Arg { private[chisel3] def forcedWidth = widthArg.known private[chisel3] def width: Width = if (forcedWidth) widthArg else Width(minWidth) - override def fullName(ctx: Component): String = name + override def contextualName(ctx: Component): String = name // Ensure the node representing this LitArg has a ref to it and a literal binding. def bindLitArg[T <: Element](elem: T): T = { elem.bind(ElementLitBinding(this)) @@ -91,6 +97,14 @@ abstract class LitArg(val num: BigInt, widthArg: Width) extends Arg { elem } + /** Provides a mechanism that LitArgs can have their width adjusted + * to match other members of a VecLiteral + * + * @param newWidth the new width for this + * @return + */ + def cloneWithWidth(newWidth: Width): this.type + protected def minWidth: Int if (forcedWidth) { require(widthArg.get >= minWidth, @@ -106,6 +120,10 @@ case class ULit(n: BigInt, w: Width) extends LitArg(n, w) { def name: String = "UInt" + width + "(\"h0" + num.toString(16) + "\")" def minWidth: Int = 1 max n.bitLength + def cloneWithWidth(newWidth: Width): this.type = { + ULit(n, newWidth).asInstanceOf[this.type] + } + require(n >= 0, s"UInt literal ${n} is negative") } @@ -115,6 +133,10 @@ case class SLit(n: BigInt, w: Width) extends LitArg(n, w) { s"asSInt(${ULit(unsigned, width).name})" } def minWidth: Int = 1 + n.bitLength + + def cloneWithWidth(newWidth: Width): this.type = { + SLit(n, newWidth).asInstanceOf[this.type] + } } case class FPLit(n: BigInt, w: Width, binaryPoint: BinaryPoint) extends LitArg(n, w) { @@ -123,6 +145,10 @@ case class FPLit(n: BigInt, w: Width, binaryPoint: BinaryPoint) extends LitArg(n s"asFixedPoint(${ULit(unsigned, width).name}, ${binaryPoint.asInstanceOf[KnownBinaryPoint].value})" } def minWidth: Int = 1 + n.bitLength + + def cloneWithWidth(newWidth: Width): this.type = { + FPLit(n, newWidth, binaryPoint).asInstanceOf[this.type] + } } case class IntervalLit(n: BigInt, w: Width, binaryPoint: BinaryPoint) extends LitArg(n, w) { @@ -135,20 +161,45 @@ case class IntervalLit(n: BigInt, w: Width, binaryPoint: BinaryPoint) extends Li IntervalRange.getBound(isClosed = true, BigDecimal(n)), IntervalRange.getRangeWidth(binaryPoint)) } def minWidth: Int = 1 + n.bitLength + + def cloneWithWidth(newWidth: Width): this.type = { + IntervalLit(n, newWidth, binaryPoint).asInstanceOf[this.type] + } } case class Ref(name: String) extends Arg +/** Arg for ports of Modules + * @param mod the module this port belongs to + * @param name the name of the port + */ case class ModuleIO(mod: BaseModule, name: String) extends Arg { - override def fullName(ctx: Component): String = + override def contextualName(ctx: Component): String = if (mod eq ctx.id) name else s"${mod.getRef.name}.$name" } +/** Ports of cloned modules (CloneModuleAsRecord) + * @param mod The original module for which these ports are a clone + * @param name the name of the module instance + */ +case class ModuleCloneIO(mod: BaseModule, name: String) extends Arg { + override def localName = "" + override def contextualName(ctx: Component): String = + // NOTE: mod eq ctx.id only occurs in Target and Named-related APIs + if (mod eq ctx.id) localName else name +} case class Slot(imm: Node, name: String) extends Arg { - override def fullName(ctx: Component): String = - if (imm.fullName(ctx).isEmpty) name else s"${imm.fullName(ctx)}.${name}" + override def contextualName(ctx: Component): String = { + val immName = imm.contextualName(ctx) + if (immName.isEmpty) name else s"$immName.$name" + } + override def localName: String = { + val immName = imm.localName + if (immName.isEmpty) name else s"$immName.$name" + } } case class Index(imm: Arg, value: Arg) extends Arg { def name: String = s"[$value]" - override def fullName(ctx: Component): String = s"${imm.fullName(ctx)}[${value.fullName(ctx)}]" + override def contextualName(ctx: Component): String = s"${imm.contextualName(ctx)}[${value.contextualName(ctx)}]" + override def localName: String = s"${imm.localName}[${value.localName}]" } object Width { @@ -158,6 +209,7 @@ object Width { sealed abstract class Width { type W = Int + def min(that: Width): Width = this.op(that, _ min _) def max(that: Width): Width = this.op(that, _ max _) def + (that: Width): Width = this.op(that, _ + _) def + (that: Int): Width = this.op(this, (a, b) => a + that) @@ -734,14 +786,14 @@ case class Attach(sourceInfo: SourceInfo, locs: Seq[Node]) extends Command case class ConnectInit(sourceInfo: SourceInfo, loc: Node, exp: Arg) extends Command case class Stop(sourceInfo: SourceInfo, clock: Arg, ret: Int) extends Command case class Port(id: Data, dir: SpecifiedDirection) -case class Printf(sourceInfo: SourceInfo, clock: Arg, pable: Printable) extends Command +case class Printf(id: printf.Printf, sourceInfo: SourceInfo, clock: Arg, pable: Printable) extends Definition object Formal extends Enumeration { val Assert = Value("assert") val Assume = Value("assume") val Cover = Value("cover") } -case class Verification(op: Formal.Value, sourceInfo: SourceInfo, clock: Arg, - predicate: Arg, message: String) extends Command +case class Verification[T <: BaseSim](id: T, op: Formal.Value, sourceInfo: SourceInfo, clock: Arg, + predicate: Arg, message: String) extends Definition abstract class Component extends Arg { def id: BaseModule def name: String @@ -750,4 +802,7 @@ abstract class Component extends Arg { case class DefModule(id: RawModule, name: String, ports: Seq[Port], commands: Seq[Command]) extends Component case class DefBlackBox(id: BaseBlackBox, name: String, ports: Seq[Port], topDir: SpecifiedDirection, params: Map[String, Param]) extends Component -case class Circuit(name: String, components: Seq[Component], annotations: Seq[ChiselAnnotation] = Seq.empty) +case class Circuit(name: String, components: Seq[Component], annotations: Seq[ChiselAnnotation], renames: RenameMap) { + def firrtlAnnotations: Iterable[Annotation] = annotations.flatMap(_.toFirrtl.update(renames)) + +} diff --git a/core/src/main/scala/chisel3/package.scala b/core/src/main/scala/chisel3/package.scala index d5a4bfae..64cfa8b9 100644 --- a/core/src/main/scala/chisel3/package.scala +++ b/core/src/main/scala/chisel3/package.scala @@ -207,9 +207,6 @@ package object chisel3 { a.allElements } def getModulePorts(m: Module): Seq[Port] = m.getPorts - // Invalidate API - a DontCare element for explicit assignment to outputs, - // indicating the signal is intentionally not driven. - val DontCare = chisel3.internal.InternalDontCare class BindingException(message: String) extends ChiselException(message) /** A function expected a Chisel type but got a hardware object |
