Skip to content

AMBA AXI · Module 15

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.

In Chapter 15.1 we built a slave with two hand-coded registers. That doesn't scale: a real peripheral has dozens or hundreds of control/status registers, and hand-writing a case arm per register is error-prone and unmaintainable. This chapter turns the slave into a parameterised register bank — a single, reusable block whose register map is described by a table (address, access type, reset value, fields), with generic decode, write, and read logic that loops over that table. The AXI4-Lite handshake engines from 15.1 stay exactly the same; what changes is that the register file becomes data-driven. This is how production CSR blocks are actually built, and it's the structure register generators (IP-XACT/SystemRDL → RTL) emit.

1. From Hand-Coded Registers to a Description Table

The key shift is separating what the register map is (a table of descriptors) from how it's accessed (generic logic that reads the table). Each register is described once — its byte offset, access type, reset value, and which bits are writable — and the decode/write/read logic is written once, parameterised over the number of registers.

AXI4-Lite FSMs feed a generic decode, which indexes a register description table; generic write and read logic apply access-type rules to the register array.AXI4-Lite FSMsunchanged (15.1)Generic decodeaddr → indexDescription tableoffset/type/reset/fieldsGeneric writeWSTRB + access typeRegister arrayreg_q[N]Generic readindex → RDATA mux12
Figure 1 — the register bank's layered structure. A register description table (offset, access type, reset, fields) is the single source of truth. Generic decode maps an AXI address to a register index; generic write applies WSTRB and access-type rules; generic read muxes the addressed register. The AXI4-Lite handshake FSMs from Chapter 15.1 are unchanged and sit in front of this datapath.

The table is a parameter — an array of descriptors. In SystemVerilog you can express it with a packed struct and a parameter array:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
typedef enum logic [1:0] { RW, RO, W1C, W1S } acc_e;  // access types
 
typedef struct packed {
  logic [ADDR_W-1:0] offset;   // byte offset (word-aligned)
  acc_e              access;   // access type
  logic [DATA_W-1:0] reset;    // reset/default value
  logic [DATA_W-1:0] wmask;    // writable-bit mask (reserved bits = 0)
} reg_desc_t;
 
parameter int NREG = 4;
parameter reg_desc_t MAP [NREG] = '{
  '{ offset: 'h00, access: RW,  reset: 32'h0000_0000, wmask: 32'hFFFF_FFFF }, // CTRL
  '{ offset: 'h04, access: RO,  reset: 32'h0000_0000, wmask: 32'h0000_0000 }, // STATUS
  '{ offset: 'h08, access: W1C, reset: 32'h0000_0000, wmask: 32'h0000_00FF }, // IRQ_STATUS
  '{ offset: 'h0C, access: RW,  reset: 32'h0000_00FF, wmask: 32'h0000_00FF }  // IRQ_ENABLE
};

2. Generic Address Decode

Decode becomes a loop over the table instead of a hand-written case. It compares the incoming word offset against every descriptor's offset, producing a one-hot select (or an index) and a "hit" flag; no hit means DECERR. Because it's generated from the table, adding a register is a one-line table edit — the logic never changes.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
function automatic logic [$clog2(NREG)-1:0] decode_index
  (input logic [ADDR_W-1:0] addr, output logic hit);
  hit = 1'b0;
  decode_index = '0;
  for (int i = 0; i < NREG; i++) begin
    if (addr[ADDR_W-1:2] == MAP[i].offset[ADDR_W-1:2]) begin
      decode_index = i[$clog2(NREG)-1:0];
      hit = 1'b1;
    end
  end
endfunction
Address compared to table; hit selects register index and dispatches to write/read; miss returns DECERR.yesnoAXI word addressCompare vs table(loop over NREG)hit?Dispatch w/ index +descriptorDECERR
Figure 2 — generic decode and dispatch. The incoming AXI word address is compared against every entry in the description table to produce a register index and a hit flag. A hit dispatches to the write or read datapath with the matched descriptor; a miss returns DECERR. The same comparator structure serves reads and writes.

3. Generic Write with Access-Type and WSTRB Logic

The write datapath now applies two masks per byte: the WSTRB byte enable (which bytes the manager wants to write) and the descriptor's wmask (which bits are writable, so reserved/RO bits are protected). On top of that, the access type decides the update rule — RW writes the masked value, W1C clears bits where a 1 is written, W1S sets them, RO ignores writes entirely.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Inside the write-commit state, idx = decoded register index
always_ff @(posedge aclk) begin
  if (!aresetn) begin
    for (int i = 0; i < NREG; i++) reg_q[i] <= MAP[i].reset;
  end else if (do_write && hit_w) begin
    for (int b = 0; b < DATA_W/8; b++) begin
      if (wstrb_q[b]) begin
        logic [7:0] wb   = wdata_q[b*8 +: 8];
        logic [7:0] mask = MAP[idx_w].wmask[b*8 +: 8];   // writable bits
        unique case (MAP[idx_w].access)
          RW : reg_q[idx_w][b*8 +: 8] <= (reg_q[idx_w][b*8 +: 8] & ~mask)
                                       | (wb & mask);
          W1C: reg_q[idx_w][b*8 +: 8] <= reg_q[idx_w][b*8 +: 8] & ~(wb & mask);
          W1S: reg_q[idx_w][b*8 +: 8] <= reg_q[idx_w][b*8 +: 8] |  (wb & mask);
          RO : ; // ignore write data
        endcase
      end
    end
  end
end

Hardware updates (e.g. a status bit a peripheral sets, or a W1C event bit) are merged separately — typically a hardware-set path ORs into W1C/RO bits — but the software-side rules above are uniform across the whole map.

Per-byte write: gated by WSTRB, then by wmask writable bits, then access-type operation RW/W1C/W1S/RO updates the register byte.WSTRB gatebyte enabled?wmaskwritable bitsAccess typeRW/W1C/W1S/ROreg_q[idx]updated byte12
Figure 3 — the generic write update, per byte lane. The byte is written only if its WSTRB bit is set; within that byte, only writable bits (the descriptor's wmask) are affected; and the access type selects the operation — RW substitutes the masked value, W1C clears written-one bits, W1S sets them, RO ignores. Reserved bits (wmask=0) are never disturbed.

4. Generic Read Mux and Reset

Reads become a single indexed mux over the register array — no per-register case. Reset initializes every register to its descriptor's reset value in one loop (shown above). For registers with read side effects (RC — read-clears), the read state also triggers the clear; for plain RW/RO/W1C the read is a pure mux.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Read datapath: idx_r = decoded read index, hit_r from decode
always_ff @(posedge aclk) begin
  if (aresetn && do_read) begin
    if (hit_r) begin
      s_rdata <= reg_q[idx_r];
      s_rresp <= 2'b00;            // OKAY
    end else begin
      s_rdata <= '0;
      s_rresp <= 2'b11;            // DECERR (unmapped)
    end
  end
end

The whole bank is now defined by the MAP table plus this fixed datapath. Adding a register — say an IRQ_RAW W1C status at 0x10 — is a single new row in MAP and a bump to NREG; no decode, write, or read code changes. That's the payoff of the table-driven structure, and it's exactly what register generators automate.

Description table parameterises decode, write, read blocks over the register array; AXI FSMs unchanged; table edits change the map without touching datapath.MAP table (truth)edit to change mapParameterisesNREG, offsets, typesAXI4-Lite FSMsfixed, reusedDecodeaddr → idx + hitWrite + readaccess-type rulesreg_q[NREG]the state12
Figure 4 — the complete table-driven register bank. The description table parameterises three generic blocks — decode, write, and read — that operate over a register array sized by NREG. The AXI4-Lite FSMs are reused unchanged. Adding or changing a register is a table edit only; the RTL datapath is fixed and reusable across every peripheral.

5. Common Misconceptions

6. Debugging Insight

7. Verification Insight

8. Interview Questions

9. Summary

A register bank generalizes the hand-coded slave by separating the register map (a description table of offset, access type, reset value, and writable-bit mask) from the access logic (generic decode, write, and read that loop over the table). The AXI4-Lite handshake FSMs from Chapter 15.1 are reused unchanged. Decode compares the incoming word address against every table entry to produce an index and a hit (miss → DECERR). Write gates each byte by WSTRB (manager intent) and wmask (writable bits), then applies the descriptor's access-type rule — RW substitutes, W1C clears written-ones, W1S sets them, RO ignores. Read is a single indexed mux, and reset loads every register from its descriptor. Reserved bits (wmask = 0) stay write-protected for forward compatibility.

The payoff is that adding or changing a register is a one-row table edit with no datapath change — the same structure register generators emit from SystemRDL/IP-XACT — and the same table can drive the UVM register model, so RTL and verification share one source of truth. Verification proves the shared generic logic deeply on per-type representatives and sweeps the whole map model-driven, making even hundred-register banks tractable. Next, we zoom back into the engines themselves and design the write-side FSM rigorously.

10. What Comes Next

You've made the register file scalable; next we return to the handshake engines and design the write FSM rigorously:

  • 15.3 — AXI Write FSM (coming next) — the write-side state machine in depth: AW/W sequencing, response generation, and the corner cases a robust write engine must handle.

Previous: 15.1 — A Simple AXI4-Lite Slave. Related: 10.4 — Common CSR Design Patterns for the access-type semantics this bank implements, 6.7 — Write Strobes (WSTRB) for byte enables, and 10.5 — AXI4-Lite Verification Checklist for the register-model verification strategy.