diff --git a/core/src/main/scala/chisel3/BlackBox.scala b/core/src/main/scala/chisel3/BlackBox.scala index e7a64eba79a..59a53cb06ec 100644 --- a/core/src/main/scala/chisel3/BlackBox.scala +++ b/core/src/main/scala/chisel3/BlackBox.scala @@ -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)) { @@ -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 } diff --git a/core/src/main/scala/chisel3/experimental/ExtModule.scala b/core/src/main/scala/chisel3/experimental/ExtModule.scala index 3371f17b903..184b083e2eb 100644 --- a/core/src/main/scala/chisel3/experimental/ExtModule.scala +++ b/core/src/main/scala/chisel3/experimental/ExtModule.scala @@ -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") @@ -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 } diff --git a/core/src/main/scala/chisel3/experimental/hierarchy/core/Instance.scala b/core/src/main/scala/chisel3/experimental/hierarchy/core/Instance.scala index f58af86b625..0c819554de7 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/core/Instance.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/core/Instance.scala @@ -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 ) Some(component) } diff --git a/core/src/main/scala/chisel3/internal/BaseBlackBox.scala b/core/src/main/scala/chisel3/internal/BaseBlackBox.scala index c6cd3bc389f..0f99266e56d 100644 --- a/core/src/main/scala/chisel3/internal/BaseBlackBox.scala +++ b/core/src/main/scala/chisel3/internal/BaseBlackBox.scala @@ -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) } diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index e40c2864828..9a55370c708 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -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( diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index 1ef45992355..506f0b5f70c 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -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 { diff --git a/core/src/main/scala/chisel3/internal/firrtl/Serializer.scala b/core/src/main/scala/chisel3/internal/firrtl/Serializer.scala index 8ebf7efc6bc..c741b4c27fd 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Serializer.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Serializer.scala @@ -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 diff --git a/firrtl/src/main/scala/firrtl/ir/IR.scala b/firrtl/src/main/scala/firrtl/ir/IR.scala index d7b20780f72..c0bcbb9b973 100644 --- a/firrtl/src/main/scala/firrtl/ir/IR.scala +++ b/firrtl/src/main/scala/firrtl/ir/IR.scala @@ -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 diff --git a/firrtl/src/main/scala/firrtl/ir/Serializer.scala b/firrtl/src/main/scala/firrtl/ir/Serializer.scala index 2474a7df1c6..ce5053f1ffd 100644 --- a/firrtl/src/main/scala/firrtl/ir/Serializer.scala +++ b/firrtl/src/main/scala/firrtl/ir/Serializer.scala @@ -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 diff --git a/firrtl/src/test/scala/firrtlTests/ExtModuleTests.scala b/firrtl/src/test/scala/firrtlTests/ExtModuleTests.scala index f0328b7f297..c5474292d81 100644 --- a/firrtl/src/test/scala/firrtlTests/ExtModuleTests.scala +++ b/firrtl/src/test/scala/firrtlTests/ExtModuleTests.scala @@ -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" diff --git a/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala b/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala index 7d442e1b371..cc91bcb4547 100644 --- a/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala +++ b/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala @@ -75,6 +75,7 @@ object SerializerSpec { ), "child", Seq.empty, + Seq.empty, Seq.empty ) @@ -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, "", Seq.empty, Seq.empty)) should include( + Serializer.serialize( + ExtModule(NoInfo, "42_extmodule", Seq.empty, "", Seq.empty, Seq.empty, Seq.empty) + ) should include( "extmodule `42_extmodule`" ) Serializer.serialize(IntModule(NoInfo, "42_intmodule", Seq.empty, "foo", Seq.empty)) should include( diff --git a/src/main/scala/chisel3/stage/phases/AddDedupGroupAnnotations.scala b/src/main/scala/chisel3/stage/phases/AddDedupGroupAnnotations.scala index 48e1873b2bd..b1c0a135120 100644 --- a/src/main/scala/chisel3/stage/phases/AddDedupGroupAnnotations.scala +++ b/src/main/scala/chisel3/stage/phases/AddDedupGroupAnnotations.scala @@ -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) } diff --git a/src/test/scala-2/chiselTests/ExtModule.scala b/src/test/scala-2/chiselTests/ExtModule.scala index f8df222b03a..6ffe076f93d 100644 --- a/src/test/scala-2/chiselTests/ExtModule.scala +++ b/src/test/scala-2/chiselTests/ExtModule.scala @@ -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 + ) + } + }