Phase 1 complete — all 4 leaf modules verified: - rng_sync.v: 256-bit Galois LFSR PRNG (10/10 PASS) - sample_cbd_sync.v: CBD sampler with keccak_core PRF (2560/2560 PASS) - sample_ntt_sync.v: SHAKE-128 rejection sampling for A matrix (1536/1536 PASS) - xsim Verilog TBs for sha3 module (tb_sha3_xsim.v, tb_sha3_xsim_simple.v, tb_keccak_core_xsim.v)
113 lines
3.7 KiB
Python
113 lines
3.7 KiB
Python
"""gen_vectors.py - Test vector generator for rng module.
|
|
|
|
Generates expected 256-bit LFSR output values using the same Galois LFSR
|
|
polynomial as the RTL: x^256 + x^255 + x^253 + x^252 + x^247 + 1
|
|
Taps: [255, 253, 252, 247, 0]
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
# Add test_framework/lib to path for VectorGenerator base class
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'lib'))
|
|
|
|
from vector_gen import VectorGenerator
|
|
|
|
# Default seed matches RTL SEED parameter
|
|
DEFAULT_SEED = 0xDEADBEEFCAFEBABEFEEDFACEDECAFBAD1234567887654321ABCDEF010FEDCBA9
|
|
|
|
# 256-bit mask
|
|
MASK_256 = (1 << 256) - 1
|
|
|
|
|
|
class _LFSR256:
|
|
"""256-bit Galois LFSR matching rng_sync.v implementation.
|
|
|
|
Polynomial: x^256 + x^255 + x^253 + x^252 + x^247 + 1
|
|
Taps: [255, 253, 252, 247, 0]
|
|
"""
|
|
|
|
def __init__(self, seed: int):
|
|
self.state = seed & MASK_256
|
|
|
|
def next(self) -> int:
|
|
"""Advance LFSR one step and return the new state."""
|
|
feedback = self.state & 1 # LSB = state[0]
|
|
|
|
# Shift right by 1, feedback into MSB (position 255)
|
|
new_state = (self.state >> 1) | (feedback << 255)
|
|
|
|
# XOR feedback into tap positions (shifted)
|
|
if feedback:
|
|
new_state ^= (1 << 254) # tap 255 -> position 254
|
|
new_state ^= (1 << 252) # tap 253 -> position 252
|
|
new_state ^= (1 << 251) # tap 252 -> position 251
|
|
new_state ^= (1 << 246) # tap 247 -> position 246
|
|
|
|
self.state = new_state & MASK_256
|
|
return self.state
|
|
|
|
|
|
class RngVectorGenerator(VectorGenerator):
|
|
"""Generates test vectors for the rng_sync module."""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._lfsr = None
|
|
|
|
def _ensure_lfsr(self, params: dict) -> None:
|
|
"""Initialize LFSR on first call or when params change seed."""
|
|
seed = params.get('seed', DEFAULT_SEED)
|
|
if self._lfsr is None:
|
|
self._lfsr = _LFSR256(seed)
|
|
|
|
def generate_one(self, params: dict) -> dict:
|
|
"""Generate one LFSR output value.
|
|
|
|
On each call, advances the LFSR from its current state and returns
|
|
the new state. This matches the RTL behavior: valid_i advances the
|
|
LFSR, and the new value appears on data_o.
|
|
|
|
Args:
|
|
params: Optional dict with 'seed' key to override default seed.
|
|
|
|
Returns:
|
|
dict with 'input' (empty) and 'expected' {'data': lfsr_state_hex}.
|
|
"""
|
|
self._ensure_lfsr(params)
|
|
new_state = self._lfsr.next()
|
|
return {
|
|
'input': {},
|
|
'expected': {'data': new_state}
|
|
}
|
|
|
|
def write_hex_file(self, vectors: list[dict], filepath: str) -> None:
|
|
"""Write input file with one dummy line per vector.
|
|
|
|
The RNG has no input signals, but the testbench reads the hex file
|
|
to determine the number of vectors to generate. Each line acts as
|
|
a pulse trigger.
|
|
|
|
Args:
|
|
vectors: List of vector dicts from generate_one().
|
|
filepath: Path to write the hex file.
|
|
"""
|
|
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
with open(filepath, 'w') as f:
|
|
for _ in vectors:
|
|
f.write('0\n')
|
|
|
|
def write_expected_file(self, vectors: list[dict], filepath: str) -> None:
|
|
"""Write expected output as 64-char hex strings, one per line.
|
|
|
|
Args:
|
|
vectors: List of vector dicts from generate_one().
|
|
filepath: Path to write the expected hex file.
|
|
"""
|
|
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
|
with open(filepath, 'w') as f:
|
|
for v in vectors:
|
|
data = v['expected']['data']
|
|
# Format as 64-character uppercase hex string
|
|
f.write(f'{data:064X}\n')
|