diff options
| author | Adam Izraelevitz | 2018-10-30 19:30:03 -0700 |
|---|---|---|
| committer | GitHub | 2018-10-30 19:30:03 -0700 |
| commit | 0a4bcaa4053aca16f21f899ba76b1b751cfb47b3 (patch) | |
| tree | df4ded76ea4c0e448f4839c6fc8838799263dea0 /src/main | |
| parent | 1e89e41604c9925c7de89eb85c7d7d0fa48e1e08 (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')
22 files changed, 1937 insertions, 273 deletions
diff --git a/src/main/scala/firrtl/Compiler.scala b/src/main/scala/firrtl/Compiler.scala index 9044c5a8..80ba42c4 100644 --- a/src/main/scala/firrtl/Compiler.scala +++ b/src/main/scala/firrtl/Compiler.scala @@ -4,121 +4,23 @@ package firrtl import logger._ import java.io.Writer -import annotations._ -import scala.collection.mutable - -import firrtl.annotations._ // Note that wildcard imports are not great.... -import firrtl.ir.Circuit -import firrtl.Utils.{error, throwInternalError} - -object RenameMap { - def apply(map: Map[Named, Seq[Named]]) = { - val rm = new RenameMap - rm.addMap(map) - rm - } - def apply() = new RenameMap -} -/** Map old names to new names - * - * Transforms that modify names should return a [[RenameMap]] with the [[CircuitState]] - * These are mutable datastructures for convenience - */ -// TODO This should probably be refactored into immutable and mutable versions -final class RenameMap private () { - private val underlying = mutable.HashMap[Named, Seq[Named]]() - /** Get renames of a [[CircuitName]] - * @note A [[CircuitName]] can only be renamed to a single [[CircuitName]] - */ - def get(key: CircuitName): Option[CircuitName] = underlying.get(key).map { - case Seq(c: CircuitName) => c - case other => error(s"Unsupported Circuit rename to $other!") - } - /** Get renames of a [[ModuleName]] - * @note A [[ModuleName]] can only be renamed to one-or-more [[ModuleName]]s - */ - def get(key: ModuleName): Option[Seq[ModuleName]] = { - def nestedRename(m: ModuleName): Option[Seq[ModuleName]] = - this.get(m.circuit).map(cname => Seq(ModuleName(m.name, cname))) - underlying.get(key) match { - case Some(names) => Some(names.flatMap { - case m: ModuleName => - nestedRename(m).getOrElse(Seq(m)) - case other => error(s"Unsupported Module rename of $key to $other") - }) - case None => nestedRename(key) - } - } - /** Get renames of a [[ComponentName]] - * @note A [[ComponentName]] can only be renamed to one-or-more [[ComponentName]]s - */ - def get(key: ComponentName): Option[Seq[ComponentName]] = { - def nestedRename(c: ComponentName): Option[Seq[ComponentName]] = - this.get(c.module).map { modules => - modules.map(mname => ComponentName(c.name, mname)) - } - underlying.get(key) match { - case Some(names) => Some(names.flatMap { - case c: ComponentName => - nestedRename(c).getOrElse(Seq(c)) - case other => error(s"Unsupported Component rename of $key to $other") - }) - case None => nestedRename(key) - } - } - /** Get new names for an old name - * - * This is analogous to get on standard Scala collection Maps - * None indicates the key was not renamed - * Empty indicates the name was deleted - */ - def get(key: Named): Option[Seq[Named]] = key match { - case c: ComponentName => this.get(c) - case m: ModuleName => this.get(m) - // The CircuitName version returns Option[CircuitName] - case c: CircuitName => this.get(c).map(Seq(_)) - } +import firrtl.RenameMap.{CircularRenameException, IllegalRenameException} - // Mutable helpers - private var circuitName: String = "" - private var moduleName: String = "" - def setModule(s: String) = - moduleName = s - def setCircuit(s: String) = - circuitName = s - def rename(from: String, to: String): Unit = rename(from, Seq(to)) - def rename(from: String, tos: Seq[String]): Unit = { - val fromName = ComponentName(from, ModuleName(moduleName, CircuitName(circuitName))) - val tosName = tos map { to => - ComponentName(to, ModuleName(moduleName, CircuitName(circuitName))) - } - rename(fromName, tosName) - } - def rename(from: Named, to: Named): Unit = rename(from, Seq(to)) - def rename(from: Named, tos: Seq[Named]): Unit = (from, tos) match { - case (x, Seq(y)) if x == y => // TODO is this check expensive in common case? - case _ => - underlying(from) = underlying.getOrElse(from, Seq.empty) ++ tos - } - def delete(names: Seq[String]): Unit = names.foreach(delete(_)) - def delete(name: String): Unit = - delete(ComponentName(name, ModuleName(moduleName, CircuitName(circuitName)))) - def delete(name: Named): Unit = - underlying(name) = Seq.empty - def addMap(map: Map[Named, Seq[Named]]) = - underlying ++= map - def serialize: String = underlying.map { case (k, v) => - k.serialize + "=>" + v.map(_.serialize).mkString(", ") - }.mkString("\n") -} +import scala.collection.mutable +import firrtl.annotations._ +import firrtl.ir.{Circuit, Expression} +import firrtl.Utils.{error, throwInternalError} +import firrtl.annotations.TargetToken +import firrtl.annotations.TargetToken.{Field, Index} +import firrtl.annotations.transforms.{EliminateTargetPaths, ResolvePaths} /** Container of all annotations for a Firrtl compiler */ class AnnotationSeq private (private[firrtl] val underlying: List[Annotation]) { def toSeq: Seq[Annotation] = underlying.toSeq } object AnnotationSeq { - def apply(xs: Seq[Annotation]) = new AnnotationSeq(xs.toList) + def apply(xs: Seq[Annotation]): AnnotationSeq = new AnnotationSeq(xs.toList) } /** Current State of the Circuit @@ -145,15 +47,45 @@ case class CircuitState( case None => throw new FIRRTLException(s"No EmittedCircuit found! Did you delete any annotations?\n$deletedAnnotations") } + /** Helper function for extracting emitted components from annotations */ def emittedComponents: Seq[EmittedComponent] = annotations.collect { case emitted: EmittedAnnotation[_] => emitted.value } def deletedAnnotations: Seq[Annotation] = annotations.collect { case anno: DeletedAnnotation => anno } + + /** Returns a new CircuitState with all targets being resolved. + * Paths through instances are replaced with a uniquified final target + * Includes modifying the circuit and annotations + * @param targets + * @return + */ + def resolvePaths(targets: Seq[CompleteTarget]): CircuitState = { + val newCS = new EliminateTargetPaths().runTransform(this.copy(annotations = ResolvePaths(targets) +: annotations )) + newCS.copy(form = form) + } + + /** Returns a new CircuitState with the targets of every annotation of a type in annoClasses + * @param annoClasses + * @return + */ + def resolvePathsOf(annoClasses: Class[_]*): CircuitState = { + val targets = getAnnotationsOf(annoClasses:_*).flatMap(_.getTargets) + if(targets.nonEmpty) resolvePaths(targets.flatMap{_.getComplete}) else this + } + + /** Returns all annotations which are of a class in annoClasses + * @param annoClasses + * @return + */ + def getAnnotationsOf(annoClasses: Class[_]*): AnnotationSeq = { + annotations.collect { case a if annoClasses.contains(a.getClass) => a } + } } + object CircuitState { def apply(circuit: Circuit, form: CircuitForm): CircuitState = apply(circuit, form, Seq()) - def apply(circuit: Circuit, form: CircuitForm, annotations: AnnotationSeq) = + def apply(circuit: Circuit, form: CircuitForm, annotations: AnnotationSeq): CircuitState = new CircuitState(circuit, form, annotations, None) } @@ -170,6 +102,9 @@ sealed abstract class CircuitForm(private val value: Int) extends Ordered[Circui // Note that value is used only to allow comparisons def compare(that: CircuitForm): Int = this.value - that.value } + +// scalastyle:off magic.number +// These magic numbers give an ordering to CircuitForm /** Chirrtl Form * * The form of the circuit emitted by Chisel. Not a true Firrtl form. @@ -178,7 +113,7 @@ sealed abstract class CircuitForm(private val value: Int) extends Ordered[Circui * * See [[CDefMemory]] and [[CDefMPort]] */ -final case object ChirrtlForm extends CircuitForm(3) +final case object ChirrtlForm extends CircuitForm(value = 3) /** High Form * * As detailed in the Firrtl specification @@ -216,6 +151,7 @@ final case object LowForm extends CircuitForm(0) final case object UnknownForm extends CircuitForm(-1) { override def compare(that: CircuitForm): Int = { sys.error("Illegal to compare UnknownForm"); 0 } } +// scalastyle:on magic.number /** The basic unit of operating on a Firrtl AST */ abstract class Transform extends LazyLogging { @@ -246,6 +182,12 @@ abstract class Transform extends LazyLogging { state.annotations.collect { case a: LegacyAnnotation if a.transform == this.getClass => a } } + /** Executes before any transform's execute method + * @param state + * @return + */ + private[firrtl] def prepare(state: CircuitState): CircuitState = state + /** Perform the transform and update annotations. * * @param state Input Firrtl AST @@ -254,7 +196,7 @@ abstract class Transform extends LazyLogging { final def runTransform(state: CircuitState): CircuitState = { logger.info(s"======== Starting Transform $name ========") - val (timeMillis, result) = Utils.time { execute(state) } + val (timeMillis, result) = Utils.time { execute(prepare(state)) } logger.info(s"""----------------------------${"-" * name.size}---------\n""") logger.info(f"Time: $timeMillis%.1f ms") @@ -296,10 +238,23 @@ abstract class Transform extends LazyLogging { // For each annotation, rename all annotations. val renames = renameOpt.getOrElse(RenameMap()) - for { - anno <- newAnnotations.toSeq - newAnno <- anno.update(renames) - } yield newAnno + val remapped2original = mutable.LinkedHashMap[Annotation, mutable.LinkedHashSet[Annotation]]() + val keysOfNote = mutable.LinkedHashSet[Annotation]() + val finalAnnotations = newAnnotations.flatMap { anno => + val remappedAnnos = anno.update(renames) + remappedAnnos.foreach { remapped => + val set = remapped2original.getOrElseUpdate(remapped, mutable.LinkedHashSet.empty[Annotation]) + set += anno + if(set.size > 1) keysOfNote += remapped + } + remappedAnnos + }.toSeq + keysOfNote.foreach { key => + logger.debug(s"""The following original annotations are renamed to the same new annotation.""") + logger.debug(s"""Original Annotations:\n ${remapped2original(key).mkString("\n ")}""") + logger.debug(s"""New Annotation:\n $key""") + } + finalAnnotations } } @@ -321,6 +276,19 @@ abstract class SeqTransform extends Transform with SeqTransformBased { } } +/** Extend for transforms that require resolved targets in their annotations + * Ensures all targets in annotations of a class in annotationClasses are resolved before the execute method + */ +trait ResolvedAnnotationPaths { + this: Transform => + + val annotationClasses: Traversable[Class[_]] + + override def prepare(state: CircuitState): CircuitState = { + state.resolvePathsOf(annotationClasses.toSeq:_*) + } +} + /** Defines old API for Emission. Deprecated */ trait Emitter extends Transform { @deprecated("Use emission annotations instead", "firrtl 1.0") @@ -406,8 +374,8 @@ trait Compiler extends LazyLogging { def transforms: Seq[Transform] // Similar to (input|output)Form on [[Transform]] but derived from this Compiler's transforms - def inputForm = transforms.head.inputForm - def outputForm = transforms.last.outputForm + def inputForm: CircuitForm = transforms.head.inputForm + def outputForm: CircuitForm = transforms.last.outputForm private def transformsLegal(xforms: Seq[Transform]): Boolean = if (xforms.size < 2) { diff --git a/src/main/scala/firrtl/RenameMap.scala b/src/main/scala/firrtl/RenameMap.scala new file mode 100644 index 00000000..e95260af --- /dev/null +++ b/src/main/scala/firrtl/RenameMap.scala @@ -0,0 +1,424 @@ +// See LICENSE for license details. + +package firrtl + +import annotations._ +import firrtl.RenameMap.{CircularRenameException, IllegalRenameException} +import firrtl.annotations.TargetToken.{Field, Index} + +import scala.collection.mutable + +object RenameMap { + @deprecated("Use create with CompleteTarget instead, this will be removed in 1.3", "1.2") + def apply(map: collection.Map[Named, Seq[Named]]): RenameMap = { + val rm = new RenameMap + rm.addMap(map) + rm + } + + def create(map: collection.Map[CompleteTarget, Seq[CompleteTarget]]): RenameMap = { + val rm = new RenameMap + rm.recordAll(map) + rm + } + + def apply(): RenameMap = new RenameMap + + abstract class RenameTargetException(reason: String) extends Exception(reason) + case class IllegalRenameException(reason: String) extends RenameTargetException(reason) + case class CircularRenameException(reason: String) extends RenameTargetException(reason) +} + +/** Map old names to new names + * + * Transforms that modify names should return a [[RenameMap]] with the [[CircuitState]] + * These are mutable datastructures for convenience + */ +// TODO This should probably be refactored into immutable and mutable versions +final class RenameMap private () { + + /** Record that the from [[CircuitTarget]] is renamed to another [[CircuitTarget]] + * @param from + * @param to + */ + def record(from: CircuitTarget, to: CircuitTarget): Unit = completeRename(from, Seq(to)) + + /** Record that the from [[CircuitTarget]] is renamed to another sequence of [[CircuitTarget]]s + * @param from + * @param tos + */ + def record(from: CircuitTarget, tos: Seq[CircuitTarget]): Unit = completeRename(from, tos) + + /** Record that the from [[IsMember]] is renamed to another [[IsMember]] + * @param from + * @param to + */ + def record(from: IsMember, to: IsMember): Unit = completeRename(from, Seq(to)) + + /** Record that the from [[IsMember]] is renamed to another sequence of [[IsMember]]s + * @param from + * @param tos + */ + def record(from: IsMember, tos: Seq[IsMember]): Unit = completeRename(from, tos) + + /** Records that the keys in map are also renamed to their corresponding value seqs. + * Only ([[CircuitTarget]] -> Seq[ [[CircuitTarget]] ]) and ([[IsMember]] -> Seq[ [[IsMember]] ]) key/value allowed + * @param map + */ + def recordAll(map: collection.Map[CompleteTarget, Seq[CompleteTarget]]): Unit = + map.foreach{ + case (from: IsComponent, tos: Seq[IsMember]) => completeRename(from, tos) + case (from: IsModule, tos: Seq[IsMember]) => completeRename(from, tos) + case (from: CircuitTarget, tos: Seq[CircuitTarget]) => completeRename(from, tos) + case other => Utils.throwInternalError(s"Illegal rename: ${other._1} -> ${other._2}") + } + + /** Records that a [[CompleteTarget]] is deleted + * @param name + */ + def delete(name: CompleteTarget): Unit = underlying(name) = Seq.empty + + /** Renames a [[CompleteTarget]] + * @param t target to rename + * @return renamed targets + */ + def apply(t: CompleteTarget): Seq[CompleteTarget] = completeGet(t).getOrElse(Seq(t)) + + /** Get renames of a [[CircuitTarget]] + * @param key Target referencing the original circuit + * @return Optionally return sequence of targets that key remaps to + */ + def get(key: CompleteTarget): Option[Seq[CompleteTarget]] = completeGet(key) + + /** Get renames of a [[CircuitTarget]] + * @param key Target referencing the original circuit + * @return Optionally return sequence of targets that key remaps to + */ + def get(key: CircuitTarget): Option[Seq[CircuitTarget]] = completeGet(key).map( _.map { case x: CircuitTarget => x } ) + + /** Get renames of a [[IsMember]] + * @param key Target referencing the original member of the circuit + * @return Optionally return sequence of targets that key remaps to + */ + def get(key: IsMember): Option[Seq[IsMember]] = completeGet(key).map { _.map { case x: IsMember => x } } + + + /** Create new [[RenameMap]] that merges this and renameMap + * @param renameMap + * @return + */ + def ++ (renameMap: RenameMap): RenameMap = RenameMap(underlying ++ renameMap.getUnderlying) + + /** Returns the underlying map of rename information + * @return + */ + def getUnderlying: collection.Map[CompleteTarget, Seq[CompleteTarget]] = underlying + + /** @return Whether this [[RenameMap]] has collected any changes */ + def hasChanges: Boolean = underlying.nonEmpty + + def getReverseRenameMap: RenameMap = { + val reverseMap = mutable.HashMap[CompleteTarget, Seq[CompleteTarget]]() + underlying.keysIterator.foreach{ key => + apply(key).foreach { v => + reverseMap(v) = key +: reverseMap.getOrElse(v, Nil) + } + } + RenameMap.create(reverseMap) + } + + def keys: Iterator[CompleteTarget] = underlying.keysIterator + + /** Serialize the underlying remapping of keys to new targets + * @return + */ + def serialize: String = underlying.map { case (k, v) => + k.serialize + "=>" + v.map(_.serialize).mkString(", ") + }.mkString("\n") + + /** Maps old names to new names. New names could still require renaming parts of their name + * Old names must refer to existing names in the old circuit + */ + private val underlying = mutable.HashMap[CompleteTarget, Seq[CompleteTarget]]() + + /** Records which local InstanceTargets will require modification. + * Used to reduce time to rename nonlocal targets who's path does not require renaming + */ + private val sensitivity = mutable.HashSet[IsComponent]() + + /** Caches results of recursiveGet. Is cleared any time a new rename target is added + */ + private val getCache = mutable.HashMap[CompleteTarget, Seq[CompleteTarget]]() + + /** Updates [[sensitivity]] + * @param from original target + * @param to new target + */ + private def recordSensitivity(from: CompleteTarget, to: CompleteTarget): Unit = { + (from, to) match { + case (f: IsMember, t: IsMember) => + val fromSet = f.pathAsTargets.toSet + val toSet = t.pathAsTargets + sensitivity ++= (fromSet -- toSet) + sensitivity ++= (fromSet.map(_.asReference) -- toSet.map(_.asReference)) + case other => + } + } + + /** Get renames of a [[CompleteTarget]] + * @param key Target referencing the original circuit + * @return Optionally return sequence of targets that key remaps to + */ + private def completeGet(key: CompleteTarget): Option[Seq[CompleteTarget]] = { + val errors = mutable.ArrayBuffer[String]() + val ret = if(hasChanges) { + val ret = recursiveGet(mutable.LinkedHashSet.empty[CompleteTarget], errors)(key) + if(errors.nonEmpty) { throw IllegalRenameException(errors.mkString("\n")) } + if(ret.size == 1 && ret.head == key) { None } else { Some(ret) } + } else { None } + ret + } + + // scalastyle:off + // This function requires a large cyclomatic complexity, and is best naturally expressed as a large function + /** Recursively renames a target so the returned targets are complete renamed + * @param set Used to detect circular renames + * @param errors Used to record illegal renames + * @param key Target to rename + * @return Renamed targets + */ + private def recursiveGet(set: mutable.LinkedHashSet[CompleteTarget], + errors: mutable.ArrayBuffer[String] + )(key: CompleteTarget): Seq[CompleteTarget] = { + if(getCache.contains(key)) { + getCache(key) + } else { + // First, check if whole key is remapped + // Note that remapped could hold stale parent targets that require renaming + val remapped = underlying.getOrElse(key, Seq(key)) + + // If we've seen this key before in recursive calls to parentTargets, then we know a circular renaming + // mapping has occurred, and no legal name exists + if(set.contains(key) && !key.isInstanceOf[CircuitTarget]) { + throw CircularRenameException(s"Illegal rename: circular renaming is illegal - ${set.mkString(" -> ")}") + } + + // Add key to set to detect circular renaming + set += key + + // Curry recursiveGet for cleaner syntax below + val getter = recursiveGet(set, errors)(_) + + // For each remapped key, call recursiveGet on their parentTargets + val ret = remapped.flatMap { + + // If t is a CircuitTarget, return it because it has no parent target + case t: CircuitTarget => Seq(t) + + // If t is a ModuleTarget, try to rename parent target, then update t's parent + case t: ModuleTarget => getter(t.targetParent).map { + case CircuitTarget(c) => ModuleTarget(c, t.module) + } + + /** If t is an InstanceTarget (has a path) but has no references: + * 1) Check whether the instance has been renamed (asReference) + * 2) Check whether the ofModule of the instance has been renamed (only 1:1 renaming is ok) + */ + case t: InstanceTarget => + getter(t.asReference).map { + case t2:InstanceTarget => t2 + case t2@ReferenceTarget(c, m, p, r, Nil) => + val t3 = InstanceTarget(c, m, p, r, t.ofModule) + val ofModuleTarget = t3.ofModuleTarget + getter(ofModuleTarget) match { + case Seq(ModuleTarget(newCircuit, newOf)) if newCircuit == t3.circuit => t3.copy(ofModule = newOf) + case other => + errors += s"Illegal rename: ofModule of $t is renamed to $other - must rename $t directly." + t + } + case other => + errors += s"Illegal rename: $t has new instance reference $other" + t + } + + /** If t is a ReferenceTarget: + * 1) Check parentTarget to tokens + * 2) Check ReferenceTarget with one layer stripped from its path hierarchy (i.e. a new root module) + */ + case t: ReferenceTarget => + val ret: Seq[CompleteTarget] = if(t.component.nonEmpty) { + val last = t.component.last + getter(t.targetParent).map{ x => + (x, last) match { + case (t2: ReferenceTarget, Field(f)) => t2.field(f) + case (t2: ReferenceTarget, Index(i)) => t2.index(i) + case other => + errors += s"Illegal rename: ${t.targetParent} cannot be renamed to ${other._1} - must rename $t directly" + t + } + } + } else { + val pathTargets = sensitivity.empty ++ (t.pathAsTargets ++ t.pathAsTargets.map(_.asReference)) + if(t.pathAsTargets.nonEmpty && sensitivity.intersect(pathTargets).isEmpty) Seq(t) else { + getter(t.pathTarget).map { + case newPath: IsModule => t.setPathTarget(newPath) + case other => + errors += s"Illegal rename: path ${t.pathTarget} of $t cannot be renamed to $other - must rename $t directly" + t + } + } + } + ret.flatMap { + case y: IsComponent if !y.isLocal => + val encapsulatingInstance = y.path.head._1.value + getter(y.stripHierarchy(1)).map { + _.addHierarchy(y.moduleOpt.get, encapsulatingInstance) + } + case other => Seq(other) + } + } + + // Remove key from set as visiting the same key twice is ok, as long as its not during the same recursive call + set -= key + + // Cache result + getCache(key) = ret + + // Return result + ret + + } + } + // scalastyle:on + + /** Fully renames from to tos + * @param from + * @param tos + */ + private def completeRename(from: CompleteTarget, tos: Seq[CompleteTarget]): Unit = { + def check(from: CompleteTarget, to: CompleteTarget)(t: CompleteTarget): Unit = { + require(from != t, s"Cannot record $from to $to, as it is a circular constraint") + t match { + case _: CircuitTarget => + case other: IsMember => check(from, to)(other.targetParent) + } + } + tos.foreach { to => if(from != to) check(from, to)(to) } + (from, tos) match { + case (x, Seq(y)) if x == y => + case _ => + tos.foreach{recordSensitivity(from, _)} + val existing = underlying.getOrElse(from, Seq.empty) + val updated = existing ++ tos + underlying(from) = updated + getCache.clear() + } + } + + /* DEPRECATED ACCESSOR/SETTOR METHODS WITH [[Named]] */ + + @deprecated("Use record with CircuitTarget instead, this will be removed in 1.3", "1.2") + def rename(from: Named, to: Named): Unit = rename(from, Seq(to)) + + @deprecated("Use record with IsMember instead, this will be removed in 1.3", "1.2") + def rename(from: Named, tos: Seq[Named]): Unit = recordAll(Map(from.toTarget -> tos.map(_.toTarget))) + + @deprecated("Use record with IsMember instead, this will be removed in 1.3", "1.2") + def rename(from: ComponentName, to: ComponentName): Unit = record(from, to) + + @deprecated("Use record with IsMember instead, this will be removed in 1.3", "1.2") + def rename(from: ComponentName, tos: Seq[ComponentName]): Unit = record(from, tos.map(_.toTarget)) + + @deprecated("Use delete with CircuitTarget instead, this will be removed in 1.3", "1.2") + def delete(name: CircuitName): Unit = underlying(name) = Seq.empty + + @deprecated("Use delete with IsMember instead, this will be removed in 1.3", "1.2") + def delete(name: ModuleName): Unit = underlying(name) = Seq.empty + + @deprecated("Use delete with IsMember instead, this will be removed in 1.3", "1.2") + def delete(name: ComponentName): Unit = underlying(name) = Seq.empty + + @deprecated("Use recordAll with CompleteTarget instead, this will be removed in 1.3", "1.2") + def addMap(map: collection.Map[Named, Seq[Named]]): Unit = + recordAll(map.map { case (key, values) => (Target.convertNamed2Target(key), values.map(Target.convertNamed2Target)) }) + + @deprecated("Use get with CircuitTarget instead, this will be removed in 1.3", "1.2") + def get(key: CircuitName): Option[Seq[CircuitName]] = { + get(Target.convertCircuitName2CircuitTarget(key)).map(_.collect{ case c: CircuitTarget => c.toNamed }) + } + + @deprecated("Use get with IsMember instead, this will be removed in 1.3", "1.2") + def get(key: ModuleName): Option[Seq[ModuleName]] = { + get(Target.convertModuleName2ModuleTarget(key)).map(_.collect{ case m: ModuleTarget => m.toNamed }) + } + + @deprecated("Use get with IsMember instead, this will be removed in 1.3", "1.2") + def get(key: ComponentName): Option[Seq[ComponentName]] = { + get(Target.convertComponentName2ReferenceTarget(key)).map(_.collect{ case c: IsComponent => c.toNamed }) + } + + @deprecated("Use get with IsMember instead, this will be removed in 1.3", "1.2") + def get(key: Named): Option[Seq[Named]] = key match { + case t: CompleteTarget => get(t) + case other => get(key.toTarget).map(_.collect{ case c: IsComponent => c.toNamed }) + } + + + // Mutable helpers - APIs that set these are deprecated! + private var circuitName: String = "" + private var moduleName: String = "" + + /** Sets mutable state to record current module we are visiting + * @param module + */ + @deprecated("Use typesafe rename defs instead, this will be removed in 1.3", "1.2") + def setModule(module: String): Unit = moduleName = module + + /** Sets mutable state to record current circuit we are visiting + * @param circuit + */ + @deprecated("Use typesafe rename defs instead, this will be removed in 1.3", "1.2") + def setCircuit(circuit: String): Unit = circuitName = circuit + + /** Records how a reference maps to a new reference + * @param from + * @param to + */ + @deprecated("Use typesafe rename defs instead, this will be removed in 1.3", "1.2") + def rename(from: String, to: String): Unit = rename(from, Seq(to)) + + /** Records how a reference maps to a new reference + * The reference's root module and circuit are determined by whomever called setModule or setCircuit last + * @param from + * @param tos + */ + @deprecated("Use typesafe rename defs instead, this will be removed in 1.3", "1.2") + def rename(from: String, tos: Seq[String]): Unit = { + val mn = ModuleName(moduleName, CircuitName(circuitName)) + val fromName = ComponentName(from, mn).toTarget + val tosName = tos map { to => ComponentName(to, mn).toTarget } + record(fromName, tosName) + } + + /** Records named reference is deleted + * The reference's root module and circuit are determined by whomever called setModule or setCircuit last + * @param name + */ + @deprecated("Use typesafe rename defs instead, this will be removed in 1.3", "1.2") + def delete(name: String): Unit = { + Target(Some(circuitName), Some(moduleName), AnnotationUtils.toSubComponents(name)).getComplete match { + case Some(t: CircuitTarget) => delete(t) + case Some(m: IsMember) => delete(m) + case other => + } + } + + /** Records that references in names are all deleted + * The reference's root module and circuit are determined by whomever called setModule or setCircuit last + * @param names + */ + @deprecated("Use typesafe rename defs instead, this will be removed in 1.3", "1.2") + def delete(names: Seq[String]): Unit = names.foreach(delete(_)) +} + + diff --git a/src/main/scala/firrtl/Utils.scala b/src/main/scala/firrtl/Utils.scala index 32893411..2d1b0b74 100644 --- a/src/main/scala/firrtl/Utils.scala +++ b/src/main/scala/firrtl/Utils.scala @@ -7,11 +7,14 @@ import firrtl.PrimOps._ import firrtl.Mappers._ import firrtl.WrappedExpression._ import firrtl.WrappedType._ + import scala.collection.mutable -import scala.collection.mutable.{StringBuilder, ArrayBuffer, LinkedHashMap, HashMap, HashSet} +import scala.collection.mutable.{ArrayBuffer, HashMap, HashSet, LinkedHashMap, StringBuilder} import scala.util.matching.Regex import java.io.PrintWriter -import logger.LazyLogging + +import firrtl.annotations.{ReferenceTarget, TargetToken} +import _root_.logger.LazyLogging object FIRRTLException { def defaultMessage(message: String, cause: Throwable) = { @@ -178,7 +181,7 @@ object Utils extends LazyLogging { error("Internal Error! %sPlease file an issue at https://github.com/ucb-bar/firrtl/issues".format(string), throwable) } - private[firrtl] def time[R](block: => R): (Double, R) = { + def time[R](block: => R): (Double, R) = { val t0 = System.nanoTime() val result = block val t1 = System.nanoTime() @@ -259,6 +262,45 @@ object Utils extends LazyLogging { exps ++ create_exps(WSubIndex(ex, i, t.tpe,gender(ex)))) } } + + /** Like create_exps, but returns intermediate Expressions as well + * @param e + * @return + */ + def expandRef(e: Expression): Seq[Expression] = e match { + case ex: Mux => + val e1s = expandRef(ex.tval) + val e2s = expandRef(ex.fval) + e1s zip e2s map {case (e1, e2) => + Mux(ex.cond, e1, e2, mux_type_and_widths(e1, e2)) + } + case ex: ValidIf => expandRef(ex.value) map (e1 => ValidIf(ex.cond, e1, e1.tpe)) + case ex => ex.tpe match { + case (_: GroundType) => Seq(ex) + case (t: BundleType) => (t.fields foldLeft Seq[Expression](ex))((exps, f) => + exps ++ create_exps(WSubField(ex, f.name, f.tpe,times(gender(ex), f.flip)))) + case (t: VectorType) => (0 until t.size foldLeft Seq[Expression](ex))((exps, i) => + exps ++ create_exps(WSubIndex(ex, i, t.tpe,gender(ex)))) + } + } + def toTarget(main: String, module: String)(expression: Expression): ReferenceTarget = { + val tokens = mutable.ArrayBuffer[TargetToken]() + var ref = "???" + def onExp(expr: Expression): Expression = { + expr map onExp match { + case e: WRef => ref = e.name + case e: Reference => tokens += TargetToken.Ref(e.name) + case e: WSubField => tokens += TargetToken.Field(e.name) + case e: SubField => tokens += TargetToken.Field(e.name) + case e: WSubIndex => tokens += TargetToken.Index(e.value) + case e: SubIndex => tokens += TargetToken.Index(e.value) + case other => throwInternalError("Cannot call Utils.toTarget on non-referencing expression") + } + expr + } + onExp(expression) + ReferenceTarget(main, module, Nil, ref, tokens) + } def get_flip(t: Type, i: Int, f: Orientation): Orientation = { if (i >= get_size(t)) throwInternalError(s"get_flip: shouldn't be here - $i >= get_size($t)") t match { diff --git a/src/main/scala/firrtl/analyses/InstanceGraph.scala b/src/main/scala/firrtl/analyses/InstanceGraph.scala index 6eb67938..00689a51 100644 --- a/src/main/scala/firrtl/analyses/InstanceGraph.scala +++ b/src/main/scala/firrtl/analyses/InstanceGraph.scala @@ -3,12 +3,12 @@ package firrtl.analyses import scala.collection.mutable - import firrtl._ import firrtl.ir._ import firrtl.graph._ import firrtl.Utils._ import firrtl.Mappers._ +import firrtl.annotations.TargetToken.{Instance, OfModule} /** A class representing the instance hierarchy of a working IR Circuit @@ -99,6 +99,12 @@ class InstanceGraph(c: Circuit) { */ def getChildrenInstances: mutable.LinkedHashMap[String, mutable.LinkedHashSet[WDefInstance]] = childInstances + /** Given a circuit, returns a map from module name to children + * instance/module [[firrtl.annotations.TargetToken]]s + */ + def getChildrenInstanceOfModule: mutable.LinkedHashMap[String, mutable.LinkedHashSet[(Instance, OfModule)]] = + childInstances.map(kv => kv._1 -> kv._2.map(i => (Instance(i.name), OfModule(i.module)))) + } 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)) + } +} diff --git a/src/main/scala/firrtl/passes/Inline.scala b/src/main/scala/firrtl/passes/Inline.scala index f963e762..d6af69c1 100644 --- a/src/main/scala/firrtl/passes/Inline.scala +++ b/src/main/scala/firrtl/passes/Inline.scala @@ -128,11 +128,13 @@ class InlineInstances extends Transform { val port = ComponentName(s"$ref.$field", currentModule) val inst = ComponentName(s"$ref", currentModule) (renames.get(port), renames.get(inst)) match { - case (Some(p :: Nil), None) => WRef(p.name, tpe, WireKind, gen) + case (Some(p :: Nil), _) => + p.toTarget match { + case ReferenceTarget(_, _, Seq(), r, Seq(TargetToken.Field(f))) => wsf.copy(expr = wr.copy(name = r), name = f) + case ReferenceTarget(_, _, Seq(), r, Seq()) => WRef(r, tpe, WireKind, gen) + } case (None, Some(i :: Nil)) => wsf.map(appendRefPrefix(currentModule, renames)) case (None, None) => wsf - case (Some(p), Some(i)) => throw new PassException( - s"Inlining found multiple renames for ports ($p) and/or instances ($i). This should be impossible...") } case wr@ WRef(name, _, _, _) => val comp = ComponentName(name, currentModule) diff --git a/src/main/scala/firrtl/passes/Uniquify.scala b/src/main/scala/firrtl/passes/Uniquify.scala index 10d4e97f..73f967f4 100644 --- a/src/main/scala/firrtl/passes/Uniquify.scala +++ b/src/main/scala/firrtl/passes/Uniquify.scala @@ -36,7 +36,7 @@ object Uniquify extends Transform { def outputForm = UnknownForm private case class UniquifyException(msg: String) extends FIRRTLException(msg) private def error(msg: String)(implicit sinfo: Info, mname: String) = - throw new UniquifyException(s"$sinfo: [module $mname] $msg") + throw new UniquifyException(s"$sinfo: [moduleOpt $mname] $msg") // For creation of rename map private case class NameMapNode(name: String, elts: Map[String, NameMapNode]) @@ -45,7 +45,7 @@ object Uniquify extends Transform { // We don't add an _ in the collision check because elts could be Seq("") // In this case, we're just really checking if prefix itself collides @tailrec - private [firrtl] def findValidPrefix( + def findValidPrefix( prefix: String, elts: Seq[String], namespace: collection.mutable.HashSet[String]): String = { diff --git a/src/main/scala/firrtl/passes/VerilogRename.scala b/src/main/scala/firrtl/passes/VerilogRename.scala new file mode 100644 index 00000000..4d51128c --- /dev/null +++ b/src/main/scala/firrtl/passes/VerilogRename.scala @@ -0,0 +1,11 @@ +package firrtl.passes +import firrtl.ir.Circuit +import firrtl.transforms.VerilogRename + +@deprecated("Use transforms.VerilogRename, will be removed in 1.3", "1.2") +object VerilogRename extends Pass { + override def run(c: Circuit): Circuit = new VerilogRename().run(c) + @deprecated("Use transforms.VerilogRename, will be removed in 1.3", "1.2") + def verilogRenameN(n: String): String = + if (firrtl.Utils.v_keywords(n)) "%s$".format(n) else n +} diff --git a/src/main/scala/firrtl/passes/wiring/WiringTransform.scala b/src/main/scala/firrtl/passes/wiring/WiringTransform.scala index bb73beb4..6927075e 100644 --- a/src/main/scala/firrtl/passes/wiring/WiringTransform.scala +++ b/src/main/scala/firrtl/passes/wiring/WiringTransform.scala @@ -26,7 +26,7 @@ case class SinkAnnotation(target: Named, pin: String) extends def duplicate(n: Named) = this.copy(target = n) } -/** Wires a Module's Source Component to one or more Sink +/** Wires a Module's Source Target to one or more Sink * Modules/Components * * Sinks are wired to their closest source through their lowest diff --git a/src/main/scala/firrtl/passes/wiring/WiringUtils.scala b/src/main/scala/firrtl/passes/wiring/WiringUtils.scala index b89649d3..c5a7f21b 100644 --- a/src/main/scala/firrtl/passes/wiring/WiringUtils.scala +++ b/src/main/scala/firrtl/passes/wiring/WiringUtils.scala @@ -182,7 +182,7 @@ object WiringUtils { .collect { case (k, v) if sinkInsts.contains(k) => (k, v.flatten) }.toMap } - /** Helper script to extract a module name from a named Module or Component */ + /** Helper script to extract a module name from a named Module or Target */ def getModuleName(n: Named): String = { n match { case ModuleName(m, _) => m diff --git a/src/main/scala/firrtl/transforms/CheckCombLoops.scala b/src/main/scala/firrtl/transforms/CheckCombLoops.scala index 98033a2f..44785c62 100644 --- a/src/main/scala/firrtl/transforms/CheckCombLoops.scala +++ b/src/main/scala/firrtl/transforms/CheckCombLoops.scala @@ -26,9 +26,9 @@ case object DontCheckCombLoopsAnnotation extends NoTargetAnnotation case class CombinationalPath(sink: ComponentName, sources: Seq[ComponentName]) extends Annotation { override def update(renames: RenameMap): Seq[Annotation] = { - val newSources = sources.flatMap { s => renames.get(s).getOrElse(Seq(s)) } - val newSinks = renames.get(sink).getOrElse(Seq(sink)) - newSinks.map(snk => CombinationalPath(snk, newSources)) + val newSources: Seq[IsComponent] = sources.flatMap { s => renames.get(s).getOrElse(Seq(s.toTarget)) }.collect {case x: IsComponent if x.isLocal => x} + val newSinks = renames.get(sink).getOrElse(Seq(sink.toTarget)).collect { case x: IsComponent if x.isLocal => x} + newSinks.map(snk => CombinationalPath(snk.toNamed, newSources.map(_.toNamed))) } } diff --git a/src/main/scala/firrtl/transforms/ConstantPropagation.scala b/src/main/scala/firrtl/transforms/ConstantPropagation.scala index 0d30446c..da7f1a46 100644 --- a/src/main/scala/firrtl/transforms/ConstantPropagation.scala +++ b/src/main/scala/firrtl/transforms/ConstantPropagation.scala @@ -12,6 +12,7 @@ import firrtl.PrimOps._ import firrtl.graph.DiGraph import firrtl.WrappedExpression.weq import firrtl.analyses.InstanceGraph +import firrtl.annotations.TargetToken.Ref import annotation.tailrec import collection.mutable @@ -46,11 +47,13 @@ object ConstantPropagation { } } -class ConstantPropagation extends Transform { +class ConstantPropagation extends Transform with ResolvedAnnotationPaths { import ConstantPropagation._ def inputForm = LowForm def outputForm = LowForm + override val annotationClasses: Traversable[Class[_]] = Seq(classOf[DontTouchAnnotation]) + trait FoldCommutativeOp { def fold(c1: Literal, c2: Literal): Expression def simplify(e: Expression, lhs: Literal, rhs: Expression): Expression @@ -520,7 +523,7 @@ class ConstantPropagation extends Transform { def execute(state: CircuitState): CircuitState = { val dontTouches: Seq[(String, String)] = state.annotations.collect { - case DontTouchAnnotation(ComponentName(c, ModuleName(m, _))) => m -> c + case DontTouchAnnotation(Target(_, Some(m), Seq(Ref(c)))) => m -> c } // Map from module name to component names val dontTouchMap: Map[String, Set[String]] = diff --git a/src/main/scala/firrtl/transforms/DeadCodeElimination.scala b/src/main/scala/firrtl/transforms/DeadCodeElimination.scala index c98b892c..523c997b 100644 --- a/src/main/scala/firrtl/transforms/DeadCodeElimination.scala +++ b/src/main/scala/firrtl/transforms/DeadCodeElimination.scala @@ -30,7 +30,7 @@ import java.io.{File, FileWriter} * circumstances of their instantiation in their parent module, they will still not be removed. To * remove such modules, use the [[NoDedupAnnotation]] to prevent deduplication. */ -class DeadCodeElimination extends Transform { +class DeadCodeElimination extends Transform with ResolvedAnnotationPaths { def inputForm = LowForm def outputForm = LowForm @@ -321,9 +321,12 @@ class DeadCodeElimination extends Transform { state.copy(circuit = newCircuit, renames = Some(renames)) } + override val annotationClasses: Traversable[Class[_]] = + Seq(classOf[DontTouchAnnotation], classOf[OptimizableExtModuleAnnotation]) + def execute(state: CircuitState): CircuitState = { val dontTouches: Seq[LogicNode] = state.annotations.collect { - case DontTouchAnnotation(component) => LogicNode(component) + case DontTouchAnnotation(component: ReferenceTarget) if component.isLocal => LogicNode(component) } val doTouchExtMods: Seq[String] = state.annotations.collect { case OptimizableExtModuleAnnotation(ModuleName(name, _)) => name diff --git a/src/main/scala/firrtl/transforms/Dedup.scala b/src/main/scala/firrtl/transforms/Dedup.scala index 5630cecf..a33eeca6 100644 --- a/src/main/scala/firrtl/transforms/Dedup.scala +++ b/src/main/scala/firrtl/transforms/Dedup.scala @@ -6,17 +6,18 @@ package transforms import firrtl.ir._ import firrtl.Mappers._ import firrtl.analyses.InstanceGraph +import firrtl.annotations.TargetToken.{Instance, OfModule, Ref} import firrtl.annotations._ import firrtl.passes.{InferTypes, MemPortUtils} +import firrtl.Utils.throwInternalError // Datastructures import scala.collection.mutable -/** A component, e.g. register etc. Must be declared only once under the TopAnnotation - */ +/** A component, e.g. register etc. Must be declared only once under the TopAnnotation */ case class NoDedupAnnotation(target: ModuleName) extends SingleTargetAnnotation[ModuleName] { - def duplicate(n: ModuleName) = NoDedupAnnotation(n) + def duplicate(n: ModuleName): NoDedupAnnotation = NoDedupAnnotation(n) } /** Only use on legal Firrtl. @@ -28,62 +29,64 @@ class DedupModules extends Transform { def inputForm: CircuitForm = HighForm def outputForm: CircuitForm = HighForm - /** - * Deduplicate a Circuit + /** Deduplicate a Circuit * @param state Input Firrtl AST * @return A transformed Firrtl AST */ def execute(state: CircuitState): CircuitState = { val noDedups = state.annotations.collect { case NoDedupAnnotation(ModuleName(m, c)) => m } - val (newC, renameMap) = run(state.circuit, noDedups) + val (newC, renameMap) = run(state.circuit, noDedups, state.annotations) state.copy(circuit = newC, renames = Some(renameMap)) } - /** - * Deduplicates a circuit, and records renaming + /** Deduplicates a circuit, and records renaming * @param c Circuit to dedup * @param noDedups Modules not to dedup * @return Deduped Circuit and corresponding RenameMap */ - def run(c: Circuit, noDedups: Seq[String]): (Circuit, RenameMap) = { + def run(c: Circuit, noDedups: Seq[String], annos: Seq[Annotation]): (Circuit, RenameMap) = { // RenameMap val renameMap = RenameMap() renameMap.setCircuit(c.main) // Maps module name to corresponding dedup module - val dedupMap = DedupModules.deduplicate(c, noDedups.toSet, renameMap) + val dedupMap = DedupModules.deduplicate(c, noDedups.toSet, annos, renameMap) // Use old module list to preserve ordering val dedupedModules = c.modules.map(m => dedupMap(m.name)).distinct val cname = CircuitName(c.main) - renameMap.addMap(dedupMap.map { case (from, to) => + val map = dedupMap.map { case (from, to) => logger.debug(s"[Dedup] $from -> ${to.name}") ModuleName(from, cname) -> List(ModuleName(to.name, cname)) - }) + } + renameMap.recordAll( + map.map { + case (k: ModuleName, v: List[ModuleName]) => Target.convertNamed2Target(k) -> v.map(Target.convertNamed2Target) + } + ) (InferTypes.run(c.copy(modules = dedupedModules)), renameMap) } } -/** - * Utility functions for [[DedupModules]] - */ +/** Utility functions for [[DedupModules]] */ object DedupModules { - /** - * Change's a module's internal signal names, types, infos, and modules. + + /** Change's a module's internal signal names, types, infos, and modules. * @param rename Function to rename a signal. Called on declaration and references. * @param retype Function to retype a signal. Called on declaration, references, and subfields * @param reinfo Function to re-info a statement - * @param renameModule Function to rename an instance's module + * @param renameOfModule Function to rename an instance's module * @param module Module to change internals * @return Changed Module */ def changeInternals(rename: String=>String, retype: String=>Type=>Type, reinfo: Info=>Info, - renameModule: String=>String + renameOfModule: (String, String)=>String, + renameExps: Boolean = true )(module: DefModule): DefModule = { def onPort(p: Port): Port = Port(reinfo(p.info), rename(p.name), p.direction, retype(p.name)(p.tpe)) def onExp(e: Expression): Expression = e match { @@ -98,10 +101,13 @@ object DedupModules { case other => other map onExp } def onStmt(s: Statement): Statement = s match { + case DefNode(info, name, value) => + if(renameExps) DefNode(reinfo(info), rename(name), onExp(value)) + else DefNode(reinfo(info), rename(name), value) case WDefInstance(i, n, m, t) => - val newmod = renameModule(m) + val newmod = renameOfModule(n, m) WDefInstance(reinfo(i), rename(n), newmod, retype(n)(t)) - case DefInstance(i, n, m) => DefInstance(reinfo(i), rename(n), renameModule(m)) + case DefInstance(i, n, m) => DefInstance(reinfo(i), rename(n), renameOfModule(n, m)) case d: DefMemory => val oldType = MemPortUtils.memType(d) val newType = retype(d.name)(oldType) @@ -123,55 +129,85 @@ object DedupModules { retype(d.name + ";&*^$")(d.dataType) } d.copy(dataType = newDataType) map rename map reinfo - case h: IsDeclaration => h map rename map retype(h.name) map onExp map reinfo - case other => other map reinfo map onExp map onStmt + case h: IsDeclaration => + val temp = h map rename map retype(h.name) map reinfo + if(renameExps) temp map onExp else temp + case other => + val temp = other map reinfo map onStmt + if(renameExps) temp map onExp else temp } module map onPort map onStmt } - /** - * Turns a module into a name-agnostic module + def uniquifyField(ref: String, depth: Int, field: String): String = ref + depth + field + + /** Turns a module into a name-agnostic module * @param module module to change * @return name-agnostic module */ - def agnostify(module: DefModule, name2tag: mutable.HashMap[String, String], tag2name: mutable.HashMap[String, String]): DefModule = { + def agnostify(top: CircuitTarget, + module: DefModule, + renameMap: RenameMap + ): DefModule = { + + val namespace = Namespace() - val nameMap = mutable.HashMap[String, String]() val typeMap = mutable.HashMap[String, Type]() + val nameMap = mutable.HashMap[String, String]() + + val mod = top.module(module.name) + def rename(name: String): String = { - if (nameMap.contains(name)) nameMap(name) else { + nameMap.getOrElseUpdate(name, { val newName = namespace.newTemp - nameMap(name) = newName + renameMap.record(mod.ref(name), mod.ref(newName)) newName - } + }) } + def retype(name: String)(tpe: Type): Type = { if (typeMap.contains(name)) typeMap(name) else { - def onType(tpe: Type): Type = tpe map onType match { - case BundleType(fields) => BundleType(fields.map(f => Field(rename(f.name), f.flip, f.tpe))) + def onType(depth: Int)(tpe: Type): Type = tpe map onType(depth + 1) match { + //TODO bugfix: ref.data.data and ref.datax.data will not rename to the right tags, even if they should be + case BundleType(fields) => + BundleType(fields.map(f => Field(rename(uniquifyField(name, depth, f.name)), f.flip, f.tpe))) case other => other } - val newType = onType(tpe) + val newType = onType(0)(tpe) typeMap(name) = newType newType } } - def remodule(name: String): String = tag2name(name2tag(name)) - changeInternals(rename, retype, {i: Info => NoInfo}, remodule)(module) + + def reOfModule(instance: String, ofModule: String): String = { + renameMap.get(top.module(ofModule)) match { + case Some(Seq(Target(_, Some(ofModuleTag), Nil))) => ofModuleTag + case None => ofModule + case other => throwInternalError(other.toString) + } + } + + val renamedModule = changeInternals(rename, retype, {i: Info => NoInfo}, reOfModule)(module) + renamedModule } /** Dedup a module's instances based on dedup map * * Will fixes up module if deduped instance's ports are differently named * - * @param moduleName Module name who's instances will be deduped + * @param top CircuitTarget of circuit + * @param originalModule Module name who's instances will be deduped * @param moduleMap Map of module name to its original module * @param name2name Map of module name to the module deduping it. Not mutated in this function. * @param renameMap Will be modified to keep track of renames in this function * @return fixed up module deduped instances */ - def dedupInstances(moduleName: String, moduleMap: Map[String, DefModule], name2name: mutable.Map[String, String], renameMap: RenameMap): DefModule = { - val module = moduleMap(moduleName) + def dedupInstances(top: CircuitTarget, + originalModule: String, + moduleMap: Map[String, DefModule], + name2name: Map[String, String], + renameMap: RenameMap): DefModule = { + val module = moduleMap(originalModule) // If black box, return it (it has no instances) if (module.isInstanceOf[ExtModule]) return module @@ -187,7 +223,14 @@ object DedupModules { moduleMap(name2name(old)) } // Define rename functions - def renameModule(name: String): String = getNewModule(name).name + def renameOfModule(instance: String, ofModule: String): String = { + val newOfModule = name2name(ofModule) + renameMap.record( + top.module(originalModule).instOf(instance, ofModule), + top.module(originalModule).instOf(instance, newOfModule) + ) + newOfModule + } val typeMap = mutable.HashMap[String, Type]() def retype(name: String)(tpe: Type): Type = { if (typeMap.contains(name)) typeMap(name) else { @@ -198,100 +241,202 @@ object DedupModules { case (old, nuu) => renameMap.rename(old.serialize, nuu.serialize) } newType - } else tpe + } else { + tpe + } } } renameMap.setModule(module.name) // Change module internals - changeInternals({n => n}, retype, {i => i}, renameModule)(module) + changeInternals({n => n}, retype, {i => i}, renameOfModule)(module) } - /** - * Deduplicate - * @param circuit Circuit - * @param noDedups list of modules to not dedup - * @param renameMap rename map to populate when deduping - * @return Map of original Module name -> Deduped Module + //scalastyle:off + /** Returns + * 1) map of tag to all matching module names, + * 2) renameMap of module name to tag (agnostic name) + * 3) maps module name to agnostic renameMap + * @param top CircuitTarget + * @param moduleLinearization Sequence of modules from leaf to top + * @param noDedups Set of modules to not dedup + * @param annotations All annotations to check if annotations are identical + * @return */ - def deduplicate(circuit: Circuit, - noDedups: Set[String], - renameMap: RenameMap): Map[String, DefModule] = { - - // Order of modules, from leaf to top - val moduleLinearization = new InstanceGraph(circuit).moduleOrder.map(_.name).reverse - - // Maps module name to original module - val moduleMap = circuit.modules.map(m => m.name -> m).toMap + def buildRTLTags(top: CircuitTarget, + moduleLinearization: Seq[DefModule], + noDedups: Set[String], + annotations: Seq[Annotation] + ): (collection.Map[String, collection.Set[String]], RenameMap) = { - // Maps a module's tag to its deduplicated module - val tag2name = mutable.HashMap.empty[String, String] - // Maps a module's name to its tag - val name2tag = mutable.HashMap.empty[String, String] + // Maps a module name to its agnostic name + val tagMap = RenameMap() // Maps a tag to all matching module names - val tag2all = mutable.HashMap.empty[String, mutable.Set[String]] + val tag2all = mutable.HashMap.empty[String, mutable.HashSet[String]] - // Build dedupMap - moduleLinearization.foreach { moduleName => - // Get original module - val originalModule = moduleMap(moduleName) + val module2Annotations = mutable.HashMap.empty[String, mutable.HashSet[Annotation]] + annotations.foreach { a => + a.getTargets.foreach { t => + val annos = module2Annotations.getOrElseUpdate(t.moduleOpt.get, mutable.HashSet.empty[Annotation]) + annos += a + } + } + def fastSerializedHash(s: Statement): Int ={ + def serialize(builder: StringBuilder, nindent: Int)(s: Statement): Unit = s match { + case Block(stmts) => stmts.map { + val x = serialize(builder, nindent)(_) + builder ++= "\n" + x + } + case Conditionally(info, pred, conseq, alt) => + builder ++= (" " * nindent) + builder ++= s"when ${pred.serialize} :" + builder ++= info.serialize + serialize(builder, nindent + 1)(conseq) + builder ++= "\n" + (" " * nindent) + builder ++= "else :\n" + serialize(builder, nindent + 1)(alt) + case Print(info, string, args, clk, en) => + builder ++= (" " * nindent) + val strs = Seq(clk.serialize, en.serialize, string.string) ++ + (args map (_.serialize)) + builder ++= "printf(" + (strs mkString ", ") + ")" + info.serialize + case other: Statement => + builder ++= (" " * nindent) + builder ++= other.serialize + } + val builder = new mutable.StringBuilder() + serialize(builder, 0)(s) + builder.hashCode() + } + + val agnosticRename = RenameMap() + moduleLinearization.foreach { originalModule => // Replace instance references to new deduped modules val dontcare = RenameMap() dontcare.setCircuit("dontcare") - //val fixedModule = DedupModules.dedupInstances(originalModule, tag2module, name2tag, name2module, dontcare) if (noDedups.contains(originalModule.name)) { // Don't dedup. Set dedup module to be the same as fixed module - name2tag(originalModule.name) = originalModule.name - tag2name(originalModule.name) = originalModule.name - //templateModules += originalModule.name + tag2all(originalModule.name) = mutable.HashSet(originalModule.name) } else { // Try to dedup // Build name-agnostic module - val agnosticModule = DedupModules.agnostify(originalModule, name2tag, tag2name) + val agnosticModule = DedupModules.agnostify(top, originalModule, agnosticRename) + agnosticRename.record(top.module(originalModule.name), top.module("thisModule")) + val agnosticAnnos = module2Annotations.getOrElse( + originalModule.name, mutable.HashSet.empty[Annotation] + ).map(_.update(agnosticRename)) + agnosticRename.delete(top.module(originalModule.name)) // Build tag - val tag = (agnosticModule match { - case Module(i, n, ps, b) => - ps.map(_.serialize).mkString + b.serialize + val builder = new mutable.ArrayBuffer[Any]() + agnosticModule.ports.foreach { builder ++= _.serialize } + builder ++= agnosticAnnos + + agnosticModule match { + case Module(i, n, ps, b) => builder ++= fastSerializedHash(b).toString()//.serialize case ExtModule(i, n, ps, dn, p) => - ps.map(_.serialize).mkString + dn + p.map(_.serialize).mkString - }).hashCode().toString + builder ++= dn + p.foreach { builder ++= _.serialize } + } + val tag = builder.hashCode().toString // Match old module name to its tag - name2tag(originalModule.name) = tag + agnosticRename.record(top.module(originalModule.name), top.module(tag)) + tagMap.record(top.module(originalModule.name), top.module(tag)) // Set tag's module to be the first matching module - if (!tag2name.contains(tag)) { - tag2name(tag) = originalModule.name - tag2all(tag) = mutable.Set(originalModule.name) - } else { - tag2all(tag) += originalModule.name - } + val all = tag2all.getOrElseUpdate(tag, mutable.HashSet.empty[String]) + all += originalModule.name } } + (tag2all, tagMap) + } + //scalastyle:on + + /** Deduplicate + * @param circuit Circuit + * @param noDedups list of modules to not dedup + * @param renameMap rename map to populate when deduping + * @return Map of original Module name -> Deduped Module + */ + def deduplicate(circuit: Circuit, + noDedups: Set[String], + annotations: Seq[Annotation], + renameMap: RenameMap): Map[String, DefModule] = { + + val (moduleMap, moduleLinearization) = { + val iGraph = new InstanceGraph(circuit) + (iGraph.moduleMap, iGraph.moduleOrder.reverse) + } + val top = CircuitTarget(circuit.main) + val (tag2all, tagMap) = buildRTLTags(top, moduleLinearization, noDedups, annotations) // Set tag2name to be the best dedup module name val moduleIndex = circuit.modules.zipWithIndex.map{case (m, i) => m.name -> i}.toMap def order(l: String, r: String): String = if (moduleIndex(l) < moduleIndex(r)) l else r + + // Maps a module's tag to its deduplicated module + val tag2name = mutable.HashMap.empty[String, String] tag2all.foreach { case (tag, all) => tag2name(tag) = all.reduce(order)} // Create map from original to dedup name - val name2name = name2tag.map({ case (name, tag) => name -> tag2name(tag) }) + val name2name = moduleMap.keysIterator.map{ originalModule => + tagMap.get(top.module(originalModule)) match { + case Some(Seq(Target(_, Some(tag), Nil))) => originalModule -> tag2name(tag) + case None => originalModule -> originalModule + case other => throwInternalError(other.toString) + } + }.toMap // Build Remap for modules with deduped module references - val tag2module = tag2name.map({ case (tag, name) => tag -> DedupModules.dedupInstances(name, moduleMap, name2name, renameMap) }) + val dedupedName2module = tag2name.map({ case (tag, name) => name -> DedupModules.dedupInstances(top, name, moduleMap, name2name, renameMap) }) // Build map from original name to corresponding deduped module - val name2module = name2tag.map({ case (name, tag) => name -> tag2module(tag) }) + val name2module = tag2all.flatMap({ case (tag, names) => names.map(n => n -> dedupedName2module(tag2name(tag))) }) + + // Build renameMap + val indexedTargets = mutable.HashMap[String, IndexedSeq[ReferenceTarget]]() + name2module.foreach { case (originalName, depModule) => + if(originalName != depModule.name) { + val toSeq = indexedTargets.getOrElseUpdate(depModule.name, computeIndexedNames(circuit.main, depModule)) + val fromSeq = computeIndexedNames(circuit.main, moduleMap(originalName)) + computeRenameMap(fromSeq, toSeq, renameMap) + } + } name2module.toMap } + def computeIndexedNames(main: String, m: DefModule): IndexedSeq[ReferenceTarget] = { + val refs = mutable.ArrayBuffer[ReferenceTarget]() + def rename(name: String): String = name + + def retype(name: String)(tpe: Type): Type = { + val exps = Utils.expandRef(WRef(name, tpe, ExpKind, UNKNOWNGENDER)) + refs ++= exps.map(Utils.toTarget(main, m.name)) + tpe + } + + changeInternals(rename, retype, {i => i}, {(x, y) => x}, renameExps = false)(m) + refs + } + + def computeRenameMap(originalNames: IndexedSeq[ReferenceTarget], + dedupedNames: IndexedSeq[ReferenceTarget], + renameMap: RenameMap): Unit = { + + originalNames.zip(dedupedNames).foreach { + case (o, d) => if (o.component != d.component || o.ref != d.ref) renameMap.record(o, d) + } + + } + def getAffectedExpressions(root: Expression): Seq[Expression] = { val all = mutable.ArrayBuffer[Expression]() diff --git a/src/main/scala/firrtl/transforms/OptimizationAnnotations.scala b/src/main/scala/firrtl/transforms/OptimizationAnnotations.scala index fab540da..a66bd4ce 100644 --- a/src/main/scala/firrtl/transforms/OptimizationAnnotations.scala +++ b/src/main/scala/firrtl/transforms/OptimizationAnnotations.scala @@ -4,6 +4,7 @@ package transforms import firrtl.annotations._ import firrtl.passes.PassException +import firrtl.transforms /** Indicate that DCE should not be run */ case object NoDCEAnnotation extends NoTargetAnnotation @@ -12,13 +13,14 @@ case object NoDCEAnnotation extends NoTargetAnnotation * * DCE treats the component as a top-level sink of the circuit */ -case class DontTouchAnnotation(target: ComponentName) extends SingleTargetAnnotation[ComponentName] { - def duplicate(n: ComponentName) = this.copy(n) +case class DontTouchAnnotation(target: ReferenceTarget) extends SingleTargetAnnotation[ReferenceTarget] { + def targets = Seq(target) + def duplicate(n: ReferenceTarget) = this.copy(n) } object DontTouchAnnotation { class DontTouchNotFoundException(module: String, component: String) extends PassException( - s"Component marked dontTouch ($module.$component) not found!\n" + + s"Target marked dontTouch ($module.$component) not found!\n" + "It was probably accidentally deleted. Please check that your custom transforms are not" + "responsible and then file an issue on Github." ) |
