Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add .toRelativeTargetToHierarchy #4067

Merged
merged 7 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions core/src/main/scala/chisel3/Module.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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.
jackkoenig marked this conversation as resolved.
Show resolved Hide resolved
*/
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by the way @mikeurbach @azidar are these scaladoc comments intentionally wrong? I guess a toRelativeTarget for a definition is always going to be an absoluteTarget, but is this just a copy-paste weirdness that I am perpetuating

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these comments have been here since before I started contributing to Chisel, but maybe we can at least update the new ones?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i will clean these up more comprehenstively in a follow-up

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh wait I added these comments 😂 sorry for the noise.

* @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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
23 changes: 22 additions & 1 deletion core/src/main/scala/chisel3/internal/Builder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions core/src/main/scala/chisel3/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions firrtl/src/main/scala/firrtl/annotations/Target.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down
111 changes: 109 additions & 2 deletions src/test/scala/chiselTests/ToTargetSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 = _
Expand Down Expand Up @@ -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")
}

}
Loading