This repository has been archived on 2026-08-21. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
ciciec2026/sdk/software/examples/mlkem_test/main.c

311 lines
12 KiB
C

/**
* 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 <string.h>
#include <stdio.h>
#include <stdint.h>
#include "dma.h"
/* ─── 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 MLKEM_EK_WINDOW (MLKEM_BASE + 0x1000) /* W preload ek, R read ek */
#define MLKEM_DK_WINDOW (MLKEM_BASE + 0x2000) /* W preload dk, R read dk */
#define MLKEM_CT_WINDOW (MLKEM_BASE + 0x3000) /* W preload c, R read ct */
#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))
#define PHYS_ADDR(addr) ((uint32_t)(addr) & 0x1FFFFFFFu)
#define UNCACHED_ADDR(addr) ((PHYS_ADDR(addr)) | 0xA0000000u)
/* 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 */
static volatile uint8_t *uncached_u8(uint8_t *buf)
{
return (volatile uint8_t *)UNCACHED_ADDR(buf);
}
static void dma_to_mlkem(uint32_t dst_window, volatile uint8_t *src, int n)
{
dma_start_transfer(0, PHYS_ADDR(src), PHYS_ADDR(dst_window), (uint32_t)n, 0);
dma_wait_polling(0);
}
static void dma_from_mlkem(volatile uint8_t *dst, uint32_t src_window, int n)
{
dma_start_transfer(0, PHYS_ADDR(src_window), PHYS_ADDR(dst), (uint32_t)n, 0);
dma_wait_polling(0);
}
static void print_hex_uc(const char *label, volatile uint8_t *b, int n)
{
int i;
printf("%s", label);
for (i = 0; i < n; i++)
printf("%02x", b[i]);
printf("\n");
}
static void clear_uc(volatile uint8_t *b, int n)
{
int i;
for (i = 0; i < n; i++)
b[i] = 0;
}
/* Arbitrary but fixed test seeds / message (32 bytes each). */
static uint8_t SEED_D[32] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F
};
static uint8_t SEED_Z[32] = {
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F
};
static uint8_t MSG_M[32] = {
0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF
};
static uint8_t ek[EK_BYTES] __attribute__((aligned(64)));
static uint8_t dk[DK_BYTES] __attribute__((aligned(64)));
static uint8_t ct[CT_BYTES] __attribute__((aligned(64)));
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;
volatile uint8_t *ek_uc = uncached_u8(ek);
volatile uint8_t *dk_uc = uncached_u8(dk);
volatile uint8_t *ct_uc = uncached_u8(ct);
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);
dma_from_mlkem(ek_uc, MLKEM_EK_WINDOW, EK_BYTES);
dma_from_mlkem(dk_uc, MLKEM_DK_WINDOW, DK_BYTES);
print_hex_uc("EK32=", ek_uc, 32); /* first 32 bytes as a fingerprint */
/* ── 2. Encaps: ek + msg -> ss, ct ────────────────────────────── */
dma_to_mlkem(MLKEM_EK_WINDOW, ek_uc, EK_BYTES);
write_block32(MLKEM_MSG, MSG_M);
run_op(OP_ENCAPS);
read_block32(MLKEM_SS, ss_enc);
dma_from_mlkem(ct_uc, MLKEM_CT_WINDOW, CT_BYTES);
print_hex("SS_ENC=", ss_enc, 32);
/* ── 3. Decaps: dk + ct -> ss' ────────────────────────────────── */
dma_to_mlkem(MLKEM_DK_WINDOW, dk_uc, DK_BYTES);
dma_to_mlkem(MLKEM_CT_WINDOW, ct_uc, 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");
SEED_D[2] = 0;
SEED_Z[2] = 0;
MSG_M[2] = 0;
clear_uc(ek_uc, sizeof(ek));
clear_uc(dk_uc, sizeof(dk));
clear_uc(ct_uc, sizeof(ct));
memset(ss_enc, 0, sizeof(ss_enc));
memset(ss_dec, 0, sizeof(ss_dec));
/* ── 1. KeyGen: d,z -> ek, dk ─────────────────────────────────── */
write_block32(MLKEM_D, SEED_D);
write_block32(MLKEM_Z, SEED_Z);
run_op(OP_KEYGEN);
dma_from_mlkem(ek_uc, MLKEM_EK_WINDOW, EK_BYTES);
dma_from_mlkem(dk_uc, MLKEM_DK_WINDOW, DK_BYTES);
print_hex_uc("EK32=", ek_uc, 32); /* first 32 bytes as a fingerprint */
/* ── 2. Encaps: ek + msg -> ss, ct ────────────────────────────── */
dma_to_mlkem(MLKEM_EK_WINDOW, ek_uc, EK_BYTES);
write_block32(MLKEM_MSG, MSG_M);
run_op(OP_ENCAPS);
read_block32(MLKEM_SS, ss_enc);
dma_from_mlkem(ct_uc, MLKEM_CT_WINDOW, CT_BYTES);
print_hex("SS_ENC=", ss_enc, 32);
/* ── 3. Decaps: dk + ct -> ss' ────────────────────────────────── */
dma_to_mlkem(MLKEM_DK_WINDOW, dk_uc, DK_BYTES);
dma_to_mlkem(MLKEM_CT_WINDOW, ct_uc, 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;
}