diff options
| author | Adam Izraelevitz | 2020-07-29 20:48:31 -0700 |
|---|---|---|
| committer | GitHub | 2020-07-29 20:48:31 -0700 |
| commit | 164490c8fbf132ca65644d05d6ff8d0d7a3beb20 (patch) | |
| tree | 862750b85dca5b8496c40c24b3a4e5e67c268bd4 | |
| parent | 8aeb39b9b3755ccd0e3aa600b813ed4220ac72d8 (diff) | |
Improved Chisel Naming via Compiler Plugins + Prefixing (#1448)
Added prefixing and a compiler plugin to improve naming. Only works for Scala 2.12 and above.
Co-authored-by: Jack Koenig <koenig@sifive.com>
18 files changed, 1111 insertions, 46 deletions
@@ -125,6 +125,54 @@ lazy val chiselSettings = Seq ( } ) +autoCompilerPlugins := true + +// Plugin must be fully cross-versioned (published for Scala minor version) +// The plugin only works in Scala 2.12+ +lazy val pluginScalaVersions = Seq( + "2.11.12", // Only to support chisel3 cross building for 2.11, plugin does nothing in 2.11 + // scalamacros paradise version used is not published for 2.12.0 and 2.12.1 + "2.12.2", + "2.12.3", + "2.12.4", + "2.12.5", + "2.12.6", + "2.12.7", + "2.12.8", + "2.12.9", + "2.12.10", + "2.12.11" +) + +lazy val plugin = (project in file("plugin")). + settings(name := "chisel3-plugin"). + settings(commonSettings: _*). + settings(publishSettings: _*). + settings( + libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value, + scalacOptions += "-Xfatal-warnings", + crossScalaVersions := pluginScalaVersions, + // Must be published for Scala minor version + crossVersion := CrossVersion.full, + crossTarget := { + // workaround for https://github.com/sbt/sbt/issues/5097 + target.value / s"scala-${scalaVersion.value}" + }, + // Only publish for Scala 2.12 + publish / skip := !scalaVersion.value.startsWith("2.12") + ) + +lazy val usePluginSettings = Seq( + scalacOptions in Compile ++= { + val jar = (plugin / Compile / Keys.`package`).value + val addPlugin = "-Xplugin:" + jar.getAbsolutePath + // add plugin timestamp to compiler options to trigger recompile of + // main after editing the plugin. (Otherwise a 'clean' is needed.) + val dummy = "-Jdummy=" + jar.lastModified + Seq(addPlugin, dummy) + } +) + lazy val macros = (project in file("macros")). settings(name := "chisel3-macros"). settings(commonSettings: _*). @@ -162,9 +210,10 @@ lazy val chisel = (project in file(".")). settings(commonSettings: _*). settings(chiselSettings: _*). settings(publishSettings: _*). + settings(usePluginSettings: _*). dependsOn(macros). dependsOn(core). - aggregate(macros, core). + aggregate(macros, core, plugin). settings( scalacOptions in Test ++= Seq("-language:reflectiveCalls"), scalacOptions in Compile in doc ++= Seq( diff --git a/core/src/main/scala/chisel3/Data.scala b/core/src/main/scala/chisel3/Data.scala index 46c98bae..983307a6 100644 --- a/core/src/main/scala/chisel3/Data.scala +++ b/core/src/main/scala/chisel3/Data.scala @@ -283,6 +283,14 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { } } + override def autoSeed(name: String): this.type = { + topBindingOpt match { + // Ports are special in that the autoSeed will keep the first name, not the last name + case Some(PortBinding(m)) if hasAutoSeed && Builder.currentModule.contains(m) => this + case _ => super.autoSeed(name) + } + } + // User-specified direction, local at this node only. // Note that the actual direction of this node can differ from child and parent specifiedDirection. private var _specifiedDirection: SpecifiedDirection = SpecifiedDirection.Unspecified @@ -490,7 +498,11 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { * @param that the $coll to connect to * @group Connect */ - final def := (that: Data)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Unit = this.connect(that)(sourceInfo, connectionCompileOptions) + final def := (that: => Data)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Unit = { + prefix(this) { + this.connect(that)(sourceInfo, connectionCompileOptions) + } + } /** Connect this $coll to that $coll bi-directionally and element-wise. * @@ -499,7 +511,11 @@ abstract class Data extends HasId with NamedComponent with SourceInfoDoc { * @param that the $coll to connect to * @group Connect */ - final def <> (that: Data)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Unit = this.bulkConnect(that)(sourceInfo, connectionCompileOptions) + final def <> (that: => Data)(implicit sourceInfo: SourceInfo, connectionCompileOptions: CompileOptions): Unit = { + prefix(this) { + this.bulkConnect(that)(sourceInfo, connectionCompileOptions) + } + } @chiselRuntimeDeprecated @deprecated("litArg is deprecated, use litOption or litTo*Option", "3.2") diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index 140e3003..25037b00 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -41,6 +41,8 @@ object Module extends SourceInfoDoc { // Save then clear clock and reset to prevent leaking scope, must be set again in the Module val (saveClock, saveReset) = (Builder.currentClock, Builder.currentReset) + val savePrefix = Builder.getPrefix() + Builder.clearPrefix() Builder.currentClock = None Builder.currentReset = None @@ -67,6 +69,8 @@ object Module extends SourceInfoDoc { val component = module.generateComponent() Builder.components += component + Builder.setPrefix(savePrefix) + // Handle connections at enclosing scope if(!Builder.currentModule.isEmpty) { pushCommand(DefInstance(sourceInfo, module, component.ports)) diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index 0fcec266..5b609384 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -42,7 +42,7 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) private[chisel3] def namePorts(names: HashMap[HasId, String]): Unit = { for (port <- getModulePorts) { - port.suggestedName.orElse(names.get(port)) match { + port.computeName(None, None).orElse(names.get(port)) match { case Some(name) => if (_namespace.contains(name)) { Builder.error(s"""Unable to name port $port to "$name" in $this,""" + @@ -75,13 +75,21 @@ abstract class RawModule(implicit moduleCompileOptions: CompileOptions) // All suggestions are in, force names to every node. for (id <- getIds) { id match { - case id: BaseModule => id.forceName(default=id.desiredName, _namespace) - case id: MemBase[_] => id.forceName(default="_T", _namespace) + case id: BaseModule => id.forceName(None, default=id.desiredName, _namespace) + case id: MemBase[_] => id.forceName(None, default="MEM", _namespace) case id: Data => if (id.isSynthesizable) { id.topBinding match { - case OpBinding(_) | MemoryPortBinding(_) | PortBinding(_) | RegBinding(_) | WireBinding(_) => - id.forceName(default="_T", _namespace) + case OpBinding(_) => + id.forceName(Some(""), default="T", _namespace) + case MemoryPortBinding(_) => + id.forceName(None, default="MPORT", _namespace) + case PortBinding(_) => + id.forceName(None, default="PORT", _namespace) + case RegBinding(_) => + id.forceName(None, default="REG", _namespace) + case WireBinding(_) => + id.forceName(Some(""), default="WIRE", _namespace) case _ => // don't name literals } } // else, don't name unbound types @@ -152,8 +160,8 @@ trait RequireSyncReset extends MultiIOModule { abstract class MultiIOModule(implicit moduleCompileOptions: CompileOptions) extends RawModule { // Implicit clock and reset pins - final val clock: Clock = IO(Input(Clock())) - final val reset: Reset = IO(Input(mkReset)) + final val clock: Clock = IO(Input(Clock())).autoSeed("clock") + final val reset: Reset = IO(Input(mkReset)).autoSeed("reset") private[chisel3] def mkReset: Reset = { // Top module and compatibility mode use Bool for reset @@ -164,6 +172,7 @@ abstract class MultiIOModule(implicit moduleCompileOptions: CompileOptions) // Setup ClockAndReset Builder.currentClock = Some(clock) Builder.currentReset = Some(reset) + Builder.clearPrefix() private[chisel3] override def initializeInParent(parentCompileOptions: CompileOptions): Unit = { implicit val sourceInfo = UnlocatableSourceInfo diff --git a/core/src/main/scala/chisel3/SeqUtils.scala b/core/src/main/scala/chisel3/SeqUtils.scala index 9f09b2c2..9f068898 100644 --- a/core/src/main/scala/chisel3/SeqUtils.scala +++ b/core/src/main/scala/chisel3/SeqUtils.scala @@ -3,7 +3,7 @@ package chisel3 import chisel3.experimental.FixedPoint -import chisel3.internal.throwException +import chisel3.internal.{prefix, throwException} import scala.language.experimental.macros import chisel3.internal.sourceinfo._ @@ -23,8 +23,12 @@ private[chisel3] object SeqUtils { if (in.tail.isEmpty) { in.head.asUInt } else { - val left = asUInt(in.slice(0, in.length/2)) - val right = asUInt(in.slice(in.length/2, in.length)) + val left = prefix("left") { + asUInt(in.slice(0, in.length/2)) + }.autoSeed("left") + val right = prefix("right") { + asUInt(in.slice(in.length/2, in.length)) + }.autoSeed("right") right ## left } } diff --git a/core/src/main/scala/chisel3/experimental/package.scala b/core/src/main/scala/chisel3/experimental/package.scala index da103318..71fd186c 100644 --- a/core/src/main/scala/chisel3/experimental/package.scala +++ b/core/src/main/scala/chisel3/experimental/package.scala @@ -136,4 +136,18 @@ package object experimental { } } } + + // Use to add a prefix to any component generated in input scope + val prefix = chisel3.internal.prefix + // Use to remove prefixes not in provided scope + val noPrefix = chisel3.internal.noPrefix + // Used by Chisel's compiler plugin to automatically name signals + def autoNameRecursively[T <: Any](name: String, nameMe: T): T = { + chisel3.internal.Builder.nameRecursively( + name.replace(" ", ""), + nameMe, + (id: chisel3.internal.HasId, n: String) => id.autoSeed(n) + ) + nameMe + } } diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index 510919bc..ceb14900 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -9,6 +9,7 @@ import chisel3.experimental._ import chisel3.internal.firrtl._ import chisel3.internal.naming._ import _root_.firrtl.annotations.{CircuitName, ComponentName, IsMember, ModuleName, Named, ReferenceTarget} +import chisel3.internal.Builder.Prefix import scala.collection.mutable @@ -88,25 +89,133 @@ private[chisel3] trait HasId extends InstanceId { override def hashCode: Int = super.hashCode() override def equals(that: Any): Boolean = super.equals(that) - // Facilities for 'suggesting' a name to this. - // Post-name hooks called to carry the suggestion to other candidates as needed - private var suggested_name: Option[String] = None - private val postname_hooks = scala.collection.mutable.ListBuffer.empty[String=>Unit] - // Only takes the first suggestion! - def suggestName(name: =>String): this.type = { - if(suggested_name.isEmpty) suggested_name = Some(name) - for(hook <- postname_hooks) { hook(name) } + // Contains suggested seed (user-decided seed) + private var suggested_seed: Option[String] = None + + // Contains the seed computed automatically by the compiler plugin + private var auto_seed: Option[String] = None + + // Prefix at time when this class is constructed + private val construction_prefix: Prefix = Builder.getPrefix() + + // Prefix when the latest [[suggestSeed]] or [[autoSeed]] is called + private var prefix_seed: Prefix = List.empty[Either[String, Data]] + + // Post-seed hooks called to carry the suggested seeds to other candidates as needed + private val suggest_postseed_hooks = scala.collection.mutable.ListBuffer.empty[String=>Unit] + + // Post-seed hooks called to carry the auto seeds to other candidates as needed + private val auto_postseed_hooks = scala.collection.mutable.ListBuffer.empty[String=>Unit] + + /** Takes the last seed suggested. Multiple calls to this function will take the last given seed, unless + * this HasId is a module port (see overridden method in Data.scala). + * + * If the final computed name conflicts with the final name of another signal, the final name may get uniquified by + * appending a digit at the end of the name. + * + * Is a lower priority than [[suggestName]], in that regardless of whether [[autoSeed]] + * was called, [[suggestName]] will always take precedence if it was called. + * + * @param seed Seed for the name of this component + * @return this object + */ + def autoSeed(seed: String): this.type = { + auto_seed = Some(seed) + for(hook <- auto_postseed_hooks) { hook(seed) } + prefix_seed = Builder.getPrefix() this } - private[chisel3] def suggestedName: Option[String] = suggested_name - private[chisel3] def addPostnameHook(hook: String=>Unit): Unit = postname_hooks += hook + + /** Takes the first seed suggested. Multiple calls to this function will be ignored. + * If the final computed name conflicts with another name, it may get uniquified by appending + * a digit at the end. + * + * Is a higher priority than [[autoSeed]], in that regardless of whether [[autoSeed]] + * was called, [[suggestName]] will always take precedence. + * + * @param seed The seed for the name of this component + * @return this object + */ + def suggestName(seed: =>String): this.type = { + if(suggested_seed.isEmpty) suggested_seed = Some(seed) + prefix_seed = Builder.getPrefix() + for(hook <- suggest_postseed_hooks) { hook(seed) } + this + } + + /** Computes the name of this HasId, if one exists + * @param defaultPrefix Optionally provide a default prefix for computing the name + * @param defaultSeed Optionally provide default seed for computing the name + * @return the name, if it can be computed + */ + def computeName(defaultPrefix: Option[String], defaultSeed: Option[String]): Option[String] = { + // Recursively builds a name if referenced fields of an aggregate type + def buildAggName(id: HasId): Option[String] = { + def recArg(node: Arg): Option[String] = node match { + case Slot(imm, name) => recArg(imm).map(_ + "_" + name) + case Index(imm, ILit(num)) => recArg(imm).map(_ + "_" + num) + case Index(imm, n: LitArg) => recArg(imm).map(_ + "_" + n.num) + case Index(imm, _: Node) => recArg(imm) + case Node(id) => recArg(id.getOptionRef.get) + case Ref(name) => Some(name) + case ModuleIO(mod, name) if _parent.contains(mod) => Some(name) + case ModuleIO(mod, name) => recArg(mod.getRef).map(_ + "_" + name) + } + id.getOptionRef.flatMap(recArg) + } + + /** Computes a name of this signal, given the seed and prefix + * @param seed + * @param prefix + * @return + */ + def buildName(seed: String, prefix: Prefix): String = { + val builder = new StringBuilder() + prefix.foreach { + case Left(s: String) => builder ++= s + "_" + case Right(d: HasId) => + buildAggName(d) match { + case Some(n) => builder ++= n + "_" + case _ => + } + case _ => + } + builder ++= seed + builder.toString + } + + if(hasSeed) { + Some(buildName(seedOpt.get, prefix_seed)) + } else { + defaultSeed.map { default => + defaultPrefix match { + case Some(p) => buildName(default, Left(p) +: construction_prefix) + case None => buildName(default, construction_prefix) + } + } + } + } + + /** This resolves the precedence of [[autoSeed]] and [[suggestName]] + * + * @return the current calculation of a name, if it exists + */ + private[chisel3] def seedOpt: Option[String] = suggested_seed.orElse(auto_seed) + + /** @return Whether either autoName or suggestName has been called */ + def hasSeed: Boolean = seedOpt.isDefined + + private[chisel3] def hasAutoSeed: Boolean = auto_seed.isDefined + + private[chisel3] def addSuggestPostnameHook(hook: String=>Unit): Unit = suggest_postseed_hooks += hook + private[chisel3] def addAutoPostnameHook(hook: String=>Unit): Unit = auto_postseed_hooks += hook // Uses a namespace to convert suggestion into a true name // Will not do any naming if the reference already assigned. // (e.g. tried to suggest a name to part of a Record) - private[chisel3] def forceName(default: =>String, namespace: Namespace): Unit = + private[chisel3] def forceName(prefix: Option[String], default: =>String, namespace: Namespace): Unit = if(_ref.isEmpty) { - val candidate_name = suggested_name.getOrElse(default) + val candidate_name = computeName(prefix, Some(default)).get val available_name = namespace.name(candidate_name) setRef(Ref(available_name)) } @@ -124,7 +233,7 @@ private[chisel3] trait HasId extends InstanceId { case Some(p) => p._component match { case Some(c) => _ref match { case Some(arg) => arg fullName c - case None => suggested_name.getOrElse("??") + case None => computeName(None, None).get } case None => throwException("signalName/pathName should be called after circuit elaboration") } @@ -158,9 +267,14 @@ private[chisel3] trait HasId extends InstanceId { } } val valNames = getValNames(this.getClass) - def isPublicVal(m: java.lang.reflect.Method) = - m.getParameterTypes.isEmpty && valNames.contains(m.getName) && !m.getDeclaringClass.isAssignableFrom(rootClass) - this.getClass.getMethods.sortWith(_.getName < _.getName).filter(isPublicVal(_)) + def isPublicVal(m: java.lang.reflect.Method) = { + val noParameters = m.getParameterTypes.isEmpty + val aVal = valNames.contains(m.getName) + val notAssignable = !m.getDeclaringClass.isAssignableFrom(rootClass) + val notWeirdVal = !m.getName.contains('$') + noParameters && aVal && notAssignable && notWeirdVal + } + this.getClass.getMethods.filter(isPublicVal).sortWith(_.getName < _.getName) } } /** Holds the implementation of toNamed for Data and MemBase */ @@ -199,6 +313,9 @@ private[chisel3] class ChiselContext() { // Record the Bundle instance, class name, method name, and reverse stack trace position of open Bundles val bundleStack: ArrayBuffer[(Bundle, String, String, Int)] = ArrayBuffer() + + // Records the different prefixes which have been scoped at this point in time + val prefixStack: ArrayBuffer[Either[String, HasId]] = ArrayBuffer() } private[chisel3] class DynamicContext() { @@ -223,6 +340,10 @@ private[chisel3] class DynamicContext() { } private[chisel3] object Builder { + + // Represents the current state of the prefixes given + type Prefix = List[Either[String, Data]] + // All global mutable state must be referenced via dynamicContextVar!! private val dynamicContextVar = new DynamicVariable[Option[DynamicContext]](None) private def dynamicContext: DynamicContext = { @@ -258,6 +379,43 @@ private[chisel3] object Builder { def annotations: ArrayBuffer[ChiselAnnotation] = dynamicContext.annotations def namingStack: NamingStack = dynamicContext.namingStack + // Puts either a prefix string or hasId onto the prefix stack + def pushPrefix(d: Either[String, HasId]): Unit = { + chiselContext.get().prefixStack += d + } + + // Puts a prefix string onto the prefix stack + def pushPrefix(d: String): Unit = { + chiselContext.get().prefixStack += Left(d) + } + + // Puts a prefix data onto the prefix stack + def pushPrefix(d: HasId): Unit = { + chiselContext.get().prefixStack += Right(d) + } + + // Remove a prefix from top of the stack + def popPrefix(): Either[String, HasId] = { + val ps = chiselContext.get().prefixStack + ps.remove(ps.size - 1) + } + + // Removes all prefixes from the prefix stack + def clearPrefix(): Unit = { + val ps = chiselContext.get().prefixStack + ps.clear() + } + + // Clears existing prefixes and sets to new prefix stack + def setPrefix(prefix: Prefix): Unit = { + val ps = chiselContext.get().prefixStack + clearPrefix() + ps.insertAll(0, prefix) + } + + // Returns the prefix stack at this moment + def getPrefix(): Prefix = chiselContext.get().prefixStack.toList.asInstanceOf[Prefix] + def currentModule: Option[BaseModule] = dynamicContextVar.value match { case Some(dyanmicContext) => dynamicContext.currentModule case _ => None @@ -411,7 +569,7 @@ private[chisel3] object Builder { dynamicContextVar.withValue(Some(new DynamicContext())) { errors.info("Elaborating design...") val mod = f - mod.forceName(mod.name, globalNamespace) + mod.forceName(None, mod.name, globalNamespace) errors.checkpoint() errors.info("Done elaborating.") @@ -446,7 +604,7 @@ object DynamicNamingStack { } prefixRef } - + def length() : Int = Builder.namingStackOption.get.length } diff --git a/core/src/main/scala/chisel3/internal/Namer.scala b/core/src/main/scala/chisel3/internal/Namer.scala index 999971a4..0153c0df 100644 --- a/core/src/main/scala/chisel3/internal/Namer.scala +++ b/core/src/main/scala/chisel3/internal/Namer.scala @@ -72,7 +72,7 @@ class NamingContext extends NamingContextInterface { val descendants = new IdentityHashMap[AnyRef, ListBuffer[NamingContext]]() val anonymousDescendants = ListBuffer[NamingContext]() val items = ListBuffer[(AnyRef, String)]() - var closed = false // a sanity check to ensure no more name() calls are done after name_prefix + var closed = false // a sanity check to ensure no more name() calls are done after namePrefix /** Adds a NamingContext object as a descendant - where its contained objects will have names * prefixed with the name given to the reference object, if the reference object is named in the @@ -87,7 +87,7 @@ class NamingContext extends NamingContextInterface { } def name[T](obj: T, name: String): T = { - assert(!closed, "Can't name elements after name_prefix called") + assert(!closed, "Can't name elements after namePrefix called") obj match { case _: NoChiselNamePrefix => // Don't name things with NoChiselNamePrefix case ref: AnyRef => items += ((ref, name)) @@ -149,6 +149,6 @@ class NamingStack { namingStack.top.addDescendant(prefixRef, until) } } - + def length() : Int = namingStack.length } diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 44fdb833..1783f68f 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -72,11 +72,11 @@ abstract class Arg { case class Node(id: HasId) extends Arg { override def fullName(ctx: Component): String = id.getOptionRef match { case Some(arg) => arg.fullName(ctx) - case None => id.suggestedName.getOrElse("??") + case None => id.instanceName } def name: String = id.getOptionRef match { case Some(arg) => arg.name - case None => id.suggestedName.getOrElse("??") + case None => id.instanceName } } diff --git a/core/src/main/scala/chisel3/internal/prefix.scala b/core/src/main/scala/chisel3/internal/prefix.scala new file mode 100644 index 00000000..fbb1318c --- /dev/null +++ b/core/src/main/scala/chisel3/internal/prefix.scala @@ -0,0 +1,84 @@ +// See LICENSE for license details. + +package chisel3.internal + +/** Use to add a prefix to any components generated in the provided scope. + * + * @example {{{ + * + * val x1 = prefix("first") { + * // Anything generated here will be prefixed with "first" + * } + * + * val x2 = prefix(mysignal) { + * // Anything generated here will be prefixed with the name of mysignal + * } + * + * }}} + * + */ +private[chisel3] object prefix { // scalastyle:ignore + + /** Use to add a prefix to any components generated in the provided scope + * The prefix is the name of the provided which, which may not be known yet. + * + * @param name The signal/instance whose name will be the prefix + * @param f a function for which any generated components are given the prefix + * @tparam T The return type of the provided function + * @return The return value of the provided function + */ + def apply[T](name: HasId)(f: => T): T = { + Builder.pushPrefix(name) + val ret = f + Builder.popPrefix() + ret + } + + /** Use to add a prefix to any components generated in the provided scope + * The prefix is a string, which must be known when this function is used. + * + * @param name The name which will be the prefix + * @param f a function for which any generated components are given the prefix + * @tparam T The return type of the provided function + * @return The return value of the provided function + */ + def apply[T](name: String)(f: => T): T = { + Builder.pushPrefix(name) + val ret = f + // Sometimes val's can occur between the Module.apply and Module constructor + // This causes extra prefixes to be added, and subsequently cleared in the + // Module constructor. Thus, we need to just make sure if the previous push + // was an incorrect one, to not pop off an empty stack + if(Builder.getPrefix().nonEmpty) Builder.popPrefix() + ret + } +} + +/** Use to eliminate any existing prefixes within the provided scope. + * + * @example {{{ + * + * val x1 = noPrefix { + * // Anything generated here will not be prefixed by anything outside this scope + * } + * + * }}} + * + */ +private[chisel3] object noPrefix { + + /** Use to clear existing prefixes so no signals within the scope are prefixed by signals/names + * outside the scope + * + * @param f a function for which any generated components are given the prefix + * @tparam T The return type of the provided function + * @return The return value of the provided function + */ + def apply[T](f: => T): T = { + val prefix = Builder.getPrefix() + Builder.clearPrefix() + val ret = f + Builder.setPrefix(prefix) + ret + } +} diff --git a/macros/src/main/scala/chisel3/internal/naming/NamingAnnotations.scala b/macros/src/main/scala/chisel3/internal/naming/NamingAnnotations.scala index 88baa2b1..d8f2ff40 100644 --- a/macros/src/main/scala/chisel3/internal/naming/NamingAnnotations.scala +++ b/macros/src/main/scala/chisel3/internal/naming/NamingAnnotations.scala @@ -112,7 +112,7 @@ class NamingTransforms(val c: Context) { } } - /** Applies the val name transform to a class body. + /** Applies the val name transform to a class body. * Closes context on top level or return local context to englobing context. * Closing context only makes sense when top level a Module. * A Module is always the naming top level. @@ -128,7 +128,7 @@ class NamingTransforms(val c: Context) { if($globalNamingStack.length == 1){ $contextVar.namePrefix("") } - $globalNamingStack.popReturnContext(this, $contextVar) + $globalNamingStack.popReturnContext(this, $contextVar) """ } diff --git a/plugin/src/main/resources/scalac-plugin.xml b/plugin/src/main/resources/scalac-plugin.xml new file mode 100644 index 00000000..b2b44fc6 --- /dev/null +++ b/plugin/src/main/resources/scalac-plugin.xml @@ -0,0 +1,4 @@ +<plugin> + <name>chiselplugin</name> + <classname>chisel3.plugin.ChiselPlugin</classname> +</plugin> diff --git a/plugin/src/main/scala-2.12/chisel3/plugin/ChiselPlugin.scala b/plugin/src/main/scala-2.12/chisel3/plugin/ChiselPlugin.scala new file mode 100644 index 00000000..785a253d --- /dev/null +++ b/plugin/src/main/scala-2.12/chisel3/plugin/ChiselPlugin.scala @@ -0,0 +1,139 @@ +// See LICENSE for license details. + +package chisel3.plugin + +import scala.tools.nsc +import nsc.{Global, Phase} +import nsc.plugins.Plugin +import nsc.plugins.PluginComponent +import scala.reflect.internal.Flags +import scala.tools.nsc.transform.TypingTransformers + +// The plugin to be run by the Scala compiler during compilation of Chisel code +class ChiselPlugin(val global: Global) extends Plugin { + val name = "chiselplugin" + val description = "Plugin for Chisel 3 Hardware Description Language" + val components = List[PluginComponent](new ChiselComponent(global)) +} + +// The component of the chisel plugin. Not sure exactly what the difference is between +// a Plugin and a PluginComponent. +class ChiselComponent(val global: Global) extends PluginComponent with TypingTransformers { + import global._ + val runsAfter = List[String]("typer") + override val runsRightAfter: Option[String] = Some("typer") + val phaseName: String = "chiselcomponent" + def newPhase(_prev: Phase): ChiselComponentPhase = new ChiselComponentPhase(_prev) + class ChiselComponentPhase(prev: Phase) extends StdPhase(prev) { + override def name: String = phaseName + def apply(unit: CompilationUnit): Unit = { + // This plugin doesn't work on Scala 2.11. Rather than complicate the sbt build flow, + // instead we just check the version and if its an early Scala version, the plugin does nothing + if(scala.util.Properties.versionNumberString.split('.')(1).toInt >= 12) { + unit.body = new MyTypingTransformer(unit).transform(unit.body) + } + } + } + + class MyTypingTransformer(unit: CompilationUnit) + extends TypingTransformer(unit) { + + // Determines if the chisel plugin should match on this type + def shouldMatch(q: Type, bases: Seq[Tree]): Boolean = { + + // If subtype of Data or BaseModule, its a match! + def terminate(t: Type): Boolean = bases.exists { base => t <:< inferType(base) } + + // Recurse through subtype hierarchy finding containers + // Seen is only updated when we recurse into type parameters, thus it is typically small + def recShouldMatch(s: Type, seen: Set[Type]): Boolean = { + def outerMatches(t: Type): Boolean = { + val str = t.toString + str.startsWith("Option[") || str.startsWith("Iterable[") + } + if (terminate(s)) { + true + } else if (seen.contains(s)) { + false + } else if (outerMatches(s)) { + // These are type parameters, loops *are* possible here + recShouldMatch(s.typeArgs.head, seen + s) + } else { + // This is the standard inheritance hierarchy, Scalac catches loops here + s.parents.exists( p => recShouldMatch(p, seen) ) + } + } + + // If doesn't match container pattern, exit early + def earlyExit(t: Type): Boolean = { + !(t.matchesPattern(inferType(tq"Iterable[_]")) || t.matchesPattern(inferType(tq"Option[_]"))) + } + + // First check if a match, then check early exit, then recurse + if(terminate(q)){ + true + } else if(earlyExit(q)) { + false + } else { + recShouldMatch(q, Set.empty) + } + } + + // Given a type tree, infer the type and return it + def inferType(t: Tree): Type = localTyper.typed(t, nsc.Mode.TYPEmode).tpe + + // Indicates whether a ValDef is properly formed to get name + def okVal(dd: ValDef, bases: Tree*): Boolean = { + + // These were found through trial and error + def okFlags(mods: Modifiers): Boolean = { + val badFlags = Set( + Flag.PARAM, + Flag.SYNTHETIC, + Flag.DEFERRED, + Flags.TRIEDCOOKING, + Flags.CASEACCESSOR, + Flags.PARAMACCESSOR + ) + badFlags.forall{ x => !mods.hasFlag(x)} + } + + // Ensure expression isn't null, as you can't call `null.autoName("myname")` + val isNull = dd.rhs match { + case Literal(Constant(null)) => true + case _ => false + } + okFlags(dd.mods) && shouldMatch(inferType(dd.tpt), bases) && !isNull && dd.rhs != EmptyTree + } + + // Whether this val is directly enclosed by a Bundle type + def inBundle(dd: ValDef): Boolean = { + dd.symbol.logicallyEnclosingMember.thisType <:< inferType(tq"chisel3.Bundle") + } + + // Method called by the compiler to modify source tree + override def transform(tree: Tree): Tree = tree match { + // If a Data and in a Bundle, just get the name but not a prefix + case dd @ ValDef(mods, name, tpt, rhs) if okVal(dd, tq"chisel3.Data") && inBundle(dd) => + val TermName(str: String) = name + val newRHS = super.transform(rhs) + val named = q"chisel3.experimental.autoNameRecursively($str, $newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + // If a Data or a Memory, get the name and a prefix + case dd @ ValDef(mods, name, tpt, rhs) if okVal(dd, tq"chisel3.Data", tq"chisel3.MemBase[_]") => + val TermName(str: String) = name + val newRHS = super.transform(rhs) + val prefixed = q"chisel3.experimental.prefix.apply[$tpt](name=$str)(f=$newRHS)" + val named = q"chisel3.experimental.autoNameRecursively($str, $prefixed)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + // If an instance, just get a name but no prefix + case dd @ ValDef(mods, name, tpt, rhs) if okVal(dd, tq"chisel3.experimental.BaseModule") => + val TermName(str: String) = name + val newRHS = super.transform(rhs) + val named = q"chisel3.experimental.autoNameRecursively($str, $newRHS)" + treeCopy.ValDef(dd, mods, name, tpt, localTyper typed named) + // Otherwise, continue + case _ => super.transform(tree) + } + } +} diff --git a/src/main/scala/chisel3/util/TransitName.scala b/src/main/scala/chisel3/util/TransitName.scala index 3b9a3869..992f48d2 100644 --- a/src/main/scala/chisel3/util/TransitName.scala +++ b/src/main/scala/chisel3/util/TransitName.scala @@ -43,7 +43,9 @@ object TransitName { * @return the `from` parameter */ def apply[T<:HasId](from: T, to: HasId): T = { - from.addPostnameHook((given_name: String) => {to.suggestName(given_name)}) + // To transit a name, we need to hook on both the suggestName and autoSeed mechanisms + from.addSuggestPostnameHook((given_name: String) => {to.suggestName(given_name)}) + from.addAutoPostnameHook((given_name: String) => {to.autoSeed(given_name)}) from } @@ -55,7 +57,9 @@ object TransitName { * @return the `from` parameter */ def withSuffix[T<:HasId](suffix: String)(from: T, to: HasId): T = { - from.addPostnameHook((given_name: String) => {to.suggestName(given_name + suffix)}) + // To transit a name, we need to hook on both the suggestName and autoSeed mechanisms + from.addSuggestPostnameHook((given_name: String) => {to.suggestName(given_name + suffix)}) + from.addAutoPostnameHook((given_name: String) => {to.autoSeed(given_name + suffix)}) from } diff --git a/src/test/scala/chiselTests/BetterNamingTests.scala b/src/test/scala/chiselTests/BetterNamingTests.scala index 032b634e..dd17a015 100644 --- a/src/test/scala/chiselTests/BetterNamingTests.scala +++ b/src/test/scala/chiselTests/BetterNamingTests.scala @@ -36,12 +36,14 @@ class IterableNaming extends NamedModuleTester { expectName(WireDefault(2.U), "optSet_2"), expectName(WireDefault(3.U), "optSet_3"))) - val stack = mutable.Stack[Module]() - for (i <- 0 until 4) { - val j = 3 - i - stack.push(expectName(Module(new Other(i)), s"stack_$j")) + val stack = { + val s = mutable.Stack[Module]() + for (i <- 0 until 4) { + val j = 3 - i + s.push(expectName(Module(new Other(i)), s"stack_$j")) + } + s } - def streamFrom(x: Int): Stream[Module] = expectName(Module(new Other(x)), s"list_$x") #:: streamFrom(x + 1) val stream = streamFrom(0) // Check that we don't get into infinite loop diff --git a/src/test/scala/chiselTests/ChiselSpec.scala b/src/test/scala/chiselTests/ChiselSpec.scala index 2ee6fbcf..7980e772 100644 --- a/src/test/scala/chiselTests/ChiselSpec.scala +++ b/src/test/scala/chiselTests/ChiselSpec.scala @@ -14,6 +14,9 @@ import firrtl.annotations.DeletedAnnotation import firrtl.util.BackendCompilationUtilities import java.io.ByteArrayOutputStream import java.security.Permission + +import chisel3.aop.Aspect +import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage, NoRunFirrtlCompilerAnnotation, PrintFullStackTraceAnnotation} import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks import scala.reflect.ClassTag @@ -270,6 +273,27 @@ trait Utils { } } + + /** A tester which runs generator and uses an aspect to check the returned object + * @param gen function to generate a Chisel module + * @param f a function to check the Chisel module + * @tparam T the Chisel module class + */ + def aspectTest[T <: RawModule](gen: () => T)(f: T => Unit)(implicit scalaMajorVersion: Int): Unit = { + // Runs chisel stage + def run[T <: RawModule](gen: () => T, annotations: AnnotationSeq): AnnotationSeq = { + new ChiselStage().run(Seq(ChiselGeneratorAnnotation(gen), NoRunFirrtlCompilerAnnotation, PrintFullStackTraceAnnotation) ++ annotations) + } + // Creates a wrapping aspect to contain checking function + case object BuiltAspect extends Aspect[T] { + override def toAnnotation(top: T): AnnotationSeq = {f(top); Nil} + } + val currentMajorVersion = scala.util.Properties.versionNumberString.split('.')(1).toInt + if(currentMajorVersion >= scalaMajorVersion) { + run(gen, Seq(BuiltAspect)) + } + } + /** Run some code and rethrow an exception with a specific type if an exception of that type occurs anywhere in the * stack trace. * @@ -306,5 +330,4 @@ trait Utils { } } - } diff --git a/src/test/scala/chiselTests/naming/NamePluginSpec.scala b/src/test/scala/chiselTests/naming/NamePluginSpec.scala new file mode 100644 index 00000000..fc90264d --- /dev/null +++ b/src/test/scala/chiselTests/naming/NamePluginSpec.scala @@ -0,0 +1,207 @@ +// See LICENSE for license details. + +package chiselTests.naming + +import chisel3._ +import chisel3.aop.Select +import chisel3.experimental.{prefix, treedump} +import chiselTests.{ChiselFlatSpec, Utils} + +class NamePluginSpec extends ChiselFlatSpec with Utils { + implicit val minimumScalaVersion: Int = 12 + + "Scala plugin" should "name internally scoped components" in { + class Test extends MultiIOModule { + { val mywire = Wire(UInt(3.W))} + } + aspectTest(() => new Test) { + top: Test => Select.wires(top).head.toTarget.ref should be("mywire") + } + } + + "Scala plugin" should "name internally scoped instances" in { + class Inner extends MultiIOModule { } + class Test extends MultiIOModule { + { val myinstance = Module(new Inner) } + } + aspectTest(() => new Test) { + top: Test => Select.instances(top).head.instanceName should be("myinstance") + } + } + + "Scala plugin" should "interact with prefixing" in { + class Test extends MultiIOModule { + def builder() = { + val wire = Wire(UInt(3.W)) + } + prefix("first") { + builder() + } + prefix("second") { + builder() + } + } + aspectTest(() => new Test) { + top: Test => Select.wires(top).map(_.instanceName) should be (List("first_wire", "second_wire")) + } + } + + "Scala plugin" should "interact with prefixing so last val name wins" in { + class Test extends MultiIOModule { + def builder() = { + val wire1 = Wire(UInt(3.W)) + val wire2 = Wire(UInt(3.W)) + wire2 + } + { + val x1 = prefix("first") { + builder() + } + } + { + val x2 = prefix("second") { + builder() + } + } + } + aspectTest(() => new Test) { + top: Test => Select.wires(top).map(_.instanceName) should be (List("x1_first_wire1", "x1", "x2_second_wire1", "x2")) + } + } + + "Naming on option" should "work" in { + + class Test extends MultiIOModule { + def builder(): Option[UInt] = { + val a = Wire(UInt(3.W)) + Some(a) + } + + { val blah = builder() } + } + aspectTest(() => new Test) { + top: Test => + Select.wires(top).map(_.instanceName) should be (List("blah")) + } + } + + + "Naming on iterables" should "work" in { + + class Test extends MultiIOModule { + def builder(): Seq[UInt] = { + val a = Wire(UInt(3.W)) + val b = Wire(UInt(3.W)) + Seq(a, b) + } + { + val blah = { + builder() + } + } + } + aspectTest(() => new Test) { + top: Test => + Select.wires(top).map(_.instanceName) should be (List("blah_0", "blah_1")) + } + } + + "Naming on nested iterables" should "work" in { + + class Test extends MultiIOModule { + def builder(): Seq[Seq[UInt]] = { + val a = Wire(UInt(3.W)) + val b = Wire(UInt(3.W)) + val c = Wire(UInt(3.W)) + val d = Wire(UInt(3.W)) + Seq(Seq(a, b), Seq(c, d)) + } + { + val blah = { + builder() + } + } + } + aspectTest(() => new Test) { + top: Test => + Select.wires(top).map(_.instanceName) should be ( + List( + "blah_0_0", + "blah_0_1", + "blah_1_0", + "blah_1_1" + )) + } + } + + "Naming on custom case classes" should "not work" in { + case class Container(a: UInt, b: UInt) + + class Test extends MultiIOModule { + def builder(): Container = { + val a = Wire(UInt(3.W)) + val b = Wire(UInt(3.W)) + Container(a, b) + } + + { val blah = builder() } + } + aspectTest(() => new Test) { + top: Test => + Select.wires(top).map(_.instanceName) should be (List("a", "b")) + } + } + + "Multiple names on an IO within a module" should "get the first name" in { + class Test extends RawModule { + { + val a = IO(Output(UInt(3.W))) + val b = a + } + } + + aspectTest(() => new Test) { + top: Test => + Select.ios(top).map(_.instanceName) should be (List("a")) + } + } + + "Multiple names on a non-IO" should "get the last name" in { + class Test extends MultiIOModule { + { + val a = Wire(UInt(3.W)) + val b = a + } + } + + aspectTest(() => new Test) { + top: Test => + Select.wires(top).map(_.instanceName) should be (List("b")) + } + } + + "Unapply assignments" should "still be named" in { + class Test extends MultiIOModule { + { + val (a, b) = (Wire(UInt(3.W)), Wire(UInt(3.W))) + } + } + + aspectTest(() => new Test) { + top: Test => + Select.wires(top).map(_.instanceName) should be (List("a", "b")) + } + } + + "Recursive types" should "not infinitely loop" in { + // When this fails, it causes a StackOverflow when compiling the tests + // Unfortunately, this doesn't seem to work with assertCompiles(...), it probably ignores the + // custom project scalacOptions + def func(x: String) = { + // We only check types of vals, we don't actually want to run this code though + val y = scala.xml.XML.loadFile(x) + y + } + } +} + diff --git a/src/test/scala/chiselTests/naming/PrefixSpec.scala b/src/test/scala/chiselTests/naming/PrefixSpec.scala new file mode 100644 index 00000000..df350829 --- /dev/null +++ b/src/test/scala/chiselTests/naming/PrefixSpec.scala @@ -0,0 +1,348 @@ +// See LICENSE for license details. + +package chiselTests.naming + +import chisel3._ +import chisel3.aop.Select +import chisel3.experimental.{dump, noPrefix, prefix, treedump} +import chiselTests.{ChiselPropSpec, Utils} + +class PrefixSpec extends ChiselPropSpec with Utils { + implicit val minimumMajorVersion: Int = 12 + property("Scala plugin should interact with prefixing so last plugin name wins?") { + class Test extends MultiIOModule { + def builder(): UInt = { + val wire1 = Wire(UInt(3.W)) + val wire2 = Wire(UInt(3.W)) + wire2 + } + + { + val x1 = prefix("first") { + builder() + } + } + { + val x2 = prefix("second") { + builder() + } + } + } + aspectTest(() => new Test) { + top: Test => Select.wires(top).map(_.instanceName) should be (List("x1_first_wire1", "x1", "x2_second_wire1", "x2")) + } + } + + property("Nested prefixes should work") { + class Test extends MultiIOModule { + def builder2(): UInt = { + val wire1 = Wire(UInt(3.W)) + val wire2 = Wire(UInt(3.W)) + wire2 + } + def builder(): UInt = { + val wire1 = Wire(UInt(3.W)) + val wire2 = Wire(UInt(3.W)) + prefix("foo") { + builder2() + } + } + { val x1 = builder() } + { val x2 = builder() } + } + aspectTest(() => new Test) { + top: Test => + Select.wires(top).map(_.instanceName) should be ( + List( + "x1_wire1", + "x1_wire2", + "x1_foo_wire1", + "x1", + "x2_wire1", + "x2_wire2", + "x2_foo_wire1", + "x2" + ) + ) + } + } + + property("Prefixing seeded with signal") { + class Test extends MultiIOModule { + @treedump + @dump + def builder(): UInt = { + val wire = Wire(UInt(3.W)) + wire := 3.U + wire + } + { + val x1 = Wire(UInt(3.W)) + x1 := { + builder() + } + val x2 = Wire(UInt(3.W)) + x2 := { + builder() + } + } + } + aspectTest(() => new Test) { + top: Test => + Select.wires(top).map(_.instanceName) should be (List("x1", "x1_wire", "x2", "x2_wire")) + } + } + + property("Automatic prefixing should work") { + + class Test extends MultiIOModule { + def builder(): UInt = { + val a = Wire(UInt(3.W)) + val b = Wire(UInt(3.W)) + b + } + + { + val ADAM = builder() + val JACOB = builder() + } + } + aspectTest(() => new Test) { + top: Test => + Select.wires(top).map(_.instanceName) should be (List("ADAM_a", "ADAM", "JACOB_a", "JACOB")) + } + } + + property("No prefixing annotation on defs should work") { + + class Test extends MultiIOModule { + def builder(): UInt = noPrefix { + val a = Wire(UInt(3.W)) + val b = Wire(UInt(3.W)) + b + } + + { val noprefix = builder() } + } + aspectTest(() => new Test) { + top: Test => + Select.wires(top).map(_.instanceName) should be (List("a", "noprefix")) + } + } + + property("Prefixing on temps should work") { + + class Test extends MultiIOModule { + def builder(): UInt = { + val a = Wire(UInt(3.W)) + val b = Wire(UInt(3.W)) + a +& (b * a) + } + + { val blah = builder() } + } + aspectTest(() => new Test) { + top: Test => + Select.ops(top).map(x => (x._1, x._2.instanceName)) should be (List( + ("mul", "_blah_T"), + ("add", "blah") + )) + } + } + + property("Prefixing should not leak into child modules") { + class Child extends MultiIOModule { + { + val wire = Wire(UInt()) + } + } + + class Test extends MultiIOModule { + { + val child = prefix("InTest") { + Module(new Child) + } + } + } + aspectTest(() => new Test) { + top: Test => + Select.wires(Select.instances(top).head).map(_.instanceName) should be (List("wire")) + } + } + + property("Prefixing should not leak into child modules, example 2") { + class Child extends MultiIOModule { + { + val wire = Wire(UInt()) + } + } + + class Test extends MultiIOModule { + val x = IO(Input(UInt(3.W))) + val y = { + lazy val module = new Child + val child = Module(module) + } + } + aspectTest(() => new Test) { + top: Test => + Select.wires(Select.instances(top).head).map(_.instanceName) should be (List("wire")) + } + } + + property("Instance names should not be added to prefix") { + class Child(tpe: UInt) extends MultiIOModule { + { + val io = IO(Input(tpe)) + } + } + + class Test extends MultiIOModule { + { + lazy val module = { + val x = UInt(3.W) + new Child(x) + } + val child = Module(module) + } + } + aspectTest(() => new Test) { + top: Test => + Select.ios(Select.instances(top).head).map(_.instanceName) should be (List("clock", "reset", "io")) + } + } + + + property("Prefixing should not be caused by nested Iterable[Iterable[Any]]") { + class Test extends MultiIOModule { + { + val iia = { + val wire = Wire(UInt(3.W)) + List(List("Blah")) + } + } + } + aspectTest(() => new Test) { + top: Test => + Select.wires(top).map(_.instanceName) should be (List("wire")) + } + } + + property("Prefixing should be caused by nested Iterable[Iterable[Data]]") { + class Test extends MultiIOModule { + { + val iia = { + val wire = Wire(UInt(3.W)) + List(List(3.U)) + } + } + } + aspectTest(() => new Test) { + top: Test => + Select.wires(top).map(_.instanceName) should be (List("iia_wire")) + } + } + + property("Prefixing should be the prefix during the last call to autoName/suggestName") { + class Test extends MultiIOModule { + { + val wire = { + val x = Wire(UInt(3.W)).suggestName("mywire") + x + } + } + } + aspectTest(() => new Test) { + top: Test => + Select.wires(top).map(_.instanceName) should be (List("mywire")) + Select.wires(top).map(_.instanceName) shouldNot be (List("wire_mywire")) + } + } + + property("Prefixing have intuitive behavior") { + class Test extends MultiIOModule { + { + val wire = { + val x = Wire(UInt(3.W)).suggestName("mywire") + val y = Wire(UInt(3.W)).suggestName("mywire2") + y := x + y + } + } + } + aspectTest(() => new Test) { + top: Test => Select.wires(top).map(_.instanceName) should be (List("wire_mywire", "mywire2")) + } + } + + property("Prefixing on connection to subfields work") { + class Test extends MultiIOModule { + { + val wire = Wire(new Bundle { + val x = UInt(3.W) + val y = UInt(3.W) + val vec = Vec(4, UInt(3.W)) + }) + wire.x := RegNext(3.U) + wire.y := RegNext(3.U) + wire.vec(0) := RegNext(3.U) + wire.vec(wire.x) := RegNext(3.U) + } + } + aspectTest(() => new Test) { + top: Test => + Select.registers(top).map(_.instanceName) should be (List( + "wire_x_REG", + "wire_y_REG", + "wire_vec_0_REG", + "wire_vec_REG" + )) + } + } + + property("Prefixing on connection to IOs should work") { + class Child extends MultiIOModule { + val in = IO(Input(UInt(3.W))) + val out = IO(Output(UInt(3.W))) + out := RegNext(in) + } + class Test extends MultiIOModule { + { + val child = Module(new Child) + child.in := RegNext(3.U) + } + } + aspectTest(() => new Test) { + top: Test => + Select.registers(top).map(_.instanceName) should be (List( + "child_in_REG" + )) + Select.registers(Select.instances(top).head).map(_.instanceName) should be (List( + "out_REG" + )) + } + } + + property("Prefixing on bulk connects should work") { + class Child extends MultiIOModule { + val in = IO(Input(UInt(3.W))) + val out = IO(Output(UInt(3.W))) + out := RegNext(in) + } + class Test extends MultiIOModule { + { + val child = Module(new Child) + child.in <> RegNext(3.U) + } + } + aspectTest(() => new Test) { + top: Test => + Select.registers(top).map(_.instanceName) should be (List( + "child_in_REG" + )) + Select.registers(Select.instances(top).head).map(_.instanceName) should be (List( + "out_REG" + )) + } + } +} |
