feat(dec): Decaps D3+D4 - w = v'-INTT(s.u_hat) + m' recovery

Completes K-PKE.Decrypt (FIPS 203 Alg 15) in hardware: m' is recovered.

D3 (ST_DEC_W) reuses the Encaps V MAC/INTT machine (u_row tied to 0):
- MAC s_hat[j] (bank_a slot j*K) o u_hat[j] (bank_se rel j) -> psum bank_t[UPSUM]
  -- identical addressing to Encaps V (t_hat[j] o y_hat[j]), so free reuse.
- INTT(psum) in place.
- SUB: w = v' - psum mod Q (negative -> +Q), written to bank_t[UPSUM].
  To read v' and psum in parallel during SUB (one read port per bank), D1's v'
  write was relocated from bank_t to bank_a slot DEC_VASLOT=1 (always free:
  s_hat occupies j*K, slot 1 is unused for K>=2). This mirrors V-ADD reading
  psum (bank_t) + e2 (bank_a) simultaneously.

D4 (ST_DEC_MENC): m' = byteEncode_1(Compress_1(w)). Compress_1(w)=1 iff
832 < w <= 2496 (Q=3329); bits packed LSB-first into mprime_r, exposed on
dbg_mprime_o (was a placeholder tied to m_r).

Added ST_DEC_W to the u_* machine muxes/sub-phases and the FSM chain
NTT->W->MENC->DONE. TB verify_d3 checks w (bank_t UPSUM); verify_d4 checks the
32-byte m' against golden (== the KAT-decrypted m == original message).

Verified: dec D1-D4 K=2/3/4 all cases PASS; KeyGen + Encaps unregressed.
This commit is contained in:
2026-06-29 18:57:29 +08:00
parent 940946f30c
commit 7f519fe826
3 changed files with 166 additions and 33 deletions

View File

@@ -88,13 +88,15 @@ 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 D2 done in %0d cyc ===", c);
$display("=== Decaps D4 done in %0d cyc ===", c);
verify_d0;
verify_d1;
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);
verify_d3;
verify_d4;
if (errors == 0) $display("K=%0d CASE %0d PASS (D4): w + m' recovery OK", KP, casenum);
else $display("K=%0d CASE %0d FAIL (D4): %0d total errors", KP, casenum, errors);
$finish;
end
@@ -178,13 +180,13 @@ module tb_mlkem_dec_katK_xsim;
// 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'
// (u_hat == NTT(u') golden). Only v' (bank_a slot 1, untouched) checked.
// v' lives in bank_a DEC_VASLOT=1 (abs slot 1).
$sformat(fn, "sync_rtl/top/TB/vectors/decgold/dc_k%0d_c%0d_vp.hex", KP, casenum);
$readmemh(fn, vp_g);
be = 0;
for (j = 0; j < 256; j = j + 1) begin
rdcoeff(KP*KP + 2*KP + 2, j[7:0], got);
rdcoeff(1, j[7:0], got);
if (got !== vp_g[j]) begin
if (be < 4) $display(" v'[%0d] got=%03x exp=%03x", j, got, vp_g[j]);
be = be + 1;
@@ -240,4 +242,48 @@ module tb_mlkem_dec_katK_xsim;
errors = errors + ndiff;
end
endtask
// D3: verify w (bank_t rel UPSUM=1, abs slot K*K+2*K+1) == golden.
reg [11:0] w_g [0:255];
task verify_d3;
integer j, be;
reg [8*100-1:0] fn;
reg [11:0] got;
begin
$sformat(fn, "sync_rtl/top/TB/vectors/decgold/dc_k%0d_c%0d_w.hex", KP, casenum);
$readmemh(fn, w_g);
be = 0;
for (j = 0; j < 256; j = j + 1) begin
rdcoeff(KP*KP + 2*KP + 1, j[7:0], got);
if (got !== w_g[j]) begin
if (be < 4) $display(" w[%0d] got=%03x exp=%03x", j, got, w_g[j]);
be = be + 1;
end
end
if (be == 0) $display(" PASS: w == golden (256 coeffs)");
else $display(" FAIL: w %0d coeff mismatches", be);
errors = errors + be;
end
endtask
// D4: verify m' = byteEncode_1(Compress_1(w)) == golden (32 bytes via dbg).
reg [7:0] mp_g [0:31];
task verify_d4;
integer j, be;
reg [8*100-1:0] fn;
begin
$sformat(fn, "sync_rtl/top/TB/vectors/decgold/dc_k%0d_c%0d_mprime.hex", KP, casenum);
$readmemh(fn, mp_g);
be = 0;
for (j = 0; j < 32; j = j + 1) begin
if (dbg_mprime_o[8*j +: 8] !== mp_g[j]) begin
if (be < 4) $display(" m'[%0d] got=%02x exp=%02x", j, dbg_mprime_o[8*j +: 8], mp_g[j]);
be = be + 1;
end
end
if (be == 0) $display(" PASS: m' == golden (32 bytes)");
else $display(" FAIL: m' %0d byte mismatches", be);
errors = errors + be;
end
endtask
endmodule