Ready/valid drivers and backpressure
Write a driver that raises valid on its own, holds the offer unchanged until the sink accepts it, and gives up loudly instead of hanging.
- Lesson
- 6.5
- Time
- 25 min
- Runs on
- Verilator
- Check
- 5 questions
A new driver waited for rready before raising rvalid, to be polite. The DUT's sink raised rready only after it saw rvalid, which the protocol allows. Each side waited for the other, nothing ever moved, and the test sat at the same simulation time until the farm killed it after two hours.
The sink is allowed to wait for rvalid; the source is not allowed to wait for rready.
The rules of the handshake
A ready/valid source follows three rules. It raises rvalid whenever it has data, without waiting for rready. Once rvalid is high, it keeps rvalid high and rdata unchanged until a transfer happens. And it changes rdata or drops rvalid only after an edge where both were 1.
The sink has more freedom. It may raise rready before any data is offered, or wait until it sees rvalid, and it may drop rready at any time to apply backpressure. That asymmetry is what prevents the deadlock in the opening story.
A driver loop that follows them
Offer
Drive
rvalidandrdatathrough the clocking block, without looking atrreadyfirst.rvalid 1rdata next beatWait for an edge
Wait with
@(vif.drv_cb), then read the sampledrreadyfor that edge.Stalled?
If the sampled
rreadywas 0, nothing moved: keep everything as it is and go back to waiting. Count the stall.rvalid still 1rdata unchangedTransferred
If it was 1, the beat moved at that edge. Drive the next beat at once, or drop
rvalidif there is none.
Because the driver reads rready through the clocking block, it sees exactly the value that decided the transfer at that edge. Reading the raw rready after the clocking event could show the sink's value for the following edge instead.
Back-to-back beats
After a transfer, a source with more data drives the next beat straight away, so rvalid stays high and a new beat can move at the very next edge. Dropping rvalid for a cycle between beats is legal, but it halves the throughput and hides bugs that only appear under full load.
The example's sink accepts on a fixed pattern of ready cycles. The driver keeps rvalid high throughout, so every edge with rready high moves a beat, and the stall counts follow directly from the pattern.
The tempting mistakes
Offer for one cycle, then move on
vif.drv_cb.rvalid <= 1;
vif.drv_cb.rdata <= d;
@(vif.drv_cb);
vif.drv_cb.rvalid <= 0; // accepted or not
A beat offered during a stall is dropped, and the sink never receives it.
Hold until a sampled ready
vif.drv_cb.rvalid <= 1;
vif.drv_cb.rdata <= d;
do @(vif.drv_cb);
while (!vif.drv_cb.rready);
The offer stands, unchanged, until the edge where it moves.
Changing rdata during a stall is the other common slip, often from code that prepares the next beat early. The sink may capture data at any edge where it raises rready, so the value must already be final.
Wait with a limit
A sink that never raises rready turns the hold loop into an endless wait at the same point. Give the loop a bound, such as 16 stalled edges, and stop with a message that names the port and the beat.
Choose the bound from the specification, for example the longest stall the DUT is allowed to apply, plus a margin. A bound that fires in a passing test is a sign the specification and the test disagree.
Reset and the driver
When reset is asserted, the driver must drop rvalid and discard any beat it was offering, because the DUT has forgotten it. This is the driver's side of the scoreboard clear from lesson 2.7.
After reset, the driver starts again with the next transaction from its queue. A driver that resumes its old offer after reset sends a beat the scoreboard no longer expects.
Test the driver against many sinks
A driver that works against a sink with rready always high has not been tested. Vary the sink: always ready, ready every other cycle, long stalls, and ready that rises in the same cycle as rvalid.
The fixed pattern in the example is one such sink. In a full testbench, the sink's ready pattern is randomized, which Module 5 covers, so that each run stresses the driver differently.
Count what really moved
The scoreboard should add an expectation, or check a response, only at a transfer, never at an offer. A monitor that reads rvalid, rready and rdata from one clocking block sees each beat exactly once, at the edge where it moved.
The same monitor can check the source's side of the bargain: while a beat is stalled, rdata must not change. The next lesson turns that check into an assertion.
Try it
The sink's ready pattern stalled the first beat for two edges and the third for one, and let the second beat move at the very next edge. The monitor saw each beat once and found no data change during a stall.
Replace the driver's while loop with a single @(vif.drv_cb); and predict which beats the monitor still sees.
t=35 transfer rdata=10 t=45 transfer rdata=20 t=65 transfer rdata=30 t=95 transfer rdata=40 stall cycles per beat: 2 0 1 2 data changed while stalled: 0 timesCheck yourself
Remember
- A source raises
rvalidon its own; waiting forrreadyfirst can deadlock against a legal sink. - Once offered,
rvalidandrdatastay unchanged until an edge where bothrvalidandrreadyare 1. - Decide whether a beat moved from the sampled
rreadyat that edge, through the clocking block. - Bound every wait for
rreadyand stop with a message that names the port and the beat. - Update the scoreboard at transfers only, and check that data holds during stalls.
Practice
- Traces that expose broken hold checkers Break it · Easy
- Send a burst under backpressure Implement · Hard