perf(mlkem): stream compress feed (B1) — ~27% fewer Encaps cycles

Decouple the ENC_C1/C2 compress micro-sequence into a streaming
producer/collector instead of feeding one coeff and draining the whole
6-stage comp_decomp pipeline per coeff.

- comp_decomp_sync: ready_o = 1'b1 (fully pipelined, consumer never
  backpressures since ready_i is tied high), so feeders can issue a new
  coeff every cycle.
- mlkem_top ENC_C1/C2: producer feeds one coeff every 2 cycles
  (<=5.5 bit/cyc, under the single-port ct drain of 8 bit/cyc so the bit
  buffer stays bounded); a per-cycle collector appends compressed bits
  and drains one ct byte/cycle.

Verified byte-exact against NIST KAT (Vivado 2019.2 XSIM) for KeyGen,
Encaps (9/9), Decaps (9/9 incl. reject path + DECOMP), hello_world e2e.

K=2 Encaps: ENC_C1 6273->1035, ENC_C2 2945->522, total 27851->20190.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 02:00:03 +08:00
parent 6a1b374bc1
commit ce40a6df42
2 changed files with 103 additions and 71 deletions

View File

@@ -75,9 +75,13 @@ module comp_decomp_sync (
wire pipe_busy = valid_s0 | valid_s1 | valid_s2 | valid_s3 | valid_s4 | valid_s5;
// Accept a new command only when the pipeline and output slot are both free.
// Current users issue one coefficient at a time and wait for valid_o.
assign ready_o = !pipe_busy && (!valid_r || ready_i);
// Fully pipelined: accept one coefficient per cycle. Every stage advances
// unconditionally (valid_sN <= valid_s(N-1)), and the sole consumer ties
// ready_i high, so the output register never needs to backpressure the
// pipeline. Streaming feeders (Encaps C1/C2, Decaps DECOMP) can therefore
// issue a new coeff every cycle instead of draining the 6-stage pipe each
// time. pipe_busy retained only for the legacy drain-based users' info.
assign ready_o = 1'b1;
assign coeff_out = data_r;
assign valid_o = valid_r;