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.

Two polite blocks and a bus that never movesThe source has a beat and the sink has room, yet each waits for the other's first move.
validreadyDMA enginesource, holds a1Memory ctrlsink, 4 free slots

Both blocks wait. Neither wire ever lights, so no beat ever moves.

01234clkhas_beatroomvalidready
  1. Cycle 0

    The engine holds beat a1. It will raise valid as soon as it sees ready.

  2. Cycle 1

    The controller has four free slots. It will raise ready as soon as it sees valid.

  3. 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.

MechanismSignalsThe receiver can be busyWhere it fits
Strobeone pulse, sender to receiverNo: a busy receiver misses the pulseRegister writes, interrupts, status updates
Request and acknowledgerequest out, acknowledge back, no clock neededYes: the acknowledge waitsLinks between clock domains, asynchronous interfaces
Valid and readyvalid out, ready back, sampled on one clockYes: ready stays lowAXI, 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.

A four-phase handshake moves one byteEach level change is answered by the other side before anything else happens.
0123456reqackdatax7cx
  1. Cycle 1

    Phase 1: the sender places 7c and raises req.

  2. Cycle 2

    Phase 2: the receiver has the byte and raises ack. This is the transfer.

  3. Cycle 4

    Phase 3: the sender sees ack and drops req.

  4. 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.

Three beats, two stallsWatch the pill: it moves only in a cycle where valid and ready are both 1.
validdatareadySourceoffers beatsSinktakes beats

A lit wire carries a 1. A pill is a transfer in that cycle; slots are the entries held.

01234567clkvalidreadydataxa1xb2c3
  1. Cycle 1

    The source raises valid with a1. ready is 0, so the offer waits.

  2. Cycle 2

    Still waiting. valid and a1 are unchanged: an offer, once made, is held.

  3. Cycle 3

    ready is 1 at this edge. The beat moves, exactly once.

  4. Cycle 4

    ready stays 1 but valid is 0. Nothing moves: ready alone is permission, never a transfer.

  5. Cycle 5

    Both are 1 in the first cycle of the offer, so b2 moves with no stall at all.

  6. Cycle 6

    c3 is offered while the sink is busy again.

  7. 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.

  1. Rule 1, the transfer

    A beat moves at a rising edge where valid and ready are both 1, and at no other edge. Both sides count the same edges.

  2. Rule 2, hold

    Once valid is 1 and ready is 0, valid stays 1, with the same data, until the transfer. An offer is never withdrawn.

  3. Rule 3, who moves first

    valid must never wait for ready. ready may wait for valid, or may be high in advance.

  4. Rule 4, reset

    valid is 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.

Two offers that stall, one withdrawnOne lane per stalled edge of valid && !ready |=> valid.valid && !ready |=> validMeasured on Verilator 5.052: one copy of the assertion per lane
attempt startspassfail
Two offers that stall, one withdrawnstalled @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).edgetime15215325435545655765875clkvalidreadyattemptsstalled @15stalled @15: passes at edge 3 (t=25)stalled @25stalled @25: passes at edge 4 (t=35)stalled @55stalled @55: fails at edge 7 (t=65)
  1. Edge 2 · t=15

    The first offer is stalled at 15, so an attempt starts and looks at the next edge.

  2. Edge 4 · t=35

    Stalled again at 25, taken at 35. Both attempts pass: valid was still there.

  3. 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
  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: 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 broken
Read 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

Predict the outputThe sink holds two beats and its consumer sleeps until the stimulus wakes it. The checker prints one summary line at the end. What is it?
`timescale 1ns/1ps
module tb;
  logic clk = 0, rst_n = 0;
  always #5 clk = ~clk;                 // rising edges at 5, 15, 25, ...
  logic       valid = 0, last = 0, ready;
  logic [7:0] data = 'x;
  logic       drain = 0;         // 1: the consumer behind the FIFO takes a beat every cycle
  logic       out_valid, out_last;
  logic [7:0] out_data;
  int         count;
  link_sink #(.DEPTH(2)) sink (.clk(clk), .rst_n(rst_n), .valid(valid), .data(data), .last(last), .ready(ready),
    .drain(drain), .out_valid(out_valid), .out_data(out_data), .out_last(out_last), .count(count));
  link_checker chk (.clk(clk), .rst_n(rst_n), .valid(valid), .ready(ready), .data(data), .last(last));
  `include "link_tasks.svh"

  initial begin
    repeat (2) @(posedge clk); #1 rst_n = 1;   // reset released after edge 1
    @(posedge clk); #1;                        // the first offer may start after edge 2
    valid = 1; data = 8'h11;           // offered from cycle 3
    @(posedge clk); #1;                // taken at edge 3 (ready is 1: the FIFO has room)
    data = 8'h22;                      // a second beat, still valid
    @(posedge clk); #1;                // taken at edge 4; the FIFO is now full
    data = 8'h33;                      // a third beat waits
    @(posedge clk); @(posedge clk); #1 // stalled at edges 5 and 6
    drain = 1;                         // the consumer takes one beat per cycle from edge 7
    @(posedge clk); @(posedge clk); #1 // the pop at edge 7 makes room; 33 is taken at edge 8
    valid = 0; data = 'x;
    repeat (2) @(posedge clk);
    $finish;
  end
endmodule
Read 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
Spot the bugThe sink has one entry and its consumer sleeps until edge 7, so after the first beat ready is 0 for several edges. Each option offers one more beat. Which one breaks a rule of the link?
Read 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
ConceptWhy must a source never wait for ready before raising valid?
Read the waveformAt which cycle is beat 3c taken?
One offer on the link
01234validreadydatax3cx

One value per cycle; the edge at the end of a cycle samples that cycle's values.

ConceptA monitor counts a beat whenever it sees valid high. On a link with stalls, what does it report?

Remember

  • A handshake is a signal each way. A beat moves at a rising edge where valid and ready are both 1, and at no other edge.
  • Once offered, a beat is held: valid and the data stay until the transfer. A withdrawn offer is the most common handshake bug.
  • valid must never wait for ready; ready may wait for valid or be high in advance. That one rule prevents deadlock.
  • valid is 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