// tb_en_xsim.v - Encaps-only KAT testbench for mlkem_top // // Runs full ML-KEM flow (kg→en→de) but only CHECKS Encaps results. // Uses existing vector files from sync_rtl/top/TB/vectors/. // // Usage: // xvlog -sv -i . .v sync_rtl/en/TB/tb_en_xsim.v // xelab tb_en_xsim -s tb_en_xsim --timescale 1ns/1ps // xsim tb_en_xsim -R `timescale 1ns / 1ps module tb_en_xsim; parameter VECTOR_FILE = "sync_rtl/top/TB/vectors/mlkem_top_input.hex"; parameter EXPECTED_FILE = "sync_rtl/top/TB/vectors/mlkem_top_expected.hex"; parameter MAX_VECTORS = 16; parameter TIMEOUT_CYCLES = 10000000; parameter K_PARAM = 4; localparam PK_WIDTH = 12 * K_PARAM * 256; localparam SK_WIDTH = 12 * K_PARAM * 256; localparam EXP_PK_WIDTH = 6400; localparam EXP_SK_WIDTH = 13056; localparam EXP_CT_WIDTH = 6144; localparam EXP_SS_WIDTH = 256; // DUT signals reg clk; reg rst_n; reg [1:0] mode; reg [2:0] i_k; reg valid_i; wire ready_o; wire [PK_WIDTH-1:0] pk_o; wire [SK_WIDTH-1:0] sk_o; wire pk_valid; wire sk_valid; wire [EXP_CT_WIDTH*K_PARAM/2-1:0] ct_o; wire [255:0] K_o; wire ct_valid; wire K_valid; wire [255:0] K_o_dec; wire K_valid_dec; wire done_o; // DUT instantiation mlkem_top #(.K(K_PARAM)) u_dut ( .clk(clk), .rst_n(rst_n), .mode(mode), .i_k(i_k), .valid_i(valid_i), .ready_o(ready_o), .pk_o(pk_o), .sk_o(sk_o), .pk_valid(pk_valid), .sk_valid(sk_valid), .ct_o(ct_o), .K_o(K_o), .ct_valid(ct_valid), .K_valid(K_valid), .K_o_dec(K_o_dec), .K_valid_dec(K_valid_dec), .done_o(done_o) ); initial clk = 1'b0; always #5 clk = ~clk; reg [767:0] input_mem [0:MAX_VECTORS-1]; reg [25855:0] expected_mem [0:MAX_VECTORS-1]; integer vec_count, idx, en_pass, en_fail; function [7:0] nibble_to_ascii; input [3:0] nibble; nibble_to_ascii = (nibble < 4'd10) ? (8'h30 + {4'd0, nibble}) : (8'h41 + ({4'd0, nibble} - 4'd10)); endfunction task print_hex256; input [255:0] val; input [256*8:1] label; integer bi; begin $write("%s: ", label); for (bi = 63; bi >= 0; bi = bi - 1) $write("%c", nibble_to_ascii(val[(bi*4)+:4])); $write("\n"); end endtask integer wfd_result; task wait_for_done; input [256*8:1] op_name; integer cyc; begin cyc = 0; while (!done_o && cyc < TIMEOUT_CYCLES) begin @(posedge clk); cyc = cyc + 1; end if (cyc >= TIMEOUT_CYCLES) begin $display("ERROR: %s timeout after %0d cycles", op_name, TIMEOUT_CYCLES); wfd_result = 0; end else begin $display("INFO: %s done after %0d cycles", op_name, cyc); wfd_result = 1; end end endtask initial begin vec_count = 0; $readmemh(VECTOR_FILE, input_mem); $readmemh(EXPECTED_FILE, expected_mem); begin integer found_end = 0; for (idx = 0; idx < MAX_VECTORS; idx = idx + 1) begin if (!found_end && (input_mem[idx] === 768'hx || input_mem[idx] === 768'hz)) found_end = 1; else if (!found_end) vec_count = vec_count + 1; end end if (vec_count == 0) begin $display("ERROR: No vectors loaded from %s", VECTOR_FILE); $finish; end $display("===================================================="); $display("MLKEM_TOP Encaps-ONLY KAT TESTBENCH"); $display(" Vectors loaded: %0d", vec_count); $display("===================================================="); mode <= 2'd0; i_k <= 3'd2; valid_i <= 1'b0; rst_n <= 1'b0; repeat (5) @(posedge clk); rst_n <= 1'b1; @(posedge clk); force u_dut.chain_kc_ready_o = 1'b1; force u_dut.ntt_valid_o = 1'b1; en_pass = 0; en_fail = 0; for (idx = 0; idx < vec_count; idx = idx + 1) begin reg [255:0] d_val, msg_val, z_val; reg [EXP_CT_WIDTH-1:0] exp_ct; reg [EXP_SS_WIDTH-1:0] exp_ss; d_val = input_mem[idx][767:512]; msg_val = input_mem[idx][511:256]; z_val = input_mem[idx][255:0]; exp_ss = expected_mem[idx][0 +: EXP_SS_WIDTH]; exp_ct = expected_mem[idx][EXP_SS_WIDTH +: EXP_CT_WIDTH]; // KeyGen (run but don't check) force u_dut.d_reg = d_val; mode <= 2'b00; i_k <= 3'd2; valid_i <= 1'b1; @(posedge clk); valid_i <= 1'b0; wait_for_done("KeyGen"); if (wfd_result) release u_dut.d_reg; else begin release u_dut.d_reg; rst_n <= 1'b0; repeat (5) @(posedge clk); rst_n <= 1'b1; force u_dut.chain_kc_ready_o = 1'b1; force u_dut.ntt_valid_o = 1'b1; @(posedge clk); end repeat (2) @(posedge clk); // Encaps (check this) $display("--- Vector %0d: Encaps ---", idx); print_hex256(msg_val, " msg"); force u_dut.m_reg = msg_val; mode <= 2'b01; i_k <= 3'd2; valid_i <= 1'b1; @(posedge clk); valid_i <= 1'b0; wait_for_done("Encaps"); if (wfd_result) begin release u_dut.m_reg; if (ct_valid && K_valid) begin if (ct_o[EXP_CT_WIDTH-1:0] == exp_ct) begin $display(" PASS: ct matches expected"); en_pass = en_pass + 1; end else begin $display(" FAIL: ct mismatch"); en_fail = en_fail + 1; end if (K_o == exp_ss) $display(" PASS: K matches expected ss"); else begin $display(" FAIL: K mismatch"); en_fail = en_fail + 1; end end else begin $display(" FAIL: ct_valid=%b K_valid=%b", ct_valid, K_valid); en_fail = en_fail + 1; end end else begin release u_dut.m_reg; $display(" FAIL: Encaps timeout"); en_fail = en_fail + 1; rst_n <= 1'b0; repeat (5) @(posedge clk); rst_n <= 1'b1; force u_dut.chain_kc_ready_o = 1'b1; force u_dut.ntt_valid_o = 1'b1; @(posedge clk); end repeat (2) @(posedge clk); // Decaps (run but don't check) force u_dut.z_reg = z_val; mode <= 2'b10; i_k <= 3'd2; valid_i <= 1'b1; @(posedge clk); valid_i <= 1'b0; wait_for_done("Decaps"); if (wfd_result) release u_dut.z_reg; else begin release u_dut.z_reg; rst_n <= 1'b0; repeat (5) @(posedge clk); rst_n <= 1'b1; force u_dut.chain_kc_ready_o = 1'b1; force u_dut.ntt_valid_o = 1'b1; @(posedge clk); end repeat (2) @(posedge clk); end release u_dut.chain_kc_ready_o; release u_dut.ntt_valid_o; $display("===================================================="); $display("ENCAPS TEST COMPLETE"); $display(" PASS: %0d FAIL: %0d", en_pass, en_fail); $display("===================================================="); $finish; end initial begin #(TIMEOUT_CYCLES * 10 * 100); $display("FATAL: Global simulation timeout"); $finish; end endmodule