Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion core/src/main/scala/chisel3/BlackBox.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ abstract class BlackBox(
// Allow access to bindings from the compatibility package
protected def _compatIoPortBound() = _io.exists(portsContains(_))

override def requirements = Seq.empty[String]

private[chisel3] override def generateComponent(): Option[Component] = {
// Restrict IO to just io, clock, and reset
if (!_io.exists(portsContains)) {
Expand Down Expand Up @@ -104,7 +106,15 @@ abstract class BlackBox(
Port(namedPort._2, namedPort._2.specifiedDirection, Seq.empty, ioSourceInfo)
}

val component = DefBlackBox(this, name, firrtlPorts, io.specifiedDirection, params, getKnownLayers)
val component = DefBlackBox(
this,
name,
firrtlPorts,
io.specifiedDirection,
params,
getKnownLayers,
requirements = requirements
)
_component = Some(component)
_component
}
Expand Down
19 changes: 16 additions & 3 deletions core/src/main/scala/chisel3/experimental/ExtModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,13 @@ case class RawParam(value: String) extends Param
*/
@deprecated("this has moved from `chisel3.experimental` to `chisel3`", since)
abstract class ExtModule(
val params: Map[String, Param] = Map.empty[String, Param],
override protected final val knownLayers: Seq[Layer] = Seq.empty[Layer]
val params: Map[String, Param] = Map.empty[String, Param],
override protected final val knownLayers: Seq[Layer] = Seq.empty[Layer],
override protected final val requirements: Seq[String] = Seq.empty[String]
) extends BaseBlackBox {
def this(params: Map[String, Param], knownLayers: Seq[Layer]) =
this(params, knownLayers, Seq.empty[String])

private[chisel3] override def generateComponent(): Option[Component] = {
require(!_closed, "Can't generate module more than once")

Expand All @@ -112,7 +116,16 @@ abstract class ExtModule(
val firrtlPorts = getModulePortsAndLocators.map { case (port, sourceInfo, associations) =>
Port(port, port.specifiedDirection, associations, sourceInfo)
}
val component = DefBlackBox(this, name, firrtlPorts, SpecifiedDirection.Unspecified, params, getKnownLayers)
val component =
DefBlackBox(
this,
name,
firrtlPorts,
SpecifiedDirection.Unspecified,
params,
getKnownLayers,
getRequirements
)
_component = Some(component)
_component
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ object Instance extends SourceInfoDoc {
firrtlPorts,
SpecifiedDirection.Unspecified,
params,
importedDefinition.proto.layers
importedDefinition.proto.layers,
// Imported Definitions necessarily don't have external requirements
Seq.empty
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They don't necessarily but they might right?

Copy link
Contributor Author

@trmckay trmckay Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, they might contain modules with external requirements. But the module body of the imported Definition cannot since it would be generated by Chisel rather than being an extmodule of arbitrary verilog.

You would want to look at the instance graph in the circuit of the imported definition to find external requirements of those modules (none of which would correspond directly to the imported definition itself).

When you did the same for the instance graph in the circuit to which you imported the definition, you would find that it depends on an extmodule for that definition.

Combining the two results, you have everything you need to compile the second circuit.

)
Some(component)
}
Expand Down
25 changes: 25 additions & 0 deletions core/src/main/scala/chisel3/internal/BaseBlackBox.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,29 @@ private[chisel3] abstract class BaseBlackBox extends BaseModule {
}

knownLayers.foreach(addLayer)

/** Build requirements. This will be serialized to the FIRRTL for downstream
* tools.
*/
protected def requirements: Seq[String]

// Internal tracking of _requirements. This can be appended to with
// `addRequirement`.
private val _requirements: mutable.LinkedHashSet[String] = mutable.LinkedHashSet.empty[String]

/** Add a requirement to this module. */
private[chisel3] def addRequirement(requirement: String) = {
_requirements += requirement
}

/** Get the requirements.
*
* @throw IllegalArgumentException if the module is not closed
*/
private[chisel3] def getRequirements: Seq[String] = {
require(isClosed, "Can't get requirements before module is closed")
_requirements.toSeq
}

requirements.foreach(addRequirement)
}
5 changes: 3 additions & 2 deletions core/src/main/scala/chisel3/internal/firrtl/Converter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -480,14 +480,15 @@ private[chisel3] object Converter {
(ports ++ ctx.secretPorts).map(p => convert(p, typeAliases)),
convert(block, ctx, typeAliases)
)
case ctx @ DefBlackBox(id, name, ports, topDir, params, knownLayers) =>
case ctx @ DefBlackBox(id, name, ports, topDir, params, knownLayers, requirements) =>
fir.ExtModule(
convert(id._getSourceLocator),
name,
(ports ++ ctx.secretPorts).map(p => convert(p, typeAliases, topDir)),
id.desiredName,
params.keys.toList.sorted.map { name => convert(name, params(name)) },
knownLayers.map(_.fullName)
knownLayers.map(_.fullName),
requirements
)
case ctx @ DefIntrinsicModule(id, name, ports, topDir, params) =>
fir.IntModule(
Expand Down
13 changes: 7 additions & 6 deletions core/src/main/scala/chisel3/internal/firrtl/IR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -570,12 +570,13 @@ private[chisel3] object ir {
) extends Component

case class DefBlackBox(
id: BaseBlackBox,
name: String,
ports: Seq[Port],
topDir: SpecifiedDirection,
params: Map[String, Param],
knownLayers: Seq[chisel3.layer.Layer]
id: BaseBlackBox,
name: String,
ports: Seq[Port],
topDir: SpecifiedDirection,
params: Map[String, Param],
knownLayers: Seq[chisel3.layer.Layer],
requirements: Seq[String]
) extends Component

object DefTestMarker {
Expand Down
5 changes: 4 additions & 1 deletion core/src/main/scala/chisel3/internal/firrtl/Serializer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -627,12 +627,15 @@ private[chisel3] object Serializer {
}
Iterator(start) ++ serialize(block, ctx, typeAliases)(indent + 1, suppressSourceInfo)

case ctx @ DefBlackBox(id, name, ports, topDir, params, knownLayers) =>
case ctx @ DefBlackBox(id, name, ports, topDir, params, knownLayers, requirements) =>
implicit val b = new StringBuilder
doIndent(0); b ++= "extmodule "; b ++= legalize(name);
if (knownLayers.nonEmpty) {
b ++= knownLayers.map(_.fullName).mkString(" knownlayer ", ", ", "")
}
if (requirements.nonEmpty) {
b ++= requirements.map(r => fir.StringLit(r).escape).mkString(" requires ", ", ", "")
}
b ++= " :"; serialize(id._getSourceLocator)
(ports ++ ctx.secretPorts).foreach { p => newLineAndIndent(1); serialize(p, typeAliases, topDir) }
newLineAndIndent(1); b ++= "defname = "; b ++= id.desiredName
Expand Down
13 changes: 7 additions & 6 deletions firrtl/src/main/scala/firrtl/ir/IR.scala
Original file line number Diff line number Diff line change
Expand Up @@ -787,12 +787,13 @@ case class Module(info: Info, name: String, public: Boolean, layers: Seq[String]
*/
@deprecated("All APIs in package firrtl are deprecated.", "Chisel 7.0.0")
case class ExtModule(
info: Info,
name: String,
ports: Seq[Port],
defname: String,
params: Seq[Param],
layers: Seq[String]
info: Info,
name: String,
ports: Seq[Port],
defname: String,
params: Seq[Param],
layers: Seq[String],
requirements: Seq[String]
) extends DefModule
with UseSerializer

Expand Down
5 changes: 4 additions & 1 deletion firrtl/src/main/scala/firrtl/ir/Serializer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -556,12 +556,15 @@ object Serializer {
b.toString
}
Iterator(start) ++ sIt(body)(indent + 1)
case ExtModule(info, name, ports, defname, params, knownLayers) =>
case ExtModule(info, name, ports, defname, params, knownLayers, requirements) =>
implicit val b = new StringBuilder
doIndent(0); b ++= "extmodule "; b ++= legalize(name);
if (knownLayers.nonEmpty) {
b ++= knownLayers.mkString(" knownlayer ", ", ", "")
}
if (requirements.nonEmpty) {
b ++= requirements.map(r => StringLit(r).escape).mkString(" requires ", ", ", "")
}
b ++= " :"; s(info)
ports.foreach { p => newLineAndIndent(1); s(p) }
newLineAndIndent(1); b ++= "defname = "; b ++= defname
Expand Down
3 changes: 2 additions & 1 deletion firrtl/src/test/scala/firrtlTests/ExtModuleTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class ExtModuleTests extends FirrtlFlatSpec {
DoubleParam("REAL", -1.7),
RawStringParam("TYP", "bit")
),
Seq("A", "B", "C")
Seq("A", "B", "C"),
Seq.empty
)
),
"Top"
Expand Down
5 changes: 4 additions & 1 deletion firrtl/src/test/scala/firrtlTests/SerializerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ object SerializerSpec {
),
"child",
Seq.empty,
Seq.empty,
Seq.empty
)

Expand Down Expand Up @@ -328,7 +329,9 @@ class SerializerSpec extends AnyFlatSpec with Matchers {
"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, Seq.empty)) should include(
Serializer.serialize(
ExtModule(NoInfo, "42_extmodule", Seq.empty, "<TODO>", Seq.empty, Seq.empty, Seq.empty)
) should include(
"extmodule `42_extmodule`"
)
Serializer.serialize(IntModule(NoInfo, "42_intmodule", Seq.empty, "foo", Seq.empty)) should include(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ class AddDedupGroupAnnotations extends Phase {
val skipAnnos = annotations.collect { case x: DedupGroupAnnotation => x.target }.toSet

val annos = elaboratedCircuit._circuit.components.filter {
case x @ DefBlackBox(id, _, _, _, _, _) => !id._isImportedDefinition
case DefIntrinsicModule(_, _, _, _, _) => false
case DefClass(_, _, _, _) => false
case x => true
case d: DefBlackBox => !d.id._isImportedDefinition
case DefIntrinsicModule(_, _, _, _, _) => false
case DefClass(_, _, _, _) => false
case x => true
}.collect {
case x if !(skipAnnos.contains(x.id.toTarget)) => DedupGroupAnnotation(x.id.toTarget, x.id._proposedName)
}
Expand Down
57 changes: 57 additions & 0 deletions src/test/scala-2/chiselTests/ExtModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -301,4 +301,61 @@ class ExtModuleSpec extends AnyFlatSpec with Matchers with ChiselSim with FileCh
)
}

it should "emit require statements" in {
class Bar extends ExtModule(requirements = Seq.empty)
class Baz extends ExtModule(requirements = Seq("libdv"))
class Qux extends ExtModule(requirements = Seq("libdv", "vcs>=202505"))

class Foo extends Module {
private val bar = Module(new Bar)
private val baz = Module(new Baz)
private val qux = Module(new Qux)
}

info("emitted CHIRRTL looks correct")
ChiselStage
.emitCHIRRTL(new Foo)
.fileCheck()(
"""|CHECK: extmodule Bar :
|CHECK: extmodule Baz requires "libdv" :
|CHECK: extmodule Qux requires "libdv", "vcs>=202505" :
|""".stripMargin
)
}

it should "emit both knownlayer and requires when both are specified" in {
object A extends layer.Layer(layer.LayerConfig.Extract())

class Bar extends ExtModule(knownLayers = Seq(A), requirements = Seq("libdv"))

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

info("emitted CHIRRTL looks correct")
ChiselStage
.emitCHIRRTL(new Foo)
.fileCheck()(
"""|CHECK: layer A,
|CHECK: extmodule Bar knownlayer A requires "libdv" :
|""".stripMargin
)
}

it should "escape special characters in requirements" in {
class Bar extends ExtModule(requirements = Seq("\"quoted\"", "back\\slash"))

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

info("emitted CHIRRTL looks correct")
ChiselStage
.emitCHIRRTL(new Foo)
.fileCheck()(
"""|CHECK: extmodule Bar requires "\"quoted\"", "back\\slash" :
|""".stripMargin
)
}

}