From 0e191719107b2d9ee9a98170a6c3419784152307 Mon Sep 17 00:00:00 2001 From: Michael Maloney Date: Fri, 8 Nov 2024 17:27:38 -0700 Subject: [PATCH] Add withModulePrefix (#4487) withModulePrefix can be used to create "prefixing domains" which will prefix all modules instantiated within them. These prefixes apply recursively to children of prefixed modules. Applying multiple prefixes will nest them. Prefixes are separated from the name of the module (and each other) by `_`. Co-authored-by: Jack Koenig Co-authored-by: Megan Wachs --- .../experimental/hierarchy/Instantiate.scala | 6 +- core/src/main/scala/chisel3/MemImpl.scala | 2 + core/src/main/scala/chisel3/ModuleImpl.scala | 64 +++- .../hierarchy/InstantiateImpl.scala | 5 +- .../main/scala/chisel3/internal/Builder.scala | 34 +++ docs/src/explanations.md | 1 + docs/src/explanations/moduleprefix.md | 134 +++++++++ src/main/scala/chisel3/util/SRAM.scala | 1 + src/test/scala/chiselTests/PrefixSpec.scala | 278 ++++++++++++++++++ 9 files changed, 516 insertions(+), 9 deletions(-) create mode 100644 docs/src/explanations/moduleprefix.md create mode 100644 src/test/scala/chiselTests/PrefixSpec.scala diff --git a/core/src/main/scala-2/chisel3/experimental/hierarchy/Instantiate.scala b/core/src/main/scala-2/chisel3/experimental/hierarchy/Instantiate.scala index f4af49d9a6e..eee97db6462 100644 --- a/core/src/main/scala-2/chisel3/experimental/hierarchy/Instantiate.scala +++ b/core/src/main/scala-2/chisel3/experimental/hierarchy/Instantiate.scala @@ -57,7 +57,7 @@ object Instantiate extends InstantiateImpl { ): Instance[A] = _instanceImpl(args, f) /** This is not part of the public API, do not call directly! */ - def _defination[K, A <: BaseModule: ru.WeakTypeTag]( + def _definition[K, A <: BaseModule: ru.WeakTypeTag]( args: K, f: K => A ): Definition[A] = _definitionImpl(args, f) @@ -160,11 +160,11 @@ object Instantiate extends InstantiateImpl { val funcArgTypes = args.map(_.asInstanceOf[Tree].tpe.widen) val constructor = q"(($funcArg: (..$funcArgTypes)) => new $tpname[..$tparams](...$conArgs))" val tup = q"(..$args)" - q"chisel3.experimental.hierarchy.Instantiate._defination[(..$funcArgTypes), $tpname]($tup, $constructor)" + q"chisel3.experimental.hierarchy.Instantiate._definition[(..$funcArgTypes), $tpname]($tup, $constructor)" case _ => val msg = - s"Argument to Instantiate.defination(...) must be of form 'new ()'.\n" + + s"Argument to Instantiate.definition(...) must be of form 'new ()'.\n" + "Note that named arguments are currently not supported.\n" + s"Got: '$con'" c.error(con.pos, msg) diff --git a/core/src/main/scala/chisel3/MemImpl.scala b/core/src/main/scala/chisel3/MemImpl.scala index de429fc26ad..5dcb5a3f8a8 100644 --- a/core/src/main/scala/chisel3/MemImpl.scala +++ b/core/src/main/scala/chisel3/MemImpl.scala @@ -23,6 +23,7 @@ private[chisel3] trait ObjectMemImpl { val mem = new Mem(mt, size, sourceInfo) mt.bind(MemTypeBinding(mem)) pushCommand(DefMemory(sourceInfo, mem, mt, size)) + ModulePrefixAnnotation.annotate(mem) mem } @@ -207,6 +208,7 @@ private[chisel3] trait ObjectSyncReadMemImpl { val mem = new SyncReadMem(mt, size, ruw, sourceInfo) mt.bind(MemTypeBinding(mem)) pushCommand(DefSeqMemory(sourceInfo, mem, mt, size, ruw)) + ModulePrefixAnnotation.annotate(mem) mem } diff --git a/core/src/main/scala/chisel3/ModuleImpl.scala b/core/src/main/scala/chisel3/ModuleImpl.scala index f75908d88c7..2eb81f58173 100644 --- a/core/src/main/scala/chisel3/ModuleImpl.scala +++ b/core/src/main/scala/chisel3/ModuleImpl.scala @@ -12,17 +12,27 @@ import chisel3.internal.firrtl.ir._ import chisel3.experimental.{requireIsChiselType, BaseModule, SourceInfo, UnlocatableSourceInfo} import chisel3.properties.{Class, Property} import chisel3.reflect.DataMirror -import _root_.firrtl.annotations.{InstanceTarget, IsModule, ModuleName, ModuleTarget} +import _root_.firrtl.annotations.{ + Annotation, + InstanceTarget, + IsMember, + IsModule, + ModuleName, + ModuleTarget, + SingleTargetAnnotation, + Target +} import _root_.firrtl.AnnotationSeq import chisel3.internal.plugin.autoNameRecursively import chisel3.util.simpleClassName +import chisel3.experimental.{annotate, ChiselAnnotation} import chisel3.experimental.hierarchy.Hierarchy private[chisel3] trait ObjectModuleImpl { protected def _applyImpl[T <: BaseModule](bc: => T)(implicit sourceInfo: SourceInfo): T = { // Instantiate the module definition. - val module = evaluate[T](bc) + val module: T = evaluate[T](bc) // Handle connections at enclosing scope // We use _component because Modules that don't generate them may still have one @@ -165,6 +175,9 @@ private[chisel3] trait ObjectModuleImpl { /** Returns the current Module */ def currentModule: Option[BaseModule] = Builder.currentModule + /** Returns the current nested module prefix */ + def currentModulePrefix: String = Builder.getModulePrefix + private[chisel3] def do_pseudo_apply[T <: BaseModule]( bc: => T )( @@ -225,6 +238,10 @@ private[chisel3] trait ObjectModuleImpl { /** Explicitly Asynchronous Reset */ case object Asynchronous extends Type } + + def getModulePrefixList: List[String] = { + Builder.getModulePrefixList + } } private[chisel3] trait ModuleImpl extends RawModule with ImplicitClock with ImplicitReset { @@ -677,8 +694,9 @@ package experimental { // PseudoModules are not "true modules" and thus should share // their original modules names without uniquification this match { - case _: PseudoModule => desiredName - case _ => Builder.globalNamespace.name(desiredName) + case _: PseudoModule => Module.currentModulePrefix + desiredName + case _: BaseBlackBox => Builder.globalNamespace.name(desiredName) + case _ => Builder.globalNamespace.name(Module.currentModulePrefix + desiredName) } } catch { case e: NullPointerException => @@ -915,5 +933,43 @@ package experimental { case Some(c) => getRef.fullName(c) } + /** Returns the current nested module prefix */ + val modulePrefix: String = Builder.getModulePrefix + } +} + +/** + * Creates a block under which any generator that gets run results in a module whose name is prepended with the given prefix. + */ +object withModulePrefix { + + /** + * @param arg prefix Prefix is the module prefix, blank means ignore. + */ + def apply[T](prefix: String)(block: => T): T = { + if (prefix != "") { + Builder.pushModulePrefix(prefix) + } + val res = block // execute block + if (prefix != "") { + Builder.popModulePrefix() + } + res + } +} + +private case class ModulePrefixAnnotation(target: IsMember, prefix: String) extends SingleTargetAnnotation[IsMember] { + def duplicate(n: IsMember): ModulePrefixAnnotation = this.copy(target = n) +} + +private object ModulePrefixAnnotation { + def annotate[T <: HasId](target: T): Unit = { + val prefix = Builder.getModulePrefix + if (prefix != "") { + val annotation: ChiselAnnotation = new ChiselAnnotation { + def toFirrtl: Annotation = ModulePrefixAnnotation(target.toTarget, prefix) + } + chisel3.experimental.annotate(annotation) + } } } diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/InstantiateImpl.scala b/core/src/main/scala/chisel3/experimental/hierarchy/InstantiateImpl.scala index 653074a9516..39938879ec4 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/InstantiateImpl.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/InstantiateImpl.scala @@ -73,7 +73,7 @@ private[chisel3] trait InstantiateImpl { import chisel3.internal.BuilderContextCache // Include type of module in key since different modules could have the same arguments - private case class CacheKey[A <: BaseModule](args: Any, tt: ru.WeakTypeTag[A]) + private case class CacheKey[A <: BaseModule](args: Any, tt: ru.WeakTypeTag[A], modulePrefix: List[String]) extends BuilderContextCache.Key[Definition[A]] protected def _instanceImpl[K, A <: BaseModule: ru.WeakTypeTag]( @@ -88,9 +88,10 @@ private[chisel3] trait InstantiateImpl { args: K, f: K => A ): Definition[A] = { + val modulePrefix = Builder.getModulePrefixList Builder.contextCache .getOrElseUpdate( - CacheKey(boxAllData(args), implicitly[ru.WeakTypeTag[A]]), { + CacheKey(boxAllData(args), implicitly[ru.WeakTypeTag[A]], modulePrefix), { // The definition needs to have no source locator because otherwise it will be unstably // derived from the first invocation of Instantiate for the particular Module Definition.do_apply(f(args))(UnlocatableSourceInfo) diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index 86a1b60a988..e027fa0be90 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -461,6 +461,7 @@ private[chisel3] trait NamedComponent extends HasId { // Mutable global state for chisel that can appear outside a Builder context private[chisel3] class ChiselContext() { + val modulePrefixSeperator: String = "_" val idGen = new IdGen // Records the different prefixes which have been scoped at this point in time @@ -470,6 +471,8 @@ private[chisel3] class ChiselContext() { // The namespace outside of Builder context is useless, but it ensures that views can still be created // and the resulting .toTarget is very clearly useless (_$$View$$_...) val viewNamespace = Namespace.empty + + var modulePrefixStack: Prefix = Nil } private[chisel3] class DynamicContext( @@ -1163,6 +1166,37 @@ private[chisel3] object Builder extends LazyLogging { ) } } + + // Puts a module prefix string onto the module prefix stack + def pushModulePrefix(prefix: String): Unit = { + val context = chiselContext.get() + context.modulePrefixStack = prefix :: context.modulePrefixStack + } + + // Remove the module prefix on top of the stack + def popModulePrefix(): List[String] = { + val context = chiselContext.get() + val tail = context.modulePrefixStack.tail + context.modulePrefixStack = tail + tail + } + + // Returns the nested module prefix at this moment + def getModulePrefix: String = { + val ctx = chiselContext.get() + val modulePrefixStack = ctx.modulePrefixStack + val sep = ctx.modulePrefixSeperator + if (modulePrefixStack.isEmpty) { + "" + } else { + modulePrefixStack.foldLeft("")((a, b) => b + sep + a) + } + } + + def getModulePrefixList: List[String] = { + chiselContext.get().modulePrefixStack + } + initializeSingletons() } diff --git a/docs/src/explanations.md b/docs/src/explanations.md index 637e5bd1d7b..ecb7086f117 100644 --- a/docs/src/explanations.md +++ b/docs/src/explanations.md @@ -41,3 +41,4 @@ read these documents in the following order: * [Deep Dive into Legacy Connection Operators](explanations/connection-operators) * [Properties](explanations/properties) * [Layers](explanations/layers) +* [Module Prefixing](explanations/moduleprefix) diff --git a/docs/src/explanations/moduleprefix.md b/docs/src/explanations/moduleprefix.md new file mode 100644 index 00000000000..f2634c028e4 --- /dev/null +++ b/docs/src/explanations/moduleprefix.md @@ -0,0 +1,134 @@ +# Module Prefixing + +Chisel supports a feature called module prefixing. +Module prefixing allows you to create namespaces in the Verilog output of your design. +They are especially useful for when you want to name a particular subsystem of your design, +and you want to make it easy to identify which subsystem a file belongs to by its name. + +We can open a module prefix block using `withModulePrefix`: + +```scala mdoc:silent +import chisel3._ + +class Top extends Module { + withModulePrefix("Foo") { + // ... + } +} +``` + +All modules defined inside of this block, whether an immediate submodule or a descendent, will be given a prefix `Foo`. +(The prefix is separated by an underscore `_`). + +For example, suppose we write the following: + +```scala mdoc:silent:reset +import chisel3._ + +class Top extends Module { + val sub = withModulePrefix("Foo") { + Module(new Sub) + } +} + +class Sub extends Module { + // .. +} +``` + +The result will be a design with two module definitions: `Top` and `Foo_Sub`. + +Note that the `val sub =` part must be pulled outside of the `withModulePrefix` block, +or else the module will not be accessible to the rest of the `Top` module. + +If a generator is run in multiple prefix blocks, the result is multiple identical copies of the module definition, +each with its own distinct prefix. +For example, consider if we create two instances of `Sub` above like this: + +```scala mdoc:silent:reset +import chisel3._ + +class Top extends Module { + val foo_sub = withModulePrefix("Foo") { + Module(new Sub) + } + + val bar_sub = withModulePrefix("Bar") { + Module(new Sub) + } +} + +class Sub extends Module { + // .. +} +``` + +Then, the resulting Verilog will have three module definitions: `Top`, `Foo_Sub`, and `Bar_Sub`. +Both `Foo_Sub` and `Bar_Sub` will be identical to each other. + +Module prefixes can also be nested. + +```scala mdoc:silent:reset +import chisel3._ + +class Top extends Module { + val mid = withModulePrefix("Foo") { + Module(new Mid) + } +} + +class Mid extends Module { + val sub = withModulePrefix("Bar") { + Module(new Sub) + } +} + +class Sub extends Module { + // .. +} +``` + +This results in three module definitions: `Top`, `Foo_Mid`, and `Foo_Bar_Sub`. + +The `withModulePrefix` blocks also work with the `Instantiate` API. + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.experimental.hierarchy.{instantiable, Instantiate} + +@instantiable +class Sub extends Module { + // ... +} + +class Top extends Module { + val foo_sub = withModulePrefix("Foo") { + Instantiate(new Sub) + } + + val bar_sub = withModulePrefix("Bar") { + Instantiate(new Sub) + } + + val noprefix_sub = Instantiate(new Sub) +} +``` + +In this example, we end up with four modules: `Top`, `Foo_Sub`, `Bar_Sub`, and `Sub`. + +When using `Definition` and `Instance`, all `Definition` calls will be affected by `withModulePrefix`. +However, `Instance` will not be effected, since it always creates an instance of the captured definition. + +`BlackBox` and `ExtModule` are unaffected by `withModulePrefix`. +If you wish to have one that is sensitive to the module prefix, +you can explicitly name the module like this: + +```scala mdoc:silent:reset +import chisel3._ +import chisel3.experimental.hierarchy.{instantiable, Instantiate} +import chisel3.experimental.ExtModule + +class Sub extends ExtModule { + override def desiredName = modulePrefix + "Sub" +} +``` diff --git a/src/main/scala/chisel3/util/SRAM.scala b/src/main/scala/chisel3/util/SRAM.scala index 753c40f6e3f..b9e216c1125 100644 --- a/src/main/scala/chisel3/util/SRAM.scala +++ b/src/main/scala/chisel3/util/SRAM.scala @@ -639,6 +639,7 @@ object SRAM { descriptionInstance.hierarchyIn := Property(Path(mem)) description := descriptionInstance.getPropertyReference } + ModulePrefixAnnotation.annotate(mem) _out } diff --git a/src/test/scala/chiselTests/PrefixSpec.scala b/src/test/scala/chiselTests/PrefixSpec.scala new file mode 100644 index 00000000000..79091c9c3fe --- /dev/null +++ b/src/test/scala/chiselTests/PrefixSpec.scala @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests + +import chisel3._ +import chisel3.experimental.ExtModule +import chisel3.experimental.hierarchy.{instantiable, public, Definition, Instance, Instantiate} +import circt.stage.ChiselStage.emitCHIRRTL +import circt.stage.ChiselStage +import chisel3.util.SRAM + +object PrefixSpec { + // This has to be defined at the top-level because @instantiable doesn't work when nested. + @instantiable + class AddOne(width: Int) extends Module { + @public val in = IO(Input(UInt(width.W))) + @public val out = IO(Output(UInt(width.W))) + out := in + 1.U + } +} + +class PrefixSpec extends ChiselFlatSpec with ChiselRunners with Utils with MatchesAndOmits { + import PrefixSpec._ + behavior.of("withModulePrefix") + + it should "prefix modules in a withModulePrefix block, but not outside" in { + class Foo extends RawModule { + val a = Wire(Bool()) + } + + class Top extends RawModule { + val foo = Module(new Foo) + + val pref_foo = withModulePrefix("Pref") { Module(new Foo) } + } + + val chirrtl = emitCHIRRTL(new Top) + + val lines = """ + module Foo : + wire a : UInt<1> + module Pref_Foo : + wire a : UInt<1> + module Top : + inst foo of Foo + inst pref_foo of Pref_Foo + """.linesIterator.map(_.trim).toSeq + matchesAndOmits(chirrtl)(lines: _*)() + } + + it should "Allow nested module prefixes" in { + class Bar extends RawModule { + val a = Wire(Bool()) + } + + class Foo extends RawModule { + withModulePrefix("Inner") { + val bar = Module(new Bar) + } + } + + class Top extends RawModule { + withModulePrefix("Outer") { + val foo = Module(new Foo) + } + } + + val chirrtl = emitCHIRRTL(new Top) + + val lines = + """ + module Outer_Inner_Bar : + wire a : UInt<1> + module Outer_Foo + inst bar of Outer_Inner_Bar + module Top : + inst foo of Outer_Foo + """.linesIterator.map(_.trim).toSeq + + matchesAndOmits(chirrtl)(lines: _*)() + } + + it should "Instantiate should create distinct module definitions when instantiated with distinct prefixes" in { + class Top extends Module { + val width = 8 + val in = IO(Input(UInt(width.W))) + val out = IO(Output(UInt(width.W))) + + val foo_inst = withModulePrefix("Foo") { + Instantiate(new AddOne(width)) + } + + val bar_inst = withModulePrefix("Bar") { + Instantiate(new AddOne(width)) + } + + // np: no prefix + val np_inst = Instantiate(new AddOne(width)) + + foo_inst.in := in + bar_inst.in := foo_inst.out + out := bar_inst.out + np_inst.in := in + } + + val chirrtl = emitCHIRRTL(new Top) + + val lines = """ + module Foo_AddOne : + module Bar_AddOne : + module AddOne : + public module Top : + inst np_inst of AddOne + inst foo_inst of Foo_AddOne + inst bar_inst of Bar_AddOne + """.linesIterator.map(_.trim).toSeq + + matchesAndOmits(chirrtl)(lines: _*)("AddOne_1") + } + + it should "Instantiate should reference the same module definitions when instantiated with the same prefix" in { + class Top extends Module { + val width = 8 + val in = IO(Input(UInt(width.W))) + val out = IO(Output(UInt(width.W))) + val foo_inst1 = withModulePrefix("Foo") { + Instantiate(new AddOne(width)) + } + + val foo_inst2 = withModulePrefix("Foo") { + Instantiate(new AddOne(width)) + } + + foo_inst1.in := in + foo_inst2.in := in + out := foo_inst1.out + } + + val chirrtl = emitCHIRRTL(new Top) + + val lines = """ + module Foo_AddOne : + public module Top : + inst foo_inst1 of Foo_AddOne + inst foo_inst2 of Foo_AddOne + """.linesIterator.map(_.trim).toSeq + + matchesAndOmits(chirrtl)(lines: _*)("AddOne_1", "Bar_AddOne") + } + + it should "Memories work" in { + class Top extends Module { + val io = IO(new Bundle { + val enable = Input(Bool()) + val write = Input(Bool()) + val addr = Input(UInt(10.W)) + val dataIn = Input(UInt(8.W)) + val dataOut = Output(UInt(8.W)) + }) + + val smem = withModulePrefix("Foo") { + SyncReadMem(1024, UInt(8.W)) + } + + val cmem = withModulePrefix("Bar") { + Mem(1024, UInt(8.W)) + } + + val sram = withModulePrefix("Baz") { + SRAM(1024, UInt(8.W), 1, 1, 0) + } + + smem.write(io.addr, io.dataIn) + io.dataOut := smem.read(io.addr, io.enable) + } + + val chirrtl = emitCHIRRTL(new Top) + + val lines = """ + { + "class":"chisel3.ModulePrefixAnnotation", + "target":"~Top|Top>smem", + "prefix":"Foo_" + }, + { + "class":"chisel3.ModulePrefixAnnotation", + "target":"~Top|Top>cmem", + "prefix":"Bar_" + }, + { + "class":"chisel3.ModulePrefixAnnotation", + "target":"~Top|Top>sram_sram", + "prefix":"Baz_" + } + """.linesIterator.map(_.trim).toSeq + + matchesAndOmits(chirrtl)(lines: _*)() + } + + it should "Definitions that appear within withModulePrefix get prefixed" in { + class Top extends Module { + val dfn = withModulePrefix("Foo") { + Definition(new AddOne(8)) + } + + val addone = Instance(dfn) + } + + val chirrtl = emitCHIRRTL(new Top) + + val lines = """ + module Foo_AddOne + module Top + """.linesIterator.map(_.trim).toSeq + + matchesAndOmits(chirrtl)(lines: _*)() + } + + it should "allow definitions to be instantiated within a withModulePrefix block without prefixing it" in { + class Child(defn: Definition[AddOne]) extends Module { + val addone = Instance(defn) + } + + class Top extends Module { + val defn = Definition(new AddOne(8)) + + val child = withModulePrefix("Foo") { + Module(new Child(defn)) + } + } + + val chirrtl = emitCHIRRTL(new Top) + + val lines = """ + module AddOne + module Foo_Child + public module Top + """.linesIterator.map(_.trim).toSeq + } + + it should "withModulePrefix does not automatically affect ExtModules" in { + class Sub extends ExtModule {} + + class Top extends Module { + val sub_foo = withModulePrefix("Foo") { Module(new Sub) } + } + + val chirrtl = emitCHIRRTL(new Top) + + val lines = """ + extmodule Sub + defname = Sub + module Top + """.linesIterator.map(_.trim).toSeq + + matchesAndOmits(chirrtl)(lines: _*)() + } + + it should "Using modulePrefix to force the name of an extmodule" in { + class Sub extends ExtModule { + override def desiredName = modulePrefix + "Sub" + } + + class Top extends Module { + val sub_foo = withModulePrefix("Foo") { Module(new Sub) } + } + + val chirrtl = emitCHIRRTL(new Top) + + val lines = """ + extmodule Foo_Sub + defname = Foo_Sub + module Top + """.linesIterator.map(_.trim).toSeq + + matchesAndOmits(chirrtl)(lines: _*)() + } +}