// mlkem_top.v - ML-KEM KeyGen top-level integration. Runtime-selectable // parameter set via k_i: k=2 (ML-KEM-512, eta1=3), k=3 (768), k=4 (1024). // Storage is sized for KMAX (worst case = ML-KEM-1024); k_i picks the // active sub-range at start_i. // // Streaming valid/ready interface. Given seeds d and z, computes the // ML-KEM key pair per FIPS 203 Algorithm 16 (KeyGen_internal): // (rho,sigma) = G(d || K) // A_hat[i][j] = SampleNTT(rho || j || i) i,j in 0..K-1 // s[i] = CBD3(PRF(sigma, i)), e[i] = CBD3(PRF(sigma, K+i)) // s_hat[i] = NTT(s[i]); e_hat[i] = NTT(e[i]) // t_hat[i] = e_hat[i] + sum_j A_hat[i][j] o s_hat[j] // ek = byteEncode12(t_hat[0..K-1]) || rho // dk = byteEncode12(s_hat[0..K-1]) || ek || H(ek) || z // // Built incrementally and verified stage-by-stage against ml-kem-r golden // vectors (test_framework/modules/mlkem_keygen/golden) and NIST KAT. // // Uses independent (verified) leaf modules, each with its own keccak_core: // sha3_top, sample_ntt_sync, sample_cbd_sync, ntt_core, poly_mul_sync, // mod_add_sync. No shared-keccak arbiter. `include "sync_rtl/common/defines.vh" module mlkem_top #( parameter KMAX = 4 // storage sizing (worst case = ML-KEM-1024) ) ( input clk, input rst_n, input [2:0] k_i, // RUNTIME ML-KEM param: 2=512, 3=768, 4=1024 input op_i, // 0 = KeyGen, 1 = Encaps (captured at start_i) input [255:0] d_i, // KeyGen seed d (byte 0 in d_i[7:0]) input [255:0] z_i, // implicit-rejection seed z input [255:0] msg_i, // Encaps message m (byte 0 in msg_i[7:0]) input start_i, // pulse to begin KeyGen output busy_o, // high while running output done_o, // pulse when ek/dk ready // Encaps ek input: stream ek bytes (=pk) into ek_bram before start_i with // op_i=1. ek_in_we writes byte ek_in_byte at address ek_in_addr. input ek_in_we, // write one ek byte (only honored in ST_IDLE for Encaps preload) input [10:0] ek_in_addr, // ek byte address 0..ek_bytes-1 input [7:0] ek_in_byte, // ek byte value // Encaps shared secret output (= K), valid at done_o output [255:0] ss_o, // Encaps ciphertext readback tap: ct byte by index 0..ct_bytes-1 input [10:0] dbg_ct_idx_i, output [7:0] dbg_ct_o, // Debug readback tap: read one stored coefficient by (poly slot, index). // Lets stage TBs verify intermediates without wide buses. input [3:0] dbg_slot_i, // poly slot (see localparams below) input [7:0] dbg_idx_i, // coefficient index 0..255 output [11:0] dbg_coeff_o, // Debug byte readback: ek (sel=0, 0..799) / dk_pke (sel=1, 0..767) input dbg_byte_sel_i, input [10:0] dbg_byte_idx_i, output [7:0] dbg_byte_o, // Debug full-dk readback: dk = dk_pke(768) || ek(800) || H(ek)(32) || z(32) // = 1632 bytes. Index 0..1631. input [11:0] dbg_dk_idx_i, output [7:0] dbg_dk_o, // Debug taps for hash outputs output [255:0] dbg_rho_o, output [255:0] dbg_sigma_o, // Encaps debug taps: r (G high half) and H(ek) output [255:0] dbg_r_o, output [255:0] dbg_hek_o ); localparam Q = `Q; // 3329 // Runtime ML-KEM parameter, captured at start_i. reg [2:0] k_r; // FIPS 203: eta1 = 3 for ML-KEM-512 (k=2), else 2 (k=3/4). wire [1:0] eta1_rt = (k_r == 3'd2) ? 2'd3 : 2'd2; // Runtime-derived sizes (k_r in {2,3,4}). Small multiplies are cheap. wire [5:0] kk_rt = k_r * k_r; // 4/9/16 wire [5:0] slot_s_rt = kk_rt; // s_hat base slot wire [5:0] slot_e_rt = kk_rt + k_r; // e_hat base slot wire [5:0] slot_t_rt = kk_rt + {1'b0, k_r} + {1'b0, k_r}; // t_hat base = kk+2k wire [11:0] ek_bytes_rt = 12'd384 * {9'b0, k_r} + 12'd32; // 800/1184/1568 wire [11:0] dk_bytes_rt = 12'd384 * {9'b0, k_r}; // 768/1152/1536 // H(ek) block count = ceil((ek_bytes+1)/136): 6/9/12 for k=2/3/4 (table) wire [3:0] h_nblk_rt = (k_r == 3'd2) ? 4'd6 : (k_r == 3'd3) ? 4'd9 : 4'd12; wire [11:0] h_last_rt = {6'b0, h_nblk_rt} * 12'd136 - 12'd1; // final padded byte index // ---- Encaps runtime params ---- reg op_r; // 0=KeyGen 1=Encaps (captured at start) reg [255:0] m_r; // Encaps message m (captured at start) reg [255:0] ss_r; // Encaps shared secret K (= G output low half) reg [255:0] r_r; // Encaps PRF seed r (= G output high half) // FIPS 203: eta2 = 2 for all parameter sets. wire [1:0] eta2_rt = 2'd2; // Compression params: (du,dv) = (10,4) for k=2/3, (11,5) for k=4. wire [4:0] du_rt = (k_r == 3'd4) ? 5'd11 : 5'd10; wire [4:0] dv_rt = (k_r == 3'd4) ? 5'd5 : 5'd4; // Ciphertext byte sizes: c1 = 32*du*k, c2 = 32*dv, ct = c1+c2. wire [11:0] c1_bytes_rt = 12'd32 * {7'b0, du_rt} * {9'b0, k_r}; // 640/960/1408 wire [11:0] c2_bytes_rt = 12'd32 * {7'b0, dv_rt}; // 128/128/160 wire [11:0] ct_bytes_rt = c1_bytes_rt + c2_bytes_rt; // 768/1088/1568 assign ss_o = ss_r; // ---- E1: rho-load + byteDecode12 bookkeeping ---- // rho load: read 32 bytes from ek_bram at offset 384*k into rho_r. reg [5:0] rl_idx; // 0..33 read-ahead pointer over rho bytes reg [5:0] rl_widx; // 0..31 byte being written (1 cyc behind rl_idx) reg rl_vld; // a byte is arriving this cycle wire [11:0] rl_ek_off = dk_bytes_rt + {6'b0, rl_idx}; // ek addr = 384k + idx // byteDecode12: read 3 ek bytes -> 2 coeffs into bank_t. Walk K polys * 128 // triples. 5-cycle micro-phase per triple: ph0..2 fetch b0,b1,b2 (read-ahead, // 1-cycle bram latency), ph3 write c0, ph4 write c1. reg [2:0] td_poly; // 0..k-1 t_hat poly index reg [7:0] td_trip; // 0..127 triple within poly reg [2:0] td_ph; // 0..4 micro-phase reg [7:0] td_b0, td_b1, td_b2; // captured ek bytes reg td_done; // ek byte address for the triple: poly*384 + trip*3 + (ph for ph<3) wire [11:0] td_base = {td_poly, 7'd0} * 12'd3; // poly*384 (=poly*256*1.5)... see below // poly*384 + trip*3 + byteidx; byteidx = ph (0,1,2) during fetch phases wire [11:0] td_byteidx = {2'd0, td_trip, 1'b0} + {3'd0, td_trip}; // trip*3 wire [11:0] td_ekaddr = ({4'd0,td_poly}*12'd384) + td_byteidx + {9'd0, td_ph}; // decoded coeffs: c0 = b0 | ((b1&0xF)<<8); c1 = (b1>>4) | (b2<<4) wire [11:0] td_c0 = {td_b1[3:0], td_b0}; wire [11:0] td_c1 = {td_b2, td_b1[7:4]}; // bank_a write for TDEC (registered); t_hat[td_poly] -> bank_a slot // td_poly*K. td_wa is a bank_a byte address (PA_AW=12). reg td_we; reg [11:0] td_wa; // bank_a address (PA_AW=12) reg [11:0] td_wd; // ---- ct_bram: ciphertext byte buffer (<=1568 B). Written by E5/E7 // (compress + byteEncode_du/dv), read back via dbg_ct tap. ---- wire [10:0] ct_rd_addr; wire [7:0] ct_rd_data; reg ct_we; reg [10:0] ct_wa; reg [7:0] ct_wd; sd_bram #(.W(8), .D(2048), .A(11)) u_ct_bram ( .clk(clk), .rd_addr(ct_rd_addr), .rd_data(ct_rd_data), .wr_en(ct_we), .wr_addr(ct_wa), .wr_data(ct_wd) ); assign ct_rd_addr = dbg_ct_idx_i; assign dbg_ct_o = ct_rd_data; // ================================================================ // Polynomial storage, sized for KMAX (worst case). Runtime k uses a // sub-range. Slot layout (each slot = 256 coeffs): // A_hat[i][j] : slots 0 .. k*k-1 at index i*k + j // s_hat[i] : slots slot_s_rt .. +k-1 // e_hat[i] : slots slot_e_rt .. +k-1 // t_hat[i] : slots slot_t_rt .. +k-1 // NUM_SLOTS = KMAX*KMAX + 3*KMAX = 28 for KMAX=4. // ================================================================ localparam NUM_SLOTS = KMAX*KMAX + 3*KMAX; localparam SAW = 5; // slot-address width (>=clog2(28)) // ================================================================ // Coefficient storage as 3 banks (Phase 2). Was one async-read reg // array polymem[0:28*256-1]; split by polynomial so each becomes a // registered-read sd_bram (infers BRAM / ASIC SRAM) in the final step. // bank_a : A_hat[i][j] slots 0..K^2-1 -> D=KMAX*KMAX*256=4096 // bank_se: s_hat||e_hat slots slot_s.. -> D=2*KMAX*256=2048 (rel 0..2K-1) // bank_t : t_hat[i] slots slot_t.. -> D=KMAX*256=1024 (rel 0..K-1) // Addresses are base-relative: bank index = abs_slot - base_slot. // Stage 2b (in progress): per-consumer registered read-ahead. Banks are // still async reg arrays here; converted consumers read via their own // 1-cycle pipeline reg (== sd_bram timing). Once every consumer of a // bank is registered, the array is replaced by an sd_bram with its read // port muxed across the (phase-disjoint) consumers. // ================================================================ localparam PA_AW = 12; // bank_a addr width (4096) localparam PSE_AW = 11; // bank_se addr width (2048) localparam PT_AW = 10; // bank_t addr width (1024) // bank_a: A_hat. Reader = ST_M load (pm_a) + dbg. Writer = ST_A. // Read port muxed by phase; sd_bram's internal rd_addr_r register is the // 1-cycle read pipeline (replaces the explicit pm_a_rd reg). Write port is // driven combinationally (sd_bram registers the write at posedge, matching // the old reg-array nonblocking write timing). wire [PA_AW-1:0] ba_rd_addr; wire [11:0] ba_rd_data; wire ba_we; wire [PA_AW-1:0] ba_wa; wire [11:0] ba_wd; sd_bram #(.W(12), .D(1<0 running t_hat) + ST_E ek-half + dbg. // Writer = ST_M acc. Read port muxed by phase; sd_bram's rd_addr_r is the // 1-cycle read register. RMW is safe: the acc read addr leads the write // addr by 1 (m_oidx+1 vs m_oidx), so read/write never alias in a cycle and // sd_bram write-first == reg-array read-old. Write port combinational. wire [PT_AW-1:0] bt_rd_addr; wire [11:0] bt_rd_data; wire bt_we; wire [PT_AW-1:0] bt_wa; wire [11:0] bt_wd; sd_bram #(.W(12), .D(1< bank_t rel slot 0, free during C/N/U). (TDEC writes bank_a now.) wire e2_we = (st == ST_ENC_C) && c_busy && cbd_vo && cbd_ack && (c_poly == {1'b0, k_r, 1'b0}); // c_poly == 2K // Encaps E4/E6 bank_t writes: MAC psum (u_sub0, on pm_vo) and INTT in-place // (u_sub1, on ntt_vo). Both target bank_t rel slot UPSUM. ST_ENC_V (E6) adds // the V-ADD writeback (psum + e2 + mu) at UPSUM via u_v_we below. wire u_psum_we = (st == ST_ENC_U || st == ST_ENC_V) && (u_sub == 2'd0) && pm_vo; wire u_intt_we = (st == ST_ENC_U || st == ST_ENC_V) && (u_sub == 2'd1) && ntt_vo; assign bt_we = ((st == ST_M) && pm_vo) || e2_we || u_psum_we || u_intt_we || u_v_we; assign bt_wa = e2_we ? (c_widx & ((1<= slot_t_rt) dbg_coeff_r <= bt_rd_data; // bank_t (sd_bram) else if (dbg_slot_i >= slot_s_rt) dbg_coeff_r <= bse_rd_data; // bank_se (sd_bram) else dbg_coeff_r <= ba_rd_data; // bank_a (sd_bram) end assign dbg_coeff_o = dbg_coeff_r; // ek and dk_pke byte memories sized for KMAX. localparam EK_MAX = 384*KMAX + 32; // 1568 localparam DK_MAX = 384*KMAX; // 1536 // ek / dk_pke byte storage in BRAM (sd_bram: 1R/1W, 1-cycle read latency). // Each single read port is shared time-disjointly: ST_H assemble reads ek // (in ST_H), debug readback reads ek/dk (in ST_DONE). Write port driven by // ST_E (1 byte/cycle) and the rho-tail copy. wire [10:0] ek_rd_addr, dkp_rd_addr; wire [7:0] ek_rd_data, dkp_rd_data; reg ek_we, dkp_we; reg [10:0] ek_wa, dkp_wa; reg [7:0] ek_wd, dkp_wd; // ek BRAM write port: KeyGen ST_E drives ek_we/ek_wa/ek_wd; Encaps preloads // ek from the external ek_in_* port (TB streams ek=pk before start_i). The // two never overlap (preload happens in ST_IDLE before an Encaps run). wire ek_we_mux = ek_in_we ? 1'b1 : ek_we; wire [10:0] ek_wa_mux = ek_in_we ? ek_in_addr : ek_wa; wire [7:0] ek_wd_mux = ek_in_we ? ek_in_byte : ek_wd; sd_bram #(.W(8), .D(2048), .A(11)) u_ek_bram ( .clk(clk), .rd_addr(ek_rd_addr), .rd_data(ek_rd_data), .wr_en(ek_we_mux), .wr_addr(ek_wa_mux), .wr_data(ek_wd_mux) ); sd_bram #(.W(8), .D(2048), .A(11)) u_dkp_bram ( .clk(clk), .rd_addr(dkp_rd_addr), .rd_data(dkp_rd_data), .wr_en(dkp_we), .wr_addr(dkp_wa), .wr_data(dkp_wd) ); // full dk = dk_pke(dk_bytes) || ek(ek_bytes) || H(ek)(32) || z(32) wire [11:0] dk_ek_end = dk_bytes_rt + ek_bytes_rt; // ek region end wire [11:0] dk_hek_end = dk_ek_end + 12'd32; // H(ek) region end // Debug-region selects for dk readback (combinational region decode). wire dbgdk_in_dkp = (dbg_dk_idx_i < dk_bytes_rt); wire dbgdk_in_ek = (dbg_dk_idx_i >= dk_bytes_rt) && (dbg_dk_idx_i < dk_ek_end); // Read-address muxes (declared here; depend on ST_H h_g defined below via // the function-free expression h_blk*136 + h_byte). See assigns after the // H-stage register declarations. // dbg_byte_o / dbg_dk_o are combinational taps over the (registered) BRAM // read data; net 1-cycle latency, well within the TB's 2-cycle read wait. assign dbg_byte_o = dbg_byte_sel_i ? dkp_rd_data : ek_rd_data; assign dbg_dk_o = dbgdk_in_dkp ? dkp_rd_data : dbgdk_in_ek ? ek_rd_data : (dbg_dk_idx_i < dk_hek_end) ? hek_r[(dbg_dk_idx_i - dk_ek_end)*8 +: 8] : z_i[(dbg_dk_idx_i - dk_hek_end)*8 +: 8]; // ================================================================ // Top-level FSM (built incrementally). Stage 2a: G only. // ================================================================ localparam ST_IDLE = 5'd0; localparam ST_G = 5'd1; // run G(d||K), capture rho/sigma localparam ST_A = 5'd2; // generate A_hat[i][j] via SampleNTT localparam ST_C = 5'd3; // generate s[i],e[i] via CBD localparam ST_N = 5'd4; // forward NTT of s[i],e[i] in place localparam ST_M = 5'd5; // matrix accumulate t_hat = e_hat + sum A o s_hat localparam ST_E = 5'd6; // byteEncode12 -> ek/dk BRAM localparam ST_H = 5'd7; // H(ek) via multi-block SHA3-256 // ---- Encaps states ---- localparam ST_ENC_LOAD = 5'd8; // ek already preloaded; placeholder/settle localparam ST_ENC_H = 5'd9; // H(ek) via multi-block SHA3-256 (reuses ST_H logic) localparam ST_ENC_G = 5'd10; // (K,r) = G(m || H(ek)), 64-byte single block localparam ST_ENC_A = 5'd11; // regenerate A_hat (transpose used in U) localparam ST_ENC_TDEC = 5'd12; // byteDecode12: ek -> t_hat (bank_a slot j*K) localparam ST_ENC_C = 5'd13; // sample y,e1,e2 via CBD (eta1/eta2) localparam ST_ENC_N = 5'd14; // forward NTT of y in place localparam ST_ENC_U = 5'd15; // u = INTT(sum A^T o y_hat) + e1 localparam ST_ENC_C1 = 5'd16; // Compress_du + byteEncode_du -> ct c1 localparam ST_ENC_V = 5'd17; // v = INTT(sum t_hat o y_hat) + e2 + mu localparam ST_ENC_C2 = 5'd18; // Compress_dv + byteEncode_dv -> ct c2 localparam ST_ENC_E2MV = 5'd19; // relocate e2 bank_t[0] -> bank_a[E2_ASLOT] localparam ST_DONE = 5'd31; reg [4:0] st, st_next; reg [255:0] rho_r, sigma_r; // A-generation bookkeeping: explicit i/j counters (avoid runtime divide) reg [2:0] a_i; // row 0..k-1 reg [2:0] a_j; // col 0..k-1 reg [4:0] a_pair; // 0..k*k pairs done (for done test) reg [7:0] a_widx; // write index 0..255 within current poly reg a_busy; // 1 once current pair's request accepted (gates collect) wire [SAW-1:0] a_slot = a_i*k_r + a_j; // A_hat[i][j] slot = i*k + j // C-generation bookkeeping: 2*k polys (s[0..k-1] then e[0..k-1]) reg [4:0] c_poly; // 0..2k reg [7:0] c_widx; reg c_busy; wire [7:0] c_nonce = {3'b0, c_poly}; // s:0..k-1 e:k..2k-1 == nonce // slot: c_poly < k -> s_hat[c_poly], else e_hat[c_poly-k] wire [SAW-1:0] c_slot = (c_poly < {2'b0, k_r}) ? (slot_s_rt + c_poly) : (slot_e_rt + (c_poly - {2'b0, k_r})); assign busy_o = (st != ST_IDLE); assign done_o = (st == ST_DONE); assign dbg_rho_o = rho_r; assign dbg_sigma_o = sigma_r; assign dbg_r_o = r_r; assign dbg_hek_o = hek_r; // ---- sha3_top in G mode: data_i = {K_byte, d} (d byte0 in [7:0]) ---- reg sha3_valid; wire sha3_ready; wire [511:0] sha3_hash; wire sha3_vo; reg sha3_ack; // consumer ready for hash wire [511:0] kg_g_data = {248'b0, 5'b0, k_r, d_i}; // KeyGen G: [263:256]=k, [255:0]=d wire [511:0] enc_g_data = {hek_r, m_r}; // Encaps G: m || H(ek), 64 bytes wire [511:0] g_data = (st == ST_ENC_G) ? enc_g_data : kg_g_data; // ================================================================ // Shared keccak_core + phase mux (3 consumers -> 1 core) // // G/H (u_sha3), SampleNTT (u_snt) and CBD (u_cbd) each need a Keccak // permutation, but they run in DISJOINT top-FSM phases (ST_G/ST_H, ST_A, // ST_C respectively), so at most one is ever active. One keccak_core // serves all three: kc_state_i/kc_valid_i are muxed by phase; kc_valid_o // is broadcast but GATED per consumer so an inactive consumer never // latches a permutation result meant for another (the samplers latch // squeeze state unconditionally on their kc_valid_o input). // ================================================================ wire [1599:0] kc_state_o; // shared core output (to all consumers) wire kc_valid_o; // shared core output-valid wire kc_ready_o; // shared core input-ready // per-consumer drives toward the core wire [1599:0] sha3_kc_state_i, snt_kc_state_i, cbd_kc_state_i; wire sha3_kc_valid_i, snt_kc_valid_i, cbd_kc_valid_i; /* verilator lint_off UNUSEDSIGNAL */ wire sha3_kc_ready_i, snt_kc_ready_i, cbd_kc_ready_i; // all 1'b1 /* verilator lint_on UNUSEDSIGNAL */ // phase selects (mutually exclusive). Encaps adds ST_ENC_H/G (sha3), // ST_ENC_A (snt), ST_ENC_C (cbd). wire sel_sha3 = (st == ST_G) || (st == ST_H) || (st == ST_ENC_H) || (st == ST_ENC_G); wire sel_snt = (st == ST_A) || (st == ST_ENC_A); wire sel_cbd = (st == ST_C) || (st == ST_ENC_C); // gated output-valid: only the active consumer sees kc_valid_o wire kc_valid_o_sha3 = kc_valid_o & sel_sha3; wire kc_valid_o_snt = kc_valid_o & sel_snt; wire kc_valid_o_cbd = kc_valid_o & sel_cbd; // input mux: route the active consumer's request to the core wire [1599:0] kc_state_i_mux = sel_snt ? snt_kc_state_i : sel_cbd ? cbd_kc_state_i : sha3_kc_state_i; wire kc_valid_i_mux = sel_snt ? snt_kc_valid_i : sel_cbd ? cbd_kc_valid_i : sel_sha3 ? sha3_kc_valid_i : 1'b0; keccak_core #(.ROUNDS(24)) u_keccak ( .clk(clk), .rst_n(rst_n), .state_i(kc_state_i_mux), .valid_i(kc_valid_i_mux), .ready_o(kc_ready_o), .state_o(kc_state_o), .valid_o(kc_valid_o), .ready_i(1'b1) // consumers always accept (kc_ready_i=1) ); // ---- single shared sha3_top serving BOTH G and H ---- // G (ST_G) uses single-block mode (mb_en=0); H(ek) (ST_H) uses the // multi-block absorb path (mb_en=1). These phases are disjoint in the // top FSM, so one sha3_top (one keccak_core) is sufficient. mb_en and // ready_i are muxed by phase; data_i/mode only matter while mb_en=0. wire sha3_mb_en = (st == ST_H) || (st == ST_ENC_H); // sha3 single-block mode: 2'b00 = G(33B) for KeyGen, 2'b11 = G(64B) for // Encaps (m||H(ek)). mode only matters when mb_en=0. wire [1:0] sha3_mode = (st == ST_ENC_G) ? 2'b11 : 2'b00; sha3_top_shared u_sha3 ( .clk(clk), .rst_n(rst_n), .mode(sha3_mode), // G = SHA3-512 (only used when mb_en=0) .data_i(g_data), .valid_i(sha3_valid), .ready_o(sha3_ready), .hash_o(sha3_hash), .valid_o(sha3_vo), .ready_i(sha3_mb_en ? h_ack : sha3_ack), .mb_en(sha3_mb_en), .mb_block_i(h_block_r), .mb_valid_i(h_mbvalid), .mb_last_i(h_mblast), .mb_ready_o(h_mbready), // shared keccak_core interface (gated by phase in the mux below) .kc_state_o(kc_state_o), .kc_valid_o(kc_valid_o_sha3), .kc_ready_o(kc_ready_o), .kc_state_i(sha3_kc_state_i), .kc_valid_i(sha3_kc_valid_i), .kc_ready_i(sha3_kc_ready_i) ); // ---- multi-block H(ek) state (SHA3-256, 6/9/12 blocks); fed to shared u_sha3 ---- reg [1087:0] h_block_r; // current pre-padded rate block reg h_mbvalid; reg h_mblast; wire h_mbready; wire [511:0] h_hash; wire h_vo; reg h_ack; reg [255:0] hek_r; // captured H(ek) reg [3:0] h_blk; // 0..H_NBLK-1 block index (up to 11 for K=4) reg [7:0] h_byte; // 0..136 byte address being presented (assemble) reg [1:0] h_phase; // 0=assemble 1=feed 2=wait-perm 3=done // ek BRAM read is registered (1-cycle latency); assemble presents the // address for h_byte this cycle and writes the byte that arrived (for the // address presented last cycle) into h_block_r. Writeback pipeline regs: reg h_wb_vld; // a byte is arriving this cycle reg [7:0] h_wb_idx; // its position within the 136-byte block reg [11:0] h_wb_g; // its global ek byte index reg [7:0] h_wb_pad; // pad constant to use if g is out of ek range reg h_wb_inek; // 1 if g is within ek range (use BRAM data) // h_hash / h_vo are now served by the shared u_sha3 above (mb_en=1 during // ST_H). They alias the single core's outputs; the H consumer logic below // already gates on st==ST_H, and u_sha3's valid_o/hash_o are mb-selected. assign h_hash = sha3_hash; assign h_vo = sha3_vo; // SHA3-256 over ek (ek_bytes_rt bytes): rate=136. Padded length = h_nblk_rt*136. // pad: byte ek_bytes_rt = 0x06 (domain + first pad bit), last byte |= 0x80. // byte b (0..135) of block blk: global g = blk*136 + b. // ek bytes come from the ek BRAM (registered read); only the pad/zero bytes // are constants, returned by h_padconst. Reads runtime ek_bytes_rt/h_last_rt. function [7:0] h_padconst(input [3:0] blk, input [7:0] b); integer g; begin g = blk*136 + b; if (g == h_last_rt && g == ek_bytes_rt) h_padconst = 8'h86; // 0x06|0x80 else if (g == ek_bytes_rt) h_padconst = 8'h06; else if (g == h_last_rt) h_padconst = 8'h80; else h_padconst = 8'h00; end endfunction // Global ek byte index for the address currently presented in ST_H assemble. wire [11:0] h_g_addr = {8'd0, h_blk} * 12'd136 + {4'd0, h_byte}; // ek BRAM read-address mux: ST_H assemble drives it; otherwise debug readback. // dbg_dk ek-region takes priority (its loop runs after the ek loop), so the // single read port serves dbg_byte_o (ek loop) then dbg_dk_o (dk loop). wire [11:0] dbgdk_ek_off = dbg_dk_idx_i - dk_bytes_rt; // offset within ek region assign ek_rd_addr = (st == ST_H || st == ST_ENC_H) ? h_g_addr[10:0] : (st == ST_ENC_LOAD) ? rl_ek_off[10:0] : // rho load (st == ST_ENC_TDEC) ? td_ekaddr[10:0] : // byteDecode12 dbgdk_in_ek ? dbgdk_ek_off[10:0] : dbg_byte_idx_i; // dkp BRAM read-address mux: dbg_byte (sel=1) or dbg_dk (dkp region). assign dkp_rd_addr = dbg_byte_sel_i ? dbg_byte_idx_i : dbg_dk_idx_i[10:0]; // ---- sample_ntt_sync: Â[i][j] = SampleNTT(rho || j || i) ---- reg snt_valid; wire snt_ready; wire [11:0] snt_coeff; wire snt_vo; wire snt_last; reg snt_ack; // we accept coeffs sample_ntt_sync_shared #(.K(KMAX)) u_snt ( .clk(clk), .rst_n(rst_n), .rho_i(rho_r), .k_i(k_r), .i_idx(a_i[1:0]), .j_idx(a_j[1:0]), .valid_i(snt_valid), .ready_o(snt_ready), .coeff_o(snt_coeff), .valid_o(snt_vo), .ready_i(snt_ack), .last_o(snt_last), // shared keccak_core interface .kc_state_o(kc_state_o), .kc_valid_o(kc_valid_o_snt), .kc_ready_o(kc_ready_o), .kc_state_i(snt_kc_state_i), .kc_valid_i(snt_kc_valid_i), .kc_ready_i(snt_kc_ready_i) ); // ---- sample_cbd_sync: s[i]=CBD3(PRF(sigma,i)), e[i]=CBD3(PRF(sigma,K+i)) ---- reg cbd_valid; wire cbd_ready; wire [11:0] cbd_coeff; // 12-bit signed (two's complement) wire cbd_vo; 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). 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(cbd_seed), .nonce_i(c_nonce), .eta_i(cbd_eta), .valid_i(cbd_valid), .ready_o(cbd_ready), .coeff_o(cbd_coeff), .valid_o(cbd_vo), .ready_i(cbd_ack), .last_o(cbd_last), // shared keccak_core interface .kc_state_o(kc_state_o), .kc_valid_o(kc_valid_o_cbd), .kc_ready_o(kc_ready_o), .kc_state_i(cbd_kc_state_i), .kc_valid_i(cbd_kc_valid_i), .kc_ready_i(cbd_kc_ready_i) ); // signed (two's complement) -> [0,Q): add Q when negative wire [11:0] cbd_modq = cbd_coeff[11] ? (cbd_coeff + 12'(Q)) : cbd_coeff; // ---- 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]) reg [8:0] n_ridx; // load read-AHEAD pointer 0..256 (leads consume by 1) reg [7:0] n_widx; // output write index 0..255 reg n_valid; // feeding coeffs to ntt_core (delayed 1 cyc vs n_ridx) reg n_loading; // 1 while presenting load addresses to bank_se reg n_pending; // waiting for ntt_core IDLE to start next slot wire [SAW-1:0] n_slot_addr = slot_s_rt + n_slot; // s_hat then e_hat contiguous // NTT slot count: KeyGen processes 2K (s,e); Encaps processes K (y only). wire [4:0] n_slot_max = (st == ST_ENC_N) ? {2'b0, k_r} : {1'b0, k_r, 1'b0}; wire ntt_ready; wire [11:0] ntt_coeff; wire ntt_vo; wire ntt_done; // bank_se read addr for the NTT load (relative slot = n_slot); sd_bram // registers it into bse_rd_data, which feeds ntt_core 1 cycle later. wire [13:0] ntt_rd_full = n_slot*256 + n_ridx[7:0]; // ntt_core inputs muxed: KeyGen/Encaps fwd NTT feeds bse_rd_data (mode 0); // Encaps E4/E6 INTT sub-phase feeds psum (bt_rd_data) with mode 1. wire u_intt_act = (st == ST_ENC_U || st == ST_ENC_V) && (u_sub == 2'd1); wire [11:0] ntt_in = u_intt_act ? bt_rd_data : bse_rd_data; wire ntt_vin = u_intt_act ? u_nvalid : n_valid; wire ntt_mode = u_intt_act ? 1'b1 : 1'b0; ntt_core u_ntt ( .clk(clk), .rst_n(rst_n), .coeff_in(ntt_in), .valid_i(ntt_vin), .ready_o(ntt_ready), .mode(ntt_mode), // 0 = fwd (NTT), 1 = inv (INTT, x3303) .coeff_out(ntt_coeff), .valid_o(ntt_vo), .ready_i(1'b1), // always accept output .done_o(ntt_done) ); // ---- poly_mul_sync: t_hat[i] = e_hat[i] + sum_j A_hat[i][j] o s_hat[j] ---- // M-stage bookkeeping. For each (i,j): LOAD 256 (A,shat) pairs, then accumulate // 256 products into T_i (init from E_i when j==0, else from running T_i). reg [2:0] m_i; // row 0..K (needs to reach K to exit) reg [2:0] m_j; // col 0..K-1 reg [8:0] m_ld; // load index 0..256 reg [7:0] m_oidx; // output/accum index 0..255 reg m_loading; // 1 while streaming pairs into poly_mul reg m_pending; // wait for poly_mul IDLE before next (i,j) // ---- Stage 2f: byteEncode12 serializer ---- // Pack each poly (2 coeffs -> 3 bytes, LSB-first 12-bit). ek = t_hat[0..K-1] // bytes || rho; dk_pke = s_hat[0..K-1] bytes. Walk coeff pairs per poly. reg [4:0] e_poly; // 0..2K-1: [0,K) = t_hat -> ek; [K,2K) = s_hat -> dk_pke reg [7:0] e_pair; // 0..127 coeff-pair within poly reg [9:0] e_rho; // 0..31 rho byte copy index (ek tail) reg e_done; // serialization complete // source poly slot: t_hat[e_poly] for ek half, s_hat[e_poly-K] for dk half wire e_is_dk = (e_poly >= {1'b0, k_r}); wire [4:0] e_pidx = e_is_dk ? (e_poly - {1'b0, k_r}) : e_poly; // index within target // Registered single-port read (sd_bram timing). One bank read per cycle: // ek half reads bank_t (t_hat), dk half reads bank_se (s_hat). The two // coeffs of a pair are serialized across a 4-cycle micro-phase e_ph: // 0 = fetch c0 // 1 = c0 ready: write b0, save c0[11:8], fetch c1 // 2 = c1 ready: write b1, save c1[11:4] // 3 = write b2, advance pair reg [1:0] e_ph; // per-pair micro-phase 0..3 reg [3:0] e_c0_hi; // saved c0[11:8] for b1 reg [7:0] e_c1_hi; // saved c1[11:4] for b2 wire e_rd_half = (e_ph == 2'd1); // ph0 -> c0, ph1 -> c1 wire [13:0] e_rd_full = e_pidx*256 + {e_pair, e_rd_half}; // bse/bt rd addr in ST_E // dk half reads bank_se (bse_rd_data), ek half reads bank_t (bt_rd_data); // both registered inside their sd_bram with the same 1-cycle latency, so // the coeff for the addr presented last cycle is selected here. wire [11:0] e_rd_coeff = e_is_dk ? bse_rd_data : bt_rd_data; // byteEncode write byte offset within the target memory: pair*3 + byte index. wire [11:0] e_base = e_pidx * 12'd384; // poly index *384 (=128 pairs*3) wire [11:0] e_boff = e_base + {e_pair, 1'b0} + {2'b0, e_pair}; // pair*3 wire [1:0] e_wb = e_ph - 2'd1; // ph1->byte0, ph2->byte1, ph3->byte2 wire [7:0] e_wbyte = (e_ph == 2'd1) ? e_rd_coeff[7:0] // b0 = c0[7:0] : (e_ph == 2'd2) ? {e_rd_coeff[3:0], e_c0_hi} // b1 = {c1[3:0],c0[11:8]} : e_c1_hi; // b2 = c1[11:4] wire [SAW-1:0] m_aslot = m_i*k_r + m_j; // A_hat[i][j] slot = i*k + j wire [SAW-1:0] m_sslot = slot_s_rt + m_j; // s_hat[j] wire [SAW-1:0] m_eslot = slot_e_rt + m_i; // e_hat[i] wire [SAW-1:0] m_tslot = slot_t_rt + m_i; // t_hat[i] // ================================================================ // E4: u[i] = INTT(sum_j A^T[i][j] o y_hat[j]) + e1[i] (Encaps ST_ENC_U) // Per row i, three sub-phases (u_sub): // 0 MAC : sum_j A_hat[j][i] o y_hat[j] (transpose) -> psum bank_t[UPSUM] // 1 INTT: INTT(psum) mode=1 in place in bank_t[UPSUM] // 2 ADD : u[i][w] = psum[w] + e1[i][w] mod Q -> bank_se rel (K+i) (over e1) // y_hat (bank_se 0..K-1) survives for V. Reuses shared u_pmul + u_ntt. // ================================================================ localparam UPSUM = 10'd1; // bank_t rel slot for the NTT-domain psum reg [2:0] u_row; // output row i 0..K-1 reg [1:0] u_sub; // 0=MAC 1=INTT 2=ADD // MAC bookkeeping (mirrors ST_M) reg [2:0] u_j; // term col j 0..K-1 reg [8:0] u_ld; // load read-ahead 0..256 reg [7:0] u_oidx; // accumulate index 0..255 reg u_loading; // streaming pairs into poly_mul reg u_pending; // wait poly_mul IDLE before next j reg u_j0q; // (u_j==0) delayed 1 cyc (first-term=0 select) // INTT bookkeeping (mirrors ST_N) reg [8:0] u_ridx; // INTT load read-ahead 0..256 reg [7:0] u_widx; // INTT output index 0..255 reg u_nvalid; // feed psum to ntt_core (mode=1) reg u_nloading; // presenting INTT load addresses reg u_npending; // (unused placeholder for symmetry) // ADD bookkeeping reg [8:0] u_aidx; // ADD read-ahead 0..256 reg [7:0] u_awidx; // ADD write index 0..255 reg u_avalid; // ADD write enable pipeline // MAC addresses: A^T -> A_hat[u_j][u_row] slot = u_j*k+u_row; y_hat[u_j] rel u_j wire [SAW-1:0] u_aslot = u_j*k_r + u_row; wire [13:0] u_pm_a_full = u_aslot*256 + u_ld[7:0]; // bank_a wire [13:0] u_pm_b_full = u_j*256 + u_ld[7:0]; // bank_se y_hat[u_j] wire [7:0] u_acc_radr = pm_vo ? (u_oidx + 8'd1) : u_oidx; wire [13:0] u_psum_full = UPSUM*256 + u_acc_radr; // bank_t psum acc addr (MAC) wire [11:0] u_acc_src = u_j0q ? 12'd0 : bt_rd_data; // first term init 0 wire [12:0] u_sum = {1'b0, u_acc_src} + {1'b0, pm_coeff}; wire [11:0] u_accq = (u_sum >= 13'(Q)) ? (u_sum - 13'(Q)) : u_sum[11:0]; // INTT load addr (psum read) + in-place writeback to bank_t[UPSUM] wire [13:0] u_intt_rd = UPSUM*256 + u_ridx[7:0]; wire [13:0] u_intt_wr = UPSUM*256 + u_widx; // ADD: read psum (bank_t) + e1 (bank_se K+u_row), write u over e1 (read-ahead 1) wire [13:0] u_add_prd = UPSUM*256 + u_aidx[7:0]; // bank_t psum wire [13:0] u_add_e1rd = ({2'b0,k_r}+{2'b0,u_row})*256 + u_aidx[7:0]; // bank_se e1 (lead) wire [13:0] u_add_uwr = ({2'b0,k_r}+{2'b0,u_row})*256 + u_awidx; // bank_se u (write) wire [12:0] u_usum = {1'b0, bt_rd_data} + {1'b0, bse_rd_data}; // psum + e1 wire [11:0] u_uq = (u_usum >= 13'(Q)) ? (u_usum - 13'(Q)) : u_usum[11:0]; // ================================================================ // E6: v = INTT(sum_j t_hat[j] o y_hat[j]) + e2 + mu (Encaps ST_ENC_V). // Reuses the u_* MAC/INTT/ADD machine with u_row tied to 0 (single output // poly). Differences from E4: // - MAC reads t_hat[j] from bank_a slot j*K (== u_aslot=u_j*K, u_row=0), // so the bank_a MAC address is unchanged. // - ADD computes psum + e2 + mu (not psum + e1); e2 lives in bank_a slot // E2_ASLOT (relocated from bank_t[0] in ST_ENC_E2MV), v writes back to // bank_t[UPSUM] in place. // - mu[w] = Decompress_1(m bit w) = m_r[w] ? 1665 : 0 (1665 = round(Q/2)). // u_row_max bounds the row loop: K for U, 1 for V. localparam E2_ASLOT = 12'd1; // bank_a slot holding relocated e2 (never a t_hat slot, K>=2) wire [2:0] u_row_max = (st == ST_ENC_V) ? 3'd1 : k_r; // V-ADD: read psum (bank_t UPSUM) + e2 (bank_a E2_ASLOT) at u_aidx (lead), // add mu (by write index u_awidx), write v to bank_t[UPSUM] at u_awidx. wire [13:0] u_v_e2rd = E2_ASLOT*256 + u_aidx[7:0]; // bank_a e2 (lead) wire [13:0] u_vadd_wr = UPSUM*256 + u_awidx; // bank_t v (write) wire [11:0] u_mu = m_r[u_awidx] ? 12'd1665 : 12'd0; // mu by msg bit wire [13:0] u_vsum = {2'b0, bt_rd_data} + {2'b0, ba_rd_data} + {2'b0, u_mu}; // psum+e2+mu wire [13:0] u_vsub1 = (u_vsum >= 14'(Q)) ? (u_vsum - 14'(Q)) : u_vsum; wire [11:0] u_vq = (u_vsub1 >= 14'(Q)) ? (u_vsub1 - 14'(Q)) : u_vsub1[11:0]; wire u_v_we = (st == ST_ENC_V) && (u_sub == 2'd2) && u_avalid; // ST_ENC_E2MV: copy e2 (bank_t rel slot 0, 256 coeffs) into bank_a[E2_ASLOT]. // bank_t read leads (em_ridx), bank_a write trails 1 cycle (em_we/em_widx). reg [8:0] em_ridx; // 0..256 read-ahead over e2 reg [7:0] em_widx; // bank_a write index reg em_we; // a coeff is being written this cycle reg em_done; wire [13:0] em_rd = 14'd0 + em_ridx[7:0]; // bank_t rel slot 0 (e2) wire [11:0] em_wa = E2_ASLOT*256 + em_widx; // bank_a E2_ASLOT // ================================================================ // E5/E7: Compress_d + byteEncode_d -> ciphertext (Encaps ST_ENC_C1/C2). // Per coeff: read poly coeff -> comp_decomp (mode 0 compress, d=du/dv) -> // bit-packer (LSB-first) -> emit bytes to ct_bram. c1 = K polys of u // (d=du), then c2 = 1 poly of v (d=dv). Per poly = 256 coeffs -> 32*d // bytes (whole), so the bit buffer empties at each poly boundary. // micro-phase cp_ph: 0 present coeff addr; 1 feed comp_decomp (cd_valid); // 2 wait pipe; 3 capture compressed + accumulate bits; 4..n drain bytes. // ================================================================ wire cd_active = (st == ST_ENC_C1) || (st == ST_ENC_C2); reg [11:0] cd_coeff; // coeff presented to comp_decomp reg cd_valid; // 1-cyc pulse to comp_decomp wire cd_ready; wire [11:0] cd_out; // compressed value (low d bits valid) wire cd_vo; wire [4:0] cp_d = (st == ST_ENC_C2) ? dv_rt : du_rt; // compress width comp_decomp_sync u_comp ( .clk(clk), .rst_n(rst_n), .coeff_in(cd_coeff), .d(cp_d), .mode(1'b0), // compress .valid_i(cd_valid), .ready_o(cd_ready), .coeff_out(cd_out), .valid_o(cd_vo), .ready_i(1'b1) ); // bit-packer / ct walk bookkeeping reg [2:0] cp_poly; // c1: 0..K-1 (u rows); c2: single v reg [7:0] cp_idx; // coeff 0..255 within poly reg [2:0] cp_ph; // micro-phase reg [24:0] cp_buf; // bit accumulator (LSB-first) reg [5:0] cp_nbits; // valid bits in cp_buf reg [11:0] cp_wa; // ct_bram byte write address (runs c1 then c2) reg cp_done; // serialization complete (this region) // coeff source: c1 (ST_ENC_C1) reads u[cp_poly] in bank_se rel (K+cp_poly); // c2 (ST_ENC_C2) reads v in bank_t rel slot UPSUM (single poly, cp_poly=0). wire [13:0] cp_se_full = ({2'b0,k_r}+{2'b0,cp_poly})*256 + cp_idx; // bank_se u[cp_poly] wire [13:0] cp_bt_full = UPSUM*256 + cp_idx; // bank_t v (UPSUM) // packer generalization (E5 c1 vs E7 c2): // coeff source: c1 = bank_se (u), c2 = bank_t (v) // poly count: c1 = K (u rows), c2 = 1 (single v) // bit width cp_d already = dv_rt for C2 else du_rt (declared above). wire [11:0] cp_coeff_src = (st == ST_ENC_C2) ? bt_rd_data : bse_rd_data; wire [2:0] cp_poly_max = (st == ST_ENC_C2) ? 3'd1 : k_r; reg pm_valid; wire pm_ready; wire [11:0] pm_coeff; wire pm_vo; // pm_a: A_hat[i][j] in bank_a (abs slot m_aslot). pm_b: s_hat[j] in // bank_se (relative slot = m_sslot - slot_s_rt = m_j). m_ld is a read-ahead // pointer; both bank reads come from their sd_bram (registered, 1-cycle // latency) and feed poly_mul one cycle later (pm_valid delayed 1 cyc). wire [13:0] pm_a_full = m_aslot*256 + m_ld[7:0]; wire [13:0] pm_b_full = m_j*256 + m_ld[7:0]; wire [11:0] pm_a_in = ba_rd_data; // bank_a sd_bram registered read wire [11:0] pm_b_in = bse_rd_data; // bank_se sd_bram registered read (load phase) poly_mul_sync u_pmul ( .clk(clk), .rst_n(rst_n), .coeff_a_in(pm_a_in), .coeff_b_in(pm_b_in), .valid_i(pm_valid), .ready_o(pm_ready), .coeff_out(pm_coeff), .valid_o(pm_vo), .ready_i(1'b1) ); // accumulator source: e_hat[i] for first term (j==0), else running t_hat[i]. // e_hat[i] lives in bank_se at relative slot (slot_e_rt-slot_s_rt + m_i) = K+m_i. // t_hat[i] lives in bank_t at relative slot m_i. Both reads come from their // sd_bram (bse_rd_data / bt_rd_data) with the same 1-cycle latency. // Registered read-ahead: present the index the NEXT pm_vo will consume // m_acc_radr = pm_vo ? m_oidx+1 : m_oidx // The j-select is applied on the registered outputs (m_jq = (m_j==0) // delayed 1 cyc to align with the read latency). RMW read-old holds: read // addr (m_oidx+1) leads write addr (m_oidx). Cadence CALC/C0/C1. wire [7:0] m_acc_radr = pm_vo ? (m_oidx + 8'd1) : m_oidx; wire [13:0] m_eacc_full = ({2'b0, k_r} + {2'b0, m_i})*256 + m_acc_radr; // K+m_i (bse_rd_addr in acc) wire [13:0] m_tacc_full = m_i*256 + m_acc_radr; // bt_rd_addr reg m_jq; // (m_j==0) delayed 1 cyc to match read latency // selected accumulator source aligned with pm_coeff wire [11:0] m_acc_src = m_jq ? bse_rd_data : bt_rd_data; // (a + b) mod Q (both < Q, sum < 2Q): one conditional subtract wire [12:0] m_sum = {1'b0, m_acc_src} + {1'b0, pm_coeff}; wire [11:0] m_accq = (m_sum >= 13'(Q)) ? (m_sum - 13'(Q)) : m_sum[11:0]; always @(*) begin st_next = st; case (st) ST_IDLE: if (start_i) st_next = op_i ? ST_ENC_H : ST_G; ST_G: if (sha3_vo) st_next = ST_A; ST_A: if (a_pair >= kk_rt) st_next = ST_C; ST_C: if (c_poly >= {1'b0, k_r, 1'b0}) st_next = ST_N; ST_N: if (n_slot >= {1'b0, k_r, 1'b0}) st_next = ST_M; ST_M: if (m_i >= k_r) st_next = ST_E; ST_E: if (e_done) st_next = ST_H; ST_H: if (h_phase == 2'd3) st_next = ST_DONE; // ---- Encaps ---- 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_C; ST_ENC_C: if (c_poly >= {k_r, 1'b1}) st_next = ST_ENC_N; // 2K+1 polys done ST_ENC_N: if (n_slot >= {2'b0, k_r}) st_next = ST_ENC_U; // K slots (y_hat) ST_ENC_U: if (u_row >= k_r) st_next = ST_ENC_C1; // u[0..K-1] done ST_ENC_C1: if (cp_done) st_next = ST_ENC_TDEC; // E5 done -> E6 ST_ENC_TDEC: if (td_done) st_next = ST_ENC_E2MV; // t_hat decoded -> relocate e2 ST_ENC_E2MV: if (em_done) st_next = ST_ENC_V; // e2 relocated -> compute v ST_ENC_V: if (u_row >= 3'd1) st_next = ST_ENC_C2; // E6: v done -> E7 ST_ENC_C2: if (cp_done) st_next = ST_DONE; // E7: c2 packed -> done ST_DONE: st_next = ST_IDLE; default: st_next = ST_IDLE; endcase end always @(posedge clk or negedge rst_n) begin if (!rst_n) begin st <= ST_IDLE; k_r <= 3'd0; op_r <= 1'b0; m_r <= 256'd0; ss_r <= 256'd0; r_r <= 256'd0; rl_idx <= 6'd0; rl_widx <= 6'd0; rl_vld <= 1'b0; td_poly <= 3'd0; td_trip <= 8'd0; td_ph <= 3'd0; td_b0 <= 8'd0; td_b1 <= 8'd0; td_b2 <= 8'd0; td_done <= 1'b0; td_we <= 1'b0; td_wa <= 12'd0; td_wd <= 12'd0; rho_r <= 256'd0; sigma_r <= 256'd0; sha3_valid <= 1'b0; sha3_ack <= 1'b0; snt_valid <= 1'b0; snt_ack <= 1'b0; a_pair <= 5'd0; a_i <= 3'd0; a_j <= 3'd0; a_widx <= 8'd0; a_busy <= 1'b0; cbd_valid <= 1'b0; cbd_ack <= 1'b0; c_poly <= 3'd0; c_widx <= 8'd0; c_busy <= 1'b0; n_slot <= 3'd0; n_ridx <= 9'd0; n_widx <= 8'd0; n_valid <= 1'b0; n_loading <= 1'b0; n_pending <= 1'b0; m_i <= 2'd0; m_j <= 2'd0; m_ld <= 9'd0; m_oidx <= 8'd0; m_loading <= 1'b0; m_pending <= 1'b0; pm_valid <= 1'b0; m_jq <= 1'b0; u_row <= 3'd0; u_sub <= 2'd0; u_j <= 3'd0; u_ld <= 9'd0; u_oidx <= 8'd0; u_loading <= 1'b0; u_pending <= 1'b0; u_j0q <= 1'b0; u_ridx <= 9'd0; u_widx <= 8'd0; u_nvalid <= 1'b0; u_nloading <= 1'b0; u_npending <= 1'b0; u_aidx <= 9'd0; u_awidx <= 8'd0; u_avalid <= 1'b0; em_ridx <= 9'd0; em_widx <= 8'd0; em_we <= 1'b0; em_done <= 1'b0; cd_coeff <= 12'd0; cd_valid <= 1'b0; cp_poly <= 3'd0; cp_idx <= 8'd0; cp_ph <= 3'd0; cp_buf <= 25'd0; cp_nbits <= 6'd0; cp_wa <= 12'd0; cp_done <= 1'b0; ct_we <= 1'b0; ct_wa <= 11'd0; ct_wd <= 8'd0; e_poly <= 3'd0; e_pair <= 8'd0; e_ph <= 2'd0; e_c0_hi <= 4'd0; e_c1_hi <= 8'd0; e_rho <= 10'd0; e_done <= 1'b0; ek_we <= 1'b0; dkp_we <= 1'b0; ek_wa <= 11'd0; dkp_wa <= 11'd0; ek_wd <= 8'd0; dkp_wd <= 8'd0; h_block_r <= 1088'd0; h_mbvalid <= 1'b0; h_mblast <= 1'b0; h_ack <= 1'b0; hek_r <= 256'd0; h_blk <= 3'd0; h_byte <= 8'd0; h_phase <= 2'd0; h_wb_vld <= 1'b0; h_wb_idx <= 8'd0; h_wb_g <= 12'd0; h_wb_pad <= 8'd0; h_wb_inek <= 1'b0; end else begin st <= st_next; // BRAM write-enables default low; pulsed where a byte is written. ek_we <= 1'b0; dkp_we <= 1'b0; td_we <= 1'b0; // TDEC bank_a write default low ct_we <= 1'b0; // ct_bram byte write default low (E5/E7) em_we <= 1'b0; // e2-relocate bank_a write default low (E6) // Kick off when entering from IDLE: KeyGen starts G; Encaps captures // op/m and arms the H(ek) machinery (ST_ENC_H reuses the ST_H FSM). if (st == ST_IDLE && start_i) begin k_r <= k_i; // capture runtime ML-KEM param op_r <= op_i; if (op_i) begin m_r <= msg_i; // capture Encaps message // arm H(ek) (same fields the ST_E->ST_H arming sets) h_blk <= 3'd0; h_byte <= 8'd0; h_phase <= 2'd0; // assemble h_mbvalid<= 1'b0; h_mblast <= 1'b0; h_ack <= 1'b1; // ready to consume final digest h_wb_vld <= 1'b0; end else begin sha3_valid <= 1'b1; sha3_ack <= 1'b1; end end // Drop valid once accepted if (sha3_valid && sha3_ready) sha3_valid <= 1'b0; // Capture rho/sigma when G completes; arm A stage if (st == ST_G && sha3_vo) begin rho_r <= sha3_hash[255:0]; // rho = G output bytes 0..31 sigma_r <= sha3_hash[511:256]; // sigma = bytes 32..63 sha3_ack <= 1'b0; snt_valid <= 1'b1; // start first SampleNTT snt_ack <= 1'b1; a_pair <= 5'd0; a_i <= 3'd0; a_j <= 3'd0; a_widx <= 8'd0; a_busy <= 1'b0; end // ---- ST_A / ST_ENC_A: drive SampleNTT, store 256 coeffs per pair ---- if (st == ST_A || st == ST_ENC_A) begin // mark busy once this pair's request accepted if (snt_valid && snt_ready) begin snt_valid <= 1'b0; a_busy <= 1'b1; end // store each output coefficient only while busy (ignore stale // last coeff from prior poly). The bank_a write itself is the // combinational ba_we/ba_wa/ba_wd assigns above; here we only // advance the write index and (i,j) bookkeeping. if (a_busy && snt_vo && snt_ack) begin if (snt_last) begin // finished this poly; advance (i,j) in row-major order a_pair <= a_pair + 5'd1; a_widx <= 8'd0; a_busy <= 1'b0; if (a_j + 3'd1 < k_r) begin a_j <= a_j + 3'd1; end else begin a_j <= 3'd0; a_i <= a_i + 3'd1; end // start next SampleNTT if more pairs remain if (a_pair + 5'd1 < kk_rt) snt_valid <= 1'b1; end else begin a_widx <= a_widx + 8'd1; end end end // 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; c_widx <= 8'd0; c_busy <= 1'b0; end // ---- 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 if (cbd_last) begin c_poly <= c_poly + 3'd1; c_widx <= 8'd0; c_busy <= 1'b0; // 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 end end // Arm N stage when C finishes: prime load of slot S0. n_ridx is a // read-ahead pointer; bank_se read is registered inside sd_bram (bse_rd_data) // and fed to ntt_core one cycle later, so valid starts low (priming). if ((st == ST_C && st_next == ST_N) || (st == ST_ENC_C && st_next == ST_ENC_N)) begin n_slot <= 3'd0; n_ridx <= 9'd0; n_widx <= 8'd0; n_valid <= 1'b0; n_loading <= 1'b1; // begin presenting load addresses n_pending <= 1'b0; end // ---- ST_N / ST_ENC_N: forward NTT in place. KeyGen: 2K slots // (s,e). Encaps: K slots (y only; e1/e2 stay time-domain). ---- if (st == ST_N || st == ST_ENC_N) begin // slot-count bound: 2K for KeyGen, K for Encaps // (n_slot_max below); same LOAD/OUTPUT cadence either way. if (n_loading) begin if (n_ridx == 9'd256) begin // 256th coeff consumed this cycle; stop presenting addr n_loading <= 1'b0; n_valid <= 1'b0; end else begin n_ridx <= n_ridx + 9'd1; n_valid <= 1'b1; // data presented last cycle is valid end end // OUTPUT phase: collect 256 results, write back to same slot. if (ntt_vo) begin n_widx <= n_widx + 8'd1; // wraps 255->0 after last end // Slot complete when ntt_core returns to DONE if (ntt_done) begin if (n_slot + 3'd1 < n_slot_max) begin n_slot <= n_slot + 3'd1; n_widx <= 8'd0; n_pending <= 1'b1; // wait one cycle for core IDLE end else begin n_slot <= n_slot + 3'd1; // == n_slot_max -> exit end end // Kick next slot's load once core is back IDLE (re-prime) if (n_pending && ntt_ready && !ntt_done) begin n_ridx <= 9'd0; n_valid <= 1'b0; n_loading <= 1'b1; n_pending <= 1'b0; end end // Arm M stage when N finishes: prime first (i=0,j=0) poly_mul load. // m_ld read-ahead pointer; bank_a/bank_se sd_bram reads land 1 cyc // later, pm_valid asserted one cycle after an address is presented. if (st == ST_N && st_next == ST_M) begin m_i <= 2'd0; m_j <= 2'd0; m_ld <= 9'd0; m_oidx <= 8'd0; m_loading <= 1'b1; m_pending <= 1'b0; pm_valid <= 1'b0; end // ---- ST_M: t_hat[i] = e_hat[i] + sum_j A[i][j] o s_hat[j] ---- if (st == ST_M) begin // accumulator reads come from sd_bram (bse_rd_data e_hat / // bt_rd_data t_hat). m_jq aligns the j-select with the 1-cycle // read latency. (bse_rd_addr muxes load vs acc by m_loading.) m_jq <= (m_j == 2'd0); // LOAD: present read-ahead addr to bank_a/bank_se via their // sd_bram read ports (ba_rd_addr=pm_a_full, bse_rd_addr=pm_b_full // while m_loading); ba_rd_data/bse_rd_data land next cycle and // are consumed by poly_mul (pm_valid). poly_mul holds ready high // through LOAD, so a fixed 1-cycle skew suffices. if (m_loading) begin if (m_ld == 9'd256) begin pm_valid <= 1'b0; // 256th pair consumed this cycle m_loading <= 1'b0; m_ld <= 9'd0; m_oidx <= 8'd0; end else begin m_ld <= m_ld + 9'd1; pm_valid <= 1'b1; // pair presented last cycle is valid end end // ACCUMULATE: each product coeff += e_hat (j==0) or running t_hat. // The bank_t write is the combinational bt_we/bt_wa/bt_wd assigns // below; here we only advance the accumulate index / (i,j). if (pm_vo) begin if (m_oidx == 8'd255) begin // finished this (i,j) term; advance if (m_j + 2'd1 < k_r) begin m_j <= m_j + 2'd1; m_pending <= 1'b1; // next term, same row end else begin m_j <= 2'd0; m_i <= m_i + 2'd1; // next row (or == K -> DONE) if (m_i + 2'd1 < k_r) m_pending <= 1'b1; end end else begin m_oidx <= m_oidx + 8'd1; end end // Start next (i,j) poly_mul load once core is IDLE again (re-prime) if (m_pending && pm_ready && !pm_vo) begin m_loading <= 1'b1; m_ld <= 9'd0; m_oidx <= 8'd0; pm_valid <= 1'b0; m_pending <= 1'b0; end end // Arm Encaps U stage when N finishes: prime first row MAC (i=0,j=0). if (st == ST_ENC_N && st_next == ST_ENC_U) begin u_row <= 3'd0; u_sub <= 2'd0; // MAC u_j <= 3'd0; u_ld <= 9'd0; u_oidx <= 8'd0; u_loading <= 1'b1; u_pending <= 1'b0; pm_valid <= 1'b0; end // ---- ST_ENC_U/V sub-phase 0: MAC sum_j (A^T or t_hat)[j] o y_hat[j] ---- // psum (init 0 at j==0) accumulates into bank_t[UPSUM]. Mirrors ST_M // load/accumulate cadence (read-ahead by 1, j-select via u_j0q). // U: A^T[u_row][j] (bank_a u_j*K+u_row). V: t_hat[j] (bank_a u_j*K, u_row=0). if ((st == ST_ENC_U || st == ST_ENC_V) && u_sub == 2'd0) begin u_j0q <= (u_j == 3'd0); if (u_loading) begin if (u_ld == 9'd256) begin pm_valid <= 1'b0; u_loading <= 1'b0; u_ld <= 9'd0; u_oidx <= 8'd0; end else begin u_ld <= u_ld + 9'd1; pm_valid <= 1'b1; end end if (pm_vo) begin // bank_t psum write is combinational (u_psum_we); advance idx if (u_oidx == 8'd255) begin if (u_j + 3'd1 < k_r) begin u_j <= u_j + 3'd1; u_pending <= 1'b1; // next term, same row end else begin // row's MAC done -> INTT sub-phase u_sub <= 2'd1; u_ridx <= 9'd0; u_widx <= 8'd0; u_nvalid <= 1'b0; u_nloading <= 1'b1; end end else begin u_oidx <= u_oidx + 8'd1; end end // re-prime next term's poly_mul load if (u_pending && pm_ready && !pm_vo) begin u_loading <= 1'b1; u_ld <= 9'd0; u_oidx <= 8'd0; pm_valid <= 1'b0; u_pending <= 1'b0; end end // ---- ST_ENC_U/V sub-phase 1: INTT(psum) mode=1 in place (bank_t) ---- if ((st == ST_ENC_U || st == ST_ENC_V) && u_sub == 2'd1) begin if (u_nloading) begin if (u_ridx == 9'd256) begin u_nloading <= 1'b0; u_nvalid <= 1'b0; end else begin u_ridx <= u_ridx + 9'd1; u_nvalid <= 1'b1; end end if (ntt_vo) u_widx <= u_widx + 8'd1; // in-place write (u_intt_we) if (ntt_done) begin // INTT done -> ADD sub-phase (psum + e1[u_row]) u_sub <= 2'd2; u_aidx <= 9'd0; u_awidx <= 8'd0; u_avalid <= 1'b0; end end // ---- ST_ENC_U/V sub-phase 2: ADD + writeback ---- // U: u[u_row] = psum + e1[u_row] mod Q -> bank_se (u_add_we). // V: v = psum + e2 + mu mod Q -> bank_t[UPSUM] (u_v_we). // Read psum (bank_t) + e1/e2 at u_aidx; both arrive 1 cyc later, so // register (valid,widx) and write next cycle. u_row_max bounds the // row loop (K for U, 1 for V). if ((st == ST_ENC_U || st == ST_ENC_V) && u_sub == 2'd2) begin if (u_aidx < 9'd256) begin u_aidx <= u_aidx + 9'd1; u_avalid <= 1'b1; // addr presented this cyc -> write next u_awidx <= u_aidx[7:0]; end else begin u_avalid <= 1'b0; // no more addresses end // writeback (u_add_we / u_v_we) commits index u_awidx this cycle. // Advance row after the 256th coeff (u_awidx==255) is written. if (u_avalid && u_awidx == 8'd255) begin if (u_row + 3'd1 < u_row_max) begin u_row <= u_row + 3'd1; u_sub <= 2'd0; // next row MAC u_j <= 3'd0; u_ld <= 9'd0; u_oidx <= 8'd0; u_loading <= 1'b1; u_pending <= 1'b0; pm_valid <= 1'b0; end else begin u_row <= u_row + 3'd1; // U: ==K ->C1; V: ==1 ->DONE end end end // Arm E5 (ST_ENC_C1) when U finishes: c1 = byteEncode_du(Compress_du(u)). // Walk K polys * 256 coeffs; reset bit-packer + ct write pointer. if (st == ST_ENC_U && st_next == ST_ENC_C1) begin cp_poly <= 3'd0; cp_idx <= 8'd0; cp_ph <= 3'd0; cp_buf <= 25'd0; cp_nbits <= 6'd0; cp_wa <= 12'd0; cp_done <= 1'b0; cd_valid <= 1'b0; end // ---- ST_ENC_C1/C2: Compress_d -> byteEncode_d -> ct region ---- // C1 (E5): Compress_du(u[0..K-1]) from bank_se -> ct[0..c1_bytes). // C2 (E7): Compress_dv(v) from bank_t[UPSUM] -> ct[c1_bytes..ct_bytes). // Per coeff, 5-phase micro-sequence (read-ahead 1 cyc bram + 1 cyc // comp_decomp pipe), then a drain sub-phase emitting whole bytes: // ph0: present coeff addr (cp_se_full / cp_bt_full by state). // ph1: coeff arrives (cp_coeff_src) -> latch into cd_coeff, pulse cd_valid. // ph2: drop cd_valid (1-cyc pulse); comp_decomp captures. // ph3: cd_vo high -> cd_out (low cp_d bits) valid; append LSB-first. // ph4: drain: while >=8 bits buffered, emit one ct byte/cycle; then advance. // Each poly = 256 coeffs = 32*d bytes (whole), so cp_buf empties at // each poly boundary (no carry across polys / regions). if ((st == ST_ENC_C1 || st == ST_ENC_C2) && !cp_done) begin case (cp_ph) 3'd0: cp_ph <= 3'd1; // addr presented; wait read 3'd1: begin cd_coeff <= cp_coeff_src; // u (bank_se) or v (bank_t) cd_valid <= 1'b1; // feed comp_decomp (1-cyc pulse) cp_ph <= 3'd2; end 3'd2: begin cd_valid <= 1'b0; // comp_decomp captured this cyc cp_ph <= 3'd3; end 3'd3: begin // cd_out valid (cd_vo): append cp_d bits LSB-first at bit cp_nbits cp_buf <= cp_buf | (({13'd0, cd_out} & ((25'd1 << cp_d) - 25'd1)) << cp_nbits); cp_nbits <= cp_nbits + {1'b0, cp_d}; cp_ph <= 3'd4; end default: begin // 3'd4: drain whole bytes if (cp_nbits >= 6'd8) begin ct_we <= 1'b1; ct_wa <= cp_wa; ct_wd <= cp_buf[7:0]; cp_wa <= cp_wa + 12'd1; cp_buf <= cp_buf >> 8; cp_nbits <= cp_nbits - 6'd8; end else begin // coeff fully packed; advance coeff / poly if (cp_idx == 8'd255) begin cp_idx <= 8'd0; if (cp_poly + 3'd1 < cp_poly_max) cp_poly <= cp_poly + 3'd1; else cp_done <= 1'b1; // region complete end else begin cp_idx <= cp_idx + 8'd1; end cp_ph <= 3'd0; end end endcase end // Arm E7 (ST_ENC_C2) when V finishes: c2 = byteEncode_dv(Compress_dv(v)). // Single poly (v at bank_t UPSUM); ct write pointer cp_wa CONTINUES // from c1_bytes (NOT reset) so c2 lands right after c1. cp_buf/nbits // are 0 here (c1 ended on a poly/byte boundary) but reset for safety. if (st == ST_ENC_V && st_next == ST_ENC_C2) begin cp_poly <= 3'd0; cp_idx <= 8'd0; cp_ph <= 3'd0; cp_buf <= 25'd0; cp_nbits <= 6'd0; cp_done <= 1'b0; cd_valid <= 1'b0; // cp_wa intentionally preserved (= c1_bytes_rt from C1). end // Arm E stage when M finishes if (st == ST_M && st_next == ST_E) begin e_poly <= 3'd0; e_pair <= 8'd0; e_ph <= 2'd0; // start at fetch-c0 of first pair e_rho <= 10'd0; e_done <= 1'b0; end // ---- ST_E: byteEncode12 t_hat -> ek, s_hat -> dk_pke ---- // Single registered bank read per cycle; 4-cycle micro-phase per // coeff pair (ph0 fetch c0, ph1 write b0 + fetch c1, ph2 write b1, // ph3 write b2). The coeff for the addr presented last cycle is // e_rd_coeff (= bse_rd_data for dk-half, bt_rd_data for ek-half), // both registered inside their sd_bram (bse_rd_addr/bt_rd_addr = // e_rd_full in ST_E). if (st == ST_E && !e_done) begin if (e_poly < {1'b0, k_r, 1'b0}) begin // ph0: fetch only (prime). ph1..3: write one packed byte. if (e_ph != 2'd0) begin if (!e_is_dk) begin ek_we <= 1'b1; ek_wa <= e_boff[10:0] + {9'd0, e_wb}; ek_wd <= e_wbyte; end else begin dkp_we <= 1'b1; dkp_wa <= e_boff[10:0] + {9'd0, e_wb}; dkp_wd <= e_wbyte; end end // save coeff high nibbles as they become available if (e_ph == 2'd1) e_c0_hi <= e_rd_coeff[11:8]; // c0[11:8] if (e_ph == 2'd2) e_c1_hi <= e_rd_coeff[11:4]; // c1[11:4] // advance micro-phase / pair / poly if (e_ph == 2'd3) begin e_ph <= 2'd0; if (e_pair == 8'd127) begin e_pair <= 8'd0; e_poly <= e_poly + 5'd1; // next poly or -> rho phase end else begin e_pair <= e_pair + 8'd1; end end else begin e_ph <= e_ph + 2'd1; end end else begin // rho copy: ek[384*K + r] = rho byte r (r = 0..31), 1 byte/cycle ek_we <= 1'b1; ek_wa <= dk_bytes_rt[10:0] + {1'b0, e_rho}; ek_wd <= rho_r[e_rho*8 +: 8]; if (e_rho == 10'd31) e_done <= 1'b1; else e_rho <= e_rho + 10'd1; end end // Arm H stage when E finishes if (st == ST_E && st_next == ST_H) begin h_blk <= 3'd0; h_byte <= 8'd0; h_phase <= 2'd0; // assemble h_mbvalid<= 1'b0; h_mblast <= 1'b0; h_ack <= 1'b1; // ready to consume final digest h_wb_vld <= 1'b0; // no pending writeback yet end // ---- ST_H / ST_ENC_H: H(ek) via multi-block SHA3-256 ---- // Same logic for KeyGen (ek just written by ST_E) and Encaps (ek // preloaded into ek_bram by the TB). hek_r captures H(ek) either way. if (st == ST_H || st == ST_ENC_H) begin case (h_phase) // assemble 136 bytes of block h_blk into h_block_r. // ek BRAM read is registered: present addr for h_byte this // cycle, write back the byte that arrived for the addr we // presented last cycle (h_wb_*). h_byte runs 0..136 (one // extra cycle to flush the final writeback). 2'd0: begin // writeback the byte read for the previous address if (h_wb_vld) h_block_r[h_wb_idx*8 +: 8] <= h_wb_inek ? ek_rd_data : h_wb_pad; if (h_byte <= 8'd135) begin // set up writeback for the address presented this cycle h_wb_vld <= 1'b1; h_wb_idx <= h_byte; h_wb_g <= h_g_addr; h_wb_inek <= (h_g_addr < ek_bytes_rt); h_wb_pad <= h_padconst(h_blk, h_byte); h_byte <= h_byte + 8'd1; end else begin // h_byte==136: final writeback (for byte 135) done above h_wb_vld <= 1'b0; h_byte <= 8'd0; h_mbvalid <= 1'b1; h_mblast <= (h_blk == h_nblk_rt - 4'd1); h_phase <= 2'd1; // feed end end // feed: hold valid until accepted (mb_ready drops) 2'd1: begin if (h_mbvalid && !h_mbready) begin h_mbvalid <= 1'b0; h_mblast <= 1'b0; h_phase <= 2'd2; // wait permute end end // wait permute done: ready again (more blocks) or digest valid (last) 2'd2: begin if (h_vo) begin hek_r <= h_hash[255:0]; h_phase <= 2'd3; // done end else if (h_mbready) begin h_blk <= h_blk + 3'd1; h_phase <= 2'd0; // assemble next block end end default: ; // 2'd3 done: hold endcase end // Arm Encaps G when H(ek) finishes: fire the 64-byte single-block // G(m||H(ek)). hek_r now holds H(ek); enc_g_data = {hek_r, m_r}. if (st == ST_ENC_H && st_next == ST_ENC_G) begin sha3_valid <= 1'b1; sha3_ack <= 1'b1; end // Capture (K, r) when Encaps G completes. ss = K = low half, r = high. if (st == ST_ENC_G && sha3_vo) begin ss_r <= sha3_hash[255:0]; // K (shared secret) = G bytes 0..31 r_r <= sha3_hash[511:256]; // r = G bytes 32..63 sha3_ack <= 1'b0; end // Arm rho-load when entering ST_ENC_LOAD. rho = ek[384k .. 384k+31]. if (st == ST_ENC_G && st_next == ST_ENC_LOAD) begin rl_idx <= 6'd0; rl_widx <= 6'd0; rl_vld <= 1'b0; end // ---- ST_ENC_LOAD: stream 32 rho bytes from ek_bram into rho_r ---- // ek read is registered (1-cyc latency): the data arriving this cycle // is for the address presented last cycle (rl_idx-1). Present addr // rl_idx (valid 0..31); write rho_r[rl_idx-1] when rl_idx in 1..32. if (st == ST_ENC_LOAD) begin if (rl_idx >= 6'd1 && rl_idx <= 6'd32) rho_r[(rl_idx-6'd1)*8 +: 8] <= ek_rd_data; rl_idx <= rl_idx + 6'd1; // exit when rl_idx >= 33 end // Arm ST_ENC_A (regenerate A_hat via SampleNTT, same as ST_A). if (st == ST_ENC_LOAD && st_next == ST_ENC_A) begin snt_valid <= 1'b1; snt_ack <= 1'b1; a_pair <= 5'd0; a_i <= 3'd0; a_j <= 3'd0; a_widx <= 8'd0; a_busy <= 1'b0; end // Arm ST_ENC_TDEC (byteDecode12: ek -> t_hat in bank_a). Now follows // E5 (ST_ENC_C1) since e2 occupied bank_t during C/N/U and t_hat is // only needed for V. (Was armed off ST_ENC_A in the E1 scaffold.) if (st == ST_ENC_C1 && st_next == ST_ENC_TDEC) begin td_poly <= 3'd0; td_trip <= 8'd0; td_ph <= 3'd0; td_done <= 1'b0; end // ---- ST_ENC_TDEC: byteDecode12 ek -> t_hat[0..k-1] in bank_t ---- // 5-cycle micro-phase per triple (read-ahead, 1-cyc bram latency): // ph0: present b0 addr; ph1: capture b0, present b1; ph2: capture // b1, present b2; ph3: capture b2 + write c0; ph4: write c1, advance. if (st == ST_ENC_TDEC && !td_done) begin // capture the byte that arrived for the address presented last cycle if (td_ph == 3'd1) td_b0 <= ek_rd_data; if (td_ph == 3'd2) td_b1 <= ek_rd_data; if (td_ph == 3'd3) td_b2 <= ek_rd_data; // write decoded coeffs into bank_a at slot td_poly*K (so V's MAC // reads t_hat[j] via the same u_aslot=u_j*K addressing, u_row=0). if (td_ph == 3'd3) begin td_we <= 1'b1; td_wa <= (({4'd0,td_poly}*{9'd0,k_r})*256 + {td_trip, 1'b0}) & ((1< bank_a[E2_ASLOT], 256 coeffs ---- // Present read addr em_ridx (bank_t rel 0); bank_t read is registered // so bt_rd_data next cycle = e2[em_ridx]. Schedule the bank_a write // (em_we/em_widx) for that next cycle with em_widx == em_ridx, so the // write commits e2[R] into slot[R] (no off-by-one). if (st == ST_ENC_E2MV && !em_done) begin if (em_ridx <= 9'd255) begin // presenting a valid e2 addr this cycle em_we <= 1'b1; // -> write next cycle (ba_wd = bt_rd_data) em_widx <= em_ridx[7:0]; end if (em_ridx == 9'd256) em_done <= 1'b1; // last write (e2[255]) issued else em_ridx <= em_ridx + 9'd1; end // Arm ST_ENC_V when e2 relocated: prime first (only) row MAC (i=0,j=0). // Reuses the u_* MAC/INTT/ADD machine with u_row tied to 0. if (st == ST_ENC_E2MV && st_next == ST_ENC_V) begin u_row <= 3'd0; u_sub <= 2'd0; // MAC u_j <= 3'd0; u_ld <= 9'd0; u_oidx <= 8'd0; u_loading <= 1'b1; u_pending <= 1'b0; pm_valid <= 1'b0; end end end endmodule