Files
mlkem-sync/sync_rtl/mod_add/TB/gen_vectors.py
FallenSigh d4c3fc86fc 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
2026-06-25 20:48:38 +08:00

113 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Generate test vectors for mod_add_sync module.
Produces mod_add_input.hex: one 24-bit hex word per line.
Each word encodes {a[11:0], b[11:0]} as a single hex value
suitable for $readmemh.
Also prints expected results to stdout for reference.
"""
import os
Q = 3329
def mod_add(a, b):
"""Compute (a + b) mod Q."""
s = a + b
if s >= Q:
s -= Q
return s
# Test cases: (a, b, description)
test_cases = [
# Normal operations
(0, 0, "zero + zero"),
(100, 200, "small + small (no overflow)"),
(500, 1000, "medium + medium (no overflow)"),
(2000, 1000, "medium pair (no overflow)"),
(1500, 1500, "medium pair (no overflow)"),
(3328, 0, "max + zero"),
(0, 3328, "zero + max"),
# Near-Q values (still < Q each)
(3000, 300, "near max + small (no overflow)"),
(300, 3000, "small + near max (no overflow)"),
(2000, 1500, "medium pair (overflow by small)"),
(2000, 2000, "medium pair (overflow)"),
(2500, 2500, "large pair (overflow)"),
(3000, 1000, "large + medium (overflow)"),
# Exact Q boundary
(3328, 1, "max + 1 (exact overflow to zero)"),
(1, 3328, "1 + max (exact overflow to zero)"),
# Max values (both Q-1)
(3328, 3328, "max + max (double overflow)"),
# Various overflow amounts
(2000, 3328, "medium + max (overflow)"),
(3328, 2000, "max + medium (overflow)"),
(2500, 1000, "large + medium (overflow)"),
(1000, 2500, "medium + large (overflow)"),
(1234, 2100, "random pair (overflow)"),
(456, 2900, "small + near max (overflow)"),
# No overflow but close
(1500, 1828, "pair sums exactly to Q-1"),
(1829, 1500, "pair sums exactly to Q (overflow -> 0)"),
# Back-to-back stress (all different values)
(1111, 2222, "stress pair"),
(333, 444, "stress pair"),
(777, 1888, "stress pair"),
(2999, 333, "stress pair near overflow"),
(500, 2800, "stress pair near max"),
]
# Restore previously removed concise test cases
additional = [
(42, 42),
(777, 1337),
(1664, 1664), # exactly Q//2 + Q//2 = Q-1? 1664+1664=3328 = Q-1
(200, 3100), # overflow
(0, 1),
(1, 0),
(3327, 2), # exact overflow: 3329 → 0
(1000, 2329), # overflow: 3329 → 0
(50, 3279), # overflow: 3329 → 0
(1600, 1729), # overflow by 0: 3329 → 0
]
test_cases.extend([(a, b) for a, b in additional])
def format_hex(val, bits):
"""Format integer as zero-padded hex string."""
nibbles = (bits + 3) // 4
return f"{val:0{nibbles}x}"
vectors_dir = os.path.join(os.path.dirname(__file__), "vectors")
os.makedirs(vectors_dir, exist_ok=True)
output_path = os.path.join(vectors_dir, "mod_add_input.hex")
with open(output_path, "w") as f:
for idx, item in enumerate(test_cases):
if len(item) == 3:
a, b, desc = item
else:
a, b = item
desc = ""
# Ensure inputs are in valid range
assert 0 <= a < Q, f"a={a} out of range"
assert 0 <= b < Q, f"b={b} out of range"
# Pack: upper 12 bits = a, lower 12 bits = b
word = (a << 12) | b
expected = mod_add(a, b)
f.write(format_hex(word, 24) + "\n")
if desc:
print(f"[{idx:3d}] a={a:4d} b={b:4d} -> expected={expected:4d} ({desc})")
else:
print(f"[{idx:3d}] a={a:4d} b={b:4d} -> expected={expected:4d}")
print(f"\nWrote {len(test_cases)} vectors to {output_path}")