diff options
| author | Aditya Naik | 2024-07-24 01:07:36 -0700 |
|---|---|---|
| committer | Aditya Naik | 2024-07-24 01:08:07 -0700 |
| commit | f998a07cc51d62db7f66be059d2a69d54a43e0b1 (patch) | |
| tree | 4053026a0f49a3d44c752996614c41629b186ffa | |
| parent | 9c6253691eec325a2b69abe68bfdf16f5d378827 (diff) | |
Update Builder.scala
| -rw-r--r-- | core/src/main/scala/chisel3/Aggregate.scala | 8 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/Annotation.scala | 2 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/Bits.scala | 2 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/Data.scala | 16 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/Module.scala | 39 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/RawModule.scala | 131 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/experimental/ChiselEnum.scala | 2 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/Builder.scala | 266 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/Error.scala | 2 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/firrtl/IR.scala | 7 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/package.scala | 77 | ||||
| -rw-r--r-- | src/main/scala/chisel3/util/Cat.scala | 5 | ||||
| -rw-r--r-- | src/main/scala/chisel3/util/MixedVec.scala | 2 |
13 files changed, 362 insertions, 197 deletions
diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala index ab7fe53f..2c4f67db 100644 --- a/core/src/main/scala/chisel3/Aggregate.scala +++ b/core/src/main/scala/chisel3/Aggregate.scala @@ -148,8 +148,9 @@ class Vec[T <: Data] private[chisel3] (gen: => T, val vec_length: Int) extends A // simpler. private lazy val self: Seq[T] = { val _self = Vector.fill(vec_length)(gen) + val thisNode = Node(this) // Share the same Node for all elements. for ((elt, i) <- _self.zipWithIndex) - elt.setRef(this, i) + elt.setRef(thisNode, i) _self } @@ -225,7 +226,7 @@ class Vec[T <: Data] private[chisel3] (gen: => T, val vec_length: Int) extends A port.bind(ChildBinding(this), reconstructedResolvedDirection) val i = Vec.truncateIndex(p, length) - port.setRef(this, i) + port.setRef(Node(this), i) port } @@ -728,8 +729,9 @@ abstract class Record extends Aggregate { !opaqueType || (elements.size == 1 && elements.head._1 == ""), s"Opaque types must have exactly one element with an empty name, not ${elements.size}: ${elements.keys.mkString(", ")}" ) + val thisNode = Node(this) // Share the same Node for all elements. for ((name, elt) <- elements) { - elt.setRef(this, _namespace.name(name, leadingDigitOk = true), opaque = opaqueType) + elt.setRef(thisNode, _namespace.name(name, leadingDigitOk = true), opaque = opaqueType) } } diff --git a/core/src/main/scala/chisel3/Annotation.scala b/core/src/main/scala/chisel3/Annotation.scala index f527182f..fa51fdcd 100644 --- a/core/src/main/scala/chisel3/Annotation.scala +++ b/core/src/main/scala/chisel3/Annotation.scala @@ -3,7 +3,7 @@ package chisel3.experimental import scala.language.existentials -import chisel3.internal.{Builder, InstanceId} +import chisel3.internal.Builder import chisel3.{Data, RawModule} import firrtl.Transform import firrtl.annotations._ diff --git a/core/src/main/scala/chisel3/Bits.scala b/core/src/main/scala/chisel3/Bits.scala index 927b1c19..b0fb82e1 100644 --- a/core/src/main/scala/chisel3/Bits.scala +++ b/core/src/main/scala/chisel3/Bits.scala @@ -553,7 +553,7 @@ sealed class UInt private[chisel3] (width: Width) extends Bits(width) with Num[U "Chisel 3.5" ) -def unary_! : Bool = this === 0.U(1.W) + def unary_! : Bool = this === 0.U(1.W) override def <<(that: Int): UInt = binop(UInt(this.width + that), ShiftLeftOp, validateShiftAmount(that)) diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index 1a7a0244..370a79db 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -446,7 +446,7 @@ abstract class Data extends HasId with NamedComponent { override def autoSeed(name: String): this.type = { topBindingOpt match { // Ports are special in that the autoSeed will keep the first name, not the last name - case Some(PortBinding(m)) if hasAutoSeed && Builder.currentModule.contains(m) => this + case Some(PortBinding(m)) if hasSeed && Builder.currentModule.contains(m) => this case _ => super.autoSeed(name) } } @@ -604,12 +604,12 @@ abstract class Data extends HasId with NamedComponent { ): Unit = { // requireIsHardware(this, s"data to be bulk-connected") // requireIsHardware(that, s"data to be bulk-connected") - (this.topBinding, that.topBinding) match { - case (_: ReadOnlyBinding, _: ReadOnlyBinding) => throwException(s"Both $this and $that are read-only") - // DontCare cannot be a sink (LHS) - case (_: DontCareBinding, _) => throw BiConnect.DontCareCantBeSink - case _ => // fine - } + (this.topBinding, that.topBinding) match { + case (_: ReadOnlyBinding, _: ReadOnlyBinding) => throwException(s"Both $this and $that are read-only") + // DontCare cannot be a sink (LHS) + case (_: DontCareBinding, _) => throw BiConnect.DontCareCantBeSink + case _ => // fine + } try { BiConnect.connect(this, that, Builder.currentModule.get) } catch { @@ -1046,7 +1046,7 @@ object WireDefault { /** 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 { +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. diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index 3419cda0..727a1320 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -242,6 +242,13 @@ package experimental { abstract class BaseModule extends HasId { _parent.foreach(_.addId(this)) + // Set if the returned top-level module of a nested call to the Chisel Builder, see Definition.apply + private var _circuitVar: BaseModule = null // using nullable var for better memory usage + private[chisel3] def _circuit: Option[BaseModule] = Option(_circuitVar) + private[chisel3] def _circuit_=(target: Option[BaseModule]): Unit = { + _circuitVar = target.getOrElse(null) + } + // // Builder Internals - this tracks which Module RTL construction belongs to. // @@ -409,6 +416,27 @@ package experimental { } } + /** Returns a FIRRTL ReferenceTarget that references this object, relative to an optional root. + * + * If `root` is defined, the target is a hierarchical path starting from `root`. + * + * If `root` is not defined, the target is a hierarchical path equivalent to `toAbsoluteTarget`. + * + * @note If `root` is defined, and has not finished elaboration, this must be called within `atModuleBodyEnd`. + * @note The NamedComponent must be a descendant of `root`, if it is defined. + * @note This doesn't have special handling for Views. + */ + final def toRelativeTarget(root: Option[BaseModule]): ReferenceTarget = { + val localTarget = toTarget + def makeTarget(p: BaseModule) = + p.toRelativeTarget(root).ref(localTarget.ref).copy(component = localTarget.component) + _parent match { + // case Some(ViewParent) => makeTarget(reifyParent) TODO add with datamirror + case Some(parent) => makeTarget(parent) + case None => localTarget + } + } + /** * Internal API. Returns a list of this module's generated top-level ports as a map of a String * (FIRRTL name) to the IO object. Only valid after the module is closed. @@ -454,17 +482,6 @@ 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/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index 8eeda03c..bab1a5b1 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -3,30 +3,105 @@ package chisel3 import scala.util.Try -import scala.annotation.nowarn import chisel3.experimental.BaseModule import chisel3.internal._ -import chisel3.internal.BaseModule.ModuleClone import chisel3.internal.Builder._ import chisel3.internal.firrtl._ import _root_.firrtl.annotations.{IsModule, ModuleTarget} import scala.collection.immutable.VectorBuilder +import scala.collection.mutable.ArrayBuffer /** 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 * multiple IO() declarations. */ -@nowarn("msg=class Port") // delete when Port becomes private abstract class RawModule extends BaseModule { + + /** Hook to invoke hardware generators after the rest of the Module is constructed. + * + * This is a power-user API, and should not normally be needed. + * + * In rare cases, it is necessary to run hardware generators at a late stage, but still within the scope of the + * Module. In these situations, atModuleBodyEnd may be used to register such generators. For example: + * + * {{{ + * class Example extends RawModule { + * atModuleBodyEnd { + * val extraPort0 = IO(Output(Bool())) + * extraPort0 := 0.B + * } + * } + * }}} + * + * Any generators registered with atModuleBodyEnd are the last code to execute when the Module is constructed. The + * execution order is: + * + * - The constructors of any super classes or traits the Module extends + * - The constructor of the Module itself + * - The atModuleBodyEnd generators + * + * The atModuleBodyEnd generators execute in the lexical order they appear in the Module constructor. + * + * For example: + * + * {{{ + * trait Parent { + * // Executes first. + * val foo = ... + * } + * + * class Example extends Parent { + * // Executes second. + * val bar = ... + * + * atModuleBodyEnd { + * // Executes fourth. + * val qux = ... + * } + * + * atModuleBodyEnd { + * // Executes fifth. + * val quux = ... + * } + * + * // Executes third.. + * val baz = ... + * } + * }}} + * + * If atModuleBodyEnd is used in a Definition, any generated hardware will be included in the Definition. However, it + * is currently not possible to annotate any val within atModuleBodyEnd as @public. + */ + protected def atModuleBodyEnd(gen: => Unit): Unit = { + _atModuleBodyEnd += { () => gen } + } + private val _atModuleBodyEnd = new ArrayBuffer[() => Unit] + // // RTL construction internals // // Perhaps this should be an ArrayBuffer (or ArrayBuilder), but DefModule is public and has Seq[Command] // so our best option is to share a single Seq datastructure with that private val _commands = new VectorBuilder[Command]() - private[chisel3] def addCommand(c: Command) = { + + /** The current region to which commands will be added. */ + private var _currentRegion = _commands + + private[chisel3] def changeRegion(newRegion: VectorBuilder[Command]): Unit = { + _currentRegion = newRegion + } + + private[chisel3] def withRegion[A](newRegion: VectorBuilder[Command])(thunk: => A): A = { + var oldRegion = _currentRegion + changeRegion(newRegion) + val result = thunk + changeRegion(oldRegion) + result + } + + private[chisel3] def addCommand(c: Command): Unit = { require(!_closed, "Can't write to module after module close") - _commands += c + _currentRegion += c } protected def getCommands: Seq[Command] = { require(_closed, "Can't get commands before module close") @@ -159,49 +234,3 @@ trait RequireSyncReset extends Module { override private[chisel3] def mkReset: Bool = Bool() } -/** Mix with a [[RawModule]] to automatically connect DontCare to the module's ports, wires, and children instance IOs. */ - -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 - - /* Check if a String name is a temporary name */ - def isTemp(name: String): Boolean = name.nonEmpty && name.head == '_' - - /** Creates a name String from a prefix and a seed - * @param prefix The prefix associated with the seed (must be in correct order, *not* reversed) - * @param seed The seed for computing the name (if available) - */ - def buildName(seed: String, prefix: Prefix): String = { - // Don't bother copying the String if there's no prefix - if (prefix.isEmpty) { - seed - } else { - // Using Java's String builder to micro-optimize appending a String excluding 1st character - // for temporaries - val builder = new java.lang.StringBuilder() - // Starting with _ is the indicator of a temporary - val temp = isTemp(seed) - // Make sure the final result is also a temporary if this is a temporary - if (temp) { - builder.append('_') - } - prefix.foreach { p => - builder.append(p) - builder.append('_') - } - if (temp) { - // We've moved the leading _ to the front, drop it here - builder.append(seed, 1, seed.length) - } else { - builder.append(seed) - } - builder.toString - } - } -} diff --git a/core/src/main/scala/chisel3/experimental/ChiselEnum.scala b/core/src/main/scala/chisel3/experimental/ChiselEnum.scala index 9d69fe37..d0ad6949 100644 --- a/core/src/main/scala/chisel3/experimental/ChiselEnum.scala +++ b/core/src/main/scala/chisel3/experimental/ChiselEnum.scala @@ -7,7 +7,7 @@ import chisel3._ import chisel3.internal.Builder.pushOp import chisel3.internal.firrtl.PrimOp._ import chisel3.internal.firrtl._ -import chisel3.internal.{throwException, Binding, Builder, ChildBinding, ConstrainedBinding, InstanceId} +import chisel3.internal.{throwException, Binding, Builder, ChildBinding, ConstrainedBinding} import firrtl.annotations._ object EnumAnnotations { diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index 5a6cd3a7..5782a2f5 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -11,13 +11,14 @@ import chisel3.internal.naming._ import _root_.firrtl.annotations.{CircuitName, ComponentName, IsMember, ModuleName, Named, ReferenceTarget} import _root_.firrtl.annotations.AnnotationUtils.validComponentName import _root_.firrtl.{AnnotationSeq, RenameMap} +import _root_.firrtl.{ir => fir} import chisel3.internal.Builder.Prefix import logger.LazyLogging import scala.collection.mutable import scala.annotation.tailrec -private[chisel3] class Namespace(keywords: Set[String]) { +private[chisel3] class Namespace(keywords: Set[String], separator: Char = '_') { // This HashMap is compressed, not every name in the namespace is present here. // If the same name is requested multiple times, it only takes 1 entry in the HashMap and the // value is incremented for each time the name is requested. @@ -25,13 +26,12 @@ private[chisel3] class Namespace(keywords: Set[String]) { // checking if a name is present in the Namespace is more complex than just checking the HashMap, // see getIndex below. 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 @tailrec private def rename(n: String, index: Long): String = { - val tryName = s"${n}_${index}" + val tryName = s"${n}${separator}${index}" if (names.contains(tryName)) { rename(n, index + 1) } else { @@ -40,15 +40,6 @@ private[chisel3] class Namespace(keywords: Set[String]) { } } - private def sanitize(s: String, leadingDigitOk: Boolean = false): String = { - // TODO what character set does FIRRTL truly support? using ANSI C for now - def legalStart(c: Char) = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' - def legal(c: Char) = legalStart(c) || (c >= '0' && c <= '9') - val res = if (s.forall(legal)) s else s.filter(legal) - val headOk = (!res.isEmpty) && (leadingDigitOk || legalStart(res.head)) - if (headOk) res else s"_$res" - } - /** Checks if `n` ends in `_\d+` and returns the substring before `_` if so, null otherwise */ // TODO can and should this be folded in to sanitize? Same iteration as the forall? private def prefix(n: String): Int = { @@ -58,8 +49,8 @@ private[chisel3] class Namespace(keywords: Set[String]) { i -= 1 } // Will get i == 0 for all digits or _\d+ with empty prefix, those have no prefix so returning 0 is correct - if (i == n.size) 0 // no digits - else if (n(i) != '_') 0 // no _ + if (i >= (n.size - 1)) 0 // no digits + else if (n(i) != separator) 0 // no _ else i } @@ -99,6 +90,7 @@ private[chisel3] class Namespace(keywords: Set[String]) { private[chisel3] object Namespace { /** Constructs an empty Namespace */ + def empty(separator: Char): Namespace = new Namespace(Set.empty[String], separator) def empty: Namespace = new Namespace(Set.empty[String]) } @@ -111,28 +103,7 @@ private[chisel3] class IdGen { def value: Long = counter } -/** Public API to access Node/Signal names. - * currently, the node's name, the full path name, and references to its parent Module and component. - * These are only valid once the design has been elaborated, and should not be used during its construction. - */ -trait InstanceId { - def instanceName: String - def pathName: String - def parentPathName: String - def parentModName: String - - /** Returns a FIRRTL Named that refers to this object in the elaborated hardware graph */ - def toNamed: Named - - /** Returns a FIRRTL IsMember that refers to this object in the elaborated hardware graph */ - def toTarget: IsMember - - /** Returns a FIRRTL IsMember that refers to the absolute path to this object in the elaborated hardware graph */ - def toAbsoluteTarget: IsMember -} - -private[chisel3] trait HasId extends InstanceId { - private[chisel3] def _onModuleClose: Unit = {} +private[chisel3] trait HasId extends chisel3.InstanceId { // using nullable var for better memory usage private var _parentVar: BaseModule = Builder.currentModule.getOrElse(null) private[chisel3] def _parent: Option[BaseModule] = Option(_parentVar) @@ -140,38 +111,24 @@ private[chisel3] trait HasId extends InstanceId { _parentVar = target.getOrElse(null) } - // Set if the returned top-level module of a nested call to the Chisel Builder, see Definition.apply - private var _circuitVar: BaseModule = null // using nullable var for better memory usage - private[chisel3] def _circuit: Option[BaseModule] = Option(_circuitVar) - private[chisel3] def _circuit_=(target: Option[BaseModule]): Unit = { - _circuitVar = target.getOrElse(null) - } - private[chisel3] val _id: Long = Builder.idGen.next // TODO: remove this, but its removal seems to cause a nasty Scala compiler crash. override def hashCode: Int = super.hashCode() override def equals(that: Any): Boolean = super.equals(that) - // Contains suggested seed (user-decided seed) + // Did the user suggest a name? This overrides names suggested by the compiler plugin. + private var _nameIsSuggested: Boolean = false + // Contains name seed (either user suggested or generated by the plugin). private var suggested_seedVar: String = null // using nullable var for better memory usage private def suggested_seed: Option[String] = Option(suggested_seedVar) - // Contains the seed computed automatically by the compiler plugin - private var auto_seedVar: String = null // using nullable var for better memory usage - private def auto_seed: Option[String] = Option(auto_seedVar) - // Prefix for use in naming // - Defaults to prefix at time when object is created // - Overridden when [[suggestSeed]] or [[autoSeed]] is called + // - Note that suggestSeed does *not* prevent autoSeed from changing this private var naming_prefix: Prefix = Builder.getPrefix - // Post-seed hooks called to carry the suggested seeds to other candidates as needed - private var suggest_postseed_hooks: List[String => Unit] = Nil - - // Post-seed hooks called to carry the auto seeds to other candidates as needed - private var auto_postseed_hooks: List[String => Unit] = Nil - /** Takes the last seed suggested. Multiple calls to this function will take the last given seed, unless * this HasId is a module port (see overridden method in Data.scala). * @@ -187,8 +144,9 @@ private[chisel3] trait HasId extends InstanceId { private[chisel3] def autoSeed(seed: String): this.type = forceAutoSeed(seed) // Bypass the overridden behavior of autoSeed in [[Data]], apply autoSeed even to ports private[chisel3] def forceAutoSeed(seed: String): this.type = { - auto_seedVar = seed - for (hook <- auto_postseed_hooks.reverse) { hook(seed) } + if (!_nameIsSuggested) { + suggested_seedVar = seed + } naming_prefix = Builder.getPrefix this } @@ -208,33 +166,22 @@ private[chisel3] trait HasId extends InstanceId { * If the final computed name conflicts with another name, it may get uniquified by appending * a digit at the end. * - * Is a higher priority than [[autoSeed]], in that regardless of whether [[autoSeed]] + * Is a higher priority than `autoSeed`, in that regardless of whether `autoSeed` * was called, [[suggestName]] will always take precedence. * * @param seed The seed for the name of this component * @return this object */ def suggestName(seed: => String): this.type = { - if (suggested_seed.isEmpty) { + if (!_nameIsSuggested) { suggested_seedVar = seed // Only set the prefix if a seed hasn't been suggested naming_prefix = Builder.getPrefix - for (hook <- suggest_postseed_hooks.reverse) { hook(seed) } + _nameIsSuggested = true } this } - // Internal version of .suggestName that can override a user-suggested name - // This only exists for maintaining "val io" naming in compatibility-mode Modules without IO - // wrapping - private[chisel3] def forceFinalName(seed: String): this.type = { - // This could be called with user prefixes, ignore them - noPrefix { - suggested_seedVar = seed - this.suggestName(seed) - } - } - /** Computes the name of this HasId, if one exists * @param defaultSeed Optionally provide default seed for computing the name * @return the name, if it can be computed @@ -249,24 +196,47 @@ private[chisel3] trait HasId extends InstanceId { * * @return the current calculation of a name, if it exists */ - private[chisel3] def seedOpt: Option[String] = suggested_seed.orElse(auto_seed) + private[chisel3] def seedOpt: Option[String] = suggested_seed /** @return Whether either autoName or suggestName has been called */ def hasSeed: Boolean = seedOpt.isDefined - private[chisel3] def hasAutoSeed: Boolean = auto_seed.isDefined - - private[chisel3] def addSuggestPostnameHook(hook: String => Unit): Unit = suggest_postseed_hooks ::= hook - private[chisel3] def addAutoPostnameHook(hook: String => Unit): Unit = auto_postseed_hooks ::= hook - // Uses a namespace to convert suggestion into a true name // Will not do any naming if the reference already assigned. // (e.g. tried to suggest a name to part of a Record) - private[chisel3] def forceName(default: => String, namespace: Namespace): Unit = + private[chisel3] def forceName( + default: => String, + namespace: Namespace, + errorIfDup: Boolean = false, + refBuilder: String => Arg = Ref(_) + ): Unit = if (_ref.isEmpty) { - val candidate_name = _computeName(Some(default)).get + val candidate_name = _computeName(Some(default).filterNot(_ => errorIfDup)).getOrElse { + throwException( + s"Attempted to name a nameless IO port ($this): this is usually caused by instantiating an IO but not assigning it to a val.\n" + + s"Assign $this to a val, or explicitly call suggestName to seed a unique name" + ) + } + + val sanitized = sanitize(candidate_name) val available_name = namespace.name(candidate_name) - setRef(Ref(available_name)) + + // Check for both cases of name duplication + if (errorIfDup && (available_name != sanitized)) { + // If sanitization occurred, then the sanitized name duplicate an existing name + if ((candidate_name != sanitized)) { + Builder.error( + s"Attempted to name $this with an unsanitary name '$candidate_name': sanitization results in a duplicated name '$sanitized'. Please seed a more unique name" + ) + } else { + // Otherwise the candidate name duplicates an existing name + Builder.error( + s"Attempted to name $this with a duplicated name '$candidate_name'. Use suggestName to seed a unique name" + ) + } + } + + setRef(refBuilder(available_name)) // Clear naming prefix to free memory naming_prefix = Nil } @@ -279,32 +249,26 @@ private[chisel3] trait HasId extends InstanceId { _refVar = imm } } - private[chisel3] def setRef(parent: HasId, name: String, opaque: Boolean = false): Unit = { - if (!opaque) setRef(Slot(Node(parent), name)) - else setRef(OpaqueSlot(Node(parent))) + private[chisel3] def setRef(parent: Node, name: String, opaque: Boolean = false): Unit = { + if (!opaque) setRef(Slot(parent, name)) + else setRef(OpaqueSlot(parent)) } - private[chisel3] def setRef(parent: HasId, index: Int): Unit = setRef(Index(Node(parent), ILit(index))) - private[chisel3] def setRef(parent: HasId, index: UInt): Unit = setRef(Index(Node(parent), index.ref)) + private[chisel3] def setRef(parent: Node, index: Int): Unit = setRef(LitIndex(parent, index)) + private[chisel3] def setRef(parent: Node, index: UInt): Unit = index.litOption match { + case Some(lit) if lit.isValidInt => setRef(LitIndex(parent, lit.intValue)) + case _ => setRef(Index(parent, index.ref)) + } 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 => - // This is super hacky but this is just for a short term deprecation - // These accesses occur after Chisel elaboration so we cannot use the normal - // Builder.deprecated mechanism, we have to create our own one off ErrorLog and print the - // warning right away. - // It's especially bad because --warnings-as-errors does not work with these warnings - val errors = new ErrorLog(false) - val logger = new _root_.logger.Logger(this.getClass.getName) - val msg = - "Accessing the .instanceName or .toTarget of non-hardware Data is deprecated" + _errorContext + ". " + - "This will become an error in Chisel 3.6." - errors.deprecated(msg, None) - errors.checkpoint(logger) - _computeName(None).get + case None => { + throwException( + "You cannot access the .instanceName or .toTarget of non-hardware Data" + _errorContext + ) + } } private[chisel3] def _errorContext: String = { @@ -314,6 +278,7 @@ private[chisel3] trait HasId extends InstanceId { } val parentGuess: String = _parent match { + // case Some(ViewParent) => s", in module '${reifyParent.pathName}'" TODO with datamirror case Some(p) => s", in module '${p.pathName}'" case None => "" } @@ -345,13 +310,18 @@ private[chisel3] trait HasId extends InstanceId { 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 { + def circuitName: String = _parent match { case None => - _circuit match { - case None => instanceName - case Some(o) => o.circuitName + // Only modules have circuits + this match { + case b: BaseModule => + b._circuit match { + case Some(c) => c.circuitName + case None => instanceName + } + case _ => instanceName } + // case Some(ViewParent) => reifyParent.circuitName TODO with datamirror case Some(p) => p.circuitName } @@ -414,6 +384,26 @@ private[chisel3] trait NamedComponent extends HasId { } } + /** Returns a FIRRTL ReferenceTarget that references this object, relative to an optional root. + * + * If `root` is defined, the target is a hierarchical path starting from `root`. + * + * If `root` is not defined, the target is a hierarchical path equivalent to `toAbsoluteTarget`. + * + * @note If `root` is defined, and has not finished elaboration, this must be called within `atModuleBodyEnd`. + * @note The NamedComponent must be a descendant of `root`, if it is defined. + * @note This doesn't have special handling for Views. + */ + final def toRelativeTarget(root: Option[BaseModule]): ReferenceTarget = { + val localTarget = toTarget + def makeTarget(p: BaseModule) = + p.toRelativeTarget(root).ref(localTarget.ref).copy(component = localTarget.component) + _parent match { + case Some(parent) => makeTarget(parent) + case None => localTarget + } + } + private def assertValidTarget(): Unit = { val isVecSubaccess = getOptionRef.map { case Index(_, _: ULit) => true // Vec literal indexing @@ -451,10 +441,6 @@ private[chisel3] class DynamicContext( val newAnnotations = ArrayBuffer[ChiselMultiAnnotation]() var currentModule: Option[BaseModule] = None - // Enum annotations are added every time a ChiselEnum is bound - // To keep the number down, we keep them unique in the annotations - val enumAnnos = mutable.HashSet[ChiselAnnotation]() - /** Contains a mapping from a elaborated module to their aspect * Set by [[ModuleAspect]] */ @@ -482,6 +468,9 @@ private[chisel3] object Builder extends LazyLogging { dynamicContextVar.value.get } + /** Check if we are in a Builder context */ + def inContext: Boolean = dynamicContextVar.value.isDefined + // Used to suppress warnings when casting from a UInt to an Enum var suppressEnumCastWarning: Boolean = false @@ -513,11 +502,21 @@ private[chisel3] object Builder extends LazyLogging { def idGen: IdGen = chiselContext.get.idGen - def globalNamespace: Namespace = dynamicContext.globalNamespace + def globalNamespace: Namespace = dynamicContext.globalNamespace + // def globalIdentifierNamespace: Namespace = dynamicContext.globalIdentifierNamespace + + // A mapping from previously named bundles to their hashed structural/FIRRTL types, for + // disambiguation purposes when emitting type aliases + // Records are used as the key for this map to both represent their alias name and preserve + // the chisel Bundle structure when passing everything off to the Converter + private[chisel3] val aliasMap: mutable.LinkedHashMap[String, (fir.Type)] = + mutable.LinkedHashMap.empty[String, (fir.Type)] + def components: ArrayBuffer[Component] = dynamicContext.components + def annotations: ArrayBuffer[ChiselAnnotation] = dynamicContext.annotations - def enumAnnos: mutable.HashSet[ChiselAnnotation] = dynamicContext.enumAnnos + // def contextCache: BuilderContextCache = dynamicContext.contextCache // TODO : Unify this with annotations in the future - done this way for backward compatability def newAnnotations: ArrayBuffer[ChiselMultiAnnotation] = dynamicContext.newAnnotations @@ -542,8 +541,11 @@ private[chisel3] object Builder extends LazyLogging { case Slot(_, field) => Some(field) // Record case OpaqueSlot(_) => None // OpaqueSlots don't contribute to the name case Index(_, ILit(n)) => Some(n.toString) // Vec static indexing + case LitIndex(_, n) => Some(n.toString) // Vec static indexing case Index(_, ULit(n, _)) => Some(n.toString) // Vec lit indexing case Index(_, _: Node) => None // Vec dynamic indexing + case f => + throw new InternalErrorException(s"Match Error: field=$f") } def map2[A, B](a: Option[A], b: Option[A])(f: (A, A) => B): Option[B] = a.flatMap(ax => b.map(f(ax, _))) @@ -666,9 +668,11 @@ private[chisel3] object Builder extends LazyLogging { } def forcedClock: Clock = currentClock.getOrElse( + // TODO add implicit clock change to Builder.exception throwException("Error: No implicit clock.") ) def forcedReset: Reset = currentReset.getOrElse( + // TODO add implicit clock change to Builder.exception throwException("Error: No implicit reset.") ) @@ -698,28 +702,39 @@ private[chisel3] object Builder extends LazyLogging { for ((elt, i) <- iter.zipWithIndex) { nameRecursively(s"${prefix}_${i}", elt, namer) } + case product: Product => + product.productIterator.zip(product.productElementNames).foreach { + case (elt, fullName) => + val name = fullName.stripPrefix("_") + val prefixedName = if (name.nonEmpty) s"${prefix}_${name}" else prefix + nameRecursively(prefixedName, elt, namer) + } case _ => // Do nothing } def errors: ErrorLog = dynamicContext.errors def error(m: => String): Unit = { // If --throw-on-first-error is requested, throw an exception instead of aggregating errors - if (dynamicContextVar.value.isDefined && !dynamicContextVar.value.get.throwOnFirstError) { + if (dynamicContextVar.value.isDefined) { errors.error(m) } else { throwException(m) } } - def warning(m: => String): Unit = if (dynamicContextVar.value.isDefined) errors.warning(m) + def warningNoLoc(m: => String): Unit = if (dynamicContextVar.value.isDefined) errors.warningNoLoc(m) - def deprecated(m: => String, location: Option[String] = None): Unit = + + def warning(warning: Warning): Unit = + if (dynamicContextVar.value.isDefined) errors.warning("warn") // TODO fix when synced Error.scala + + def deprecated(m: => String, location: Option[String] = None): Unit = if (dynamicContextVar.value.isDefined) errors.deprecated(m, location) /** Record an exception as an error, and throw it. * * @param m exception message */ - @throws(classOf[ChiselException]) + @throws(classOf[chisel3.ChiselException]) def exception(m: => String): Nothing = { error(m) throwException(m) @@ -730,8 +745,21 @@ private[chisel3] object Builder extends LazyLogging { dynamicContext: DynamicContext, forceModName: Boolean = true ): (Circuit, T) = { + buildImpl(f, dynamicContext, forceModName) + } + + private[chisel3] def buildImpl[T <: BaseModule]( + f: => T, + dynamicContext: DynamicContext, + forceModName: Boolean = true + ): (Circuit, T) = { dynamicContextVar.withValue(Some(dynamicContext)) { - // in tiny designs/testcases that never access anything in chisel3.internal + // in tiny designs/testcases that never access anything in + // chisel3.internal Must initialize the singleton or + // OutOfMemoryErrors and StackOverflowErrors will instead report + // as "java.lang.NoClassDefFoundError: Could not initialize + // class scala.util.control.NonFatal$". + scala.util.control.NonFatal: Unit logger.info("Elaborating design...") val mod = f if (forceModName) { // This avoids definition name index skipping with D/I @@ -740,7 +768,15 @@ private[chisel3] object Builder extends LazyLogging { errors.checkpoint(logger) logger.info("Done elaborating.") - (Circuit(components.last.name, components.toSeq, annotations.toSeq, null, newAnnotations.toSeq), mod) + ( + Circuit( + components.last.name, + components.toSeq, + annotations.toSeq, + null, + newAnnotations.toSeq), + mod + ) } } initializeSingletons() diff --git a/core/src/main/scala/chisel3/internal/Error.scala b/core/src/main/scala/chisel3/internal/Error.scala index da968d2a..b7c4be35 100644 --- a/core/src/main/scala/chisel3/internal/Error.scala +++ b/core/src/main/scala/chisel3/internal/Error.scala @@ -324,7 +324,7 @@ private class Error(msg: => String, line: Option[StackTraceElement]) extends Log def format: String = tag("error", Console.RED) } -private class Warning(msg: => String, line: Option[StackTraceElement]) extends LogEntry(msg, line) { +class Warning(msg: => String, line: Option[StackTraceElement]) extends LogEntry(msg, line) { def format: String = tag("warn", Console.YELLOW) } diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 551b6138..6789b041 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -209,6 +209,13 @@ case class Index(imm: Arg, value: Arg) extends Arg { override def localName: String = s"${imm.localName}[${value.localName}]" } +// Like index above, except the index is a literal, used for elements of Vecs +case class LitIndex(imm: Arg, value: Int) extends Arg { + def name: String = s"[$value]" + override def contextualName(ctx: Component): String = s"${imm.contextualName(ctx)}[$value]" + override def localName: String = s"${imm.localName}[$value]" +} + object Width { def apply(x: Int): Width = KnownWidth(x) def apply(): Width = UnknownWidth() diff --git a/core/src/main/scala/chisel3/package.scala b/core/src/main/scala/chisel3/package.scala index 72a3515c..6633e7ab 100644 --- a/core/src/main/scala/chisel3/package.scala +++ b/core/src/main/scala/chisel3/package.scala @@ -1,11 +1,15 @@ // SPDX-License-Identifier: Apache-2.0 import chisel3.internal.firrtl.BinaryPoint +import chisel3.internal.ExceptionHelpers import java.util.{MissingFormatArgumentException, UnknownFormatConversionException} -import scala.collection.mutable import chisel3.experimental.VecLiterals._ import chisel3.experimental.BundleLiterals._ +import scala.collection.mutable +import scala.annotation.tailrec + + /** This package contains the main chisel3 API. */ package object chisel3 { @@ -181,8 +185,17 @@ package object chisel3 { // object UInt extends UIntFactory // object SInt extends SIntFactory // object Bool extends BoolFactory + /** Public API to access Node/Signal names. + * currently, the node's name, the full path name, and references to its parent Module and component. + * These are only valid once the design has been elaborated, and should not be used during its construction. + */ - type InstanceId = internal.InstanceId + trait InstanceId { + def instanceName: String + def pathName: String + def parentPathName: String + def parentModName: String + } @deprecated("MultiIOModule is now just Module", "Chisel 3.5") type MultiIOModule = chisel3.Module @@ -332,7 +345,65 @@ package object chisel3 { implicit def string2Printable(str: String): Printable = PString(str) - type ChiselException = internal.ChiselException + class InternalErrorException(message: String, cause: Throwable = null) + extends ChiselException( + "Internal Error: Please file an issue at https://github.com/chipsalliance/chisel3/issues:" + message, + cause + ) + + class ChiselException(message: String, cause: Throwable = null) extends Exception(message, cause, true, true) { + + /** Examine a [[Throwable]], to extract all its causes. Innermost cause is first. + * @param throwable an exception to examine + * @return a sequence of all the causes with innermost cause first + */ + @tailrec + private def getCauses(throwable: Throwable, acc: Seq[Throwable] = Seq.empty): Seq[Throwable] = + throwable.getCause() match { + case null => throwable +: acc + case a => getCauses(a, throwable +: acc) + } + + /** Returns true if an exception contains */ + private def containsBuilder(throwable: Throwable): Boolean = + throwable + .getStackTrace() + .collectFirst { + 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 + * a stack trace element whose declaring class is the [[ExceptionHelpers.builderName]]. If no such element exists, return this + * [[ChiselException]]. + */ + private lazy val likelyCause: Throwable = + getCauses(this).collectFirst { case a if containsBuilder(a) => a }.getOrElse(this) + + /** For an exception, return a stack trace trimmed to user code only + * + * This does the following actions: + * + * 1. Trims the top of the stack trace while elements match [[ExceptionHelpers.packageTrimlist]] + * 2. Trims the bottom of the stack trace until an element matches [[ExceptionHelpers.builderName]] + * 3. Trims from the [[ExceptionHelpers.builderName]] all [[ExceptionHelpers.packageTrimlist]] + * + * @param throwable the exception whose stack trace should be trimmed + * @return an array of stack trace elements + */ + private def trimmedStackTrace(throwable: Throwable): Array[StackTraceElement] = { + def isBlacklisted(ste: StackTraceElement) = { + val packageName = ste.getClassName().takeWhile(_ != '.') + ExceptionHelpers.packageTrimlist.contains(packageName) + } + + val trimmedLeft = throwable.getStackTrace().view.dropWhile(isBlacklisted) + val trimmedReverse = trimmedLeft.toIndexedSeq.reverse.view + .dropWhile(ste => !ste.getClassName.startsWith(ExceptionHelpers.builderName)) + .dropWhile(isBlacklisted) + trimmedReverse.toIndexedSeq.reverse.toArray + } + } // Debugger/Tester access to internal Chisel data structures and methods. def getDataElements(a: Aggregate): Seq[Element] = { diff --git a/src/main/scala/chisel3/util/Cat.scala b/src/main/scala/chisel3/util/Cat.scala index 4f4c87ca..535fdfa8 100644 --- a/src/main/scala/chisel3/util/Cat.scala +++ b/src/main/scala/chisel3/util/Cat.scala @@ -19,7 +19,10 @@ object Cat { /** Concatenates the argument data elements, in argument order, together. The first argument * forms the most significant bits, while the last argument forms the least significant bits. */ - def apply[T <: Bits](@deprecatedName(Symbol("a"), "Chisel 3.5") a: T, @deprecatedName(Symbol("r"), "Chisel 3.5") r: T*): UInt = apply( + def apply[T <: Bits]( + @deprecatedName(Symbol("a"), "Chisel 3.5") a: T, + @deprecatedName(Symbol("r"), "Chisel 3.5") r: T* + ): UInt = apply( a :: r.toList ) diff --git a/src/main/scala/chisel3/util/MixedVec.scala b/src/main/scala/chisel3/util/MixedVec.scala index cbf0dfe9..349da58e 100644 --- a/src/main/scala/chisel3/util/MixedVec.scala +++ b/src/main/scala/chisel3/util/MixedVec.scala @@ -121,7 +121,7 @@ final class MixedVec[T <: Data](private val eltsIn: Seq[T]) extends Record with */ def length: Int = elts.length - override val elements = ListMap(elts.zipWithIndex.map { case (element, index) => (index.toString, element) }*) + override val elements = ListMap(elts.zipWithIndex.map { case (element, index) => (index.toString, element) } *) // Need to re-clone again since we could have been bound since object creation. override def cloneType: this.type = MixedVec(elts.map(_.cloneTypeFull)).asInstanceOf[this.type] |
