Skip to content

Commit

Permalink
Add withModulePrefix (#4487)
Browse files Browse the repository at this point in the history
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 <[email protected]>
Co-authored-by: Megan Wachs <[email protected]>
  • Loading branch information
3 people authored Nov 9, 2024
1 parent 57c95ce commit 0e19171
Show file tree
Hide file tree
Showing 9 changed files with 516 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 <T <: chisel3.Module>(<arguments...>)'.\n" +
s"Argument to Instantiate.definition(...) must be of form 'new <T <: chisel3.Module>(<arguments...>)'.\n" +
"Note that named arguments are currently not supported.\n" +
s"Got: '$con'"
c.error(con.pos, msg)
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala/chisel3/MemImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}

Expand Down
64 changes: 60 additions & 4 deletions core/src/main/scala/chisel3/ModuleImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
)(
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 =>
Expand Down Expand Up @@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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](
Expand All @@ -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)
Expand Down
34 changes: 34 additions & 0 deletions core/src/main/scala/chisel3/internal/Builder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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()
}

Expand Down
1 change: 1 addition & 0 deletions docs/src/explanations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
134 changes: 134 additions & 0 deletions docs/src/explanations/moduleprefix.md
Original file line number Diff line number Diff line change
@@ -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"
}
```
1 change: 1 addition & 0 deletions src/main/scala/chisel3/util/SRAM.scala
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ object SRAM {
descriptionInstance.hierarchyIn := Property(Path(mem))
description := descriptionInstance.getPropertyReference
}
ModulePrefixAnnotation.annotate(mem)
_out
}

Expand Down
Loading

0 comments on commit 0e19171

Please sign in to comment.