// tb_mlkem_dec_katK_xsim.v - ML-KEM Decaps vs NIST KAT, parametric K (KP) + CASE. // D0 stage: stream dk (=sk) into the design via dk_in_* (routed to // dk_pke/ek_pke/h/z by region) and ct via c_in_*, pulse start with op=2, and // verify the dk PARSE: H(ek) (dbg_dech_o), z (dbg_decz_o), and round-trip a few // ek_pke bytes (dbg_byte sel=0) and dk_pke bytes (sel=1) back out of BRAM. // // xelab -generic_top KP=2|3|4 ; xsim -testplusarg CASE=n // dk/ct/ss vectors: sync_rtl/top/TB/vectors/dec_k{K}_c{N}_{dk,ct,ss,ctn,ssn}.hex `timescale 1ns/1ps module tb_mlkem_dec_katK_xsim; parameter KP = 2; localparam DKB = 768*KP + 96; // dk (=sk) bytes: 1632/2400/3168 localparam EKB = 384*KP + 32; // ek_pke bytes within dk localparam DKPB = 384*KP; // dk_pke bytes localparam CTB = (KP==4) ? 1568 : (32*(10*KP+4)); // ct bytes: 768/1088/1568 reg clk=0, rst_n=0, start_i=0; reg [2:0] k_i; reg [255:0] d_i=0, z_i=0, m_i=0; wire busy_o, done_o; // ek preload port (unused in Decaps; ek_pke comes from dk) reg ek_in_we=0; reg [10:0] ek_in_addr=0; reg [7:0] ek_in_byte=0; // dk / c input ports reg dk_in_we=0; reg [11:0] dk_in_addr=0; reg [7:0] dk_in_byte=0; reg c_in_we=0; reg [10:0] c_in_addr=0; reg [7:0] c_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; wire [255:0] dbg_mprime_o, dbg_kbar_o, dbg_decz_o, dbg_dech_o; mlkem_top dut ( .clk(clk), .rst_n(rst_n), .k_i(k_i), .op_i(2'd2), .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(dk_in_we), .dk_in_addr(dk_in_addr), .dk_in_byte(dk_in_byte), .c_in_we(c_in_we), .c_in_addr(c_in_addr), .c_in_byte(c_in_byte), .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_mprime_o), .dbg_kbar_o(dbg_kbar_o), .dbg_decz_o(dbg_decz_o), .dbg_dech_o(dbg_dech_o) ); always #5 clk = ~clk; reg [7:0] dk_b [0:DKB-1]; reg [7:0] ct_b [0:CTB-1]; reg [7:0] ss_b [0:31]; integer c, i, j, errors, casenum; reg [8*80-1:0] tag, dkfile, ctfile, ssfile; initial begin if (!$value$plusargs("CASE=%d", casenum)) casenum = 0; $sformat(tag, "k%0d", KP); $sformat(dkfile, "sync_rtl/top/TB/vectors/dec_%0s_c%0d_dk.hex", tag, casenum); $sformat(ctfile, "sync_rtl/top/TB/vectors/dec_%0s_c%0d_ct.hex", tag, casenum); $sformat(ssfile, "sync_rtl/top/TB/vectors/dec_%0s_c%0d_ss.hex", tag, casenum); $readmemh(dkfile, dk_b); $readmemh(ctfile, ct_b); $readmemh(ssfile, ss_b); k_i = KP[2:0]; $display("=== ML-KEM K=%0d Decaps KAT case %0d (D0: load+parse) ===", KP, casenum); rst_n=0; repeat(4) @(posedge clk); rst_n=1; @(posedge clk); // ---- stream dk into the design (1 byte/cycle) ---- for (i = 0; i < DKB; i = i + 1) begin dk_in_we = 1'b1; dk_in_addr = i[11:0]; dk_in_byte = dk_b[i]; @(posedge clk); end dk_in_we = 1'b0; // ---- stream ct into c_in_bram (1 byte/cycle) ---- for (i = 0; i < CTB; i = i + 1) begin c_in_we = 1'b1; c_in_addr = i[10:0]; c_in_byte = ct_b[i]; @(posedge clk); end c_in_we = 1'b0; @(posedge clk); // ---- run Decaps ---- 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("=== Decaps D0 done in %0d cyc ===", c); verify_d0; $finish; end initial begin #120000000; $display("FAIL: global timeout"); $finish; end // D0: verify dk parse. H(ek)=dk[768K+32:+32], z=dk[768K+64:+32] captured into // hek_r/z_r (dbg_dech_o/dbg_decz_o). ek_pke=dk[384K:768K+32] in ek_bram // (dbg_byte sel=0), dk_pke=dk[0:384K] in dkp_bram (sel=1). task verify_d0; integer be; reg [7:0] got; begin errors = 0; // H(ek) for (j = 0; j < 32; j = j + 1) if (dbg_dech_o[8*j +: 8] !== dk_b[DKPB + EKB + j]) errors = errors + 1; if (errors == 0) $display(" PASS: H(ek) parsed == dk[768K+32 ..]"); else $display(" FAIL: H(ek) %0d byte mismatches", errors); // z be = 0; for (j = 0; j < 32; j = j + 1) if (dbg_decz_o[8*j +: 8] !== dk_b[DKPB + EKB + 32 + j]) be = be + 1; if (be == 0) $display(" PASS: z parsed == dk[768K+64 ..]"); else $display(" FAIL: z %0d byte mismatches", be); errors = errors + be; // ek_pke round-trip (every 97th byte to keep it quick) be = 0; for (i = 0; i < EKB; i = i + 97) begin dbg_byte_sel_i = 1'b0; dbg_byte_idx_i = i[10:0]; @(posedge clk); @(posedge clk); if (dbg_byte_o !== dk_b[DKPB + i]) begin if (be < 6) $display(" ekpke[%0d] got=%02x exp=%02x", i, dbg_byte_o, dk_b[DKPB+i]); be = be + 1; end end if (be == 0) $display(" PASS: ek_pke round-trip (BRAM) == dk[384K ..]"); else $display(" FAIL: ek_pke %0d byte mismatches", be); errors = errors + be; // dk_pke round-trip be = 0; for (i = 0; i < DKPB; i = i + 97) begin dbg_byte_sel_i = 1'b1; dbg_byte_idx_i = i[10:0]; @(posedge clk); @(posedge clk); if (dbg_byte_o !== dk_b[i]) begin if (be < 6) $display(" dkpke[%0d] got=%02x exp=%02x", i, dbg_byte_o, dk_b[i]); be = be + 1; end end if (be == 0) $display(" PASS: dk_pke round-trip (BRAM) == dk[0 ..]"); else $display(" FAIL: dk_pke %0d byte mismatches", be); errors = errors + be; if (errors == 0) $display("K=%0d CASE %0d PASS (D0): dk parse OK", KP, casenum); else $display("K=%0d CASE %0d FAIL (D0): %0d total errors", KP, casenum, errors); end endtask endmodule