From 7281df356fdb3b181e43b3643f048d9a82b87eb8 Mon Sep 17 00:00:00 2001 From: FallenSigh Date: Sun, 5 Jul 2026 17:27:26 +0800 Subject: [PATCH] feat: optimize startup and linking --- sdk/software/bsp/common.mk | 13 ++- sdk/software/bsp/drivers/fft.c | 14 --- sdk/software/bsp/include/dma.h | 56 ------------ sdk/software/bsp/include/fft.h | 16 ---- sdk/software/examples/asm/Makefile | 12 ++- .../examples/asm/script_matmul_fast.lds | 90 +++++++++++++++++++ sdk/software/examples/asm/start_matmul_fast.S | 54 +++++++++++ sdk/software/examples/asm/user-sample.c | 23 ++++- .../drivers => examples/mlkem_test}/dma.c | 4 +- sdk/software/examples/mlkem_test/dma.h | 55 ++++++++++++ sdk/software/examples/mlkem_test/main.c | 52 ++++++++++- 11 files changed, 292 insertions(+), 97 deletions(-) delete mode 100644 sdk/software/bsp/drivers/fft.c delete mode 100644 sdk/software/bsp/include/dma.h delete mode 100644 sdk/software/bsp/include/fft.h create mode 100644 sdk/software/examples/asm/script_matmul_fast.lds create mode 100644 sdk/software/examples/asm/start_matmul_fast.S rename sdk/software/{bsp/drivers => examples/mlkem_test}/dma.c (86%) create mode 100644 sdk/software/examples/mlkem_test/dma.h diff --git a/sdk/software/bsp/common.mk b/sdk/software/bsp/common.mk index 88764f5..b40ec06 100644 --- a/sdk/software/bsp/common.mk +++ b/sdk/software/bsp/common.mk @@ -19,27 +19,34 @@ CFLAGS += -nostartfiles -nostdlib -nostdinc -static -fno-builtin CFLAGS += -DCLOCKS_PER_SEC=CORE_CLOCKS_PER_SEC -D_CLOCKS_PER_SEC_=CORE_CLOCKS_PER_SEC CFLAGS += -DUSE_CPU_CLOCK_COUNT +LINKER_SCRIPT ?= $(COMMON_DIR)/env/script.lds +BSP_STARTUP ?= $(COMMON_DIR)/env/start.S +BSP_USE_TRAP ?= 1 +BSP_USE_DRIVERS ?= 1 + #若使用 newlib , 将下面的 -lsemihost 替换为 -lgloss LDFLAGS += -T $(LINKER_SCRIPT) \ -Wl,--gc-sections -Wl,--check-sections \ -lc -lm -lg -lsemihost -lgcc -L$(PICOLIBC_DIR)/lib -LINKER_SCRIPT := $(COMMON_DIR)/env/script.lds - -ASM_SRCS += $(COMMON_DIR)/env/start.S +ASM_SRCS += $(BSP_STARTUP) +ifeq ($(BSP_USE_TRAP),1) ifeq ($(TARGET), RTThread_Nano) else ASM_SRCS += $(COMMON_DIR)/env/trap_handler.S endif +endif +ifeq ($(BSP_USE_DRIVERS),1) C_SRCS += $(COMMON_DIR)/drivers/confreg_time.c C_SRCS += $(COMMON_DIR)/drivers/core_time.c C_SRCS += $(COMMON_DIR)/drivers/common_func.c C_SRCS += $(COMMON_DIR)/drivers/dvi.c \ $(COMMON_DIR)/drivers/led.c \ $(COMMON_DIR)/drivers/seg7.c +endif INCLUDES += -I./ \ -I$(COMMON_DIR)/include \ diff --git a/sdk/software/bsp/drivers/fft.c b/sdk/software/bsp/drivers/fft.c deleted file mode 100644 index 4476886..0000000 --- a/sdk/software/bsp/drivers/fft.c +++ /dev/null @@ -1,14 +0,0 @@ -#include "fft.h" -#include "common_func.h" - -void fft_start() { - RegWrite(FFT_CSR_REG, FFT_CTRL_START); -} - -void fft_wait() { - while ((RegRead(FFT_CSR_REG) & FFT_STAT_DONE) == 0) {} -} - -unsigned int fft_get_csr() { - return RegRead(FFT_CSR_REG); -} \ No newline at end of file diff --git a/sdk/software/bsp/include/dma.h b/sdk/software/bsp/include/dma.h deleted file mode 100644 index aea47fb..0000000 --- a/sdk/software/bsp/include/dma.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include - - -typedef struct { - volatile uint32_t SRC_ADDR; // 0x00: 源地址 - volatile uint32_t DST_ADDR; // 0x04: 目的地址 - volatile uint32_t LENGTH; // 0x08: 传输总字节数 - volatile uint32_t TAG; // 0x0C: 软件 Tag (硬件不处理,留给软件标记任务用) - volatile uint32_t CTRL; // 0x10: 控制寄存器 (配置 Burst 属性 + 启动) - volatile uint32_t STATUS; // 0x14: 状态寄存器 (Busy, Done) - volatile uint32_t MODE; // 0x18: 0=mem2mem, 1=mem2ep, 2=ep2mem - volatile uint32_t EP_CFG; // 0x1C: [3:0]=bank, [15:8]=word offset, [19:16]=endpoint id - volatile uint32_t RESV[8]; // 0x20 ~ 0x3C: 保留空间,凑齐 0x40 字节 -} dma_ch_regs_t; - -// DMA 控制器基地址 -#define CDMA_MUX_BASE 0xbf300000 -#define DMA_CH(i) ((volatile dma_ch_regs_t*)(CDMA_MUX_BASE + (i) * 0x40)) - -typedef struct { - volatile uint32_t CTRL; // 0x200: bit0 start - volatile uint32_t STATUS; // 0x204: bit0 busy, bit1 done (W1C) - volatile uint32_t SRC_BASE; // 0x208 - volatile uint32_t DST_BASE; // 0x20C - volatile uint32_t GROUPS; // 0x210 - volatile uint32_t MAT_BASE; // 0x214: 保留,兼容旧软件 - volatile uint32_t SRC_STRIDE; // 0x218 - volatile uint32_t DST_STRIDE; // 0x21C -} dma_seq_regs_t; - -#define DMA_SEQ ((volatile dma_seq_regs_t*)(CDMA_MUX_BASE + 0x200)) - -// CTRL 寄存器位定义 -#define CTRL_START_BIT (1 << 0) -#define CTRL_BURST_SIZE(x) (((x) & 0x7) << 3) // Bits [5:3]: AXI AxSIZE (0=1B, 1=2B, 2=4B, 3=8B) -#define CTRL_BURST_LEN(x) (((x) & 0xFF) << 6) // Bits [13:6]: AXI AxLEN (0=1 beat, 15=16 beats) - -#define DMA_MODE_MEM2MEM 0 -#define DMA_MODE_MEM2EP 1 -#define DMA_MODE_EP2MEM 2 -#define DMA_EP_CFG(id, bank, word_off) \ - ((((id) & 0xF) << 16) | (((word_off) & 0xFF) << 8) | ((bank) & 0xF)) - -// STATUS 寄存器位定义 -#define STATUS_BUSY_BIT (1 << 0) -#define STATUS_DONE_BIT (1 << 1) - -#define SEQ_START_BIT (1 << 0) -#define SEQ_SKIP_STORE_BIT (1 << 1) -#define SEQ_BUSY_BIT (1 << 0) -#define SEQ_DONE_BIT (1 << 1) - -void dma_start_transfer(int ch_id, uint32_t src, uint32_t dst, uint32_t bytes, uint32_t tag); -void dma_wait_polling(int ch_id); diff --git a/sdk/software/bsp/include/fft.h b/sdk/software/bsp/include/fft.h deleted file mode 100644 index dca7dec..0000000 --- a/sdk/software/bsp/include/fft.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#define FFT_BASE 0xbf400000 -#define FFT_IN_RE_BASE (FFT_BASE + 0x1000) -#define FFT_IN_IM_BASE (FFT_BASE + 0x2000) -#define FFT_OUT_RE_BASE (FFT_BASE + 0x3000) -#define FFT_OUT_IM_BASE (FFT_BASE + 0x4000) -#define FFT_CSR_REG (FFT_BASE + 0xF000) -#define FFT_CTRL_START (1 << 4) -#define FFT_STAT_DONE (1 << 1) -#define FFT_STAT_BUSY (1 << 0) -#define FFT_POINT_NUM 1024 - -void fft_start(); -void fft_wait(); -unsigned int fft_get_csr(); \ No newline at end of file diff --git a/sdk/software/examples/asm/Makefile b/sdk/software/examples/asm/Makefile index 8e3e323..369c63d 100644 --- a/sdk/software/examples/asm/Makefile +++ b/sdk/software/examples/asm/Makefile @@ -1,7 +1,9 @@ TARGET = user-sample MATMUL_GROUP_NUM ?= 5000 -CFLAGS += -O3 -g -DMATMUL_GROUP_NUM=$(MATMUL_GROUP_NUM) +MATMUL_WRITEBACK_RESULTS ?= 0 +MATMUL_FAST_START ?= 1 +CFLAGS += -O3 -g -DMATMUL_GROUP_NUM=$(MATMUL_GROUP_NUM) -DMATMUL_WRITEBACK_RESULTS=$(MATMUL_WRITEBACK_RESULTS) -DMATMUL_FAST_START=$(MATMUL_FAST_START) C_SRCS := $(wildcard ./*.c ) @@ -9,6 +11,14 @@ OBJDIR = obj COMMON_DIR = ../../bsp GCC_DIR=../../../toolchains/loongson-gnu-toolchain-8.3-x86_64-loongarch32r-linux-gnusf-v2.0 PICOLIBC_DIR=../../../toolchains/picolibc + +ifeq ($(MATMUL_FAST_START),1) +LINKER_SCRIPT := script_matmul_fast.lds +BSP_STARTUP := start_matmul_fast.S +BSP_USE_TRAP := 0 +BSP_USE_DRIVERS := 0 +endif + include ../../bsp/common.mk # Strip picolibc: no -lc -lm -lg -lsemihost, keep -lgcc only diff --git a/sdk/software/examples/asm/script_matmul_fast.lds b/sdk/software/examples/asm/script_matmul_fast.lds new file mode 100644 index 0000000..d6a6eea --- /dev/null +++ b/sdk/software/examples/asm/script_matmul_fast.lds @@ -0,0 +1,90 @@ +OUTPUT_ARCH(loongarch) +ENTRY(_start) + +MEMORY +{ + isram (rwxai) : ORIGIN = 0x1c000000, LENGTH = 512K + dsram (rwxai) : ORIGIN = 0x1c080000, LENGTH = 512K +} + +SECTIONS +{ + __stack_size = DEFINED(__stack_size) ? __stack_size : 4K; + + .init : + { + KEEP(*(SORT_NONE(.init))) + } >isram AT>isram + + . = ALIGN(16); + .text : + { + _ftext = .; + *(.text.unlikely .text.unlikely.*) + *(.text.startup .text.startup.*) + *(.text .text.*) + *(.gnu.linkonce.t.*) + } >isram AT>isram + + . = ALIGN(16); + .rodata : + { + *(.rdata) + *(.rodata .rodata.*) + *(.gnu.linkonce.r.*) + *(.srodata.cst16) + *(.srodata.cst8) + *(.srodata.cst4) + *(.srodata.cst2) + *(.srodata .srodata.*) + } >isram AT>isram + + . = ALIGN(16); + PROVIDE(__etext = .); + PROVIDE(_etext = .); + PROVIDE(etext = .); + PROVIDE(_data_lma = .); + + .data ORIGIN(dsram) : + { + PROVIDE(_data = .); + *(.data .data.*) + *(.gnu.linkonce.d.*) + . = ALIGN(16); + PROVIDE(__global_pointer$ = . + 0x800); + *(.sdata .sdata.*) + *(.gnu.linkonce.s.*) + . = ALIGN(16); + PROVIDE(_edata = .); + PROVIDE(edata = .); + } >dsram AT>isram + + .bss : + { + PROVIDE(__bss_start = .); + *(.sbss*) + *(.sbss.*) + *(.dynsbss) + *(.scommon) + *(.bss) + *(.bss.*) + *(.dynbss) + *(.gnu.linkonce.sb.*) + *(.gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(16); + PROVIDE(_end = .); + PROVIDE(end = .); + } >dsram AT>dsram + + PROVIDE(__heap_start = _end); + + .stack ORIGIN(dsram) + LENGTH(dsram) - __stack_size : + { + PROVIDE(_heap_end = .); + PROVIDE(__heap_end = _heap_end); + PROVIDE(__heap_size = __heap_end - __heap_start); + . = __stack_size; + PROVIDE(_stack = .); + } >dsram AT>dsram +} diff --git a/sdk/software/examples/asm/start_matmul_fast.S b/sdk/software/examples/asm/start_matmul_fast.S new file mode 100644 index 0000000..f276ad7 --- /dev/null +++ b/sdk/software/examples/asm/start_matmul_fast.S @@ -0,0 +1,54 @@ +#include "regdef.h" + + .section .init + .globl _start + .type _start, @function + +_start: + /* Enable direct mapped windows used by SRAM and uncached peripherals. */ + li.w t0, 0x8 + li.w t1, 0x18 + csrxchg t0, t1, csr_crmd + + csrwr zero, csr_dmw0 + csrwr zero, csr_dmw1 + li.w t0, 0x19 + csrwr t0, csr_dmw0 + li.w t0, 0xa0000009 + csrwr t0, csr_dmw1 + + li.w t0, 0x10 + li.w t1, 0x18 + csrxchg t0, t1, csr_crmd + + /* Polling-only benchmark: keep interrupts disabled. */ + li.w t0, 0x4 + csrxchg zero, t0, csr_crmd + csrwr zero, csr_prmd + csrwr zero, csr_ecfg + + /* UART0 init, 115200 baud with the same divisor selection as BSP start.S. */ + li.w t0, 0xbf000000 + li.w t1, 0x07 + st.b t1, t0, 2 + li.w t1, 0x80 + st.b t1, t0, 3 + st.b zero, t0, 1 + li.w t2, 0xbf20f500 + ld.w t2, t2, 0 + li.w t1, 0x1b + beq zero, t2, 1f + li.w t1, 0x1 +1: + st.b t1, t0, 0 + li.w t1, 0x03 + st.b t1, t0, 3 + st.b zero, t0, 4 + + la.local sp, _stack + li.w a0, 0 + li.w a1, 0 + bl main + +1: + b 1b diff --git a/sdk/software/examples/asm/user-sample.c b/sdk/software/examples/asm/user-sample.c index 95f304a..1505ab6 100644 --- a/sdk/software/examples/asm/user-sample.c +++ b/sdk/software/examples/asm/user-sample.c @@ -26,6 +26,23 @@ static void uart_puts(const char *s) } } +static void uart_put_matmul_start_fifo(void) +{ + UART_THR = 'M'; + UART_THR = 'A'; + UART_THR = 'T'; + UART_THR = 'M'; + UART_THR = 'U'; + UART_THR = 'L'; + UART_THR = '_'; + UART_THR = 'S'; + UART_THR = 'T'; + UART_THR = 'A'; + UART_THR = 'R'; + UART_THR = 'T'; + UART_THR = '\n'; +} + static void uart_puthex(unsigned int val) { int i; @@ -44,7 +61,9 @@ static void uart_puthex(unsigned int val) #define MATMUL_WRITEBACK_RESULTS 1 #endif +#if MATMUL_FAST_START == 0 unsigned long UART_BASE = 0xbf000000; +#endif #define EXTRAM_BASE_PHYS 0x1c400000u #define SRC_BASE_WORDS 0u @@ -65,8 +84,6 @@ int main(void) return 0; } - uart_puts("MATMUL_START\n"); - DMA_SEQ->STATUS = SEQ_DONE_BIT; DMA_SEQ->SRC_BASE = EXTRAM_PHYS_AB(0); DMA_SEQ->DST_BASE = EXTRAM_PHYS_C(0); @@ -78,6 +95,8 @@ int main(void) #if MATMUL_WRITEBACK_RESULTS == 0 seq_ctrl |= SEQ_SKIP_STORE_BIT; #endif + + uart_put_matmul_start_fifo(); DMA_SEQ->CTRL = seq_ctrl; while ((DMA_SEQ->STATUS & SEQ_DONE_BIT) == 0u) { diff --git a/sdk/software/bsp/drivers/dma.c b/sdk/software/examples/mlkem_test/dma.c similarity index 86% rename from sdk/software/bsp/drivers/dma.c rename to sdk/software/examples/mlkem_test/dma.c index 551b725..312d33c 100644 --- a/sdk/software/bsp/drivers/dma.c +++ b/sdk/software/examples/mlkem_test/dma.c @@ -1,6 +1,6 @@ #include "dma.h" -void dma_start_transfer(int ch_id, uint32_t src, uint32_t dst, uint32_t bytes, uint32_t tag) { +void dma_start_transfer(int ch_id, u32 src, u32 dst, u32 bytes, u32 tag) { volatile dma_ch_regs_t* ch = DMA_CH(ch_id); // 检查通道是否空闲 防止覆盖正在运行的任务 @@ -18,7 +18,7 @@ void dma_start_transfer(int ch_id, uint32_t src, uint32_t dst, uint32_t bytes, u // 置 Burst 属性并触发传输 // AXI SIZE: 2 (代表 2^2 = 4 Bytes,与 32-bit 数据线匹配) // AXI LEN: 15 (代表 16 beats 突发传输,最高效) - uint32_t ctrl_val = CTRL_BURST_SIZE(2) | CTRL_BURST_LEN(15); + u32 ctrl_val = CTRL_BURST_SIZE(2) | CTRL_BURST_LEN(63); // 写入 CTRL 并拉高 Bit 0 (Start) ch->CTRL = ctrl_val | CTRL_START_BIT; diff --git a/sdk/software/examples/mlkem_test/dma.h b/sdk/software/examples/mlkem_test/dma.h new file mode 100644 index 0000000..aba80b5 --- /dev/null +++ b/sdk/software/examples/mlkem_test/dma.h @@ -0,0 +1,55 @@ +#pragma once + +typedef unsigned int u32; + +typedef struct { + volatile u32 SRC_ADDR; // 0x00: source address + volatile u32 DST_ADDR; // 0x04: destination address + volatile u32 LENGTH; // 0x08: transfer byte count + volatile u32 TAG; // 0x0C: software tag (ignored by HW) + volatile u32 CTRL; // 0x10: control (burst config + start) + volatile u32 STATUS; // 0x14: status (busy, done) + volatile u32 MODE; // 0x18: 0=mem2mem, 1=mem2ep, 2=ep2mem + volatile u32 EP_CFG; // 0x1C: [3:0]=bank, [15:8]=word offset, [19:16]=endpoint id + volatile u32 RESV[8]; // 0x20 ~ 0x3C: padding to 0x40 bytes +} dma_ch_regs_t; + +// DMA 控制器基地址 +#define CDMA_MUX_BASE 0xbf300000 +#define DMA_CH(i) ((volatile dma_ch_regs_t*)(CDMA_MUX_BASE + (i) * 0x40)) + +typedef struct { + volatile u32 CTRL; // 0x200: bit0 start + volatile u32 STATUS; // 0x204: bit0 busy, bit1 done (W1C) + volatile u32 SRC_BASE; // 0x208 + volatile u32 DST_BASE; // 0x20C + volatile u32 GROUPS; // 0x210 + volatile u32 MAT_BASE; // 0x214: reserved for compatibility + volatile u32 SRC_STRIDE; // 0x218 + volatile u32 DST_STRIDE; // 0x21C +} dma_seq_regs_t; + +#define DMA_SEQ ((volatile dma_seq_regs_t*)(CDMA_MUX_BASE + 0x200)) + +// CTRL 寄存器位定义 +#define CTRL_START_BIT (1 << 0) +#define CTRL_BURST_SIZE(x) (((x) & 0x7) << 3) // Bits [5:3]: AXI AxSIZE (0=1B, 1=2B, 2=4B, 3=8B) +#define CTRL_BURST_LEN(x) (((x) & 0xFF) << 6) // Bits [13:6]: AXI AxLEN (0=1 beat, 15=16 beats) + +#define DMA_MODE_MEM2MEM 0 +#define DMA_MODE_MEM2EP 1 +#define DMA_MODE_EP2MEM 2 +#define DMA_EP_CFG(id, bank, word_off) \ + ((((id) & 0xF) << 16) | (((word_off) & 0xFF) << 8) | ((bank) & 0xF)) + +// STATUS 寄存器位定义 +#define STATUS_BUSY_BIT (1 << 0) +#define STATUS_DONE_BIT (1 << 1) + +#define SEQ_START_BIT (1 << 0) +#define SEQ_SKIP_STORE_BIT (1 << 1) +#define SEQ_BUSY_BIT (1 << 0) +#define SEQ_DONE_BIT (1 << 1) + +void dma_start_transfer(int ch_id, u32 src, u32 dst, u32 bytes, u32 tag); +void dma_wait_polling(int ch_id); diff --git a/sdk/software/examples/mlkem_test/main.c b/sdk/software/examples/mlkem_test/main.c index 5f3fa43..eac9527 100644 --- a/sdk/software/examples/mlkem_test/main.c +++ b/sdk/software/examples/mlkem_test/main.c @@ -16,6 +16,7 @@ * automatically wait-stated on the bus). */ +#include #include #include @@ -149,15 +150,15 @@ static void readback(uint32_t sel, uint8_t *buf, int n) /* PLACEHOLDER2 */ /* Arbitrary but fixed test seeds / message (32 bytes each). */ -static const uint8_t SEED_D[32] = { +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 const uint8_t SEED_Z[32] = { +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 const uint8_t MSG_M[32] = { +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 }; @@ -206,6 +207,51 @@ int main(void) 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; + memset(ek, 0, sizeof(ek)); + memset(dk, 0, sizeof(dk)); + memset(ct, 0, 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); + 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++)