AMBA CHI · Module 3 · Why CHI Exists
AHB Recap (for CHI Context)
AHB was the shared-bus generation, and two ideas explain both its usefulness and its limits. First it pipelines: the address of one transfer overlaps the data of the previous one, so the bus is rarely idle. Second it gates every transfer on HREADY — a slave can insert wait states, and because the bus is shared, that stall blocks everyone. Add single-master arbitration and no coherency at all, and you have a protocol that carries control and low-to-mid-performance traffic well but cannot serve a coherent multi-core fabric. This chapter recaps AHB precisely — phases, HREADY, arbitration — and shows exactly where it stops and why the next generations were needed. The AHB here is representative, not the full specification.
Foundation14 min readAMBA CHIAHBAMBAHREADYInterconnect
Module 3 · Chapter 3.2 · Why CHI Exists
Project thread — 3.1 traced the whole evolution. This chapter zooms into AHB — the shared-bus generation — to pin down its mechanics and its ceiling; 3.3 does the same for AXI.
1. Learning Outcomes
By the end of this chapter you should be able to:
- Describe AHB's two-phase pipeline — the address phase and the data phase, offset by one cycle.
- Explain the HREADY handshake and how a slave inserts wait states.
- Recognize that HREADY is shared, so one slave's stall blocks every master (head-of-line blocking).
- Place AHB correctly: single-master arbitration, one outstanding transfer, no coherency.
- Implement a representative AHB wait-state slave in SystemVerilog, Verilog-2001, and VHDL.
- Argue precisely why a pipelined shared bus cannot carry coherent multi-master traffic.
2. Why Should I Learn This?
CHI's headline features — independent channels, deep outstanding, packet routing, coherency — are best understood as fixes for exactly what AHB could not do. To see the gap, you have to know AHB's mechanics: what its pipeline buys, what HREADY costs when the bus is shared, and why "one master at a time" plus "no coherency" is a hard ceiling for multi-core.
AHB is also still everywhere — control and configuration buses, low-power subsystems, peripheral fabrics. Knowing where it fits (and where it must not) is a live design decision, not just history.
3. Key Terms
4. Previous Chapter Connection
Chapter 3.1 traced APB → AHB → AXI → ACE → CHI and named AHB's wall in one line: a shared bus grants one master at a time. This chapter makes that precise and adds the second half of AHB's ceiling.
The evolution said what AHB couldn't do; here we see why at the signal level. Two mechanisms — the address/data pipeline and the shared HREADY handshake — plus single-master arbitration and zero coherency, together explain exactly why AXI (channels, outstanding) and then CHI (coherency, packets) had to exist.
5. Core Concept — a pipelined, shared, non-coherent bus
AHB is defined by four properties. The first two are strengths; the last two are the ceiling.
- Two-phase pipeline. Every transfer has an address phase (drive HADDR/HTRANS/HWRITE) and a data phase (move HWDATA/HRDATA). They are offset by one cycle: while transfer N is in its data phase, transfer N+1 is already in its address phase. This overlap keeps the bus busy — pipeline depth one.
- HREADY handshake. A slave that needs time drives HREADYOUT low, inserting wait states; the transfer holds until HREADY is high. This lets slow slaves coexist with fast ones — but HREADY is shared.
- Single-master arbitration. An arbiter grants the bus to one master; others wait (Chapter 3.1). Aggregate bandwidth does not grow with masters.
- No coherency. AHB has no snoop, no cache-state signalling — masters cannot see each other's caches at all.
Put together:
AHB pipelines well and tolerates slow slaves, but the bus is one shared resource: one master owns it, one HREADY gates it, and nothing coordinates caches. A slow slave stalls everyone (head-of-line blocking), and coherent multi-core is simply not expressible. That is the ceiling AXI and CHI were built to break.
6. Engineering Mental Model — a single-track railway with signals
Think of AHB as a single-track railway through a station.
- Only one train (master) is on the track at a time — the signal box (arbiter) admits one.
- The track is pipelined like block signalling: as one train clears the platform (data phase), the next is already entering the approach (address phase) — but just one block ahead, never a deep queue of trains in motion.
- A train that stops at the platform (a slave asserting HREADY low) holds up every train behind it — there is no second track to route around it (head-of-line blocking).
- And no train carries word of what the others are doing (no coherency).
AXI lays parallel tracks (independent channels, many trains in flight); CHI builds a switched network with a dispatcher (packets + directory). AHB is the single track that made those necessary.
7. Engineering Diagram — AHB topology
Note what is absent: there is no link between the masters. Coherency would need one master to see another's cache — AHB has no such path.
8. Worked Example — the pipeline, cycle by cycle
Follow three back-to-back read transfers, T0/T1/T2, with a wait state inserted on the third. Address phase leads data phase by one cycle; a wait state (HREADY low) freezes both.
| Cycle | HADDR (address phase) | HRDATA (data phase) | HREADY | What is happening |
|---|---|---|---|---|
| t0 | A0 | — | 1 | T0 address phase |
| t1 | A1 | D0 | 1 | T1 address over T0 data — the pipeline |
| t2 | A2 | D1 | 1 | T2 address over T1 data |
| t3 | A2 (held) | D1 (held) | 0 | slave inserts a wait — whole bus stalls |
| t4 | A3 | D2 | 1 | resume: T2 data completes, T3 address |
| t5 | — | D3 | 1 | T3 data phase |
Two facts to carry forward: (1) data lags address by exactly one cycle — the depth-one pipeline; (2) at t3 the wait does not stall only the waiting transfer — it freezes the entire bus, including any other master that wanted it. That second fact is AHB's scaling problem in one row.
9. Transaction Walkthrough — one AHB read with a wait state
A single read from the CPU master, step by step:
- Grant. The CPU requests; the arbiter grants it the bus (Chapter 3.1). No other master transfers now.
- Address phase. The CPU drives HADDR, HTRANS = NONSEQ, HWRITE = 0. The decoder asserts HSEL to the addressed slave.
- Data phase (next cycle). The slave returns HRDATA. If ready, it holds HREADYOUT high and the transfer completes in one data cycle.
- Wait state. If the slave needs time, it drives HREADYOUT low. HREADY goes low bus-wide; the CPU holds HADDR/controls, and every master is stalled until the slave is ready.
- Completion. The slave raises HREADYOUT; HREADY goes high; HRDATA is sampled on that rising edge. The pipeline advances.
The single-owner grant plus the shared HREADY mean this one slow slave paced the whole bus — there is no way for another master to make progress meanwhile.
10. RTL / Hardware View — a representative AHB wait-state slave
Here is AHB's defining slave behavior in miniature: capture the address in the address phase, insert a fixed number of wait states by driving HREADYOUT low, then present read data in the data phase. Representative and simplified — no HRESP error protocol, bursts, or BUSY handling.
// Representative AHB-lite wait-state slave (educational, not full AHB).
// Captures HADDR in the address phase, inserts WAITS wait states via HREADYOUT,
// and returns read data in the data phase. HREADYOUT low stalls the shared bus.
module ahb_wait_slave #(
parameter int WAITS = 1 // wait states inserted per transfer
)(
input logic hclk,
input logic hresetn,
input logic hsel, // this slave is addressed
input logic hready, // bus ready: address phase valid when high
input logic [1:0] htrans, // 2'b10 NONSEQ, 2'b11 SEQ are active
input logic [31:0] haddr,
output logic hreadyout, // this slave's ready — low = wait state
output logic [31:0] hrdata
);
logic [3:0] wcnt; // data-phase wait countdown
logic [31:0] addr_q; // address captured in the address phase
wire active = hsel && hready && htrans[1]; // selected, active transfer
always_ff @(posedge hclk or negedge hresetn) begin
if (!hresetn) begin
wcnt <= 4'd0; addr_q <= 32'd0;
end else if (active) begin
addr_q <= haddr; // address-phase capture -> data phase
wcnt <= WAITS[3:0]; // load wait states for this transfer
end else if (wcnt != 4'd0) begin
wcnt <= wcnt - 4'd1; // count wait states down
end
end
assign hreadyout = (wcnt == 4'd0); // low while waiting -> stalls the bus
assign hrdata = addr_q; // representative read data (echo address)
endmoduleThe same behavior in Verilog-2001:
// Representative AHB-lite wait-state slave (Verilog-2001).
module ahb_wait_slave #(
parameter WAITS = 1
)(
input hclk,
input hresetn,
input hsel,
input hready,
input [1:0] htrans,
input [31:0] haddr,
output hreadyout,
output [31:0] hrdata
);
reg [3:0] wcnt;
reg [31:0] addr_q;
wire active = hsel & hready & htrans[1];
always @(posedge hclk or negedge hresetn)
if (!hresetn) begin
wcnt <= 4'd0; addr_q <= 32'd0;
end else if (active) begin
addr_q <= haddr;
wcnt <= WAITS[3:0];
end else if (wcnt != 4'd0) begin
wcnt <= wcnt - 4'd1;
end
assign hreadyout = (wcnt == 4'd0);
assign hrdata = addr_q;
endmoduleAnd in VHDL:
-- Representative AHB-lite wait-state slave (VHDL).
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity ahb_wait_slave is
generic ( WAITS : integer := 1 );
port (
hclk, hresetn : in std_logic;
hsel, hready : in std_logic;
htrans : in std_logic_vector(1 downto 0);
haddr : in std_logic_vector(31 downto 0);
hreadyout : out std_logic;
hrdata : out std_logic_vector(31 downto 0)
);
end entity;
architecture rtl of ahb_wait_slave is
signal wcnt : unsigned(3 downto 0) := (others => '0');
signal addr_q : std_logic_vector(31 downto 0) := (others => '0');
signal active : std_logic;
begin
active <= hsel and hready and htrans(1); -- selected, active transfer
process(hclk, hresetn)
begin
if hresetn = '0' then
wcnt <= (others => '0'); addr_q <= (others => '0');
elsif rising_edge(hclk) then
if active = '1' then
addr_q <= haddr;
wcnt <= to_unsigned(WAITS, wcnt'length);
elsif wcnt /= 0 then
wcnt <= wcnt - 1;
end if;
end if;
end process;
hreadyout <= '1' when wcnt = 0 else '0';
hrdata <= addr_q;
end architecture;All three model the two AHB essentials: an address-phase capture feeding the data phase (the pipeline register), and a HREADYOUT that drops to insert wait states — and, because HREADY is shared, that stall is felt bus-wide.
11. Timing View — the pipeline and a wait state
Cycle-level behavior is AHB's identity, so it is worth seeing on a waveform: address leading data by one cycle, and one wait state freezing both.
AHB pipeline: address leads data by one cycle; HREADY low inserts a wait
6 cyclesThe wait at cycle 3 does not belong to one master — HREADY is shared, so whatever else wanted the bus waits too. That is head-of-line blocking drawn out in time.
12. Verification View — wait states and the shared stall
Two properties: the slave inserts the intended wait states, and — the systemic point — HREADYOUT low means the bus is not advancing.
// Bind to ahb_wait_slave (WAITS = 1 shown).
// 1. A new active transfer with WAITS>0 forces at least one wait cycle.
property p_inserts_wait;
@(posedge hclk) disable iff (!hresetn)
(active && (WAITS > 0)) |=> !hreadyout;
endproperty
assert property (p_inserts_wait);
// 2. While this slave waits (HREADYOUT low), its captured address is stable —
// the data phase is frozen, mirroring the bus-wide stall.
property p_addr_stable_while_wait;
@(posedge hclk) disable iff (!hresetn)
(!hreadyout) |=> $stable(addr_q);
endproperty
assert property (p_addr_stable_while_wait);The system point, beyond the two checks:
In a real AHB fabric, HREADY is the logical AND of the active slave's HREADYOUT with the bus's readiness — one signal shared by all masters. So a slave that waits does not delay only its own transfer; it delays every master. There is no per-master back-pressure. That is precisely what AXI's independent channels and outstanding transactions remove.
- What it proves: wait states appear as intended and freeze the data phase.
- What it does not prove: anything about coherency (AHB has none) or fairness across masters (that is the arbiter, Chapter 3.1).
- Bug signature: HREADYOUT stuck low → a hung transfer that stalls the whole bus (the DebugLab).
13. Testbench — observe the wait state
Deterministic single-master stimulus; HREADYOUT and HRDATA are checked after each edge.
module tb_ahb_wait_slave;
logic hclk = 0, hresetn;
logic hsel, hready;
logic [1:0] htrans;
logic [31:0] haddr;
logic hreadyout;
logic [31:0] hrdata;
int errors = 0;
ahb_wait_slave #(.WAITS(1)) dut (.*);
always #5 hclk = ~hclk;
task automatic step(input logic sel, input logic [1:0] tr,
input logic [31:0] a, input logic exp_ready, input string tag);
hsel = sel; htrans = tr; haddr = a; hready = 1;
@(posedge hclk); #1;
if (hreadyout !== exp_ready) begin
errors++; $display("FAIL [%s] hreadyout=%b exp=%b", tag, hreadyout, exp_ready);
end else
$display("PASS [%s] hreadyout=%b hrdata=%h", tag, hreadyout, hrdata);
endtask
initial begin
hresetn = 0; step(0, 2'b00, 32'h0, 1'b1, "reset"); hresetn = 1;
// Active transfer -> WAITS=1 -> next cycle HREADYOUT low (one wait).
step(1, 2'b10, 32'hA000, 1'b0, "addr A000 -> wait");
// Idle: wait counts down -> ready again, data = captured address.
step(0, 2'b00, 32'h0, 1'b1, "wait done -> ready");
// Next active transfer -> another wait.
step(1, 2'b10, 32'hB004, 1'b0, "addr B004 -> wait");
step(0, 2'b00, 32'h0, 1'b1, "wait done -> ready");
if (errors == 0) $display("ALL TESTS PASSED");
else $display("%0d FAILURE(S)", errors);
$finish;
end
endmoduleExpected output:
PASS [reset] hreadyout=1 hrdata=00000000
PASS [addr A000 -> wait] hreadyout=0 hrdata=0000a000
PASS [wait done -> ready] hreadyout=1 hrdata=0000a000
PASS [addr B004 -> wait] hreadyout=0 hrdata=0000b004
PASS [wait done -> ready] hreadyout=1 hrdata=0000b00414. DebugLab — one hung slave freezes the whole bus
One hung slave freezes the whole bus
SHARED HREADY + HUNG SLAVE -> WHOLE-BUS HEAD-OF-LINE BLOCKThe entire SoC appears frozen — every master stops making progress, watchdogs fire — yet only one peripheral is actually misbehaving. Nothing else on the bus is broken.
The shared HREADY is stuck low, traced to one slave's HREADYOUT:
cyc hsel htrans hreadyout hready(bus) note
4 1 NONSEQ 0 0 slave starts a wait
5 0 IDLE 0 0 still waiting...
6 0 IDLE 0 0 still waiting...
... 0 0 never returns -> bus frozenhready(bus) follows the hung slave's hreadyout; no other master can advance while it is low.
Cycle 4: the slave asserts a wait it will never release (its wait counter never reaches zero because it is gated on an external event that does not occur). From this cycle the bus stops advancing.
HREADY is a single shared signal. A slave that waits indefinitely holds it low for everyone — classic head-of-line blocking. AHB has no per-master back-pressure and no way for other masters to proceed around a stuck transfer. One slow slave equals a system stall.
Locally: bound the wait — a timeout that completes the transfer with an HRESP error rather than hanging. Architecturally, this is the AHB ceiling: the real fix is independent channels and outstanding transactions (AXI) so a slow slave blocks only its own traffic, and ultimately a packet-switched, credited network (CHI) where no shared ready signal exists to freeze. The bug is not a coding slip — it is what "shared bus" costs.
15. Common Mistakes
- Collapsing the two phases. Assumption: address and data happen in the same cycle. Bug: off-by-one timing, sampling HRDATA a cycle early. Prevention: data lags address by one cycle — depth-one pipeline.
- Thinking HREADY is per-master. Assumption: a slave's wait affects only its own transfer. Bug: unexplained system-wide stalls. Prevention: HREADY is shared; one wait stalls the whole bus.
- Assuming AHB has deep outstanding. Assumption: many transactions in flight like AXI. Bug: over-estimating AHB throughput. Prevention: the pipeline is depth one — one address phase over one data phase.
- Expecting hardware coherency. Assumption: multiple masters stay cache-consistent automatically. Bug: stale shared reads (Chapter 1.8). Prevention: AHB has no coherency; that is ACE/CHI.
- Using AHB for the coherent CPU-memory fabric. Assumption: a fast bus is enough. Bug: contention, head-of-line blocking, no coherency at core count. Prevention: AHB fits control/peripheral paths; use AXI/CHI for the performance fabric.
- Treating this slave as full AHB. Assumption: capture + wait counter is the whole protocol. Bug: missing HRESP two-cycle error, bursts, BUSY, split/retry. Prevention: this is a representative slice, not the spec.
16. Engineering Checklist
- Separate the address phase from the data phase; remember the one-cycle offset.
- Treat HREADY as shared — a wait stalls every master, not just one transfer.
- Budget AHB as one outstanding transfer (depth-one pipeline), not deep pipelining.
- Never rely on AHB for coherency — it has none.
- Bound slave wait states (timeout / HRESP) so a hung slave cannot freeze the bus.
- Reserve AHB for control / low-to-mid paths; use AXI/CHI for the coherent performance fabric.
17. Key Takeaways
- AHB is a pipelined, shared, non-coherent bus: address phase and data phase overlap, offset by one cycle (depth one).
- HREADY lets slaves insert wait states — but it is shared, so one slow slave stalls every master (head-of-line blocking).
- Single-master arbitration means aggregate bandwidth does not grow with masters (Chapter 3.1).
- AHB has no coherency — no snoop, no cache-state signalling — so coherent multi-core is not expressible on it.
- Those limits are exactly what AXI (independent channels, outstanding) and CHI (packets, directory coherency) were built to remove.
- The AHB here is representative — enough to place it and see its ceiling, not the full specification.
18. Quick Revision
AHB recap. A pipelined shared bus. Address phase (HADDR/HTRANS/HWRITE) leads the data phase (HRDATA/HWDATA) by one cycle — pipeline depth one. HREADY low inserts wait states; because HREADY is shared, one slow slave stalls all masters (head-of-line blocking). One master at a time via arbitration (3.1); aggregate bandwidth does not scale. No coherency at all — no snoop, no cache state. Fits control and low-to-mid traffic; cannot carry a coherent multi-core fabric. That ceiling is why AXI (channels + outstanding) and CHI (packets + directory) exist. Representative AHB, not the full specification.
Coming Next
Chapter 3.3 — AXI Recap (for CHI Context). AXI answers AHB's head-of-line blocking with five independent channels and deep outstanding, out-of-order transactions — throughput without the shared stall. The next chapter recaps that model and then draws its own line: AXI is powerful but non-coherent, the exact gap that ACE and CHI step in to close.