diff --git a/core/src/main/scala/chisel3/BlackBox.scala b/core/src/main/scala/chisel3/BlackBox.scala index c9d78d4b2f7..91c9e594acb 100644 --- a/core/src/main/scala/chisel3/BlackBox.scala +++ b/core/src/main/scala/chisel3/BlackBox.scala @@ -126,8 +126,8 @@ package experimental { // Ports are named in the same way as regular Modules namePorts() - val firrtlPorts = getModulePorts.map { case port => - Port(port, port.specifiedDirection, UnlocatableSourceInfo) + val firrtlPorts = getModulePortsAndLocators.map { case (port, _, associations) => + Port(port, port.specifiedDirection, associations, UnlocatableSourceInfo) } val component = DefBlackBox(this, name, firrtlPorts, SpecifiedDirection.Unspecified, params, getKnownLayers) _component = Some(component) @@ -217,9 +217,14 @@ abstract class BlackBox( port.setRef(ModuleIO(this, _namespace.name(name)), force = true) } + // Note: BlackBoxes, because they have a single `io` cannot currently + // support associations because associations require having multiple ports. + // If this restriction is lifted, then this code should be updated. + require(!hasAsssociations, "BlackBoxes cannot support associations at this time, use an ExtModule") val firrtlPorts = namedPorts.map { namedPort => - Port(namedPort._2, namedPort._2.specifiedDirection, UnlocatableSourceInfo) + Port(namedPort._2, namedPort._2.specifiedDirection, Seq.empty, UnlocatableSourceInfo) } + val component = DefBlackBox(this, name, firrtlPorts, io.specifiedDirection, params, getKnownLayers) _component = Some(component) _component diff --git a/core/src/main/scala/chisel3/IO.scala b/core/src/main/scala/chisel3/IO.scala index 9a997684132..c4749e3c9a1 100644 --- a/core/src/main/scala/chisel3/IO.scala +++ b/core/src/main/scala/chisel3/IO.scala @@ -1,5 +1,6 @@ package chisel3 +import chisel3.domain import chisel3.internal.{throwException, Builder} import chisel3.experimental.{noPrefix, requireIsChiselType, SourceInfo} import chisel3.properties.{Class, Property} diff --git a/core/src/main/scala/chisel3/Module.scala b/core/src/main/scala/chisel3/Module.scala index 328501315af..ccec2281e0c 100644 --- a/core/src/main/scala/chisel3/Module.scala +++ b/core/src/main/scala/chisel3/Module.scala @@ -576,6 +576,11 @@ package experimental { private val _ports = new ArrayBuffer[(Data, SourceInfo)]() + private val _associations = new HashMap[Data, LinkedHashSet[domain.Type]]() + + /** Return true if this module has any port associations. */ + private[chisel3] def hasAsssociations: Boolean = _associations.nonEmpty + // getPorts unfortunately already used for tester compatibility protected[chisel3] def getModulePorts: Seq[Data] = { require(_closed, "Can't get ports before module close") @@ -583,9 +588,11 @@ package experimental { } // gets Ports along with there source locators - private[chisel3] def getModulePortsAndLocators: Seq[(Data, SourceInfo)] = { + private[chisel3] def getModulePortsAndLocators: Seq[(Data, SourceInfo, Seq[domain.Type])] = { require(_closed, "Can't get ports before module close") - _ports.toSeq + _ports.toSeq.map { case (port, info) => + (port, info, _associations.get(port).fold(Seq.empty[domain.Type])(_.toSeq)) + } } /** Get IOs that are currently bound to this module. @@ -608,6 +615,22 @@ package experimental { protected def portsSize: Int = _ports.size + /* Associate a port of this module with one or more domains. */ + final def associate(port: Data, domains: domain.Type*)(implicit si: SourceInfo): Unit = { + require(domains.nonEmpty, "cannot associate a port with zero domains") + if (!portsContains(port)) { + val domainsString = domains.mkString(", ") + Builder.error( + s"""Unable to associate port '$port' to domains '$domainsString' because the port does not exist in this module""" + )(si) + return + } + _associations.updateWith(port) { + case Some(acc) => Some(acc ++= domains) + case None => Some(LinkedHashSet.empty[domain.Type] ++= domains) + } + } + /** Generates the FIRRTL Component (Module or Blackbox) of this Module. * Also closes the module so no more construction can happen inside. */ @@ -618,7 +641,7 @@ package experimental { private[chisel3] def initializeInParent(): Unit private[chisel3] def namePorts(): Unit = { - for ((port, source) <- getModulePortsAndLocators) { + for ((port, source, _) <- getModulePortsAndLocators) { port._computeName(None) match { case Some(name) => if (_namespace.contains(name)) { @@ -882,7 +905,7 @@ package experimental { private[chisel3] def addSecretIO[A <: Data](iodef: A)(implicit sourceInfo: SourceInfo): A = { val name = iodef._computeName(None).getOrElse("secret") iodef.setRef(ModuleIO(this, _namespace.name(name))) - val newPort = new Port(iodef, iodef.specifiedDirection, sourceInfo) + val newPort = new Port(iodef, iodef.specifiedDirection, Seq.empty, sourceInfo) if (_closed) { _component.get.secretPorts += newPort } else secretPorts += newPort @@ -905,7 +928,9 @@ package experimental { * TODO(twigg): Specifically walk the Data definition to call out which nodes * are problematic. */ - protected def IO[T <: Data](iodef: => T)(implicit sourceInfo: SourceInfo): T = { + protected def IO[T <: Data](iodef: => T)( + implicit sourceInfo: SourceInfo + ): T = { chisel3.IO.apply(iodef) } diff --git a/core/src/main/scala/chisel3/RawModule.scala b/core/src/main/scala/chisel3/RawModule.scala index dae51bb3a46..59cd5063e06 100644 --- a/core/src/main/scala/chisel3/RawModule.scala +++ b/core/src/main/scala/chisel3/RawModule.scala @@ -91,7 +91,7 @@ abstract class RawModule extends BaseModule { private var _firrtlPorts: Option[Seq[Port]] = None private[chisel3] def checkPorts(): Unit = { - for ((port, source) <- getModulePortsAndLocators) { + for ((port, source, _) <- getModulePortsAndLocators) { if (port._computeName(None).isEmpty) { Builder.error( s"Unable to name port $port in $this, " + @@ -175,8 +175,8 @@ abstract class RawModule extends BaseModule { nameId(id) } - val firrtlPorts = getModulePortsAndLocators.map { case (port, sourceInfo) => - Port(port, port.specifiedDirection, sourceInfo) + val firrtlPorts = getModulePortsAndLocators.map { case (port, sourceInfo, associations) => + Port(port, port.specifiedDirection, associations, sourceInfo) } _firrtlPorts = Some(firrtlPorts) diff --git a/core/src/main/scala/chisel3/domain/Domain.scala b/core/src/main/scala/chisel3/domain/Domain.scala new file mode 100644 index 00000000000..89eb16c73d7 --- /dev/null +++ b/core/src/main/scala/chisel3/domain/Domain.scala @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.domain + +import chisel3.experimental.SourceInfo +import chisel3.util.simpleClassName + +object Field { + sealed trait Type + + /** A boolean type */ + object Boolean extends Type + + /** An integer type */ + object Integer extends Type + + /** A string type */ + object String extends Type +} + +/** A [[Domain]] represents a kind information, and the schema of that + * information, that can be associated with certain hardware in a Chisel + * design. + * + * A domain is intended to represent a specific _kind_ of hardware-related + * concern that is not captured with the digital, synchronous logic that core + * Chisel represents. Examples of domains are clock, reset, and power domains. + * And while some domains are provided as part of Chisel, domain kinds are + * intentionall user-extensible. + * + * To create a new user-defined domain kind, define an object that extends the + * [[Domain]] class. Add fields to define the domain's schema by overridding + * the `fields` method: + * {{{ + * import chisel3.domain.{Domain, Field} + * + * object FooDomain extends Domain { + * override def fields: Seq[(String, Field.Type)] = Seq( + * "bar" -> Field.Boolean, + * "baz" -> Field.Integer, + * "qux" -> Field.String + * ) + * } + * }}} + * + * @see [[chisel3.domains.ClockDomain]] + */ +abstract class Domain()(implicit val sourceInfo: SourceInfo) { self: Singleton => + + // The name that will be used when generating FIRRTL. + private[chisel3] def name: String = simpleClassName(this.getClass) + + /** A sequence of name--type pairs that define the schema for this domain. + * + * The fields comprise the information that a user, after Verilog generation, + * should set in order to interact with, generate collateral files related + * to, or check the correctness of their choices for a domain. + * + * Alternatively, the fields are the "parameters" for the domain. E.g., a + * clock domain could be parameterzied by an integer frequency. Chisel + * itself has no knowledge of this frequency, nor does it need a frequency to + * generate Verilog. However, in order to generate an implementation + * constraints file, the user must provide a frequency. + * + * To change the fields from the default, override this method in your + * domain. + * {{{ + * override def fields: Seq[(String, Field.Type)] = Seq( + * "foo" -> Field.Boolean + * ) + * }}} + */ + def fields: Seq[(String, Field.Type)] = Seq.empty + + /** Construct a type of this domain kind. + * + * For a given domain, this is used to create a Chisel type which can be used + * in a port. This is typically used to create domain type ports. + * + * E.g., to create a [[chisel3.domains.ClockDomain]] port, use: + * {{{ + * import chisel3.domains.ClockDomain + * + * val A = IO(Input(ClockDomain.Type())) + * }}} + */ + final def Type() = new chisel3.domain.Type(this) + +} diff --git a/core/src/main/scala/chisel3/domain/Type.scala b/core/src/main/scala/chisel3/domain/Type.scala new file mode 100644 index 00000000000..7c3c6f3d2eb --- /dev/null +++ b/core/src/main/scala/chisel3/domain/Type.scala @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.domain + +import chisel3.{fromIntToLiteral, Data, Element, Printable, UInt, UnknownWidth, Width} +import chisel3.experimental.SourceInfo +import chisel3.internal.{throwException, Builder} + +/** A [[Data]] that is used to communicate information of a specific domain + * kind. + */ +final class Type private[domain] (val domain: Domain) extends Element { self => + + private[chisel3] def _asUIntImpl(first: Boolean)(implicit sourceInfo: SourceInfo): chisel3.UInt = { + Builder.error(s"${this._localErrorContext} does not support .asUInt.") + 0.U + } + + override protected def _fromUInt(that: UInt)(implicit sourceInfo: SourceInfo): Data = { + Builder.exception(s"${this._localErrorContext} cannot be driven by UInt") + } + + override def cloneType: this.type = new Type(domain).asInstanceOf[this.type] + + override def toPrintable: Printable = + throwException(s"'domain.Type' does not support hardware printing" + this._errorContext) + + private[chisel3] def width: Width = UnknownWidth + + addDomain(domain) + +} diff --git a/core/src/main/scala/chisel3/domain/package.scala b/core/src/main/scala/chisel3/domain/package.scala new file mode 100644 index 00000000000..11327e8bc89 --- /dev/null +++ b/core/src/main/scala/chisel3/domain/package.scala @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3 + +import chisel3.internal.Builder +import chisel3.internal.firrtl.ir +import chisel3.experimental.SourceInfo + +package object domain { + + /** Add a [[Domain]] kind to Chisel's runtime Builder so that it will be + * unconditionally emitted during FIRRTL emission. + * + * @param domain the kind of domain to add + */ + def addDomain(domain: Domain) = { + Builder.domains += domain + } + + /** Forward a domain from a source to a sink. + * + * @param sink the destination of the forward + * @param source the source of the forward + */ + def define[A <: domain.Type](sink: A, source: A)(implicit sourceInfo: SourceInfo): Unit = { + Builder.pushCommand(ir.DomainDefine(sourceInfo, sink.lref, source.ref)) + } + +} diff --git a/core/src/main/scala/chisel3/domains/ClockDomain.scala b/core/src/main/scala/chisel3/domains/ClockDomain.scala new file mode 100644 index 00000000000..f20b0df2287 --- /dev/null +++ b/core/src/main/scala/chisel3/domains/ClockDomain.scala @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chisel3.domains + +import chisel3.domain.{Domain, Field} +import chisel3.experimental.UnlocatableSourceInfo + +object ClockDomain extends Domain()(sourceInfo = UnlocatableSourceInfo) { + + override def fields: Seq[(String, Field.Type)] = Seq( + "name" -> Field.String + ) + +} diff --git a/core/src/main/scala/chisel3/experimental/IntrinsicModule.scala b/core/src/main/scala/chisel3/experimental/IntrinsicModule.scala index f98b8e2ad2a..4fefd064b09 100644 --- a/core/src/main/scala/chisel3/experimental/IntrinsicModule.scala +++ b/core/src/main/scala/chisel3/experimental/IntrinsicModule.scala @@ -23,8 +23,8 @@ abstract class IntrinsicModule(intrinsicName: String, val params: Map[String, Pa // Ports are named in the same way as regular Modules namePorts() - val firrtlPorts = getModulePorts.map { case port => - Port(port, port.specifiedDirection, UnlocatableSourceInfo) + val firrtlPorts = getModulePortsAndLocators.map { case (port, _, associations) => + Port(port, port.specifiedDirection, associations, UnlocatableSourceInfo) } val component = DefIntrinsicModule(this, name, firrtlPorts, SpecifiedDirection.Unspecified, params) _component = Some(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 6f14117d07c..8518c97b862 100644 --- a/core/src/main/scala/chisel3/experimental/hierarchy/core/Instance.scala +++ b/core/src/main/scala/chisel3/experimental/hierarchy/core/Instance.scala @@ -133,8 +133,14 @@ object Instance extends SourceInfoDoc { require(!_closed, s"Can't generate $desiredName module more than once") evaluateAtModuleBodyEnd() _closed = true - val firrtlPorts = importedDefinition.proto.getModulePortsAndLocators.map { case (port, sourceInfo) => - Port(port, port.specifiedDirection, sourceInfo): @nowarn // Deprecated code allowed for internal use + val firrtlPorts = importedDefinition.proto.getModulePortsAndLocators.map { + case (port, sourceInfo, associations) => + Port( + port, + port.specifiedDirection, + associations, + sourceInfo + ): @nowarn // Deprecated code allowed for internal use } val component = DefBlackBox( diff --git a/core/src/main/scala/chisel3/internal/Builder.scala b/core/src/main/scala/chisel3/internal/Builder.scala index 5c88c61af2b..a0e7d55a0d4 100644 --- a/core/src/main/scala/chisel3/internal/Builder.scala +++ b/core/src/main/scala/chisel3/internal/Builder.scala @@ -532,6 +532,7 @@ private[chisel3] class DynamicContext( val annotations = ArrayBuffer[() => Seq[Annotation]]() val layers = mutable.LinkedHashSet[layer.Layer]() val options = mutable.LinkedHashSet[choice.Case]() + val domains = mutable.LinkedHashSet[domain.Domain]() var currentModule: Option[BaseModule] = None // Views that do not correspond to a single ReferenceTarget and thus require renaming @@ -603,6 +604,7 @@ private[chisel3] object Builder extends LazyLogging { def layers: mutable.LinkedHashSet[layer.Layer] = dynamicContext.layers def options: mutable.LinkedHashSet[choice.Case] = dynamicContext.options + def domains: mutable.LinkedHashSet[domain.Domain] = dynamicContext.domains def contextCache: BuilderContextCache = dynamicContext.contextCache @@ -1124,7 +1126,8 @@ private[chisel3] object Builder extends LazyLogging { typeAliases, layerAdjacencyList(layer.Layer.Root).map(foldLayers).toSeq, optionDefs, - dynamicContext.suppressSourceInfo + dynamicContext.suppressSourceInfo, + domains.toSeq.map(d => Domain(d.sourceInfo, d.name, d.fields)) ) (ElaboratedCircuit(circuit, dynamicContext.annotationSeq.toSeq), mod) } diff --git a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala index ed027523159..95941f885a1 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Converter.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Converter.scala @@ -280,6 +280,8 @@ private[chisel3] object Converter { convert(block, ctx, typeAliases) case FirrtlComment(text) => fir.Comment(text) + case DomainDefine(info, sink, source) => + fir.DomainDefine(convert(info), convert(sink, ctx, info), convert(source, ctx, info)) } /** Convert Chisel IR Commands into FIRRTL Statements @@ -397,6 +399,7 @@ private[chisel3] object Converter { extractType(t._elements.head._2, childClearDir, info, checkProbe, true, typeAliases) } case t: Property[_] => t.getPropertyType + case t: domain.Type => fir.DomainType(t.domain.name) } def convert(name: String, param: Param): fir.Param = param match { @@ -441,7 +444,13 @@ private[chisel3] object Converter { case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => false } val tpe = extractType(port.id, clearDir, port.sourceInfo, true, true, typeAliases) - fir.Port(convert(port.sourceInfo), getRef(port.id, port.sourceInfo).name, dir, tpe) + fir.Port( + convert(port.sourceInfo), + getRef(port.id, port.sourceInfo).name, + dir, + tpe, + port.associations.map(getRef(_, UnlocatableSourceInfo).name) + ) } def convert(component: Component, typeAliases: Seq[String]): fir.DefModule = component match { diff --git a/core/src/main/scala/chisel3/internal/firrtl/IR.scala b/core/src/main/scala/chisel3/internal/firrtl/IR.scala index ee9911a1300..d76fa2ee983 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/IR.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/IR.scala @@ -481,6 +481,12 @@ private[chisel3] object ir { chiselLayer: layer.Layer ) + final case class Domain( + sourceInfo: SourceInfo, + name: String, + fields: Seq[(String, domain.Field.Type)] + ) + class LayerBlock(val sourceInfo: SourceInfo, val layer: chisel3.layer.Layer) extends Command { val region = new Block(sourceInfo) } @@ -498,7 +504,7 @@ private[chisel3] object ir { case class DefOption(sourceInfo: SourceInfo, name: String, cases: Seq[DefOptionCase]) case class DefOptionCase(sourceInfo: SourceInfo, name: String) - case class Port(id: Data, dir: SpecifiedDirection, sourceInfo: SourceInfo) + case class Port(id: Data, dir: SpecifiedDirection, associations: Seq[domain.Type], sourceInfo: SourceInfo) case class Printf( id: printf.Printf, @@ -520,6 +526,8 @@ private[chisel3] object ir { case class ProbeForce(sourceInfo: SourceInfo, clock: Arg, cond: Arg, probe: Arg, value: Arg) extends Command case class ProbeRelease(sourceInfo: SourceInfo, clock: Arg, cond: Arg, probe: Arg) extends Command + case class DomainDefine(sourceInfo: SourceInfo, sink: Arg, source: Arg) extends Command + object Formal extends Enumeration { val Assert = Value("assert") val Assume = Value("assume") @@ -607,7 +615,8 @@ private[chisel3] object ir { typeAliases: Seq[DefTypeAlias], layers: Seq[Layer], options: Seq[DefOption], - suppressSourceInfo: Boolean + suppressSourceInfo: Boolean, + domains: Seq[Domain] ) { def firrtlAnnotations: Iterable[Annotation] = annotations.flatMap(_().flatMap(_.update(renames))) } diff --git a/core/src/main/scala/chisel3/internal/firrtl/Serializer.scala b/core/src/main/scala/chisel3/internal/firrtl/Serializer.scala index 1c82b745c7d..1f20fe654c8 100644 --- a/core/src/main/scala/chisel3/internal/firrtl/Serializer.scala +++ b/core/src/main/scala/chisel3/internal/firrtl/Serializer.scala @@ -320,6 +320,12 @@ private[chisel3] object Serializer { } it.hasNext }) () + case e @ DomainDefine(info, sink, source) => + b ++= "domain_define " + serialize(sink, ctx, info) + b ++= " = " + serialize(source, ctx, info); + serialize(info) } private def serializeCommand(cmd: Command, ctx: Component, typeAliases: Seq[String])( @@ -516,6 +522,7 @@ private[chisel3] object Serializer { case t: Property[_] => // TODO can we not use FIRRTL types here? b ++= fir.Serializer.serialize(t.getPropertyType) + case t: domain.Type => b ++= "Domain of "; b ++= t.domain.name; } private def serialize(name: String, param: Param)(implicit b: StringBuilder): Unit = param match { @@ -570,6 +577,14 @@ private[chisel3] object Serializer { b ++= legalize(getRef(port.id, port.sourceInfo).name) b ++= " : " val tpe = serializeType(port.id, clearDir, port.sourceInfo, true, true, typeAliases) + if (port.associations.nonEmpty) { + b ++= " domains [" + port.associations.zipWithIndex.foreach { case (assoc, i) => + if (i > 0) b ++= ", " + b ++= legalize(getRef(assoc, UnlocatableSourceInfo).name) + } + b ++= "]" + } serialize(port.sourceInfo) } @@ -678,6 +693,40 @@ private[chisel3] object Serializer { // serialize(ta.sourceInfo) TODO: Uncomment once firtool accepts infos for type aliases } + private def serialize(fieldType: domain.Field.Type)(implicit b: StringBuilder, indent: Int): Unit = { + b ++= { + fieldType match { + case domain.Field.Boolean => "Bool" + case domain.Field.Integer => "Integer" + case domain.Field.String => "String" + } + } + } + + private def serialize(domain: Domain)(implicit b: StringBuilder, indent: Int): Unit = { + newLineAndIndent() + b ++= "domain " + b ++= domain.name + b ++= " :" + domain.fields.map { case (name, tpe) => + newLineAndIndent(1) + b ++= name + b ++= " : " + serialize(tpe) + } + newLineNoIndent() + } + + private def serializeDomains(domains: Seq[Domain])(implicit indent: Int): Iterator[String] = { + if (domains.isEmpty) + return Iterator.empty + + implicit val b = new StringBuilder + domains.foreach(serialize) + newLineNoIndent() + Iterator(b.toString) + } + // TODO make Annotation serialization lazy private def serialize(circuit: Circuit, annotations: Seq[Annotation]): Iterator[String] = { implicit val indent: Int = 0 @@ -713,12 +762,14 @@ private[chisel3] object Serializer { Iterator(b.toString) } else Iterator.empty val layers = serialize(circuit.layers)(indent + 1, suppressSourceInfo) + val domains = serializeDomains(circuit.domains)(indent + 1) // TODO what is typeAliases for? Should it be a Set? val typeAliasesSeq: Seq[String] = circuit.typeAliases.map(_.name) prelude ++ options ++ typeAliases ++ layers ++ + domains ++ circuit.components.iterator.zipWithIndex.flatMap { case (m, i) => val newline = Iterator(if (i == 0) s"$NewLine" else s"${NewLine}${NewLine}") newline ++ serialize(m, typeAliasesSeq)(indent + 1, suppressSourceInfo) diff --git a/core/src/main/scala/chisel3/properties/Class.scala b/core/src/main/scala/chisel3/properties/Class.scala index 25e6ac532e1..017b32f862a 100644 --- a/core/src/main/scala/chisel3/properties/Class.scala +++ b/core/src/main/scala/chisel3/properties/Class.scala @@ -63,8 +63,8 @@ class Class extends BaseModule { } // Create IR Ports and set the firrtlPorts variable. - val ports = getModulePortsAndLocators.map { case (port, sourceInfo) => - Port(port, port.specifiedDirection, sourceInfo) + val ports = getModulePortsAndLocators.map { case (port, sourceInfo, _) => + Port(port, port.specifiedDirection, Seq.empty, sourceInfo) } // No more commands. diff --git a/firrtl/src/main/scala/firrtl/ir/IR.scala b/firrtl/src/main/scala/firrtl/ir/IR.scala index 5174e8f9fb0..b1e01c28157 100644 --- a/firrtl/src/main/scala/firrtl/ir/IR.scala +++ b/firrtl/src/main/scala/firrtl/ir/IR.scala @@ -507,6 +507,9 @@ case class DefOption(info: Info, name: String, cases: Seq[DefOptionCase]) @deprecated("All APIs in package firrtl are deprecated.", "Chisel 7.0.0") case class DefOptionCase(info: Info, name: String) +@deprecated("All APIs in package firrtl are deprecated.", "Chisel 7.0.0") +case class Domain(info: Info, name: String) + @deprecated("All APIs in package firrtl are deprecated.", "Chisel 7.0.0") case class IntrinsicExpr(intrinsic: String, args: Seq[Expression], params: Seq[Param], tpe: Type) extends Expression @@ -707,6 +710,12 @@ case class ClassPropertyType(name: String) extends PropertyType @deprecated("All APIs in package firrtl are deprecated.", "Chisel 7.0.0") case object AnyRefPropertyType extends PropertyType +@deprecated("All APIs in package firrtl are deprecated.", "Chisel 7.0.0") +case class DomainType(domain: String) extends Type with UseSerializer + +@deprecated("All APIs in package firrtl are deprecated.", "Chisel 7.0.0") +case class DomainDefine(info: Info, sink: Expression, source: Expression) extends Statement with UseSerializer + @deprecated("All APIs in package firrtl are deprecated.", "Chisel 7.0.0") case object UnknownType extends Type with UseSerializer @@ -724,7 +733,7 @@ case object Output extends Direction { /** [[DefModule]] Port */ @deprecated("All APIs in package firrtl are deprecated.", "Chisel 7.0.0") -case class Port(info: Info, name: String, direction: Direction, tpe: Type) +case class Port(info: Info, name: String, direction: Direction, tpe: Type, associations: Seq[String]) extends FirrtlNode with IsDeclaration with UseSerializer @@ -833,7 +842,8 @@ case class Circuit( main: String, typeAliases: Seq[DefTypeAlias] = Seq.empty, layers: Seq[Layer] = Seq.empty, - options: Seq[DefOption] = Seq.empty + options: Seq[DefOption] = Seq.empty, + domains: Seq[Domain] = Seq.empty ) extends FirrtlNode with HasInfo with UseSerializer diff --git a/firrtl/src/main/scala/firrtl/ir/Serializer.scala b/firrtl/src/main/scala/firrtl/ir/Serializer.scala index 49fea6f2dad..907be5fc49f 100644 --- a/firrtl/src/main/scala/firrtl/ir/Serializer.scala +++ b/firrtl/src/main/scala/firrtl/ir/Serializer.scala @@ -377,6 +377,8 @@ object Serializer { } it.hasNext }) () + case DomainDefine(info, sink, source) => + b ++= "domain_define "; s(sink); b ++= " = "; s(source); s(info) case other => b ++= other.serialize // Handle user-defined nodes } @@ -484,6 +486,7 @@ object Serializer { case ClassPropertyType(name) => b ++= "Inst<"; b ++= name; b += '>' case AnyRefPropertyType => b ++= "AnyRef" case AliasType(name) => b ++= name + case DomainType(domain) => b ++= "Domain of "; b ++= (domain); case UnknownType => b += '?' case other => b ++= other.serialize // Handle user-defined nodes } @@ -495,8 +498,18 @@ object Serializer { } private def s(node: Port)(implicit b: StringBuilder, indent: Int): Unit = node match { - case Port(info, name, direction, tpe) => - s(direction); b += ' '; b ++= legalize(name); b ++= " : "; s(tpe); s(info) + case Port(info, name, direction, tpe, associations) => + s(direction); b += ' '; b ++= legalize(name); + b ++= " : "; s(tpe); + if (associations.nonEmpty) { + b ++= " domains ["; + associations.zipWithIndex.foreach { case (assoc, i) => + if (i > 0) b ++= ", " + b ++= assoc + } + b ++= "]" + } + s(info) } private def s(node: Param)(implicit b: StringBuilder, indent: Int): Unit = node match { diff --git a/firrtl/src/test/scala/firrtlTests/ExtModuleTests.scala b/firrtl/src/test/scala/firrtlTests/ExtModuleTests.scala index a26c9ef5c9c..f0328b7f297 100644 --- a/firrtl/src/test/scala/firrtlTests/ExtModuleTests.scala +++ b/firrtl/src/test/scala/firrtlTests/ExtModuleTests.scala @@ -27,7 +27,10 @@ class ExtModuleTests extends FirrtlFlatSpec { ExtModule( NoInfo, "Top", - Seq(Port(NoInfo, "y", Input, UIntType(IntWidth(0))), Port(NoInfo, "x", Output, UIntType(IntWidth(1)))), + Seq( + Port(NoInfo, "y", Input, UIntType(IntWidth(0)), Seq.empty), + Port(NoInfo, "x", Output, UIntType(IntWidth(1)), Seq.empty) + ), "ParameterizedExtModule", Seq( IntParam("VALUE", 1), diff --git a/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala b/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala index 7656177a143..7d442e1b371 100644 --- a/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala +++ b/firrtl/src/test/scala/firrtlTests/SerializerSpec.scala @@ -31,7 +31,7 @@ object SerializerSpec { val testModule: String = """module test : | input in : UInt<8> - | output out : UInt<8> + | output out : UInt<8> domains [ClockDomain, ResetDomain] | | inst c of child | connect c.in, in @@ -45,7 +45,10 @@ object SerializerSpec { "test", false, Seq.empty, - Seq(Port(NoInfo, "in", Input, UIntType(IntWidth(8))), Port(NoInfo, "out", Output, UIntType(IntWidth(8)))), + Seq( + Port(NoInfo, "in", Input, UIntType(IntWidth(8)), Seq.empty), + Port(NoInfo, "out", Output, UIntType(IntWidth(8)), Seq("ClockDomain", "ResetDomain")) + ), Block( Seq( DefInstance("c", "child"), @@ -66,7 +69,10 @@ object SerializerSpec { val childModuleIR: ExtModule = ExtModule( NoInfo, "child", - Seq(Port(NoInfo, "in", Input, UIntType(IntWidth(8))), Port(NoInfo, "out", Output, UIntType(IntWidth(8)))), + Seq( + Port(NoInfo, "in", Input, UIntType(IntWidth(8)), Seq.empty), + Port(NoInfo, "out", Output, UIntType(IntWidth(8)), Seq.empty) + ), "child", Seq.empty, Seq.empty @@ -177,7 +183,7 @@ class SerializerSpec extends AnyFlatSpec with Matchers { val constAsyncReset = DefWire(NoInfo, "constAsyncReset", ConstType(AsyncResetType)) Serializer.serialize(constAsyncReset) should be("wire constAsyncReset : const AsyncReset") - val constInput = Port(NoInfo, "in", Input, ConstType(SIntType(IntWidth(8)))) + val constInput = Port(NoInfo, "in", Input, ConstType(SIntType(IntWidth(8))), Seq.empty) Serializer.serialize(constInput) should be("input in : const SInt<8>") val constBundle = DefWire( @@ -222,7 +228,8 @@ class SerializerSpec extends AnyFlatSpec with Matchers { NoInfo, "foo", Output, - RWProbeType(BundleType(Seq(Field("bar", Default, UIntType(IntWidth(32)))))) + RWProbeType(BundleType(Seq(Field("bar", Default, UIntType(IntWidth(32)))))), + Seq.empty ) Serializer.serialize(rwProbeBundle) should be("output foo : RWProbe<{ bar : UInt<32>}>") @@ -230,7 +237,8 @@ class SerializerSpec extends AnyFlatSpec with Matchers { NoInfo, "foo", Output, - RWProbeType(VectorType(UIntType(IntWidth(32)), 4)) + RWProbeType(VectorType(UIntType(IntWidth(32)), 4)), + Seq.empty ) Serializer.serialize(probeVec) should be("output foo : RWProbe[4]>") @@ -256,6 +264,11 @@ class SerializerSpec extends AnyFlatSpec with Matchers { Serializer.serialize(probeRelease) should be("release(clock, cond, outProbe)") } + it should "support emitting domain defines" in { + val define = DomainDefine(NoInfo, Reference("foo"), Reference("bar")) + Serializer.serialize(define) should be("domain_define foo = bar") + } + it should "support emitting intrinsic expressions and statements" in { val intrinsicNode = DefNode(NoInfo, "foo", IntrinsicExpr("test", Seq(Reference("arg")), Seq(), UIntType(IntWidth(1)))) @@ -323,7 +336,9 @@ class SerializerSpec extends AnyFlatSpec with Matchers { ) info("ports okay!") - Serializer.serialize(Port(NoInfo, "42_port", Input, UIntType(IntWidth(1)))) should include("input `42_port`") + Serializer.serialize(Port(NoInfo, "42_port", Input, UIntType(IntWidth(1)), Seq.empty)) should include( + "input `42_port`" + ) info("types okay!") Serializer.serialize(BundleType(Seq(Field("42_field", Default, UIntType(IntWidth(1)))))) should include( diff --git a/release.mill b/release.mill index 9ef430bff8f..e084f060e53 100644 --- a/release.mill +++ b/release.mill @@ -51,7 +51,12 @@ trait Unipublish extends ScalaModule with ChiselPublishModule with Mima { override def mimaBinaryIssueFilters = super.mimaBinaryIssueFilters() ++ Seq( // chisel3.internal.firrtl.ir is package private ProblemFilter.exclude[DirectMissingMethodProblem]("chisel3.internal.firrtl.ir*"), - ProblemFilter.exclude[MissingTypesProblem]("chisel3.internal.firrtl.ir*") + ProblemFilter.exclude[IncompatibleResultTypeProblem]("chisel3.internal.firrtl.ir*"), + ProblemFilter.exclude[MissingTypesProblem]("chisel3.internal.firrtl.ir*"), + // firrtl is deprecated and is fine to break. + ProblemFilter.exclude[DirectMissingMethodProblem]("firrtl.ir*"), + ProblemFilter.exclude[IncompatibleResultTypeProblem]("firrtl.ir*"), + ProblemFilter.exclude[MissingTypesProblem]("firrtl.ir*") ) /** Publish both this project and the plugin (for the default Scala version) */ diff --git a/src/test/scala-2/chisel3/stage/ChiselOptionsViewSpec.scala b/src/test/scala-2/chisel3/stage/ChiselOptionsViewSpec.scala index 048cc45d690..73c34e291fc 100644 --- a/src/test/scala-2/chisel3/stage/ChiselOptionsViewSpec.scala +++ b/src/test/scala-2/chisel3/stage/ChiselOptionsViewSpec.scala @@ -16,7 +16,7 @@ class ChiselOptionsViewSpec extends AnyFlatSpec with Matchers { behavior.of(ChiselOptionsView.getClass.getName) it should "construct a view from an AnnotationSeq" in { - val bar = Circuit("bar", Seq.empty, Seq.empty, RenameMap("bar"), Seq.empty, Seq.empty, Seq.empty, false) + val bar = Circuit("bar", Seq.empty, Seq.empty, RenameMap("bar"), Seq.empty, Seq.empty, Seq.empty, false, Seq.empty) val circuit = ElaboratedCircuit(bar, Seq.empty) val annotations = Seq( PrintFullStackTraceAnnotation, diff --git a/src/test/scala-2/chiselTests/DomainSpec.scala b/src/test/scala-2/chiselTests/DomainSpec.scala new file mode 100644 index 00000000000..144a608dfd6 --- /dev/null +++ b/src/test/scala-2/chiselTests/DomainSpec.scala @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: Apache-2.0 + +package chiselTests + +import chisel3._ +import chisel3.domain.{Domain, Field} +import chisel3.domains.ClockDomain +import chisel3.experimental.ExtModule +import chisel3.testing.FileCheck +import circt.stage.ChiselStage +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class DomainSpec extends AnyFlatSpec with Matchers with FileCheck { + + behavior of "Domains" + + they should "emit FIRRTL for internal and user-defined domains" in { + + object PowerDomain extends Domain { + + override def fields = Seq( + ("name", Field.String), + ("voltage", Field.Integer), + ("alwaysOn", Field.Boolean) + ) + + } + + class Foo extends RawModule { + val A = IO(Input(ClockDomain.Type())) + val B = IO(Input(PowerDomain.Type())) + val a = IO(Input(Bool())) + val b = IO(Input(Bool())) + + associate(a, A) + associate(b, B) + } + + ChiselStage.emitCHIRRTL(new Foo).fileCheck() { + """|CHECK: circuit Foo : + |CHECK: domain ClockDomain : + |CHECK-NEXT: name : String + | + |CHECK: domain PowerDomain : + |CHECK-NEXT: name : String + |CHECK-NEXT: voltage : Integer + |CHECK-NEXT: alwaysOn : Bool + | + |CHECK: public module Foo : + |CHECK-NEXT: input A : Domain of ClockDomain + |CHECK-NEXT: input B : Domain of PowerDomain + |CHECK-NEXT: input a : UInt<1> domains [A] + |CHECK-NEXT: input b : UInt<1> domains [B] + |""".stripMargin + } + + } + + they should "fail to work with blackboxes" in { + + class Bar extends BlackBox { + val io = IO { + new Bundle { + val A = Input(ClockDomain.Type()) + val a = Input(UInt(1.W)) + } + } + associate(io.a, io.A) + } + + class Foo extends Module { + private val bar = Module(new Bar) + } + + intercept[ChiselException] { + ChiselStage.elaborate(new Foo, Array("--throw-on-first-error")) + }.getMessage should include("Unable to associate port") + + } + + they should "work for extmodules" in { + + class Bar extends ExtModule { + val A = IO(Input(ClockDomain.Type())) + val a = IO(Input(UInt(1.W))) + associate(a, A) + } + + class Foo extends RawModule { + private val bar = Module(new Bar) + } + + ChiselStage.emitCHIRRTL(new Foo).fileCheck() { + """|CHECK: extmodule Bar : + |CHECK-NEXT: input A : Domain of ClockDomain + |CHECK-NEXT: input a : UInt<1> domains [A] + |""".stripMargin + } + + } + + they should "be capable of being forwarded with the domain define operation" in { + + class Foo extends RawModule { + val a = IO(Input(ClockDomain.Type())) + val b = IO(Output(ClockDomain.Type())) + + domain.define(b, a) + } + + ChiselStage.emitCHIRRTL(new Foo).fileCheck() { + """|CHECK: module Foo : + |CHECK: domain_define b = a + |""".stripMargin + } + + } + + behavior of "The associate method" + + it should "error if given zero arguments" in { + + class Foo extends RawModule { + val a = IO(Input(Bool())) + associate(a) + } + + intercept[IllegalArgumentException] { + ChiselStage.elaborate(new Foo) + }.getMessage should include("cannot associate a port with zero domains") + + } + +}