aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/scala/firrtl/features/LetterCaseTransform.scala29
-rw-r--r--src/main/scala/firrtl/stage/FirrtlAnnotations.scala13
-rw-r--r--src/main/scala/firrtl/transforms/ManipulateNames.scala461
-rw-r--r--src/main/scala/firrtl/transforms/RemoveKeywordCollisions.scala216
-rw-r--r--src/test/scala/firrtlTests/VerilogEmitterTests.scala24
-rw-r--r--src/test/scala/firrtlTests/features/LetterCaseTransformSpec.scala168
-rw-r--r--src/test/scala/firrtlTests/transforms/ManipulateNamesSpec.scala229
7 files changed, 924 insertions, 216 deletions
diff --git a/src/main/scala/firrtl/features/LetterCaseTransform.scala b/src/main/scala/firrtl/features/LetterCaseTransform.scala
new file mode 100644
index 00000000..a6cd270a
--- /dev/null
+++ b/src/main/scala/firrtl/features/LetterCaseTransform.scala
@@ -0,0 +1,29 @@
+// See LICENSE for license details.
+
+package firrtl.features
+
+import firrtl.Namespace
+import firrtl.transforms.ManipulateNames
+
+import scala.reflect.ClassTag
+
+/** Parent of transforms that do change the letter case of names in a FIRRTL circuit */
+abstract class LetterCaseTransform[A <: ManipulateNames[_] : ClassTag] extends ManipulateNames[A] {
+
+ protected def newName: String => String
+
+ final def manipulate = (a: String, ns: Namespace) => newName(a) match {
+ case `a` => None
+ case b => Some(ns.newName(b))
+ }
+}
+
+/** Convert all FIRRTL names to lowercase */
+final class LowerCaseNames extends LetterCaseTransform[LowerCaseNames] {
+ override protected def newName = (a: String) => a.toLowerCase
+}
+
+/** Convert all FIRRTL names to UPPERCASE */
+final class UpperCaseNames extends LetterCaseTransform[UpperCaseNames] {
+ override protected def newName = (a: String) => a.toUpperCase
+}
diff --git a/src/main/scala/firrtl/stage/FirrtlAnnotations.scala b/src/main/scala/firrtl/stage/FirrtlAnnotations.scala
index 5be84bb9..1604e92e 100644
--- a/src/main/scala/firrtl/stage/FirrtlAnnotations.scala
+++ b/src/main/scala/firrtl/stage/FirrtlAnnotations.scala
@@ -193,7 +193,18 @@ object RunFirrtlTransformAnnotation extends HasShellOptions {
s"Unknown error when instantiating class $txName", e) }),
helpText = "Run these transforms during compilation",
shortOption = Some("fct"),
- helpValueName = Some("<package>.<class>") ) )
+ helpValueName = Some("<package>.<class>") ),
+ new ShellOption[String](
+ longOption = "change-name-case",
+ toAnnotationSeq = _ match {
+ case "lower" => Seq(RunFirrtlTransformAnnotation(new firrtl.features.LowerCaseNames))
+ case "upper" => Seq(RunFirrtlTransformAnnotation(new firrtl.features.UpperCaseNames))
+ case a => throw new OptionsException(s"Unknown case '$a'. Did you misspell it?")
+ },
+ helpText = "Convert all FIRRTL names to a specific case",
+ helpValueName = Some("<lower|upper>")
+ )
+ )
}
diff --git a/src/main/scala/firrtl/transforms/ManipulateNames.scala b/src/main/scala/firrtl/transforms/ManipulateNames.scala
new file mode 100644
index 00000000..956b39e6
--- /dev/null
+++ b/src/main/scala/firrtl/transforms/ManipulateNames.scala
@@ -0,0 +1,461 @@
+// See LICENSE for license details.
+
+package firrtl.transforms
+
+import firrtl._
+import firrtl.analyses.InstanceGraph
+import firrtl.Mappers._
+
+import firrtl.annotations.{
+ CircuitTarget,
+ CompleteTarget,
+ InstanceTarget,
+ ModuleTarget,
+ MultiTargetAnnotation,
+ ReferenceTarget,
+ Target,
+ TargetToken
+}
+import firrtl.options.Dependency
+import firrtl.stage.Forms
+import firrtl.stage.TransformManager.TransformDependency
+
+import scala.collection.mutable
+import scala.reflect.ClassTag
+
+/** Base trait for annotations that control the behavior of transforms that sub-class ManipulateNames
+ * @see [[ManipulateNamesBlocklistAnnotation]]
+ * @see [[ManipulateNamesAllowlistAnnotation]]
+ * @define noteLocalTargets All targets must be local. Name modification in a non-local target (e.g., a node in a
+ * specific instance) makes no structural modification and will be ignored during deduplication. If you want this
+ * behavior, use a combination of a sub-class of this annotation and a [[firrtl.transforms.NoDedupAnnotation
+ * NoDedupAnnotation]].
+ */
+sealed trait ManipulateNamesListAnnotation[A <: ManipulateNames[_]] extends MultiTargetAnnotation {
+
+ def transform: Dependency[A]
+
+ /* Throw an exception if targets are non-local */
+ targets.flatten.collect {
+ case a if !a.isLocal => a
+ } match {
+ case Nil =>
+ case a =>
+ val aString = a.map(_.serialize).mkString("\n - ", "\n - ", "")
+ throw new IllegalArgumentException(s"""'${this.getClass.getName}' given non-local targets: $aString""")
+ }
+
+}
+
+/** Annotation to prevent name manipulation of[[firrtl.annotations.Target Target]]s in a transform that subclasses
+ * [[ManipulateNames]]. All listed targets will not be modified.
+ *
+ * @param targets FIRRTL IR targets to exclude from name manipulation
+ * @param transform the transform that this should apply to
+ * @tparam A a sub-type of [[ManipulateNames]]
+ * @throws java.lang.IllegalArgumentException if any non-local targets are given
+ * @note $noteLocalTargets
+ */
+case class ManipulateNamesBlocklistAnnotation[A <: ManipulateNames[_]](
+ targets: Seq[Seq[Target]],
+ transform: Dependency[A]) extends ManipulateNamesListAnnotation[A] {
+
+ override def duplicate(a: Seq[Seq[Target]]) = this.copy(targets = a)
+
+}
+
+/** Annotation to filter name manipulation to only manipulate specific [[firrtl.annotations.Target Target]]s in a
+ * transform that subclasses [[ManipulateNames]]. Targets will be renamed if they are not listed in a
+ * [[ManipulateNamesBlocklistAnnotation]] and also listed in this annotation.
+ *
+ * Not providing a [[ManipulateNamesAllowlistAnnotation]] means that all targets in a circuit may be renamed.
+ *
+ * @param targets FIRRTL IR targets to include in name manipulation
+ * @param transform the transform that this should apply to
+ * @tparam A a sub-type of [[ManipulateNames]]
+ * @throws java.lang.IllegalArgumentException if any non-local targets are given
+ * @note $noteLocalTargets
+ */
+case class ManipulateNamesAllowlistAnnotation[A <: ManipulateNames[_]](
+ targets: Seq[Seq[Target]],
+ transform: Dependency[A]) extends ManipulateNamesListAnnotation[A] {
+
+ override def duplicate(a: Seq[Seq[Target]]) = this.copy(targets = a)
+
+}
+
+/** Records the result of name changes for any targets included in a [[ManipulateNamesAllowlistAnnotation]]
+ *
+ * If targets are later removed, then a target and old target will be removed from this annotation. If all targets are
+ * removed, then this annotation will be deleted.
+ *
+ * @param targets the new targets
+ * @param transform the transform that performed this rename
+ * @param oldTargets the old targets
+ */
+case class ManipulateNamesAllowlistResultAnnotation[A <: ManipulateNames[_]](
+ targets: Seq[Seq[Target]],
+ transform: Dependency[A],
+ oldTargets: Seq[Seq[Target]]) extends MultiTargetAnnotation {
+
+ override def duplicate(a: Seq[Seq[Target]]) = this.copy(targets = a)
+
+ override def update(renames: RenameMap) = {
+ val (targetsx, oldTargetsx) = targets.zip(oldTargets).foldLeft((Seq.empty[Seq[Target]], Seq.empty[Seq[Target]])) {
+ case ((accT, accO), (t, o)) => t.flatMap(renames(_)) match {
+ /* If the target was deleted, delete the old target */
+ case tx if tx.isEmpty => (accT, accO)
+ case tx => (Seq(tx) ++ accT, Seq(o) ++ accO)
+ }
+ }
+ targetsx match {
+ /* If all targets were deleted, delete the annotation */
+ case Nil => Seq.empty
+ case _ => Seq(this.copy(targets = targetsx, oldTargets = oldTargetsx))
+ }
+ }
+
+ /** Return [[firrtl.RenameMap RenameMap]] from old targets to new targets */
+ def toRenameMap: RenameMap = {
+ val m = oldTargets.zip(targets).flatMap {
+ case (a, b) => a.map(_ -> b)
+ }.toMap.asInstanceOf[Map[CompleteTarget, Seq[CompleteTarget]]]
+ RenameMap.create(m)
+ }
+
+}
+
+/** A datastructure used to do single-pass name manipulation
+ * @param circuit the [[ir.Circuit]] that will be manipulated
+ * @param renames a rename map
+ * @param block a function that returns true if a [[firrtl.annotations.Target Target]] should not be renamed
+ * @param allow a function that returns true if a [[firrtl.annotations.Target Target]] should be renamed
+ */
+private class RenameDataStructure(
+ circuit: ir.Circuit,
+ val renames: RenameMap,
+ val block: Target => Boolean,
+ val allow: Target => Boolean) {
+
+ /** A mapping of targets to associated namespaces */
+ val namespaces: mutable.HashMap[CompleteTarget, Namespace] =
+ mutable.HashMap(CircuitTarget(circuit.main) -> Namespace(circuit))
+
+ /** A mapping of a reference to either an instance or a memory (encoded as a [[ReferenceTarget]] */
+ val instanceMap: mutable.HashMap[ReferenceTarget, Either[ReferenceTarget, InstanceTarget]] =
+ new mutable.HashMap[ReferenceTarget, Either[ReferenceTarget, InstanceTarget]] {
+ override def apply(a: ReferenceTarget) = try {
+ super.apply(a)
+ } catch {
+ case t: NoSuchElementException => throw new FirrtlUserException(
+ s"""|Reference target '${a.serialize}' did not exist in mapping of reference targets to insts/mems.
+ | This is indicative of a circuit that has not been run through LowerTypes.""".stripMargin, t)
+ }
+ }
+
+ /** Return true if a target should be skipped based on allow and block parameters */
+ def skip(a: Target): Boolean = block(a) || !allow(a)
+
+}
+
+/** Transform for manipulate all the names in a FIRRTL circuit.
+ * @tparam A the type of the child transform
+ */
+abstract class ManipulateNames[A <: ManipulateNames[_] : ClassTag] extends Transform with DependencyAPIMigration {
+
+ /** A function used to manipulate a name in a FIRRTL circuit */
+ def manipulate: (String, Namespace) => Option[String]
+
+ override def prerequisites: Seq[TransformDependency] = Seq(Dependency(firrtl.passes.LowerTypes))
+ override def optionalPrerequisites: Seq[TransformDependency] = Seq.empty
+ override def optionalPrerequisiteOf: Seq[TransformDependency] = Forms.LowEmitters
+ override def invalidates(a: Transform) = a match {
+ case _: analyses.GetNamespace => true
+ case _ => false
+ }
+
+ /** Compute a new name for some target and record the rename if the new name differs. If the top module or the circuit
+ * is renamed, both will be renamed.
+ * @param name the string to rename
+ * @param r a data structure containing information necessary for renaming
+ * @param target the target associated with the name
+ * @return a new name (or the old name if no renaming is necessary)
+ */
+ private def doRename(name: String, r: RenameDataStructure, target: CompleteTarget): String = {
+ /* Compute the new name and, if the name is a new name, a new target. */
+ val (namex: String, ax: Option[CompleteTarget]) = target match {
+ /* Do not rename if this is designated as a skip */
+ case a if r.skip(a) =>
+ (name, None)
+ /* Circuit renaming */
+ case a@ CircuitTarget(b) => manipulate(b, r.namespaces(a)) match {
+ case Some(str) => (str, Some(a.copy(circuit = str)))
+ case None => (b, None)
+ }
+ /* Module renaming for non-top modules */
+ case a@ ModuleTarget(_, b) => manipulate(b, r.namespaces(a.circuitTarget)) match {
+ case Some(str) => (str, Some(a.copy(module = str)))
+ case None => (b, None)
+ }
+ /* Instance renaming */
+ case a@ InstanceTarget(_, _, Nil, b, c) => manipulate(b, r.namespaces(a.moduleTarget)) match {
+ case Some(str) => (str, Some(a.copy(instance = str)))
+ case None => (b, None)
+ }
+ /* Rename either a module component or a memory */
+ case a@ ReferenceTarget(_, _, _, b, Nil) => manipulate(b, r.namespaces(a.moduleTarget)) match {
+ case Some(str) => (str, Some(a.copy(ref = str)))
+ case None => (b, None)
+ }
+ /* Rename an instance port or a memory reader/writer/readwriter */
+ case a@ ReferenceTarget(_, _, _, b, (token@ TargetToken.Field(c)) :: Nil) =>
+ val ref = r.instanceMap(a.moduleTarget.ref(b)) match {
+ case Right(inst) => inst.ofModuleTarget
+ case Left(mem) => mem
+ }
+ manipulate(c, r.namespaces(ref)) match {
+ case Some(str) => (str, Some(a.copy(component = Seq(token.copy(str)))))
+ case None => (c, None)
+ }
+ }
+ /* Record the optional rename. If the circuit was renamed, also rename the top module. If the top module was
+ * renamed, also rename the circuit. */
+ ax.foreach(
+ axx => target match {
+ case c: CircuitTarget =>
+ r.renames.rename(target, r.renames(axx))
+ r.renames.rename(c.module(c.circuit), CircuitTarget(namex).module(namex))
+ /* Note: this code path is not exercised by the implementation of the [[run]] and [[onModule]] methods. Those
+ * only use [[doRename]] on the circuit and [[maybeRename]] on the top module.
+ */
+ case m: ModuleTarget if m.module == m.circuit =>
+ r.renames.rename(target, r.renames(axx))
+ r.renames.rename(m.circuitTarget, axx.circuitTarget)
+ case _ =>
+ r.renames.rename(target, r.renames(axx))
+ }
+ )
+ namex
+ }
+
+ /** Change a name based on known renames. Do not record any new renames.
+ * @param name the string to rename
+ * @param r a data structure containing information necessary for renaming
+ * @param target the target associated with the name
+ * @return a new name (or the old name if no renaming is necessary)
+ */
+ private def maybeRename(name: String, r: RenameDataStructure, t: CompleteTarget): String =
+ r.renames.underlying.get(t) match {
+ case Some(ax) if ax.size == 1 =>
+ ax match {
+ case Seq(foo: CircuitTarget) => foo.name
+ case Seq(foo: ModuleTarget) => foo.module
+ case Seq(foo: InstanceTarget) => foo.instance
+ case Seq(foo: ReferenceTarget) => foo.tokens.last match {
+ case TargetToken.Ref(value) => value
+ case TargetToken.Field(value) => value
+ case _ => Utils.throwInternalError(
+ s"""|Reference target '${t.serialize}'must end in 'Ref' or 'Field'
+ | This is indicative of a circuit that has not been run through LowerTypes.""",
+ Some(new MatchError(foo.serialize)))
+ }
+ }
+ case s@ Some(ax) => Utils.throwInternalError(
+ s"""Found multiple renames '${t}' -> [${ax.map(_.serialize).mkString(",")}]. This should be impossible.""",
+ Some(new MatchError(s)))
+ case None => name
+ }
+
+ /** Rename an expression
+ *
+ * This logic exploits the known structure of the output of [[LowerTypes]] such that the only possible expressions in
+ * a module are: (1) references to module components, (2) subfields of references are instance components, and (3)
+ * subfields of subfields or references are memory ports.
+ */
+ private def onExpression(e: ir.Expression, r: RenameDataStructure, t: ModuleTarget): ir.Expression = e match {
+ /* A reference to something inside this module */
+ case w: WRef => w.copy(name = maybeRename(w.name, r, Target.asTarget(t)(w)))
+ /* This is either the subfield of an instance or a subfield of a memory reader/writer/readwriter */
+ case w@ WSubField(expr, ref, _, _) => expr match {
+ /* This is an instance */
+ case we@ WRef(inst, _, _, _) =>
+ val tx = Target.asTarget(t)(we)
+ val (rTarget: ReferenceTarget, iTarget: InstanceTarget) = r.instanceMap(tx) match {
+ case Right(a) => (a.ofModuleTarget.ref(ref), a)
+ case a@ Left(ref) => throw new FirrtlUserException(
+ s"""|Unexpected '${ref.serialize}' in instanceMap for key '${tx.serialize}' on expression '${w.serialize}'.
+ | This is indicative of a circuit that has not been run through LowerTypes.""", new MatchError(a))
+ }
+ w.copy(we.copy(name=maybeRename(inst, r, iTarget)), name=maybeRename(ref, r, rTarget))
+ /* This is a reader/writer/readwriter */
+ case ws@ WSubField(expr, port, _, _) => expr match {
+ /* This is the memory. */
+ case wr@ WRef(mem, _, _, _) =>
+ w.copy(
+ expr=ws.copy(
+ expr=wr.copy(name=maybeRename(mem, r, t.ref(mem))),
+ name=maybeRename(port, r, t.ref(mem).field(port))))
+ }
+ }
+ case e => e.map(onExpression(_: ir.Expression, r, t))
+ }
+
+ /** Rename a statement
+ *
+ * Instances will update the rename data structure. Memories are treated specially to rename their readers, writers,
+ * and readwriters.
+ */
+ private def onStatement(s: ir.Statement, r: RenameDataStructure, t: ModuleTarget): ir.Statement = s match {
+ case decl: ir.IsDeclaration => decl match {
+ case decl@ WDefInstance(_, inst, mod, _) =>
+ val modx = maybeRename(mod, r, t.circuitTarget.module(mod))
+ val instx = doRename(inst, r, t.instOf(inst, mod))
+ r.instanceMap(t.ref(inst)) = Right(t.instOf(inst, mod))
+ decl.copy(name = instx, module = modx)
+ case decl: ir.DefMemory =>
+ val namex = doRename(decl.name, r, t.ref(decl.name))
+ val tx = t.ref(decl.name)
+ r.namespaces(tx) = Namespace(decl.readers ++ decl.writers ++ decl.readwriters)
+ r.instanceMap(tx) = Left(tx)
+ decl
+ .copy(
+ name = namex,
+ readers = decl.readers.map(_r => doRename(_r, r, tx.field(_r))),
+ writers = decl.writers.map(_w => doRename(_w, r, tx.field(_w))),
+ readwriters = decl.readwriters.map(_rw => doRename(_rw, r, tx.field(_rw)))
+ )
+ .map(onExpression(_: ir.Expression, r, t))
+ case decl =>
+ decl
+ .map(doRename(_: String, r, t.ref(decl.name)))
+ .map(onExpression(_: ir.Expression, r, t))
+ }
+ case s =>
+ s
+ .map(onStatement(_: ir.Statement, r, t))
+ .map(onExpression(_: ir.Expression, r, t))
+ }
+
+ /** Rename a port */
+ private def onPort(p: ir.Port, r: RenameDataStructure, t: ModuleTarget): ir.Port = {
+ p.map(doRename(_: String, r, t.ref(p.name)))
+ }
+
+ /** Rename a [[DefModule]] and it's internals (ports and statements) to fix keyword collisions and update instance
+ * references to respect previous renames
+ * @param renames a [[RenameMap]]
+ * @param circuit the enclosing [[CircuitName]]
+ * @return a [[DefModule]] without keyword conflicts
+ */
+ private def onModule(m: ir.DefModule, r: RenameDataStructure, t: CircuitTarget): ir.DefModule = m match {
+ case _: ir.ExtModule => m
+ case ir.Module(_, main, _, _) =>
+ val moduleTarget = t.module(m.name)
+ r.namespaces(moduleTarget) = Namespace(m)
+
+ /* If top module, use [[maybeRename]]: circuit renaming already recorded a top-module rename if one should happen.
+ * Otherwise, use [[doRename]]: compute a new name and record it.
+ */
+ val onName: String => String = t.circuit match {
+ case `main` => maybeRename(_, r, moduleTarget)
+ case _ => doRename(_, r, moduleTarget)
+ }
+
+ m
+ .map(onName)
+ .map(onPort(_: ir.Port, r, moduleTarget))
+ .map(onStatement(_: ir.Statement, r, moduleTarget))
+ }
+
+ /** Manipulate all names in a circuit
+ *
+ * @param c an input circuit
+ * @param renames a rename map that will be updated as names are manipulated
+ * @param block a function that returns true if a [[firrtl.annotations.Target Target]] should not be renamed
+ * @param allow a function that returns true if a [[firrtl.annotations.Target Target]] should be renamed
+ * @return the circuit with manipulated names
+ */
+ def run(
+ c: ir.Circuit,
+ renames: RenameMap,
+ block: Target => Boolean,
+ allow: Target => Boolean)
+ : ir.Circuit = {
+ val t = CircuitTarget(c.main)
+
+ /* If the circuit is a skip, return the original circuit. Otherwise, walk all the modules and rename them. Rename the
+ * circuit if the main module was renamed.
+ */
+ (block(t), !allow(t)) match {
+ case (true, _) =>
+ logger.info(s"Circuit '${t.serialize}' is excluded by the 'block' parameter. No renaming will occur.")
+ c
+ case (false, true) =>
+ logger.info(s"Circuit '${t.serialize}' is not included by the 'allow' parameter. No renaming will occur.")
+ c
+ case _ =>
+ val r = new RenameDataStructure(c, renames, block, allow)
+
+ /* Record a rename for the circuit if the top module will be renamed. This allows all the module renames to be
+ * aware of the circuit rename when generating their own renames. E.g., this allows renames to be generated
+ * that can be resolved in a single step:
+ * ~foo -> FOO
+ * ~foo|bar -> ~FOO|BAR
+ * Instead of renames which require multiple steps:
+ * ~foo -> FOO
+ * ~foo|bar -> ~foo|BAR
+ */
+ val mainx = r.skip(t.module(c.main)) match {
+ case true => c.main
+ case false =>
+ val tx = CircuitTarget(doRename(c.main, r, t))
+ logger.info(s"Main module will be renamed. Renaming circuit: '${t.serialize}' -> ['${tx.serialize}']")
+ renames.record(t, tx)
+ tx.circuit
+ }
+
+ /* Rename all modules from leafs to root in one pass while updating a shared rename map. Going from leafs to
+ * roots ensures that the rename map is safe for parents to blindly consult. Store this in mapping of old module
+ * target to new module to allow the modules to be put in the old order.
+ */
+ val modulesx: Map[ModuleTarget, ir.DefModule] = new InstanceGraph(c).moduleOrder.reverse
+ .map(m => t.module(m.name) -> onModule(m, r, t))
+ .toMap
+
+ /* Replace the old modules making sure that they are still in the same order */
+ c.copy(modules = c.modules.map(m => modulesx(t.module(m.name))),
+ main = mainx)
+ }
+ }
+
+ /** Return a circuit state with all sensitive names manipulated */
+ def execute(state: CircuitState): CircuitState = {
+
+ val block = state.annotations.collect {
+ case ManipulateNamesBlocklistAnnotation(targetSeq, _: Dependency[A]) => targetSeq
+ }.flatten.flatten.toSet
+
+ val allow = state.annotations.collect {
+ case ManipulateNamesAllowlistAnnotation(targetSeq, _: Dependency[A]) => targetSeq
+ } match {
+ case Nil => (a: Target) => true
+ case a => a.flatten.flatten.toSet
+ }
+
+ val renames = RenameMap()
+ val circuitx = run(state.circuit, renames, block, allow)
+
+ val annotationsx = state.annotations.flatMap {
+ /* Consume blocklist annotations */
+ case ManipulateNamesBlocklistAnnotation(_, _: Dependency[A]) => None
+ /* Convert allowlist annotations to result annotations */
+ case ManipulateNamesAllowlistAnnotation(a, t: Dependency[A]) => (a, a.map(_.map(renames(_)).flatten)) match {
+ case (a, b) => Some(ManipulateNamesAllowlistResultAnnotation(b, t, a))
+ }
+ case a => Some(a)
+ }
+
+ state.copy(circuit = circuitx, annotations = annotationsx, renames = Some(renames))
+ }
+
+}
diff --git a/src/main/scala/firrtl/transforms/RemoveKeywordCollisions.scala b/src/main/scala/firrtl/transforms/RemoveKeywordCollisions.scala
index c5f20363..840a3d99 100644
--- a/src/main/scala/firrtl/transforms/RemoveKeywordCollisions.scala
+++ b/src/main/scala/firrtl/transforms/RemoveKeywordCollisions.scala
@@ -4,230 +4,28 @@ package firrtl.transforms
import firrtl._
-import firrtl.analyses.InstanceGraph
-import firrtl.annotations.{Named, CircuitName, ModuleName, ComponentName}
-import firrtl.ir
-import firrtl.passes.{Uniquify, PassException}
import firrtl.Utils.v_keywords
-import firrtl.Mappers._
import firrtl.options.Dependency
-
-import scala.collection.mutable
+import firrtl.passes.Uniquify
/** Transform that removes collisions with reserved keywords
* @param keywords a set of reserved words
- * @define implicitRename @param renames the [[RenameMap]] to query when renaming
- * @define implicitNamespace @param ns an encolosing [[Namespace]] with which new names must not conflict
- * @define implicitScope @param scope the enclosing scope of this name. If [[None]], then this is a [[Circuit]] name
*/
-class RemoveKeywordCollisions(keywords: Set[String]) extends Transform with DependencyAPIMigration {
- private type ModuleType = mutable.HashMap[String, ir.Type]
+class RemoveKeywordCollisions(keywords: Set[String]) extends ManipulateNames {
+
private val inlineDelim = "_"
/** Generate a new name, by appending underscores, that will not conflict with the existing namespace
* @param n a name
* @param ns a [[Namespace]]
- * @return a conflict-free name
+ * @return Some name if a rename occurred, None otherwise
* @note prefix uniqueness is not respected
*/
- private def safeName(n: String, ns: Namespace): String =
- Uniquify.findValidPrefix(n + inlineDelim, Seq(""), ns.cloneUnderlying ++ keywords)
-
- /** Modify a name to not conflict with a Verilog keywords while respecting existing renames and a namespace
- * @param n the name to rename
- * @param renames the [[RenameMap]] to query when renaming
- * $implicitRename
- * $implicitNamespace
- * $implicitScope
- * @return a name without keyword conflicts
- */
- private def onName(n: String)(implicit renames: RenameMap, ns: Namespace, scope: Option[Named]): String = {
-
- // Convert a [[String]] into [[Named]] based on the provided scope.
- def wrap(name: String, scope: Option[Named]): Named = scope match {
- case None => CircuitName(name)
- case Some(cir: CircuitName) => ModuleName(name, cir)
- case Some(mod: ModuleName) => ComponentName(name, mod)
- case Some(com: ComponentName) => ComponentName(s"${com.name}.$name", com.module)
- }
-
- val named = wrap(n, scope)
-
- // If this has already been renamed use that name. If it conflicts with a keyword, determine a new, safe name and
- // update the renames. Otherwise, leave it alone.
- val namedx: Seq[Named] = renames.get(named) match {
- case Some(x) => x
- case None if keywords(n) =>
- val sn = wrap(safeName(n, ns), scope)
- renames.rename(named, sn)
- Seq(sn)
- case _ => Seq(wrap(n, scope))
- }
-
- namedx match {
- case Seq(ComponentName(n, _)) => n
- case Seq(ModuleName(n, _)) => n
- case Seq(CircuitName(n)) => n
- case x => throw new PassException(
- s"Verilog renaming shouldn't result in multiple renames, but found '$named -> $namedx'")
- }
- }
-
- /** Rename the fields of a [[Type]] to match the ports of an instance
- * @param t the type to rename
- * $implicitRename
- * $implicitNamespace
- * $implicitScope
- * @return a [[Type]] with updated names
- * @note This is not intended for fixing arbitrary types, only [[BundleType]] in instance [[WRef]]s
- */
- private def onType(t: ir.Type)
- (implicit renames: RenameMap,
- ns: Namespace,
- scope: Option[ModuleName]): ir.Type = t match {
- case b: ir.BundleType => b.copy(fields = b.fields.map(f => f.copy(name = onName(f.name))))
- case _ => t
- }
-
- /** Rename an [[Expression]] to respect existing renames and avoid keyword collisions
- * @param e the [[Expression]] to rename
- * $implicitRename
- * $implicitNamespace
- * $implicitScope
- * @return an [[Expression]] without keyword conflicts
- */
- private def onExpression(e: ir.Expression)
- (implicit renames: RenameMap,
- ns: Namespace,
- scope: Option[ModuleName],
- iToM: mutable.Map[ComponentName, ModuleName],
- modType: ModuleType): ir.Expression = e match {
- case wsf@ WSubField(wr@ WRef(name, _, InstanceKind, _), port, _, _) =>
- val subInst = ComponentName(name, scope.get)
- val subModule = iToM(subInst)
- val subPort = ComponentName(port, subModule)
-
- val wrx = wr.copy(
- name = renames.get(subInst).orElse(Some(Seq(subInst))).get.head.name,
- tpe = modType(subModule.name))
-
- wsf.copy(
- expr = wrx,
- name = renames.get(subPort).orElse(Some(Seq(subPort))).get.head.name)
- case wr: WRef => wr.copy(name=onName(wr.name))
- case ex => ex.map(onExpression)
- }
-
- /** Rename a [[Statement]] to respect existing renames and avoid keyword collisions
- * $implicitRename
- * $implicitNamespace
- * $implicitScope
- * @return a [[Statement]] without keyword conflicts
- */
- private def onStatement(s: ir.Statement)
- (implicit renames: RenameMap,
- ns: Namespace,
- scope: Option[ModuleName],
- iToM: mutable.Map[ComponentName, ModuleName],
- modType: ModuleType): ir.Statement = s match {
- case wdi: WDefInstance =>
- val subModule = ModuleName(wdi.module, scope.get.circuit)
- val modulex = renames.get(subModule).orElse(Some(Seq(subModule))).get.head.name
- val wdix = wdi.copy(module = modulex,
- name = onName(wdi.name),
- tpe = onType(wdi.tpe)(renames, ns, Some(ModuleName(modulex, scope.get.circuit))))
- iToM(ComponentName(wdi.name, scope.get)) = ModuleName(wdix.module, scope.get.circuit)
- wdix
- case _ => s
- .map(onStatement)
- .map(onExpression)
- .map(onName)
- }
-
- /** Rename a [[Port]] to avoid keyword collisions
- * $implicitRename
- * $implicitNamespace
- * $implicitScope
- * @return a [[Port]] without keyword conflicts
- */
- private def onPort(p: ir.Port)(implicit renames: RenameMap, ns: Namespace, scope: Option[ModuleName]): ir.Port =
- p.copy(name = onName(p.name))
-
- /** Rename a [[DefModule]] and it's internals (ports and statements) to fix keyword collisions and update instance
- * references to respect previous renames
- * @param renames a [[RenameMap]]
- * @param circuit the enclosing [[CircuitName]]
- * @return a [[DefModule]] without keyword conflicts
- */
- private def onModule(renames: RenameMap,
- circuit: CircuitName,
- modType: ModuleType)
- (m: ir.DefModule): ir.DefModule = {
- implicit val moduleNamespace: Namespace = Namespace(m)
- implicit val scope: Option[ModuleName] = Some(ModuleName(m.name, circuit))
- implicit val r: RenameMap = renames
- implicit val mType: ModuleType = modType
-
- // Store local renames of refs to instances to their renamed modules. This is needed when renaming port connections
- // on subfields where only the local instance name is available.
- implicit val iToM: mutable.Map[ComponentName, ModuleName] = mutable.Map.empty
-
- val mx = m
- .map(onPort)
- .map(onStatement)
- .map(onName(_: String)(renames, moduleNamespace, Some(circuit)))
-
- // Must happen after renaming the name and ports of the module itself
- mType += (mx.name -> onType(Utils.module_type(mx)))
- mx
- }
-
- /** Fix any Verilog keyword collisions in a [[firrtl.ir Circuit]]
- * @param c a [[firrtl.ir Circuit]] with possible name collisions
- * @param renames a [[RenameMap]] to update. If you don't want to propagate renames, this can be ignored.
- * @return a [[firrtl.ir Circuit]] without keyword conflicts
- */
- def run(c: ir.Circuit, renames: RenameMap = RenameMap()): ir.Circuit = {
- implicit val circuitNamespace: Namespace = Namespace(c)
- implicit val scope: Option[CircuitName] = Some(CircuitName(c.main))
- val modType: ModuleType = new ModuleType()
-
- // Rename all modules from leafs to root in one pass while updating a shared rename map. Going from leafs to roots
- // ensures that the rename map is safe for parents to blindly consult.
- val modulesx: Map[ModuleName, Seq[ir.DefModule]] = new InstanceGraph(c).moduleOrder.reverse
- .map(onModule(renames, scope.get, modType))
- .groupBy(m => ModuleName(m.name, scope.get))
-
- // Reorder the renamed modules into the original circuit order.
- val modulesxx: Seq[ir.DefModule] = c.modules.flatMap{ orig =>
- val named = ModuleName(orig.name, scope.get)
- modulesx(renames.get(named).orElse(Some(Seq(named))).get.head)
- }
-
- // Rename the circuit if the top module was renamed
- val mainx = renames.get(ModuleName(c.main, CircuitName(c.main))) match {
- case Some(Seq(ModuleName(m, _))) =>
- renames.rename(CircuitName(c.main), CircuitName(m))
- m
- case x@ Some(_) => throw new PassException(
- s"Verilog renaming shouldn't result in multiple renames, but found '${c.main} -> $x'")
- case None =>
- c.main
- }
-
- // Apply all updates
- c.copy(modules = modulesxx, main = mainx)
+ override def manipulate = (n: String, ns: Namespace) => keywords.contains(n) match {
+ case true => Some(Uniquify.findValidPrefix(n + inlineDelim, Seq(""), ns.cloneUnderlying ++ keywords))
+ case false => None
}
- /** Fix any Verilog keyword name collisions in a [[CircuitState]] while propagating renames
- * @param state the [[CircuitState]] with possible name collisions
- * @return a [[CircuitState]] without name collisions
- */
- def execute(state: CircuitState): CircuitState = {
- val renames = RenameMap()
- renames.setCircuit(state.circuit.main)
- state.copy(circuit = run(state.circuit, renames), renames = Some(renames))
- }
}
/** Transform that removes collisions with Verilog keywords */
diff --git a/src/test/scala/firrtlTests/VerilogEmitterTests.scala b/src/test/scala/firrtlTests/VerilogEmitterTests.scala
index ae06c331..171bce78 100644
--- a/src/test/scala/firrtlTests/VerilogEmitterTests.scala
+++ b/src/test/scala/firrtlTests/VerilogEmitterTests.scala
@@ -420,27 +420,39 @@ class VerilogEmitterSpec extends FirrtlFlatSpec {
| input always: UInt<1>
| output always$: UInt<1>
| inst assign of endmodule
+ | inst edge of endmodule_
| node always_ = not(always)
| node always__ = and(always_, assign.fork)
- | always$ <= always__
+ | node always___ = and(always__, edge.fork)
+ | always$ <= always___
| module endmodule:
| output fork: UInt<1>
| node const = add(UInt<4>("h1"), UInt<3>("h2"))
| fork <= const
+ | module endmodule_:
+ | output fork: UInt<1>
+ | node const = add(UInt<4>("h1"), UInt<3>("h1"))
+ | fork <= const
|""".stripMargin
val check_firrtl =
"""|circuit parameter_:
| module parameter_:
- | input always___: UInt<1>
+ | input always____: UInt<1>
| output always$: UInt<1>
- | inst assign_ of endmodule_
- | node always_ = not(always___)
+ | inst assign_ of endmodule__
+ | inst edge_ of endmodule_
+ | node always_ = not(always____)
| node always__ = and(always_, assign_.fork_)
- | always$ <= always__
- | module endmodule_:
+ | node always___ = and(always__, edge_.fork_)
+ | always$ <= always___
+ | module endmodule__:
| output fork_: UInt<1>
| node const_ = add(UInt<4>("h1"), UInt<3>("h2"))
| fork_ <= const_
+ | module endmodule_:
+ | output fork_: UInt<1>
+ | node const_ = add(UInt<4>("h1"), UInt<3>("h1"))
+ | fork_ <= const_
|""".stripMargin
val state = CircuitState(parse(input), UnknownForm, Seq.empty, None)
val output = Seq( ToWorkingIR, ResolveKinds, InferTypes, new VerilogRename )
diff --git a/src/test/scala/firrtlTests/features/LetterCaseTransformSpec.scala b/src/test/scala/firrtlTests/features/LetterCaseTransformSpec.scala
new file mode 100644
index 00000000..e0053fa8
--- /dev/null
+++ b/src/test/scala/firrtlTests/features/LetterCaseTransformSpec.scala
@@ -0,0 +1,168 @@
+// See LICENSE for license details.
+
+package firrtlTests.features
+
+import firrtl.{ir, CircuitState, Parser, WDefInstance, WRef, WSubField}
+import firrtl.annotations.{CircuitTarget, IsMember, SingleTargetAnnotation}
+import firrtl.features.{LowerCaseNames, UpperCaseNames}
+import firrtl.options.Dependency
+import firrtl.transforms.ManipulateNamesBlocklistAnnotation
+import firrtl.testutils.FirrtlCheckers._
+
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class LetterCaseTransformSpec extends AnyFlatSpec with Matchers {
+
+ case class TrackingAnnotation(val target: IsMember) extends SingleTargetAnnotation[IsMember] {
+ override def duplicate(a: IsMember) = this.copy(target=a)
+ }
+
+ class CircuitFixture {
+ private val input =
+ """|circuit Foo:
+ | module Bar:
+ | output OuT: UInt<2>
+ | OuT <= UInt<2>(0)
+ | module Baz:
+ | output OuT: UInt<2>
+ | OuT <= UInt<2>(1)
+ | module baz:
+ | output OuT: UInt<2>
+ | OuT <= UInt<2>(2)
+ | extmodule Ext:
+ | output OuT: UInt<1>
+ | module Foo:
+ | input CLk: Clock
+ | input rst_P: UInt<1>
+ | input addr: UInt<8>
+ | node Bar = UInt<1>(0)
+ | reg baz: UInt<1>, CLk with: (reset => (rst_P, Bar))
+ | wire QUX: UInt<1>
+ | QUX <= UInt<1>(0)
+ | node quuxQuux = UInt<1>(0)
+ | mem MeM : @[Source.scala 1:4]
+ | data-type => UInt<8>
+ | depth => 32
+ | read-latency => 0
+ | write-latency => 1
+ | reader => Read
+ | writer => wRITE
+ | readwriter => rw
+ | read-under-write => undefined
+ | MeM.Read is invalid
+ | MeM.wRITE is invalid
+ | MeM.rw is invalid
+ | inst SuB1 of Bar
+ | inst SuB2 of Baz
+ | inst SuB3 of baz
+ | inst SuB4 of Ext
+ | node sub1 = UInt<1>(0)
+ | node corge_corge = SuB1.OuT
+ | node QuuzQuuz = and(SuB2.OuT, SuB3.OuT)
+ | node graultGrault = not(SuB4.OuT)
+ |""".stripMargin
+
+ private val Foo = CircuitTarget("Foo")
+ private val Bar = Foo.module("Bar")
+
+ val annotations = Seq(TrackingAnnotation(Foo.module("Foo").ref("MeM").field("wRITE")field("en")),
+ ManipulateNamesBlocklistAnnotation(Seq(Seq(Bar)), Dependency[LowerCaseNames]))
+ val state = CircuitState(Parser.parse(input), annotations)
+ }
+
+ behavior of "LowerCaseNames"
+
+ it should "change all names to lowercase" in new CircuitFixture {
+ val tm = new firrtl.stage.transforms.Compiler(Seq(firrtl.options.Dependency[LowerCaseNames]))
+ val statex = tm.execute(state)
+ val expected: Seq[PartialFunction[Any, Boolean]] = Seq(
+ { case ir.Circuit(_, _, "foo") => true },
+ { case ir.Module(_, "foo",
+ Seq(ir.Port(_, "clk", _, _), ir.Port(_, "rst_p", _, _), ir.Port(_, "addr", _, _)), _) => true },
+ /* Module "Bar" should be skipped via a ManipulateNamesBlocklistAnnotation */
+ { case ir.Module(_, "Bar", Seq(ir.Port(_, "out", _, _)), _) => true },
+ { case ir.Module(_, "baz_0", Seq(ir.Port(_, "out", _, _)), _) => true },
+ { case ir.Module(_, "baz", Seq(ir.Port(_, "out", _, _)), _) => true },
+ /* External module "Ext" is not renamed */
+ { case ir.ExtModule(_, "Ext", Seq(ir.Port(_, "OuT", _, _)), _, _) => true },
+ { case ir.DefNode(_, "bar", _) => true },
+ { case ir.DefRegister(_, "baz", _, WRef("clk", _, _, _), WRef("rst_p", _, _, _), WRef("bar", _, _, _)) => true },
+ { case ir.DefWire(_, "qux", _) => true },
+ { case ir.Connect(_, WRef("qux", _, _, _), _) => true },
+ { case ir.DefNode(_, "quuxquux", _) => true },
+ { case ir.DefMemory(_, "mem", _, _, _, _, Seq("read"), Seq("write"), Seq("rw"), _) => true },
+ /* Ports of memories should be ignored, but these are already lower case */
+ { case ir.IsInvalid(_, WSubField(WSubField(WRef("mem", _, _, _), "read", _, _), "addr", _, _)) => true },
+ { case ir.IsInvalid(_, WSubField(WSubField(WRef("mem", _, _, _), "write", _, _), "addr", _, _)) => true },
+ { case ir.IsInvalid(_, WSubField(WSubField(WRef("mem", _, _, _), "rw", _, _), "addr", _, _)) => true },
+ /* Module "Bar" was skipped via a ManipulateNamesBlocklistAnnotation. The instance "SuB1" is renamed to "sub1_0"
+ * because node "sub1" already exists. This differs from the upper case test.
+ */
+ { case WDefInstance(_, "sub1_0", "Bar", _) => true },
+ { case WDefInstance(_, "sub2", "baz_0", _) => true },
+ { case WDefInstance(_, "sub3", "baz", _) => true },
+ /* External module instance names are renamed */
+ { case WDefInstance(_, "sub4", "Ext", _) => true },
+ { case ir.DefNode(_, "sub1", _) => true },
+ { case ir.DefNode(_, "corge_corge", WSubField(WRef("sub1_0", _, _, _), "out", _, _)) => true },
+ { case ir.DefNode(_, "quuzquuz",
+ ir.DoPrim(_,Seq(WSubField(WRef("sub2", _, _, _), "out", _, _),
+ WSubField(WRef("sub3", _, _, _), "out", _, _)), _, _)) => true },
+ /* References to external module ports are not renamed, e.g., OuT */
+ { case ir.DefNode(_, "graultgrault",
+ ir.DoPrim(_, Seq(WSubField(WRef("sub4", _, _, _), "OuT", _, _)), _, _)) => true }
+ )
+ expected.foreach( statex should containTree (_) )
+ }
+
+ behavior of "UpperCaseNames"
+
+ it should "change all names to uppercase" in new CircuitFixture {
+ val tm = new firrtl.stage.transforms.Compiler(Seq(firrtl.options.Dependency[UpperCaseNames]))
+ val statex = tm.execute(state)
+ val expected: Seq[PartialFunction[Any, Boolean]] = Seq(
+ { case ir.Circuit(_, _, "FOO") => true },
+ { case ir.Module(_, "FOO",
+ Seq(ir.Port(_, "CLK", _, _), ir.Port(_, "RST_P", _, _), ir.Port(_, "ADDR", _, _)), _) => true },
+ /* Module "Bar" should be skipped via a ManipulateNamesBlocklistAnnotation */
+ { case ir.Module(_, "Bar", Seq(ir.Port(_, "OUT", _, _)), _) => true },
+ { case ir.Module(_, "BAZ", Seq(ir.Port(_, "OUT", _, _)), _) => true },
+ { case ir.Module(_, "BAZ_0", Seq(ir.Port(_, "OUT", _, _)), _) => true },
+ /* External module "Ext" is not renamed */
+ { case ir.ExtModule(_, "Ext", Seq(ir.Port(_, "OuT", _, _)), _, _) => true },
+ { case ir.DefNode(_, "BAR", _) => true },
+ { case ir.DefRegister(_, "BAZ", _, WRef("CLK", _, _, _), WRef("RST_P", _, _, _), WRef("BAR", _, _, _)) => true },
+ { case ir.DefWire(_, "QUX", _) => true },
+ { case ir.Connect(_, WRef("QUX", _, _, _), _) => true },
+ { case ir.DefNode(_, "QUUXQUUX", _) => true },
+ { case ir.DefMemory(_, "MEM", _, _, _, _, Seq("READ"), Seq("WRITE"), Seq("RW"), _) => true },
+ /* Ports of memories should be ignored while readers/writers are renamed, e.g., "Read" is converted to upper case
+ * while "addr" is not touched.
+ */
+ { case ir.IsInvalid(_, WSubField(WSubField(WRef("MEM", _, _, _), "READ", _, _), "addr", _, _)) => true },
+ { case ir.IsInvalid(_, WSubField(WSubField(WRef("MEM", _, _, _), "WRITE", _, _), "addr", _, _)) => true },
+ { case ir.IsInvalid(_, WSubField(WSubField(WRef("MEM", _, _, _), "RW", _, _), "addr", _, _)) => true },
+ /* Module "Bar" was skipped via a ManipulateNamesBlocklistAnnotation. The instance "SuB1" is renamed to "SUB1"
+ * because this statement occurs before the "sub1" node later. This differs from the lower case test.
+ */
+ { case WDefInstance(_, "SUB1", "Bar", _) => true },
+ /* Instance "SuB2" and "SuB3" switch their modules from the lower case test due to namespace behavior. */
+ { case WDefInstance(_, "SUB2", "BAZ", _) => true },
+ { case WDefInstance(_, "SUB3", "BAZ_0", _) => true },
+ /* External module "Ext" was skipped via a ManipulateBlocklistAnnotation */
+ { case WDefInstance(_, "SUB4", "Ext", _) => true },
+ /* Node "sub1" becomes "SUB1_0" because instance "SuB1" already got the "SUB1" name. */
+ { case ir.DefNode(_, "SUB1_0", _) => true },
+ { case ir.DefNode(_, "CORGE_CORGE", WSubField(WRef("SUB1", _, _, _), "OUT", _, _)) => true },
+ { case ir.DefNode(_, "QUUZQUUZ",
+ ir.DoPrim(_,Seq(WSubField(WRef("SUB2", _, _, _), "OUT", _, _),
+ WSubField(WRef("SUB3", _, _, _), "OUT", _, _)), _, _)) => true },
+ /* References to external module ports are not renamed, e.g., "OuT" */
+ { case ir.DefNode(_, "GRAULTGRAULT",
+ ir.DoPrim(_, Seq(WSubField(WRef("SUB4", _, _, _), "OuT", _, _)), _, _)) => true }
+ )
+ expected.foreach( statex should containTree (_) )
+ }
+
+}
diff --git a/src/test/scala/firrtlTests/transforms/ManipulateNamesSpec.scala b/src/test/scala/firrtlTests/transforms/ManipulateNamesSpec.scala
new file mode 100644
index 00000000..b1e2eeb9
--- /dev/null
+++ b/src/test/scala/firrtlTests/transforms/ManipulateNamesSpec.scala
@@ -0,0 +1,229 @@
+// See LICENSE for license details.
+
+package firrtlTests.transforms
+
+import firrtl.{
+ ir,
+ CircuitState,
+ FirrtlUserException,
+ Namespace,
+ Parser,
+ RenameMap
+}
+import firrtl.annotations.CircuitTarget
+import firrtl.options.Dependency
+import firrtl.testutils.FirrtlCheckers._
+import firrtl.transforms.{
+ ManipulateNames,
+ ManipulateNamesBlocklistAnnotation,
+ ManipulateNamesAllowlistAnnotation,
+ ManipulateNamesAllowlistResultAnnotation
+}
+
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+object ManipulateNamesSpec {
+
+ class AddPrefix extends ManipulateNames {
+ override def manipulate = (a: String, b: Namespace) => Some(b.newName("prefix_" + a))
+ }
+
+}
+
+class ManipulateNamesSpec extends AnyFlatSpec with Matchers {
+
+ import ManipulateNamesSpec._
+
+ class CircuitFixture {
+ protected val input =
+ """|circuit Foo:
+ | module Bar:
+ | node a = UInt<1>(0)
+ | module Foo:
+ | inst bar of Bar
+ | inst bar2 of Bar
+ |""".stripMargin
+ val `~Foo` = CircuitTarget("Foo")
+ val `~Foo|Foo` = `~Foo`.module("Foo")
+ val `~Foo|Foo/bar:Bar` = `~Foo|Foo`.instOf("bar", "Bar")
+ val `~Foo|Foo/bar2:Bar` = `~Foo|Foo`.instOf("bar2", "Bar")
+ val `~Foo|Bar` = `~Foo`.module("Bar")
+ val `~Foo|Bar>a` = `~Foo|Bar`.ref("a")
+ val tm = new firrtl.stage.transforms.Compiler(Seq(Dependency[AddPrefix]))
+ }
+
+ behavior of "ManipulateNames"
+
+ it should "rename everything by default" in new CircuitFixture {
+ val state = CircuitState(Parser.parse(input), Seq.empty)
+ val statex = tm.execute(state)
+ val expected: Seq[PartialFunction[Any, Boolean]] = Seq(
+ { case ir.Circuit(_, _, "prefix_Foo") => true },
+ { case ir.Module(_, "prefix_Foo", _, _) => true},
+ { case ir.Module(_, "prefix_Bar", _, _) => true}
+ )
+ expected.foreach(statex should containTree (_))
+ }
+
+ it should "do nothing if the circuit is blocklisted" in new CircuitFixture {
+ val annotations = Seq(ManipulateNamesBlocklistAnnotation(Seq(Seq(`~Foo`)), Dependency[AddPrefix]))
+ val state = CircuitState(Parser.parse(input), annotations)
+ val statex = tm.execute(state)
+ state.circuit.serialize should be (statex.circuit.serialize)
+ }
+
+ it should "not rename the circuit if the top module is blocklisted" in new CircuitFixture {
+ val annotations = Seq(ManipulateNamesBlocklistAnnotation(Seq(Seq(`~Foo|Foo`)), Dependency[AddPrefix]))
+ val state = CircuitState(Parser.parse(input), annotations)
+ val expected: Seq[PartialFunction[Any, Boolean]] = Seq(
+ { case ir.Circuit(_, _, "Foo") => true },
+ { case ir.Module(_, "Foo", _, _) => true},
+ { case ir.Module(_, "prefix_Bar", _, _) => true}
+ )
+ val statex = tm.execute(state)
+ expected.foreach(statex should containTree (_))
+ }
+
+ it should "not rename instances if blocklisted" in new CircuitFixture {
+ val annotations = Seq(ManipulateNamesBlocklistAnnotation(Seq(Seq(`~Foo|Foo/bar:Bar`)), Dependency[AddPrefix]))
+ val state = CircuitState(Parser.parse(input), annotations)
+ val expected: Seq[PartialFunction[Any, Boolean]] = Seq(
+ { case ir.DefInstance(_, "bar", "prefix_Bar", _) => true},
+ { case ir.Module(_, "prefix_Bar", _, _) => true}
+ )
+ val statex = tm.execute(state)
+ expected.foreach(statex should containTree (_))
+ }
+
+ it should "do nothing if the circuit is not allowlisted" in new CircuitFixture {
+ val annotations = Seq(
+ ManipulateNamesAllowlistAnnotation(Seq(Seq(`~Foo|Foo`)), Dependency[AddPrefix])
+ )
+ val state = CircuitState(Parser.parse(input), annotations)
+ val statex = tm.execute(state)
+ state.circuit.serialize should be (statex.circuit.serialize)
+ }
+
+ it should "rename only the circuit if allowlisted" in new CircuitFixture {
+ val annotations = Seq(
+ ManipulateNamesAllowlistAnnotation(Seq(Seq(`~Foo`)), Dependency[AddPrefix]),
+ ManipulateNamesAllowlistAnnotation(Seq(Seq(`~Foo|Foo`)), Dependency[AddPrefix])
+ )
+ val state = CircuitState(Parser.parse(input), annotations)
+ val statex = tm.execute(state)
+ val expected: Seq[PartialFunction[Any, Boolean]] = Seq(
+ { case ir.Circuit(_, _, "prefix_Foo") => true },
+ { case ir.Module(_, "prefix_Foo", _, _) => true},
+ { case ir.DefInstance(_, "bar", "Bar", _) => true},
+ { case ir.DefInstance(_, "bar2", "Bar", _) => true},
+ { case ir.Module(_, "Bar", _, _) => true},
+ { case ir.DefNode(_, "a", _) => true}
+ )
+ expected.foreach(statex should containTree (_))
+ }
+
+ it should "rename an instance via allowlisting" in new CircuitFixture {
+ val annotations = Seq(
+ ManipulateNamesAllowlistAnnotation(Seq(Seq(`~Foo`)), Dependency[AddPrefix]),
+ ManipulateNamesAllowlistAnnotation(Seq(Seq(`~Foo|Foo/bar:Bar`)), Dependency[AddPrefix])
+ )
+ val state = CircuitState(Parser.parse(input), annotations)
+ val statex = tm.execute(state)
+ val expected: Seq[PartialFunction[Any, Boolean]] = Seq(
+ { case ir.Circuit(_, _, "Foo") => true },
+ { case ir.Module(_, "Foo", _, _) => true},
+ { case ir.DefInstance(_, "prefix_bar", "Bar", _) => true},
+ { case ir.DefInstance(_, "bar2", "Bar", _) => true},
+ { case ir.Module(_, "Bar", _, _) => true},
+ { case ir.DefNode(_, "a", _) => true}
+ )
+ expected.foreach(statex should containTree (_))
+ }
+
+ it should "rename a node via allowlisting" in new CircuitFixture {
+ val annotations = Seq(
+ ManipulateNamesAllowlistAnnotation(Seq(Seq(`~Foo`)), Dependency[AddPrefix]),
+ ManipulateNamesAllowlistAnnotation(Seq(Seq(`~Foo|Bar>a`)), Dependency[AddPrefix])
+ )
+ val state = CircuitState(Parser.parse(input), annotations)
+ val statex = tm.execute(state)
+ val expected: Seq[PartialFunction[Any, Boolean]] = Seq(
+ { case ir.Circuit(_, _, "Foo") => true },
+ { case ir.Module(_, "Foo", _, _) => true},
+ { case ir.DefInstance(_, "bar", "Bar", _) => true},
+ { case ir.DefInstance(_, "bar2", "Bar", _) => true},
+ { case ir.Module(_, "Bar", _, _) => true},
+ { case ir.DefNode(_, "prefix_a", _) => true}
+ )
+ expected.foreach(statex should containTree (_))
+ }
+
+ it should "throw user errors on circuits that haven't been run through LowerTypes" in {
+ val input =
+ """|circuit Foo:
+ | module Foo:
+ | wire bar: {a: UInt<1>, b: UInt<1>}
+ | node baz = bar.a
+ |""".stripMargin
+ val state = CircuitState(Parser.parse(input), Seq.empty)
+ intercept [FirrtlUserException] {
+ (new AddPrefix).transform(state)
+ }.getMessage should include ("LowerTypes")
+ }
+
+ behavior of "ManipulateNamesBlocklistAnnotation"
+
+ it should "throw an exception if a non-local target is skipped" in new CircuitFixture {
+ val barA = CircuitTarget("Foo").module("Foo").instOf("bar", "Bar").ref("a")
+ assertThrows[java.lang.IllegalArgumentException]{
+ Seq(ManipulateNamesBlocklistAnnotation(Seq(Seq(barA)), Dependency[AddPrefix]))
+ }
+ }
+
+ behavior of "ManipulateNamesAllowlistResultAnnotation"
+
+ it should "delete itself if the new target is deleted" in {
+ val `~Foo|Bar` = CircuitTarget("Foo").module("Bar")
+ val `~Foo|prefix_Bar` = CircuitTarget("Foo").module("prefix_Bar")
+
+ val a = ManipulateNamesAllowlistResultAnnotation(
+ targets = Seq(Seq(`~Foo|prefix_Bar`)),
+ transform = Dependency[AddPrefix],
+ oldTargets = Seq(Seq(`~Foo|Bar`))
+ )
+
+ val r = RenameMap()
+ r.delete(`~Foo|prefix_Bar`)
+
+ a.update(r) should be (empty)
+ }
+
+ it should "drop a deleted target" in {
+ val `~Foo|Bar` = CircuitTarget("Foo").module("Bar")
+ val `~Foo|prefix_Bar` = CircuitTarget("Foo").module("prefix_Bar")
+ val `~Foo|Baz` = CircuitTarget("Foo").module("Baz")
+ val `~Foo|prefix_Baz` = CircuitTarget("Foo").module("prefix_Baz")
+
+ val a = ManipulateNamesAllowlistResultAnnotation(
+ targets = Seq(Seq(`~Foo|prefix_Bar`), Seq(`~Foo|prefix_Baz`)),
+ transform = Dependency[AddPrefix],
+ oldTargets = Seq(Seq(`~Foo|Bar`), Seq(`~Foo|Baz`))
+ )
+
+ val r = RenameMap()
+ r.delete(`~Foo|prefix_Bar`)
+
+ val ax = a.update(r).collect {
+ case b: ManipulateNamesAllowlistResultAnnotation[_] => b
+ }
+
+ ax should not be length (1)
+
+ val keys = ax.head.toRenameMap.getUnderlying.keys
+
+ keys should not contain (`~Foo|Bar`)
+ keys should contain (`~Foo|Baz`)
+ }
+
+}