diff options
Diffstat (limited to 'core/src/main/scala/chisel3/experimental')
12 files changed, 1220 insertions, 24 deletions
diff --git a/core/src/main/scala/chisel3/experimental/Analog.scala b/core/src/main/scala/chisel3/experimental/Analog.scala index df76fd70..6cca81f5 100644 --- a/core/src/main/scala/chisel3/experimental/Analog.scala +++ b/core/src/main/scala/chisel3/experimental/Analog.scala @@ -45,7 +45,8 @@ final class Analog private (private[chisel3] val width: Width) extends Element { // Define setter/getter pairing // Analog can only be bound to Ports and Wires (and Unbound) - private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection) { + private[chisel3] override def bind(target: Binding, parentDirection: SpecifiedDirection): Unit = { + _parent.foreach(_.addId(this)) SpecifiedDirection.fromParent(parentDirection, specifiedDirection) match { case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => case x => throwException(s"Analog may not have explicit direction, got '$x'") @@ -55,6 +56,7 @@ final class Analog private (private[chisel3] val width: Width) extends Element { case ChildBinding(parent) => parent.topBinding // See https://github.com/freechipsproject/chisel3/pull/946 case SampleElementBinding(parent) => parent.topBinding + case a: MemTypeBinding[_] => a } targetTopBinding match { @@ -82,4 +84,3 @@ final class Analog private (private[chisel3] val width: Width) extends Element { object Analog { def apply(width: Width): Analog = new Analog(width) } - diff --git a/core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala b/core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala new file mode 100644 index 00000000..55dd8505 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/dataview/DataProduct.scala @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.dataview + +import chisel3.experimental.BaseModule +import chisel3.{Data, getRecursiveFields} + +import scala.annotation.implicitNotFound + +/** Typeclass interface for getting elements of type [[Data]] + * + * This is needed for validating [[DataView]]s targeting type `A`. + * Can be thought of as "can be the Target of a DataView". + * + * Chisel provides some implementations in [[DataProduct$ object DataProduct]] that are available + * by default in the implicit scope. + * + * @tparam A Type that has elements of type [[Data]] + * @see [[https://www.chisel-lang.org/chisel3/docs/explanations/dataview#dataproduct Detailed Documentation]] + */ +@implicitNotFound("Could not find implicit value for DataProduct[${A}].\n" + + "Please see https://www.chisel-lang.org/chisel3/docs/explanations/dataview#dataproduct") +trait DataProduct[-A] { + /** Provides [[Data]] elements within some containing object + * + * @param a Containing object + * @param path Hierarchical path to current signal (for error reporting) + * @return Data elements and associated String paths (Strings for error reporting only!) + */ + def dataIterator(a: A, path: String): Iterator[(Data, String)] + + /** Returns a checker to test if the containing object contains a `Data` object + * @note Implementers may want to override if iterating on all `Data` is expensive for `A` and `A` + * will primarily be used in `PartialDataViews` + * @note The returned value is a function, not a true Set, but is describing the functionality of + * Set containment + * @param a Containing object + * @return A checker that itself returns True if a given `Data` is contained in `a` + * as determined by an `==` test + */ + def dataSet(a: A): Data => Boolean = dataIterator(a, "").map(_._1).toSet +} + +/** Encapsulating object for automatically provided implementations of [[DataProduct]] + * + * @note DataProduct implementations provided in this object are available in the implicit scope + */ +object DataProduct { + /** [[DataProduct]] implementation for [[Data]] */ + implicit val dataDataProduct: DataProduct[Data] = new DataProduct[Data] { + def dataIterator(a: Data, path: String): Iterator[(Data, String)] = + getRecursiveFields.lazily(a, path).iterator + } + + /** [[DataProduct]] implementation for [[BaseModule]] */ + implicit val userModuleDataProduct: DataProduct[BaseModule] = new DataProduct[BaseModule] { + def dataIterator(a: BaseModule, path: String): Iterator[(Data, String)] = { + a.getIds.iterator.flatMap { + case d: Data if d.getOptionRef.isDefined => // Using ref to decide if it's truly hardware in the module + Seq(d -> s"${path}.${d.instanceName}") + case b: BaseModule => dataIterator(b, s"$path.${b.instanceName}") + case _ => Seq.empty + } + } + // Overridden for performance + override def dataSet(a: BaseModule): Data => Boolean = { + val lastId = a._lastId // Not cheap to compute + // Return a function + e => e._id > a._id && e._id <= lastId + } + } +} diff --git a/core/src/main/scala/chisel3/experimental/dataview/DataView.scala b/core/src/main/scala/chisel3/experimental/dataview/DataView.scala new file mode 100644 index 00000000..caf004c2 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/dataview/DataView.scala @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.dataview + +import chisel3._ +import chisel3.internal.sourceinfo.SourceInfo +import scala.reflect.runtime.universe.WeakTypeTag + +import annotation.implicitNotFound + + +/** Mapping between a target type `T` and a view type `V` + * + * Enables calling `.viewAs[T]` on instances of the target type. + * + * ==Detailed documentation== + * - [[https://www.chisel-lang.org/chisel3/docs/explanations/dataview Explanation]] + * - [[https://www.chisel-lang.org/chisel3/docs/cookbooks/dataview Cookbook]] + * + * @example {{{ + * class Foo(val w: Int) extends Bundle { + * val a = UInt(w.W) + * } + * class Bar(val w: Int) extends Bundle { + * val b = UInt(w.W) + * } + * // DataViews are created using factory methods in the companion object + * implicit val view = DataView[Foo, Bar]( + * // The first argument is a function constructing a Foo from a Bar + * foo => new Bar(foo.w) + * // The remaining arguments are a variable number of field pairings + * _.a -> _.b + * ) + * }}} + * + * @tparam T Target type (must have an implementation of [[DataProduct]]) + * @tparam V View type + * @see [[DataView$ object DataView]] for factory methods + * @see [[PartialDataView object PartialDataView]] for defining non-total `DataViews` + */ +@implicitNotFound("Could not find implicit value for DataView[${T}, ${V}].\n" + + "Please see https://www.chisel-lang.org/chisel3/docs/explanations/dataview") +sealed class DataView[T : DataProduct, V <: Data] private[chisel3] ( + /** Function constructing an object of the View type from an object of the Target type */ + private[chisel3] val mkView: T => V, + /** Function that returns corresponding fields of the target and view */ + private[chisel3] val mapping: (T, V) => Iterable[(Data, Data)], + // Aliasing this with a def below to make the ScalaDoc show up for the field + _total: Boolean +)( + implicit private[chisel3] val sourceInfo: SourceInfo +) { + /** Indicates if the mapping contains every field of the target */ + def total: Boolean = _total + + override def toString: String = { + val base = sourceInfo.makeMessage(x => x) + val loc = if (base.nonEmpty) base else "@unknown" + val name = if (total) "DataView" else "PartialDataView" + s"$name(defined $loc)" + } + + /** Compose two `DataViews` together to construct a view from the target of this `DataView` to the + * view type of the second `DataView` + * + * @param g a DataView from `V` to new view-type `V2` + * @tparam V2 View type of `DataView` `g` + * @return a new `DataView` from the original `T` to new view-type `V2` + */ + def andThen[V2 <: Data](g: DataView[V, V2])(implicit sourceInfo: SourceInfo): DataView[T, V2] = { + val self = this + // We have to pass the DataProducts and DataViews manually to .viewAs below + val tdp = implicitly[DataProduct[T]] + val vdp = implicitly[DataProduct[V]] + new DataView[T, V2]( + t => g.mkView(mkView(t)), + { case (t, v2) => List(t.viewAs[V](tdp, self).viewAs[V2](vdp, g) -> v2) }, + this.total && g.total + ) { + override def toString: String = s"$self andThen $g" + } + } +} + +/** Factory methods for constructing [[DataView]]s, see class for example use */ +object DataView { + + /** Default factory method, alias for [[pairs]] */ + def apply[T : DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] = + DataView.pairs(mkView, pairs: _*) + + /** Construct [[DataView]]s with pairs of functions from the target and view to corresponding fields */ + def pairs[T : DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] = + mapping(mkView: T => V, swizzle(pairs)) + + /** More general factory method for complex mappings */ + def mapping[T : DataProduct, V <: Data](mkView: T => V, mapping: (T, V) => Iterable[(Data, Data)])(implicit sourceInfo: SourceInfo): DataView[T, V] = + new DataView[T, V](mkView, mapping, _total = true) + + /** Provides `invert` for invertible [[DataView]]s + * + * This must be done as an extension method because it applies an addition constraint on the `Target` + * type parameter, namely that it must be a subtype of [[Data]]. + * + * @note [[PartialDataView]]s are **not** invertible and will result in an elaboration time exception + */ + implicit class InvertibleDataView[T <: Data : WeakTypeTag, V <: Data : WeakTypeTag](view: DataView[T, V]) { + def invert(mkView: V => T): DataView[V, T] = { + // It would've been nice to make this a compiler error, but it's unclear how to make that work. + // We tried having separate TotalDataView and PartialDataView and only defining inversion for + // TotalDataView. For some reason, implicit resolution wouldn't invert TotalDataViews. This is + // probably because it was looking for the super-type DataView and since invertDataView was + // only defined on TotalDataView, it wasn't included in implicit resolution. Thus we end up + // with a runtime check. + if (!view.total) { + val tt = implicitly[WeakTypeTag[T]].tpe + val vv = implicitly[WeakTypeTag[V]].tpe + val msg = s"Cannot invert '$view' as it is non-total.\n Try providing a DataView[$vv, $tt]." + + s"\n Please see https://www.chisel-lang.org/chisel3/docs/explanations/dataview." + throw InvalidViewException(msg) + } + implicit val sourceInfo = view.sourceInfo + new DataView[V, T](mkView, swapArgs(view.mapping), view.total) + } + } + + private[dataview] def swizzle[A, B, C, D](fs: Iterable[(A, B) => (C, D)]): (A, B) => Iterable[(C, D)] = { + case (a, b) => fs.map(f => f(a, b)) + } + + private def swapArgs[A, B, C, D](f: (A, B) => Iterable[(C, D)]): (B, A) => Iterable[(D, C)] = { + case (b, a) => f(a, b).map(_.swap) + } + + /** All Chisel Data are viewable as their own type */ + implicit def identityView[A <: Data](implicit sourceInfo: SourceInfo): DataView[A, A] = + DataView[A, A](chiselTypeOf.apply, { case (x, y) => (x, y) }) +} + +/** Factory methods for constructing non-total [[DataView]]s */ +object PartialDataView { + + /** Default factory method, alias for [[pairs]] */ + def apply[T: DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] = + PartialDataView.pairs(mkView, pairs: _*) + + /** Construct [[DataView]]s with pairs of functions from the target and view to corresponding fields */ + def pairs[T: DataProduct, V <: Data](mkView: T => V, pairs: ((T, V) => (Data, Data))*)(implicit sourceInfo: SourceInfo): DataView[T, V] = + mapping(mkView, DataView.swizzle(pairs)) + + /** More general factory method for complex mappings */ + def mapping[T: DataProduct, V <: Data](mkView: T => V, mapping: (T, V) => Iterable[(Data, Data)])(implicit sourceInfo: SourceInfo): DataView[T, V] = + new DataView[T, V](mkView, mapping, _total = false) +} diff --git a/core/src/main/scala/chisel3/experimental/dataview/package.scala b/core/src/main/scala/chisel3/experimental/dataview/package.scala new file mode 100644 index 00000000..1acf43e1 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/dataview/package.scala @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental + +import chisel3._ +import chisel3.internal._ +import chisel3.internal.sourceinfo.SourceInfo + +import scala.annotation.{implicitNotFound, tailrec} +import scala.collection.mutable +import scala.collection.immutable.LazyList // Needed for 2.12 alias + +package object dataview { + case class InvalidViewException(message: String) extends ChiselException(message) + + /** Provides `viewAs` for types that have an implementation of [[DataProduct]] + * + * Calling `viewAs` also requires an implementation of [[DataView]] for the target type + */ + implicit class DataViewable[T](target: T) { + def viewAs[V <: Data](implicit dataproduct: DataProduct[T], dataView: DataView[T, V]): V = { + // TODO put a try catch here for ExpectedHardwareException and perhaps others + // It's likely users will accidentally use chiselTypeOf or something that may error, + // The right thing to use is DataMirror...chiselTypeClone because of composition with DataView.andThen + // Another option is that .andThen could give a fake binding making chiselTypeOfs in the user code safe + val result: V = dataView.mkView(target) + requireIsChiselType(result, "viewAs") + + doBind(target, result, dataView) + + // Setting the parent marks these Data as Views + result.setAllParents(Some(ViewParent)) + // The names of views do not matter except for when a view is annotated. For Views that correspond + // To a single Data, we just forward the name of the Target. For Views that correspond to more + // than one Data, we return this assigned name but rename it in the Convert stage + result.forceName(None, "view", Builder.viewNamespace) + result + } + } + + // This private type alias lets us provide a custom error message for misuing the .viewAs for upcasting Bundles + @implicitNotFound("${A} is not a subtype of ${B}! Did you mean .viewAs[${B}]? " + + "Please see https://www.chisel-lang.org/chisel3/docs/cookbooks/dataview") + private type SubTypeOf[A, B] = A <:< B + + /** Provides `viewAsSupertype` for subclasses of [[Bundle]] */ + implicit class BundleUpcastable[T <: Bundle](target: T) { + /** View a [[Bundle]] or [[Record]] as a parent type (upcast) */ + def viewAsSupertype[V <: Bundle](proto: V)(implicit ev: SubTypeOf[T, V], sourceInfo: SourceInfo): V = { + implicit val dataView = PartialDataView.mapping[T, V](_ => proto, { + case (a, b) => + val aElts = a.elements + val bElts = b.elements + val bKeys = bElts.keySet + val keys = aElts.keysIterator.filter(bKeys.contains) + keys.map(k => aElts(k) -> bElts(k)).toSeq + }) + target.viewAs[V] + } + } + + private def nonTotalViewException(dataView: DataView[_, _], target: Any, view: Data, targetFields: Seq[String], viewFields: Seq[String]) = { + def missingMsg(name: String, fields: Seq[String]): Option[String] = { + val str = fields.mkString(", ") + fields.size match { + case 0 => None + case 1 => Some(s"$name field '$str' is missing") + case _ => Some(s"$name fields '$str' are missing") + } + } + val vs = missingMsg("view", viewFields) + val ts = missingMsg("target", targetFields) + val reasons = (ts ++ vs).mkString(" and ").capitalize + val suggestion = if (ts.nonEmpty) "\n If the view *should* be non-total, try a 'PartialDataView'." else "" + val msg = s"Viewing $target as $view is non-Total!\n $reasons.\n DataView used is $dataView.$suggestion" + throw InvalidViewException(msg) + } + + // TODO should this be moved to class Aggregate / can it be unified with Aggregate.bind? + private def doBind[T : DataProduct, V <: Data](target: T, view: V, dataView: DataView[T, V]): Unit = { + val mapping = dataView.mapping(target, view) + val total = dataView.total + // Lookups to check the mapping results + val viewFieldLookup: Map[Data, String] = getRecursiveFields(view, "_").toMap + val targetContains: Data => Boolean = implicitly[DataProduct[T]].dataSet(target) + + // Resulting bindings for each Element of the View + val childBindings = + new mutable.HashMap[Data, mutable.ListBuffer[Element]] ++ + viewFieldLookup.view + .collect { case (elt: Element, _) => elt } + .map(_ -> new mutable.ListBuffer[Element]) + + def viewFieldName(d: Data): String = + viewFieldLookup.get(d).map(_ + " ").getOrElse("") + d.toString + + // Helper for recording the binding of each + def onElt(te: Element, ve: Element): Unit = { + // TODO can/should we aggregate these errors? + def err(name: String, arg: Data) = + throw InvalidViewException(s"View mapping must only contain Elements within the $name, got $arg") + + // The elements may themselves be views, look through the potential chain of views for the Elements + // that are actually members of the target or view + val tex = unfoldView(te).find(targetContains).getOrElse(err("Target", te)) + val vex = unfoldView(ve).find(viewFieldLookup.contains).getOrElse(err("View", ve)) + + if (tex.getClass != vex.getClass) { + val fieldName = viewFieldName(vex) + throw InvalidViewException(s"Field $fieldName specified as view of non-type-equivalent value $tex") + } + // View width must be unknown or match target width + if (vex.widthKnown && vex.width != tex.width) { + def widthAsString(x: Element) = x.widthOption.map("<" + _ + ">").getOrElse("<unknown>") + val fieldName = viewFieldName(vex) + val vwidth = widthAsString(vex) + val twidth = widthAsString(tex) + throw InvalidViewException(s"View field $fieldName has width ${vwidth} that is incompatible with target value $tex's width ${twidth}") + } + childBindings(vex) += tex + } + + mapping.foreach { + // Special cased because getMatchedFields checks typeEquivalence on Elements (and is used in Aggregate path) + // Also saves object allocations on common case of Elements + case (ae: Element, be: Element) => onElt(ae, be) + + case (aa: Aggregate, ba: Aggregate) => + if (!ba.typeEquivalent(aa)) { + val fieldName = viewFieldLookup(ba) + throw InvalidViewException(s"field $fieldName specified as view of non-type-equivalent value $aa") + } + getMatchedFields(aa, ba).foreach { + case (aelt: Element, belt: Element) => onElt(aelt, belt) + case _ => // Ignore matching of Aggregates + } + } + + // Errors in totality of the View, use var List to keep fast path cheap (no allocation) + var viewNonTotalErrors: List[Data] = Nil + var targetNonTotalErrors: List[String] = Nil + + val targetSeen: Option[mutable.Set[Data]] = if (total) Some(mutable.Set.empty[Data]) else None + + val resultBindings = childBindings.map { case (data, targets) => + val targetsx = targets match { + case collection.Seq(target: Element) => target + case collection.Seq() => + viewNonTotalErrors = data :: viewNonTotalErrors + data.asInstanceOf[Element] // Return the Data itself, will error after this map, cast is safe + case x => + throw InvalidViewException(s"Got $x, expected Seq(_: Direct)") + } + // TODO record and report aliasing errors + targetSeen.foreach(_ += targetsx) + data -> targetsx + }.toMap + + // Check for totality of Target + targetSeen.foreach { seen => + val lookup = implicitly[DataProduct[T]].dataIterator(target, "_") + for (missed <- lookup.collect { case (d: Element, name) if !seen(d) => name }) { + targetNonTotalErrors = missed :: targetNonTotalErrors + } + } + if (viewNonTotalErrors != Nil || targetNonTotalErrors != Nil) { + val viewErrors = viewNonTotalErrors.map(f => viewFieldLookup.getOrElse(f, f.toString)) + nonTotalViewException(dataView, target, view, targetNonTotalErrors, viewErrors) + } + + view match { + case elt: Element => view.bind(ViewBinding(resultBindings(elt))) + case agg: Aggregate => + // We record total Data mappings to provide a better .toTarget + val topt = target match { + case d: Data if total => Some(d) + case _ => + // Record views that don't have the simpler .toTarget for later renaming + Builder.unnamedViews += view + None + } + // TODO We must also record children as unnamed, some could be namable but this requires changes to the Binding + getRecursiveFields.lazily(view, "_").foreach { + case (agg: Aggregate, _) if agg != view => + Builder.unnamedViews += agg + case _ => // Do nothing + } + agg.bind(AggregateViewBinding(resultBindings, topt)) + } + } + + // Traces an Element that may (or may not) be a view until it no longer maps + // Inclusive of the argument + private def unfoldView(elt: Element): LazyList[Element] = { + def rec(e: Element): LazyList[Element] = e.topBindingOpt match { + case Some(ViewBinding(target)) => target #:: rec(target) + case Some(AggregateViewBinding(mapping, _)) => + val target = mapping(e) + target #:: rec(target) + case Some(_) | None => LazyList.empty + } + elt #:: rec(elt) + } + + // Safe for all Data + private[chisel3] def isView(d: Data): Boolean = d._parent.contains(ViewParent) + + /** Turn any [[Element]] that could be a View into a concrete Element + * + * This is the fundamental "unwrapping" or "tracing" primitive operation for handling Views within + * Chisel. + */ + private[chisel3] def reify(elt: Element): Element = + reify(elt, elt.topBinding) + + /** Turn any [[Element]] that could be a View into a concrete Element + * + * This is the fundamental "unwrapping" or "tracing" primitive operation for handling Views within + * Chisel. + */ + @tailrec private[chisel3] def reify(elt: Element, topBinding: TopBinding): Element = + topBinding match { + case ViewBinding(target) => reify(target, elt.topBinding) + case _ => elt + } + + /** Determine the target of a View if it is a single Target + * + * @note An Aggregate may be a view of unrelated [[Data]] (eg. like a Seq or tuple) and thus this + * there is no single Data representing the Target and this function will return None + * @return The single Data target of this view or None if a single Data doesn't exist + */ + private[chisel3] def reifySingleData(data: Data): Option[Data] = { + val candidate: Option[Data] = + data.binding.collect { // First check if this is a total mapping of an Aggregate + case AggregateViewBinding(_, Some(t)) => t + }.orElse { // Otherwise look via top binding + data.topBindingOpt match { + case None => None + case Some(ViewBinding(target)) => Some(target) + case Some(AggregateViewBinding(lookup, _)) => lookup.get(data) + case Some(_) => None + } + } + candidate.flatMap { d => + // Candidate may itself be a view, keep tracing in those cases + if (isView(d)) reifySingleData(d) else Some(d) + } + } + +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala new file mode 100644 index 00000000..0cc3d131 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import scala.language.experimental.macros +import chisel3._ + +import scala.collection.mutable.HashMap +import chisel3.internal.{Builder, DynamicContext} +import chisel3.internal.sourceinfo.{DefinitionTransform, DefinitionWrapTransform, SourceInfo} +import chisel3.experimental.BaseModule +import chisel3.internal.BaseModule.IsClone +import firrtl.annotations.{IsModule, ModuleTarget} + +/** User-facing Definition type. + * Represents a definition of an object of type [[A]] which are marked as @instantiable + * Can be created using Definition.apply method. + * + * These definitions are then used to create multiple [[Instance]]s. + * + * @param cloned The internal representation of the instance, which may be either be directly the object, or a clone of an object + */ +case class Definition[+A] private[chisel3] (private[chisel3] cloned: Either[A, IsClone[A]]) extends IsLookupable { + private[chisel3] def proto: A = cloned match { + case Left(value: A) => value + case Right(i: IsClone[A]) => i._proto + } + /** Used by Chisel's internal macros. DO NOT USE in your normal Chisel code!!! + * Instead, mark the field you are accessing with [[@public]] + * + * Given a selector function (that) which selects a member from the original, return the + * corresponding member from the instance. + * + * Our @instantiable and @public macros generate the calls to this apply method + * + * By calling this function, we summon the proper Lookupable typeclass from our implicit scope. + * + * @param that a user-specified lookup function + * @param lookup typeclass which contains the correct lookup function, based on the types of A and B + * @param macroGenerated a value created in the macro, to make it harder for users to use this API + */ + def _lookup[B, C](that: A => B)(implicit lookup: Lookupable[B], macroGenerated: chisel3.internal.MacroGenerated): lookup.C = { + lookup.definitionLookup(that, this) + } + + /** Updated by calls to [[apply]], to avoid recloning returned Data's */ + private [chisel3] val cache = HashMap[Data, Data]() + + + /** @return the context of any Data's return from inside the instance */ + private[chisel3] def getInnerDataContext: Option[BaseModule] = proto match { + case value: BaseModule => + val newChild = Module.do_apply(new internal.BaseModule.DefinitionClone(value))(chisel3.internal.sourceinfo.UnlocatableSourceInfo, chisel3.ExplicitCompileOptions.Strict) + newChild._circuit = value._circuit.orElse(Some(value)) + newChild._parent = None + Some(newChild) + case value: IsInstantiable => None + } + +} + +/** Factory methods for constructing [[Definition]]s */ +object Definition extends SourceInfoDoc { + implicit class DefinitionBaseModuleExtensions[T <: BaseModule](d: Definition[T]) { + /** If this is an instance of a Module, returns the toTarget of this instance + * @return target of this instance + */ + def toTarget: ModuleTarget = d.proto.toTarget + + /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance + * @return absoluteTarget of this instance + */ + def toAbsoluteTarget: IsModule = d.proto.toAbsoluteTarget + } + /** A construction method to build a Definition of a Module + * + * @param proto the Module being defined + * + * @return the input module as a Definition + */ + def apply[T <: BaseModule with IsInstantiable](proto: => T): Definition[T] = macro DefinitionTransform.apply[T] + + /** A construction method to build a Definition of a Module + * + * @param bc the Module being defined + * + * @return the input module as a Definition + */ + def do_apply[T <: BaseModule with IsInstantiable](proto: => T) (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Definition[T] = { + val dynamicContext = new DynamicContext(Nil) + Builder.globalNamespace.copyTo(dynamicContext.globalNamespace) + dynamicContext.inDefinition = true + val (ir, module) = Builder.build(Module(proto), dynamicContext) + Builder.components ++= ir.components + Builder.annotations ++= ir.annotations + module._circuit = Builder.currentModule + dynamicContext.globalNamespace.copyTo(Builder.globalNamespace) + new Definition(Left(module)) + } +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala new file mode 100644 index 00000000..9b17bfce --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import scala.collection.mutable.{ArrayBuffer, HashMap} +import scala.language.experimental.macros +import chisel3._ +import chisel3.internal.BaseModule.{InstantiableClone, IsClone, ModuleClone} +import chisel3.internal.sourceinfo.{InstanceTransform, SourceInfo} +import chisel3.experimental.BaseModule +import firrtl.annotations.IsModule + +/** User-facing Instance type. + * Represents a unique instance of type [[A]] which are marked as @instantiable + * Can be created using Instance.apply method. + * + * @param cloned The internal representation of the instance, which may be either be directly the object, or a clone of an object + */ +case class Instance[+A] private [chisel3] (private[chisel3] cloned: Either[A, IsClone[A]]) { + + /** Returns the original object which is instantiated here. + * If this is an instance of a clone, return that clone's original proto + * + * @return the original object which was instantiated + */ + private[chisel3] def proto: A = cloned match { + case Left(value: A) => value + case Right(i: IsClone[A]) => i._proto + } + + /** @return the context of any Data's return from inside the instance */ + private[chisel3] def getInnerDataContext: Option[BaseModule] = cloned match { + case Left(value: BaseModule) => Some(value) + case Left(value: IsInstantiable) => None + case Right(i: BaseModule) => Some(i) + case Right(i: InstantiableClone[_]) => i._parent + } + + /** @return the context this instance. Note that for non-module clones, getInnerDataContext will be the same as getClonedParent */ + private[chisel3] def getClonedParent: Option[BaseModule] = cloned match { + case Left(value: BaseModule) => value._parent + case Right(i: BaseModule) => i._parent + case Right(i: InstantiableClone[_]) => i._parent + } + + /** Updated by calls to [[apply]], to avoid recloning returned Data's */ + private [chisel3] val cache = HashMap[Data, Data]() + + /** Used by Chisel's internal macros. DO NOT USE in your normal Chisel code!!! + * Instead, mark the field you are accessing with [[@public]] + * + * Given a selector function (that) which selects a member from the original, return the + * corresponding member from the instance. + * + * Our @instantiable and @public macros generate the calls to this apply method + * + * By calling this function, we summon the proper Lookupable typeclass from our implicit scope. + * + * @param that a user-specified lookup function + * @param lookup typeclass which contains the correct lookup function, based on the types of A and B + * @param macroGenerated a value created in the macro, to make it harder for users to use this API + */ + def _lookup[B, C](that: A => B)(implicit lookup: Lookupable[B], macroGenerated: chisel3.internal.MacroGenerated): lookup.C = { + lookup.instanceLookup(that, this) + } + + /** Returns the definition of this Instance */ + def toDefinition: Definition[A] = new Definition(Left(proto)) + +} + +/** Factory methods for constructing [[Instance]]s */ +object Instance extends SourceInfoDoc { + implicit class InstanceBaseModuleExtensions[T <: BaseModule](i: Instance[T]) { + /** If this is an instance of a Module, returns the toTarget of this instance + * @return target of this instance + */ + def toTarget: IsModule = i.cloned match { + case Left(x: BaseModule) => x.getTarget + case Right(x: IsClone[_] with BaseModule) => x.getTarget + } + + /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance + * @return absoluteTarget of this instance + */ + def toAbsoluteTarget: IsModule = i.cloned match { + case Left(x) => x.toAbsoluteTarget + case Right(x: IsClone[_] with BaseModule) => x.toAbsoluteTarget + } + + } + /** A constructs an [[Instance]] from a [[Definition]] + * + * @param definition the Module being created + * @return an instance of the module definition + */ + def apply[T <: BaseModule with IsInstantiable](definition: Definition[T]): Instance[T] = macro InstanceTransform.apply[T] + + /** A constructs an [[Instance]] from a [[Definition]] + * + * @param definition the Module being created + * @return an instance of the module definition + */ + def do_apply[T <: BaseModule with IsInstantiable](definition: Definition[T])(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Instance[T] = { + val ports = experimental.CloneModuleAsRecord(definition.proto) + val clone = ports._parent.get.asInstanceOf[ModuleClone[T]] + clone._madeFromDefinition = true + new Instance(Right(clone)) + } + +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala new file mode 100644 index 00000000..26ba0286 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +/** While this is public, it is not recommended for users to extend directly. + * Instead, use the [[@instantiable]] annotation on your trait or class. + * + * This trait indicates whether a class can be returned from an Instance. + * + */ +trait IsInstantiable + +object IsInstantiable { + implicit class IsInstantiableExtensions[T <: IsInstantiable](i: T) { + def toInstance: Instance[T] = new Instance(Left(i)) + } +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala new file mode 100644 index 00000000..37d29a43 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/IsLookupable.scala @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +/** A User-extendable trait to mark metadata-containers, e.g. parameter case classes, as valid to return unchanged + * from an instance. + * + * This should only be true of the metadata returned is identical for ALL instances! + * + * @example For instances of the same proto, metadata or other construction parameters + * may be useful to access outside of the instance construction. For parameters that are + * the same for all instances, we should mark it as IsLookupable + * {{{ + * case class Params(debugMessage: String) extends IsLookupable + * class MyModule(p: Params) extends MultiIOModule { + * printf(p.debugMessage) + * } + * val myParams = Params("Hello World") + * val definition = Definition(new MyModule(myParams)) + * val i0 = Instance(definition) + * val i1 = Instance(definition) + * require(i0.p == i1.p) // p is only accessable because it extends IsLookupable + * }}} + */ +trait IsLookupable diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala new file mode 100644 index 00000000..b9617723 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import chisel3.experimental.BaseModule +import chisel3.internal.sourceinfo.SourceInfo +import chisel3.internal.BaseModule.{InstanceClone, InstantiableClone, IsClone, ModuleClone} + +import scala.annotation.implicitNotFound +import scala.collection.mutable.HashMap +import chisel3._ +import chisel3.experimental.dataview.{isView, reify, reifySingleData} +import chisel3.internal.firrtl.{Arg, ILit, Index, Slot, ULit} +import chisel3.internal.{AggregateViewBinding, Builder, ChildBinding, ViewBinding, ViewParent, throwException} + +/** Represents lookup typeclass to determine how a value accessed from an original IsInstantiable + * should be tweaked to return the Instance's version + * Sealed. + */ +@implicitNotFound("@public is only legal within a class marked @instantiable and only on vals of type" + + " Data, BaseModule, IsInstantiable, IsLookupable, or Instance[_], or in an Iterable or Option") +sealed trait Lookupable[-B] { + type C // Return type of the lookup + /** Function called to modify the returned value of type B from A, into C + * + * @param that function that selects B from A + * @param instance Instance of A, used to determine C's context + * @return + */ + def instanceLookup[A](that: A => B, instance: Instance[A]): C + + /** Function called to modify the returned value of type B from A, into C + * + * @param that function that selects B from A + * @param definition Definition of A, used to determine C's context + * @return + */ + def definitionLookup[A](that: A => B, definition: Definition[A]): C +} + +private[chisel3] object Lookupable { + + /** Clones a data and sets its internal references to its parent module to be in a new context. + * + * @param data data to be cloned + * @param context new context + * @return + */ + private[chisel3] def cloneDataToContext[T <: Data](data: T, context: BaseModule) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { + internal.requireIsHardware(data, "cross module reference type") + data._parent match { + case None => data + case Some(parent) => + val newParent = cloneModuleToContext(Left(parent), context) + newParent match { + case Left(p) if p == parent => data + case Right(m: BaseModule) => + val newChild = data.cloneTypeFull + newChild.setRef(data.getRef, true) + newChild.bind(internal.CrossModuleBinding) + newChild.setAllParents(Some(m)) + newChild + } + } + } + // The business logic of lookupData + // Also called by cloneViewToContext which potentially needs to lookup stuff from ioMap or the cache + private[chisel3] def doLookupData[A, B <: Data](data: B, cache: HashMap[Data, Data], ioMap: Option[Map[Data, Data]], context: Option[BaseModule]) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): B = { + def impl[C <: Data](d: C): C = d match { + case x: Data if ioMap.nonEmpty && ioMap.get.contains(x) => ioMap.get(x).asInstanceOf[C] + case x: Data if cache.contains(x) => cache(x).asInstanceOf[C] + case _ => + assert(context.nonEmpty) // TODO is this even possible? Better error message here + val ret = cloneDataToContext(d, context.get) + cache(d) = ret + ret + } + data.binding match { + case Some(_: ChildBinding) => mapRootAndExtractSubField(data, impl) + case _ => impl(data) + } + } + + // Helper for co-iterating on Elements of aggregates, they must be the same type but that is unchecked + private def coiterate(a: Data, b: Data): Iterable[(Element, Element)] = { + val as = getRecursiveFields.lazily(a, "_") + val bs = getRecursiveFields.lazily(b, "_") + as.zip(bs).collect { case ((ae: Element, _), (be: Element, _)) => (ae, be) } + } + + /** Given a Data, find the root of its binding, apply a function to the root to get a "new root", + * and find the equivalent child Data in the "new root" + * + * @example {{{ + * Given `arg = a.b[2].c` and some `f`: + * 1. a = root(arg) = root(a.b[2].c) + * 2. newRoot = f(root(arg)) = f(a) + * 3. return newRoot.b[2].c + * }}} + * + * Invariants that elt is a Child of something of the type of data is dynamically checked as we traverse + */ + private def mapRootAndExtractSubField[A <: Data](arg: A, f: Data => Data): A = { + def err(msg: String) = throwException(s"Internal Error! $msg") + def unrollCoordinates(res: List[Arg], d: Data): (List[Arg], Data) = d.binding.get match { + case ChildBinding(parent) => d.getRef match { + case arg @ (_: Slot | _: Index) => unrollCoordinates(arg :: res, parent) + case other => err(s"Unroll coordinates failed for '$arg'! Unexpected arg '$other'") + } + case _ => (res, d) + } + def applyCoordinates(fullCoor: List[Arg], start: Data): Data = { + def rec(coor: List[Arg], d: Data): Data = { + if (coor.isEmpty) d + else { + val next = (coor.head, d) match { + case (Slot(_, name), rec: Record) => rec.elements(name) + case (Index(_, ILit(n)), vec: Vec[_]) => vec.apply(n.toInt) + case (arg, _) => err(s"Unexpected Arg '$arg' applied to '$d'! Root was '$start'.") + } + applyCoordinates(coor.tail, next) + } + } + rec(fullCoor, start) + } + val (coor, root) = unrollCoordinates(Nil, arg) + val newRoot = f(root) + val result = applyCoordinates(coor, newRoot) + try { + result.asInstanceOf[A] + } catch { + case _: ClassCastException => err(s"Applying '$coor' to '$newRoot' somehow resulted in '$result'") + } + } + + // TODO this logic is complicated, can any of it be unified with viewAs? + // If `.viewAs` would capture its arguments, we could potentially use it + // TODO Describe what this is doing at a high level + private[chisel3] def cloneViewToContext[A, B <: Data](data: B, cache: HashMap[Data, Data], ioMap: Option[Map[Data, Data]], context: Option[BaseModule]) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): B = { + // alias to shorten lookups + def lookupData[C <: Data](d: C) = doLookupData(d, cache, ioMap, context) + + val result = data.cloneTypeFull + + // We have to lookup the target(s) of the view since they may need to be cloned into the current context + val newBinding = data.topBinding match { + case ViewBinding(target) => ViewBinding(lookupData(reify(target))) + case avb @ AggregateViewBinding(map, targetOpt) => data match { + case _: Element => ViewBinding(lookupData(reify(map(data)))) + case _: Aggregate => + // Provide a 1:1 mapping if possible + val singleTargetOpt = targetOpt.filter(_ => avb == data.binding.get).flatMap(reifySingleData) + singleTargetOpt match { + case Some(singleTarget) => // It is 1:1! + // This is a little tricky because the values in newMap need to point to Elements of newTarget + val newTarget = lookupData(singleTarget) + val newMap = coiterate(result, data).map { case (res, from) => + (res: Data) -> mapRootAndExtractSubField(map(from), _ => newTarget) + }.toMap + AggregateViewBinding(newMap, Some(newTarget)) + + case None => // No 1:1 mapping so we have to do a flat binding + // Just remap each Element of this aggregate + val newMap = coiterate(result, data).map { + // Upcast res to Data since Maps are invariant in the Key type parameter + case (res, from) => (res: Data) -> lookupData(reify(map(from))) + }.toMap + AggregateViewBinding(newMap, None) + } + } + } + + // TODO Unify the following with `.viewAs` + // We must also mark non-1:1 and child Aggregates in the view for renaming + newBinding match { + case _: ViewBinding => // Do nothing + case AggregateViewBinding(_, target) => + if (target.isEmpty) { + Builder.unnamedViews += result + } + // Binding does not capture 1:1 for child aggregates views + getRecursiveFields.lazily(result, "_").foreach { + case (agg: Aggregate, _) if agg != result => + Builder.unnamedViews += agg + case _ => // Do nothing + } + } + + result.bind(newBinding) + result.setAllParents(Some(ViewParent)) + result.forceName(None, "view", Builder.viewNamespace) + result + } + /** Given a module (either original or a clone), clone it to a new context + * + * This function effectively recurses up the parents of module to find whether: + * (1) A parent is already in the context; then we do nothing and return module + * (2) A parent is in a different clone of the context; then we clone all the parents up + * to that parent and set their parents to be in this cloned context + * (3) A parent has no root; in that case, we do nothing and return the module. + * + * @param module original or clone to be cloned into a new context + * @param context new context + * @return original or clone in the new context + */ + private[chisel3] def cloneModuleToContext[T <: BaseModule](module: Either[T, IsClone[T]], context: BaseModule) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Either[T, IsClone[T]] = { + // Recursive call + def rec[A <: BaseModule](m: A): Either[A, IsClone[A]] = { + def clone(x: A, p: Option[BaseModule], name: () => String): Either[A, IsClone[A]] = { + val newChild = Module.do_apply(new internal.BaseModule.InstanceClone(x, name)) + newChild._parent = p + Right(newChild) + } + (m, context) match { + case (c, ctx) if ctx == c => Left(c) + case (c, ctx: IsClone[_]) if ctx.isACloneOf(c) => Right(ctx.asInstanceOf[IsClone[A]]) + case (c, ctx) if c._parent.isEmpty => Left(c) + case (_, _) => + cloneModuleToContext(Left(m._parent.get), context) match { + case Left(p) => Left(m) + case Right(p: BaseModule) => + clone(m, Some(p), () => m.instanceName) + } + } + } + module match { + case Left(m) => rec(m) + case Right(m: ModuleClone[_]) => + rec(m) match { + case Left(mx) => Right(mx) + case Right(i: InstanceClone[_]) => + val newChild = Module.do_apply(new InstanceClone(m._proto, () => m.instanceName)) + newChild._parent = i._parent + Right(newChild) + } + case Right(m: InstanceClone[_]) => + rec(m) match { + case Left(mx) => Right(mx) + case Right(i: InstanceClone[_]) => + val newChild = Module.do_apply(new InstanceClone(m._proto, () => m.instanceName)) + newChild._parent = i._parent + Right(newChild) + } + } + } + + class SimpleLookupable[X] extends Lookupable[X] { + type B = X + type C = X + def definitionLookup[A](that: A => B, definition: Definition[A]): C = that(definition.proto) + def instanceLookup[A](that: A => B, instance: Instance[A]): C = that(instance.proto) + } + + implicit def lookupInstance[B <: BaseModule](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[Instance[B]] { + type C = Instance[B] + def definitionLookup[A](that: A => Instance[B], definition: Definition[A]): C = { + val ret = that(definition.proto) + new Instance(cloneModuleToContext(ret.cloned, definition.getInnerDataContext.get)) + } + def instanceLookup[A](that: A => Instance[B], instance: Instance[A]): C = { + val ret = that(instance.proto) + instance.cloned match { + // If instance is just a normal module, no changing of context is necessary + case Left(_) => new Instance(ret.cloned) + case Right(_) => new Instance(cloneModuleToContext(ret.cloned, instance.getInnerDataContext.get)) + } + } + } + + implicit def lookupModule[B <: BaseModule](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[B] { + type C = Instance[B] + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + val ret = that(definition.proto) + new Instance(cloneModuleToContext(Left(ret), definition.getInnerDataContext.get)) + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + val ret = that(instance.proto) + instance.cloned match { + // If instance is just a normal module, no changing of context is necessary + case Left(_) => new Instance(Left(ret)) + case Right(_) => new Instance(cloneModuleToContext(Left(ret), instance.getInnerDataContext.get)) + } + } + } + + implicit def lookupData[B <: Data](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[B] { + type C = B + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + val ret = that(definition.proto) + if (isView(ret)) { + ??? // TODO!!!!!! cloneViewToContext(ret, instance, ioMap, instance.getInnerDataContext) + } else { + doLookupData(ret, definition.cache, None, definition.getInnerDataContext) + } + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + val ret = that(instance.proto) + val ioMap: Option[Map[Data, Data]] = instance.cloned match { + case Right(x: ModuleClone[_]) => Some(x.ioMap) + case Left(x: BaseModule) => Some(x.getChiselPorts.map { case (_, data) => data -> data }.toMap) + case _ => None + } + if (isView(ret)) { + cloneViewToContext(ret, instance.cache, ioMap, instance.getInnerDataContext) + } else { + doLookupData(ret, instance.cache, ioMap, instance.getInnerDataContext) + } + + } + } + + import scala.language.higherKinds // Required to avoid warning for lookupIterable type parameter + implicit def lookupIterable[B, F[_] <: Iterable[_]](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions, lookupable: Lookupable[B]) = new Lookupable[F[B]] { + type C = F[lookupable.C] + def definitionLookup[A](that: A => F[B], definition: Definition[A]): C = { + val ret = that(definition.proto).asInstanceOf[Iterable[B]] + ret.map{ x: B => lookupable.definitionLookup[A](_ => x, definition) }.asInstanceOf[C] + } + def instanceLookup[A](that: A => F[B], instance: Instance[A]): C = { + import instance._ + val ret = that(proto).asInstanceOf[Iterable[B]] + ret.map{ x: B => lookupable.instanceLookup[A](_ => x, instance) }.asInstanceOf[C] + } + } + implicit def lookupOption[B](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions, lookupable: Lookupable[B]) = new Lookupable[Option[B]] { + type C = Option[lookupable.C] + def definitionLookup[A](that: A => Option[B], definition: Definition[A]): C = { + val ret = that(definition.proto) + ret.map{ x: B => lookupable.definitionLookup[A](_ => x, definition) } + } + def instanceLookup[A](that: A => Option[B], instance: Instance[A]): C = { + import instance._ + val ret = that(proto) + ret.map{ x: B => lookupable.instanceLookup[A](_ => x, instance) } + } + } + implicit def lookupIsInstantiable[B <: IsInstantiable](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new Lookupable[B] { + type C = Instance[B] + def definitionLookup[A](that: A => B, definition: Definition[A]): C = { + val ret = that(definition.proto) + val cloned = new InstantiableClone(ret) + cloned._parent = definition.getInnerDataContext + new Instance(Right(cloned)) + } + def instanceLookup[A](that: A => B, instance: Instance[A]): C = { + val ret = that(instance.proto) + val cloned = new InstantiableClone(ret) + cloned._parent = instance.getInnerDataContext + new Instance(Right(cloned)) + } + } + + implicit def lookupIsLookupable[B <: IsLookupable](implicit sourceInfo: SourceInfo, compileOptions: CompileOptions) = new SimpleLookupable[B]() + + implicit val lookupInt = new SimpleLookupable[Int]() + implicit val lookupByte = new SimpleLookupable[Byte]() + implicit val lookupShort = new SimpleLookupable[Short]() + implicit val lookupLong = new SimpleLookupable[Long]() + implicit val lookupFloat = new SimpleLookupable[Float]() + implicit val lookupChar = new SimpleLookupable[Char]() + implicit val lookupString = new SimpleLookupable[String]() + implicit val lookupBoolean = new SimpleLookupable[Boolean]() + implicit val lookupBigInt = new SimpleLookupable[BigInt]() +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/package.scala b/core/src/main/scala/chisel3/experimental/hierarchy/package.scala new file mode 100644 index 00000000..c309ab52 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/package.scala @@ -0,0 +1,48 @@ +package chisel3.experimental + +package object hierarchy { + + /** Classes or traits which will be used with the [[Definition]] + [[Instance]] api should be marked + * with the [[@instantiable]] annotation at the class/trait definition. + * + * @example {{{ + * @instantiable + * class MyModule extends Module { + * ... + * } + * + * val d = Definition(new MyModule) + * val i0 = Instance(d) + * val i1 = Instance(d) + * }}} + */ + class instantiable extends chisel3.internal.instantiable + + /** Classes marked with [[@instantiable]] can have their vals marked with the [[@public]] annotation to + * enable accessing these values from a [[Definition]] or [[Instance]] of the class. + * + * Only vals of the the following types can be marked [[@public]]: + * 1. IsInstantiable + * 2. IsLookupable + * 3. Data + * 4. BaseModule + * 5. Iterable/Option containing a type that meets these requirements + * 6. Basic type like String, Int, BigInt etc. + * + * @example {{{ + * @instantiable + * class MyModule extends Module { + * @public val in = IO(Input(UInt(3.W))) + * @public val out = IO(Output(UInt(3.W))) + * .. + * } + * + * val d = Definition(new MyModule) + * val i0 = Instance(d) + * val i1 = Instance(d) + * + * i1.in := i0.out + * }}} + */ + class public extends chisel3.internal.public +} diff --git a/core/src/main/scala/chisel3/experimental/package.scala b/core/src/main/scala/chisel3/experimental/package.scala index ec3f2a79..8018159f 100644 --- a/core/src/main/scala/chisel3/experimental/package.scala +++ b/core/src/main/scala/chisel3/experimental/package.scala @@ -2,6 +2,9 @@ package chisel3 +import chisel3.internal.NamedComponent +import chisel3.internal.sourceinfo.SourceInfo + /** Package for experimental features, which may have their API changed, be removed, etc. * * Because its contents won't necessarily have the same level of stability and support as @@ -20,13 +23,6 @@ package object experimental { type ChiselEnum = EnumFactory - @deprecated("Use the version in chisel3._", "3.2") - val withClockAndReset = chisel3.withClockAndReset - @deprecated("Use the version in chisel3._", "3.2") - val withClock = chisel3.withClock - @deprecated("Use the version in chisel3._", "3.2") - val withReset = chisel3.withReset - // Rocket Chip-style clonemodule /** A record containing the results of CloneModuleAsRecord @@ -131,14 +127,48 @@ package object experimental { object BundleLiterals { implicit class AddBundleLiteralConstructor[T <: Record](x: T) { - def Lit(elems: (T => (Data, Data))*): T = { + def Lit(elems: (T => (Data, Data))*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): T = { x._makeLit(elems: _*) } } } + /** This class provides the `Lit` method needed to define a `Vec` literal + */ + object VecLiterals { + implicit class AddVecLiteralConstructor[T <: Data](x: Vec[T]) { + /** Given a generator of a list tuples of the form [Int, Data] + * constructs a Vec literal, parallel concept to `BundleLiteral` + * + * @param elems tuples of an index and a literal value + * @return + */ + def Lit(elems: (Int, T)*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = { + x._makeLit(elems: _*) + } + } + + implicit class AddObjectLiteralConstructor(x: Vec.type) { + /** This provides an literal construction method for cases using + * object `Vec` as in `Vec.Lit(1.U, 2.U)` + */ + def Lit[T <: Data](elems: T*)(implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Vec[T] = { + require(elems.nonEmpty, s"Lit.Vec(...) must have at least one element") + val indexElements = elems.zipWithIndex.map { case (element, index) => (index, element)} + val widestElement = elems.maxBy(_.getWidth) + val vec: Vec[T] = Vec.apply(indexElements.length, chiselTypeOf(widestElement)) + vec.Lit(indexElements:_*) + } + } + } + // Use to add a prefix to any component generated in input scope val prefix = chisel3.internal.prefix // Use to remove prefixes not in provided scope val noPrefix = chisel3.internal.noPrefix + + /** Base simulation-only component. */ + abstract class BaseSim extends NamedComponent { + _parent.foreach(_.addId(this)) + } } diff --git a/core/src/main/scala/chisel3/experimental/verification/package.scala b/core/src/main/scala/chisel3/experimental/verification/package.scala index 5c71bd5f..190083fd 100644 --- a/core/src/main/scala/chisel3/experimental/verification/package.scala +++ b/core/src/main/scala/chisel3/experimental/verification/package.scala @@ -2,40 +2,59 @@ package chisel3.experimental -import chisel3.{Bool, CompileOptions} +import chisel3._ import chisel3.internal.Builder -import chisel3.internal.Builder.pushCommand import chisel3.internal.firrtl.{Formal, Verification} import chisel3.internal.sourceinfo.SourceInfo package object verification { + object assert { + /** Named class for assertions. */ + final class Assert(private[chisel3] val predicate: Bool) extends BaseSim + + def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions): Unit = { - val clock = Builder.forcedClock - pushCommand(Verification(Formal.Assert, sourceInfo, clock.ref, - predicate.ref, msg)) + compileOptions: CompileOptions): Assert = { + val a = new Assert(predicate) + when (!Module.reset.asBool) { + val clock = Module.clock + Builder.pushCommand(Verification(a, Formal.Assert, sourceInfo, clock.ref, predicate.ref, msg)) + } + a } } object assume { + /** Named class for assumes. */ + final class Assume(private[chisel3] val predicate: Bool) extends BaseSim + def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions): Unit = { - val clock = Builder.forcedClock - pushCommand(Verification(Formal.Assume, sourceInfo, clock.ref, - predicate.ref, msg)) + compileOptions: CompileOptions): Assume = { + val a = new Assume(predicate) + when (!Module.reset.asBool) { + val clock = Module.clock + Builder.pushCommand(Verification(a, Formal.Assume, sourceInfo, clock.ref, predicate.ref, msg)) + } + a } } object cover { + /** Named class for covers. */ + final class Cover(private[chisel3] val predicate: Bool) extends BaseSim + def apply(predicate: Bool, msg: String = "")( implicit sourceInfo: SourceInfo, - compileOptions: CompileOptions): Unit = { - val clock = Builder.forcedClock - pushCommand(Verification(Formal.Cover, sourceInfo, clock.ref, - predicate.ref, msg)) + compileOptions: CompileOptions): Cover = { + val clock = Module.clock + val c = new Cover(predicate) + when (!Module.reset.asBool) { + Builder.pushCommand(Verification(c, Formal.Cover, sourceInfo, clock.ref, predicate.ref, msg)) + } + c } } } |
