summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJack Koenig2021-08-12 17:04:11 -0700
committerGitHub2021-08-12 17:04:11 -0700
commit1ceb974c55c6785c21ab3934fa750ade0702e276 (patch)
treebc8559e8ef558e3216ecc5612593f5904f9a6b60
parent713de6b823d8707246935b9e31ed2fbafeaeca32 (diff)
Add DataView (#1955)
DataView is a mechanism for "viewing" Scala objects as a subtype of `Data`. Often, this is useful for viewing one subtype of `Data`, as another. One can think about a DataView as a cross between a customizable cast and an untagged union. A DataView has a Target type `T`, and a View type `V`. DataView requires that an implementation of `DataProduct` is available for Target types. DataProduct is a type class that provides a way to iterate on `Data` children of objects of implementing types. If a DataView is provided for a type T to a type V, then the function .viewAs[V] (of type T => V) is available. The object (of type T) returned by .viewAs is called a "View" and can be used as both an rvalue and an lvalue. Unlike when using an .asTypeOf cast, connecting to a "View" will connect to the associated field or fields of the underlying Target. DataView also enables .viewAsSupertype which is available for viewing Bundles as a parent Bundle type. It is similar to .viewAs but requires a prototype object of the Target type which will be cloned in order to create the returned View. .viewAsSupertype maps between the corresponding fields of the parent and child Bundle types.
-rw-r--r--build.sbt3
-rw-r--r--core/src/main/scala-2.12/scala/collection/immutable/package.scala3
-rw-r--r--core/src/main/scala/chisel3/Aggregate.scala42
-rw-r--r--core/src/main/scala/chisel3/Data.scala83
-rw-r--r--core/src/main/scala/chisel3/Element.scala4
-rw-r--r--core/src/main/scala/chisel3/Module.scala42
-rw-r--r--core/src/main/scala/chisel3/ModuleAspect.scala4
-rw-r--r--core/src/main/scala/chisel3/RawModule.scala31
-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/internal/BiConnect.scala6
-rw-r--r--core/src/main/scala/chisel3/internal/Binding.scala9
-rw-r--r--core/src/main/scala/chisel3/internal/Builder.scala66
-rw-r--r--core/src/main/scala/chisel3/internal/MonoConnect.scala6
-rw-r--r--core/src/main/scala/chisel3/internal/firrtl/IR.scala7
-rw-r--r--docs/src/cookbooks/cookbook.md9
-rw-r--r--docs/src/cookbooks/dataview.md179
-rw-r--r--docs/src/explanations/dataview.md520
-rw-r--r--src/main/scala/chisel3/stage/phases/Convert.scala3
-rw-r--r--src/test/scala/chiselTests/ChiselSpec.scala29
-rw-r--r--src/test/scala/chiselTests/experimental/DataView.scala542
-rw-r--r--src/test/scala/chiselTests/experimental/DataViewIntegrationSpec.scala57
-rw-r--r--src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala169
-rw-r--r--src/test/scala/chiselTests/experimental/ModuleDataProductSpec.scala91
-rw-r--r--src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala3
26 files changed, 2307 insertions, 78 deletions
diff --git a/build.sbt b/build.sbt
index 1a47d7c1..5d7fad2d 100644
--- a/build.sbt
+++ b/build.sbt
@@ -235,7 +235,8 @@ lazy val docs = project // new documentation project
scalacOptions += "-language:reflectiveCalls",
mdocIn := file("docs/src"),
mdocOut := file("docs/generated"),
- mdocExtraArguments := Seq("--cwd", "docs"),
+ // None of our links are hygienic because they're primarily used on the website with .html
+ mdocExtraArguments := Seq("--cwd", "docs", "--no-link-hygiene"),
mdocVariables := Map(
"BUILD_DIR" -> "docs-target" // build dir for mdoc programs to dump temp files
)
diff --git a/core/src/main/scala-2.12/scala/collection/immutable/package.scala b/core/src/main/scala-2.12/scala/collection/immutable/package.scala
index 7ae87d9b..c06935d0 100644
--- a/core/src/main/scala-2.12/scala/collection/immutable/package.scala
+++ b/core/src/main/scala-2.12/scala/collection/immutable/package.scala
@@ -10,4 +10,7 @@ package object immutable {
val VectorMap = ListMap
type VectorMap[K, +V] = ListMap[K, V]
+
+ val LazyList = Stream
+ type LazyList[+A] = Stream[A]
}
diff --git a/core/src/main/scala/chisel3/Aggregate.scala b/core/src/main/scala/chisel3/Aggregate.scala
index 3a766628..58bc5ccb 100644
--- a/core/src/main/scala/chisel3/Aggregate.scala
+++ b/core/src/main/scala/chisel3/Aggregate.scala
@@ -3,6 +3,7 @@
package chisel3
import chisel3.experimental.VecLiterals.AddVecLiteralConstructor
+import chisel3.experimental.dataview.{InvalidViewException, isView}
import scala.collection.immutable.{SeqMap, VectorMap}
import scala.collection.mutable.{HashSet, LinkedHashMap}
@@ -88,44 +89,6 @@ sealed abstract class Aggregate extends Data {
}
}
- // Returns pairs of all fields, element-level and containers, in a Record and their path names
- private[chisel3] def getRecursiveFields(data: Data, path: String): Seq[(Data, String)] = data match {
- case data: Record =>
- data.elements.map { case (fieldName, fieldData) =>
- getRecursiveFields(fieldData, s"$path.$fieldName")
- }.fold(Seq(data -> path)) {
- _ ++ _
- }
- case data: Vec[_] =>
- data.getElements.zipWithIndex.map { case (fieldData, fieldIndex) =>
- getRecursiveFields(fieldData, path = s"$path($fieldIndex)")
- }.fold(Seq(data -> path)) {
- _ ++ _
- }
- case data => Seq(data -> path) // we don't support or recurse into other Aggregate types here
- }
-
-
- // Returns pairs of corresponding fields between two Records of the same type
- private[chisel3] def getMatchedFields(x: Data, y: Data): Seq[(Data, Data)] = (x, y) match {
- case (x: Element, y: Element) =>
- require(x typeEquivalent y)
- Seq(x -> y)
- case (x: Record, y: Record) =>
- (x.elements zip y.elements).map { case ((xName, xElt), (yName, yElt)) =>
- require(xName == yName) // assume fields returned in same, deterministic order
- getMatchedFields(xElt, yElt)
- }.fold(Seq(x -> y)) {
- _ ++ _
- }
- case (x: Vec[_], y: Vec[_]) =>
- (x.getElements zip y.getElements).map { case (xElt, yElt) =>
- getMatchedFields(xElt, yElt)
- }.fold(Seq(x -> y)) {
- _ ++ _
- }
- }
-
override def do_asUInt(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): UInt = {
SeqUtils.do_asUInt(flatten.map(_.asUInt()))
}
@@ -297,6 +260,9 @@ sealed class Vec[T <: Data] private[chisel3] (gen: => T, val length: Int)
def do_apply(p: UInt)(implicit compileOptions: CompileOptions): T = {
requireIsHardware(this, "vec")
requireIsHardware(p, "vec index")
+ if (isView(this)) {
+ throw InvalidViewException("Dynamic indexing of Views is not yet supported")
+ }
val port = gen
// Reconstruct the resolvedDirection (in Aggregate.bind), since it's not stored.
diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala
index 5513035b..32d83008 100644
--- a/core/src/main/scala/chisel3/Data.scala
+++ b/core/src/main/scala/chisel3/Data.scala
@@ -2,13 +2,16 @@
package chisel3
+import chisel3.experimental.dataview.reify
+
import scala.language.experimental.macros
-import chisel3.experimental.{Analog, DataMirror, FixedPoint, Interval}
+import chisel3.experimental.{Analog, BaseModule, DataMirror, FixedPoint, Interval}
import chisel3.internal.Builder.pushCommand
import chisel3.internal._
import chisel3.internal.firrtl._
import chisel3.internal.sourceinfo.{DeprecatedSourceInfo, SourceInfo, SourceInfoTransform, UnlocatableSourceInfo}
+import scala.collection.immutable.LazyList // Needed for 2.12 alias
import scala.util.Try
/** User-specified directions.
@@ -236,6 +239,62 @@ private[chisel3] object cloneSupertype {
}
}
+// Returns pairs of all fields, element-level and containers, in a Record and their path names
+private[chisel3] object getRecursiveFields {
+ def apply(data: Data, path: String): Seq[(Data, String)] = data match {
+ case data: Record =>
+ data.elements.map { case (fieldName, fieldData) =>
+ getRecursiveFields(fieldData, s"$path.$fieldName")
+ }.fold(Seq(data -> path)) {
+ _ ++ _
+ }
+ case data: Vec[_] =>
+ data.getElements.zipWithIndex.map { case (fieldData, fieldIndex) =>
+ getRecursiveFields(fieldData, path = s"$path($fieldIndex)")
+ }.fold(Seq(data -> path)) {
+ _ ++ _
+ }
+ case data: Element => Seq(data -> path)
+ }
+
+ def lazily(data: Data, path: String): Seq[(Data, String)] = data match {
+ case data: Record =>
+ LazyList(data -> path) ++
+ data.elements.view.flatMap { case (fieldName, fieldData) =>
+ getRecursiveFields(fieldData, s"$path.$fieldName")
+ }
+ case data: Vec[_] =>
+ LazyList(data -> path) ++
+ data.getElements.view.zipWithIndex.flatMap { case (fieldData, fieldIndex) =>
+ getRecursiveFields(fieldData, path = s"$path($fieldIndex)")
+ }
+ case data: Element => LazyList(data -> path)
+ }
+}
+
+// Returns pairs of corresponding fields between two Records of the same type
+// TODO it seems wrong that Elements are checked for typeEquivalence in Bundle and Vec lit creation
+private[chisel3] object getMatchedFields {
+ def apply(x: Data, y: Data): Seq[(Data, Data)] = (x, y) match {
+ case (x: Element, y: Element) =>
+ require(x typeEquivalent y)
+ Seq(x -> y)
+ case (x: Record, y: Record) =>
+ (x.elements zip y.elements).map { case ((xName, xElt), (yName, yElt)) =>
+ require(xName == yName) // assume fields returned in same, deterministic order
+ getMatchedFields(xElt, yElt)
+ }.fold(Seq(x -> y)) {
+ _ ++ _
+ }
+ case (x: Vec[_], y: Vec[_]) =>
+ (x.getElements zip y.getElements).map { case (xElt, yElt) =>
+ getMatchedFields(xElt, yElt)
+ }.fold(Seq(x -> y)) {
+ _ ++ _
+ }
+ }
+}
+
/** Returns the chisel type of a hardware object, allowing other hardware to be constructed from it.
*/
object chiselTypeOf {
@@ -488,6 +547,14 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc {
}
requireIsHardware(this)
topBindingOpt match {
+ // DataView
+ case Some(ViewBinding(target)) => reify(target).ref
+ case Some(AggregateViewBinding(viewMap, _)) =>
+ viewMap.get(this) match {
+ case None => materializeWire() // FIXME FIRRTL doesn't have Aggregate Init expressions
+ // This should not be possible because Element does the lookup in .topBindingOpt
+ case x: Some[_] => throwException(s"Internal Error: In .ref for $this got '$topBindingOpt' and '$x'")
+ }
// Literals
case Some(ElementLitBinding(litArg)) => litArg
case Some(BundleLitBinding(litMap)) =>
@@ -514,6 +581,20 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc {
}
}
+
+ // Recursively set the parent of the start Data and any children (eg. in an Aggregate)
+ private[chisel3] def setAllParents(parent: Option[BaseModule]): Unit = {
+ def rec(data: Data): Unit = {
+ data._parent = parent
+ data match {
+ case _: Element =>
+ case agg: Aggregate =>
+ agg.getElements.foreach(rec)
+ }
+ }
+ rec(this)
+ }
+
private[chisel3] def width: Width
private[chisel3] def legacyConnect(that: Data)(implicit sourceInfo: SourceInfo): Unit
diff --git a/core/src/main/scala/chisel3/Element.scala b/core/src/main/scala/chisel3/Element.scala
index 40291b12..bc006922 100644
--- a/core/src/main/scala/chisel3/Element.scala
+++ b/core/src/main/scala/chisel3/Element.scala
@@ -34,6 +34,10 @@ abstract class Element extends Data {
case Some(litArg) => Some(ElementLitBinding(litArg))
case _ => Some(DontCareBinding())
}
+ case Some(b @ AggregateViewBinding(viewMap, _)) => viewMap.get(this) match {
+ case Some(elt) => Some(ViewBinding(elt))
+ case _ => throwException(s"Internal Error! $this missing from topBinding $b")
+ }
case topBindingOpt => topBindingOpt
}
diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala
index 41fe4554..64065ec9 100644
--- a/core/src/main/scala/chisel3/Module.scala
+++ b/core/src/main/scala/chisel3/Module.scala
@@ -184,7 +184,7 @@ package internal {
object BaseModule {
// Private internal class to serve as a _parent for Data in cloned ports
- private[chisel3] class ModuleClone(_proto: BaseModule) extends BaseModule {
+ private[chisel3] class ModuleClone(_proto: BaseModule) extends PseudoModule {
// 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 = _
@@ -195,7 +195,7 @@ package internal {
_component = _proto._component
None
}
- // This module doesn't acutally exist in the FIRRTL so no initialization to do
+ // This module doesn't actually exist in the FIRRTL so no initialization to do
private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = ()
override def desiredName: String = _proto.name
@@ -226,19 +226,6 @@ package internal {
override def cloneType = (new ClonePorts(elts: _*)).asInstanceOf[this.type]
}
- // Recursively set the parent of the start Data and any children (eg. in an Aggregate)
- private def setAllParents(start: Data, parent: Option[BaseModule]): Unit = {
- def rec(data: Data): Unit = {
- data._parent = parent
- data match {
- case _: Element =>
- case agg: Aggregate =>
- agg.getElements.foreach(rec)
- }
- }
- rec(start)
- }
-
private[chisel3] def cloneIORecord(proto: BaseModule)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): ClonePorts = {
require(proto.isClosed, "Can't clone a module before module close")
// Fake Module to serve as the _parent of the cloned ports
@@ -249,7 +236,7 @@ package internal {
// currentModule (and not clonePorts)
val clonePorts = new ClonePorts(proto.getModulePorts: _*)
clonePorts.bind(PortBinding(cloneParent))
- setAllParents(clonePorts, Some(cloneParent))
+ clonePorts.setAllParents(Some(cloneParent))
cloneParent._portsRecord = clonePorts
// Normally handled during Module construction but ClonePorts really lives in its parent's parent
if (!compileOptions.explicitInvalidate) {
@@ -303,7 +290,15 @@ package experimental {
}
}
- protected def getIds = {
+ // Returns the last id contained within a Module
+ private[chisel3] def _lastId: Long = _ids.last match {
+ case mod: BaseModule => mod._lastId
+ case _ =>
+ // Ideally we could just take last._id, but Records store and thus bind their Data in reverse order
+ _ids.maxBy(_._id)._id
+ }
+
+ private[chisel3] def getIds = {
require(_closed, "Can't get ids before module close")
_ids.toSeq
}
@@ -368,10 +363,10 @@ package experimental {
/** Legalized name of this module. */
final lazy val name = try {
- // ModuleAspects and ModuleClones are not "true modules" and thus should share
+ // PseudoModules are not "true modules" and thus should share
// their original modules names without uniquification
this match {
- case (_: ModuleAspect | _: internal.BaseModule.ModuleClone) => desiredName
+ case _: PseudoModule => desiredName
case _ => Builder.globalNamespace.name(desiredName)
}
} catch {
@@ -399,7 +394,14 @@ package experimental {
final def toAbsoluteTarget: IsModule = {
_parent match {
case Some(parent) => parent.toAbsoluteTarget.instOf(this.instanceName, toTarget.module)
- case None => toTarget
+ case None =>
+ // FIXME Special handling for Views - evidence of "weirdness" of .toAbsoluteTarget
+ // In theory, .toAbsoluteTarget should not be necessary, .toTarget combined with the
+ // target disambiguation in FIRRTL's deduplication transform should ensure that .toTarget
+ // is always unambigous. However, legacy workarounds for Chisel's lack of an instance API
+ // have lead some to use .toAbsoluteTarget as a workaround. A proper instance API will make
+ // it possible to deprecate and remove .toAbsoluteTarget
+ if (this == ViewParent) ViewParent.absoluteTarget else toTarget
}
}
diff --git a/core/src/main/scala/chisel3/ModuleAspect.scala b/core/src/main/scala/chisel3/ModuleAspect.scala
index 4e37ab35..a528fa72 100644
--- a/core/src/main/scala/chisel3/ModuleAspect.scala
+++ b/core/src/main/scala/chisel3/ModuleAspect.scala
@@ -2,7 +2,7 @@
package chisel3
-import chisel3.internal.Builder
+import chisel3.internal.{Builder, PseudoModule}
/** Used by Chisel Aspects to inject Chisel code into modules, after they have been elaborated.
* This is an internal API - don't use!
@@ -13,7 +13,7 @@ import chisel3.internal.Builder
* @param moduleCompileOptions
*/
abstract class ModuleAspect private[chisel3] (module: RawModule)
- (implicit moduleCompileOptions: CompileOptions) extends RawModule {
+ (implicit moduleCompileOptions: CompileOptions) extends RawModule with PseudoModule {
Builder.addAspect(module, this)
diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala
index fadb8dae..74e9db6c 100644
--- a/core/src/main/scala/chisel3/RawModule.scala
+++ b/core/src/main/scala/chisel3/RawModule.scala
@@ -11,6 +11,7 @@ import chisel3.internal.BaseModule.ModuleClone
import chisel3.internal.Builder._
import chisel3.internal.firrtl._
import chisel3.internal.sourceinfo.UnlocatableSourceInfo
+import _root_.firrtl.annotations.{IsModule, ModuleTarget}
/** Abstract base class for Modules that contain Chisel RTL.
* This abstract base class is a user-defined module which does not include implicit clock and reset and supports
@@ -157,6 +158,9 @@ trait RequireSyncReset extends Module {
package object internal {
+ /** Marker trait for modules that are not true modules */
+ private[chisel3] trait PseudoModule extends BaseModule
+
// Private reflective version of "val io" to maintain Chisel.Module semantics without having
// io as a virtual method. See https://github.com/freechipsproject/chisel3/pull/1550 for more
// information about the removal of "val io"
@@ -264,4 +268,31 @@ package object internal {
}
}
}
+
+ /** Internal API for [[ViewParent]] */
+ sealed private[chisel3] class ViewParentAPI extends RawModule()(ExplicitCompileOptions.Strict) with PseudoModule {
+ // We must provide `absoluteTarget` but not `toTarget` because otherwise they would be exactly
+ // the same and we'd have no way to distinguish the kind of target when renaming view targets in
+ // the Converter
+ // Note that this is not overriding .toAbsoluteTarget, that is a final def in BaseModule that delegates
+ // to this method
+ private[chisel3] val absoluteTarget: IsModule = ModuleTarget(this.circuitName, "_$$AbsoluteView$$_")
+
+ // This module is not instantiable
+ override private[chisel3] def generateComponent(): Option[Component] = None
+ override private[chisel3] def initializeInParent(parentCompileOptions: CompileOptions): Unit = ()
+ // This module is not really part of the circuit
+ _parent = None
+
+ // Sigil to mark views, starts with '_' to make it a legal FIRRTL target
+ override def desiredName = "_$$View$$_"
+
+ private[chisel3] val fakeComponent: Component = DefModule(this, desiredName, Nil, Nil)
+ }
+
+ /** Special internal object representing the parent of all views
+ *
+ * @note this is a val instead of an object because of the need to wrap in Module(...)
+ */
+ private[chisel3] val ViewParent = Module.do_apply(new ViewParentAPI)(UnlocatableSourceInfo, ExplicitCompileOptions.Strict)
}
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/internal/BiConnect.scala b/core/src/main/scala/chisel3/internal/BiConnect.scala
index aa7d7ac3..4a9bb4f5 100644
--- a/core/src/main/scala/chisel3/internal/BiConnect.scala
+++ b/core/src/main/scala/chisel3/internal/BiConnect.scala
@@ -3,9 +3,11 @@
package chisel3.internal
import chisel3._
+import chisel3.experimental.dataview.reify
import chisel3.experimental.{Analog, BaseModule, attach}
import chisel3.internal.Builder.pushCommand
import chisel3.internal.firrtl.{Connect, DefInvalid}
+
import scala.language.experimental.macros
import chisel3.internal.sourceinfo._
@@ -225,8 +227,10 @@ private[chisel3] object BiConnect {
// This function checks if element-level connection operation allowed.
// Then it either issues it or throws the appropriate exception.
- def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, left: Element, right: Element, context_mod: RawModule): Unit = {
+ def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, _left: Element, _right: Element, context_mod: RawModule): Unit = {
import BindingDirection.{Internal, Input, Output} // Using extensively so import these
+ val left = reify(_left)
+ val right = reify(_right)
// If left or right have no location, assume in context module
// This can occur if one of them is a literal, unbound will error previously
val left_mod: BaseModule = left.topBinding.location.getOrElse(context_mod)
diff --git a/core/src/main/scala/chisel3/internal/Binding.scala b/core/src/main/scala/chisel3/internal/Binding.scala
index 300803ce..6f4ab4b0 100644
--- a/core/src/main/scala/chisel3/internal/Binding.scala
+++ b/core/src/main/scala/chisel3/internal/Binding.scala
@@ -120,6 +120,15 @@ case class MemTypeBinding[T <: Data](parent: MemBase[T]) extends Binding {
// It is a source (RHS). It may only be connected/applied to sinks.
case class DontCareBinding() extends UnconstrainedBinding
+// Views currently only support 1:1 Element-level mappings
+private[chisel3] case class ViewBinding(target: Element) extends UnconstrainedBinding
+/** Binding for Aggregate Views
+ * @param childMap Mapping from children of this view to each child's target
+ * @param target Optional Data this Aggregate views if the view is total and the target is a Data
+ */
+private[chisel3] case class AggregateViewBinding(childMap: Map[Data, Element], target: Option[Data]) extends UnconstrainedBinding
+
+
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 fef12093..ddaedd2e 100644
--- a/core/src/main/scala/chisel3/internal/Builder.scala
+++ b/core/src/main/scala/chisel3/internal/Builder.scala
@@ -10,7 +10,8 @@ import chisel3.internal.firrtl._
import chisel3.internal.naming._
import _root_.firrtl.annotations.{CircuitName, ComponentName, IsMember, ModuleName, Named, ReferenceTarget}
import _root_.firrtl.annotations.AnnotationUtils.validComponentName
-import _root_.firrtl.AnnotationSeq
+import _root_.firrtl.{AnnotationSeq, RenameMap}
+import chisel3.experimental.dataview.{reify, reifySingleData}
import chisel3.internal.Builder.Prefix
import logger.LazyLogging
@@ -215,32 +216,48 @@ 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 {
+ case Some(arg) => arg fullName c
+ case None => computeName(None, None).get
+ }
+
+ // Helper for reifying views if they map to a single Target
+ private[chisel3] def reifyTarget: Option[Data] = this match {
+ case d: Data => reifySingleData(d) // Only Data can be views
+ case bad => throwException(s"This shouldn't be possible - got $bad with ${_parent}")
+ }
+
+ // Helper for reifying the parent of a view if the view maps to a single Target
+ private[chisel3] def reifyParent: BaseModule = reifyTarget.flatMap(_._parent).getOrElse(ViewParent)
+
// 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) => _ref match {
- case Some(arg) => arg fullName c
- case None => computeName(None, None).get
- }
+ case Some(c) => localName(c)
case None => throwException("signalName/pathName should be called after circuit elaboration")
}
case None => throwException("this cannot happen")
}
def pathName: String = _parent match {
case None => instanceName
+ case Some(ViewParent) => s"${reifyParent.pathName}.$instanceName"
case Some(p) => s"${p.pathName}.$instanceName"
}
def parentPathName: String = _parent match {
+ case Some(ViewParent) => reifyParent.pathName
case Some(p) => p.pathName
case None => throwException(s"$instanceName doesn't have a parent")
}
def parentModName: String = _parent match {
+ case Some(ViewParent) => reifyParent.name
case Some(p) => p.name
case None => throwException(s"$instanceName doesn't have a parent")
}
// TODO Should this be public?
protected def circuitName: String = _parent match {
case None => instanceName
+ case Some(ViewParent) => reifyParent.circuitName
case Some(p) => p.circuitName
}
@@ -288,8 +305,10 @@ private[chisel3] trait NamedComponent extends HasId {
final def toAbsoluteTarget: ReferenceTarget = {
val localTarget = toTarget
+ def makeTarget(p: BaseModule) = p.toAbsoluteTarget.ref(localTarget.ref).copy(component = localTarget.component)
_parent match {
- case Some(parent) => parent.toAbsoluteTarget.ref(localTarget.ref).copy(component = localTarget.component)
+ case Some(ViewParent) => makeTarget(reifyParent)
+ case Some(parent) => makeTarget(parent)
case None => localTarget
}
}
@@ -304,6 +323,11 @@ private[chisel3] class ChiselContext() {
// Records the different prefixes which have been scoped at this point in time
var prefixStack: Prefix = Nil
+
+ // Views belong to a separate namespace (for renaming)
+ // The namespace outside of Builder context is useless, but it ensures that views can still be created
+ // and the resulting .toTarget is very clearly useless (_$$View$$_...)
+ val viewNamespace = Namespace.empty
}
private[chisel3] class DynamicContext(val annotationSeq: AnnotationSeq) {
@@ -319,6 +343,9 @@ private[chisel3] class DynamicContext(val annotationSeq: AnnotationSeq) {
*/
val aspectModule: mutable.HashMap[BaseModule, BaseModule] = mutable.HashMap.empty[BaseModule, BaseModule]
+ // Views that do not correspond to a single ReferenceTarget and thus require renaming
+ val unnamedViews: ArrayBuffer[Data] = ArrayBuffer.empty
+
// Set by object Module.apply before calling class Module constructor
// Used to distinguish between no Module() wrapping, multiple wrappings, and rewrapping
var readyForModuleConstr: Boolean = false
@@ -370,6 +397,9 @@ private[chisel3] object Builder extends LazyLogging {
def annotationSeq: AnnotationSeq = dynamicContext.annotationSeq
def namingStack: NamingStack = dynamicContext.namingStack
+ def unnamedViews: ArrayBuffer[Data] = dynamicContext.unnamedViews
+ def viewNamespace: Namespace = chiselContext.get.viewNamespace
+
// Puts a prefix string onto the prefix stack
def pushPrefix(d: String): Unit = {
val context = chiselContext.get()
@@ -403,6 +433,7 @@ private[chisel3] object Builder extends LazyLogging {
case PortBinding(mod) if Builder.currentModule.contains(mod) => data.seedOpt
case PortBinding(mod) => map2(mod.seedOpt, data.seedOpt)(_ + "_" + _)
case (_: LitBinding | _: DontCareBinding) => None
+ case _ => Some("view_") // TODO implement
}
id match {
case d: Data => recData(d)
@@ -646,8 +677,29 @@ private[chisel3] object Builder extends LazyLogging {
}
}
+ // Builds a RenameMap for all Views that do not correspond to a single Data
+ // These Data give a fake ReferenceTarget for .toTarget and .toReferenceTarget that the returned
+ // RenameMap can split into the constituent parts
+ private[chisel3] def makeViewRenameMap: RenameMap = {
+ val renames = RenameMap()
+ for (view <- unnamedViews) {
+ val localTarget = view.toTarget
+ val absTarget = view.toAbsoluteTarget
+ val elts = getRecursiveFields.lazily(view, "")
+ .collect { case (elt: Element, _) => elt }
+ for (elt <- elts) {
+ val targetOfView = reify(elt)
+ renames.record(localTarget, targetOfView.toTarget)
+ renames.record(absTarget, targetOfView.toAbsoluteTarget)
+ }
+ }
+ renames
+ }
+
private [chisel3] def build[T <: RawModule](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
checkScalaVersion()
logger.warn("Elaborating design...")
val mod = f
@@ -655,7 +707,7 @@ private[chisel3] object Builder extends LazyLogging {
errors.checkpoint(logger)
logger.warn("Done elaborating.")
- (Circuit(components.last.name, components.toSeq, annotations.toSeq), mod)
+ (Circuit(components.last.name, components.toSeq, annotations.toSeq, makeViewRenameMap), mod)
}
}
initializeSingletons()
diff --git a/core/src/main/scala/chisel3/internal/MonoConnect.scala b/core/src/main/scala/chisel3/internal/MonoConnect.scala
index b979ebae..5cbab329 100644
--- a/core/src/main/scala/chisel3/internal/MonoConnect.scala
+++ b/core/src/main/scala/chisel3/internal/MonoConnect.scala
@@ -5,7 +5,9 @@ package chisel3.internal
import chisel3._
import chisel3.experimental.{Analog, BaseModule, EnumType, FixedPoint, Interval, UnsafeEnum}
import chisel3.internal.Builder.pushCommand
+import chisel3.experimental.dataview.reify
import chisel3.internal.firrtl.{Connect, DefInvalid}
+
import scala.language.experimental.macros
import chisel3.internal.sourceinfo.SourceInfo
@@ -188,8 +190,10 @@ private[chisel3] object MonoConnect {
// This function checks if element-level connection operation allowed.
// Then it either issues it or throws the appropriate exception.
- def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, sink: Element, source: Element, context_mod: RawModule): Unit = {
+ def elemConnect(implicit sourceInfo: SourceInfo, connectCompileOptions: CompileOptions, _sink: Element, _source: Element, context_mod: RawModule): Unit = {
import BindingDirection.{Internal, Input, Output} // Using extensively so import these
+ val sink = reify(_sink)
+ val source = reify(_source)
// If source has no location, assume in context module
// This can occur if is a literal, unbound will error previously
val sink_mod: BaseModule = sink.topBinding.location.getOrElse(throw UnwritableSinkException)
diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala
index 5796522c..f8a3cf7f 100644
--- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala
+++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala
@@ -8,7 +8,8 @@ import chisel3.internal._
import chisel3.internal.sourceinfo.SourceInfo
import chisel3.experimental._
import _root_.firrtl.{ir => firrtlir}
-import _root_.firrtl.PrimOps
+import _root_.firrtl.{PrimOps, RenameMap}
+import _root_.firrtl.annotations.Annotation
import scala.collection.immutable.NumericRange
import scala.math.BigDecimal.RoundingMode
@@ -789,4 +790,6 @@ abstract class Component extends Arg {
case class DefModule(id: RawModule, name: String, ports: Seq[Port], commands: Seq[Command]) extends Component
case class DefBlackBox(id: BaseBlackBox, name: String, ports: Seq[Port], topDir: SpecifiedDirection, params: Map[String, Param]) extends Component
-case class Circuit(name: String, components: Seq[Component], annotations: Seq[ChiselAnnotation] = Seq.empty)
+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/cookbook.md b/docs/src/cookbooks/cookbook.md
index 1b47ad14..f033c285 100644
--- a/docs/src/cookbooks/cookbook.md
+++ b/docs/src/cookbooks/cookbook.md
@@ -9,12 +9,13 @@ section: "chisel3"
Please note that these examples make use of [Chisel's scala-style printing](../explanations/printing#scala-style).
-* Converting Chisel Types to/from UInt
+* Type Conversions
* [How do I create a UInt from an instance of a Bundle?](#how-do-i-create-a-uint-from-an-instance-of-a-bundle)
* [How do I create a Bundle from a UInt?](#how-do-i-create-a-bundle-from-a-uint)
* [How can I tieoff a Bundle/Vec to 0?](#how-can-i-tieoff-a-bundlevec-to-0)
* [How do I create a Vec of Bools from a UInt?](#how-do-i-create-a-vec-of-bools-from-a-uint)
* [How do I create a UInt from a Vec of Bool?](#how-do-i-create-a-uint-from-a-vec-of-bool)
+ * [How do I connect a subset of Bundle fields?](#how-do-i-connect-a-subset-of-bundle-fields)
* Vectors and Registers
* [How do I create a Vector of Registers?](#how-do-i-create-a-vector-of-registers)
* [How do I create a Reg of type Vec?](#how-do-i-create-a-reg-of-type-vec)
@@ -27,7 +28,7 @@ Please note that these examples make use of [Chisel's scala-style printing](../e
* [How do I get Chisel to name the results of vector reads properly?](#how-do-i-get-chisel-to-name-the-results-of-vector-reads-properly)
* [How can I dynamically set/parametrize the name of a module?](#how-can-i-dynamically-setparametrize-the-name-of-a-module)
-## Converting Chisel Types to/from UInt
+## Type Conversions
### How do I create a UInt from an instance of a Bundle?
@@ -148,6 +149,10 @@ class Foo extends RawModule {
}
```
+### How do I connect a subset of Bundle fields?
+
+See the [DataView cookbook](dataview#how-do-i-connect-a-subset-of-bundle-fields).
+
## Vectors and Registers
### How do I create a Vector of Registers?
diff --git a/docs/src/cookbooks/dataview.md b/docs/src/cookbooks/dataview.md
new file mode 100644
index 00000000..ed969ca1
--- /dev/null
+++ b/docs/src/cookbooks/dataview.md
@@ -0,0 +1,179 @@
+---
+layout: docs
+title: "DataView Cookbook"
+section: "chisel3"
+---
+
+# DataView Cookbook
+
+* [How do I view a Data as a UInt or vice versa?](#how-do-i-view-a-data-as-a-uint-or-vice-versa)
+* [How do I create a DataView for a Bundle has a type parameter?](#how-do-i-create-a-dataview-for-a-bundle-has-a-type-parameter)
+* [How do I create a DataView for a Bundle with optional fields?](#how-do-i-create-a-dataview-for-a-bundle-with-optional-fields)
+* [How do I connect a subset of Bundle fields?](#how-do-i-connect-a-subset-of-bundle-fields)
+ * [How do I view a Bundle as a parent type (superclass)?](#how-do-i-view-a-bundle-as-a-parent-type-superclass)
+ * [How do I view a Bundle as a parent type when the parent type is abstract (like a trait)?](#how-do-i-view-a-bundle-as-a-parent-type-when-the-parent-type-is-abstract-like-a-trait)
+
+## How do I view a Data as a UInt or vice versa?
+
+Subword viewing (using concatenations or bit extractions in `DataViews`) is not yet supported.
+We intend to implement this in the future, but for the time being, use regular casts
+(`.asUInt` and `.asTypeOf`).
+
+## How do I create a DataView for a Bundle has a type parameter?
+
+Instead of using a `val`, use a `def` which can have type parameters:
+
+```scala mdoc:silent:reset
+import chisel3._
+import chisel3.experimental.dataview._
+
+class Foo[T <: Data](val foo: T) extends Bundle
+class Bar[T <: Data](val bar: T) extends Bundle
+
+object Foo {
+ implicit def view[T <: Data]: DataView[Foo[T], Bar[T]] = {
+ DataView(f => new Bar(f.foo.cloneType), _.foo -> _.bar)
+ // .cloneType is necessary because the f passed to this function will be bound hardware
+ }
+}
+```
+
+```scala mdoc:invisible
+// Make sure this works during elaboration, not part of doc
+class MyModule extends RawModule {
+ val in = IO(Input(new Foo(UInt(8.W))))
+ val out = IO(Output(new Bar(UInt(8.W))))
+ out := in.viewAs[Bar[UInt]]
+}
+chisel3.stage.ChiselStage.emitVerilog(new MyModule)
+```
+If you think about type parameterized classes as really being a family of different classes
+(one for each type parameter), you can think about the `implicit def` as a generator of `DataViews`
+for each type parameter.
+
+## How do I create a DataView for a Bundle with optional fields?
+
+Instead of using the default `DataView` apply method, use `DataView.mapping`:
+
+```scala mdoc:silent:reset
+import chisel3._
+import chisel3.experimental.dataview._
+
+class Foo(val w: Option[Int]) extends Bundle {
+ val foo = UInt(8.W)
+ val opt = w.map(x => UInt(x.W))
+}
+class Bar(val w: Option[Int]) extends Bundle {
+ val bar = UInt(8.W)
+ val opt = w.map(x => UInt(x.W))
+}
+
+object Foo {
+ implicit val view: DataView[Foo, Bar] =
+ DataView.mapping(
+ // First argument is always the function to make the view from the target
+ f => new Bar(f.w),
+ // Now instead of a varargs of tuples of individual mappings, we have a single function that
+ // takes a target and a view and returns an Iterable of tuple
+ (f, b) => List(f.foo -> b.bar) ++ f.opt.map(_ -> b.opt.get)
+ // ^ Note that we can append options since they are Iterable!
+
+ )
+}
+```
+
+```scala mdoc:invisible
+// Make sure this works during elaboration, not part of doc
+class MyModule extends RawModule {
+ val in = IO(Input(new Foo(Some(8))))
+ val out = IO(Output(new Bar(Some(8))))
+ out := in.viewAs[Bar]
+}
+chisel3.stage.ChiselStage.emitVerilog(new MyModule)
+```
+
+## How do I connect a subset of Bundle fields?
+
+Chisel 3 requires types to match exactly for connections.
+DataView provides a mechanism for "viewing" one `Bundle` object as if it were the type of another,
+which allows them to be connected.
+
+### How do I view a Bundle as a parent type (superclass)?
+
+For viewing `Bundles` as the type of the parent, it is as simple as using `viewAsSupertype` and providing a
+template object of the parent type:
+
+```scala mdoc:silent:reset
+import chisel3._
+import chisel3.experimental.dataview._
+
+class Foo extends Bundle {
+ val foo = UInt(8.W)
+}
+class Bar extends Foo {
+ val bar = UInt(8.W)
+}
+class MyModule extends Module {
+ val foo = IO(Input(new Foo))
+ val bar = IO(Output(new Bar))
+ bar.viewAsSupertype(new Foo) := foo // bar.foo := foo.foo
+ bar.bar := 123.U // all fields need to be connected
+}
+```
+```scala mdoc:verilog
+chisel3.stage.ChiselStage.emitVerilog(new MyModule)
+```
+
+### How do I view a Bundle as a parent type when the parent type is abstract (like a trait)?
+
+Given the following `Bundles` that share a common `trait`:
+
+```scala mdoc:silent:reset
+import chisel3._
+import chisel3.experimental.dataview._
+
+trait Super extends Bundle {
+ def bitwidth: Int
+ val a = UInt(bitwidth.W)
+}
+class Foo(val bitwidth: Int) extends Super {
+ val foo = UInt(8.W)
+}
+class Bar(val bitwidth: Int) extends Super {
+ val bar = UInt(8.W)
+}
+```
+
+`Foo` and `Bar` cannot be connected directly, but they could be connected by viewing them both as if
+they were instances of their common supertype, `Super`.
+A straightforward approach might run into an issue like the following:
+
+```scala mdoc:fail
+class MyModule extends Module {
+ val foo = IO(Input(new Foo(8)))
+ val bar = IO(Output(new Bar(8)))
+ bar.viewAsSupertype(new Super) := foo.viewAsSupertype(new Super)
+}
+```
+
+The problem is that `viewAs` requires an object to use as a type template (so that it can be cloned),
+but `traits` are abstract and cannot be instantiated.
+The solution is to create an instance of an _anonymous class_ and use that object as the argument to `viewAs`.
+We can do this like so:
+
+```scala mdoc:silent
+class MyModule extends Module {
+ val foo = IO(Input(new Foo(8)))
+ val bar = IO(Output(new Bar(8)))
+ val tpe = new Super { // Adding curly braces creates an anonymous class
+ def bitwidth = 8 // We must implement any abstract methods
+ }
+ bar.viewAsSupertype(tpe) := foo.viewAsSupertype(tpe)
+}
+```
+By adding curly braces after the name of the trait, we're telling Scala to create a new concrete
+subclass of the trait, and create an instance of it.
+As indicated in the comment, abstract methods must still be implemented.
+This is the same that happens when one writes `new Bundle {}`,
+the curly braces create a new concrete subclass; however, because `Bundle` has no abstract methods,
+the contents of the body can be empty.
diff --git a/docs/src/explanations/dataview.md b/docs/src/explanations/dataview.md
new file mode 100644
index 00000000..2f229bfc
--- /dev/null
+++ b/docs/src/explanations/dataview.md
@@ -0,0 +1,520 @@
+---
+layout: docs
+title: "DataView"
+section: "chisel3"
+---
+
+# DataView
+
+_New in Chisel 3.5_
+
+```scala mdoc:invisible
+import chisel3._
+import chisel3.stage.ChiselStage.emitVerilog
+```
+
+## Introduction
+
+DataView is a mechanism for "viewing" Scala objects as a subtype of `chisel3.Data`.
+Often, this is useful for viewing one subtype of `chisel3.Data`, as another.
+One can think about a `DataView` as a mapping from a _Target_ type `T` to a _View_ type `V`.
+This is similar to a cast (eg. `.asTypeOf`) with a few differences:
+1. Views are _connectable_—connections to the view will occur on the target
+2. Whereas casts are _structural_ (a reinterpretation of the underlying bits), a DataView is a customizable mapping
+3. Views can be _partial_—not every field in the target must be included in the mapping
+
+## A Motivating Example (AXI4)
+
+[AXI4](https://en.wikipedia.org/wiki/Advanced_eXtensible_Interface) is a common interface in digital
+design.
+A typical Verilog peripheral using AXI4 will define a write channel as something like:
+```verilog
+module my_module(
+ // Write Channel
+ input AXI_AWVALID,
+ output AXI_AWREADY,
+ input [3:0] AXI_AWID,
+ input [19:0] AXI_AWADDR,
+ input [1:0] AXI_AWLEN,
+ input [1:0] AXI_AWSIZE,
+ // ...
+);
+```
+
+This would correspond to the following Chisel Bundle:
+
+```scala mdoc
+class VerilogAXIBundle(val addrWidth: Int) extends Bundle {
+ val AWVALID = Output(Bool())
+ val AWREADY = Input(Bool())
+ val AWID = Output(UInt(4.W))
+ val AWADDR = Output(UInt(addrWidth.W))
+ val AWLEN = Output(UInt(2.W))
+ val AWSIZE = Output(UInt(2.W))
+ // The rest of AW and other AXI channels here
+}
+
+// Instantiated as
+class my_module extends RawModule {
+ val AXI = IO(new VerilogAXIBundle(20))
+}
+```
+
+Expressing something that matches a standard Verilog interface is important when instantiating Verilog
+modules in a Chisel design as `BlackBoxes`.
+Generally though, Chisel developers prefer to use composition via utilities like `Decoupled` rather
+than a flat handling of `ready` and `valid` as in the above.
+A more "Chisel-y" implementation of this interface might look like:
+
+```scala mdoc
+// Note that both the AW and AR channels look similar and could use the same Bundle definition
+class AXIAddressChannel(val addrWidth: Int) extends Bundle {
+ val id = UInt(4.W)
+ val addr = UInt(addrWidth.W)
+ val len = UInt(2.W)
+ val size = UInt(2.W)
+ // ...
+}
+import chisel3.util.Decoupled
+// We can compose the various AXI channels together
+class AXIBundle(val addrWidth: Int) extends Bundle {
+ val aw = Decoupled(new AXIAddressChannel(addrWidth))
+ // val ar = new AXIAddressChannel
+ // ... Other channels here ...
+}
+// Instantiated as
+class MyModule extends RawModule {
+ val axi = IO(new AXIBundle(20))
+}
+```
+
+Of course, this would result in very different looking Verilog:
+
+```scala mdoc:verilog
+emitVerilog(new MyModule {
+ override def desiredName = "MyModule"
+ axi := DontCare // Just to generate Verilog in this stub
+})
+```
+
+So how can we use our more structured types while maintaining expected Verilog interfaces?
+Meet DataView:
+
+```scala mdoc
+import chisel3.experimental.dataview._
+
+// We recommend putting DataViews in a companion object of one of the involved types
+object AXIBundle {
+ // Don't be afraid of the use of implicits, we will discuss this pattern in more detail later
+ implicit val axiView = DataView[VerilogAXIBundle, AXIBundle](
+ // The first argument is a function constructing an object of View type (AXIBundle)
+ // from an object of the Target type (VerilogAXIBundle)
+ vab => new AXIBundle(vab.addrWidth),
+ // The remaining arguments are a mapping of the corresponding fields of the two types
+ _.AWVALID -> _.aw.valid,
+ _.AWREADY -> _.aw.ready,
+ _.AWID -> _.aw.bits.id,
+ _.AWADDR -> _.aw.bits.addr,
+ _.AWLEN -> _.aw.bits.len,
+ _.AWSIZE -> _.aw.bits.size,
+ // ...
+ )
+}
+```
+
+This `DataView` is a mapping between our flat, Verilog-style AXI Bundle to our more compositional,
+Chisel-style AXI Bundle.
+It allows us to define our ports to match the expected Verilog interface, while manipulating it as if
+it were the more structured type:
+
+```scala mdoc
+class AXIStub extends RawModule {
+ val AXI = IO(new VerilogAXIBundle(20))
+ val view = AXI.viewAs[AXIBundle]
+
+ // We can now manipulate `AXI` via `view`
+ view.aw.bits := 0.U.asTypeOf(new AXIAddressChannel(20)) // zero everything out by default
+ view.aw.valid := true.B
+ when (view.aw.ready) {
+ view.aw.bits.id := 5.U
+ view.aw.bits.addr := 1234.U
+ // We can still manipulate AXI as well
+ AXI.AWLEN := 1.U
+ }
+}
+```
+
+This will generate Verilog that matches the standard naming convention:
+
+```scala mdoc:verilog
+emitVerilog(new AXIStub)
+```
+
+Note that if both the _Target_ and the _View_ types are subtypes of `Data` (as they are in this example),
+the `DataView` is _invertible_.
+This means that we can easily create a `DataView[AXIBundle, VerilogAXIBundle]` from our existing
+`DataView[VerilogAXIBundle, AXIBundle]`, all we need to do is provide a function to construct
+a `VerilogAXIBundle` from an instance of an `AXIBundle`:
+
+```scala mdoc:silent
+// Note that typically you should define these together (eg. inside object AXIBundle)
+implicit val axiView2 = AXIBundle.axiView.invert(ab => new VerilogAXIBundle(ab.addrWidth))
+```
+
+The following example shows this and illustrates another use case of `DataView`—connecting unrelated
+types:
+
+```scala mdoc
+class ConnectionExample extends RawModule {
+ val in = IO(new AXIBundle(20))
+ val out = IO(Flipped(new VerilogAXIBundle(20)))
+ out.viewAs[AXIBundle] <> in
+}
+```
+
+This results in the corresponding fields being connected in the emitted Verilog:
+
+```scala mdoc:verilog
+emitVerilog(new ConnectionExample)
+```
+
+## Other Use Cases
+
+While the ability to map between `Bundle` types as in the AXI4 example is pretty compelling,
+DataView has many other applications.
+Importantly, because the _Target_ of the `DataView` need not be a `Data`, it provides a way to use
+`non-Data` objects with APIs that require `Data`.
+
+### Tuples
+
+Perhaps the most helpful use of `DataView` for a non-`Data` type is viewing Scala tuples as `Bundles`.
+For example, in Chisel prior to the introduction of `DataView`, one might try to `Mux` tuples and
+see an error like the following:
+
+<!-- Todo will need to ensure built-in code for Tuples is suppressed once added to stdlib -->
+
+```scala mdoc:fail
+class TupleExample extends RawModule {
+ val a, b, c, d = IO(Input(UInt(8.W)))
+ val cond = IO(Input(Bool()))
+ val x, y = IO(Output(UInt(8.W)))
+ (x, y) := Mux(cond, (a, b), (c, d))
+}
+```
+
+The issue, is that Chisel primitives like `Mux` and `:=` only operate on subtypes of `Data` and
+Tuples (as members of the Scala standard library), are not subclasses of `Data`.
+`DataView` provides a mechanism to _view_ a `Tuple` as if it were a `Data`:
+
+<!-- TODO replace this with stdlib import -->
+
+```scala mdoc:invisible
+// ProductDataProduct
+implicit val productDataProduct: DataProduct[Product] = new DataProduct[Product] {
+ def dataIterator(a: Product, path: String): Iterator[(Data, String)] = {
+ a.productIterator.zipWithIndex.collect { case (d: Data, i) => d -> s"$path._$i" }
+ }
+}
+```
+
+```scala mdoc
+// We need a type to represent the Tuple
+class HWTuple2[A <: Data, B <: Data](val _1: A, val _2: B) extends Bundle
+
+// Provide DataView between Tuple and HWTuple
+implicit def view[A <: Data, B <: Data]: DataView[(A, B), HWTuple2[A, B]] =
+ DataView(tup => new HWTuple2(tup._1.cloneType, tup._2.cloneType),
+ _._1 -> _._1, _._2 -> _._2)
+```
+
+Now, we can use `.viewAs` to view Tuples as if they were subtypes of `Data`:
+
+```scala mdoc
+class TupleVerboseExample extends RawModule {
+ val a, b, c, d = IO(Input(UInt(8.W)))
+ val cond = IO(Input(Bool()))
+ val x, y = IO(Output(UInt(8.W)))
+ (x, y).viewAs[HWTuple2[UInt, UInt]] := Mux(cond, (a, b).viewAs[HWTuple2[UInt, UInt]], (c, d).viewAs[HWTuple2[UInt, UInt]])
+}
+```
+
+This is much more verbose than the original idea of just using the Tuples directly as if they were `Data`.
+We can make this better by providing an implicit conversion that views a `Tuple` as a `HWTuple2`:
+
+```scala mdoc
+implicit def tuple2hwtuple[A <: Data, B <: Data](tup: (A, B)): HWTuple2[A, B] =
+ tup.viewAs[HWTuple2[A, B]]
+```
+
+Now, the original code just works!
+
+```scala mdoc
+class TupleExample extends RawModule {
+ val a, b, c, d = IO(Input(UInt(8.W)))
+ val cond = IO(Input(Bool()))
+ val x, y = IO(Output(UInt(8.W)))
+ (x, y) := Mux(cond, (a, b), (c, d))
+}
+```
+
+```scala mdoc:invisible
+// Always emit Verilog to make sure it actually works
+emitVerilog(new TupleExample)
+```
+
+Note that this example ignored `DataProduct` which is another required piece (see [the documentation
+about it below](#dataproduct)).
+
+All of this is slated to be included the Chisel standard library.
+
+## Totality and PartialDataView
+
+A `DataView` is _total_ if all fields of the _Target_ type and all fields of the _View_ type are
+included in the mapping.
+Chisel will error if a field is accidentally left out from a `DataView`.
+For example:
+
+```scala mdoc
+class BundleA extends Bundle {
+ val foo = UInt(8.W)
+ val bar = UInt(8.W)
+}
+class BundleB extends Bundle {
+ val fizz = UInt(8.W)
+}
+```
+
+```scala mdoc:crash
+{ // Using an extra scope here to avoid a bug in mdoc (documentation generation)
+// We forgot BundleA.foo in the mapping!
+implicit val myView = DataView[BundleA, BundleB](_ => new BundleB, _.bar -> _.fizz)
+class BadMapping extends Module {
+ val in = IO(Input(new BundleA))
+ val out = IO(Output(new BundleB))
+ out := in.viewAs[BundleB]
+}
+// We must run Chisel to see the error
+emitVerilog(new BadMapping)
+}
+```
+
+As that error suggests, if we *want* the view to be non-total, we can use a `PartialDataView`:
+
+```scala mdoc
+// A PartialDataView does not have to be total for the Target
+implicit val myView = PartialDataView[BundleA, BundleB](_ => new BundleB, _.bar -> _.fizz)
+class PartialDataViewModule extends Module {
+ val in = IO(Input(new BundleA))
+ val out = IO(Output(new BundleB))
+ out := in.viewAs[BundleB]
+}
+```
+
+```scala mdoc:verilog
+emitVerilog(new PartialDataViewModule)
+```
+
+While `PartialDataViews` need not be total for the _Target_, both `PartialDataViews` and `DataViews`
+must always be total for the _View_.
+This has the consequence that `PartialDataViews` are **not** invertible in the same way as `DataViews`.
+
+For example:
+
+```scala mdoc:crash
+{ // Using an extra scope here to avoid a bug in mdoc (documentation generation)
+implicit val myView2 = myView.invert(_ => new BundleA)
+class PartialDataViewModule2 extends Module {
+ val in = IO(Input(new BundleA))
+ val out = IO(Output(new BundleB))
+ // Using the inverted version of the mapping
+ out.viewAs[BundleA] := in
+}
+// We must run Chisel to see the error
+emitVerilog(new PartialDataViewModule2)
+}
+```
+
+As noted, the mapping must **always** be total for the `View`.
+
+## Advanced Details
+
+`DataView` takes advantage of features of Scala that may be new to many users of Chisel—in particular
+[Type Classes](#type-classes).
+
+### Type Classes
+
+[Type classes](https://en.wikipedia.org/wiki/Type_class) are powerful language feature for writing
+polymorphic code.
+They are a common feature in "modern programming languages" like
+Scala,
+Swift (see [protocols](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html)),
+and Rust (see [traits](https://doc.rust-lang.org/book/ch10-02-traits.html)).
+Type classes may appear similar to inheritance in object-oriented programming but there are some
+important differences:
+
+1. You can provide a type class for a type you don't own (eg. one defined in a 3rd party library,
+ the Scala standard library, or Chisel itself)
+2. You can write a single type class for many types that do not have a sub-typing relationship
+3. You can provide multiple different type classes for the same type
+
+For `DataView`, (1) is crucial because we want to be able to implement `DataViews` of built-in Scala
+types like tuples and `Seqs`. Furthermore, `DataView` has two type parameters (the _Target_ and the
+_View_ types) so inheritance does not really make sense—which type would `extend` `DataView`?
+
+In Scala 2, type classes are not a built-in language feature, but rather are implemented using implicits.
+There are great resources out there for interested readers:
+* [Basic Tutorial](https://scalac.io/blog/typeclasses-in-scala/)
+* [Fantastic Explanation on StackOverflow](https://stackoverflow.com/a/5598107/2483329)
+
+Note that Scala 3 has added built-in syntax for type classes that does not apply to Chisel 3 which
+currently only supports Scala 2.
+
+### Implicit Resolution
+
+Given that `DataView` is implemented using implicits, it is important to understand implicit
+resolution.
+Whenever the compiler sees an implicit argument is required, it first looks in _current scope_
+before looking in the _implicit scope_.
+
+1. Current scope
+ * Values defined in the current scope
+ * Explicit imports
+ * Wildcard imports
+2. Implicit scope
+ * Companion object of a type
+ * Implicit scope of an argument's type
+ * Implicit scope of type parameters
+
+If at either stage, multiple implicits are found, then the static overloading rule is used to resolve
+it.
+Put simply, if one implicit applies to a more-specific type than the other, the more-specific one
+will be selected.
+If multiple implicits apply within a given stage, then the compiler throws an ambiguous implicit
+resolution error.
+
+
+This section draws heavily from [[1]](https://stackoverflow.com/a/5598107/2483329) and
+[[2]](https://stackoverflow.com/a/5598107/2483329).
+In particular, see [1] for examples.
+
+#### Implicit Resolution Example
+
+To help clarify a bit, let us consider how implicit resolution works for `DataView`.
+Consider the definition of `viewAs`:
+
+```scala
+def viewAs[V <: Data](implicit dataView: DataView[T, V]): V
+```
+
+Armed with the knowledge from the previous section, we know that whenever we call `.viewAs`, the
+Scala compiler will first look for a `DataView[T, V]` in the current scope (defined in, or imported),
+then it will look in the companion objects of `DataView`, `T`, and `V`.
+This enables a fairly powerful pattern, namely that default or typical implementations of a `DataView`
+should be defined in the companion object for one of the two types.
+We can think about `DataViews` defined in this way as "low priority defaults".
+They can then be overruled by a specific import if a given user ever wants different behavior.
+For example:
+
+Given the following types:
+
+```scala mdoc
+class Foo extends Bundle {
+ val a = UInt(8.W)
+ val b = UInt(8.W)
+}
+class Bar extends Bundle {
+ val c = UInt(8.W)
+ val d = UInt(8.W)
+}
+object Foo {
+ implicit val f2b = DataView[Foo, Bar](_ => new Bar, _.a -> _.c, _.b -> _.d)
+ implicit val b2f = f2b.invert(_ => new Foo)
+}
+```
+
+This provides an implementation of `DataView` in the _implicit scope_ as a "default" mapping between
+`Foo` and `Bar` (and it doesn't even require an import!):
+
+```scala mdoc
+class FooToBar extends Module {
+ val foo = IO(Input(new Foo))
+ val bar = IO(Output(new Bar))
+ bar := foo.viewAs[Bar]
+}
+```
+
+```scala mdoc:verilog
+emitVerilog(new FooToBar)
+```
+
+However, it's possible that some user of `Foo` and `Bar` wants different behavior,
+perhaps they would prefer more of "swizzling" behavior rather than a direct mapping:
+
+```scala mdoc
+object Swizzle {
+ implicit val swizzle = DataView[Foo, Bar](_ => new Bar, _.a -> _.d, _.b -> _.c)
+}
+// Current scope always wins over implicit scope
+import Swizzle._
+class FooToBarSwizzled extends Module {
+ val foo = IO(Input(new Foo))
+ val bar = IO(Output(new Bar))
+ bar := foo.viewAs[Bar]
+}
+```
+
+```scala mdoc:verilog
+emitVerilog(new FooToBarSwizzled)
+```
+
+### DataProduct
+
+`DataProduct` is a type class used by `DataView` to validate the correctness of a user-provided mapping.
+In order for a type to be "viewable" (ie. the `Target` type of a `DataView`), it must have an
+implementation of `DataProduct`.
+
+For example, say we have some non-Bundle type:
+```scala mdoc
+// Loosely based on chisel3.util.Counter
+class MyCounter(val width: Int) {
+ /** Indicates if the Counter is incrementing this cycle */
+ val active = WireDefault(false.B)
+ val value = RegInit(0.U(width.W))
+ def inc(): Unit = {
+ active := true.B
+ value := value + 1.U
+ }
+ def reset(): Unit = {
+ value := 0.U
+ }
+}
+```
+
+Say we want to view `MyCounter` as a `Valid[UInt]`:
+
+```scala mdoc:fail
+import chisel3.util.Valid
+implicit val counterView = DataView[MyCounter, Valid[UInt]](c => Valid(UInt(c.width.W)), _.value -> _.bits, _.active -> _.valid)
+```
+
+As you can see, this fails Scala compliation.
+We need to provide an implementation of `DataProduct[MyCounter]` which provides Chisel a way to access
+the objects of type `Data` within `MyCounter`:
+
+```scala mdoc:silent
+import chisel3.util.Valid
+implicit val counterProduct = new DataProduct[MyCounter] {
+ // The String part of the tuple is a String path to the object to help in debugging
+ def dataIterator(a: MyCounter, path: String): Iterator[(Data, String)] =
+ List(a.value -> s"$path.value", a.active -> s"$path.active").iterator
+}
+// Now this works
+implicit val counterView = DataView[MyCounter, Valid[UInt]](c => Valid(UInt(c.width.W)), _.value -> _.bits, _.active -> _.valid)
+```
+
+Why is this useful?
+This is how Chisel is able to check for totality as [described above](#totality-and-partialdataview).
+In addition to checking if a user has left a field out of the mapping, it also allows Chisel to check
+if the user has included a `Data` in the mapping that isn't actually a part of the _target_ nor the
+_view_.
+
diff --git a/src/main/scala/chisel3/stage/phases/Convert.scala b/src/main/scala/chisel3/stage/phases/Convert.scala
index bf42b58a..b5b01b8d 100644
--- a/src/main/scala/chisel3/stage/phases/Convert.scala
+++ b/src/main/scala/chisel3/stage/phases/Convert.scala
@@ -29,8 +29,7 @@ class Convert extends Phase {
/* Convert all Chisel Annotations to FIRRTL Annotations */
a
.circuit
- .annotations
- .map(_.toFirrtl) ++
+ .firrtlAnnotations ++
a
.circuit
.annotations
diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala
index e513189e..8e35273d 100644
--- a/src/test/scala/chiselTests/ChiselSpec.scala
+++ b/src/test/scala/chiselTests/ChiselSpec.scala
@@ -7,9 +7,11 @@ import chisel3.aop.Aspect
import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage, NoRunFirrtlCompilerAnnotation, PrintFullStackTraceAnnotation}
import chisel3.testers._
import firrtl.annotations.Annotation
+import firrtl.ir.Circuit
import firrtl.util.BackendCompilationUtilities
import firrtl.{AnnotationSeq, EmittedVerilogCircuitAnnotation}
import _root_.logger.Logger
+import firrtl.stage.FirrtlCircuitAnnotation
import org.scalacheck._
import org.scalatest._
import org.scalatest.flatspec.AnyFlatSpec
@@ -87,6 +89,33 @@ trait ChiselRunners extends Assertions with BackendCompilationUtilities {
case EmittedVerilogCircuitAnnotation(a) => a.value
}.getOrElse(fail("No Verilog circuit was emitted by the FIRRTL compiler!"))
}
+
+ def elaborateAndGetModule[A <: RawModule](t: => A): A = {
+ var res: Any = null
+ ChiselStage.elaborate {
+ res = t
+ res.asInstanceOf[A]
+ }
+ res.asInstanceOf[A]
+ }
+
+ /** Compiles a Chisel Module to FIRRTL
+ * NOTE: This uses the "test_run_dir" as the default directory for generated code.
+ * @param t the generator for the module
+ * @return The FIRRTL Circuit and Annotations _before_ FIRRTL compilation
+ */
+ def getFirrtlAndAnnos(t: => RawModule): (Circuit, Seq[Annotation]) = {
+ val args = Array(
+ "--target-dir",
+ createTestDirectory(this.getClass.getSimpleName).toString,
+ "--no-run-firrtl"
+ )
+ val annos = (new ChiselStage).execute(args, Seq(ChiselGeneratorAnnotation(() => t)))
+ val circuit = annos.collectFirst {
+ case FirrtlCircuitAnnotation(c) => c
+ }.getOrElse(fail("No FIRRTL Circuit found!!"))
+ (circuit, annos)
+ }
}
/** Spec base class for BDD-style testers. */
diff --git a/src/test/scala/chiselTests/experimental/DataView.scala b/src/test/scala/chiselTests/experimental/DataView.scala
new file mode 100644
index 00000000..381cfeb5
--- /dev/null
+++ b/src/test/scala/chiselTests/experimental/DataView.scala
@@ -0,0 +1,542 @@
+// See LICENSE for license details.
+
+package chiselTests.experimental
+
+import chiselTests.ChiselFlatSpec
+import chisel3._
+import chisel3.experimental.dataview._
+import chisel3.experimental.DataMirror.internal.chiselTypeClone
+import chisel3.stage.ChiselStage
+import chisel3.util.{Decoupled, DecoupledIO}
+
+object SimpleBundleDataView {
+ class BundleA(val w: Int) extends Bundle {
+ val foo = UInt(w.W)
+ }
+ class BundleB(val w: Int) extends Bundle {
+ val bar = UInt(w.W)
+ }
+ implicit val v1 = DataView[BundleA, BundleB](a => new BundleB(a.w), _.foo -> _.bar)
+ implicit val v2 = v1.invert(b => new BundleA(b.w))
+}
+
+object VecBundleDataView {
+ class MyBundle extends Bundle {
+ val foo = UInt(8.W)
+ val bar = UInt(8.W)
+ }
+ implicit val v1: DataView[MyBundle, Vec[UInt]] = DataView(_ => Vec(2, UInt(8.W)), _.foo -> _(1), _.bar -> _(0))
+ implicit val v2 = v1.invert(_ => new MyBundle)
+}
+
+// This should become part of Chisel in a later PR
+object Tuple2DataProduct {
+ implicit def tuple2DataProduct[A : DataProduct, B : DataProduct] = new DataProduct[(A, B)] {
+ def dataIterator(tup: (A, B), path: String): Iterator[(Data, String)] = {
+ val dpa = implicitly[DataProduct[A]]
+ val dpb = implicitly[DataProduct[B]]
+ val (a, b) = tup
+ dpa.dataIterator(a, s"$path._1") ++ dpb.dataIterator(b, s"$path._2")
+ }
+ }
+}
+
+// This should become part of Chisel in a later PR
+object HWTuple {
+ import Tuple2DataProduct._
+
+ class HWTuple2[A <: Data, B <: Data](val _1: A, val _2: B) extends Bundle
+
+ implicit def view[T1 : DataProduct, T2 : DataProduct, V1 <: Data, V2 <: Data](
+ implicit v1: DataView[T1, V1], v2: DataView[T2, V2]
+ ): DataView[(T1, T2), HWTuple2[V1, V2]] =
+ DataView.mapping(
+ { case (a, b) => new HWTuple2(a.viewAs[V1].cloneType, b.viewAs[V2].cloneType)},
+ { case ((a, b), hwt) =>
+ Seq(a.viewAs[V1] -> hwt._1,
+ b.viewAs[V2] -> hwt._2)
+ }
+ )
+
+ implicit def tuple2hwtuple[T1 : DataProduct, T2 : DataProduct, V1 <: Data, V2 <: Data](
+ tup: (T1, T2))(implicit v1: DataView[T1, V1], v2: DataView[T2, V2]
+ ): HWTuple2[V1, V2] = tup.viewAs[HWTuple2[V1, V2]]
+}
+
+// This should become part of Chisel in a later PR
+object SeqDataProduct {
+ // Should we special case Seq[Data]?
+ implicit def seqDataProduct[A : DataProduct]: DataProduct[Seq[A]] = new DataProduct[Seq[A]] {
+ def dataIterator(a: Seq[A], path: String): Iterator[(Data, String)] = {
+ val dpa = implicitly[DataProduct[A]]
+ a.iterator
+ .zipWithIndex
+ .flatMap { case (elt, idx) =>
+ dpa.dataIterator(elt, s"$path[$idx]")
+ }
+ }
+ }
+}
+
+object SeqToVec {
+ import SeqDataProduct._
+
+ // TODO this would need a better way to determine the prototype for the Vec
+ implicit def seqVec[A : DataProduct, B <: Data](implicit dv: DataView[A, B]): DataView[Seq[A], Vec[B]] =
+ DataView.mapping[Seq[A], Vec[B]](
+ xs => Vec(xs.size, chiselTypeClone(xs.head.viewAs[B])), // xs.head is not correct in general
+ { case (s, v) => s.zip(v).map { case (a, b) => a.viewAs[B] -> b } }
+ )
+
+ implicit def seq2Vec[A : DataProduct, B <: Data](xs: Seq[A])(implicit dv: DataView[A, B]): Vec[B] =
+ xs.viewAs[Vec[B]]
+}
+
+class DataViewSpec extends ChiselFlatSpec {
+
+ behavior of "DataView"
+
+ it should "support simple Bundle viewing" in {
+ import SimpleBundleDataView._
+ class MyModule extends Module {
+ val in = IO(Input(new BundleA(8)))
+ val out = IO(Output(new BundleB(8)))
+ out := in.viewAs[BundleB]
+ }
+ val chirrtl = ChiselStage.emitChirrtl(new MyModule)
+ chirrtl should include("out.bar <= in.foo")
+ }
+
+ it should "be a bidirectional mapping" in {
+ import SimpleBundleDataView._
+ class MyModule extends Module {
+ val in = IO(Input(new BundleA(8)))
+ val out = IO(Output(new BundleB(8)))
+ out.viewAs[BundleA] := in
+ }
+ val chirrtl = ChiselStage.emitChirrtl(new MyModule)
+ chirrtl should include("out.bar <= in.foo")
+ }
+
+ it should "handle viewing UInts as UInts" in {
+ class MyModule extends Module {
+ val in = IO(Input(UInt(8.W)))
+ val foo = IO(Output(UInt(8.W)))
+ val bar = IO(Output(UInt(8.W)))
+ foo := in.viewAs[UInt]
+ bar.viewAs[UInt] := in
+ }
+ val chirrtl = ChiselStage.emitChirrtl(new MyModule)
+ chirrtl should include("foo <= in")
+ chirrtl should include("bar <= in")
+ }
+
+ it should "handle viewing Bundles as their same concrete type" in {
+ class MyBundle extends Bundle {
+ val foo = UInt(8.W)
+ }
+ class MyModule extends Module {
+ val in = IO(Input(new MyBundle))
+ val fizz = IO(Output(new MyBundle))
+ val buzz = IO(Output(new MyBundle))
+ fizz := in.viewAs[MyBundle]
+ buzz.viewAs[MyBundle] := in
+ }
+ val chirrtl = ChiselStage.emitChirrtl(new MyModule)
+ chirrtl should include("fizz.foo <= in.foo")
+ chirrtl should include("buzz.foo <= in.foo")
+ }
+
+ it should "handle viewing Vecs as their same concrete type" in {
+ class MyModule extends Module {
+ val in = IO(Input(Vec(1, UInt(8.W))))
+ val fizz = IO(Output(Vec(1, UInt(8.W))))
+ val buzz = IO(Output(Vec(1, UInt(8.W))))
+ fizz := in.viewAs[Vec[UInt]]
+ buzz.viewAs[Vec[UInt]] := in
+ }
+ val chirrtl = ChiselStage.emitChirrtl(new MyModule)
+ chirrtl should include("fizz[0] <= in[0]")
+ chirrtl should include("buzz[0] <= in[0]")
+ }
+
+ it should "handle viewing Vecs as Bundles and vice versa" in {
+ import VecBundleDataView._
+ class MyModule extends Module {
+ val in = IO(Input(new MyBundle))
+ val out = IO(Output(Vec(2, UInt(8.W))))
+ val out2 = IO(Output(Vec(2, UInt(8.W))))
+ out := in.viewAs[Vec[UInt]]
+ out2.viewAs[MyBundle] := in
+ }
+ val chirrtl = ChiselStage.emitChirrtl(new MyModule)
+ chirrtl should include("out[0] <= in.bar")
+ chirrtl should include("out[1] <= in.foo")
+ chirrtl should include("out2[0] <= in.bar")
+ chirrtl should include("out2[1] <= in.foo")
+ }
+
+ 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)
+ class MyModule extends Module {
+ val enq = IO(Flipped(Decoupled(new FizzBuzz)))
+ val deq = IO(new FlatDecoupled)
+ val deq2 = IO(new FlatDecoupled)
+ deq <> enq.viewAs[FlatDecoupled]
+ deq2.viewAs[DecoupledIO[FizzBuzz]] <> enq
+ }
+ val chirrtl = ChiselStage.emitChirrtl(new MyModule)
+ chirrtl should include("deq.valid <= enq.valid")
+ chirrtl should include("enq.ready <= deq.ready")
+ chirrtl should include("deq.fizz <= enq.bits.fizz")
+ chirrtl should include("deq.buzz <= enq.bits.buzz")
+ chirrtl should include("deq2.valid <= enq.valid")
+ chirrtl should include("enq.ready <= deq2.ready")
+ chirrtl should include("deq2.fizz <= enq.bits.fizz")
+ chirrtl should include("deq2.buzz <= enq.bits.buzz")
+ }
+
+ it should "support viewing a Bundle as a Parent Bundle type" in {
+ class Foo extends Bundle {
+ val foo = UInt(8.W)
+ }
+ class Bar extends Foo {
+ val bar = UInt(8.W)
+ }
+ class MyModule extends Module {
+ val fooIn = IO(Input(new Foo))
+ val barOut = IO(Output(new Bar))
+ barOut.viewAsSupertype(new Foo) := fooIn
+
+ val barIn = IO(Input(new Bar))
+ val fooOut = IO(Output(new Foo))
+ fooOut := barIn.viewAsSupertype(new Foo)
+ }
+ val chirrtl = ChiselStage.emitChirrtl(new MyModule)
+ chirrtl should include("barOut.foo <= fooIn.foo")
+ chirrtl should include("fooOut.foo <= barIn.foo")
+ }
+
+ it should "error if viewing a parent Bundle as a child Bundle type" in {
+ assertTypeError("""
+ class Foo extends Bundle {
+ val foo = UInt(8.W)
+ }
+ class Bar extends Foo {
+ val bar = UInt(8.W)
+ }
+ class MyModule extends Module {
+ val barIn = IO(Input(new Bar))
+ val fooOut = IO(Output(new Foo))
+ fooOut.viewAs(new Bar) := barIn
+ }
+ """)
+ }
+
+ it should "work in UInt operations" in {
+ class MyBundle extends Bundle {
+ val value = UInt(8.W)
+ }
+ class MyModule extends Module {
+ val a = IO(Input(UInt(8.W)))
+ val b = IO(Input(new MyBundle))
+ val cond = IO(Input(Bool()))
+ val and, mux, bitsCat = IO(Output(UInt(8.W)))
+ // Chisel unconditionally emits a node, so name it at least
+ val x = a.viewAs[UInt] & b.viewAs[MyBundle].value
+ and.viewAs[UInt] := x
+
+ val y = Mux(cond.viewAs[Bool], a.viewAs[UInt], b.value.viewAs[UInt])
+ mux.viewAs[UInt] := y
+
+ // TODO should we have a macro so that we don't need .apply?
+ val aBits = a.viewAs[UInt].apply(3, 0)
+ val bBits = b.viewAs[MyBundle].value(3, 0)
+ val abCat = aBits.viewAs[UInt] ## bBits.viewAs[UInt]
+ bitsCat := abCat
+ }
+ val chirrtl = ChiselStage.emitChirrtl(new MyModule)
+ val expected = List(
+ "node x = and(a, b.value)",
+ "and <= x",
+ "node y = mux(cond, a, b.value)",
+ "mux <= y",
+ "node aBits = bits(a, 3, 0)",
+ "node bBits = bits(b.value, 3, 0)",
+ "node abCat = cat(aBits, bBits)",
+ "bitsCat <= abCat"
+ )
+ for (line <- expected) {
+ chirrtl should include(line)
+ }
+ }
+
+ it should "support .asUInt of Views" in {
+ import VecBundleDataView._
+ class MyModule extends Module {
+ val barIn = IO(Input(new MyBundle))
+ val fooOut = IO(Output(UInt()))
+ val cat = barIn.viewAs[Vec[UInt]].asUInt
+ fooOut := cat
+ }
+ val chirrtl = ChiselStage.emitChirrtl(new MyModule)
+ chirrtl should include ("node cat = cat(barIn.foo, barIn.bar)")
+ chirrtl should include ("fooOut <= cat")
+ }
+
+ it should "be composable" in {
+ // Given DataView[A, B] and DataView[B, C], derive DataView[A, C]
+ class Foo(val foo: UInt) extends Bundle
+ class Bar(val bar: UInt) extends Bundle
+ class Fizz(val fizz: UInt) extends Bundle
+
+ implicit val foo2bar = DataView[Foo, Bar](f => new Bar(chiselTypeClone(f.foo)), _.foo -> _.bar)
+ implicit val bar2fizz = DataView[Bar, Fizz](b => new Fizz(chiselTypeClone(b.bar)), _.bar -> _.fizz)
+
+ implicit val foo2fizz: DataView[Foo, Fizz] = foo2bar.andThen(bar2fizz)
+
+ class MyModule extends Module {
+ val a, b = IO(Input(new Foo(UInt(8.W))))
+ val y, z = IO(Output(new Fizz(UInt(8.W))))
+ y := a.viewAs[Fizz]
+ z := b.viewAs[Bar].viewAs[Fizz]
+ }
+ val chirrtl = ChiselStage.emitChirrtl(new MyModule)
+ chirrtl should include ("y.fizz <= a.foo")
+ chirrtl should include ("z.fizz <= b.foo")
+ }
+
+ // This example should be turned into a built-in feature
+ it should "enable implementing \"HardwareTuple\"" in {
+ import HWTuple._
+
+ class MyModule extends Module {
+ val a, b, c, d = IO(Input(UInt(8.W)))
+ val sel = IO(Input(Bool()))
+ val y, z = IO(Output(UInt(8.W)))
+ (y, z) := Mux(sel, (a, b), (c, d))
+ }
+ // Verilog instead of CHIRRTL because the optimizations make it much prettier
+ val verilog = ChiselStage.emitVerilog(new MyModule)
+ verilog should include ("assign y = sel ? a : c;")
+ verilog should include ("assign z = sel ? b : d;")
+ }
+
+ it should "support nesting of tuples" in {
+ import Tuple2DataProduct._
+ import HWTuple._
+
+ class MyModule extends Module {
+ val a, b, c, d = IO(Input(UInt(8.W)))
+ val w, x, y, z = IO(Output(UInt(8.W)))
+ ((w, x), (y, z)) := ((a, b), (c, d))
+ }
+ val chirrtl = ChiselStage.emitChirrtl(new MyModule)
+ chirrtl should include ("w <= a")
+ chirrtl should include ("x <= b")
+ chirrtl should include ("y <= c")
+ chirrtl should include ("z <= d")
+ }
+
+ // This example should be turned into a built-in feature
+ it should "enable viewing Seqs as Vecs" in {
+ import SeqToVec._
+
+ class MyModule extends Module {
+ val a, b, c = IO(Input(UInt(8.W)))
+ val x, y, z = IO(Output(UInt(8.W)))
+ Seq(x, y, z) := VecInit(a, b, c)
+ }
+ // Verilog instead of CHIRRTL because the optimizations make it much prettier
+ val verilog = ChiselStage.emitVerilog(new MyModule)
+ verilog should include ("assign x = a;")
+ verilog should include ("assign y = b;")
+ verilog should include ("assign z = c;")
+ }
+
+ it should "support recursive composition of views" in {
+ import Tuple2DataProduct._
+ import SeqDataProduct._
+ import SeqToVec._
+ import HWTuple._
+
+ class MyModule extends Module {
+ val a, b, c, d = IO(Input(UInt(8.W)))
+ val w, x, y, z = IO(Output(UInt(8.W)))
+
+ // A little annoying that we need the type annotation on VecInit to get the implicit conversion to work
+ // Note that one can just use the Seq on the RHS so there is an alternative (may lack discoverability)
+ // We could also overload `VecInit` instead of relying on the implicit conversion
+ Seq((w, x), (y, z)) := VecInit[HWTuple2[UInt, UInt]]((a, b), (c, d))
+ }
+ val verilog = ChiselStage.emitVerilog(new MyModule)
+ verilog should include ("assign w = a;")
+ verilog should include ("assign x = b;")
+ verilog should include ("assign y = c;")
+ verilog should include ("assign z = d;")
+ }
+
+ it should "error if you try to dynamically index a Vec view" in {
+ import SeqDataProduct._
+ import SeqToVec._
+ import Tuple2DataProduct._
+ import HWTuple._
+
+ class MyModule extends Module {
+ val inA, inB = IO(Input(UInt(8.W)))
+ val outA, outB = IO(Output(UInt(8.W)))
+ val idx = IO(Input(UInt(1.W)))
+
+ val a, b, c, d = RegInit(0.U)
+
+ // Dynamic indexing is more of a "generator" in Chisel3 than an individual node
+ val selected = Seq((a, b), (c, d)).apply(idx)
+ selected := (inA, inB)
+ (outA, outB) := selected
+ }
+ (the [InvalidViewException] thrownBy {
+ ChiselStage.emitChirrtl(new MyModule)
+ }).getMessage should include ("Dynamic indexing of Views is not yet supported")
+ }
+
+ it should "error if the mapping is non-total in the view" in {
+ class MyBundle(val foo: UInt, val bar: UInt) extends Bundle
+ implicit val dv = DataView[UInt, MyBundle](_ => new MyBundle(UInt(), UInt()), _ -> _.bar)
+ class MyModule extends Module {
+ val tpe = new MyBundle(UInt(8.W), UInt(8.W))
+ val in = IO(Input(UInt(8.W)))
+ val out = IO(Output(tpe))
+ out := in.viewAs[MyBundle]
+ }
+ val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule))
+ err.toString should include ("View field '_.foo' is missing")
+ }
+
+ it should "error if the mapping is non-total in the target" in {
+ import Tuple2DataProduct._
+ implicit val dv = DataView[(UInt, UInt), UInt](_ => UInt(), _._1 -> _)
+ class MyModule extends Module {
+ val a, b = IO(Input(UInt(8.W)))
+ val out = IO(Output(UInt(8.W)))
+ out := (a, b).viewAs[UInt]
+ }
+ val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule))
+ err.toString should include ("Target field '_._2' is missing")
+ }
+
+ it should "error if the mapping contains Data that are not part of the Target" in {
+ class BundleA extends Bundle {
+ val foo = UInt(8.W)
+ }
+ class BundleB extends Bundle {
+ val fizz = UInt(8.W)
+ val buzz = UInt(8.W)
+ }
+ implicit val dv = DataView[BundleA, BundleB](_ => new BundleB, _.foo -> _.fizz, (_, b) => (3.U, b.buzz))
+ class MyModule extends Module {
+ val in = IO(Input(new BundleA))
+ val out = IO(Output(new BundleB))
+ out := in.viewAs[BundleB]
+ }
+ val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule))
+ err.toString should include ("View mapping must only contain Elements within the Target")
+ }
+
+ it should "error if the mapping contains Data that are not part of the View" in {
+ class BundleA extends Bundle {
+ val foo = UInt(8.W)
+ }
+ class BundleB extends Bundle {
+ val fizz = UInt(8.W)
+ val buzz = UInt(8.W)
+ }
+ implicit val dv = DataView[BundleA, BundleB](_ => new BundleB, _.foo -> _.fizz, (_, b) => (3.U, b.buzz))
+ implicit val dv2 = dv.invert(_ => new BundleA)
+ class MyModule extends Module {
+ val in = IO(Input(new BundleA))
+ val out = IO(Output(new BundleB))
+ out.viewAs[BundleA] := in
+ }
+ val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule))
+ err.toString should include ("View mapping must only contain Elements within the View")
+ }
+
+ it should "error if a view has a width that does not match the target" in {
+ class BundleA extends Bundle {
+ val foo = UInt(8.W)
+ }
+ class BundleB extends Bundle {
+ val bar = UInt(4.W)
+ }
+ implicit val dv = DataView[BundleA, BundleB](_ => new BundleB, _.foo -> _.bar)
+ class MyModule extends Module {
+ val in = IO(Input(new BundleA))
+ val out = IO(Output(new BundleB))
+ out := in.viewAs[BundleB]
+ }
+ val err = the [InvalidViewException] thrownBy ChiselStage.emitChirrtl(new MyModule)
+ val expected = """View field _\.bar UInt<4> has width <4> that is incompatible with target value .+'s width <8>""".r
+ err.getMessage should fullyMatch regex expected
+ }
+
+ it should "error if a view has a known width when the target width is unknown" in {
+ class BundleA extends Bundle {
+ val foo = UInt()
+ }
+ class BundleB extends Bundle {
+ val bar = UInt(4.W)
+ }
+ implicit val dv = DataView[BundleA, BundleB](_ => new BundleB, _.foo -> _.bar)
+ class MyModule extends Module {
+ val in = IO(Input(new BundleA))
+ val out = IO(Output(new BundleB))
+ out := in.viewAs[BundleB]
+ }
+ val err = the [InvalidViewException] thrownBy ChiselStage.emitChirrtl(new MyModule)
+ val expected = """View field _\.bar UInt<4> has width <4> that is incompatible with target value .+'s width <unknown>""".r
+ err.getMessage should fullyMatch regex expected
+ }
+
+ behavior of "PartialDataView"
+
+ it should "still error if the mapping is non-total in the view" in {
+ class MyBundle(val foo: UInt, val bar: UInt) extends Bundle
+ implicit val dv = PartialDataView[UInt, MyBundle](_ => new MyBundle(UInt(), UInt()), _ -> _.bar)
+ class MyModule extends Module {
+ val in = IO(Input(UInt(8.W)))
+ val out = IO(Output(new MyBundle(UInt(8.W), UInt(8.W))))
+ out := in.viewAs[MyBundle]
+ }
+ val err = the [InvalidViewException] thrownBy (ChiselStage.emitVerilog(new MyModule))
+ err.toString should include ("View field '_.foo' is missing")
+ }
+
+ it should "NOT error if the mapping is non-total in the target" in {
+ import Tuple2DataProduct._
+ implicit val dv = PartialDataView[(UInt, UInt), UInt](_ => UInt(), _._2 -> _)
+ class MyModule extends Module {
+ val a, b = IO(Input(UInt(8.W)))
+ val out = IO(Output(UInt(8.W)))
+ out := (a, b).viewAs[UInt]
+ }
+ val verilog = ChiselStage.emitVerilog(new MyModule)
+ verilog should include ("assign out = b;")
+ }
+}
diff --git a/src/test/scala/chiselTests/experimental/DataViewIntegrationSpec.scala b/src/test/scala/chiselTests/experimental/DataViewIntegrationSpec.scala
new file mode 100644
index 00000000..3f149f75
--- /dev/null
+++ b/src/test/scala/chiselTests/experimental/DataViewIntegrationSpec.scala
@@ -0,0 +1,57 @@
+// See LICENSE for license details.
+
+package chiselTests.experimental
+
+import chisel3._
+import chisel3.experimental.{BaseModule, ExtModule}
+import chisel3.experimental.dataview._
+import chisel3.util.{Decoupled, DecoupledIO, Queue, QueueIO, log2Ceil}
+import chiselTests.ChiselFlatSpec
+import firrtl.transforms.DontTouchAnnotation
+
+// Let's put it all together!
+object DataViewIntegrationSpec {
+
+ class QueueIntf[T <: Data](gen: T, entries: Int) extends Bundle {
+ val ports = new QueueIO(gen, entries)
+ // Let's grab a reference to something internal too
+ // Output because can't have directioned and undirectioned stuff
+ val enq_ptr = Output(UInt(log2Ceil(entries).W))
+ }
+
+ // It's not clear if a view of a Module ever _can_ be total since internal nodes are part of the Module
+ implicit def queueView[T <: Data] = PartialDataView[Queue[T], QueueIntf[T]](
+ q => new QueueIntf(q.gen, q.entries),
+ _.io -> _.ports,
+ // Some token internal signal
+ _.enq_ptr.value -> _.enq_ptr
+ )
+
+ object MyQueue {
+ def apply[T <: Data](enq: DecoupledIO[T], n: Int): QueueIntf[T] = {
+ val queue = Module(new Queue[T](enq.bits.cloneType, n))
+ val view = queue.viewAs[QueueIntf[T]]
+ view.ports.enq <> enq
+ view
+ }
+ }
+
+ class MyModule extends Module {
+ val enq = IO(Flipped(Decoupled(UInt(8.W))))
+ val deq = IO(Decoupled(UInt(8.W)))
+
+ val queue = MyQueue(enq, 4)
+ deq <> queue.ports.deq
+ dontTouch(queue.enq_ptr)
+ }
+}
+
+class DataViewIntegrationSpec extends ChiselFlatSpec {
+ import DataViewIntegrationSpec.MyModule
+
+ "Users" should "be able to view and annotate Modules" in {
+ val (_, annos) = getFirrtlAndAnnos(new MyModule)
+ val ts = annos.collect { case DontTouchAnnotation(t) => t.serialize }
+ ts should equal (Seq("~MyModule|Queue>enq_ptr_value"))
+ }
+}
diff --git a/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala b/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala
new file mode 100644
index 00000000..41636da7
--- /dev/null
+++ b/src/test/scala/chiselTests/experimental/DataViewTargetSpec.scala
@@ -0,0 +1,169 @@
+// See LICENSE for license details.
+
+package chiselTests.experimental
+
+import chisel3._
+import chisel3.experimental.dataview._
+import chisel3.experimental.{ChiselAnnotation, annotate}
+import chisel3.stage.ChiselStage
+import chiselTests.ChiselFlatSpec
+
+object DataViewTargetSpec {
+ import firrtl.annotations._
+ private case class DummyAnno(target: ReferenceTarget, id: Int) extends SingleTargetAnnotation[ReferenceTarget] {
+ override def duplicate(n: ReferenceTarget) = this.copy(target = n)
+ }
+ private def mark(d: Data, id: Int) = annotate(new ChiselAnnotation {
+ override def toFirrtl: Annotation = DummyAnno(d.toTarget, id)
+ })
+ private def markAbs(d: Data, id: Int) = annotate(new ChiselAnnotation {
+ override def toFirrtl: Annotation = DummyAnno(d.toAbsoluteTarget, id)
+ })
+}
+
+class DataViewTargetSpec extends ChiselFlatSpec {
+ import DataViewTargetSpec._
+ private val checks: Seq[Data => String] = Seq(
+ _.toTarget.toString,
+ _.toAbsoluteTarget.toString,
+ _.instanceName,
+ _.pathName,
+ _.parentPathName,
+ _.parentModName,
+ )
+
+ // Check helpers
+ private def checkAll(impl: Data, refs: String*): Unit = {
+ refs.size should be (checks.size)
+ for ((check, value) <- checks.zip(refs)) {
+ check(impl) should be (value)
+ }
+ }
+ private def checkSameAs(impl: Data, refs: Data*): Unit =
+ for (ref <- refs) {
+ checkAll(impl, checks.map(_(ref)):_*)
+ }
+
+ behavior of "DataView Naming"
+
+ it should "support views of Elements" in {
+ class MyChild extends Module {
+ val out = IO(Output(UInt(8.W)))
+ val insideView = out.viewAs[UInt]
+ out := 0.U
+ }
+ class MyParent extends Module {
+ val out = IO(Output(UInt(8.W)))
+ val inst = Module(new MyChild)
+ out := inst.out
+ }
+ val m = elaborateAndGetModule(new MyParent)
+ val outsideView = m.inst.out.viewAs[UInt]
+ checkSameAs(m.inst.out, m.inst.insideView, outsideView)
+ }
+
+ it should "support 1:1 mappings of Aggregates and their children" in {
+ class MyBundle extends Bundle {
+ val foo = UInt(8.W)
+ val bars = Vec(2, UInt(8.W))
+ }
+ implicit val dv = DataView[MyBundle, Vec[UInt]](_ => Vec(3, UInt(8.W)), _.foo -> _(0), _.bars(0) -> _(1), _.bars(1) -> _(2))
+ class MyChild extends Module {
+ val out = IO(Output(new MyBundle))
+ val outView = out.viewAs[Vec[UInt]] // Note different type
+ val outFooView = out.foo.viewAs[UInt]
+ val outBarsView = out.bars.viewAs[Vec[UInt]]
+ val outBars0View = out.bars(0).viewAs[UInt]
+ out := 0.U.asTypeOf(new MyBundle)
+ }
+ class MyParent extends Module {
+ val out = IO(Output(new MyBundle))
+ val inst = Module(new MyChild)
+ out := inst.out
+ }
+ val m = elaborateAndGetModule(new MyParent)
+ val outView = m.inst.out.viewAs[Vec[UInt]]// Note different type
+ val outFooView = m.inst.out.foo.viewAs[UInt]
+ val outBarsView = m.inst.out.bars.viewAs[Vec[UInt]]
+ val outBars0View = m.inst.out.bars(0).viewAs[UInt]
+
+ checkSameAs(m.inst.out, m.inst.outView, outView)
+ checkSameAs(m.inst.out.foo, m.inst.outFooView, m.inst.outView(0), outFooView, outView(0))
+ checkSameAs(m.inst.out.bars, m.inst.outBarsView, outBarsView)
+ checkSameAs(m.inst.out.bars(0), m.inst.outBars0View, outBars0View, m.inst.outView(1), outView(1),
+ m.inst.outBarsView(0), outBarsView(0))
+ }
+
+ // Ideally this would work 1:1 but that requires changing the binding
+ it should "support annotation renaming of Aggregate children of Aggregate views" in {
+ class MyBundle extends Bundle {
+ val foo = Vec(2, UInt(8.W))
+ }
+ class MyChild extends Module {
+ val out = IO(Output(new MyBundle))
+ val outView = out.viewAs[MyBundle]
+ mark(out.foo, 0)
+ mark(outView.foo, 1)
+ markAbs(out.foo, 2)
+ markAbs(outView, 3)
+ out := 0.U.asTypeOf(new MyBundle)
+ }
+ class MyParent extends Module {
+ val out = IO(Output(new MyBundle))
+ val inst = Module(new MyChild)
+ out := inst.out
+ }
+ val (_, annos) = getFirrtlAndAnnos(new MyParent)
+ val pairs = annos.collect { case DummyAnno(t, idx) => (idx, t.toString) }.sortBy(_._1)
+ val expected = Seq(
+ 0 -> "~MyParent|MyChild>out.foo",
+ // The child of the view that was itself an Aggregate got split because 1:1 is lacking here
+ 1 -> "~MyParent|MyChild>out.foo[0]",
+ 1 -> "~MyParent|MyChild>out.foo[1]",
+ 2 -> "~MyParent|MyParent/inst:MyChild>out.foo",
+ 3 -> "~MyParent|MyParent/inst:MyChild>out"
+ )
+ pairs should equal (expected)
+ }
+
+ it should "support annotating views that cannot be mapped to a single ReferenceTarget" in {
+ import HWTuple._
+ class MyBundle extends Bundle {
+ val a, b = Input(UInt(8.W))
+ val c, d = Output(UInt(8.W))
+ }
+ // Note that each use of a Tuple as Data causes an implicit conversion creating a View
+ class MyChild extends Module {
+ val io = IO(new MyBundle)
+ (io.c, io.d) := (io.a, io.b)
+ // The type annotations create the views via the implicit conversion
+ val view1: Data = (io.a, io.b)
+ val view2: Data = (io.c, io.d)
+ mark(view1, 0)
+ mark(view2, 1)
+ markAbs(view1, 2)
+ markAbs(view2, 3)
+ mark((io.b, io.d), 4) // Mix it up for fun
+ }
+ class MyParent extends Module {
+ val io = IO(new MyBundle)
+ val inst = Module(new MyChild)
+ io <> inst.io
+ }
+ val (_, annos) = getFirrtlAndAnnos(new MyParent)
+ val pairs = annos.collect { case DummyAnno(t, idx) => (idx, t.toString) }.sorted
+ val expected = Seq(
+ 0 -> "~MyParent|MyChild>io.a",
+ 0 -> "~MyParent|MyChild>io.b",
+ 1 -> "~MyParent|MyChild>io.c",
+ 1 -> "~MyParent|MyChild>io.d",
+ 2 -> "~MyParent|MyParent/inst:MyChild>io.a",
+ 2 -> "~MyParent|MyParent/inst:MyChild>io.b",
+ 3 -> "~MyParent|MyParent/inst:MyChild>io.c",
+ 3 -> "~MyParent|MyParent/inst:MyChild>io.d",
+ 4 -> "~MyParent|MyChild>io.b",
+ 4 -> "~MyParent|MyChild>io.d",
+ )
+ pairs should equal (expected)
+ }
+}
diff --git a/src/test/scala/chiselTests/experimental/ModuleDataProductSpec.scala b/src/test/scala/chiselTests/experimental/ModuleDataProductSpec.scala
new file mode 100644
index 00000000..78986517
--- /dev/null
+++ b/src/test/scala/chiselTests/experimental/ModuleDataProductSpec.scala
@@ -0,0 +1,91 @@
+// See LICENSE for license details.
+
+package chiselTests.experimental
+
+import chisel3._
+import chisel3.experimental.{BaseModule, ExtModule}
+import chisel3.experimental.dataview.DataProduct
+import chiselTests.ChiselFlatSpec
+
+object ModuleDataProductSpec {
+ class MyBundle extends Bundle {
+ val foo = UInt(8.W)
+ val bar = UInt(8.W)
+ }
+ trait MyIntf extends BaseModule {
+ val in = IO(Input(new MyBundle))
+ val out = IO(Output(new MyBundle))
+ }
+ class Passthrough extends RawModule {
+ val in = IO(Input(UInt(8.W)))
+ val out = IO(Output(UInt(8.W)))
+ out := in
+ }
+ class MyUserModule extends Module with MyIntf {
+ val inst = Module(new Passthrough)
+ inst.in := in.foo
+ val r = RegNext(in)
+ out := r
+ }
+
+ class MyExtModule extends ExtModule with MyIntf
+ class MyExtModuleWrapper extends RawModule with MyIntf {
+ val inst = Module(new MyExtModule)
+ inst.in := in
+ out := inst.out
+ }
+}
+
+class ModuleDataProductSpec extends ChiselFlatSpec {
+ import ModuleDataProductSpec._
+
+ behavior of "DataProduct"
+
+ it should "work for UserModules (recursively)" in {
+ val m = elaborateAndGetModule(new MyUserModule)
+ val expected = Seq(
+ m.clock -> "m.clock",
+ m.reset -> "m.reset",
+ m.in -> "m.in",
+ m.in.foo -> "m.in.foo",
+ m.in.bar -> "m.in.bar",
+ m.out -> "m.out",
+ m.out.foo -> "m.out.foo",
+ m.out.bar -> "m.out.bar",
+ m.r -> "m.r",
+ m.r.foo -> "m.r.foo",
+ m.r.bar -> "m.r.bar",
+ m.inst.in -> "m.inst.in",
+ m.inst.out -> "m.inst.out"
+ )
+
+ val impl = implicitly[DataProduct[MyUserModule]]
+ val set = impl.dataSet(m)
+ for ((d, _) <- expected) {
+ set(d) should be (true)
+ }
+ val it = impl.dataIterator(m, "m")
+ it.toList should contain theSameElementsAs (expected)
+ }
+
+ it should "work for (wrapped) ExtModules" in {
+ val m = elaborateAndGetModule(new MyExtModuleWrapper).inst
+ val expected = Seq(
+ m.in -> "m.in",
+ m.in.foo -> "m.in.foo",
+ m.in.bar -> "m.in.bar",
+ m.out -> "m.out",
+ m.out.foo -> "m.out.foo",
+ m.out.bar -> "m.out.bar"
+ )
+
+ val impl = implicitly[DataProduct[MyExtModule]]
+ val set = impl.dataSet(m)
+ for ((d, _) <- expected) {
+ set(d) should be (true)
+ }
+ val it = impl.dataIterator(m, "m")
+ it.toList should contain theSameElementsAs (expected)
+ }
+
+}
diff --git a/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala b/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala
index 35e354a6..99c0f7c0 100644
--- a/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala
+++ b/src/test/scala/chiselTests/stage/ChiselOptionsViewSpec.scala
@@ -4,6 +4,7 @@ package chiselTests.stage
import firrtl.options.Viewer.view
+import firrtl.RenameMap
import chisel3.stage._
import chisel3.internal.firrtl.Circuit
@@ -15,7 +16,7 @@ class ChiselOptionsViewSpec extends AnyFlatSpec with Matchers {
behavior of ChiselOptionsView.getClass.getName
it should "construct a view from an AnnotationSeq" in {
- val bar = Circuit("bar", Seq.empty, Seq.empty)
+ val bar = Circuit("bar", Seq.empty, Seq.empty, RenameMap())
val annotations = Seq(
NoRunFirrtlCompilerAnnotation,
PrintFullStackTraceAnnotation,