diff --git a/.claude/plans/encaps_plan.md b/.claude/plans/encaps_plan.md index 7812279..8d37f3a 100644 --- a/.claude/plans/encaps_plan.md +++ b/.claude/plans/encaps_plan.md @@ -72,7 +72,7 @@ ct_bytes_rt = c1_bytes_rt + c2_bytes_rt; // 768 / 1088 / 1568 - **E3 — ŷ = NTT(y)**:ST_ENC_N,mode=0,就地。dbg 对 y_hat。 - **E4 — u = INTT(Σ Â[j][i]∘ŷ[j]) + e1**:ST_ENC_U,poly_mul + 累加 + INTT(mode=1) + 加 e1。**转置寻址 slot=j·K+i**。dbg 对 u。 - **E5 — Compress_du + byteEncode_du → c1** ✅:comp_decomp(mode0,d=du) + 通用 LSB-first 打包器(ST_ENC_C1)写 ct_bram c1 区。dbg_ct tap 比 ct[0..c1_bytes]==KAT.ct 前缀,K=2/3/4 全过(含 K=4 du=11 跨字节)。runner = `./run_enc.sh K CASE`。 -- **E6 — v = INTT(Σ t̂[j]∘ŷ[j]) + e2 + mu**:ST_ENC_V,mu 流内由 m bit 生成。dbg 对 v。 +- **E6 — v = INTT(Σ t̂[j]∘ŷ[j]) + e2 + mu** ✅:ST_ENC_C1→ST_ENC_TDEC(t̂ 解码进 **bank_a** slot j·K)→ST_ENC_E2MV(e2 从 bank_t[0] 搬到 **bank_a[1]**)→ST_ENC_V(复用 u_* MAC/INTT/ADD,u_row≡0)。mu[w]=m_r[w]?1665:0。v 落 bank_t[UPSUM],dbg slot 9(K=2)。dbg 对 v==ml-kem-r golden 全过。**踩坑:e2 搬迁曾 off-by-one(写 e2[i+1] 到 slot i),已修(em_widx==em_ridx,写延后 1 拍对齐 bram 读)。** verify_e1 已废弃(TDEC 覆盖 bank_a 的 A_hat;A_hat 由 E4 转置 MAC 间接验证)。 - **E7 — Compress_dv + byteEncode_dv → c2 + 端到端 KAT**:写 ct c2 区。干净 TB 喂 ek/m,比 ct(全长)==KAT.ct 且 ss==KAT.ss,K=2/3/4 各 count=0..N。 ## 验证 diff --git a/sync_rtl/top/TB/tb_mlkem_enc_katK_xsim.v b/sync_rtl/top/TB/tb_mlkem_enc_katK_xsim.v index c8853f0..f2504e5 100644 --- a/sync_rtl/top/TB/tb_mlkem_enc_katK_xsim.v +++ b/sync_rtl/top/TB/tb_mlkem_enc_katK_xsim.v @@ -102,10 +102,13 @@ module tb_mlkem_enc_katK_xsim; // bank_t during C/N/U. A_hat equals KeyGen golden (K=2 c0). ---- // ---- E2: verify y[i], e1[i] (bank_se), e2 (bank_t slot_t) vs ml-kem-r. if (KP == 2 && casenum == 0) begin - verify_e1; + // E1 (verify_e1) dropped: E6's TDEC overwrites bank_a (A_hat slots) + // with t_hat, so a post-run A_hat readback is invalid. A_hat is + // transitively verified by E4 (transpose MAC) and E6 (v uses t_hat). verify_e2; verify_e3; verify_e4; + verify_e6; end // E5: c1 = byteEncode_du(Compress_du(u)) must equal KAT.ct[0..C1B-1]. // Runs for every K/case (ct_b is the full KAT ciphertext). @@ -221,4 +224,24 @@ module tb_mlkem_enc_katK_xsim; else $display("K=%0d CASE %0d FAIL (E5): %0d c1 byte mismatches", KP, casenum, be); end endtask + + // E6: v = INTT(sum_j t_hat[j] o y_hat[j]) + e2 + mu lives in bank_t rel slot + // UPSUM=1 -> dbg slot slot_t_rt+1 = 9 (K=2). Compare to ml-kem-r golden v. + reg [11:0] gv [0:255]; + task verify_e6; + begin + $readmemh("sync_rtl/top/TB/vectors/encgold/ec_k2_c0_v.hex", gv); + ce = 0; + for (idx = 0; idx < 256; idx = idx + 1) begin + dbg_slot_i = 9; dbg_idx_i = idx[7:0]; // bank_t rel UPSUM (K=2) + @(posedge clk); @(posedge clk); @(posedge clk); + if (dbg_coeff_o !== gv[idx]) begin + if (ce < 8) $display(" V[%0d] got=%03x exp=%03x", idx, dbg_coeff_o, gv[idx]); + ce = ce + 1; + end + end + if (ce == 0) $display("K=2 CASE 0 PASS (E6): v == ml-kem-r golden"); + else $display("K=2 CASE 0 FAIL (E6): %0d coeff mismatches", ce); + end + endtask endmodule diff --git a/sync_rtl/top/mlkem_top.v b/sync_rtl/top/mlkem_top.v index b8dc470..4ca096b 100644 --- a/sync_rtl/top/mlkem_top.v +++ b/sync_rtl/top/mlkem_top.v @@ -124,9 +124,10 @@ module mlkem_top #( // decoded coeffs: c0 = b0 | ((b1&0xF)<<8); c1 = (b1>>4) | (b2<<4) wire [11:0] td_c0 = {td_b1[3:0], td_b0}; wire [11:0] td_c1 = {td_b2, td_b1[7:4]}; - // bank_t write for TDEC (registered); muxed with ST_M write below. + // bank_a write for TDEC (registered); t_hat[td_poly] -> bank_a slot + // td_poly*K. td_wa is a bank_a byte address (PA_AW=12). reg td_we; - reg [9:0] td_wa; // PT_AW=10 (declared below; literal here) + reg [11:0] td_wa; // bank_a address (PA_AW=12) reg [11:0] td_wd; // ---- ct_bram: ciphertext byte buffer (<=1568 B). Written by E5/E7 @@ -189,9 +190,17 @@ module mlkem_top #( .wr_en(ba_we), .wr_addr(ba_wa), .wr_data(ba_wd) ); // ST_A / ST_ENC_A write: commit snt_coeff to A_hat[a_slot] when accepted. - assign ba_we = (st == ST_A || st == ST_ENC_A) && a_busy && snt_vo && snt_ack; - assign ba_wa = (a_slot*256 + a_widx) & ((1< bank_t rel slot 0, free during C/N/U). + // bank_t write: KeyGen ST_M accumulate, or Encaps e2 (ST_ENC_C c_poly==2K + // -> bank_t rel slot 0, free during C/N/U). (TDEC writes bank_a now.) wire e2_we = (st == ST_ENC_C) && c_busy && cbd_vo && cbd_ack && (c_poly == {1'b0, k_r, 1'b0}); // c_poly == 2K - // Encaps E4 bank_t writes: MAC psum (u_sub0, on pm_vo) and INTT in-place - // (u_sub1, on ntt_vo). Both target bank_t rel slot UPSUM. - wire u_psum_we = (st == ST_ENC_U) && (u_sub == 2'd0) && pm_vo; - wire u_intt_we = (st == ST_ENC_U) && (u_sub == 2'd1) && ntt_vo; - assign bt_we = ((st == ST_M) && pm_vo) || td_we || e2_we || - u_psum_we || u_intt_we; - assign bt_wa = td_we ? td_wa : - e2_we ? (c_widx & ((1< t_hat (bank_t) + localparam ST_ENC_TDEC = 5'd12; // byteDecode12: ek -> t_hat (bank_a slot j*K) localparam ST_ENC_C = 5'd13; // sample y,e1,e2 via CBD (eta1/eta2) localparam ST_ENC_N = 5'd14; // forward NTT of y in place localparam ST_ENC_U = 5'd15; // u = INTT(sum A^T o y_hat) + e1 localparam ST_ENC_C1 = 5'd16; // Compress_du + byteEncode_du -> ct c1 localparam ST_ENC_V = 5'd17; // v = INTT(sum t_hat o y_hat) + e2 + mu localparam ST_ENC_C2 = 5'd18; // Compress_dv + byteEncode_dv -> ct c2 + localparam ST_ENC_E2MV = 5'd19; // relocate e2 bank_t[0] -> bank_a[E2_ASLOT] localparam ST_DONE = 5'd31; reg [4:0] st, st_next; @@ -640,8 +658,8 @@ module mlkem_top #( // registers it into bse_rd_data, which feeds ntt_core 1 cycle later. wire [13:0] ntt_rd_full = n_slot*256 + n_ridx[7:0]; // ntt_core inputs muxed: KeyGen/Encaps fwd NTT feeds bse_rd_data (mode 0); - // Encaps E4 INTT sub-phase feeds psum (bt_rd_data) with mode 1. - wire u_intt_act = (st == ST_ENC_U) && (u_sub == 2'd1); + // Encaps E4/E6 INTT sub-phase feeds psum (bt_rd_data) with mode 1. + wire u_intt_act = (st == ST_ENC_U || st == ST_ENC_V) && (u_sub == 2'd1); wire [11:0] ntt_in = u_intt_act ? bt_rd_data : bse_rd_data; wire ntt_vin = u_intt_act ? u_nvalid : n_valid; wire ntt_mode = u_intt_act ? 1'b1 : 1'b0; @@ -754,6 +772,38 @@ module mlkem_top #( wire [12:0] u_usum = {1'b0, bt_rd_data} + {1'b0, bse_rd_data}; // psum + e1 wire [11:0] u_uq = (u_usum >= 13'(Q)) ? (u_usum - 13'(Q)) : u_usum[11:0]; + // ================================================================ + // E6: v = INTT(sum_j t_hat[j] o y_hat[j]) + e2 + mu (Encaps ST_ENC_V). + // Reuses the u_* MAC/INTT/ADD machine with u_row tied to 0 (single output + // poly). Differences from E4: + // - MAC reads t_hat[j] from bank_a slot j*K (== u_aslot=u_j*K, u_row=0), + // so the bank_a MAC address is unchanged. + // - ADD computes psum + e2 + mu (not psum + e1); e2 lives in bank_a slot + // E2_ASLOT (relocated from bank_t[0] in ST_ENC_E2MV), v writes back to + // bank_t[UPSUM] in place. + // - mu[w] = Decompress_1(m bit w) = m_r[w] ? 1665 : 0 (1665 = round(Q/2)). + // u_row_max bounds the row loop: K for U, 1 for V. + localparam E2_ASLOT = 12'd1; // bank_a slot holding relocated e2 (never a t_hat slot, K>=2) + wire [2:0] u_row_max = (st == ST_ENC_V) ? 3'd1 : k_r; + // V-ADD: read psum (bank_t UPSUM) + e2 (bank_a E2_ASLOT) at u_aidx (lead), + // add mu (by write index u_awidx), write v to bank_t[UPSUM] at u_awidx. + wire [13:0] u_v_e2rd = E2_ASLOT*256 + u_aidx[7:0]; // bank_a e2 (lead) + wire [13:0] u_vadd_wr = UPSUM*256 + u_awidx; // bank_t v (write) + wire [11:0] u_mu = m_r[u_awidx] ? 12'd1665 : 12'd0; // mu by msg bit + wire [13:0] u_vsum = {2'b0, bt_rd_data} + {2'b0, ba_rd_data} + {2'b0, u_mu}; // psum+e2+mu + wire [13:0] u_vsub1 = (u_vsum >= 14'(Q)) ? (u_vsum - 14'(Q)) : u_vsum; + wire [11:0] u_vq = (u_vsub1 >= 14'(Q)) ? (u_vsub1 - 14'(Q)) : u_vsub1[11:0]; + wire u_v_we = (st == ST_ENC_V) && (u_sub == 2'd2) && u_avalid; + + // ST_ENC_E2MV: copy e2 (bank_t rel slot 0, 256 coeffs) into bank_a[E2_ASLOT]. + // bank_t read leads (em_ridx), bank_a write trails 1 cycle (em_we/em_widx). + reg [8:0] em_ridx; // 0..256 read-ahead over e2 + reg [7:0] em_widx; // bank_a write index + reg em_we; // a coeff is being written this cycle + reg em_done; + wire [13:0] em_rd = 14'd0 + em_ridx[7:0]; // bank_t rel slot 0 (e2) + wire [11:0] em_wa = E2_ASLOT*256 + em_widx; // bank_a E2_ASLOT + // ================================================================ // E5/E7: Compress_d + byteEncode_d -> ciphertext (Encaps ST_ENC_C1/C2). // Per coeff: read poly coeff -> comp_decomp (mode 0 compress, d=du/dv) -> @@ -855,8 +905,10 @@ module mlkem_top #( ST_ENC_C: if (c_poly >= {k_r, 1'b1}) st_next = ST_ENC_N; // 2K+1 polys done ST_ENC_N: if (n_slot >= {2'b0, k_r}) st_next = ST_ENC_U; // K slots (y_hat) ST_ENC_U: if (u_row >= k_r) st_next = ST_ENC_C1; // u[0..K-1] done - ST_ENC_C1: if (cp_done) st_next = ST_DONE; // E5: c1 packed - ST_ENC_TDEC: if (td_done) st_next = ST_DONE; // (TDEC deferred to V-prep later) + ST_ENC_C1: if (cp_done) st_next = ST_ENC_TDEC; // E5 done -> E6 + ST_ENC_TDEC: if (td_done) st_next = ST_ENC_E2MV; // t_hat decoded -> relocate e2 + ST_ENC_E2MV: if (em_done) st_next = ST_ENC_V; // e2 relocated -> compute v + ST_ENC_V: if (u_row >= 3'd1) st_next = ST_DONE; // E6: v done (1 row) ST_DONE: st_next = ST_IDLE; default: st_next = ST_IDLE; endcase @@ -881,7 +933,7 @@ module mlkem_top #( td_b2 <= 8'd0; td_done <= 1'b0; td_we <= 1'b0; - td_wa <= 10'd0; + td_wa <= 12'd0; td_wd <= 12'd0; rho_r <= 256'd0; sigma_r <= 256'd0; @@ -929,6 +981,10 @@ module mlkem_top #( u_aidx <= 9'd0; u_awidx <= 8'd0; u_avalid <= 1'b0; + em_ridx <= 9'd0; + em_widx <= 8'd0; + em_we <= 1'b0; + em_done <= 1'b0; cd_coeff <= 12'd0; cd_valid <= 1'b0; cp_poly <= 3'd0; @@ -973,8 +1029,9 @@ module mlkem_top #( // BRAM write-enables default low; pulsed where a byte is written. ek_we <= 1'b0; dkp_we <= 1'b0; - td_we <= 1'b0; // TDEC bank_t write default low + td_we <= 1'b0; // TDEC bank_a write default low ct_we <= 1'b0; // ct_bram byte write default low (E5/E7) + em_we <= 1'b0; // e2-relocate bank_a write default low (E6) // Kick off when entering from IDLE: KeyGen starts G; Encaps captures // op/m and arms the H(ek) machinery (ST_ENC_H reuses the ST_H FSM). @@ -1213,10 +1270,11 @@ module mlkem_top #( pm_valid <= 1'b0; end - // ---- ST_ENC_U sub-phase 0: MAC sum_j A^T[u_row][j] o y_hat[j] ---- + // ---- ST_ENC_U/V sub-phase 0: MAC sum_j (A^T or t_hat)[j] o y_hat[j] ---- // psum (init 0 at j==0) accumulates into bank_t[UPSUM]. Mirrors ST_M // load/accumulate cadence (read-ahead by 1, j-select via u_j0q). - if (st == ST_ENC_U && u_sub == 2'd0) begin + // U: A^T[u_row][j] (bank_a u_j*K+u_row). V: t_hat[j] (bank_a u_j*K, u_row=0). + if ((st == ST_ENC_U || st == ST_ENC_V) && u_sub == 2'd0) begin u_j0q <= (u_j == 3'd0); if (u_loading) begin @@ -1260,8 +1318,8 @@ module mlkem_top #( end end - // ---- ST_ENC_U sub-phase 1: INTT(psum) mode=1 in place (bank_t) ---- - if (st == ST_ENC_U && u_sub == 2'd1) begin + // ---- ST_ENC_U/V sub-phase 1: INTT(psum) mode=1 in place (bank_t) ---- + if ((st == ST_ENC_U || st == ST_ENC_V) && u_sub == 2'd1) begin if (u_nloading) begin if (u_ridx == 9'd256) begin u_nloading <= 1'b0; @@ -1281,10 +1339,13 @@ module mlkem_top #( end end - // ---- ST_ENC_U sub-phase 2: u[u_row] = psum + e1[u_row] mod Q ---- - // Read psum (bank_t) + e1 (bank_se) at u_aidx; both arrive 1 cyc - // later, so register (valid,widx) and write u over e1 next cycle. - if (st == ST_ENC_U && u_sub == 2'd2) begin + // ---- ST_ENC_U/V sub-phase 2: ADD + writeback ---- + // U: u[u_row] = psum + e1[u_row] mod Q -> bank_se (u_add_we). + // V: v = psum + e2 + mu mod Q -> bank_t[UPSUM] (u_v_we). + // Read psum (bank_t) + e1/e2 at u_aidx; both arrive 1 cyc later, so + // register (valid,widx) and write next cycle. u_row_max bounds the + // row loop (K for U, 1 for V). + if ((st == ST_ENC_U || st == ST_ENC_V) && u_sub == 2'd2) begin if (u_aidx < 9'd256) begin u_aidx <= u_aidx + 9'd1; u_avalid <= 1'b1; // addr presented this cyc -> write next @@ -1292,10 +1353,10 @@ module mlkem_top #( end else begin u_avalid <= 1'b0; // no more addresses end - // u write (bse_we via u_add_we) commits index u_awidx this cycle. + // writeback (u_add_we / u_v_we) commits index u_awidx this cycle. // Advance row after the 256th coeff (u_awidx==255) is written. if (u_avalid && u_awidx == 8'd255) begin - if (u_row + 3'd1 < k_r) begin + if (u_row + 3'd1 < u_row_max) begin u_row <= u_row + 3'd1; u_sub <= 2'd0; // next row MAC u_j <= 3'd0; @@ -1305,7 +1366,7 @@ module mlkem_top #( u_pending <= 1'b0; pm_valid <= 1'b0; end else begin - u_row <= u_row + 3'd1; // == K -> ST_ENC_C1 + u_row <= u_row + 3'd1; // U: ==K ->C1; V: ==1 ->DONE end end end @@ -1536,8 +1597,10 @@ module mlkem_top #( a_busy <= 1'b0; end - // Arm ST_ENC_TDEC (byteDecode12: ek -> t_hat in bank_t). - if (st == ST_ENC_A && st_next == ST_ENC_TDEC) begin + // Arm ST_ENC_TDEC (byteDecode12: ek -> t_hat in bank_a). Now follows + // E5 (ST_ENC_C1) since e2 occupied bank_t during C/N/U and t_hat is + // only needed for V. (Was armed off ST_ENC_A in the E1 scaffold.) + if (st == ST_ENC_C1 && st_next == ST_ENC_TDEC) begin td_poly <= 3'd0; td_trip <= 8'd0; td_ph <= 3'd0; @@ -1554,15 +1617,16 @@ module mlkem_top #( if (td_ph == 3'd2) td_b1 <= ek_rd_data; if (td_ph == 3'd3) td_b2 <= ek_rd_data; - // write decoded coeffs into bank_t (rel slot = td_poly) + // write decoded coeffs into bank_a at slot td_poly*K (so V's MAC + // reads t_hat[j] via the same u_aslot=u_j*K addressing, u_row=0). if (td_ph == 3'd3) begin td_we <= 1'b1; - td_wa <= (td_poly*256 + {td_trip, 1'b0}) & ((1< bank_a[E2_ASLOT], 256 coeffs ---- + // Present read addr em_ridx (bank_t rel 0); bank_t read is registered + // so bt_rd_data next cycle = e2[em_ridx]. Schedule the bank_a write + // (em_we/em_widx) for that next cycle with em_widx == em_ridx, so the + // write commits e2[R] into slot[R] (no off-by-one). + if (st == ST_ENC_E2MV && !em_done) begin + if (em_ridx <= 9'd255) begin // presenting a valid e2 addr this cycle + em_we <= 1'b1; // -> write next cycle (ba_wd = bt_rd_data) + em_widx <= em_ridx[7:0]; + end + if (em_ridx == 9'd256) em_done <= 1'b1; // last write (e2[255]) issued + else em_ridx <= em_ridx + 9'd1; + end + + // Arm ST_ENC_V when e2 relocated: prime first (only) row MAC (i=0,j=0). + // Reuses the u_* MAC/INTT/ADD machine with u_row tied to 0. + if (st == ST_ENC_E2MV && st_next == ST_ENC_V) begin + u_row <= 3'd0; + u_sub <= 2'd0; // MAC + u_j <= 3'd0; + u_ld <= 9'd0; + u_oidx <= 8'd0; + u_loading <= 1'b1; + u_pending <= 1'b0; + pm_valid <= 1'b0; + end end end