feat(tb): add Vivado XSIM Verilog testbenches for all 10 sync modules

Add file-based vector testbenches ( + ) for:
- mod_add_sync, rng_sync, poly_arith_sync, comp_decomp_sync
- s_bram/sd_bram, sha3_chain_top
- ntt_core, poly_mul_sync
- sample_cbd_sync, sample_ntt_sync

Each module includes:
- tb_<module>_xsim.v: Vivado XSIM testbench
- gen_vectors.py: Python vector generator (stdlib only)
- vectors/<module>_input.hex: test input vectors
- xsim_run.tcl: compile + elaborate + simulate script
This commit is contained in:
2026-06-25 20:48:38 +08:00
parent ae5f0ca048
commit d4c3fc86fc
42 changed files with 7745 additions and 0 deletions

View File

@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""gen_vectors.py - Generate test vectors for poly_arith_sync XSIM testbench.
Produces sync_rtl/poly_arith/TB/vectors/poly_arith_input.hex
Each line is a 40-bit hex value (10 hex chars, no spaces):
bits[39:28] = expected[11:0]
bits[27:16] = coeff_b_in[11:0]
bits[15:4] = coeff_a_in[11:0]
bit[3] = mode (0=add, 1=sub)
bits[2:0] = padding (0)
expected = (coeff_a +/- coeff_b) mod Q, Q = 3329
Edge cases covered:
- zeros
- max values (Q-1 = 3328)
- mid-range
- overflow/underflow (a + b >= Q, a - b < 0)
"""
import os
import sys
Q = 3329
Q_MINUS_1 = Q - 1 # 3328
# Output path (relative to project root, where script should be run)
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "vectors")
OUTPUT_FILE = os.path.join(OUTPUT_DIR, "poly_arith_input.hex")
def mod_add(a: int, b: int) -> int:
"""(a + b) mod Q."""
return (a + b) % Q
def mod_sub(a: int, b: int) -> int:
"""(a - b) mod Q."""
return (a - b) % Q
def pack_vector(expected: int, coeff_b: int, coeff_a: int, mode: int) -> int:
"""Pack a single test vector into a 40-bit value."""
val = 0
val |= (expected & 0xFFF) << 28 # bits 39:28
val |= (coeff_b & 0xFFF) << 16 # bits 27:16
val |= (coeff_a & 0xFFF) << 4 # bits 15:4
val |= (mode & 0x1) << 3 # bit 3
# bits 2:0 are padding = 0
return val
def generate() -> list[int]:
"""Generate all test vectors. Returns list of packed 40-bit values."""
vectors: list[int] = []
# Helper to add a vector
def add_vector(a: int, b: int, mode: int) -> None:
if mode == 0:
exp = mod_add(a, b)
else:
exp = mod_sub(a, b)
vectors.append(pack_vector(exp, b, a, mode))
# ---- ADD mode vectors (mode=0) ----
# Zero + zero
add_vector(0, 0, 0)
# Max + max = (3328 + 3328) % 3329 = 6656 % 3329 = 3327
add_vector(Q_MINUS_1, Q_MINUS_1, 0)
# Zero + max
add_vector(0, Q_MINUS_1, 0)
add_vector(Q_MINUS_1, 0, 0)
# Mid-range values
for a, b in [(1000, 2000), (1500, 1500), (1, 2), (42, 137)]:
add_vector(a, b, 0)
# Overflow: a + b > Q
add_vector(2000, 2000, 0) # 4000 % 3329 = 671
add_vector(3000, 1000, 0) # 4000 % 3329 = 671
add_vector(3328, 1, 0) # 3329 % 3329 = 0
# Edge: Q-sized values that just fit
add_vector(1664, 1665, 0) # 3329 -> 0
add_vector(1664, 1664, 0) # 3328 -> 3328
# ---- SUB mode vectors (mode=1) ----
# Zero - zero
add_vector(0, 0, 1)
# Max - max
add_vector(Q_MINUS_1, Q_MINUS_1, 1)
# Zero - max: (0 - 3328) % 3329 = 1
add_vector(0, Q_MINUS_1, 1)
# Max - zero
add_vector(Q_MINUS_1, 0, 1)
# Mid-range values
for a, b in [(2000, 1000), (1500, 1500), (2, 1), (137, 42)]:
add_vector(a, b, 1)
# Underflow: a < b -> negative result
add_vector(1000, 2000, 1) # (1000 - 2000) % 3329 = 2329
add_vector(0, 1, 1) # 3328
add_vector(1, 3328, 1) # (1 - 3328) % 3329 = 2
add_vector(0, 2, 1) # 3327
# Edge cases
add_vector(1, 3328, 1) # (1 - 3328) % 3329 = 2
# Run through all combinations of a few edge values
for a in [0, 1, 1664, 3327, 3328]:
for b in [0, 1, 1664, 3327, 3328]:
add_vector(a, b, 0)
add_vector(a, b, 1)
return vectors
def write_vectors(vectors: list[int]) -> None:
"""Write vectors to hex file."""
os.makedirs(OUTPUT_DIR, exist_ok=True)
with open(OUTPUT_FILE, "w") as f:
for v in vectors:
# 40 bits = 10 hex digits
f.write(f"{v:010X}\n")
print(f"Generated {len(vectors)} test vectors -> {OUTPUT_FILE}")
def main() -> int:
vectors = generate()
write_vectors(vectors)
# Print a few samples for debugging
print("\nSample vectors (first 5):")
for i, v in enumerate(vectors[:5]):
exp = (v >> 28) & 0xFFF
coeff_b = (v >> 16) & 0xFFF
coeff_a = (v >> 4) & 0xFFF
mode = (v >> 3) & 0x1
op = "ADD" if mode == 0 else "SUB"
print(f" [{i}] a={coeff_a:04d} b={coeff_b:04d} mode={op} expected={exp:04d}")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,254 @@
// tb_poly_arith_xsim.v - Standard Verilog testbench for poly_arith_sync targeting Vivado xsim
//
// Reads test vectors from a hex file using $readmemh.
// Each line is a 40-bit hex value (10 hex chars, no spaces):
// bits[39:28] = expected[11:0]
// bits[27:16] = coeff_b_in[11:0]
// bits[15:4] = coeff_a_in[11:0]
// bit[3] = mode (0=add, 1=sub)
// bits[2:0] = padding
//
// Drives poly_arith_sync, waits for valid_o, compares coeff_out with expected,
// and reports pass/fail.
//
// Parameters:
// VECTOR_FILE - path to input hex file (default: "sync_rtl/poly_arith/TB/vectors/poly_arith_input.hex")
// RESULT_FILE - path to output file (default: "sync_rtl/poly_arith/TB/vectors/poly_arith_result.hex")
//
// Usage with xsim_run.tcl or manual:
// xvlog -sv sync_rtl/common/pipeline_reg.v sync_rtl/poly_arith/poly_arith_sync.v tb_poly_arith_xsim.v
// xelab tb_poly_arith_xsim -s tb_poly_arith_xsim
// xsim tb_poly_arith_xsim -R
`timescale 1ns / 1ps
module tb_poly_arith_xsim;
// ================================================================
// Parameters
// ================================================================
parameter VECTOR_FILE = "sync_rtl/poly_arith/TB/vectors/poly_arith_input.hex";
parameter RESULT_FILE = "sync_rtl/poly_arith/TB/vectors/poly_arith_result.hex";
parameter MAX_VECTORS = 256;
parameter TIMEOUT_CYCLES = 10000;
parameter Q = 3329;
// ================================================================
// DUT signals
// ================================================================
reg clk;
reg rst_n;
reg [11:0] coeff_a_in;
reg [11:0] coeff_b_in;
reg mode;
reg valid_i;
wire ready_o;
wire [11:0] coeff_out;
wire valid_o;
reg ready_i;
// ================================================================
// DUT instantiation (named ports)
// ================================================================
poly_arith_sync u_dut (
.clk (clk),
.rst_n (rst_n),
.coeff_a_in (coeff_a_in),
.coeff_b_in (coeff_b_in),
.mode (mode),
.valid_i (valid_i),
.ready_o (ready_o),
.coeff_out (coeff_out),
.valid_o (valid_o),
.ready_i (ready_i)
);
// ================================================================
// Clock generation: 100 MHz (10 ns period)
// ================================================================
initial clk = 1'b0;
always #5 clk = ~clk;
// ================================================================
// Vector memory (loaded by $readmemh)
// 40 bits per word:
// bits[39:28] = expected
// bits[27:16] = coeff_b_in
// bits[15:4] = coeff_a_in
// bit[3] = mode
// bits[2:0] = padding
// ================================================================
reg [39:0] vector_mem [0:MAX_VECTORS-1];
integer vec_count;
integer idx;
integer cycle_count;
integer result_fd;
// Test result tracking
integer pass_count;
integer fail_count;
// ================================================================
// Hex-to-ASCII conversion helper
// ================================================================
function [7:0] nibble_to_ascii;
input [3:0] nibble;
begin
if (nibble < 4'd10)
nibble_to_ascii = 8'h30 + {4'd0, nibble}; // '0'-'9'
else
nibble_to_ascii = 8'h41 + ({4'd0, nibble} - 4'd10); // 'A'-'F'
end
endfunction
// ================================================================
// Main test sequence
// ================================================================
initial begin
// Count loaded vectors
vec_count = 0;
// Load vectors from hex file
$readmemh(VECTOR_FILE, vector_mem);
// Count non-X/non-Z entries to determine actual vector count
begin
integer found_end;
found_end = 0;
for (idx = 0; idx < MAX_VECTORS; idx = idx + 1) begin
if (!found_end && (vector_mem[idx] === 40'hx || vector_mem[idx] === 40'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);
$display(" Check that the file exists and is in the correct format.");
$display(" Each line: <10 hex chars> = {expected[11:0], coeff_b[11:0], coeff_a[11:0], mode, 3'b0}");
$finish;
end
$display("INFO: Loaded %0d test vectors from %s", vec_count, VECTOR_FILE);
// Open result file
result_fd = $fopen(RESULT_FILE, "w");
if (result_fd == 0) begin
$display("ERROR: Cannot open result file: %s", RESULT_FILE);
$finish;
end
// Initialize DUT inputs
coeff_a_in <= 12'd0;
coeff_b_in <= 12'd0;
mode <= 1'b0;
valid_i <= 1'b0;
ready_i <= 1'b1; // always ready to accept output
// Reset sequence: rst_n low for 3 cycles, then high
rst_n <= 1'b0;
repeat (3) @(posedge clk);
rst_n <= 1'b1;
@(posedge clk);
pass_count = 0;
fail_count = 0;
// ============================================================
// Process each vector
// ============================================================
for (idx = 0; idx < vec_count; idx = idx + 1) begin
begin
reg [11:0] vec_expected;
reg [11:0] vec_coeff_b;
reg [11:0] vec_coeff_a;
reg vec_mode;
reg [11:0] captured_out;
// Extract fields from vector
vec_expected = vector_mem[idx][39:28];
vec_coeff_b = vector_mem[idx][27:16];
vec_coeff_a = vector_mem[idx][15:4];
vec_mode = vector_mem[idx][3];
$display("INFO: Vector %0d - a=%0d b=%0d mode=%s expected=%0d",
idx, vec_coeff_a, vec_coeff_b,
vec_mode ? "SUB" : "ADD", vec_expected);
// Drive DUT inputs
coeff_a_in <= vec_coeff_a;
coeff_b_in <= vec_coeff_b;
mode <= vec_mode;
valid_i <= 1'b1;
@(posedge clk);
valid_i <= 1'b0;
// Wait for valid_o assertion (1 pipeline stage latency)
cycle_count = 0;
while (!valid_o && cycle_count < TIMEOUT_CYCLES) begin
@(posedge clk);
cycle_count = cycle_count + 1;
end
if (cycle_count >= TIMEOUT_CYCLES) begin
$display("ERROR: Timeout waiting for valid_o on vector %0d", idx);
fail_count = fail_count + 1;
$fwrite(result_fd, "RESULT: VECTOR %0d TIMEOUT\n", idx);
end else begin
// Capture output (valid_o is high, coeff_out is valid)
captured_out = coeff_out;
// Compare with expected
if (captured_out == vec_expected) begin
pass_count = pass_count + 1;
$fwrite(result_fd, "PASS: %0d - mode=%s a=%03X b=%03X expected=%03X got=%03X\n",
idx, vec_mode ? "SUB" : "ADD",
vec_coeff_a, vec_coeff_b, vec_expected, captured_out);
end else begin
$display("FAIL: Vector %0d - mode=%s a=%0d b=%0d expected=%0d got=%0d",
idx, vec_mode ? "SUB" : "ADD",
vec_coeff_a, vec_coeff_b, vec_expected, captured_out);
fail_count = fail_count + 1;
$fwrite(result_fd, "FAIL: %0d - mode=%s a=%03X b=%03X expected=%03X got=%03X\n",
idx, vec_mode ? "SUB" : "ADD",
vec_coeff_a, vec_coeff_b, vec_expected, captured_out);
end
end
// One extra cycle for valid_o -> ready_i handshake (pipeline clears valid_o)
@(posedge clk);
end
end
// ============================================================
// Summary
// ============================================================
$fclose(result_fd);
$display("========================================");
$display("TEST COMPLETE");
$display(" Total vectors: %0d", vec_count);
$display(" Passed: %0d", pass_count);
$display(" Failed: %0d", fail_count);
$display(" Results written to: %s", RESULT_FILE);
if (fail_count == 0) begin
$display(" STATUS: ALL TESTS PASSED");
end else begin
$display(" STATUS: %0d FAILURE(S) DETECTED", fail_count);
end
$display("========================================");
$finish;
end
// ================================================================
// Timeout watchdog (TIMEOUT_CYCLES * 10ns * vectors * margin)
// ================================================================
initial begin
#(TIMEOUT_CYCLES * 10 * MAX_VECTORS * 2);
$display("FATAL: Global simulation timeout reached");
$finish;
end
endmodule

View File

@@ -0,0 +1,76 @@
0000000000
CFFD00D000
D00D000000
D00000D000
BB87D03E80
BB85DC5DC0
0030020010
0B308902A0
29F7D07D00
29F3E8BB80
000001D000
0006816800
D006806800
0000000008
000D00D008
001D000008
D00000D008
3E83E87D08
0005DC5DC8
0010010028
05F02A0898
9197D03E88
D000010008
002D000018
CFF0020008
002D000018
0000000000
0000000008
0010010000
D000010008
6806800000
6816800008
CFFCFF0000
002CFF0008
D00D000000
001D000008
0010000010
0010000018
0020010010
0000010018
6816800010
6826800018
D00CFF0010
003CFF0018
000D000010
002D000018
6800006800
6800006808
6810016800
67F0016808
D006806800
0006806808
67ECFF6800
682CFF6808
67FD006800
681D006808
CFF000CFF0
CFF000CFF8
D00001CFF0
CFE001CFF8
67E680CFF0
67F680CFF8
CFDCFFCFF0
000CFFCFF8
CFED00CFF0
D00D00CFF8
D00000D000
D00000D008
000001D000
CFF001D008
67F680D000
680680D008
CFECFFD000
001CFFD008
CFFD00D000
000D00D008

View File

@@ -0,0 +1,60 @@
# xsim_run.tcl - Vivado xsim compilation and simulation script for poly_arith_sync
#
# Compiles poly_arith_sync RTL + dependencies + testbench and runs simulation.
# Run from the project root: ~/Dev/mlkem/
#
# Prerequisites:
# source /opt/Xilinx/Vivado/2019.2/settings64.sh
#
# Usage examples:
# # Step-by-step (from Tcl):
# xsim -runall xsim_run.tcl
#
# # Or via Vivado batch mode:
# vivado -mode batch -source xsim_run.tcl
#
# # Or manually:
# xvlog -sv sync_rtl/common/pipeline_reg.v sync_rtl/poly_arith/poly_arith_sync.v sync_rtl/poly_arith/TB/tb_poly_arith_xsim.v
# xelab tb_poly_arith_xsim -s tb_poly_arith_xsim
# xsim tb_poly_arith_xsim -R
# ================================================================
# Configuration
# ================================================================
set RTL_DIR sync_rtl
set DUT_DIR sync_rtl/poly_arith
set TB_DIR sync_rtl/poly_arith/TB
# ================================================================
# Step 1: Compile all source files (xvlog)
# ================================================================
puts "=== Compiling RTL sources for poly_arith_sync ==="
# Common dependency (pipeline register)
xvlog -sv -include_dirs . ${RTL_DIR}/common/pipeline_reg.v
# DUT (poly_arith_sync) — uses `include "sync_rtl/common/defines.vh"
xvlog -sv -include_dirs . ${DUT_DIR}/poly_arith_sync.v
# ================================================================
# Step 2: Compile testbench
# ================================================================
puts "=== Compiling testbench ==="
xvlog -sv ${TB_DIR}/tb_poly_arith_xsim.v
# ================================================================
# Step 3: Elaborate snapshot (xelab)
# ================================================================
puts "=== Elaborating snapshot ==="
xelab tb_poly_arith_xsim -s tb_poly_arith_xsim
# ================================================================
# Step 4: Run simulation
# ================================================================
puts "=== Running poly_arith_sync XSIM test ==="
xsim tb_poly_arith_xsim -R
puts ""
puts "=== Simulation complete ==="