Handshakes: request and acknowledge, valid and ready
Say exactly when a beat moves between two blocks, hold an offer until it is taken, and explain why the side that offers must never wait for the side that accepts.
- Lesson
- P1.3
- Time
- 30 min
- Runs on
- Your browser
- Check
- 5 questions
A new DMA engine and a new memory controller each passed their own tests, and on the first system run nothing moved. The engine was written to raise valid only after it saw ready, because "why offer data to a block that cannot take it". The controller was written to raise ready only after it saw valid, because "why advertise room before there is a request". Each block waited politely for the other, and the system sat idle until the watchdog fired.
Both blocks wait. Neither wire ever lights, so no beat ever moves.
- Cycle 0
The engine holds beat a1. It will raise
validas soon as it seesready. - Cycle 1
The controller has four free slots. It will raise
readyas soon as it seesvalid. - Cycle 4
Four edges later nothing has changed, and nothing ever will. A rule about who moves first is the only cure.
A deadlock on the very first transfer. Both blocks obey a sensible local rule; the rules do not fit together.
A handshake names one moment
A handshake is a pair of signals, one in each direction, that together name the moment a piece of data changes hands. The sender says "here is something" and the receiver says "I take it". Only when both are true at once does the beat move, so neither side can be surprised and neither side can lose data.
Compare that with a strobe: one pulse from the sender, with no answer. A strobe works when the receiver is always ready, such as a register bank that can absorb a write every cycle. The moment the receiver can be busy, a strobe either drops data or forces the sender to guess how long to wait.
| Mechanism | Signals | The receiver can be busy | Where it fits |
|---|---|---|---|
| Strobe | one pulse, sender to receiver | No: a busy receiver misses the pulse | Register writes, interrupts, status updates |
| Request and acknowledge | request out, acknowledge back, no clock needed | Yes: the acknowledge waits | Links between clock domains, asynchronous interfaces |
| Valid and ready | valid out, ready back, sampled on one clock | Yes: ready stays low | AXI, AXI-Stream, Avalon-ST, TileLink, most on-chip links |
Three ways to move one beat. The two handshakes can stall; the strobe cannot.
Request and acknowledge: four phases or two
The oldest handshake needs no clock at all. The sender places the data and raises request. The receiver takes the data and raises acknowledge. The sender sees the acknowledge and drops request. The receiver sees that and drops acknowledge. Four edges, one transfer, and the link is back where it started.
- Cycle 1
Phase 1: the sender places 7c and raises
req. - Cycle 2
Phase 2: the receiver has the byte and raises
ack. This is the transfer. - Cycle 4
Phase 3: the sender sees
ackand dropsreq. - Cycle 5
Phase 4: the receiver drops
ack. Both wires are back at 0, ready for the next byte.
The data stays stable from phase 1 until phase 3, so the receiver may take it at any time while req is high.
A two-phase handshake saves half of that: every change of request is a new transfer, and every change of acknowledge is its answer. It is faster and harder to debug, because a high level on the wire no longer means "busy".
Both forms belong between blocks that do not share a clock. Inside one clock domain, a simpler form wins.
Valid and ready: the clocked handshake
On a clocked link the two signals are called valid and ready, and the clock does the work of the four phases. The source drives valid high with the data. The sink drives ready high when it can take a beat. At every rising edge where both are high, one beat moves. Nothing moves at any other edge.
valid and ready are both 1.A lit wire carries a 1. A pill is a transfer in that cycle; slots are the entries held.
- Cycle 1
The source raises
validwith a1.readyis 0, so the offer waits. - Cycle 2
Still waiting.
validand a1 are unchanged: an offer, once made, is held. - Cycle 3
readyis 1 at this edge. The beat moves, exactly once. - Cycle 4
readystays 1 butvalidis 0. Nothing moves:readyalone is permission, never a transfer. - Cycle 5
Both are 1 in the first cycle of the offer, so b2 moves with no stall at all.
- Cycle 6
c3 is offered while the sink is busy again.
- Cycle 7
Taken. Three beats, three cycles where both wires were 1.
The sink counts the cycles where both are 1 and gets three beats. So does the source, so does every monitor.
Four rules make this safe, and the fourth is the one the opening story broke.
Rule 1, the transfer
A beat moves at a rising edge where
validandreadyare both 1, and at no other edge. Both sides count the same edges.Rule 2, hold
Once
validis 1 andreadyis 0,validstays 1, with the same data, until the transfer. An offer is never withdrawn.Rule 3, who moves first
validmust never wait forready.readymay wait forvalid, or may be high in advance.Rule 4, reset
validis 0 while reset is active, and may rise only at an edge after reset has been released.
Rule 3 breaks the deadlock. If the source always moves first, a sink that waits for valid still gets its signal. A sink that keeps ready high in advance is served on the first edge. The engine in the opening story broke rule 3, and the controller did nothing wrong at all.
The hold rule as an assertion
Rule 2 is the one a checker sees most often, because a source that drops valid during a stall looks fine on every test where the sink happens to be fast. In assertion form it reads: at any edge where valid is 1 and ready is 0, valid is 1 again at the next edge.
valid && !ready |=> valid.- Edge 2 · t=15
The first offer is stalled at 15, so an attempt starts and looks at the next edge.
- Edge 4 · t=35
Stalled again at 25, taken at 35. Both attempts pass:
validwas still there. - Edge 7 · t=65
The second offer, stalled at 55, is gone at 65. The attempt from 55 fails: the offer was withdrawn.
Read the lanes as text
- stalled @15, from edge 2 (t=15): passes at edge 3 (t=25).
- stalled @25, from edge 3 (t=25): passes at edge 4 (t=35).
- stalled @55, from edge 6 (t=55): fails at edge 7 (t=65).
Sampled values, edge 1 to 8: valid 0 1 1 1 0 1 0 0; ready 0 0 0 1 0 0 0 0.
The program behind the lanes
The lanes come from running this program on Verilator 5.052. Each lane is a separate copy of hold_valid that can start only at that lane's edge, so every verdict and its edge are read from that copy's own output.
module tb;
logic clk = 0;
always #5 clk = ~clk; // rising edges at 5, 15, 25, ...
logic valid = 0, ready = 0;
hold_valid: assert property (@(posedge clk) valid && !ready |=> valid);
initial begin
@(negedge clk); valid = 1; // offer, sampled at 15; ready is 0
@(negedge clk); // still offered at 25, still stalled
@(negedge clk); ready = 1; // taken at 35
@(negedge clk); valid = 0; ready = 0;
@(negedge clk); valid = 1; // second offer, sampled at 55, stalled
@(negedge clk); valid = 0; // withdrawn: at 65 valid is 0 while ready never came
@(negedge clk);
@(negedge clk);
$finish;
end
endmodule
Each stalled edge starts its own attempt. The sink saw a correct link until the second offer vanished.
The bench on this page runs in your browser, on a simulator without concurrent assertions. Its checker is a clocked procedure that compares each edge with the one before, and it names the same rule, hold, when an offer is withdrawn.
Where you will meet it
Every AXI channel is one of these handshakes, and so are AXI-Stream, Avalon-ST and TileLink. The names change, the rules do not. Wishbone names the request STB and the answer ACK, and the answer may come in the same cycle. APB and AHB have no per-beat valid: the initiator always moves first, and only a wait signal from the target can stretch a transfer.
Asked in interviews: handshakes
Why must valid not depend on ready?
Because a sink is allowed to wait for valid before it raises ready. If the source also waits, neither signal ever rises and the link deadlocks on the first beat.
When exactly does a transfer happen?
At a rising clock edge where valid and ready are both 1. Both sides, and any monitor, count the same edges, so nobody is ever off by one.
Can a source drop valid if it changes its mind?
No. Once valid is 1 and ready is 0, valid and the data must stay until the transfer. A sink may have committed to that beat already.
What is the difference between a strobe and a handshake?
A strobe is a one-way pulse that assumes the receiver is ready. A handshake adds a signal back, so the receiver can stall the sender instead of losing data.
May ready be high before valid?
Yes. Ready in advance is allowed and gives the best throughput; ready may also wait for valid. Only valid is forbidden from waiting.
Try it
The sink holds two beats and its consumer wakes at edge 8, so the third offer waits four edges. Run it and read the waves: valid stays high through the stall and data stays c3.
Then break rule 2 by hand. Replace the third send with valid = 1; data = 8'hc3; @(posedge clk); #1 valid = 0; and run again: the checker names the rule and the edge.
edge 3: beat a1 taken edge 4: beat b2 taken edge 9: beat c3 taken edge 10: beat d4 taken checker: 4 transfers, 4 stalled edges, 0 rules brokenRead included file: link_sink.sv
// link_sink.sv: the target of the generic link (read-only in this lesson).
// A DEPTH-entry FIFO behind valid/ready. It takes a beat at every rising edge where valid and
// ready are both 1, and hands beats on to its consumer at every edge where drain and out_valid
// are both 1. ready is 1 while the FIFO has room: it never waits for valid.
module link_sink #(parameter int DEPTH = 4, parameter int WIDTH = 8) (
input logic clk,
input logic rst_n,
input logic valid,
input logic [WIDTH-1:0] data,
input logic last,
output logic ready,
input logic drain, // the consumer behind the FIFO takes a beat while 1
output logic out_valid,
output logic [WIDTH-1:0] out_data,
output logic out_last,
output int count // beats held right now
);
logic [WIDTH:0] mem [DEPTH]; // {last, data}
int wr = 0, rd = 0;
assign ready = (count < DEPTH);
assign out_valid = (count > 0);
assign {out_last, out_data} = mem[rd];
wire push = valid && ready;
wire pop = drain && out_valid;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr <= 0; rd <= 0; count <= 0;
end else begin
if (push) begin mem[wr] <= {last, data}; wr <= (wr + 1) % DEPTH; end
if (pop) rd <= (rd + 1) % DEPTH;
count <= count + (push ? 1 : 0) - (pop ? 1 : 0);
end
end
endmodule
Read included file: link_checker.sv
// link_checker.sv: the rules of the generic link, checked at every rising edge (read-only).
// Each broken rule is named with the edge where it was seen:
// reset valid is 0 while reset is active;
// hold once valid is 1 and ready is 0, valid is still 1 at the next edge (an offer is not withdrawn);
// stable while an offer waits, data and last do not change;
// known data is not x while valid is 1.
// The one rule no checker can see on the wires, "valid must not wait for ready", is the stimulus's
// own responsibility (lesson P1.3).
// It also counts transfers and stalled edges, and prints one summary line at the end of the run.
module link_checker #(parameter int WIDTH = 8) (
input logic clk,
input logic rst_n,
input logic valid,
input logic ready,
input logic [WIDTH-1:0] data,
input logic last
);
int cycle = -1, transfers = 0, stalls = 0, broken = 0;
logic pv = 0, pr = 0, pl = 0;
logic [WIDTH-1:0] pd = 'x;
always @(posedge clk) begin
cycle++;
if (!rst_n) begin
if (valid === 1) report("reset", "valid is 1 during reset");
end else begin
if (pv && !pr && valid !== 1) report("hold", "valid dropped before ready: the offer was withdrawn");
if (pv && !pr && valid === 1 && data !== pd) report("stable", "data changed while the offer waited");
if (pv && !pr && valid === 1 && last !== pl) report("stable", "last changed while the offer waited");
if (valid === 1 && $isunknown(data)) report("known", "data is x while valid is 1");
if (valid === 1 && ready === 1) transfers++;
if (valid === 1 && ready !== 1) stalls++;
end
pv = (valid === 1); pr = (ready === 1); pd = data; pl = last;
end
function void report(input string rule, input string what);
broken++;
$display("RULE %s broken at edge %0d: %s", rule, cycle, what);
endfunction
final $display("checker: %0d transfers, %0d stalled edges, %0d rules broken", transfers, stalls, broken);
endmodule
Read included file: link_tasks.svh
// link_tasks.svh: the source side of the generic link, as tasks for the stimulus (read-only).
// Every task returns one time unit after the rising edge where its last beat transferred, so the
// next call starts driving in the following cycle. `cycle` counts rising edges from 0.
int cycle = -1;
always @(posedge clk) cycle = cycle + 1;
// Offer one beat and hold it until the sink takes it.
task automatic send(input logic [7:0] d, input logic is_last = 0);
valid = 1; data = d; last = is_last;
do @(posedge clk); while (!ready);
#1 $display("edge %0d: beat %02h taken", cycle, d);
valid = 0; data = 'x; last = 0;
endtask
// A burst of n beats with consecutive values; last marks the final beat.
task automatic send_burst(input logic [7:0] first, input int n);
for (int i = 0; i < n; i++) begin
valid = 1; data = first + i[7:0]; last = (i == n - 1);
do @(posedge clk); while (!ready);
#1 $display("edge %0d: beat %02h taken%s", cycle, first + i[7:0], last ? " (last)" : "");
end
valid = 0; data = 'x; last = 0;
endtask
// Drive nothing for n cycles.
task automatic idle(input int n);
repeat (n) @(posedge clk);
#1;
endtask
Check yourself
Remember
- A handshake is a signal each way. A beat moves at a rising edge where
validandreadyare both 1, and at no other edge. - Once offered, a beat is held:
validand the data stay until the transfer. A withdrawn offer is the most common handshake bug. validmust never wait forready;readymay wait forvalidor be high in advance. That one rule prevents deadlock.validis 0 in reset and rises only after reset is released.- A strobe has no answer and only works when the receiver is always ready. Request/acknowledge handshakes do the same job without a clock.
Practice
- Implement a sink Implement · Medium
- Catch the broken sources Verify · Hard