AMBA AXI · Module 15
AXI Read FSM
Design a robust full-AXI4 read-side state machine — accept the address (AR), then stream ARLEN+1 R beats each carrying RDATA and a per-beat RRESP, marking the final beat with RLAST — and see why the read path is simpler than the write path (data and response share one channel, no AW/W pairing).
The read side is the write side's mirror image — and a simpler one. A full AXI4 read takes one address (AR) and returns a burst of ARLEN+1 data beats on the R channel, each beat carrying both RDATA and its own RRESP, the final beat flagged by RLAST. There's no separate response channel and no second input channel to pair with the address, which removes the write side's two trickiest hazards (AW/W decoupling and single-response aggregation). This chapter designs the read FSM rigorously — accept the address, generate per-beat addresses and data, attach a per-beat response, count to RLAST, and handle manager back-pressure on RREADY — and explains precisely why it's the easier of the two engines.
1. What the Read FSM Must Do
The read engine has two jobs: capture the burst address/parameters from AR, then produce ARLEN+1 data beats on R. Each beat is read from the source (memory) at a per-beat address computed from the burst type, carries its own RRESP (so a fault on one beat is reported on that beat, not aggregated), and the last beat asserts RLAST. Data flows out of the slave, paced by the manager's RREADY.
2. The State Machine
Two states suffice: IDLE (accept AR, latch the descriptor) and DATA (drive R beats, advancing on each RVALID && RREADY handshake, asserting RLAST on the final beat). There's no separate response state because the response rides each R beat — when the last beat is accepted, the burst is simply done.
typedef enum logic { R_IDLE, R_DATA } rstate_e;
rstate_e rstate;
logic [ADDR_W-1:0] addr_q; // current beat address
logic [7:0] len_q; // ARLEN latched
logic [7:0] beat_q; // beats sent so far
logic [2:0] size_q;
logic [1:0] burst_q;
assign s_arready = (rstate == R_IDLE);
assign s_rlast = (rstate == R_DATA) && (beat_q == len_q);
always_ff @(posedge aclk) begin
if (!aresetn) begin
rstate <= R_IDLE; beat_q <= '0; s_rvalid <= 1'b0;
end else case (rstate)
R_IDLE: if (s_arvalid && s_arready) begin
addr_q <= s_araddr; len_q <= s_arlen; size_q <= s_arsize;
burst_q <= s_arburst; beat_q <= '0;
s_rdata <= mem_read(s_araddr); // first beat's data
s_rresp <= beat_resp(s_araddr); // per-beat response
s_rvalid <= 1'b1;
rstate <= R_DATA;
end
R_DATA: if (s_rvalid && s_rready) begin // beat accepted
if (beat_q == len_q) begin
s_rvalid <= 1'b0; // last beat done
rstate <= R_IDLE;
end else begin
addr_q <= next_addr(addr_q, size_q, burst_q, len_q);
beat_q <= beat_q + 8'd1;
s_rdata <= mem_read(next_addr(addr_q, size_q, burst_q, len_q));
s_rresp <= beat_resp(next_addr(addr_q, size_q, burst_q, len_q));
// s_rvalid stays high — next beat is ready
end
end
endcase
end3. The Burst on the Wire
A length-4 INCR read shows the timing: one AR handshake, then four R beats (the fourth with RLAST), each carrying data and response. The manager may stall RREADY mid-burst; the slave holds the current beat (RVALID stays high, payload stable) until accepted.
4-beat INCR read burst
9 cycles4. Why the Read Path Is Simpler
The read engine is genuinely easier than the write engine, for structural reasons worth naming — they're the inverse of the write side's hazards:
- One input channel, not two. A read has a single address input (
AR); there's no second data-input channel to pair with it, so there's no AW/W-style decoupling/ordering hazard. The engine captures the address and is immediately in control of producing the rest. - Response per beat, not aggregated. Each
Rbeat carries its ownRRESP, so a fault on a particular beat is reported precisely on that beat — no need to accumulate a worst-case status into a single response. There's also no separate response channel/state to manage. - The slave paces the data. On a read, the slave produces and the manager consumes; back-pressure is only
RREADY. On a write, the slave must receive a stream it doesn't control. Producing on demand is simpler than absorbing an external stream. - No early-data problem. Write data can arrive before its address (W-before-AW); read data can't exist before its address, since the slave generates it. The ordering is intrinsic.
What the read side still must get right: per-beat address generation (identical math to the write side — INCR/WRAP/FIXED), counting on the handshake only, RLAST on exactly beat ARLEN, and payload stability while RVALID is held waiting for RREADY.
5. Common Misconceptions
6. Debugging Insight
7. Verification Insight
8. Interview Questions
9. Summary
The AXI4 read FSM turns one address into a self-describing stream of data beats. IDLE captures the AR burst descriptor (address, length, size, type) and fetches the first beat; DATA drives one R beat per accepted handshake — each carrying RDATA and its own RRESP, fetched at a per-beat address derived from the burst type — advancing the counter only on RVALID && RREADY, asserting RLAST on exactly beat ARLEN, and returning to IDLE when that last beat is accepted. There is no separate response channel and no response state: the response rides every beat.
The read path is structurally simpler than the write path because it has one input channel (no AW/W pairing), per-beat responses (no aggregation), slave-paced output (it produces rather than absorbs), and intrinsic ordering (data can't precede its address). What it still must nail is the shared core — per-beat addressing, handshake-only counting, RLAST alignment, and payload stability during an RREADY stall. Verification sweeps burst length × type, injects RREADY stalls at every position, and asserts last-beat alignment and per-beat response — a smaller matrix than the write side's. Next, we build the component that makes these registered handshakes run at full throughput without bubbles: the skid buffer.
10. What Comes Next
You've built both engines; next comes the building block that makes their handshakes fast and timing-clean:
- 15.5 — The Skid Buffer (coming next) — a two-entry buffer that registers
VALID/READYfor timing closure while sustaining full throughput, the workhorse of pipelined AXI.
Previous: 15.3 — AXI Write FSM. Related: 2.2 — The Read Path for the end-to-end read flow, 7.5 — Burst Address Calculation for the per-beat addressing math, and 6.8 — RRESP, BRESP & RLAST for the response and last-beat semantics.