Skip to content

Commit

Permalink
Add Public trait to create public FIRRTL modules (#3813)
Browse files Browse the repository at this point in the history
Add a new trait, `Public`, which can be used to emit FIRRTL modules which
have the public keyword.  This is a weak form of "public modules" that we
want to support for FIRRTL 4.0.0.  This does not allow for multiple,
disjoint instance graphs to be contained in a circuit, nor does it remove
the "main module" from the circuit.  However, it provides a mechanism for
plucking modules out of a circuit later and working with them.

This is self-typed on `RawModule` to prevent mix-in to an external module.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@sifive.com>
Co-authored-by: Jack Koenig <koenig@sifive.com>
  • Loading branch information
seldridge and jackkoenig authored Feb 8, 2024
1 parent cba3158 commit 1f05995
Show file tree
Hide file tree
Showing 13 changed files with 98 additions and 8 deletions.
19 changes: 19 additions & 0 deletions core/src/main/scala/chisel3/Public.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0

package chisel3

/** A trait that can be mixed into a Chisel module to indicate that a module has external users.
*
* This will result in a public FIRRTL module being produced.
*/
trait Public { this: RawModule =>

override private[chisel3] def _isPublic = isPublic

/** Is this module public?
*
* Users can override this if they need more control over when outputs of this Module should
* be considered public
*/
def isPublic: Boolean = true
}
6 changes: 5 additions & 1 deletion core/src/main/scala/chisel3/RawModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ abstract class RawModule extends BaseModule {
}
}

/** Private variable that tracks if a module is public. */
private[chisel3] def _isPublic: Boolean = false

/** Finalize name for an id created during this RawModule's constructor.
*
* @param id The id to finalize.
Expand Down Expand Up @@ -192,7 +195,8 @@ 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, Builder.enabledLayers.toSeq, firrtlPorts, _commands.result())
val component =
DefModule(this, name, _isPublic, Builder.enabledLayers.toSeq, firrtlPorts, _commands.result())

// Secret connections can be staged if user bored into children modules
component.secretCommands ++= stagedSecretCommands
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/scala/chisel3/internal/Builder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,19 @@ private[chisel3] object Builder extends LazyLogging {
)
}

// Make the main module (the last component) public.
components.last match {
case module: DefModule =>
components.update(
components.size - 1, {
val newModule = module.copy(isPublic = true)
newModule.secretCommands ++= module.secretCommands
newModule
}
)
case _ =>
}

(
Circuit(
components.last.name,
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, layers, ports, cmds) =>
case ctx @ DefModule(id, name, public, layers, ports, cmds) =>
fir.Module(
convert(id._getSourceLocator),
name,
public,
layers.map(_.fullName),
(ports ++ ctx.secretPorts).map(p => convert(p, typeAliases)),
convert(cmds ++ ctx.secretCommands, ctx, typeAliases)
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/chisel3/internal/firrtl/IR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ private[chisel3] object ir {
case class DefModule(
id: RawModule,
name: String,
isPublic: Boolean,
layers: Seq[chisel3.layer.Layer],
ports: Seq[Port],
commands: Seq[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, Nil)
private[chisel3] val fakeComponent: Component = DefModule(this, desiredName, false, Nil, Nil, Nil)
}

/** Special internal object representing the parent of all views
Expand Down
2 changes: 1 addition & 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,7 @@ abstract class DefModule extends FirrtlNode with IsDeclaration {
*
* An instantiable hardware block
*/
case class Module(info: Info, name: String, layers: Seq[String], ports: Seq[Port], body: Statement)
case class Module(info: Info, name: String, public: Boolean, layers: Seq[String], ports: Seq[Port], body: Statement)
extends DefModule
with UseSerializer

Expand Down
7 changes: 5 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,13 @@ object Serializer {
}

private def sIt(node: DefModule)(implicit indent: Int): Iterator[String] = node match {
case Module(info, name, layers, ports, body) =>
case Module(info, name, public, layers, ports, body) =>
val start = {
implicit val b = new StringBuilder
doIndent(0); b ++= "module "; b ++= legalize(name);
doIndent(0);
if (public)
b ++= "public "
b ++= "module "; b ++= legalize(name);
layers.foreach(l => b ++= s" enablelayer $l")
b ++= " :"; s(info)
ports.foreach { p => newLineAndIndent(1); s(p) }
Expand Down
4 changes: 3 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",
false,
Seq.empty,
Seq(Port(NoInfo, "in", Input, UIntType(IntWidth(8))), Port(NoInfo, "out", Output, UIntType(IntWidth(8)))),
Block(
Expand Down Expand Up @@ -100,6 +101,7 @@ object SMemTestCircuit {
Module(
NoInfo,
"Example",
false,
Seq.empty,
Seq.empty,
Block(
Expand Down Expand Up @@ -290,7 +292,7 @@ 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, Seq.empty, Block(Seq.empty))) should include(
Serializer.serialize(Module(NoInfo, "42_module", false, Seq.empty, Seq.empty, Block(Seq.empty))) should include(
"module `42_module`"
)
// TODO: an external module with a numeric defname should probably be rejected
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class FirrtlOptionsViewSpec extends AnyFlatSpec with Matchers {
ir.Module(
ir.NoInfo,
main,
false,
Seq.empty,
Seq.empty,
ir.Block(
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, Nil, m.ports, ir.Block(addStmtMap(m.name)))
ir.Module(m.info, m.name, false, Nil, m.ports, ir.Block(addStmtMap(m.name)))
case other: ir.DefModule => other
}
}
Expand Down
45 changes: 45 additions & 0 deletions src/test/scala/chiselTests/PublicModuleSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: Apache-2.0

package chiselTests

import chisel3._
import circt.stage.ChiselStage

class PublicModuleSpec extends ChiselFlatSpec with MatchesAndOmits {

class Qux extends RawModule

class Baz extends RawModule with Public {
val qux = Module(new Qux)
override def isPublic = false
}

class Bar extends RawModule with Public {
val baz = Module(new Baz)
}

class Foo extends RawModule {
val bar = Module(new Bar)
}

val chirrtl = ChiselStage.emitCHIRRTL(new Foo)

"the main module" should "be implicitly public" in {
chirrtl should include("public module Foo")
}

"non-main modules" should "be implicitly private" in {
matchesAndOmits(chirrtl)("module Qux")("public module Qux")
}

behavior.of("the Public trait")

it should "cause a module that mixes it in to be public" in {
chirrtl should include("public module Bar")
}

it should "allow making a module that mixes it in private via an override" in {
matchesAndOmits(chirrtl)("module Baz")("public module Baz")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class AddImplicitOutputFileSpec extends AnyFlatSpec with Matchers {
ir.Module(
ir.NoInfo,
"foo",
false,
Seq.empty,
Seq.empty,
ir.Block(
Expand Down

0 comments on commit 1f05995

Please sign in to comment.