From 152678861fc977d807ec278a60b8b716657a27fa Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Tue, 12 May 2026 18:46:07 -0400 Subject: [PATCH] [core] Allow for domains to use connect operators Allow for domains to be connected using the standard connection operators. This matches behavior for properties and probes which do not support connects in FIRRTL, but instead allow for Chisel connections which then lower to these. This avoids a slightly sharp edge where previously connection operators could be used, e.g., `:<=`, but their use would result in a Chisel runtime error. Assisted-by: pi.dev:claude-opus-4-7 Signed-off-by: Schuyler Eldridge --- core/src/main/scala/chisel3/domain/Type.scala | 2 ++ .../main/scala/chisel3/domain/package.scala | 2 +- .../scala/chisel3/internal/BiConnect.scala | 16 ++++++++++ .../scala/chisel3/internal/MonoConnect.scala | 27 ++++++++++++++++- src/test/scala/chiselTests/DomainSpec.scala | 29 +++++++++++++++++-- 5 files changed, 71 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/chisel3/domain/Type.scala b/core/src/main/scala/chisel3/domain/Type.scala index 6842f331327..54ef8c8b15a 100644 --- a/core/src/main/scala/chisel3/domain/Type.scala +++ b/core/src/main/scala/chisel3/domain/Type.scala @@ -61,6 +61,8 @@ final class Type private[domain] (val domain: Domain) extends Element { self => override def cloneType: this.type = new Type(domain).asInstanceOf[this.type] + override def toString: String = s"${domain.name}.Type" + override def toPrintable: Printable = throwException(s"'domain.Type' does not support hardware printing" + this._errorContext) diff --git a/core/src/main/scala/chisel3/domain/package.scala b/core/src/main/scala/chisel3/domain/package.scala index e10f2613ceb..9834e9b97db 100644 --- a/core/src/main/scala/chisel3/domain/package.scala +++ b/core/src/main/scala/chisel3/domain/package.scala @@ -23,7 +23,7 @@ package object domain { * @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)) + chisel3.internal.MonoConnect.domainDefine(sourceInfo, sink, source, Builder.referenceUserContainer) } /** Unsafe cast to a variadic list of domains. diff --git a/core/src/main/scala/chisel3/internal/BiConnect.scala b/core/src/main/scala/chisel3/internal/BiConnect.scala index 30f205943c6..5f2d8b65f56 100644 --- a/core/src/main/scala/chisel3/internal/BiConnect.scala +++ b/core/src/main/scala/chisel3/internal/BiConnect.scala @@ -116,6 +116,22 @@ private[chisel3] object BiConnect { } pushCommand(DefInvalid(sourceInfo, left_a.lref(sourceInfo))) case (DontCare, right_a: Analog) => connect(sourceInfo, right, left, context_mod) + // Two domains are bi-connected at the root. `domain_define` has no + // direction, so :<=/<> are simply lowered to the same node as `domain.define`. + case (left_d: chisel3.domain.Type, right_d: chisel3.domain.Type) => + // Pick the directional sense using the same rule as Records: if the + // left-hand side can't be a sink, flip so that the sink is writable. + val (sink, source) = + if (!MonoConnect.canBeSink(left_d, context_mod) && MonoConnect.canBeSink(right_d, context_mod)) { + (right_d, left_d) + } else { + (left_d, right_d) + } + try { + MonoConnect.domainDefine(sourceInfo, sink, source, context_mod) + } catch { + case MonoConnectException(message) => throw BiConnectException(message) + } case (left_e: Element, right_e: Element) => { elemConnect(sourceInfo, left_e, right_e, context_mod) // TODO(twigg): Verify the element-level classes are connectable diff --git a/core/src/main/scala/chisel3/internal/MonoConnect.scala b/core/src/main/scala/chisel3/internal/MonoConnect.scala index 51bd4114243..dd83a2b03b1 100644 --- a/core/src/main/scala/chisel3/internal/MonoConnect.scala +++ b/core/src/main/scala/chisel3/internal/MonoConnect.scala @@ -7,7 +7,7 @@ import chisel3._ import chisel3.experimental.{Analog, BaseModule, SourceInfo} import chisel3.internal.binding._ import chisel3.internal.Builder.pushCommand -import chisel3.internal.firrtl.ir.{Block, Connect, DefInvalid, ProbeDefine, PropAssign} +import chisel3.internal.firrtl.ir.{Block, Connect, DefInvalid, DomainDefine, ProbeDefine, PropAssign} import chisel3.internal.firrtl.Converter import chisel3.experimental.dataview.{isView, reify, reifyIdentityView} import chisel3.properties.{Class, Property} @@ -80,6 +80,10 @@ private[chisel3] object MonoConnect { MonoConnectException(s"Source ${formatName(source)} of Probed type cannot participate in a mono connection (:=)") def SinkProbeMonoConnectionException(sink: Data) = MonoConnectException(s"Sink ${formatName(sink)} of Probed type cannot participate in a mono connection (:=)") + def MismatchedDomainException(sink: chisel3.domain.Type, source: chisel3.domain.Type) = + MonoConnectException( + s"Sink (${sink.domain.name}) and Source (${source.domain.name}) are different kinds of domains." + ) /** Check if the argument is visible from current block scope * @@ -155,6 +159,9 @@ private[chisel3] object MonoConnect { elemConnect(sourceInfo, sink_e, source_e, context_mod) case (sink_p: Property[_], source_p: Property[_]) => propConnect(sourceInfo, sink_p, source_p, context_mod) + // Two domains are connected at the root. + case (sink_d: chisel3.domain.Type, source_d: chisel3.domain.Type) => + domainDefine(sourceInfo, sink_d, source_d, context_mod) // Handle Vec case case (sink_v: Vec[Data @unchecked], source_v: Vec[Data @unchecked]) => @@ -456,6 +463,24 @@ private[chisel3] object MonoConnect { } } + def domainDefine( + sourceInfo: SourceInfo, + sink: chisel3.domain.Type, + source: chisel3.domain.Type, + context: BaseModule + ): Unit = { + implicit val info: SourceInfo = sourceInfo + if (sink.domain != source.domain) { + throw MismatchedDomainException(sink, source) + } + checkConnect.checkConnection(sourceInfo, sink, source, context) + context match { + case rm: RawModule => + rm.addCommand(DomainDefine(sourceInfo, sink.lref, source.ref)) + case _ => throwException("Internal Error! Domain connection can only occur within RawModule.") + } + } + def probeDefine( sourceInfo: SourceInfo, sinkProbe: Data, diff --git a/src/test/scala/chiselTests/DomainSpec.scala b/src/test/scala/chiselTests/DomainSpec.scala index 8a96cccfdec..f7b6d5863db 100644 --- a/src/test/scala/chiselTests/DomainSpec.scala +++ b/src/test/scala/chiselTests/DomainSpec.scala @@ -110,23 +110,46 @@ class DomainSpec extends AnyFlatSpec with Matchers with FileCheck { } - they should "be capable of being forwarded with the domain define operation" in { + they should "be capable of being forwarded with the domain define operation or connects" in { class Foo extends RawModule { val a = IO(Input(ClockDomain.Type())) val b = IO(Output(ClockDomain.Type())) domain.define(b, a) + b := a + b <> a + b :<= a + b :<>= a } ChiselStage.emitCHIRRTL(new Foo).fileCheck() { - """|CHECK: module Foo : - |CHECK: domain_define b = a + """|CHECK: module Foo : + |CHECK-COUNT-5: domain_define b = a + |CHECK-NOT: domain_define |""".stripMargin } } + they should "error if connecting two different kinds of domains with :=" in { + + object DomainA extends Domain + object DomainB extends Domain + + class Foo extends RawModule { + val a = IO(Input(DomainA.Type())) + val b = IO(Output(DomainB.Type())) + b := a + } + + val exception = intercept[ChiselException] { + ChiselStage.elaborate(new Foo, Array("--throw-on-first-error")) + } + exception.getMessage should include("are different kinds of domains") + + } + behavior of "The associate method" it should "error if given zero arguments" in {