diff options
Diffstat (limited to 'chiselFrontend/src/main/scala/chisel3/Aggregate.scala')
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/Aggregate.scala | 979 |
1 files changed, 979 insertions, 0 deletions
diff --git a/chiselFrontend/src/main/scala/chisel3/Aggregate.scala b/chiselFrontend/src/main/scala/chisel3/Aggregate.scala new file mode 100644 index 00000000..0db55344 --- /dev/null +++ b/chiselFrontend/src/main/scala/chisel3/Aggregate.scala @@ -0,0 +1,979 @@ +// See LICENSE for license details. + +package chisel3 + +import scala.collection.immutable.ListMap +import scala.collection.mutable.{HashSet, LinkedHashMap} +import scala.language.experimental.macros + +import chisel3.experimental.BaseModule +import chisel3.experimental.BundleLiteralException +import chisel3.internal._ +import chisel3.internal.Builder.pushCommand +import chisel3.internal.firrtl._ +import chisel3.internal.sourceinfo._ + +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) { // scalastyle:ignore cyclomatic.complexity line.size.limit + binding = target + + val resolvedDirection = SpecifiedDirection.fromParent(parentDirection, specifiedDirection) + val duplicates = getElements.groupBy(identity).collect { case (x, elts) if elts.size > 1 => x } + if (!duplicates.isEmpty) { + throw new AliasedAggregateFieldException(s"Aggregate $this contains aliased fields $duplicates") + } + for (child <- getElements) { + child.bind(ChildBinding(this), resolvedDirection) + } + + // Check that children obey the directionality rules. + val childDirections = getElements.map(_.direction).toSet - ActualDirection.Empty + direction = ActualDirection.fromChildren(childDirections, resolvedDirection) match { + case Some(dir) => dir + case None => + val childWithDirections = getElements zip getElements.map(_.direction) + throw MixedDirectionAggregateException( + s"Aggregate '$this' can't have elements that are both directioned and undirectioned: $childWithDirections") + } + } + + override def litOption: Option[BigInt] = ??? // TODO implement me + + /** Returns a Seq of the immediate contents of this Aggregate, in order. + */ + 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. + if (that == DontCare) { + pushCommand(DefInvalid(sourceInfo, Node(this))) + } else { + pushCommand(BulkConnect(sourceInfo, Node(this), Node(that))) + } + } + + 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 = { + var i = 0 + val bits = WireDefault(UInt(this.width), that) // handles width padding + for (x <- flatten) { + val fieldWidth = x.getWidth + if (fieldWidth > 0) { + x.connectFromBits(bits(i + fieldWidth - 1, i)) + i += fieldWidth + } else { + // There's a zero-width field in this bundle. + // Zero-width fields can't really be assigned to, but the frontend complains if there are uninitialized fields, + // so we assign it to DontCare. We can't use connectFromBits() on DontCare, so use := instead. + x := DontCare + } + } + } +} + +trait VecFactory extends SourceInfoDoc { + /** Creates a new [[Vec]] with `n` entries of the specified data type. + * + * @note elements are NOT assigned by default and have no value + */ + def apply[T <: Data](n: Int, gen: T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = { + if (compileOptions.declaredTypeMustBeUnbound) { + requireIsChiselType(gen, "vec type") + } + new Vec(gen.cloneTypeFull, n) + } + + /** Truncate an index to implement modulo-power-of-2 addressing. */ + private[chisel3] def truncateIndex(idx: UInt, n: BigInt)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = { // scalastyle:ignore line.size.limit + // scalastyle:off if.brace + val w = (n-1).bitLength + if (n <= 1) 0.U + else if (idx.width.known && idx.width.get <= w) idx + else if (idx.width.known) idx(w-1,0) + else (idx | 0.U(w.W))(w-1,0) + // scalastyle:on if.brace + } +} + +// scalastyle:off line.size.limit +/** A vector (array) of [[Data]] elements. Provides hardware versions of various + * collection transformation functions found in software array implementations. + * + * Careful consideration should be given over the use of [[Vec]] vs + * [[scala.collection.immutable.Seq Seq]] or some other Scala collection. In general [[Vec]] only + * needs to be used when there is a need to express the hardware collection in a [[Reg]] or IO + * [[Bundle]] or when access to elements of the array is indexed via a hardware signal. + * + * Example of indexing into a [[Vec]] using a hardware address and where the [[Vec]] is defined in + * an IO [[Bundle]] + * + * {{{ + * val io = IO(new Bundle { + * val in = Input(Vec(20, UInt(16.W))) + * val addr = UInt(5.W) + * val out = Output(UInt(16.W)) + * }) + * io.out := io.in(io.addr) + * }}} + * + * @tparam T type of elements + * + * @note + * - when multiple conflicting assignments are performed on a Vec element, the last one takes effect (unlike Mem, where the result is undefined) + * - Vecs, unlike classes in Scala's collection library, are propagated intact to FIRRTL as a vector type, which may make debugging easier + */ +// scalastyle:on line.size.limit +sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int) + extends Aggregate with VecLike[T] { + override def toString: String = { + val elementType = sample_element.cloneType + s"$elementType[$length]$bindingToString" + } + + private[chisel3] override def typeEquivalent(that: Data): Boolean = that match { + case that: Vec[T] => + this.length == that.length && + (this.sample_element typeEquivalent that.sample_element) + case _ => false + } + + private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection) { + binding = target + + val resolvedDirection = SpecifiedDirection.fromParent(parentDirection, specifiedDirection) + sample_element.bind(SampleElementBinding(this), resolvedDirection) + for (child <- getElements) { // assume that all children are the same + child.bind(ChildBinding(this), resolvedDirection) + } + + direction = sample_element.direction + } + + // Note: the constructor takes a gen() function instead of a Seq to enforce + // that all elements must be the same and because it makes FIRRTL generation + // simpler. + private val self: Seq[T] = Vector.fill(length)(gen) + for ((elt, i) <- self.zipWithIndex) + elt.setRef(this, i) + + /** + * sample_element 'tracks' all changes to the elements. + * For consistency, sample_element is always used for creating dynamically + * indexed ports and outputing the FIRRTL type. + * + * Needed specifically for the case when the Vec is length 0. + */ + private[chisel3] val sample_element: T = gen + + // allElements current includes sample_element + // This is somewhat weird although I think the best course of action here is + // to deprecate allElements in favor of dispatched functions to Data or + // a pattern matched recursive descent + private[chisel3] final override def allElements: Seq[Element] = + (sample_element +: self).flatMap(_.allElements) + + /** Strong bulk connect, assigning elements in this Vec from elements in a Seq. + * + * @note the length of this Vec must match the length of the input Seq + */ + def <> (that: Seq[T])(implicit sourceInfo: SourceInfo, moduleCompileOptions: CompileOptions): Unit = { + if (this.length != that.length) { + Builder.error("Vec and Seq being bulk connected have different lengths!") + } + for ((a, b) <- this zip that) + a <> b + } + + // TODO: eliminate once assign(Seq) isn't ambiguous with assign(Data) since Vec extends Seq and Data + def <> (that: Vec[T])(implicit sourceInfo: SourceInfo, moduleCompileOptions: CompileOptions): Unit = this bulkConnect that.asInstanceOf[Data] // scalastyle:ignore line.size.limit + + /** Strong bulk connect, assigning elements in this Vec from elements in a Seq. + * + * @note the length of this Vec must match the length of the input Seq + */ + def := (that: Seq[T])(implicit sourceInfo: SourceInfo, moduleCompileOptions: CompileOptions): Unit = { + require(this.length == that.length) + for ((a, b) <- this zip that) + a := b + } + + // TODO: eliminate once assign(Seq) isn't ambiguous with assign(Data) since Vec extends Seq and Data + def := (that: Vec[T])(implicit sourceInfo: SourceInfo, moduleCompileOptions: CompileOptions): Unit = this connect that + + /** Creates a dynamically indexed read or write accessor into the array. + */ + override def apply(p: UInt): T = macro CompileOptionsTransform.pArg + + /** @group SourceInfoTransformMacro */ + def do_apply(p: UInt)(implicit compileOptions: CompileOptions): T = { + requireIsHardware(p, "vec index") + val port = gen + + // Reconstruct the resolvedDirection (in Aggregate.bind), since it's not stored. + // It may not be exactly equal to that value, but the results are the same. + val reconstructedResolvedDirection = direction match { + case ActualDirection.Input => SpecifiedDirection.Input + case ActualDirection.Output => SpecifiedDirection.Output + case ActualDirection.Bidirectional(ActualDirection.Default) | ActualDirection.Unspecified => + SpecifiedDirection.Unspecified + case ActualDirection.Bidirectional(ActualDirection.Flipped) => SpecifiedDirection.Flip + } + // TODO port technically isn't directly child of this data structure, but the result of some + // muxes / demuxes. However, this does make access consistent with the top-level bindings. + // Perhaps there's a cleaner way of accomplishing this... + port.bind(ChildBinding(this), reconstructedResolvedDirection) + + val i = Vec.truncateIndex(p, length)(UnlocatableSourceInfo, compileOptions) + port.setRef(this, i) + + port + } + + /** Creates a statically indexed read or write accessor into the array. + */ + def apply(idx: Int): T = self(idx) + + override def cloneType: this.type = { + new Vec(gen.cloneTypeFull, length).asInstanceOf[this.type] + } + + override def getElements: Seq[Data] = + (0 until length).map(apply(_)) + + /** Default "pretty-print" implementation + * Analogous to printing a Seq + * Results in "Vec(elt0, elt1, ...)" + */ + def toPrintable: Printable = { + // scalastyle:off if.brace + val elts = + if (length == 0) List.empty[Printable] + else self flatMap (e => List(e.toPrintable, PString(", "))) dropRight 1 + // scalastyle:on if.brace + PString("Vec(") + Printables(elts) + PString(")") + } +} + +object VecInit extends SourceInfoDoc { + /** Creates a new [[Vec]] composed of elements of the input Seq of [[Data]] + * nodes. + * + * @note input elements should be of the same type (this is checked at the + * FIRRTL level, but not at the Scala / Chisel level) + * @note the width of all output elements is the width of the largest input + * element + * @note output elements are connected from the input elements + */ + def apply[T <: Data](elts: Seq[T]): Vec[T] = macro VecTransform.apply_elts + + /** @group SourceInfoTransformMacro */ + def do_apply[T <: Data](elts: Seq[T])(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = { + // REVIEW TODO: this should be removed in favor of the apply(elts: T*) + // varargs constructor, which is more in line with the style of the Scala + // collection API. However, a deprecation phase isn't possible, since + // changing apply(elt0, elts*) to apply(elts*) causes a function collision + // with apply(Seq) after type erasure. Workarounds by either introducing a + // DummyImplicit or additional type parameter will break some code. + + // Check that types are homogeneous. Width mismatch for Elements is safe. + require(!elts.isEmpty) + 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) + } + vec + } + + /** Creates a new [[Vec]] composed of the input [[Data]] nodes. + * + * @note input elements should be of the same type (this is checked at the + * FIRRTL level, but not at the Scala / Chisel level) + * @note the width of all output elements is the width of the largest input + * element + * @note output elements are connected from the input elements + */ + def apply[T <: Data](elt0: T, elts: T*): Vec[T] = macro VecTransform.apply_elt0 + + /** @group SourceInfoTransformMacro */ + def do_apply[T <: Data](elt0: T, elts: T*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = + apply(elt0 +: elts.toSeq) + + /** Creates a new [[Vec]] of length `n` composed of the results of the given + * function applied over a range of integer values starting from 0. + * + * @param n number of elements in the 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)(gen: (Int) => T): Vec[T] = macro VecTransform.tabulate + + /** @group SourceInfoTransformMacro */ + def do_tabulate[T <: Data](n: Int)(gen: (Int) => T)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = // scalastyle:ignore line.size.limit + apply((0 until n).map(i => gen(i))) +} + +/** A trait for [[Vec]]s containing common hardware generators for collection + * operations. + */ +trait VecLike[T <: Data] extends collection.IndexedSeq[T] with HasId with SourceInfoDoc { + def apply(p: UInt): T = macro CompileOptionsTransform.pArg + + /** @group SourceInfoTransformMacro */ + def do_apply(p: UInt)(implicit compileOptions: CompileOptions): T + + // IndexedSeq has its own hashCode/equals that we must not use + override def hashCode: Int = super[HasId].hashCode + override def equals(that: Any): Boolean = super[HasId].equals(that) + + @chiselRuntimeDeprecated + @deprecated("Use Vec.apply instead", "chisel3") + def read(idx: UInt)(implicit compileOptions: CompileOptions): T = do_apply(idx)(compileOptions) + + @chiselRuntimeDeprecated + @deprecated("Use Vec.apply instead", "chisel3") + def write(idx: UInt, data: T)(implicit compileOptions: CompileOptions): Unit = { + do_apply(idx)(compileOptions).:=(data)(DeprecatedSourceInfo, compileOptions) + } + + /** Outputs true if p outputs true for every element. + */ + def forall(p: T => Bool): Bool = macro SourceInfoTransform.pArg + + /** @group SourceInfoTransformMacro */ + def do_forall(p: T => Bool)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = + (this map p).fold(true.B)(_ && _) + + /** Outputs true if p outputs true for at least one element. + */ + def exists(p: T => Bool): Bool = macro SourceInfoTransform.pArg + + /** @group SourceInfoTransformMacro */ + def do_exists(p: T => Bool)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Bool = + (this map p).fold(false.B)(_ || _) + + /** Outputs true if the vector contains at least one element equal to x (using + * the === operator). + */ + def contains(x: T)(implicit ev: T <:< UInt): Bool = macro VecTransform.contains + + /** @group SourceInfoTransformMacro */ + def do_contains(x: T)(implicit sourceInfo: SourceInfo, ev: T <:< UInt, compileOptions: CompileOptions): Bool = + this.exists(_ === x) + + /** Outputs the number of elements for which p is true. + */ + def count(p: T => Bool): UInt = macro SourceInfoTransform.pArg + + /** @group SourceInfoTransformMacro */ + def do_count(p: T => Bool)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = + SeqUtils.count(this map p) + + /** Helper function that appends an index (literal value) to each element, + * useful for hardware generators which output an index. + */ + private def indexWhereHelper(p: T => Bool) = this map p zip (0 until length).map(i => i.asUInt) + + /** Outputs the index of the first element for which p outputs true. + */ + def indexWhere(p: T => Bool): UInt = macro SourceInfoTransform.pArg + + /** @group SourceInfoTransformMacro */ + def do_indexWhere(p: T => Bool)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = + SeqUtils.priorityMux(indexWhereHelper(p)) + + /** Outputs the index of the last element for which p outputs true. + */ + def lastIndexWhere(p: T => Bool): UInt = macro SourceInfoTransform.pArg + + /** @group SourceInfoTransformMacro */ + def do_lastIndexWhere(p: T => Bool)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = + SeqUtils.priorityMux(indexWhereHelper(p).reverse) + + /** Outputs the index of the element for which p outputs true, assuming that + * the there is exactly one such element. + * + * The implementation may be more efficient than a priority mux, but + * incorrect results are possible if there is not exactly one true element. + * + * @note the assumption that there is only one element for which p outputs + * true is NOT checked (useful in cases where the condition doesn't always + * hold, but the results are not used in those cases) + */ + def onlyIndexWhere(p: T => Bool): UInt = macro SourceInfoTransform.pArg + + /** @group SourceInfoTransformMacro */ + def do_onlyIndexWhere(p: T => Bool)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = + SeqUtils.oneHotMux(indexWhereHelper(p)) +} + +/** Base class for Aggregates based on key values pairs of String and Data + * + * Record should only be extended by libraries and fairly sophisticated generators. + * RTL writers should use [[Bundle]]. See [[Record#elements]] for an example. + */ +abstract class Record(private[chisel3] implicit val compileOptions: CompileOptions) extends Aggregate { + private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection): Unit = { + try { + super.bind(target, parentDirection) + } catch { // nasty compatibility mode shim, where anything flies + case e: MixedDirectionAggregateException if !compileOptions.dontAssumeDirectionality => + val resolvedDirection = SpecifiedDirection.fromParent(parentDirection, specifiedDirection) + direction = resolvedDirection match { + case SpecifiedDirection.Unspecified => ActualDirection.Bidirectional(ActualDirection.Default) + case SpecifiedDirection.Flip => ActualDirection.Bidirectional(ActualDirection.Flipped) + case _ => ActualDirection.Bidirectional(ActualDirection.Default) + } + } + } + + /** Creates a Bundle literal of this type with specified values. this must be a chisel type. + * + * @param elems literal values, specified as a pair of the Bundle field to the literal value. + * The Bundle field is specified as a function from an object of this type to the field. + * Fields that aren't initialized to DontCare, and assignment to a wire will overwrite any + * existing value with DontCare. + * @return a Bundle literal of this type with subelement values specified + * + * @example {{{ + * class MyBundle extends Bundle { + * val a = UInt(8.W) + * val b = Bool() + * } + * + * (mew MyBundle).Lit( + * _.a -> 42.U, + * _.b -> true.B + * ) + * }}} + */ + private[chisel3] def _makeLit(elems: (this.type => (Data, Data))*): this.type = { // scalastyle:ignore line.size.limit method.length method.name cyclomatic.complexity + // Returns pairs of all fields, element-level and containers, in a Record and their path names + def getRecursiveFields(data: Data, path: String): Seq[(Data, String)] = data match { + case data: Record => data.elements.map { case (fieldName, fieldData) => + getRecursiveFields(fieldData, s"$path.$fieldName") + }.fold(Seq(data -> path)) { _ ++ _ } + case data => Seq(data -> path) // we don't support or recurse into other Aggregate types here + } + + // Returns pairs of corresponding fields between two Records of the same type + def getMatchedFields(x: Data, y: Data): Seq[(Data, Data)] = (x, y) match { + case (x: Element, y: Element) => + require(x typeEquivalent y) + Seq(x -> y) + case (x: Record, y: Record) => + (x.elements zip y.elements).map { case ((xName, xElt), (yName, yElt)) => + require(xName == yName) // assume fields returned in same, deterministic order + getMatchedFields(xElt, yElt) + }.fold(Seq(x -> y)) { _ ++ _ } + } + + requireIsChiselType(this, "bundle literal constructor model") + val clone = cloneType + val cloneFields = getRecursiveFields(clone, "(bundle root)").toMap + + // Create the Bundle literal binding from litargs of arguments + val bundleLitMap = elems.map { fn => fn(clone) }.flatMap { case (field, value) => + val fieldName = cloneFields.getOrElse(field, + throw new BundleLiteralException(s"field $field (with value $value) is not a field," + + s" ensure the field is specified as a function returning a field on an object of class ${this.getClass}," + + s" eg '_.a' to select hypothetical bundle field 'a'") + ) + val valueBinding = value.topBindingOpt match { + case Some(litBinding: LitBinding) => litBinding + case _ => throw new BundleLiteralException(s"field $fieldName specified with non-literal value $value") + } + + field match { // Get the litArg(s) for this field + case field: Bits => + if (field.getClass != value.getClass) { // TODO typeEquivalent is too strict because it checks width + throw new BundleLiteralException(s"Field $fieldName $field specified with non-type-equivalent value $value") + } + val litArg = valueBinding match { + case ElementLitBinding(litArg) => litArg + case BundleLitBinding(litMap) => litMap.getOrElse(value, + throw new BundleLiteralException(s"Field $fieldName specified with unspecified value")) + } + Seq(field -> litArg) + case field: Record => + if (!(field typeEquivalent value)) { + throw new BundleLiteralException(s"field $fieldName $field specified with non-type-equivalent value $value") + } + // Copy the source BundleLitBinding with fields (keys) remapped to the clone + val remap = getMatchedFields(value, field).toMap + value.topBinding.asInstanceOf[BundleLitBinding].litMap.map { case (valueField, valueValue) => + remap(valueField) -> valueValue + } + case _ => throw new BundleLiteralException(s"unsupported field $fieldName of type $field") + } + } // don't convert to a Map yet to preserve duplicate keys + val duplicates = bundleLitMap.map(_._1).groupBy(identity).collect { case (x, elts) if elts.size > 1 => x } + if (!duplicates.isEmpty) { + val duplicateNames = duplicates.map(cloneFields(_)).mkString(", ") + throw new BundleLiteralException(s"duplicate fields $duplicateNames in Bundle literal constructor") + } + clone.bind(BundleLitBinding(bundleLitMap.toMap)) + clone + } + + /** The collection of [[Data]] + * + * This underlying datastructure is a ListMap because the elements must + * remain ordered for serialization/deserialization. Elements added later + * are higher order when serialized (this is similar to [[Vec]]). For example: + * {{{ + * // Assume we have some type MyRecord that creates a Record from the ListMap + * val record = MyRecord(ListMap("fizz" -> UInt(16.W), "buzz" -> UInt(16.W))) + * // "buzz" is higher order because it was added later than "fizz" + * record("fizz") := "hdead".U + * record("buzz") := "hbeef".U + * val uint = record.asUInt + * assert(uint === "hbeefdead".U) // This will pass + * }}} + */ + override def toString: String = { + val bindingString = topBindingOpt match { + case Some(BundleLitBinding(_)) => + val contents = elements.toList.reverse.map { case (name, data) => + s"$name=$data" + }.mkString(", ") + s"($contents)" + case _ => bindingToString + } + s"$className$bindingString" + } + + val elements: ListMap[String, Data] + + /** Name for Pretty Printing */ + def className: String = this.getClass.getSimpleName + + private[chisel3] override def typeEquivalent(that: Data): Boolean = that match { + case that: Record => + this.getClass == that.getClass && + this.elements.size == that.elements.size && + this.elements.forall{case (name, model) => + that.elements.contains(name) && + (that.elements(name) typeEquivalent model)} + case _ => false + } + + // NOTE: This sets up dependent references, it can be done before closing the Module + private[chisel3] override def _onModuleClose: Unit = { // scalastyle:ignore method.name + // Since Bundle names this via reflection, it is impossible for two elements to have the same + // identifier; however, Namespace sanitizes identifiers to make them legal for Firrtl/Verilog + // which can cause collisions + val _namespace = Namespace.empty + for ((name, elt) <- elements) { elt.setRef(this, _namespace.name(name, leadingDigitOk=true)) } + } + + private[chisel3] final def allElements: Seq[Element] = elements.toIndexedSeq.flatMap(_._2.allElements) + + override def getElements: Seq[Data] = elements.toIndexedSeq.map(_._2) + + // Helper because Bundle elements are reversed before printing + private[chisel3] def toPrintableHelper(elts: Seq[(String, Data)]): Printable = { + // scalastyle:off if.brace + val xs = + if (elts.isEmpty) List.empty[Printable] // special case because of dropRight below + else elts flatMap { case (name, data) => + List(PString(s"$name -> "), data.toPrintable, PString(", ")) + } dropRight 1 // Remove trailing ", " + // scalastyle:on if.brace + PString(s"$className(") + Printables(xs) + PString(")") + } + /** Default "pretty-print" implementation + * Analogous to printing a Map + * Results in "`\$className(elt0.name -> elt0.value, ...)`" + */ + def toPrintable: Printable = toPrintableHelper(elements.toList) +} + +/** + * Mix-in for Bundles that have arbitrary Seqs of Chisel types that aren't + * involved in hardware construction. + * + * Used to avoid raising an error/exception when a Seq is a public member of the + * bundle. + * This is useful if we those public Seq fields in the Bundle are unrelated to + * hardware construction. + */ +trait IgnoreSeqInBundle { + this: Bundle => + + override def ignoreSeq: Boolean = true +} + +class AutoClonetypeException(message: String) extends ChiselException(message) + +package experimental { + + class BundleLiteralException(message: String) extends ChiselException(message) + +} + +/** Base class for data types defined as a bundle of other data types. + * + * Usage: extend this class (either as an anonymous or named class) and define + * members variables of [[Data]] subtypes to be elements in the Bundle. + * + * Example of an anonymous IO bundle + * {{{ + * class MyModule extends Module { + * val io = IO(new Bundle { + * val in = Input(UInt(64.W)) + * val out = Output(SInt(128.W)) + * }) + * } + * }}} + * + * Or as a named class + * {{{ + * class Packet extends Bundle { + * val header = UInt(16.W) + * val addr = UInt(16.W) + * val data = UInt(32.W) + * } + * class MyModule extends Module { + * val io = IO(new Bundle { + * val inPacket = Input(new Packet) + * val outPacket = Output(new Packet) + * }) + * val reg = Reg(new Packet) + * reg <> inPacket + * outPacket <> reg + * } + * }}} + */ +abstract class Bundle(implicit compileOptions: CompileOptions) extends Record { + override def className: String = this.getClass.getSimpleName match { + case name if name.startsWith("$anon$") => "AnonymousBundle" // fallback for anonymous Bundle case + case "" => "AnonymousBundle" // ditto, but on other platforms + case name => name + } + /** The collection of [[Data]] + * + * Elements defined earlier in the Bundle are higher order upon + * serialization. For example: + * @example + * {{{ + * class MyBundle extends Bundle { + * val foo = UInt(16.W) + * val bar = UInt(16.W) + * } + * // Note that foo is higher order because its defined earlier in the Bundle + * val bundle = Wire(new MyBundle) + * bundle.foo := 0x1234.U + * bundle.bar := 0x5678.U + * val uint = bundle.asUInt + * assert(uint === "h12345678".U) // This will pass + * }}} + */ + final lazy val elements: ListMap[String, Data] = { + val nameMap = LinkedHashMap[String, Data]() + for (m <- getPublicFields(classOf[Bundle])) { + getBundleField(m) match { + case Some(d: Data) => + if (nameMap contains m.getName) { + require(nameMap(m.getName) eq d) + } else { + nameMap(m.getName) = d + } + case None => + if (!ignoreSeq) { + m.invoke(this) match { + case s: scala.collection.Seq[Any] if s.nonEmpty => s.head match { + // Ignore empty Seq() + case d: Data => throwException("Public Seq members cannot be used to define Bundle elements " + + s"(found public Seq member '${m.getName}'). " + + "Either use a Vec if all elements are of the same type, or MixedVec if the elements " + + "are of different types. If this Seq member is not intended to construct RTL, mix in the trait " + + "IgnoreSeqInBundle.") + case _ => // don't care about non-Data Seq + } + case _ => // not a Seq + } + } + } + } + ListMap(nameMap.toSeq sortWith { case ((an, a), (bn, b)) => (a._id > b._id) || ((a eq b) && (an > bn)) }: _*) + // scalastyle:ignore method.length + } + + /** + * Overridden by [[IgnoreSeqInBundle]] to allow arbitrary Seqs of Chisel elements. + */ + def ignoreSeq: Boolean = false + + /** Returns a field's contained user-defined Bundle element if it appears to + * be one, otherwise returns None. + */ + private def getBundleField(m: java.lang.reflect.Method): Option[Data] = m.invoke(this) match { + case d: Data => Some(d) + case Some(d: Data) => Some(d) + case _ => None + } + + // 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. + // _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) + + override def cloneType : this.type = { // scalastyle:ignore cyclomatic.complexity method.length + // This attempts to infer constructor and arguments to clone this Bundle subtype without + // requiring the user explicitly overriding cloneType. + import scala.language.existentials + import scala.reflect.runtime.universe._ + + val clazz = this.getClass + + def autoClonetypeError(desc: String): Nothing = { + throw new AutoClonetypeException(s"Unable to automatically infer cloneType on $clazz: $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.") + } + } + } + + val mirror = runtimeMirror(clazz.getClassLoader) + val classSymbolOption = try { + Some(mirror.reflect(this).symbol) + } catch { + case e: scala.reflect.internal.Symbols#CyclicReference => None // Workaround for a scala bug + } + + val enclosingClassOption = (clazz.getEnclosingClass, classSymbolOption) match { + case (null, _) => None + case (_, Some(classSymbol)) if classSymbol.isStatic => None // allows support for members of companion objects + case (outerClass, _) => Some(outerClass) + } + + // For compatibility with pre-3.1, where null is tried as an argument to the constructor. + // This stores potential error messages which may be used later. + var outerClassError: Option[String] = None + + // Check if this is an inner class, and if so, try to get the outer instance + val outerClassInstance = enclosingClassOption.map { outerClass => + def canAssignOuterClass(x: Object) = outerClass.isAssignableFrom(x.getClass) + + val outerInstance = _outerInst match { + case Some(outerInstance) => outerInstance // use _outerInst if defined + case None => // determine outer instance if not already recorded + try { + // Prefer this if it works, but doesn't work in all cases, namely anonymous inner Bundles + val outer = clazz.getDeclaredField("$outer").get(this) + _outerInst = Some(outer) + outer + } catch { + case (_: NoSuchFieldException | _: IllegalAccessException) => + // Fallback using guesses based on common patterns + val allOuterCandidates = Seq( + _containingModule.toSeq, + _containingBundles + ).flatten.distinct + allOuterCandidates.filter(canAssignOuterClass(_)) match { + case outer :: Nil => + _outerInst = Some(outer) // record the guess for future use + outer + case Nil => // TODO: replace with fatal autoClonetypeError once compatibility period is dropped + outerClassError = Some(s"Unable to determine instance of outer class $outerClass," + + s" no candidates assignable to outer class types; examined $allOuterCandidates") + null + case candidates => // TODO: replace with fatal autoClonetypeError once compatibility period is dropped + outerClassError = Some(s"Unable to determine instance of outer class $outerClass," + + s" multiple possible candidates $candidates assignable to outer class type") + null + } + } + } + (outerClass, outerInstance) + } + + // If possible (constructor with no arguments), try Java reflection first + // This handles two cases that Scala reflection doesn't: + // 1. getting the ClassSymbol of a class with an anonymous outer class fails with a + // CyclicReference exception + // 2. invoking the constructor of an anonymous inner class seems broken (it expects the outer + // class as an argument, but fails because the number of arguments passed in is incorrect) + if (clazz.getConstructors.size == 1) { + var ctor = clazz.getConstructors.head + val argTypes = ctor.getParameterTypes.toList + val clone = (argTypes, outerClassInstance) match { + case (Nil, None) => // no arguments, no outer class, invoke constructor directly + Some(ctor.newInstance().asInstanceOf[this.type]) + case (argType :: Nil, Some((_, outerInstance))) => + if (outerInstance == null) { + Builder.deprecated(s"chisel3.1 autoclonetype failed, falling back to 3.0 behavior using null as the outer instance." + // scalastyle:ignore line.size.limit + s" Autoclonetype failure reason: ${outerClassError.get}", + Some(s"$clazz")) + Some(ctor.newInstance(outerInstance).asInstanceOf[this.type]) + } else if (argType isAssignableFrom outerInstance.getClass) { + Some(ctor.newInstance(outerInstance).asInstanceOf[this.type]) + } else { + None + } + case _ => None + + } + clone match { + case Some(clone) => + clone._outerInst = this._outerInst + validateClone(clone, "Constructor argument values were not inferred, ensure constructor is deterministic.") + return clone.asInstanceOf[this.type] + case None => + } + } + + // Get constructor parameters and accessible fields + val classSymbol = classSymbolOption.getOrElse(autoClonetypeError(s"scala reflection failed." + + " This is known to occur with inner classes on anonymous outer classes." + + " In those cases, autoclonetype only works with no-argument constructors, or you can define a custom cloneType.")) // scalastyle:ignore line.size.limit + + val decls = classSymbol.typeSignature.decls + val ctors = decls.collect { case meth: MethodSymbol if meth.isConstructor => meth } + if (ctors.size != 1) { + autoClonetypeError(s"found multiple constructors ($ctors)." + + " Either remove all but the default constructor, or define a custom cloneType method.") + } + val ctor = ctors.head + val ctorParamss = ctor.paramLists + val ctorParams = ctorParamss match { + case Nil => List() + case ctorParams :: Nil => ctorParams + case ctorParams :: ctorImplicits :: Nil => ctorParams ++ ctorImplicits + case _ => autoClonetypeError(s"internal error, unexpected ctorParamss = $ctorParamss") + } + val ctorParamsNames = ctorParams.map(_.name.toString) + + // Special case for anonymous inner classes: their constructor consists of just the outer class reference + // Scala reflection on anonymous inner class constructors seems broken + if (ctorParams.size == 1 && outerClassInstance.isDefined && + ctorParams.head.typeSignature == mirror.classSymbol(outerClassInstance.get._1).toType) { + // Fall back onto Java reflection + val ctors = clazz.getConstructors + require(ctors.size == 1) // should be consistent with Scala constructors + try { + val clone = ctors.head.newInstance(outerClassInstance.get._2).asInstanceOf[this.type] + clone._outerInst = this._outerInst + + validateClone(clone, "Outer class instance was inferred, ensure constructor is deterministic.") + return clone + } catch { + case e @ (_: java.lang.reflect.InvocationTargetException | _: IllegalArgumentException) => + autoClonetypeError(s"unexpected failure at constructor invocation, got $e.") + } + } + + // Get all the class symbols up to (but not including) Bundle and get all the accessors. + // (each ClassSymbol's decls only includes those declared in the class itself) + val bundleClassSymbol = mirror.classSymbol(classOf[Bundle]) + val superClassSymbols = classSymbol.baseClasses.takeWhile(_ != bundleClassSymbol) + val superClassDecls = superClassSymbols.map(_.typeSignature.decls).flatten + val accessors = superClassDecls.collect { case meth: MethodSymbol if meth.isParamAccessor => meth } + + // Get constructor argument values + // Check that all ctor params are immutable and accessible. Immutability is required to avoid + // potential subtle bugs (like values changing after cloning). + // This also generates better error messages (all missing elements shown at once) instead of + // failing at the use site one at a time. + val accessorsName = accessors.filter(_.isStable).map(_.name.toString) + val paramsDiff = ctorParamsNames.toSet -- accessorsName.toSet + if (!paramsDiff.isEmpty) { + // scalastyle:off line.size.limit + autoClonetypeError(s"constructor has parameters (${paramsDiff.toList.sorted.mkString(", ")}) that are not both immutable and accessible." + + " Either make all parameters immutable and accessible (vals) so cloneType can be inferred, or define a custom cloneType method.") + // scalastyle:on line.size.limit + } + + // Get all the argument values + val accessorsMap = accessors.map(accessor => accessor.name.toString -> accessor).toMap + val instanceReflect = mirror.reflect(this) + val ctorParamsNameVals = ctorParamsNames.map { + paramName => paramName -> instanceReflect.reflectMethod(accessorsMap(paramName)).apply() + } + + // Opportunistic sanity check: ensure any arguments of type Data is not bound + // (which could lead to data conflicts, since it's likely the user didn't know to re-bind them). + // This is not guaranteed to catch all cases (for example, Data in Tuples or Iterables). + val boundDataParamNames = ctorParamsNameVals.collect { + case (paramName, paramVal: Data) if paramVal.topBindingOpt.isDefined => paramName + } + if (boundDataParamNames.nonEmpty) { + // scalastyle:off line.size.limit + autoClonetypeError(s"constructor parameters (${boundDataParamNames.sorted.mkString(", ")}) have values that are hardware types, which is likely to cause subtle errors." + + " Use chisel types instead: use the value before it is turned to a hardware type (with Wire(...), Reg(...), etc) or use chiselTypeOf(...) to extract the chisel type.") + // scalastyle:on line.size.limit + } + + // Clone unbound parameters in case they are being used as bundle fields. + val ctorParamsVals = ctorParamsNameVals.map { + case (_, paramVal: Data) => paramVal.cloneTypeFull + case (_, paramVal) => paramVal + } + + // Invoke ctor + val classMirror = outerClassInstance match { + case Some((_, null)) => autoClonetypeError(outerClassError.get) // deals with the null hack for 3.0 compatibility + case Some((_, outerInstance)) => mirror.reflect(outerInstance).reflectClass(classSymbol) + case _ => mirror.reflectClass(classSymbol) + } + val clone = classMirror.reflectConstructor(ctor).apply(ctorParamsVals:_*).asInstanceOf[this.type] + clone._outerInst = this._outerInst + + validateClone(clone, + "Constructor argument values were inferred:" + + " ensure that variable names are consistent and have the same value throughout the constructor chain," + + " and that the constructor is deterministic." + ) + clone + } + + /** Default "pretty-print" implementation + * Analogous to printing a Map + * Results in "`Bundle(elt0.name -> elt0.value, ...)`" + * @note The order is reversed from the order of elements in order to print + * the fields in the order they were defined + */ + override def toPrintable: Printable = toPrintableHelper(elements.toList.reverse) + // scalastyle:off method.length +} +// scalastyle:off file.size.limit |
