# 计划：将 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，回退该阶段并报告根因，不带病提交。
