diff options
27 files changed, 2671 insertions, 54 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)) + } diff --git a/docs/src/cookbooks/hierarchy.md b/docs/src/cookbooks/hierarchy.md new file mode 100644 index 00000000..91d99aa6 --- /dev/null +++ b/docs/src/cookbooks/hierarchy.md @@ -0,0 +1,204 @@ +--- +layout: docs +title: "Hierarchy Cookbook" +section: "chisel3" +--- + +# Hierarchy Cookbook + +* [How do I instantiate multiple instances with the same module parameterization, but avoid re-elaboration?](#how-do-i-instantiate-multiple-instances-with-the-same-module-parameterization) +* [How do I access internal fields of an instance?](#how-do-i-access-internal-fields-of-an-instance) +* [How do I make my parameters accessable from an instance?](#how-do-i-make-my-parameters-accessable-from-an-instance) +* [How do I reuse a previously elaborated module, if my new module has the same parameterization?](#how-do-i-reuse-a-previously-elaborated-module-if-my-new-module-has-the-same-parameterization) + +## How do I instantiate multiple instances with the same module parameterization? + +Prior to this package, Chisel users relied on deduplication in a FIRRTL compiler to combine +structurally equivalent modules into one module (aka "deduplication"). +This package introduces the following new APIs to enable multiply-instantiated modules directly in Chisel. + +`Definition(...)` enables elaborating a module, but does not actually instantiate that module. +Instead, it returns a `Definition` class which represents that module's definition. + +`Instance(...)` takes a `Definition` and instantiates it, returning an `Instance` object. + +Modules (classes or traits) which will be used with the `Definition`/`Instance` api should be marked +with the `@instantiable` annotation at the class/trait definition. + +To make a Module's members variables accessible from an `Instance` object, they must be annotated +with the `@public` annotation. Note that this is only accessible from a Scala sense—this is not +in and of itself a mechanism for cross-module references. + +In the following example, use `Definition`, `Instance`, `@instantiable` and `@public` to create +multiple instances of one specific parameterization of a module, `AddOne`. + +```scala mdoc:silent +import chisel3._ +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, public} + +@instantiable +class AddOne(width: Int) extends Module { + @public val in = IO(Input(UInt(width.W))) + @public val out = IO(Output(UInt(width.W))) + out := in + 1.U +} + +class AddTwo(width: Int) extends Module { + val in = IO(Input(UInt(width.W))) + val out = IO(Output(UInt(width.W))) + val addOneDef = Definition(new AddOne(width)) + val i0 = Instance(addOneDef) + val i1 = Instance(addOneDef) + i0.in := in + i1.in := i0.out + out := i1.out +} +``` +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new AddTwo(10)) +``` + +## How do I access internal fields of an instance? + +You can mark internal members of a class or trait marked with `@instantiable` with the `@public` annotation. +The requirements are that the field is publicly accessible, is a `val` or `lazy val`, and is a valid type. +The list of valid types are: + +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. + +To mark a superclass's member as `@public`, use the following pattern (shown with `val clock`). + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.experimental.hierarchy.{instantiable, public} + +@instantiable +class MyModule extends Module { + @public val clock = clock +} +``` + +You'll get the following error message for improperly marking something as `@public`: + +```scala mdoc:reset:fail +import chisel3._ +import chisel3.experimental.hierarchy.{instantiable, public} + +object NotValidType + +@instantiable +class MyModule extends Module { + @public val x = NotValidType +} +``` + +## How do I make my parameters accessible from an instance? + +If an instance's parameters are simple (e.g. `Int`, `String` etc.) they can be marked directly with `@public`. + +Often, parameters are more complicated and are contained in case classes. +In such cases, mark the case class with the `IsLookupable` trait. +This indicates to Chisel that instances of the `IsLookupable` class may be accessed from within instances. + +However, ensure that these parameters are true for **all** instances of a definition. +For example, if our parameters contained an id field which was instance-specific but defaulted to zero, +then the definition's id would be returned for all instances. +This change in behavior could lead to bugs if other code presumed the id field was correct. + +Thus, it is important that when converting normal modules to use this package, +you are careful about what you mark as `IsLookupable`. + +In the following example, we added the trait `IsLookupable` to allow the member to be marked `@public`. + +```scala mdoc:reset:silent +import chisel3._ +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, IsLookupable, public} + +case class MyCaseClass(width: Int) extends IsLookupable + +@instantiable +class MyModule extends Module { + @public val x = MyCaseClass(10) +} + +class Top extends Module { + val inst = Instance(Definition(new MyModule)) + println(s"Width is ${inst.x.width}") +} +``` +```scala mdoc:passthrough +println("```") +chisel3.stage.ChiselStage.elaborate(new Top) +println("```") +``` + +## How do I look up parameters from a Definition, if I don't want to instantiate it? + +Just like `Instance`s, `Definition`'s also contain accessors for `@public` members. +As such, you can directly access them: + +```scala mdoc:reset:silent +import chisel3._ +import chisel3.experimental.hierarchy.{Definition, instantiable, public} + +@instantiable +class AddOne(val width: Int) extends Module { + @public val width = width + @public val in = IO(Input(UInt(width.W))) + @public val out = IO(Output(UInt(width.W))) + out := in + 1.U +} + +class Top extends Module { + val definition = Definition(new AddOne(10)) + println(s"Width is: ${definition.width}") +} +``` +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new Top()) +``` + +## How do I parameterize a module by its children instances? + +Prior to the introduction of this package, a parent module would have to pass all necessary parameters +when instantiating a child module. +This had the unfortunate consequence of requiring a parent's parameters to always contain the child's +parameters, which was an unnecessary coupling which lead to some anti-patterns. + +Now, a parent can take a child `Definition` as an argument, and instantiate it directly. +In addition, it can analyze the parameters used in the definition to parameterize itself. +In a sense, now the child can actually parameterize the parent. + +In the following example, we create a definition of `AddOne`, and pass the definition to `AddTwo`. +The width of the `AddTwo` ports are now derived from the parameterization of the `AddOne` instance. + +```scala mdoc:reset +import chisel3._ +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, public} + +@instantiable +class AddOne(val width: Int) extends Module { + @public val width = width + @public val in = IO(Input(UInt(width.W))) + @public val out = IO(Output(UInt(width.W))) + out := in + 1.U +} + +class AddTwo(addOneDef: Definition[AddOne]) extends Module { + val i0 = Instance(addOneDef) + val i1 = Instance(addOneDef) + val in = IO(Input(UInt(addOneDef.width.W))) + val out = IO(Output(UInt(addOneDef.width.W))) + i0.in := in + i1.in := i0.out + out := i1.out +} +``` +```scala mdoc:verilog +chisel3.stage.ChiselStage.emitVerilog(new AddTwo(Definition(new AddOne(10)))) +``` diff --git a/macros/src/main/scala/chisel3/internal/InstantiableMacro.scala b/macros/src/main/scala/chisel3/internal/InstantiableMacro.scala new file mode 100644 index 00000000..1d374198 --- /dev/null +++ b/macros/src/main/scala/chisel3/internal/InstantiableMacro.scala @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.internal + +import scala.language.experimental.macros +import scala.annotation.StaticAnnotation +import scala.reflect.macros.whitebox + + +private[chisel3] object instantiableMacro { + + def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = { + import c.universe._ + def processBody(stats: Seq[Tree]): (Seq[Tree], Iterable[Tree]) = { + val extensions = scala.collection.mutable.ArrayBuffer.empty[Tree] + extensions += q"implicit val mg = new chisel3.internal.MacroGenerated{}" + val resultStats = stats.flatMap { + case x @ q"@public val $tpname: $tpe = $name" if tpname.toString() == name.toString() => + extensions += atPos(x.pos)(q"def $tpname = module._lookup(_.$tpname)") + Nil + case x @ q"@public val $tpname: $tpe = $_" => + extensions += atPos(x.pos)(q"def $tpname = module._lookup(_.$tpname)") + Seq(x) + case x @ q"@public lazy val $tpname: $tpe = $_" => + extensions += atPos(x.pos)(q"def $tpname = module._lookup(_.$tpname)") + Seq(x) + case other => Seq(other) + } + (resultStats, extensions) + } + val result = { + val (clz, objOpt) = annottees.map(_.tree).toList match { + case Seq(c, o) => (c, Some(o)) + case Seq(c) => (c, None) + } + val (newClz, implicitClzs, tpname) = clz match { + case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => + val defname = TypeName(tpname + c.freshName()) + val instname = TypeName(tpname + c.freshName()) + val (newStats, extensions) = processBody(stats) + (q""" $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents with chisel3.experimental.hierarchy.IsInstantiable { $self => ..$newStats } """, + Seq(q"""implicit class $defname(module: chisel3.experimental.hierarchy.Definition[$tpname]) { ..$extensions }""", + q"""implicit class $instname(module: chisel3.experimental.hierarchy.Instance[$tpname]) { ..$extensions } """), + tpname) + case q"$mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => + val defname = TypeName(tpname + c.freshName()) + val instname = TypeName(tpname + c.freshName()) + val (newStats, extensions) = processBody(stats) + (q"$mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents with chisel3.experimental.hierarchy.IsInstantiable { $self => ..$newStats }", + Seq(q"""implicit class $defname(module: chisel3.experimental.hierarchy.Definition[$tpname]) { ..$extensions }""", + q"""implicit class $instname(module: chisel3.experimental.hierarchy.Instance[$tpname]) { ..$extensions } """), + tpname) + } + val newObj = objOpt match { + case None => q"""object ${tpname.toTermName} { ..$implicitClzs } """ + case Some(obj @ q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }") => + q""" + $mods object $tname extends { ..$earlydefns } with ..$parents { $self => + ..$implicitClzs + ..$body + } + """ + } + q""" + $newClz + + $newObj + """ + } + c.Expr[Any](result) + } +} + +private[chisel3] class instantiable extends StaticAnnotation { + def macroTransform(annottees: Any*): Any = macro instantiableMacro.impl +} +private[chisel3] class public extends StaticAnnotation diff --git a/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala b/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala index 6121bc1e..a8a7e8d5 100644 --- a/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala +++ b/macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala @@ -47,6 +47,36 @@ class InstTransform(val c: Context) extends SourceInfoTransformMacro { } // Workaround for https://github.com/sbt/sbt/issues/3966 +object DefinitionTransform +// Module instantiation transform +class DefinitionTransform(val c: Context) extends SourceInfoTransformMacro { + import c.universe._ + def apply[T: c.WeakTypeTag](proto: c.Tree): c.Tree = { + q"$thisObj.do_apply($proto)($implicitSourceInfo, $implicitCompileOptions)" + } +} + +object DefinitionWrapTransform +// Module instantiation transform +class DefinitionWrapTransform(val c: Context) extends SourceInfoTransformMacro { + import c.universe._ + def wrap[T: c.WeakTypeTag](proto: c.Tree): c.Tree = { + q"$thisObj.do_wrap($proto)($implicitSourceInfo)" + } +} + + +// Workaround for https://github.com/sbt/sbt/issues/3966 +object InstanceTransform +// Module instantiation transform +class InstanceTransform(val c: Context) extends SourceInfoTransformMacro { + import c.universe._ + def apply[T: c.WeakTypeTag](definition: c.Tree): c.Tree = { + q"$thisObj.do_apply($definition)($implicitSourceInfo, $implicitCompileOptions)" + } +} + +// Workaround for https://github.com/sbt/sbt/issues/3966 object MemTransform class MemTransform(val c: Context) extends SourceInfoTransformMacro { import c.universe._ diff --git a/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala b/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala index b1302ba3..af22e6a7 100644 --- a/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala +++ b/plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala @@ -82,6 +82,7 @@ class ChiselComponent(val global: Global) extends PluginComponent with TypingTra private val shouldMatchData : Type => Boolean = shouldMatchGen(tq"chisel3.Data") private val shouldMatchDataOrMem : Type => Boolean = shouldMatchGen(tq"chisel3.Data", tq"chisel3.MemBase[_]") private val shouldMatchModule : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.BaseModule") + private val shouldMatchInstance : Type => Boolean = shouldMatchGen(tq"chisel3.experimental.hierarchy.Instance[_]") // Given a type tree, infer the type and return it private def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe @@ -188,6 +189,11 @@ class ChiselComponent(val global: Global) extends PluginComponent with TypingTra val newRHS = transform(rhs) val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + } else if (shouldMatchInstance(tpe)) { + val str = stringFromTermName(name) + val newRHS = transform(rhs) + val named = q"chisel3.internal.plugin.autoNameRecursively($str)($newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) } else { // Otherwise, continue super.transform(tree) diff --git a/src/main/scala/chisel3/aop/Select.scala b/src/main/scala/chisel3/aop/Select.scala index 078422bb..2384c4d3 100644 --- a/src/main/scala/chisel3/aop/Select.scala +++ b/src/main/scala/chisel3/aop/Select.scala @@ -3,13 +3,16 @@ package chisel3.aop import chisel3._ -import chisel3.experimental.{BaseModule, FixedPoint} -import chisel3.internal.HasId +import chisel3.internal.{HasId} +import chisel3.experimental.BaseModule +import chisel3.experimental.FixedPoint import chisel3.internal.firrtl._ +import chisel3.internal.PseudoModule import chisel3.internal.BaseModule.ModuleClone import firrtl.annotations.ReferenceTarget import scala.collection.mutable +import chisel3.internal.naming.chiselName /** Use to select Chisel components in a module, after that module has been constructed * Useful for adding additional Chisel annotations or for use within an [[Aspect]] @@ -84,7 +87,8 @@ object Select { module._component.get match { case d: DefModule => d.commands.flatMap { case i: DefInstance => i.id match { - case _: ModuleClone => None + case m: ModuleClone[_] if !m._madeFromDefinition => None + case _: PseudoModule => throw new Exception("Aspect APIs are currently incompatible with Definition/Instance") case other => Some(other) } case _ => None diff --git a/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala index c540fc83..1a476f61 100644 --- a/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala +++ b/src/main/scala/chisel3/aop/injecting/InjectingAspect.scala @@ -4,6 +4,7 @@ package chisel3.aop.injecting import chisel3.{Module, ModuleAspect, RawModule, withClockAndReset} import chisel3.aop._ +import chisel3.experimental.hierarchy.IsInstantiable import chisel3.internal.{Builder, DynamicContext} import chisel3.internal.firrtl.DefModule import chisel3.stage.DesignAnnotation diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala index 8e35273d..8647d903 100644 --- a/src/test/scala/chiselTests/ChiselSpec.scala +++ b/src/test/scala/chiselTests/ChiselSpec.scala @@ -16,6 +16,7 @@ import org.scalacheck._ import org.scalatest._ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.freespec.AnyFreeSpec +import org.scalatest.funspec.AnyFunSpec import org.scalatest.propspec.AnyPropSpec import org.scalatest.matchers.should.Matchers import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks @@ -104,13 +105,14 @@ trait ChiselRunners extends Assertions with BackendCompilationUtilities { * @param t the generator for the module * @return The FIRRTL Circuit and Annotations _before_ FIRRTL compilation */ - def getFirrtlAndAnnos(t: => RawModule): (Circuit, Seq[Annotation]) = { + def getFirrtlAndAnnos(t: => RawModule, providedAnnotations: Seq[Annotation] = Nil): (Circuit, Seq[Annotation]) = { val args = Array( "--target-dir", createTestDirectory(this.getClass.getSimpleName).toString, - "--no-run-firrtl" + "--no-run-firrtl", + "--full-stacktrace" ) - val annos = (new ChiselStage).execute(args, Seq(ChiselGeneratorAnnotation(() => t))) + val annos = (new ChiselStage).execute(args, Seq(ChiselGeneratorAnnotation(() => t)) ++ providedAnnotations) val circuit = annos.collectFirst { case FirrtlCircuitAnnotation(c) => c }.getOrElse(fail("No FIRRTL Circuit found!!")) @@ -124,6 +126,9 @@ abstract class ChiselFlatSpec extends AnyFlatSpec with ChiselRunners with Matche /** Spec base class for BDD-style testers. */ abstract class ChiselFreeSpec extends AnyFreeSpec with ChiselRunners with Matchers +/** Spec base class for BDD-style testers. */ +abstract class ChiselFunSpec extends AnyFunSpec with ChiselRunners with Matchers + /** Spec base class for property-based testers. */ abstract class ChiselPropSpec extends AnyPropSpec with ChiselRunners with ScalaCheckPropertyChecks with Matchers { diff --git a/src/test/scala/chiselTests/aop/SelectSpec.scala b/src/test/scala/chiselTests/aop/SelectSpec.scala index 46c62d67..e09e78c8 100644 --- a/src/test/scala/chiselTests/aop/SelectSpec.scala +++ b/src/test/scala/chiselTests/aop/SelectSpec.scala @@ -163,7 +163,7 @@ class SelectSpec extends ChiselFlatSpec { val out = IO(Output(UInt(8.W))) out := in } - class Top extends MultiIOModule { + class Top extends Module { val in = IO(Input(UInt(8.W))) val out = IO(Output(UInt(8.W))) val inst0 = Module(new Child) @@ -182,5 +182,34 @@ class SelectSpec extends ChiselFlatSpec { Select.instances(top) should equal (Seq(top.inst0)) } + "Using Definition/Instance with Injecting Aspects" should "throw an error" in { + import chisel3.experimental.CloneModuleAsRecord + import chisel3.experimental.hierarchy._ + @instantiable + class Child extends RawModule { + @public val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + out := in + } + class Top extends Module { + val in = IO(Input(UInt(8.W))) + val out = IO(Output(UInt(8.W))) + val definition = Definition(new Child) + val inst0 = Instance(definition) + val inst1 = Instance(definition) + inst0.in := in + inst1.in := inst0.out + out := inst1.out + } + val top = ChiselGeneratorAnnotation(() => { + new Top() + }).elaborate + .collectFirst { case DesignAnnotation(design: Top) => design } + .get + intercept[Exception] { Select.collectDeep(top) { case x => x } } + intercept[Exception] { Select.getDeep(top)(x => Seq(x)) } + intercept[Exception] { Select.instances(top) } + } + } diff --git a/src/test/scala/chiselTests/experimental/DataView.scala b/src/test/scala/chiselTests/experimental/DataView.scala index 381cfeb5..d1620e88 100644 --- a/src/test/scala/chiselTests/experimental/DataView.scala +++ b/src/test/scala/chiselTests/experimental/DataView.scala @@ -29,6 +29,27 @@ object VecBundleDataView { implicit val v2 = v1.invert(_ => new MyBundle) } +object FlatDecoupledDataView { + class FizzBuzz extends Bundle { + val fizz = UInt(8.W) + val buzz = UInt(8.W) + } + class FlatDecoupled extends Bundle { + val valid = Output(Bool()) + val ready = Input(Bool()) + val fizz = Output(UInt(8.W)) + val buzz = Output(UInt(8.W)) + } + implicit val view = DataView[FlatDecoupled, DecoupledIO[FizzBuzz]]( + _ => Decoupled(new FizzBuzz), + _.valid -> _.valid, + _.ready -> _.ready, + _.fizz -> _.bits.fizz, + _.buzz -> _.bits.buzz + ) + implicit val view2 = view.invert(_ => new FlatDecoupled) +} + // This should become part of Chisel in a later PR object Tuple2DataProduct { implicit def tuple2DataProduct[A : DataProduct, B : DataProduct] = new DataProduct[(A, B)] { @@ -177,24 +198,7 @@ class DataViewSpec extends ChiselFlatSpec { } it should "work with bidirectional connections for nested types" in { - class FizzBuzz extends Bundle { - val fizz = UInt(8.W) - val buzz = UInt(8.W) - } - class FlatDecoupled extends Bundle { - val valid = Output(Bool()) - val ready = Input(Bool()) - val fizz = Output(UInt(8.W)) - val buzz = Output(UInt(8.W)) - } - implicit val view = DataView[FlatDecoupled, DecoupledIO[FizzBuzz]]( - _ => Decoupled(new FizzBuzz), - _.valid -> _.valid, - _.ready -> _.ready, - _.fizz -> _.bits.fizz, - _.buzz -> _.bits.buzz - ) - implicit val view2 = view.invert(_ => new FlatDecoupled) + import FlatDecoupledDataView._ class MyModule extends Module { val enq = IO(Flipped(Decoupled(new FizzBuzz))) val deq = IO(new FlatDecoupled) diff --git a/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala b/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala index 41636da7..92091631 100644 --- a/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala +++ b/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala @@ -1,4 +1,4 @@ -// See LICENSE for license details. +// SPDX-License-Identifier: Apache-2.0 package chiselTests.experimental @@ -166,4 +166,6 @@ class DataViewTargetSpec extends ChiselFlatSpec { ) pairs should equal (expected) } + + // TODO check these properties when using @instance API (especially preservation of totality) } diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala b/src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala new file mode 100644 index 00000000..43111fdd --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.experimental.hierarchy + +import _root_.firrtl.annotations._ +import chisel3.experimental.{annotate, BaseModule} +import chisel3.Data +import chisel3.experimental.hierarchy.{Instance, Definition} + +object Annotations { + case class MarkAnnotation(target: IsMember, tag: String) extends SingleTargetAnnotation[IsMember] { + def duplicate(n: IsMember): Annotation = this.copy(target = n) + } + case class MarkChiselInstanceAnnotation[B <: BaseModule](d: Instance[B], tag: String, isAbsolute: Boolean) extends chisel3.experimental.ChiselAnnotation { + def toFirrtl = MarkAnnotation(d.toTarget, tag) + } + case class MarkChiselDefinitionAnnotation[B <: BaseModule](d: Definition[B], tag: String, isAbsolute: Boolean) extends chisel3.experimental.ChiselAnnotation { + def toFirrtl = MarkAnnotation(d.toTarget, tag) + } + case class MarkChiselAnnotation(d: Data, tag: String, isAbsolute: Boolean) extends chisel3.experimental.ChiselAnnotation { + def toFirrtl = if(isAbsolute) MarkAnnotation(d.toAbsoluteTarget, tag) else MarkAnnotation(d.toTarget, tag) + } + def mark(d: Data, tag: String): Unit = annotate(MarkChiselAnnotation(d, tag, false)) + def mark[B <: BaseModule](d: Instance[B], tag: String): Unit = annotate(MarkChiselInstanceAnnotation(d, tag, false)) + def mark[B <: BaseModule](d: Definition[B], tag: String): Unit = annotate(MarkChiselDefinitionAnnotation(d, tag, false)) + def amark(d: Data, tag: String): Unit = annotate(MarkChiselAnnotation(d, tag, true)) + def amark[B <: BaseModule](d: Instance[B], tag: String): Unit = annotate(MarkChiselInstanceAnnotation(d, tag, true)) +} diff --git a/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala b/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala new file mode 100644 index 00000000..19261c36 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests +package experimental.hierarchy + +import chisel3._ +import chisel3.experimental.BaseModule +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, public} + +// TODO/Notes +// - In backport, clock/reset are not automatically assigned. I think this is fixed in 3.5 +// - CircuitTarget for annotations on the definition are wrong - needs to be fixed. +class DefinitionSpec extends ChiselFunSpec with Utils { + import Annotations._ + import Examples._ + describe("0: Definition instantiation") { + it("0.0: module name of a definition should be correct") { + class Top extends Module { + val definition = Definition(new AddOne) + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("module AddOne :") + } + it("0.2: accessing internal fields through non-generated means is hard to do") { + class Top extends Module { + val definition = Definition(new AddOne) + //definition.lookup(_.in) // Uncommenting this line will give the following error: + //"You are trying to access a macro-only API. Please use the @public annotation instead." + definition.in + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("module AddOne :") + } + it("0.2: reset inference is not defaulted to Bool for definitions") { + class Top extends Module with RequireAsyncReset { + val definition = Definition(new HasUninferredReset) + val i0 = Instance(definition) + i0.in := 0.U + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("inst i0 of HasUninferredReset") + } + } + describe("1: Annotations on definitions in same chisel compilation") { + it("1.0: should work on a single definition, annotating the definition") { + class Top extends Module { + val definition: Definition[AddOne] = Definition(new AddOne) + mark(definition, "mark") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne".mt, "mark")) + } + it("1.1: should work on a single definition, annotating an inner wire") { + class Top extends Module { + val definition: Definition[AddOne] = Definition(new AddOne) + mark(definition.innerWire, "i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne>innerWire".rt, "i0.innerWire")) + } + it("1.2: should work on a two nested definitions, annotating the definition") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + mark(definition.definition, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne".mt, "i0.i0")) + } + it("1.2: should work on an instance in a definition, annotating the instance") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + mark(definition.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne".it, "i0.i0")) + } + it("1.2: should work on a definition in an instance, annotating the definition") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + val i0 = Instance(definition) + mark(i0.definition, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne".mt, "i0.i0")) + } + it("1.3: should work on a wire in an instance in a definition") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + mark(definition.i0.innerWire, "i0.i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "i0.i0.innerWire")) + } + it("1.4: should work on a nested module in a definition, annotating the module") { + class Top extends Module { + val definition: Definition[AddTwoMixedModules] = Definition(new AddTwoMixedModules) + mark(definition.i1, "i0.i1") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwoMixedModules/i1:AddOne_2".it, "i0.i1")) + } + // Can you define an instantiable container? I think not. + // Instead, we can test the instantiable container in a definition + it("1.5: should work on an instantiable container, annotating a wire in the defintion") { + class Top extends Module { + val definition: Definition[AddOneWithInstantiableWire] = Definition(new AddOneWithInstantiableWire) + mark(definition.wireContainer.innerWire, "i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableWire>innerWire".rt, "i0.innerWire")) + } + it("1.6: should work on an instantiable container, annotating a module") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableModule) + mark(definition.moduleContainer.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableModule/i0:AddOne".it, "i0.i0")) + } + it("1.7: should work on an instantiable container, annotating an instance") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstance) + mark(definition.instanceContainer.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableInstance/i0:AddOne".it, "i0.i0")) + } + it("1.8: should work on an instantiable container, annotating an instantiable container's module") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstantiable) + mark(definition.containerContainer.container.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableInstantiable/i0:AddOne".it, "i0.i0")) + } + it("1.9: should work on public member which references public member of another instance") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstantiable) + mark(definition.containerContainer.container.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithInstantiableInstantiable/i0:AddOne".it, "i0.i0")) + } + it("1.10: should work for targets on definition to have correct circuit name"){ + class Top extends Module { + val definition = Definition(new AddOneWithAnnotation) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithAnnotation>innerWire".rt, "innerWire")) + } + } + describe("2: Annotations on designs not in the same chisel compilation") { + it("2.0: should work on an innerWire, marked in a different compilation") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Definition(new ViewerParent(x, false, true)) + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain(MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "first")) + } + it("2.1: should work on an innerWire, marked in a different compilation, in instanced instantiable") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Definition(new ViewerParent(x, true, false)) + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain(MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "second")) + } + it("2.2: should work on an innerWire, marked in a different compilation, in instanced module") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Definition(new ViewerParent(x, false, false)) + mark(parent.viewer.x.i0.innerWire, "third") + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain(MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "third")) + } + } + describe("3: @public") { + it("3.0: should work on multi-vals") { + class Top() extends Module { + val mv = Definition(new MultiVal()) + mark(mv.x, "mv.x") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|MultiVal>x".rt, "mv.x")) + } + it("3.1: should work on lazy vals") { + class Top() extends Module { + val lv = Definition(new LazyVal()) + mark(lv.x, lv.y) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|LazyVal>x".rt, "Hi")) + } + it("3.2: should work on islookupables") { + class Top() extends Module { + val p = Parameters("hi", 0) + val up = Definition(new UsesParameters(p)) + mark(up.x, up.y.string + up.y.int) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|UsesParameters>x".rt, "hi0")) + } + it("3.3: should work on lists") { + class Top() extends Module { + val i = Definition(new HasList()) + mark(i.x(1), i.y(1).toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasList>x_1".rt, "2")) + } + it("3.4: should work on seqs") { + class Top() extends Module { + val i = Definition(new HasSeq()) + mark(i.x(1), i.y(1).toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasSeq>x_1".rt, "2")) + } + it("3.5: should work on options") { + class Top() extends Module { + val i = Definition(new HasOption()) + i.x.map(x => mark(x, "x")) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasOption>x".rt, "x")) + } + it("3.6: should work on vecs") { + class Top() extends Module { + val i = Definition(new HasVec()) + mark(i.x, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasVec>x".rt, "blah")) + } + it("3.7: should work on statically indexed vectors external to module") { + class Top() extends Module { + val i = Definition(new HasVec()) + mark(i.x(1), "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasVec>x[1]".rt, "blah")) + } + it("3.8: should work on statically indexed vectors internal to module") { + class Top() extends Module { + val i = Definition(new HasIndexedVec()) + mark(i.y, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasIndexedVec>x[1]".rt, "blah")) + } + ignore("3.9: should work on vals in constructor arguments") { + class Top() extends Module { + val i = Definition(new HasPublicConstructorArgs(10)) + //mark(i.x, i.int.toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|HasPublicConstructorArgs>x".rt, "10")) + } + } + describe("4: toDefinition") { + it("4.0: should work on modules") { + class Top() extends Module { + val i = Module(new AddOne()) + f(i.toDefinition) + } + def f(i: Definition[AddOne]): Unit = mark(i.innerWire, "blah") + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne>innerWire".rt, "blah")) + } + it("4.2: should work on seqs of modules") { + class Top() extends Module { + val is = Seq(Module(new AddTwo()), Module(new AddTwo())).map(_.toDefinition) + mark(f(is), "blah") + } + def f(i: Seq[Definition[AddTwo]]): Data = i.head.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("4.2: should work on options of modules") { + class Top() extends Module { + val is: Option[Definition[AddTwo]] = Some(Module(new AddTwo())).map(_.toDefinition) + mark(f(is), "blah") + } + def f(i: Option[Definition[AddTwo]]): Data = i.get.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + } + describe("5: Absolute Targets should work as expected") { + it("5.0: toAbsoluteTarget on a port of a definition") { + class Top() extends Module { + val i = Definition(new AddTwo()) + amark(i.in, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo>in".rt, "blah")) + } + it("5.1: toAbsoluteTarget on a subinstance's data within a definition") { + class Top() extends Module { + val i = Definition(new AddTwo()) + amark(i.i0.innerWire, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("5.2: toAbsoluteTarget on a submodule's data within a definition") { + class Top() extends Module { + val i = Definition(new AddTwoMixedModules()) + amark(i.i1.in, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwoMixedModules/i1:AddOne_2>in".rt, "blah")) + } + it("5.3: toAbsoluteTarget on a submodule's data, in an aggregate, within a definition") { + class Top() extends Module { + val i = Definition(new InstantiatesHasVec()) + amark(i.i1.x.head, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|InstantiatesHasVec/i1:HasVec_2>x[0]".rt, "blah")) + } + } + describe("6: @instantiable traits should work as expected") { + class MyBundle extends Bundle { + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + } + @instantiable + trait ModuleIntf extends BaseModule { + @public val io = IO(new MyBundle) + } + @instantiable + class ModuleWithCommonIntf(suffix: String = "") extends Module with ModuleIntf { + override def desiredName: String = super.desiredName + suffix + @public val sum = io.in + 1.U + + io.out := sum + } + class BlackBoxWithCommonIntf extends BlackBox with ModuleIntf + + it("6.0: A Module that implements an @instantiable trait should be definable as that trait") { + class Top extends Module { + val i: Definition[ModuleIntf] = Definition(new ModuleWithCommonIntf) + mark(i.io.in, "gotcha") + mark(i, "inst") + } + val expected = List( + "~Top|ModuleWithCommonIntf>io.in".rt -> "gotcha", + "~Top|ModuleWithCommonIntf".mt -> "inst" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.1 An @instantiable Module that implements an @instantiable trait should be able to use extension methods from both") { + class Top extends Module { + val i: Definition[ModuleWithCommonIntf] = Definition(new ModuleWithCommonIntf) + mark(i.io.in, "gotcha") + mark(i.sum, "also this") + mark(i, "inst") + } + val expected = List( + "~Top|ModuleWithCommonIntf>io.in".rt -> "gotcha", + "~Top|ModuleWithCommonIntf>sum".rt -> "also this", + "~Top|ModuleWithCommonIntf".mt -> "inst" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.2 A BlackBox that implements an @instantiable trait should be instantiable as that trait") { + class Top extends Module { + val m: ModuleIntf = Module(new BlackBoxWithCommonIntf) + val d: Definition[ModuleIntf] = m.toDefinition + mark(d.io.in, "gotcha") + mark(d, "module") + } + val expected = List( + "~Top|BlackBoxWithCommonIntf>in".rt -> "gotcha", + "~Top|BlackBoxWithCommonIntf".mt -> "module" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.3 It should be possible to have Vectors of @instantiable traits mixing concrete subclasses") { + class Top extends Module { + val definition = Definition(new ModuleWithCommonIntf("X")) + val insts: Seq[Definition[ModuleIntf]] = Vector( + Module(new ModuleWithCommonIntf("Y")).toDefinition, + Module(new BlackBoxWithCommonIntf).toDefinition, + definition + ) + mark(insts(0).io.in, "foo") + mark(insts(1).io.in, "bar") + mark(insts(2).io.in, "fizz") + } + val expected = List( + "~Top|ModuleWithCommonIntfY>io.in".rt -> "foo", + "~Top|BlackBoxWithCommonIntf>in".rt -> "bar", + "~Top|ModuleWithCommonIntfX>io.in".rt -> "fizz" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } + describe("7: @instantiable and @public should compose with DataView") { + import chisel3.experimental.dataview._ + ignore("7.0: should work on simple Views") { + @instantiable + class MyModule extends RawModule { + val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + val sum = in + 1.U + out := sum + 1.U + @public val foo = in.viewAs[UInt] + @public val bar = sum.viewAs[UInt] + } + class Top extends RawModule { + val foo = IO(Input(UInt(8.W))) + val bar = IO(Output(UInt(8.W))) + val d = Definition(new MyModule) + val i = Instance(d) + i.foo := foo + bar := i.out + mark(d.out, "out") + mark(d.foo, "foo") + mark(d.bar, "bar") + } + val expectedAnnos = List( + "~Top|MyModule>out".rt -> "out", + "~Top|MyModule>in".rt -> "foo", + "~Top|MyModule>sum".rt -> "bar" + ) + val expectedLines = List( + "i.in <= foo", + "bar <= i.out" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- expectedLines) { + text should include (line) + } + for (e <- expectedAnnos.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + ignore("7.1: should work on Aggregate Views that are mapped 1:1") { + import chiselTests.experimental.SimpleBundleDataView._ + @instantiable + class MyModule extends RawModule { + private val a = IO(Input(new BundleA(8))) + private val b = IO(Output(new BundleA(8))) + @public val in = a.viewAs[BundleB] + @public val out = b.viewAs[BundleB] + out := in + } + class Top extends RawModule { + val foo = IO(Input(new BundleB(8))) + val bar = IO(Output(new BundleB(8))) + val d = Definition(new MyModule) + val i = Instance(d) + i.in := foo + bar.bar := i.out.bar + mark(d.in, "in") + mark(d.in.bar, "in_bar") + } + val expectedAnnos = List( + "~Top|MyModule>a".rt -> "in", + "~Top|MyModule>a.foo".rt -> "in_bar", + ) + val expectedLines = List( + "i.a <= foo", + "bar <= i.b.foo" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- expectedLines) { + text should include (line) + } + for (e <- expectedAnnos.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } +} diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala new file mode 100644 index 00000000..23b8c9c0 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/Examples.scala @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.experimental.hierarchy + +import chisel3._ +import chisel3.util.Valid +import chisel3.experimental.hierarchy._ +import chisel3.experimental.BaseModule + +object Examples { + import Annotations._ + @instantiable + class AddOne extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val innerWire = Wire(UInt(32.W)) + innerWire := in + 1.U + out := innerWire + } + @instantiable + class AddOneWithAnnotation extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val innerWire = Wire(UInt(32.W)) + mark(innerWire, "innerWire") + innerWire := in + 1.U + out := innerWire + } + @instantiable + class AddOneWithAbsoluteAnnotation extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val innerWire = Wire(UInt(32.W)) + amark(innerWire, "innerWire") + innerWire := in + 1.U + out := innerWire + } + @instantiable + class AddTwo extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val definition = Definition(new AddOne) + @public val i0: Instance[AddOne] = Instance(definition) + @public val i1: Instance[AddOne] = Instance(definition) + i0.in := in + i1.in := i0.out + out := i1.out + } + @instantiable + class AddTwoMixedModules extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + val definition = Definition(new AddOne) + @public val i0: Instance[AddOne] = Instance(definition) + @public val i1 = Module(new AddOne) + i0.in := in + i1.in := i0.out + out := i1.out + } + @instantiable + class AggregatePortModule extends Module { + @public val io = IO(new Bundle { + val in = Input(UInt(32.W)) + val out = Output(UInt(32.W)) + }) + io.out := io.in + } + @instantiable + class WireContainer { + @public val innerWire = Wire(UInt(32.W)) + } + @instantiable + class AddOneWithInstantiableWire extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val wireContainer = new WireContainer() + wireContainer.innerWire := in + 1.U + out := wireContainer.innerWire + } + @instantiable + class AddOneContainer { + @public val i0 = Module(new AddOne) + } + @instantiable + class AddOneWithInstantiableModule extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val moduleContainer = new AddOneContainer() + moduleContainer.i0.in := in + out := moduleContainer.i0.out + } + @instantiable + class AddOneInstanceContainer { + val definition = Definition(new AddOne) + @public val i0 = Instance(definition) + } + @instantiable + class AddOneWithInstantiableInstance extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val instanceContainer = new AddOneInstanceContainer() + instanceContainer.i0.in := in + out := instanceContainer.i0.out + } + @instantiable + class AddOneContainerContainer { + @public val container = new AddOneContainer + } + @instantiable + class AddOneWithInstantiableInstantiable extends Module { + @public val in = IO(Input(UInt(32.W))) + @public val out = IO(Output(UInt(32.W))) + @public val containerContainer = new AddOneContainerContainer() + containerContainer.container.i0.in := in + out := containerContainer.container.i0.out + } + @instantiable + class Viewer(val y: AddTwo, markPlease: Boolean) { + @public val x = y + if(markPlease) mark(x.i0.innerWire, "first") + } + @instantiable + class ViewerParent(val x: AddTwo, markHere: Boolean, markThere: Boolean) extends Module { + @public val viewer = new Viewer(x, markThere) + if(markHere) mark(viewer.x.i0.innerWire, "second") + } + @instantiable + class MultiVal() extends Module { + @public val (x, y) = (Wire(UInt(3.W)), Wire(UInt(3.W))) + } + @instantiable + class LazyVal() extends Module { + @public val x = Wire(UInt(3.W)) + @public lazy val y = "Hi" + } + case class Parameters(string: String, int: Int) extends IsLookupable + @instantiable + class UsesParameters(p: Parameters) extends Module { + @public val y = p + @public val x = Wire(UInt(3.W)) + } + @instantiable + class HasList() extends Module { + @public val y = List(1, 2, 3) + @public val x = List.fill(3)(Wire(UInt(3.W))) + } + @instantiable + class HasSeq() extends Module { + @public val y = Seq(1, 2, 3) + @public val x = Seq.fill(3)(Wire(UInt(3.W))) + } + @instantiable + class HasOption() extends Module { + @public val x: Option[UInt] = Some(Wire(UInt(3.W))) + } + @instantiable + class HasVec() extends Module { + @public val x = VecInit(1.U, 2.U, 3.U) + } + @instantiable + class HasIndexedVec() extends Module { + val x = VecInit(1.U, 2.U, 3.U) + @public val y = x(1) + } + @instantiable + class HasSubFieldAccess extends Module { + val in = IO(Input(Valid(UInt(8.W)))) + @public val valid = in.valid + @public val bits = in.bits + } + @instantiable + class HasPublicConstructorArgs(@public val int: Int) extends Module { + @public val x = Wire(UInt(3.W)) + } + @instantiable + class InstantiatesHasVec() extends Module { + @public val i0 = Instance(Definition(new HasVec())) + @public val i1 = Module(new HasVec()) + } + @instantiable + class HasUninferredReset() extends Module { + @public val in = IO(Input(UInt(3.W))) + @public val out = IO(Output(UInt(3.W))) + out := RegNext(in) + } +} diff --git a/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala new file mode 100644 index 00000000..3866bf87 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala @@ -0,0 +1,709 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests +package experimental.hierarchy + +import chisel3._ +import chisel3.experimental.BaseModule +import chisel3.experimental.hierarchy.{Definition, Instance, instantiable, public} +import chisel3.util.{DecoupledIO, Valid} + + +// TODO/Notes +// - In backport, clock/reset are not automatically assigned. I think this is fixed in 3.5 +// - CircuitTarget for annotations on the definition are wrong - needs to be fixed. +class InstanceSpec extends ChiselFunSpec with Utils { + import Annotations._ + import Examples._ + describe("0: Instance instantiation") { + it("0.0: name of an instance should be correct") { + class Top extends Module { + val definition = Definition(new AddOne) + val i0 = Instance(definition) + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("inst i0 of AddOne") + } + it("0.1: name of an instanceclone should not error") { + class Top extends Module { + val definition = Definition(new AddTwo) + val i0 = Instance(definition) + val i = i0.i0 // This should not error + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("inst i0 of AddTwo") + } + it("0.2: accessing internal fields through non-generated means is hard to do") { + class Top extends Module { + val definition = Definition(new AddOne) + val i0 = Instance(definition) + //i0.lookup(_.in) // Uncommenting this line will give the following error: + //"You are trying to access a macro-only API. Please use the @public annotation instead." + i0.in + } + val (chirrtl, _) = getFirrtlAndAnnos(new Top) + chirrtl.serialize should include ("inst i0 of AddOne") + } + } + describe("1: Annotations on instances in same chisel compilation") { + it("1.0: should work on a single instance, annotating the instance") { + class Top extends Module { + val definition: Definition[AddOne] = Definition(new AddOne) + val i0: Instance[AddOne] = Instance(definition) + mark(i0, "i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOne".it, "i0")) + } + it("1.1: should work on a single instance, annotating an inner wire") { + class Top extends Module { + val definition: Definition[AddOne] = Definition(new AddOne) + val i0: Instance[AddOne] = Instance(definition) + mark(i0.innerWire, "i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOne>innerWire".rt, "i0.innerWire")) + } + it("1.2: should work on a two nested instances, annotating the instance") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + val i0: Instance[AddTwo] = Instance(definition) + mark(i0.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddTwo/i0:AddOne".it, "i0.i0")) + } + it("1.3: should work on a two nested instances, annotating the inner wire") { + class Top extends Module { + val definition: Definition[AddTwo] = Definition(new AddTwo) + val i0: Instance[AddTwo] = Instance(definition) + mark(i0.i0.innerWire, "i0.i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddTwo/i0:AddOne>innerWire".rt, "i0.i0.innerWire")) + } + it("1.4: should work on a nested module in an instance, annotating the module") { + class Top extends Module { + val definition: Definition[AddTwoMixedModules] = Definition(new AddTwoMixedModules) + val i0: Instance[AddTwoMixedModules] = Instance(definition) + mark(i0.i1, "i0.i1") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddTwoMixedModules/i1:AddOne_2".it, "i0.i1")) + } + it("1.5: should work on an instantiable container, annotating a wire") { + class Top extends Module { + val definition: Definition[AddOneWithInstantiableWire] = Definition(new AddOneWithInstantiableWire) + val i0: Instance[AddOneWithInstantiableWire] = Instance(definition) + mark(i0.wireContainer.innerWire, "i0.innerWire") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableWire>innerWire".rt, "i0.innerWire")) + } + it("1.6: should work on an instantiable container, annotating a module") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableModule) + val i0 = Instance(definition) + mark(i0.moduleContainer.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableModule/i0:AddOne".it, "i0.i0")) + } + it("1.7: should work on an instantiable container, annotating an instance") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstance) + val i0 = Instance(definition) + mark(i0.instanceContainer.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableInstance/i0:AddOne".it, "i0.i0")) + } + it("1.8: should work on an instantiable container, annotating an instantiable container's module") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstantiable) + val i0 = Instance(definition) + mark(i0.containerContainer.container.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableInstantiable/i0:AddOne".it, "i0.i0")) + } + it("1.9: should work on public member which references public member of another instance") { + class Top extends Module { + val definition = Definition(new AddOneWithInstantiableInstantiable) + val i0 = Instance(definition) + mark(i0.containerContainer.container.i0, "i0.i0") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/i0:AddOneWithInstantiableInstantiable/i0:AddOne".it, "i0.i0")) + } + it("1.10: should work for targets on definition to have correct circuit name"){ + class Top extends Module { + val definition = Definition(new AddOneWithAnnotation) + val i0 = Instance(definition) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|AddOneWithAnnotation>innerWire".rt, "innerWire")) + } + } + describe("2: Annotations on designs not in the same chisel compilation") { + it("2.0: should work on an innerWire, marked in a different compilation") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Instance(Definition(new ViewerParent(x, false, true))) + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain (MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "first")) + } + it("2.1: should work on an innerWire, marked in a different compilation, in instanced instantiable") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Instance(Definition(new ViewerParent(x, true, false))) + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain (MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "second")) + } + it("2.2: should work on an innerWire, marked in a different compilation, in instanced module") { + val first = elaborateAndGetModule(new AddTwo) + class Top(x: AddTwo) extends Module { + val parent = Instance(Definition(new ViewerParent(x, false, false))) + mark(parent.viewer.x.i0.innerWire, "third") + } + val (_, annos) = getFirrtlAndAnnos(new Top(first)) + annos should contain (MarkAnnotation("~AddTwo|AddTwo/i0:AddOne>innerWire".rt, "third")) + } + } + describe("3: @public") { + it("3.0: should work on multi-vals") { + class Top() extends Module { + val mv = Instance(Definition(new MultiVal())) + mark(mv.x, "mv.x") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/mv:MultiVal>x".rt, "mv.x")) + } + it("3.1: should work on lazy vals") { + class Top() extends Module { + val lv = Instance(Definition(new LazyVal())) + mark(lv.x, lv.y) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain (MarkAnnotation("~Top|Top/lv:LazyVal>x".rt, "Hi")) + } + it("3.2: should work on islookupables") { + class Top() extends Module { + val p = Parameters("hi", 0) + val up = Instance(Definition(new UsesParameters(p))) + mark(up.x, up.y.string + up.y.int) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/up:UsesParameters>x".rt, "hi0")) + } + it("3.3: should work on lists") { + class Top() extends Module { + val i = Instance(Definition(new HasList())) + mark(i.x(1), i.y(1).toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasList>x_1".rt, "2")) + } + it("3.4: should work on seqs") { + class Top() extends Module { + val i = Instance(Definition(new HasSeq())) + mark(i.x(1), i.y(1).toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasSeq>x_1".rt, "2")) + } + it("3.5: should work on options") { + class Top() extends Module { + val i = Instance(Definition(new HasOption())) + i.x.map(x => mark(x, "x")) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasOption>x".rt, "x")) + } + it("3.6: should work on vecs") { + class Top() extends Module { + val i = Instance(Definition(new HasVec())) + mark(i.x, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasVec>x".rt, "blah")) + } + it("3.7: should work on statically indexed vectors external to module") { + class Top() extends Module { + val i = Instance(Definition(new HasVec())) + mark(i.x(1), "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasVec>x[1]".rt, "blah")) + } + it("3.8: should work on statically indexed vectors internal to module") { + class Top() extends Module { + val i = Instance(Definition(new HasIndexedVec())) + mark(i.y, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasIndexedVec>x[1]".rt, "blah")) + } + it("3.9: should work on accessed subfields of aggregate ports") { + class Top extends Module { + val input = IO(Input(Valid(UInt(8.W)))) + val i = Instance(Definition(new HasSubFieldAccess)) + i.valid := input.valid + i.bits := input.bits + mark(i.valid, "valid") + mark(i.bits, "bits") + } + val expected = List( + "~Top|Top/i:HasSubFieldAccess>in.valid".rt -> "valid", + "~Top|Top/i:HasSubFieldAccess>in.bits".rt -> "bits" + ) + val lines = List( + "i.in.valid <= input.valid", + "i.in.bits <= input.bits" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- lines) { + text should include (line) + } + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + ignore("3.10: should work on vals in constructor arguments") { + class Top() extends Module { + val i = Instance(Definition(new HasPublicConstructorArgs(10))) + //mark(i.x, i.int.toString) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:HasPublicConstructorArgs>x".rt, "10")) + } + } + describe("4: toInstance") { + it("4.0: should work on modules") { + class Top() extends Module { + val i = Module(new AddOne()) + f(i.toInstance) + } + def f(i: Instance[AddOne]): Unit = mark(i.innerWire, "blah") + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOne>innerWire".rt, "blah")) + } + it("4.1: should work on isinstantiables") { + class Top() extends Module { + val i = Module(new AddTwo()) + val v = new Viewer(i, false) + mark(f(v.toInstance), "blah") + } + def f(i: Instance[Viewer]): Data = i.x.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("4.2: should work on seqs of modules") { + class Top() extends Module { + val is = Seq(Module(new AddTwo()), Module(new AddTwo())).map(_.toInstance) + mark(f(is), "blah") + } + def f(i: Seq[Instance[AddTwo]]): Data = i.head.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("4.3: should work on seqs of isInstantiables") { + class Top() extends Module { + val i = Module(new AddTwo()) + val vs = Seq(new Viewer(i, false), new Viewer(i, false)).map(_.toInstance) + mark(f(vs), "blah") + } + def f(i: Seq[Instance[Viewer]]): Data = i.head.x.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("4.2: should work on options of modules") { + class Top() extends Module { + val is: Option[Instance[AddTwo]] = Some(Module(new AddTwo())).map(_.toInstance) + mark(f(is), "blah") + } + def f(i: Option[Instance[AddTwo]]): Data = i.get.i0.innerWire + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + } + describe("5: Absolute Targets should work as expected") { + it("5.0: toAbsoluteTarget on a port of an instance") { + class Top() extends Module { + val i = Instance(Definition(new AddTwo())) + amark(i.in, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:AddTwo>in".rt, "blah")) + } + it("5.1: toAbsoluteTarget on a subinstance's data within an instance") { + class Top() extends Module { + val i = Instance(Definition(new AddTwo())) + amark(i.i0.innerWire, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:AddTwo/i0:AddOne>innerWire".rt, "blah")) + } + it("5.2: toAbsoluteTarget on a submodule's data within an instance") { + class Top() extends Module { + val i = Instance(Definition(new AddTwoMixedModules())) + amark(i.i1.in, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:AddTwoMixedModules/i1:AddOne_2>in".rt, "blah")) + } + it("5.3: toAbsoluteTarget on a submodule's data, in an aggregate, within an instance") { + class Top() extends Module { + val i = Instance(Definition(new InstantiatesHasVec())) + amark(i.i1.x.head, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:InstantiatesHasVec/i1:HasVec_2>x[0]".rt, "blah")) + } + it("5.4: toAbsoluteTarget on a submodule's data, in an aggregate, within an instance, ILit") { + class MyBundle extends Bundle { val x = UInt(3.W) } + @instantiable + class HasVec() extends Module { + @public val x = Wire(Vec(3, new MyBundle())) + } + @instantiable + class InstantiatesHasVec() extends Module { + @public val i0 = Instance(Definition(new HasVec())) + @public val i1 = Module(new HasVec()) + } + class Top() extends Module { + val i = Instance(Definition(new InstantiatesHasVec())) + amark(i.i1.x.head.x, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:InstantiatesHasVec/i1:HasVec_2>x[0].x".rt, "blah")) + } + it("5.5: toAbsoluteTarget on a subinstance") { + class Top() extends Module { + val i = Instance(Definition(new AddTwo())) + amark(i.i1, "blah") + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|Top/i:AddTwo/i1:AddOne".it, "blah")) + } + it("5.6: should work for absolute targets on definition to have correct circuit name"){ + class Top extends Module { + val definition = Definition(new AddOneWithAbsoluteAnnotation) + val i0 = Instance(definition) + } + val (_, annos) = getFirrtlAndAnnos(new Top) + annos should contain(MarkAnnotation("~Top|AddOneWithAbsoluteAnnotation>innerWire".rt, "innerWire")) + } + } + describe("6: @instantiable traits should work as expected") { + class MyBundle extends Bundle { + val in = Input(UInt(8.W)) + val out = Output(UInt(8.W)) + } + @instantiable + trait ModuleIntf extends BaseModule { + @public val io = IO(new MyBundle) + } + @instantiable + class ModuleWithCommonIntf(suffix: String = "") extends Module with ModuleIntf { + override def desiredName: String = super.desiredName + suffix + @public val sum = io.in + 1.U + + io.out := sum + } + class BlackBoxWithCommonIntf extends BlackBox with ModuleIntf + + it("6.0: A Module that implements an @instantiable trait should be instantiable as that trait") { + class Top extends Module { + val i: Instance[ModuleIntf] = Instance(Definition(new ModuleWithCommonIntf)) + mark(i.io.in, "gotcha") + mark(i, "inst") + } + val expected = List( + "~Top|Top/i:ModuleWithCommonIntf>io.in".rt -> "gotcha", + "~Top|Top/i:ModuleWithCommonIntf".it -> "inst" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.1 An @instantiable Module that implements an @instantiable trait should be able to use extension methods from both") { + class Top extends Module { + val i: Instance[ModuleWithCommonIntf] = Instance(Definition(new ModuleWithCommonIntf)) + mark(i.io.in, "gotcha") + mark(i.sum, "also this") + mark(i, "inst") + } + val expected = List( + "~Top|Top/i:ModuleWithCommonIntf>io.in".rt -> "gotcha", + "~Top|Top/i:ModuleWithCommonIntf>sum".rt -> "also this", + "~Top|Top/i:ModuleWithCommonIntf".it -> "inst" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.2 A BlackBox that implements an @instantiable trait should be instantiable as that trait") { + class Top extends Module { + val i: Instance[ModuleIntf] = Module(new BlackBoxWithCommonIntf).toInstance + mark(i.io.in, "gotcha") + mark(i, "module") + } + val expected = List( + "~Top|BlackBoxWithCommonIntf>in".rt -> "gotcha", + "~Top|BlackBoxWithCommonIntf".mt -> "module" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("6.3 It should be possible to have Vectors of @instantiable traits mixing concrete subclasses") { + class Top extends Module { + val proto = Definition(new ModuleWithCommonIntf("X")) + val insts: Seq[Instance[ModuleIntf]] = Vector( + Module(new ModuleWithCommonIntf("Y")).toInstance, + Module(new BlackBoxWithCommonIntf).toInstance, + Instance(proto) + ) + mark(insts(0).io.in, "foo") + mark(insts(1).io.in, "bar") + mark(insts(2).io.in, "fizz") + } + val expected = List( + "~Top|ModuleWithCommonIntfY>io.in".rt -> "foo", + "~Top|BlackBoxWithCommonIntf>in".rt -> "bar", + "~Top|Top/insts_2:ModuleWithCommonIntfX>io.in".rt -> "fizz" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } + // TODO don't forget to test this with heterogeneous Views (eg. viewing a tuple of a port and non-port as a single Bundle) + describe("7: @instantiable and @public should compose with DataView") { + import chisel3.experimental.dataview._ + it("7.0: should work on simple Views") { + @instantiable + class MyModule extends RawModule { + val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + val sum = in + 1.U + out := sum + 1.U + @public val foo = in.viewAs[UInt] + @public val bar = sum.viewAs[UInt] + } + class Top extends RawModule { + val foo = IO(Input(UInt(8.W))) + val bar = IO(Output(UInt(8.W))) + val i = Instance(Definition(new MyModule)) + i.foo := foo + bar := i.out + mark(i.out, "out") + mark(i.foo, "foo") + mark(i.bar, "bar") + } + val expectedAnnos = List( + "~Top|Top/i:MyModule>out".rt -> "out", + "~Top|Top/i:MyModule>in".rt -> "foo", + "~Top|Top/i:MyModule>sum".rt -> "bar" + ) + val expectedLines = List( + "i.in <= foo", + "bar <= i.out" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- expectedLines) { + text should include (line) + } + for (e <- expectedAnnos.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + + ignore("7.1: should work on Aggregate Views") { + import chiselTests.experimental.FlatDecoupledDataView._ + type RegDecoupled = DecoupledIO[FizzBuzz] + @instantiable + class MyModule extends RawModule { + private val a = IO(Flipped(new FlatDecoupled)) + private val b = IO(new FlatDecoupled) + @public val enq = a.viewAs[RegDecoupled] + @public val deq = b.viewAs[RegDecoupled] + @public val enq_valid = enq.valid // Also return a subset of the view + deq <> enq + } + class Top extends RawModule { + val foo = IO(Flipped(new RegDecoupled(new FizzBuzz))) + val bar = IO(new RegDecoupled(new FizzBuzz)) + val i = Instance(Definition(new MyModule)) + i.enq <> foo + i.enq_valid := foo.valid // Make sure connections also work for @public on elements of a larger Aggregate + i.deq.ready := bar.ready + bar.valid := i.deq.valid + bar.bits := i.deq.bits + mark(i.enq, "enq") + mark(i.enq.bits, "enq.bits") + mark(i.deq.bits.fizz, "deq.bits.fizz") + mark(i.enq_valid, "enq_valid") + } + val expectedAnnos = List( + "~Top|Top/i:MyModule>a".rt -> "enq", // Not split, checks 1:1 + "~Top|Top/i:MyModule>a.fizz".rt -> "enq.bits", // Split, checks non-1:1 inner Aggregate + "~Top|Top/i:MyModule>a.buzz".rt -> "enq.bits", + "~Top|Top/i:MyModule>b.fizz".rt -> "deq.bits.fizz", // Checks 1 inner Element + "~Top|Top/i:MyModule>a.valid".rt -> "enq_valid" + ) + val expectedLines = List( + "i.a.valid <= foo.valid", + "foo.ready <= i.a.ready", + "i.a.fizz <= foo.bits.fizz", + "i.a.buzz <= foo.bits.buzz", + "bar.valid <= i.b.valid", + "i.b.ready <= bar.ready", + "bar.bits.fizz <= i.b.fizz", + "bar.bits.buzz <= i.b.buzz", + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- expectedLines) { + text should include (line) + } + for (e <- expectedAnnos.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + + it("7.2: should work on views of views") { + import chiselTests.experimental.SimpleBundleDataView._ + @instantiable + class MyModule extends RawModule { + private val a = IO(Input(UInt(8.W))) + private val b = IO(Output(new BundleA(8))) + @public val in = a.viewAs[UInt].viewAs[UInt] + @public val out = b.viewAs[BundleB].viewAs[BundleA].viewAs[BundleB] + out.bar := in + } + class Top extends RawModule { + val foo = IO(Input(UInt(8.W))) + val bar = IO(Output(new BundleB(8))) + val i = Instance(Definition(new MyModule)) + i.in := foo + bar := i.out + bar.bar := i.out.bar + mark(i.in, "in") + mark(i.out.bar, "out_bar") + } + val expected = List( + "~Top|Top/i:MyModule>a".rt -> "in", + "~Top|Top/i:MyModule>b.foo".rt -> "out_bar", + ) + val lines = List( + "i.a <= foo", + "bar.bar <= i.b.foo" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- lines) { + text should include (line) + } + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + + it("7.3: should work with DataView + implicit conversion") { + import chiselTests.experimental.SeqToVec._ + @instantiable + class MyModule extends RawModule { + private val a = IO(Input(UInt(8.W))) + private val b = IO(Output(UInt(8.W))) + @public val ports = Seq(a, b) + b := a + } + class Top extends RawModule { + val foo = IO(Input(UInt(8.W))) + val bar = IO(Output(UInt(8.W))) + val i = Instance(Definition(new MyModule)) + i.ports <> Seq(foo, bar) + mark(i.ports, "i.ports") + } + val expected = List( + // Not 1:1 so will get split out + "~Top|Top/i:MyModule>a".rt -> "i.ports", + "~Top|Top/i:MyModule>b".rt -> "i.ports", + ) + val lines = List( + "i.a <= foo", + "bar <= i.b" + ) + val (chirrtl, annos) = getFirrtlAndAnnos(new Top) + val text = chirrtl.serialize + for (line <- lines) { + text should include (line) + } + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } + + describe("8: @instantiable and @public should compose with CloneModuleAsRecord") { + it("8.0: it should support @public on a CMAR Record in Definitions") { + @instantiable + class HasCMAR extends Module { + @public val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + @public val m = Module(new AggregatePortModule) + @public val c = experimental.CloneModuleAsRecord(m) + } + class Top extends Module { + val d = Definition(new HasCMAR) + mark(d.c("io"), "c.io") + val bun = d.c("io").asInstanceOf[Record] + mark(bun.elements("out"), "c.io.out") + } + val expected = List( + "~Top|HasCMAR/c:AggregatePortModule>io".rt -> "c.io", + "~Top|HasCMAR/c:AggregatePortModule>io.out".rt -> "c.io.out" + + ) + val (_, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + it("8.1: it should support @public on a CMAR Record in Instances") { + @instantiable + class HasCMAR extends Module { + @public val in = IO(Input(UInt(8.W))) + @public val out = IO(Output(UInt(8.W))) + @public val m = Module(new AggregatePortModule) + @public val c = experimental.CloneModuleAsRecord(m) + } + class Top extends Module { + val i = Instance(Definition(new HasCMAR)) + mark(i.c("io"), "i.c.io") + val bun = i.c("io").asInstanceOf[Record] + mark(bun.elements("out"), "i.c.io.out") + } + val expected = List( + "~Top|Top/i:HasCMAR/c:AggregatePortModule>io".rt -> "i.c.io", + "~Top|Top/i:HasCMAR/c:AggregatePortModule>io.out".rt -> "i.c.io.out" + + ) + val (_, annos) = getFirrtlAndAnnos(new Top) + for (e <- expected.map(MarkAnnotation.tupled)) { + annos should contain (e) + } + } + } +} + diff --git a/src/test/scala/chiselTests/experimental/hierarchy/Utils.scala b/src/test/scala/chiselTests/experimental/hierarchy/Utils.scala new file mode 100644 index 00000000..a2e51765 --- /dev/null +++ b/src/test/scala/chiselTests/experimental/hierarchy/Utils.scala @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests.experimental.hierarchy + +import chisel3._ +import _root_.firrtl.annotations._ +import chisel3.stage.{ChiselCircuitAnnotation, CircuitSerializationAnnotation, DesignAnnotation} +import chiselTests.ChiselRunners +import firrtl.stage.FirrtlCircuitAnnotation +import org.scalatest.matchers.should.Matchers + +trait Utils extends ChiselRunners with chiselTests.Utils with Matchers { + import Annotations._ + // TODO promote to standard API (in FIRRTL) and perhaps even implement with a macro + implicit class Str2RefTarget(str: String) { + def rt: ReferenceTarget = Target.deserialize(str).asInstanceOf[ReferenceTarget] + def it: InstanceTarget = Target.deserialize(str).asInstanceOf[InstanceTarget] + def mt: ModuleTarget = Target.deserialize(str).asInstanceOf[ModuleTarget] + def ct: CircuitTarget = Target.deserialize(str).asInstanceOf[CircuitTarget] + } +} |
