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.
16 KiB
16 KiB