diff options
Diffstat (limited to 'core/src/main/scala/chisel3/experimental')
7 files changed, 244 insertions, 92 deletions
diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala index 0cc3d131..2ac61807 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Definition.scala @@ -18,13 +18,9 @@ import firrtl.annotations.{IsModule, ModuleTarget} * * 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 + * @param underlying The internal representation of the definition, 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 - } +final case class Definition[+A] private[chisel3] (private[chisel3] underlying: Underlying[A]) extends IsLookupable with SealedHierarchy[A] { /** Used by Chisel's internal macros. DO NOT USE in your normal Chisel code!!! * Instead, mark the field you are accessing with [[@public]] * @@ -43,20 +39,20 @@ case class Definition[+A] private[chisel3] (private[chisel3] cloned: Either[A, I 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) + val newChild = Module.do_pseudo_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 } + override def toDefinition: Definition[A] = this + override def toInstance: Instance[A] = new Instance(underlying) + + } /** Factory methods for constructing [[Definition]]s */ @@ -95,6 +91,7 @@ object Definition extends SourceInfoDoc { Builder.annotations ++= ir.annotations module._circuit = Builder.currentModule dynamicContext.globalNamespace.copyTo(Builder.globalNamespace) - new Definition(Left(module)) + new Definition(Proto(module)) } + } diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Hierarchy.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Hierarchy.scala new file mode 100644 index 00000000..503e437b --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Hierarchy.scala @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import chisel3._ +import scala.collection.mutable.{HashMap, HashSet} +import scala.reflect.runtime.universe.TypeTag +import chisel3.internal.BaseModule.IsClone +import chisel3.experimental.BaseModule +import _root_.firrtl.annotations.IsModule +import scala.annotation.implicitNotFound + +/** Super-trait for Instance and Definition + * + * Enables writing functions which are Instance/Definition agnostic + */ +sealed trait Hierarchy[+A] { + private[chisel3] def underlying: Underlying[A] + private[chisel3] def proto: A = underlying match { + case Proto(value: A) => value + case Clone(i: IsClone[A]) => i.getProto + } + + /** Updated by calls to [[_lookup]], to avoid recloning returned Data's */ + private[chisel3] val cache = HashMap[Data, Data]() + private[chisel3] def getInnerDataContext: Option[BaseModule] + + /** Determine whether underlying proto is of type provided. + * + * @note IMPORTANT: this function requires summoning a TypeTag[B], which will fail if B is an inner class. + * @note IMPORTANT: this function IGNORES type parameters, akin to normal type erasure. + * @note IMPORTANT: this function relies on Java reflection for underlying proto, but Scala reflection for provided type + * + * E.g. isA[List[Int]] will return true, even if underlying proto is of type List[String] + * @return Whether underlying proto is of provided type (with caveats outlined above) + */ + def isA[B : TypeTag]: Boolean = { + val tptag = implicitly[TypeTag[B]] + val name = tptag.tpe.toString + inBaseClasses(name) + } + + + // This code handles a special-case where, within an mdoc context, the type returned from + // scala reflection (typetag) looks different than when returned from java reflection. + // This function detects this case and reshapes the string to match. + private def modifyReplString(clz: String): String = { + if(clz != null) { + clz.split('.').toList match { + case "repl" :: "MdocSession" :: app :: rest => s"$app.this." + rest.mkString(".") + case other => clz + } + } else clz + } + private lazy val superClasses = calculateSuperClasses(proto.getClass()) + private def calculateSuperClasses(clz: Class[_]): Set[String] = { + if(clz != null) { + Set(modifyReplString(clz.getCanonicalName())) ++ + clz.getInterfaces().flatMap(i => calculateSuperClasses(i)) ++ + calculateSuperClasses(clz.getSuperclass()) + } else { + Set.empty[String] + } + } + private def inBaseClasses(clz: String): Boolean = superClasses.contains(clz) + + + /** 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 hierarchy. + * + * 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 + + /** @return Return the underlying Definition[A] of this Hierarchy[A] */ + def toDefinition: Definition[A] + + /** @return Convert this Hierarchy[A] as a top-level Instance[A] */ + def toInstance: Instance[A] +} + +// Used to effectively seal Hierarchy, without requiring Definition and Instance to be in this file. +private[chisel3] trait SealedHierarchy[+A] extends Hierarchy[A] + +object Hierarchy { + implicit class HierarchyBaseModuleExtensions[T <: BaseModule](i: Hierarchy[T]) { + /** Returns the toTarget of this hierarchy + * @return target of this hierarchy + */ + def toTarget: IsModule = i match { + case d: Definition[T] => new Definition.DefinitionBaseModuleExtensions(d).toTarget + case i: Instance[T] => new Instance.InstanceBaseModuleExtensions(i).toTarget + } + + /** Returns the toAbsoluteTarget of this hierarchy + * @return absoluteTarget of this Hierarchy + */ + def toAbsoluteTarget: IsModule = i match { + case d: Definition[T] => new Definition.DefinitionBaseModuleExtensions(d).toAbsoluteTarget + case i: Instance[T] => new Instance.InstanceBaseModuleExtensions(i).toAbsoluteTarget + } + } +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala index 9b17bfce..97b62c23 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Instance.scala @@ -14,38 +14,29 @@ import firrtl.annotations.IsModule * 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 + * @param underlying 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 +final case class Instance[+A] private [chisel3] (private[chisel3] underlying: Underlying[A]) extends SealedHierarchy[A] { + underlying match { + case Proto(p: IsClone[_]) => chisel3.internal.throwException("Cannot have a Proto with a clone!") + case other => //Ok } /** @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 + private[chisel3] def getInnerDataContext: Option[BaseModule] = underlying match { + case Proto(value: BaseModule) => Some(value) + case Proto(value: IsInstantiable) => None + case Clone(i: BaseModule) => Some(i) + case Clone(i: InstantiableClone[_]) => i.getInnerContext } /** @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 + private[chisel3] def getClonedParent: Option[BaseModule] = underlying match { + case Proto(value: BaseModule) => value._parent + case Clone(i: BaseModule) => i._parent + case Clone(i: InstantiableClone[_]) => i.getInnerContext } - /** 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]] * @@ -65,7 +56,8 @@ case class Instance[+A] private [chisel3] (private[chisel3] cloned: Either[A, Is } /** Returns the definition of this Instance */ - def toDefinition: Definition[A] = new Definition(Left(proto)) + override def toDefinition: Definition[A] = new Definition(Proto(proto)) + override def toInstance: Instance[A] = this } @@ -75,17 +67,17 @@ object Instance extends SourceInfoDoc { /** 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 + def toTarget: IsModule = i.underlying match { + case Proto(x: BaseModule) => x.getTarget + case Clone(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 + def toAbsoluteTarget: IsModule = i.underlying match { + case Proto(x) => x.toAbsoluteTarget + case Clone(x: IsClone[_] with BaseModule) => x.toAbsoluteTarget } } @@ -105,7 +97,7 @@ object Instance extends SourceInfoDoc { val ports = experimental.CloneModuleAsRecord(definition.proto) val clone = ports._parent.get.asInstanceOf[ModuleClone[T]] clone._madeFromDefinition = true - new Instance(Right(clone)) + new Instance(Clone(clone)) } } diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala index 26ba0286..4f3c2d42 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/IsInstantiable.scala @@ -12,6 +12,6 @@ trait IsInstantiable object IsInstantiable { implicit class IsInstantiableExtensions[T <: IsInstantiable](i: T) { - def toInstance: Instance[T] = new Instance(Left(i)) + def toInstance: Instance[T] = new Instance(Proto(i)) } } diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/LibraryHooks.scala b/core/src/main/scala/chisel3/experimental/hierarchy/LibraryHooks.scala new file mode 100644 index 00000000..c16cc633 --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/LibraryHooks.scala @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import scala.annotation.implicitNotFound + +@implicitNotFound("These functions are only for building hierarchy-compatible Chisel libraries! Users beware!") +// DO NOT extend unless you know what you are doing!!!!!! Not for the casual user! +trait InsideHierarchyLibraryExtension + +// Collection of public functions to give non-core-Chisel libraries the ability to build integrations with +// the experimental hierarchy package +object LibraryHooks { + + /** Builds a new instance given a definition and function to create a new instance-specific Underlying, from the + * definition's Underlying + * @note Implicitly requires being inside a Hierarchy Library Extension + */ + def buildInstance[A](definition: Definition[A], + createUnderlying: Underlying[A] => Underlying[A] + )(implicit inside: InsideHierarchyLibraryExtension): Instance[A] = { + new Instance(createUnderlying(definition.underlying)) + } + + /** Builds a new definition given an Underlying implementation + * @note Implicitly requires being inside a Hierarchy Library Extension + */ + def buildDefinition[A](underlying: Underlying[A])(implicit inside: InsideHierarchyLibraryExtension): Definition[A] = { + new Definition(underlying) + } +} diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala index b9617723..ff4d676c 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Lookupable.scala @@ -19,7 +19,7 @@ import chisel3.internal.{AggregateViewBinding, Builder, ChildBinding, ViewBindin */ @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] { +trait Lookupable[-B] { type C // Return type of the lookup /** Function called to modify the returned value of type B from A, into C * @@ -36,9 +36,11 @@ sealed trait Lookupable[-B] { * @return */ def definitionLookup[A](that: A => B, definition: Definition[A]): C + protected def getProto[A](h: Hierarchy[A]): A = h.proto + protected def getUnderlying[A](h: Hierarchy[A]): Underlying[A] = h.underlying } -private[chisel3] object Lookupable { +object Lookupable { /** Clones a data and sets its internal references to its parent module to be in a new context. * @@ -52,10 +54,10 @@ private[chisel3] object Lookupable { data._parent match { case None => data case Some(parent) => - val newParent = cloneModuleToContext(Left(parent), context) + val newParent = cloneModuleToContext(Proto(parent), context) newParent match { - case Left(p) if p == parent => data - case Right(m: BaseModule) => + case Proto(p) if p == parent => data + case Clone(m: BaseModule) => val newChild = data.cloneTypeFull newChild.setRef(data.getRef, true) newChild.bind(internal.CrossModuleBinding) @@ -145,7 +147,7 @@ private[chisel3] object Lookupable { 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 + // We have to lookup the target(s) of the view since they may need to be underlying into the current context val newBinding = data.topBinding match { case ViewBinding(target) => ViewBinding(lookupData(reify(target))) case avb @ AggregateViewBinding(map, targetOpt) => data match { @@ -199,51 +201,51 @@ private[chisel3] object Lookupable { * 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 + * to that parent and set their parents to be in this underlying 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 module original or clone to be underlying 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]] = { + private[chisel3] def cloneModuleToContext[T <: BaseModule](module: Underlying[T], context: BaseModule) + (implicit sourceInfo: SourceInfo, compileOptions: CompileOptions): Underlying[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)) + def rec[A <: BaseModule](m: A): Underlying[A] = { + def clone(x: A, p: Option[BaseModule], name: () => String): Underlying[A] = { + val newChild = Module.do_pseudo_apply(new internal.BaseModule.InstanceClone(x, name)) newChild._parent = p - Right(newChild) + Clone(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 (c, ctx) if ctx == c => Proto(c) + case (c, ctx: IsClone[_]) if ctx.hasSameProto(c) => Clone(ctx.asInstanceOf[IsClone[A]]) + case (c, ctx) if c._parent.isEmpty => Proto(c) case (_, _) => - cloneModuleToContext(Left(m._parent.get), context) match { - case Left(p) => Left(m) - case Right(p: BaseModule) => + cloneModuleToContext(Proto(m._parent.get), context) match { + case Proto(p) => Proto(m) + case Clone(p: BaseModule) => clone(m, Some(p), () => m.instanceName) } } } module match { - case Left(m) => rec(m) - case Right(m: ModuleClone[_]) => + case Proto(m) => rec(m) + case Clone(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)) + case Proto(mx) => Clone(mx) + case Clone(i: InstanceClone[_]) => + val newChild = Module.do_pseudo_apply(new InstanceClone(m.getProto, () => m.instanceName)) newChild._parent = i._parent - Right(newChild) + Clone(newChild) } - case Right(m: InstanceClone[_]) => + case Clone(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)) + case Proto(mx) => Clone(mx) + case Clone(i: InstanceClone[_]) => + val newChild = Module.do_pseudo_apply(new InstanceClone(m.getProto, () => m.instanceName)) newChild._parent = i._parent - Right(newChild) + Clone(newChild) } } } @@ -259,14 +261,14 @@ private[chisel3] object Lookupable { 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)) + new Instance(cloneModuleToContext(ret.underlying, definition.getInnerDataContext.get)) } def instanceLookup[A](that: A => Instance[B], instance: Instance[A]): C = { val ret = that(instance.proto) - instance.cloned match { + instance.underlying 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)) + case Proto(_) => new Instance(ret.underlying) + case Clone(_) => new Instance(cloneModuleToContext(ret.underlying, instance.getInnerDataContext.get)) } } } @@ -275,14 +277,14 @@ private[chisel3] object Lookupable { 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)) + new Instance(cloneModuleToContext(Proto(ret), definition.getInnerDataContext.get)) } def instanceLookup[A](that: A => B, instance: Instance[A]): C = { val ret = that(instance.proto) - instance.cloned match { + instance.underlying 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)) + case Proto(_) => new Instance(Proto(ret)) + case Clone(_) => new Instance(cloneModuleToContext(Proto(ret), instance.getInnerDataContext.get)) } } } @@ -299,9 +301,9 @@ private[chisel3] object Lookupable { } 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) + val ioMap: Option[Map[Data, Data]] = instance.underlying match { + case Clone(x: ModuleClone[_]) => Some(x.ioMap) + case Proto(x: BaseModule) => Some(x.getChiselPorts.map { case (_, data) => data -> data }.toMap) case _ => None } if (isView(ret)) { @@ -342,15 +344,19 @@ private[chisel3] object Lookupable { 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)) + val underlying = new InstantiableClone[B] { + val getProto = ret + lazy val _innerContext = definition + } + new Instance(Clone(underlying)) } 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)) + val underlying = new InstantiableClone[B] { + val getProto = ret + lazy val _innerContext = instance + } + new Instance(Clone(underlying)) } } diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/Underlying.scala b/core/src/main/scala/chisel3/experimental/hierarchy/Underlying.scala new file mode 100644 index 00000000..864cc8af --- /dev/null +++ b/core/src/main/scala/chisel3/experimental/hierarchy/Underlying.scala @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.experimental.hierarchy + +import chisel3.internal.BaseModule.IsClone + +/** Represents the underlying implementation of a Definition or Instance */ +sealed trait Underlying[+T] + +/** A clone of a real implementation */ +final case class Clone[+T](isClone: IsClone[T]) extends Underlying[T] + +/** An actual implementation */ +final case class Proto[+T](proto: T) extends Underlying[T] |
