From 8f3c2ddfc31edb487f567877730ad490c97c69a6 Mon Sep 17 00:00:00 2001 From: "Wesley W. Terpstra" Date: Fri, 14 Oct 2016 16:54:09 -0700 Subject: [PATCH 1/4] tilelink2 Crossing: these asserts should be done by the AsyncQueue --- src/main/scala/uncore/tilelink2/Crossing.scala | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/scala/uncore/tilelink2/Crossing.scala b/src/main/scala/uncore/tilelink2/Crossing.scala index 3e81d926..590cbc13 100644 --- a/src/main/scala/uncore/tilelink2/Crossing.scala +++ b/src/main/scala/uncore/tilelink2/Crossing.scala @@ -25,15 +25,10 @@ class TLAsyncCrossingSource(sync: Int = 3) extends LazyModule out.a <> ToAsyncBundle(in.a, depth, sync) in.d <> FromAsyncBundle(out.d, sync) - assert (!in.a.valid || sink_reset_n, "A channel request sent to a missing manager") - if (bce) { in.b <> FromAsyncBundle(out.b, sync) out.c <> ToAsyncBundle(in.c, depth, sync) out.e <> ToAsyncBundle(in.e, depth, sync) - - assert (!in.c.valid || sink_reset_n, "C channel response sent to a missing manager") - assert (!in.e.valid || sink_reset_n, "E channel response sent to a missing manager") } else { in.b.valid := Bool(false) in.c.ready := Bool(true) @@ -63,14 +58,10 @@ class TLAsyncCrossingSink(depth: Int = 8, sync: Int = 3) extends LazyModule out.a <> FromAsyncBundle(in.a, sync) in.d <> ToAsyncBundle(out.d, depth, sync) - assert (!out.d.valid || source_reset_n, "D channel respose sent to missing client") - if (bce) { in.b <> ToAsyncBundle(out.b, depth, sync) out.c <> FromAsyncBundle(in.c, sync) out.e <> FromAsyncBundle(in.e, sync) - - assert (!out.b.valid || source_reset_n, "B channel request sent to missing client") } else { in.b.widx := UInt(0) in.c.ridx := UInt(0) From ac0bb841da0cb8c3416082b65568844d6d1ad4f4 Mon Sep 17 00:00:00 2001 From: "Wesley W. Terpstra" Date: Fri, 14 Oct 2016 18:05:35 -0700 Subject: [PATCH 2/4] AsyncQueue: cope with far reset propagation delay --- src/main/scala/uncore/tilelink2/Bundles.scala | 8 +- src/main/scala/util/AsyncQueue.scala | 91 +++++++++++++------ 2 files changed, 70 insertions(+), 29 deletions(-) diff --git a/src/main/scala/uncore/tilelink2/Bundles.scala b/src/main/scala/uncore/tilelink2/Bundles.scala index c6e79f7c..0122b3d5 100644 --- a/src/main/scala/uncore/tilelink2/Bundles.scala +++ b/src/main/scala/uncore/tilelink2/Bundles.scala @@ -222,9 +222,11 @@ object TLBundleSnoop final class AsyncBundle[T <: Data](val depth: Int, gen: T) extends Bundle { require (isPow2(depth)) + val mem = Vec(depth, gen) val ridx = UInt(width = log2Up(depth)+1).flip val widx = UInt(width = log2Up(depth)+1) - val mem = Vec(depth, gen) + val ridx_valid = Bool().flip + val widx_valid = Bool() val source_reset_n = Bool() val sink_reset_n = Bool().flip @@ -236,7 +238,9 @@ object FromAsyncBundle def apply[T <: Data](x: AsyncBundle[T], sync: Int = 3): DecoupledIO[T] = { val sink = Module(new AsyncQueueSink(x.mem(0), x.depth, sync)) x.ridx := sink.io.ridx + x.ridx_valid := sink.io.ridx_valid sink.io.widx := x.widx + sink.io.widx_valid := x.widx_valid sink.io.mem := x.mem sink.io.source_reset_n := x.source_reset_n x.sink_reset_n := !sink.reset @@ -257,8 +261,10 @@ object ToAsyncBundle x.ready := source.io.enq.ready val out = Wire(new AsyncBundle(depth, x.bits)) source.io.ridx := out.ridx + source.io.ridx_valid := out.ridx_valid out.mem := source.io.mem out.widx := source.io.widx + out.widx_valid := source.io.widx_valid source.io.sink_reset_n := out.sink_reset_n out.source_reset_n := !source.reset out diff --git a/src/main/scala/util/AsyncQueue.scala b/src/main/scala/util/AsyncQueue.scala index a347b62f..0af2067f 100644 --- a/src/main/scala/util/AsyncQueue.scala +++ b/src/main/scala/util/AsyncQueue.scala @@ -27,7 +27,15 @@ object UIntSyncChain { } } -class AsyncQueueSource[T <: Data](gen: T, depth: Int, sync: Int) extends Module { +class AsyncValidSync(sync: Int, desc: String) extends Module { + val io = new Bundle { + val in = Bool(INPUT) + val out = Bool(OUTPUT) + } + io.out := UIntSyncChain(io.in.asUInt, sync, desc)(0) +} + +class AsyncQueueSource[T <: Data](gen: T, depth: Int, sync: Int, safe: Boolean = true) extends Module { val bits = log2Ceil(depth) val io = new Bundle { // These come from the source domain @@ -36,17 +44,15 @@ class AsyncQueueSource[T <: Data](gen: T, depth: Int, sync: Int) extends Module val ridx = UInt(INPUT, width = bits+1) val widx = UInt(OUTPUT, width = bits+1) val mem = Vec(depth, gen).asOutput - // Reset for the other side - val sink_reset_n = Bool().flip + // Signals used to self-stabilize a safe AsyncQueue + val sink_reset_n = Bool(INPUT) + val ridx_valid = Bool(INPUT) + val widx_valid = Bool(OUTPUT) } - // extend the sink reset to a full cycle (assertion latency <= 1 cycle) - val catch_sink_reset_n = AsyncResetReg(Bool(true), clock, !io.sink_reset_n, "catch_sink_reset_n") - // reset_n has a 1 cycle shorter path to ready than ridx does - val sink_reset_n = UIntSyncChain(catch_sink_reset_n.asUInt, sync, "sink_reset_n")(0) - - val mem = Reg(Vec(depth, gen)) //This does NOT need to be asynchronously reset. - val widx = GrayCounter(bits+1, io.enq.fire(), !sink_reset_n, "widx_bin") + val sink_ready = Wire(init = Bool(true)) + val mem = Reg(Vec(depth, gen)) // This does NOT need to be reset at all. + val widx = GrayCounter(bits+1, io.enq.fire(), !sink_ready, "widx_bin") val ridx = UIntSyncChain(io.ridx, sync, "ridx_gray") val ready = widx =/= (ridx ^ UInt(depth | depth >> 1)) @@ -54,18 +60,33 @@ class AsyncQueueSource[T <: Data](gen: T, depth: Int, sync: Int) extends Module when (io.enq.fire()) { mem(index) := io.enq.bits } val ready_reg = AsyncResetReg(ready.asUInt, "ready_reg")(0) - io.enq.ready := ready_reg && sink_reset_n + io.enq.ready := ready_reg && sink_ready val widx_reg = AsyncResetReg(widx, "widx_gray") io.widx := widx_reg io.mem := mem - // It is a fatal error to reset half a Queue while it still has data - assert (sink_reset_n || widx === ridx) + io.widx_valid := Bool(true) + if (safe) { + val source_valid = Module(new AsyncValidSync(sync+1, "source_valid")) + val sink_extend = Module(new AsyncValidSync(1, "sink_extend")) + val sink_valid = Module(new AsyncValidSync(sync, "sink_valid")) + source_valid.reset := reset || !io.sink_reset_n + sink_extend .reset := reset || !io.sink_reset_n + + source_valid.io.in := Bool(true) + io.widx_valid := source_valid.io.out + sink_extend.io.in := io.ridx_valid + sink_valid.io.in := sink_extend.io.out + sink_ready := sink_valid.io.out + + assert (io.sink_reset_n || !sink_ready || !io.enq.valid, "Enque while sink is reset and AsyncQueueSource is unprotected") + assert (io.sink_reset_n || widx === ridx, "Sink reset while AsyncQueueSource not empty") + } } -class AsyncQueueSink[T <: Data](gen: T, depth: Int, sync: Int) extends Module { +class AsyncQueueSink[T <: Data](gen: T, depth: Int, sync: Int, safe: Boolean = true) extends Module { val bits = log2Ceil(depth) val io = new Bundle { // These come from the sink domain @@ -74,16 +95,14 @@ class AsyncQueueSink[T <: Data](gen: T, depth: Int, sync: Int) extends Module { val ridx = UInt(OUTPUT, width = bits+1) val widx = UInt(INPUT, width = bits+1) val mem = Vec(depth, gen).asInput - // Reset for the other side - val source_reset_n = Bool().flip + // Signals used to self-stabilize a safe AsyncQueue + val source_reset_n = Bool(INPUT) + val ridx_valid = Bool(OUTPUT) + val widx_valid = Bool(INPUT) } - // extend the source reset to a full cycle (assertion latency <= 1 cycle) - val catch_source_reset_n = AsyncResetReg(Bool(true), clock, !io.source_reset_n, "catch_source_reset_n") - // reset_n has a 1 cycle shorter path to valid than widx does - val source_reset_n = UIntSyncChain(catch_source_reset_n.asUInt, sync, "source_reset_n")(0) - - val ridx = GrayCounter(bits+1, io.deq.fire(), !source_reset_n, "ridx_bin") + val source_ready = Wire(init = Bool(true)) + val ridx = GrayCounter(bits+1, io.deq.fire(), !source_ready, "ridx_bin") val widx = UIntSyncChain(io.widx, sync, "widx_gray") val valid = ridx =/= widx @@ -99,22 +118,36 @@ class AsyncQueueSink[T <: Data](gen: T, depth: Int, sync: Int) extends Module { io.deq.bits := RegEnable(io.mem(index), valid) val valid_reg = AsyncResetReg(valid.asUInt, "valid_reg")(0) - io.deq.valid := valid_reg && source_reset_n + io.deq.valid := valid_reg && source_ready val ridx_reg = AsyncResetReg(ridx, "ridx_gray") io.ridx := ridx_reg - // It is a fatal error to reset half a Queue while it still has data - assert (source_reset_n || widx === ridx) + io.ridx_valid := Bool(true) + if (safe) { + val sink_valid = Module(new AsyncValidSync(sync+1, "sink_valid")) + val source_extend = Module(new AsyncValidSync(1, "source_extend")) + val source_valid = Module(new AsyncValidSync(sync, "source_valid")) + sink_valid .reset := reset || !io.source_reset_n + source_extend.reset := reset || !io.source_reset_n + + sink_valid.io.in := Bool(true) + io.ridx_valid := sink_valid.io.out + source_extend.io.in := io.widx_valid + source_valid.io.in := source_extend.io.out + source_ready := source_valid.io.out + + assert (io.source_reset_n || widx === ridx, "Source reset while AsyncQueueSink not empty") + } } -class AsyncQueue[T <: Data](gen: T, depth: Int = 8, sync: Int = 3) extends Crossing[T] { +class AsyncQueue[T <: Data](gen: T, depth: Int = 8, sync: Int = 3, safe: Boolean = true) extends Crossing[T] { require (sync >= 2) require (depth > 0 && isPow2(depth)) val io = new CrossingIO(gen) - val source = Module(new AsyncQueueSource(gen, depth, sync)) - val sink = Module(new AsyncQueueSink (gen, depth, sync)) + val source = Module(new AsyncQueueSource(gen, depth, sync, safe)) + val sink = Module(new AsyncQueueSink (gen, depth, sync, safe)) source.clock := io.enq_clock source.reset := io.enq_reset @@ -130,4 +163,6 @@ class AsyncQueue[T <: Data](gen: T, depth: Int = 8, sync: Int = 3) extends Cross sink.io.mem := source.io.mem sink.io.widx := source.io.widx source.io.ridx := sink.io.ridx + sink.io.widx_valid := source.io.widx_valid + source.io.ridx_valid := sink.io.ridx_valid } From 680a944f07b5187b765f1da5ee105f3d2be1f2f7 Mon Sep 17 00:00:00 2001 From: "Wesley W. Terpstra" Date: Fri, 14 Oct 2016 18:06:24 -0700 Subject: [PATCH 3/4] regmapper RegisterCrossing: safe AsyncQueues are overkill here --- src/main/scala/regmapper/RegisterCrossing.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/scala/regmapper/RegisterCrossing.scala b/src/main/scala/regmapper/RegisterCrossing.scala index b0e7f4bd..fefedb8f 100644 --- a/src/main/scala/regmapper/RegisterCrossing.scala +++ b/src/main/scala/regmapper/RegisterCrossing.scala @@ -41,7 +41,10 @@ class RegisterCrossingAssertion extends Module { val slave_reset = Bool(INPUT) } - assert (io.master_bypass || !io.slave_reset) + val up = RegInit(Bool(false)) + up := !io.slave_reset + + assert (io.master_bypass || !up || !io.slave_reset) } // RegField should support connecting to one of these @@ -88,7 +91,7 @@ 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 control = Module(new BusyRegisterCrossing) - val crossing = Module(new AsyncQueue(gen, 1, sync)) + val crossing = Module(new AsyncQueue(gen, 1, sync, safe=false)) control.clock := io.master_clock control.reset := io.master_reset @@ -141,7 +144,7 @@ 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 control = Module(new BusyRegisterCrossing) - val crossing = Module(new AsyncQueue(gen, 1, sync)) + val crossing = Module(new AsyncQueue(gen, 1, sync, safe=false)) control.clock := io.master_clock control.reset := io.master_reset From 20288729b90b04626d5c2ae7849820942cb6155e Mon Sep 17 00:00:00 2001 From: "Wesley W. Terpstra" Date: Fri, 14 Oct 2016 18:06:57 -0700 Subject: [PATCH 4/4] tilelink2 Isolation: cross the valid signals as well Refactor the code to be less copy-pasty --- .../scala/uncore/tilelink2/Isolation.scala | 71 ++++++++++--------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/src/main/scala/uncore/tilelink2/Isolation.scala b/src/main/scala/uncore/tilelink2/Isolation.scala index 3f02053e..3be5caca 100644 --- a/src/main/scala/uncore/tilelink2/Isolation.scala +++ b/src/main/scala/uncore/tilelink2/Isolation.scala @@ -22,44 +22,47 @@ class TLIsolation(fOut: (Bool, UInt) => UInt, fIn: (Bool, UInt) => UInt) extends def ISOo[T <: Data](x: T): T = x.fromBits(fOut(io.iso_out, x.asUInt)) def ISOi[T <: Data](x: T): T = x.fromBits(fIn (io.iso_in, x.asUInt)) + def ABo[T <: Data](x: AsyncBundle[T], y: AsyncBundle[T]) { + x.mem := ISOo(y.mem) + x.widx := ISOo(y.widx) + x.widx_valid := ISOo(y.widx_valid) + x.source_reset_n := ISOo(y.source_reset_n) + y.ridx := ISOi(x.ridx) + y.ridx_valid := ISOi(x.ridx_valid) + y.sink_reset_n := ISOi(x.sink_reset_n) + } + + def ABi[T <: Data](x: AsyncBundle[T], y: AsyncBundle[T]) { + x.mem := ISOi(y.mem) + x.widx := ISOi(y.widx) + x.widx_valid := ISOi(y.widx_valid) + x.source_reset_n := ISOi(y.source_reset_n) + y.ridx := ISOo(x.ridx) + y.ridx_valid := ISOo(x.ridx_valid) + y.sink_reset_n := ISOo(x.sink_reset_n) + } + + def ABz[T <: Data](x: AsyncBundle[T], y: AsyncBundle[T]) { + x.widx := UInt(0) + x.widx_valid := Bool(false) + x.source_reset_n := Bool(false) + y.ridx := UInt(0) + y.ridx_valid := Bool(false) + y.sink_reset_n := Bool(false) + } + ((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) => - - out.a.mem := ISOo(in .a.mem) - out.a.widx := ISOo(in .a.widx) - in .a.ridx := ISOi(out.a.ridx) - out.d.ridx := ISOo(in .d.ridx) - in .d.widx := ISOi(out.d.widx) - in .d.mem := ISOi(out.d.mem) - - out.a.source_reset_n := ISOo(in .a.source_reset_n) - in .a.sink_reset_n := ISOi(out.a.sink_reset_n) - out.d.sink_reset_n := ISOo(in .d.sink_reset_n) - in .d.source_reset_n := ISOi(out.d.source_reset_n) + ABo(out.a, in .a) + ABi(in .d, out.d) if (edgeOut.manager.base.anySupportAcquire && edgeOut.client.base.anySupportProbe) { - in .b.widx := ISOi(out.b.widx) - in .c.ridx := ISOi(out.c.ridx) - in .e.ridx := ISOi(out.e.ridx) - out.b.ridx := ISOo(in .b.ridx) - out.c.widx := ISOo(in .c.widx) - out.e.widx := ISOo(in .e.widx) - in .b.mem := ISOi(out.b.mem) - out.c.mem := ISOo(in .c.mem) - out.e.mem := ISOo(in .e.mem) - - out.b.sink_reset_n := ISOo(in .b.sink_reset_n) - in .b.source_reset_n := ISOi(out.b.source_reset_n) - out.c.source_reset_n := ISOo(in .c.source_reset_n) - in .c.sink_reset_n := ISOi(out.c.sink_reset_n) - out.e.source_reset_n := ISOo(in .e.source_reset_n) - in .e.sink_reset_n := ISOi(out.e.sink_reset_n) + ABi(in .b, out.b) + ABo(out.c, in .c) + ABo(out.e, in .e) } else { - in .b.widx := UInt(0) - in .c.ridx := UInt(0) - in .e.ridx := UInt(0) - out.b.ridx := UInt(0) - out.c.widx := UInt(0) - out.e.widx := UInt(0) + ABz(in .b, out.b) + ABz(out.c, in .c) + ABz(out.e, in .e) } } }