diff --git a/.claude/plans/decaps_plan.md b/.claude/plans/decaps_plan.md index 00a39d5..007922c 100644 --- a/.claude/plans/decaps_plan.md +++ b/.claude/plans/decaps_plan.md @@ -63,7 +63,7 @@ ## 实现阶段(逐阶段 dbg/KAT 对拍) - **D0 — 脚手架 + dk/c 载入 + 解析** ✅:op_i 加宽 2-bit(00 KG/01 Enc/10 Dec),ST_DEC_LOAD(D0 暂直接→DONE)。dk 流入按 region 路由:dk_pke→dkp_bram、ek_pke→ek_bram、h→hek_r、z→z_r;ct→c_in_bram(独立于 ct_bram)。dbg 验证 h/z/ek_pke/dk_pke。**踩坑1:载入路由用 k_r 但 k_r 在 start_i 才锁存 → 预载期 region 边界全 0,路由全错。改用 LIVE k_i 边界(dkp_bytes_ld 等)。踩坑2:旧 KG/Enc TB 未接新端口(dk_in_*/c_in_*/dbg_*)→ X 漂入 write mux,KeyGen/Encaps 超时回归。补 tie-off 0。** runner = `./run_tb.sh dec [K] [CASE]`。K=2/3/4 D0 全过,KG/Enc 回归通过。 - **D1 — byteDecode_d + Decompress → u'/v'** ✅:复用 comp_decomp(改 mode 可选:Encaps C1/C2 compress mode=0,Decaps DECOMP mode=1)。ST_DEC_DECOMP 内联 byteDecode 走子机:逐字节读 c_in_bram,LSB-first 累进 bit buffer,凑够 d 位抽符号→comp_decomp 解压→写 bank。c1(K 多项式,d=du)→u'[i] bank_se rel i;c2(1 多项式,d=dv)→v' bank_t rel DEC_VSLOT=2(避开 UPSUM=1)。dbg_slot_i 加宽 4→6 bit(K=4 v' 在 slot 26)。dump_decaps.rs(ml-kem-r 工作树)产 u'/v'/s_hat/u_hat/w/m' golden 到 vectors/decgold/。**踩坑:dbg coeff 读回延迟 = bank(1)+dbg_coeff_r(1),TB rdcoeff 等 2 拍少一拍 → 数据整体错位一格;改 3 拍修正。** K=2/3/4 全过,KG/Enc 回归通过。 -- **D2 — s_hat 解码 + u_hat = NTT(u')**:TDEC 复用(dk_pke 源)、ntt_core fwd。dbg 对 s_hat / u_hat。 +- **D2 — s_hat 解码 + u_hat = NTT(u')** ✅:复用 Encaps TDEC 机(ST_DEC_SDEC),字节源从 ek 切到 dkp_bram(td_byte mux + dkp_rd_addr 在 SDEC 走 td_ekaddr),s_hat 写 bank_a slot j*K(与 t_hat 同布局,D3 MAC 可直接读)。复用前向 NTT 机(ST_DEC_NTT,n_slot_max=k_r)对 bank_se rel 0..K-1 的 u' 原地变换成 u_hat。**踩坑:NTT 原地覆盖 u' → verify_d1 复查 u' 必失败;改为 verify_d1 只查 v'(bank_t 未动),u' 正确性由 u_hat==NTT(u') golden 传递性证明。** K=2/3/4 全过,KG/Enc 回归通过。 - **D3 — w = v' − INTT(Σ s∘u_hat)**:V 机器 SUB 变体。dbg 对 w。 - **D4 — m' = byteEncode₁(Compress₁(w))**:打包器 d=1。dbg 对 m'(== KAT 解密的 m')。 - **D5 — G(m'‖h) → (K',r') + J(z‖c) → K̄**:G 复用、J 多块(0x1F pad)。dbg 对 K'/r'/K̄。 diff --git a/sync_rtl/top/TB/tb_mlkem_dec_katK_xsim.v b/sync_rtl/top/TB/tb_mlkem_dec_katK_xsim.v index 5fa63fd..b39f340 100644 --- a/sync_rtl/top/TB/tb_mlkem_dec_katK_xsim.v +++ b/sync_rtl/top/TB/tb_mlkem_dec_katK_xsim.v @@ -88,12 +88,13 @@ module tb_mlkem_dec_katK_xsim; start_i=1; @(posedge clk); start_i=0; c=0; while(!done_o && c<2000000) begin @(posedge clk); c=c+1; end if(!done_o) begin $display("FAIL K=%0d case %0d: timeout", KP, casenum); $finish; end - $display("=== Decaps D1 done in %0d cyc ===", c); + $display("=== Decaps D2 done in %0d cyc ===", c); verify_d0; verify_d1; - if (errors == 0) $display("K=%0d CASE %0d PASS (D1): u'/v' decode-decompress OK", KP, casenum); - else $display("K=%0d CASE %0d FAIL (D1): %0d total errors", KP, casenum, errors); + verify_d2; + if (errors == 0) $display("K=%0d CASE %0d PASS (D2): s_hat + u_hat=NTT(u') OK", KP, casenum); + else $display("K=%0d CASE %0d FAIL (D2): %0d total errors", KP, casenum, errors); $finish; end @@ -174,22 +175,10 @@ module tb_mlkem_dec_katK_xsim; reg [11:0] got; begin ndiff = 0; - // u'[i] - for (i = 0; i < KP; i = i + 1) begin - $sformat(fn, "sync_rtl/top/TB/vectors/decgold/dc_k%0d_c%0d_up_%0d.hex", KP, casenum, i); - $readmemh(fn, up_g); - be = 0; - for (j = 0; j < 256; j = j + 1) begin - rdcoeff(KP*KP + i, j[7:0], got); - if (got !== up_g[j]) begin - if (be < 4) $display(" u'[%0d][%0d] got=%03x exp=%03x", i, j, got, up_g[j]); - be = be + 1; - end - end - if (be == 0) $display(" PASS: u'[%0d] == golden (256 coeffs)", i); - else $display(" FAIL: u'[%0d] %0d coeff mismatches", i, be); - ndiff = ndiff + be; - end + // u'[i] is NOT checked here: D2's forward NTT transforms u' in place + // in bank_se rel 0..K-1, so by the time the run finishes those slots + // hold u_hat. u' correctness is proven transitively in verify_d2 + // (u_hat == NTT(u') golden). Only v' (bank_t, untouched) is checked. // v' $sformat(fn, "sync_rtl/top/TB/vectors/decgold/dc_k%0d_c%0d_vp.hex", KP, casenum); $readmemh(fn, vp_g); @@ -207,4 +196,48 @@ module tb_mlkem_dec_katK_xsim; errors = errors + ndiff; end endtask + + // D2: verify s_hat[i] (bank_a slot i*K, byteDecode12 dk_pke) and + // u_hat[i] (bank_se rel slot i, = NTT(u'[i])) against golden. + reg [11:0] sh_g [0:255]; + reg [11:0] uh_g [0:255]; + task verify_d2; + integer i, j, be, ndiff; + reg [8*100-1:0] fn; + reg [11:0] got; + begin + ndiff = 0; + for (i = 0; i < KP; i = i + 1) begin + // s_hat[i] at bank_a slot i*KP + $sformat(fn, "sync_rtl/top/TB/vectors/decgold/dc_k%0d_c%0d_shat_%0d.hex", KP, casenum, i); + $readmemh(fn, sh_g); + be = 0; + for (j = 0; j < 256; j = j + 1) begin + rdcoeff(i*KP, j[7:0], got); + if (got !== sh_g[j]) begin + if (be < 4) $display(" s_hat[%0d][%0d] got=%03x exp=%03x", i, j, got, sh_g[j]); + be = be + 1; + end + end + if (be == 0) $display(" PASS: s_hat[%0d] == golden", i); + else $display(" FAIL: s_hat[%0d] %0d mismatches", i, be); + ndiff = ndiff + be; + // u_hat[i] at bank_se rel slot i (abs KP*KP + i) + $sformat(fn, "sync_rtl/top/TB/vectors/decgold/dc_k%0d_c%0d_uhat_%0d.hex", KP, casenum, i); + $readmemh(fn, uh_g); + be = 0; + for (j = 0; j < 256; j = j + 1) begin + rdcoeff(KP*KP + i, j[7:0], got); + if (got !== uh_g[j]) begin + if (be < 4) $display(" u_hat[%0d][%0d] got=%03x exp=%03x", i, j, got, uh_g[j]); + be = be + 1; + end + end + if (be == 0) $display(" PASS: u_hat[%0d] == golden", i); + else $display(" FAIL: u_hat[%0d] %0d mismatches", i, be); + ndiff = ndiff + be; + end + errors = errors + ndiff; + end + endtask endmodule diff --git a/sync_rtl/top/mlkem_top.v b/sync_rtl/top/mlkem_top.v index d1b40c1..56dae51 100644 --- a/sync_rtl/top/mlkem_top.v +++ b/sync_rtl/top/mlkem_top.v @@ -158,6 +158,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]}; + // TDEC byte source: Encaps reads ek (ek_rd_data); Decaps D2 (ST_DEC_SDEC) + // reuses the same machine to byteDecode12 dk_pke -> s_hat, reading dkp_bram. + wire td_dec_s = (st == ST_DEC_SDEC); + wire [7:0] td_byte = td_dec_s ? dkp_rd_data : ek_rd_data; // 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; @@ -336,7 +340,7 @@ module mlkem_top #( dbg_t_addr[PT_AW-1:0]; // bank_se read port: ST_N load (s/e NTT), ST_M load (pm_b s_hat[j]) vs // accumulate (e_hat, mutually exclusive via m_loading), ST_E dk-half, dbg. - assign bse_rd_addr = (st == ST_N || st == ST_ENC_N) ? ntt_rd_full[PSE_AW-1:0] : + assign bse_rd_addr = (st == ST_N || st == ST_ENC_N || st == ST_DEC_NTT) ? ntt_rd_full[PSE_AW-1:0] : (st == ST_M) ? (m_loading ? pm_b_full[PSE_AW-1:0] : m_eacc_full[PSE_AW-1:0]) : (st == ST_E) ? e_rd_full[PSE_AW-1:0] : @@ -358,15 +362,15 @@ module mlkem_top #( assign bse_we = ((st == ST_C) && c_busy && cbd_vo && cbd_ack) || ((st == ST_ENC_C) && c_busy && cbd_vo && cbd_ack && (c_poly < {1'b0, k_r, 1'b0})) || - (((st == ST_N) || (st == ST_ENC_N)) && ntt_vo) || + (((st == ST_N) || (st == ST_ENC_N) || (st == ST_DEC_NTT)) && ntt_vo) || u_add_we || dec_u_we; assign bse_wa = u_add_we ? u_add_uwr[PSE_AW-1:0] : dec_u_we ? dec_u_wr[PSE_AW-1:0] : - (st == ST_N || st == ST_ENC_N) ? ((n_slot*256 + n_widx) & ((1<= slot_t_rt) dbg_coeff_r <= bt_rd_data; // bank_t (sd_bram) else if (dbg_slot_i >= slot_s_rt) dbg_coeff_r <= bse_rd_data; // bank_se (sd_bram) @@ -465,6 +469,8 @@ module mlkem_top #( // ---- Decaps states ---- localparam ST_DEC_LOAD = 5'd20; // dk/c already streamed in; parse/settle (D0) localparam ST_DEC_DECOMP = 5'd21; // D1: byteDecode_d + Decompress c1->u', c2->v' + localparam ST_DEC_SDEC = 5'd22; // D2: byteDecode12 dk_pke -> s_hat (bank_a slot j*K) + localparam ST_DEC_NTT = 5'd23; // D2: u_hat[i] = NTT(u'[i]) in place (bank_se rel i) localparam ST_DONE = 5'd31; reg [4:0] st, st_next; @@ -648,8 +654,10 @@ module mlkem_top #( (st == ST_ENC_TDEC) ? td_ekaddr[10:0] : // byteDecode12 dbgdk_in_ek ? dbgdk_ek_off[10:0] : dbg_byte_idx_i; - // dkp BRAM read-address mux: dbg_byte (sel=1) or dbg_dk (dkp region). - assign dkp_rd_addr = dbg_byte_sel_i ? dbg_byte_idx_i : dbg_dk_idx_i[10:0]; + // dkp BRAM read-address mux: D2 byteDecode12 (ST_DEC_SDEC) walks dk_pke via + // td_ekaddr (poly*384 + trip*3 + ph); else dbg_byte (sel=1) / dbg_dk. + assign dkp_rd_addr = (st == ST_DEC_SDEC) ? td_ekaddr[10:0] : + dbg_byte_sel_i ? dbg_byte_idx_i : dbg_dk_idx_i[10:0]; // ---- sample_ntt_sync: Â[i][j] = SampleNTT(rho || j || i) ---- reg snt_valid; @@ -729,7 +737,7 @@ module mlkem_top #( reg n_pending; // waiting for ntt_core IDLE to start next slot wire [SAW-1:0] n_slot_addr = slot_s_rt + n_slot; // s_hat then e_hat contiguous // NTT slot count: KeyGen processes 2K (s,e); Encaps processes K (y only). - wire [4:0] n_slot_max = (st == ST_ENC_N) ? {2'b0, k_r} : {1'b0, k_r, 1'b0}; + wire [4:0] n_slot_max = (st == ST_ENC_N || st == ST_DEC_NTT) ? {2'b0, k_r} : {1'b0, k_r, 1'b0}; wire ntt_ready; wire [11:0] ntt_coeff; @@ -1017,7 +1025,11 @@ module mlkem_top #( // D0: settle after dk/c parse, then D1 Decompress. D1 lands in DONE // once u'/v' are computed so the stage can be dbg-checked. ST_DEC_LOAD: st_next = ST_DEC_DECOMP; - ST_DEC_DECOMP: if (dec_dc_done) st_next = ST_DONE; + ST_DEC_DECOMP: if (dec_dc_done) st_next = ST_DEC_SDEC; + // D2: s_hat decode (reuse TDEC machine), then u_hat = NTT(u'). D2 + // lands in DONE once u_hat is ready so the stage can be dbg-checked. + ST_DEC_SDEC: if (td_done) st_next = ST_DEC_NTT; + ST_DEC_NTT: if (n_slot >= {2'b0, k_r}) st_next = ST_DONE; ST_G: if (sha3_vo) st_next = ST_A; ST_A: if (a_pair >= kk_rt) st_next = ST_C; ST_C: if (c_poly >= {1'b0, k_r, 1'b0}) st_next = ST_N; @@ -1300,7 +1312,8 @@ module mlkem_top #( // read-ahead pointer; bank_se read is registered inside sd_bram (bse_rd_data) // and fed to ntt_core one cycle later, so valid starts low (priming). if ((st == ST_C && st_next == ST_N) || - (st == ST_ENC_C && st_next == ST_ENC_N)) begin + (st == ST_ENC_C && st_next == ST_ENC_N) || + (st == ST_DEC_SDEC && st_next == ST_DEC_NTT)) begin n_slot <= 3'd0; n_ridx <= 9'd0; n_widx <= 8'd0; @@ -1309,9 +1322,9 @@ module mlkem_top #( n_pending <= 1'b0; end - // ---- ST_N / ST_ENC_N: forward NTT in place. KeyGen: 2K slots - // (s,e). Encaps: K slots (y only; e1/e2 stay time-domain). ---- - if (st == ST_N || st == ST_ENC_N) begin + // ---- ST_N / ST_ENC_N / ST_DEC_NTT: forward NTT in place. KeyGen: 2K + // slots (s,e). Encaps: K slots (y). Decaps D2: K slots (u' -> u_hat). ---- + if (st == ST_N || st == ST_ENC_N || st == ST_DEC_NTT) begin // slot-count bound: 2K for KeyGen, K for Encaps // (n_slot_max below); same LOAD/OUTPUT cadence either way. if (n_loading) begin @@ -1858,16 +1871,22 @@ module mlkem_top #( td_ph <= 3'd0; td_done <= 1'b0; end + // Arm D2 s_hat decode (ST_DEC_SDEC) when DECOMP finishes: same TDEC + // machine, byteDecode12 dk_pke -> s_hat in bank_a slot j*K. + if (st == ST_DEC_DECOMP && st_next == ST_DEC_SDEC) begin + td_poly <= 3'd0; + td_trip <= 8'd0; + td_ph <= 3'd0; + td_done <= 1'b0; + end - // ---- ST_ENC_TDEC: byteDecode12 ek -> t_hat[0..k-1] in bank_t ---- - // 5-cycle micro-phase per triple (read-ahead, 1-cyc bram latency): - // ph0: present b0 addr; ph1: capture b0, present b1; ph2: capture - // b1, present b2; ph3: capture b2 + write c0; ph4: write c1, advance. - if (st == ST_ENC_TDEC && !td_done) begin + // Encaps: ek -> t_hat. Decaps D2: dk_pke -> s_hat. Same 5-cycle + // micro-phase per triple; only the byte source (td_byte) differs. + if ((st == ST_ENC_TDEC || st == ST_DEC_SDEC) && !td_done) begin // capture the byte that arrived for the address presented last cycle - if (td_ph == 3'd1) td_b0 <= ek_rd_data; - if (td_ph == 3'd2) td_b1 <= ek_rd_data; - if (td_ph == 3'd3) td_b2 <= ek_rd_data; + if (td_ph == 3'd1) td_b0 <= td_byte; + if (td_ph == 3'd2) td_b1 <= td_byte; + if (td_ph == 3'd3) td_b2 <= td_byte; // 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).