# 计划:将 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 用例 ### 阶段 1:ek_mem / dkp_mem → BRAM(风险较低,先做) 1. 例化 `ek_bram`、`dkp_bram`(sd_bram,W=8)。 2. **重写 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 字节/周期,不变。 3. **ST_H 的 h_padbyte 读 ek 改为地址提前一拍**:assemble 子状态先发地址、下一拍取数据 写入 h_block_r(h_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。 ### 阶段 2:polymem → 4 bank BRAM(风险较高) 1. 例化 4 个 `sd_bram #(.W(12))`:bank_a(D=4096)、bank_s/e/t(D=1024)。 2. 写口改写:ST_A→bank_a;ST_C→bank_s/e;ST_N→bank_s/e;ST_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 ACC:m_acc_src 读地址跟踪即将到来的 m_oidx(提前一拍),对齐 pm_vo。 - ST_E:每周期读 1 个系数(e_c0、e_c1 拆两拍),配合阶段 1 的 1 字节/周期写。 4. dbg_coeff_o:4 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,回退该阶段并报告根因,不带病提交。