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.

Each side waits for the other
01234driver statewait readyrvalidrreadydeadlock

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

  1. Offer

    Drive rvalid and rdata through the clocking block, without looking at rready first.

    rvalid1
    rdatanext beat
  2. Wait for an edge

    Wait with @(vif.drv_cb), then read the sampled rready for that edge.

  3. Stalled?

    If the sampled rready was 0, nothing moved: keep everything as it is and go back to waiting. Count the stall.

    rvalidstill 1
    rdataunchanged
  4. Transferred

    If it was 1, the beat moved at that edge. Drive the next beat at once, or drop rvalid if 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.

RUNVerilator · simulator service
Edit the code, then run it
Expected: 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 times

Check yourself

Predict the outputA source holds rvalid high until it has sent three beats. Each element of ready is one rising edge. Predict the exact output.
module tb;
  bit ready [8] = '{0, 1, 0, 0, 1, 1, 0, 1};
  int beat;
  string s;
  initial begin
    // rvalid is high in every cycle until three beats have moved.
    foreach (ready[i])
      if (beat < 3 && ready[i]) begin
        s = {s, $sformatf("b%0d@%0d,", beat, i)};
        beat++;
      end
    $display("%s", s);
  end
endmodule
ConceptWhich source behaviour can deadlock against a legal sink?
ConceptDuring a stall, the driver already knows the next beat's data. May it put that data on rdata early, while rvalid stays high?
Read the waveformThe driver holds each beat until a transfer. In which cycle does beat B transfer?
Two beats against backpressure
012345rvalidrreadyrdataABC

Values sampled at each rising edge.

Spot the bugEach option is the body of the driver task send(logic [7:0] d), which must deliver every beat to a sink that stalls. Which option loses beats?

Remember

  • A source raises rvalid on its own; waiting for rready first can deadlock against a legal sink.
  • Once offered, rvalid and rdata stay unchanged until an edge where both rvalid and rready are 1.
  • Decide whether a beat moved from the sampled rready at that edge, through the clocking block.
  • Bound every wait for rready and 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