Skip to content

AMBA CHI · Module 14 · CHI Flow Control

The Credit Mechanism

Module 13 moved data assuming channels never overflowed; this module makes that true via the credit mechanism. A CHI sender transmits a flit only when it holds a credit — one free slot in the receiver's buffer. Each send consumes a credit; the receiver returns one when it frees a slot. The invariant: flits in flight never exceed buffer capacity — so the buffer never overflows and no flit is dropped, needing no retransmit layer. Exact accounting is everything: decrement on every send, increment on every return. The failure to avoid is not decrementing on a send: the sender oversends and overflows the buffer, silently losing a flit whose absence later hangs a transaction. Representative model, not the specification.

Intermediate16 min readAMBA CHIFlow ControlCreditL-CreditBackpressure

Module 14 · Chapter 14.1 · CHI Flow Control

Project thread — Module 13 moved data. Module 14 keeps the channels from overflowing; 14.1 is the credit mechanism, 14.2 the per-channel accounting.

1. Learning Outcomes

By the end of this chapter you should be able to:

  • Explain that a sender may transmit a flit only when it holds a credit.
  • State that a credit represents one free slot in the receiver's buffer.
  • Describe the handshake — consume a credit on send, return one on freeing a slot.
  • State the invariant — flits in flight ≤ credits granted ≤ buffer capacity.
  • Diagnose the buffer overflow from miscounting credits (not decrementing on send).
  • Implement a representative credit counter in SystemVerilog, Verilog-2001, and VHDL.

2. Why Should I Learn This?

Everything you have built assumes a flit that is sent is received — a response arrives, data lands, a snoop is delivered. That assumption is not free; it is manufactured by flow control. Without it, a fast sender would outrun a busy receiver, the receiver's buffer would fill, and flits would be dropped — a lost response hangs a transaction, a lost data flit corrupts a line. The credit mechanism is what makes "sent implies received" true.

The idea is simple and strict: a credit is a reservation for exactly one buffer slot, and a sender may only transmit against a credit it holds. Because the receiver never grants more credits than it has buffer, and the sender never sends without a credit, the buffer cannot overflow — by construction, not by luck. This is why CHI links have no packet-drop or retransmit layer: overflow is prevented. But the guarantee is only as good as the sender's counting — one missed decrement and the sender oversends, the buffer overflows, and a flit is silently lost. This chapter is the mechanism and the invariant it enforces.

3. Key Terms

4. Previous Chapter Connection

Every channel you have used — REQ, RSP, SNP, DAT (Chapter 6.1) — carries flits under credit-based flow control, but the modules so far treated delivery as given. This module opens that up: delivery is guaranteed because of credits.

The stakes connect back to correctness. A dropped response flit hangs the transaction waiting on it — the same hang you saw from an uncorrelatable forward (Chapter 13.4), but from a different cause: the flit never arrived rather than arrived mis-tagged. A dropped data flit corrupts or loses a line (Chapters 13.1, 13.6). Flow control is the layer that ensures those flits are never dropped in the first place, so the higher-layer guarantees you have relied on actually hold. This chapter is the foundation the rest of the protocol stands on.

5. Core Concept — a credit is a reserved buffer slot

Credit-based flow control lets a sender transmit only against a credit, where each credit is one free slot in the receiver's buffer.

  • A credit is a reservation. Each credit represents exactly one free buffer slot at the receiver. Holding a credit means the receiver has guaranteed room for one flit.
  • Send consumes a credit. The sender may transmit a flit only if it holds a credit, and each transmission decrements its credit count by one.
  • The receiver returns credits. When the receiver processes a flit and frees the slot, it returns a credit to the sender, which increments the sender's count.
  • The invariant holds. Because the receiver grants no more credits than it has buffer, and the sender sends only against credits, the number of flits in flight never exceeds the buffer capacity — so the buffer never overflows.

The synthesis:

A credit is a reservation for one free slot in the receiver's buffer. A sender transmits a flit only against a credit and decrements on each send; the receiver returns a credit when it frees a slot, and the sender increments. Because credits never exceed buffer capacity and sends never exceed credits, the buffer cannot overflow — no flit is dropped. The guarantee depends on the sender's accounting being exact.

6. Engineering Mental Model — restaurant reservations

Think of a restaurant (the receiver) with a fixed number of tables (buffer slots).

  • The restaurant issues reservations (credits) — never more than it has tables. Holding a reservation means a table is guaranteed for you.
  • You (the sender) may only send a party (a flit) if you hold a reservation, and each party you send uses up one reservation.
  • When a party finishes and leaves (the receiver processes a flit and frees the slot), the restaurant issues a fresh reservation back to you — a table is free again.
  • Because reservations never exceed tables, and you never send a party without one, the restaurant is never overbooked — no party ever arrives to find no table.

Now imagine you forget to cross off a reservation when you use it (you don't decrement on send). You think you still hold it, send another party against the same reservation — and now two parties arrive for one table. The restaurant is overbooked; someone is turned away. That turned-away party is a dropped flit.

7. Engineering Diagram — the credit loop

The credit loop on a CHI channel. The sender holds a credit count and may transmit a flit only when the count is above zero, decrementing on each send. Flits land in the receiver's buffer. When the receiver processes a flit and frees a slot, it returns a credit, which the sender adds back to its count. Because credits never exceed buffer capacity, the buffer never overflows.Sendercredit count · send if> 0Receiver bufferfixed capacityReceiverprocessesfrees a slotflit (−1 credit)draincredit return (+1)12
Figure 1 — the credit loop on a CHI channel. The sender holds a credit count; it may transmit a flit only when the count is above zero, decrementing on each send. Flits land in the receiver's buffer. When the receiver processes a flit and frees a slot, it returns a credit, which the sender adds back. Because credits never exceed buffer capacity, the buffer never overflows.

The loop is closed: a flit costs a credit, and a freed slot returns one. The sender's count tracks exactly how many slots the receiver has guaranteed free. Send only when the count is above zero and the buffer is safe forever; miscount and the loop's guarantee breaks.

8. The Credit Handshake, Step by Step

Each event and its effect on the count.

EventSender credit countReceiver buffer
Initial grant (N slots)set to NN free
Sender transmits a flit−1one slot filled
Receiver processes a flit(unchanged yet)one slot freed
Receiver returns a credit+1(already free)
Count reaches 0must stop sendingbuffer may be full

The rule to carry: the sender's count is a live mirror of the receiver's free slots. It starts at the buffer capacity, drops by one per send, and rises by one per return. When it reaches zero, every slot the receiver guaranteed is spoken for, and the sender must not transmit until a credit returns. The two operations that must be exact are the decrement on send and the increment on return — either error desynchronizes the mirror from reality.

9. Why the Buffer Never Overflows

The invariant, made explicit.

  • Credits ≤ capacity. The receiver never grants more credits than it has buffer slots — it cannot promise room it does not have.
  • In-flight ≤ credits. The sender transmits only against credits it holds, so the number of flits sent-but-not-yet-freed never exceeds the credits granted.
  • Therefore in-flight ≤ capacity. Chaining the two: flits in flight ≤ credits ≤ capacity. The buffer can hold every flit that could possibly be in it.
  • No drops, no retransmit. Because overflow is impossible, the link needs no drop-detection or retransmission — the complexity a lossy link would require is simply absent.

The point to carry:

Credit flow control is a proof by construction that overflow cannot happen, and that is a stronger thing than a mechanism that recovers from overflow. A lossy design would detect a drop, buffer for retransmission, number flits to reorder them, and time out to retry — a large, bug-prone apparatus. Credits make all of it unnecessary by ensuring the bad state is never reached: the sender physically cannot emit a flit the receiver has no room for, because emitting requires spending a credit and a credit is the room. The elegance is that a single shared count enforces a global safety property with purely local action — the sender consults only its own counter, yet the effect is that a buffer it cannot see never overflows. The catch is equally sharp: the safety property holds only while the count is exact, so credit accounting is not bookkeeping, it is the invariant.

10. A Credit Sequence — sending under a small budget

A sender with 2 credits and a receiver that drains slowly.

  1. Grant: 2 credits. The receiver has 2 free slots; the sender's count is 2.
  2. Send flit A. Count → 1. One slot filled.
  3. Send flit B. Count → 0. Both slots filled — the sender must stop.
  4. Sender wants to send C, but count is 0. It waits — no credit, no send. (This is backpressure, Chapter 14.4.)
  5. Receiver processes A, returns a credit. Count → 1. A slot is free again.
  6. Send flit C. Count → 0. The freed slot is now used by C.

The sender never exceeded the receiver's 2 slots because it stopped at count zero and resumed only on a return. The DebugLab is a sender that, at step 3, failed to decrement and so believed it still had credit — sending C into a full buffer.

11. RTL / Hardware View — a credit counter

The sender increments on returns, decrements on sends, and sends only when the count is positive. Representative.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Representative link-credit counter (educational).
// A credit == one free slot in the receiver's buffer. Send ONLY when credits > 0;
// decrement on every send; increment on every credit return. The count must exactly
// track free slots, or the sender can oversend and overflow the receiver's buffer.
module chi_credit_counter #(parameter MAXC = 8) (
  input  logic                     clk, rst_n,
  input  logic                     credit_return,   // receiver freed a slot
  input  logic                     want_send,       // sender has a flit to send
  input  logic [$clog2(MAXC+1)-1:0] init_credits,   // granted at reset (<= buffer cap)
  output logic                     can_send,        // a credit is available
  output logic                     do_send,         // a flit is actually transmitted
  output logic [$clog2(MAXC+1)-1:0] credits
);
  logic [$clog2(MAXC+1)-1:0] cr_q;
  assign credits  = cr_q;
  assign can_send = (cr_q != 0);
  assign do_send  = want_send && can_send;   // never send without a credit
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      cr_q <= init_credits;
    end else begin
      // +1 on a return, -1 on a send; both can occur in the same cycle.
      case ({credit_return, do_send})
        2'b10:   cr_q <= cr_q + 1'b1;   // return only
        2'b01:   cr_q <= cr_q - 1'b1;   // send only  (MUST decrement)
        default: cr_q <= cr_q;          // both or neither: net zero
      endcase
    end
  end
endmodule

The same behavior in Verilog-2001:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Representative link-credit counter (Verilog-2001).
module chi_credit_counter #(parameter MAXC = 8, parameter CW = 4) (
  input  wire          clk, rst_n, credit_return, want_send,
  input  wire [CW-1:0] init_credits,
  output wire          can_send, do_send,
  output wire [CW-1:0] credits
);
  reg [CW-1:0] cr_q;
  assign credits  = cr_q;
  assign can_send = (cr_q != {CW{1'b0}});
  assign do_send  = want_send & can_send;
 
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
      cr_q <= init_credits;
    else if (credit_return & ~do_send)
      cr_q <= cr_q + 1'b1;              // return only
    else if (~credit_return & do_send)
      cr_q <= cr_q - 1'b1;              // send only (decrement!)
    // both or neither: net zero, hold
  end
endmodule

And in VHDL:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- Representative link-credit counter (VHDL).
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
 
entity chi_credit_counter is
  generic ( CW : integer := 4 );
  port (
    clk, rst_n     : in  std_logic;
    credit_return  : in  std_logic;
    want_send      : in  std_logic;
    init_credits   : in  unsigned(CW-1 downto 0);
    can_send       : out std_logic;
    do_send        : out std_logic;
    credits        : out unsigned(CW-1 downto 0)
  );
end entity;
 
architecture rtl of chi_credit_counter is
  signal cr_q      : unsigned(CW-1 downto 0) := (others => '0');
  signal have_cred : std_logic;
  signal snd       : std_logic;
begin
  have_cred <= '0' when cr_q = 0 else '1';   -- a credit is available
  snd       <= want_send and have_cred;      -- send only with a credit
  can_send  <= have_cred;
  do_send   <= snd;
  credits   <= cr_q;
 
  process (clk, rst_n)
  begin
    if rst_n = '0' then
      cr_q <= init_credits;
    elsif rising_edge(clk) then
      if credit_return = '1' and snd = '0' then
        cr_q <= cr_q + 1;              -- return only
      elsif credit_return = '0' and snd = '1' then
        cr_q <= cr_q - 1;              -- send only (decrement)
      end if;
    end if;
  end process;
end architecture;

All three send only when credits > 0 and decrement on every send while incrementing on every return — the count mirrors the receiver's free slots exactly. The DebugLab is a counter that omits the decrement on send.

12. Verification View — the count never overshoots capacity

The properties that keep the buffer safe: never send at zero, never exceed the granted budget.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Bind to chi_credit_counter.
// 1. A flit is never sent without a credit.
property p_no_send_without_credit;
  @(posedge clk) disable iff (!rst_n)
    do_send |-> (credits != 0);
endproperty
 
// 2. Credits never exceed the initial grant (buffer capacity).
property p_credits_bounded;
  @(posedge clk) disable iff (!rst_n)
    credits <= MAXC;
endproperty
 
// 3. In-flight flits (grant - credits) never exceed capacity -> no overflow.
//    Tracked as: sends_minus_returns <= init_credits at all times.

The system point, beyond the checks:

The most valuable property here is the one that is hardest to test directly — that the receiver's buffer never overflows — because overflow is a property of the receiver, yet it is enforced entirely by the sender's counter. This is the signature of good flow control: a safety property about component B is guaranteed by an invariant maintained in component A, with only a thin credit-return wire between them. The verification consequence is that you check the invariant at its source — the sender never sends at zero, the count never exceeds the grant — and the receiver-side safety follows as a theorem, not a separate test. A subtle corollary: the initial grant must not exceed the buffer's real capacity, or the invariant is armed with the wrong bound and the proof is vacuous. So two numbers must agree across the link — the credits granted and the slots that exist — and everything downstream trusts that they do.

  • What it proves: no send without a credit; credits stay within the granted budget.
  • What it does not prove: the grant matches real buffer capacity — a design-time obligation.
  • Bug signature: a send while credits == 0, or a count that climbs past the grant.

13. Testbench — the sender must stop at zero credits

Drains slowly and checks the sender never sends without a credit.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_chi_credit_counter;
  localparam MAXC = 8, CW = 4;
  logic clk = 0, rst_n = 0, credit_return, want_send;
  logic [CW-1:0] init_credits;
  logic can_send, do_send;
  logic [CW-1:0] credits;
  int errors = 0, inflight = 0;
 
  chi_credit_counter #(.MAXC(MAXC)) dut (.*);
  always #5 clk = ~clk;
 
  // Track in-flight flits: +1 on send, -1 on return. Must never exceed the grant.
  always @(posedge clk) if (rst_n) begin
    if (do_send)       inflight++;
    if (credit_return) inflight--;
    if (inflight > init_credits) begin errors++; $display("FAIL overflow: inflight=%0d > grant=%0d", inflight, init_credits); end
    if (do_send && credits == 0) begin errors++; $display("FAIL sent with 0 credits"); end
  end
 
  initial begin
    init_credits = 4'd2; credit_return = 0; want_send = 0;
    @(posedge clk) rst_n = 1;
 
    // Try to send continuously; receiver returns a credit only every 3rd cycle.
    want_send = 1;
    repeat (20) begin
      @(posedge clk);
      credit_return = ($time % 30 == 15);   // sparse returns
    end
    want_send = 0; credit_return = 0;
    repeat (3) @(posedge clk);
 
    if (errors == 0) $display("ALL TESTS PASSED (no overflow, no credit-less send)");
    else             $display("%0d FAILURE(S)", errors);
    $finish;
  end
endmodule

Expected output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ALL TESTS PASSED (no overflow, no credit-less send)

14. DebugLab — a sender that forgets to decrement on send

1

A sender that forgets to decrement on send

SENDER DOES NOT DECREMENT ON SEND -> OVERSENDS PAST BUFFER CAPACITY -> FLIT DROPPED, PACKET LOST
Symptom

Intermittently lost flits under load — a transaction hangs waiting for a response that was sent, or a line is corrupted missing a data beat — and it correlates with bursty, back-to-back sends on a channel whose receiver drains slowly. Light traffic is fine; sustained bursts drop flits.

Evidence

The sender oversent because it never decremented:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
grant = 2 credits; receiver buffer = 2 slots
sender sends flit A -> should be credits = 1, but does NOT decrement -> credits still 2
sender sends flit B -> still no decrement -> credits still 2
sender sends flit C -> buffer already holds A and B (full!) -> C OVERFLOWS
  -> receiver drops/overwrites a flit -> packet lost
correct: decrement on each send -> after A,B credits = 0 -> sender STOPS before C

The sender believed it held credit it had already spent.

First Divergence

The sender did not decrement its credit count on transmission, updating it only from credit returns. From that point its count overstated the receiver's free slots.

Root Cause

A credit is spent when a flit is sent, so the sender must decrement on send; otherwise its count overstates the receiver's free buffer and it oversends into overflow. The no-overflow invariant (flits in flight ≤ credits ≤ capacity) holds only if each send consumes a credit. Counting only returns while ignoring sends breaks the "in-flight ≤ credits" half of the invariant: the sender treats already-used credits as still available, transmits past the buffer's capacity, and the receiver — which has no drop-recovery layer, because credits were supposed to make it unnecessary — silently loses the overflowing flit. The lost flit then surfaces far away as a hang or corruption.

Fix

Make credit accounting exact: decrement the count on every transmission and increment only on a genuine credit return, exactly as the counter does. The count then mirrors the receiver's free slots, and the sender stops at zero — never sending past the buffer's capacity.

15. Common Mistakes

  • Not decrementing on send. Assumption: returns alone track credit. Bug: oversend, overflow (the DebugLab). Prevention: decrement every send.
  • Sending at zero credits. Assumption: one more is fine. Bug: buffer overflow. Prevention: gate sends on credits > 0.
  • Double-counting a return. Assumption: extra credit is safe. Bug: count exceeds capacity, oversend. Prevention: one increment per return.
  • Granting more than capacity. Assumption: the number is arbitrary. Bug: invariant armed with wrong bound. Prevention: grant ≤ real buffer.
  • Assuming a retransmit layer exists. Assumption: drops recover. Bug: silent loss. Prevention: credits prevent drops — there is no recovery.
  • Sharing a count across channels. Assumption: one pool. Bug: cross-channel overflow (Chapter 14.2). Prevention: per-channel counts.

16. Engineering Checklist

  • Transmit a flit only when a credit is held (count > 0).
  • Decrement the credit count on every send.
  • Increment the count on every credit return.
  • Grant credits the receiver's real buffer capacity.
  • Stop sending at zero credits — wait for a return.
  • Confirm flits in flight never exceed the grant — no overflow.

17. Key Takeaways

  • A credit is a reservation for one free slot in the receiver's buffer.
  • A sender transmits only against a credit and decrements on each send.
  • The receiver returns a credit when it frees a slot; the sender increments.
  • Flits in flight ≤ credits ≤ capacity, so the buffer never overflows.
  • Miscounting (not decrementing on send) oversends and drops a flit.
  • Exact accounting is the invariant; the model here is representative.

18. Quick Revision

The credit mechanism. CHI links use credit-based flow control: a sender may transmit a flit only when it holds a credit, and a credit represents one free slot in the receiver's buffer. The receiver grants credits up to its buffer capacity, each send consumes (decrements) one, and the receiver returns (the sender increments) a credit whenever it processes a flit and frees a slot. This yields a hard invariant — flits in flight ≤ credits granted ≤ buffer capacity — so the receiver's buffer can never overflow and no flit is dropped; that is why a CHI link needs no drop-and-retransmit layer. The whole guarantee rests on exact accounting: decrement on every send, increment on every return, send only at count > 0, and stop at zero. The failure to avoid: not decrementing on a send (or double-counting a return), so the sender's count overstates the receiver's free slots, it transmits past the buffer's capacity, and the buffer overflows — silently losing a flit whose absence later hangs a transaction or corrupts a line. The count must mirror the receiver's free slots exactly. Representative model; 14.2 covers per-channel credit accounting.

Coming Next

Chapter 14.2 — Link Flow Control. The credit mechanism governs one channel; CHI has several, and they must not interfere. Chapter 14.2 covers link flow control — how each channel (REQ, RSP, SNP, DAT) keeps its own independent credit pool, why a credit for one channel can never be spent on another, and how per-channel accounting stops one channel's backlog from starving or overflowing another.