AMBA AXI · Module 15
A Simple AXI4-Lite Slave
Build a minimal, correct AXI4-Lite slave from scratch — the five-channel interface, a clean write FSM (AW/W → B) and read FSM (AR → R), WSTRB-aware register writes, address decode with OKAY/SLVERR/DECERR, and the handshake rules that keep it deadlock-free.
Module 15 turns from concepts to construction. We begin with the smallest useful, fully correct AXI4-Lite slave: a handful of memory-mapped registers reachable over the AXI4-Lite subset. This is the canonical first piece of AXI RTL every engineer writes — a peripheral's control/status interface. We'll build it the way you should in real RTL: a clear write datapath (AW + W → B) and read datapath (AR → R), each driven by a tiny FSM, with WSTRB-aware writes, a clean address decode that produces OKAY/SLVERR/DECERR, and handshakes that obey the one rule that prevents deadlock. The goal is not cleverness — it's a slave you can trust and reuse.
1. The Slave's Interface and Structure
An AXI4-Lite slave exposes the five channels — write address (AW), write data (W), write response (B), read address (AR), read data (R) — to a small block of registers. Because Lite is single-beat with no IDs, no bursts, and fixed full-width data, the slave is mostly a pair of independent handshake engines wrapped around a register file and an address decoder.
The port list is the AXI4-Lite subset — note there are no *LEN, *SIZE, *BURST, *ID, or *LAST signals:
module axil_slave #(
parameter int ADDR_W = 8, // byte address width into this slave
parameter int DATA_W = 32 // 32 or 64 for AXI4-Lite
) (
input logic aclk,
input logic aresetn, // active-low
// Write address channel
input logic [ADDR_W-1:0] s_awaddr,
input logic s_awvalid,
output logic s_awready,
// Write data channel
input logic [DATA_W-1:0] s_wdata,
input logic [DATA_W/8-1:0] s_wstrb,
input logic s_wvalid,
output logic s_wready,
// Write response channel
output logic [1:0] s_bresp,
output logic s_bvalid,
input logic s_bready,
// Read address channel
input logic [ADDR_W-1:0] s_araddr,
input logic s_arvalid,
output logic s_arready,
// Read data channel
output logic [DATA_W-1:0] s_rdata,
output logic [1:0] s_rresp,
output logic s_rvalid,
input logic s_rready
);2. The Write FSM (AW + W → B)
The write side must collect both an address (AW) and a data beat (W) — which can arrive in either order or simultaneously — then write the register and emit exactly one response (B). A clean three-state FSM captures this: IDLE (accept AW and W), WRITE (commit to the register file), RESP (hold BVALID until BREADY).
The write logic accepts the two address/data handshakes, decodes the offset, and applies WSTRB byte-by-byte. Writing only the strobed bytes is what makes partial-word and byte-field writes correct:
typedef enum logic [1:0] { W_IDLE, W_WRITE, W_RESP } wstate_e;
wstate_e wstate;
logic [ADDR_W-1:0] awaddr_q;
logic aw_done, w_done;
logic [DATA_W-1:0] wdata_q;
logic [DATA_W/8-1:0] wstrb_q;
// Accept AW and W independently while in IDLE
assign s_awready = (wstate == W_IDLE) && !aw_done;
assign s_wready = (wstate == W_IDLE) && !w_done;
always_ff @(posedge aclk) begin
if (!aresetn) begin
wstate <= W_IDLE; aw_done <= 1'b0; w_done <= 1'b0;
s_bvalid <= 1'b0; s_bresp <= 2'b00;
end else begin
// Latch address/data as each handshake completes
if (s_awvalid && s_awready) begin awaddr_q <= s_awaddr; aw_done <= 1'b1; end
if (s_wvalid && s_wready ) begin wdata_q <= s_wdata; wstrb_q <= s_wstrb; w_done <= 1'b1; end
case (wstate)
W_IDLE: if ((aw_done || (s_awvalid && s_awready)) &&
(w_done || (s_wvalid && s_wready ))) wstate <= W_WRITE;
W_WRITE: begin
// Address decode + WSTRB-masked write happen here (see §4)
s_bresp <= decode_err ? 2'b11 /*DECERR*/ : 2'b00 /*OKAY*/;
s_bvalid <= 1'b1;
wstate <= W_RESP;
end
W_RESP: if (s_bvalid && s_bready) begin
s_bvalid <= 1'b0; aw_done <= 1'b0; w_done <= 1'b0;
wstate <= W_IDLE;
end
endcase
end
end3. The Read FSM (AR → R)
The read side is simpler — one address in, one data beat out. A two-state FSM suffices: IDLE (accept AR), RESP (drive RVALID with the looked-up data and response, hold until RREADY).
typedef enum logic { R_IDLE, R_RESP } rstate_e;
rstate_e rstate;
assign s_arready = (rstate == R_IDLE);
always_ff @(posedge aclk) begin
if (!aresetn) begin
rstate <= R_IDLE; s_rvalid <= 1'b0; s_rresp <= 2'b00;
end else begin
case (rstate)
R_IDLE: if (s_arvalid && s_arready) begin
// Decode s_araddr, read register into s_rdata (see §4)
s_rresp <= rdecode_err ? 2'b11 : 2'b00;
s_rvalid <= 1'b1;
rstate <= R_RESP;
end
R_RESP: if (s_rvalid && s_rready) begin
s_rvalid <= 1'b0;
rstate <= R_IDLE;
end
endcase
end
end4. Address Decode and WSTRB Writes
The decode maps a byte offset to a register and signals an error for unmapped offsets. A two-register example (CTRL at 0x0, STATUS at 0x4, read-only) shows the pattern, including the per-byte WSTRB masking that preserves un-strobed bytes:
logic [DATA_W-1:0] reg_ctrl; // RW
logic [DATA_W-1:0] reg_status; // RO (driven by hardware)
logic decode_err, rdecode_err;
// --- Write commit (inside W_WRITE), word offset = awaddr_q[ADDR_W-1:2] ---
always_comb decode_err = 1'b0;
always_ff @(posedge aclk) begin
if (aresetn && wstate == W_WRITE) begin
case (awaddr_q[ADDR_W-1:2])
'h0: for (int b = 0; b < DATA_W/8; b++)
if (wstrb_q[b]) reg_ctrl[b*8 +: 8] <= wdata_q[b*8 +: 8];
'h1: ; // STATUS is read-only: ignore the write data, respond per policy
default: decode_err <= 1'b1; // unmapped → DECERR
endcase
end
end
// --- Read mux (registered into s_rdata at AR handshake) ---
always_ff @(posedge aclk) begin
if (aresetn && rstate == R_IDLE && s_arvalid && s_arready) begin
rdecode_err <= 1'b0;
case (s_araddr[ADDR_W-1:2])
'h0: s_rdata <= reg_ctrl;
'h1: s_rdata <= reg_status;
default: begin s_rdata <= '0; rdecode_err <= 1'b1; end // DECERR
endcase
end
endThe two transactions — a write to CTRL followed by a read of STATUS — exercise both FSMs end to end:
AXI4-Lite write then read
12 cycles5. Common Misconceptions
6. Debugging Insight
7. Verification Insight
8. Interview Questions
9. Summary
A minimal AXI4-Lite slave is two small handshake engines wrapped around a register file. The write path collects an address (AW) and a data beat (W) — which may arrive in any order — using aw_done/w_done latches, commits the WSTRB-masked data to the decoded register, and emits exactly one B response (OKAY/DECERR), held until BREADY. The read path accepts an address (AR), registers the decoded register value onto RDATA with RRESP, and holds RVALID until RREADY. The address decode must be total — an explicit default returns DECERR for unmapped offsets — and writes must honor WSTRB byte-by-byte to preserve neighbors. The one non-negotiable handshake rule is that *VALID (and its payload) must be driven independently of *READY and held stable until accepted; violating it deadlocks the bus. Reads and writes are independent and run on separate FSMs.
Because the slave is single-beat with no bursts, IDs, or outstanding, it's verifiable with directed register tests (read-after-write, RO-write, WSTRB neighbor preservation, unmapped→DECERR, AW/W ordering) plus always-on handshake assertions. This skeleton — clean FSMs, total decode, WSTRB masking, independent handshakes — is the reusable foundation for everything in Module 15.
10. What Comes Next
You've built one slave with hand-coded registers; next we parameterise it into a reusable bank:
- 15.2 — AXI4-Lite Register Bank (coming next) — generalize the decode and register file into a parameterised, table-driven register bank you can drop into any peripheral.
Previous: 14.5 — CDC Handshake Safety. Related: 10.2 — Register Access with AXI4-Lite for the access-side view, 6.7 — Write Strobes (WSTRB) for byte-enable semantics, and 10.5 — AXI4-Lite Verification Checklist for how to verify what you just built.
Continue learning
Related tutorials
- Related topic
AMBA — AHB · APB · AXI
ARM's AMBA family — channels, handshakes, ordering rules, and verification strategy.
- Related topic
AXI4 Write Channel — AW, W & B Handshake
The AMBA 5 AXI4 write path — AW/W/B channels, the VALID/READY handshake, channel-dependency rules, and BRESP write-response semantics.
- Related topic
AXI4-Lite Register Bank
Generalize the hand-coded AXI4-Lite slave into a parameterised, table-driven register bank — a register description table that captures address, access type, and reset, with generic decode, WSTRB-aware write, read mux, and per-field access-type logic (RW/RO/W1C/W1S) that scales to hundreds of registers.
- Related topic
AXI4 vs AXI3 vs AXI4-Lite vs AXI4-Stream
Map the four AXI variants — AXI4, AXI3, AXI4-Lite, and AXI4-Stream — what each keeps or drops, and when an engineer reaches for each.
Standards & specifications
- Governing standard
- Arm AMBA AXI Protocol Specification (IHI 0022)(opens Arm in a new tab)
Defines the AXI channels, handshake and ordering rules. RTL structure, interconnect topology and verification strategy are design choices this specification does not mandate.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the AMBA AXI curriculum.
