Flow control: backpressure, FIFO depth and credits

Say where a beat waits when the sink cannot take it, size a FIFO from the stall it must absorb, and know when a credit beats a ready wire.

Lesson
P1.6
Time
30 min
Runs on
Your browser
Check
5 questions

A video pipeline fed a 16-deep FIFO at one beat per cycle and the block behind it stalled for 20 cycles whenever it swapped buffers. The FIFO had no ready back to the source, because "sixteen entries is plenty". Five beats fell on the floor at every swap, the picture tore once per frame, and the first suspect was the DMA engine three blocks upstream.

Beats held when the stall beginsBeats arriving during the stallBeats leavingNeededDepthLost
120021165

The arithmetic the design review skipped. A FIFO absorbs a burst only when it is deeper than the burst.

Where a beat waits when the sink stalls

Flow control is the set of rules that stop a source from sending what the sink cannot take. The sink has a FIFO, so a short stall of its consumer costs nothing: beats pile up in the FIFO and drain later. A long stall fills the FIFO, and then one of two things happens. Either the sink lowers ready and the source holds its beat, or there is no ready and the beat is lost.

The consumer stops; the FIFO fills; the source is heldA 4-entry FIFO between a source that offers one beat per cycle and a consumer that stops for four cycles.
validdatareadyouttakeSourceone beat per cycleFIFO4 entriesConsumerstops for 4 cycles

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

01234567891011validdataxb0b1b2b3b4b5b6xreadyout_validout_dataxb0b1b2b3b4b5take
  1. Cycle 2

    One beat in, one beat out: the FIFO holds a single beat.

  2. Cycle 3

    The consumer stops. Beats keep arriving, so the FIFO fills.

  3. Cycle 6

    Full. ready drops and the source holds b5: that is backpressure.

  4. Cycle 7

    The consumer takes b1. Room appears only after this edge, so ready is still 0.

  5. Cycle 8

    ready is back and b5 moves. Nothing was lost; the stall cost the source two cycles.

Backpressure moves the stall upstream instead of losing data. The source pays with its own cycles.

The held beat waits in the source's own output register, and if the source is itself a pipeline, its stages fill one by one from the end. Backpressure is this chain: each stage lowers its ready when it is full. The stall walks upstream until it reaches something that can wait, usually the block that started the traffic.

A FIFO absorbs bursts and nothing else

A FIFO can hide a stall only when it is deeper than the beats that arrive during it. The depth you need is the number held when the stall begins, plus the arrivals during the stall, minus the beats that leave meanwhile. On the bench, a source at one beat per cycle meets a consumer that stops for 12 cycles. One beat is inside, 12 arrive while the consumer sleeps, and 1 more arrives at the edge where it wakes, because the beat it takes frees room only after that edge. That is 14 beats, so a depth of 8 loses 6 of them and a depth of 14 loses none.

The same arithmetic shows what a FIFO cannot do. If the consumer takes one beat every two cycles for good, the FIFO fills at half a beat per cycle and any depth overflows after twice that many cycles. A rate mismatch is fixed by backpressure or a faster consumer, never by depth.

The same stall with no ready wireSame traffic, same 4-entry FIFO, but the link has no ready. A beat that finds the FIFO full is dropped.
dataouttakedropSourcenever heldFIFO4 entries, no readyConsumerstops for 4 cyclesDroppednobody is told

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

01234567891011validdataxb0b1b2b3b4b5b6b7xdropout_validout_dataxb0b1b2b3b4b7take
  1. Cycle 3

    The consumer stops and the FIFO fills, as before.

  2. Cycle 6

    Full, and there is no ready to say so. b5 arrives and is thrown away.

  3. Cycle 7

    b6 is lost too: the beat taken at this edge frees room only afterwards.

  4. Cycle 11

    The consumer gets b4 and then b7. Two beats vanished, and no signal recorded it.

Without flow control the FIFO depth is the whole safety margin, and every stall longer than it loses data silently.

Credits: permission granted in advance

A ready wire answers in the same cycle, which works while the two blocks sit next to each other. Across a long wire, a chip boundary or a network-on-chip hop, the answer takes several cycles to come back. By then the source has already sent more beats than the sink had room for.

Credits turn the question around. The sink hands the source one credit per free entry before any traffic flows. The source spends a credit for every beat it sends and stops by itself when none are left. Each time the consumer frees an entry, the sink returns a credit, which may take as long as it likes to arrive. Overflow is impossible, because every beat on the wire already owns a slot.

Three credits on a link with a two-cycle returnThe source's slots are its credits; the sink's slots are its entries. A credit comes back two cycles after the consumer takes a beat.
datacreditSourcespends creditsSink3 entries

A beat spends a credit from the source; a returned credit lights one again. The sink never sees more beats than it has slots.

0123456789has_beatvaliddataxb0b1b2xb3xb4b5takecredit
  1. Cycle 1

    The source starts with three credits, one per free slot, and spends one per beat.

  2. Cycle 3

    Three beats sent, no credits left. The source stops by itself: nothing asked it to.

  3. Cycle 4

    The credit for b0 arrives, two cycles after the consumer took it.

  4. Cycle 5

    One credit, one beat. The sink cannot overflow: every beat in flight owns a slot.

  5. Cycle 8

    Credits trickle back as the consumer drains. On a longer wire the source would need more of them to keep busy.

The number of credits must cover the round trip: beats sent during the time a credit takes to come back.

That last point is the sizing rule for credits. With a return that takes R cycles and a link that moves one beat per cycle, the source needs about R credits to send without pausing. Fewer credits throttle the link, as the bench shows with two credits on a four-cycle return. PCIe meters every link this way, with separate credits per packet type. Network-on-chip links and chip-to-chip interfaces use the same scheme.

Where you will meet it

Every AMBA channel uses ready for flow control, and AXI-Stream carries it beat by beat. Avalon-ST adds a ready latency, so ready may apply a few cycles later, half way to a credit. PCIe and most NoC fabrics use credits. The FIFO sizing arithmetic is the same everywhere, and it is the first thing to check when a link drops data under load.

Asked in interviews: flow control
  1. How do you size a FIFO between two clocked blocks?

    From the longest stall the consumer can make: the beats held when it starts, plus the beats arriving during it, minus the beats leaving. A rate mismatch cannot be sized away; only backpressure or a faster consumer fixes it.

  2. What is backpressure?

    The sink lowering ready so the source holds its beat. The stall moves upstream one stage at a time until it reaches something that can wait, and no data is lost.

  3. What happens without flow control?

    The FIFO depth is the only margin. Any stall longer than the depth drops beats, usually silently, and the symptom appears far downstream.

  4. What are credits and when are they better than ready?

    Permission granted in advance: one credit per free entry, spent per beat and returned per freed entry. They win when the answer takes cycles to travel, across long wires, chip boundaries or a NoC.

  5. How many credits does a link need?

    Enough to cover the round trip: about the beats the source can send while a credit travels back. Fewer credits throttle the link even when the sink is empty.

Try it

The sink holds 8 beats and its consumer stops for 12 cycles. The first run offers 24 beats with no flow control at all; the second run offers the same 24 beats with 8 credits and a two-cycle return. Read the two summary lines.

Then set BACKPRESSURE(1) on the sink and run again: no drops, and the source is held instead. Set DEPTH(14) to absorb the stall outright, or RETURN_DELAY(6) and CREDITS(2) to watch credits throttle an empty sink.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: no backpressure: peak 8 held, 6 dropped, source held 0 edges, waited 0 cycles for credit credits 8: peak 8 held, 0 dropped, source held 0 edges, waited 9 cycles for credit
Read included file: lossy_sink.sv
// lossy_sink.sv: a DEPTH-entry sink with the ports of link_sink and two ways to meet a full FIFO.
// BACKPRESSURE = 0: ready stays 1, and a beat that arrives while the FIFO is full is lost and
// counted in drops. BACKPRESSURE = 1: ready is 0 while the FIFO is full, so the source holds its
// beat (the behaviour of link_sink). popped is 1 for the cycle after a beat leaves: the credit a
// credit-counting source gets back. The consumer behind the FIFO takes a beat while drain is 1.
module lossy_sink #(parameter int DEPTH = 8, parameter bit BACKPRESSURE = 0, 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,
  output logic             out_valid,
  output logic [WIDTH-1:0] out_data,
  output logic             out_last,
  output int               count,       // beats held right now
  output int               drops,       // beats lost so far
  output logic             popped
);
  logic [WIDTH:0] mem [DEPTH];
  int wr = 0, rd = 0;
  wire full = (count >= DEPTH);
  assign ready     = BACKPRESSURE ? !full : 1'b1;
  assign out_valid = (count > 0);
  assign {out_last, out_data} = mem[rd];
  wire push = valid && !full;
  wire drop = valid && full && !BACKPRESSURE;
  wire pop  = drain && out_valid;
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      wr <= 0; rd <= 0; count <= 0; drops <= 0; popped <= 0;
    end else begin
      if (push) begin mem[wr] <= {last, data}; wr <= (wr + 1) % DEPTH; end
      if (pop) rd <= (rd + 1) % DEPTH;
      if (drop) drops <= drops + 1;
      count  <= count + (push ? 1 : 0) - (pop ? 1 : 0);
      popped <= pop;
    end
  end
endmodule
Read included file: credit_counter.sv
// credit_counter.sv: the source side of credit-based flow control (read-only).
// It starts with CREDITS credits, one per free entry in the sink. Every beat sent spends one.
// Every credit the sink returns (its popped pulse) restores one after RETURN_DELAY cycles of wire.
// can_send is 1 while a credit is left, so the source never looks at ready.
module credit_counter #(parameter int CREDITS = 8, parameter int RETURN_DELAY = 2) (
  input  logic clk,
  input  logic rst_n,
  input  logic sent,          // a beat left the source at this edge
  input  logic returned,      // the sink freed an entry (its popped pulse)
  output int   credits,
  output logic can_send
);
  localparam int LAST = (RETURN_DELAY > 0) ? RETURN_DELAY - 1 : 0;
  logic pipe [RETURN_DELAY + 1];
  wire arrived = (RETURN_DELAY == 0) ? returned : pipe[LAST];
  assign can_send = (credits > 0);
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      credits <= CREDITS;
      for (int i = 0; i <= RETURN_DELAY; i++) pipe[i] <= 0;
    end else begin
      if (RETURN_DELAY > 0) begin
        pipe[0] <= returned;
        for (int i = 1; i < RETURN_DELAY; i++) pipe[i] <= pipe[i-1];
      end
      credits <= credits + (arrived ? 1 : 0) - (sent ? 1 : 0);
    end
  end
endmodule
Read included file: flow_tasks.svh
// flow_tasks.svh: the source side of the flow-control bench, as tasks (read-only).
// push(n) offers n beats back to back (values 0, 1, 2, ...) and honours ready; nothing is printed
// per beat. push_with_credits(n) sends a beat only while can_send is 1 and never looks at ready.
// summary(label) prints one line; reset_stats() clears the counters between runs.
int cycle = -1, peak = 0, source_held = 0, waited = 0;
always @(posedge clk) begin
  cycle = cycle + 1;
  if (count > peak) peak = count;
  if (rst_n && valid && !ready) source_held = source_held + 1;
end
task automatic push(input int n);
  for (int i = 0; i < n; i++) begin
    valid = 1; data = i[7:0];
    do @(posedge clk); while (!ready);       // taken at this edge
    #1;
  end
  valid = 0; data = 'x;
endtask
task automatic push_with_credits(input int n);
  for (int i = 0; i < n; i++) begin
    while (!can_send) begin                  // no credit: offer nothing and wait a cycle
      valid = 0; data = 'x; waited = waited + 1;
      @(posedge clk); #1;
    end
    valid = 1; data = i[7:0];
    @(posedge clk); #1;                      // taken at this edge: a credit promised the room
  end
  valid = 0; data = 'x;
endtask
task automatic reset_stats();
  peak = 0; source_held = 0; waited = 0;
endtask
task automatic summary(input string label);
  $display("%s: peak %0d held, %0d dropped, source held %0d edges, waited %0d cycles for credit",
           label, peak, drops, source_held, waited);
endtask

Check yourself

Predict the outputA 4-entry sink with no flow control. Before the stall it always holds exactly one beat, because a beat taken at an edge leaves at the next. The source offers one beat per cycle and the consumer stops for 6 cycles. What line does the bench print?
`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 = 1;                // 1: the consumer behind the FIFO takes a beat every cycle
  logic       out_valid, out_last, popped, can_send;
  logic [7:0] out_data;
  int         count, drops, credits;
  lossy_sink #(.DEPTH(4), .BACKPRESSURE(0)) 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), .drops(drops), .popped(popped));
  credit_counter #(.CREDITS(8), .RETURN_DELAY(2)) cc (.clk(clk), .rst_n(rst_n),
    .sent(valid && ready), .returned(popped), .credits(credits), .can_send(can_send));
  `include "flow_tasks.svh"
  // The consumer takes a beat every cycle, except for 6 cycles from edge 8.
  initial begin repeat (8) @(posedge clk); #1 drain = 0; repeat (6) @(posedge clk); #1 drain = 1; end
  initial begin
    repeat (2) @(posedge clk); #1 rst_n = 1;   // reset released after edge 1
    @(posedge clk); #1;                        // the first beat may start after edge 2
    push(12);
    repeat (8) @(posedge clk);
    summary("depth 4, stall 6");
    repeat (2) @(posedge clk);
    $finish;
  end
endmodule
Read included file: lossy_sink.sv
// lossy_sink.sv: a DEPTH-entry sink with the ports of link_sink and two ways to meet a full FIFO.
// BACKPRESSURE = 0: ready stays 1, and a beat that arrives while the FIFO is full is lost and
// counted in drops. BACKPRESSURE = 1: ready is 0 while the FIFO is full, so the source holds its
// beat (the behaviour of link_sink). popped is 1 for the cycle after a beat leaves: the credit a
// credit-counting source gets back. The consumer behind the FIFO takes a beat while drain is 1.
module lossy_sink #(parameter int DEPTH = 8, parameter bit BACKPRESSURE = 0, 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,
  output logic             out_valid,
  output logic [WIDTH-1:0] out_data,
  output logic             out_last,
  output int               count,       // beats held right now
  output int               drops,       // beats lost so far
  output logic             popped
);
  logic [WIDTH:0] mem [DEPTH];
  int wr = 0, rd = 0;
  wire full = (count >= DEPTH);
  assign ready     = BACKPRESSURE ? !full : 1'b1;
  assign out_valid = (count > 0);
  assign {out_last, out_data} = mem[rd];
  wire push = valid && !full;
  wire drop = valid && full && !BACKPRESSURE;
  wire pop  = drain && out_valid;
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      wr <= 0; rd <= 0; count <= 0; drops <= 0; popped <= 0;
    end else begin
      if (push) begin mem[wr] <= {last, data}; wr <= (wr + 1) % DEPTH; end
      if (pop) rd <= (rd + 1) % DEPTH;
      if (drop) drops <= drops + 1;
      count  <= count + (push ? 1 : 0) - (pop ? 1 : 0);
      popped <= pop;
    end
  end
endmodule
Read included file: credit_counter.sv
// credit_counter.sv: the source side of credit-based flow control (read-only).
// It starts with CREDITS credits, one per free entry in the sink. Every beat sent spends one.
// Every credit the sink returns (its popped pulse) restores one after RETURN_DELAY cycles of wire.
// can_send is 1 while a credit is left, so the source never looks at ready.
module credit_counter #(parameter int CREDITS = 8, parameter int RETURN_DELAY = 2) (
  input  logic clk,
  input  logic rst_n,
  input  logic sent,          // a beat left the source at this edge
  input  logic returned,      // the sink freed an entry (its popped pulse)
  output int   credits,
  output logic can_send
);
  localparam int LAST = (RETURN_DELAY > 0) ? RETURN_DELAY - 1 : 0;
  logic pipe [RETURN_DELAY + 1];
  wire arrived = (RETURN_DELAY == 0) ? returned : pipe[LAST];
  assign can_send = (credits > 0);
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      credits <= CREDITS;
      for (int i = 0; i <= RETURN_DELAY; i++) pipe[i] <= 0;
    end else begin
      if (RETURN_DELAY > 0) begin
        pipe[0] <= returned;
        for (int i = 1; i < RETURN_DELAY; i++) pipe[i] <= pipe[i-1];
      end
      credits <= credits + (arrived ? 1 : 0) - (sent ? 1 : 0);
    end
  end
endmodule
Read included file: flow_tasks.svh
// flow_tasks.svh: the source side of the flow-control bench, as tasks (read-only).
// push(n) offers n beats back to back (values 0, 1, 2, ...) and honours ready; nothing is printed
// per beat. push_with_credits(n) sends a beat only while can_send is 1 and never looks at ready.
// summary(label) prints one line; reset_stats() clears the counters between runs.
int cycle = -1, peak = 0, source_held = 0, waited = 0;
always @(posedge clk) begin
  cycle = cycle + 1;
  if (count > peak) peak = count;
  if (rst_n && valid && !ready) source_held = source_held + 1;
end
task automatic push(input int n);
  for (int i = 0; i < n; i++) begin
    valid = 1; data = i[7:0];
    do @(posedge clk); while (!ready);       // taken at this edge
    #1;
  end
  valid = 0; data = 'x;
endtask
task automatic push_with_credits(input int n);
  for (int i = 0; i < n; i++) begin
    while (!can_send) begin                  // no credit: offer nothing and wait a cycle
      valid = 0; data = 'x; waited = waited + 1;
      @(posedge clk); #1;
    end
    valid = 1; data = i[7:0];
    @(posedge clk); #1;                      // taken at this edge: a credit promised the room
  end
  valid = 0; data = 'x;
endtask
task automatic reset_stats();
  peak = 0; source_held = 0; waited = 0;
endtask
task automatic summary(input string label);
  $display("%s: peak %0d held, %0d dropped, source held %0d edges, waited %0d cycles for credit",
           label, peak, drops, source_held, waited);
endtask
Spot the bugA credited source feeds a 4-entry sink that has no ready; it starts with 4 credits and the consumer stops for 8 cycles. Each option is the wait that runs before a beat is sent. Which one overflows the sink?
Read included file: lossy_sink.sv
// lossy_sink.sv: a DEPTH-entry sink with the ports of link_sink and two ways to meet a full FIFO.
// BACKPRESSURE = 0: ready stays 1, and a beat that arrives while the FIFO is full is lost and
// counted in drops. BACKPRESSURE = 1: ready is 0 while the FIFO is full, so the source holds its
// beat (the behaviour of link_sink). popped is 1 for the cycle after a beat leaves: the credit a
// credit-counting source gets back. The consumer behind the FIFO takes a beat while drain is 1.
module lossy_sink #(parameter int DEPTH = 8, parameter bit BACKPRESSURE = 0, 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,
  output logic             out_valid,
  output logic [WIDTH-1:0] out_data,
  output logic             out_last,
  output int               count,       // beats held right now
  output int               drops,       // beats lost so far
  output logic             popped
);
  logic [WIDTH:0] mem [DEPTH];
  int wr = 0, rd = 0;
  wire full = (count >= DEPTH);
  assign ready     = BACKPRESSURE ? !full : 1'b1;
  assign out_valid = (count > 0);
  assign {out_last, out_data} = mem[rd];
  wire push = valid && !full;
  wire drop = valid && full && !BACKPRESSURE;
  wire pop  = drain && out_valid;
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      wr <= 0; rd <= 0; count <= 0; drops <= 0; popped <= 0;
    end else begin
      if (push) begin mem[wr] <= {last, data}; wr <= (wr + 1) % DEPTH; end
      if (pop) rd <= (rd + 1) % DEPTH;
      if (drop) drops <= drops + 1;
      count  <= count + (push ? 1 : 0) - (pop ? 1 : 0);
      popped <= pop;
    end
  end
endmodule
Read included file: credit_counter.sv
// credit_counter.sv: the source side of credit-based flow control (read-only).
// It starts with CREDITS credits, one per free entry in the sink. Every beat sent spends one.
// Every credit the sink returns (its popped pulse) restores one after RETURN_DELAY cycles of wire.
// can_send is 1 while a credit is left, so the source never looks at ready.
module credit_counter #(parameter int CREDITS = 8, parameter int RETURN_DELAY = 2) (
  input  logic clk,
  input  logic rst_n,
  input  logic sent,          // a beat left the source at this edge
  input  logic returned,      // the sink freed an entry (its popped pulse)
  output int   credits,
  output logic can_send
);
  localparam int LAST = (RETURN_DELAY > 0) ? RETURN_DELAY - 1 : 0;
  logic pipe [RETURN_DELAY + 1];
  wire arrived = (RETURN_DELAY == 0) ? returned : pipe[LAST];
  assign can_send = (credits > 0);
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      credits <= CREDITS;
      for (int i = 0; i <= RETURN_DELAY; i++) pipe[i] <= 0;
    end else begin
      if (RETURN_DELAY > 0) begin
        pipe[0] <= returned;
        for (int i = 1; i < RETURN_DELAY; i++) pipe[i] <= pipe[i-1];
      end
      credits <= credits + (arrived ? 1 : 0) - (sent ? 1 : 0);
    end
  end
endmodule
Read included file: flow_tasks.svh
// flow_tasks.svh: the source side of the flow-control bench, as tasks (read-only).
// push(n) offers n beats back to back (values 0, 1, 2, ...) and honours ready; nothing is printed
// per beat. push_with_credits(n) sends a beat only while can_send is 1 and never looks at ready.
// summary(label) prints one line; reset_stats() clears the counters between runs.
int cycle = -1, peak = 0, source_held = 0, waited = 0;
always @(posedge clk) begin
  cycle = cycle + 1;
  if (count > peak) peak = count;
  if (rst_n && valid && !ready) source_held = source_held + 1;
end
task automatic push(input int n);
  for (int i = 0; i < n; i++) begin
    valid = 1; data = i[7:0];
    do @(posedge clk); while (!ready);       // taken at this edge
    #1;
  end
  valid = 0; data = 'x;
endtask
task automatic push_with_credits(input int n);
  for (int i = 0; i < n; i++) begin
    while (!can_send) begin                  // no credit: offer nothing and wait a cycle
      valid = 0; data = 'x; waited = waited + 1;
      @(posedge clk); #1;
    end
    valid = 1; data = i[7:0];
    @(posedge clk); #1;                      // taken at this edge: a credit promised the room
  end
  valid = 0; data = 'x;
endtask
task automatic reset_stats();
  peak = 0; source_held = 0; waited = 0;
endtask
task automatic summary(input string label);
  $display("%s: peak %0d held, %0d dropped, source held %0d edges, waited %0d cycles for credit",
           label, peak, drops, source_held, waited);
endtask
ConceptA consumer stops for at most 30 cycles. The source sends one beat per cycle and the FIFO holds 2 beats when the stall begins. Which depth loses nothing?
ConceptWhat does backpressure do?
ConceptWhen is credit-based flow control the better choice over a ready wire?

Remember

  • A FIFO absorbs a stall only when it is deeper than the beats held plus the beats arriving during it. A rate mismatch cannot be sized away.
  • Backpressure is the sink lowering ready and the source holding its beat; the stall walks upstream and no data is lost.
  • Without flow control the FIFO depth is the whole safety margin, and a longer stall drops beats silently.
  • Credits grant permission in advance: one per free entry, spent per beat, returned per freed entry. They suit links where the answer takes cycles to travel.
  • A link needs about as many credits as beats it can send during the credit's round trip; fewer throttle it.

Practice