feat(enc): Encaps E2 - sample y/e1/e2 (CBD eta1/eta2, r seed)

ST_ENC_C reuses the ST_C CBD datapath with Encaps muxes: seed=r (not sigma),
eta=eta1 for y[0..K-1] then eta2 for e1[0..K-1]/e2, nonce 0..2K. 2K+1 polys
(vs KeyGen 2K). y/e1 -> bank_se rel slots 0..2K-1; e2 -> bank_t rel slot 0
(free during C/N/U since TDEC is deferred to V-prep so the 28-slot banks hold
all of A+y_hat+e1+e2 at K=4 without resizing).

Bring-up golden via ml-kem-r dump_encaps_full (working-tree example):
vectors/encgold/ec_k2_c0_{y,e1,e2}.hex. Verified (K=2 c0) y[0..1],e1[0..1],e2
== ml-kem-r; A_hat (E1) and ss (E0) still pass; K=3/4 no timeout.
This commit is contained in:
2026-06-29 01:55:47 +08:00
parent 31c967c8a4
commit cdc5ce25b1
12 changed files with 2645 additions and 34 deletions

View File

@@ -212,12 +212,17 @@ module mlkem_top #(
.clk(clk), .rd_addr(bt_rd_addr), .rd_data(bt_rd_data),
.wr_en(bt_we), .wr_addr(bt_wa), .wr_data(bt_wd)
);
// bank_t write: KeyGen ST_M accumulate, or Encaps TDEC byteDecode12.
// ST_M: t_hat[m_i][m_oidx] <= (acc+product) mod Q when pm_vo.
// ST_ENC_TDEC: t_hat[td_poly][2*td_trip(+1)] <= decoded coeff (td_we/wa/wd).
assign bt_we = ((st == ST_M) && pm_vo) || td_we;
assign bt_wa = td_we ? td_wa : ((m_i*256 + m_oidx) & ((1<<PT_AW)-1));
assign bt_wd = td_we ? td_wd : m_accq;
// bank_t write: KeyGen ST_M accumulate, Encaps TDEC byteDecode12, or Encaps
// e2 (ST_ENC_C c_poly==2K -> bank_t rel slot 0, free during C/N/U).
wire e2_we = (st == ST_ENC_C) && c_busy && cbd_vo && cbd_ack &&
(c_poly == {1'b0, k_r, 1'b0}); // c_poly == 2K
assign bt_we = ((st == ST_M) && pm_vo) || td_we || e2_we;
assign bt_wa = td_we ? td_wa :
e2_we ? (c_widx & ((1<<PT_AW)-1)) :
((m_i*256 + m_oidx) & ((1<<PT_AW)-1));
assign bt_wd = td_we ? td_wd :
e2_we ? cbd_modq :
m_accq;
// Debug readback (registered for timing)
reg [11:0] dbg_coeff_r;
@@ -243,7 +248,11 @@ module mlkem_top #(
dbg_se_addr[PSE_AW-1:0];
// bank_se write port: ST_C CBD store (rel slot c_poly), ST_N NTT writeback
// (rel slot n_slot). Disjoint states.
// bank_se write: KeyGen ST_C CBD (s/e), Encaps ST_ENC_C CBD (y/e1, c_poly<2K;
// e2 at c_poly==2K goes to bank_t instead), ST_N NTT writeback. Disjoint.
assign bse_we = ((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})) ||
((st == ST_N) && ntt_vo);
assign bse_wa = (st == ST_N) ? ((n_slot*256 + n_widx) & ((1<<PSE_AW)-1))
: ((c_poly*256 + c_widx) & ((1<<PSE_AW)-1));
@@ -547,11 +556,19 @@ module mlkem_top #(
wire cbd_last;
reg cbd_ack;
// CBD inputs muxed KeyGen vs Encaps:
// KeyGen ST_C: seed=sigma, eta=eta1 (all polys), nonce=c_poly.
// Encaps ST_ENC_C: seed=r, nonce=c_poly; eta = eta1 for y (c_poly<K),
// eta2 for e1/e2 (c_poly>=K). nonce sequence 0..2K matches FIPS.
wire cbd_enc = (st == ST_ENC_C);
wire [255:0] cbd_seed = cbd_enc ? r_r : sigma_r;
wire [1:0] cbd_eta = cbd_enc ? ((c_poly < {2'b0, k_r}) ? eta1_rt : eta2_rt)
: eta1_rt;
sample_cbd_sync_shared u_cbd (
.clk(clk), .rst_n(rst_n),
.seed_i(sigma_r),
.seed_i(cbd_seed),
.nonce_i(c_nonce),
.eta_i(eta1_rt),
.eta_i(cbd_eta),
.valid_i(cbd_valid),
.ready_o(cbd_ready),
.coeff_o(cbd_coeff),
@@ -708,8 +725,9 @@ module mlkem_top #(
ST_ENC_H: if (h_phase == 2'd3) st_next = ST_ENC_G;
ST_ENC_G: if (sha3_vo) st_next = ST_ENC_LOAD; // (K,r) captured
ST_ENC_LOAD: if (rl_idx >= 6'd33) st_next = ST_ENC_A; // rho loaded
ST_ENC_A: if (a_pair >= kk_rt) st_next = ST_ENC_TDEC;
ST_ENC_TDEC: if (td_done) st_next = ST_DONE; // E1: stop here (A, t_hat ready)
ST_ENC_A: if (a_pair >= kk_rt) st_next = ST_ENC_C;
ST_ENC_C: if (c_poly >= {k_r, 1'b1}) st_next = ST_DONE; // E2: stop (2K+1 polys: y,e1,e2)
ST_ENC_TDEC: if (td_done) st_next = ST_DONE; // (TDEC deferred to V-prep later)
ST_DONE: st_next = ST_IDLE;
default: st_next = ST_IDLE;
endcase
@@ -869,8 +887,9 @@ module mlkem_top #(
end
end
// Arm C stage when A finishes
if (st == ST_A && st_next == ST_C) begin
// Arm C stage when A finishes (KeyGen ST_A or Encaps ST_ENC_A)
if ((st == ST_A && st_next == ST_C) ||
(st == ST_ENC_A && st_next == ST_ENC_C)) begin
cbd_valid <= 1'b1;
cbd_ack <= 1'b1;
c_poly <= 3'd0;
@@ -878,21 +897,26 @@ module mlkem_top #(
c_busy <= 1'b0;
end
// ---- ST_C: drive CBD, store 256 mod-q coeffs per poly ----
if (st == ST_C) begin
// ---- ST_C / ST_ENC_C: drive CBD, store 256 mod-q coeffs per poly ----
// KeyGen: 2K polys (s,e). Encaps: 2K+1 polys (y,e1,e2). Loop bound
// differs: KeyGen restarts while c_poly+1 < 2K; Encaps while < 2K+1.
if (st == ST_C || st == ST_ENC_C) begin
if (cbd_valid && cbd_ready) begin
cbd_valid <= 1'b0;
c_busy <= 1'b1;
end
if (c_busy && cbd_vo && cbd_ack) begin
// bank_se write is the combinational bse_we/bse_wa/bse_wd
// assigns (rel slot c_poly); here only advance counters.
if (cbd_last) begin
c_poly <= c_poly + 3'd1;
c_widx <= 8'd0;
c_busy <= 1'b0;
if (c_poly + 3'd1 < {1'b0, k_r, 1'b0}) cbd_valid <= 1'b1;
// restart for next poly while more remain (state-dependent bound)
if (st == ST_ENC_C) begin
if (c_poly + 3'd1 < {k_r, 1'b1}) cbd_valid <= 1'b1; // < 2K+1
end else begin
if (c_poly + 3'd1 < {1'b0, k_r, 1'b0}) cbd_valid <= 1'b1; // < 2K
end
end else begin
c_widx <= c_widx + 8'd1;
end