diff options
| author | Adam Izraelevitz | 2021-09-05 12:11:32 -0700 |
|---|---|---|
| committer | GitHub | 2021-09-05 12:11:32 -0700 |
| commit | 9fa8da227569455a77596355aeb114f9c164510a (patch) | |
| tree | 3be3dd579fcc7ae83297d3c28020e97417a6b984 /core | |
| parent | 7fb2c1ebc23ca07e5de6416a284e1be1b62a48ac (diff) | |
Add Definition and Instance API (#2045)
This introduces a new experimental API for module instantiation that disentagles
elaborating the definition (or implementation) from instantiation of
a given module. This solves Chisel's longstanding reliance on
"Deduplication" for generating Verilog with multiple instances of the
same module.
The new API resides in package chisel3.experimental.hierarchy. Please
see the hierarchy ScalaDoc, documentation, and tests for examples of
use.
Co-authored-by: Jack Koenig <koenig@sifive.com>
Co-authored-by: Megan Wachs <megan@sifive.com>
Co-authored-by: Schuyler Eldridge <schuyler.eldridge@sifive.com>
Diffstat (limited to 'core')
12 files changed, 846 insertions, 28 deletions
diff --git a/core/src/main/scala/chisel3/BlackBox.scala b/core/src/main/scala/chisel3/BlackBox.scala index 0c42600f..38b08193 100644 --- a/core/src/main/scala/chisel3/BlackBox.scala +++ b/core/src/main/scala/chisel3/BlackBox.scala @@ -158,11 +158,12 @@ abstract class BlackBox(val params: Map[String, Param] = Map.empty[String, Param val namedPorts = _io.elements.toSeq.reverse // ListMaps are stored in reverse order - // setRef is not called on the actual io. // There is a risk of user improperly attempting to connect directly with io // Long term solution will be to define BlackBox IO differently as part of // it not descending from the (current) Module for ((name, port) <- namedPorts) { + // We are setting a 'fake' ref for io, so that cloneType works but if a user connects to io, it still fails. + this.findPort("io").get.setRef(ModuleIO(internal.ViewParent, ""), force = true) // We have to force override the _ref because it was set during IO binding port.setRef(ModuleIO(this, _namespace.name(name)), force = true) } diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index 64065ec9..d0171693 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -121,7 +121,8 @@ abstract class Module(implicit moduleCompileOptions: CompileOptions) extends Raw private[chisel3] def mkReset: Reset = { // Top module and compatibility mode use Bool for reset - val inferReset = _parent.isDefined && moduleCompileOptions.inferModuleReset + // Note that a Definition elaboration will lack a parent, but still not be a Top module + val inferReset = (_parent.isDefined || Builder.inDefinition) && moduleCompileOptions.inferModuleReset if (inferReset) Reset() else Bool() } @@ -181,13 +182,31 @@ package experimental { package internal { import chisel3.experimental.BaseModule + import chisel3.experimental.hierarchy.IsInstantiable object BaseModule { + /** Represents a clone of an underlying object. This is used to support CloneModuleAsRecord and Instance/Definition. + * + * @note We don't actually "clone" anything in the traditional sense but is a placeholder so we lazily clone internal state + */ + private [chisel3] trait IsClone[+T] { + // Underlying object of which this is a clone of + val _proto: T + def getProto: T = _proto + def isACloneOf(a: Any): Boolean = this == a || _proto == a + } + // Private internal class to serve as a _parent for Data in cloned ports - private[chisel3] class ModuleClone(_proto: BaseModule) extends PseudoModule { + private[chisel3] class ModuleClone[T <: BaseModule] (val _proto: T) extends PseudoModule with IsClone[T] { + override def toString = s"ModuleClone(${_proto})" + def getPorts = _portsRecord // ClonePorts that hold the bound ports for this module // Used for setting the refs of both this module and the Record private[BaseModule] var _portsRecord: Record = _ + // This is necessary for correctly supporting .toTarget on a Module Clone. If it is made from the + // Instance/Definition API, it should return an instanceTarget. If made from CMAR, it should return a + // ModuleTarget. + private[chisel3] var _madeFromDefinition: Boolean = false // Don't generate a component, but point to the one for the cloned Module private[chisel3] def generateComponent(): Option[Component] = { require(!_closed, "Can't generate module more than once") @@ -195,9 +214,15 @@ package internal { _component = _proto._component None } + // Maps proto ports to module clone's ports + private[chisel3] lazy val ioMap: Map[Data, Data] = { + val name2Port = getPorts.elements + _proto.getChiselPorts.map { case (name, data) => data -> name2Port(name) }.toMap + } // This module doesn't actually exist in the FIRRTL so no initialization to do private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () + // Name of this instance's module is the same as the proto's name override def desiredName: String = _proto.name private[chisel3] def setRefAndPortsRef(namespace: Namespace): Unit = { @@ -215,6 +240,53 @@ package internal { } } + /** Represents a module viewed from a different instance context. + * + * @note Why do we need both ModuleClone and InstanceClone? If we are annotating a reference in a module-clone, + * all submodules must be also be 'cloned' so the toTarget can be computed properly. However, we don't need separate + * connectable ports for this instance; all that's different from the proto is the parent. + * + * @note In addition, the instance name of an InstanceClone is going to be the SAME as the proto, but this is not true + * for ModuleClone. + */ + private[chisel3] final class InstanceClone[T <: BaseModule] (val _proto: T, val instName: () => String) extends PseudoModule with IsClone[T] { + override def toString = s"InstanceClone(${_proto})" + // No addition components are generated + private[chisel3] def generateComponent(): Option[Component] = None + // Necessary for toTarget to work + private[chisel3] def setAsInstanceRef(): Unit = { this.setRef(Ref(instName())) } + // This module doesn't acutally exist in the FIRRTL so no initialization to do + private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () + // Instance name is the same as proto's instance name + override def instanceName = instName() + // Module name is the same as proto's module name + override def desiredName: String = _proto.name + } + + /** Represents a Definition root module, when accessing something from a definition + * + * @note This is necessary to distinguish between the toTarget behavior for a Module returned from a Definition, + * versus a normal Module. A normal Module.toTarget will always return a local target. If calling toTarget + * on a Module returned from a Definition (and thus wrapped in an Instance), we need to return the non-local + * target whose root is the Definition. This DefinitionClone is used to represent the root parent of the + * InstanceClone (which represents the returned module). + */ + private[chisel3] class DefinitionClone[T <: BaseModule] (val _proto: T) extends PseudoModule with IsClone[T] { + override def toString = s"DefinitionClone(${_proto})" + // No addition components are generated + private[chisel3] def generateComponent(): Option[Component] = None + // Necessary for toTarget to work + private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = () + // Module name is the same as proto's module name + override def desiredName: String = _proto.name + } + + /** @note If we are cloning a non-module, we need another object which has the proper _parent set! + */ + private[chisel3] final class InstantiableClone[T <: IsInstantiable] (val _proto: T) extends IsClone[T] { + private[chisel3] var _parent: Option[BaseModule] = internal.Builder.currentModule + } + /** Record type returned by CloneModuleAsRecord * * @note These are not true Data (the Record doesn't correspond to anything in the emitted @@ -232,6 +304,9 @@ package internal { // We make this before clonePorts because we want it to come up first in naming in // currentModule val cloneParent = Module(new ModuleClone(proto)) + require(proto.isClosed, "Can't clone a module before module close") + require(cloneParent.getOptionRef.isEmpty, "Can't have ref set already!") + // Fake Module to serve as the _parent of the cloned ports // We don't create this inside the ModuleClone because we need the ref to be set by the // currentModule (and not clonePorts) val clonePorts = new ClonePorts(proto.getModulePorts: _*) @@ -253,10 +328,19 @@ package internal { package experimental { + import chisel3.experimental.hierarchy.IsInstantiable + + object BaseModule { + implicit class BaseModuleExtensions[T <: BaseModule](b: T) { + import chisel3.experimental.hierarchy.{Instance, Definition} + def toInstance: Instance[T] = new Instance(Left(b)) + def toDefinition: Definition[T] = new Definition(Left(b)) + } + } /** Abstract base class for Modules, an instantiable organizational unit for RTL. */ // TODO: seal this? - abstract class BaseModule extends HasId { + abstract class BaseModule extends HasId with IsInstantiable { _parent.foreach(_.addId(this)) // @@ -379,13 +463,24 @@ package experimental { * * @note Should not be called until circuit elaboration is complete */ - final def toNamed: ModuleName = toTarget.toNamed + final def toNamed: ModuleName = ModuleTarget(this.circuitName, this.name).toNamed /** Returns a FIRRTL ModuleTarget that references this object * * @note Should not be called until circuit elaboration is complete */ - final def toTarget: ModuleTarget = ModuleTarget(this.circuitName, this.name) + final def toTarget: IsModule = { + this match { + case m: internal.BaseModule.InstanceClone[_] if m._parent.nonEmpty => m._parent.get.toTarget.instOf(instanceName, name) + case m: internal.BaseModule.InstanceClone[_] => ModuleTarget(this.circuitName, this.name) + case m: internal.BaseModule.ModuleClone[_] if m._madeFromDefinition => m._parent.get.toTarget.instOf(instanceName, name) + case m: internal.BaseModule.ModuleClone[_] => ModuleTarget(this.circuitName, this.name) + // Without this, we get the wrong CircuitName for the Definition + case m: internal.BaseModule.DefinitionClone[_] if m._circuit.nonEmpty => ModuleTarget(this._circuit.get.circuitName, this.name) + case m: internal.BaseModule.DefinitionClone[_] => ModuleTarget(this.circuitName, this.name) + case m => ModuleTarget(this.circuitName, this.name) + } + } /** Returns a FIRRTL ModuleTarget that references this object * @@ -393,7 +488,7 @@ package experimental { */ final def toAbsoluteTarget: IsModule = { _parent match { - case Some(parent) => parent.toAbsoluteTarget.instOf(this.instanceName, toTarget.module) + case Some(parent) => parent.toAbsoluteTarget.instOf(this.instanceName, name) case None => // FIXME Special handling for Views - evidence of "weirdness" of .toAbsoluteTarget // In theory, .toAbsoluteTarget should not be necessary, .toTarget combined with the diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index 74e9db6c..27f16ad4 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -7,7 +7,7 @@ import scala.util.Try import scala.language.experimental.macros import chisel3.experimental.{BaseModule, BaseSim} import chisel3.internal._ -import chisel3.internal.BaseModule.ModuleClone +import chisel3.internal.BaseModule.{ModuleClone, InstanceClone} import chisel3.internal.Builder._ import chisel3.internal.firrtl._ import chisel3.internal.sourceinfo.UnlocatableSourceInfo @@ -77,7 +77,8 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) // All suggestions are in, force names to every node. for (id <- getIds) { id match { - case id: ModuleClone => id.setRefAndPortsRef(_namespace) // special handling + case id: ModuleClone[_] => id.setRefAndPortsRef(_namespace) // special handling + case id: InstanceClone[_] => id.setAsInstanceRef() case id: BaseModule => id.forceName(None, default=id.desiredName, _namespace) case id: MemBase[_] => id.forceName(None, default="MEM", _namespace) case id: BaseSim => id.forceName(None, default="SIM", _namespace) @@ -158,6 +159,10 @@ trait RequireSyncReset extends Module { package object internal { + import scala.annotation.implicitNotFound + @implicitNotFound("You are trying to access a macro-only API. Please use the @public annotation instead.") + trait MacroGenerated + /** Marker trait for modules that are not true modules */ private[chisel3] trait PseudoModule extends BaseModule diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala new file mode 100644 index 00000000..2e917dfa --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import scala.language.experimental.macros + +import chisel3._ +import scala.collection.mutable.HashMap +import chisel3.internal.{Builder, DynamicContext} +import chisel3.internal.sourceinfo.{DefinitionTransform, DefinitionWrapTransform, SourceInfo} +import chisel3.experimental.BaseModule +import chisel3.internal.BaseModule.IsClone + +/** User-facing Definition type. + * Represents a definition of an object of type [[A]] which are marked as @instantiable + * Can be created using Definition.apply method. + * + * These definitions are then used to create multiple [[Instance]]s. + * + * @param cloned The internal representation of the instance, which may be either be directly the object, or a clone of an object + */ +case class Definition[+A] private[chisel3] (private[chisel3] cloned: Either[A, IsClone[A]]) extends IsLookupable { + private[chisel3] def proto: A = cloned match { + case Left(value: A) => value + case Right(i: IsClone[A]) => i._proto + } + /** Used by Chisel's internal macros. DO NOT USE in your normal Chisel code!!! + * Instead, mark the field you are accessing with [[@public]] + * + * Given a selector function (that) which selects a member from the original, return the + * corresponding member from the instance. + * + * Our @instantiable and @public macros generate the calls to this apply method + * + * By calling this function, we summon the proper Lookupable typeclass from our implicit scope. + * + * @param that a user-specified lookup function + * @param lookup typeclass which contains the correct lookup function, based on the types of A and B + * @param macroGenerated a value created in the macro, to make it harder for users to use this API + */ + def _lookup[B, C](that: A => B)(implicit lookup: Lookupable[B], macroGenerated: chisel3.internal.MacroGenerated): lookup.C = { + lookup.definitionLookup(that, this) + } + + /** Updated by calls to [[apply]], to avoid recloning returned Data's */ + private [chisel3] val cache = HashMap[Data, Data]() + + + /** @return the context of any Data's return from inside the instance */ + private[chisel3] def getInnerDataContext: Option[BaseModule] = proto match { + case value: BaseModule => + val newChild = Module.do_apply(new internal.BaseModule.DefinitionClone(value))(chisel3.internal.sourceinfo.UnlocatableSourceInfo, chisel3.ExplicitCompileOptions.Strict) + newChild._circuit = value._circuit.orElse(Some(value)) + newChild._parent = None + Some(newChild) + case value: IsInstantiable => None + } + +} + +/** Factory methods for constructing [[Definition]]s */ +object Definition extends SourceInfoDoc { + implicit class DefinitionBaseModuleExtensions[T <: BaseModule](d: Definition[T]) { + /** If this is an instance of a Module, returns the toTarget of this instance + * @return target of this instance + */ + def toTarget = d.proto.toTarget + + /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance + * @return absoluteTarget of this instance + */ + def toAbsoluteTarget = d.proto.toAbsoluteTarget + } + /** A construction method to build a Definition of a Module + * + * @param proto the Module being defined + * + * @return the input module as a Definition + */ + def apply[T <: BaseModule with IsInstantiable](proto: => T): Definition[T] = macro DefinitionTransform.apply[T] + + /** A construction method to build a Definition of a Module + * + * @param bc the Module being defined + * + * @return the input module as a Definition + */ + def do_apply[T <: BaseModule with IsInstantiable](proto: => T) (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Definition[T] = { + val dynamicContext = new DynamicContext(Nil) + Builder.globalNamespace.copyTo(dynamicContext.globalNamespace) + dynamicContext.inDefinition = true + val (ir, module) = Builder.build(Module(proto), dynamicContext) + Builder.components ++= ir.components + Builder.annotations ++= ir.annotations + module._circuit = Builder.currentModule + dynamicContext.globalNamespace.copyTo(Builder.globalNamespace) + new Definition(Left(module)) + } +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala new file mode 100644 index 00000000..aec42da3 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import scala.collection.mutable.{ArrayBuffer, HashMap} +import scala.language.experimental.macros + +import chisel3._ +import chisel3.internal.BaseModule.{ModuleClone, IsClone, InstantiableClone} +import chisel3.internal.sourceinfo.{InstanceTransform, SourceInfo} +import chisel3.experimental.BaseModule + +/** User-facing Instance type. + * Represents a unique instance of type [[A]] which are marked as @instantiable + * Can be created using Instance.apply method. + * + * @param cloned The internal representation of the instance, which may be either be directly the object, or a clone of an object + */ +case class Instance[+A] private [chisel3] (private[chisel3] cloned: Either[A, IsClone[A]]) { + + /** Returns the original object which is instantiated here. + * If this is an instance of a clone, return that clone's original proto + * + * @return the original object which was instantiated + */ + private[chisel3] def proto: A = cloned match { + case Left(value: A) => value + case Right(i: IsClone[A]) => i._proto + } + + /** @return the context of any Data's return from inside the instance */ + private[chisel3] def getInnerDataContext: Option[BaseModule] = cloned match { + case Left(value: BaseModule) => Some(value) + case Left(value: IsInstantiable) => None + case Right(i: BaseModule) => Some(i) + case Right(i: InstantiableClone[_]) => i._parent + } + + /** @return the context this instance. Note that for non-module clones, getInnerDataContext will be the same as getClonedParent */ + private[chisel3] def getClonedParent: Option[BaseModule] = cloned match { + case Left(value: BaseModule) => value._parent + case Right(i: BaseModule) => i._parent + case Right(i: InstantiableClone[_]) => i._parent + } + + /** Updated by calls to [[apply]], to avoid recloning returned Data's */ + private [chisel3] val cache = HashMap[Data, Data]() + + /** Used by Chisel's internal macros. DO NOT USE in your normal Chisel code!!! + * Instead, mark the field you are accessing with [[@public]] + * + * Given a selector function (that) which selects a member from the original, return the + * corresponding member from the instance. + * + * Our @instantiable and @public macros generate the calls to this apply method + * + * By calling this function, we summon the proper Lookupable typeclass from our implicit scope. + * + * @param that a user-specified lookup function + * @param lookup typeclass which contains the correct lookup function, based on the types of A and B + * @param macroGenerated a value created in the macro, to make it harder for users to use this API + */ + def _lookup[B, C](that: A => B)(implicit lookup: Lookupable[B], macroGenerated: chisel3.internal.MacroGenerated): lookup.C = { + lookup.instanceLookup(that, this) + } + + /** Returns the definition of this Instance */ + def toDefinition: Definition[A] = new Definition(Left(proto)) + +} + +/** Factory methods for constructing [[Instance]]s */ +object Instance extends SourceInfoDoc { + implicit class InstanceBaseModuleExtensions[T <: BaseModule](i: Instance[T]) { + /** If this is an instance of a Module, returns the toTarget of this instance + * @return target of this instance + */ + def toTarget = i.cloned match { + case Left(x: BaseModule) => x.toTarget + case Right(x: IsClone[_] with BaseModule) => x.toTarget + } + + /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance + * @return absoluteTarget of this instance + */ + def toAbsoluteTarget = i.cloned match { + case Left(x) => x.toAbsoluteTarget + case Right(x: IsClone[_] with BaseModule) => x.toAbsoluteTarget + } + + } + /** A constructs an [[Instance]] from a [[Definition]] + * + * @param definition the Module being created + * @return an instance of the module definition + */ + def apply[T <: BaseModule with IsInstantiable](definition: Definition[T]): Instance[T] = macro InstanceTransform.apply[T] + + /** A constructs an [[Instance]] from a [[Definition]] + * + * @param definition the Module being created + * @return an instance of the module definition + */ + def do_apply[T <: BaseModule with IsInstantiable](definition: Definition[T])(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Instance[T] = { + val ports = experimental.CloneModuleAsRecord(definition.proto) + val clone = ports._parent.get.asInstanceOf[ModuleClone[T]] + clone._madeFromDefinition = true + new Instance(Right(clone)) + } + +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala new file mode 100644 index 00000000..26ba0286 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +/** While this is public, it is not recommended for users to extend directly. + * Instead, use the [[@instantiable]] annotation on your trait or class. + * + * This trait indicates whether a class can be returned from an Instance. + * + */ +trait IsInstantiable + +object IsInstantiable { + implicit class IsInstantiableExtensions[T <: IsInstantiable](i: T) { + def toInstance: Instance[T] = new Instance(Left(i)) + } +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala new file mode 100644 index 00000000..37d29a43 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +/** A User-extendable trait to mark metadata-containers, e.g. parameter case classes, as valid to return unchanged + * from an instance. + * + * This should only be true of the metadata returned is identical for ALL instances! + * + * @example For instances of the same proto, metadata or other construction parameters + * may be useful to access outside of the instance construction. For parameters that are + * the same for all instances, we should mark it as IsLookupable + * {{{ + * case class Params(debugMessage: String) extends IsLookupable + * class MyModule(p: Params) extends MultiIOModule { + * printf(p.debugMessage) + * } + * val myParams = Params("Hello World") + * val definition = Definition(new MyModule(myParams)) + * val i0 = Instance(definition) + * val i1 = Instance(definition) + * require(i0.p == i1.p) // p is only accessable because it extends IsLookupable + * }}} + */ +trait IsLookupable diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala new file mode 100644 index 00000000..b9617723 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import chisel3.experimental.BaseModule +import chisel3.internal.sourceinfo.SourceInfo +import chisel3.internal.BaseModule.{InstanceClone, InstantiableClone, IsClone, ModuleClone} + +import scala.annotation.implicitNotFound +import scala.collection.mutable.HashMap +import chisel3._ +import chisel3.experimental.dataview.{isView, reify, reifySingleData} +import chisel3.internal.firrtl.{Arg, ILit, Index, Slot, ULit} +import chisel3.internal.{AggregateViewBinding, Builder, ChildBinding, ViewBinding, ViewParent, throwException} + +/** Represents lookup typeclass to determine how a value accessed from an original IsInstantiable + * should be tweaked to return the Instance's version + * Sealed. + */ +@implicitNotFound("@public is only legal within a class marked @instantiable and only on vals of type" + + " Data, BaseModule, IsInstantiable, IsLookupable, or Instance[_], or in an Iterable or Option") +sealed trait Lookupable[-B] { + type C // Return type of the lookup + /** Function called to modify the returned value of type B from A, into C + * + * @param that function that selects B from A + * @param instance Instance of A, used to determine C's context + * @return + */ + def instanceLookup[A](that: A => B, instance: Instance[A]): C + + /** Function called to modify the returned value of type B from A, into C + * + * @param that function that selects B from A + * @param definition Definition of A, used to determine C's context + * @return + */ + def definitionLookup[A](that: A => B, definition: Definition[A]): C +} + +private[chisel3] object Lookupable { + + /** Clones a data and sets its internal references to its parent module to be in a new context. + * + * @param data data to be cloned + * @param context new context + * @return + */ + private[chisel3] def cloneDataToContext[T <: Data](data: T, context: BaseModule) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { + internal.requireIsHardware(data, "cross module reference type") + data._parent match { + case None => data + case Some(parent) => + val newParent = cloneModuleToContext(Left(parent), context) + newParent match { + case Left(p) if p == parent => data + case Right(m: BaseModule) => + val newChild = data.cloneTypeFull + newChild.setRef(data.getRef, true) + newChild.bind(internal.CrossModuleBinding) + newChild.setAllParents(Some(m)) + newChild + } + } + } + // The business logic of lookupData + // Also called by cloneViewToContext which potentially needs to lookup stuff from ioMap or the cache + private[chisel3] def doLookupData[A, B <: Data](data: B, cache: HashMap[Data, Data], ioMap: Option[Map[Data, Data]], context: Option[BaseModule]) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): B = { + def impl[C <: Data](d: C): C = d match { + case x: Data if ioMap.nonEmpty && ioMap.get.contains(x) => ioMap.get(x).asInstanceOf[C] + case x: Data if cache.contains(x) => cache(x).asInstanceOf[C] + case _ => + assert(context.nonEmpty) // TODO is this even possible? Better error message here + val ret = cloneDataToContext(d, context.get) + cache(d) = ret + ret + } + data.binding match { + case Some(_: ChildBinding) => mapRootAndExtractSubField(data, impl) + case _ => impl(data) + } + } + + // Helper for co-iterating on Elements of aggregates, they must be the same type but that is unchecked + private def coiterate(a: Data, b: Data): Iterable[(Element, Element)] = { + val as = getRecursiveFields.lazily(a, "_") + val bs = getRecursiveFields.lazily(b, "_") + as.zip(bs).collect { case ((ae: Element, _), (be: Element, _)) => (ae, be) } + } + + /** Given a Data, find the root of its binding, apply a function to the root to get a "new root", + * and find the equivalent child Data in the "new root" + * + * @example {{{ + * Given `arg = a.b[2].c` and some `f`: + * 1. a = root(arg) = root(a.b[2].c) + * 2. newRoot = f(root(arg)) = f(a) + * 3. return newRoot.b[2].c + * }}} + * + * Invariants that elt is a Child of something of the type of data is dynamically checked as we traverse + */ + private def mapRootAndExtractSubField[A <: Data](arg: A, f: Data => Data): A = { + def err(msg: String) = throwException(s"Internal Error! $msg") + def unrollCoordinates(res: List[Arg], d: Data): (List[Arg], Data) = d.binding.get match { + case ChildBinding(parent) => d.getRef match { + case arg @ (_: Slot | _: Index) => unrollCoordinates(arg :: res, parent) + case other => err(s"Unroll coordinates failed for '$arg'! Unexpected arg '$other'") + } + case _ => (res, d) + } + def applyCoordinates(fullCoor: List[Arg], start: Data): Data = { + def rec(coor: List[Arg], d: Data): Data = { + if (coor.isEmpty) d + else { + val next = (coor.head, d) match { + case (Slot(_, name), rec: Record) => rec.elements(name) + case (Index(_, ILit(n)), vec: Vec[_]) => vec.apply(n.toInt) + case (arg, _) => err(s"Unexpected Arg '$arg' applied to '$d'! Root was '$start'.") + } + applyCoordinates(coor.tail, next) + } + } + rec(fullCoor, start) + } + val (coor, root) = unrollCoordinates(Nil, arg) + val newRoot = f(root) + val result = applyCoordinates(coor, newRoot) + try { + result.asInstanceOf[A] + } catch { + case _: ClassCastException => err(s"Applying '$coor' to '$newRoot' somehow resulted in '$result'") + } + } + + // TODO this logic is complicated, can any of it be unified with viewAs? + // If `.viewAs` would capture its arguments, we could potentially use it + // TODO Describe what this is doing at a high level + private[chisel3] def cloneViewToContext[A, B <: Data](data: B, cache: HashMap[Data, Data], ioMap: Option[Map[Data, Data]], context: Option[BaseModule]) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): B = { + // alias to shorten lookups + def lookupData[C <: Data](d: C) = doLookupData(d, cache, ioMap, context) + + val result = data.cloneTypeFull + + // We have to lookup the target(s) of the view since they may need to be cloned into the current context + val newBinding = data.topBinding match { + case ViewBinding(target) => ViewBinding(lookupData(reify(target))) + case avb @ AggregateViewBinding(map, targetOpt) => data match { + case _: Element => ViewBinding(lookupData(reify(map(data)))) + case _: Aggregate => + // Provide a 1:1 mapping if possible + val singleTargetOpt = targetOpt.filter(_ => avb == data.binding.get).flatMap(reifySingleData) + singleTargetOpt match { + case Some(singleTarget) => // It is 1:1! + // This is a little tricky because the values in newMap need to point to Elements of newTarget + val newTarget = lookupData(singleTarget) + val newMap = coiterate(result, data).map { case (res, from) => + (res: Data) -> mapRootAndExtractSubField(map(from), _ => newTarget) + }.toMap + AggregateViewBinding(newMap, Some(newTarget)) + + case None => // No 1:1 mapping so we have to do a flat binding + // Just remap each Element of this aggregate + val newMap = coiterate(result, data).map { + // Upcast res to Data since Maps are invariant in the Key type parameter + case (res, from) => (res: Data) -> lookupData(reify(map(from))) + }.toMap + AggregateViewBinding(newMap, None) + } + } + } + + // TODO Unify the following with `.viewAs` + // We must also mark non-1:1 and child Aggregates in the view for renaming + newBinding match { + case _: ViewBinding => // Do nothing + case AggregateViewBinding(_, target) => + if (target.isEmpty) { + Builder.unnamedViews += result + } + // Binding does not capture 1:1 for child aggregates views + getRecursiveFields.lazily(result, "_").foreach { + case (agg: Aggregate, _) if agg != result => + Builder.unnamedViews += agg + case _ => // Do nothing + } + } + + result.bind(newBinding) + result.setAllParents(Some(ViewParent)) + result.forceName(None, "view", Builder.viewNamespace) + result + } + /** Given a module (either original or a clone), clone it to a new context + * + * This function effectively recurses up the parents of module to find whether: + * (1) A parent is already in the context; then we do nothing and return module + * (2) A parent is in a different clone of the context; then we clone all the parents up + * to that parent and set their parents to be in this cloned context + * (3) A parent has no root; in that case, we do nothing and return the module. + * + * @param module original or clone to be cloned into a new context + * @param context new context + * @return original or clone in the new context + */ + private[chisel3] def cloneModuleToContext[T <: BaseModule](module: Either[T, IsClone[T]], context: BaseModule) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Either[T, IsClone[T]] = { + // Recursive call + def rec[A <: BaseModule](m: A): Either[A, IsClone[A]] = { + def clone(x: A, p: Option[BaseModule], name: () => String): Either[A, IsClone[A]] = { + val newChild = Module.do_apply(new internal.BaseModule.InstanceClone(x, name)) + newChild._parent = p + Right(newChild) + } + (m, context) match { + case (c, ctx) if ctx == c => Left(c) + case (c, ctx: IsClone[_]) if ctx.isACloneOf(c) => Right(ctx.asInstanceOf[IsClone[A]]) + case (c, ctx) if c._parent.isEmpty => Left(c) + case (_, _) => + cloneModuleToContext(Left(m._parent.get), context) match { + case Left(p) => Left(m) + case Right(p: BaseModule) => + clone(m, Some(p), () => m.instanceName) + } + } + } + module match { + case Left(m) => rec(m) + case Right(m: ModuleClone[_]) => + rec(m) match { + case Left(mx) => Right(mx) + case Right(i: InstanceClone[_]) => + val newChild = Module.do_apply(new InstanceClone(m._proto, () => m.instanceName)) + newChild._parent = i._parent + Right(newChild) + } + case Right(m: InstanceClone[_]) => + rec(m) match { + case Left(mx) => Right(mx) + case Right(i: InstanceClone[_]) => + val newChild = Module.do_apply(new InstanceClone(m._proto, () => m.instanceName)) + newChild._parent = i._parent + Right(newChild) + } + } + } + + class SimpleLookupable[X] extends Lookupable[X] { + type B = X + type C = X + def definitionLookup[A](that: A => B, definition: Definition[A]): C = that(definition.proto) + def instanceLookup[A](that: A => B, instance: Instance[A]): C = that(instance.proto) + } + + implicit def lookupInstance[B <: BaseModule](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[Instance[B]] { + type C = Instance[B] + def definitionLookup[A](that: A => Instance[B], definition: Definition[A]): C = { + val ret = that(definition.proto) + new Instance(cloneModuleToContext(ret.cloned, definition.getInnerDataContext.get)) + } + def instanceLookup[A](that: A => Instance[B], instance: Instance[A]): C = { + val ret = that(instance.proto) + instance.cloned match { + // If instance is just a normal module, no changing of context is necessary + case Left(_) => new Instance(ret.cloned) + case Right(_) => new Instance(cloneModuleToContext(ret.cloned, instance.getInnerDataContext.get)) + } + } + } + + implicit def lookupModule[B <: BaseModule](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[B] { + type C = Instance[B] + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + val ret = that(definition.proto) + new Instance(cloneModuleToContext(Left(ret), definition.getInnerDataContext.get)) + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + val ret = that(instance.proto) + instance.cloned match { + // If instance is just a normal module, no changing of context is necessary + case Left(_) => new Instance(Left(ret)) + case Right(_) => new Instance(cloneModuleToContext(Left(ret), instance.getInnerDataContext.get)) + } + } + } + + implicit def lookupData[B <: Data](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[B] { + type C = B + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + val ret = that(definition.proto) + if (isView(ret)) { + ??? // TODO!!!!!! cloneViewToContext(ret, instance, ioMap, instance.getInnerDataContext) + } else { + doLookupData(ret, definition.cache, None, definition.getInnerDataContext) + } + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + val ret = that(instance.proto) + val ioMap: Option[Map[Data, Data]] = instance.cloned match { + case Right(x: ModuleClone[_]) => Some(x.ioMap) + case Left(x: BaseModule) => Some(x.getChiselPorts.map { case (_, data) => data -> data }.toMap) + case _ => None + } + if (isView(ret)) { + cloneViewToContext(ret, instance.cache, ioMap, instance.getInnerDataContext) + } else { + doLookupData(ret, instance.cache, ioMap, instance.getInnerDataContext) + } + + } + } + + import scala.language.higherKinds // Required to avoid warning for lookupIterable type parameter + implicit def lookupIterable[B, F[_] <: Iterable[_]](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions, lookupable: Lookupable[B]) = new Lookupable[F[B]] { + type C = F[lookupable.C] + def definitionLookup[A](that: A => F[B], definition: Definition[A]): C = { + val ret = that(definition.proto).asInstanceOf[Iterable[B]] + ret.map{ x: B => lookupable.definitionLookup[A](_ => x, definition) }.asInstanceOf[C] + } + def instanceLookup[A](that: A => F[B], instance: Instance[A]): C = { + import instance._ + val ret = that(proto).asInstanceOf[Iterable[B]] + ret.map{ x: B => lookupable.instanceLookup[A](_ => x, instance) }.asInstanceOf[C] + } + } + implicit def lookupOption[B](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions, lookupable: Lookupable[B]) = new Lookupable[Option[B]] { + type C = Option[lookupable.C] + def definitionLookup[A](that: A => Option[B], definition: Definition[A]): C = { + val ret = that(definition.proto) + ret.map{ x: B => lookupable.definitionLookup[A](_ => x, definition) } + } + def instanceLookup[A](that: A => Option[B], instance: Instance[A]): C = { + import instance._ + val ret = that(proto) + ret.map{ x: B => lookupable.instanceLookup[A](_ => x, instance) } + } + } + implicit def lookupIsInstantiable[B <: IsInstantiable](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[B] { + type C = Instance[B] + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + val ret = that(definition.proto) + val cloned = new InstantiableClone(ret) + cloned._parent = definition.getInnerDataContext + new Instance(Right(cloned)) + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + val ret = that(instance.proto) + val cloned = new InstantiableClone(ret) + cloned._parent = instance.getInnerDataContext + new Instance(Right(cloned)) + } + } + + implicit def lookupIsLookupable[B <: IsLookupable](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new SimpleLookupable[B]() + + implicit val lookupInt = new SimpleLookupable[Int]() + implicit val lookupByte = new SimpleLookupable[Byte]() + implicit val lookupShort = new SimpleLookupable[Short]() + implicit val lookupLong = new SimpleLookupable[Long]() + implicit val lookupFloat = new SimpleLookupable[Float]() + implicit val lookupChar = new SimpleLookupable[Char]() + implicit val lookupString = new SimpleLookupable[String]() + implicit val lookupBoolean = new SimpleLookupable[Boolean]() + implicit val lookupBigInt = new SimpleLookupable[BigInt]() +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/package.scala b/core/src/main/scala/chisel3/experimental/hierarchy/package.scala new file mode 100644 index 00000000..c309ab52 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/package.scala @@ -0,0 +1,48 @@ +package chisel3.experimental + +package object hierarchy { + + /** Classes or traits which will be used with the [[Definition]] + [[Instance]] api should be marked + * with the [[@instantiable]] annotation at the class/trait definition. + * + * @example {{{ + * @instantiable + * class MyModule extends Module { + * ... + * } + * + * val d = Definition(new MyModule) + * val i0 = Instance(d) + * val i1 = Instance(d) + * }}} + */ + class instantiable extends chisel3.internal.instantiable + + /** Classes marked with [[@instantiable]] can have their vals marked with the [[@public]] annotation to + * enable accessing these values from a [[Definition]] or [[Instance]] of the class. + * + * Only vals of the the following types can be marked [[@public]]: + * 1. IsInstantiable + * 2. IsLookupable + * 3. Data + * 4. BaseModule + * 5. Iterable/Option containing a type that meets these requirements + * 6. Basic type like String, Int, BigInt etc. + * + * @example {{{ + * @instantiable + * class MyModule extends Module { + * @public val in = IO(Input(UInt(3.W))) + * @public val out = IO(Output(UInt(3.W))) + * .. + * } + * + * val d = Definition(new MyModule) + * val i0 = Instance(d) + * val i1 = Instance(d) + * + * i1.in := i0.out + * }}} + */ + class public extends chisel3.internal.public +} diff --git a/core/src/main/scala/chisel3/internal/Binding.scala b/core/src/main/scala/chisel3/internal/Binding.scala index 6f4ab4b0..a0dcc20c 100644 --- a/core/src/main/scala/chisel3/internal/Binding.scala +++ b/core/src/main/scala/chisel3/internal/Binding.scala @@ -129,6 +129,11 @@ private[chisel3] case class ViewBinding(target: Element) extends UnconstrainedBi private[chisel3] case class AggregateViewBinding(childMap: Map[Data, Element], target: Option[Data]) extends UnconstrainedBinding +/** Binding for Data's returned from accessing an Instance/Definition members, if not readable/writable port */ +private[chisel3] case object CrossModuleBinding extends TopBinding { + def location = None +} + sealed trait LitBinding extends UnconstrainedBinding with ReadOnlyBinding // Literal binding attached to a element that is not part of a Bundle. case class ElementLitBinding(litArg: LitArg) extends LitBinding diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index f7306d5d..1d15247d 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -6,6 +6,7 @@ import scala.util.DynamicVariable import scala.collection.mutable.ArrayBuffer import chisel3._ import chisel3.experimental._ +import chisel3.experimental.hierarchy.Instance import chisel3.internal.firrtl._ import chisel3.internal.naming._ import _root_.firrtl.annotations.{CircuitName, ComponentName, IsMember, ModuleName, Named, ReferenceTarget} @@ -19,6 +20,7 @@ import scala.collection.mutable private[chisel3] class Namespace(keywords: Set[String]) { private val names = collection.mutable.HashMap[String, Long]() + def copyTo(other: Namespace): Unit = names.foreach { case (s: String, l: Long) => other.names(s) = l } for (keyword <- keywords) names(keyword) = 1 @@ -87,6 +89,9 @@ private[chisel3] trait HasId extends InstanceId { private[chisel3] def _onModuleClose: Unit = {} private[chisel3] var _parent: Option[BaseModule] = Builder.currentModule + // Set if the returned top-level module of a nested call to the Chisel Builder, see Definition.apply + private[chisel3] var _circuit: Option[BaseModule] = None + private[chisel3] val _id: Long = Builder.idGen.next // TODO: remove this, but its removal seems to cause a nasty Scala compiler crash. @@ -216,7 +221,7 @@ private[chisel3] trait HasId extends InstanceId { private[chisel3] def getRef: Arg = _ref.get private[chisel3] def getOptionRef: Option[Arg] = _ref - private def localName(c: Component): String = _ref match { + private def refName(c: Component): String = _ref match { case Some(arg) => arg fullName c case None => computeName(None, None).get } @@ -232,11 +237,13 @@ private[chisel3] trait HasId extends InstanceId { // Implementation of public methods. def instanceName: String = _parent match { - case Some(ViewParent) => reifyTarget.map(_.instanceName).getOrElse(this.localName(ViewParent.fakeComponent)) - case Some(p) => p._component match { - case Some(c) => localName(c) - case None => throwException("signalName/pathName should be called after circuit elaboration") - } + case Some(ViewParent) => reifyTarget.map(_.instanceName).getOrElse(this.refName(ViewParent.fakeComponent)) + case Some(p) => + (p._component, this) match { + case (Some(c), _) => refName(c) + case (None, d: Data) if d.topBindingOpt == Some(CrossModuleBinding) => _ref.get.localName + case (None, _) => throwException(s"signalName/pathName should be called after circuit elaboration: $this, ${_parent}") + } case None => throwException("this cannot happen") } def pathName: String = _parent match { @@ -256,7 +263,10 @@ private[chisel3] trait HasId extends InstanceId { } // TODO Should this be public? protected def circuitName: String = _parent match { - case None => instanceName + case None => _circuit match { + case None => instanceName + case Some(o) => o.circuitName + } case Some(ViewParent) => reifyParent.circuitName case Some(p) => p.circuitName } @@ -296,8 +306,12 @@ private[chisel3] trait NamedComponent extends HasId { val name = this.instanceName if (!validComponentName(name)) throwException(s"Illegal component name: $name (note: literals are illegal)") import _root_.firrtl.annotations.{Target, TargetToken} + val root = _parent.map { + case ViewParent => reifyParent + case other => other + }.get.toTarget // All NamedComponents will have a parent, only the top module can have None here Target.toTargetTokens(name).toList match { - case TargetToken.Ref(r) :: components => ReferenceTarget(this.circuitName, this.parentModName, Nil, r, components) + case TargetToken.Ref(r) :: components => root.ref(r).copy(component = components) case other => throw _root_.firrtl.annotations.Target.NamedException(s"Cannot convert $name into [[ReferenceTarget]]: $other") } @@ -354,6 +368,8 @@ private[chisel3] class DynamicContext(val annotationSeq: AnnotationSeq) { var currentReset: Option[Reset] = None val errors = new ErrorLog val namingStack = new NamingStack + // Used to indicate if this is the top-level module of full elaboration, or from a Definition + var inDefinition: Boolean = false } private[chisel3] object Builder extends LazyLogging { @@ -368,6 +384,11 @@ private[chisel3] object Builder extends LazyLogging { dynamicContextVar.value.get } + // Returns the current dynamic context + def captureContext(): DynamicContext = dynamicContext + // Sets the current dynamic contents + def restoreContext(dc: DynamicContext) = dynamicContextVar.value = Some(dc) + // Ensure we have a thread-specific ChiselContext private val chiselContext = new ThreadLocal[ChiselContext]{ override def initialValue: ChiselContext = { @@ -563,6 +584,12 @@ private[chisel3] object Builder extends LazyLogging { dynamicContext.currentReset = newReset } + def inDefinition: Boolean = { + dynamicContextVar.value + .map(_.inDefinition) + .getOrElse(false) + } + // This should only be used for testing, must be true outside of Builder context def allowReflectiveAutoCloneType: Boolean = { dynamicContextVar.value @@ -632,6 +659,10 @@ private[chisel3] object Builder extends LazyLogging { * (Note: Map is Iterable[Tuple2[_,_]] and thus excluded) */ def nameRecursively(prefix: String, nameMe: Any, namer: (HasId, String) => Unit): Unit = nameMe match { + case (id: Instance[_]) => id.cloned match { + case Right(m: internal.BaseModule.ModuleClone[_]) => namer(m.getPorts, prefix) + case _ => + } case (id: HasId) => namer(id, prefix) case Some(elt) => nameRecursively(prefix, elt, namer) case (iter: Iterable[_]) if iter.hasDefiniteSize => @@ -696,7 +727,7 @@ private[chisel3] object Builder extends LazyLogging { renames } - private [chisel3] def build[T <: RawModule](f: => T, dynamicContext: DynamicContext): (Circuit, T) = { + private [chisel3] def build[T <: BaseModule](f: => T, dynamicContext: DynamicContext): (Circuit, T) = { dynamicContextVar.withValue(Some(dynamicContext)) { ViewParent // Must initialize the singleton in a Builder context or weird things can happen // in tiny designs/testcases that never access anything in chisel3.internal diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index f8a3cf7f..0b568548 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -65,13 +65,19 @@ object PrimOp { } abstract class Arg { - def fullName(ctx: Component): String = name + def localName: String = name + def contextualName(ctx: Component): String = name + def fullName(ctx: Component): String = contextualName(ctx) def name: String } case class Node(id: HasId) extends Arg { - override def fullName(ctx: Component): String = id.getOptionRef match { - case Some(arg) => arg.fullName(ctx) + override def contextualName(ctx: Component): String = id.getOptionRef match { + case Some(arg) => arg.contextualName(ctx) + case None => id.instanceName + } + override def localName: String = id.getOptionRef match { + case Some(arg) => arg.localName case None => id.instanceName } def name: String = id.getOptionRef match { @@ -83,7 +89,7 @@ case class Node(id: HasId) extends Arg { abstract class LitArg(val num: BigInt, widthArg: Width) extends Arg { private[chisel3] def forcedWidth = widthArg.known private[chisel3] def width: Width = if (forcedWidth) widthArg else Width(minWidth) - override def fullName(ctx: Component): String = name + override def contextualName(ctx: Component): String = name // Ensure the node representing this LitArg has a ref to it and a literal binding. def bindLitArg[T <: Element](elem: T): T = { elem.bind(ElementLitBinding(this)) @@ -167,7 +173,7 @@ case class Ref(name: String) extends Arg * @param name the name of the port */ case class ModuleIO(mod: BaseModule, name: String) extends Arg { - override def fullName(ctx: Component): String = + override def contextualName(ctx: Component): String = if (mod eq ctx.id) name else s"${mod.getRef.name}.$name" } /** Ports of cloned modules (CloneModuleAsRecord) @@ -175,19 +181,25 @@ case class ModuleIO(mod: BaseModule, name: String) extends Arg { * @param name the name of the module instance */ case class ModuleCloneIO(mod: BaseModule, name: String) extends Arg { - override def fullName(ctx: Component): String = + override def localName = "" + override def contextualName(ctx: Component): String = // NOTE: mod eq ctx.id only occurs in Target and Named-related APIs - if (mod eq ctx.id) "" else name + if (mod eq ctx.id) localName else name } case class Slot(imm: Node, name: String) extends Arg { - override def fullName(ctx: Component): String = { - val immName = imm.fullName(ctx) + override def contextualName(ctx: Component): String = { + val immName = imm.contextualName(ctx) + if (immName.isEmpty) name else s"$immName.$name" + } + override def localName: String = { + val immName = imm.localName if (immName.isEmpty) name else s"$immName.$name" } } case class Index(imm: Arg, value: Arg) extends Arg { def name: String = s"[$value]" - override def fullName(ctx: Component): String = s"${imm.fullName(ctx)}[${value.fullName(ctx)}]" + override def contextualName(ctx: Component): String = s"${imm.contextualName(ctx)}[${value.contextualName(ctx)}]" + override def localName: String = s"${imm.localName}[${value.localName}]" } object Width { @@ -792,4 +804,5 @@ case class DefBlackBox(id: BaseBlackBox, name: String, ports: Seq[Port], topDir: case class Circuit(name: String, components: Seq[Component], annotations: Seq[ChiselAnnotation], renames: RenameMap) { def firrtlAnnotations: Iterable[Annotation] = annotations.flatMap(_.toFirrtl.update(renames)) + } |
