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 /core | |
| 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>
Diffstat (limited to 'core')
| -rw-r--r-- | core/src/main/scala/chisel3/Data.scala | 20 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/Module.scala | 4 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/RawModule.scala | 23 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/SeqUtils.scala | 10 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/experimental/package.scala | 14 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/Builder.scala | 194 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/Namer.scala | 6 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/firrtl/IR.scala | 4 | ||||
| -rw-r--r-- | core/src/main/scala/chisel3/internal/prefix.scala | 84 |
9 files changed, 324 insertions, 35 deletions
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 + } +} |
