Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions core/src/main/scala/chisel3/domain/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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: _*) }
}

}
102 changes: 102 additions & 0 deletions src/test/scala/chiselTests/DomainSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading