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).
4.8 KiB
4.8 KiB
计划:将 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)。
核心约束(已从代码确认)
- BRAM 读有 1 周期延迟(reg 数组是组合读)。所有读出点都要改成“地址提前一拍”。
- BRAM 每端口每周期 ≤1 访问。当前
polymem在同周期最多有 2 个不同读地址 (ST_M LOAD 同时读 A 和 ŝ),且 ST_E 同周期读同一 poly 的 2 个系数。 - 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 用例
阶段 1:ek_mem / dkp_mem → BRAM(风险较低,先做)
- 例化
ek_bram、dkp_bram(sd_bram,W=8)。 - 重写 ST_E 为每周期写 1 字节(3 字节拆 3 拍):新增子计数器 b∈{0,1,2}, 按 b 选 e_b0/e_b1/e_b2 与目标地址 e_boff+b。ST_E 周期数约 ×3(K=4 增 ~2K 周期, 占总量 ~4%,可接受)。rho 拷贝阶段本就 1 字节/周期,不变。
- ST_H 的 h_padbyte 读 ek 改为地址提前一拍:assemble 子状态先发地址、下一拍取数据 写入 h_block_r(h_byte 流水线 +1 级)。
- 回读路径(dbg_byte_o / dbg_dk_o):改为直接用 bram 的 rd_data(去掉多余的中间寄存器, 保持总延迟 ≤2 拍,落在 TB 的 2 周期等待内)。
- 跑
./run_tb.sh top全 11 例,要求 0 个cannot be opened、全 PASS。
阶段 2:polymem → 4 bank BRAM(风险较高)
- 例化 4 个
sd_bram #(.W(12)):bank_a(D=4096)、bank_s/e/t(D=1024)。 - 写口改写:ST_A→bank_a;ST_C→bank_s/e;ST_N→bank_s/e;ST_M ACC→bank_t。
- 读口加“地址提前一拍”流水:
- ST_N LOAD:读地址 = 下一个 n_ridx,数据对齐 ntt_core 的 valid_i。
- ST_M LOAD:读地址 = 下一个 m_ld,数据对齐 poly_mul 的 pm_valid。
- ST_M ACC:m_acc_src 读地址跟踪即将到来的 m_oidx(提前一拍),对齐 pm_vo。
- ST_E:每周期读 1 个系数(e_c0、e_c1 拆两拍),配合阶段 1 的 1 字节/周期写。
- dbg_coeff_o:4 bank rd_data 按 dbg_slot 区间选择。
- 跑全 11 例验证。
验证方法(每阶段)
- 干净重跑:
rm -rf xsim.dir .Xil后./run_tb.sh top。 - 检查日志:
grep -c 'cannot be opened'(=0) 且 11/11PASS。 - 记录周期数变化(预期 ek/dk BRAM 化使 ST_E 变长、polymem 读延迟引入少量气泡)。
- 阶段 2 完成后,可选:跑
vivado -mode batch综合,确认 polymem 现在落在 RAMB 而非 LUTRAM。
不在本次范围
- 是否把 ek/dk 暴露为对外输出端口(上一条被打断的请求)—— 独立问题,本次只改存储实现, 保持现有 dbg_* 接口不变。如需对外输出,迁移完成后另议。
风险
- 读延迟流水改造触及所有 5 个计算阶段的握手时序,是 KAT 回归的主要风险点。
- 缓解:分两阶段、每阶段独立 KAT 回归;先做低风险的 ek/dk,再做 polymem。
- 若某阶段无法在合理尝试内通过 KAT,回退该阶段并报告根因,不带病提交。