# ML-KEM 硬件实现（FIPS 203）

基于 SystemVerilog 的 **ML-KEM**（Module-Lattice-based Key Encapsulation Mechanism，NIST 后量子密码标准，源自 Kyber）同步流水线硬件实现。面向 FPGA，使用 Vivado XSIM 与 Verilator 进行仿真验证。

本文档重点介绍顶层密钥生成模块 **`mlkem_top`** 的工作流程及其测试流程。各底层算子（SHA-3、NTT、CBD 采样等）已独立验证，本文不再展开。

## 概述

ML-KEM 是 NIST 在 FIPS 203 中标准化的后量子密钥封装机制，其安全性基于多项式环 Z_q[x]/(x²⁵⁶+1) 上的 Module-LWE 难题。

`mlkem_top` 实现 **FIPS 203 算法 16（KeyGen_internal）** 的完整密钥生成数据通路：给定种子 `d` 与 `z`，输出封装密钥 `ek`（公钥）和解封装密钥 `dk`（私钥）。模块运行于 **100 MHz**（10 ns 周期），低电平复位。

### 运行时参数选择

**ML-KEM 的安全等级 K 在运行时通过输入端口 `k_i` 选择**，而非编译期参数。存储按最坏情况（ML-KEM-1024，KMAX=4）静态分配，`k_i` 在 `start_i` 时被采样到内部寄存器 `k_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` 仅在 `start_i` 时采样，且假定取值 ∈ {2,3,4}；越界值（0/1/5–7）当前不做保护，会产生错误的尺寸计算。

固定参数：**q = 3329**（素数模）、**n = 256**（多项式次数）。du/dv 仅用于封装/解封装，KeyGen 不涉及。

## `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 种子 d（byte0 在 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
);
```

`busy_o`/`done_o` 提供握手；`dbg_*` 端口为只读调试抽头，让 TB 可以逐系数 / 逐字节读出中间结果与最终的 ek/dk，而无需引出整条数据总线。

## 工作流程

`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
```

### 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_mem，ek 尾接 ρ
   │                          │
   │                          ▼
   │                        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 |

其中 `K ∈ {2,3,4}`、`n` 为用例号。当前覆盖：**K=2 共 5 个用例（c0–c4），K=3 / K=4 各 3 个用例（c0–c2）**，合计 11 个用例。

向量采用 “byte0 在低位” 约定：256-bit 值满足 `bit[8m+:8] = byte m`。

### 2. 参数化测试平台

`sync_rtl/top/TB/tb_mlkem_kg_katK_xsim.v` 是参数化自检 TB：

- 通过 `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`（读 ek，0..EKB-1）与 `dbg_dk_o`（读完整 dk，0..DKB-1）逐字节回读，与黄金向量比对；
- 全部相符打印 `K=<K> CASE <n> PASS`，否则打印前 8 个不匹配字节并报 `FAIL`。

### 3. 运行测试

测试经由统一脚本 `run_tb.sh` 分发（自动 source Vivado 环境并设置 `LD_PRELOAD`）：

```bash
./run_tb.sh top
```

该命令执行 `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
```

### 验证注意事项

- **干净重跑**：每轮仿真前清理 `xsim.dir`、`.Xil`，避免旧快照污染（`rm -rf xsim.dir .Xil`）。
- **`$readmemh` 缺文件只是 WARNING**：文件名拼错时数据读为 X，不会报错，极易造成假 PASS。务必确认日志中无 `cannot be opened` 警告。
- **以日志文件为准**：将每个 `xsim` 调用重定向到独立日志后再 grep `PASS|FAIL|cannot be opened`，不要只看终端滚屏的模糊输出。

## 手动 XSIM 命令

```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 检查
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 202: SHA-3 / SHAKE](https://csrc.nist.gov/pubs/fips/202/final) —— Keccak 哈希族
- [CRYSTALS-Kyber](https://pq-crystals.org/kyber/) —— 原始提案


