// tb_mlkem_enc_katK_xsim.v - ML-KEM Encaps vs NIST KAT, parametric K (KP) + CASE. // E0 stage: verify H(ek), G(m||H(ek)) -> (ss=K, r). Preloads ek into ek_bram, // pulses start with op=1, waits for done, checks ss == KAT.ss and dumps H(ek)/r. // // xelab -generic_top KP=2|3|4 ; xsim -testplusarg CASE=n // ek/m/ct/ss vectors: sync_rtl/top/TB/vectors/enc_k{K}_c{N}_{ek,m,ct,ss}.hex // (per-byte hex, byte 0 first). `timescale 1ns/1ps module tb_mlkem_enc_katK_xsim; parameter KP = 2; localparam EKB = 384*KP + 32; // ek (=pk) bytes localparam CTB = (KP==4) ? 1568 : (32*(10*KP+4)); // ct bytes: K2 768,K3 1088,K4 1568 localparam DU = (KP==4) ? 11 : 10; // compression du localparam C1B = 32*DU*KP; // c1 byte count: K2 640,K3 960,K4 1408 reg clk=0, rst_n=0, start_i=0; reg [2:0] k_i; reg [255:0] d_i, z_i, m_i; wire busy_o, done_o; // ek preload port reg ek_in_we=0; reg [10:0] ek_in_addr=0; reg [7:0] ek_in_byte=0; wire [255:0] ss_o; reg [10:0] dbg_ct_idx_i=0; wire [7:0] dbg_ct_o; reg [3:0] dbg_slot_i=0; reg [7:0] dbg_idx_i=0; wire [11:0] dbg_coeff_o; reg dbg_byte_sel_i=0; reg [10:0] dbg_byte_idx_i=0; wire [7:0] dbg_byte_o; reg [11:0] dbg_dk_idx_i=0; wire [7:0] dbg_dk_o; wire [255:0] dbg_rho_o, dbg_sigma_o, dbg_r_o, dbg_hek_o; mlkem_top dut ( .clk(clk), .rst_n(rst_n), .k_i(k_i), .op_i(2'd1), .d_i(d_i), .z_i(z_i), .msg_i(m_i), .start_i(start_i), .busy_o(busy_o), .done_o(done_o), .ek_in_we(ek_in_we), .ek_in_addr(ek_in_addr), .ek_in_byte(ek_in_byte), .dk_in_we(1'b0), .dk_in_addr(12'd0), .dk_in_byte(8'd0), .c_in_we(1'b0), .c_in_addr(11'd0), .c_in_byte(8'd0), .ss_o(ss_o), .dbg_ct_idx_i(dbg_ct_idx_i), .dbg_ct_o(dbg_ct_o), .dbg_slot_i(dbg_slot_i), .dbg_idx_i(dbg_idx_i), .dbg_coeff_o(dbg_coeff_o), .dbg_byte_sel_i(dbg_byte_sel_i), .dbg_byte_idx_i(dbg_byte_idx_i), .dbg_byte_o(dbg_byte_o), .dbg_dk_idx_i(dbg_dk_idx_i), .dbg_dk_o(dbg_dk_o), .dbg_rho_o(dbg_rho_o), .dbg_sigma_o(dbg_sigma_o), .dbg_r_o(dbg_r_o), .dbg_hek_o(dbg_hek_o), .dbg_mprime_o(), .dbg_kbar_o(), .dbg_decz_o(), .dbg_dech_o() ); always #5 clk = ~clk; reg [7:0] ek_b [0:EKB-1]; reg [7:0] m_b [0:31]; reg [7:0] ss_b [0:31]; reg [7:0] ct_b [0:CTB-1]; integer c, i, errors, casenum, j; reg [8*80-1:0] tag, ekfile, mfile, ssfile, ctfile; initial begin if (!$value$plusargs("CASE=%d", casenum)) casenum = 0; $sformat(tag, "k%0d", KP); $sformat(ekfile, "sync_rtl/top/TB/vectors/enc_%0s_c%0d_ek.hex", tag, casenum); $sformat(mfile, "sync_rtl/top/TB/vectors/enc_%0s_c%0d_m.hex", tag, casenum); $sformat(ssfile, "sync_rtl/top/TB/vectors/enc_%0s_c%0d_ss.hex", tag, casenum); $sformat(ctfile, "sync_rtl/top/TB/vectors/enc_%0s_c%0d_ct.hex", tag, casenum); $readmemh(ekfile, ek_b); $readmemh(mfile, m_b); $readmemh(ssfile, ss_b); $readmemh(ctfile, ct_b); // build m_i: byte i in m_i[8*i +: 8] m_i = 256'd0; for (j = 0; j < 32; j = j + 1) m_i[8*j +: 8] = m_b[j]; k_i = KP[2:0]; $display("=== ML-KEM K=%0d Encaps KAT case %0d (E0) ===", KP, casenum); $write(" m = "); for (j=0;j<32;j=j+1) $write("%02x", m_b[j]); $write("\n"); rst_n=0; repeat(4) @(posedge clk); rst_n=1; @(posedge clk); // ---- preload ek into ek_bram (1 byte/cycle) ---- for (i = 0; i < EKB; i = i + 1) begin ek_in_we = 1'b1; ek_in_addr = i[10:0]; ek_in_byte = ek_b[i]; @(posedge clk); end ek_in_we = 1'b0; @(posedge clk); // ---- run Encaps ---- start_i=1; @(posedge clk); start_i=0; c=0; while(!done_o && c<2000000) begin @(posedge clk); c=c+1; end if(!done_o) begin $display("FAIL K=%0d case %0d: timeout", KP, casenum); $finish; end $display("=== Encaps E0 done in %0d cyc ===", c); $write(" H(ek) = "); for (j=0;j<32;j=j+1) $write("%02x", dbg_hek_o[8*j +: 8]); $write("\n"); $write(" r = "); for (j=0;j<32;j=j+1) $write("%02x", dbg_r_o[8*j +: 8]); $write("\n"); $write(" ss = "); for (j=0;j<32;j=j+1) $write("%02x", ss_o[8*j +: 8]); $write("\n"); // ---- check ss == KAT.ss ---- errors = 0; for (j = 0; j < 32; j = j + 1) if (ss_o[8*j +: 8] !== ss_b[j]) begin if (errors < 8) $display(" SS[%0d] got=%02x exp=%02x", j, ss_o[8*j +: 8], ss_b[j]); errors = errors + 1; end if (errors == 0) $display("K=%0d CASE %0d PASS (E0): ss == KAT.ss", KP, casenum); else $display("K=%0d CASE %0d FAIL (E0): %0d ss mismatches", KP, casenum, errors); // ---- E1: verify A_hat (slots 0..K^2-1). t_hat (byteDecode12) is re- // verified at E6 (V uses it); TDEC is deferred to V-prep so e2 can use // bank_t during C/N/U. A_hat equals KeyGen golden (K=2 c0). ---- // ---- E2: verify y[i], e1[i] (bank_se), e2 (bank_t slot_t) vs ml-kem-r. if (KP == 2 && casenum == 0) begin // E1 (verify_e1) dropped: E6's TDEC overwrites bank_a (A_hat slots) // with t_hat, so a post-run A_hat readback is invalid. A_hat is // transitively verified by E4 (transpose MAC) and E6 (v uses t_hat). verify_e2; verify_e3; verify_e4; verify_e6; end // E5: c1 = byteEncode_du(Compress_du(u)) must equal KAT.ct[0..C1B-1]. // Runs for every K/case (ct_b is the full KAT ciphertext). verify_e5; // E7: full ct = c1 || c2 must equal KAT.ct (all CTB bytes). End-to-end. verify_e7; $finish; end // E1 golden: A_hat[i][j] (KeyGen golden, K=2 c0) reg [11:0] ga [0:4*256-1]; // E2 golden: y0,y1,e1_0,e1_1 (bank_se rel slots 0..3), e2 (bank_t slot 0) reg [11:0] gy [0:4*256-1]; reg [11:0] ge2 [0:255]; integer ce, slot, idx; task verify_e1; begin $readmemh("test_framework/modules/mlkem_keygen/golden/c000_Ahat_0_0.hex", ga, 0, 255); $readmemh("test_framework/modules/mlkem_keygen/golden/c000_Ahat_0_1.hex", ga, 256, 511); $readmemh("test_framework/modules/mlkem_keygen/golden/c000_Ahat_1_0.hex", ga, 512, 767); $readmemh("test_framework/modules/mlkem_keygen/golden/c000_Ahat_1_1.hex", ga, 768, 1023); ce = 0; for (slot = 0; slot < 4; slot = slot + 1) for (idx = 0; idx < 256; idx = idx + 1) begin dbg_slot_i = slot[3:0]; dbg_idx_i = idx[7:0]; @(posedge clk); @(posedge clk); @(posedge clk); if (dbg_coeff_o !== ga[slot*256+idx]) begin if (ce < 8) $display(" A[s%0d,%0d] got=%03x exp=%03x", slot, idx, dbg_coeff_o, ga[slot*256+idx]); ce = ce + 1; end end if (ce == 0) $display("K=2 CASE 0 PASS (E1): A_hat (1024) == KeyGen golden"); else $display("K=2 CASE 0 FAIL (E1): %0d A mismatches", ce); end endtask // y[i],e1[i] live in bank_se at rel slots 0..K-1 (y), K..2K-1 (e1). // dbg slot for bank_se = slot_s_rt + rel. K=2: slot_s_rt=4 -> y0=4,y1=5,e1_0=6,e1_1=7. // e2 lives in bank_t rel slot 0 -> dbg slot = slot_t_rt = 8. task verify_e2; begin $readmemh("sync_rtl/top/TB/vectors/encgold/ec_k2_c0_e2.hex", ge2); ce = 0; // e2 at bank_t dbg slot 8 (survives; e1 is consumed/overwritten by u // in E4 -> e1 correctness is transitively verified by E4's u check). for (idx = 0; idx < 256; idx = idx + 1) begin dbg_slot_i = 8; dbg_idx_i = idx[7:0]; @(posedge clk); @(posedge clk); @(posedge clk); if (dbg_coeff_o !== ge2[idx]) begin if (ce < 8) $display(" E2[%0d] got=%03x exp=%03x", idx, dbg_coeff_o, ge2[idx]); ce = ce + 1; end end if (ce == 0) $display("K=2 CASE 0 PASS (E2): e2 == ml-kem-r golden"); else $display("K=2 CASE 0 FAIL (E2): %0d e2 mismatches", ce); end endtask // y_hat[i] = NTT(y[i]) in place at bank_se rel slots 0..K-1 -> dbg slots 4..5 (K=2). reg [11:0] gyh [0:2*256-1]; task verify_e3; begin $readmemh("sync_rtl/top/TB/vectors/encgold/ec_k2_c0_yhat_0.hex", gyh, 0, 255); $readmemh("sync_rtl/top/TB/vectors/encgold/ec_k2_c0_yhat_1.hex", gyh, 256, 511); ce = 0; for (slot = 0; slot < 2; slot = slot + 1) for (idx = 0; idx < 256; idx = idx + 1) begin dbg_slot_i = (4+slot); dbg_idx_i = idx[7:0]; @(posedge clk); @(posedge clk); @(posedge clk); if (dbg_coeff_o !== gyh[slot*256+idx]) begin if (ce < 8) $display(" YH[s%0d,%0d] got=%03x exp=%03x", slot, idx, dbg_coeff_o, gyh[slot*256+idx]); ce = ce + 1; end end if (ce == 0) $display("K=2 CASE 0 PASS (E3): y_hat[0..1] == ml-kem-r golden"); else $display("K=2 CASE 0 FAIL (E3): %0d coeff mismatches", ce); end endtask // u[i] = INTT(sum A^T o y_hat) + e1[i] over e1 in bank_se rel K+i -> dbg 6,7 (K=2). reg [11:0] gu [0:2*256-1]; task verify_e4; begin $readmemh("sync_rtl/top/TB/vectors/encgold/ec_k2_c0_u_0.hex", gu, 0, 255); $readmemh("sync_rtl/top/TB/vectors/encgold/ec_k2_c0_u_1.hex", gu, 256, 511); ce = 0; for (slot = 0; slot < 2; slot = slot + 1) for (idx = 0; idx < 256; idx = idx + 1) begin dbg_slot_i = (6+slot); dbg_idx_i = idx[7:0]; // bank_se rel K+slot (K=2) @(posedge clk); @(posedge clk); @(posedge clk); if (dbg_coeff_o !== gu[slot*256+idx]) begin if (ce < 8) $display(" U[s%0d,%0d] got=%03x exp=%03x", slot, idx, dbg_coeff_o, gu[slot*256+idx]); ce = ce + 1; end end if (ce == 0) $display("K=2 CASE 0 PASS (E4): u[0..1] == ml-kem-r golden"); else $display("K=2 CASE 0 FAIL (E4): %0d coeff mismatches", ce); end endtask initial begin #120000000; $display("FAIL: global timeout"); $finish; end // E5: read ct_bram bytes 0..C1B-1 via dbg_ct tap; compare to KAT.ct prefix. // dbg_ct_idx_i -> ct_rd_addr (1-cyc registered read) -> dbg_ct_o (comb tap): // wait 3 cycles per byte (same cadence as the coeff readback tasks). task verify_e5; integer be; begin be = 0; for (i = 0; i < C1B; i = i + 1) begin dbg_ct_idx_i = i[10:0]; @(posedge clk); @(posedge clk); @(posedge clk); if (dbg_ct_o !== ct_b[i]) begin if (be < 8) $display(" C1[%0d] got=%02x exp=%02x", i, dbg_ct_o, ct_b[i]); be = be + 1; end end if (be == 0) $display("K=%0d CASE %0d PASS (E5): c1 (%0d B) == KAT.ct prefix", KP, casenum, C1B); else $display("K=%0d CASE %0d FAIL (E5): %0d c1 byte mismatches", KP, casenum, be); end endtask // E6: v = INTT(sum_j t_hat[j] o y_hat[j]) + e2 + mu lives in bank_t rel slot // UPSUM=1 -> dbg slot slot_t_rt+1 = 9 (K=2). Compare to ml-kem-r golden v. reg [11:0] gv [0:255]; task verify_e6; begin $readmemh("sync_rtl/top/TB/vectors/encgold/ec_k2_c0_v.hex", gv); ce = 0; for (idx = 0; idx < 256; idx = idx + 1) begin dbg_slot_i = 9; dbg_idx_i = idx[7:0]; // bank_t rel UPSUM (K=2) @(posedge clk); @(posedge clk); @(posedge clk); if (dbg_coeff_o !== gv[idx]) begin if (ce < 8) $display(" V[%0d] got=%03x exp=%03x", idx, dbg_coeff_o, gv[idx]); ce = ce + 1; end end if (ce == 0) $display("K=2 CASE 0 PASS (E6): v == ml-kem-r golden"); else $display("K=2 CASE 0 FAIL (E6): %0d coeff mismatches", ce); end endtask // E7: full ciphertext ct = c1 || c2 read from ct_bram (0..CTB-1) via dbg_ct // tap; compare to KAT.ct byte-exact. This is the end-to-end Encaps check // (ct == KAT.ct). ss == KAT.ss is already checked at E0. task verify_e7; integer be; begin be = 0; // Dump the hardware-produced ct (read from ct_bram via dbg_ct tap), // then compare to KAT.ct byte-exact. The $write loop prints the whole // ciphertext on one line (byte 0 first), same format as ml-kem-r's // encaps_io example, so the two can be eyeballed / diffed. $write(" ct = "); for (i = 0; i < CTB; i = i + 1) begin dbg_ct_idx_i = i[10:0]; @(posedge clk); @(posedge clk); @(posedge clk); $write("%02x", dbg_ct_o); if (dbg_ct_o !== ct_b[i]) begin be = be + 1; end end $write("\n"); if (be == 0) $display("K=%0d CASE %0d PASS (E7): ct (%0d B) == KAT.ct [ct==KAT.ct && ss==KAT.ss]", KP, casenum, CTB); else begin $display("K=%0d CASE %0d FAIL (E7): %0d ct byte mismatches", KP, casenum, be); // re-scan to print the first few mismatching byte positions be = 0; for (i = 0; i < CTB; i = i + 1) begin dbg_ct_idx_i = i[10:0]; @(posedge clk); @(posedge clk); @(posedge clk); if (dbg_ct_o !== ct_b[i]) begin if (be < 8) $display(" CT[%0d] got=%02x exp=%02x", i, dbg_ct_o, ct_b[i]); be = be + 1; end end end end endtask endmodule