diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index 3e11c0a1f5b..81f6869e240 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -13,10 +13,11 @@ import chisel3.experimental.{requireIsChiselType, BaseModule, SourceInfo, Unloca import chisel3.internal.sourceinfo.{InstTransform} import chisel3.properties.{Class, Property} import chisel3.reflect.DataMirror -import _root_.firrtl.annotations.{IsModule, ModuleName, ModuleTarget} +import _root_.firrtl.annotations.{InstanceTarget, IsModule, ModuleName, ModuleTarget} import _root_.firrtl.AnnotationSeq import chisel3.internal.plugin.autoNameRecursively import chisel3.util.simpleClassName +import chisel3.experimental.hierarchy.Hierarchy object Module extends SourceInfoDoc { @@ -738,12 +739,56 @@ package experimental { case (_, Some(parent)) => parent.toRelativeTarget(root).instOf(this.instanceName, name) // If root was defined, and there is no parent, the root was not an ancestor. case (Some(definedRoot), None) => - throwException(s"Requested .toRelativeTarget relative to ${definedRoot.name}, but it is not an ancestor") + throwException( + s"Requested .toRelativeTarget relative to ${definedRoot.name}, but it is not an ancestor of $this" + ) // If root was not defined, and there is no parent, return this. case (None, None) => getTarget } } + /** Returns a FIRRTL ModuleTarget that references this object, relative to an optional root. + * + * If `root` is defined, the target is a hierarchical path starting from `root`. + * + * If `root` is not defined, the target is a hierarchical path equivalent to `toAbsoluteTarget`. + * + * @note If `root` is defined, and has not finished elaboration, this must be called within `atModuleBodyEnd`. + * @note The BaseModule must be a descendant of `root`, if it is defined. + * @note This doesn't have special handling for Views. + */ + final def toRelativeTargetToHierarchy(root: Option[Hierarchy[BaseModule]]): IsModule = { + def fail() = throwException( + s"No common ancestor between\n ${this.toAbsoluteTarget} and\n ${root.get.toAbsoluteTarget}" + ) + + // Algorithm starts from the top of both absolute paths + // and walks down them checking for equality, + // and terminates once the root is just a ModuleTarget + def recurse(thisRelative: IsModule, rootRelative: IsModule): IsModule = { + (thisRelative, rootRelative) match { + case (t: IsModule, r: ModuleTarget) => { + if (t.module == r.module) t else fail() + } + case (t: ModuleTarget, r: InstanceTarget) => fail() + case (t: InstanceTarget, r: InstanceTarget) => { + if ((t.module == r.module) && (r.asPath.head == t.asPath.head)) + recurse(t.stripHierarchy(1), r.stripHierarchy(1)) + else fail() + } + } + } + + if (root.isEmpty) (this.toAbsoluteTarget) + else if (this == ViewParent) ViewParent.absoluteTarget + else { + val thisAbsolute = this.toAbsoluteTarget + val rootAbsolute = root.get.toAbsoluteTarget + if (thisAbsolute.circuit != thisAbsolute.circuit) fail() + recurse(thisAbsolute, rootAbsolute) + } + } + /** * Internal API. Returns a list of this module's generated top-level ports as a map of a String * (FIRRTL name) to the IO object. Only valid after the module is closed. diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/core/Definition.scala b/core/src/main/scala/chisel3/experimental/hierarchy/core/Definition.scala index 4adf2696f23..e4bb2548fc4 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/core/Definition.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/core/Definition.scala @@ -78,6 +78,18 @@ object Definition extends SourceInfoDoc { * @return absoluteTarget of this instance */ def toAbsoluteTarget: IsModule = d.proto.toAbsoluteTarget + + /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance + * @return absoluteTarget of this instance + */ + def toRelativeTarget(root: Option[BaseModule]): IsModule = d.proto.toRelativeTarget(root) + + /** If this is an instance of a Module, returns the toAbsoluteTarget of this instance + * @return absoluteTarget of this instance + */ + def toRelativeTargetToHierarchy(root: Option[Hierarchy[BaseModule]]): IsModule = + d.proto.toRelativeTargetToHierarchy(root) + } /** A construction method to build a Definition of a Module diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/core/Hierarchy.scala b/core/src/main/scala/chisel3/experimental/hierarchy/core/Hierarchy.scala index 4fcb937dcd1..184716ff3ad 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/core/Hierarchy.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/core/Hierarchy.scala @@ -116,5 +116,24 @@ object Hierarchy { case i: Instance[T] => new Instance.InstanceBaseModuleExtensions(i).toAbsoluteTarget case _ => throw new InternalErrorException("Match error: toAbsoluteTarget i=$i") } + + /** Returns the toRelativeTarget of this hierarchy + * @return relativeTarget of this Hierarchy + */ + def toRelativeTarget(root: Option[BaseModule]): IsModule = i match { + case d: Definition[T] => new Definition.DefinitionBaseModuleExtensions(d).toRelativeTarget(root) + case i: Instance[T] => new Instance.InstanceBaseModuleExtensions(i).toRelativeTarget(root) + case _ => throw new InternalErrorException("Match error: toAbsoluteTarget i=$i") + } + + /** Returns the toRelativeTarget of this hierarchy + * @return relativeTarget of this Hierarchy + */ + def toRelativeTargetToHierarchy(root: Option[Hierarchy[BaseModule]]): IsModule = i match { + case d: Definition[T] => new Definition.DefinitionBaseModuleExtensions(d).toRelativeTargetToHierarchy(root) + case i: Instance[T] => new Instance.InstanceBaseModuleExtensions(i).toRelativeTargetToHierarchy(root) + case _ => throw new InternalErrorException("Match error: toAbsoluteTarget i=$i") + } + } } diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/core/Instance.scala b/core/src/main/scala/chisel3/experimental/hierarchy/core/Instance.scala index 574d36d29c7..5e60ed6628b 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/core/Instance.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/core/Instance.scala @@ -96,6 +96,28 @@ object Instance extends SourceInfoDoc { case _ => throw new InternalErrorException("Match error: i.underlying=${i.underlying}") } + /** Returns a FIRRTL ReferenceTarget that references this object, relative to an optional root. + * + * If `root` is defined, the target is a hierarchical path starting from `root`. + * + * If `root` is not defined, the target is a hierarchical path equivalent to `toAbsoluteTarget`. + * + * @note If `root` is defined, and has not finished elaboration, this must be called within `atModuleBodyEnd`. + * @note The NamedComponent must be a descendant of `root`, if it is defined. + * @note This doesn't have special handling for Views. + */ + def toRelativeTarget(root: Option[BaseModule]): IsModule = i.underlying match { + case Proto(x) => x.toRelativeTarget(root) + case Clone(x: IsClone[_] with BaseModule) => x.toRelativeTarget(root) + case _ => throw new InternalErrorException("Match error: i.underlying=${i.underlying}") + } + + def toRelativeTargetToHierarchy(root: Option[Hierarchy[BaseModule]]): IsModule = i.underlying match { + case Proto(x) => x.toRelativeTargetToHierarchy(root) + case Clone(x: IsClone[_] with BaseModule) => x.toRelativeTargetToHierarchy(root) + case _ => throw new InternalErrorException("Match error: i.underlying=${i.underlying}") + } + def suggestName(name: String): Unit = i.underlying match { case Clone(m: BaseModule) => m.suggestName(name) case Proto(m) => m.suggestName(name) diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index 55560f746dd..ef5be29b532 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -6,7 +6,7 @@ import scala.util.DynamicVariable import scala.collection.mutable.ArrayBuffer import chisel3._ import chisel3.experimental._ -import chisel3.experimental.hierarchy.core.{Clone, Definition, ImportDefinitionAnnotation, Instance} +import chisel3.experimental.hierarchy.core.{Clone, Definition, Hierarchy, ImportDefinitionAnnotation, Instance} import chisel3.properties.Class import chisel3.internal.firrtl.ir._ import chisel3.internal.firrtl.Converter @@ -417,6 +417,27 @@ private[chisel3] trait NamedComponent extends HasId { } } + /** Returns a FIRRTL ReferenceTarget that references this object, relative to an optional root. + * + * If `root` is defined, the target is a hierarchical path starting from `root`. + * + * If `root` is not defined, the target is a hierarchical path equivalent to `toAbsoluteTarget`. + * + * @note If `root` is defined, and has not finished elaboration, this must be called within `atModuleBodyEnd`. + * @note The NamedComponent must be a descendant of `root`, if it is defined. + * @note This doesn't have special handling for Views. + */ + final def toRelativeTargetToHierarchy(root: Option[Hierarchy[BaseModule]]): ReferenceTarget = { + val localTarget = toTarget + def makeTarget(p: BaseModule) = + p.toRelativeTargetToHierarchy(root).ref(localTarget.ref).copy(component = localTarget.component) + _parent match { + case Some(ViewParent) => makeTarget(reifyParent) + case Some(parent) => makeTarget(parent) + case None => localTarget + } + } + private def assertValidTarget(): Unit = { val isVecSubaccess = getOptionRef.map { case Index(_, _: ULit) => true // Vec literal indexing diff --git a/core/src/main/scala/chisel3/package.scala b/core/src/main/scala/chisel3/package.scala index a106167df3f..8675a573a19 100644 --- a/core/src/main/scala/chisel3/package.scala +++ b/core/src/main/scala/chisel3/package.scala @@ -3,6 +3,7 @@ import firrtl.annotations.{IsMember, Named, ReferenceTarget} import chisel3.internal.{ExceptionHelpers, NamedComponent} import chisel3.experimental.BaseModule +import chisel3.experimental.hierarchy.Hierarchy import java.util.{MissingFormatArgumentException, UnknownFormatConversionException} import scala.collection.mutable @@ -423,7 +424,8 @@ package object chisel3 { sealed trait HasTarget { def toTarget: ReferenceTarget def toAbsoluteTarget: ReferenceTarget - def toRelativeTarget(root: Option[BaseModule]): ReferenceTarget + def toRelativeTarget(root: Option[BaseModule]): ReferenceTarget + def toRelativeTargetToHierarchy(root: Option[Hierarchy[BaseModule]]): ReferenceTarget /** Exposes the suggestName method of the NamedComponent so users can * provide a seed to influence the name generation of this component. @@ -441,7 +443,8 @@ package object chisel3 { private[chisel3] def apply(t: NamedComponent): HasTarget = new HasTarget { def toTarget = t.toTarget def toAbsoluteTarget = t.toAbsoluteTarget - def toRelativeTarget(root: Option[BaseModule]) = t.toRelativeTarget(root) + def toRelativeTarget(root: Option[BaseModule]) = t.toRelativeTarget(root) + def toRelativeTargetToHierarchy(root: Option[Hierarchy[BaseModule]]) = t.toRelativeTargetToHierarchy(root) def suggestName(seed: String): Unit = t.suggestName(seed) } diff --git a/firrtl/src/main/scala/firrtl/annotations/Target.scala b/firrtl/src/main/scala/firrtl/annotations/Target.scala index e1a99102491..98f7421c897 100644 --- a/firrtl/src/main/scala/firrtl/annotations/Target.scala +++ b/firrtl/src/main/scala/firrtl/annotations/Target.scala @@ -606,6 +606,7 @@ case class ModuleTarget(circuit: String, module: String) extends IsModule { override def toNamed: ModuleName = ModuleName(module, CircuitName(circuit)) override def leafModule: String = module + } /** Target pointing to a declared named component in a [[firrtl.ir.DefModule]] diff --git a/src/test/scala/chiselTests/ToTargetSpec.scala b/src/test/scala/chiselTests/ToTargetSpec.scala index 2f49f172bcd..608e83c9b36 100644 --- a/src/test/scala/chiselTests/ToTargetSpec.scala +++ b/src/test/scala/chiselTests/ToTargetSpec.scala @@ -5,13 +5,16 @@ package chiselTests import chisel3._ import chisel3.properties.{Path, Property} import circt.stage.ChiselStage +import chisel3.experimental.hierarchy.{instantiable, public, Definition, Instance} +@instantiable class RelativeInnerModule extends RawModule { - val wire = Wire(Bool()) + @public val wire = Wire(Bool()) } +@instantiable class RelativeMiddleModule extends RawModule { - val inner = Module(new RelativeInnerModule()) + @public val inner = Module(new RelativeInnerModule()) } class RelativeOuterRootModule extends RawModule { @@ -82,6 +85,65 @@ class RelativeSiblingsModule extends RawModule { } } +class RelativeSiblingsInstancesModule extends RawModule { + val middle = Definition(new RelativeMiddleModule()) + val middle1 = Instance(middle) + val middle2 = Instance(middle) +} + +class RelativeSiblingsInstancesBadModule extends RelativeSiblingsInstancesModule { + val noCommonAncestorOut = IO(Output(Property[Path]())) + + atModuleBodyEnd { + val noCommonAncestor = middle1.inner.wire.toRelativeTargetToHierarchy(Some(middle2)) + noCommonAncestorOut := Property(Path(noCommonAncestor)) + } +} + +class RelativeSiblingsDefinitionBadModule1 extends RelativeSiblingsInstancesModule { + val noCommonAncestorOut = IO(Output(Property[Path]())) + + atModuleBodyEnd { + val noCommonAncestor = middle1.inner.wire.toRelativeTargetToHierarchy(Some(middle)) + noCommonAncestorOut := Property(Path(noCommonAncestor)) + } +} + +class RelativeSiblingsDefinitionBadModule2 extends RelativeSiblingsInstancesModule { + val noCommonAncestorOut = IO(Output(Property[Path]())) + + atModuleBodyEnd { + val noCommonAncestor = middle.inner.wire.toRelativeTargetToHierarchy(Some(middle1)) + noCommonAncestorOut := Property(Path(noCommonAncestor)) + } +} + +class RelativeSiblingsInstancesParent extends RawModule { + val outer = Module(new RelativeSiblingsInstancesModule()) + + atModuleBodyEnd { + val referenceInstanceAbsolute = outer.middle1.toRelativeTargetToHierarchy(None) + val referenceInstanceAbsoluteOut = IO(Output(Property[Path]())) + referenceInstanceAbsoluteOut := Property(Path(referenceInstanceAbsolute)) + + val referenceInstance = outer.middle1.inner.toRelativeTargetToHierarchy(Some(outer.middle1)) + val referenceInstanceOut = IO(Output(Property[Path]())) + referenceInstanceOut := Property(Path(referenceInstance)) + + val referenceInstanceWire = outer.middle2.inner.wire.toRelativeTargetToHierarchy(Some(outer.middle2)) + val referenceInstanceWireOut = IO(Output(Property[Path]())) + referenceInstanceWireOut := Property(Path(referenceInstanceWire)) + + val referenceDefinitionWire = outer.middle.inner.wire.toRelativeTargetToHierarchy(Some(outer.middle)) + val referenceDefinitionWireOut = IO(Output(Property[Path]())) + referenceDefinitionWireOut := Property(Path(referenceDefinitionWire)) + + val referenceDefinitionInstance = outer.middle.inner.toRelativeTargetToHierarchy(Some(outer.middle)) + val referenceDefinitionInstanceOut = IO(Output(Property[Path]())) + referenceDefinitionInstanceOut := Property(Path(referenceDefinitionInstance)) + } +} + class ToTargetSpec extends ChiselFlatSpec with Utils { var m: InstanceNameModule = _ @@ -183,4 +245,49 @@ class ToTargetSpec extends ChiselFlatSpec with Utils { (e.getMessage should include).regex("Requested .toRelativeTarget relative to .+, but it is not an ancestor") } + + behavior.of(".toRelativeTargetToHierarchy") + + it should "work to get relative targets to an instance of an Instance" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RelativeSiblingsInstancesParent) + + chirrtl should include( + "propassign referenceInstanceOut, path(\"OMInstanceTarget:~RelativeSiblingsInstancesParent|RelativeMiddleModule/inner:RelativeInnerModule" + ) + } + it should "work to get relative targets to a wire in an Instance" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RelativeSiblingsInstancesParent) + + chirrtl should include( + "propassign referenceInstanceWireOut, path(\"OMReferenceTarget:~RelativeSiblingsInstancesParent|RelativeMiddleModule/inner:RelativeInnerModule>wire" + ) + } + + it should "work to get relative targets to a wire in a Definition" in { + val chirrtl = ChiselStage.emitCHIRRTL(new RelativeSiblingsInstancesParent) + + chirrtl should include( + "propassign referenceDefinitionWireOut, path(\"OMReferenceTarget:~RelativeSiblingsInstancesParent|RelativeMiddleModule/inner:RelativeInnerModule>wire" + ) + } + + it should "raise an exception when the requested root is an Instance but is not an ancestor" in { + val e = the[ChiselException] thrownBy { + ChiselStage.emitCHIRRTL(new RelativeSiblingsInstancesBadModule) + } + e.getMessage should include("No common ancestor between") + } + it should "raise an exception when the requested root is a Definition but is not an ancestor" in { + val e = the[ChiselException] thrownBy { + ChiselStage.emitCHIRRTL(new RelativeSiblingsDefinitionBadModule1) + } + e.getMessage should include("No common ancestor between") + } + it should "raise an exception when the request is from a wire in a Definition that is not the root" in { + val e = the[ChiselException] thrownBy { + ChiselStage.emitCHIRRTL(new RelativeSiblingsDefinitionBadModule2) + } + e.getMessage should include("No common ancestor between") + } + }