fix(sample_ntt): suppress spurious 257th valid_o after last_o
Phase-1 (d1 output) lacked the 'need_more' guard that phase-2 (d2) had, so when the 256th accepted coefficient was a d1 whose group then advanced, the FSM could fire one extra valid_o after last_o for certain seeds (e.g. KAT count=0 rho, seed i=0/j=1 emitted 257 pulses). In mlkem_top KeyGen this trailing pulse leaked into the next poly's index 0, shifting the stream. Fix: gate phase-1 d1 output with 'd1_acc_r && need_more' (mirrors phase-2). Applied to both sample_ntt_sync and sample_ntt_sync_shared. Standalone TB had a coverage blind spot (stopped reading at 256, never checked valid_o stayed low after last_o). Added a regression assertion: counts spurious post-last_o pulses and fails if any. Verified the assertion catches the bug on a reverted-fix copy (3 spurious) and passes on the fix. Verified: - 40-seed audit (sync) + 24-seed audit (shared): all exactly 256 pulses, last_o@256, zero post-last pulses. - Verilator vs hashlib oracle: 1536/1536 (no real coeff dropped). - Full framework regression: 4334/4334. - mlkem_top KeyGen Stage 2c: 2048/2048 A_hat+s+e coeffs exact.
This commit is contained in:
@@ -208,8 +208,22 @@ module tb_sample_ntt_xsim;
|
||||
// Wait for DUT to return to IDLE before next vector.
|
||||
// The DUT may still be processing its last Keccak permutation
|
||||
// (ST_WAIT → ST_DONE → ST_IDLE), which takes ~20+ cycles.
|
||||
while (!ready_o) begin
|
||||
@(posedge clk);
|
||||
// ASSERTION: after the 256th coeff (last_o), valid_o must stay
|
||||
// low until IDLE — any extra pulse is a spurious 257th output
|
||||
// (regression guard for the phase-1 need_more fix).
|
||||
begin
|
||||
integer extra_pulses;
|
||||
extra_pulses = 0;
|
||||
while (!ready_o) begin
|
||||
@(posedge clk);
|
||||
if (valid_o) extra_pulses = extra_pulses + 1;
|
||||
end
|
||||
if (extra_pulses != 0) begin
|
||||
$display("ERROR: Vector %0d emitted %0d spurious valid_o pulse(s) after last_o",
|
||||
idx, extra_pulses);
|
||||
fail_count = fail_count + 1;
|
||||
if (pass_count > 0) pass_count = pass_count - 1; // revoke the earlier PASS
|
||||
end
|
||||
end
|
||||
end // inner begin block
|
||||
end
|
||||
|
||||
@@ -334,7 +334,7 @@ module sample_ntt_sync #(parameter K = 4) (
|
||||
|
||||
// ----- Phase 1: output d1 -----
|
||||
2'd1: begin
|
||||
if (d1_acc_r) begin
|
||||
if (d1_acc_r && need_more) begin
|
||||
if (!valid_o_r) begin
|
||||
// First cycle: assert output
|
||||
coeff_o_r <= d1_r;
|
||||
@@ -349,7 +349,7 @@ module sample_ntt_sync #(parameter K = 4) (
|
||||
end
|
||||
end
|
||||
end else begin
|
||||
// d1 rejected, skip to phase 2
|
||||
// d1 rejected or no longer needed: skip to phase 2
|
||||
valid_o_r <= 1'b0;
|
||||
sq_phase_r <= 2'd2;
|
||||
end
|
||||
|
||||
@@ -348,7 +348,7 @@ module sample_ntt_sync_shared #(parameter K = 4) (
|
||||
|
||||
// ----- Phase 1: output d1 -----
|
||||
2'd1: begin
|
||||
if (d1_acc_r) begin
|
||||
if (d1_acc_r && need_more) begin
|
||||
if (!valid_o_r) begin
|
||||
// First cycle: assert output
|
||||
coeff_o_r <= d1_r;
|
||||
@@ -363,7 +363,7 @@ module sample_ntt_sync_shared #(parameter K = 4) (
|
||||
end
|
||||
end
|
||||
end else begin
|
||||
// d1 rejected, skip to phase 2
|
||||
// d1 rejected or no longer needed: skip to phase 2
|
||||
valid_o_r <= 1'b0;
|
||||
sq_phase_r <= 2'd2;
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user