diff --git a/core/src/main/scala/chisel3/domain/package.scala b/core/src/main/scala/chisel3/domain/package.scala index e10f2613ceb..9ddd64a0501 100644 --- a/core/src/main/scala/chisel3/domain/package.scala +++ b/core/src/main/scala/chisel3/domain/package.scala @@ -5,6 +5,7 @@ package chisel3 import chisel3.internal.Builder import chisel3.internal.firrtl.ir import chisel3.experimental.SourceInfo +import chisel3.reflect.DataMirror package object domain { @@ -42,4 +43,64 @@ package object domain { ) } + /** Connect the aligned elements of [[source]] to the aligned elements of [[sink]], with each + * aligned [[source]] element cast to [[domains]]. + * + * This is the aligned-direction half of a bidirectional unsafe domain cast. Pair with + * [[unsafeConnectFlipped]] to replace `sink :<>= source` across a domain boundary: + * {{{ + * domain.unsafeConnectAligned(y, x, B) // aligned: x -> y, cast to B + * domain.unsafeConnectFlipped(y, x, A) // flipped: y -> x, cast to A + * }}} + * + * @param sink the Data whose aligned leaves are driven + * @param source the Data whose aligned leaves are the sources (each cast to domains) + * @param domains variadic list of domains to cast each source element to + */ + def unsafeConnectAligned[A <: Data]( + sink: A, + source: A, + domains: domain.Type* + )( + implicit sourceInfo: SourceInfo + ): Unit = { + val sinkElts = DataMirror.collectAlignedDeep(sink) { case e: Element => e } + val sourceElts = DataMirror.collectAlignedDeep(source) { case e: Element => e } + require( + sinkElts.length == sourceElts.length, + s"unsafeConnectAligned: sink has ${sinkElts.length} aligned element(s) but source has ${sourceElts.length}" + ) + sinkElts.zip(sourceElts).foreach { case (lhs, rhs) => lhs :<= domain.unsafeCast(rhs, domains: _*) } + } + + /** Connect the flipped elements of [[source]] to the flipped elements of [[sink]], with each + * flipped [[sink]] element cast to [[domains]]. + * + * This is the flipped-direction half of a bidirectional unsafe domain cast. Pair with + * [[unsafeConnectAligned]] to replace `sink :<>= source` across a domain boundary: + * {{{ + * domain.unsafeConnectAligned(y, x, B) // aligned: x -> y, cast to B + * domain.unsafeConnectFlipped(y, x, A) // flipped: y -> x, cast to A + * }}} + * + * @param sink the Data whose flipped leaves are the sources (each cast to domains) + * @param source the Data whose flipped leaves are driven + * @param domains variadic list of domains to cast each flipped sink element to + */ + def unsafeConnectFlipped[A <: Data]( + sink: A, + source: A, + domains: domain.Type* + )( + implicit sourceInfo: SourceInfo + ): Unit = { + val sinkElts = DataMirror.collectFlippedDeep(sink) { case e: Element => e } + val sourceElts = DataMirror.collectFlippedDeep(source) { case e: Element => e } + require( + sinkElts.length == sourceElts.length, + s"unsafeConnectFlipped: sink has ${sinkElts.length} flipped element(s) but source has ${sourceElts.length}" + ) + sinkElts.zip(sourceElts).foreach { case (rhs, lhs) => lhs :<= domain.unsafeCast(rhs, domains: _*) } + } + } diff --git a/src/test/scala/chiselTests/DomainSpec.scala b/src/test/scala/chiselTests/DomainSpec.scala index 42b415708c4..f29cbeb0dc3 100644 --- a/src/test/scala/chiselTests/DomainSpec.scala +++ b/src/test/scala/chiselTests/DomainSpec.scala @@ -215,6 +215,108 @@ class DomainSpec extends AnyFlatSpec with Matchers with FileCheck { } + behavior of "unsafeConnectAligned and unsafeConnectFlipped" + + they should "perform a bidirectional domain cast for a mixed-direction Bundle" in { + class Bar extends Bundle { + val a = UInt(1.W) + val b = Flipped(UInt(1.W)) + } + + class Foo extends RawModule { + val A = IO(Input(ClockDomain.Type())) + val B = IO(Input(ClockDomain.Type())) + val x = IO(Flipped(new Bar)) + val y = IO(new Bar) + + associate(x, A) + associate(y, B) + + domain.unsafeConnectAligned(y, x, B) + domain.unsafeConnectFlipped(y, x, A) + } + + ChiselStage.emitCHIRRTL(new Foo).fileCheck() { + """|CHECK: node [[n1:.*]] = unsafe_domain_cast(x.a, B) + |CHECK: connect y.a, [[n1]] + |CHECK: node [[n2:.*]] = unsafe_domain_cast(y.b, A) + |CHECK: connect x.b, [[n2]] + |""".stripMargin + } + } + + they should "error if sink and source have different numbers of aligned elements" in { + class Sink extends Bundle { + val a = UInt(1.W) + val b = UInt(1.W) + } + class Source extends Bundle { + val a = UInt(1.W) + } + + class Foo extends RawModule { + val B = IO(Input(ClockDomain.Type())) + val x = IO(Input(new Source)) + val y = IO(Output(new Sink)) + + intercept[IllegalArgumentException] { + domain.unsafeConnectAligned(y, x, B) + }.getMessage should include("unsafeConnectAligned") + } + + ChiselStage.elaborate(new Foo) + } + + they should "error if sink and source have different numbers of flipped elements" in { + class Sink extends Bundle { + val a = Flipped(UInt(1.W)) + val b = Flipped(UInt(1.W)) + } + class Source extends Bundle { + val a = Flipped(UInt(1.W)) + } + + class Foo extends RawModule { + val A = IO(Input(ClockDomain.Type())) + val x = IO(new Source) + val y = IO(new Sink) + + intercept[IllegalArgumentException] { + domain.unsafeConnectFlipped(y, x, A) + }.getMessage should include("unsafeConnectFlipped") + } + + ChiselStage.elaborate(new Foo) + } + + they should "work for a fully-aligned Bundle (unsafeConnectFlipped is a no-op)" in { + class Bar extends Bundle { + val a = UInt(1.W) + val b = UInt(1.W) + } + + class Foo extends RawModule { + val A = IO(Input(ClockDomain.Type())) + val B = IO(Input(ClockDomain.Type())) + val x = IO(Input(new Bar)) + val y = IO(Output(new Bar)) + + associate(x, A) + associate(y, B) + + domain.unsafeConnectAligned(y, x, B) + domain.unsafeConnectFlipped(y, x, A) + } + + ChiselStage.emitCHIRRTL(new Foo).fileCheck() { + """|CHECK-DAG: node {{.*}} = unsafe_domain_cast(x.a, B) + |CHECK-DAG: node {{.*}} = unsafe_domain_cast(x.b, B) + |CHECK-DAG: connect y.a, + |CHECK-DAG: connect y.b, + |""".stripMargin + } + } + behavior of "domain subfield access" it should "allow accessing fields of a domain port" in {