/** * mlkem_test - ML-KEM hardware accelerator example (CIC-IEC 2026) * * Exercises the mlkem_axi_slave at 0xbf400000 by running a full * KeyGen -> Encaps -> Decaps round trip on ML-KEM-512 (k=2) and verifying * that the decapsulated shared secret matches the encapsulated one. This is a * self-consistency check: it needs no external golden vectors, yet touches * every register path (seeds, start/poll, byte preload, byte readback, ss). * * KeyGen : d,z -> ek (800 B), dk (1632 B) * Encaps : ek + msg -> ss, ct (768 B) * Decaps : dk + ct -> ss' (must equal ss) * * The core runs at sys_clk/2 (25 MHz); the wrapper hides the clock crossing, * so software just does normal MMIO loads/stores (preload writes are * automatically wait-stated on the bus). */ #include #include /* ─── BSP globals ─────────────────────────────────────────────────── */ unsigned long UART_BASE = 0xbf000000; unsigned long CONFREG_TIMER_BASE = 0xbf20f100; unsigned long CONFREG_CLOCKS_PER_SEC = 50000000L; unsigned long CORE_CLOCKS_PER_SEC = 33000000L; /* ─── ML-KEM IP register map (base 0xbf400000) ────────────────────── */ #define MLKEM_BASE 0xbf400000 #define MLKEM_CTRL (MLKEM_BASE + 0x000) /* W bit0 = start */ #define MLKEM_STATUS (MLKEM_BASE + 0x004) /* R busy/done/preload_busy */ #define MLKEM_CONFIG (MLKEM_BASE + 0x008) /* RW [2:0]=k [5:4]=op */ #define MLKEM_D (MLKEM_BASE + 0x010) /* RW seed d (8 words) */ #define MLKEM_Z (MLKEM_BASE + 0x030) /* RW seed z (8 words) */ #define MLKEM_MSG (MLKEM_BASE + 0x050) /* RW message (8 words) */ #define MLKEM_SS (MLKEM_BASE + 0x070) /* R shared secret (8 words) */ #define MLKEM_PRE_SEL (MLKEM_BASE + 0x100) /* RW 0=ek 1=dk 2=c */ #define MLKEM_PRE_ADR (MLKEM_BASE + 0x104) /* RW byte addr (auto-incr) */ #define MLKEM_PRE_DAT (MLKEM_BASE + 0x108) /* W byte -> selected port */ #define MLKEM_RDB_SEL (MLKEM_BASE + 0x10C) /* RW 0=ek 1=dk_pke 2=dk 3=ct */ #define MLKEM_RDB_ADR (MLKEM_BASE + 0x110) /* RW output byte index */ #define MLKEM_RDB_DAT (MLKEM_BASE + 0x114) /* R selected byte (low 8b) */ #define CTRL_START 0x1 #define STATUS_BUSY 0x1 #define STATUS_DONE 0x2 #define OP_KEYGEN 0 #define OP_ENCAPS 1 #define OP_DECAPS 2 /* preload select */ #define PRE_EK 0 #define PRE_DK 1 #define PRE_C 2 /* readback select */ #define RDB_EK 0 #define RDB_DKP 1 #define RDB_DK 2 #define RDB_CT 3 /* ─── ML-KEM-512 (k=2) byte sizes ─────────────────────────────────── */ #define KVAL 2 #define EK_BYTES (384 * KVAL + 32) /* 800 */ #define DKP_BYTES (384 * KVAL) /* 768 */ #define DK_BYTES (DKP_BYTES + EK_BYTES + 32 + 32) /* 1632 */ #define CT_BYTES 768 /* ─── Convenience macros ──────────────────────────────────────────── */ #define REG_WR(addr, val) (*(volatile uint32_t *)(addr) = (uint32_t)(val)) #define REG_RD(addr) (*(volatile uint32_t *)(addr)) /* PLACEHOLDER */ /* ─── Wait until the core finishes the current op ─────────────────── */ static void wait_done(void) { while (!(REG_RD(MLKEM_STATUS) & STATUS_DONE)) ; } /* Clear the latched done flag (W1C) before launching a new op. */ static void clear_done(void) { REG_WR(MLKEM_STATUS, STATUS_DONE); } /* Launch the op currently configured in CONFIG and block until done. */ static void run_op(uint32_t op) { REG_WR(MLKEM_CONFIG, ((op & 0x3) << 4) | (KVAL & 0x7)); clear_done(); REG_WR(MLKEM_CTRL, CTRL_START); wait_done(); } /* Write a 256-bit value (32 bytes) into one of the seed/msg windows. * Byte i of the logical value lands in word[i>>2] byte (i&3) — i.e. the * core sees byte 0 in D[0][7:0], matching mlkem_top's d_i[7:0] convention. */ static void write_block32(uint32_t base, const uint8_t *bytes) { int w; for (w = 0; w < 8; w++) { uint32_t v = (uint32_t)bytes[w*4 + 0] | ((uint32_t)bytes[w*4 + 1] << 8) | ((uint32_t)bytes[w*4 + 2] << 16) | ((uint32_t)bytes[w*4 + 3] << 24); REG_WR(base + w*4, v); } } /* Read a 256-bit value (32 bytes) back from a window (used for SS). */ static void read_block32(uint32_t base, uint8_t *bytes) { int w; for (w = 0; w < 8; w++) { uint32_t v = REG_RD(base + w*4); bytes[w*4 + 0] = (uint8_t)(v & 0xFF); bytes[w*4 + 1] = (uint8_t)((v >> 8) & 0xFF); bytes[w*4 + 2] = (uint8_t)((v >> 16) & 0xFF); bytes[w*4 + 3] = (uint8_t)((v >> 24) & 0xFF); } } /* Stream `n` bytes into a preload port. Each PRE_DAT write auto-increments * the preload address; the bus wait-states each write until the cross-domain * byte handshake completes, so a plain loop is correct. */ static void preload(uint32_t sel, const uint8_t *buf, int n) { int i; REG_WR(MLKEM_PRE_SEL, sel); REG_WR(MLKEM_PRE_ADR, 0); for (i = 0; i < n; i++) REG_WR(MLKEM_PRE_DAT, buf[i]); } /* Read `n` bytes out of a readback tap. RDB_ADR has no auto-increment, so set * the index for each byte (write addr, then read data). */ static void readback(uint32_t sel, uint8_t *buf, int n) { int i; REG_WR(MLKEM_RDB_SEL, sel); for (i = 0; i < n; i++) { REG_WR(MLKEM_RDB_ADR, i); buf[i] = (uint8_t)(REG_RD(MLKEM_RDB_DAT) & 0xFF); } } /* PLACEHOLDER2 */ /* Arbitrary but fixed test seeds / message (32 bytes each). */ static const uint8_t SEED_D[32] = { 0x6d,0xbb,0xc4,0x37,0x51,0x36,0xdf,0x3b,0x07,0xf7,0xc7,0x0e,0x63,0x9e,0x22,0x3e, 0x17,0x7e,0x7f,0xd5,0x3b,0x16,0x1b,0x3f,0x4d,0x57,0x79,0x17,0x94,0xf1,0x26,0x24 }; static const uint8_t SEED_Z[32] = { 0xf6,0x96,0x48,0x40,0x48,0xec,0x21,0xf9,0x6c,0xf5,0x0a,0x56,0xd0,0x75,0x9c,0x44, 0x8f,0x37,0x79,0x75,0x2f,0x03,0x83,0xd3,0x74,0x49,0x69,0x06,0x94,0xcf,0x7a,0x68 }; static const uint8_t MSG_M[32] = { 0x20,0xa7,0xb7,0xe1,0x0f,0x70,0x49,0x6c,0xc3,0x82,0x20,0xb9,0x44,0xde,0xf6,0x99, 0xbf,0x14,0xd1,0x4e,0x55,0xcf,0x4c,0x90,0xa1,0x2c,0x1b,0x33,0xfc,0x80,0xff,0xff }; static uint8_t ek[EK_BYTES]; static uint8_t dk[DK_BYTES]; static uint8_t ct[CT_BYTES]; static uint8_t ss_enc[32]; static uint8_t ss_dec[32]; static void print_hex(const char *label, const uint8_t *b, int n) { int i; printf("%s", label); for (i = 0; i < n; i++) printf("%02x", b[i]); printf("\n"); } int main(void) { int i, mismatch; printf("MLKEM_START\n"); /* ── 1. KeyGen: d,z -> ek, dk ─────────────────────────────────── */ write_block32(MLKEM_D, SEED_D); write_block32(MLKEM_Z, SEED_Z); run_op(OP_KEYGEN); readback(RDB_EK, ek, EK_BYTES); readback(RDB_DK, dk, DK_BYTES); print_hex("EK32=", ek, 32); /* first 32 bytes as a fingerprint */ /* ── 2. Encaps: ek + msg -> ss, ct ────────────────────────────── */ preload(PRE_EK, ek, EK_BYTES); write_block32(MLKEM_MSG, MSG_M); run_op(OP_ENCAPS); read_block32(MLKEM_SS, ss_enc); readback(RDB_CT, ct, CT_BYTES); print_hex("SS_ENC=", ss_enc, 32); /* ── 3. Decaps: dk + ct -> ss' ────────────────────────────────── */ preload(PRE_DK, dk, DK_BYTES); preload(PRE_C, ct, CT_BYTES); run_op(OP_DECAPS); read_block32(MLKEM_SS, ss_dec); print_hex("SS_DEC=", ss_dec, 32); /* ── 4. Verify shared secrets match ───────────────────────────── */ mismatch = 0; for (i = 0; i < 32; i++) if (ss_enc[i] != ss_dec[i]) mismatch++; if (mismatch == 0) printf("MLKEM_RESULT=PASS\n"); else printf("MLKEM_RESULT=FAIL (%d byte mismatch)\n", mismatch); printf("MLKEM_DONE\n"); return 0; }