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:
179
sync_rtl/comp_decomp/TB/gen_vectors.py
Normal file
179
sync_rtl/comp_decomp/TB/gen_vectors.py
Normal 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())
|
||||
254
sync_rtl/comp_decomp/TB/tb_comp_decomp_xsim.v
Normal file
254
sync_rtl/comp_decomp/TB/tb_comp_decomp_xsim.v
Normal 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
|
||||
135
sync_rtl/comp_decomp/TB/vectors/comp_decomp_input.hex
Normal file
135
sync_rtl/comp_decomp/TB/vectors/comp_decomp_input.hex
Normal 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
|
||||
60
sync_rtl/comp_decomp/TB/xsim_run.tcl
Normal file
60
sync_rtl/comp_decomp/TB/xsim_run.tcl
Normal 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 ==="
|
||||
112
sync_rtl/mod_add/TB/gen_vectors.py
Normal file
112
sync_rtl/mod_add/TB/gen_vectors.py
Normal 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}")
|
||||
238
sync_rtl/mod_add/TB/tb_mod_add_xsim.v
Normal file
238
sync_rtl/mod_add/TB/tb_mod_add_xsim.v
Normal 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
|
||||
39
sync_rtl/mod_add/TB/vectors/mod_add_input.hex
Normal file
39
sync_rtl/mod_add/TB/vectors/mod_add_input.hex
Normal 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
|
||||
59
sync_rtl/mod_add/TB/xsim_run.tcl
Normal file
59
sync_rtl/mod_add/TB/xsim_run.tcl
Normal 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 ==="
|
||||
241
sync_rtl/ntt/TB/gen_vectors.py
Normal file
241
sync_rtl/ntt/TB/gen_vectors.py
Normal 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()
|
||||
317
sync_rtl/ntt/TB/tb_ntt_core_xsim.v
Normal file
317
sync_rtl/ntt/TB/tb_ntt_core_xsim.v
Normal 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
|
||||
43
sync_rtl/ntt/TB/vectors/ntt_core_input.hex
Normal file
43
sync_rtl/ntt/TB/vectors/ntt_core_input.hex
Normal 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
|
||||
62
sync_rtl/ntt/TB/xsim_run.tcl
Normal file
62
sync_rtl/ntt/TB/xsim_run.tcl
Normal 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 ==="
|
||||
153
sync_rtl/poly_arith/TB/gen_vectors.py
Normal file
153
sync_rtl/poly_arith/TB/gen_vectors.py
Normal 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())
|
||||
254
sync_rtl/poly_arith/TB/tb_poly_arith_xsim.v
Normal file
254
sync_rtl/poly_arith/TB/tb_poly_arith_xsim.v
Normal 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
|
||||
76
sync_rtl/poly_arith/TB/vectors/poly_arith_input.hex
Normal file
76
sync_rtl/poly_arith/TB/vectors/poly_arith_input.hex
Normal 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
|
||||
60
sync_rtl/poly_arith/TB/xsim_run.tcl
Normal file
60
sync_rtl/poly_arith/TB/xsim_run.tcl
Normal 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 ==="
|
||||
195
sync_rtl/poly_mul/TB/gen_vectors.py
Normal file
195
sync_rtl/poly_mul/TB/gen_vectors.py
Normal 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()
|
||||
312
sync_rtl/poly_mul/TB/tb_poly_mul_xsim.v
Normal file
312
sync_rtl/poly_mul/TB/tb_poly_mul_xsim.v
Normal 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_CALC→COMP_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
|
||||
27
sync_rtl/poly_mul/TB/vectors/poly_mul_input.hex
Normal file
27
sync_rtl/poly_mul/TB/vectors/poly_mul_input.hex
Normal 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
|
||||
69
sync_rtl/poly_mul/TB/xsim_run.tcl
Normal file
69
sync_rtl/poly_mul/TB/xsim_run.tcl
Normal 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 ==="
|
||||
85
sync_rtl/rng/TB/gen_vectors.py
Normal file
85
sync_rtl/rng/TB/gen_vectors.py
Normal 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()
|
||||
230
sync_rtl/rng/TB/tb_rng_xsim.v
Normal file
230
sync_rtl/rng/TB/tb_rng_xsim.v
Normal 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
|
||||
32
sync_rtl/rng/TB/vectors/rng_input.hex
Normal file
32
sync_rtl/rng/TB/vectors/rng_input.hex
Normal 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
|
||||
54
sync_rtl/rng/TB/xsim_run.tcl
Normal file
54
sync_rtl/rng/TB/xsim_run.tcl
Normal 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 ==="
|
||||
270
sync_rtl/sample_cbd/TB/gen_vectors.py
Normal file
270
sync_rtl/sample_cbd/TB/gen_vectors.py
Normal 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()
|
||||
257
sync_rtl/sample_cbd/TB/tb_sample_cbd_xsim.v
Normal file
257
sync_rtl/sample_cbd/TB/tb_sample_cbd_xsim.v
Normal 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
|
||||
1028
sync_rtl/sample_cbd/TB/vectors/sample_cbd_expected.hex
Normal file
1028
sync_rtl/sample_cbd/TB/vectors/sample_cbd_expected.hex
Normal file
File diff suppressed because it is too large
Load Diff
4
sync_rtl/sample_cbd/TB/vectors/sample_cbd_input.hex
Normal file
4
sync_rtl/sample_cbd/TB/vectors/sample_cbd_input.hex
Normal file
@@ -0,0 +1,4 @@
|
||||
27F692D88854B85E2DF61697261CCC8931119F593F90A499EA6AD3229F6301D280E
|
||||
24A7D2BAA804507EA9069043D6D395FBF42E1BB02BE0A1894B98E8F5F734FE5C22A
|
||||
3C7E07D2297A5E9B056F40AA0AF595CB3121E03CCBB86CC25C012EC008D07DC8481
|
||||
3A0A67134FA9332AD1430AF5003D24E856EB38A0219B48B68A24C5C85E73AF79026
|
||||
56
sync_rtl/sample_cbd/TB/xsim_run.tcl
Normal file
56
sync_rtl/sample_cbd/TB/xsim_run.tcl
Normal 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 ==="
|
||||
503
sync_rtl/sample_ntt/TB/gen_vectors.py
Normal file
503
sync_rtl/sample_ntt/TB/gen_vectors.py
Normal 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()
|
||||
265
sync_rtl/sample_ntt/TB/tb_sample_ntt_xsim.v
Normal file
265
sync_rtl/sample_ntt/TB/tb_sample_ntt_xsim.v
Normal 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
|
||||
1028
sync_rtl/sample_ntt/TB/vectors/sample_ntt_expected.hex
Normal file
1028
sync_rtl/sample_ntt/TB/vectors/sample_ntt_expected.hex
Normal file
File diff suppressed because it is too large
Load Diff
4
sync_rtl/sample_ntt/TB/vectors/sample_ntt_input.hex
Normal file
4
sync_rtl/sample_ntt/TB/vectors/sample_ntt_input.hex
Normal file
@@ -0,0 +1,4 @@
|
||||
0BF6E5D64607BA30905E9F3976195E2B9A0C82BE0DB27480902BA8CA44BAAF0F50
|
||||
14EF02248243A8FDAC343C3B298DEA5CEAA2F520D79153B4D3F7009ED90D0E63FA
|
||||
24A606EFDC962E55F0BC95332332D82BCC5ACB04497FA99EF06A1D71BF11AF8297
|
||||
426F23F87730D6FE0FE18F27180ECA3DE60D0CA77846F40F8D5FB024030E0CEC0F
|
||||
56
sync_rtl/sample_ntt/TB/xsim_run.tcl
Normal file
56
sync_rtl/sample_ntt/TB/xsim_run.tcl
Normal 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 ==="
|
||||
85
sync_rtl/sha3_chain/TB/gen_vectors.py
Normal file
85
sync_rtl/sha3_chain/TB/gen_vectors.py
Normal 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()
|
||||
287
sync_rtl/sha3_chain/TB/tb_sha3_chain_xsim.v
Normal file
287
sync_rtl/sha3_chain/TB/tb_sha3_chain_xsim.v
Normal 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
|
||||
8
sync_rtl/sha3_chain/TB/vectors/sha3_chain_input.hex
Normal file
8
sync_rtl/sha3_chain/TB/vectors/sha3_chain_input.hex
Normal file
@@ -0,0 +1,8 @@
|
||||
0000000000000000000000000000000000000000000000000000000000000000
|
||||
FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
0000000000000000000000000000000000000000000000000000000000000001
|
||||
8000000000000000000000000000000000000000000000000000000000000000
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
5555555555555555555555555555555555555555555555555555555555555555
|
||||
01A2B3C4D5E6F708192A3B4C5D6E7F8091A2B3C4D5E6F708192A3B4C5D6E7F80
|
||||
F0E1D2C3B4A5968778695A4B3C2D1E0FF0E1D2C3B4A5968778695A4B3C2D1E0F
|
||||
64
sync_rtl/sha3_chain/TB/xsim_run.tcl
Normal file
64
sync_rtl/sha3_chain/TB/xsim_run.tcl
Normal 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 ==="
|
||||
88
sync_rtl/storage/TB/gen_vectors.py
Normal file
88
sync_rtl/storage/TB/gen_vectors.py
Normal 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()
|
||||
380
sync_rtl/storage/TB/tb_storage_xsim.v
Normal file
380
sync_rtl/storage/TB/tb_storage_xsim.v
Normal 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
|
||||
28
sync_rtl/storage/TB/vectors/storage_input.hex
Normal file
28
sync_rtl/storage/TB/vectors/storage_input.hex
Normal 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
|
||||
48
sync_rtl/storage/TB/xsim_run.tcl
Normal file
48
sync_rtl/storage/TB/xsim_run.tcl
Normal 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 ==="
|
||||
Reference in New Issue
Block a user