AMBA CHI · Module 14 · CHI Flow Control
Backpressure
Backpressure is how the fabric slows gracefully when a receiver is busy — straight from the credit mechanism, no extra signalling. When a home node's buffer fills it returns credits slowly; the sender's credit count falls to zero and it stalls, holding its flit until a credit returns. That withholding of credit returns is backpressure. It is graceful, not lossy: a stalled sender waits, never drops the flit, and needs no retry, the held flit sending automatically when a credit returns. The failure to avoid is misreading backpressure as a fault: the sender drops the held flit or re-issues the transaction, flooding the congested receiver into a retry storm rather than waiting. Representative model, not the specification.
Advanced15 min readAMBA CHIBackpressureCreditThrottleCongestion
Module 14 · Chapter 14.4 · CHI Flow Control
Project thread — 14.3 kept the fabric deadlock-free. 14.4 is graceful throttling; 14.5 is explicit retry when resources run out.
1. Learning Outcomes
By the end of this chapter you should be able to:
- Explain that backpressure is a receiver withholding credit returns to throttle a sender.
- State that a sender with zero credits stalls — holding its flit, not dropping it.
- Describe backpressure as the natural consequence of the credit mechanism — no extra signalling.
- Explain why a stalled flit resumes automatically when a credit returns.
- Diagnose the retry storm from misreading backpressure as a fault (dropping or re-issuing).
- Implement a representative backpressure model in SystemVerilog, Verilog-2001, and VHDL.
2. Why Should I Learn This?
Congestion is normal — a home node serving many requesters will sometimes fall behind, its buffers filling. The question is what happens next, and the credit mechanism already has the answer: the receiver stops returning credits, the sender's count hits zero, and the sender waits. This is backpressure, and it is the difference between a system that slows down under load and one that falls apart.
The crucial insight is that backpressure needs no new mechanism — it is the credit loop (Chapter 14.1) operating in its stalled regime. A sender that understands this simply holds its flit and lets the returning credit resume it. A sender that misunderstands it — treating stuck credits as an error and dropping the flit or re-issuing the transaction — turns a temporary slowdown into a retry storm: it floods the already-congested receiver with duplicate traffic, deepening the congestion it was reacting to. This chapter is how to slow down correctly: wait, do not thrash.
3. Key Terms
4. Previous Chapter Connection
Chapter 14.1 built the credit loop; this chapter is that loop under load. When the receiver drains slowly, credit returns slow down — you saw the mechanics in 14.1's small-budget sequence (the sender stopping at zero credits). This chapter names that state backpressure and studies it as a feature.
It also completes a picture from Chapter 14.3. There, the concern was a stall becoming permanent (deadlock); the drain rule guaranteed stalls are temporary. Backpressure is the temporary stall in action: the sender waits, responses keep draining (Chapter 14.3), credits return, and the sender resumes. Backpressure and deadlock avoidance are two sides of one coin — backpressure is the benign stall that deadlock avoidance guarantees will always end. This chapter is the benign side.
5. Core Concept — throttle by withholding credits
Backpressure is a receiver withholding credit returns, which stalls the sender, which holds its flit until a credit comes back — graceful, lossless throttling with no extra mechanism.
- Congestion withholds credits. When the receiver's buffer fills and it drains slowly, it returns credits slowly — or not at all while full. The sender's credit count falls to zero.
- The sender stalls, holding the flit. With zero credits the sender cannot send (Chapter 14.1) — so it waits, holding the flit. It does not drop it.
- A returned credit resumes it automatically. When the receiver frees a slot and returns a credit, the sender's count rises, and the held flit sends — no retry, no new signalling.
- It is the credit loop, stalled. Backpressure is not a separate feature; it is the built-in behavior of the credit mechanism when returns slow down. Congestion becomes waiting, never loss.
The synthesis:
Backpressure is a receiver throttling a sender by withholding credit returns. The sender's count falls to zero, it stalls — holding its flit rather than dropping it — and when a credit finally returns, the held flit sends automatically. It is the credit mechanism (Chapter 14.1) in its stalled regime: graceful, lossless throttling with no extra signalling. Misreading it as a fault — dropping or re-issuing — turns a slowdown into a retry storm.
6. Engineering Mental Model — a queue at a single window
Think of a service counter with one window and a line of people (flits) waiting to be served.
- The window can only serve so fast. When it is busy, it simply does not call the next person — that is withholding a credit. The line waits.
- A waiting person stays in line. They do not leave and come back, and they certainly do not shove — they just hold their place until the window calls them.
- When the clerk finishes and calls "next" (returns a credit), the front person steps up automatically. The line advances by exactly one.
- Now imagine an impatient person who, seeing the window busy, gives up and rejoins at the back repeatedly, or cuts to the front demanding service. If everyone did this, the area floods with people re-queuing, the clerk is mobbed, and service gets slower — the line's orderly waiting collapses into a scrum. That scrum is a retry storm.
Waiting in line is backpressure working. Leaving and re-queuing is the bug — it makes the congestion worse, not better.
7. Engineering Diagram — credits withheld, then resumed
The sender transmits while credits flow, stalls when they are withheld, and resumes when one returns — holding its flit the whole time. The self-message is the stall: waiting, not dropping. The DebugLab replaces that wait with a drop or a re-issue.
8. Backpressure vs Dropping
The contrast that defines graceful throttling.
| Property | Backpressure (correct) | Drop / re-issue (bug) |
|---|---|---|
| Flit under congestion | held, waits | dropped or duplicated |
| Extra signalling | none — credit loop | timeouts, re-sends |
| Effect on receiver | load eases (fewer arrivals) | load worsens (flood) |
| Recovery | automatic on credit return | retry storm, maybe never |
| Correctness | lossless, in-order | lost or duplicate flits |
The rule to carry: under backpressure, do nothing but wait. The credit mechanism is already handling the congestion — the sender's job is simply to hold and let a returning credit resume it. Every "active" response to backpressure (drop, timeout, re-issue) is not just unnecessary, it is harmful: it adds traffic to a receiver that is congested precisely because it has too much traffic. The correct action is the passive one.
9. Why Waiting Is the Right Response
The reasoning behind the passive response.
- Backpressure is self-limiting. It exists because the receiver is full; the sender waiting reduces arrivals, giving the receiver time to drain. Waiting is negative feedback.
- Re-issuing is positive feedback. Dropping and re-sending adds arrivals to a full receiver — amplifying the congestion that caused the backpressure. Positive feedback on congestion is a storm.
- The credit guarantees resumption. A withheld credit is not lost — the receiver will drain and return it (deadlock avoidance guarantees the stall ends, Chapter 14.3). So waiting always terminates.
- No timeout is appropriate. Because the credit is guaranteed to return, a timeout on a backpressured flit is a false alarm — it fires on a healthy stall and triggers a harmful re-issue.
The point to carry:
Backpressure is a control system, and the sender's response determines whether the loop is stable or unstable. Waiting is negative feedback: more congestion → more waiting → fewer arrivals → congestion eases. That loop converges. Dropping-and-retrying is positive feedback: more congestion → more timeouts → more re-issues → more congestion. That loop diverges — a retry storm where the fabric spends its bandwidth carrying duplicate attempts and useful work grinds to a halt. The credit mechanism is engineered so that the stable response is also the trivial one: literally do nothing, hold the flit, and let the returning credit resume you. A designer's only job is to not add a destabilizing active response on top — no timeout, no speculative re-issue, no drop. The most robust behavior under congestion is the most passive one, which is a counterintuitive but recurring truth in flow-controlled systems.
10. A Congestion Episode — waiting it out
The home node gets busy; the sender rides it out.
- Credits flowing. The RN sends requests; the HN returns credits as it processes them. Steady state.
- The HN's buffer fills. A burst arrives; the HN cannot drain fast enough and its buffer fills. It stops returning credits.
- The RN's credits hit zero. After its in-flight sends, the RN's count reaches 0. Its next flit cannot send — the RN holds it and waits.
- The RN waits — no drop, no retry. The held flit sits, ready. The RN issues nothing new on this channel. Arrivals at the HN drop to zero, letting it drain.
- The HN drains and returns a credit. A slot frees; the HN returns a credit. The RN's count → 1.
- The held flit sends automatically. The RN transmits the flit it was holding. Normal flow resumes; the episode is over.
The congestion resolved itself because the RN waited — reducing load until the HN caught up. The DebugLab is an RN that, at step 4, times out and re-issues, flooding the HN instead of easing off.
11. RTL / Hardware View — holding a flit under backpressure
A pending flit is held while credits are zero and sent automatically when a credit returns — never dropped. Representative.
// Representative backpressure model (educational).
// When credits are withheld (credits == 0), a pending flit is HELD, not dropped or
// re-issued. It transmits automatically the moment a credit returns. Backpressure is
// just the credit mechanism stalled -- the correct response is to WAIT.
module chi_backpressure #(parameter CW = 4) (
input logic clk, rst_n,
input logic credit_return, // receiver freed a slot
input logic new_flit, // a flit becomes pending to send
input logic [CW-1:0] init_credits,
output logic flit_pending, // a flit is waiting to be sent
output logic do_send, // the flit is transmitted this cycle
output logic [CW-1:0] credits
);
logic [CW-1:0] cr_q;
logic pend_q;
assign credits = cr_q;
assign flit_pending = pend_q;
// Send only when a flit is pending AND a credit exists. Under backpressure (cr_q==0)
// the flit simply stays pending -- it is never dropped.
assign do_send = pend_q && (cr_q != 0);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cr_q <= init_credits; pend_q <= 1'b0;
end else begin
// Credit accounting (as Chapter 14.1).
case ({credit_return, do_send})
2'b10: cr_q <= cr_q + 1'b1;
2'b01: cr_q <= cr_q - 1'b1;
default: cr_q <= cr_q;
endcase
// Pending flit: set on arrival, cleared ONLY when actually sent. Never dropped.
if (new_flit) pend_q <= 1'b1;
else if (do_send) pend_q <= 1'b0;
end
end
endmoduleThe same behavior in Verilog-2001:
// Representative backpressure model (Verilog-2001).
module chi_backpressure #(parameter CW = 4) (
input wire clk, rst_n, credit_return, new_flit,
input wire [CW-1:0] init_credits,
output wire flit_pending, do_send,
output wire [CW-1:0] credits
);
reg [CW-1:0] cr_q;
reg pend_q;
assign credits = cr_q;
assign flit_pending = pend_q;
assign do_send = pend_q & (cr_q != {CW{1'b0}});
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cr_q <= init_credits; pend_q <= 1'b0;
end else begin
if (credit_return & ~do_send) cr_q <= cr_q + 1'b1;
else if (~credit_return & do_send) cr_q <= cr_q - 1'b1;
if (new_flit) pend_q <= 1'b1;
else if (do_send) pend_q <= 1'b0; // cleared only on real send
end
end
endmoduleAnd in VHDL:
-- Representative backpressure model (VHDL).
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity chi_backpressure is
generic ( CW : integer := 4 );
port (
clk, rst_n : in std_logic;
credit_return : in std_logic;
new_flit : in std_logic;
init_credits : in unsigned(CW-1 downto 0);
flit_pending : out std_logic;
do_send : out std_logic;
credits : out unsigned(CW-1 downto 0)
);
end entity;
architecture rtl of chi_backpressure is
signal cr_q : unsigned(CW-1 downto 0) := (others => '0');
signal pend_q : std_logic := '0';
signal has_cred : std_logic;
signal snd : std_logic;
begin
has_cred <= '0' when cr_q = 0 else '1';
snd <= pend_q and has_cred; -- send only with a credit
do_send <= snd;
flit_pending <= pend_q;
credits <= cr_q;
process (clk, rst_n)
begin
if rst_n = '0' then
cr_q <= init_credits; pend_q <= '0';
elsif rising_edge(clk) then
if credit_return = '1' and snd = '0' then cr_q <= cr_q + 1;
elsif credit_return = '0' and snd = '1' then cr_q <= cr_q - 1; end if;
if new_flit = '1' then pend_q <= '1';
elsif snd = '1' then pend_q <= '0'; -- cleared only on real send
end if;
end if;
end process;
end architecture;In all three, a pending flit is cleared only by an actual send (do_send) — under backpressure (credits == 0) it stays pending and waits, transmitting automatically when a credit returns. The DebugLab clears the pending flit on a timeout instead, dropping or re-issuing it.
12. Verification View — a held flit is never dropped
The properties that keep backpressure lossless: a pending flit persists until sent, and a send needs a credit.
// Bind to chi_backpressure.
// 1. A pending flit is never lost -- it stays pending until it is actually sent.
property p_pending_until_sent;
@(posedge clk) disable iff (!rst_n)
(flit_pending && !do_send && !new_flit) |=> flit_pending;
endproperty
// 2. Under backpressure (no credits), a pending flit is held, not sent or dropped.
property p_hold_under_backpressure;
@(posedge clk) disable iff (!rst_n)
(flit_pending && credits == 0) |-> !do_send ##0 (flit_pending);
endproperty
// 3. When a credit is available, a pending flit is sent (auto-resume).
property p_auto_resume;
@(posedge clk) disable iff (!rst_n)
(flit_pending && credits != 0) |-> do_send;
endpropertyThe system point, beyond the checks:
The property that matters most is the liveness one — a held flit is eventually sent — and it is guaranteed only by composing this chapter with the previous one. Backpressure alone gives safety: the flit is never dropped,
p_pending_until_sentholds. But "never dropped" is compatible with "never sent" — a flit could be held forever if credits never return. What guarantees they do return is deadlock avoidance (Chapter 14.3): because responses and data always drain, the receiver always makes progress, always frees slots, and always returns credits. So the stall always ends. This is why the two chapters belong together — backpressure is only benign because deadlock avoidance makes it temporary. A held flit in a deadlock-free fabric is a flit that will be sent; the same held flit in a fabric with a dependency cycle is a flit that might wait forever. Safety is local to this module; liveness is a property of the whole fabric's graph.
- What it proves: a pending flit persists until sent; it is held (not dropped) under backpressure; it auto-resumes on a credit.
- What it does not prove: that credits eventually return — that is deadlock avoidance (Chapter 14.3).
- Bug signature:
flit_pendingdeasserting without ado_send— a flit dropped or re-issued.
13. Testbench — a stalled flit must survive and auto-resume
Withholds credits, checks the flit is held, then returns one and checks it sends.
module tb_chi_backpressure;
localparam CW = 4;
logic clk = 0, rst_n = 0, credit_return = 0, new_flit = 0;
logic [CW-1:0] init_credits;
logic flit_pending, do_send;
logic [CW-1:0] credits;
int errors = 0;
chi_backpressure #(.CW(CW)) dut (.*);
always #5 clk = ~clk;
initial begin
init_credits = 4'd1;
@(posedge clk) rst_n = 1;
// Spend the only credit on a first flit.
@(posedge clk) new_flit = 1;
@(posedge clk) new_flit = 0; // flit1 sent, credits now 0
// A second flit arrives under backpressure (credits == 0).
@(posedge clk) new_flit = 1;
@(posedge clk) new_flit = 0;
// It must be HELD, not sent, not dropped.
repeat (4) begin
@(posedge clk);
if (!flit_pending) begin errors++; $display("FAIL held flit was dropped under backpressure"); end
if (do_send) begin errors++; $display("FAIL sent without a credit"); end
end
$display("PASS flit held under backpressure (credits=%0d)", credits);
// Return a credit -> the held flit must send automatically.
@(posedge clk) credit_return = 1;
@(posedge clk) credit_return = 0;
#1;
if (flit_pending) begin errors++; $display("FAIL held flit did not auto-resume"); end
else $display("PASS held flit sent automatically on credit return");
if (errors == 0) $display("ALL TESTS PASSED");
else $display("%0d FAILURE(S)", errors);
$finish;
end
endmoduleExpected output:
PASS flit held under backpressure (credits=0)
PASS held flit sent automatically on credit return
ALL TESTS PASSED14. DebugLab — misreading backpressure as a fault
Misreading backpressure as a fault
BACKPRESSURE MISREAD AS A FAULT -> DROP/RE-ISSUE -> RETRY STORM DEEPENS CONGESTIONThroughput collapses under load rather than gracefully leveling off — as offered load rises past the receiver's capacity, useful throughput falls, and the fabric fills with duplicate requests. Lost or doubled transactions appear. Reducing the offered load abruptly recovers it.
The sender re-issued instead of waiting:
HN congested -> withholds credit returns -> RN credits = 0 (normal backpressure)
buggy RN: credits stuck at 0 for N cycles -> "timeout!" -> re-issue the request
-> original flit dropped/duplicated
-> re-issued request adds load to the already-full HN
-> HN more congested -> withholds credits longer -> more timeouts -> more re-issues
=> positive feedback -> RETRY STORM -> throughput collapses
correct: credits = 0 -> HOLD the flit -> it sends automatically on credit returnThe RN attacked a healthy stall as if it were a failure.
The sender timed out on a backpressured flit and re-issued it, instead of holding it. From that point it was adding load to a congested receiver rather than easing off.
Backpressure is a normal, self-limiting throttle, so the sender must wait; dropping or re-issuing turns negative feedback into a positive-feedback retry storm. A withheld credit is not a fault — the credit mechanism guarantees it returns once the receiver drains (deadlock avoidance ensures the stall is temporary, Chapter 14.3). Waiting reduces arrivals and lets the receiver catch up (negative feedback). Re-issuing adds arrivals to a receiver that is congested because it already has too many (positive feedback), amplifying the congestion into a storm. This is distinct from a genuine resource-exhaustion retry (Chapter 14.5), which the receiver explicitly requests — here nothing was requested; the sender invented a fault where there was only backpressure.
Treat backpressure as a graceful throttle: hold the pending flit and wait, letting it transmit automatically when a credit returns, exactly as the model does. No timeout on a backpressured flit, no drop, no re-issue. The passive response is the correct one.
15. Common Mistakes
- Timing out on backpressure. Assumption: a stall is a fault. Bug: retry storm (the DebugLab). Prevention: no timeout on a backpressured flit.
- Dropping a held flit. Assumption: stuck means lost. Bug: lost transaction. Prevention: hold until sent.
- Re-issuing under congestion. Assumption: retry helps. Bug: floods the receiver. Prevention: wait, do not re-send.
- Adding load when throttled. Assumption: push harder. Bug: positive feedback. Prevention: ease off — negative feedback.
- Confusing backpressure with resource retry. Assumption: same thing. Bug: unrequested re-issue. Prevention: backpressure is implicit (14.4); retry is explicit (14.5).
- Clearing a pending flit on anything but a send. Assumption: convenient reset. Bug: silent drop. Prevention: clear only on real send.
16. Engineering Checklist
- Under zero credits, hold the pending flit — do not drop it.
- Let a returning credit auto-resume the held flit.
- Apply no timeout to a backpressured flit.
- Never re-issue a transaction in response to backpressure.
- Clear a pending flit only on an actual send.
- Treat backpressure as negative feedback — ease off, do not push.
17. Key Takeaways
- Backpressure is a receiver withholding credit returns to throttle a sender.
- A sender with zero credits stalls — holding its flit, not dropping it.
- It is the credit mechanism in its stalled regime — no extra signalling.
- A held flit resumes automatically when a credit returns.
- Misreading backpressure as a fault causes a retry storm that deepens congestion.
- Under backpressure, wait; the model here is representative.
18. Quick Revision
Backpressure. When a receiver (e.g. a busy home node) fills and drains slowly, it returns credits slowly — or not at all — so the upstream sender's credit count falls to zero and it stalls, holding its flit until a credit comes back. That withholding of credit returns, propagating upstream, is backpressure: graceful, lossless throttling that is simply the credit mechanism (Chapter 14.1) in its stalled regime — no extra signalling. The held flit resumes automatically the moment a credit returns, and the stall is guaranteed temporary because deadlock avoidance (Chapter 14.3) ensures the receiver keeps draining. The correct response is entirely passive: wait. The failure to avoid: misreading backpressure as a fault — timing out, dropping the held flit, or re-issuing the transaction. That loses or duplicates the flit and adds load to a receiver congested precisely because it has too much load, turning waiting's negative feedback into a positive-feedback retry storm that collapses throughput. A withheld credit is not lost — it will return. Hold the flit, do nothing, and let the credit resume it. Representative model; 14.5 covers explicit retry when resources (not buffers) run out.
Coming Next
Chapter 14.5 — Congestion Handling. Backpressure throttles when a buffer is full; a different mechanism handles a home node that cannot allocate a resource at all. Chapter 14.5 covers congestion handling — the explicit retry protocol, where a home node returns a RetryAck to a request it cannot accept and later issues a protocol credit (PCrdGrant) inviting the retry, and why a request node must wait for that grant rather than spin-retrying.