Skip to content

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.

AR descriptor feeds address generator and beat counter; each beat fetched from memory and driven on R with per-beat RRESP; last beat asserts RLAST.AR captureaddr/len/size/typeAddress genper-beat addressBeat counter0 … ARLENMemory fetchread each beatR beatsRDATA + RRESPRLASTon final beat12
Figure 1 — the read engine's responsibilities. The AR channel delivers the burst descriptor (address, length, size, type). A beat counter and address generator place each read; each beat is fetched from memory and driven onto R with its own RRESP; the final beat asserts RLAST. Unlike the write side, there is no separate response channel — response travels per beat on R.

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.

Read FSM: IDLE to DATA on AR captured, DATA self-loop per R beat, DATA to IDLE on RLAST beat accepted.AR capturedR beat (count++)RLASTacceptedIDLEDATA
Figure 2 — the read FSM. IDLE captures the AR burst descriptor. DATA drives one R beat per cycle the manager accepts (RVALID/RREADY), fetching each beat at the per-beat address, attaching its RRESP, incrementing the counter, and asserting RLAST on the beat at index ARLEN. When the last beat handshakes, the FSM returns to IDLE — no separate response phase.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
end

3. 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 cycles
One AR handshake, four R beats with per-beat RRESP, a mid-burst RREADY stall, and RLAST on the fourth beat.ARR beats (stall @4)RLASTmanager stallmanager stalllast beatlast beatACLKARVALIDARREADYRVALIDRREADYRDATA..D0 D1 DRRESP..OK OK ORLASTt0t1t2t3t4t5t6t7t8
Figure 3 — a 4-beat INCR read burst. AR handshakes once (cycle 1). Four R beats transfer (cycles 2–6), each carrying RDATA and RRESP=OKAY; the manager stalls RREADY at cycle 4, so the slave holds that beat's data stable until acceptance. RLAST marks the fourth beat (cycle 6). One address in, four data-plus-response beats out, no separate response channel.

4. 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 R beat carries its own RRESP, 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.

Read engine: one input, per-beat response, slave-paced, intrinsic order. Write engine: two inputs, one aggregated response, slave-absorbs, ordering hazard. Shared: addressing, counting, last marker.Read: 1 inputAR onlyPer-beat RRESPno aggregationSlave-pacedproduces on demandWrite: 2 inputsAW + W pairingOne B (aggregate)worst-of-burstShared concernsaddr gen, count, last12
Figure 4 — read vs. write engine, side by side. The read side has one input channel (AR), per-beat response on R, slave-paced output, and intrinsic ordering — versus the write side's two input channels (AW+W), single aggregated B response, slave-absorbed input stream, and AW/W ordering hazard. The shared concerns are per-beat addressing, handshake-only counting, and last-beat marking.

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/READY for 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.