Skip to content

Commit 69463d8

Browse files
authored
[util] Add a withShadowLayer Queue (#4589)
Add methods to `Queue` and `Queue$` that can be used for creating "shadow" queues. These are queues that are tracking other, auxiliiary data not in the original queue and may possibly be instantiated elsewhere in the design. This is added in support of a design verification use case of building "shadow pipelines" that track additional information about the execution of some complicated pipeline (e.g., an out-of-order datapath) to expose this information to tests or as part of debugging the pipeline. Signed-off-by: Schuyler Eldridge <[email protected]>
1 parent 81896e6 commit 69463d8

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

integration-tests/src/test/scala/chiselTest/QueueSpec.scala

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,46 @@ class QueueFactoryTester(elements: Seq[Int], queueDepth: Int, bitWidth: Int, tap
208208
}
209209
}
210210

211+
/** Test that a Shadow Queue keeps track of shadow identifiers that are fed to
212+
* it. This feeds data into a queue and an identifier (which is `data >> 1`)
213+
* into the shadow queue. It then checks that each data read out has the
214+
* expected identifier.
215+
*/
216+
class ShadowQueueFactoryTester(queueDepth: Int, tap: Int, useSyncReadMem: Boolean) extends BasicTester {
217+
val enq, deq = Wire(Decoupled(UInt(32.W)))
218+
219+
private val (dataCounter, _) = Counter(0 to 31 by 2, enable = enq.fire)
220+
221+
enq.valid :<= true.B
222+
deq.ready :<= LFSR(16)(tap)
223+
224+
enq.bits :<= dataCounter
225+
226+
private val idIn = Wire(probe.Probe(UInt(4.W), layers.Verification))
227+
private val idOut = Wire(probe.Probe(Valid(UInt(4.W)), layers.Verification))
228+
layer.block(layers.Verification) {
229+
probe.define(idIn, probe.ProbeValue(dataCounter >> 1))
230+
231+
when(deq.fire) {
232+
assert(deq.bits >> 1 === probe.read(idOut).bits)
233+
}
234+
}
235+
236+
private val (_, done) = Counter(0 to 8, enable = deq.fire)
237+
when(done) {
238+
stop()
239+
}
240+
241+
private val (queue, shadow) =
242+
Queue.withShadow(
243+
enq = enq,
244+
entries = queueDepth,
245+
useSyncReadMem = useSyncReadMem
246+
)
247+
deq :<>= queue
248+
probe.define(idOut, shadow(probe.read(idIn), layers.Verification))
249+
}
250+
211251
class QueueSpec extends ChiselPropSpec {
212252

213253
property("Queue should have things pass through") {
@@ -303,4 +343,13 @@ class QueueSpec extends ChiselPropSpec {
303343
chirrtl should include("inst foo_q of Queue")
304344
chirrtl should include("inst bar_q of Queue")
305345
}
346+
347+
property("A shadow queue should track an identifier") {
348+
forAll(vecSizes, Gen.choose(0, 15), Gen.oneOf(true, false)) { (depth, tap, isSync) =>
349+
info(s"depth: $depth, tap: $tap, isSync: $isSync")
350+
assertTesterPasses {
351+
new ShadowQueueFactoryTester(depth, tap, isSync)
352+
}
353+
}
354+
}
306355
}

src/main/scala/chisel3/util/Queue.scala

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package chisel3.util
33

44
import chisel3._
55
import chisel3.experimental.requireIsChiselType
6+
import chisel3.layer.{block, Layer}
7+
import chisel3.probe.{define, Probe, ProbeValue}
8+
import chisel3.util.experimental.BoringUtils
69

710
/** An I/O Bundle for Queues
811
* @param gen The type of data to queue
@@ -139,6 +142,27 @@ class Queue[T <: Data](
139142
* generator's `typeName`
140143
*/
141144
override def desiredName = s"Queue${entries}_${gen.typeName}"
145+
146+
/** Create a "shadow" `Queue` in a specific layer that will be queued and
147+
* dequeued in lockstep with an original `Queue`. Connections are made using
148+
* `BoringUtils.tapAndRead` which allows this method to be called anywhere in
149+
* the hierarchy.
150+
*
151+
* An intended use case of this is as a building block of a "shadow" design
152+
* verification datapath which augments an existing design datapath with
153+
* additional information. E.g., a shadow datapath that tracks transations
154+
* in an interconnect.
155+
*
156+
* @param data a hardware data that should be enqueued together with the
157+
* original `Queue`'s data
158+
* @param layer the `Layer` in which this queue should be created
159+
* @return a layer-colored `Valid` interface of probe type
160+
*/
161+
def shadow[A <: Data](data: A, layer: Layer): Valid[A] =
162+
withClockAndReset(BoringUtils.tapAndRead(clock), BoringUtils.tapAndRead(reset)) {
163+
val shadow = new Queue.ShadowFactory(enq = io.enq, deq = io.deq, entries, pipe, flow, useSyncReadMem, io.flush)
164+
shadow(data, layer)
165+
}
142166
}
143167

144168
/** Factory for a generic hardware queue. */
@@ -185,6 +209,94 @@ object Queue {
185209
}
186210
}
187211

212+
/** A factory for creating shadow queues. This is created using the
213+
* `withShadow` method.
214+
*/
215+
class ShadowFactory private[Queue] (
216+
enq: ReadyValidIO[Data],
217+
deq: ReadyValidIO[Data],
218+
entries: Int,
219+
pipe: Boolean,
220+
flow: Boolean,
221+
useSyncReadMem: Boolean,
222+
flush: Option[Bool]) {
223+
224+
/** The clock used when building the original Queue. */
225+
private val clock = Module.clock
226+
227+
/** The reset used when elaborating the original Queue. */
228+
private val reset = Module.reset
229+
230+
/** Create a "shadow" `Queue` in a specific layer that will be queued and
231+
* dequeued in lockstep with an original `Queue`. Connections are made
232+
* using `BoringUtils.tapAndRead` which allows this method to be called
233+
* anywhere in the hierarchy.
234+
*
235+
* An intended use case of this is as a building block of a "shadow" design
236+
* verification datapath which augments an existing design datapath with
237+
* additional information. E.g., a shadow datapath that tracks transations
238+
* in an interconnect.
239+
*
240+
* @param data a hardware data that should be enqueued together with the
241+
* original `Queue`'s data
242+
* @param layer the `Layer` in which this queue should be created
243+
* @return a layer-colored `Valid` interface of probe type
244+
*/
245+
def apply[A <: Data](data: A, layer: Layer): Valid[A] =
246+
withClockAndReset(BoringUtils.tapAndRead(clock), BoringUtils.tapAndRead(reset)) {
247+
val shadowDeq = Wire(Probe(Valid(chiselTypeOf(data)), layer))
248+
249+
block(layer) {
250+
val shadowEnq = Wire(Decoupled(chiselTypeOf(data)))
251+
val probeEnq = BoringUtils.tapAndRead(enq)
252+
shadowEnq.valid :<= probeEnq.valid
253+
shadowEnq.bits :<= data
254+
255+
val shadowQueue = Queue(shadowEnq, entries, pipe, flow, useSyncReadMem, flush.map(BoringUtils.tapAndRead))
256+
257+
val _shadowDeq = Wire(Valid(chiselTypeOf(data)))
258+
_shadowDeq.valid :<= shadowQueue.valid
259+
_shadowDeq.bits :<= shadowQueue.bits
260+
shadowQueue.ready :<= BoringUtils.tapAndRead(deq).ready
261+
define(shadowDeq, ProbeValue(_shadowDeq))
262+
}
263+
264+
shadowDeq
265+
}
266+
}
267+
268+
/** Create a [[Queue]] and supply a [[DecoupledIO]] containing the product.
269+
* This additionally returns a [[ShadowFactory]] which can be used to build
270+
* shadow datapaths that work in lockstep with this [[Queue]].
271+
*
272+
* @param enq input (enqueue) interface to the queue, also determines type of
273+
* queue elements.
274+
* @param entries depth (number of elements) of the queue
275+
* @param pipe True if a single entry queue can run at full throughput (like
276+
* a pipeline). The `ready` signals are combinationally coupled.
277+
* @param flow True if the inputs can be consumed on the same cycle (the
278+
* inputs "flow" through the queue immediately). The `valid`
279+
* signals are coupled.
280+
* @param useSyncReadMem True uses SyncReadMem instead of Mem as an internal
281+
* memory element.
282+
* @param flush Optional [[Bool]] signal, if defined, the [[Queue.hasFlush]]
283+
* will be true, and connect correspond signal to [[Queue]]
284+
* instance.
285+
* @return output (dequeue) interface from the queue and a [[ShadowFactory]]
286+
* for creating shadow [[Queue]]s
287+
*/
288+
def withShadow[T <: Data](
289+
enq: ReadyValidIO[T],
290+
entries: Int = 2,
291+
pipe: Boolean = false,
292+
flow: Boolean = false,
293+
useSyncReadMem: Boolean = false,
294+
flush: Option[Bool] = None
295+
): (DecoupledIO[T], ShadowFactory) = {
296+
val deq = apply(enq, entries, pipe, flow, useSyncReadMem, flush)
297+
(deq, new ShadowFactory(enq, deq, entries, pipe, flow, useSyncReadMem, flush))
298+
}
299+
188300
/** Create a queue and supply a [[IrrevocableIO]] containing the product.
189301
* Casting from [[DecoupledIO]] is safe here because we know the [[Queue]] has
190302
* Irrevocable semantics.

0 commit comments

Comments
 (0)