aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/firrtl/annotations
diff options
context:
space:
mode:
authorAdam Izraelevitz2018-10-30 19:30:03 -0700
committerGitHub2018-10-30 19:30:03 -0700
commit0a4bcaa4053aca16f21f899ba76b1b751cfb47b3 (patch)
treedf4ded76ea4c0e448f4839c6fc8838799263dea0 /src/main/scala/firrtl/annotations
parent1e89e41604c9925c7de89eb85c7d7d0fa48e1e08 (diff)
Instance Annotations (#926)
Formerly #865 Major Code Changes/Features Added: Added Target trait as replacement for Named Added TargetToken as token in building Target Added GenericTarget as a catch-all Target Added CircuitTarget, ModuleTarget, ReferenceTarget, and InstanceTarget Added ResolvePaths annotation Added EliminateTargetPaths (and helper class DuplicationHelper) Updated Dedup to work with instance annotations Updated RenameMap to work with instance annotations DCE & ConstantProp extend ResolveAnnotationPaths
Diffstat (limited to 'src/main/scala/firrtl/annotations')
-rw-r--r--src/main/scala/firrtl/annotations/Annotation.scala71
-rw-r--r--src/main/scala/firrtl/annotations/AnnotationUtils.scala18
-rw-r--r--src/main/scala/firrtl/annotations/JsonProtocol.scala30
-rw-r--r--src/main/scala/firrtl/annotations/Named.scala30
-rw-r--r--src/main/scala/firrtl/annotations/Target.scala652
-rw-r--r--src/main/scala/firrtl/annotations/TargetToken.scala46
-rw-r--r--src/main/scala/firrtl/annotations/analysis/DuplicationHelper.scala146
-rw-r--r--src/main/scala/firrtl/annotations/transforms/EliminateTargetPaths.scala167
8 files changed, 1109 insertions, 51 deletions
diff --git a/src/main/scala/firrtl/annotations/Annotation.scala b/src/main/scala/firrtl/annotations/Annotation.scala
index 4b0591bf..62c2b335 100644
--- a/src/main/scala/firrtl/annotations/Annotation.scala
+++ b/src/main/scala/firrtl/annotations/Annotation.scala
@@ -5,11 +5,15 @@ package annotations
import net.jcazevedo.moultingyaml._
import firrtl.annotations.AnnotationYamlProtocol._
+import firrtl.Utils.throwInternalError
+
+import scala.collection.mutable
case class AnnotationException(message: String) extends Exception(message)
/** Base type of auxiliary information */
-trait Annotation {
+trait Annotation extends Product {
+
/** Update the target based on how signals are renamed */
def update(renames: RenameMap): Seq[Annotation]
@@ -18,13 +22,29 @@ trait Annotation {
* @note In [[logger.LogLevel.Debug]] this is called on every Annotation after every Transform
*/
def serialize: String = this.toString
+
+ /** Recurses through ls to find all [[Target]] instances
+ * @param ls
+ * @return
+ */
+ private def extractComponents(ls: scala.collection.Traversable[_]): Seq[Target] = {
+ ls.collect {
+ case c: Target => Seq(c)
+ case ls: scala.collection.Traversable[_] => extractComponents(ls)
+ }.foldRight(Seq.empty[Target])((seq, c) => c ++ seq)
+ }
+
+ /** Returns all [[Target]] members in this annotation
+ * @return
+ */
+ def getTargets: Seq[Target] = extractComponents(productIterator.toSeq)
}
/** If an Annotation does not target any [[Named]] thing in the circuit, then all updates just
* return the Annotation itself
*/
trait NoTargetAnnotation extends Annotation {
- def update(renames: RenameMap) = Seq(this)
+ def update(renames: RenameMap): Seq[NoTargetAnnotation] = Seq(this)
}
/** An Annotation that targets a single [[Named]] thing */
@@ -37,18 +57,27 @@ trait SingleTargetAnnotation[T <: Named] extends Annotation {
// This mess of @unchecked and try-catch is working around the fact that T is unknown due to type
// erasure. We cannot that newTarget is of type T, but a CastClassException will be thrown upon
// invoking duplicate if newTarget cannot be cast to T (only possible in the concrete subclass)
- def update(renames: RenameMap): Seq[Annotation] =
- renames.get(target).map(_.map(newT => (newT: @unchecked) match {
- case newTarget: T @unchecked =>
- try {
- duplicate(newTarget)
- } catch {
- case _: java.lang.ClassCastException =>
- val msg = s"${this.getClass.getName} target ${target.getClass.getName} " +
- s"cannot be renamed to ${newTarget.getClass}"
- throw AnnotationException(msg)
- }
- })).getOrElse(List(this))
+ def update(renames: RenameMap): Seq[Annotation] = {
+ target match {
+ case c: Target =>
+ val x = renames.get(c)
+ x.map(newTargets => newTargets.map(t => duplicate(t.asInstanceOf[T]))).getOrElse(List(this))
+ case _: Named =>
+ val ret = renames.get(Target.convertNamed2Target(target))
+ ret.map(_.map(newT => Target.convertTarget2Named(newT: @unchecked) match {
+ case newTarget: T @unchecked =>
+ try {
+ duplicate(newTarget)
+ }
+ catch {
+ case _: java.lang.ClassCastException =>
+ val msg = s"${this.getClass.getName} target ${target.getClass.getName} " +
+ s"cannot be renamed to ${newTarget.getClass}"
+ throw AnnotationException(msg)
+ }
+ })).getOrElse(List(this))
+ }
+ }
}
@deprecated("Just extend NoTargetAnnotation", "1.1")
@@ -58,7 +87,7 @@ trait SingleStringAnnotation extends NoTargetAnnotation {
object Annotation {
@deprecated("This returns a LegacyAnnotation, use an explicit Annotation type", "1.1")
- def apply(target: Named, transform: Class[_ <: Transform], value: String) =
+ def apply(target: Named, transform: Class[_ <: Transform], value: String): LegacyAnnotation =
new LegacyAnnotation(target, transform, value)
@deprecated("This uses LegacyAnnotation, use an explicit Annotation type", "1.1")
def unapply(a: LegacyAnnotation): Option[(Named, Class[_ <: Transform], String)] =
@@ -92,13 +121,13 @@ final case class LegacyAnnotation private[firrtl] (
}
def propagate(from: Named, tos: Seq[Named], dup: Named=>Annotation): Seq[Annotation] = tos.map(dup(_))
def check(from: Named, tos: Seq[Named], which: Annotation): Unit = {}
- def duplicate(n: Named) = new LegacyAnnotation(n, transform, value)
+ def duplicate(n: Named): LegacyAnnotation = new LegacyAnnotation(n, transform, value)
}
// Private so that LegacyAnnotation can only be constructed via deprecated Annotation.apply
private[firrtl] object LegacyAnnotation {
// ***** Everything below here is to help people migrate off of old annotations *****
- def errorIllegalAnno(name: String) =
+ def errorIllegalAnno(name: String): Annotation =
throw new Exception(s"Old-style annotations that look like $name are no longer supported")
private val OldDeletedRegex = """(?s)DELETED by ([^\n]*)\n(.*)""".r
@@ -111,7 +140,9 @@ private[firrtl] object LegacyAnnotation {
import firrtl.passes.memlib._
import firrtl.passes.wiring._
import firrtl.passes.clocklist._
+
// Attempt to convert common Annotations and error on the rest of old-style build-in annotations
+ // scalastyle:off
def convertLegacyAnno(anno: LegacyAnnotation): Annotation = anno match {
// All old-style Emitter annotations are illegal
case LegacyAnnotation(_,_,"emitCircuit") => errorIllegalAnno("EmitCircuitAnnotation")
@@ -144,7 +175,8 @@ private[firrtl] object LegacyAnnotation {
case LegacyAnnotation(c: ComponentName, _, SourceRegex(pin)) => SourceAnnotation(c, pin)
case LegacyAnnotation(n, _, SinkRegex(pin)) => SinkAnnotation(n, pin)
case LegacyAnnotation(m: ModuleName, t, text) if t == classOf[BlackBoxSourceHelper] =>
- text.split("\n", 3).toList match {
+ val nArgs = 3
+ text.split("\n", nArgs).toList match {
case "resource" :: id :: _ => BlackBoxResourceAnno(m, id)
case "inline" :: name :: text :: _ => BlackBoxInlineAnno(m, name, text)
case "targetDir" :: targetDir :: _ => BlackBoxTargetDirAnno(targetDir)
@@ -152,11 +184,12 @@ private[firrtl] object LegacyAnnotation {
}
case LegacyAnnotation(_, transform, "noDCE!") if transform == classOf[DeadCodeElimination] =>
NoDCEAnnotation
- case LegacyAnnotation(c: ComponentName, _, "DONTtouch!") => DontTouchAnnotation(c)
+ case LegacyAnnotation(c: ComponentName, _, "DONTtouch!") => DontTouchAnnotation(c.toTarget)
case LegacyAnnotation(c: ModuleName, _, "optimizableExtModule!") =>
OptimizableExtModuleAnnotation(c)
case other => other
}
+ // scalastyle:on
def convertLegacyAnnos(annos: Seq[Annotation]): Seq[Annotation] = {
var warned: Boolean = false
annos.map {
diff --git a/src/main/scala/firrtl/annotations/AnnotationUtils.scala b/src/main/scala/firrtl/annotations/AnnotationUtils.scala
index 517cea26..ba9220f7 100644
--- a/src/main/scala/firrtl/annotations/AnnotationUtils.scala
+++ b/src/main/scala/firrtl/annotations/AnnotationUtils.scala
@@ -64,13 +64,29 @@ object AnnotationUtils {
case Array(c, m, x) => ComponentName(x, ModuleName(m, CircuitName(c)))
}
+ /** Converts a serialized FIRRTL component into a sequence of target tokens
+ * @param s
+ * @return
+ */
+ def toSubComponents(s: String): Seq[TargetToken] = {
+ import TargetToken._
+ def exp2subcomp(e: ir.Expression): Seq[TargetToken] = e match {
+ case ir.Reference(name, _) => Seq(Ref(name))
+ case ir.SubField(expr, name, _) => exp2subcomp(expr) :+ Field(name)
+ case ir.SubIndex(expr, idx, _) => exp2subcomp(expr) :+ Index(idx)
+ case ir.SubAccess(expr, idx, _) => Utils.throwInternalError(s"For string $s, cannot convert a subaccess $e into a Target")
+ }
+ exp2subcomp(toExp(s))
+ }
+
+
/** Given a serialized component/subcomponent reference, subindex, subaccess,
* or subfield, return the corresponding IR expression.
* E.g. "foo.bar" becomes SubField(Reference("foo", UnknownType), "bar", UnknownType)
*/
def toExp(s: String): Expression = {
def parse(tokens: Seq[String]): Expression = {
- val DecPattern = """([1-9]\d*)""".r
+ val DecPattern = """(\d+)""".r
def findClose(tokens: Seq[String], index: Int, nOpen: Int): Seq[String] = {
if(index >= tokens.size) {
Utils.error("Cannot find closing bracket ]")
diff --git a/src/main/scala/firrtl/annotations/JsonProtocol.scala b/src/main/scala/firrtl/annotations/JsonProtocol.scala
index 7b2617f5..36699151 100644
--- a/src/main/scala/firrtl/annotations/JsonProtocol.scala
+++ b/src/main/scala/firrtl/annotations/JsonProtocol.scala
@@ -35,11 +35,39 @@ object JsonProtocol {
{ case named: ComponentName => JString(named.serialize) }
))
+
+ class TargetSerializer extends CustomSerializer[Target](format => (
+ { case JString(s) => Target.deserialize(s) },
+ { case named: Target => JString(named.serialize) }
+ ))
+ class GenericTargetSerializer extends CustomSerializer[GenericTarget](format => (
+ { case JString(s) => Target.deserialize(s).asInstanceOf[GenericTarget] },
+ { case named: GenericTarget => JString(named.serialize) }
+ ))
+ class CircuitTargetSerializer extends CustomSerializer[CircuitTarget](format => (
+ { case JString(s) => Target.deserialize(s).asInstanceOf[CircuitTarget] },
+ { case named: CircuitTarget => JString(named.serialize) }
+ ))
+ class ModuleTargetSerializer extends CustomSerializer[ModuleTarget](format => (
+ { case JString(s) => Target.deserialize(s).asInstanceOf[ModuleTarget] },
+ { case named: ModuleTarget => JString(named.serialize) }
+ ))
+ class InstanceTargetSerializer extends CustomSerializer[InstanceTarget](format => (
+ { case JString(s) => Target.deserialize(s).asInstanceOf[InstanceTarget] },
+ { case named: InstanceTarget => JString(named.serialize) }
+ ))
+ class ReferenceTargetSerializer extends CustomSerializer[ReferenceTarget](format => (
+ { case JString(s) => Target.deserialize(s).asInstanceOf[ReferenceTarget] },
+ { case named: ReferenceTarget => JString(named.serialize) }
+ ))
+
/** Construct Json formatter for annotations */
def jsonFormat(tags: Seq[Class[_ <: Annotation]]) = {
Serialization.formats(FullTypeHints(tags.toList)).withTypeHintFieldName("class") +
new TransformClassSerializer + new NamedSerializer + new CircuitNameSerializer +
- new ModuleNameSerializer + new ComponentNameSerializer
+ new ModuleNameSerializer + new ComponentNameSerializer + new TargetSerializer +
+ new GenericTargetSerializer + new CircuitTargetSerializer + new ModuleTargetSerializer +
+ new InstanceTargetSerializer + new ReferenceTargetSerializer
}
/** Serialize annotations to a String for emission */
diff --git a/src/main/scala/firrtl/annotations/Named.scala b/src/main/scala/firrtl/annotations/Named.scala
deleted file mode 100644
index 3da75884..00000000
--- a/src/main/scala/firrtl/annotations/Named.scala
+++ /dev/null
@@ -1,30 +0,0 @@
-// See LICENSE for license details.
-
-package firrtl
-package annotations
-
-import firrtl.ir.Expression
-import AnnotationUtils.{validModuleName, validComponentName, toExp}
-
-/**
- * Named classes associate an annotation with a component in a Firrtl circuit
- */
-sealed trait Named {
- def serialize: String
-}
-
-final case class CircuitName(name: String) extends Named {
- if(!validModuleName(name)) throw AnnotationException(s"Illegal circuit name: $name")
- def serialize: String = name
-}
-
-final case class ModuleName(name: String, circuit: CircuitName) extends Named {
- if(!validModuleName(name)) throw AnnotationException(s"Illegal module name: $name")
- def serialize: String = circuit.serialize + "." + name
-}
-
-final case class ComponentName(name: String, module: ModuleName) extends Named {
- if(!validComponentName(name)) throw AnnotationException(s"Illegal component name: $name")
- def expr: Expression = toExp(name)
- def serialize: String = module.serialize + "." + name
-}
diff --git a/src/main/scala/firrtl/annotations/Target.scala b/src/main/scala/firrtl/annotations/Target.scala
new file mode 100644
index 00000000..dcf5cb02
--- /dev/null
+++ b/src/main/scala/firrtl/annotations/Target.scala
@@ -0,0 +1,652 @@
+// See LICENSE for license details.
+
+package firrtl
+package annotations
+
+import firrtl.ir.Expression
+import AnnotationUtils.{toExp, validComponentName, validModuleName}
+import TargetToken._
+
+import scala.collection.mutable
+
+/** Refers to something in a FIRRTL [[firrtl.ir.Circuit]]. Used for Annotation targets.
+ *
+ * Can be in various states of completion/resolved:
+ * - Legal: [[TargetToken]]'s in tokens are in an order that makes sense
+ * - Complete: circuitOpt and moduleOpt are non-empty, and all Instance(_) are followed by OfModule(_)
+ * - Local: tokens does not refer to things through an instance hierarchy (no Instance(_) or OfModule(_) tokens)
+ */
+sealed trait Target extends Named {
+
+ /** @return Circuit name, if it exists */
+ def circuitOpt: Option[String]
+
+ /** @return Module name, if it exists */
+ def moduleOpt: Option[String]
+
+ /** @return [[Target]] tokens */
+ def tokens: Seq[TargetToken]
+
+ /** @return Returns a new [[GenericTarget]] with new values */
+ def modify(circuitOpt: Option[String] = circuitOpt,
+ moduleOpt: Option[String] = moduleOpt,
+ tokens: Seq[TargetToken] = tokens): GenericTarget = GenericTarget(circuitOpt, moduleOpt, tokens.toVector)
+
+ /** @return Human-readable serialization */
+ def serialize: String = {
+ val circuitString = "~" + circuitOpt.getOrElse("???")
+ val moduleString = "|" + moduleOpt.getOrElse("???")
+ val tokensString = tokens.map {
+ case Ref(r) => s">$r"
+ case Instance(i) => s"/$i"
+ case OfModule(o) => s":$o"
+ case Field(f) => s".$f"
+ case Index(v) => s"[$v]"
+ case Clock => s"@clock"
+ case Reset => s"@reset"
+ case Init => s"@init"
+ }.mkString("")
+ if(moduleOpt.isEmpty && tokens.isEmpty) {
+ circuitString
+ } else if(tokens.isEmpty) {
+ circuitString + moduleString
+ } else {
+ circuitString + moduleString + tokensString
+ }
+ }
+
+ /** @return Converts this [[Target]] into a [[GenericTarget]] */
+ def toGenericTarget: GenericTarget = GenericTarget(circuitOpt, moduleOpt, tokens.toVector)
+
+ /** @return Converts this [[Target]] into either a [[CircuitName]], [[ModuleName]], or [[ComponentName]] */
+ @deprecated("Use Target instead, will be removed in 1.3", "1.2")
+ def toNamed: Named = toGenericTarget.toNamed
+
+ /** @return If legal, convert this [[Target]] into a [[CompleteTarget]] */
+ def getComplete: Option[CompleteTarget]
+
+ /** @return Converts this [[Target]] into a [[CompleteTarget]] */
+ def complete: CompleteTarget = getComplete.get
+
+ /** @return Converts this [[Target]] into a [[CompleteTarget]], or if it can't, return original [[Target]] */
+ def tryToComplete: Target = getComplete.getOrElse(this)
+
+ /** Whether the target is directly instantiated in its root module */
+ def isLocal: Boolean
+}
+
+object Target {
+
+ def apply(circuitOpt: Option[String], moduleOpt: Option[String], reference: Seq[TargetToken]): GenericTarget =
+ GenericTarget(circuitOpt, moduleOpt, reference.toVector)
+
+ def unapply(t: Target): Option[(Option[String], Option[String], Seq[TargetToken])] =
+ Some((t.circuitOpt, t.moduleOpt, t.tokens))
+
+ case class NamedException(message: String) extends Exception(message)
+
+ implicit def convertCircuitTarget2CircuitName(c: CircuitTarget): CircuitName = c.toNamed
+ implicit def convertModuleTarget2ModuleName(c: ModuleTarget): ModuleName = c.toNamed
+ implicit def convertIsComponent2ComponentName(c: IsComponent): ComponentName = c.toNamed
+ implicit def convertTarget2Named(c: Target): Named = c.toNamed
+ implicit def convertCircuitName2CircuitTarget(c: CircuitName): CircuitTarget = c.toTarget
+ implicit def convertModuleName2ModuleTarget(c: ModuleName): ModuleTarget = c.toTarget
+ implicit def convertComponentName2ReferenceTarget(c: ComponentName): ReferenceTarget = c.toTarget
+ implicit def convertNamed2Target(n: Named): CompleteTarget = n.toTarget
+
+ /** Converts [[ComponentName]]'s name into TargetTokens
+ * @param name
+ * @return
+ */
+ def toTargetTokens(name: String): Seq[TargetToken] = {
+ val tokens = AnnotationUtils.tokenize(name)
+ val subComps = mutable.ArrayBuffer[TargetToken]()
+ subComps += Ref(tokens.head)
+ if(tokens.tail.nonEmpty) {
+ tokens.tail.zip(tokens.tail.tail).foreach {
+ case (".", value: String) => subComps += Field(value)
+ case ("[", value: String) => subComps += Index(value.toInt)
+ case other =>
+ }
+ }
+ subComps
+ }
+
+ /** Checks if seq only contains [[TargetToken]]'s with select keywords
+ * @param seq
+ * @param keywords
+ * @return
+ */
+ def isOnly(seq: Seq[TargetToken], keywords:String*): Boolean = {
+ seq.map(_.is(keywords:_*)).foldLeft(false)(_ || _) && keywords.nonEmpty
+ }
+
+ /** @return [[Target]] from human-readable serialization */
+ def deserialize(s: String): Target = {
+ val regex = """(?=[~|>/:.\[@])"""
+ s.split(regex).foldLeft(GenericTarget(None, None, Vector.empty)) { (t, tokenString) =>
+ val value = tokenString.tail
+ tokenString(0) match {
+ case '~' if t.circuitOpt.isEmpty && t.moduleOpt.isEmpty && t.tokens.isEmpty =>
+ if(value == "???") t else t.copy(circuitOpt = Some(value))
+ case '|' if t.moduleOpt.isEmpty && t.tokens.isEmpty =>
+ if(value == "???") t else t.copy(moduleOpt = Some(value))
+ case '/' => t.add(Instance(value))
+ case ':' => t.add(OfModule(value))
+ case '>' => t.add(Ref(value))
+ case '.' => t.add(Field(value))
+ case '[' if value.dropRight(1).toInt >= 0 => t.add(Index(value.dropRight(1).toInt))
+ case '@' if value == "clock" => t.add(Clock)
+ case '@' if value == "init" => t.add(Init)
+ case '@' if value == "reset" => t.add(Reset)
+ case other => throw NamedException(s"Cannot deserialize Target: $s")
+ }
+ }.tryToComplete
+ }
+}
+
+/** Represents incomplete or non-standard [[Target]]s
+ * @param circuitOpt Optional circuit name
+ * @param moduleOpt Optional module name
+ * @param tokens [[TargetToken]]s to represent the target in a circuit and module
+ */
+case class GenericTarget(circuitOpt: Option[String],
+ moduleOpt: Option[String],
+ tokens: Vector[TargetToken]) extends Target {
+
+ override def toGenericTarget: GenericTarget = this
+
+ override def toNamed: Named = getComplete match {
+ case Some(c: IsComponent) if c.isLocal => c.toNamed
+ case Some(c: ModuleTarget) => c.toNamed
+ case Some(c: CircuitTarget) => c.toNamed
+ case other => throw Target.NamedException(s"Cannot convert $this to [[Named]]")
+ }
+
+ override def toTarget: CompleteTarget = getComplete.get
+
+ override def getComplete: Option[CompleteTarget] = {
+ if(!isComplete) None else {
+ val target = this match {
+ case GenericTarget(Some(c), None, Vector()) => CircuitTarget(c)
+ case GenericTarget(Some(c), Some(m), Vector()) => ModuleTarget(c, m)
+ case GenericTarget(Some(c), Some(m), Ref(r) +: component) => ReferenceTarget(c, m, Nil, r, component)
+ case GenericTarget(Some(c), Some(m), Instance(i) +: OfModule(o) +: Vector()) => InstanceTarget(c, m, Nil, i, o)
+ case GenericTarget(Some(c), Some(m), component) =>
+ val path = getPath.getOrElse(Nil)
+ (getRef, getInstanceOf) match {
+ case (Some((r, comps)), _) => ReferenceTarget(c, m, path, r, comps)
+ case (None, Some((i, o))) => InstanceTarget(c, m, path, i, o)
+ }
+ }
+ Some(target)
+ }
+ }
+
+ override def isLocal: Boolean = !(getPath.nonEmpty && getPath.get.nonEmpty)
+
+ /** If complete, return this [[GenericTarget]]'s path
+ * @return
+ */
+ def getPath: Option[Seq[(Instance, OfModule)]] = if(isComplete) {
+ val allInstOfs = tokens.grouped(2).collect { case Seq(i: Instance, o:OfModule) => (i, o)}.toSeq
+ if(tokens.nonEmpty && tokens.last.isInstanceOf[OfModule]) Some(allInstOfs.dropRight(1)) else Some(allInstOfs)
+ } else {
+ None
+ }
+
+ /** If complete and a reference, return the reference and subcomponents
+ * @return
+ */
+ def getRef: Option[(String, Seq[TargetToken])] = if(isComplete) {
+ val (optRef, comps) = tokens.foldLeft((None: Option[String], Vector.empty[TargetToken])) {
+ case ((None, v), Ref(r)) => (Some(r), v)
+ case ((r: Some[String], comps), c) => (r, comps :+ c)
+ case ((r, v), other) => (None, v)
+ }
+ optRef.map(x => (x, comps))
+ } else {
+ None
+ }
+
+ /** If complete and an instance target, return the instance and ofmodule
+ * @return
+ */
+ def getInstanceOf: Option[(String, String)] = if(isComplete) {
+ tokens.grouped(2).foldLeft(None: Option[(String, String)]) {
+ case (instOf, Seq(i: Instance, o: OfModule)) => Some((i.value, o.value))
+ case (instOf, _) => None
+ }
+ } else {
+ None
+ }
+
+ /** Requires the last [[TargetToken]] in tokens to be one of the [[TargetToken]] keywords
+ * @param default Return value if tokens is empty
+ * @param keywords
+ */
+ private def requireLast(default: Boolean, keywords: String*): Unit = {
+ val isOne = if (tokens.isEmpty) default else tokens.last.is(keywords: _*)
+ require(isOne, s"${tokens.last} is not one of $keywords")
+ }
+
+ /** Appends a target token to tokens, asserts legality
+ * @param token
+ * @return
+ */
+ def add(token: TargetToken): GenericTarget = {
+ token match {
+ case _: Instance => requireLast(true, "inst", "of")
+ case _: OfModule => requireLast(false, "inst")
+ case _: Ref => requireLast(true, "inst", "of")
+ case _: Field => requireLast(true, "ref", "[]", ".", "init", "clock", "reset")
+ case _: Index => requireLast(true, "ref", "[]", ".", "init", "clock", "reset")
+ case Init => requireLast(true, "ref", "[]", ".", "init", "clock", "reset")
+ case Clock => requireLast(true, "ref", "[]", ".", "init", "clock", "reset")
+ case Reset => requireLast(true, "ref", "[]", ".", "init", "clock", "reset")
+ }
+ this.copy(tokens = tokens :+ token)
+ }
+
+ /** Removes n number of target tokens from the right side of [[tokens]] */
+ def remove(n: Int): GenericTarget = this.copy(tokens = tokens.dropRight(n))
+
+ /** Optionally tries to append token to tokens, fails return is not a legal Target */
+ def optAdd(token: TargetToken): Option[Target] = {
+ try{
+ Some(add(token))
+ } catch {
+ case _: IllegalArgumentException => None
+ }
+ }
+
+ /** Checks whether the component is legal (incomplete is ok)
+ * @return
+ */
+ def isLegal: Boolean = {
+ try {
+ var comp: GenericTarget = this.copy(tokens = Vector.empty)
+ for(token <- tokens) {
+ comp = comp.add(token)
+ }
+ true
+ } catch {
+ case _: IllegalArgumentException => false
+ }
+ }
+
+ /** Checks whether the component is legal and complete, meaning the circuitOpt and moduleOpt are nonEmpty and
+ * all Instance(_) are followed by OfModule(_)
+ * @return
+ */
+ def isComplete: Boolean = {
+ isLegal && (isCircuitTarget || isModuleTarget || (isComponentTarget && tokens.tails.forall {
+ case Instance(_) +: OfModule(_) +: tail => true
+ case Instance(_) +: x +: tail => false
+ case x +: OfModule(_) +: tail => false
+ case _ => true
+ } ))
+ }
+
+
+ def isCircuitTarget: Boolean = circuitOpt.nonEmpty && moduleOpt.isEmpty && tokens.isEmpty
+ def isModuleTarget: Boolean = circuitOpt.nonEmpty && moduleOpt.nonEmpty && tokens.isEmpty
+ def isComponentTarget: Boolean = circuitOpt.nonEmpty && moduleOpt.nonEmpty && tokens.nonEmpty
+}
+
+/** Concretely points to a FIRRTL target, no generic selectors
+ * IsLegal
+ */
+trait CompleteTarget extends Target {
+
+ /** @return The circuit of this target */
+ def circuit: String
+
+ /** @return The [[CircuitTarget]] of this target's circuit */
+ def circuitTarget: CircuitTarget = CircuitTarget(circuitOpt.get)
+
+ def getComplete: Option[CompleteTarget] = Some(this)
+
+ /** Adds another level of instance hierarchy
+ * Example: Given root=A and instance=b, transforms (Top, B)/c:C -> (Top, A)/b:B/c:C
+ * @param root
+ * @param instance
+ * @return
+ */
+ def addHierarchy(root: String, instance: String): IsComponent
+
+ override def toTarget: CompleteTarget = this
+}
+
+
+/** A member of a FIRRTL Circuit (e.g. cannot point to a CircuitTarget)
+ * Concrete Subclasses are: [[ModuleTarget]], [[InstanceTarget]], and [[ReferenceTarget]]
+ */
+trait IsMember extends CompleteTarget {
+
+ /** @return Root module, e.g. top-level module of this target */
+ def module: String
+
+ /** @return Returns the instance hierarchy path, if one exists */
+ def path: Seq[(Instance, OfModule)]
+
+ /** @return Creates a path, assuming all Instance and OfModules in this [[IsMember]] is used as a path */
+ def asPath: Seq[(Instance, OfModule)]
+
+ /** @return Tokens of just this member's path */
+ def justPath: Seq[TargetToken]
+
+ /** @return Local tokens of what this member points (not a path) */
+ def notPath: Seq[TargetToken]
+
+ /** @return Same target without a path */
+ def pathlessTarget: IsMember
+
+ /** @return Member's path target */
+ def pathTarget: CompleteTarget
+
+ /** @return Member's top-level module target */
+ def moduleTarget: ModuleTarget = ModuleTarget(circuitOpt.get, moduleOpt.get)
+
+ /** @return Member's parent target */
+ def targetParent: CompleteTarget
+
+ /** @return List of local Instance Targets refering to each instance/ofModule in this member's path */
+ def pathAsTargets: Seq[InstanceTarget] = {
+ val targets = mutable.ArrayBuffer[InstanceTarget]()
+ path.foldLeft((module, Vector.empty[InstanceTarget])) {
+ case ((m, vec), (Instance(i), OfModule(o))) =>
+ (o, vec :+ InstanceTarget(circuit, m, Nil, i, o))
+ }._2
+ }
+
+ /** Resets this target to have a new path
+ * @param newPath
+ * @return
+ */
+ def setPathTarget(newPath: IsModule): CompleteTarget
+}
+
+/** References a module-like target (e.g. a [[ModuleTarget]] or an [[InstanceTarget]])
+ */
+trait IsModule extends IsMember {
+
+ /** @return Creates a new Target, appending a ref */
+ def ref(value: String): ReferenceTarget
+
+ /** @return Creates a new Target, appending an instance and ofmodule */
+ def instOf(instance: String, of: String): InstanceTarget
+}
+
+/** A component of a FIRRTL Module (e.g. cannot point to a CircuitTarget or ModuleTarget)
+ */
+trait IsComponent extends IsMember {
+
+ /** @return The [[ModuleTarget]] of the module that directly contains this component */
+ def encapsulatingModule: String = if(path.isEmpty) module else path.last._2.value
+
+ /** Removes n levels of instance hierarchy
+ *
+ * Example: n=1, transforms (Top, A)/b:B/c:C -> (Top, B)/c:C
+ * @param n
+ * @return
+ */
+ def stripHierarchy(n: Int): IsMember
+
+ override def toNamed: ComponentName = {
+ if(isLocal){
+ val mn = ModuleName(module, CircuitName(circuit))
+ Seq(tokens:_*) match {
+ case Seq(Ref(name)) => ComponentName(name, mn)
+ case Ref(_) :: tail if Target.isOnly(tail, ".", "[]") =>
+ val name = tokens.foldLeft(""){
+ case ("", Ref(name)) => name
+ case (string, Field(value)) => s"$string.$value"
+ case (string, Index(value)) => s"$string[$value]"
+ }
+ ComponentName(name, mn)
+ case Seq(Instance(name), OfModule(o)) => ComponentName(name, mn)
+ }
+ } else {
+ throw new Exception(s"Cannot convert $this to [[ComponentName]]")
+ }
+ }
+
+ override def justPath: Seq[TargetToken] = path.foldLeft(Vector.empty[TargetToken]) {
+ case (vec, (i, o)) => vec ++ Seq(i, o)
+ }
+
+ override def pathTarget: IsModule = {
+ if(path.isEmpty) moduleTarget else {
+ val (i, o) = path.last
+ InstanceTarget(circuit, module, path.dropRight(1), i.value, o.value)
+ }
+ }
+
+ override def tokens = justPath ++ notPath
+
+ override def isLocal = path.isEmpty
+}
+
+
+/** Target pointing to a FIRRTL [[firrtl.ir.Circuit]]
+ * @param circuit Name of a FIRRTL circuit
+ */
+case class CircuitTarget(circuit: String) extends CompleteTarget {
+
+ /** Creates a [[ModuleTarget]] of provided name and this circuit
+ * @param m
+ * @return
+ */
+ def module(m: String): ModuleTarget = ModuleTarget(circuit, m)
+
+ override def circuitOpt: Option[String] = Some(circuit)
+
+ override def moduleOpt: Option[String] = None
+
+ override def tokens = Nil
+
+ override def isLocal = true
+
+ override def addHierarchy(root: String, instance: String): ReferenceTarget =
+ ReferenceTarget(circuit, root, Nil, instance, Nil)
+
+ override def toNamed: CircuitName = CircuitName(circuit)
+}
+
+/** Target pointing to a FIRRTL [[firrtl.ir.DefModule]]
+ * @param circuit Circuit containing the module
+ * @param module Name of the module
+ */
+case class ModuleTarget(circuit: String, module: String) extends IsModule {
+
+ override def circuitOpt: Option[String] = Some(circuit)
+
+ override def moduleOpt: Option[String] = Some(module)
+
+ override def tokens: Seq[TargetToken] = Nil
+
+ override def targetParent: CircuitTarget = CircuitTarget(circuit)
+
+ override def addHierarchy(root: String, instance: String): InstanceTarget = InstanceTarget(circuit, root, Nil, instance, module)
+
+ override def ref(value: String): ReferenceTarget = ReferenceTarget(circuit, module, Nil, value, Nil)
+
+ override def instOf(instance: String, of: String): InstanceTarget = InstanceTarget(circuit, module, Nil, instance, of)
+
+ override def asPath = Nil
+
+ override def path: Seq[(Instance, OfModule)] = Nil
+
+ override def justPath: Seq[TargetToken] = Nil
+
+ override def notPath: Seq[TargetToken] = Nil
+
+ override def pathlessTarget: ModuleTarget = this
+
+ override def pathTarget: ModuleTarget = this
+
+ override def isLocal = true
+
+ override def setPathTarget(newPath: IsModule): IsModule = newPath
+
+ override def toNamed: ModuleName = ModuleName(module, CircuitName(circuit))
+}
+
+/** Target pointing to a declared named component in a [[firrtl.ir.DefModule]]
+ * This includes: [[firrtl.ir.Port]], [[firrtl.ir.DefWire]], [[firrtl.ir.DefRegister]], [[firrtl.ir.DefInstance]],
+ * [[firrtl.ir.DefMemory]], [[firrtl.ir.DefNode]]
+ * @param circuit Name of the encapsulating circuit
+ * @param module Name of the root module of this reference
+ * @param path Path through instance/ofModules
+ * @param ref Name of component
+ * @param component Subcomponent of this reference, e.g. field or index
+ */
+case class ReferenceTarget(circuit: String,
+ module: String,
+ override val path: Seq[(Instance, OfModule)],
+ ref: String,
+ component: Seq[TargetToken]) extends IsComponent {
+
+ /** @param value Index value of this target
+ * @return A new [[ReferenceTarget]] to the specified index of this [[ReferenceTarget]]
+ */
+ def index(value: Int): ReferenceTarget = ReferenceTarget(circuit, module, path, ref, component :+ Index(value))
+
+ /** @param value Field name of this target
+ * @return A new [[ReferenceTarget]] to the specified field of this [[ReferenceTarget]]
+ */
+ def field(value: String): ReferenceTarget = ReferenceTarget(circuit, module, path, ref, component :+ Field(value))
+
+ /** @return The initialization value of this reference, must be to a [[firrtl.ir.DefRegister]] */
+ def init: ReferenceTarget = ReferenceTarget(circuit, module, path, ref, component :+ Init)
+
+ /** @return The reset signal of this reference, must be to a [[firrtl.ir.DefRegister]] */
+ def reset: ReferenceTarget = ReferenceTarget(circuit, module, path, ref, component :+ Reset)
+
+ /** @return The clock signal of this reference, must be to a [[firrtl.ir.DefRegister]] */
+ def clock: ReferenceTarget = ReferenceTarget(circuit, module, path, ref, component :+ Clock)
+
+ override def circuitOpt: Option[String] = Some(circuit)
+
+ override def moduleOpt: Option[String] = Some(module)
+
+ override def targetParent: CompleteTarget = component match {
+ case Nil =>
+ if(path.isEmpty) moduleTarget else {
+ val (i, o) = path.last
+ InstanceTarget(circuit, module, path.dropRight(1), i.value, o.value)
+ }
+ case other => ReferenceTarget(circuit, module, path, ref, component.dropRight(1))
+ }
+
+ override def notPath: Seq[TargetToken] = Ref(ref) +: component
+
+ override def addHierarchy(root: String, instance: String): ReferenceTarget =
+ ReferenceTarget(circuit, root, (Instance(instance), OfModule(module)) +: path, ref, component)
+
+ override def stripHierarchy(n: Int): ReferenceTarget = {
+ require(path.size >= n, s"Cannot strip $n levels of hierarchy from $this")
+ if(n == 0) this else {
+ val newModule = path(n - 1)._2.value
+ ReferenceTarget(circuit, newModule, path.drop(n), ref, component)
+ }
+ }
+
+ override def pathlessTarget: ReferenceTarget = ReferenceTarget(circuit, encapsulatingModule, Nil, ref, component)
+
+ override def setPathTarget(newPath: IsModule): ReferenceTarget =
+ ReferenceTarget(newPath.circuit, newPath.module, newPath.asPath, ref, component)
+
+ override def asPath: Seq[(Instance, OfModule)] = path
+}
+
+/** Points to an instance declaration of a module (termed an ofModule)
+ * @param circuit Encapsulating circuit
+ * @param module Root module (e.g. the base module of this target)
+ * @param path Path through instance/ofModules
+ * @param instance Name of the instance
+ * @param ofModule Name of the instance's module
+ */
+case class InstanceTarget(circuit: String,
+ module: String,
+ override val path: Seq[(Instance, OfModule)],
+ instance: String,
+ ofModule: String) extends IsModule with IsComponent {
+
+ /** @return a [[ReferenceTarget]] referring to this declaration of this instance */
+ def asReference: ReferenceTarget = ReferenceTarget(circuit, module, path, instance, Nil)
+
+ /** @return a [[ReferenceTarget]] referring to declaration of this ofModule */
+ def ofModuleTarget: ModuleTarget = ModuleTarget(circuit, ofModule)
+
+ override def circuitOpt: Option[String] = Some(circuit)
+
+ override def moduleOpt: Option[String] = Some(module)
+
+ override def targetParent: IsModule = {
+ if(isLocal) ModuleTarget(circuit, module) else {
+ val (newInstance, newOfModule) = path.last
+ InstanceTarget(circuit, module, path.dropRight(1), newInstance.value, newOfModule.value)
+ }
+ }
+
+ override def addHierarchy(root: String, inst: String): InstanceTarget =
+ InstanceTarget(circuit, root, (Instance(inst), OfModule(module)) +: path, instance, ofModule)
+
+ override def ref(value: String): ReferenceTarget = ReferenceTarget(circuit, module, asPath, value, Nil)
+
+ override def instOf(inst: String, of: String): InstanceTarget = InstanceTarget(circuit, module, asPath, inst, of)
+
+ override def stripHierarchy(n: Int): IsModule = {
+ require(path.size >= n, s"Cannot strip $n levels of hierarchy from $this")
+ if(n == 0) this else {
+ val newModule = path(n - 1)._2.value
+ InstanceTarget(circuit, newModule, path.drop(n), instance, ofModule)
+ }
+ }
+
+ override def asPath: Seq[(Instance, OfModule)] = path :+ (Instance(instance), OfModule(ofModule))
+
+ override def pathlessTarget: InstanceTarget = InstanceTarget(circuit, encapsulatingModule, Nil, instance, ofModule)
+
+ override def notPath = Seq(Instance(instance), OfModule(ofModule))
+
+ override def setPathTarget(newPath: IsModule): InstanceTarget =
+ InstanceTarget(newPath.circuit, newPath.module, newPath.asPath, instance, ofModule)
+}
+
+
+/** Named classes associate an annotation with a component in a Firrtl circuit */
+@deprecated("Use Target instead, will be removed in 1.3", "1.2")
+sealed trait Named {
+ def serialize: String
+ def toTarget: CompleteTarget
+}
+
+@deprecated("Use Target instead, will be removed in 1.3", "1.2")
+final case class CircuitName(name: String) extends Named {
+ if(!validModuleName(name)) throw AnnotationException(s"Illegal circuit name: $name")
+ def serialize: String = name
+ def toTarget: CircuitTarget = CircuitTarget(name)
+}
+
+@deprecated("Use Target instead, will be removed in 1.3", "1.2")
+final case class ModuleName(name: String, circuit: CircuitName) extends Named {
+ if(!validModuleName(name)) throw AnnotationException(s"Illegal module name: $name")
+ def serialize: String = circuit.serialize + "." + name
+ def toTarget: ModuleTarget = ModuleTarget(circuit.name, name)
+}
+
+@deprecated("Use Target instead, will be removed in 1.3", "1.2")
+final case class ComponentName(name: String, module: ModuleName) extends Named {
+ if(!validComponentName(name)) throw AnnotationException(s"Illegal component name: $name")
+ def expr: Expression = toExp(name)
+ def serialize: String = module.serialize + "." + name
+ def toTarget: ReferenceTarget = {
+ Target.toTargetTokens(name).toList match {
+ case Ref(r) :: components => ReferenceTarget(module.circuit.name, module.name, Nil, r, components)
+ case other => throw Target.NamedException(s"Cannot convert $this into [[ReferenceTarget]]: $other")
+ }
+ }
+}
diff --git a/src/main/scala/firrtl/annotations/TargetToken.scala b/src/main/scala/firrtl/annotations/TargetToken.scala
new file mode 100644
index 00000000..587f30eb
--- /dev/null
+++ b/src/main/scala/firrtl/annotations/TargetToken.scala
@@ -0,0 +1,46 @@
+// See LICENSE for license details.
+
+package firrtl.annotations
+
+/** Building block to represent a [[Target]] of a FIRRTL component */
+sealed trait TargetToken {
+ def keyword: String
+ def value: Any
+
+ /** Returns whether this token is one of the type of tokens whose keyword is passed as an argument
+ * @param keywords
+ * @return
+ */
+ def is(keywords: String*): Boolean = {
+ keywords.map { kw =>
+ require(TargetToken.keyword2targettoken.keySet.contains(kw),
+ s"Keyword $kw must be in set ${TargetToken.keyword2targettoken.keys}")
+ val lastClass = this.getClass
+ lastClass == TargetToken.keyword2targettoken(kw)("0").getClass
+ }.reduce(_ || _)
+ }
+}
+
+/** Object containing all [[TargetToken]] subclasses */
+case object TargetToken {
+ case class Instance(value: String) extends TargetToken { override def keyword: String = "inst" }
+ case class OfModule(value: String) extends TargetToken { override def keyword: String = "of" }
+ case class Ref(value: String) extends TargetToken { override def keyword: String = "ref" }
+ case class Index(value: Int) extends TargetToken { override def keyword: String = "[]" }
+ case class Field(value: String) extends TargetToken { override def keyword: String = "." }
+ case object Clock extends TargetToken { override def keyword: String = "clock"; val value = "" }
+ case object Init extends TargetToken { override def keyword: String = "init"; val value = "" }
+ case object Reset extends TargetToken { override def keyword: String = "reset"; val value = "" }
+
+ val keyword2targettoken = Map(
+ "inst" -> ((value: String) => Instance(value)),
+ "of" -> ((value: String) => OfModule(value)),
+ "ref" -> ((value: String) => Ref(value)),
+ "[]" -> ((value: String) => Index(value.toInt)),
+ "." -> ((value: String) => Field(value)),
+ "clock" -> ((value: String) => Clock),
+ "init" -> ((value: String) => Init),
+ "reset" -> ((value: String) => Reset)
+ )
+}
+
diff --git a/src/main/scala/firrtl/annotations/analysis/DuplicationHelper.scala b/src/main/scala/firrtl/annotations/analysis/DuplicationHelper.scala
new file mode 100644
index 00000000..ba3ca9a9
--- /dev/null
+++ b/src/main/scala/firrtl/annotations/analysis/DuplicationHelper.scala
@@ -0,0 +1,146 @@
+// See LICENSE for license details.
+
+package firrtl.annotations.analysis
+
+import firrtl.Namespace
+import firrtl.annotations._
+import firrtl.annotations.TargetToken.{Instance, OfModule, Ref}
+import firrtl.Utils.throwInternalError
+
+import scala.collection.mutable
+
+/** Used by [[firrtl.annotations.transforms.EliminateTargetPaths]] to eliminate target paths
+ * Calculates needed modifications to a circuit's module/instance hierarchy
+ */
+case class DuplicationHelper(existingModules: Set[String]) {
+ // Maps instances to the module it instantiates (an ofModule)
+ type InstanceOfModuleMap = mutable.HashMap[Instance, OfModule]
+
+ // Maps a module to the instance/ofModules it instantiates
+ type ModuleHasInstanceOfModuleMap = mutable.HashMap[String, InstanceOfModuleMap]
+
+ // Maps original module names to new duplicated modules and their encapsulated instance/ofModules
+ type DupMap = mutable.HashMap[String, ModuleHasInstanceOfModuleMap]
+
+ // Internal state to keep track of how paths duplicate
+ private val dupMap = new DupMap()
+
+ // Internal record of which paths are renamed to which new names, in the case of a collision
+ private val cachedNames = mutable.HashMap[(String, Seq[(Instance, OfModule)]), String]() ++
+ existingModules.map(m => (m, Nil) -> m)
+
+ // Internal record of all paths to ensure unique name generation
+ private val allModules = mutable.HashSet[String]() ++ existingModules
+
+ /** Updates internal state (dupMap) to calculate instance hierarchy modifications so t's tokens in an instance can be
+ * expressed as a tokens in a module (e.g. uniquify/duplicate the instance path in t's tokens)
+ * @param t An instance-resolved component
+ */
+ def expandHierarchy(t: IsMember): Unit = {
+ val path = t.asPath
+ path.reverse.tails.map { _.reverse }.foreach { duplicate(t.module, _) }
+ }
+
+ /** Updates dupMap with how original module names map to new duplicated module names
+ * @param top Root module of a component
+ * @param path Path down instance hierarchy of a component
+ */
+ private def duplicate(top: String, path: Seq[(Instance, OfModule)]): Unit = {
+ val (originalModule, instance, ofModule) = path.size match {
+ case 0 => return
+ case 1 => (top, path.head._1, path.head._2)
+ case _ => (path(path.length - 2)._2.value, path.last._1, path.last._2)
+ }
+ val originalModuleToDupedModule = dupMap.getOrElseUpdate(originalModule, new ModuleHasInstanceOfModuleMap())
+ val dupedModule = getModuleName(top, path.dropRight(1))
+ val dupedModuleToInstances = originalModuleToDupedModule.getOrElseUpdate(dupedModule, new InstanceOfModuleMap())
+ val dupedInstanceModule = getModuleName(top, path)
+ dupedModuleToInstances += ((instance, OfModule(dupedInstanceModule)))
+
+ val originalInstanceModuleToDupedModule = dupMap.getOrElseUpdate(ofModule.value, new ModuleHasInstanceOfModuleMap())
+ originalInstanceModuleToDupedModule.getOrElseUpdate(dupedInstanceModule, new InstanceOfModuleMap())
+ }
+
+ /** Deterministic name-creation of a duplicated module
+ * @param top
+ * @param path
+ * @return
+ */
+ def getModuleName(top: String, path: Seq[(Instance, OfModule)]): String = {
+ cachedNames.get((top, path)) match {
+ case None => // Need a new name
+ val prefix = path.last._2.value + "___"
+ val postfix = top + "_" + path.map { case (i, m) => i.value }.mkString("_")
+ val ns = mutable.HashSet(allModules.toSeq: _*)
+ val finalName = firrtl.passes.Uniquify.findValidPrefix(prefix, Seq(postfix), ns) + postfix
+ allModules += finalName
+ cachedNames((top, path)) = finalName
+ finalName
+ case Some(newName) => newName
+ }
+ }
+
+ /** Return the duplicated module (formerly originalOfModule) instantiated by instance in newModule (formerly
+ * originalModule)
+ * @param originalModule original encapsulating module
+ * @param newModule new name of encapsulating module
+ * @param instance instance name being declared in encapsulating module
+ * @param originalOfModule original module being instantiated in originalModule
+ * @return
+ */
+ def getNewOfModule(originalModule: String,
+ newModule: String,
+ instance: Instance,
+ originalOfModule: OfModule): OfModule = {
+ dupMap.get(originalModule) match {
+ case None => // No duplication, can return originalOfModule
+ originalOfModule
+ case Some(newDupedModules) =>
+ newDupedModules.get(newModule) match {
+ case None if newModule != originalModule => throwInternalError("BAD")
+ case None => // No duplication, can return originalOfModule
+ originalOfModule
+ case Some(newDupedModule) =>
+ newDupedModule.get(instance) match {
+ case None => // Not duped, can return originalOfModule
+ originalOfModule
+ case Some(newOfModule) =>
+ newOfModule
+ }
+ }
+ }
+ }
+
+ /** Returns the names of this module's duplicated (including the original name)
+ * @param module
+ * @return
+ */
+ def getDuplicates(module: String): Set[String] = {
+ dupMap.get(module).map(_.keys.toSet[String]).getOrElse(Set.empty[String]) ++ Set(module)
+ }
+
+ /** Rewrites t with new module/instance hierarchy calculated after repeated calls to [[expandHierarchy]]
+ * @param t A target
+ * @return t rewritten, is a seq because if the t.module has been duplicated, it must now refer to multiple modules
+ */
+ def makePathless(t: IsMember): Seq[IsMember] = {
+ val top = t.module
+ val path = t.asPath
+ val newTops = getDuplicates(top)
+ newTops.map { newTop =>
+ val newPath = mutable.ArrayBuffer[TargetToken]()
+ path.foldLeft((top, newTop)) { case ((originalModule, newModule), (instance, ofModule)) =>
+ val newOfModule = getNewOfModule(originalModule, newModule, instance, ofModule)
+ newPath ++= Seq(instance, newOfModule)
+ (ofModule.value, newOfModule.value)
+ }
+ val module = if(newPath.nonEmpty) newPath.last.value.toString else newTop
+ t.notPath match {
+ case Seq() => ModuleTarget(t.circuit, module)
+ case Instance(i) +: OfModule(m) +: Seq() => ModuleTarget(t.circuit, module)
+ case Ref(r) +: components => ReferenceTarget(t.circuit, module, Nil, r, components)
+ }
+ }.toSeq
+ }
+}
+
diff --git a/src/main/scala/firrtl/annotations/transforms/EliminateTargetPaths.scala b/src/main/scala/firrtl/annotations/transforms/EliminateTargetPaths.scala
new file mode 100644
index 00000000..8f604c9f
--- /dev/null
+++ b/src/main/scala/firrtl/annotations/transforms/EliminateTargetPaths.scala
@@ -0,0 +1,167 @@
+// See LICENSE for license details.
+
+package firrtl.annotations.transforms
+
+import firrtl.Mappers._
+import firrtl.analyses.InstanceGraph
+import firrtl.annotations.TargetToken.{Instance, OfModule}
+import firrtl.annotations.analysis.DuplicationHelper
+import firrtl.annotations._
+import firrtl.ir._
+import firrtl.{CircuitForm, CircuitState, FIRRTLException, HighForm, RenameMap, Transform, WDefInstance}
+
+import scala.collection.mutable
+
+
+/** Group of targets that should become local targets
+ * @param targets
+ */
+case class ResolvePaths(targets: Seq[CompleteTarget]) extends Annotation {
+ override def update(renames: RenameMap): Seq[Annotation] = {
+ val newTargets = targets.flatMap(t => renames.get(t).getOrElse(Seq(t)))
+ Seq(ResolvePaths(newTargets))
+ }
+}
+
+case class NoSuchTargetException(message: String) extends FIRRTLException(message)
+
+/** For a set of non-local targets, modify the instance/module hierarchy of the circuit such that
+ * the paths in each non-local target can be removed
+ *
+ * In other words, if targeting a specific instance of a module, duplicate that module with a unique name
+ * and instantiate the new module instead.
+ *
+ * Consumes [[ResolvePaths]]
+ *
+ * E.g. for non-local target A/b:B/c:C/d, rename the following
+ * A/b:B/c:C/d -> C_/d
+ * A/b:B/c:C -> B_/c:C_
+ * A/b:B -> A/b:B_
+ * B/x -> (B/x, B_/x) // where x is any reference in B
+ * C/x -> (C/x, C_/x) // where x is any reference in C
+ */
+class EliminateTargetPaths extends Transform {
+
+ def inputForm: CircuitForm = HighForm
+
+ def outputForm: CircuitForm = HighForm
+
+ /** Replaces old ofModules with new ofModules by calling dupMap methods
+ * Updates oldUsedOfModules, newUsedOfModules
+ * @param originalModule Original name of this module
+ * @param newModule New name of this module
+ * @param s
+ * @return
+ */
+ private def onStmt(dupMap: DuplicationHelper,
+ oldUsedOfModules: mutable.HashSet[String],
+ newUsedOfModules: mutable.HashSet[String])
+ (originalModule: String, newModule: String)
+ (s: Statement): Statement = s match {
+ case d@DefInstance(_, name, module) =>
+ val ofModule = dupMap.getNewOfModule(originalModule, newModule, Instance(name), OfModule(module)).value
+ newUsedOfModules += ofModule
+ oldUsedOfModules += module
+ d.copy(module = ofModule)
+ case d@WDefInstance(_, name, module, _) =>
+ val ofModule = dupMap.getNewOfModule(originalModule, newModule, Instance(name), OfModule(module)).value
+ newUsedOfModules += ofModule
+ oldUsedOfModules += module
+ d.copy(module = ofModule)
+ case other => other map onStmt(dupMap, oldUsedOfModules, newUsedOfModules)(originalModule, newModule)
+ }
+
+ /** Returns a modified circuit and [[RenameMap]] containing the associated target remapping
+ * @param cir
+ * @param targets
+ * @return
+ */
+ def run(cir: Circuit, targets: Seq[IsMember]): (Circuit, RenameMap) = {
+
+ val dupMap = DuplicationHelper(cir.modules.map(_.name).toSet)
+
+ // For each target, record its path and calculate the necessary modifications to circuit
+ targets.foreach { t => dupMap.expandHierarchy(t) }
+
+ // Records original list of used ofModules
+ val oldUsedOfModules = mutable.HashSet[String]()
+ oldUsedOfModules += cir.main
+
+ // Records new list of used ofModules
+ val newUsedOfModules = mutable.HashSet[String]()
+ newUsedOfModules += cir.main
+
+ // Contains new list of module declarations
+ val duplicatedModuleList = mutable.ArrayBuffer[DefModule]()
+
+ // Foreach module, calculate the unique names of its duplicates
+ // Then, update the ofModules of instances that it encapsulates
+ cir.modules.foreach { m =>
+ dupMap.getDuplicates(m.name).foreach { newName =>
+ val newM = m match {
+ case e: ExtModule => e.copy(name = newName)
+ case o: Module =>
+ o.copy(name = newName, body = onStmt(dupMap, oldUsedOfModules, newUsedOfModules)(m.name, newName)(o.body))
+ }
+ duplicatedModuleList += newM
+ }
+ }
+
+ // Calculate the final module list
+ // A module is in the final list if:
+ // 1) it is a module that is instantiated (new or old)
+ // 2) it is an old module that was not instantiated and is still not instantiated
+ val finalModuleList = duplicatedModuleList.filter(m =>
+ newUsedOfModules.contains(m.name) || (!newUsedOfModules.contains(m.name) && !oldUsedOfModules.contains(m.name))
+ )
+
+ // Records how targets have been renamed
+ val renameMap = RenameMap()
+
+ // Foreach target, calculate the pathless version and only rename targets that are instantiated
+ targets.foreach { t =>
+ val newTsx = dupMap.makePathless(t)
+ val newTs = newTsx.filter(c => newUsedOfModules.contains(c.moduleOpt.get))
+ if(newTs.nonEmpty) {
+ renameMap.record(t, newTs)
+ }
+ }
+
+ // Return modified circuit and associated renameMap
+ (cir.copy(modules = finalModuleList), renameMap)
+ }
+
+ override protected def execute(state: CircuitState): CircuitState = {
+
+ val annotations = state.annotations.collect { case a: ResolvePaths => a }
+
+ // Collect targets that are not local
+ val targets = annotations.flatMap(_.targets.collect { case x: IsMember => x })
+
+ // Check validity of paths in targets
+ val instanceOfModules = new InstanceGraph(state.circuit).getChildrenInstanceOfModule
+ val targetsWithInvalidPaths = mutable.ArrayBuffer[IsMember]()
+ targets.foreach { t =>
+ val path = t match {
+ case m: ModuleTarget => Nil
+ case i: InstanceTarget => i.asPath
+ case r: ReferenceTarget => r.path
+ }
+ path.foldLeft(t.module) { case (module, (inst: Instance, of: OfModule)) =>
+ val childrenOpt = instanceOfModules.get(module)
+ if(childrenOpt.isEmpty || !childrenOpt.get.contains((inst, of))) {
+ targetsWithInvalidPaths += t
+ }
+ of.value
+ }
+ }
+ if(targetsWithInvalidPaths.nonEmpty) {
+ val string = targetsWithInvalidPaths.mkString(",")
+ throw NoSuchTargetException(s"""Some targets have illegal paths that cannot be resolved/eliminated: $string""")
+ }
+
+ val (newCircuit, renameMap) = run(state.circuit, targets)
+
+ state.copy(circuit = newCircuit, renames = Some(renameMap))
+ }
+}