From e30a65bd72c63fe17ce82f5454da3a141bef80f4 Mon Sep 17 00:00:00 2001 From: FallenSigh Date: Sat, 11 Jul 2026 07:42:35 +0800 Subject: [PATCH] perf(mlkem): pipeline CBD->bank write to cut 100 MHz critical path At 100 MHz the worst path (WNS -1.58 ns) was CBD sample: squeeze_buf[buf_ptr] extraction -> CBD add/sub -> mod-Q -> bank_t/bank_se write, all combinational in one cycle and routing-dominated (squeeze_buf and the banks place far apart). Register the CBD write (data/addr/enable) so the bank write commits one cycle later; the registered write takes priority in the bank_se/bank_t mux so the 1-cycle spillover into the next state uses the correct addr/data. No consumer reads these coeffs until the following stage (>1 cycle later), so no RAW hazard. Verified byte-exact vs NIST KAT: Encaps 9/9, Decaps 9/9, KeyGen 11/11, hello_world e2e -- all PASS. Co-Authored-By: Claude Opus 4.8 (1M context) --- rtl/ip/mlkem/top/mlkem_top.v | 44 +++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/rtl/ip/mlkem/top/mlkem_top.v b/rtl/ip/mlkem/top/mlkem_top.v index 306df7a..04362d8 100644 --- a/rtl/ip/mlkem/top/mlkem_top.v +++ b/rtl/ip/mlkem/top/mlkem_top.v @@ -312,15 +312,15 @@ module mlkem_top #( // D1 DECOMP writeback: cd_vo while decoding c2 writes v' to bank_a[DEC_VASLOT]. wire dec_v_we = (st == ST_DEC_DECOMP) && dec_in_c2 && cd_vo; wire [13:0] dec_v_wr = DEC_VASLOT*256 + dec_widx; - assign bt_we = ((st == ST_M) && pm_vo) || e2_we || + assign bt_we = ((st == ST_M) && pm_vo) || cbdw_bt_en || u_psum_we || u_intt_we || u_v_we || u_w_we; - assign bt_wa = e2_we ? (c_widx & ((1< [0,Q): add Q when negative wire [11:0] cbd_modq = cbd_coeff[11] ? (cbd_coeff + 12'd3329) : cbd_coeff; + // Pipeline the CBD -> bank write by one cycle. The squeeze_buf[buf_ptr] + // extraction -> CBD add/sub -> mod-Q -> BRAM-write route is long and + // routing-dominated (the mlkem_clk critical path at 100 MHz). Register the + // write data/addr/enable here; the bank write commits one cycle later. No + // consumer reads these coeffs until the following stage, which begins more + // than one cycle after the last CBD coeff, so there is no read-after-write + // hazard. The registered write takes priority in the bank_se/bank_t mux so + // the 1-cycle spillover into the next state uses the correct addr/data. + reg cbdw_se_en, cbdw_bt_en; + reg [4:0] cbdw_poly; + reg [7:0] cbdw_widx; + reg [11:0] cbdw_data; + always @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + cbdw_se_en <= 1'b0; cbdw_bt_en <= 1'b0; + cbdw_poly <= 5'd0; cbdw_widx <= 8'd0; cbdw_data <= 12'd0; + end else begin + cbdw_se_en <= ((st == ST_C) && c_busy && cbd_vo && cbd_ack) || + ((st == ST_ENC_C) && c_busy && cbd_vo && cbd_ack && + (c_poly < {1'b0, k_r, 1'b0})); + cbdw_bt_en <= (st == ST_ENC_C) && c_busy && cbd_vo && cbd_ack && + (c_poly == {1'b0, k_r, 1'b0}); + cbdw_poly <= c_poly; + cbdw_widx <= c_widx; + cbdw_data <= cbd_modq; + end + end + // ---- ntt_core: forward NTT (mode=0, no scaling) of s[i],e[i] in place ---- // N-stage bookkeeping: process slots S0,S1,E0,E1 (= SLOT_S0 + n_slot). reg [4:0] n_slot; // 0..2K (process s_hat[0..K-1] then e_hat[0..K-1])