Skip to content

Commit

Permalink
Allow modules to globally enable layers (#3799)
Browse files Browse the repository at this point in the history
Add a function that allows the layer to be "enabled" within the current
module scope.  This has the effect of emitting the module with the
"enablelayer <layer>" FIRRTL format.  This is not currently in the FIRRTL
spec---it is being prototyped inside CIRCT and is expected to land in the
spec soon.

This is done to allow for the use case of a Test Harness needing to enable
one or more layers (more than one layer is the union of several layers)
inside a DUT in order to drive it properly.  The specific use case is to
get access to an expensive core trace inside a microprocessor that is
hidden behind a layer.

This was originally written using an API where the layer provided a trait
that enabled the layer.  This runs afoul of Scala's restriction on
inheriting the same trait multiple times.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@sifive.com>
  • Loading branch information
seldridge committed Jan 31, 2024
1 parent 0a5a6de commit 507f989
Show file tree
Hide file tree
Showing 14 changed files with 61 additions and 10 deletions.
6 changes: 6 additions & 0 deletions core/src/main/scala/chisel3/Layer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,10 @@ object layer {
Builder.layerStack = Builder.layerStack.tail
}

/** Call this function from within a `Module` body to enable this layer globally for that module. */
final def enable(layer: Layer): Unit = layer match {
case Layer.Root =>
case _ => Builder.enabledLayers += layer
}

}
7 changes: 6 additions & 1 deletion core/src/main/scala/chisel3/Module.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
package chisel3

import scala.collection.immutable.ListMap
import scala.collection.mutable.{ArrayBuffer, HashMap}
import scala.collection.mutable.{ArrayBuffer, HashMap, LinkedHashSet}
import scala.language.experimental.macros

import chisel3.internal._
Expand Down Expand Up @@ -80,6 +80,10 @@ object Module extends SourceInfoDoc {
Builder.currentClock = None
Builder.currentReset = None

// Save the currently enabled layer. Clear any enabled layers.
val saveEnabledLayers = Builder.enabledLayers
Builder.enabledLayers = LinkedHashSet.empty

// Execute the module, this has the following side effects:
// - set currentModule
// - unset readyForModuleConstr
Expand Down Expand Up @@ -111,6 +115,7 @@ object Module extends SourceInfoDoc {
Builder.currentClock = saveClock // Back to clock and reset scope
Builder.currentReset = saveReset
Builder.setPrefix(savePrefix)
Builder.enabledLayers = saveEnabledLayers

module
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/chisel3/RawModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ abstract class RawModule extends BaseModule {

// Generate IO invalidation commands to initialize outputs as unused,
// unless the client wants explicit control over their generation.
val component = DefModule(this, name, firrtlPorts, _commands.result())
val component = DefModule(this, name, Builder.enabledLayers.toSeq, firrtlPorts, _commands.result())

// Secret connections can be staged if user bored into children modules
component.secretCommands ++= stagedSecretCommands
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/chisel3/internal/Builder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ private[chisel3] class DynamicContext(
var currentClock: Option[Delayed[Clock]] = None
var currentReset: Option[Delayed[Reset]] = None
var currentDisable: Disable.Type = Disable.BeforeReset
var enabledLayers: mutable.LinkedHashSet[layer.Layer] = mutable.LinkedHashSet.empty
var layerStack: List[layer.Layer] = layer.Layer.root :: Nil
val errors = new ErrorLog(warningFilters, sourceRoots, throwOnFirstError)
val namingStack = new NamingStack
Expand Down Expand Up @@ -831,6 +832,11 @@ private[chisel3] object Builder extends LazyLogging {
dynamicContext.currentDisable = newDisable
}

def enabledLayers: mutable.LinkedHashSet[layer.Layer] = dynamicContext.enabledLayers
def enabledLayers_=(s: mutable.LinkedHashSet[layer.Layer]): Unit = {
dynamicContext.enabledLayers = s
}

def layerStack: List[layer.Layer] = dynamicContext.layerStack
def layerStack_=(s: List[layer.Layer]): Unit = {
dynamicContext.layerStack = s
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/scala/chisel3/internal/firrtl/Converter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -456,10 +456,11 @@ private[chisel3] object Converter {
}

def convert(component: Component, typeAliases: Seq[String]): fir.DefModule = component match {
case ctx @ DefModule(id, name, ports, cmds) =>
case ctx @ DefModule(id, name, layers, ports, cmds) =>
fir.Module(
convert(id._getSourceLocator),
name,
layers.map(_.fullName),
(ports ++ ctx.secretPorts).map(p => convert(p, typeAliases)),
convert(cmds ++ ctx.secretCommands, ctx, typeAliases)
)
Expand Down
8 changes: 7 additions & 1 deletion core/src/main/scala/chisel3/internal/firrtl/IR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,13 @@ private[chisel3] object ir {

case class DefTypeAlias(sourceInfo: SourceInfo, underlying: fir.Type, val name: String)

case class DefModule(id: RawModule, name: String, ports: Seq[Port], commands: Seq[Command]) extends Component {
case class DefModule(
id: RawModule,
name: String,
layers: Seq[chisel3.layer.Layer],
ports: Seq[Port],
commands: Seq[Command])
extends Component {
val secretCommands: mutable.ArrayBuffer[Command] = mutable.ArrayBuffer[Command]()
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/chisel3/internal/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ package object internal {
// Sigil to mark views, starts with '_' to make it a legal FIRRTL target
override def desiredName = "_$$View$$_"

private[chisel3] val fakeComponent: Component = DefModule(this, desiredName, Nil, Nil)
private[chisel3] val fakeComponent: Component = DefModule(this, desiredName, Nil, Nil, Nil)
}

/** Special internal object representing the parent of all views
Expand Down
4 changes: 3 additions & 1 deletion firrtl/src/main/scala/firrtl/ir/IR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,9 @@ abstract class DefModule extends FirrtlNode with IsDeclaration {
*
* An instantiable hardware block
*/
case class Module(info: Info, name: String, ports: Seq[Port], body: Statement) extends DefModule with UseSerializer
case class Module(info: Info, name: String, layers: Seq[String], ports: Seq[Port], body: Statement)
extends DefModule
with UseSerializer

/** External Module
*
Expand Down
6 changes: 4 additions & 2 deletions firrtl/src/main/scala/firrtl/ir/Serializer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,12 @@ object Serializer {
}

private def sIt(node: DefModule)(implicit indent: Int): Iterator[String] = node match {
case Module(info, name, ports, body) =>
case Module(info, name, layers, ports, body) =>
val start = {
implicit val b = new StringBuilder
doIndent(0); b ++= "module "; b ++= legalize(name); b ++= " :"; s(info)
doIndent(0); b ++= "module "; b ++= legalize(name);
layers.foreach(l => b ++= s" enablelayer $l")
b ++= " :"; s(info)
ports.foreach { p => newLineAndIndent(1); s(p) }
newLineNoIndent() // add a blank line between port declaration and body
newLineNoIndent() // newline for body, sIt will indent
Expand Down
6 changes: 5 additions & 1 deletion firrtl/src/test/scala/firrtlTests/SerializerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ object SerializerSpec {
Module(
NoInfo,
"test",
Seq.empty,
Seq(Port(NoInfo, "in", Input, UIntType(IntWidth(8))), Port(NoInfo, "out", Output, UIntType(IntWidth(8)))),
Block(
Seq(
Expand Down Expand Up @@ -100,6 +101,7 @@ object SMemTestCircuit {
NoInfo,
"Example",
Seq.empty,
Seq.empty,
Block(
CDefMemory(
NoInfo,
Expand Down Expand Up @@ -288,7 +290,9 @@ class SerializerSpec extends AnyFlatSpec with Matchers {
Serializer.serialize(Circuit(NoInfo, Seq.empty[DefModule], "42_Circuit")) should include("circuit `42_Circuit`")

info("modules okay!")
Serializer.serialize(Module(NoInfo, "42_module", Seq.empty, Block(Seq.empty))) should include("module `42_module`")
Serializer.serialize(Module(NoInfo, "42_module", Seq.empty, Seq.empty, Block(Seq.empty))) should include(
"module `42_module`"
)
// TODO: an external module with a numeric defname should probably be rejected
Serializer.serialize(ExtModule(NoInfo, "42_extmodule", Seq.empty, "<TODO>", Seq.empty)) should include(
"extmodule `42_extmodule`"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class FirrtlOptionsViewSpec extends AnyFlatSpec with Matchers {
ir.NoInfo,
main,
Seq.empty,
Seq.empty,
ir.Block(
ir.DefNode(
ir.NoInfo,
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/chisel3/aop/injecting/InjectingPhase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class InjectingPhase extends Phase {
m.copy(body = ir.Block(m.body +: addStmtMap(m.name)))
case m: _root_.firrtl.ir.ExtModule if addStmtMap.contains(m.name) =>
logger.debug(s"Injecting to ${m.name} with statement: \n${ir.Block(addStmtMap(m.name)).serialize}")
ir.Module(m.info, m.name, m.ports, ir.Block(addStmtMap(m.name)))
ir.Module(m.info, m.name, Nil, m.ports, ir.Block(addStmtMap(m.name)))
case other: ir.DefModule => other
}
}
Expand Down
17 changes: 17 additions & 0 deletions src/test/scala/chiselTests/LayerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class LayerSpec extends ChiselFlatSpec with Utils with MatchesAndOmits {
object B extends layer.Layer(layer.Convention.Bind)
}

object C extends layer.Layer(layer.Convention.Bind)

"Layers" should "allow for creation of a layer and nested layers" in {

class Foo extends RawModule {
Expand Down Expand Up @@ -66,6 +68,21 @@ class LayerSpec extends ChiselFlatSpec with Utils with MatchesAndOmits {
)()
}

they should "be enabled with a trait" in {

class Foo extends RawModule {
layer.enable(A.B)
layer.enable(C)
// This should be a no-op.
layer.enable(layer.Layer.root)
}

matchesAndOmits(ChiselStage.emitCHIRRTL(new Foo))(
"module Foo enablelayer A.B enablelayer C :"
)()

}

"Layers error checking" should "require that a nested layer definition matches its declaration nesting" in {

class Foo extends RawModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class AddImplicitOutputFileSpec extends AnyFlatSpec with Matchers {
ir.NoInfo,
"foo",
Seq.empty,
Seq.empty,
ir.Block(
ir.DefNode(
ir.NoInfo,
Expand Down

0 comments on commit 507f989

Please sign in to comment.