docs: 重写 README,覆盖 KeyGen/Encaps/Decaps 全功能微架构与 mlkem_top I/O

原 README 仅介绍 KeyGen。重写后覆盖:
- 三种操作(op_i=0/1/2)与三种安全等级的运行时选择、尺寸/周期表
- 完整 mlkem_top 端口清单(控制握手、ss_o、ek/dk/c 流式输入、dbg 回读抽头)
- 微架构:叶子算子、共享 keccak、三个系数 BRAM 库 + 字节 BRAM、存储布局
- 顶层 FSM 三条数据通路(含 Decaps 复用 Encaps 重加密 + 隐式拒绝)
- 各操作的 FIPS 203 算法分解与对应状态
- 测试矩阵(KAT 分阶段 + hello_world 单/双实例端到端)与运行脚本
This commit is contained in:
2026-06-29 22:26:01 +08:00
parent ee2bf1cda8
commit 6b99e5abba

398
README.md
View File

@@ -1,245 +1,255 @@
# ML-KEM 硬件实现FIPS 203
基于 SystemVerilog 的 **ML-KEM**Module-Lattice-based Key Encapsulation MechanismNIST 后量子密码标准,源自 Kyber同步流水线硬件实现。面向 FPGA使用 Vivado XSIM 与 Verilator 进行仿真验证。
基于 SystemVerilog 的 **ML-KEM**Module-Lattice-based Key Encapsulation MechanismNIST 后量子密码标准,源自 CRYSTALS-Kyber同步流水线硬件实现。面向 FPGA使用 Vivado XSIM 仿真验证,全部与 NIST KAT 标准答案逐字节比对
本文档重点介绍顶层密钥生成模块 **`mlkem_top`** 的工作流程及其测试流程。各底层算子SHA-3、NTT、CBD 采样等)已独立验证,本文不再展开。
单一顶层模块 **`mlkem_top`** 在运行时同时支持三种操作与三种安全等级:
## 概述
| 操作 | `op_i` | FIPS 203 算法 | 输入 | 输出 |
|:---|:---:|:---|:---|:---|
| **密钥生成** KeyGen | `0` | 算法 16 | 种子 `d`, `z` | `ek`(公钥), `dk`(私钥) |
| **密钥封装** Encaps | `1` | 算法 17 | `ek`, 消息 `m` | 共享密钥 `K`, 密文 `c` |
| **密钥解封装** Decaps | `2` | 算法 18 | `dk`, 密文 `c` | 共享密钥 `K`(含隐式拒绝) |
ML-KEM 是 NIST 在 FIPS 203 中标准化的后量子密钥封装机制,其安全性基于多项式环 Z_q[x]/(x²⁵⁶+1) 上的 Module-LWE 难题
模块运行于 **100 MHz**10 ns 周期),低电平异步复位
`mlkem_top` 实现 **FIPS 203 算法 16KeyGen_internal** 的完整密钥生成数据通路:给定种子 `d``z`,输出封装密钥 `ek`(公钥)和解封装密钥 `dk`(私钥)。模块运行于 **100 MHz**10 ns 周期),低电平复位。
---
## 一、概述
ML-KEM 的安全性基于多项式环 **Z_q[x]/(x²⁵⁶+1)** 上的 Module-LWE 难题。固定参数 **q = 3329**(素数模)、**n = 256**(多项式次数)。
### 运行时参数选择
**ML-KEM 的安全等级 K 在运行时通过输入端口 `k_i` 选择**而非编译期参数。存储按最坏情况ML-KEM-1024KMAX=4静态分配`k_i``start_i` 时被采样到内部寄存器 `k_r`,据此选取激活的子区间。
**安全等级 K 通过输入端口 `k_i` 在运行时选择**而非编译期参数。存储按最坏情况ML-KEM-1024KMAX=4静态分配`k_i``op_i``start_i` 时被采样到内部寄存器`k_r`/`op_r`,据此选取激活的子区间与数据通路
| k_i | 方案 | 模块秩 K | η₁ | ek 字节数 | dk 字节数 | KeyGen 周期数 |
|:---:|:---|:---:|:---:|:---:|:---:|:---:|
| 2 | ML-KEM-512 | 2 | 3 | 800 | 1632 | 21 403 |
| 3 | ML-KEM-768 | 3 | 2 | 1184 | 2400 | 36 207 |
| 4 | ML-KEM-1024 | 4 | 2 | 1568 | 3168 | 54 005 |
| k_i | 方案 | K | η₁ | η₂ | (d_u, d_v) | ek | dk | 密文 c | 共享密钥 |
|:---:|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 2 | ML-KEM-512 | 2 | 3 | 2 | (10, 4) | 800 B | 1632 B | 768 B | 32 B |
| 3 | ML-KEM-768 | 3 | 2 | 2 | (10, 4) | 1184 B | 2400 B | 1088 B | 32 B |
| 4 | ML-KEM-1024 | 4 | 2 | 2 | (11, 5) | 1568 B | 3168 B | 1568 B | 32 B |
> 注:`k_i` 仅在 `start_i` 时采样,且假定取值 ∈ {2,3,4}越界值0/1/57当前不做保护会产生错误的尺寸计算
参考周期数K=2hello_world 实测KeyGen ≈ 22.9k、Encaps ≈ 32.5k、Decaps ≈ 50.8k 周期
固定参数:**q = 3329**(素数模)、**n = 256**多项式次数。du/dv 仅用于封装/解封装KeyGen 不涉及
> 注:`k_i`/`op_i` 仅在 `start_i` 时采样,且假定 `k_i ∈ {2,3,4}`;越界值不做保护
## `mlkem_top` 接口
### 数据约定
```
module mlkem_top #(parameter KMAX = 4) (
input clk, rst_n,
input [2:0] k_i, // 运行时方案选择2/3/4
input [255:0] d_i, // KeyGen 种子 dbyte0 在 d_i[7:0]
input [255:0] z_i, // 隐式拒绝种子 z
input start_i, // 启动脉冲
output busy_o, // 运行中拉高
output done_o, // ek/dk 就绪时拉高
// 调试回读端口(供 TB 逐字节核对,无需宽总线)
input [3:0] dbg_slot_i, input [7:0] dbg_idx_i, output [11:0] dbg_coeff_o,
input dbg_byte_sel_i, input [10:0] dbg_byte_idx_i, output [7:0] dbg_byte_o,
input [11:0] dbg_dk_idx_i, output [7:0] dbg_dk_o,
output [255:0] dbg_rho_o, dbg_sigma_o
所有 256-bit 端口采用 **“byte0 在低位”** 约定:`value[8m +: 8] = byte m`。例如 `d_i[7:0]` 是种子 d 的第 0 字节。
---
## 二、`mlkem_top` 接口
```verilog
module mlkem_top #(parameter KMAX = 4) ( // KMAX 决定存储规模(最坏情况 = 1024
input clk,
input rst_n, // 低电平复位
input [2:0] k_i, // 运行时方案2/3/4start_i 时采样)
input [1:0] op_i, // 0=KeyGen 1=Encaps 2=Decapsstart_i 时采样)
input [255:0] d_i, // KeyGen 种子 d
input [255:0] z_i, // 隐式拒绝种子 z写入 dkDecaps 由 dk 解析)
input [255:0] msg_i, // Encaps 消息 m
input start_i, // 启动脉冲
output busy_o, // 运行中拉高
output done_o, // 完成时拉高(结果就绪)
output [255:0] ss_o, // 共享密钥 KEncaps/Decaps 在 done_o 时有效)
// ---- 流式输入端口start_i 之前在 ST_IDLE 预加载,每拍 1 字节)----
// Encaps把 ek公钥写入 ek_bram
input ek_in_we,
input [10:0] ek_in_addr, // 0 .. ek_bytes-1
input [7:0] ek_in_byte,
// Decaps把 dk私钥写入按字节区间自动路由
// [0, 384K) -> dk_pkedkp_bram = ŝ 编码
// [384K, 768K+32) -> ek_pkeek_bram = t̂ 编码 ‖ ρ
// [768K+32, 768K+64) -> H(ek)(寄存器)
// [768K+64, 768K+96) -> z寄存器
input dk_in_we,
input [11:0] dk_in_addr, // 0 .. (768K+96)-1
input [7:0] dk_in_byte,
// Decaps把密文 c 写入 c_in_bram与 ct_bram 分开,便于 c'==c 比较)
input c_in_we,
input [10:0] c_in_addr, // 0 .. ct_bytes-1
input [7:0] c_in_byte,
// ---- 调试 / 结果回读端口(只读抽头,逐字节,免去宽总线)----
input [10:0] dbg_ct_idx_i, output [7:0] dbg_ct_o, // 密文 cct_bram逐字节
input [5:0] dbg_slot_i, input [7:0] dbg_idx_i, output [11:0] dbg_coeff_o, // 系数回读
input dbg_byte_sel_i, input [10:0] dbg_byte_idx_i, output [7:0] dbg_byte_o, // ek(0)/dk_pke(1)
input [11:0] dbg_dk_idx_i, output [7:0] dbg_dk_o, // 完整 dk1632/2400/3168 B逐字节
output [255:0] dbg_rho_o, // ρ
output [255:0] dbg_sigma_o, // σ
output [255:0] dbg_r_o, // rG 高半 / Decaps r'
output [255:0] dbg_hek_o, // H(ek)
output [255:0] dbg_mprime_o, // m'Decaps 恢复的消息,存于 m_r
output [255:0] dbg_kbar_o, // K̄ = J(z‖c)Decaps 拒绝路径密钥)
output [255:0] dbg_decz_o, // 解析出的 z
output [255:0] dbg_dech_o // 解析出的 H(ek)
);
```
`busy_o`/`done_o` 提供握手;`dbg_*` 端口为只读调试抽头,让 TB 可以逐系数 / 逐字节读出中间结果与最终的 ek/dk而无需引出整条数据总线。
### 端口分组说明
## 工作流程
- **控制握手**`start_i` 拉一拍启动;`busy_o` 标志运行;`done_o` 在结果就绪时拉高。
- **结果**`ss_o` 给出 Encaps/Decaps 的共享密钥;`ek`/`dk`/`c` 经各自 BRAM 通过 `dbg_*` 抽头逐字节读出。
- **流式输入**`ek_in_*`/`dk_in_*`/`c_in_*``start_i` 之前于 `ST_IDLE` 把外部数据预加载进对应 BRAM。
- **调试抽头**`dbg_*` 为只读,供测试平台核对中间量与最终产物,不引出整条数据总线。
`mlkem_top` 复用了已独立验证的叶子模块(每个模块自带 keccak_core无共享仲裁器`sha3_top``sample_ntt_sync``sample_cbd_sync``ntt_core``poly_mul_sync`。顶层是一个 8 状态 FSM串行驱动这些算子并把所有中间多项式存放在统一的系数寄存器阵列 `polymem` 中。
---
### KeyGen 算法FIPS 203 算法 16
## 三、微架构
```
(ρ, σ) = G(d ‖ K) // SHA3-512
Â[i][j] = SampleNTT(ρ ‖ j ‖ i) i,j ∈ 0..K-1 // SHAKE-128 拒绝采样
s[i] = CBD_η1(PRF(σ, i)) i ∈ 0..K-1 // SHAKE-256
e[i] = CBD_η1(PRF(σ, K+i)) i ∈ 0..K-1
ŝ[i] = NTT(s[i]), ê[i] = NTT(e[i])
t̂[i] = ê[i] + Σⱼ Â[i][j] ∘ ŝ[j] // NTT 域逐点乘 + 累加
ek = byteEncode₁₂(t̂[0..K-1]) ‖ ρ
dk = byteEncode₁₂(ŝ[0..K-1]) ‖ ek ‖ H(ek) ‖ z
```
`mlkem_top` 把 ML-KEM 的全部运算分解为若干已独立验证的**叶子算子**,由顶层 FSM 串行驱动;所有多项式与字节产物存放在统一的 **BRAM 存储库**中,算子与存储之间以 valid/ready 握手串接。
### FSM 状态机
### 叶子算子
```
start_i
ST_IDLE ─────────────────► ST_G
▲ │ G(d‖K),捕获 ρ/σ
│ ▼
│ ST_A 生成 Â[i][j]K² 个多项式SampleNTT
│ │
│ ▼
│ ST_C 采样 s[i], e[i]2K 个多项式CBD
│ │
│ ▼
│ ST_N 原地前向 NTTŝ[i], ê[i]2K 次)
│ │
│ ▼
│ ST_M t̂[i] = ê[i] + Σⱼ Â[i][j]∘ŝ[j]
│ │
│ ▼
│ ST_E byteEncode₁₂ → ek_mem / dkp_memek 尾接 ρ
│ │
│ ▼
│ ST_H H(ek):多块 SHA3-256
│ done_o │
└────────── ST_DONE ◄───────┘
```
| 状态 | 名称 | 动作 | 使用的算子 |
|:---:|:---|:---|:---|
| ST_G | 哈希 G | `(ρ,σ)=G(d‖K)``data_i={K_byte, d}` | `sha3_top`SHA3-512 |
| ST_A | 矩阵采样 | 逐个生成 Â[i][j],行主序写入 slot `i*K+j`,每个 256 系数 | `sample_ntt_sync` |
| ST_C | CBD 采样 | s[0..K-1]nonce 0..K-1、e[0..K-1]nonce K..2K-1有符号→模 q | `sample_cbd_sync` |
| ST_N | 前向 NTT | 对 ŝ、ê 共 2K 个多项式逐个原地变换 | `ntt_core`mode=0 |
| ST_M | 矩阵乘累加 | 对每个 (i,j):流入 256 对 (Â,ŝ) 做逐点乘,累加进 t̂[i]j=0 时以 ê[i] 初始化) | `poly_mul_sync` |
| ST_E | 字节编码 | byteEncode₁₂t̂→ek_memŝ→dkp_mem末尾拷入 ρ 的 32 字节 | — |
| ST_H | 哈希 H | 对 ek 做多块 SHA3-256得到 H(ek)调用方预填充末块0x06…0x80 | `sha3_top`(多块模式) |
各状态之间以 valid/ready 握手串接FSM 拉高对应算子的 `valid_i`,在 `ready_o` 时认为请求被接收,再逐拍收集 `valid_o` 输出,直到 `last_o`/`done_o`
### 存储布局
所有多项式存于 `polymem``NUM_SLOTS×256` 个 12-bit 系数,`NUM_SLOTS = KMAX²+3·KMAX = 28`)。每个 slot 256 系数,槽基址在运行时由 `k_r` 推导:
```
slot 0 .. K²-1 : Â[i][j] (下标 i*K + j
slot_s_rt = K² : ŝ[i] ST_N 原地覆盖 s[i]
slot_e_rt = K² + K : ê[i]
slot_t_rt = K² + 2K : t̂[i]
```
字节输出存于 `ek_mem`KMAX 最大 1568B`dkp_mem`(最大 1536B。byteEncode₁₂ 规则:每 2 个系数打包成 3 字节LSB 优先 12-bit
```
b0 = c0[7:0]
b1 = {c1[3:0], c0[11:8]}
b2 = c1[11:4]
```
完整私钥 dk 的字节布局(与 NIST KAT 的 sk 对齐):
```
dk = dk_pke(384·K) ‖ ek(384·K+32) ‖ H(ek)(32) ‖ z(32)
```
H(ek) 阶段采用预填充多块吸收:调用方逐块组装 136 字节速率块,在 `ek_bytes` 位置填 `0x06`、末块最后字节或上 `0x80`;分块数 `h_nblk_rt` 为 6/9/12对应 K=2/3/4
## 测试流程
`mlkem_top` 的验证策略是:**对全部三种安全等级,把硬件产出的 `ek`/`dk` 与 NIST KAT 标准答案逐字节比对**。验证素材、参数化 TB、运行脚本三者配合完成。
### 1. 黄金向量NIST KAT
测试向量来自 NIST FIPS 203 的 KAT 响应文件,经 `sync_rtl/top/TB/gen_vectors.py` 解析后生成每个用例的独立 hex 文件,存于 `sync_rtl/top/TB/vectors/`
| 文件 | 内容 | 字节长度(按 K |
| 模块 | 功能 | 备注 |
|:---|:---|:---|
| `kat_k<K>_c<n>_d.hex` | KeyGen 种子 d | 32 |
| `kat_k<K>_c<n>_z.hex` | 隐式拒绝种子 z | 32 |
| `kat_k<K>_c<n>_ek.hex` | 期望公钥 pk== ek | 384·K+32 |
| `kat_k<K>_c<n>_dk.hex` | 期望私钥 sk== dk | 768·K+96 |
| `keccak_core` / `keccak_round` | Keccak-f[1600] 置换 | 单核共享给 G/H/J、SampleNTT、CBD |
| `sha3_top_shared` | SHA3-512 / SHA3-256 / SHAKE-256单块 + 多块吸收 | 哈希族封装 |
| `sample_ntt_sync_shared` | SampleNTT —— SHAKE-128 拒绝采样生成 Â | 共享 keccak |
| `sample_cbd_sync_shared` | CBD_η 中心二项分布采样s/e/y/e1/e2 | 共享 keccak |
| `ntt_core` | 前向 / 逆向 NTTmode 选择) | 蝶形 + ζ ROM |
| `poly_mul_sync` | NTT 域逐点乘basecase | 用于 MAC |
| `comp_decomp_sync` | Compress_d / Decompress_d | 封装/解封装压缩 |
| `sd_bram` | 1R/1W 寄存读 SRAM1 拍读延迟) | 全部存储库基元 |
其中 `K ∈ {2,3,4}``n` 为用例号。当前覆盖:**K=2 共 5 个用例c0c4K=3 / K=4 各 3 个用例c0c2**,合计 11 个用例
> **共享 Keccak**G/H/J、SampleNTT、CBD 三类消费者各自需要 Keccak 置换,但分处互斥的 FSM 阶段,因此共用一个 `keccak_core`,由相位多路选择输入、并对每个消费者门控输出有效信号
向量采用 “byte0 在低位” 约定256-bit 值满足 `bit[8m+:8] = byte m`
### 存储库
### 2. 参数化测试平台
多项式系数12-bit分布在三个 BRAM 库中,槽基址在运行时由 `k_r` 推导;字节产物存于另外几个 8-bit BRAM
`sync_rtl/top/TB/tb_mlkem_kg_katK_xsim.v` 是参数化自检 TB
| 存储库 | 宽×深 | 用途 |
|:---|:---|:---|
| `bank_a` | 12×4096 | Â[i][j] 矩阵KeyGen/EncapsDecaps 中转 ŝ |
| `bank_se` | 12×2048 | ŝ/êKeyGen、ŷEncaps、ûDecaps |
| `bank_t` | 12×1024 | t̂、累加结果、Encaps 的 v、Decaps 的 w |
| `ek_bram` | 8×2048 | ek公钥= byteEncode₁₂(t̂) ‖ ρ |
| `dkp_bram` | 8×2048 | dk_pke = byteEncode₁₂(ŝ) |
| `ct_bram` | 8×2048 | 计算出的密文 cEncaps/ c'Decaps 重加密) |
| `c_in_bram` | 8×2048 | Decaps 输入密文 c与 ct_bram 分开以便比较) |
- 通过 `parameter KP`(由 `xelab -generic_top KP=2|3|4` 设定)选择安全等级;
- 通过 `+CASE=n` plusarg 选择用例号,据此加载对应的 `kat_k<KP>_c<n>_*.hex`
- **将 `KP` 驱动到 DUT 的运行时输入 `k_i`**不再用参数覆盖KMAX 取默认 4
- 复位 → 加载 d/z 与 k_i → 拉 `start_i` 一拍 → 轮询 `done_o`(超时上限 200 万周期);
- 完成后通过 `dbg_byte_o`(读 ek0..EKB-1`dbg_dk_o`(读完整 dk0..DKB-1逐字节回读与黄金向量比对
- 全部相符打印 `K=<K> CASE <n> PASS`,否则打印前 8 个不匹配字节并报 `FAIL`
槽位布局相对基址K 个一组):`Â` 占 slot 0..K²-1`slot_s = K²``slot_e = K²+K``slot_t = K²+2K`
### 3. 运行测试
### 顶层 FSM
测试经由统一脚本 `run_tb.sh` 分发(自动 source Vivado 环境并设置 `LD_PRELOAD`
一个 5-bit 状态机三条数据通路KeyGen / Encaps / Decaps`ST_IDLE``op_r` 分支
```
┌─ op=0 KeyGen ─► G → A → C → N → M → E → H ──────────────────────┐
ST_IDLE ─start─►├─ op=1 Encaps ─► ENC_H → ENC_G → ENC_LOAD → ENC_A → ENC_TDEC │
│ → ENC_C → ENC_N → ENC_U → ENC_C1 → ENC_E2MV ├─► ST_DONE
│ → ENC_V → ENC_C2 ───────────────────────────────┤
└─ op=2 Decaps ─► DEC_LOAD → DEC_DECOMP → DEC_SDEC → DEC_NTT │
→ DEC_W → DEC_MENC → DEC_G → DEC_J │
→(复用 Encaps 重加密 ENC_LOAD..ENC_C2→ DEC_CMP ┘
```
#### KeyGenFIPS 203 算法 16
```
(ρ,σ) = G(d‖K) ST_G SHA3-512
Â[i][j] = SampleNTT(ρ‖j‖i) ST_A SHAKE-128
s[i]=CBD_η1(PRF(σ,i)), e[i]=CBD_η1(PRF(σ,K+i)) ST_C SHAKE-256
ŝ[i]=NTT(s[i]), ê[i]=NTT(e[i]) ST_N
t̂[i]=ê[i]+Σⱼ Â[i][j]∘ŝ[j] ST_M 逐点乘+累加
ek = byteEncode₁₂(t̂)‖ρ, dk_pke=byteEncode₁₂(ŝ) ST_E
H(ek) ST_H 多块 SHA3-256
dk = dk_pke ‖ ek ‖ H(ek) ‖ z
```
#### EncapsFIPS 203 算法 17
```
H(ek), (K,r)=G(m‖H(ek)) ENC_H, ENC_G SHA3-256 / SHA3-512
ρ 载入, Â 重生成, t̂=byteDecode₁₂(ek) ENC_LOAD/A/TDEC
y,e1,e2 = CBD 采样(PRF(r,·)), ŷ=NTT(y) ENC_C, ENC_N
u = INTT(Σ Âᵀ∘ŷ) + e1 ENC_U
c1 = byteEncode_du(Compress_du(u)) ENC_C1
v = INTT(Σ t̂∘ŷ) + e2 + Decompress₁(m) ENC_V
c2 = byteEncode_dv(Compress_dv(v)) ENC_C2
c = c1‖c2, 共享密钥 = K
```
#### DecapsFIPS 203 算法 18含 FO 变换与隐式拒绝)
```
u'=Decompress_du(byteDecode(c1)), v'=Decompress_dv(byteDecode(c2)) DEC_DECOMP D1
ŝ = byteDecode₁₂(dk_pke), û[i]=NTT(u'[i]) DEC_SDEC/NTT D2
w = v' - INTT(Σⱼ ŝ[j]∘û[j]) DEC_W D3
m' = byteEncode₁(Compress₁(w)) DEC_MENC D4
(K',r')=G(m'‖h), K̄=J(z‖c) DEC_G/DEC_J D5
c' = K-PKE.Encrypt(ek_pke, m', r') ←── 复用整条 Encaps 流水 (重加密) D6
共享密钥 = (c'==c) ? K' : K̄ ←── 逐字节比较 + 隐式拒绝 DEC_CMP D7
```
> **设计亮点**Decaps 的重加密D6**直接复用整条 Encaps 流水线**ENC_LOAD..ENC_C2不另起数据通路——m' 写入 `m_r`、r' 在 `r_r`、ek_pke 留在 `ek_bram`前置条件天然就位。D7 逐字节比较 c'`ct_bram`)与 c`c_in_bram`),恒定工作量(无早退),按结果在 K'`ss_r`)与 K̄`kbar_r`)间选择 `ss_o`。
---
## 四、测试与验证
验证策略:**对全部三种安全等级,把硬件产出与 NIST KAT 标准答案逐字节比对**分阶段D0..D7 / E1..E7验证中间量再做端到端协议测试。
### 黄金向量
测试向量来自 NIST FIPS 203 的 KAT 响应文件,由 `sync_rtl/top/TB/` 下的 Python 脚本解析为每用例独立的 hex 文件,存于 `sync_rtl/top/TB/vectors/`。Decaps 还包含 `ct_n`/`ss_n`(损坏密文 + 对应拒绝密钥),用于验证隐式拒绝路径。
### 参数化测试平台
| 测试平台 | 操作 | 说明 |
|:---|:---|:---|
| `tb_mlkem_kg_katK_xsim.v` | KeyGen | 逐字节核对 `ek==pk``dk==sk` |
| `tb_mlkem_enc_katK_xsim.v` | Encaps | 核对 `ss``c` |
| `tb_mlkem_dec_katK_xsim.v` | Decaps | 核对 D0..D7 各阶段;正常密文 `ss==KAT.ss`、损坏密文 `ss==KAT.ss_n` |
| `tb_mlkem_hello_world_xsim.v` | 全流程(单实例) | 复现 `hello_world.rs`KeyGen→Encaps→XOR→Decaps→XOR |
| `tb_mlkem_two_inst_xsim.v` | 全流程(双实例) | 实例 A 做 KeyGen+Encaps实例 B 做 Decaps |
每个参数化 TB 通过 `xelab -generic_top KP=2|3|4` 选安全等级(驱动到运行时 `k_i``+CASE=n` 选用例号。
### 运行测试
统一脚本 `run_tb.sh`(自动 source Vivado 环境并设置 `LD_PRELOAD`
```bash
./run_tb.sh top
./run_tb.sh top # KeyGen全部 K 全部用例
./run_tb.sh enc # Encaps
./run_tb.sh dec # Decaps含拒绝路径
./run_tb.sh dec 2 0 # 仅 K=2 用例 0
./run_hello.sh # hello_world 端到端(单实例)
./run_hello.sh two # hello_world 端到端双实例genenc + dec
```
该命令执行 `sync_rtl/top/TB/xsim_run.tcl`,其流程为:
1. **编译**`xvlog`):全部叶子算子 RTL + `mlkem_top.v` + 参数化 TB
2. **细化**`xelab`):为每种 K 生成一个快照 —— `KP=2→mlkem_kg_k2``KP=3→mlkem_kg_k3``KP=4→mlkem_kg_k4`
3. **仿真**`xsim -R -testplusarg CASE=n`):依次跑完每种 K 的全部用例。
整体测试矩阵:
```
K=2 (ML-KEM-512) : CASE 0,1,2,3,4 → ek 800B, dk 1632B
K=3 (ML-KEM-768) : CASE 0,1,2 → ek 1184B, dk 2400B
K=4 (ML-KEM-1024): CASE 0,1,2 → ek 1568B, dk 3168B
```
### 4. 预期结果
11 个用例全部 PASS每个用例确认 `ek == pk``dk == sk` 逐字节相等:
```
=== ML-KEM K=2 KAT case 0: KeyGen done in 21403 cyc ===
K=2 CASE 0 PASS: ek (800B)==pk, dk (1632B)==sk
...
K=4 CASE 2 PASS: ek (1568B)==pk, dk (3168B)==sk
```
`hello_world` 硬件输出与 Rust 参考逐字节一致:`shared_key=ced0c031a4bee34a...``encrypted=a6b5ac5dcb9e9425b9e3b8``decrypted="hello world"`
### 验证注意事项
- **干净重跑**:每轮仿真前清理 `xsim.dir``.Xil`,避免旧快照污染`rm -rf xsim.dir .Xil`
- **干净重跑**:每轮仿真前清理 `xsim.dir``.Xil`,避免旧快照污染。
- **`$readmemh` 缺文件只是 WARNING**:文件名拼错时数据读为 X不会报错极易造成假 PASS。务必确认日志中无 `cannot be opened` 警告。
- **以日志文件为准**:将每个 `xsim` 调用重定向到独立日志后再 grep `PASS|FAIL|cannot be opened`,不要只看终端滚屏的模糊输出
- **以日志文件为准**:将每个 `xsim` 调用重定向到独立日志后再 grep `PASS|FAIL|cannot be opened`
## 手动 XSIM 命令
---
## 五、先决条件与兼容性
- **Vivado 2019.2+**XSIM 仿真):`/opt/Xilinx/Vivado/2019.2/`
- **Python 3.10+**(向量生成,仅标准库)
Vivado 2019.2 在 Fedora 上经实测的必要 workaround
```bash
source /opt/Xilinx/Vivado/2019.2/settings64.sh
export LD_PRELOAD=/usr/lib64/libtinfo.so.5 # Vivado 2019.2 的 ncurses 兼容修复
rm -rf xsim.dir .Xil
# 1) 编译(叶子算子 + 顶层 + TB
xvlog -sv --relax -i . \
sync_rtl/sha3/keccak_round.v sync_rtl/sha3/keccak_core.v sync_rtl/sha3/sha3_top.v \
sync_rtl/sample_ntt/sample_ntt_sync.v sync_rtl/sample_cbd/sample_cbd_sync.v \
sync_rtl/ntt/barrett_mul.v sync_rtl/ntt/zeta_rom.v sync_rtl/ntt/butterfly_unit.v sync_rtl/ntt/ntt_core.v \
sync_rtl/poly_mul/basecase_mul.v sync_rtl/poly_mul/poly_mul_zeta_rom.v sync_rtl/poly_mul/poly_mul_sync.v \
sync_rtl/top/mlkem_top.v \
sync_rtl/top/TB/tb_mlkem_kg_katK_xsim.v
# 2) 细化某一种 K
xelab tb_mlkem_kg_katK_xsim -generic_top KP=4 -s mlkem_kg_k4 --timescale 1ns/1ps
# 3) 跑某个用例
xsim mlkem_kg_k4 -R -testplusarg CASE=0
```
## 先决条件
- **Vivado 2019.2+**XSIM 仿真):`/opt/Xilinx/Vivado/2019.2/`
- **Verilator 5.046**(底层算子 C++ 验证)
- **Python 3.10+**(向量生成,仅用标准库)
## Vivado 2019.2 兼容性说明
在 Fedora 44 上经实测的必要 workaround
```bash
export LD_PRELOAD=/usr/lib64/libtinfo.so.5 # 必需ncurses 兼容库
xvlog -sv --relax -i . <file>.v # 用 -i非 -include_dirs指定包含目录;--relax 放宽严格 SV 检查
xvlog -sv --relax -i . <file>.v # -i 指定包含目录;--relax 放宽严格 SV 检查
xelab <top> -s <snap> --timescale 1ns/1ps # xelab 需显式 --timescale
```
---
## 参考
- [FIPS 203: ML-KEM](https://csrc.nist.gov/pubs/fips/203/final) —— NIST 标准(算法 16 KeyGen_internal
- [FIPS 203: ML-KEM](https://csrc.nist.gov/pubs/fips/203/final) —— NIST 标准(算法 16/17/18
- [FIPS 202: SHA-3 / SHAKE](https://csrc.nist.gov/pubs/fips/202/final) —— Keccak 哈希族
- [CRYSTALS-Kyber](https://pq-crystals.org/kyber/) —— 原始提案