feat(dec): Decaps D2 - s_hat=byteDecode12(dk_pke) + u_hat=NTT(u')

K-PKE.Decrypt steps 2-3 (FIPS 203 Alg 15), both by reusing Encaps machines:

- ST_DEC_SDEC reuses the Encaps TDEC (byteDecode12) machine: only the byte
  source changes (td_byte mux -> dkp_rd_data; dkp_rd_addr driven by td_ekaddr
  in SDEC). Decodes dk_pke -> s_hat[j] into bank_a slot j*K, the same layout
  t_hat uses, so the D3 MAC can read s_hat[j] with the existing addressing.
- ST_DEC_NTT reuses the forward-NTT machine (n_slot_max=k_r) to transform
  u'[i] in place in bank_se rel slots 0..K-1 -> u_hat[i]. Added ST_DEC_NTT to
  the bank_se read/write muxes and the NTT load/process/arm blocks alongside
  ST_N/ST_ENC_N.
- FSM: DECOMP -> SDEC -> NTT -> DONE.

TB verify_d2 checks s_hat[i] (bank_a slot i*K) and u_hat[i] (bank_se rel i)
against golden. verify_d1 narrowed to v' only: D2's in-place NTT overwrites u'
in bank_se, so u' correctness is now proven transitively via u_hat==NTT(u').

Verified: dec D2 K=2/3/4 all cases PASS; KeyGen + Encaps unregressed.
This commit is contained in:
2026-06-29 18:00:44 +08:00
parent ecc00d6dd5
commit 940946f30c
3 changed files with 92 additions and 40 deletions

View File

@@ -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