summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Izraelevitz2021-09-05 12:11:32 -0700
committerGitHub2021-09-05 12:11:32 -0700
commit9fa8da227569455a77596355aeb114f9c164510a (patch)
tree3be3dd579fcc7ae83297d3c28020e97417a6b984
parent7fb2c1ebc23ca07e5de6416a284e1be1b62a48ac (diff)
Add Definition and Instance API (#2045)
This introduces a new experimental API for module instantiation that disentagles elaborating the definition (or implementation) from instantiation of a given module. This solves Chisel's longstanding reliance on "Deduplication" for generating Verilog with multiple instances of the same module. The new API resides in package chisel3.experimental.hierarchy. Please see the hierarchy ScalaDoc, documentation, and tests for examples of use. Co-authored-by: Jack Koenig <koenig@sifive.com> Co-authored-by: Megan Wachs <megan@sifive.com> Co-authored-by: Schuyler Eldridge <schuyler.eldridge@sifive.com>
-rw-r--r--core/src/main/scala/chisel3/BlackBox.scala3
-rw-r--r--core/src/main/scala/chisel3/Module.scala107
-rw-r--r--core/src/main/scala/chisel3/RawModule.scala9
-rw-r--r--core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala99
-rw-r--r--core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala111
-rw-r--r--core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala17
-rw-r--r--core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala25
-rw-r--r--core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala368
-rw-r--r--core/src/main/scala/chisel3/experimental/hierarchy/package.scala48
-rw-r--r--core/src/main/scala/chisel3/internal/Binding.scala5
-rw-r--r--core/src/main/scala/chisel3/internal/Builder.scala49
-rw-r--r--core/src/main/scala/chisel3/internal/firrtl/IR.scala33
-rw-r--r--docs/src/cookbooks/hierarchy.md204
-rw-r--r--macros/src/main/scala/chisel3/internal/InstantiableMacro.scala77
-rw-r--r--macros/src/main/scala/chisel3/internal/sourceinfo/SourceInfoTransform.scala30
-rw-r--r--plugin/src/main/scala/chisel3/internal/plugin/ChiselComponent.scala6
-rw-r--r--src/main/scala/chisel3/aop/Select.scala10
-rw-r--r--src/main/scala/chisel3/aop/injecting/InjectingAspect.scala1
-rw-r--r--src/test/scala/chiselTests/ChiselSpec.scala11
-rw-r--r--src/test/scala/chiselTests/aop/SelectSpec.scala31
-rw-r--r--src/test/scala/chiselTests/experimental/DataView.scala40
-rw-r--r--src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala4
-rw-r--r--src/test/scala/chiselTests/experimental/hierarchy/Annotations.scala28
-rw-r--r--src/test/scala/chiselTests/experimental/hierarchy/DefinitionSpec.scala493
-rw-r--r--src/test/scala/chiselTests/experimental/hierarchy/Examples.scala186
-rw-r--r--src/test/scala/chiselTests/experimental/hierarchy/InstanceSpec.scala709
-rw-r--r--src/test/scala/chiselTests/experimental/hierarchy/Utils.scala21
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]
+ }
+}