AMBA CHI · Module 18 · CHI Debugging
Credit Issues
A credit issue freezes one channel by breaking its flow control. Credit flow control depends on conservation — every credit consumed on a send must be returned when the receiver frees the slot. A credit leak breaks that: a slot is freed but no credit returned, so the sender's count drifts down to a permanent stall — even though the receiver has room. The symptom is a sender stalling while the receiver is not full. The distinguishing signature is a count drifting down while the receiver has free slots — separating a leak from a backpressure stall. The diagnosis is a conservation check: granted must equal available plus in-flight; a short sum means a leak. Representative model, not the specification.
Advanced15 min readAMBA CHIDebuggingCredit LeakConservationFlow Control
Module 18 · Chapter 18.7 · CHI Debugging
Project thread — 18.6 debugged deadlock. 18.7 debugs credit issues; 18.8 debugs ordering violations.
1. Learning Outcomes
By the end of this chapter you should be able to:
- Define a credit leak — credits consumed but never returned, so the count drifts down.
- Recognize the symptom — a sender stalling while the receiver has room.
- Distinguish a credit leak from legitimate backpressure (buffer really full).
- Read the signature — the credit count drifting down monotonically.
- Diagnose by credit conservation — granted = available + in-flight.
- Implement a representative credit-leak detector in SystemVerilog, Verilog-2001, and VHDL.
2. Why Should I Learn This?
Credit-based flow control (Chapter 14.1) is what keeps a sender from overflowing a receiver — but it depends on a conservation law: every credit consumed on a send must eventually be returned when the receiver frees the slot. If a credit is lost — a slot freed without returning a credit, or a return flit dropped — the sender's usable credit count drifts down and never recovers. Eventually it hits zero and the sender stalls forever, even though the receiver's buffer has plenty of room. Throughput on that channel decays to nothing.
The trap is that this looks like backpressure — a stalled sender — but it is not: under backpressure the buffer is genuinely full and the stall resolves; under a credit leak the buffer is empty and the stall is permanent. The distinguishing signature is the credit count drifting down over time while the receiver has free slots. The diagnosis is a conservation check: the credits granted must always equal the credits available (at the sender) plus the credits in flight (slots occupied at the receiver) — and when that sum drops below the grant, a credit has leaked. This chapter is how to spot a leak versus a stall and find the accounting bug.
3. Key Terms
4. Previous Chapter Connection
This chapter debugs the credit mechanism of Chapter 14.1. The root cause is a violation of its accounting discipline — not returning a credit when a slot frees, double-consuming, or a lost return flit — so the conservation the mechanism relies on is broken. This chapter finds the leak from its drift signature.
It is the debug counterpart of Chapter 14.1's invariant, and closely related to Chapter 14.4's backpressure. There, a stall was benign — the buffer was full and the stall would resolve when the receiver drained. Here, the stall is pathological — the buffer is not full, and the stall is permanent because a credit was lost. The debug turns on distinguishing these two: same symptom (a stalled sender), opposite cause (full buffer vs leaked credit). The conservation check is what tells them apart — under backpressure conservation holds (the credits are in-flight), under a leak it is broken (the sum is short). This chapter is the flow-control-accounting debug.
5. Core Concept — check credit conservation
A credit leak is credits consumed but never returned, drifting the count down until the sender stalls with the receiver not full; the diagnosis is credit conservation — granted = available + in-flight.
- Credits must be conserved. Every credit consumed on a send must be returned when the receiver frees the slot — so the total is constant: granted = available + in-flight.
- A leak breaks conservation. A slot freed without returning a credit (or a lost return) loses a credit — available + in-flight drops below granted, permanently.
- The count drifts down. With credits leaking, the sender's available count trends downward over time until it reaches zero and the sender stalls forever.
- Not backpressure. Under backpressure the buffer is full (in-flight = granted, available = 0) and the stall resolves; under a leak the buffer has room yet the sender is stalled — conservation is violated.
The synthesis:
A credit leak is credits consumed but never returned — a slot freed without a credit return, or a lost return flit — so the sender's available count drifts down until it stalls permanently while the receiver has room. It is not backpressure (full buffer, resolving stall). The diagnosis is conservation: granted = available + in-flight must always hold; when available + in-flight < granted, a credit has leaked.
6. Engineering Mental Model — a coat-check with vanishing tickets
Think of a coat-check with a fixed number of hooks (buffer slots) and matching tickets (credits).
- Each coat checked in takes a ticket (consumes a credit); each coat retrieved frees a hook and the ticket comes back into circulation (a credit return). The total tickets — in-hand plus on-coats — is constant.
- Credit leak: occasionally a coat is retrieved (a hook frees) but the ticket is thrown away instead of returned to circulation. The number of tickets in circulation slowly shrinks.
- Over time, fewer and fewer tickets exist, until there are none left to hand out — so no new coat can be checked in, even though hooks are empty. The line is stuck with an empty cloakroom.
- To diagnose, you count: tickets-in-hand plus coats-on-hooks should equal the original ticket count. When it comes up short, tickets have leaked — and you look for the retrieval that failed to return its ticket.
The tickets are credits; the hooks are buffer slots; the shrinking count is the drift; the count-vs-original is the conservation check. A permanently stuck cloakroom with empty hooks is a leak, not a genuinely full room.
7. Engineering Diagram — the credit leak
The receiver freed a slot but did not return the credit — that credit is lost. Repeated, the sender's count drifts to zero and it stalls, though the buffer has room. The bug is the missing credit return on a slot free — found by the conservation shortfall. The debugger sees the count drift with an empty buffer.
8. Waveform Signature
Credit leak: count drifts down while the buffer has room
6 cyclesThe signature is avail+flight falling below granted (8) — conservation is broken — while in_flight shows the buffer is not full. The leak signal marks where the sum first drops. A credit count drifting down with a non-full buffer is a leak, not backpressure (where the sum would stay at 8 with in_flight rising to fill it).
9. Diagnosis Path
The methodical conservation check.
| Step | Action | What it finds |
|---|---|---|
| 1. Symptom | a sender stalls; channel throughput decays | suspect a credit issue |
| 2. Buffer state | is the receiver buffer full or has room? | leak (room) vs backpressure (full) |
| 3. Conservation | compute available + in-flight vs granted | equal (ok) or short (leak) |
| 4. Onset | when the sum first dropped below granted | the leak's onset |
| 5. The event | which slot free did not return a credit | the leaking event |
| 6. Root cause | missing return, double-consume, or lost flit | the accounting bug (14.1) |
The rule to carry: conservation distinguishes a leak from a stall. A stalled sender is ambiguous — it could be benign backpressure (buffer full) or a leak (buffer empty). The conservation check disambiguates: under backpressure, granted = available + in-flight still holds (the credits are all in-flight); under a leak, the sum is short (a credit vanished). Once you see the shortfall, trace to the slot free that failed to return a credit — the accounting bug — and the drift stops.
10. Tracing a Credit Leak — a worked trace
A channel's sender stalls and its throughput decays to zero over time, though the receiver is idle.
- Symptom: decaying throughput. The sender sends fewer and fewer flits over time until it stops — yet the receiver's buffer is mostly empty.
- Buffer has room. The receiver's buffer occupancy is low — it is not applying backpressure. So the stall is not legitimate; suspect a leak.
- Check conservation. Grant = 8. Over time, available drifts to 1 while in-flight is 4 — sum = 5, short of 8 by 3. Three credits have leaked.
- Find the onset. The sum first dropped below 8 at a specific event — a slot free at the receiver.
- The leaking event. At that free, the receiver processed a flit and freed the slot but did not send a credit return — so a credit vanished (the Chapter 14.1 bug). It happened again on later frees.
- Root cause and fix. The receiver's credit-return logic misses a slot-free case (a return not issued). Fix it to return a credit on every slot free; conservation holds and the drift stops.
The decaying throughput traced through the conservation shortfall to a missing credit return. The DebugLab formalizes this.
11. Detector View — a credit-conservation detector
Check that available plus in-flight equals granted; flag a leak when the sum is short. Representative.
// Representative credit-conservation detector (educational).
// Credits are conserved: granted == available (at sender) + in_flight (slots occupied at
// receiver). A credit consumed on a send must be returned on a slot free. When
// available + in_flight < granted, a credit has LEAKED (freed without returning, or lost).
module chi_credit_leak #(parameter W = 8) (
input logic [W-1:0] granted, // total credits granted (constant)
input logic [W-1:0] available, // credits the sender currently holds
input logic [W-1:0] in_flight, // credits spent on flits still occupying slots
output logic [W:0] accounted, // available + in_flight (should equal granted)
output logic leak, // conservation broken -> a credit leaked
output logic [W:0] leaked_count // how many credits leaked
);
assign accounted = available + in_flight;
// A leak: fewer credits accounted for than were granted.
assign leak = (accounted < granted);
assign leaked_count = (accounted < granted) ? (granted - accounted) : '0;
endmoduleThe same behavior in Verilog-2001:
// Representative credit-conservation detector (Verilog-2001).
module chi_credit_leak #(parameter W = 8) (
input [W-1:0] granted, available, in_flight,
output [W:0] accounted,
output leak,
output [W:0] leaked_count
);
assign accounted = available + in_flight;
assign leak = (accounted < granted);
assign leaked_count = (accounted < granted) ? (granted - accounted) : {(W+1){1'b0}};
endmoduleAnd in VHDL:
-- Representative credit-conservation detector (VHDL).
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity chi_credit_leak is
generic ( W : integer := 8 );
port (
granted : in unsigned(W-1 downto 0);
available : in unsigned(W-1 downto 0);
in_flight : in unsigned(W-1 downto 0);
accounted : out unsigned(W downto 0);
leak : out std_logic;
leaked_count : out unsigned(W downto 0)
);
end entity;
architecture rtl of chi_credit_leak is
signal acc : unsigned(W downto 0);
begin
acc <= resize(available, W+1) + resize(in_flight, W+1);
accounted <= acc;
leak <= '1' when acc < resize(granted, W+1) else '0';
leaked_count <= (resize(granted, W+1) - acc) when acc < resize(granted, W+1)
else (others => '0');
end architecture;All three compute accounted = available + in_flight and flag leak when it is less than granted, reporting the leaked count. Bind it to a channel's credit signals and it fires the instant conservation breaks — at the leaking slot-free, not the far-later stall. The DebugLab's leak is a missing credit return.
12. Assertion View — credits are conserved
The properties formalize the invariant: available plus in-flight always equals granted.
// Bind to chi_credit_leak.
// 1. Credits are conserved: available + in_flight always equals granted.
property p_credits_conserved;
@(posedge clk) disable iff (!rst_n)
(available + in_flight == granted);
endproperty
// 2. A leak is flagged exactly when the accounted total is short.
property p_leak_iff_short;
@(posedge clk) disable iff (!rst_n)
leak == (available + in_flight < granted);
endproperty
// 3. The accounted total never EXCEEDS granted (that would be a double-return bug).
property p_no_over_credit;
@(posedge clk) disable iff (!rst_n)
(available + in_flight <= granted);
endpropertyThe system point, beyond the checks:
A conservation law is the most powerful kind of invariant a debugger can have, because it is checkable at every instant from a small amount of state and it localizes in time the exact moment the law breaks. Credits are a conserved quantity —
grantedis constant, andavailable + in_flightmust equal it forever — so any deviation is a bug at the cycle it appears, no matter where the far-away symptom (a stall) shows up. This is the same shape as the dirty-data conservation of Chapter 18.1: identify the conserved quantity, express it as an equality that must always hold, and a violation points at the transaction that broke it. The two failure directions are both caught: available + in_flight < granted is a leak (a credit vanished — a missing return or lost flit), and available + in_flight > granted is an over-credit (a credit was returned twice or spuriously — which is worse, because it lets the sender overflow the receiver, Chapter 14.1's original bug). So the single conservation equality catches both the stall-causing leak and the overflow-causing double-return. The general debugging lesson, recurring through this module, is to find the system's conservation laws — dirty data, credits, ownership — and monitor them as equalities, because they turn a distant, confusing symptom into a precise, time-localized violation.
- What it proves: credits are conserved (sum equals granted); leaks and over-credits are flagged.
- What it does not prove: the grant matches the real buffer capacity — a design-time obligation (14.1).
- Bug signature:
available + in_flight != granted— short (leak) or over (double-return).
13. Testbench — a leaked credit must break conservation
Leaks credits (sum falls below granted) and checks the detector flags it, distinguishing from backpressure.
module tb_chi_credit_leak;
localparam W = 8;
logic [W-1:0] granted, available, in_flight;
logic [W:0] accounted, leaked_count;
logic leak;
int errors = 0;
chi_credit_leak #(.W(W)) dut (.*);
initial begin
granted = 8;
// CONSERVED (backpressure): buffer full -> in_flight=8, available=0, sum=8. NOT a leak.
available = 0; in_flight = 8; #1;
if (leak) begin errors++; $display("FAIL backpressure flagged as leak"); end
else $display("PASS full buffer (backpressure): conserved, no leak (sum=%0d)", accounted);
// CONSERVED (normal): available=5, in_flight=3, sum=8. No leak.
available = 5; in_flight = 3; #1;
if (leak) begin errors++; $display("FAIL normal state flagged as leak"); end
else $display("PASS normal: conserved (sum=%0d = granted)", accounted);
// LEAK: available=1, in_flight=4, sum=5 < 8 -> 3 credits leaked (buffer has room!).
available = 1; in_flight = 4; #1;
if (!leak) begin errors++; $display("FAIL leak NOT detected"); end
else if (leaked_count !== 3) begin errors++; $display("FAIL wrong leaked count: %0d", leaked_count); end
else $display("PASS leak detected: %0d credits leaked (sum=%0d < granted=8)", leaked_count, accounted);
if (errors == 0) $display("ALL TESTS PASSED");
else $display("%0d FAILURE(S)", errors);
$finish;
end
endmoduleExpected output:
PASS full buffer (backpressure): conserved, no leak (sum=8)
PASS normal: conserved (sum=8 = granted)
PASS leak detected: 3 credits leaked (sum=5 < granted=8)
ALL TESTS PASSED14. DebugLab — a channel's throughput decaying to a stall
A channel's throughput decaying to a stall
SENDER STALLS WITH AN EMPTY RECEIVER -> CREDITS LEAK (SLOT FREED WITHOUT RETURNING A CREDIT) -> CONSERVATION BROKENA channel's throughput decays over time until the sender stalls completely, yet the receiver's buffer is mostly empty — it is not applying backpressure. Other channels are fine. The decay is gradual and monotonic, distinguishing it from a sudden freeze.
The conservation check comes up short:
symptom: sender throughput decays to 0; receiver buffer LOW (not full) -> not backpressure
step 3 - conservation: granted = 8; over time available = 1, in_flight = 4
-> available + in_flight = 5 < granted 8 -> 3 credits LEAKED
step 4/5 - onset: sum first fell below 8 at a receiver SLOT-FREE event
-> receiver freed a slot but did NOT send a credit return -> credit vanished
-> repeated frees without returns drove available -> 0 -> permanent stall
correct: return a credit on EVERY slot free -> sum stays = 8 -> no driftThe buffer had room, but the sender had no credits — because credits leaked.
A receiver slot-free event failed to return its credit — the first cycle available + in-flight fell below granted. Each subsequent missed return leaked another credit, drifting the sender's count toward zero.
Credits are a conserved quantity — granted equals available plus in-flight — so a credit leak (a slot freed without returning a credit, or a lost return) breaks conservation and drifts the sender's count down to a permanent stall while the receiver has room. The stall looks like backpressure but is not: under backpressure conservation holds (all credits are in-flight, buffer full); under a leak the sum is short (a credit vanished, buffer empty). The conservation equality distinguishes them and localizes the leak to the slot-free that missed its return — a Chapter 14.1 accounting bug (a return not issued on freeing a slot). The same equality catches the opposite bug too: an over-count (double-return) that would let the sender overflow the receiver. Monitor the conservation law as an equality.
Return a credit on every slot free — as Chapter 14.1's accounting requires — so credits are conserved and granted = available + in-flight always holds. The sender's count stops decaying and throughput recovers. Confirm with the detector that the accounted total never falls below (leak) or rises above (double-return) the grant. Conserve credits exactly.
15. Common Mistakes
- Mistaking a leak for backpressure. Assumption: a stalled sender means a full buffer. Bug: the buffer is empty. Prevention: check conservation.
- Not conserving credits. Assumption: returns are best-effort. Bug: drift to a stall. Prevention: return on every slot free.
- Missing a slot-free case. Assumption: all frees return a credit. Bug: a leaking path. Prevention: audit every free.
- Double-returning a credit. Assumption: extra returns are safe. Bug: over-credit, overflow. Prevention: one return per free.
- Ignoring the drift. Assumption: throughput is just low. Bug: a slow leak. Prevention: watch the count trend.
- Confusing with deadlock. Assumption: same frozen fabric. Bug: flow-control vs cycle. Prevention: 18.6 is a cycle; 18.7 is conservation.
16. Engineering Checklist
- On a stalled sender, check if the receiver buffer has room (leak) or is full (backpressure).
- Compute credit conservation — available + in-flight vs granted.
- Flag a leak when the sum is short, an over-credit when it is over.
- Find the onset — the slot free that missed its credit return.
- Return a credit on every slot free; never double-return.
- Watch the sender's credit count for a downward drift.
17. Key Takeaways
- A credit leak is credits consumed but never returned — the count drifts down.
- The symptom is a sender stalling while the receiver has room.
- It is not backpressure — under backpressure the buffer is genuinely full.
- The signature is a credit count drifting down monotonically.
- Diagnose by conservation — granted = available + in-flight.
- Conserve credits exactly; the model here is representative.
18. Quick Revision
Credit issues. Credit-based flow control (Chapter 14.1) depends on a conservation law: every credit consumed on a send must be returned when the receiver frees the slot, so granted = available + in-flight holds forever. A credit leak breaks it — a slot freed without returning a credit, or a lost return flit — so the sender's available count drifts down over time until it hits zero and the sender stalls permanently, even though the receiver's buffer has room and throughput on the channel decays. The trap is that this looks like backpressure, but it is the opposite: under backpressure the buffer is genuinely full (all credits in-flight, sum still = granted) and the stall resolves; under a leak the buffer is empty (sum short) and the stall is permanent. The distinguishing signature is a credit count drifting down while the receiver has free slots. The diagnosis is the conservation check: available + in-flight vs granted — short means a leak, over means a double-return (which lets the sender overflow, the original 14.1 bug). Trace the shortfall to the slot free that missed its credit return — the accounting bug — and fix it to return a credit on every free. A conservation law turns a distant stall into a time-localized violation. Representative model; 18.8 debugs ordering violations.
Coming Next
Chapter 18.8 — Ordering Violations. A credit issue starves a channel; an ordering violation delivers events in a forbidden order. Chapter 18.8 covers ordering violations — events observed in an order the protocol forbids, the signature of two events with the wrong relative timing, and the diagnosis of checking each required precedence to find the ordering constraint that was not enforced.