diff options
| author | Jack Koenig | 2020-03-22 18:13:58 -0700 |
|---|---|---|
| committer | Jack Koenig | 2020-03-25 19:17:15 -0700 |
| commit | fbf5e6f1a0e8bf535d465b748ad554575fe62156 (patch) | |
| tree | 578858ab6d219ca6daf44cf87b73f75054989097 /chiselFrontend/src/main/scala/chisel3/Aggregate.scala | |
| parent | b2e004fb615a3c931d910a338b9faa99c1c975d7 (diff) | |
Rename subprojects to more canonical names
* Rename coreMacros to macros
* Rename chiselFrontend to core
Also make each subproject publish with "chisel3-" as a prefix
Diffstat (limited to 'chiselFrontend/src/main/scala/chisel3/Aggregate.scala')
| -rw-r--r-- | chiselFrontend/src/main/scala/chisel3/Aggregate.scala | 1016 |
1 files changed, 0 insertions, 1016 deletions
diff --git a/chiselFrontend/src/main/scala/chisel3/Aggregate.scala b/chiselFrontend/src/main/scala/chisel3/Aggregate.scala deleted file mode 100644 index 6c1e8dfb..00000000 --- a/chiselFrontend/src/main/scala/chisel3/Aggregate.scala +++ /dev/null @@ -1,1016 +0,0 @@ -// 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.experimental.EnumType -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] = None // 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 = if (that.isLit) that else 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 = Input(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) - } - - // Since all children are the same, we can just use the sample_element rather than all children - // .get is safe because None means mixed directions, we only pass 1 so that's not possible - direction = ActualDirection.fromChildren(Set(sample_element.direction), resolvedDirection).get - } - - // 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 lazy val self: Seq[T] = { - val _self = Vector.fill(length)(gen) - for ((elt, i) <- _self.zipWithIndex) - elt.setRef(this, i) - _self - } - - /** - * 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, s"Cannot assign to a Vec of length ${this.length} from a Seq of different 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(this, "vec") - 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(")") - } - - /** A reduce operation in a tree like structure instead of sequentially - * @example An adder tree - * {{{ - * val sumOut = inputNums.reduceTree((a: T, b: T) => (a + b)) - * }}} - */ - def reduceTree(redOp: (T, T) => T): T = macro VecTransform.reduceTreeDefault - - /** A reduce operation in a tree like structure instead of sequentially - * @example A pipelined adder tree - * {{{ - * val sumOut = inputNums.reduceTree( - * (a: T, b: T) => RegNext(a + b), - * (a: T) => RegNext(a) - * ) - * }}} - */ - def reduceTree(redOp: (T, T) => T, layerOp: (T) => T): T = macro VecTransform.reduceTree - - def do_reduceTree(redOp: (T, T) => T, layerOp: (T) => T = (x: T) => x) - (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) : T = { - require(!isEmpty, "Cannot apply reduction on a vec of size 0") - var curLayer = this - while (curLayer.length > 1) { - curLayer = VecInit(curLayer.grouped(2).map( x => - if (x.length == 1) layerOp(x(0)) else redOp(x(0), x(1)) - ).toSeq) - } - curLayer(0) - } -} - -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) - - /** 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 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 - } - 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 - 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 <> io.inPacket - * io.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 |
