Stage 0+1 of mlkem_top KeyGen integration: - sha3_top: add multi-block SHA3-256 absorb FSM (mb_en/mb_block_i/mb_valid_i/ mb_last_i/mb_ready_o). Caller pre-pads final block; module does pure absorb loop (state^=block; Keccak-p). Single-block G/H/J paths bit-identical when mb_en=0. Sticky digest register holds output until consumer acks. - tb_sha3_mb_xsim: self-checking TB streams 800B ek (6 blocks) -> H(ek), verified == hashlib.sha3_256. Proper valid/ready handshake (no force). - Existing G/H/J TBs (xsim + Verilator) tie off mb_* ports; both frameworks regress clean (Verilator 25/25, XSIM G/H/J + keccak + 7-vec + multiblock). - test_framework/modules/mlkem_keygen/golden: full 256-coeff per-stage intermediates (rho/sigma, A_hat, s/e, s_hat/e_hat, t_hat, ek, dk_pke) for KAT count=0..4, dumped by ml-kem-r and self-verified against NIST KAT.
181 lines
7.0 KiB
Verilog
181 lines
7.0 KiB
Verilog
// tb_sha3_xsim_simple.v - Self-checking testbench for sha3_top (G/H/J modes)
|
|
//
|
|
// Drives sha3_top in all three modes and checks hash_o against expected
|
|
// values computed by the verified Python reference (server_code SHA_3,
|
|
// KAT-validated). Vectors are deterministic (gen_vectors seed=20260627).
|
|
//
|
|
// G (mode=00, SHA3-512): data_i[263:0] = d||k, output 512 bits
|
|
// H (mode=01, SHA3-256): data_i[255:0] = ek, output low 256 bits
|
|
// J (mode=10, SHAKE-256): data_i[511:0] = z||c, output low 256 bits
|
|
//
|
|
// data_i / hash_o use MSB-first hex packing (hex[511:0] literal == data_i).
|
|
//
|
|
// Usage:
|
|
// xvlog -sv sha3_top.v tb_sha3_xsim_simple.v
|
|
// xelab tb_sha3_xsim_simple -s sha3_sim
|
|
// xsim sha3_sim -R
|
|
|
|
`timescale 1ns / 1ps
|
|
|
|
module tb_sha3_xsim_simple;
|
|
|
|
// ================================================================
|
|
// DUT signals
|
|
// ================================================================
|
|
reg clk;
|
|
reg rst_n;
|
|
reg [1:0] mode;
|
|
reg [511:0] data_i;
|
|
reg valid_i;
|
|
wire ready_o;
|
|
wire [511:0] hash_o;
|
|
wire valid_o;
|
|
reg ready_i;
|
|
|
|
// ================================================================
|
|
// DUT instantiation
|
|
// ================================================================
|
|
sha3_top u_dut (
|
|
.clk (clk),
|
|
.rst_n (rst_n),
|
|
.mode (mode),
|
|
.data_i (data_i),
|
|
.valid_i (valid_i),
|
|
.ready_o (ready_o),
|
|
.hash_o (hash_o),
|
|
.valid_o (valid_o),
|
|
.ready_i (ready_i),
|
|
// multi-block absorb path disabled for single-block G/H/J tests
|
|
.mb_en (1'b0),
|
|
.mb_block_i (1088'b0),
|
|
.mb_valid_i (1'b0),
|
|
.mb_last_i (1'b0),
|
|
.mb_ready_o ()
|
|
);
|
|
|
|
// ================================================================
|
|
// Clock generation: 100 MHz (10 ns period)
|
|
// ================================================================
|
|
initial clk = 1'b0;
|
|
always #5 clk = ~clk;
|
|
|
|
// ================================================================
|
|
// Expected vectors (verified Python reference, seed=20260627)
|
|
// ================================================================
|
|
// G: data_i[263:0] = {k=2, d}; output 512-bit SHA3-512
|
|
localparam [511:0] G_DATA = 512'h000000000000000000000000000000000000000000000000000000000000007DBC2AC0D13D719B37B4E2D4691951FF890A97854EF5D3A8957EF67A54978E26C9;
|
|
localparam [511:0] G_EXP = 512'h615E530C77D5D834311E922DB99D5B7F1D57C7B08F029FD829914D3F4035FB730350FD00A852A5CCE9CFC79CF8C61384FEA115030E41750A6AE2CFE055D7976D;
|
|
|
|
// H: data_i[255:0] = ek; output 256-bit SHA3-256 (in hash_o[255:0])
|
|
localparam [511:0] H_DATA = 512'h000000000000000000000000000000000000000000000000000000000000000047885BF4E257CF39645D34C593047B7570D6ABBA300599D96171A950BB5027D5;
|
|
localparam [255:0] H_EXP = 256'h6B44867E24B29A9231570DF5E1D6D14DB0C29EBD7A40AE98606EF66B8244C308;
|
|
|
|
// J: data_i[511:0] = {c, z}; output 256-bit SHAKE-256 (in hash_o[255:0])
|
|
localparam [511:0] J_DATA = 512'hB73DCA0F437D2334320494E5D0F728D73F5275E342572FF9B0219DC338CB3C2F0F7398474A3D68C4C90B777F42FA4C12B1FC8F70E1DAADF20755473CC2653D3C;
|
|
localparam [255:0] J_EXP = 256'hECEC4DAD11DFF42D911925DB83F13D119209AB4EE182E9E9BA0F29F5524B240E;
|
|
|
|
integer error_count;
|
|
integer cycle_count;
|
|
parameter TIMEOUT = 200;
|
|
|
|
// ================================================================
|
|
// Drive one mode and self-check against expected output
|
|
// ================================================================
|
|
task run_mode(input [1:0] m, input [511:0] din,
|
|
input [511:0] exp, input integer out_bits,
|
|
input [127:0] label);
|
|
reg [511:0] got;
|
|
begin
|
|
@(posedge clk);
|
|
mode <= m;
|
|
data_i <= din;
|
|
valid_i <= 1'b1;
|
|
@(posedge clk);
|
|
valid_i <= 1'b0;
|
|
|
|
cycle_count = 0;
|
|
while (!valid_o && cycle_count < TIMEOUT) begin
|
|
@(posedge clk);
|
|
cycle_count = cycle_count + 1;
|
|
end
|
|
|
|
if (cycle_count >= TIMEOUT) begin
|
|
$display("FAIL [%0s]: TIMEOUT, valid_o not asserted", label);
|
|
error_count = error_count + 1;
|
|
end else begin
|
|
got = hash_o;
|
|
if (out_bits == 512) begin
|
|
if (got !== exp) begin
|
|
$display("FAIL [%0s]: hash mismatch", label);
|
|
$display(" got = 512'h%0h", got);
|
|
$display(" exp = 512'h%0h", exp);
|
|
error_count = error_count + 1;
|
|
end else begin
|
|
$display("PASS [%0s]: hash_o = 512'h%0h", label, got);
|
|
end
|
|
end else begin
|
|
// Compare low 256 bits only
|
|
if (got[255:0] !== exp[255:0]) begin
|
|
$display("FAIL [%0s]: hash mismatch", label);
|
|
$display(" got = 256'h%0h", got[255:0]);
|
|
$display(" exp = 256'h%0h", exp[255:0]);
|
|
error_count = error_count + 1;
|
|
end else begin
|
|
$display("PASS [%0s]: hash_o = 256'h%0h", label, got[255:0]);
|
|
end
|
|
end
|
|
end
|
|
|
|
// Wait for DUT to return to IDLE
|
|
@(posedge clk);
|
|
while (!ready_o) @(posedge clk);
|
|
end
|
|
endtask
|
|
|
|
// ================================================================
|
|
// Test sequence
|
|
// ================================================================
|
|
initial begin
|
|
error_count = 0;
|
|
|
|
$display("========================================");
|
|
$display(" SHA3 Top Testbench (G / H / J modes)");
|
|
$display("========================================");
|
|
|
|
mode <= 2'd0;
|
|
data_i <= 512'd0;
|
|
valid_i <= 1'b0;
|
|
ready_i <= 1'b1;
|
|
|
|
rst_n <= 1'b0;
|
|
repeat (3) @(posedge clk);
|
|
rst_n <= 1'b1;
|
|
@(posedge clk);
|
|
|
|
$display("INFO: Reset complete. Running G/H/J...");
|
|
|
|
run_mode(2'd0, G_DATA, G_EXP, 512, "G SHA3-512");
|
|
run_mode(2'd1, H_DATA, {256'd0,H_EXP},256, "H SHA3-256");
|
|
run_mode(2'd2, J_DATA, {256'd0,J_EXP},256, "J SHAKE-256");
|
|
|
|
$display("========================================");
|
|
if (error_count == 0)
|
|
$display("ALL TESTS PASSED (3/3 modes)");
|
|
else
|
|
$display("TESTS FAILED: %0d error(s)", error_count);
|
|
$display("========================================");
|
|
|
|
$finish;
|
|
end
|
|
|
|
// ================================================================
|
|
// Timeout watchdog
|
|
// ================================================================
|
|
initial begin
|
|
#(TIMEOUT * 10 * 100);
|
|
$display("FATAL: Global simulation timeout");
|
|
$finish;
|
|
end
|
|
|
|
endmodule
|