chore(tb): remove Verilator TBs + framework; parallelize XSIM runs

Verilator is no longer used (all verification is via Vivado XSIM). Remove:
- 10 per-module tb_*.cpp Verilator testbenches
- the entire test_framework/ Verilator harness (lib/, run_all.py, config.json,
  per-module test_plan.json/gen_vectors.py, golden vectors, reports)
- stale specs: verilator-conventions.md, test_framework/structure.md
  (index.md updated to drop the Verilator entry)

Parallelize run_tb.sh K x case execution (modules stay serial):
- new run_xsim_jobs helper: compile+elaborate once (serial, populates the
  shared xsim.dir), then run each (K,case) xsim in its own private workdir
  with a COPY of xsim.dir (~1MB) so concurrent same-snapshot runs don't clobber
  each other's runtime logs. Each workdir symlinks the repo sync_rtl tree so
  the TB's repo-relative $readmemh vector paths resolve.
- top/enc/dec runners refactored to build a (snapshot:K:case) spec list and
  hand it to run_xsim_jobs; ordered PASS/FAIL summary + per-job /tmp logs
  preserved. Bare './run_tb.sh top' now also takes the parallel path.

Speedup (20 cores): top full sweep 2:11 -> 0:51 (~2.6x), ~320% CPU.
Verified: top (11) / enc (9) / dec (9) all PASS; missing-vector runs still
fail (file-not-found guard -> exit 1).
This commit is contained in:
2026-06-29 16:05:06 +08:00
parent 030931d4e5
commit e46d2258d9
135 changed files with 347 additions and 22072 deletions

View File

@@ -0,0 +1,80 @@
# 计划:将 mlkem_top 的大数据存储迁移到 BRAM
## 目标
把当前用裸 `reg` 数组实现的三块大存储改为可被 Vivado 推断为 block RAM 的结构,
使设计面向综合时不再把 86 kbit 的多项式存储映射成 LUT/FF。
涉及存储:
| 当前 reg 数组 | 容量(KMAX=4) | 用途 |
|---|---|---|
| `polymem [0:28*256-1]` ×12bit | 86 016 bit | 全部多项式 Â/ŝ/ê/t̂ |
| `ek_mem [0:1567]` ×8bit | 12 544 bit | byteEncode 后 ek |
| `dkp_mem [0:1535]` ×8bit | 12 288 bit | byteEncode 后 dk_pke |
复用现有 `sync_rtl/storage/sd_bram.v`1 读 + 1 写读地址寄存→1 周期读延迟,
W≥12 & D≥64 时 Vivado 自动推断 BRAM
## 核心约束(已从代码确认)
1. **BRAM 读有 1 周期延迟**reg 数组是组合读)。所有读出点都要改成“地址提前一拍”。
2. **BRAM 每端口每周期 ≤1 访问**。当前 `polymem` 在同周期最多有 2 个不同读地址
ST_M LOAD 同时读 A 和 ŝ),且 ST_E 同周期读同一 poly 的 2 个系数。
3. **ST_E 当前每周期写 3 字节**到 ek/dk —— BRAM 单写口做不到。
## 关键结论(决定方案可行性)
- `poly_mul_sync` 在 LOAD 阶段把 256 对系数**全部存进自己的 mem_A/mem_B**,再在 COMP
阶段输出。所以 ST_M 的 LOAD读 A+ŝ)与 ACCUMULATE读 ê/t̂、写 t̂**绝不同周期**。
`polymem` 按区域分 4 个 bank {A, S, E, T} 后,每个 bank 任意周期 ≤1 读 + 1 写,
`sd_bram` 即可满足,无需真双口。
- TB 每次调试回读等 **2 个周期**`@(posedge clk); @(posedge clk);`),可吸收 BRAM 多出的
1 拍读延迟,回读路径无需改 TB。
## polymem 4-bank 划分
| bank | slot 数 | 深度(×256) | 写于 | 读于 |
|---|---|---|---|---|
| A (Â[i][j]) | KMAX²=16 | 4096 | ST_A | ST_M LOAD |
| S (ŝ[i]) | KMAX=4 | 1024 | ST_C / ST_N | ST_N LOAD, ST_M LOAD, ST_E |
| E (ê[i]) | KMAX=4 | 1024 | ST_C / ST_N | ST_N LOAD, ST_M ACC(j=0) |
| T (t̂[i]) | KMAX=4 | 1024 | ST_M ACC | ST_M ACC(j>0), ST_E |
(总深度 7168 == 当前 28×256容量不变
每个 bank 一个 `sd_bram #(.W(12))`。读地址由一个 per-bank mux 选择:计算期由 FSM 计数器驱动,
ST_DONE/回读期由 `dbg_*` 驱动;`dbg_coeff_o` 改为按 slot 选 4 个 bank 的 rd_data。
## 实施分两阶段,每阶段后跑全部 11 个 KAT 用例
### 阶段 1ek_mem / dkp_mem → BRAM风险较低先做
1. 例化 `ek_bram``dkp_bram`sd_bramW=8
2. **重写 ST_E 为每周期写 1 字节**3 字节拆 3 拍):新增子计数器 b∈{0,1,2}
按 b 选 e_b0/e_b1/e_b2 与目标地址 e_boff+b。ST_E 周期数约 ×3K=4 增 ~2K 周期,
占总量 ~4%可接受。rho 拷贝阶段本就 1 字节/周期,不变。
3. **ST_H 的 h_padbyte 读 ek 改为地址提前一拍**assemble 子状态先发地址、下一拍取数据
写入 h_block_rh_byte 流水线 +1 级)。
4. **回读路径**dbg_byte_o / dbg_dk_o改为直接用 bram 的 rd_data去掉多余的中间寄存器
保持总延迟 ≤2 拍,落在 TB 的 2 周期等待内)。
5.`./run_tb.sh top` 全 11 例,要求 0 个 `cannot be opened`、全 PASS。
### 阶段 2polymem → 4 bank BRAM风险较高
1. 例化 4 个 `sd_bram #(.W(12))`bank_a(D=4096)、bank_s/e/t(D=1024)。
2. 写口改写ST_A→bank_aST_C→bank_s/eST_N→bank_s/eST_M ACC→bank_t。
3. 读口加“地址提前一拍”流水:
- ST_N LOAD读地址 = 下一个 n_ridx数据对齐 ntt_core 的 valid_i。
- ST_M LOAD读地址 = 下一个 m_ld数据对齐 poly_mul 的 pm_valid。
- ST_M ACCm_acc_src 读地址跟踪即将到来的 m_oidx提前一拍对齐 pm_vo。
- ST_E每周期读 1 个系数e_c0、e_c1 拆两拍),配合阶段 1 的 1 字节/周期写。
4. dbg_coeff_o4 bank rd_data 按 dbg_slot 区间选择。
5. 跑全 11 例验证。
## 验证方法(每阶段)
- 干净重跑:`rm -rf xsim.dir .Xil``./run_tb.sh top`
- 检查日志:`grep -c 'cannot be opened'`(=0) 且 11/11 `PASS`
- 记录周期数变化(预期 ek/dk BRAM 化使 ST_E 变长、polymem 读延迟引入少量气泡)。
- 阶段 2 完成后,可选:跑 `vivado -mode batch` 综合,确认 polymem 现在落在 RAMB 而非 LUTRAM。
## 不在本次范围
- 是否把 ek/dk 暴露为对外输出端口(上一条被打断的请求)—— 独立问题,本次只改存储实现,
保持现有 dbg_* 接口不变。如需对外输出,迁移完成后另议。
## 风险
- 读延迟流水改造触及所有 5 个计算阶段的握手时序,是 KAT 回归的主要风险点。
- 缓解:分两阶段、每阶段独立 KAT 回归;先做低风险的 ek/dk再做 polymem。
- 若某阶段无法在合理尝试内通过 KAT回退该阶段并报告根因不带病提交。

View File

@@ -0,0 +1,175 @@
# ML-KEM KeyGen 顶层集成 — 实现计划与进度
> 本文档用于让新工程师完整接手。包含原始计划 + 已完成进度 + 经验教训 + 下一步。
## 目标与范围
- 目标:实现 `mlkem_top`**KeyGen 通路**(ML-KEM-512, K=2, η1=3),全新重写 valid/ready FSM。
- 最终验收:对 **NIST 官方 KAT**(count=0..4)端到端逐字节通过,产出完整 ML-KEM dk。
- 本计划只做 KeyGen。Encaps/Decaps 留作后续任务。
- 旧版 `mlkem_top.v`(1658 行)已于 commit 1cace51 删除,**不可用**(Decaps 是占位,且子模块握手从未真正跑通——旧 TB 用 24 处 force/release 绕过)。新版全新重写。
## 关键决策(已与用户确认)
- **Keccak 策略**:每消费者用独立、已验证的核(sha3_top / sample_ntt_sync / sample_cbd_sync 各自带 keccak_core)。**不用 arbiter,不用 `_shared` 变体**(顶层当前用独立版)。
- **SHA3 先扩多块**:已先把 sha3_top 的 H 模式扩成多块吸收(✅ 完成),再做完整 dk。
- **FSM 全新重写**:只参考算法步骤,不取回旧代码。
- **分阶段 + 逐级对拍**:每个子步骤对 golden 验证通过再进下一步。
## 环境与工具(重要)
- 工作目录:`/home/fallensigh/Dev/mlkem`,git 分支 `main`(直接提交,用户要求)。
- **Verilator** 5.046:`python3 test_framework/run_all.py [--module X]`。快速回归。
- **Vivado XSIM** 2019.2:每次需
```
source /opt/Xilinx/Vivado/2019.2/settings64.sh
export LD_PRELOAD=/usr/lib64/libtinfo.so.5
```
调用 **standalone** `xvlog -sv --relax -i . <files>` / `xelab <top> -s <snap> --timescale 1ns/1ps` / `xsim <snap> -R`。**不要**用 `vivado -mode batch`(脚本里用的是 standalone 命令,不是 Tcl)。zsh 不做无引号变量分词——用 `"$@"` 传文件列表。
- XSIM 产物(xsim.dir/ .Xil/ *.jou *.log)已 gitignore;每次跑前 `rm -rf xsim.dir .Xil`。
## Golden reference(全部已验证可用)
- **NIST 官方 KAT**:`/home/fallensigh/Dev/ml-kem-r/test_data/kat_MLKEM_{512,768,1024}.rsp`。
- **ml-kem-r**(Rust, `/home/fallensigh/Dev/ml-kem-r`):`cargo test` 全过,512/768/1024 KAT 全通过。是可信参考。
- API:`ml_kem::mlkem512::key_gen(d,z)`、`keygen_internal::<2,192>(d,z)`、`k_pke::key_gen::<2,192>(d)`。
- 公共子函数:`sample::sample_ntt(&[u8])`、`sample::sample_poly_cbd::<ETA_BYTES>`、`ntt::ntt(&mut [i32;256])`、`ntt::multiply_ntts`、`sha3::mlkem_G/mlkem_H/mlkem_PRF`、`utils::byte_encode(&[i32;256], d)`。
- `logging` feature + `examples/logging.rs` dump 中间值(4-coeff head)。
- **server_code**(Python, `~/Dev/server_code/python_project/PQC_2025/A_ML_KEM_v0`):`SHA_3.G/H/J/PRF`、`ML_KEM.py`。已对 hashlib 交叉验证。
### 白盒 golden 向量(Stage 0 产物,已生成并自校验)
- 生成器:`/home/fallensigh/Dev/ml-kem-r/examples/dump_keygen.rs`(我新增,未提交到 ml-kem-r,因为该仓库有用户大量未提交改动 —— 只在工作树用)。
- 运行:`cargo run --release --example dump_keygen -- <out_dir> 5`
- 输出目录:`test_framework/modules/mlkem_keygen/golden/`(已 commit 在 mlkem 仓库 106b292)。
- 每个 KAT case c(000..004)的全 256-coeff 中间量,文件名:
- `c000_rho.hex` / `c000_sigma.hex`:32B hex(byte0 在最低)。
- `c000_Ahat_i_j.hex`(i,j∈0..1):256 行,每行 3-hex 系数(mod q)。
- `c000_s_i.hex` / `c000_e_i.hex`:CBD 输出,**mod-q 形式**(负值 +Q,如 -1 → 0xd00=3328)。
- `c000_shat_i.hex` / `c000_ehat_i.hex`:NTT(s/e) 后。
- `c000_that_i.hex`:t_hat。
- `c000_ek.hex`(800B hex = KAT pk)、`c000_dkpke.hex`(768B = KAT sk 前 384*K)。
- dump_keygen 已自校验 ek==KAT.pk、dk_pke==KAT.sk[..768],全 5 case 通过。
## KeyGen 算法 (FIPS 203 Alg 13+16, K=2 η1=3)
1. (ρ,σ) = G(d ‖ K) — SHA3-512, 33B 输入(单块)。**G 输入 = d(byte0 低) ‖ K=2 at byte32,无字节翻转**(已确认,旧版 byte-reverse 是 bug)。ρ = hash[255:0],σ = hash[511:256]。
2. Â[i][j] = SampleNTT(ρ‖j‖i), i,j∈{0,1} — seed = rho ‖ j ‖ i(sample_ntt_sync 内部 msg={i_byte,j_byte,rho})。
3. s[i] = CBD₃(PRF(σ, **i**)), i∈{0,1} — nonce 0,1
4. e[i] = CBD₃(PRF(σ, **K+i**)), i∈{0,1} — nonce 2,3
5. ŝ[i] = NTT(s[i]); ê[i] = NTT(e[i]) — 前向 NTT ×4 (mode=0, **无缩放**)
6. t̂[i] = ê[i] + Σⱼ Â[i][j]∘ŝ[j] — poly_mul + 逐系数 modQ 累加
7. ek = byteEncode₁₂(t̂[0..1]) ‖ ρ — 12-bit 原始打包(**非压缩**)
8. dk_pke = byteEncode₁₂(ŝ[0..1])
9. dk = dk_pke ‖ ek ‖ H(ek) ‖ z — H(ek): SHA3-256 over 800B(**多块**)
**用到的叶子模块**(端口签名见下):sha3_top(G+多块H)、sample_ntt_sync、sample_cbd_sync、ntt_core(mode=0)、poly_mul_sync、mod_add_sync。**不用** comp_decomp。
## 叶子模块端口签名(已抓取)
- `rng_sync #(SEED)`: clk,rst_n,valid_i,ready_o,data_o[255:0],valid_o,ready_i
- `sha3_top`: clk,rst_n,mode[1:0],data_i[511:0],valid_i,ready_o,hash_o[511:0],valid_o,ready_i, **+多块端口** mb_en,mb_block_i[1087:0],mb_valid_i,mb_last_i,mb_ready_o
- `sample_ntt_sync #(K=4)`: clk,rst_n,rho_i[255:0],k_i[2:0],i_idx[1:0],j_idx[1:0],valid_i,ready_o,coeff_o[11:0],valid_o,ready_i,last_o
- `sample_cbd_sync`: clk,rst_n,seed_i[255:0],nonce_i[7:0],eta_i[1:0],valid_i,ready_o,coeff_o[11:0](**12-bit signed 二补码**),valid_o,ready_i,last_o
- `ntt_core`: clk,rst_n,coeff_in[11:0],valid_i,ready_o,mode(0=fwd无缩放/1=inv有缩放),coeff_out[11:0],valid_o,ready_i,done_o。协议:IDLE/LOAD 期间 ready_o 高,流式喂 256 个(valid_i 持续);算完 S_OUTPUT 流式吐 256(valid_o 高,ready_i 消费);S_DONE→IDLE。
- `poly_mul_sync`: clk,rst_n,coeff_a_in[11:0],coeff_b_in[11:0],valid_i,ready_o,coeff_out[11:0],valid_o,ready_i。协议:LOAD 阶段 **同时**喂 A、B 各 256 对(coeff_a_in/coeff_b_in 同拍),然后 COMPUTE 流式吐 256。
- `mod_add_sync`: clk,rst_n,a[11:0],b[11:0],valid_i,ready_o,sum[11:0],valid_o,ready_i。(a+b)mod Q。
- `comp_decomp_sync`: clk,rst_n,coeff_in[11:0],d[4:0],mode,valid_i,ready_o,coeff_out[11:0],valid_o,ready_i(KeyGen 不用)。
- `sd_bram #(W=48,D=64,A=6)`、`s_bram #(W=48,D=512,A=9)`(KeyGen 暂用寄存器阵列 polymem 缓存,未用 bram)。
## 已完成进度
### ✅ Stage 0:白盒 golden 向量生成器(commit 106b292)
- `examples/dump_keygen.rs`(ml-kem-r 工作树)+ `test_framework/modules/mlkem_keygen/golden/`(mlkem 仓库)。
- 自校验:ek==KAT.pk、dk_pke==KAT.sk 前缀,5/5 通过。
### ✅ Stage 1:sha3_top 多块 SHA3-256 吸收(commit 106b292)
- `sync_rtl/sha3/sha3_top.v`:新增多块吸收 FSM。新端口 `mb_en/mb_block_i[1087:0]/mb_valid_i/mb_last_i/mb_ready_o`。
- **设计**:调用方预填充末块的 SHA3-256 padding(0x06 ... 0x80),模块只做纯吸收 loop(state^=block; Keccak-p)。单块 G/H/J 路径在 mb_en=0 时逐 bit 不变。
- 状态:MB_IDLE/MB_PERMUTE/MB_DONE。`mb_state_r`(1600-bit running state),`mb_digest_r`(256-bit sticky digest,保持到消费者 ack)。
- `ready_o = !mb_en && (state_r==ST_IDLE)`、`valid_o`/`hash_o` 按 mb_en 选择。
- TB:`sync_rtl/sha3/TB/tb_sha3_mb_xsim.v`(自检,流 800B ek=6 块 → H(ek),对 hashlib.sha3_256)。向量:`vectors/mb_h_blocks.hex`、`mb_h_expected.hex`。
- **握手坑(已解决)**:TB 必须在 accept 边沿保持 valid/last 稳定(等 mb_ready_o 拉低后才撤),否则采样竞争。digest 一次有效后 sticky,TB 收完才 ack。
- 既有 G/H/J TB(xsim 3 个 + Verilator tb_sha3.cpp)**必须 tie off mb_* 端口**(mb_en=1'b0 等),否则 mb_en 浮空成 x 破坏 ready_o。已全部修。
- 回归:Verilator 25/25,XSIM 4 个 TB(simple G/H/J、keccak_core、7-vec、multiblock)全过。
### ✅ sample_ntt 幽灵脉冲修复(commit 6db3c7c)
- **bug**:`sample_ntt_sync` 的 Phase-1(输出 d1)缺少 Phase-2 有的 `need_more` 守卫。当第 256 个被接受系数是 d1 且组推进时,某些 seed 会在 last_o 后多吐一拍 valid_o(KAT count=0 rho 下,seed i=0/j=1 吐 257 拍)。集成时漏进下个 poly 的 index 0,整流后移一位。
- **修复**:Phase-1 改 `if (d1_acc_r && need_more)`。两变体(`sample_ntt_sync.v` + `sample_ntt_sync_shared.v`)都改。
- **补盲区**:`tb_sample_ntt_xsim.v` 加断言——last_o 后保持 ready_i 数多余 valid_o,有则 FAIL。已验证该断言能抓 bug(还原副本 3 个幽灵)、修复版通过。
- 验证:40-seed(sync)+24-seed(shared)审计全 256 拍/last@256/零幽灵;Verilator 1536/1536;全框架 4334/4334;Stage 2c 2048/2048。
- 记忆:`sample-ntt-257th-pulse-bug.md`。
### ✅ Stage 2a/2b/2c:KeyGen 数据通路前半(**未提交**,在工作树)
- 文件:`sync_rtl/top/mlkem_top.v`(新建)+ TB `sync_rtl/top/TB/tb_mlkem_kg_2a_xsim.v`、`tb_mlkem_kg_2c_xsim.v`、向量 `sync_rtl/top/TB/vectors/kg_c000_AsE.hex`。
- **mlkem_top 结构**(K=2,ETA1=3):
- 端口:clk,rst_n,d_i[255:0],z_i[255:0],start_i,busy_o,done_o + **调试 tap**:dbg_slot_i[3:0],dbg_idx_i[7:0],dbg_coeff_o[11:0](寄存器读 polymem,2拍延迟),dbg_rho_o[255:0],dbg_sigma_o[255:0]。
- **polymem**:`reg [11:0] polymem[0:10*256-1]`,slot 映射:0-3=A00,A01,A10,A11;4-5=S0,S1;6-7=E0,E1;8-9=T0,T1。s/s_hat 共享 slot(就地 NTT),e 同理。
- FSM 状态:ST_IDLE=0,ST_G=1,ST_A=2,ST_C=3,...,ST_DONE=15。
- **2a(G)**:sha3_top mode=00,g_data={248'b0,8'(K),d_i};完成捕获 rho_r/sigma_r。✅ rho/sigma 对 golden 精确。
- **2b(A)**:a_pair 0..3 循环,a_i=pair[1],a_j=pair[0],a_slot=pair。每 pair 跑 sample_ntt_sync,256 系数写 polymem[a_slot]。✅ 1024 系数全对。
- **2c(C)**:c_poly 0..3(s0,s1,e0,e1),c_nonce=c_poly,c_slot 映射。CBD 输出经 `cbd_modq = cbd_coeff[11] ? cbd_coeff+Q : cbd_coeff` 转 mod-q 后写 polymem。✅ 2048/2048(A+s+e)全对。
- **关键防护 a_busy/c_busy**:请求被接受(valid&&ready)时置位,last 时清零;仅 busy 时收集系数。本是为屏蔽 sample_ntt 幽灵脉冲的下游兜底;sample_ntt 根因修复后冗余但保留(无害,且对未来叶子模块是防御)。
- **字节序基准**(全部已确认,这是旧版的痛点):
- G 输入 d 原序(byte0 低),无翻转。
- SampleNTT seed=rho‖j‖i。
- CBD nonce:s[i]=i,e[i]=K+i。
- golden 多项式存 mod-q;CBD RTL 输出 12-bit 二补码,需 +Q 转换。
## 当前工作树状态(未提交)
- `sync_rtl/top/mlkem_top.v`(新,Stage 2a/b/c,FSM 到 ST_C)
- `sync_rtl/top/TB/tb_mlkem_kg_2a_xsim.v`、`tb_mlkem_kg_2c_xsim.v`(新)
- `sync_rtl/top/TB/vectors/kg_c000_AsE.hex`(新,8 poly × 256 = 2048 行,顺序 A00,A01,A10,A11,S0,S1,E0,E1)
- `.claude/plans/`(本文件)
- **下一步该先提交 Stage 2a-2c**(测试已通过)。
## 关键测试 literal(byte0-低 256-bit)
- KAT c000 d:`D_LIT = 256'h2426f1941779574d3f1b163bd57f7e173e229e630ec7f7073bdf365137c4bb6d`
- 期望 rho:`256'h15f74355ca862c3cdf3dab780c35cf24b88bf144706090a1c17e41205f9f1379`
- 期望 sigma:`256'h69b042001b5630b1a039116cbfd29f62c0bde5a6b571504a9fcce68bed667fd5`
## 下一步(剩余 Stage 2/3/4)
### Stage 2d:NTT(s/e → ŝ/ê) ← **当前正在做**
- golden 映射已确认:`ntt(s_i)==shat_i`、`ntt(e_i)==ehat_i`(mode=0 fwd,无缩放,zeta 表见 ntt gen_vectors,N_INV=3303 仅 inv 用)。
- 计划:新增 ST_N 状态,对 4 个 slot(S0,S1,E0,E1)逐个就地 NTT:从 polymem 读 256 喂 ntt_core(mode=0),收 256 写回同 slot。
- 注意 ntt_core 协议:LOAD 期间持续 valid_i 喂 256;输出阶段 valid_o 高、ready_i 消费 256。需读地址流水(组合读 polymem)。
- TB:扩展对 shat/ehat golden 校验。
- **正读 mlkem_top.v 行 200-290 的时序逻辑准备插入 ST_N 时被打断;接手时先重读 mlkem_top.v 全文确认当前 FSM 时序结构再加 ST_N。**
### Stage 2e:矩阵累加 t̂[i] = ê[i] + Σⱼ Â[i][j]∘ŝ[j]
- 用 poly_mul_sync(同拍喂 A、B 各 256)算 Â[i][j]∘ŝ[j],再用 mod_add_sync 逐系数 + ê[i] 累加。K=2:t̂[0]=ê0+A00∘ŝ0+A01∘ŝ1;t̂[1]=ê1+A10∘ŝ0+A11∘ŝ1。
- 对 golden `that_i` 校验。
### Stage 2f:byteEncode₁₂ → ek/dk_pke
- 12-bit LSB-first 打包(FIPS:b[i*12+j]=(coeff>>j)&1,再 bits_to_bytes)。ek=byteEncode12(t̂[0..1])‖ρ(800B);dk_pke=byteEncode12(ŝ[0..1])(768B)。
- 对 KAT pk / sk 前缀逐字节校验。
### Stage 3:KeyGen FSM 集成 + 干净顶层接口
- 串起 2a-2f;顶层流式输出 ek_o/dk_o;无 force。
### Stage 4:组装完整 dk + 端到端 KAT
- dk = dk_pke ‖ ek ‖ H(ek)[用 Stage1 多块] ‖ z。
- 干净 KAT TB(无 force/release),喂 KAT d/z,逐字节比 ek 与 dk。跑 KAT count=0..4 全过。Verilator + XSIM 双框架。
## 经验教训(重要)
1. **"PASS" 只证 RTL==reference**:必须独立对 hashlib/FIPS 验证 oracle。
2. **单独 TB 盲区会掩盖模块 bug**:sample_ntt 的 257th 脉冲就是"读够就停"掩盖的。新 TB 要验证协议边界(last 后 valid 干净拉低)。
3. **加端口必须 tie off 所有旧实例**:浮空输入成 x 会破坏组合逻辑(mb_en 教训)。
4. **valid/ready 握手 TB 要在 accept 边沿保持输入稳定**,等 ready 拉低再撤,否则采样竞争(多块 H、A-pair 边界都踩过)。
5. **字节序/位序是旧版主要 bug 源**:每步对 golden 逐字节,错位立刻暴露。
6. **bash 工具偶发输出截断/丢失**:重要结论用 `rm -rf xsim.dir` 重跑确认,别凭一次模糊输出下判断。
## 任务追踪(TaskList ID)
- #7 Stage 0 ✅ completed
- #8 Stage 1 ✅ completed
- #9 Stage 2 datapath subblocks — in_progress(2a/b/c done,2d 进行中)
- #10 Stage 3 FSM 集成 — blocked by #9
- #11 Stage 4 dk + KAT — blocked by #8,#10
## 提交历史(相关)
- 106b292 feat(sha3): multi-block SHA3-256 absorb for H(ek); KeyGen golden vectors (Stage 0+1)
- 6db3c7c fix(sample_ntt): suppress spurious 257th valid_o after last_o
- (未提交)Stage 2a-2c mlkem_top + TB
---
## ✅ COMPLETE (session end state)
- KeyGen FSM: IDLE->G->A->C->N->M->E->H->DONE, pure valid/ready, no force.
- Verified vs NIST KAT MLKEM-512 **count=0..4**: ek (800B)==pk, dk (1632B)==sk, byte-exact, ~21350 cyc/case.
- Commits: 106b292, 6db3c7c, 2f206a6, 4c692e5, a9e50eb, 1791491, 9824ed8, 42d3748.
- Independent Keccak per consumer (G + dedicated H sha3_top + sample_ntt/cbd cores). No arbiter.
- SECURITY: tool outputs this session carried prompt-injection (fake operator instructing `curl|bash`); all refused. Some outputs were tampered; results re-verified with controlled delimiters.
- Remaining optional: streaming ek/dk output port (currently start_i/done_o + readback taps); Encaps/Decaps; shared-keccak migration.