summaryrefslogtreecommitdiff
path: root/core/src/main/scala/chisel3/experimental
diff options
context:
space:
mode:
authorJack Koenig2021-09-17 21:01:26 -0700
committerJack Koenig2021-09-17 21:01:26 -0700
commit5c8c19345e6711279594cf1f9ddab33623c8eba7 (patch)
treed9d6ced3934aa4a8be3dec19ddcefe50a7a93d5a /core/src/main/scala/chisel3/experimental
parente63b9667d89768e0ec6dc8a9153335cb48a213a7 (diff)
parent958904cb2f2f65d02b2ab3ec6d9ec2e06d04e482 (diff)
Merge branch 'master' into 3.5-release
Diffstat (limited to 'core/src/main/scala/chisel3/experimental')
-rw-r--r--core/src/main/scala/chisel3/experimental/Analog.scala5
-rw-r--r--core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala72
-rw-r--r--core/src/main/scala/chisel3/experimental/dataview/DataView.scala154
-rw-r--r--core/src/main/scala/chisel3/experimental/dataview/package.scala251
-rw-r--r--core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala100
-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/experimental/package.scala46
-rw-r--r--core/src/main/scala/chisel3/experimental/verification/package.scala47
12 files changed, 1220 insertions, 24 deletions
diff --git a/core/src/main/scala/chisel3/experimental/Analog.scala b/core/src/main/scala/chisel3/experimental/Analog.scala
index df76fd70..6cca81f5 100644
--- a/core/src/main/scala/chisel3/experimental/Analog.scala
+++ b/core/src/main/scala/chisel3/experimental/Analog.scala
@@ -45,7 +45,8 @@ final class Analog private (private[chisel3] val width: Width) extends Element {
// Define setter/getter pairing
// Analog can only be bound to Ports and Wires (and Unbound)
- private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection) {
+ private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection): Unit = {
+ _parent.foreach(_.addId(this))
SpecifiedDirection.fromParent(parentDirection, specifiedDirection) match {
case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip =>
case x => throwException(s"Analog may not have explicit direction, got '$x'")
@@ -55,6 +56,7 @@ final class Analog private (private[chisel3] val width: Width) extends Element {
case ChildBinding(parent) => parent.topBinding
// See https://github.com/freechipsproject/chisel3/pull/946
case SampleElementBinding(parent) => parent.topBinding
+ case a: MemTypeBinding[_] => a
}
targetTopBinding match {
@@ -82,4 +84,3 @@ final class Analog private (private[chisel3] val width: Width) extends Element {
object Analog {
def apply(width: Width): Analog = new Analog(width)
}
-
diff --git a/core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala b/core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala
new file mode 100644
index 00000000..55dd8505
--- /dev/null
+++ b/core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: Apache-2.0
+
+package chisel3.experimental.dataview
+
+import chisel3.experimental.BaseModule
+import chisel3.{Data, getRecursiveFields}
+
+import scala.annotation.implicitNotFound
+
+/** Typeclass interface for getting elements of type [[Data]]
+ *
+ * This is needed for validating [[DataView]]s targeting type `A`.
+ * Can be thought of as "can be the Target of a DataView".
+ *
+ * Chisel provides some implementations in [[DataProduct$ object DataProduct]] that are available
+ * by default in the implicit scope.
+ *
+ * @tparam A Type that has elements of type [[Data]]
+ * @see [[https://www.chisel-lang.org/chisel3/docs/explanations/dataview#dataproduct Detailed Documentation]]
+ */
+@implicitNotFound("Could not find implicit value for DataProduct[${A}].\n" +
+ "Please see https://www.chisel-lang.org/chisel3/docs/explanations/dataview#dataproduct")
+trait DataProduct[-A] {
+ /** Provides [[Data]] elements within some containing object
+ *
+ * @param a Containing object
+ * @param path Hierarchical path to current signal (for error reporting)
+ * @return Data elements and associated String paths (Strings for error reporting only!)
+ */
+ def dataIterator(a: A, path: String): Iterator[(Data, String)]
+
+ /** Returns a checker to test if the containing object contains a `Data` object
+ * @note Implementers may want to override if iterating on all `Data` is expensive for `A` and `A`
+ * will primarily be used in `PartialDataViews`
+ * @note The returned value is a function, not a true Set, but is describing the functionality of
+ * Set containment
+ * @param a Containing object
+ * @return A checker that itself returns True if a given `Data` is contained in `a`
+ * as determined by an `==` test
+ */
+ def dataSet(a: A): Data => Boolean = dataIterator(a, "").map(_._1).toSet
+}
+
+/** Encapsulating object for automatically provided implementations of [[DataProduct]]
+ *
+ * @note DataProduct implementations provided in this object are available in the implicit scope
+ */
+object DataProduct {
+ /** [[DataProduct]] implementation for [[Data]] */
+ implicit val dataDataProduct: DataProduct[Data] = new DataProduct[Data] {
+ def dataIterator(a: Data, path: String): Iterator[(Data, String)] =
+ getRecursiveFields.lazily(a, path).iterator
+ }
+
+ /** [[DataProduct]] implementation for [[BaseModule]] */
+ implicit val userModuleDataProduct: DataProduct[BaseModule] = new DataProduct[BaseModule] {
+ def dataIterator(a: BaseModule, path: String): Iterator[(Data, String)] = {
+ a.getIds.iterator.flatMap {
+ case d: Data if d.getOptionRef.isDefined => // Using ref to decide if it's truly hardware in the module
+ Seq(d -> s"${path}.${d.instanceName}")
+ case b: BaseModule => dataIterator(b, s"$path.${b.instanceName}")
+ case _ => Seq.empty
+ }
+ }
+ // Overridden for performance
+ override def dataSet(a: BaseModule): Data => Boolean = {
+ val lastId = a._lastId // Not cheap to compute
+ // Return a function
+ e => e._id > a._id && e._id <= lastId
+ }
+ }
+}
diff --git a/core/src/main/scala/chisel3/experimental/dataview/DataView.scala b/core/src/main/scala/chisel3/experimental/dataview/DataView.scala
new file mode 100644
index 00000000..caf004c2
--- /dev/null
+++ b/core/src/main/scala/chisel3/experimental/dataview/DataView.scala
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: Apache-2.0
+
+package chisel3.experimental.dataview
+
+import chisel3._
+import chisel3.internal.sourceinfo.SourceInfo
+import scala.reflect.runtime.universe.WeakTypeTag
+
+import annotation.implicitNotFound
+
+
+/** Mapping between a target type `T` and a view type `V`
+ *
+ * Enables calling `.viewAs[T]` on instances of the target type.
+ *
+ * ==Detailed documentation==
+ * - [[https://www.chisel-lang.org/chisel3/docs/explanations/dataview Explanation]]
+ * - [[https://www.chisel-lang.org/chisel3/docs/cookbooks/dataview Cookbook]]
+ *
+ * @example {{{
+ * class Foo(val w: Int) extends Bundle {
+ * val a = UInt(w.W)
+ * }
+ * class Bar(val w: Int) extends Bundle {
+ * val b = UInt(w.W)
+ * }
+ * // DataViews are created using factory methods in the companion object
+ * implicit val view = DataView[Foo, Bar](
+ * // The first argument is a function constructing a Foo from a Bar
+ * foo => new Bar(foo.w)
+ * // The remaining arguments are a variable number of field pairings
+ * _.a -> _.b
+ * )
+ * }}}
+ *
+ * @tparam T Target type (must have an implementation of [[DataProduct]])
+ * @tparam V View type
+ * @see [[DataView$ object DataView]] for factory methods
+ * @see [[PartialDataView object PartialDataView]] for defining non-total `DataViews`
+ */
+@implicitNotFound("Could not find implicit value for DataView[${T}, ${V}].\n" +
+ "Please see https://www.chisel-lang.org/chisel3/docs/explanations/dataview")
+sealed class DataView[T : DataProduct, V <: Data] private[chisel3] (
+ /** Function constructing an object of the View type from an object of the Target type */
+ private[chisel3] val mkView: T => V,
+ /** Function that returns corresponding fields of the target and view */
+ private[chisel3] val mapping: (T, V) => Iterable[(Data, Data)],
+ // Aliasing this with a def below to make the ScalaDoc show up for the field
+ _total: Boolean
+)(
+ implicit private[chisel3] val sourceInfo: SourceInfo
+) {
+ /** Indicates if the mapping contains every field of the target */
+ def total: Boolean = _total
+
+ override def toString: String = {
+ val base = sourceInfo.makeMessage(x => x)
+ val loc = if (base.nonEmpty) base else "@unknown"
+ val name = if (total) "DataView" else "PartialDataView"
+ s"$name(defined $loc)"
+ }
+
+ /** Compose two `DataViews` together to construct a view from the target of this `DataView` to the
+ * view type of the second `DataView`
+ *
+ * @param g a DataView from `V` to new view-type `V2`
+ * @tparam V2 View type of `DataView` `g`
+ * @return a new `DataView` from the original `T` to new view-type `V2`
+ */
+ def andThen[V2 <: Data](g: DataView[V, V2])(implicit sourceInfo: SourceInfo): DataView[T, V2] = {
+ val self = this
+ // We have to pass the DataProducts and DataViews manually to .viewAs below
+ val tdp = implicitly[DataProduct[T]]
+ val vdp = implicitly[DataProduct[V]]
+ new DataView[T, V2](
+ t => g.mkView(mkView(t)),
+ { case (t, v2) => List(t.viewAs[V](tdp, self).viewAs[V2](vdp, g) -> v2) },
+ this.total && g.total
+ ) {
+ override def toString: String = s"$self andThen $g"
+ }
+ }
+}
+
+/** Factory methods for constructing [[DataView]]s, see class for example use */
+object DataView {
+
+ /** Default factory method, alias for [[pairs]] */
+ def apply[T : DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] =
+ DataView.pairs(mkView, pairs: _*)
+
+ /** Construct [[DataView]]s with pairs of functions from the target and view to corresponding fields */
+ def pairs[T : DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] =
+ mapping(mkView: T => V, swizzle(pairs))
+
+ /** More general factory method for complex mappings */
+ def mapping[T : DataProduct, V <: Data](mkView: T => V, mapping: (T, V) => Iterable[(Data, Data)])(implicit sourceInfo: SourceInfo): DataView[T, V] =
+ new DataView[T, V](mkView, mapping, _total = true)
+
+ /** Provides `invert` for invertible [[DataView]]s
+ *
+ * This must be done as an extension method because it applies an addition constraint on the `Target`
+ * type parameter, namely that it must be a subtype of [[Data]].
+ *
+ * @note [[PartialDataView]]s are **not** invertible and will result in an elaboration time exception
+ */
+ implicit class InvertibleDataView[T <: Data : WeakTypeTag, V <: Data : WeakTypeTag](view: DataView[T, V]) {
+ def invert(mkView: V => T): DataView[V, T] = {
+ // It would've been nice to make this a compiler error, but it's unclear how to make that work.
+ // We tried having separate TotalDataView and PartialDataView and only defining inversion for
+ // TotalDataView. For some reason, implicit resolution wouldn't invert TotalDataViews. This is
+ // probably because it was looking for the super-type DataView and since invertDataView was
+ // only defined on TotalDataView, it wasn't included in implicit resolution. Thus we end up
+ // with a runtime check.
+ if (!view.total) {
+ val tt = implicitly[WeakTypeTag[T]].tpe
+ val vv = implicitly[WeakTypeTag[V]].tpe
+ val msg = s"Cannot invert '$view' as it is non-total.\n Try providing a DataView[$vv, $tt]." +
+ s"\n Please see https://www.chisel-lang.org/chisel3/docs/explanations/dataview."
+ throw InvalidViewException(msg)
+ }
+ implicit val sourceInfo = view.sourceInfo
+ new DataView[V, T](mkView, swapArgs(view.mapping), view.total)
+ }
+ }
+
+ private[dataview] def swizzle[A, B, C, D](fs: Iterable[(A, B) => (C, D)]): (A, B) => Iterable[(C, D)] = {
+ case (a, b) => fs.map(f => f(a, b))
+ }
+
+ private def swapArgs[A, B, C, D](f: (A, B) => Iterable[(C, D)]): (B, A) => Iterable[(D, C)] = {
+ case (b, a) => f(a, b).map(_.swap)
+ }
+
+ /** All Chisel Data are viewable as their own type */
+ implicit def identityView[A <: Data](implicit sourceInfo: SourceInfo): DataView[A, A] =
+ DataView[A, A](chiselTypeOf.apply, { case (x, y) => (x, y) })
+}
+
+/** Factory methods for constructing non-total [[DataView]]s */
+object PartialDataView {
+
+ /** Default factory method, alias for [[pairs]] */
+ def apply[T: DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] =
+ PartialDataView.pairs(mkView, pairs: _*)
+
+ /** Construct [[DataView]]s with pairs of functions from the target and view to corresponding fields */
+ def pairs[T: DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] =
+ mapping(mkView, DataView.swizzle(pairs))
+
+ /** More general factory method for complex mappings */
+ def mapping[T: DataProduct, V <: Data](mkView: T => V, mapping: (T, V) => Iterable[(Data, Data)])(implicit sourceInfo: SourceInfo): DataView[T, V] =
+ new DataView[T, V](mkView, mapping, _total = false)
+}
diff --git a/core/src/main/scala/chisel3/experimental/dataview/package.scala b/core/src/main/scala/chisel3/experimental/dataview/package.scala
new file mode 100644
index 00000000..1acf43e1
--- /dev/null
+++ b/core/src/main/scala/chisel3/experimental/dataview/package.scala
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: Apache-2.0
+
+package chisel3.experimental
+
+import chisel3._
+import chisel3.internal._
+import chisel3.internal.sourceinfo.SourceInfo
+
+import scala.annotation.{implicitNotFound, tailrec}
+import scala.collection.mutable
+import scala.collection.immutable.LazyList // Needed for 2.12 alias
+
+package object dataview {
+ case class InvalidViewException(message: String) extends ChiselException(message)
+
+ /** Provides `viewAs` for types that have an implementation of [[DataProduct]]
+ *
+ * Calling `viewAs` also requires an implementation of [[DataView]] for the target type
+ */
+ implicit class DataViewable[T](target: T) {
+ def viewAs[V <: Data](implicit dataproduct: DataProduct[T], dataView: DataView[T, V]): V = {
+ // TODO put a try catch here for ExpectedHardwareException and perhaps others
+ // It's likely users will accidentally use chiselTypeOf or something that may error,
+ // The right thing to use is DataMirror...chiselTypeClone because of composition with DataView.andThen
+ // Another option is that .andThen could give a fake binding making chiselTypeOfs in the user code safe
+ val result: V = dataView.mkView(target)
+ requireIsChiselType(result, "viewAs")
+
+ doBind(target, result, dataView)
+
+ // Setting the parent marks these Data as Views
+ result.setAllParents(Some(ViewParent))
+ // The names of views do not matter except for when a view is annotated. For Views that correspond
+ // To a single Data, we just forward the name of the Target. For Views that correspond to more
+ // than one Data, we return this assigned name but rename it in the Convert stage
+ result.forceName(None, "view", Builder.viewNamespace)
+ result
+ }
+ }
+
+ // This private type alias lets us provide a custom error message for misuing the .viewAs for upcasting Bundles
+ @implicitNotFound("${A} is not a subtype of ${B}! Did you mean .viewAs[${B}]? " +
+ "Please see https://www.chisel-lang.org/chisel3/docs/cookbooks/dataview")
+ private type SubTypeOf[A, B] = A <:< B
+
+ /** Provides `viewAsSupertype` for subclasses of [[Bundle]] */
+ implicit class BundleUpcastable[T <: Bundle](target: T) {
+ /** View a [[Bundle]] or [[Record]] as a parent type (upcast) */
+ def viewAsSupertype[V <: Bundle](proto: V)(implicit ev: SubTypeOf[T, V], sourceInfo: SourceInfo): V = {
+ implicit val dataView = PartialDataView.mapping[T, V](_ => proto, {
+ case (a, b) =>
+ val aElts = a.elements
+ val bElts = b.elements
+ val bKeys = bElts.keySet
+ val keys = aElts.keysIterator.filter(bKeys.contains)
+ keys.map(k => aElts(k) -> bElts(k)).toSeq
+ })
+ target.viewAs[V]
+ }
+ }
+
+ private def nonTotalViewException(dataView: DataView[_, _], target: Any, view: Data, targetFields: Seq[String], viewFields: Seq[String]) = {
+ def missingMsg(name: String, fields: Seq[String]): Option[String] = {
+ val str = fields.mkString(", ")
+ fields.size match {
+ case 0 => None
+ case 1 => Some(s"$name field '$str' is missing")
+ case _ => Some(s"$name fields '$str' are missing")
+ }
+ }
+ val vs = missingMsg("view", viewFields)
+ val ts = missingMsg("target", targetFields)
+ val reasons = (ts ++ vs).mkString(" and ").capitalize
+ val suggestion = if (ts.nonEmpty) "\n If the view *should* be non-total, try a 'PartialDataView'." else ""
+ val msg = s"Viewing $target as $view is non-Total!\n $reasons.\n DataView used is $dataView.$suggestion"
+ throw InvalidViewException(msg)
+ }
+
+ // TODO should this be moved to class Aggregate / can it be unified with Aggregate.bind?
+ private def doBind[T : DataProduct, V <: Data](target: T, view: V, dataView: DataView[T, V]): Unit = {
+ val mapping = dataView.mapping(target, view)
+ val total = dataView.total
+ // Lookups to check the mapping results
+ val viewFieldLookup: Map[Data, String] = getRecursiveFields(view, "_").toMap
+ val targetContains: Data => Boolean = implicitly[DataProduct[T]].dataSet(target)
+
+ // Resulting bindings for each Element of the View
+ val childBindings =
+ new mutable.HashMap[Data, mutable.ListBuffer[Element]] ++
+ viewFieldLookup.view
+ .collect { case (elt: Element, _) => elt }
+ .map(_ -> new mutable.ListBuffer[Element])
+
+ def viewFieldName(d: Data): String =
+ viewFieldLookup.get(d).map(_ + " ").getOrElse("") + d.toString
+
+ // Helper for recording the binding of each
+ def onElt(te: Element, ve: Element): Unit = {
+ // TODO can/should we aggregate these errors?
+ def err(name: String, arg: Data) =
+ throw InvalidViewException(s"View mapping must only contain Elements within the $name, got $arg")
+
+ // The elements may themselves be views, look through the potential chain of views for the Elements
+ // that are actually members of the target or view
+ val tex = unfoldView(te).find(targetContains).getOrElse(err("Target", te))
+ val vex = unfoldView(ve).find(viewFieldLookup.contains).getOrElse(err("View", ve))
+
+ if (tex.getClass != vex.getClass) {
+ val fieldName = viewFieldName(vex)
+ throw InvalidViewException(s"Field $fieldName specified as view of non-type-equivalent value $tex")
+ }
+ // View width must be unknown or match target width
+ if (vex.widthKnown && vex.width != tex.width) {
+ def widthAsString(x: Element) = x.widthOption.map("<" + _ + ">").getOrElse("<unknown>")
+ val fieldName = viewFieldName(vex)
+ val vwidth = widthAsString(vex)
+ val twidth = widthAsString(tex)
+ throw InvalidViewException(s"View field $fieldName has width ${vwidth} that is incompatible with target value $tex's width ${twidth}")
+ }
+ childBindings(vex) += tex
+ }
+
+ mapping.foreach {
+ // Special cased because getMatchedFields checks typeEquivalence on Elements (and is used in Aggregate path)
+ // Also saves object allocations on common case of Elements
+ case (ae: Element, be: Element) => onElt(ae, be)
+
+ case (aa: Aggregate, ba: Aggregate) =>
+ if (!ba.typeEquivalent(aa)) {
+ val fieldName = viewFieldLookup(ba)
+ throw InvalidViewException(s"field $fieldName specified as view of non-type-equivalent value $aa")
+ }
+ getMatchedFields(aa, ba).foreach {
+ case (aelt: Element, belt: Element) => onElt(aelt, belt)
+ case _ => // Ignore matching of Aggregates
+ }
+ }
+
+ // Errors in totality of the View, use var List to keep fast path cheap (no allocation)
+ var viewNonTotalErrors: List[Data] = Nil
+ var targetNonTotalErrors: List[String] = Nil
+
+ val targetSeen: Option[mutable.Set[Data]] = if (total) Some(mutable.Set.empty[Data]) else None
+
+ val resultBindings = childBindings.map { case (data, targets) =>
+ val targetsx = targets match {
+ case collection.Seq(target: Element) => target
+ case collection.Seq() =>
+ viewNonTotalErrors = data :: viewNonTotalErrors
+ data.asInstanceOf[Element] // Return the Data itself, will error after this map, cast is safe
+ case x =>
+ throw InvalidViewException(s"Got $x, expected Seq(_: Direct)")
+ }
+ // TODO record and report aliasing errors
+ targetSeen.foreach(_ += targetsx)
+ data -> targetsx
+ }.toMap
+
+ // Check for totality of Target
+ targetSeen.foreach { seen =>
+ val lookup = implicitly[DataProduct[T]].dataIterator(target, "_")
+ for (missed <- lookup.collect { case (d: Element, name) if !seen(d) => name }) {
+ targetNonTotalErrors = missed :: targetNonTotalErrors
+ }
+ }
+ if (viewNonTotalErrors != Nil || targetNonTotalErrors != Nil) {
+ val viewErrors = viewNonTotalErrors.map(f => viewFieldLookup.getOrElse(f, f.toString))
+ nonTotalViewException(dataView, target, view, targetNonTotalErrors, viewErrors)
+ }
+
+ view match {
+ case elt: Element => view.bind(ViewBinding(resultBindings(elt)))
+ case agg: Aggregate =>
+ // We record total Data mappings to provide a better .toTarget
+ val topt = target match {
+ case d: Data if total => Some(d)
+ case _ =>
+ // Record views that don't have the simpler .toTarget for later renaming
+ Builder.unnamedViews += view
+ None
+ }
+ // TODO We must also record children as unnamed, some could be namable but this requires changes to the Binding
+ getRecursiveFields.lazily(view, "_").foreach {
+ case (agg: Aggregate, _) if agg != view =>
+ Builder.unnamedViews += agg
+ case _ => // Do nothing
+ }
+ agg.bind(AggregateViewBinding(resultBindings, topt))
+ }
+ }
+
+ // Traces an Element that may (or may not) be a view until it no longer maps
+ // Inclusive of the argument
+ private def unfoldView(elt: Element): LazyList[Element] = {
+ def rec(e: Element): LazyList[Element] = e.topBindingOpt match {
+ case Some(ViewBinding(target)) => target #:: rec(target)
+ case Some(AggregateViewBinding(mapping, _)) =>
+ val target = mapping(e)
+ target #:: rec(target)
+ case Some(_) | None => LazyList.empty
+ }
+ elt #:: rec(elt)
+ }
+
+ // Safe for all Data
+ private[chisel3] def isView(d: Data): Boolean = d._parent.contains(ViewParent)
+
+ /** Turn any [[Element]] that could be a View into a concrete Element
+ *
+ * This is the fundamental "unwrapping" or "tracing" primitive operation for handling Views within
+ * Chisel.
+ */
+ private[chisel3] def reify(elt: Element): Element =
+ reify(elt, elt.topBinding)
+
+ /** Turn any [[Element]] that could be a View into a concrete Element
+ *
+ * This is the fundamental "unwrapping" or "tracing" primitive operation for handling Views within
+ * Chisel.
+ */
+ @tailrec private[chisel3] def reify(elt: Element, topBinding: TopBinding): Element =
+ topBinding match {
+ case ViewBinding(target) => reify(target, elt.topBinding)
+ case _ => elt
+ }
+
+ /** Determine the target of a View if it is a single Target
+ *
+ * @note An Aggregate may be a view of unrelated [[Data]] (eg. like a Seq or tuple) and thus this
+ * there is no single Data representing the Target and this function will return None
+ * @return The single Data target of this view or None if a single Data doesn't exist
+ */
+ private[chisel3] def reifySingleData(data: Data): Option[Data] = {
+ val candidate: Option[Data] =
+ data.binding.collect { // First check if this is a total mapping of an Aggregate
+ case AggregateViewBinding(_, Some(t)) => t
+ }.orElse { // Otherwise look via top binding
+ data.topBindingOpt match {
+ case None => None
+ case Some(ViewBinding(target)) => Some(target)
+ case Some(AggregateViewBinding(lookup, _)) => lookup.get(data)
+ case Some(_) => None
+ }
+ }
+ candidate.flatMap { d =>
+ // Candidate may itself be a view, keep tracing in those cases
+ if (isView(d)) reifySingleData(d) else Some(d)
+ }
+ }
+
+}
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..0cc3d131
--- /dev/null
+++ b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala
@@ -0,0 +1,100 @@
+// 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
+import firrtl.annotations.{IsModule, ModuleTarget}
+
+/** 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: ModuleTarget = d.proto.toTarget
+
+ /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance
+ * @return absoluteTarget of this instance
+ */
+ def toAbsoluteTarget: IsModule = 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..9b17bfce
--- /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.{InstantiableClone, IsClone, ModuleClone}
+import chisel3.internal.sourceinfo.{InstanceTransform, SourceInfo}
+import chisel3.experimental.BaseModule
+import firrtl.annotations.IsModule
+
+/** 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: IsModule = i.cloned match {
+ case Left(x: BaseModule) => x.getTarget
+ case Right(x: IsClone[_] with BaseModule) => x.getTarget
+ }
+
+ /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance
+ * @return absoluteTarget of this instance
+ */
+ def toAbsoluteTarget: IsModule = 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/experimental/package.scala b/core/src/main/scala/chisel3/experimental/package.scala
index ec3f2a79..8018159f 100644
--- a/core/src/main/scala/chisel3/experimental/package.scala
+++ b/core/src/main/scala/chisel3/experimental/package.scala
@@ -2,6 +2,9 @@
package chisel3
+import chisel3.internal.NamedComponent
+import chisel3.internal.sourceinfo.SourceInfo
+
/** Package for experimental features, which may have their API changed, be removed, etc.
*
* Because its contents won't necessarily have the same level of stability and support as
@@ -20,13 +23,6 @@ package object experimental {
type ChiselEnum = EnumFactory
- @deprecated("Use the version in chisel3._", "3.2")
- val withClockAndReset = chisel3.withClockAndReset
- @deprecated("Use the version in chisel3._", "3.2")
- val withClock = chisel3.withClock
- @deprecated("Use the version in chisel3._", "3.2")
- val withReset = chisel3.withReset
-
// Rocket Chip-style clonemodule
/** A record containing the results of CloneModuleAsRecord
@@ -131,14 +127,48 @@ package object experimental {
object BundleLiterals {
implicit class AddBundleLiteralConstructor[T <: Record](x: T) {
- def Lit(elems: (T => (Data, Data))*): T = {
+ def Lit(elems: (T => (Data, Data))*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = {
x._makeLit(elems: _*)
}
}
}
+ /** This class provides the `Lit` method needed to define a `Vec` literal
+ */
+ object VecLiterals {
+ implicit class AddVecLiteralConstructor[T <: Data](x: Vec[T]) {
+ /** Given a generator of a list tuples of the form [Int, Data]
+ * constructs a Vec literal, parallel concept to `BundleLiteral`
+ *
+ * @param elems tuples of an index and a literal value
+ * @return
+ */
+ def Lit(elems: (Int, T)*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = {
+ x._makeLit(elems: _*)
+ }
+ }
+
+ implicit class AddObjectLiteralConstructor(x: Vec.type) {
+ /** This provides an literal construction method for cases using
+ * object `Vec` as in `Vec.Lit(1.U, 2.U)`
+ */
+ def Lit[T <: Data](elems: T*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = {
+ require(elems.nonEmpty, s"Lit.Vec(...) must have at least one element")
+ val indexElements = elems.zipWithIndex.map { case (element, index) => (index, element)}
+ val widestElement = elems.maxBy(_.getWidth)
+ val vec: Vec[T] = Vec.apply(indexElements.length, chiselTypeOf(widestElement))
+ vec.Lit(indexElements:_*)
+ }
+ }
+ }
+
// Use to add a prefix to any component generated in input scope
val prefix = chisel3.internal.prefix
// Use to remove prefixes not in provided scope
val noPrefix = chisel3.internal.noPrefix
+
+ /** Base simulation-only component. */
+ abstract class BaseSim extends NamedComponent {
+ _parent.foreach(_.addId(this))
+ }
}
diff --git a/core/src/main/scala/chisel3/experimental/verification/package.scala b/core/src/main/scala/chisel3/experimental/verification/package.scala
index 5c71bd5f..190083fd 100644
--- a/core/src/main/scala/chisel3/experimental/verification/package.scala
+++ b/core/src/main/scala/chisel3/experimental/verification/package.scala
@@ -2,40 +2,59 @@
package chisel3.experimental
-import chisel3.{Bool, CompileOptions}
+import chisel3._
import chisel3.internal.Builder
-import chisel3.internal.Builder.pushCommand
import chisel3.internal.firrtl.{Formal, Verification}
import chisel3.internal.sourceinfo.SourceInfo
package object verification {
+
object assert {
+ /** Named class for assertions. */
+ final class Assert(private[chisel3] val predicate: Bool) extends BaseSim
+
+
def apply(predicate: Bool, msg: String = "")(
implicit sourceInfo: SourceInfo,
- compileOptions: CompileOptions): Unit = {
- val clock = Builder.forcedClock
- pushCommand(Verification(Formal.Assert, sourceInfo, clock.ref,
- predicate.ref, msg))
+ compileOptions: CompileOptions): Assert = {
+ val a = new Assert(predicate)
+ when (!Module.reset.asBool) {
+ val clock = Module.clock
+ Builder.pushCommand(Verification(a, Formal.Assert, sourceInfo, clock.ref, predicate.ref, msg))
+ }
+ a
}
}
object assume {
+ /** Named class for assumes. */
+ final class Assume(private[chisel3] val predicate: Bool) extends BaseSim
+
def apply(predicate: Bool, msg: String = "")(
implicit sourceInfo: SourceInfo,
- compileOptions: CompileOptions): Unit = {
- val clock = Builder.forcedClock
- pushCommand(Verification(Formal.Assume, sourceInfo, clock.ref,
- predicate.ref, msg))
+ compileOptions: CompileOptions): Assume = {
+ val a = new Assume(predicate)
+ when (!Module.reset.asBool) {
+ val clock = Module.clock
+ Builder.pushCommand(Verification(a, Formal.Assume, sourceInfo, clock.ref, predicate.ref, msg))
+ }
+ a
}
}
object cover {
+ /** Named class for covers. */
+ final class Cover(private[chisel3] val predicate: Bool) extends BaseSim
+
def apply(predicate: Bool, msg: String = "")(
implicit sourceInfo: SourceInfo,
- compileOptions: CompileOptions): Unit = {
- val clock = Builder.forcedClock
- pushCommand(Verification(Formal.Cover, sourceInfo, clock.ref,
- predicate.ref, msg))
+ compileOptions: CompileOptions): Cover = {
+ val clock = Module.clock
+ val c = new Cover(predicate)
+ when (!Module.reset.asBool) {
+ Builder.pushCommand(Verification(c, Formal.Cover, sourceInfo, clock.ref, predicate.ref, msg))
+ }
+ c
}
}
}