Skip to content

Conversation

seldridge
Copy link
Member

@seldridge seldridge commented Oct 1, 2025

Add support for creating domains and domain types. Add a built-in
ClockDomain type.

This is very basic support initially and many things will not work, namely
connections or compilation of these to anything meaningful.

Release Notes

Add initial support for creating domains. These are intended to model things like clock, reset, and power domains and their associations to ports in the design.

@seldridge seldridge added the Feature New feature, will be included in release notes label Oct 1, 2025
@seldridge seldridge force-pushed the dev/seldridge/domains branch 2 times, most recently from ce1f528 to 74f1910 Compare October 15, 2025 20:14
Extend the allowed binary incompatibility for more problems in
`chisel3.internal.firrtl.ir*` and the same set of problems in
`firrtl.ir*`.  This is in support of allowing breakages that are necessary
to represent domain associations.

Signed-off-by: Schuyler Eldridge <[email protected]>
@seldridge seldridge force-pushed the dev/seldridge/domains branch from 74f1910 to e40332d Compare October 15, 2025 20:28
Add support for creating domains and domain types.  Add a built-in
ClockDomain type.

This is very basic support initially and many things will not work, namely
connections or compilation of these to anything meaningful.

Signed-off-by: Schuyler Eldridge <[email protected]>
@seldridge seldridge force-pushed the dev/seldridge/domains branch from e40332d to 92af846 Compare October 15, 2025 21:04
@seldridge seldridge marked this pull request as ready for review October 15, 2025 21:04
@seldridge seldridge requested a review from jackkoenig October 15, 2025 21:04
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())
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
private[chisel3] def name: String = simpleClassName(this.getClass())
private[chisel3] def name: String = simpleClassName(this.getClass)

Nit but we typically call .getClass without ().

Comment on lines +581 to +582
protected[chisel3] def getAssociations: scala.collection.immutable.Map[Data, LinkedHashSet[Data]] =
_associations.toMap
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this really needed or can we just use _associations?

Builder.error(s"""Unable to associate port '$port' to domains '${allDomains.mkString(
", "
)}' because the port does not exist in this module""")(si)
println(getModulePorts)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
println(getModulePorts)

stray println

Comment on lines +622 to +624
Builder.error(s"""Unable to associate port '$port' to domains '${allDomains.mkString(
", "
)}' because the port does not exist in this module""")(si)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is getting formatted weirdly, can you massage it a bit, e.g. assign the result of allDomains.mkString to a val.

Comment on lines +32 to 38
private def legalize(name: String): String = {
name match {
// If the name starts with a digit, then escape it with backticks.
case _ if name.head.isDigit => s"`$name`"
case _ => name
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This change feels pointless but also is fine

require(_closed, "Can't get ports before module close")
_ports.toSeq
_ports.toSeq.map { case (port, info) =>
(port, info, _associations.get(port).map(_.toSeq).getOrElse(Seq.empty[Data]))
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
(port, info, _associations.get(port).map(_.toSeq).getOrElse(Seq.empty[Data]))
(port, info, _associations.get(port).fold(Seq.empty[Data])(_.toSeq)

I personally have taken to .fold(default)(f) instead of .map(f).getOrElse(default) but I'll defer to you on which you think is better.

protected def portsSize: Int = _ports.size

/* Associate a port of this module with one or more domains. */
final def associate(port: Data, domain: Data, domains: Data*)(implicit si: SourceInfo): Unit = {
Copy link
Contributor

Choose a reason for hiding this comment

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

So we've often used this T, T* as a trick to ensure one or more, but it makes certain things really awkward, e.g. trying to work with a Seq, you have to do:

associate(port, myDomains(0), myDomains.tail: _*)

I think it would be better to overload with:

final def associate(port: Data, domain: Data)(implicit si: SourceInfo): Unit = associate(port, Seq(domain))
final def associate(port: Data, domains: Seq[Data])(implicit si: SourceInfo): Unit = ...

Or just have a single version with varags and accept it's a runtime error if called with no domains?

Or I guess you could overload with

final def associate(port: Data, domain: Data, domains: Data*)(implicit si: SourceInfo): Unit = associate(port, Seq(domain) ++ domains)
final def associate(port: Data, domains: Seq[Data])(implicit si: SourceInfo): Unit = ...

/** A [[Data]] that is used to communicate information of a specific domain
* kind.
*/
final class Type private (val domain: Domain) extends Element { self =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Does a domain.Type really need to be an Element or Data at all?

Copy link
Member Author

Choose a reason for hiding this comment

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

They do need to be ports, so I think it would either be changing the type that IO accepts or adding a new IO creation method just for domain.Type. Either of these is doable.

The more critical thing would be that I expect libraries that assume everything in Chisel can be shoved into a Wire, like diplomacy, will want to put domain.Type into a wire. We might be able to avoid this with changes to libraries so that they don't use a single Wire. (This is related to what I was talking to you offline about assuming everything can go into a Bundle is making things hard.) WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I see, then I think Element is right. If these need to be ports and wired through then I think they run into the same needs as other things like Properties. As in the larger conversation about Bundles, maybe there's a better way out there but there has to be some way to compose these things together and Bundle (well Record) remains the only existing avenue.

Comment on lines +31 to +32
val A = IO(Input(domain.Type(ClockDomain)))
val B = IO(Input(domain.Type(PowerDomain)))
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels a bit inconsistent with the rest of Chisel, what about:

Suggested change
val A = IO(Input(domain.Type(ClockDomain)))
val B = IO(Input(domain.Type(PowerDomain)))
val A = IO(Input(ClockDomain()))
val B = IO(Input(PowerDomain()))

or perhaps

Suggested change
val A = IO(Input(domain.Type(ClockDomain)))
val B = IO(Input(domain.Type(PowerDomain)))
val A = IO(Input(ClockDomain.Type()))
val B = IO(Input(PowerDomain.Type()))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature New feature, will be included in release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants