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 f4af49d9a6..eee97db646 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 de429fc26a..5dcb5a3f8a 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 f75908d88c..2eb81f5817 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 653074a951..39938879ec 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 86a1b60a98..e027fa0be9 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 637e5bd1d7..ecb7086f11 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 0000000000..f2634c028e --- /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 753c40f6e3..b9e216c112 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 0000000000..79091c9c3f --- /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: _*)() + } +}