diff options
| author | Jack Koenig | 2021-08-12 17:04:11 -0700 |
|---|---|---|
| committer | GitHub | 2021-08-12 17:04:11 -0700 |
| commit | 1ceb974c55c6785c21ab3934fa750ade0702e276 (patch) | |
| tree | bc8559e8ef558e3216ecc5612593f5904f9a6b60 /core/src/main/scala | |
| parent | 713de6b823d8707246935b9e31ed2fbafeaeca32 (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.
Diffstat (limited to 'core/src/main/scala')
| -rw-r--r-- | core/src/main/scala/chisel3/Aggregate.scala | 42 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/Data.scala | 83 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/Element.scala | 4 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/Module.scala | 42 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/ModuleAspect.scala | 4 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/RawModule.scala | 31 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala | 72 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/experimental/dataview/DataView.scala | 154 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/experimental/dataview/package.scala | 251 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/BiConnect.scala | 6 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/Binding.scala | 9 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/Builder.scala | 66 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/MonoConnect.scala | 6 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/firrtl/IR.scala | 7 |
14 files changed, 705 insertions, 72 deletions
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)) +} |
