SEC Module · Secure Enclave
Software reference · APB slave · offsets relative to the SS1 base

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

OffsetR/WNameFunction
0x000RSEC_STATUSMain status word — ready/busy/enrolled/key/error/data-ready (bits)
0x004RSEC_ERROR_CODESAll four error-code fields in one word (codes)
0x008RMAIN_STATECurrent Main-FSM state, [5:0] (state values)
0x00CRLAST_SECTORPage address of the last/current operation, [23:0]
0x010RQSPI_PENDINGOutstanding flash commands issued by the Main FSM, [5:0]
0x014RASCON_STATEASCON control-FSM state, [5:0]
0x018RCOUNTER_LONonce counter [31:0]
0x01CRCOUNTER_HI[6:0] counter [38:32] · [10:7] counter-FSM state · [11] valid · [12] pad_ok
0x020RFE_STATUSFuzzy-extractor + QSPI boot/debug status (busy, done, error code, key handshake)
0x024RASCON_DETAILASCON detailed status (auth, hash match, FIFO path, active command)
0x028RCOUNTER_DETAILcounter_control detailed status (store_req, FIFO grant, error code)
0x02CRMC_FIFO_STATUSTX/RX FIFO + status_ctrl handshake debug bits (bits)
0x030RREQ_STATUSPending-request debug word, including sticky req_dropped (bits)
0x034RQSPI_STATUSShared QSPI FIFO/debug status (bits)
0x038RRX_READY1 = decrypted data waiting in the RX FIFO; poll before reading 0x03C
0x03CRRX_DATADestructive read: pops one decrypted word; 0xDEAD_BEEF + error if not ready
0x040R/WQSPI_TRACEWindowed flash-command trace port (protocol)
0x044WWRITE_SEQ_TRIGGEROpens / closes a 58-word write sequence (value ignored)
0x048WREAD_SEQ_TRIGGERRead/decrypt request: PWDATA[23:0] = flash page address
0x04C–0x058WBACKDOOR_KEY0–3128-bit debug/fallback key, one word each (sequence)
0x05CWBACKDOOR_GOStarts debug-key validation (only if exactly 4 key words were loaded)
0x060RKEY_SOURCE[0]: 0 = PUF-derived key · 1 = debug/fallback key (sticky until reset)
0x064R/WAPP_WR_SECTOR24-bit flash page address for the next write sequence (reset: 0x002000)
0x068–0x14CWDATA window58 plaintext words during an open write sequence — consecutive addresses required
0x150R/WPCUPerformance-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

BitNameMeaning
0readyFSM in M_READY — a new request will be accepted
1busyAny state except ready and fatal error
2enrolledHelper data exists; key provisioned/reconstructed
3key_validASCON holds a validated 128-bit key
4errorAn error is latched — read 0x004 for the codes
5data_readyRX FIFO non-empty (decrypted words waiting)
6qspi_init_doneFlash init finished (Quad-Enable set)
7qspi_idleFlash command FIFO drained, master idle
8auth_validLast decrypt produced an authentication verdict
9auth…and the verdict: 1 = tag authenticated

Error codes — 0x004

Four independent fields; the main code tells you which sub-code matters.

BitsFieldValues
[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

BitMeaning
31key_source (mirror of 0x060)
30 / 29 / 28start pulses: backdoor / read / write op being launched this cycle
27FSM ready
26 / 25 / 24pending: backdoor / read / write request waiting in the slot
23req_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

BitMeaning
[31:9]current flash command address [22:0] (bit 23 of the 8 MB device is always 0)
8 / 7RX FIFO: pop strobe / empty
6 / 5 / 4 / 3TX entry flags: erase / rw / push strobe / full
2addr_violation — helper-region guard tripped (fatal)
1 / 0master 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_AUTH before 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.

AccessFormat
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.

AccessFormat
W[31] RESET · [30] START · [29] STOP · [7:0] SELECT
R32-bit value of the SELECTed metric
SELECTMetric
0DROPPED — requests rejected while another was pending
1WR_OPS — accepted write/encrypt operations
2RD_OPS — accepted read/decrypt operations
3RUN_CYCLES — cycles while started
4QSPI_STALL — cycles a producer pushed against a full flash FIFO
5FIFO_PEAK — high-water mark of the MC TX FIFO fill level over the window
6FIFO_ACC — running sum of the FIFO fill level (software average = FIFO_ACC / RUN_CYCLES)
7BCH_ERRORS — bit errors corrected during the last key reconstruction
0xFFINFO — identification/config word

NVM flash layout (W25Q64JV, 8 MB)

Flash addressContentWritten by
0x000000–0x00015FHelper data W — 88 words, 2805 bitsFE, at enrollment. Hardware-guarded: app writes/erases touching it are dropped and enter fatal error
0x000160–0x00017FASCON-Hash256 of the key — 8 wordsMain FSM/ASCON, at enrollment
0x000180ENROLLED flag (0x0100_0000)Main FSM — strictly last enrollment write
0x001000–0x001007Counter reservation ceiling, obfuscated, 2 wordsMain FSM/counter_control — at enrollment + every 100th increment
0x002000 …Application records — one per 256 B page, 16 pages per 4 KB sectorMain 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. 0x03C while 0x038 = 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–0x00015F trips 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_SOURCE is sticky until reset.
  • Fatal errors require reset. Main codes 3/4, boot counter faults, and provision faults latch M_FATAL_ERROR; only rst_n recovers.
  • IRQ: irq_3 pulses on operation done and request errors when the SS-control enable (irq_en_3) is set; on fatal entry it pulses continuously.