summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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]
+ }
+}