Register and command reference
This page summarizes the firmware-visible interface of the SEC module. All offsets
are byte addresses within the module's 10-bit APB window
(PADDR[9:0]). Values below are derived from
sec_pkg.sv, main_fsm.sv, status_ctrl.sv,
and sec_main_fsm_fe_chain.sv.
Base address: the SoC bring-up tests access SS1 at
0x0105_1000 needs verification —
the generated SoC memory map (memory_map.h) is not finalised. All
offsets on this page are relative to that base. Reads of unmapped or write-only
offsets return the sentinel 0xDEAD_BEEF; PSLVERR is never
asserted.
Register map
| Offset | R/W | Name | Function |
|---|---|---|---|
| 0x000 | R | SEC_STATUS | Main status word — ready/busy/enrolled/key/error/data-ready (bits) |
| 0x004 | R | SEC_ERROR_CODES | All four error-code fields in one word (codes) |
| 0x008 | R | MAIN_STATE | Current Main-FSM state, [5:0] (state values) |
| 0x00C | R | LAST_SECTOR | Page address of the last/current operation, [23:0] |
| 0x010 | R | QSPI_PENDING | Outstanding flash commands issued by the Main FSM, [5:0] |
| 0x014 | R | ASCON_STATE | ASCON control-FSM state, [5:0] |
| 0x018 | R | COUNTER_LO | Nonce counter [31:0] |
| 0x01C | R | COUNTER_HI | [6:0] counter [38:32] · [10:7] counter-FSM state · [11] valid · [12] pad_ok |
| 0x020 | R | FE_STATUS | Fuzzy-extractor + QSPI boot/debug status (busy, done, error code, key handshake) |
| 0x024 | R | ASCON_DETAIL | ASCON detailed status (auth, hash match, FIFO path, active command) |
| 0x028 | R | COUNTER_DETAIL | counter_control detailed status (store_req, FIFO grant, error code) |
| 0x02C | R | MC_FIFO_STATUS | TX/RX FIFO + status_ctrl handshake debug bits (bits) |
| 0x030 | R | REQ_STATUS | Pending-request debug word, including sticky req_dropped (bits) |
| 0x034 | R | QSPI_STATUS | Shared QSPI FIFO/debug status (bits) |
| 0x038 | R | RX_READY | 1 = decrypted data waiting in the RX FIFO; poll before reading 0x03C |
| 0x03C | R | RX_DATA | Destructive read: pops one decrypted word; 0xDEAD_BEEF + error if not ready |
| 0x040 | R/W | QSPI_TRACE | Windowed flash-command trace port (protocol) |
| 0x044 | W | WRITE_SEQ_TRIGGER | Opens / closes a 58-word write sequence (value ignored) |
| 0x048 | W | READ_SEQ_TRIGGER | Read/decrypt request: PWDATA[23:0] = flash page address |
| 0x04C–0x058 | W | BACKDOOR_KEY0–3 | 128-bit debug/fallback key, one word each (sequence) |
| 0x05C | W | BACKDOOR_GO | Starts debug-key validation (only if exactly 4 key words were loaded) |
| 0x060 | R | KEY_SOURCE | [0]: 0 = PUF-derived key · 1 = debug/fallback key (sticky until reset) |
| 0x064 | R/W | APP_WR_SECTOR | 24-bit flash page address for the next write sequence (reset: 0x002000) |
| 0x068–0x14C | W | DATA window | 58 plaintext words during an open write sequence — consecutive addresses required |
| 0x150 | R/W | PCU | Performance-counter access port (protocol) |
Reads of 0x000–0x038 are forwarded to the Main FSM and may insert
wait states (PREADY low for the round trip — in the current RTL the answer is
immediate). All other transactions are zero-wait-state.
Status word — 0x000
| Bit | Name | Meaning |
|---|---|---|
| 0 | ready | FSM in M_READY — a new request will be accepted |
| 1 | busy | Any state except ready and fatal error |
| 2 | enrolled | Helper data exists; key provisioned/reconstructed |
| 3 | key_valid | ASCON holds a validated 128-bit key |
| 4 | error | An error is latched — read 0x004 for the codes |
| 5 | data_ready | RX FIFO non-empty (decrypted words waiting) |
| 6 | qspi_init_done | Flash init finished (Quad-Enable set) |
| 7 | qspi_idle | Flash command FIFO drained, master idle |
| 8 | auth_valid | Last decrypt produced an authentication verdict |
| 9 | auth | …and the verdict: 1 = tag authenticated |
Error codes — 0x004
Four independent fields; the main code tells you which sub-code matters.
| Bits | Field | Values |
|---|---|---|
| [7:0] | main_fsm code |
0 none · 1 REQ_COLLISION · 2 BAD_SECTOR ·
3 FE fatal ·
4 QSPI_VIOLATION fatal ·
5 ASCON · 6 AUTH · 7 reserved ·
8 MC_PROTOCOL · 9 COUNTER |
| [11:8] | ascon code |
0 none · 1 BAD_CMD · 2 KEY_MISSING ·
3 MSG_TOO_LONG · 4 HASH_MISMATCH · 5 AUTH_FAIL |
| [15:12] | counter code |
0 none · 1 BAD_CMD · 2 COUNTER_WRAP |
| [18:16] | fe code |
0 none · 1 ECC (BCH uncorrectable) · 2 SPI ·
3 TIMEOUT · 4 HASH |
Fatal vs. recoverable: main codes 3 (FE), 4 (flash-guard violation),
counter faults during boot, and ASCON faults during provisioning park the FSM in
M_FATAL_ERROR until reset. Everything else flushes the FIFOs,
pulses operation_done and returns to ready.
Debug words — 0x02C / 0x030 / 0x034
REQ_STATUS — 0x030
| Bit | Meaning |
|---|---|
| 31 | key_source (mirror of 0x060) |
| 30 / 29 / 28 | start pulses: backdoor / read / write op being launched this cycle |
| 27 | FSM ready |
| 26 / 25 / 24 | pending: backdoor / read / write request waiting in the slot |
| 23 | req_dropped — sticky: a request arrived while one was already pending and was rejected |
| [22:0] | page address latched for the pending/last write |
QSPI_STATUS — 0x034
| Bit | Meaning |
|---|---|
| [31:9] | current flash command address [22:0] (bit 23 of the 8 MB device is always 0) |
| 8 / 7 | RX FIFO: pop strobe / empty |
| 6 / 5 / 4 / 3 | TX entry flags: erase / rw / push strobe / full |
| 2 | addr_violation — helper-region guard tripped (fatal) |
| 1 / 0 | master idle / init done |
MC_FIFO_STATUS — 0x02C
[15:0] = {backdoor_valid, fifo_flush, mfsm_flush, rx_pop, rx_push,
rx_full, rx_empty, rd_fail, flush, seq_error, tx_valid, tx_wrw, tx_push,
tx_pop, tx_full, tx_empty}. Intended for waveform-style debugging over APB;
the strobes are single-cycle and will usually read 0.
Encrypt — write one record
One record = one 256-byte flash page = 8 B associated data + 232 B ciphertext
(58 words of payload) + 16 B tag. The page address must be ≥ 0x002000
and 256-byte aligned.
#define SS1_BASE 0x01051000u /* needs verification — see note above */
#define REG(o) (*(volatile uint32_t*)(SS1_BASE + (o)))
int sec_encrypt(uint32_t page, const uint32_t pt[58])
{
while (!(REG(0x000) & 1u)) ; /* 1. wait for ready */
REG(0x064) = page; /* 2. target page address */
REG(0x044) = 1; /* 3. open the write sequence */
for (int i = 0; i < 58; i++) /* 4. exactly 58 words, */
REG(0x068 + 4*i) = pt[i]; /* consecutive addresses */
REG(0x044) = 1; /* 5. close → validate → start */
while (!(REG(0x000) & 1u)) ; /* 6. erase+encrypt+program … */
return (REG(0x000) >> 4) & 1u /* 7. error bit? */
? -(int)REG(0x004) : 0;
}
Step 5 is where hardware takes over: sequence validation →
tx_valid → pending-request slot → Main FSM (erase if needed,
counter increment, AD write, ASCON stream, flash commit). Instead of polling,
firmware may enable irq_en_3 and sleep until irq_3.
Decrypt — read one record
int sec_decrypt(uint32_t page, uint32_t pt[58])
{
while (!(REG(0x000) & 1u)) ; /* 1. wait for ready */
REG(0x048) = page; /* 2. request: PWDATA[23:0] */
while (REG(0x038) == 0) { /* 3. poll RX_READY … */
if (REG(0x000) & (1u << 4)) /* … checking the error bit: */
return -(int)REG(0x004); /* auth failure ends here */
}
for (int i = 0; i < 58; i++) /* 4. each read pops one word */
pt[i] = REG(0x03C);
return 0;
}
Step 3 must check the error bit while polling: after an authentication failure,
the RX FIFO remains empty and RX_READY does not become 1. Firmware
should also read 0x03C only after 0x038 indicates data
availability; a premature pop returns 0xDEAD_BEEF and raises
ERR_MC_PROTOCOL with a FIFO flush.
Observing the automatic boot
Helper-data enrollment and key reconstruction run automatically after reset — there is no APB command to start them (details). Firmware only observes:
void sec_wait_boot(void)
{
uint32_t s;
do { s = REG(0x000); } while (!(s & 1u)); /* ready */
/* s bit 2: enrolled (helper data in NVM) */
/* s bit 3: key_valid (hash check passed) */
/* ready remains 0 + bit 4 set → fatal boot error, read 0x004 */
}
A fatal boot (FE failure, hash mismatch, counter corruption) leaves
ready = 0, busy = 0 and the code latched at
0x004 — poll 0x008 for state 46
(M_FATAL_ERROR) to distinguish it from a slow boot.
Debug / fallback key loading
For debug and recovery scenarios, firmware may load a known 128-bit key instead of using the PUF-derived key. This is an explicit debug feature: the key arrives in plaintext over APB, so the resulting security depends on controlling the firmware and bus access path.
void sec_load_debug_key(const uint32_t key[4])
{
while (!(REG(0x000) & 1u)) ; /* module must be ready, no open */
/* write sequence */
REG(0x04C) = key[0]; /* exactly four words — */
REG(0x050) = key[1]; /* a 5th is ignored, */
REG(0x054) = key[2]; /* fewer ⇒ GO is ignored */
REG(0x058) = key[3];
REG(0x05C) = 1; /* GO */
while (!(REG(0x000) & 1u)) ; /* ASCON loads & activates key */
/* REG(0x060) now reads 1 — sticky until the next reset */
}
- Debug-key requests have higher priority than pending write/read requests (separate slot, higher priority).
KEY_SOURCE(0x060) flips to 1 and stays there until reset.- Records carry the key mode in their AD: a record written under the PUF key
cannot be decrypted in debug-key mode, and vice versa. The AD check rejects
the request with
ERR_AUTHbefore ASCON runs. - No hash is stored or checked for the debug key.
QSPI command trace — 0x040
A 16-entry ring that records every command the SEC core enqueues toward the flash (type + address), plus monotonic totals. Read-only observability — it never influences the data path.
| Access | Format |
|---|---|
| W | [31] CLEAR · [9:8] VIEW (0 = ENTRY, 1 = STATUS, 2 = COUNTS) · [7:0] INDEX (ring entry for VIEW = ENTRY) |
| R (ENTRY) | [31] valid · [25:24] type (0 = read, 1 = program, 2 = erase) · [23:0] address |
| R (STATUS) | [31] overflow · [30:16] total pushes · [7:0] entries held |
| R (COUNTS) | [23:16] erases · [15:8] programs · [7:0] reads |
Typical use: after an encrypt, select COUNTS and check "1 erase, 64 programs", or inspect the ENTRY view to confirm the erase→program order and addresses.
Performance Counter Unit — 0x150
Optional metrics block (synthesis knob PCU_ENABLE; reads return 0
when absent). Raw counts only — statistics are software's job.
| Access | Format |
|---|---|
| W | [31] RESET · [30] START · [29] STOP · [7:0] SELECT |
| R | 32-bit value of the SELECTed metric |
| SELECT | Metric |
|---|---|
| 0 | DROPPED — requests rejected while another was pending |
| 1 | WR_OPS — accepted write/encrypt operations |
| 2 | RD_OPS — accepted read/decrypt operations |
| 3 | RUN_CYCLES — cycles while started |
| 4 | QSPI_STALL — cycles a producer pushed against a full flash FIFO |
| 5 | FIFO_PEAK — high-water mark of the MC TX FIFO fill level over the window |
| 6 | FIFO_ACC — running sum of the FIFO fill level (software average = FIFO_ACC / RUN_CYCLES) |
| 7 | BCH_ERRORS — bit errors corrected during the last key reconstruction |
| 0xFF | INFO — identification/config word |
NVM flash layout (W25Q64JV, 8 MB)
| Flash address | Content | Written by |
|---|---|---|
| 0x000000–0x00015F | Helper data W — 88 words, 2805 bits | FE, at enrollment. Hardware-guarded: app writes/erases touching it are dropped and enter fatal error |
| 0x000160–0x00017F | ASCON-Hash256 of the key — 8 words | Main FSM/ASCON, at enrollment |
| 0x000180 | ENROLLED flag (0x0100_0000) | Main FSM — strictly last enrollment write |
| 0x001000–0x001007 | Counter reservation ceiling, obfuscated, 2 words | Main FSM/counter_control — at enrollment + every 100th increment |
| 0x002000 … | Application records — one per 256 B page, 16 pages per 4 KB sector | Main FSM/ASCON per write request |
Record layout within a page: AD at +0x00 (2 words),
ciphertext at +0x08 (58 words), tag at +0xF0 (4 words).
There is no commit marker — record validity is the tag check.
Firmware checklist
- Exactly 58 words, consecutive. Any gap, repeat, or extra word inside an open write sequence makes the closing trigger fail (
seq_error+ flush). The payload never reaches the crypto path. - One request at a time. Issuing a second write/read while one is pending sets sticky
req_dropped(0x030[23]) and drops the new request. Check it during bring-up. - Poll before pop.
0x03Cwhile0x038= 0 →0xDEAD_BEEF,ERR_MC_PROTOCOL, FIFO flush. - Check the error bit while polling RX_READY. After a failed authentication, data-ready does not assert.
- Sector geometry. Writing the first page of a 4 KB sector erases all 16 pages of that sector. Lay records out sector-front-first or accept losing neighbours.
- Helper region is hardware-protected. Any application write/erase overlapping
0x000000–0x00015Ftrips the hardware guard →M_FATAL_ERROR. - Key modes are separated. PUF-keyed records are unreadable in debug-key mode (AD check →
ERR_AUTH), and vice versa.KEY_SOURCEis sticky until reset. - Fatal errors require reset. Main codes 3/4, boot counter faults, and provision faults latch
M_FATAL_ERROR; onlyrst_nrecovers. - IRQ:
irq_3pulses on operation done and request errors when the SS-control enable (irq_en_3) is set; on fatal entry it pulses continuously.