feat(phase1): implement RNG, SampleCBD, SampleNTT modules + xsim TBs
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)
This commit is contained in:
112
test_framework/modules/rng/gen_vectors.py
Normal file
112
test_framework/modules/rng/gen_vectors.py
Normal file
@@ -0,0 +1,112 @@
|
||||
"""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')
|
||||
17
test_framework/modules/rng/test_plan.json
Normal file
17
test_framework/modules/rng/test_plan.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"module": "rng",
|
||||
"rtl_top": "sync_rtl/rng/rng_sync.v",
|
||||
"rtl_deps": [],
|
||||
"tb_cpp": "sync_rtl/rng/TB/tb_rng.cpp",
|
||||
"simulator": "verilator",
|
||||
"timeout_s": 30,
|
||||
"cases": [
|
||||
{
|
||||
"id": "basic",
|
||||
"description": "Generate 10 pseudo-random 256-bit values with fixed-seed LFSR",
|
||||
"params": {},
|
||||
"num_vectors": 10,
|
||||
"tolerance": "bit_exact"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user