1
0

Merge pull request #287 from ucb-bar/crossing-take-2

Clock crossing redux
This commit is contained in:
Wesley W. Terpstra 2016-09-13 19:13:21 -07:00 committed by GitHub
commit dfd6bfb454
13 changed files with 442 additions and 190 deletions

View File

@ -0,0 +1,88 @@
// See LICENSE for license details.
package junctions
import Chisel._
object GrayCounter {
def apply(bits: Int, increment: Bool = Bool(true)): UInt = {
val binary = RegInit(UInt(0, width = bits))
val incremented = binary + increment.asUInt()
binary := incremented
incremented ^ (incremented >> UInt(1))
}
}
object AsyncGrayCounter {
def apply(in: UInt, sync: Int): UInt = {
val syncv = RegInit(Vec.fill(sync){UInt(0, width = in.getWidth)})
syncv.last := in
(syncv.init zip syncv.tail).foreach { case (sink, source) => sink := source }
syncv(0)
}
}
class AsyncQueueSource[T <: Data](gen: T, depth: Int, sync: Int, clockIn: Clock, resetIn: Bool)
extends Module(_clock = clockIn, _reset = resetIn) {
val bits = log2Ceil(depth)
val io = new Bundle {
// These come from the source domain
val enq = Decoupled(gen).flip()
// These cross to the sink clock domain
val ridx = UInt(INPUT, width = bits+1)
val widx = UInt(OUTPUT, width = bits+1)
val mem = Vec(depth, gen).asOutput
}
val mem = Reg(Vec(depth, gen))
val widx = GrayCounter(bits+1, io.enq.fire())
val ridx = AsyncGrayCounter(io.ridx, sync)
val ready = widx =/= (ridx ^ UInt(depth | depth >> 1))
val index = if (depth == 1) UInt(0) else io.widx(bits-1, 0) ^ (io.widx(bits, bits) << (bits-1))
when (io.enq.fire() && !reset) { mem(index) := io.enq.bits }
io.enq.ready := RegNext(ready, Bool(false))
io.widx := RegNext(widx, UInt(0))
io.mem := mem
}
class AsyncQueueSink[T <: Data](gen: T, depth: Int, sync: Int, clockIn: Clock, resetIn: Bool)
extends Module(_clock = clockIn, _reset = resetIn) {
val bits = log2Ceil(depth)
val io = new Bundle {
// These come from the sink domain
val deq = Decoupled(gen)
// These cross to the source clock domain
val ridx = UInt(OUTPUT, width = bits+1)
val widx = UInt(INPUT, width = bits+1)
val mem = Vec(depth, gen).asInput
}
val ridx = GrayCounter(bits+1, io.deq.fire())
val widx = AsyncGrayCounter(io.widx, sync)
val valid = ridx =/= widx
// The mux is safe because timing analysis ensures ridx has reached the register
// On an ASIC, changes to the unread location cannot affect the selected value
// On an FPGA, only one input changes at a time => mem updates don't cause glitches
// The register only latches when the selected valued is not being written
val index = if (depth == 1) UInt(0) else ridx(bits-1, 0) ^ (ridx(bits, bits) << (bits-1))
io.deq.bits := RegEnable(io.mem(index), valid && !reset)
io.deq.valid := RegNext(valid, Bool(false))
io.ridx := RegNext(ridx, UInt(0))
}
class AsyncQueue[T <: Data](gen: T, depth: Int = 8, sync: Int = 3) extends Crossing[T] {
require (sync >= 2)
require (depth > 0 && isPow2(depth))
val io = new CrossingIO(gen)
val source = Module(new AsyncQueueSource(gen, depth, sync, io.enq_clock, io.enq_reset))
val sink = Module(new AsyncQueueSink (gen, depth, sync, io.deq_clock, io.deq_reset))
source.io.enq <> io.enq
io.deq <> sink.io.deq
sink.io.mem := source.io.mem
sink.io.widx := source.io.widx
source.io.ridx := sink.io.ridx
}

View File

@ -1,150 +1,52 @@
package junctions
import Chisel._
class Crossing[T <: Data](gen: T, enq_sync: Boolean, deq_sync: Boolean) extends Bundle {
class CrossingIO[T <: Data](gen: T) extends Bundle {
// Enqueue clock domain
val enq_clock = Clock(INPUT)
val enq_reset = Bool(INPUT) // synchronously deasserted wrt. enq_clock
val enq = Decoupled(gen).flip()
// Dequeue clock domain
val deq_clock = Clock(INPUT)
val deq_reset = Bool(INPUT) // synchronously deasserted wrt. deq_clock
val deq = Decoupled(gen)
val enq_clock = if (enq_sync) Some(Clock(INPUT)) else None
val deq_clock = if (deq_sync) Some(Clock(INPUT)) else None
val enq_reset = if (enq_sync) Some(Bool(INPUT)) else None
val deq_reset = if (deq_sync) Some(Bool(INPUT)) else None
}
// Output is 1 for one cycle after any edge of 'in'
object AsyncHandshakePulse {
def apply(in: Bool, sync: Int): Bool = {
val syncv = RegInit(Vec.fill(sync+1){Bool(false)})
syncv.last := in
(syncv.init zip syncv.tail).foreach { case (sink, source) => sink := source }
syncv(0) =/= syncv(1)
abstract class Crossing[T <: Data] extends Module {
val io: CrossingIO[T]
}
class AsyncScope extends Module { val io = new Bundle }
object AsyncScope { def apply() = Module(new AsyncScope) }
object AsyncDecoupledCrossing
{
// takes from_source from the 'from' clock domain and puts it into the 'to' clock domain
def apply[T <: Data](from_clock: Clock, from_reset: Bool, from_source: DecoupledIO[T], to_clock: Clock, to_reset: Bool, depth: Int = 8, sync: Int = 3): DecoupledIO[T] = {
val crossing = Module(new AsyncQueue(from_source.bits, depth, sync)).io
crossing.enq_clock := from_clock
crossing.enq_reset := from_reset
crossing.enq <> from_source
crossing.deq_clock := to_clock
crossing.deq_reset := to_reset
crossing.deq
}
}
class AsyncHandshakeSource[T <: Data](gen: T, sync: Int, clock: Clock, reset: Bool)
extends Module(_clock = clock, _reset = reset) {
val io = new Bundle {
// These come from the source clock domain
val enq = Decoupled(gen).flip()
// These cross to the sink clock domain
val bits = gen.cloneType.asOutput
val push = Bool(OUTPUT)
val pop = Bool(INPUT)
}
val ready = RegInit(Bool(true))
val bits = Reg(gen)
val push = RegInit(Bool(false))
io.enq.ready := ready
io.bits := bits
io.push := push
val pop = AsyncHandshakePulse(io.pop, sync)
assert (!pop || !ready)
when (pop) {
ready := Bool(true)
}
when (io.enq.fire()) {
ready := Bool(false)
bits := io.enq.bits
push := !push
object AsyncDecoupledTo
{
// takes source from your clock domain and puts it into the 'to' clock domain
def apply[T <: Data](to_clock: Clock, to_reset: Bool, source: DecoupledIO[T], depth: Int = 8, sync: Int = 3): DecoupledIO[T] = {
val scope = AsyncScope()
AsyncDecoupledCrossing(scope.clock, scope.reset, source, to_clock, to_reset, depth, sync)
}
}
class AsyncHandshakeSink[T <: Data](gen: T, sync: Int, clock: Clock, reset: Bool)
extends Module(_clock = clock, _reset = reset) {
val io = new Bundle {
// These cross to the source clock domain
val bits = gen.cloneType.asInput
val push = Bool(INPUT)
val pop = Bool(OUTPUT)
// These go to the sink clock domain
val deq = Decoupled(gen)
}
val valid = RegInit(Bool(false))
val bits = Reg(gen)
val pop = RegInit(Bool(false))
io.deq.valid := valid
io.deq.bits := bits
io.pop := pop
val push = AsyncHandshakePulse(io.push, sync)
assert (!push || !valid)
when (push) {
valid := Bool(true)
bits := io.bits
}
when (io.deq.fire()) {
valid := Bool(false)
pop := !pop
}
}
class AsyncHandshake[T <: Data](gen: T, sync: Int = 2) extends Module {
val io = new Crossing(gen, true, true)
require (sync >= 2)
val source = Module(new AsyncHandshakeSource(gen, sync, io.enq_clock.get, io.enq_reset.get))
val sink = Module(new AsyncHandshakeSink (gen, sync, io.deq_clock.get, io.deq_reset.get))
source.io.enq <> io.enq
io.deq <> sink.io.deq
sink.io.bits := source.io.bits
sink.io.push := source.io.push
source.io.pop := sink.io.pop
}
class AsyncDecoupledTo[T <: Data](gen: T, depth: Int = 0, sync: Int = 2) extends Module {
val io = new Crossing(gen, false, true)
// !!! if depth == 0 { use Handshake } else { use AsyncFIFO }
val crossing = Module(new AsyncHandshake(gen, sync)).io
crossing.enq_clock.get := clock
crossing.enq_reset.get := reset
crossing.enq <> io.enq
crossing.deq_clock.get := io.deq_clock.get
crossing.deq_reset.get := io.deq_reset.get
io.deq <> crossing.deq
}
object AsyncDecoupledTo {
// source is in our clock domain, output is in the 'to' clock domain
def apply[T <: Data](to_clock: Clock, to_reset: Bool, source: DecoupledIO[T], depth: Int = 0, sync: Int = 2): DecoupledIO[T] = {
val to = Module(new AsyncDecoupledTo(source.bits, depth, sync))
to.io.deq_clock.get := to_clock
to.io.deq_reset.get := to_reset
to.io.enq <> source
to.io.deq
}
}
class AsyncDecoupledFrom[T <: Data](gen: T, depth: Int = 0, sync: Int = 2) extends Module {
val io = new Crossing(gen, true, false)
// !!! if depth == 0 { use Handshake } else { use AsyncFIFO }
val crossing = Module(new AsyncHandshake(gen, sync)).io
crossing.enq_clock.get := io.enq_clock.get
crossing.enq_reset.get := io.enq_reset.get
crossing.enq <> io.enq
crossing.deq_clock.get := clock
crossing.deq_reset.get := reset
io.deq <> crossing.deq
}
object AsyncDecoupledFrom {
// source is in the 'from' clock domain, output is in our clock domain
def apply[T <: Data](from_clock: Clock, from_reset: Bool, source: DecoupledIO[T], depth: Int = 0, sync: Int = 2): DecoupledIO[T] = {
val from = Module(new AsyncDecoupledFrom(source.bits, depth, sync))
from.io.enq_clock.get := from_clock
from.io.enq_reset.get := from_reset
from.io.enq <> source
from.io.deq
object AsyncDecoupledFrom
{
// takes from_source from the 'from' clock domain and puts it into your clock domain
def apply[T <: Data](from_clock: Clock, from_reset: Bool, from_source: DecoupledIO[T], depth: Int = 8, sync: Int = 3): DecoupledIO[T] = {
val scope = AsyncScope()
AsyncDecoupledCrossing(from_clock, from_reset, from_source, scope.clock, scope.reset, depth, sync)
}
}

View File

@ -706,32 +706,33 @@ class NastiMemoryDemux(nRoutes: Int)(implicit p: Parameters) extends NastiModule
}
}
object AsyncNastiCrossing {
// takes from_source from the 'from' clock domain to the 'to' clock domain
def apply(from_clock: Clock, from_reset: Bool, from_source: NastiIO, to_clock: Clock, to_reset: Bool, depth: Int = 8, sync: Int = 3) = {
val to_sink = Wire(new NastiIO()(from_source.p))
to_sink.aw <> AsyncDecoupledCrossing(from_clock, from_reset, from_source.aw, to_clock, to_reset, depth, sync)
to_sink.ar <> AsyncDecoupledCrossing(from_clock, from_reset, from_source.ar, to_clock, to_reset, depth, sync)
to_sink.w <> AsyncDecoupledCrossing(from_clock, from_reset, from_source.w, to_clock, to_reset, depth, sync)
from_source.b <> AsyncDecoupledCrossing(to_clock, to_reset, to_sink.b, from_clock, from_reset, depth, sync)
from_source.r <> AsyncDecoupledCrossing(to_clock, to_reset, to_sink.r, from_clock, from_reset, depth, sync)
to_sink // is now to_source
}
}
object AsyncNastiTo {
// source(master) is in our clock domain, output is in the 'to' clock domain
def apply[T <: Data](to_clock: Clock, to_reset: Bool, source: NastiIO, depth: Int = 3, sync: Int = 2)(implicit p: Parameters): NastiIO = {
val sink = Wire(new NastiIO)
sink.aw <> AsyncDecoupledTo(to_clock, to_reset, source.aw, depth, sync)
sink.ar <> AsyncDecoupledTo(to_clock, to_reset, source.ar, depth, sync)
sink.w <> AsyncDecoupledTo(to_clock, to_reset, source.w, depth, sync)
source.b <> AsyncDecoupledFrom(to_clock, to_reset, sink.b, depth, sync)
source.r <> AsyncDecoupledFrom(to_clock, to_reset, sink.r, depth, sync)
sink
// takes source from your clock domain and puts it into the 'to' clock domain
def apply(to_clock: Clock, to_reset: Bool, source: NastiIO, depth: Int = 8, sync: Int = 3): NastiIO = {
val scope = AsyncScope()
AsyncNastiCrossing(scope.clock, scope.reset, source, to_clock, to_reset, depth, sync)
}
}
object AsyncNastiFrom {
// source(master) is in the 'from' clock domain, output is in our clock domain
def apply[T <: Data](from_clock: Clock, from_reset: Bool, source: NastiIO, depth: Int = 3, sync: Int = 2)(implicit p: Parameters): NastiIO = {
val sink = Wire(new NastiIO)
sink.aw <> AsyncDecoupledFrom(from_clock, from_reset, source.aw, depth, sync)
sink.ar <> AsyncDecoupledFrom(from_clock, from_reset, source.ar, depth, sync)
sink.w <> AsyncDecoupledFrom(from_clock, from_reset, source.w, depth, sync)
source.b <> AsyncDecoupledTo(from_clock, from_reset, sink.b, depth, sync)
source.r <> AsyncDecoupledTo(from_clock, from_reset, sink.r, depth, sync)
sink
// takes from_source from the 'from' clock domain and puts it into your clock domain
def apply(from_clock: Clock, from_reset: Bool, from_source: NastiIO, depth: Int = 8, sync: Int = 3): NastiIO = {
val scope = AsyncScope()
AsyncNastiCrossing(from_clock, from_reset, from_source, scope.clock, scope.reset, depth, sync)
}
}

View File

@ -39,7 +39,7 @@ class UnitTestSuite(implicit p: Parameters) extends Module {
state := Mux(test_idx === UInt(tests.size - 1), s_done, s_start)
}
val timer = Module(new Timer(100000, tests.size))
val timer = Module(new Timer(500000, tests.size))
timer.io.start.valid := Bool(false)
timer.io.stop.valid := Bool(false)

View File

@ -87,17 +87,17 @@ class JtagDTMWithSync(implicit val p: Parameters) extends Module {
val req_sync = Module (new AsyncMailbox())
val resp_sync = Module (new AsyncMailbox())
req_sync.io.enq := jtag_dtm.io.dtm_req
req_sync.io.enq_clock.get := io.jtag.TCK
req_sync.io.enq_reset.get := io.jtag.TRST
req_sync.io.deq_clock.get := clock
req_sync.io.deq_reset.get := reset
req_sync.io.enq_clock := io.jtag.TCK
req_sync.io.enq_reset := io.jtag.TRST
req_sync.io.deq_clock := clock
req_sync.io.deq_reset := reset
dtm_req := req_sync.io.deq
jtag_dtm.io.dtm_resp := resp_sync.io.deq
resp_sync.io.deq_clock.get := io.jtag.TCK
resp_sync.io.deq_reset.get := io.jtag.TRST
resp_sync.io.enq_clock.get := clock
resp_sync.io.enq_reset.get := reset
resp_sync.io.deq_clock := io.jtag.TCK
resp_sync.io.deq_reset := io.jtag.TRST
resp_sync.io.enq_clock := clock
resp_sync.io.enq_reset := reset
resp_sync.io.enq := dtm_resp
}
}
@ -121,6 +121,5 @@ class AsyncMailbox extends BlackBox {
// this mailbox just has a fixed width of 64 bits, which is enough
// for our specific purpose here.
val io = new Crossing(UInt(width=64), true, true)
val io = new CrossingIO(UInt(width=64))
}

View File

@ -982,22 +982,28 @@ class DebugModule ()(implicit val p:cde.Parameters)
}
object AsyncDebugBusCrossing {
// takes from_source from the 'from' clock domain to the 'to' clock domain
def apply(from_clock: Clock, from_reset: Bool, from_source: DebugBusIO, to_clock: Clock, to_reset: Bool, depth: Int = 3, sync: Int = 2) = {
val to_sink = Wire(new DebugBusIO()(from_source.p))
to_sink.req <> AsyncDecoupledCrossing(from_clock, from_reset, from_source.req, to_clock, to_reset, depth, sync)
from_source.resp <> AsyncDecoupledCrossing(to_clock, to_reset, to_sink.resp, from_clock, from_reset, depth, sync)
to_sink // is now to_source
}
}
object AsyncDebugBusFrom { // OutsideClockDomain
def apply(from_clock: Clock, from_reset: Bool, source: DebugBusIO, depth: Int = 0, sync: Int = 2)(implicit p: Parameters): DebugBusIO = {
val sink = Wire(new DebugBusIO)
sink.req <> AsyncDecoupledFrom(from_clock, from_reset, source.req)
source.resp <> AsyncDecoupledTo(from_clock, from_reset, sink.resp)
sink
// takes from_source from the 'from' clock domain and puts it into your clock domain
def apply(from_clock: Clock, from_reset: Bool, from_source: DebugBusIO, depth: Int = 1, sync: Int = 3): DebugBusIO = {
val scope = AsyncScope()
AsyncDebugBusCrossing(from_clock, from_reset, from_source, scope.clock, scope.reset, depth, sync)
}
}
object AsyncDebugBusTo { // OutsideClockDomain
def apply(to_clock: Clock, to_reset: Bool, source: DebugBusIO, depth: Int = 0, sync: Int = 2)(implicit p: Parameters): DebugBusIO = {
val sink = Wire(new DebugBusIO)
sink.req <> AsyncDecoupledTo(to_clock, to_reset, source.req)
source.resp <> AsyncDecoupledFrom(to_clock, to_reset, sink.resp)
sink
// takes source from your clock domain and puts it into the 'to' clock domain
def apply(to_clock: Clock, to_reset: Bool, source: DebugBusIO, depth: Int = 1, sync: Int = 3): DebugBusIO = {
val scope = AsyncScope()
AsyncDebugBusCrossing(scope.clock, scope.reset, source, to_clock, to_reset, depth, sync)
}
}

View File

@ -22,7 +22,7 @@ class TLBuffer(entries: Int = 2, pipe: Boolean = false) extends LazyModule
if (edgeOut.manager.anySupportAcquire && edgeOut.client.anySupportProbe) {
in .b <> Queue(out.b, entries, pipe)
out.c <> Queue(in .c, entries, pipe)
out.e <> Queue(out.e, entries, pipe)
out.e <> Queue(in .e, entries, pipe)
} else {
in.b.valid := Bool(false)
in.c.ready := Bool(true)

View File

@ -0,0 +1,42 @@
// See LICENSE for license details.
package uncore.tilelink2
import Chisel._
import chisel3.internal.sourceinfo.SourceInfo
import junctions._
class TLAsyncCrossing(depth: Int = 8, sync: Int = 3) extends LazyModule
{
val node = TLIdentityNode()
lazy val module = new LazyModuleImp(this) {
val io = new Bundle {
val in = node.bundleIn
val in_clock = Clock(INPUT)
val in_reset = Bool(INPUT)
val out = node.bundleOut
val out_clock = Clock(INPUT)
val out_reset = Bool(INPUT)
}
// Transfer all TL2 bundles from/to the same domains
((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) =>
out.a <> AsyncDecoupledCrossing(io.in_clock, io.in_reset, in.a, io.out_clock, io.out_reset, depth, sync)
in.d <> AsyncDecoupledCrossing(io.out_clock, io.out_reset, out.d, io.in_clock, io.in_reset, depth, sync)
if (edgeOut.manager.anySupportAcquire && edgeOut.client.anySupportProbe) {
in.b <> AsyncDecoupledCrossing(io.out_clock, io.out_reset, out.b, io.in_clock, io.in_reset, depth, sync)
out.c <> AsyncDecoupledCrossing(io.in_clock, io.in_reset, in.c, io.out_clock, io.out_reset, depth, sync)
out.e <> AsyncDecoupledCrossing(io.in_clock, io.in_reset, in.e, io.out_clock, io.out_reset, depth, sync)
} else {
in.b.valid := Bool(false)
in.c.ready := Bool(true)
in.e.ready := Bool(true)
out.b.ready := Bool(true)
out.c.valid := Bool(false)
out.e.valid := Bool(false)
}
}
}
}

View File

@ -206,6 +206,15 @@ class TLFuzzer(
}
}
class ClockDivider extends BlackBox {
val io = new Bundle {
val clock_in = Clock(INPUT)
val reset_in = Bool(INPUT)
val clock_out = Clock(OUTPUT)
val reset_out = Bool(OUTPUT)
}
}
class TLFuzzRAM extends LazyModule
{
val model = LazyModule(new TLRAMModel)
@ -213,14 +222,29 @@ class TLFuzzRAM extends LazyModule
val gpio = LazyModule(new RRTest1(0x400))
val xbar = LazyModule(new TLXbar)
val fuzz = LazyModule(new TLFuzzer(5000))
val cross = LazyModule(new TLAsyncCrossing)
model.node := fuzz.node
xbar.node := TLWidthWidget(TLHintHandler(model.node), 16)
ram.node := TLFragmenter(TLBuffer(xbar.node), 4, 256)
cross.node := TLFragmenter(TLBuffer(xbar.node), 4, 256)
ram.node := cross.node
gpio.node := TLFragmenter(TLBuffer(xbar.node), 4, 32)
lazy val module = new LazyModuleImp(this) with HasUnitTestIO {
io.finished := fuzz.module.io.finished
// Shove the RAM into another clock domain
val clocks = Module(new ClockDivider)
ram.module.clock := clocks.io.clock_out
ram.module.reset := clocks.io.reset_out
clocks.io.clock_in := clock
clocks.io.reset_in := reset
// ... and safely cross TL2 into it
cross.module.io.in_clock := clock
cross.module.io.in_reset := reset
cross.module.io.out_clock := clocks.io.clock_out
cross.module.io.out_reset := clocks.io.reset_out
}
}

View File

@ -17,6 +17,12 @@ object RegReadFn
// effects must become visible on the cycle after ovalid && oready
implicit def apply(x: (Bool, Bool) => (Bool, Bool, UInt)) =
new RegReadFn(false, x)
implicit def apply(x: RegisterReadIO[UInt]): RegReadFn =
RegReadFn((ivalid, oready) => {
x.request.valid := ivalid
x.response.ready := oready
(x.request.ready, x.response.valid, x.response.bits)
})
// (ready: Bool) => (valid: Bool, data: UInt)
// valid must not combinationally depend on ready
// valid must eventually go high without requiring ready to go high
@ -47,6 +53,13 @@ object RegWriteFn
// effects must become visible on the cycle after ovalid && oready
implicit def apply(x: (Bool, Bool, UInt) => (Bool, Bool)) =
new RegWriteFn(false, x)
implicit def apply(x: RegisterWriteIO[UInt]): RegWriteFn =
RegWriteFn((ivalid, oready, data) => {
x.request.valid := ivalid
x.request.bits := data
x.response.ready := oready
(x.request.ready, x.response.valid)
})
// (valid: Bool, data: UInt) => (ready: Bool)
// ready may combinationally depend on data (but not valid)
// ready must eventually go high without requiring valid to go high

View File

@ -0,0 +1,130 @@
// See LICENSE for license details.
package uncore.tilelink2
import Chisel._
import junctions._
// A very simple flow control state machine, run in the specified clock domain
class BusyRegisterCrossing(clock: Clock, reset: Bool)
extends Module(_clock = clock, _reset = reset) {
val io = new Bundle {
val progress = Bool(INPUT)
val request_valid = Bool(INPUT)
val response_ready = Bool(INPUT)
val busy = Bool(OUTPUT)
}
val busy = RegInit(Bool(false))
when (io.progress) {
busy := Mux(busy, !io.response_ready, io.request_valid)
}
io.busy := busy
}
// RegField should support connecting to one of these
class RegisterWriteIO[T <: Data](gen: T) extends Bundle {
val request = Decoupled(gen).flip()
val response = Decoupled(Bool()) // ignore .bits
}
// To turn on/off a domain:
// 1. lower allow on the other side
// 2. wait for inflight traffic to resolve
// 3. turn off the domain
// 4. assert reset in the domain
// 5. turn on the domain
// 6. deassert reset in the domain
// 7. raise allow on the other side
class RegisterWriteCrossingIO[T <: Data](gen: T) extends Bundle {
// Master clock domain
val master_clock = Clock(INPUT)
val master_reset = Bool(INPUT)
val master_allow = Bool(INPUT) // actually wait for the slave
val master_port = new RegisterWriteIO(gen)
// Slave clock domain
val slave_clock = Clock(INPUT)
val slave_reset = Bool(INPUT)
val slave_allow = Bool(INPUT) // honour requests from the master
val slave_register = gen.asOutput
}
class RegisterWriteCrossing[T <: Data](gen: T, sync: Int = 3) extends Module {
val io = new RegisterWriteCrossingIO(gen)
// The crossing must only allow one item inflight at a time
val crossing = Module(new AsyncQueue(gen, 1, sync))
// We can just randomly reset one-side of a single entry AsyncQueue.
// If the enq side is reset, at worst deq.bits is reassigned from mem(0), which stays fixed.
// If the deq side is reset, at worst the master rewrites mem(0) once, deq.bits stays fixed.
crossing.io.enq_clock := io.master_clock
crossing.io.enq_reset := io.master_reset || !io.master_allow
crossing.io.deq_clock := io.slave_clock
crossing.io.deq_reset := io.slave_reset || !io.slave_allow
crossing.io.enq.bits := io.master_port.request.bits
io.slave_register := crossing.io.deq.bits
// If the slave is not operational, just drop the write.
val progress = crossing.io.enq.ready || !io.master_allow
val reg = Module(new BusyRegisterCrossing(io.master_clock, io.master_reset))
reg.io.progress := progress
reg.io.request_valid := io.master_port.request.valid
reg.io.response_ready := io.master_port.response.ready
crossing.io.deq.ready := Bool(true)
crossing.io.enq.valid := io.master_port.request.valid && !reg.io.busy
io.master_port.request.ready := progress && !reg.io.busy
io.master_port.response.valid := progress && reg.io.busy
}
// RegField should support connecting to one of these
class RegisterReadIO[T <: Data](gen: T) extends Bundle {
val request = Decoupled(Bool()).flip() // ignore .bits
val response = Decoupled(gen)
}
class RegisterReadCrossingIO[T <: Data](gen: T) extends Bundle {
// Master clock domain
val master_clock = Clock(INPUT)
val master_reset = Bool(INPUT)
val master_allow = Bool(INPUT) // actually wait for the slave
val master_port = new RegisterReadIO(gen)
// Slave clock domain
val slave_clock = Clock(INPUT)
val slave_reset = Bool(INPUT)
val slave_allow = Bool(INPUT) // honour requests from the master
val slave_register = gen.asInput
}
class RegisterReadCrossing[T <: Data](gen: T, sync: Int = 3) extends Module {
val io = new RegisterReadCrossingIO(gen)
// The crossing must only allow one item inflight at a time
val crossing = Module(new AsyncQueue(gen, 1, sync))
// We can just randomly reset one-side of a single entry AsyncQueue.
// If the enq side is reset, at worst deq.bits is reassigned from mem(0), which stays fixed.
// If the deq side is reset, at worst the slave rewrites mem(0) once, deq.bits stays fixed.
crossing.io.enq_clock := io.slave_clock
crossing.io.enq_reset := io.slave_reset || !io.slave_allow
crossing.io.deq_clock := io.master_clock
crossing.io.deq_reset := io.master_reset || !io.master_allow
crossing.io.enq.bits := io.slave_register
io.master_port.response.bits := crossing.io.deq.bits
// If the slave is not operational, just repeat the last value we saw.
val progress = crossing.io.deq.valid || !io.master_allow
val reg = Module(new BusyRegisterCrossing(io.master_clock, io.master_reset))
reg.io.progress := progress
reg.io.request_valid := io.master_port.request.valid
reg.io.response_ready := io.master_port.response.ready
io.master_port.response.valid := progress && reg.io.busy
io.master_port.request.ready := progress && !reg.io.busy
crossing.io.deq.ready := io.master_port.request.valid && !reg.io.busy
crossing.io.enq.valid := Bool(true)
}

View File

@ -212,9 +212,37 @@ trait RRTest1Bundle
{
}
trait RRTest1Module extends HasRegMap
trait RRTest1Module extends Module with HasRegMap
{
regmap(RRTest1Map.map:_*)
val clocks = Module(new ClockDivider)
clocks.io.clock_in := clock
clocks.io.reset_in := reset
def x(bits: Int) = {
val field = UInt(width = bits)
val readCross = Module(new RegisterReadCrossing(field))
readCross.io.master_clock := clock
readCross.io.master_reset := reset
readCross.io.master_allow := Bool(true)
readCross.io.slave_clock := clocks.io.clock_out
readCross.io.slave_reset := clocks.io.reset_out
readCross.io.slave_allow := Bool(true)
val writeCross = Module(new RegisterWriteCrossing(field))
writeCross.io.master_clock := clock
writeCross.io.master_reset := reset
writeCross.io.master_allow := Bool(true)
writeCross.io.slave_clock := clocks.io.clock_out
writeCross.io.slave_reset := clocks.io.reset_out
writeCross.io.slave_allow := Bool(true)
readCross.io.slave_register := writeCross.io.slave_register
RegField(bits, readCross.io.master_port, writeCross.io.master_port)
}
val map = RRTest1Map.map.drop(1) ++ Seq(0 -> Seq(x(8), x(8), x(8), x(8)))
regmap(map:_*)
}
class RRTest1(address: BigInt) extends TLRegisterRouter(address, 0, 32, Some(6), 4)(

19
vsrc/ClockDivider.v Normal file
View File

@ -0,0 +1,19 @@
// You can't divide clocks in Chisel
module ClockDivider(
input clock_in,
input reset_in,
output clock_out,
output reset_out
);
reg [2:0] shift = 3'b001;
always @(posedge clock_in)
begin
shift <= {shift[0], shift[2:1]};
end
assign reset_out = reset_in;
assign clock_out = shift[0];
endmodule