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,179 @@
#!/usr/bin/env python3
"""gen_vectors.py - Generate test vectors for comp_decomp_sync XSIM testbench.
Produces sync_rtl/comp_decomp/TB/vectors/comp_decomp_input.hex
Each line is a 32-bit hex value (8 hex chars, no spaces):
bits[31:26] = padding (0)
bits[25:14] = expected[11:0]
bits[13:2] = coeff_in[11:0]
bit[1] = d[0] (d[4:0] split across bits [6:2] and bit[1])
bit[0] = mode (0=compress, 1=decompress)
Revised layout (cleaner, 32 bits):
bits[31:20] = expected[11:0]
bits[19:8] = coeff_in[11:0]
bits[7:3] = d[4:0]
bit[2] = mode (0=compress, 1=decompress)
bits[1:0] = padding (0)
FIPS 203 formulas:
Compress_q(x, d) = round((2^d / Q) * x) mod 2^d
= ((x * 2^d + Q//2) // Q) & ((1 << d) - 1)
Decompress_q(y, d) = round((Q / 2^d) * y)
= (y * Q + (1 << (d-1))) // (1 << d)
Tests cover d in {4, 5, 10, 11} (ML-KEM standard values) plus edge d=1.
"""
import os
import sys
Q = 3329
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "vectors")
OUTPUT_FILE = os.path.join(OUTPUT_DIR, "comp_decomp_input.hex")
def compress_q(x: int, d: int) -> int:
"""Compress_q(x, d) per FIPS 203."""
if d == 0:
return 0
two_d = 1 << d
# round((2^d / Q) * x) = floor((x * 2^d + Q/2) / Q)
val = (x * two_d + Q // 2) // Q
return val & (two_d - 1)
def decompress_q(y: int, d: int) -> int:
"""Decompress_q(y, d) per FIPS 203."""
if d == 0:
return 0
two_d = 1 << d
# round((Q / 2^d) * y) = floor((y * Q + 2^(d-1)) / 2^d)
val = (y * Q + (two_d >> 1)) // two_d
return val % Q
def pack_vector(expected: int, coeff_in: int, d: int, mode: int) -> int:
"""Pack a single test vector into a 32-bit value."""
val = 0
val |= (expected & 0xFFF) << 20 # bits 31:20
val |= (coeff_in & 0xFFF) << 8 # bits 19:8
val |= (d & 0x1F) << 3 # bits 7:3
val |= (mode & 0x1) << 2 # bit 2
# bits 1:0 are padding = 0
return val
def generate() -> list[int]:
"""Generate all test vectors. Returns list of packed 32-bit values."""
vectors: list[int] = []
def add_vector(x: int, d: int, mode: int) -> None:
if mode == 0:
exp = compress_q(x, d)
else:
exp = decompress_q(x, d)
vectors.append(pack_vector(exp, x, d, mode))
# Standard ML-KEM d values
d_values = [4, 5, 10, 11]
# Test coefficient values: edges and mid-range
coeffs = [
0, # zero
1, # minimal
3328, # max (Q-1)
1000, # mid-range
2000, # mid-range
1664, # Q/2
42, # small
]
# ---- COMPRESS (mode=0) ----
for d in d_values:
for c in coeffs:
add_vector(c, d, 0)
# Compress edge: max input
add_vector(3328, d, 0)
# Compress: some systematic sweep
for c in [0, 500, 1000, 1500, 2000, 2500, 3000, 3328]:
add_vector(c, d, 0)
# ---- DECOMPRESS (mode=1) ----
# For decompress, input is in [0, 2^d-1]
for d in d_values:
two_d_mask = (1 << d) - 1
# Zero
add_vector(0, d, 1)
# Max in range
add_vector(two_d_mask, d, 1)
# Mid-range
mid = two_d_mask // 2
if mid > 0:
add_vector(mid, d, 1)
add_vector(mid - 1, d, 1) if mid > 1 else None
add_vector(mid + 1, d, 1) if mid + 1 <= two_d_mask else None
# Systematic sweep through valid range
step = max(1, two_d_mask // 8)
for y in range(0, two_d_mask + 1, step):
add_vector(y, d, 1)
# ---- Edge case: d=1 (minimum non-zero) ----
add_vector(0, 1, 0)
add_vector(3328, 1, 0)
add_vector(0, 1, 1)
add_vector(1, 1, 1)
# ---- d=12 (max for 12-bit operands, though not in ML-KEM) ----
add_vector(0, 12, 0)
add_vector(3328, 12, 0)
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:
# 32 bits = 8 hex digits
f.write(f"{v:08X}\n")
print(f"Generated {len(vectors)} test vectors -> {OUTPUT_FILE}")
def main() -> int:
vectors = generate()
write_vectors(vectors)
# Print statistics and samples
print(f"\nTotal vectors: {len(vectors)}")
compress_count = sum(1 for v in vectors if ((v >> 2) & 1) == 0)
decompress_count = sum(1 for v in vectors if ((v >> 2) & 1) == 1)
print(f" Compress: {compress_count}")
print(f" Decompress: {decompress_count}")
print("\nSample vectors (first 5):")
for i, v in enumerate(vectors[:5]):
exp = (v >> 20) & 0xFFF
coeff = (v >> 8) & 0xFFF
d = (v >> 3) & 0x1F
mode = (v >> 2) & 0x1
op = "COMPRESS" if mode == 0 else "DECOMPRESS"
if mode == 0:
print(f" [{i}] {op} x={coeff:04d} d={d:02d} expected={exp:04d}")
else:
print(f" [{i}] {op} y={coeff:04d} d={d:02d} expected={exp:04d}")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,254 @@
// tb_comp_decomp_xsim.v - Standard Verilog testbench for comp_decomp_sync targeting Vivado xsim
//
// Reads test vectors from a hex file using $readmemh.
// Each line is a 32-bit hex value (8 hex chars, no spaces):
// bits[31:20] = expected[11:0]
// bits[19:8] = coeff_in[11:0]
// bits[7:3] = d[4:0]
// bit[2] = mode (0=compress, 1=decompress)
// bits[1:0] = padding
//
// Drives comp_decomp_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/comp_decomp/TB/vectors/comp_decomp_input.hex")
// RESULT_FILE - path to output file (default: "sync_rtl/comp_decomp/TB/vectors/comp_decomp_result.hex")
//
// Usage with xsim_run.tcl or manual:
// xvlog -sv sync_rtl/common/pipeline_reg.v sync_rtl/comp_decomp/comp_decomp_sync.v tb_comp_decomp_xsim.v
// xelab tb_comp_decomp_xsim -s tb_comp_decomp_xsim
// xsim tb_comp_decomp_xsim -R
`timescale 1ns / 1ps
module tb_comp_decomp_xsim;
// ================================================================
// Parameters
// ================================================================
parameter VECTOR_FILE = "sync_rtl/comp_decomp/TB/vectors/comp_decomp_input.hex";
parameter RESULT_FILE = "sync_rtl/comp_decomp/TB/vectors/comp_decomp_result.hex";
parameter MAX_VECTORS = 512;
parameter TIMEOUT_CYCLES = 10000;
parameter Q = 3329;
// ================================================================
// DUT signals
// ================================================================
reg clk;
reg rst_n;
reg [11:0] coeff_in;
reg [4:0] d;
reg mode;
reg valid_i;
wire ready_o;
wire [11:0] coeff_out;
wire valid_o;
reg ready_i;
// ================================================================
// DUT instantiation (named ports)
// ================================================================
comp_decomp_sync u_dut (
.clk (clk),
.rst_n (rst_n),
.coeff_in (coeff_in),
.d (d),
.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)
// 32 bits per word:
// bits[31:20] = expected
// bits[19:8] = coeff_in
// bits[7:3] = d
// bit[2] = mode
// bits[1:0] = padding
// ================================================================
reg [31: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] === 32'hx || vector_mem[idx] === 32'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: <8 hex chars> = {expected[11:0], coeff_in[11:0], d[4:0], mode, 2'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_in <= 12'd0;
d <= 5'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_in;
reg [4:0] vec_d;
reg vec_mode;
reg [11:0] captured_out;
// Extract fields from vector
vec_expected = vector_mem[idx][31:20];
vec_coeff_in = vector_mem[idx][19:8];
vec_d = vector_mem[idx][7:3];
vec_mode = vector_mem[idx][2];
$display("INFO: Vector %0d - coeff=%0d d=%0d mode=%s expected=%0d",
idx, vec_coeff_in, vec_d,
vec_mode ? "DECOMPRESS" : "COMPRESS", vec_expected);
// Drive DUT inputs
coeff_in <= vec_coeff_in;
d <= vec_d;
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 coeff=%03X d=%0d expected=%03X got=%03X\n",
idx, vec_mode ? "DECOMPRESS" : "COMPRESS",
vec_coeff_in, vec_d, vec_expected, captured_out);
end else begin
$display("FAIL: Vector %0d - mode=%s coeff=%0d d=%0d expected=%0d got=%0d",
idx, vec_mode ? "DECOMPRESS" : "COMPRESS",
vec_coeff_in, vec_d, vec_expected, captured_out);
fail_count = fail_count + 1;
$fwrite(result_fd, "FAIL: %0d - mode=%s coeff=%03X d=%0d expected=%03X got=%03X\n",
idx, vec_mode ? "DECOMPRESS" : "COMPRESS",
vec_coeff_in, vec_d, 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,135 @@
00000020
00000120
000D0020
0053E820
00A7D020
00868020
00002A20
000D0020
00000020
0021F420
0053E820
0075DC20
00A7D020
00C9C420
00EBB820
000D0020
00000028
00000128
000D0028
00A3E828
0137D028
01068028
00002A28
000D0028
00000028
0051F428
00A3E828
00E5DC28
0137D028
0189C428
01DBB828
000D0028
00000050
00000150
000D0050
1343E850
2677D050
20068050
00D02A50
000D0050
00000050
09A1F450
1343E850
1CD5DC50
2677D050
3019C450
39BBB850
000D0050
00000058
00100158
7FFD0058
2673E858
4CE7D058
40068058
01A02A58
7FFD0058
00000058
1341F458
2673E858
39B5DC58
4CE7D058
6029C458
736BB858
7FFD0058
00000024
C3100F24
5B000724
4E000624
68100824
00000024
0D000124
1A000224
27000324
34000424
41000524
4E000624
5B000724
68100824
75100924
82100A24
8F100B24
9C100C24
A9100D24
B6100E24
C3100F24
0000002C
C9901F2C
61800F2C
5B000E2C
6810102C
0000002C
1380032C
2700062C
3A80092C
4E000C2C
61800F2C
7510122C
8890152C
9C10182C
AF901B2C
C3101E2C
00000054
CFE3FF54
67D1FF54
67A1FE54
68120054
00000054
19D07F54
33A0FE54
4D717D54
6731FC54
81027B54
9AD2FA54
B4A37954
CE73F854
0000005C
CFF7FF5C
67F3FF5C
67D3FE5C
6814005C
0000005C
19E0FF5C
33D1FE5C
4DB2FD5C
67A3FC5C
8184FB5C
9B75FA5C
B556F95C
CF47F85C
00000008
000D0008
0000000C
6810010C
00000060
FFFD0060

View File

@@ -0,0 +1,60 @@
# xsim_run.tcl - Vivado xsim compilation and simulation script for comp_decomp_sync
#
# Compiles comp_decomp_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/comp_decomp/comp_decomp_sync.v sync_rtl/comp_decomp/TB/tb_comp_decomp_xsim.v
# xelab tb_comp_decomp_xsim -s tb_comp_decomp_xsim
# xsim tb_comp_decomp_xsim -R
# ================================================================
# Configuration
# ================================================================
set RTL_DIR sync_rtl
set DUT_DIR sync_rtl/comp_decomp
set TB_DIR sync_rtl/comp_decomp/TB
# ================================================================
# Step 1: Compile all source files (xvlog)
# ================================================================
puts "=== Compiling RTL sources for comp_decomp_sync ==="
# Common dependency (pipeline register)
xvlog -sv -include_dirs . ${RTL_DIR}/common/pipeline_reg.v
# DUT (comp_decomp_sync) — uses `include "sync_rtl/common/defines.vh"
xvlog -sv -include_dirs . ${DUT_DIR}/comp_decomp_sync.v
# ================================================================
# Step 2: Compile testbench
# ================================================================
puts "=== Compiling testbench ==="
xvlog -sv ${TB_DIR}/tb_comp_decomp_xsim.v
# ================================================================
# Step 3: Elaborate snapshot (xelab)
# ================================================================
puts "=== Elaborating snapshot ==="
xelab tb_comp_decomp_xsim -s tb_comp_decomp_xsim
# ================================================================
# Step 4: Run simulation
# ================================================================
puts "=== Running comp_decomp_sync XSIM test ==="
xsim tb_comp_decomp_xsim -R
puts ""
puts "=== Simulation complete ==="

View File

@@ -0,0 +1,112 @@
#!/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}")

View File

@@ -0,0 +1,238 @@
// tb_mod_add_xsim.v - Standard Verilog testbench for mod_add_sync targeting Vivado xsim
//
// Reads test vectors from a hex file using $readmemh.
// Each line is a single 24-bit hex number encoding:
// bits [23:12] = a, bits [11:0] = b
//
// Drives mod_add_sync, waits for valid_o, and writes
// "RESULT: IDX A B SUM_HEX" to the output file using $fwrite.
//
// Parameters:
// VECTOR_FILE - path to input hex file (default: "vectors/mod_add_input.hex")
// RESULT_FILE - path to output file (default: "vectors/mod_add_result.hex")
//
// Usage:
// xvlog -sv sync_rtl/common/pipeline_reg.v
// xvlog -sv sync_rtl/mod_add/mod_add_sync.v
// xvlog -sv sync_rtl/mod_add/TB/tb_mod_add_xsim.v
// xelab tb_mod_add_xsim -s tb_mod_add_xsim
// xsim tb_mod_add_xsim -R
`timescale 1ns / 1ps
module tb_mod_add_xsim;
// ================================================================
// Parameters
// ================================================================
parameter VECTOR_FILE = "sync_rtl/mod_add/TB/vectors/mod_add_input.hex";
parameter RESULT_FILE = "sync_rtl/mod_add/TB/vectors/mod_add_result.hex";
parameter MAX_VECTORS = 256;
parameter TIMEOUT_CYCLES = 1000;
// ================================================================
// DUT signals
// ================================================================
reg clk;
reg rst_n;
reg [11:0] a;
reg [11:0] b;
reg valid_i;
wire ready_o;
wire [11:0] sum;
wire valid_o;
reg ready_i;
// ================================================================
// DUT instantiation
// ================================================================
mod_add_sync u_dut (
.clk (clk),
.rst_n (rst_n),
.a (a),
.b (b),
.valid_i (valid_i),
.ready_o (ready_o),
.sum (sum),
.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)
// ================================================================
reg [23: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
// Helper: write 3-hex-digit (12-bit) value to file
task write_hex_12bit;
input integer fd;
input [11:0] val;
reg [3:0] nib;
integer j;
begin
for (j = 2; j >= 0; j = j - 1) begin
nib = val[(j*4)+:4];
$fwrite(fd, "%c", nibble_to_ascii(nib));
end
end
endtask
// ================================================================
// Main test sequence
// ================================================================
initial begin
// Count loaded vectors
vec_count = 0;
// Load vectors from hex file
$readmemh(VECTOR_FILE, vector_mem);
// Count non-x 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] === 24'hx || vector_mem[idx] === 24'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: <6 hex chars> = {a[11:0], b[11:0]}");
$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
a <= 12'd0;
b <= 12'd0;
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_a;
reg [11:0] vec_b;
reg [11:0] captured_sum;
// Extract a and b from memory word
vec_a = vector_mem[idx][23:12];
vec_b = vector_mem[idx][11:0];
$display("INFO: Vector %0d - a=%0d b=%0d", idx, vec_a, vec_b);
// Drive DUT
a <= vec_a;
b <= vec_b;
valid_i <= 1'b1;
@(posedge clk);
valid_i <= 1'b0;
// Wait for valid_o
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;
end else begin
// Capture sum output
captured_sum = sum;
pass_count = pass_count + 1;
// Write result to output file
// Format: "RESULT: IDX A_HEX B_HEX SUM_HEX"
$fwrite(result_fd, "RESULT: %0d ", idx);
write_hex_12bit(result_fd, vec_a);
$fwrite(result_fd, " ");
write_hex_12bit(result_fd, vec_b);
$fwrite(result_fd, " ");
write_hex_12bit(result_fd, captured_sum);
$fwrite(result_fd, "\n");
end
// One extra cycle for valid_o handshake
@(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);
$display("========================================");
$finish;
end
// ================================================================
// Timeout watchdog
// ================================================================
initial begin
#(TIMEOUT_CYCLES * 10 * 100); // TIMEOUT_CYCLES * 10ns per cycle * extra margin
$display("FATAL: Global simulation timeout reached");
$finish;
end
endmodule

View File

@@ -0,0 +1,39 @@
000000
0640c8
1f43e8
7d03e8
5dc5dc
d00000
000d00
bb812c
12cbb8
7d05dc
7d07d0
9c49c4
bb83e8
d00001
001d00
d00d00
7d0d00
d007d0
9c43e8
3e89c4
4d2834
1c8b54
5dc724
7255dc
4578ae
14d1bc
309760
bb714d
1f4af0
02a02a
309539
680680
0c8c1c
000001
001000
cff002
3e8919
032ccf
6406c1

View File

@@ -0,0 +1,59 @@
# xsim_run.tcl - Vivado xsim compilation and simulation script for mod_add_sync
#
# Compiles mod_add_sync RTL plus the file-based vector testbench and runs simulation.
# Run from the project root: ~/Dev/mlkem/
#
# Prerequisites:
# source /opt/Xilinx/Vivado/2019.2/settings64.sh
#
# Usage:
# vivado -mode batch -source sync_rtl/mod_add/TB/xsim_run.tcl
#
# # Or step-by-step:
# xvlog -sv sync_rtl/common/pipeline_reg.v
# xvlog -sv sync_rtl/mod_add/mod_add_sync.v
# xvlog -sv sync_rtl/mod_add/TB/tb_mod_add_xsim.v
# xelab tb_mod_add_xsim -s tb_mod_add_xsim
# xsim tb_mod_add_xsim -R
# ================================================================
# Configuration
# ================================================================
set SRC_DIR sync_rtl/mod_add
set TB_DIR sync_rtl/mod_add/TB
set COMMON_DIR sync_rtl/common
# ================================================================
# Step 1: Compile all source files (xvlog)
# ================================================================
puts "=== Compiling RTL sources ==="
# Common pipeline register
xvlog -sv -include_dirs . ${COMMON_DIR}/pipeline_reg.v
# mod_add_sync (includes defines.vh from common/)
xvlog -sv -include_dirs . ${SRC_DIR}/mod_add_sync.v
# ================================================================
# Step 2: Compile testbench
# ================================================================
puts "=== Compiling testbench ==="
# File-based vector testbench
xvlog -sv ${TB_DIR}/tb_mod_add_xsim.v
# ================================================================
# Step 3: Elaborate snapshot (xelab)
# ================================================================
puts "=== Elaborating snapshot ==="
xelab tb_mod_add_xsim -s tb_mod_add_xsim
# ================================================================
# Step 4: Run simulation
# ================================================================
puts "=== Running mod_add_sync file-based vector test ==="
xsim tb_mod_add_xsim -R
puts ""
puts "=== mod_add_sync simulation complete ==="

View File

@@ -0,0 +1,241 @@
#!/usr/bin/env python3
"""gen_vectors.py - Generate NTT test vectors for Vivado XSIM testbench.
Generates input vectors for ntt_core module in hex format compatible
with $readmemh. Each line encodes {1-bit mode, 256x12-bit coefficients}
packed as a single 3073-bit hex number (769 hex chars).
Q = 3329, N = 256, primitive root zeta = 17.
NTT uses Cooley-Tukey DIT with bit-reversed zeta ROM.
INTT uses Gentleman-Sande DIF with zeta ROM in reverse.
Usage:
python3 gen_vectors.py
Output:
vectors/ntt_core_input.hex
"""
import sys
import os
import random
# ============================================================
# Constants
# ============================================================
Q = 3329 # ML-KEM modulus
N = 256 # NTT size
LAYERS = 7 # log2(N) - 1 for Cooley-Tukey
# zeta_rom values: bit-reversed powers of the primitive root zeta=17
# These match sync_rtl/ntt/zeta_rom.v exactly
ZETA_ROM = [
1, 1729, 2580, 3289, 2642, 630, 1897, 848,
1062, 1919, 193, 797, 2786, 3260, 569, 1746,
296, 2447, 1339, 1476, 3046, 56, 2240, 1333,
1426, 2094, 535, 2882, 2393, 2879, 1974, 821,
289, 331, 3253, 1756, 1197, 2304, 2277, 2055,
650, 1977, 2513, 632, 2865, 33, 1320, 1915,
2319, 1435, 807, 452, 1438, 2868, 1534, 2402,
2647, 2617, 1481, 648, 2474, 3110, 1227, 910,
17, 2761, 583, 2649, 1637, 723, 2288, 1100,
1409, 2662, 3281, 233, 756, 2156, 3015, 3050,
1703, 1651, 2789, 1789, 1847, 952, 1461, 2687,
939, 2308, 2437, 2388, 733, 2337, 268, 641,
1584, 2298, 2037, 3220, 375, 2549, 2090, 1645,
1063, 319, 2773, 757, 2099, 561, 2466, 2594,
2804, 1092, 403, 1026, 1143, 2150, 2775, 886,
1722, 1212, 1874, 1029, 2110, 2935, 885, 2154,
]
def barrett_mul(a: int, b: int) -> int:
"""Barrett modular multiplication: (a * b) mod Q."""
return (a * b) % Q
def ntt_forward(coeffs: list) -> list:
"""Forward NTT: Cooley-Tukey DIT, matches ntt_core RTL exactly.
Processes 7 layers. At each layer:
- Pairs are (j, j+layer_len)
- Zeta for each block comes from zeta_rom (index increments per block)
- Butterfly: t = zeta*b; a_out = a+t; b_out = a-t (all mod Q)
Input: normal-order coefficients (index 0..255)
Output: bit-reversed NTT result
"""
a = list(coeffs)
layer_len = 128
zeta_idx = 1 # Forward NTT starts at zeta_rom[1]
for layer in range(LAYERS):
for start in range(0, N, 2 * layer_len):
zeta = ZETA_ROM[zeta_idx]
for j in range(start, start + layer_len):
t = barrett_mul(zeta, a[j + layer_len])
a_j_plus_len = (a[j] - t) % Q
a[j] = (a[j] + t) % Q
a[j + layer_len] = a_j_plus_len
zeta_idx += 1
layer_len >>= 1
return a
def intt_inverse(coeffs: list) -> list:
"""Inverse NTT: Gentleman-Sande DIF, matches ntt_core RTL exactly.
Processes 7 layers in reverse order (len=2,4,8,...,128).
- Zeta for each block comes from zeta_rom (index decrements per block)
- Butterfly: a_out = a+b; diff = b-a; b_out = zeta*diff (all mod Q)
After all layers, output is scaled by N (no 1/2 factors in GS butterfly).
The RTL then scales output by 3303 in the OUTPUT state for mode=1.
"""
a = list(coeffs)
layer_len = 2
zeta_idx = 127 # Inverse NTT starts at zeta_rom[127]
for layer in range(LAYERS):
for start in range(0, N, 2 * layer_len):
zeta = ZETA_ROM[zeta_idx]
for j in range(start, start + layer_len):
a_sum = (a[j] + a[j + layer_len]) % Q
diff = (a[j + layer_len] - a[j]) % Q
a[j] = a_sum
a[j + layer_len] = barrett_mul(zeta, diff)
zeta_idx -= 1
layer_len <<= 1
# Apply output scaling (multiply by 3303) as the RTL does in mode=1
for i in range(N):
a[i] = barrett_mul(a[i], 3303)
return a
def coeffs_to_hex(coeffs: list) -> str:
"""Convert 256 12-bit coefficients to a 768-char hex string.
coeffs[0] is the MSB of the hex output, coeffs[255] is the LSB.
Each coefficient is 3 hex chars (12 bits).
"""
result = 0
for c in coeffs:
result = (result << 12) | (c & 0xFFF)
return f"{result:0768X}"
def write_vector(f, mode: int, coeffs: list, label: str):
"""Write a single test vector to the hex file.
Format: {mode_hex_digit}{768 hex chars for 256 coeffs}
Total: 769 hex chars per line.
mode=0 -> hex digit '0', mode=1 -> hex digit '1'.
"""
mode_hex = "0" if mode == 0 else "1"
coeffs_hex = coeffs_to_hex(coeffs)
line = mode_hex + coeffs_hex
f.write(f"// {label}\n")
f.write(line + "\n")
def hex_char_to_int(c: str) -> int:
"""Convert single hex char to integer."""
return int(c, 16)
def generate_vectors():
"""Generate test vectors for ntt_core."""
os.makedirs("vectors", exist_ok=True)
hex_path = os.path.join("vectors", "ntt_core_input.hex")
# All tests are listed here with labels
tests = []
# --- Test 0: Forward NTT on all zeros ---
zeros = [0] * N
expected = ntt_forward(zeros)
tests.append((0, zeros, expected, "FWD: all zeros"))
# --- Test 1: Forward NTT on impulse at index 0 ---
imp0 = [0] * N
imp0[0] = 1
expected = ntt_forward(imp0)
tests.append((0, imp0, expected, "FWD: impulse at [0]"))
# --- Test 2: Forward NTT on impulse at index 1 ---
imp1 = [0] * N
imp1[1] = 1
expected = ntt_forward(imp1)
tests.append((0, imp1, expected, "FWD: impulse at [1]"))
# --- Test 3: Forward NTT on ramp [0,1,2,...,255] ---
ramp = [i % Q for i in range(N)]
expected = ntt_forward(ramp)
tests.append((0, ramp, expected, "FWD: ramp 0..255"))
# --- Test 4: Forward NTT on all ones ---
ones = [1] * N
expected = ntt_forward(ones)
tests.append((0, ones, expected, "FWD: all ones"))
# --- Test 5: Inverse NTT on all zeros ---
expected = intt_inverse(zeros)
tests.append((1, zeros, expected, "INV: all zeros"))
# --- Test 6: Inverse NTT on impulse at index 0 ---
expected = intt_inverse(imp0)
tests.append((1, imp0, expected, "INV: impulse at [0]"))
# --- Test 7: Inverse NTT on NTT(impulse) → should recover impulse*256 ---
ntt_imp0 = ntt_forward(imp0)
expected = intt_inverse(ntt_imp0)
tests.append((1, ntt_imp0, expected, "INV(NTT(imp[0])) → imp[0]*256"))
# --- Test 8: Inverse NTT on NTT(ramp) → should recover ramp*256 ---
ntt_ramp = ntt_forward(ramp)
expected = intt_inverse(ntt_ramp)
tests.append((1, ntt_ramp, expected, "INV(NTT(ramp)) → ramp*256"))
# --- Tests 9-12: Forward NTT on random vectors ---
random.seed(0x5EED)
for i in range(4):
rand_vec = [random.randrange(0, Q) for _ in range(N)]
expected = ntt_forward(rand_vec)
tests.append((0, rand_vec, expected, f"FWD: random {i}"))
# --- Tests 13-16: Inverse NTT on random vectors ---
for i in range(4):
rand_vec = [random.randrange(0, Q) for _ in range(N)]
expected = intt_inverse(rand_vec)
tests.append((1, rand_vec, expected, f"INV: random {i}"))
# --- Tests 17-18: Inverse NTT on NTT(random) → roundtrip ---
for i in range(2):
rand_vec = [random.randrange(0, Q) for _ in range(N)]
ntt_vec = ntt_forward(rand_vec)
recovered = intt_inverse(ntt_vec)
tests.append((1, ntt_vec, recovered, f"INV(NTT(random {i})) → random*256"))
# Write input hex file
with open(hex_path, "w") as f:
f.write("// ntt_core test vectors\n")
f.write("// Format: {1 hex digit mode}{768 hex chars coeffs}\n")
f.write("// mode: 0=forward NTT, 1=inverse NTT\n")
f.write("// coeffs: 256 x 12-bit values, coeff[0] at MSB position\n")
f.write("\n")
for mode, coeffs, expected, label in tests:
write_vector(f, mode, coeffs, label)
print(f"Generated {len(tests)} test vectors → {hex_path}")
# Print expected results for reference (for manual verification)
print("\nExpected output summary (first 4 coeffs of each test):")
for mode, coeffs, expected, label in tests:
first4 = expected[:4]
print(f" {label:45s} → [{first4[0]:4d}, {first4[1]:4d}, {first4[2]:4d}, {first4[3]:4d}, ...]")
if __name__ == "__main__":
generate_vectors()

View File

@@ -0,0 +1,317 @@
// tb_ntt_core_xsim.v - Standard Verilog testbench for ntt_core targeting Vivado xsim
//
// Reads test vectors from a hex file using $readmemh.
// Each line is a single hex number encoding both mode and 256 input coefficients:
// - bit [3075]: mode (0 = forward NTT, 1 = inverse NTT)
// - bits [3074:3]: 256 x 12-bit input coefficients
// - bits [2:0]: unused (padding)
// - Total: 769 hex chars per line, NO spaces
//
// Drives ntt_core, streams in 256 coefficients via valid/ready handshake,
// waits for valid_o, streams out 256 transformed coefficients,
// and writes "RESULT: MODE INDEX COEFF_HEX" to the output file.
//
// ntt_core FSM: IDLE LOAD CMP_A/B/C (7 stages) OUTPUT DONE IDLE
// valid_o is asserted during OUTPUT state (before done_o).
// done_o pulses for 1 cycle AFTER all outputs have been read.
//
// Parameters:
// VECTOR_FILE - path to input hex file (default: "vectors/ntt_core_input.hex")
// RESULT_FILE - path to output file (default: "vectors/ntt_core_result.hex")
//
// Usage:
// xvlog -sv ntt_core.v butterfly_unit.v barrett_mul.v zeta_rom.v tb_ntt_core_xsim.v
// xelab tb_ntt_core_xsim -s tb_ntt_core_xsim
// xsim tb_ntt_core_xsim -R
`timescale 1ns / 1ps
module tb_ntt_core_xsim;
// ================================================================
// Parameters
// ================================================================
parameter VECTOR_FILE = "sync_rtl/ntt/TB/vectors/ntt_core_input.hex";
parameter RESULT_FILE = "sync_rtl/ntt/TB/vectors/ntt_core_result.hex";
parameter MAX_VECTORS = 64;
parameter N_COEFFS = 256;
parameter TIMEOUT_CYCLES= 100000;
// ================================================================
// DUT signals
// ================================================================
reg clk;
reg rst_n;
reg [11:0] coeff_in;
reg valid_i;
wire ready_o;
reg mode;
wire [11:0] coeff_out;
wire valid_o;
reg ready_i;
wire done_o;
// ================================================================
// DUT instantiation
// ================================================================
ntt_core u_dut (
.clk (clk),
.rst_n (rst_n),
.coeff_in (coeff_in),
.valid_i (valid_i),
.ready_o (ready_o),
.mode (mode),
.coeff_out (coeff_out),
.valid_o (valid_o),
.ready_i (ready_i),
.done_o (done_o)
);
// ================================================================
// Clock generation: 100 MHz (10 ns period)
// ================================================================
initial clk = 1'b0;
always #5 clk = ~clk;
// ================================================================
// Vector memory (loaded by $readmemh)
// 3076 bits per word:
// bit [3075]: mode (0=FWD NTT, 1=INV NTT)
// bits [3074:3]: 256 x 12-bit coefficients (coeff[0] at MSB)
// bits [2:0]: unused padding
// ================================================================
reg [3075:0] vector_mem [0:MAX_VECTORS-1];
integer vec_count;
integer idx;
integer ci;
integer cycle_count;
integer result_fd;
// Test result tracking
integer pass_count;
integer fail_count;
// Temporary storage for coefficients
reg [11:0] input_coeffs [0:N_COEFFS-1];
reg [11:0] output_coeffs [0:N_COEFFS-1];
// ================================================================
// 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 entries (XSim leaves unloaded entries as 3076'hX)
begin
integer found_end;
found_end = 0;
for (idx = 0; idx < MAX_VECTORS; idx = idx + 1) begin
if (!found_end && (vector_mem[idx] === 3076'hx ||
vector_mem[idx] === 3076'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 format is correct.");
$display(" Each line: 769 hex chars = {1 hex digit mode, 768 hex chars coeffs}");
$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
mode <= 1'b0;
coeff_in <= 12'd0;
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 vec_mode;
integer out_idx;
integer got_done;
// Extract mode and coefficients from vector memory
vec_mode = vector_mem[idx][3075];
// Extract 256 input coefficients
// coeff[0] at bits [3074:3063], coeff[255] at bits [11:0]
// NOTE: bits[2:0] are unused padding below coeff[255]
for (ci = 0; ci < N_COEFFS; ci = ci + 1) begin
input_coeffs[ci] = vector_mem[idx][(N_COEFFS-1-ci)*12 + 11
: (N_COEFFS-1-ci)*12];
end
$display("INFO: Vector %0d - mode=%0d (0=FWD, 1=INV)", idx, vec_mode);
// Set mode
mode <= vec_mode;
// ----------------------------------------------------
// LOAD phase: stream in 256 coefficients via valid/ready
// ----------------------------------------------------
for (ci = 0; ci < N_COEFFS; ci = ci + 1) begin
// Wait for ready_o before driving next input
cycle_count = 0;
while (!ready_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 ready_o (input ci=%0d) on vector %0d",
ci, idx);
fail_count = fail_count + 1;
ci = N_COEFFS; // break out of input loop
end else begin
coeff_in <= input_coeffs[ci];
valid_i <= 1'b1;
@(posedge clk);
valid_i <= 1'b0;
end
end
// ----------------------------------------------------
// OUTPUT phase: wait for valid_o, then stream out results
// ntt_core asserts valid_o during S_OUTPUT state.
// After all 256 outputs are consumed, done_o pulses.
// ----------------------------------------------------
got_done = 0;
out_idx = 0;
while (out_idx < N_COEFFS) begin
// Wait for valid_o or done_o
cycle_count = 0;
while (!valid_o && !done_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 output (out_idx=%0d) on vector %0d",
out_idx, idx);
fail_count = fail_count + 1;
out_idx = N_COEFFS; // break out
end else if (done_o) begin
// done_o before all outputs? This shouldn't happen
// unless outputs were consumed in previous test somehow.
// Treat as error.
$display("WARN: done_o asserted before output %0d on vector %0d",
out_idx, idx);
got_done = 1;
@(posedge clk); // consume done_o cycle
end else begin
// valid_o is high: capture output
output_coeffs[out_idx] = coeff_out;
out_idx = out_idx + 1;
@(posedge clk); // ready_i=1, consume this output
end
end
// After all 256 outputs captured, check if done_o was seen
// or wait briefly for it
if (!got_done) begin
cycle_count = 0;
while (!done_o && cycle_count < 100) begin
@(posedge clk);
cycle_count = cycle_count + 1;
end
if (cycle_count >= 100) begin
$display("WARN: done_o not seen after all outputs on vector %0d", idx);
end else begin
@(posedge clk); // consume done_o
end
end
pass_count = pass_count + 1;
// Write result to output file
// Format: "RESULT: MODE INDEX COEFF_HEX"
$fwrite(result_fd, "RESULT: %0d %0d ", vec_mode, idx);
// Write all 256 output coeffs as hex (3 hex chars each = 768 total)
begin
integer oi;
reg [3:0] nib;
for (oi = 0; oi < N_COEFFS; oi = oi + 1) begin
// Write 3 hex chars per coefficient (12 bits)
nib = output_coeffs[oi][11:8];
$fwrite(result_fd, "%c", nibble_to_ascii(nib));
nib = output_coeffs[oi][7:4];
$fwrite(result_fd, "%c", nibble_to_ascii(nib));
nib = output_coeffs[oi][3:0];
$fwrite(result_fd, "%c", nibble_to_ascii(nib));
end
end
$fwrite(result_fd, "\n");
// Extra cycle before next vector
@(posedge clk);
end // inner begin block
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);
$display("========================================");
$finish;
end
// ================================================================
// Timeout watchdog
// ================================================================
initial begin
// TIMEOUT_CYCLES * 10ns per cycle * 10x margin
#(TIMEOUT_CYCLES * 10 * 10);
$display("FATAL: Global simulation timeout reached");
$finish;
end
endmodule

View File

@@ -0,0 +1,43 @@
// ntt_core test vectors
// Format: {1 hex digit mode}{768 hex chars coeffs}
// mode: 0=forward NTT, 1=inverse NTT
// coeffs: 256 x 12-bit values, coeff[0] at MSB position
// FWD: all zeros
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// FWD: impulse at [0]
0001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// FWD: impulse at [1]
0000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// FWD: ramp 0..255
000000100200300400500600700800900A00B00C00D00E00F01001101201301401501601701801901A01B01C01D01E01F02002102202302402502602702802902A02B02C02D02E02F03003103203303403503603703803903A03B03C03D03E03F04004104204304404504604704804904A04B04C04D04E04F05005105205305405505605705805905A05B05C05D05E05F06006106206306406506606706806906A06B06C06D06E06F07007107207307407507607707807907A07B07C07D07E07F08008108208308408508608708808908A08B08C08D08E08F09009109209309409509609709809909A09B09C09D09E09F0A00A10A20A30A40A50A60A70A80A90AA0AB0AC0AD0AE0AF0B00B10B20B30B40B50B60B70B80B90BA0BB0BC0BD0BE0BF0C00C10C20C30C40C50C60C70C80C90CA0CB0CC0CD0CE0CF0D00D10D20D30D40D50D60D70D80D90DA0DB0DC0DD0DE0DF0E00E10E20E30E40E50E60E70E80E90EA0EB0EC0ED0EE0EF0F00F10F20F30F40F50F60F70F80F90FA0FB0FC0FD0FE0FF
// FWD: all ones
0001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001
// INV: all zeros
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// INV: impulse at [0]
1001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// INV(NTT(imp[0])) → imp[0]*256
1001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000
// INV(NTT(ramp)) → ramp*256
197DB1D1A931B74954C27001F9B3895AA5A6CA932055D08927B332339A0E790F26543325E132C47564A9E483213332632B3A09B1305A2A3B6B087986F5C7A4AB307DE68FC8006678364322E2A913C2053A36C47CF7E84468E486F88B7B5A4D86E9450C6BAA0F75CA1C148550A4218AC4643FB19089E4C98B9560B40A682667A87B6B76A77B2C8A9B51CA27710249028610DB245BB8562958B1369544341E86621FC789D6CAE8E523A0EF9DA3467C607EA4D07E332CA04333AC2E6A3927628AAD8A2E1E28A036479D865BFA768BB490203F37399F52179FBB706170EB0E8386EB995172A2D3675BB97A7C193B2923F728F1F52984E1C3006AC1C4FA77F7628637A979D6CA1CDAD44F6C1782941BA1065C6C7C7B7F228F20CC7B3857D758B09D91E928B0927A3529DBA522A06440D8CD052538948D5FCBF33090F26D88042153EE74292049F6786847F5B82888418068B0936D06F5537C57CB29300C1FA60F7E6C8C63766591A659AEB3060463EAC7A3A03DBA9DBBDB43095A22C219C6856A9D8FF
// FWD: random 0
02449D06853239596B8A407B14F79D6C8BC35CEC584389BB7682947A21A3099F65B6567429EF5533930E6580992CB8BF9423C114752479D7CC2A583E00CE5F9402A0BBE99A0C76AA30D531D1C86355195A0CCD475C3EAD93CCABC64C1B4C5C0AC6F73BEB2238E21B81C3507200220C54309307503554831A491056FCD33FB6571BBC379493F850058147BA8E2195AA73F0FE339BD24FB7AFAA1348326A91B80842648A9B06FA9B29792AA5A8667031B8A38345A8F18D3CB5FE326CB41A73592564485705E1CB689BB1622506621A34C8435E64AB4B11859E03FF42DC0783F21B03E52F8228FD4B5928464CD881B86B90856B93A54479BCCA89D675755A88A3B6F645238DB39C6EC9F8603549A042838840E29EC5FB12A36597B0161AC316155830DCB60A00835075919A0150A0934012C1B76B271D1B4ACF1C001CF1D13D14F40B8A2A274804C92B548C494A8989138B6311CF04159E91F530C61E612CCA56A2635E887A7465246FC64C5122CE17B636718867193BB56A13A49B47803919FF9CC
// FWD: random 1
055262448E6FE8456E2B00774C1D389CC768DB89CCF3D0897CA102BC891CA3B90C13CCB36271241AAC4FFAD42084DBBBA09D6F8A1567731CB8606FB46529B7271844B4C810177F20439B4ADAFEADACF98E4507C10A09B63876C7AB10BE37124E5340A11C16C05BC3012AD723345799436A49E4FC1AA3A65BDC2B86ABADB846AE98C010A4D4AA0A63709A6BBAA9CCB222B4EA2A1A724C6B73CDD89D87B70D7B96C6C3F1FDAE2235C106470323C27B642A0521A26F53072B75FB71EB3B6E274DA4E2312E482E97799D0C810F2B71549711740F28DD47E51C712B741274B4C9A02100251C4ECCEA10537416284D6A81B3A0177A96C7965D00D2A78BC83FA4D60D006448262D5A9BEEA893FB594CC0C50728784C615CE361C4608DBAB85FB72AD54D500270C9C253D71C7E92A44A800F5FCAE41684D12335DB25B8A21819D54689283BC4D14B799E52442076B3F4B6954E007511C3FCB2C31BA823A2B002160627D29AC07A08C39B059B31D40608BD37390D648B9D808B2D3F34AC9BE7D339F62B5E2
// FWD: random 2
0C57799091368B1F5F375F815CF0BAC04878468E5A73D49D16CE96E27371610D69A2D46BB7DD943C577D61EAB4216BC4B29030E4EAA4276E7CA9CECF37E63A5B2FBE03B039D528BB0C5974367CB447E763FCEAA08BCA26A119CC88B800F00ECC640D8236A0ABC76886161116B15A05E126B7E87BBA4BFDADDC1BA7C91E91C95A9D875D3AABE38AD6A603B00A6B6868C47015AE3CCB0A1A4A1388724ED42E6281558F99866E08AE509ACBC51526BCD241609B7DA214CB0708B4C124209305AB633097378576C7584109411380D9634DCC5B3B8A20C82C1DB300712030B43A661468C6427147F75968E06CC78F6F2CF2CB737515B0649EC7CA48274B8281D71F04E838060E60C358B386936A1869CFCA6B36637A1E77A9CD349F519CAA090BFECBE46AADA2D480ACEC99194290C18388B7D061947D241BE4BE0B3A25B7539CA40D57DA42CA41132AEAF555E5289A990389C5530AC9484526988F8501B3FA03A9AB430E15A364ACC05A4A522553137215F8C42B947622623F826173207A6BCCBF859
// FWD: random 3
08852790477785DA6462B48C30AF563317B045018BD9762C222778F86C7C94E7B3D9BAC9E1BF8A91718DDC6C07D3348CC6B00B635B62207936C51B64AA6048A70A79D21D9CC8D48C51155847149B82896A6C1E1BAA3D94C48B3485F34B71ACB63995CBD6864B59B38861C5148CF1372734523410BDCC9F4B8CB3BCD2AA206A333B727D264B07BAE0715061793CF29DAFA9884D56BD929C030E62915884D258E2FB08481CB9ECFDC5825FCED5D1B5E2C1BDD90DA522388CA66FB0BA2F01CC049155FBCB40D038DA24C810131CA1E83E15BB5414D21AA2FA8D644D87057B30F283A5DCA9A5E515A327B1836546BCE20C24929F22F369251754596B9F15C4BD44D7BE2BB5CA892CB3609BE783496E0EC04744668917AB4164E481375A1B11706029278F741C567BA30500507995E9FF31096D67FB666391CF903484AB7ABFB8FAFD76D7001926C435B1E647475EB7530F4FD844B69AC0CBD3A5CDA4A637842BA3C4BCA7714F5881AEC3DCFE2C224002646C2107605E57203B519EAB74277A0761653
// INV: random 0
197D71D2709E24F36F95315B99B05CD73CA655DC77E5084A95AD55B1DEA520B1B70546B3FA6B093904793BEE8319E0C888A767395C56379D3D058288AA62BB55E53BD4C43892613717BD8286F7601C90C06BB8A3FB88C94A16A15B12953A83AC3B653D0B702F38218EF8738D659EBDF4ACAD340D984BA9A91AA89E4C3B2157350C5C5608F5EE1B1220CE0C13B72000C7FBBF8435AF0DD98FA0583397ACF35C581A5852421CCB139E34A2401A431F0B870F1725CA9B391939959E593284A6A70F13540CA233700788BD56D4442BB0B14753B0C2634B3B8E0777BE0378A62E119A20D33BB74CE2B7359BBD16003EC91E95E2BCC2B4712E168F70C98EC588207D09C5BB27DAC7D35A51D4E5C461C95B3BCF3FD314900B3B97719D75302982A9008813B4440807B713ED1B58F9748BD9244AFF187271BFD66C725141AF15F7AB6B0C56E3B47A5962A480D07C944C3D92D6949B2E07D4BFAE2171304C818506A867F14E1E67CF8BFA7F6DC4CD1D0C0034B7591920337D38F267869ECD2BD380F93B678
// INV: random 1
1BCEA253B22891A56FA38A9B6643C3BC2DC3B64A5CBBAC8FA0B92172BD9C6056CA2A772BA4187D2AB2130923B287D02CA3BD1AA1E65E22E747AAA05B3BF31E172F88B6E1008C896DE11118720F9C258773ABCA8C5A72A32B0683E8380B40A82D95345849D416620106E01D1523F28DA2617AF56736BA0F7AC1591538CC9120DA3FC75A3C3A688A2459964AC071D1B90B610B6BB598135481B552B2BD4C0C4513C236759D7C341E4EC3D31B99E9C108DF97A93A13C5D45D3787C4A2EC4FBB579F281FC8151872A9215E8851A339F7488C0681E4FD3A370E7E30220212C3B2934895F0473CA7B96C01945178B951B0002E10C053D917818548CDD2683125DE20EC6754725B1EE64B2FB9A744C28C0056C098C37B426A340EAA319C452619588300330B4BD19AA4263E90F9A61F85071D71CF5DA9742C10C44BAB03CF277170C5068ABC884DB0571160CB7D6BCB29E27C4DD90718837F19973B856C9E72984228C89084CACA7A560761A20083F901CE40CC6D690A93D26613B7DD1B4C5B4333FA9C0
// INV: random 2
1068B8B1480E300B40F2AA02A99CCA5B9D757302B989EC9B22083D3CD41DC9D940B75A7E77408B53C75DFB3736822417A614A1E33EB2BC2549039973057009E9339E310BAF6A93077A43685BC34944182893A2CD617E08F70A35C67D07A0F0B669A5364B9836D15F3E4C410AC1F938B5F1A9990A30A4094CB1DC1F68C88B15D74AB3CC8296980101CC84A9E05B4AC9463CC80E647E1C8BFDBC245876537C6CA6E4AE9425981821978310A2FB69A1A22F6FE8929F2789A8FB30C3256EC3306EA4224F2FA75C42406A59C7E216599108F555CBC431230427B318AF51D52EAC45EBCE2A9FB2C8192F3787A3F75A93D4B3BF47F56CA583B944EA6279855B3C1C77893630952D190C663D68CD4D89501A7783849B203FF0F37D1783510AE222C4695B604C4A1641CC497A696C5F6A6A7523A7E555579A892070A25101C4A6E50D6184B4D008C55840A2E7EB46E31CC2FA56A8990B33BA03569179C9C028A57CF527E33847324730469D7FB3131401AA8FECFA1C31A5C7F78F16850F6D6B3B8FC36707B
// INV: random 3
1A258A84A254FAAF64C4731BA6348D78061818974EFB0682C17A2E062AC5E2F27C584B5F52FC7D4A0D3C19C89D4BD0AFE3C05F927FB9A0EF64C438BCF58D2E686A7E31E65A248653A97B88B2779A5AF420965E1F42E633284F96B1C801EC9C83F6471E845659F7C93DD8E297D86F537C6D8DE8CE6726E24809DA2A00770217BD8036A37FAA72C9972C6BD8EB461B68A54B399F92E4AB088A84C09AAF22D07B528D145AEBA4605F5E9B12BF919C7AD043B86B9D6B23676DCA6043D2A111B09D5A79E595C9191A19C8AAE156C1DB23BA3C8918B382C3DA73075A2825A8366076585D45CD9823320EF5261FA7F51556946D953746003ECE463E4554E6AE8A128B911F31A1374C4068A0F3CFC8C1C315387165F068187B4283D9B3433A9D8DBBAAB63A8E0395CA7D5334B792529DF57484D7208BB778532B02BF67F6099000B2AAD746F9C7117AE7593C4A345B5B5CC9C8B8190129D147A0B5ED99CBCF4A818775955B9682400A04916021961BF5369005332C675E45F9B182FBFC49B5037618DA0BE
// INV(NTT(random 0)) → random*256
1C893A501FB1FC01ABBAFABBFB711F40A42A71A277C0FD13D05806C4A8948853C37CB72E47CF89B74A747BB6C8808830075394FC0CB67CA30D593E7AD013C0EB3EC959189BD22303D97B1827BF6611384F6212A8E35EB822ED182575852147A7D0A37DE0EE0AF9EA60956490AA875B833CA067F73DFAF874FC99869102644426498A51964CC6006344CC9A21ACFB0A0F6491153B7B8AD5EB8687E10959E2AC8C062F08895AF934C12133CAC73784541AAA7179AABADAC67861707AA5C1CBA254E0C2B357335DF9E35FDBDA3A81552FABA269836906D14A7887FB45AAEB17163144927EBB62BF321111A2549FA3E6B49FE81746D98B646639A2A9FFC6E5A8019C8EB22BC01416361BDB2F4D89AF7FD684C8F78657374F96084F60E6BA1604A8B435FDAF875215BCD533A6CA413396AAC7163C9915243B9889FBE4A9E89FC0B4F3A5207855A2A6536898856C8336CBEC87241F0D77506CB0200C3B291540D5A2E55C76D9EE0CA56205D169A604466310FFA355858B96D1B9A83E99B26079255C68C
// INV(NTT(random 1)) → random*256
190423B37EA2101DAD3CCC02177663395CBF024A2598CF74E55712BAC08C07BD49D7D952326D497370BA82685C429D699639540A0859B0CDC19CB1B0E57146F06E42A65CC0A49C2DCA2BBB1500092C086A401F28E5C9A271F94773544A815A9EC01F3EE0379BBC5681C2581B32FB3B4CDE46B40ACCF456CA9C3E0BB8CA89E5B309208841BCA23AE2C94AE7F22B018019D4328911D27F973D1E31E10940EF56B928C05AA218D45117E69820F5591BF4AD2179A064E60690C6499A645C818748BFF7D44D84BA45A37C4D4C5F0817AFB86B3A30C41C261282565A14C7B9071DF5113355C7B3779C7B583F06FBF1479941AA700D5EDAAD45F75C4FFC78290BF56F0B975F081A523B283F7ABBA054D86249F5C500A39050065DF13C1561CD72ABFD72EBF6451589BAB21730E1A1A4B7AC2D79133D62714411047FA9BF95572BAAC33528409B0D982CA409E55E0583A5E8A6183B2A3DD4457108A4C0E5EA4B699E95F287B7B9DF6DFCDB5B14A41BE46D95C1870867473953D02A349349A6670A639D9E2

View File

@@ -0,0 +1,62 @@
# xsim_run.tcl - Vivado xsim compilation and simulation script for NTT
#
# Compiles all NTT RTL sources plus testbench and runs simulation.
# Run from the project root: ~/Dev/mlkem/
#
# Prerequisites:
# source /opt/Xilinx/Vivado/2019.2/settings64.sh
#
# Usage examples:
# # Run ntt_core testbench
# xsim ntt_core_sim -R
#
# # Step-by-step:
# vivado -mode batch -source xsim_run.tcl
# ================================================================
# Configuration
# ================================================================
set SRC_DIR sync_rtl/ntt
set TB_DIR sync_rtl/ntt/TB
# ================================================================
# Step 1: Compile all source files (xvlog)
# ================================================================
puts "=== Compiling RTL sources ==="
# Barrett modular multiplier (combinational)
xvlog -sv ${SRC_DIR}/barrett_mul.v
# Zeta ROM (combinational)
xvlog -sv ${SRC_DIR}/zeta_rom.v
# Butterfly unit (combinational, instantiates barrett_mul)
xvlog -sv ${SRC_DIR}/butterfly_unit.v
# NTT core (FSM-based, instantiates butterfly_unit + zeta_rom + barrett_mul)
xvlog -sv ${SRC_DIR}/ntt_core.v
# ================================================================
# Step 2: Compile testbench
# ================================================================
puts "=== Compiling testbench ==="
# File-based vector testbench for ntt_core
xvlog -sv ${TB_DIR}/tb_ntt_core_xsim.v
# ================================================================
# Step 3: Elaborate (xelab)
# ================================================================
puts "=== Elaborating snapshot ==="
xelab tb_ntt_core_xsim -s ntt_core_sim
# ================================================================
# Step 4: Run simulation
# ================================================================
puts ""
puts "=== Running ntt_core test ==="
xsim ntt_core_sim -R
puts ""
puts "=== Simulation complete ==="

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 ==="

View File

@@ -0,0 +1,195 @@
#!/usr/bin/env python3
"""gen_vectors.py - Generate poly_mul test vectors for Vivado XSIM testbench.
Generates input vectors for poly_mul_sync module in hex format.
Each line encodes {256x12-bit poly A, 256x12-bit poly B} = 6144 bits
packed as a single hex number (1536 hex chars).
The poly_mul_sync module uses basecase_mul (degree-1 Karatsuba
multiplication) with zeta from poly_mul_zeta_rom.
For each pair of degree-1 polynomials (A[2k], A[2k+1]) and (B[2k], B[2k+1]):
c0 = (a0*b0 + a1*b1*zeta) mod Q
c1 = (a0*b1 + a1*b0) mod Q
Usage:
python3 gen_vectors.py
Output:
vectors/poly_mul_input.hex
"""
import sys
import os
import random
# ============================================================
# Constants
# ============================================================
Q = 3329 # ML-KEM modulus
N = 256 # Number of coefficients per polynomial
# poly_mul_zeta_rom values (zeta = zeta_bitRev[k]^2 * 17 mod Q)
# These match sync_rtl/poly_mul/poly_mul_zeta_rom.v exactly
POLY_MUL_ZETA = [
17, 3312, 2761, 568, 583, 2746, 2649, 680,
1637, 1692, 723, 2606, 2288, 1041, 1100, 2229,
1409, 1920, 2662, 667, 3281, 48, 233, 3096,
756, 2573, 2156, 1173, 3015, 314, 3050, 279,
1703, 1626, 1651, 1678, 2789, 540, 1789, 1540,
1847, 1482, 952, 2377, 1461, 1868, 2687, 642,
939, 2390, 2308, 1021, 2437, 892, 2388, 941,
733, 2596, 2337, 992, 268, 3061, 641, 2688,
1584, 1745, 2298, 1031, 2037, 1292, 3220, 109,
375, 2954, 2549, 780, 2090, 1239, 1645, 1684,
1063, 2266, 319, 3010, 2773, 556, 757, 2572,
2099, 1230, 561, 2768, 2466, 863, 2594, 735,
2804, 525, 1092, 2237, 403, 2926, 1026, 2303,
1143, 2186, 2150, 1179, 2775, 554, 886, 2443,
1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300,
2110, 1219, 2935, 394, 885, 2444, 2154, 1175,
]
def barrett_mul(a: int, b: int) -> int:
"""Barrett modular multiplication: (a * b) mod Q."""
return (a * b) % Q
def compute_expected(a_coeffs: list, b_coeffs: list) -> list:
"""Compute expected output of poly_mul_sync.
The poly_mul_sync module processes coefficient pairs using basecase_mul:
For each k in 0..127:
a0=A[2k], a1=A[2k+1], b0=B[2k], b1=B[2k+1], zeta=zeta_rom[k]
c0 = (a0*b0 + a1*b1*zeta) mod Q
c1 = (a0*b1 + a1*b0) mod Q
"""
result = [0] * N
for k in range(N // 2):
a0 = a_coeffs[2 * k]
a1 = a_coeffs[2 * k + 1]
b0 = b_coeffs[2 * k]
b1 = b_coeffs[2 * k + 1]
zeta = POLY_MUL_ZETA[k]
# c0 = (a0*b0 + a1*b1*zeta) mod Q
t1 = barrett_mul(a0, b0)
t2 = barrett_mul(a1, b1)
t2_zeta = barrett_mul(t2, zeta)
c0 = (t1 + t2_zeta) % Q
# c1 = (a0*b1 + a1*b0) mod Q
t3 = barrett_mul(a0, b1)
t4 = barrett_mul(a1, b0)
c1 = (t3 + t4) % Q
result[2 * k] = c0
result[2 * k + 1] = c1
return result
def coeffs_pair_to_hex(a_coeffs: list, b_coeffs: list) -> str:
"""Convert 256+256 12-bit coefficients to a 1536-char hex string.
A coeffs in the MSB half, B coeffs in the LSB half.
A[0] at the very top, B[255] at the very bottom.
"""
result = 0
for c in a_coeffs:
result = (result << 12) | (c & 0xFFF)
for c in b_coeffs:
result = (result << 12) | (c & 0xFFF)
return f"{result:01536X}"
def write_vector(f, a_coeffs: list, b_coeffs: list, label: str):
"""Write a single test vector to the hex file."""
hex_str = coeffs_pair_to_hex(a_coeffs, b_coeffs)
f.write(f"// {label}\n")
f.write(hex_str + "\n")
def generate_vectors():
"""Generate test vectors for poly_mul_sync."""
os.makedirs("vectors", exist_ok=True)
hex_path = os.path.join("vectors", "poly_mul_input.hex")
tests = []
# --- Test 0: All zeros ---
zeros = [0] * N
expected = [0] * N
tests.append((zeros, zeros, expected, "A=0, B=0"))
# --- Test 1: A = 1 at all positions, B = all zeros ---
a_ones = [1] * N
tests.append((a_ones, zeros, [0] * N, "A=1, B=0"))
# --- Test 2: A = [1,0,1,0,...], B = [0,1,0,1,...] ---
a_10 = [1 if i % 2 == 0 else 0 for i in range(N)]
b_01 = [0 if i % 2 == 0 else 1 for i in range(N)]
expected = compute_expected(a_10, b_01)
tests.append((a_10, b_01, expected, "A=1010..., B=0101..."))
# --- Test 3: impulse at index 0 for both A and B ---
a_imp0 = [0] * N
a_imp0[0] = 1
b_imp0 = [0] * N
b_imp0[0] = 1
expected = compute_expected(a_imp0, b_imp0)
tests.append((a_imp0, b_imp0, expected, "A=imp[0], B=imp[0]"))
# --- Test 4: impulse at index 1 for both A and B ---
a_imp1 = [0] * N
a_imp1[1] = 1
b_imp1 = [0] * N
b_imp1[1] = 1
expected = compute_expected(a_imp1, b_imp1)
tests.append((a_imp1, b_imp1, expected, "A=imp[1], B=imp[1]"))
# --- Test 5: impulse pair (0,1) ---
a_imp01 = [0] * N
a_imp01[0] = 1
a_imp01[1] = 1
b_imp01 = [0] * N
b_imp01[0] = 1
b_imp01[1] = 1
expected = compute_expected(a_imp01, b_imp01)
tests.append((a_imp01, b_imp01, expected, "A=imp[0,1], B=imp[0,1]"))
# --- Test 6: all pairs identity ---
a_id = [1, 0] * (N // 2)
b_id = [0, 0] * (N // 2)
b_id[0] = 1 # B = [1,0,0,0,...]
expected = compute_expected(a_id, b_id)
tests.append((a_id, b_id, expected, "A=all pairs (1,0), B=imp[0]"))
# --- Tests 7-10: random vectors ---
random.seed(0xBEEF)
for i in range(4):
a_rand = [random.randrange(0, Q) for _ in range(N)]
b_rand = [random.randrange(0, Q) for _ in range(N)]
expected = compute_expected(a_rand, b_rand)
tests.append((a_rand, b_rand, expected, f"random {i}"))
# Write input hex file
with open(hex_path, "w") as f:
f.write("// poly_mul_sync test vectors\n")
f.write("// Format: {768 hex chars A coeffs}{768 hex chars B coeffs}\n")
f.write("// A coeffs: 256 x 12-bit values, A[0] at MSB\n")
f.write("// B coeffs: 256 x 12-bit values, B[0] after A[255]\n")
f.write("\n")
for a_coeffs, b_coeffs, expected, label in tests:
write_vector(f, a_coeffs, b_coeffs, label)
print(f"Generated {len(tests)} test vectors → {hex_path}")
# Print expected results for reference
print("\nExpected output summary (first 4 coeffs of each test):")
for a_coeffs, b_coeffs, expected, label in tests:
first4 = expected[:4]
print(f" {label:35s} → [{first4[0]:4d}, {first4[1]:4d}, {first4[2]:4d}, {first4[3]:4d}, ...]")
if __name__ == "__main__":
generate_vectors()

View File

@@ -0,0 +1,312 @@
// tb_poly_mul_xsim.v - Standard Verilog testbench for poly_mul_sync targeting Vivado xsim
//
// Reads test vectors from a hex file using $readmemh.
// Each line is a single hex number encoding 512 coefficient values:
// - bits [6143:3072]: 256 x 12-bit polynomial A coefficients
// - bits [3071:0]: 256 x 12-bit polynomial B coefficients
// - Total: 1536 hex chars per line, NO spaces
//
// Drives poly_mul_sync, streams in 256 A+B coefficient pairs via valid/ready,
// waits for valid_o output, streams out 256 result coefficients,
// and writes "RESULT: INDEX COEFF_HEX" to the output file.
//
// poly_mul_sync FSM: IDLE LOAD COMP_CALC COMP_C0 COMP_C1 ... DONE IDLE
// valid_o is asserted during COMP_C0 and COMP_C1 states.
//
// Parameters:
// VECTOR_FILE - path to input hex file (default: "vectors/poly_mul_input.hex")
// RESULT_FILE - path to output file (default: "vectors/poly_mul_result.hex")
//
// Usage:
// xvlog -sv barrett_mul.v basecase_mul.v poly_mul_zeta_rom.v poly_mul_sync.v tb_poly_mul_xsim.v
// xelab tb_poly_mul_xsim -s tb_poly_mul_xsim
// xsim tb_poly_mul_xsim -R
`timescale 1ns / 1ps
module tb_poly_mul_xsim;
// ================================================================
// Parameters
// ================================================================
parameter VECTOR_FILE = "sync_rtl/poly_mul/TB/vectors/poly_mul_input.hex";
parameter RESULT_FILE = "sync_rtl/poly_mul/TB/vectors/poly_mul_result.hex";
parameter MAX_VECTORS = 64;
parameter N_COEFFS = 256;
parameter TIMEOUT_CYCLES= 100000;
// ================================================================
// DUT signals
// ================================================================
reg clk;
reg rst_n;
reg [11:0] coeff_a_in;
reg [11:0] coeff_b_in;
reg valid_i;
wire ready_o;
wire [11:0] coeff_out;
wire valid_o;
reg ready_i;
// ================================================================
// DUT instantiation
// ================================================================
poly_mul_sync u_dut (
.clk (clk),
.rst_n (rst_n),
.coeff_a_in (coeff_a_in),
.coeff_b_in (coeff_b_in),
.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)
// 6144 bits per word:
// bits [6143:3072]: 256 x 12-bit A coefficients (A[0] at MSB)
// bits [3071:0]: 256 x 12-bit B coefficients (B[0] at MSB of lower half)
// ================================================================
reg [6143:0] vector_mem [0:MAX_VECTORS-1];
integer vec_count;
integer idx;
integer ci;
integer cycle_count;
integer result_fd;
// Test result tracking
integer pass_count;
integer fail_count;
// Temporary storage for coefficients
reg [11:0] a_coeffs [0:N_COEFFS-1];
reg [11:0] b_coeffs [0:N_COEFFS-1];
reg [11:0] output_coeffs [0:N_COEFFS-1];
// ================================================================
// 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 entries (XSim leaves unloaded entries as 6144'hX)
begin
integer found_end;
found_end = 0;
for (idx = 0; idx < MAX_VECTORS; idx = idx + 1) begin
if (!found_end && (vector_mem[idx] === 6144'hx ||
vector_mem[idx] === 6144'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 format is correct.");
$display(" Each line: 1536 hex chars (768 hex A + 768 hex B)");
$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;
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
integer out_idx;
integer input_ok;
// Extract A and B coefficients from vector memory
//
// Memory layout (A in MSB half, B in LSB half):
// A[0] bits [6143:6132]
// A[1] bits [6131:6120]
// ...
// A[255] bits [3083:3072]
// B[0] bits [3071:3060]
// B[1] bits [3059:3048]
// ...
// B[255] bits [11:0]
//
// Formula: coeff[i] = vector_mem[(N-1-i)*12+11+offset : (N-1-i)*12+offset]
// A: offset = N*12 = 3072
// B: offset = 0
for (ci = 0; ci < N_COEFFS; ci = ci + 1) begin
a_coeffs[ci] = vector_mem[idx][(N_COEFFS-1-ci)*12 + 11 + N_COEFFS*12
: (N_COEFFS-1-ci)*12 + N_COEFFS*12];
b_coeffs[ci] = vector_mem[idx][(N_COEFFS-1-ci)*12 + 11
: (N_COEFFS-1-ci)*12];
end
$display("INFO: Vector %0d", idx);
input_ok = 1;
// ----------------------------------------------------
// LOAD phase: stream in 256 A+B coefficient pairs
// ----------------------------------------------------
for (ci = 0; ci < N_COEFFS && input_ok; ci = ci + 1) begin
// Wait for ready_o before driving next input
cycle_count = 0;
while (!ready_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 ready_o (input ci=%0d) on vector %0d",
ci, idx);
fail_count = fail_count + 1;
input_ok = 0;
end else begin
coeff_a_in <= a_coeffs[ci];
coeff_b_in <= b_coeffs[ci];
valid_i <= 1'b1;
@(posedge clk);
valid_i <= 1'b0;
end
end
if (!input_ok) begin
// Skip output reading for this failed vector
@(posedge clk);
end else begin
// ----------------------------------------------------
// OUTPUT phase: wait for valid_o and stream out results
// poly_mul_sync enters COMP_CALCCOMP_C0 state after LOAD.
// valid_o is asserted during COMP_C0 and COMP_C1.
// There may be cycles with valid_o=0 between outputs
// (during COMP_CALC transitions).
// ----------------------------------------------------
out_idx = 0;
while (out_idx < N_COEFFS) begin
// Wait for valid_o
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 (output %0d) on vector %0d",
out_idx, idx);
fail_count = fail_count + 1;
out_idx = N_COEFFS; // break out
end else begin
// Capture output coefficient
output_coeffs[out_idx] = coeff_out;
out_idx = out_idx + 1;
@(posedge clk); // ready_i=1, consume output
end
end
pass_count = pass_count + 1;
// Write result to output file
// Format: "RESULT: INDEX COEFF_HEX"
$fwrite(result_fd, "RESULT: %0d ", idx);
// Write all 256 output coeffs as hex (3 chars each = 768 total)
begin
integer oi;
reg [3:0] nib;
for (oi = 0; oi < N_COEFFS; oi = oi + 1) begin
nib = output_coeffs[oi][11:8];
$fwrite(result_fd, "%c", nibble_to_ascii(nib));
nib = output_coeffs[oi][7:4];
$fwrite(result_fd, "%c", nibble_to_ascii(nib));
nib = output_coeffs[oi][3:0];
$fwrite(result_fd, "%c", nibble_to_ascii(nib));
end
end
$fwrite(result_fd, "\n");
// Wait for DUT to return to IDLE before next vector
// The DUT goes to S_DONE then S_IDLE after all outputs
cycle_count = 0;
while (!ready_o && cycle_count < 1000) begin
@(posedge clk);
cycle_count = cycle_count + 1;
end
end
// Extra cycle before next vector
@(posedge clk);
end // inner begin block
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);
$display("========================================");
$finish;
end
// ================================================================
// Timeout watchdog
// ================================================================
initial begin
// TIMEOUT_CYCLES * 10ns per cycle * 10x margin
#(TIMEOUT_CYCLES * 10 * 10);
$display("FATAL: Global simulation timeout reached");
$finish;
end
endmodule

View File

@@ -0,0 +1,27 @@
// poly_mul_sync test vectors
// Format: {768 hex chars A coeffs}{768 hex chars B coeffs}
// A coeffs: 256 x 12-bit values, A[0] at MSB
// B coeffs: 256 x 12-bit values, B[0] after A[255]
// A=0, B=0
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// A=1, B=0
001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// A=1010..., B=0101...
001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001
// A=imp[0], B=imp[0]
001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// A=imp[1], B=imp[1]
000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// A=imp[0,1], B=imp[0,1]
001001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// A=all pairs (1,0), B=imp[0]
001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// random 0
0B2837A5C17D23B4A88A0ACE5DE1CDC86644B0E74ECAA7DAA6F5144AB67E56B6F928900BA1D2B45A92D8A4287701027666A8F1A7705705EBD304D3AF0796544D97645126AB8D97F96D60F73E148564B52B5226E7602663B8E84ABCF89782018EBEBAF68C7B867758675C906D467B7408C75EA76BFE899391049AE83004577AA98A30914DB6F8D79DE4AD9516D5BF3B52726091447843B6B24C2CF6F8973BC94EE06F7CC3B95558C55051643E03579A72D59BB423C7858F29F1D1C463BA61D99897E36209791E761C8D6F5A089C873ECC46054F88339E708298B18986E7FB8A41FCCEEA2F9375DFBCC633AF7054034BDD2F016B9A781866339E9113164D15F700875C00A489B15036BC40782243AD6804FBC8BCD75DB261A8369A1B1AB7B5D9BC4271DBA6D8CC6744E5CA39104F45DB2923A8A0D6A83B7C782173870C5A8C5966DA437A5256A22086626088887A073BD68D1A8AB93126BC773799E84A43B8A82C9A98AD59E8A0503EACF40D3578D3B44C7F4835A28E7A6A62D1E15F9301579C68746849B5CC46A71BAD53504C8050590D69EE62289205F6C810DC1EA1B2AA8A24A12EAC358378E44EA15E0F73153C32E78A7002BAB1A09B889D21D8302760282F6C279ECCDF2957D1BC174A8C36EDB5F33B7B67577F78670C53972842C010C0A08AE9DABA9B3061125D6DDC5316B8524148095CA4E9BA8BDE5D23589F9A7EA316294262A53648C4B9B9617818AD7B20D0481A7F95A0F69225C1010403B36092CA04BCAE94E91D762CBA53435E602A149AC729A6094A5C0637A72E91E834B8F7FD9494AA911601564C3B33691F48F35001D9968922BE411C6913B48114F644B200E2BA425D041177A65320A21B2E01F09D772713360AAB868C3165637537237D353A30705B2F5B28FE809A169273599AD1C99455DABE299A219051904389BEA258700041BAFB3A3172CF081082AB62B18B7993B794948C142E8C2B751F6176512A5C935B5184F496793C3931E7E15BC0A1006B923F934922A72F7AC0CB2C3ABF79992D3D39F44E21A929DBC62F000B78EC4DA4A0149068AA8EFA201C433B0E00E6
// random 1
B3834B167A128C439F7ADB928B720338438A95E3FD92A6637090ADB0F8219726B426C182A85A839522C43F62EC3749860797B998224B8A2375922C951E55A3B074EA1203512C30740232F95E691334F3D7BEE0F66DE88944A0BF9C67708AD8947432D17F584604437737A4C05EE797A9C51EA7F908061B6C8FA359150A6B9509422DA7207F778302B0D65CB39B1D69587AD6189D44344A6AB389648D27E31C24D5AA7C7A696EAC477005AB94F6AABF50D53CACA30EC7294E35045856687B9843BC395EC9C802B904F011FA204C627A2B8CB84A0C827D1AA85160C55FA94F5C2A1C7774C042F77799F85198FADE613992428A3137B86445FAFF93580E643589A0B367C77877688B60222C04AFECB24242A23D01D1668201B74AB20CFAF526F3347219B6C555859796939076252F09BD0594B61B021BA276C24338E03DEA212AB29C72361BB3C8B84C852790957C2A50F894E7D7A1A5B23B0B808EC2B4C1510E2822A544CCBB05B0CDB5B031AA449019B842787C99A74C3166ECAE6C104D590BF5C866D7A8D54F43C8AE350174A0FB8F9F742CB8B5956D3B8A78C3C6AF025A5BB8043BEB805F29B19A1641272CD1BB937C6037CC3741B863499D650BC65E75204F99C98B87DD8445133479D18D5828862C224469BF3ED79556417F137B1B835BE323584035461C8564FD7504627AEB7673CCF2A595CD18804D86B01232F9CD84E86CAD8048347686390857AD80E32BE83A0737B81CC43A5CCC0D9B46F8223BF3285B096A46FBACF701A2AC6298A79891637D3AF3A928F7FE1A9BAA9977B5AA5B5AA2A63D32F5AA8595EA070121C9B85E4B522A112C37A5716D830A1D23E1DE7BA8EAC687894EC4617D2CC56959237CD5432EE969C2F8C26DC8193806CB1769C08BB728CB6BD55BD17308324C51E8FA4339670705D7A2F01D94362A84026E16C66937F76F0CE933B3DC602D7698426650CE6279C54CEEB879D069B57879633AA1826344B77AAEA3E509115D696832CEC40E8249BB5A2A7E79DB67C213455F21FF2DF5B32978376D08E039F3B3456CCE66F4A6861BB620C9037299843E9B8B6608ED
// random 2
C844BF8EE53A6C5C4ACA20BD0C6BDD49F02C56430F8B4C02020915B4143D3411691F72F442F3D90C120C98F609304AE7C240FD5C35906A5026A0A040B2631A1B48819A349960670533968C124B37A5E84A0232B59B955C67DCD7A3E53383F25C787BD77B2C74A6C33A82CA47760B163F19E50CC6D72C2916B60A96197C0E95CB787576A6CFA28767C48CAB2A973202300DE6C6BADBF091798A08A43F6AFB8129F3AE510CF867E2768B863D4F57064AEABF74B29B55578D7EE7093DE7220680B51D659FBD8C817154DF09FA6273A76A49F19B7325FECE42DCB998F354D33B23F5B7CE249F33944E7A962D13358B8CDBC799E942B8B3A57068653FF47274CC9837A79D2EEB5B2028DCAF74189D59EB68980C011C1868D215A8E0B4BAF5A74025EC0E42C0727C814187FCB7A538CF98142FD7227716944C5C821F34F67D65580827599429AC1141183AFC9A2292E8CE8B028FCC1D2484570EA6B80506A2C5A1DD8A95503B4517C892088D10A366FB2209636961FAB3C900114E52AABEEC434666998193F983D1DA9C375C162AED98CC4A7F7245716A5DB9C947C5825F50057C48CA9B28BC696CF3791700B6C5AAC305229586F49540A52868CAE87858F7A0B786A597D09F37AB1AE1E03AFC7351B59E2E130017F8DF183CB7953BB8A7938A4519338AD1AB7A637323A606C21082B0F7252462F0C467058957C3168775127C7AAEE1A3C7A1BA8AC8B95AC95D640A93748C81BD40668DF3A5CB5674A59A311DC9651BE4DE91ABFDB594F500A4C411DA7C20709AB8AC5E4526091E114A015533990A61B2EA0C1483861F33B2C616C19B919CB19A7B4F59B2742C002D54931251E91FD76881C55B7F2BD230418C18493010F2503DACEE3DE8FA55467628A758063140B3383B3364193C56EA39E93F4845EB7009CF4D383DC387D2A0207600D6F2C2FC0570B0E37104B5A179227550BA636160075B440B6006510C0117730D1A1BC02867BE13AA1C4D8B8188774618569C97EBBB4578ADC4C8292E661337EC9401D11CB9556433CBED3C236146ACCE24FCD883CB878D715EAC80914DA642AFE2A6365BD7
// random 3
AFEC344F665D166A8EB25B55C64780C961871BECEBB0910E3437821593EF5F0BBD8ADC6D9229184282028A128196FCC37B44F4B490777858438F27023D11CDC253E84098856FA4BA403CDC63F00B92225CA5BAAE38074D59DAF35A0B20B4A249B1A2E5C235E4975685A5C58E6263DD99F948122B3E79708D0889541FF33D90D07A6E30531B577258870386E860C4802E34865C5D57F0B906504E33BB19363D1021D13B292C6545818AC8EA2E6A2A8EE101B3D45D360657CD15C253B8B6AEFC694C888EC078CF058A407C76079752253543685B98750B78BD19C55D7C02027DB3BEA74C8246F65BC8BAC7966C12AD732181D0E218984863C9842D9A953CEA5AA8409CB7992F9A10F3153A6286015C587A341886A4C9B5341FA04E8BD4F6AB804121963781D460333BF046E88F4023652C5407C74188A4129653C6D09F73A093F245404CB68BE4CD294163CA58492BC24A83FC74006A7B0E38500188963C259E428C5173D3329ECA347F739998D264B2C75830060E0EE9C4A12C2CB972F6C5EA9C02D95D60AC6F18F7C646255155C073907C3308CAC2B733CC70F537827A6AC2B6F079D4F26E47A735355B83A4BA2B049BC254503397E5818C2B5113DAA5F7C33DE3F5CE9B380D7CEF9DC1E39573A55A67C990B1633D12D0ABE43963E5A73B2B5696D2620077262E2697AD517280A5EE30A9E644ABD69A7CEB00EB326AA9AD2C7B2020E5AE8E271016703C6048FD0AC5740F62B96FB10F5FB7A768101687A7F07B657874902F2873FB05BA3F2FD4BA495997346A7E49615858EA2280AAB89344C2C541065BAA0BAD60E70C222A0A4648727102BA8B1E107A22ACCB91687B120E80E729F0A90219E633C93B0AB8D126983B850773869174C793F297D7B69A5444A488815C8CF0AD94260930C83AEBE232C5F8895BBE4239148AA9E8293119B760E6A086E530002E1D9A25CA5526A9AC97835C912FD079698A993DEB5D9BACDD3EC09B4AA7F57121599EE2481F68CAAA29B7A351DAAD18FDC25932B8D060BF057EADF7DF8726EB77E29412A62D34E1F886357E031753023C5C886153415962C7A790

View File

@@ -0,0 +1,69 @@
# xsim_run.tcl - Vivado xsim compilation and simulation script for PolyMul
#
# Compiles all PolyMul RTL sources plus dependencies, then testbench,
# and runs simulation.
# Run from the project root: ~/Dev/mlkem/
#
# Prerequisites:
# source /opt/Xilinx/Vivado/2019.2/settings64.sh
#
# Usage examples:
# # Run poly_mul_sync testbench
# xsim poly_mul_sim -R
#
# # Step-by-step:
# vivado -mode batch -source xsim_run.tcl
# ================================================================
# Configuration
# ================================================================
set NTT_DIR sync_rtl/ntt
set PM_DIR sync_rtl/poly_mul
set TB_DIR sync_rtl/poly_mul/TB
# ================================================================
# Step 1: Compile dependency sources (ntt/barrett_mul)
# ================================================================
puts "=== Compiling RTL dependencies ==="
# Barrett modular multiplier (shared dependency from ntt/)
xvlog -sv ${NTT_DIR}/barrett_mul.v
# ================================================================
# Step 2: Compile PolyMul sources
# ================================================================
puts "=== Compiling PolyMul RTL sources ==="
# Basecase multiplier (instantiates barrett_mul)
xvlog -sv ${PM_DIR}/basecase_mul.v
# PolyMul zeta ROM
xvlog -sv ${PM_DIR}/poly_mul_zeta_rom.v
# PolyMul sync top (instantiates basecase_mul + poly_mul_zeta_rom)
xvlog -sv ${PM_DIR}/poly_mul_sync.v
# ================================================================
# Step 3: Compile testbench
# ================================================================
puts "=== Compiling testbench ==="
# File-based vector testbench for poly_mul_sync
xvlog -sv ${TB_DIR}/tb_poly_mul_xsim.v
# ================================================================
# Step 4: Elaborate (xelab)
# ================================================================
puts "=== Elaborating snapshot ==="
xelab tb_poly_mul_xsim -s poly_mul_sim
# ================================================================
# Step 5: Run simulation
# ================================================================
puts ""
puts "=== Running poly_mul_sync test ==="
xsim poly_mul_sim -R
puts ""
puts "=== Simulation complete ==="

View File

@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""Generate expected LFSR output states for rng_sync module.
Computes the Galois LFSR sequence for the RTL default seed and writes
expected states to rng_input.hex (one 256-bit hex word per line).
The RTL uses a Galois LFSR with taps at bits 255, 253, 252, 247, 0.
On each valid_i pulse, the LFSR advances to the next state. valid_o
is asserted the following cycle with data_o = new state.
"""
import os
# Default seed from rng_sync.v
SEED = 0xDEADBEEFCAFEBABEFEEDFACEDECAFBAD1234567887654321ABCDEF010FEDCBA9
# Number of test vectors to generate
NUM_VECTORS = 32
def lfsr_advance(state):
"""
Advance the 256-bit Galois LFSR by one step.
RTL tap positions (0-indexed): 255, 253, 252, 247, 0.
Algorithm (matching the RTL):
1. feedback = state[0] (LSB)
2. Shift right by 1: lfsr_next[i] = state[i+1] for i=0..254
3. lfsr_next[255] = feedback
4. XOR feedback into lfsr_next at tap-derived positions:
- bit 254 (tap 255 after shift: 255 -> 254)
- bit 252 (tap 253 after shift: 253 -> 252)
- bit 251 (tap 252 after shift: 252 -> 251)
- bit 246 (tap 247 after shift: 247 -> 246)
"""
feedback = state & 1
# Shift right by 1
next_state = state >> 1
# MSB gets feedback
if feedback:
next_state |= (1 << 255)
# XOR feedback at tap-derived positions
if feedback:
next_state ^= (1 << 254)
next_state ^= (1 << 252)
next_state ^= (1 << 251)
next_state ^= (1 << 246)
return next_state
def format_hex_256(val):
"""Format 256-bit integer as 64-character hex string."""
return f"{val:064x}"
def main():
vectors_dir = os.path.join(os.path.dirname(__file__), "vectors")
os.makedirs(vectors_dir, exist_ok=True)
output_path = os.path.join(vectors_dir, "rng_input.hex")
state = SEED
with open(output_path, "w") as f:
print(f"Seed: {format_hex_256(SEED)}")
print(f"Generating {NUM_VECTORS} expected LFSR states...")
print()
for i in range(NUM_VECTORS):
# Each valid_i advances the LFSR; the next state is what
# appears on data_o when valid_o is asserted.
state = lfsr_advance(state)
f.write(format_hex_256(state) + "\n")
print(f"[{i:3d}] step {i+1}: {format_hex_256(state)}")
print(f"\nWrote {NUM_VECTORS} vectors to {output_path}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,230 @@
// tb_rng_xsim.v - Standard Verilog testbench for rng_sync targeting Vivado xsim
//
// Reads expected LFSR output states from a hex file using $readmemh.
// Each line is a single 256-bit hex number = expected data_o after
// each valid_i pulse.
//
// Drives rng_sync, toggles valid_i for each vector, captures data_o,
// compares against expected value, and writes pass/fail results.
//
// Parameters:
// VECTOR_FILE - path to input hex file (default: "vectors/rng_input.hex")
// RESULT_FILE - path to output file (default: "vectors/rng_result.hex")
//
// Usage:
// xvlog -sv sync_rtl/rng/rng_sync.v
// xvlog -sv sync_rtl/rng/TB/tb_rng_xsim.v
// xelab tb_rng_xsim -s tb_rng_xsim
// xsim tb_rng_xsim -R
`timescale 1ns / 1ps
module tb_rng_xsim;
// ================================================================
// Parameters
// ================================================================
parameter VECTOR_FILE = "sync_rtl/rng/TB/vectors/rng_input.hex";
parameter RESULT_FILE = "sync_rtl/rng/TB/vectors/rng_result.hex";
parameter MAX_VECTORS = 256;
parameter TIMEOUT_CYCLES = 1000;
// ================================================================
// DUT signals
// ================================================================
reg clk;
reg rst_n;
reg valid_i;
wire ready_o;
wire [255:0] data_o;
wire valid_o;
reg ready_i;
// ================================================================
// DUT instantiation
// ================================================================
rng_sync u_dut (
.clk (clk),
.rst_n (rst_n),
.valid_i (valid_i),
.ready_o (ready_o),
.data_o (data_o),
.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)
// Each word is 256 bits = the expected data_o value
// ================================================================
reg [255: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
// Helper: write 256-bit value as 64 hex chars to file
task write_hex_256bit;
input integer fd;
input [255:0] val;
reg [3:0] nib;
integer j;
begin
for (j = 63; j >= 0; j = j - 1) begin
nib = val[(j*4)+:4];
$fwrite(fd, "%c", nibble_to_ascii(nib));
end
end
endtask
// ================================================================
// Main test sequence
// ================================================================
initial begin
// Count loaded vectors
vec_count = 0;
// Load vectors from hex file
$readmemh(VECTOR_FILE, vector_mem);
// Count non-x 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] === 256'hx || vector_mem[idx] === 256'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: <64 hex chars> = expected 256-bit LFSR state");
$finish;
end
$display("INFO: Loaded %0d expected LFSR states 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
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 [255:0] expected;
reg [255:0] captured;
expected = vector_mem[idx];
$display("INFO: Vector %0d", idx);
// Drive DUT: pulse valid_i for one cycle
valid_i <= 1'b1;
@(posedge clk);
valid_i <= 1'b0;
// Wait for valid_o
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);
$fwrite(result_fd, "FAIL: %0d - timeout\n", idx);
fail_count = fail_count + 1;
end else begin
// Capture output
captured = data_o;
// Compare with expected
if (captured === expected) begin
$display(" PASS: data_o matches expected");
$fwrite(result_fd, "PASS: %0d\n", idx);
pass_count = pass_count + 1;
end else begin
$display(" FAIL: data_o mismatch");
$display(" Expected: %h", expected);
$display(" Got: %h", captured);
$fwrite(result_fd, "FAIL: %0d - mismatch (expected mask)\n", idx);
fail_count = fail_count + 1;
end
end
// One extra cycle for valid_o handshake with ready_i
@(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);
$display("========================================");
$finish;
end
// ================================================================
// Timeout watchdog
// ================================================================
initial begin
#(TIMEOUT_CYCLES * 10 * 100); // TIMEOUT_CYCLES * 10ns per cycle * extra margin
$display("FATAL: Global simulation timeout reached");
$finish;
end
endmodule

View File

@@ -0,0 +1,32 @@
b716df77e57f5d5f7f76fd676f657dd6891a2b3c43b2a190d5e6f78087f6e5d4
5b8b6fbbf2bfaeafbfbb7eb3b7b2beeb448d159e21d950c86af37bc043fb72ea
2dc5b7ddf95fd757dfddbf59dbd95f75a2468acf10eca8643579bde021fdb975
cea2dbeefcafebabefeedfacedecafbad1234567887654321abcdef010fedcba
67516df77e57f5d5f7f76fd676f657dd6891a2b3c43b2a190d5e6f78087f6e5d
ebe8b6fbbf2bfaeafbfbb7eb3b7b2beeb448d159e21d950c86af37bc043fb72e
75f45b7ddf95fd757dfddbf59dbd95f75a2468acf10eca8643579bde021fdb97
e2ba2dbeefcafebabefeedfacedecafbad1234567887654321abcdef010fedcb
a91d16df77e57f5d5f7f76fd676f657dd6891a2b3c43b2a190d5e6f78087f6e5
8cce8b6fbbf2bfaeafbfbb7eb3b7b2beeb448d159e21d950c86af37bc043fb72
466745b7ddf95fd757dfddbf59dbd95f75a2468acf10eca8643579bde021fdb9
fb73a2dbeefcafebabefeedfacedecafbad1234567887654321abcdef010fedc
7db9d16df77e57f5d5f7f76fd676f657dd6891a2b3c43b2a190d5e6f78087f6e
3edce8b6fbbf2bfaeafbfbb7eb3b7b2beeb448d159e21d950c86af37bc043fb7
c72e745b7ddf95fd757dfddbf59dbd95f75a2468acf10eca8643579bde021fdb
bbd73a2dbeefcafebabefeedfacedecafbad1234567887654321abcdef010fed
85ab9d16df77e57f5d5f7f76fd676f657dd6891a2b3c43b2a190d5e6f78087f6
42d5ce8b6fbbf2bfaeafbfbb7eb3b7b2beeb448d159e21d950c86af37bc043fb
f92ae745b7ddf95fd757dfddbf59dbd95f75a2468acf10eca8643579bde021fd
a4d573a2dbeefcafebabefeedfacedecafbad1234567887654321abcdef010fe
526ab9d16df77e57f5d5f7f76fd676f657dd6891a2b3c43b2a190d5e6f78087f
f1755ce8b6fbbf2bfaeafbfbb7eb3b7b2beeb448d159e21d950c86af37bc043f
a0faae745b7ddf95fd757dfddbf59dbd95f75a2468acf10eca8643579bde021f
883d573a2dbeefcafebabefeedfacedecafbad1234567887654321abcdef010f
9c5eab9d16df77e57f5d5f7f76fd676f657dd6891a2b3c43b2a190d5e6f78087
966f55ce8b6fbbf2bfaeafbfbb7eb3b7b2beeb448d159e21d950c86af37bc043
9377aae745b7ddf95fd757dfddbf59dbd95f75a2468acf10eca8643579bde021
91fbd573a2dbeefcafebabefeedfacedecafbad1234567887654321abcdef010
48fdeab9d16df77e57f5d5f7f76fd676f657dd6891a2b3c43b2a190d5e6f7808
247ef55ce8b6fbbf2bfaeafbfbb7eb3b7b2beeb448d159e21d950c86af37bc04
123f7aae745b7ddf95fd757dfddbf59dbd95f75a2468acf10eca8643579bde02
091fbd573a2dbeefcafebabefeedfacedecafbad1234567887654321abcdef01

View File

@@ -0,0 +1,54 @@
# xsim_run.tcl - Vivado xsim compilation and simulation script for rng_sync
#
# Compiles rng_sync RTL plus the file-based vector testbench and runs simulation.
# Run from the project root: ~/Dev/mlkem/
#
# Prerequisites:
# source /opt/Xilinx/Vivado/2019.2/settings64.sh
#
# Usage:
# vivado -mode batch -source sync_rtl/rng/TB/xsim_run.tcl
#
# # Or step-by-step:
# xvlog -sv sync_rtl/rng/rng_sync.v
# xvlog -sv sync_rtl/rng/TB/tb_rng_xsim.v
# xelab tb_rng_xsim -s tb_rng_xsim
# xsim tb_rng_xsim -R
# ================================================================
# Configuration
# ================================================================
set SRC_DIR sync_rtl/rng
set TB_DIR sync_rtl/rng/TB
# ================================================================
# Step 1: Compile all source files (xvlog)
# ================================================================
puts "=== Compiling RTL sources ==="
# rng_sync (self-contained, no external dependencies)
xvlog -sv ${SRC_DIR}/rng_sync.v
# ================================================================
# Step 2: Compile testbench
# ================================================================
puts "=== Compiling testbench ==="
# File-based vector testbench
xvlog -sv ${TB_DIR}/tb_rng_xsim.v
# ================================================================
# Step 3: Elaborate snapshot (xelab)
# ================================================================
puts "=== Elaborating snapshot ==="
xelab tb_rng_xsim -s tb_rng_xsim
# ================================================================
# Step 4: Run simulation
# ================================================================
puts "=== Running rng_sync file-based vector test ==="
xsim tb_rng_xsim -R
puts ""
puts "=== rng_sync simulation complete ==="

View File

@@ -0,0 +1,270 @@
#!/usr/bin/env python3
"""gen_vectors.py - Test vector generator for sample_cbd_sync module.
Generates random seed+nonce+eta test vectors, computes expected CBD coefficients
using Python hashlib (stdlib only), and writes input hex and expected output hex files.
Algorithm (matching RTL sample_cbd_sync.v):
PRF(sigma, N) = SHAKE-256(sigma || N) → squeeze eta*64 bytes
For each of 256 coefficients:
eta=2: read 4 bits, coeff = (b0+b1) - (b2+b3)
eta=3: read 6 bits, coeff = (b0+b1+b2) - (b3+b4+b5)
Each coefficient in range [-eta, eta], stored as 12-bit signed.
Bit ordering (FIPS 202 / RTL match):
The RTL feeds seed_i[0] as the first bit into SHA3.
Python hashlib expects bytes[0] LSB as the first bit.
$readmemh stores hex with MSB first → seed_i[255:0] MSB-first.
So: Python input = reverse(bytes.fromhex(seed_hex)) + bytes([nonce]).
Usage:
python3 gen_vectors.py # Generate vectors
python3 gen_vectors.py --verify # Verify results against expected
"""
import hashlib
import os
import random
import sys
N_COEFFS = 256
Q = 3329 # not used for CBD, but for reference
def random_hex(bits):
"""Generate a random hex string (MSB-first) of the given bit length."""
val = random.getrandbits(bits)
num_nibbles = (bits + 3) // 4
return f"{val:0{num_nibbles}X}"
def bits_from_bytes(data):
"""Convert bytes to LSB-first bit string.
data[0] LSB = bits[0], data[0] bit 1 = bits[1], ...
"""
bits = []
for b in data:
for bit in range(8):
bits.append('1' if (b >> bit) & 1 else '0')
return ''.join(bits)
def shake256_prf(seed_hex, nonce, eta):
"""Compute SHAKE-256(seed || nonce) matching RTL bit ordering.
Args:
seed_hex: 64-char hex string (MSB-first, as stored by $readmemh).
nonce: 8-bit integer.
eta: 2 or 3.
Returns:
bytes: eta * 64 bytes of SHAKE-256 squeeze output.
"""
# seed_hex is MSB-first: seed_i[255:0] = 256'h<seed_hex>
# RTL feeds seed_i[0] first into SHA3.
# Python: reverse bytes so that byte[0] = seed_i[7:0],
# then append nonce byte.
seed_bytes = bytes.fromhex(seed_hex) # MSB-first bytes
shake_input = bytes(reversed(seed_bytes)) + bytes([nonce & 0xFF])
shake = hashlib.shake_256(shake_input)
return shake.digest(eta * 64)
def cbd_sample(prf_bytes, eta):
"""Apply Centered Binomial Distribution to PRF output bytes.
Args:
prf_bytes: bytes from SHAKE-256 squeeze (eta * 64 bytes).
eta: 2 or 3.
Returns:
list of 256 signed integers in range [-eta, eta].
"""
bits = bits_from_bytes(prf_bytes)
step = eta * 2 # 4 for eta=2, 6 for eta=3
half = eta # 2 for eta=2, 3 for eta=3
coeffs = []
for i in range(N_COEFFS):
pos_sum = sum(1 for j in range(half) if bits[step * i + j] == '1')
neg_sum = sum(1 for j in range(half) if bits[step * i + half + j] == '1')
coeffs.append(pos_sum - neg_sum)
return coeffs
def coeff_to_hex_12signed(val):
"""Convert signed 12-bit value to 3-char hex string (two's complement)."""
masked = val & 0xFFF # 12-bit unsigned
return f"{masked:03X}"
def generate_one(eta):
"""Generate a single test vector.
Returns:
dict with 'seed_hex', 'nonce', 'eta', 'coeffs' keys.
"""
seed_hex = random_hex(256)
nonce = random.randint(0, 255)
prf_bytes = shake256_prf(seed_hex, nonce, eta)
coeffs = cbd_sample(prf_bytes, eta)
return {
"seed_hex": seed_hex,
"nonce": nonce,
"eta": eta,
"coeffs": coeffs,
}
def write_input_hex(vectors, filepath):
"""Write input vectors as packed hex for $readmemh.
Each line: {eta[1:0], nonce[7:0], seed[255:0]} = 266 bits.
Written as 67 hex chars (268 bits, top 2 bits zero).
"""
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, "w") as f:
for v in vectors:
eta = v["eta"]
nonce = v["nonce"]
seed_hex = v["seed_hex"]
# Pack: {2'b00, eta[1:0], nonce[7:0], seed[255:0]} = 268 bits = 67 hex chars
# The first hex char carries eta in its lower 2 bits:
# hex value = 0x0 | eta → nibble = 0b00XX where XX = eta
# TB reads vec_eta = vector_mem[idx][265:264]
packed = f"{eta & 0x3:01X}{nonce:02X}{seed_hex}" # 1+2+64 = 67 chars
f.write(packed + "\n")
def write_expected_hex(vectors, filepath):
"""Write expected coefficients: one 12-bit hex value per line."""
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, "w") as f:
for i, v in enumerate(vectors):
f.write(f"# VECTOR_{i} eta={v['eta']} nonce=0x{v['nonce']:02X} seed={v['seed_hex']}\n")
for c in v["coeffs"]:
f.write(coeff_to_hex_12signed(c) + "\n")
def verify_results(result_file, vectors):
"""Verify RTL output against expected values.
Args:
result_file: Path to RTL result file (coeff hex per line).
vectors: List of vector dicts with 'coeffs'.
Returns:
bool: True if all vectors match.
"""
with open(result_file, "r") as f:
lines = [line.strip() for line in f
if line.strip() and not line.strip().startswith("#")]
# lines may contain trailing comments separated by " #"
cleaned = []
for line in lines:
comment_idx = line.find(" #")
if comment_idx >= 0:
line = line[:comment_idx].strip()
cleaned.append(line)
expected_all = []
for v in vectors:
for c in v["coeffs"]:
expected_all.append(coeff_to_hex_12signed(c))
if len(cleaned) != len(expected_all):
print(f" COUNT MISMATCH: got {len(cleaned)}, expected {len(expected_all)}")
return False
mismatches = 0
for i, (g, e) in enumerate(zip(cleaned, expected_all)):
if g.upper() != e.upper():
if mismatches < 10:
print(f" MISMATCH[{i}]: got={g.upper()}, expected={e.upper()}")
mismatches += 1
if mismatches > 0:
print(f" Total mismatches: {mismatches}")
return False
return True
def main():
vector_count = 4 # 2 vectors with eta=2, 2 with eta=3
base_dir = os.path.dirname(os.path.abspath(__file__))
vectors_dir = os.path.join(base_dir, "vectors")
input_file = os.path.join(vectors_dir, "sample_cbd_input.hex")
expected_file = os.path.join(vectors_dir, "sample_cbd_expected.hex")
result_file = os.path.join(vectors_dir, "sample_cbd_result.hex")
verify_mode = "--verify" in sys.argv
if verify_mode:
if not os.path.exists(result_file):
print(f"ERROR: Result file not found: {result_file}")
print(" Run simulation first to generate results.")
sys.exit(1)
if not os.path.exists(input_file):
print(f"ERROR: Input file not found: {input_file}")
sys.exit(1)
print(f"Verifying results from {result_file}...")
# Recompute expected from input file
with open(input_file, "r") as f:
input_lines = [l.strip() for l in f if l.strip()]
vectors = []
for line in input_lines:
if len(line) != 67:
print(f"WARNING: Skipping line with unexpected length {len(line)}: {line[:20]}...")
continue
# Parse: first char = eta_nibble, next 2 = nonce, next 64 = seed
eta_nibble = int(line[0], 16)
eta = eta_nibble >> 2 # bits[3:2]
nonce = int(line[1:3], 16)
seed_hex = line[3:]
prf_bytes = shake256_prf(seed_hex, nonce, eta)
coeffs = cbd_sample(prf_bytes, eta)
vectors.append({"seed_hex": seed_hex, "nonce": nonce, "eta": eta, "coeffs": coeffs})
ok = verify_results(result_file, vectors)
if ok:
print("ALL VECTORS PASSED")
else:
print("VERIFICATION FAILED")
sys.exit(1)
else:
# Generate mode
print(f"Generating {vector_count} test vectors...")
vectors = []
# eta=2 vectors
for i in range(vector_count // 2):
v = generate_one(eta=2)
vectors.append(v)
print(f" Vector {len(vectors)-1}: eta=2, nonce=0x{v['nonce']:02X}, "
f"seed={v['seed_hex'][:8]}..., coeffs[0]={v['coeffs'][0]}")
# eta=3 vectors
for i in range(vector_count // 2):
v = generate_one(eta=3)
vectors.append(v)
print(f" Vector {len(vectors)-1}: eta=3, nonce=0x{v['nonce']:02X}, "
f"seed={v['seed_hex'][:8]}..., coeffs[0]={v['coeffs'][0]}")
write_input_hex(vectors, input_file)
print(f"Wrote {len(vectors)} vectors to {input_file}")
write_expected_hex(vectors, expected_file)
print(f"Wrote expected coefficients to {expected_file}")
# Sanity checks
for v in vectors:
for c in v["coeffs"]:
assert -v["eta"] <= c <= v["eta"], \
f"Coefficient {c} out of range [-{v['eta']}, {v['eta']}]"
print("All sanity checks passed.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,257 @@
// tb_sample_cbd_xsim.v - Vivado xsim testbench for sample_cbd_sync
//
// Reads test vectors from a hex file using $readmemh.
// Each line is a packed hex word: {eta_i[1:0], nonce_i[7:0], seed_i[255:0]}
// - seed_i[255:0] : 64 hex chars (LSB = seed_i[0])
// - nonce_i[7:0] : 2 hex chars
// - eta_i[1:0] : 1 hex char (2 or 3)
// - Total: 67 hex chars (266 bits), zero-padded to fill 4-bit nibbles
//
// Drives sample_cbd_sync, waits for valid_o, collects 256 coefficients,
// and writes results to an output file.
//
// Usage:
// xvlog -sv sample_cbd_sync.v TB/tb_sample_cbd_xsim.v
// xelab tb_sample_cbd_xsim -s tb_sample_cbd_xsim
// xsim tb_sample_cbd_xsim -R
//
// Prerequisites:
// - Generate vectors: python3 TB/gen_vectors.py
// - Output file: vectors/sample_cbd_input.hex
`timescale 1ns / 1ps
module tb_sample_cbd_xsim;
// ================================================================
// Parameters
// ================================================================
parameter VECTOR_FILE = "sync_rtl/sample_cbd/TB/vectors/sample_cbd_input.hex";
parameter RESULT_FILE = "sync_rtl/sample_cbd/TB/vectors/sample_cbd_result.hex";
parameter EXPECT_FILE = "sync_rtl/sample_cbd/TB/vectors/sample_cbd_expected.hex";
parameter MAX_VECTORS = 32;
parameter TIMEOUT_CYCLES = 10000;
parameter N_COEFFS = 256;
// ================================================================
// DUT signals
// ================================================================
reg clk;
reg rst_n;
reg [255:0] seed_i;
reg [7:0] nonce_i;
reg [1:0] eta_i;
reg valid_i;
wire ready_o;
wire [11:0] coeff_o;
wire valid_o;
reg ready_i;
wire last_o;
// ================================================================
// DUT instantiation
// ================================================================
sample_cbd_sync u_dut (
.clk (clk),
.rst_n (rst_n),
.seed_i (seed_i),
.nonce_i (nonce_i),
.eta_i (eta_i),
.valid_i (valid_i),
.ready_o (ready_o),
.coeff_o (coeff_o),
.valid_o (valid_o),
.ready_i (ready_i),
.last_o (last_o)
);
// ================================================================
// Clock generation: 100 MHz (10 ns period)
// ================================================================
initial clk = 1'b0;
always #5 clk = ~clk;
// ================================================================
// Vector memory (loaded by $readmemh)
// 266 bits per word: {eta_i[1:0], nonce_i[7:0], seed_i[255:0]}
// Hex: 67 chars = 268 bits, top 2 bits zero-padded
// ================================================================
reg [267:0] vector_mem [0:MAX_VECTORS-1];
integer vec_count;
integer idx;
integer cycle_count;
integer result_fd;
integer coeff_idx;
// Test result tracking
integer pass_count;
integer fail_count;
// ================================================================
// Hex-to-ASCII conversion helper (for output file)
// ================================================================
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
vec_count = 0;
// Load vectors from hex file
$readmemh(VECTOR_FILE, vector_mem);
// Count non-zero/X 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] === {268{1'bx}} || vector_mem[idx] === {268{1'bz}}))
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: <67 hex chars> = {eta, nonce, seed}");
$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
seed_i <= 256'd0;
nonce_i <= 8'd0;
eta_i <= 2'd2;
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 [255:0] vec_seed;
reg [7:0] vec_nonce;
reg [1:0] vec_eta;
// Extract fields from packed vector_mem
// vector_mem[267:0] = {2'b0, eta_i, nonce_i, seed_i}
vec_seed = vector_mem[idx][255:0];
vec_nonce = vector_mem[idx][263:256];
vec_eta = vector_mem[idx][265:264];
$display("INFO: Vector %0d - eta=%0d, nonce=0x%02h, seed[0:31]=%0h...",
idx, vec_eta, vec_nonce, vec_seed[31:0]);
// Drive DUT with input
seed_i <= vec_seed;
nonce_i <= vec_nonce;
eta_i <= vec_eta;
valid_i <= 1'b1;
@(posedge clk);
valid_i <= 1'b0;
// Wait for valid_o, then collect all 256 coefficients
// The DUT uses ready/valid handshake; we set ready_i=1
coeff_idx = 0;
cycle_count = 0;
// Write vector header to result file
$fwrite(result_fd, "# VECTOR_%0d eta=%0d nonce=0x%02h seed=0x%064h\n",
idx, vec_eta, vec_nonce, vec_seed);
while (coeff_idx < N_COEFFS && cycle_count < TIMEOUT_CYCLES) begin
@(posedge clk);
cycle_count = cycle_count + 1;
if (valid_o) begin
// Capture coefficient
begin
integer k;
reg [3:0] nib;
for (k = 2; k >= 0; k = k - 1) begin
nib = coeff_o[(k*4)+:4];
$fwrite(result_fd, "%c", nibble_to_ascii(nib));
end
end
coeff_idx = coeff_idx + 1;
if (last_o)
$fwrite(result_fd, " # last at coeff_idx=%0d", coeff_idx);
$fwrite(result_fd, "\n");
end
end
if (cycle_count >= TIMEOUT_CYCLES) begin
$display("ERROR: Timeout on vector %0d (got %0d/%0d coefficients)",
idx, coeff_idx, N_COEFFS);
fail_count = fail_count + 1;
end else if (coeff_idx != N_COEFFS) begin
$display("ERROR: Vector %0d incomplete (got %0d/%0d coefficients)",
idx, coeff_idx, N_COEFFS);
fail_count = fail_count + 1;
end else begin
$display("INFO: Vector %0d PASSED (%0d coefficients in %0d cycles)",
idx, coeff_idx, cycle_count);
pass_count = pass_count + 1;
end
// Wait for DUT to return to IDLE before next vector
@(posedge clk);
end // inner begin block
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);
$display("========================================");
$finish;
end
// ================================================================
// Timeout watchdog (global)
// ================================================================
initial begin
#(TIMEOUT_CYCLES * 10 * 100); // TIMEOUT_CYCLES * 10ns * extra margin
$display("FATAL: Global simulation timeout reached");
$finish;
end
endmodule

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
27F692D88854B85E2DF61697261CCC8931119F593F90A499EA6AD3229F6301D280E
24A7D2BAA804507EA9069043D6D395FBF42E1BB02BE0A1894B98E8F5F734FE5C22A
3C7E07D2297A5E9B056F40AA0AF595CB3121E03CCBB86CC25C012EC008D07DC8481
3A0A67134FA9332AD1430AF5003D24E856EB38A0219B48B68A24C5C85E73AF79026

View File

@@ -0,0 +1,56 @@
# xsim_run.tcl - Vivado xsim compilation and simulation for sample_cbd_sync
#
# Compiles sample_cbd_sync RTL + SHA3 dependencies + testbench and runs simulation.
# Run from the project root: ~/Dev/mlkem/
#
# Prerequisites:
# source /opt/Xilinx/Vivado/2019.2/settings64.sh
#
# Usage:
# xsim -runall sync_rtl/sample_cbd/TB/xsim_run.tcl
#
# # Or step-by-step:
# vivado -mode batch -source sync_rtl/sample_cbd/TB/xsim_run.tcl
# ================================================================
# Configuration
# ================================================================
set SRC_DIR sync_rtl/sample_cbd
set SHA3_DIR sync_rtl/sha3
set COMMON_DIR sync_rtl/common
set TB_DIR sync_rtl/sample_cbd/TB
# ================================================================
# Step 1: Compile all source files (xvlog)
# ================================================================
puts "=== Compiling RTL sources ==="
# Keccak round (combinational, used by keccak_core)
xvlog -sv ${SHA3_DIR}/keccak_round.v
# Keccak core (24-round sequential core, used by sample_cbd_sync)
xvlog -sv ${SHA3_DIR}/keccak_core.v
# sample_cbd_sync (DUT)
xvlog -sv ${SRC_DIR}/sample_cbd_sync.v
# ================================================================
# Step 2: Compile testbench
# ================================================================
puts "=== Compiling testbench ==="
xvlog -sv ${TB_DIR}/tb_sample_cbd_xsim.v
# ================================================================
# Step 3: Elaborate snapshot (xelab)
# ================================================================
puts "=== Elaborating snapshot ==="
xelab tb_sample_cbd_xsim -s tb_sample_cbd_xsim
# ================================================================
# Step 4: Run simulation
# ================================================================
puts "=== Running simulation ==="
xsim tb_sample_cbd_xsim -R
puts ""
puts "=== sample_cbd simulation complete ==="

View File

@@ -0,0 +1,503 @@
#!/usr/bin/env python3
"""gen_vectors.py - Test vector generator for sample_ntt_sync module.
Generates random rho+k+i+j test vectors, computes expected coefficients using
Keccak-p[1600, 24] matching the RTL's per-permutation squeeze pattern.
The RTL sample_ntt_sync absorbs rho || j || i, then repeatedly permutes
the keccak state, extracting d1,d2 from bits [23:0] of each permuted state
with rejection sampling (d < Q=3329).
Algorithm (matching RTL exactly):
1. Build absorb state: pad10*1 padding for SHAKE-128 (rate=1344) on msg(272b)
2. state = keccak_p(absorb_state)
3. Extract d1 = (state[11:8] << 8) | state[7:0]
d2 = (state[23:16] << 4) | state[15:12]
4. If d1 < Q: output d1 (12-bit unsigned)
5. If d2 < Q: output d2
6. state = keccak_p(state)
7. Repeat 3-6 until 256 coefficients collected
Bit ordering (FIPS 202 / RTL match):
The RTL feeds rho_i[0] as the first message bit into SHA3.
$readmemh stores hex MSB-first → rho_i[255:0] in MSB-first order.
Python: input bytes = reversed(bytes.fromhex(rho_hex)) + bytes([j, i])
Usage:
python3 gen_vectors.py # Generate vectors
python3 gen_vectors.py --verify # Verify results against expected
python3 gen_vectors.py --selftest # Run Keccak-p self-test
"""
import os
import random
import sys
Q = 3329
N_COEFFS = 256
# ======================================================================
# Keccak-p[1600, 24] implementation (stdlib only, matches RTL exactly)
# ======================================================================
# Rotation offsets for rho step: RHO[x][y]
RHO = [
[ 0, 36, 3, 41, 18],
[ 1, 44, 10, 45, 2],
[62, 6, 43, 15, 61],
[28, 55, 25, 21, 56],
[27, 20, 39, 8, 14],
]
# Round constants for iota step
RC = [
0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000,
0x000000000000808B, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009,
0x000000000000008A, 0x0000000000000088, 0x0000000080008009, 0x000000008000000A,
0x000000008000808B, 0x800000000000008B, 0x8000000000008089, 0x8000000000008003,
0x8000000000008002, 0x8000000000000080, 0x000000000000800A, 0x800000008000000A,
0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008,
]
def ROTL64(val, n):
"""Rotate 64-bit value left by n bits."""
n = n & 63
return ((val << n) | (val >> (64 - n))) & 0xFFFFFFFFFFFFFFFF
def keccak_round(lanes, rnd_idx):
"""Single Keccak-f round: theta, rho, pi, chi, iota.
lanes: list of 25 64-bit integers, indexed as lane_idx = 5*y + x.
Returns new list of 25 lanes.
"""
# === Theta ===
C = [0] * 5 # C[x] = A[x,0] ^ A[x,1] ^ A[x,2] ^ A[x,3] ^ A[x,4]
for x in range(5):
for y in range(5):
C[x] ^= lanes[5 * y + x]
# D[x] = C[x-1] ^ ROTL(C[x+1], 1)
D = [0] * 5
for x in range(5):
D[x] = C[(x - 1) % 5] ^ ROTL64(C[(x + 1) % 5], 1)
new_lanes = [0] * 25
for y in range(5):
for x in range(5):
idx = 5 * y + x
new_lanes[idx] = lanes[idx] ^ D[x]
# === Rho + Pi ===
B = [0] * 25
for x in range(5):
for y in range(5):
src_idx = 5 * y + x
dst_row = (2 * x + 3 * y) % 5
dst_col = y
dst_idx = 5 * dst_row + dst_col
B[dst_idx] = ROTL64(new_lanes[src_idx], RHO[x][y])
# === Chi ===
result = [0] * 25
for y in range(5):
for x in range(5):
idx = 5 * y + x
idx1 = 5 * y + ((x + 1) % 5)
idx2 = 5 * y + ((x + 2) % 5)
result[idx] = B[idx] ^ ((~B[idx1] & 0xFFFFFFFFFFFFFFFF) & B[idx2])
# === Iota ===
result[0] ^= RC[rnd_idx]
return result
def keccak_p(state_int):
"""Keccak-p[1600, 24] permutation of a 1600-bit integer.
Returns 1600-bit integer.
"""
# Convert 1600-bit integer to 25 lanes of 64 bits
lanes = [0] * 25
for y in range(5):
for x in range(5):
idx = 5 * y + x
lanes[idx] = (state_int >> (64 * idx)) & 0xFFFFFFFFFFFFFFFF
for rnd in range(24):
lanes = keccak_round(lanes, rnd)
# Convert back to 1600-bit integer
result = 0
for y in range(5):
for x in range(5):
idx = 5 * y + x
result |= lanes[idx] << (64 * idx)
return result & ((1 << 1600) - 1)
def keccak_p_selftest():
"""Verify Keccak-p against known test vectors.
Validates by computing SHA3-256('') and SHA3-256('abc')
against Python's hashlib reference.
"""
import hashlib
def sha3_256_via_keccak(data):
"""Compute SHA3-256 using our keccak_p, compare with hashlib.
SHA3-256(M) = Keccak[c=512](M || 01, 256)
Rate = 1088 bits, capacity = 512 bits.
"""
# Convert message to bits, LSB-first per byte
msg_bits = ''.join(format(b, '08b')[::-1] for b in data)
# Append SHA-3 suffix '01' (bit 0, then bit 1)
msg_bits += '01'
# Apply pad10*1: '1' + required zeros + '1'
pad_len = (1088 - (len(msg_bits) % 1088)) % 1088
if pad_len == 0:
pad_len = 1088
# pad_len >= 2 always for pad10*1
msg_bits += '1' + '0' * (pad_len - 2) + '1'
# Build state by XOR-ing padded message into rate portion
state = 0
for i, bit in enumerate(msg_bits):
if bit == '1':
state ^= (1 << i)
# Absorb (single block for short messages)
state = keccak_p(state)
# Squeeze 256 bits from rate portion [255:0]
output_bytes = bytes(
(state >> (8 * i)) & 0xFF for i in range(32)
)
return output_bytes.hex()
# Test SHA3-256("") against hashlib
expected_empty = hashlib.sha3_256(b"").hexdigest()
got_empty = sha3_256_via_keccak(b"")
assert got_empty == expected_empty, \
f"SHA3-256('') mismatch:\n got: {got_empty}\n expected: {expected_empty}"
# Test SHA3-256("abc") against hashlib
expected_abc = hashlib.sha3_256(b"abc").hexdigest()
got_abc = sha3_256_via_keccak(b"abc")
assert got_abc == expected_abc, \
f"SHA3-256('abc') mismatch:\n got: {got_abc}\n expected: {expected_abc}"
# Also test that our keccak_p is not identity
assert keccak_p(0) != 0, "keccak_p(0) must not equal 0"
print("Keccak-p self-test PASSED (SHA3-256 verified against hashlib)")
return True
# ======================================================================
# Message construction (matching RTL pad10*1 for SHAKE-128)
# ======================================================================
def build_absorb_state(rho_hex, j, i):
"""Build the 1600-bit absorb state matching RTL sample_ntt_sync.
RTL absorb_state = {capacity(256b), pad10*1, suffix(1111), msg(272b)}
Message order (bit 0 = state[0]):
rho_i[0], ..., rho_i[255], j[0], ..., j[7], i[0], ..., i[7]
Args:
rho_hex: 64-char hex string (MSB-first).
j: 2-bit index (j_idx).
i: 2-bit index (i_idx).
Returns:
1600-bit integer representing the padded absorb state.
"""
# Build message bytes
rho_bytes = bytes.fromhex(rho_hex) # MSB-first → byte 0 = rho_i[255:248]
rho_rev = rho_bytes[::-1] # Reversed: byte 0 = rho_i[7:0]
msg_bytes = rho_rev + bytes([j & 0xFF, i & 0xFF])
# msg_bytes[0] = rho_i[7:0], msg_bytes[1] = rho_i[15:8], ...
# msg_bytes[32] = j, msg_bytes[33] = i
# Build 1600-bit state
# state[7:0] = msg_bytes[0]
# state[15:8] = msg_bytes[1]
# ...
# state[275:272] = 4'b1111 (SHAKE suffix)
# state[276] = 1'b1 (pad10*1 first 1)
# state[1342:277] = 0
# state[1343] = 1'b1 (pad10*1 final 1)
# state[1599:1344] = 0 (capacity)
state = 0
# Message bytes at bits [0:271]
for idx, b in enumerate(msg_bytes):
state |= (b & 0xFF) << (8 * idx)
# SHAKE suffix (1111) at bits [272:275]
state |= 0xF << 272
# pad10*1: 1 at bit 276
state |= 1 << 276
# pad10*1: 1 at bit 1343
state |= 1 << 1343
return state & ((1 << 1600) - 1)
def extract_d1_d2(state):
"""Extract d1, d2 from state[23:0] matching RTL.
Returns (d1, d2) as 12-bit unsigned integers [0, 4095].
"""
c0 = (state >> 0) & 0xFF
c1 = (state >> 8) & 0xFF
c2 = (state >> 16) & 0xFF
d1 = ((c1 & 0xF) << 8) | c0
d2 = (c2 << 4) | (c1 >> 4)
return d1, d2
def sample_ntt_compute(rho_hex, k, i, j):
"""Compute expected coefficients matching RTL sample_ntt_sync.
The RTL absorbs rho || j || i, then repeatedly permutes,
extracting d1,d2 from each permuted state with rejection sampling.
Args:
rho_hex: 64-char hex string.
k: 3-bit k value (unused in computation, passed through).
i: 2-bit i_idx.
j: 2-bit j_idx.
Returns:
list of 256 integers in [0, Q-1].
"""
state = build_absorb_state(rho_hex, j, i)
state = keccak_p(state)
coeffs = []
while len(coeffs) < N_COEFFS:
d1, d2 = extract_d1_d2(state)
if d1 < Q:
coeffs.append(d1)
if len(coeffs) == N_COEFFS:
break
if d2 < Q:
coeffs.append(d2)
if len(coeffs) == N_COEFFS:
break
# Re-permute for next squeeze
state = keccak_p(state)
return coeffs
# ======================================================================
# Helper functions
# ======================================================================
def random_hex(bits):
"""Generate a random hex string (MSB-first) of the given bit length."""
val = random.getrandbits(bits)
num_nibbles = (bits + 3) // 4
return f"{val:0{num_nibbles}X}"
def coeff_to_hex(val):
"""Convert 12-bit unsigned coefficient to 3-char hex string."""
return f"{val & 0xFFF:03X}"
# ======================================================================
# Vector generation and file I/O
# ======================================================================
def generate_one(k, i, j):
"""Generate a single test vector.
Returns dict with 'rho_hex', 'k', 'i', 'j', 'coeffs'.
"""
rho_hex = random_hex(256)
coeffs = sample_ntt_compute(rho_hex, k, i, j)
return {
"rho_hex": rho_hex,
"k": k,
"i": i,
"j": j,
"coeffs": coeffs,
}
def write_input_hex(vectors, filepath):
"""Write input vectors as packed hex for $readmemh.
Each line: {1'b0, j_idx[1:0], i_idx[1:0], k_i[2:0], rho_i[255:0]}
= 264 bits = 66 hex chars.
"""
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, "w") as f:
for v in vectors:
rho_hex = v["rho_hex"]
k = v["k"] & 0x7
i = v["i"] & 0x3
j = v["j"] & 0x3
# Pack: {1'b0, j[1:0], i[1:0], k[2:0], rho[255:0]}
header = (j << 5) | (i << 3) | k
# header occupies bits [262:256] (when zero-padded properly)
# rho occupies bits [255:0]
# Total: 256 + 7 = 263 bits → 66 hex chars (264 bits, top bit = 0)
# header_hex: 2 chars
header_hex = f"{header:02X}"
packed = header_hex + rho_hex # 2 + 64 = 66 chars
f.write(packed + "\n")
def write_expected_hex(vectors, filepath):
"""Write expected coefficients: one 12-bit hex value per line."""
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, "w") as f:
for idx, v in enumerate(vectors):
f.write(f"# VECTOR_{idx} k={v['k']} i={v['i']} j={v['j']} "
f"rho={v['rho_hex']}\n")
for c in v["coeffs"]:
f.write(coeff_to_hex(c) + "\n")
def verify_results(result_file, vectors):
"""Verify RTL output against expected values."""
with open(result_file, "r") as f:
lines = [line.strip() for line in f
if line.strip() and not line.strip().startswith("#")]
# Remove trailing comments
cleaned = []
for line in lines:
comment_idx = line.find(" #")
if comment_idx >= 0:
line = line[:comment_idx].strip()
cleaned.append(line)
expected_all = []
for v in vectors:
for c in v["coeffs"]:
expected_all.append(coeff_to_hex(c))
if len(cleaned) != len(expected_all):
print(f" COUNT MISMATCH: got {len(cleaned)}, expected {len(expected_all)}")
return False
mismatches = 0
for idx, (g, e) in enumerate(zip(cleaned, expected_all)):
if g.upper() != e.upper():
if mismatches < 10:
print(f" MISMATCH[{idx}]: got={g.upper()}, expected={e.upper()}")
mismatches += 1
if mismatches > 0:
print(f" Total mismatches: {mismatches}")
return False
return True
# ======================================================================
# Main
# ======================================================================
def main():
base_dir = os.path.dirname(os.path.abspath(__file__))
vectors_dir = os.path.join(base_dir, "vectors")
input_file = os.path.join(vectors_dir, "sample_ntt_input.hex")
expected_file = os.path.join(vectors_dir, "sample_ntt_expected.hex")
result_file = os.path.join(vectors_dir, "sample_ntt_result.hex")
if "--selftest" in sys.argv:
keccak_p_selftest()
print("Running quick smoke test on sample_ntt computation...")
rho = "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"
coeffs = sample_ntt_compute(rho, k=4, i=0, j=0)
print(f" rho={rho[:16]}...: {len(coeffs)} coefficients")
print(f" first 4: {coeffs[:4]}")
assert len(coeffs) == 256, f"Expected 256 coefficients, got {len(coeffs)}"
assert all(0 <= c < Q for c in coeffs), "Coefficient out of range"
print("Smoke test PASSED")
return
verify_mode = "--verify" in sys.argv
if verify_mode:
if not os.path.exists(result_file):
print(f"ERROR: Result file not found: {result_file}")
sys.exit(1)
if not os.path.exists(input_file):
print(f"ERROR: Input file not found: {input_file}")
sys.exit(1)
print(f"Verifying results from {result_file}...")
# Recompute expected from input file
with open(input_file, "r") as f:
input_lines = [l.strip() for l in f if l.strip()]
vectors = []
for line in input_lines:
if len(line) != 66:
print(f"WARNING: Skipping line with unexpected length {len(line)}: {line[:20]}...")
continue
header_hex = line[:2]
rho_hex = line[2:]
header = int(header_hex, 16)
j = (header >> 5) & 0x3
i = (header >> 3) & 0x3
k = header & 0x7
coeffs = sample_ntt_compute(rho_hex, k, i, j)
vectors.append({"rho_hex": rho_hex, "k": k, "i": i, "j": j, "coeffs": coeffs})
ok = verify_results(result_file, vectors)
if ok:
print("ALL VECTORS PASSED")
else:
print("VERIFICATION FAILED")
sys.exit(1)
else:
# Generate mode
vector_count = 4
print(f"Generating {vector_count} test vectors...")
print(f"Running Keccak-p self-test first...")
keccak_p_selftest()
vectors = []
for idx in range(vector_count):
k = random.choice([2, 3, 4])
i = random.randint(0, 3)
j = random.randint(0, 3)
v = generate_one(k, i, j)
vectors.append(v)
print(f" Vector {idx}: k={k}, i={i}, j={j}, "
f"rho={v['rho_hex'][:8]}..., coeffs[0]={v['coeffs'][0]}")
write_input_hex(vectors, input_file)
print(f"Wrote {len(vectors)} vectors to {input_file}")
write_expected_hex(vectors, expected_file)
print(f"Wrote expected coefficients to {expected_file}")
# Sanity checks
for v in vectors:
for c in v["coeffs"]:
assert 0 <= c < Q, f"Coefficient {c} out of range [0, {Q-1}]"
assert len(v["coeffs"]) == N_COEFFS
print("All sanity checks passed.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,265 @@
// tb_sample_ntt_xsim.v - Vivado xsim testbench for sample_ntt_sync
//
// Reads test vectors from a hex file using $readmemh.
// Each line is a packed hex word: {j_idx[1:0], i_idx[1:0], k_i[2:0], rho_i[255:0]}
// - rho_i[255:0] : 64 hex chars (LSB = rho_i[0])
// - k_i[2:0] : bits [258:256]
// - i_idx[1:0] : bits [260:259]
// - j_idx[1:0] : bits [262:261]
// - Total: 264 bits = 66 hex chars
//
// Drives sample_ntt_sync, waits for valid_o, collects 256 coefficients,
// and writes results to an output file.
//
// Usage:
// xvlog -sv sample_ntt_sync.v TB/tb_sample_ntt_xsim.v
// xelab tb_sample_ntt_xsim -s tb_sample_ntt_xsim
// xsim tb_sample_ntt_xsim -R
//
// Prerequisites:
// - Generate vectors: python3 TB/gen_vectors.py
// - Output file: vectors/sample_ntt_input.hex
`timescale 1ns / 1ps
module tb_sample_ntt_xsim;
// ================================================================
// Parameters
// ================================================================
parameter VECTOR_FILE = "sync_rtl/sample_ntt/TB/vectors/sample_ntt_input.hex";
parameter RESULT_FILE = "sync_rtl/sample_ntt/TB/vectors/sample_ntt_result.hex";
parameter EXPECT_FILE = "sync_rtl/sample_ntt/TB/vectors/sample_ntt_expected.hex";
parameter MAX_VECTORS = 32;
parameter TIMEOUT_CYCLES = 50000; // rejection sampling needs many cycles
parameter N_COEFFS = 256;
// ================================================================
// DUT signals
// ================================================================
reg clk;
reg rst_n;
reg [255:0] rho_i;
reg [2:0] k_i;
reg [1:0] i_idx;
reg [1:0] j_idx;
reg valid_i;
wire ready_o;
wire [11:0] coeff_o;
wire valid_o;
reg ready_i;
wire last_o;
// ================================================================
// DUT instantiation
// ================================================================
sample_ntt_sync u_dut (
.clk (clk),
.rst_n (rst_n),
.rho_i (rho_i),
.k_i (k_i),
.i_idx (i_idx),
.j_idx (j_idx),
.valid_i (valid_i),
.ready_o (ready_o),
.coeff_o (coeff_o),
.valid_o (valid_o),
.ready_i (ready_i),
.last_o (last_o)
);
// ================================================================
// Clock generation: 100 MHz (10 ns period)
// ================================================================
initial clk = 1'b0;
always #5 clk = ~clk;
// ================================================================
// Vector memory (loaded by $readmemh)
// 264 bits per word: {1'b0, j_idx[1:0], i_idx[1:0], k_i[2:0], rho_i[255:0]}
// Hex: 66 chars
// ================================================================
reg [263:0] vector_mem [0:MAX_VECTORS-1];
integer vec_count;
integer idx;
integer cycle_count;
integer result_fd;
integer coeff_idx;
// Test result tracking
integer pass_count;
integer fail_count;
// ================================================================
// Hex-to-ASCII conversion helper (for output file)
// ================================================================
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
vec_count = 0;
// Load vectors from hex file
$readmemh(VECTOR_FILE, vector_mem);
// Count non-zero/X 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] === {264{1'bx}} || vector_mem[idx] === {264{1'bz}}))
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: <66 hex chars> = {j, i, k, rho}");
$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
rho_i <= 256'd0;
k_i <= 3'd0;
i_idx <= 2'd0;
j_idx <= 2'd0;
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 [255:0] vec_rho;
reg [2:0] vec_k;
reg [1:0] vec_i;
reg [1:0] vec_j;
// Extract fields from packed vector_mem
// vector_mem[263:0] = {1'b0, j_idx, i_idx, k_i, rho_i}
vec_rho = vector_mem[idx][255:0];
vec_k = vector_mem[idx][258:256];
vec_i = vector_mem[idx][260:259];
vec_j = vector_mem[idx][262:261];
$display("INFO: Vector %0d - k=%0d, i=%0d, j=%0d, rho[0:31]=%0h...",
idx, vec_k, vec_i, vec_j, vec_rho[31:0]);
// Drive DUT with input
rho_i <= vec_rho;
k_i <= vec_k;
i_idx <= vec_i;
j_idx <= vec_j;
valid_i <= 1'b1;
@(posedge clk);
valid_i <= 1'b0;
// Wait for valid_o, then collect all 256 coefficients
// The DUT uses ready/valid handshake; we set ready_i=1
coeff_idx = 0;
cycle_count = 0;
// Write vector header to result file
$fwrite(result_fd, "# VECTOR_%0d k=%0d i=%0d j=%0d rho=0x%064h\n",
idx, vec_k, vec_i, vec_j, vec_rho);
while (coeff_idx < N_COEFFS && cycle_count < TIMEOUT_CYCLES) begin
@(posedge clk);
cycle_count = cycle_count + 1;
if (valid_o) begin
// Capture coefficient
begin
integer k;
reg [3:0] nib;
for (k = 2; k >= 0; k = k - 1) begin
nib = coeff_o[(k*4)+:4];
$fwrite(result_fd, "%c", nibble_to_ascii(nib));
end
end
coeff_idx = coeff_idx + 1;
if (last_o)
$fwrite(result_fd, " # last at coeff_idx=%0d", coeff_idx);
$fwrite(result_fd, "\n");
end
end
if (cycle_count >= TIMEOUT_CYCLES) begin
$display("ERROR: Timeout on vector %0d (got %0d/%0d coefficients)",
idx, coeff_idx, N_COEFFS);
fail_count = fail_count + 1;
end else if (coeff_idx != N_COEFFS) begin
$display("ERROR: Vector %0d incomplete (got %0d/%0d coefficients)",
idx, coeff_idx, N_COEFFS);
fail_count = fail_count + 1;
end else begin
$display("INFO: Vector %0d PASSED (%0d coefficients in %0d cycles)",
idx, coeff_idx, cycle_count);
pass_count = pass_count + 1;
end
// Wait for DUT to return to IDLE before next vector
// The DUT enters ST_DONE then ST_IDLE automatically
@(posedge clk);
end // inner begin block
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);
$display("========================================");
$finish;
end
// ================================================================
// Timeout watchdog (global)
// ================================================================
initial begin
#(TIMEOUT_CYCLES * 10 * 100); // TIMEOUT_CYCLES * 10ns * extra margin
$display("FATAL: Global simulation timeout reached");
$finish;
end
endmodule

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,4 @@
0BF6E5D64607BA30905E9F3976195E2B9A0C82BE0DB27480902BA8CA44BAAF0F50
14EF02248243A8FDAC343C3B298DEA5CEAA2F520D79153B4D3F7009ED90D0E63FA
24A606EFDC962E55F0BC95332332D82BCC5ACB04497FA99EF06A1D71BF11AF8297
426F23F87730D6FE0FE18F27180ECA3DE60D0CA77846F40F8D5FB024030E0CEC0F

View File

@@ -0,0 +1,56 @@
# xsim_run.tcl - Vivado xsim compilation and simulation for sample_ntt_sync
#
# Compiles sample_ntt_sync RTL + SHA3 dependencies + testbench and runs simulation.
# Run from the project root: ~/Dev/mlkem/
#
# Prerequisites:
# source /opt/Xilinx/Vivado/2019.2/settings64.sh
#
# Usage:
# xsim -runall sync_rtl/sample_ntt/TB/xsim_run.tcl
#
# # Or step-by-step:
# vivado -mode batch -source sync_rtl/sample_ntt/TB/xsim_run.tcl
# ================================================================
# Configuration
# ================================================================
set SRC_DIR sync_rtl/sample_ntt
set SHA3_DIR sync_rtl/sha3
set COMMON_DIR sync_rtl/common
set TB_DIR sync_rtl/sample_ntt/TB
# ================================================================
# Step 1: Compile all source files (xvlog)
# ================================================================
puts "=== Compiling RTL sources ==="
# Keccak round (combinational, used by keccak_core)
xvlog -sv -include_dirs . ${SHA3_DIR}/keccak_round.v
# Keccak core (24-round sequential core, used by sample_ntt_sync)
xvlog -sv -include_dirs . ${SHA3_DIR}/keccak_core.v
# sample_ntt_sync (DUT) — uses `include "sync_rtl/common/defines.vh"
xvlog -sv -include_dirs . ${SRC_DIR}/sample_ntt_sync.v
# ================================================================
# Step 2: Compile testbench
# ================================================================
puts "=== Compiling testbench ==="
xvlog -sv ${TB_DIR}/tb_sample_ntt_xsim.v
# ================================================================
# Step 3: Elaborate snapshot (xelab)
# ================================================================
puts "=== Elaborating snapshot ==="
xelab tb_sample_ntt_xsim -s tb_sample_ntt_xsim
# ================================================================
# Step 4: Run simulation
# ================================================================
puts "=== Running simulation ==="
xsim tb_sample_ntt_xsim -R
puts ""
puts "=== sample_ntt simulation complete ==="

View File

@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""gen_vectors.py — Generate SHA3-512 test vectors for tb_sha3_chain_xsim.v
Computes expected rho and sigma values for sha3_chain_top (ML-KEM G function).
The RTL computes: SHA3-512(d_in || 0x02)
- d_in: 256-bit input (32 bytes, big-endian)
- 0x02: single byte appended (k=2 parameter for ML-KEM)
- G(d || 0x02) uses SHA3-512 mode (rate=576, suffix=01)
- rho = hash[255:0] (first 256 bits of hash)
- sigma = hash[511:256] (next 256 bits of hash)
Output:
vectors/sha3_chain_input.hex — 256-bit d_in values (64 hex chars per line)
Usage:
python3 gen_vectors.py
"""
import hashlib
import os
VECTORS_DIR = os.path.join(os.path.dirname(__file__), "vectors")
OUTPUT_FILE = os.path.join(VECTORS_DIR, "sha3_chain_input.hex")
# Test vectors: (label, d_in value)
TEST_VECTORS = [
("all-zeros", 0x0000000000000000000000000000000000000000000000000000000000000000),
("all-ones", 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF),
("lsb-one", 0x0000000000000000000000000000000000000000000000000000000000000001),
("msb-one", 0x8000000000000000000000000000000000000000000000000000000000000000),
("pattern-aa", 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA),
("pattern-55", 0x5555555555555555555555555555555555555555555555555555555555555555),
("random-1", 0x1A2B3C4D5E6F708192A3B4C5D6E7F8091A2B3C4D5E6F708192A3B4C5D6E7F80),
("random-2", 0xF0E1D2C3B4A5968778695A4B3C2D1E0FF0E1D2C3B4A5968778695A4B3C2D1E0F),
]
def sha3_512_g(d_in: int) -> tuple:
"""Compute rho, sigma = G(d_in || 0x02) using SHA3-512.
Returns (rho, sigma) as 256-bit integers.
"""
# d_in as 32 bytes, big-endian (MSB first)
d_bytes = d_in.to_bytes(32, "big")
# Append k=2 as single byte 0x02
message = d_bytes + b"\x02"
# SHA3-512 hash → 64 bytes
h = hashlib.sha3_512(message).digest()
# rho = first 256 bits, sigma = next 256 bits
rho = int.from_bytes(h[0:32], "big")
sigma = int.from_bytes(h[32:64], "big")
return rho, sigma
def main():
os.makedirs(VECTORS_DIR, exist_ok=True)
d_in_values = []
print("// Expected values computed by gen_vectors.py")
print("// SHA3-512(d_in || 0x02)")
print("//" + "=" * 72)
with open(OUTPUT_FILE, "w") as f:
for label, d_in in TEST_VECTORS:
# Write d_in to hex file (256 bits = 64 hex chars)
f.write(f"{d_in:064X}\n")
d_in_values.append(d_in)
# Compute expected values
rho, sigma = sha3_512_g(d_in)
print(f"// {label}")
print(f"// d_in = 0x{d_in:064x}")
print(f"// rho = 0x{rho:064x}")
print(f"// sigma = 0x{sigma:064x}")
print()
print(f"Generated {len(TEST_VECTORS)} vectors → {OUTPUT_FILE}")
print()
print("// Copy the expected values above into tb_sha3_chain_xsim.v")
print("// as hardcoded parameters/initial blocks.")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,287 @@
// tb_sha3_chain_xsim.v - Vivado xsim testbench for sha3_chain_top
//
// Tests the ML-KEM G function: SHA3-512(d_in || 0x02) {rho, sigma}.
// Uses $readmemh file-based test vectors for d_in values.
//
// Vector format (256 bits = 64 hex chars per line):
// Each line is a 256-bit d_in value (MSB-first hex).
//
// Expected rho/sigma values are hardcoded (computed by gen_vectors.py
// using hashlib.sha3_512).
//
// Protocol: start_i / done_o handshake
// 1. Drive d_in, assert start_i=1
// 2. Wait for done_o=1 (with timeout watchdog)
// 3. Capture rho_out, sigma_out
// 4. Compare with expected values
// 5. Deassert start_i (FSM returns to IDLE)
`timescale 1ns / 1ps
module tb_sha3_chain_xsim;
// ================================================================
// Parameters
// ================================================================
parameter VECTOR_FILE = "sync_rtl/sha3_chain/TB/vectors/sha3_chain_input.hex";
parameter RESULT_FILE = "sync_rtl/sha3_chain/TB/vectors/sha3_chain_result.hex";
parameter MAX_VECTORS = 256;
parameter TIMEOUT_CYCLES = 2000;
// ================================================================
// DUT signals
// ================================================================
reg clk;
reg rst_n;
reg [255:0] d_in;
reg start_i;
wire done_o;
wire [255:0] rho_out;
wire [255:0] sigma_out;
// ================================================================
// Expected values (computed by gen_vectors.py)
// 8 test vectors total
// ================================================================
reg [255:0] expected_rho [0:7];
reg [255:0] expected_sigma [0:7];
initial begin
// all-zeros
expected_rho[0] = 256'h6a0af64a85e909df8e2816605d20b4e382b30bbb61bf3a5f821a0b5dba9ad3e7;
expected_sigma[0] = 256'he367d3e9ab3b86b64230aa8bf8815d408ef819f9e9d956ce22bbc2f68eaa9e3a;
// all-ones
expected_rho[1] = 256'hf393670510fe33b5df9efb515bf1515c84bdf625e3c53154c10c7d2c92b7f234;
expected_sigma[1] = 256'h9fac853f1e61e8389a04fc710845963c18e48fbec11c66d7d6567f8e168160cc;
// lsb-one
expected_rho[2] = 256'h90ad3aafcc6bde61dc5014cf6cdee8065504733fc0caa8bddf3e1689a7b7b302;
expected_sigma[2] = 256'h3c8686e00e96619b07c7d56fa1effce7ec5597f94a9109ec40c2a6314f6e4ada;
// msb-one
expected_rho[3] = 256'h6c914803c353f4f3b4d8cc541b67019dd2d04cd0fb2ba51b8ebaba973d8f5cbe;
expected_sigma[3] = 256'h33d64b904e1afa541dbe72162021b54c7aef182125da38d88dd7d076636af6cf;
// pattern-aa
expected_rho[4] = 256'h561a98ff76434dc201ca8152cf2b97342090157a522354e299db35fec29690aa;
expected_sigma[4] = 256'hed57f5826ed6e77c756cf821244b350e6b34eb05f9639e227f9a5e130eca649f;
// pattern-55
expected_rho[5] = 256'hff31fe4fc62849226d9197992b1a2d0429c2c325011d2c7bb48860920c04636c;
expected_sigma[5] = 256'hdb5f9599ae43d39fa182896d0ab1d1af663f19355dfeb951fed7b1c8c94eb0d3;
// random-1
expected_rho[6] = 256'h4538916fc357ba9fe24033fa8c1e18cbfb14faf7e1771e0d7521c80cb0d4379f;
expected_sigma[6] = 256'h78565d212000aabb8086e3373ad82d45c07589bcca35409a3f5d9fd30623340e;
// random-2
expected_rho[7] = 256'h176c0b7e91b90ae50757b14bbec130a9e328d09d020466565829de47f6b387cd;
expected_sigma[7] = 256'h413f41c838828e079d5e06b06de61eed6b5bd5005b334e516be6e3b900da7938;
end
// ================================================================
// DUT instantiation
// ================================================================
sha3_chain_top u_dut (
.clk (clk),
.rst_n (rst_n),
.d_in (d_in),
.start_i (start_i),
.done_o (done_o),
.rho_out (rho_out),
.sigma_out(sigma_out)
);
// ================================================================
// Clock generation: 100 MHz (10 ns period)
// ================================================================
initial clk = 1'b0;
always #5 clk = ~clk;
// ================================================================
// Vector memory (loaded by $readmemh)
// ================================================================
reg [255:0] vector_mem [0:MAX_VECTORS-1];
integer vec_count;
integer idx;
integer cycle_count;
integer result_fd;
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};
else
nibble_to_ascii = 8'h41 + ({4'd0, nibble} - 4'd10);
end
endfunction
// ================================================================
// Main test sequence
// ================================================================
initial begin
vec_count = 0;
$readmemh(VECTOR_FILE, vector_mem);
// Count non-X entries
begin
integer found_end;
found_end = 0;
for (idx = 0; idx < MAX_VECTORS; idx = idx + 1) begin
if (!found_end && (vector_mem[idx] === 256'hx || vector_mem[idx] === 256'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.");
$display(" Format: 64 hex chars per line (256-bit d_in)");
$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
d_in <= 256'd0;
start_i <= 1'b0;
// 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 [255:0] vec_d_in;
reg [255:0] captured_rho;
reg [255:0] captured_sigma;
integer bit_idx;
reg [3:0] nib;
vec_d_in = vector_mem[idx];
$display("INFO: Vector %0d - d_in=0x%064h", idx, vec_d_in);
// Drive d_in and assert start_i
d_in <= vec_d_in;
start_i <= 1'b1;
@(posedge clk);
// Wait for done_o (with timeout)
cycle_count = 0;
while (!done_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 done_o on vector %0d", idx);
$fwrite(result_fd, "TIMEOUT: vector %0d d_in=0x%064h\n", idx, vec_d_in);
fail_count = fail_count + 1;
// Force reset the FSM
start_i <= 1'b0;
@(posedge clk);
end else begin
// Capture outputs (valid on cycle done_o=1)
captured_rho = rho_out;
captured_sigma = sigma_out;
// Deassert start_i (FSM returns to IDLE on next cycle)
start_i <= 1'b0;
// Check rho
if (captured_rho !== expected_rho[idx]) begin
$display("FAIL: Vector %0d RHO mismatch", idx);
$display(" expected = 0x%064h", expected_rho[idx]);
$display(" got = 0x%064h", captured_rho);
$fwrite(result_fd, "FAIL: vector %0d RHO expected=0x%064h got=0x%064h\n",
idx, expected_rho[idx], captured_rho);
fail_count = fail_count + 1;
end else begin
pass_count = pass_count + 1;
end
// Check sigma
if (captured_sigma !== expected_sigma[idx]) begin
$display("FAIL: Vector %0d SIGMA mismatch", idx);
$display(" expected = 0x%064h", expected_sigma[idx]);
$display(" got = 0x%064h", captured_sigma);
$fwrite(result_fd, "FAIL: vector %0d SIGMA expected=0x%064h got=0x%064h\n",
idx, expected_sigma[idx], captured_sigma);
fail_count = fail_count + 1;
end else begin
pass_count = pass_count + 1;
end
// Write results to output file
$fwrite(result_fd, "RESULT: %0d ", idx);
$fwrite(result_fd, "d_in=0x");
for (bit_idx = 63; bit_idx >= 0; bit_idx = bit_idx - 1) begin
nib = vec_d_in[(bit_idx*4)+:4];
$fwrite(result_fd, "%c", nibble_to_ascii(nib));
end
$fwrite(result_fd, " rho=0x");
for (bit_idx = 63; bit_idx >= 0; bit_idx = bit_idx - 1) begin
nib = captured_rho[(bit_idx*4)+:4];
$fwrite(result_fd, "%c", nibble_to_ascii(nib));
end
$fwrite(result_fd, " sigma=0x");
for (bit_idx = 63; bit_idx >= 0; bit_idx = bit_idx - 1) begin
nib = captured_sigma[(bit_idx*4)+:4];
$fwrite(result_fd, "%c", nibble_to_ascii(nib));
end
$fwrite(result_fd, "\n");
@(posedge clk);
end
end // inner begin block
end
// ============================================================
// Summary
// ============================================================
$fclose(result_fd);
$display("========================================");
$display("SHA3 CHAIN TEST COMPLETE");
$display(" Total vectors: %0d", vec_count);
$display(" Checks (rho+sigma): %0d", vec_count * 2);
$display(" Passed: %0d", pass_count);
$display(" Failed: %0d", fail_count);
$display(" Results written to: %s", RESULT_FILE);
if (fail_count == 0)
$display(" RESULT: ALL TESTS PASSED");
else
$display(" RESULT: SOME TESTS FAILED");
$display("========================================");
$finish;
end
// ================================================================
// Timeout watchdog
// ================================================================
initial begin
#(TIMEOUT_CYCLES * 10 * 100);
$display("FATAL: Global simulation timeout reached");
$finish;
end
endmodule

View File

@@ -0,0 +1,8 @@
0000000000000000000000000000000000000000000000000000000000000000
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
0000000000000000000000000000000000000000000000000000000000000001
8000000000000000000000000000000000000000000000000000000000000000
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
5555555555555555555555555555555555555555555555555555555555555555
01A2B3C4D5E6F708192A3B4C5D6E7F8091A2B3C4D5E6F708192A3B4C5D6E7F80
F0E1D2C3B4A5968778695A4B3C2D1E0FF0E1D2C3B4A5968778695A4B3C2D1E0F

View File

@@ -0,0 +1,64 @@
# xsim_run.tcl - Vivado xsim compilation and simulation script
#
# Compiles sha3_chain_top with all SHA3 dependencies plus testbench.
# Run from the project root: ~/Dev/mlkem/
#
# Dependencies:
# sync_rtl/sha3/keccak_round.v (combinational Keccak round)
# sync_rtl/sha3/keccak_core.v (24-round Keccak core)
# sync_rtl/sha3/sha3_top.v (SHA3 G/H/J top wrapper)
# sync_rtl/sha3_chain/sha3_chain_top.v (ML-KEM G function)
#
# Prerequisites:
# source /opt/Xilinx/Vivado/2019.2/settings64.sh
#
# Usage:
# xsim -runall xsim_run.tcl
# vivado -mode batch -source xsim_run.tcl
# ================================================================
# Configuration
# ================================================================
set SHA3_DIR sync_rtl/sha3
set SHA3_CHAIN_DIR sync_rtl/sha3_chain
set TB_DIR sync_rtl/sha3_chain/TB
# ================================================================
# Step 1: Compile RTL sources (xvlog)
# ================================================================
puts "=== Compiling SHA3 RTL sources ==="
# Core Keccak module (combinational round)
xvlog -sv ${SHA3_DIR}/keccak_round.v
# Keccak core (24-round sequential core)
xvlog -sv ${SHA3_DIR}/keccak_core.v
# SHA3 top wrapper (G/H/J modes)
xvlog -sv ${SHA3_DIR}/sha3_top.v
# sha3_chain_top (ML-KEM G function)
xvlog -sv ${SHA3_CHAIN_DIR}/sha3_chain_top.v
# ================================================================
# Step 2: Compile testbench
# ================================================================
puts "=== Compiling testbench ==="
xvlog -sv ${TB_DIR}/tb_sha3_chain_xsim.v
# ================================================================
# Step 3: Elaborate snapshot (xelab)
# ================================================================
puts "=== Elaborating snapshot ==="
xelab tb_sha3_chain_xsim -s tb_sha3_chain_xsim
# ================================================================
# Step 4: Run simulation
# ================================================================
puts "=== Running sha3_chain test ==="
xsim tb_sha3_chain_xsim -R
puts ""
puts "=== sha3_chain simulation complete ==="

View File

@@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""gen_vectors.py — Generate BRAM test vectors for tb_storage_xsim.v
Vector format (64 bits = 16 hex chars per line):
bit [63] : module (0=s_bram, 1=sd_bram)
bit [62] : cmd (0=write, 1=read-verify)
bits [61:56] : addr (6-bit address)
bits [55:32] : reserved (zeros)
bits [31:0] : data (write data / expected read data)
Output: vectors/storage_input.hex
"""
import os
VECTORS_DIR = os.path.join(os.path.dirname(__file__), "vectors")
OUTPUT_FILE = os.path.join(VECTORS_DIR, "storage_input.hex")
# Module selects
S_BRAM = 0 # bit 63 = 0
SD_BRAM = 1 # bit 63 = 1
# Commands
CMD_WRITE = 0 # bit 62 = 0
CMD_READ = 1 # bit 62 = 1
def encode(module: int, cmd: int, addr: int, data: int) -> int:
"""Pack module, cmd, addr, data into a 64-bit test vector word."""
word = 0
word |= (module & 0x1) << 63
word |= (cmd & 0x1) << 62
word |= (addr & 0x3F) << 56 # 6-bit addr in bits 61:56
# bits 55:32 reserved (zero)
word |= (data & 0xFFFF_FFFF) # bits 31:0
return word
def main():
os.makedirs(VECTORS_DIR, exist_ok=True)
vectors = []
# ── s_bram writes ──
vectors.append(encode(S_BRAM, CMD_WRITE, 0, 0x00000000)) # all-zeros edge
vectors.append(encode(S_BRAM, CMD_WRITE, 1, 0x11111111))
vectors.append(encode(S_BRAM, CMD_WRITE, 2, 0x22222222))
vectors.append(encode(S_BRAM, CMD_WRITE, 10, 0xAAAAAAAA))
vectors.append(encode(S_BRAM, CMD_WRITE, 20, 0xCCCCCCCC))
vectors.append(encode(S_BRAM, CMD_WRITE, 63, 0xFFFFFFFF)) # max addr edge
vectors.append(encode(S_BRAM, CMD_WRITE, 30, 0xDEADBEEF))
# ── s_bram reads (verify) ──
vectors.append(encode(S_BRAM, CMD_READ, 0, 0x00000000))
vectors.append(encode(S_BRAM, CMD_READ, 1, 0x11111111))
vectors.append(encode(S_BRAM, CMD_READ, 2, 0x22222222))
vectors.append(encode(S_BRAM, CMD_READ, 10, 0xAAAAAAAA))
vectors.append(encode(S_BRAM, CMD_READ, 20, 0xCCCCCCCC))
vectors.append(encode(S_BRAM, CMD_READ, 63, 0xFFFFFFFF))
vectors.append(encode(S_BRAM, CMD_READ, 30, 0xDEADBEEF))
# ── sd_bram writes ──
vectors.append(encode(SD_BRAM, CMD_WRITE, 0, 0x00000000)) # all-zeros edge
vectors.append(encode(SD_BRAM, CMD_WRITE, 1, 0x33333333))
vectors.append(encode(SD_BRAM, CMD_WRITE, 2, 0x44444444))
vectors.append(encode(SD_BRAM, CMD_WRITE, 10, 0x55555555))
vectors.append(encode(SD_BRAM, CMD_WRITE, 63, 0xFFFFFFFF)) # max addr edge
vectors.append(encode(SD_BRAM, CMD_WRITE, 15, 0xCAFEBABE))
vectors.append(encode(SD_BRAM, CMD_WRITE, 20, 0xBEEFCAFE))
# ── sd_bram reads (verify) ──
vectors.append(encode(SD_BRAM, CMD_READ, 0, 0x00000000))
vectors.append(encode(SD_BRAM, CMD_READ, 1, 0x33333333))
vectors.append(encode(SD_BRAM, CMD_READ, 2, 0x44444444))
vectors.append(encode(SD_BRAM, CMD_READ, 10, 0x55555555))
vectors.append(encode(SD_BRAM, CMD_READ, 63, 0xFFFFFFFF))
vectors.append(encode(SD_BRAM, CMD_READ, 15, 0xCAFEBABE))
vectors.append(encode(SD_BRAM, CMD_READ, 20, 0xBEEFCAFE))
# Write hex file (16 hex chars = 64 bits per line, uppercase, no prefix)
with open(OUTPUT_FILE, "w") as f:
for v in vectors:
f.write(f"{v:016X}\n")
print(f"Generated {len(vectors)} vectors → {OUTPUT_FILE}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,380 @@
// tb_storage_xsim.v - Vivado xsim testbench for s_bram and sd_bram
//
// Tests both single-port (s_bram) and simple dual-port (sd_bram) BRAM
// using $readmemh file-based test vectors.
//
// Vector format (64 bits = 16 hex chars per line):
// bit [63] : module (0=s_bram, 1=sd_bram)
// bit [62] : cmd (0=write, 1=read-verify)
// bits [61:56] : addr (6-bit address)
// bits [55:32] : reserved
// bits [31:0] : data (write data / expected read data)
//
// Test coverage:
// - Write then read (same address)
// - Write multiple addresses then read all
// - Read during write on different addresses (sd_bram only)
// - Address wrap (addr 63, max for A=6)
// - All-zeros data edge case
//
// Parameters: W=32, D=64, A=6
`timescale 1ns / 1ps
module tb_storage_xsim;
// ================================================================
// Parameters
// ================================================================
parameter W = 32;
parameter D = 64;
parameter A = 6;
parameter VECTOR_FILE = "sync_rtl/storage/TB/vectors/storage_input.hex";
parameter RESULT_FILE = "sync_rtl/storage/TB/vectors/storage_result.hex";
parameter MAX_VECTORS = 256;
parameter TIMEOUT_CYCLES = 2000;
// ================================================================
// DUT signals s_bram (single-port)
// ================================================================
reg clk;
reg rst_n;
reg s_rd_en;
reg [A-1:0] s_rd_addr;
wire [W-1:0] s_rd_data;
reg s_wr_en;
reg [A-1:0] s_wr_addr;
reg [W-1:0] s_wr_data;
// ================================================================
// DUT signals sd_bram (simple dual-port)
// ================================================================
reg [A-1:0] sd_rd_addr;
wire [W-1:0] sd_rd_data;
reg sd_wr_en;
reg [A-1:0] sd_wr_addr;
reg [W-1:0] sd_wr_data;
// ================================================================
// DUT instantiations
// ================================================================
s_bram #(.W(W), .D(D), .A(A)) u_sbram (
.clk (clk),
.rd_en (s_rd_en),
.rd_addr (s_rd_addr),
.rd_data (s_rd_data),
.wr_en (s_wr_en),
.wr_addr (s_wr_addr),
.wr_data (s_wr_data)
);
sd_bram #(.W(W), .D(D), .A(A)) u_sdbram (
.clk (clk),
.rd_addr (sd_rd_addr),
.rd_data (sd_rd_data),
.wr_en (sd_wr_en),
.wr_addr (sd_wr_addr),
.wr_data (sd_wr_data)
);
// ================================================================
// Clock generation: 100 MHz (10 ns period)
// ================================================================
initial clk = 1'b0;
always #5 clk = ~clk;
// ================================================================
// Vector memory (loaded by $readmemh)
// ================================================================
reg [63:0] vector_mem [0:MAX_VECTORS-1];
integer vec_count;
integer idx;
integer cycle_count;
integer result_fd;
integer pass_count;
integer fail_count;
// ================================================================
// Main test sequence
// ================================================================
initial begin
// Load vectors from hex file
vec_count = 0;
$readmemh(VECTOR_FILE, vector_mem);
// Count non-X entries
begin
integer found_end;
found_end = 0;
for (idx = 0; idx < MAX_VECTORS; idx = idx + 1) begin
if (!found_end && (vector_mem[idx] === 64'hx || vector_mem[idx] === 64'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.");
$display(" Format: 16 hex chars = {module, cmd, addr, reserved, data}");
$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 all DUT inputs
s_rd_en <= 1'b0;
s_rd_addr <= {A{1'b0}};
s_wr_en <= 1'b0;
s_wr_addr <= {A{1'b0}};
s_wr_data <= {W{1'b0}};
sd_rd_addr <= {A{1'b0}};
sd_wr_en <= 1'b0;
sd_wr_addr <= {A{1'b0}};
sd_wr_data <= {W{1'b0}};
// 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 vec_module;
reg vec_cmd;
reg [A-1:0] vec_addr;
reg [W-1:0] vec_data;
vec_module = vector_mem[idx][63];
vec_cmd = vector_mem[idx][62];
vec_addr = vector_mem[idx][61:56];
vec_data = vector_mem[idx][31:0];
if (vec_module == 1'b0) begin
// s_bram
if (vec_cmd == 1'b0) begin
// Write
s_wr_en <= 1'b1;
s_wr_addr <= vec_addr;
s_wr_data <= vec_data;
s_rd_en <= 1'b0;
s_rd_addr <= {A{1'b0}};
@(posedge clk);
s_wr_en <= 1'b0;
end else begin
// Read-verify
s_rd_en <= 1'b1;
s_rd_addr <= vec_addr;
s_wr_en <= 1'b0;
s_wr_addr <= {A{1'b0}};
s_wr_data <= {W{1'b0}};
@(posedge clk); // 1-cycle read latency
// rd_data valid now
if (s_rd_data !== vec_data) begin
$display("FAIL: s_bram addr=%0d expected=0x%08h got=0x%08h",
vec_addr, vec_data, s_rd_data);
$fwrite(result_fd, "FAIL: s_bram addr=%0d exp=0x%08h got=0x%08h\n",
vec_addr, vec_data, s_rd_data);
fail_count = fail_count + 1;
end else begin
pass_count = pass_count + 1;
end
s_rd_en <= 1'b0;
end
end else begin
// sd_bram
if (vec_cmd == 1'b0) begin
// Write
sd_wr_en <= 1'b1;
sd_wr_addr <= vec_addr;
sd_wr_data <= vec_data;
@(posedge clk);
sd_wr_en <= 1'b0;
end else begin
// Read-verify: drive read address, wait 1 cycle
sd_rd_addr <= vec_addr;
@(posedge clk); // 1-cycle read latency
// rd_data valid now
if (sd_rd_data !== vec_data) begin
$display("FAIL: sd_bram addr=%0d expected=0x%08h got=0x%08h",
vec_addr, vec_data, sd_rd_data);
$fwrite(result_fd, "FAIL: sd_bram addr=%0d exp=0x%08h got=0x%08h\n",
vec_addr, vec_data, sd_rd_data);
fail_count = fail_count + 1;
end else begin
pass_count = pass_count + 1;
end
end
end
end // inner begin block
end
// ============================================================
// Edge-case tests (hardcoded, beyond hex vectors)
// ============================================================
// s_bram: write-priority (rd_en + wr_en both high)
begin
$display("INFO: s_bram write-priority test");
// Write 0xCAFECAFE to addr 5
s_wr_en <= 1'b1; s_wr_addr <= 6'd5; s_wr_data <= 32'hCAFECAFE;
s_rd_en <= 1'b1; s_rd_addr <= 6'd5; // same addr, rd also asserted
@(posedge clk);
s_wr_en <= 1'b0;
// Write should win memory now has CAFECAFE at addr 5
// Read back to verify
s_rd_en <= 1'b1; s_rd_addr <= 6'd5;
@(posedge clk);
if (s_rd_data !== 32'hCAFECAFE) begin
$display("FAIL: s_bram write-priority (expected CAFECAFE, got 0x%08h)", s_rd_data);
fail_count = fail_count + 1;
end else begin
$display("PASS: s_bram write-priority");
pass_count = pass_count + 1;
end
s_rd_en <= 1'b0;
end
// sd_bram: concurrent read-during-write (different addresses)
begin
$display("INFO: sd_bram read-during-write test");
// Pre-fill addr 10 with known value
sd_wr_en <= 1'b1; sd_wr_addr <= 6'd10; sd_wr_data <= 32'h12345678;
@(posedge clk);
sd_wr_en <= 1'b0;
// Now write addr 20 while reading addr 10 (different addrs)
sd_wr_en <= 1'b1; sd_wr_addr <= 6'd20; sd_wr_data <= 32'hFEEDFACE;
sd_rd_addr <= 6'd10;
@(posedge clk);
sd_wr_en <= 1'b0;
// rd_data should be OLD value at addr 10 (12345678)
if (sd_rd_data !== 32'h12345678) begin
$display("FAIL: sd_bram rd-during-wr: expected 0x12345678, got 0x%08h", sd_rd_data);
fail_count = fail_count + 1;
end else begin
$display("PASS: sd_bram read-during-write (old data)");
pass_count = pass_count + 1;
end
// Verify addr 20 got new data
sd_rd_addr <= 6'd20;
@(posedge clk);
if (sd_rd_data !== 32'hFEEDFACE) begin
$display("FAIL: sd_bram wr-during-rd: expected 0xFEEDFACE at addr 20, got 0x%08h", sd_rd_data);
fail_count = fail_count + 1;
end else begin
$display("PASS: sd_bram write-during-read (new data)");
pass_count = pass_count + 1;
end
end
// sd_bram: concurrent read-during-write (SAME address)
begin
$display("INFO: sd_bram read-during-write (same address)");
sd_wr_en <= 1'b1; sd_wr_addr <= 6'd30; sd_wr_data <= 32'hAAAAAAAA;
sd_rd_addr <= 6'd30; // same address
@(posedge clk);
sd_wr_en <= 1'b0;
// On sd_bram, mem write happens at posedge, rd_addr is registered
// So rd_data gets OLD mem[30] (before write), not new AAAAAAAA
// We just verify the read port didn't get X
if (sd_rd_data === 32'hx || sd_rd_data === 32'hz) begin
$display("FAIL: sd_bram rd-during-wr same addr: got X/Z");
fail_count = fail_count + 1;
end else begin
$display("PASS: sd_bram read-during-write (same addr, no X)");
pass_count = pass_count + 1;
end
// Now read addr 30 to verify write took effect
sd_rd_addr <= 6'd30;
@(posedge clk);
if (sd_rd_data !== 32'hAAAAAAAA) begin
$display("FAIL: sd_bram same-addr write: expected 0xAAAAAAAA, got 0x%08h", sd_rd_data);
fail_count = fail_count + 1;
end else begin
$display("PASS: sd_bram same-addr write verified");
pass_count = pass_count + 1;
end
end
// s_bram: address wrap / all-zeros data
begin
$display("INFO: s_bram address wrap test");
// Write to max address (63) and read back
s_wr_en <= 1'b1; s_wr_addr <= 6'd63; s_wr_data <= 32'hFFFF0000;
@(posedge clk);
s_wr_en <= 1'b0;
s_rd_en <= 1'b1; s_rd_addr <= 6'd63;
@(posedge clk);
if (s_rd_data !== 32'hFFFF0000) begin
$display("FAIL: s_bram addr 63: expected 0xFFFF0000, got 0x%08h", s_rd_data);
fail_count = fail_count + 1;
end else begin
$display("PASS: s_bram addr 63 (max)");
pass_count = pass_count + 1;
end
s_rd_en <= 1'b0;
// Write all-zeros to addr 0 and read back
s_wr_en <= 1'b1; s_wr_addr <= 6'd0; s_wr_data <= 32'h00000000;
@(posedge clk);
s_wr_en <= 1'b0;
s_rd_en <= 1'b1; s_rd_addr <= 6'd0;
@(posedge clk);
if (s_rd_data !== 32'h00000000) begin
$display("FAIL: s_bram all-zeros: expected 0x00000000, got 0x%08h", s_rd_data);
fail_count = fail_count + 1;
end else begin
$display("PASS: s_bram all-zeros data");
pass_count = pass_count + 1;
end
s_rd_en <= 1'b0;
end
// ============================================================
// Summary
// ============================================================
$fclose(result_fd);
$display("========================================");
$display("STORAGE TEST COMPLETE");
$display(" Total vectors: %0d", vec_count);
$display(" Edge-case tests: %0d", 7); // hardcoded count above
$display(" Passed: %0d", pass_count);
$display(" Failed: %0d", fail_count);
$display(" Results written to: %s", RESULT_FILE);
if (fail_count == 0)
$display(" RESULT: ALL TESTS PASSED");
else
$display(" RESULT: SOME TESTS FAILED");
$display("========================================");
$finish;
end
// ================================================================
// Timeout watchdog
// ================================================================
initial begin
#(TIMEOUT_CYCLES * 10 * 100);
$display("FATAL: Global simulation timeout reached");
$finish;
end
endmodule

View File

@@ -0,0 +1,28 @@
0000000000000000
0100000011111111
0200000022222222
0A000000AAAAAAAA
14000000CCCCCCCC
3F000000FFFFFFFF
1E000000DEADBEEF
4000000000000000
4100000011111111
4200000022222222
4A000000AAAAAAAA
54000000CCCCCCCC
7F000000FFFFFFFF
5E000000DEADBEEF
8000000000000000
8100000033333333
8200000044444444
8A00000055555555
BF000000FFFFFFFF
8F000000CAFEBABE
94000000BEEFCAFE
C000000000000000
C100000033333333
C200000044444444
CA00000055555555
FF000000FFFFFFFF
CF000000CAFEBABE
D4000000BEEFCAFE

View File

@@ -0,0 +1,48 @@
# xsim_run.tcl - Vivado xsim compilation and simulation script
#
# Compiles s_bram, sd_bram, and tb_storage_xsim testbench.
# Run from the project root: ~/Dev/mlkem/
#
# Prerequisites:
# source /opt/Xilinx/Vivado/2019.2/settings64.sh
#
# Usage:
# xsim -runall xsim_run.tcl
# vivado -mode batch -source xsim_run.tcl
# ================================================================
# Configuration
# ================================================================
set SRC_DIR sync_rtl/storage
set TB_DIR sync_rtl/storage/TB
# ================================================================
# Step 1: Compile RTL sources (xvlog)
# ================================================================
puts "=== Compiling RTL sources ==="
xvlog -sv ${SRC_DIR}/s_bram.v
xvlog -sv ${SRC_DIR}/sd_bram.v
# ================================================================
# Step 2: Compile testbench
# ================================================================
puts "=== Compiling testbench ==="
xvlog -sv ${TB_DIR}/tb_storage_xsim.v
# ================================================================
# Step 3: Elaborate snapshot (xelab)
# ================================================================
puts "=== Elaborating snapshot ==="
xelab tb_storage_xsim -s tb_storage_xsim
# ================================================================
# Step 4: Run simulation
# ================================================================
puts "=== Running storage test ==="
xsim tb_storage_xsim -R
puts ""
puts "=== Storage simulation complete ==="