A driver author reads exactly one document carefully: the register table. If an offset is wrong, a field is wider than its reset value, or a write-only register reads back as something else, the driver fails in ways that look like hardware faults. NanoSoC's tables are small enough to check by eye, which is precisely why they are the right training set: every rule below can be verified against a real row in seconds.
Four decisions define each register. The offset places it inside the slot: word-aligned, unique within the block, and stable forever once software exists. The width says how many bits are implemented; bits above the width are reserved and read as zero, never as copies of neighbouring fields. The access type says what a write does: read-write stores, read-only ignores, write-1-to-clear clears the bits written as one, and write-only acts without storage that software can read back. The reset value says what the register holds before any software runs, and it must fit inside the width. The DMA table from the design specification shows all four decisions at work:
Offset | Register | Fields | Access | Reset |
|---|---|---|---|---|
0x00 | CTRL | [0] GO, [1] IRQ_EN | Write both; read: GO reads 0, IRQ_EN reads back | 0x0000_0000 |
0x04 | STATUS | [0] BUSY, [1] DONE, [2] ERR | BUSY read-only; DONE and ERR write-1-to-clear | 0x0000_0000 |
0x08 | LEN | [15:0] words to move | Read-write, ignored while BUSY | 0x0000 |
0x0C | KEY | [31:0] XOR key | Read-write, ignored while BUSY | 0x0000_0000 |
0x10 | COUNT | [15:0] words moved | Read-only | 0x0000 |
other | none | none | Completes with PSLVERR | n/a |
The other NanoSoC blocks follow the same grammar. The SBR mirrors the DMA deliberately: SRC and DST addresses reset to the two SRAM bases, LEN is the same 16 bits so one software variable feeds both, CTRL adds a security bit, and STATUS repeats the BUSY, DONE, ERR pattern. The reset controller is the widest table: a write-to-clear cause register, a keyed software-reset register that only acts when bits 15 down to 8 hold 0xA5, PLL control and status, power control and status, a clock-enable register resetting to 0x3 so both DMA and SBR clocks run at boot, and a power-interrupt register. Two reset values matter for bring-up: the UART baud divider resets to 13, giving 115,384.6 baud from the 24 MHz peripheral clock, and the SBR security bit resets to non-secure, a default whose real protective value the security topic examines closely.
When a chip grows a second channel of the same engine, it does not invent new offsets. The new block takes the next free slot and reuses the parent block's table exactly: same offsets, same widths, same access types, so one driver serves both with a different base address. That reuse is replication at the integration level: identical register files in successive slots rather than one block grown until its offsets run out. (A register bank in the stricter sense, several register sets behind one bank-select bit, is the alternative when instances must share one slot; NanoSoC does not need it.) The rule that makes it safe is the slot discipline from the map topic: each bank owns a full 4 KB slot, and the driver differences live in one base-address constant per instance, never in the register code.
Every register row states offset, width, access type per field and reset value; no row is missing a column.
Reserved bits are documented as read-as-zero, and no field overlaps another field in the same register.
Identical blocks reuse one table verbatim; any per-instance difference is called out as a finding.
Read every register after reset and compare against the table before writing anything: the reset readback sweep is the cheapest test in the whole section and the verification topic builds it into a gate. Then exercise each access type: write a read-only register and confirm nothing changed, clear a status bit with a one and confirm a written zero changes nothing, and write a keyed register without the key and confirm it does not act. A mismatch in any row means the table and the RTL disagree, and the next lesson's checker exists to catch that on paper first.
DONE and ERR clear if they were set, because bits 1 and 2 were written as one. BUSY is untouched because it ignores writes, and the reserved upper bits do nothing. The register still must be written carefully: the same write during an active job would clear a completion the interrupt handler had not yet seen.