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/chisel3/internal | |
| 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/chisel3/internal')
5 files changed, 83 insertions, 11 deletions
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)) +} |
