AMBA AHB · Module 16
Register Bank Slave
Extending the simple AHB-Lite slave into a parameterised register bank — the same capture-then-act structure, but the registers are an indexed array (by addr_q): a write decoder selects which register receives HWDATA (if writable), a read mux selects the indexed register onto HRDATA. Each register has access attributes (RW / RO / write-1-to-clear / read-to-clear / reserved-reads-0). Read-to-clear especially needs the qualified select. The workhorse peripheral interface, typically generated from a register map.
Chapter 16.1 built a slave with a few fixed registers; this chapter generalizes it into a parameterised register bank — a configurable array of registers, the workhorse interface of nearly every peripheral. The structure keeps the same capture-then-act backbone (16.1): capture the address-phase context (sel_q, write_q, the register index from addr_q), then act in the data phase. But now the registers are an indexed array: a write decoder uses the index to select which register receives HWDATA (and only registers that are writable accept it), and a read mux uses the index to select the addressed register onto HRDATA. The key new dimension is per-register access attributes: registers aren't all plain read-write — some are read-only (RO, e.g. STATUS — ignore writes), some have write-one-to-clear (W1C, e.g. interrupt flags — hardware sets, software clears by writing 1), some are read-to-clear (RC — clears on read), and reserved indices read as 0 (and ignore writes). The bank is parameterised by register count and width. A subtle point: read-to-clear registers especially need the qualified select (chapter 16.1) — so an idle cycle doesn't spuriously clear them. This chapter builds the parameterised register bank with its per-register access rules.
1. What Is It?
A register bank slave is a parameterised AHB-Lite slave whose storage is an indexed array of registers with per-register access attributes. Its parts:
- Same capture-then-act — capture the address-phase context (
sel_q,write_q, the register index fromaddr_q), act in the data phase (16.1's structure). - Indexed array — the registers are a parameterised array (count, width); the index selects one.
- Write decoder — the index selects which register receives
HWDATA(only writable registers accept it). - Read mux — the index selects the addressed register onto
HRDATA. - Per-register attributes — RW / RO / W1C / RC / reserved-reads-0 govern each register's behavior.
So a register bank slave is the simple slave (16.1) scaled and parameterised: the structure (capture-then-act, qualified select, registered context) is identical, but the storage becomes a configurable indexed array, and each register gains access attributes (it's not all plain RW). The write path becomes a decoder (index → which writable register), the read path a mux (index → which register's value). The attributes are where the peripheral semantics live — RO status, W1C flags, RC registers, reserved holes. The bank is parameterised (register count, data width) so it's reusable across peripherals. So a register bank slave is the standard parameterised register-array interface — the simple slave generalized with per-register access rules. So it's the workhorse peripheral interface.
2. Why Does It Exist?
The register bank slave exists because peripherals are controlled through registers — and they need many registers with varied semantics (control, status, flags, data), so a parameterised array with per-register attributes is the standard, reusable way to build that interface.
The register-based peripheral control is the root: a peripheral (UART, timer, GPIO, etc.) is controlled by software writing/reading its registers — a control register to configure it, a status register to read its state, data registers to send/receive, flag registers for interrupts. So every peripheral needs a register interface — a set of registers the CPU accesses over the bus. So the register bank slave exists to provide that interface. So it's the peripheral's control surface. So registers are how peripherals are controlled.
The many registers, varied semantics drives the array + attributes: a peripheral has many registers (often dozens), and they have different semantics — control (RW — software sets), status (RO — hardware-driven, software reads), interrupt flags (W1C — hardware sets, software clears), FIFOs (RC or special), reserved (holes). So a single uniform "register" isn't enough — you need an array (for the many) with per-register attributes (for the varied semantics). So the bank exists as an indexed array with attributes to handle the many, varied registers. So it's array + attributes. So peripherals need this structure.
The parameterised reusability is why it's parameterised: every peripheral needs a register bank, with different counts, widths, and attributes. So building a parameterised register bank (configurable count/width/attributes) means the same RTL is reused across peripherals — you instantiate and configure it, rather than writing each from scratch. So the bank is parameterised for reuse (a register-bank generator is common). So it's reusable IP. So the register bank slave exists because: peripherals are controlled through registers (the why); they need many registers with varied semantics — control/status/flags/data (driving the indexed array with per-register attributes); and a parameterised bank is reusable across peripherals (the generalization). So the register bank slave is the standard, parameterised, reusable register-array interface that every peripheral uses — the simple slave generalized to the real peripheral need. So this chapter builds the workhorse interface. (The per-register attributes are where the peripheral's specific behavior lives, often generated from a register-map description — chapter 11.4 / IP-XACT/SystemRDL.)
3. Mental Model
Model the register bank as a wall of labeled mailboxes with different rules per box — most are normal boxes you can put mail in and take mail out (RW); some are "outgoing only" display boxes showing a notice you can read but not change (RO status); some are alarm boxes that light up when triggered and you reset by flipping their switch (W1C flags); some dispense a ticket that's consumed when you take it (RC); and some slots are just blanks in the wall (reserved — nothing there).
A wall of labeled mailboxes (the register array), each at a numbered slot (the index). You access them with a slot number (the captured address index). But the boxes have different rules. Most are normal mailboxes — you can put mail in (write) and take mail out (read), and what you put is what you get (RW registers). Some are "display only" boxes — they show a notice posted by the building (the hardware), which you can read but can't change — trying to "mail" something to them does nothing (RO status registers — ignore writes, read the hardware value). Some are alarm boxes that light up when an event triggers them (hardware sets the flag), and you reset the alarm by flipping its switch — specifically by writing a "1" to acknowledge it (W1C interrupt flags — hardware sets, software clears by writing 1). Some dispense a ticket that's consumed the moment you take it — reading it removes it (RC read-to-clear — clears on read, so you must be careful not to "peek" accidentally, which is why the qualified select matters — an idle glance shouldn't consume the ticket). And some slots are just blanks in the wall — nothing there (reserved — reads 0, ignores writes). So the wall is an array of slots with per-slot rules — and you access each by its number, respecting its rule.
This captures the register bank: the wall of numbered mailboxes = the indexed register array; the slot number = the captured address index (addr_q); normal put-in/take-out boxes = RW registers; display-only notice boxes = RO status (ignore writes); alarm boxes reset by flipping the switch = W1C flags (write 1 to clear); tickets consumed when taken = RC read-to-clear (clears on read); being careful not to peek-consume = qualifying the read so an idle cycle doesn't clear it; blank slots = reserved (reads 0). An array of slots with per-slot rules, accessed by number.
Watch a write to a RW register and a write-1-to-clear of a flag:
RW write, then W1C clear of a flag
4 cyclesThe model's lesson: an array of slots with per-slot rules, accessed by number. In the waveform, the RW register stores the write, and the W1C flag is cleared by writing a 1 — each register obeys its own attribute.
4. Real Hardware Perspective
In hardware, the register bank is a parameterised array of register flops, a write decoder (generating per-register write-enables, respecting writability), a read mux (selecting by index), and per-register attribute logic — often generated from a register-map description.
The parameterised array: the registers are declared as a parameterised array (e.g. reg [WIDTH-1:0] regs [0:COUNT-1], with WIDTH and COUNT as parameters). So the same RTL handles any count/width by setting the parameters. So in hardware, the storage is a parameterised array of flops. So it scales by parameter.
The write decoder and read mux: the write path is a decoder — in the data phase, if (sel_q && write_q), the index (addr_q) is decoded to a per-register write-enable: we[i] = (addr_q == i) && (register i is writable). Each register flop updates if (we[i]) regs[i] <= HWDATA (or, for W1C, the appropriate clear logic). The read path is a mux — HRDATA = regs[addr_q] (or the attribute-specific read value, e.g. 0 for reserved). So in hardware, the write is a decoder generating write-enables, the read a mux on the index. So those are the paths.
The per-register attribute logic: each register's attribute shapes its logic. RW: plain — write-enable updates it, read returns it. RO: no write-enable (writes ignored); read returns the hardware-driven value (not a stored flop). W1C: hardware sets bits; a write with a 1 in a bit position clears that bit (if (we && HWDATA[b]) regs[i][b] <= 0); hardware-set otherwise. RC: cleared on read (the read access, qualified, triggers a clear — must qualify so an idle cycle doesn't clear it). Reserved: read returns 0, no write-enable. So in hardware, each register's logic implements its attribute. So the attributes are RTL behaviors. So in hardware, the register bank is a parameterised flop array + a write decoder (per-register write-enables, respecting writability) + a read mux (by index) + per-register attribute logic (RW/RO/W1C/RC/reserved). Crucially, this is often generated — from a register-map description (IP-XACT, SystemRDL — chapter 11.4), a generator produces the bank RTL (with the right count/width/attributes) and the software headers, ensuring consistency. So in hardware, register banks are typically generated (not hand-written) for consistency and productivity. So the hardware reality: a parameterised, often-generated array with per-register attribute logic. So building one is configuring/generating the standard structure.
5. System Architecture Perspective
At the system level, the register bank is the standard control/status interface between software and hardware — and its per-register attributes and single-source generation (from a register map) are what make the hardware/software interface consistent and maintainable.
The software/hardware control interface: the register bank is the interface through which software controls and observes hardware. Software writes control registers to configure a peripheral, reads status registers to observe it, writes W1C flags to acknowledge interrupts, reads/writes data registers to transfer. So the register bank is the contract between the software (driver) and the hardware (peripheral). So at the system level, the register bank is the control/status interface — the boundary where software meets hardware. So it's the driver's view of the peripheral. So registers are the SW/HW boundary.
The attributes encode the contract: the per-register attributes (RW/RO/W1C/RC/reserved) encode the semantics of that contract — what each register means and how it behaves. RW = software-controllable setting; RO = hardware-reported status; W1C = interrupt acknowledge; RC = consume-on-read; reserved = don't-touch. So the attributes are the semantic specification of the interface — they tell the software how to use each register. So at the system level, the attributes are the documented behavior of the control interface. So they define the contract's semantics. So the attributes are the interface spec.
The single-source generation: because the register bank is the SW/HW contract, consistency between the hardware (the bank RTL) and the software (the driver's register definitions) is critical (chapter 11.4) — a mismatch (wrong attribute, wrong address) is a bug. So register banks are typically generated from a single register-map description (IP-XACT, SystemRDL): the generator produces the bank RTL (correct attributes/addresses), the software headers (matching addresses/fields), and the documentation — all from one source, guaranteeing consistency. So at the system level, the register bank (and its software interface) is generated from a single source for consistency and maintainability — a key SoC-design discipline. So at the system level, the register bank is the standard control/status interface between software and hardware (the SW/HW contract — the driver's view of the peripheral), its per-register attributes encode the semantic specification of that contract (RW/RO/W1C/RC/reserved — how to use each register), and it's typically generated from a single register-map source (consistency between RTL, headers, docs — chapter 11.4). So the register bank is where the bus's slave RTL meets the software interface — the most consequential slave type, because it's the control surface of every peripheral, and its correctness (attributes, addresses, generation) is foundational to a working, maintainable SoC. So it's the workhorse, generated, contract-defining interface. So get the register map right and generate from it.
6. Engineering Tradeoffs
The register bank embodies the indexed-array, per-register-attribute, parameterised-generated design.
- Parameterised array vs fixed registers. A parameterised array (configurable count/width) is reusable across peripherals at the cost of the parameterization; fixed registers (16.1) are simpler but not reusable. Parameterise for reuse.
- Per-register attributes vs uniform RW. Per-register attributes (RO/W1C/RC/reserved) match the peripheral's real semantics at the cost of attribute logic; treating all as RW is simpler but wrong (e.g. a status register isn't writable). Implement the attributes.
- Generated vs hand-written. Generating the bank (and headers) from a register-map description guarantees HW/SW consistency at the cost of the generation tooling; hand-writing risks mismatch bugs. Generate from a single source.
- Qualify reads (for RC) vs not. Qualifying reads (HSEL && real HTRANS) prevents read-to-clear registers being spuriously cleared by idle cycles (correct) at the cost of the qualification; not qualifying corrupts RC registers. Always qualify (especially for RC).
The throughline: a register bank slave generalizes the simple slave (16.1) into a parameterised indexed array of registers — the same capture-then-act structure (capture sel_q, write_q, the index from addr_q; act in the data phase), with a write decoder (index → which writable register receives HWDATA) and a read mux (index → the addressed register onto HRDATA). Each register has per-register access attributes — RW / RO / W1C / RC / reserved-reads-0 — encoding the peripheral's semantics. The bank is parameterised (count, width) and typically generated from a single register-map source (HW/SW consistency, chapter 11.4). Read-to-clear registers especially need the qualified select (so idle cycles don't spuriously clear them). It's the standard control/status interface — the SW/HW contract of every peripheral.
7. Industry Example
Build a register bank for a UART peripheral.
A UART has registers: CTRL (RW), STATUS (RO), TXDATA (W), RXDATA (RC — read pops the RX FIFO), IRQFLAGS (W1C), and reserved holes.
- The indexed array. The registers are an indexed array (by
addr_q, the captured address bits).CTRLat index 0,STATUSat 1,TXDATAat 2,RXDATAat 3,IRQFLAGSat 4, reserved elsewhere. - CTRL (RW). Writing CTRL configures the UART (baud, parity, enables) — a plain RW register: write-enable updates it, read returns it.
- STATUS (RO). STATUS reflects hardware state (TX empty, RX full, busy) — read-only: it has no write-enable (writes ignored), and reads return the hardware-driven value (not a stored flop). A write to STATUS does nothing (a robust bank might ERROR — chapter 16.6).
- RXDATA (RC, FIFO pop). Reading RXDATA returns the next received byte and pops the RX FIFO (a read side effect). Crucially, this pop is qualified: it triggers only on
sel_q = HSEL && real HTRANS(a real read). If it triggered onHSELalone, an IDLE cycle (withHSELhigh) would spuriously pop the FIFO — losing received data. The qualification prevents this. - IRQFLAGS (W1C). IRQFLAGS bits are set by hardware (on interrupt events). Software clears a flag by writing a 1 to it (write-1-to-clear):
if (we && HWDATA[b]) IRQFLAGS[b] <= 0(and hardware-set otherwise). So writing 0x04 to IRQFLAGS clears bit 2. - Reserved. Accessing a reserved index reads 0 and ignores writes (or ERRORs in a robust bank).
- Generated from a register map. The whole bank is generated from the UART's register-map description (SystemRDL/IP-XACT) — producing the bank RTL (with the right attributes), the software header (matching addresses/fields), and the docs — all consistent.
The example shows the register bank in action: an indexed array with per-register attributes (CTRL RW, STATUS RO, RXDATA RC with a qualified pop, IRQFLAGS W1C, reserved), built on the simple slave's capture-then-act, parameterised and generated from a single source. The qualified read protecting RXDATA's FIFO-pop is the critical detail. This is how a peripheral's register interface is built. This is the workhorse slave.
8. Common Mistakes
9. Interview Insight
Register bank design is a practical RTL interview topic — the indexed-array structure, the per-register attributes, and the qualify-reads-for-RC point are the signals.
The answer that lands gives the structure and the attributes: "A register bank slave is the simple slave generalized into a parameterised array of registers, indexed by the captured address bits. It keeps the same capture-then-act structure — capture the qualified select, the write flag, and the register index in the address phase, then act in the data phase — but now the write path is a decoder, generating a per-register write-enable from the index, and the read path is a mux selecting the indexed register onto HRDATA. The key thing is that registers have per-register access attributes, not all plain read-write. A read-write register stores writes and returns them. A read-only register, like a status register, ignores writes and returns the hardware-driven value. A write-one-to-clear register, like interrupt flags, is set by hardware and cleared when software writes a 1 to the bit. A read-to-clear register clears itself when read. And reserved indices read as 0 and ignore writes. The bank is parameterised by register count and width for reuse, and it's usually generated from a register-map description like SystemRDL or IP-XACT, which produces the RTL, the software headers, and the docs from one source, keeping hardware and software consistent. One critical detail: registers with read side effects — read-to-clear, or reads that pop a FIFO — must use the qualified select, HSEL AND a real HTRANS, because if you triggered the side effect on HSEL alone, an idle cycle with HSEL high would spuriously clear or pop them, corrupting state. That's a classic bug — mysterious lost interrupts or data under load." The indexed-array structure, the per-register attributes, and the qualify-reads-for-RC point are the senior signals.
10. Practice Challenge
Build and reason from the register bank.
- The structure. Describe the register bank: indexed array, write decoder, read mux, on the simple slave's capture-then-act.
- Attributes. Describe the per-register attributes (RW/RO/W1C/RC/reserved) and a register example of each.
- Read the waveform. From Figure 2, explain the RW write and the W1C clear.
- Qualify for RC. Explain why read-to-clear registers must use the qualified select and the bug otherwise.
- Generation. Explain why register banks are generated from a register-map description.
11. Key Takeaways
- A register bank slave generalizes the simple slave into a parameterised indexed array of registers — same capture-then-act, but a write decoder (index → which writable register gets
HWDATA) and a read mux (index → the addressed register ontoHRDATA). - Per-register access attributes encode the peripheral's semantics: RW (plain), RO (status — ignore writes, read HW value), W1C (flags — HW sets, write 1 clears), RC (read-to-clear), reserved (reads 0).
- Read-to-clear / side-effecting registers MUST use the qualified select — an unqualified read spuriously clears/pops them on IDLE cycles (corrupting state — lost flags/data). Critical bug to avoid.
- It's parameterised (count, width) for reuse across peripherals — and typically generated from a single register-map source (SystemRDL/IP-XACT → consistent RTL + headers + docs, chapter 11.4).
- It's the SW/HW control contract — the standard control/status interface, the driver's view of every peripheral; the attributes are the interface's semantic spec.
- Reserved registers read 0 and ignore writes (a defined, safe behavior) — not random (or ERROR in a robust bank).
12. What Comes Next
You now can build a parameterised register bank. The next chapter adds wait states for larger/slower storage:
- Memory Slave (next) — an AHB SRAM/memory slave with wait states.
- HREADYOUT Generation, HRDATA Muxing, HRESP Generation, and the rest — the richer slave RTL.
To revisit the simple slave this extends, see A Simple AHB-Lite Slave; for the register map driving generation, see The Address Map.