feat(ntt): implement synchronous NTT core with Barrett modular reduction
Phase 2.1: Merged Path00+Path01 NTT engine. - barrett_mul.v: Barrett modular multiplication (a·b mod 3329) - butterfly_unit.v: Cooley-Tukey/Gentleman-Sande butterfly - zeta_rom.v: 128-entry ROM with bit-reversed roots of unity - ntt_core.v: 7-layer NTT FSM, 256×12-bit register file - ntt_sync.v: valid/ready streaming wrapper Verified: 13/13 vectors bit-exact vs Python NTT/NTTInverse
This commit is contained in:
122
test_framework/modules/ntt/gen_vectors.py
Normal file
122
test_framework/modules/ntt/gen_vectors.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""gen_vectors.py - Test vector generator for ntt module.
|
||||
|
||||
Generates random polynomials and computes expected NTT results
|
||||
using the Python reference implementation (embedded to avoid
|
||||
side-effect imports from the original module).
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'lib'))
|
||||
from vector_gen import VectorGenerator
|
||||
|
||||
Q = 3329
|
||||
N = 256
|
||||
N_INV = 3303
|
||||
|
||||
zeta_bitRev = [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 NTT(f):
|
||||
"""Forward NTT using Cooley-Tukey, bit-exact with reference."""
|
||||
f_hat = f.copy()
|
||||
i = 1
|
||||
for len_NTT in [128, 64, 32, 16, 8, 4, 2]:
|
||||
for start in range(0, 256, 2 * len_NTT):
|
||||
zeta = zeta_bitRev[i]
|
||||
i += 1
|
||||
for j in range(start, start + len_NTT):
|
||||
t = (zeta * f_hat[j + len_NTT]) % Q
|
||||
f_hat[j + len_NTT] = (f_hat[j] - t) % Q
|
||||
f_hat[j] = (f_hat[j] + t) % Q
|
||||
return f_hat
|
||||
|
||||
|
||||
def NTTInverse(f_hat):
|
||||
"""Inverse NTT using Gentleman-Sande, bit-exact with reference."""
|
||||
f = f_hat.copy()
|
||||
i = 127
|
||||
for len_NTT in [2, 4, 8, 16, 32, 64, 128]:
|
||||
for start in range(0, 256, 2 * len_NTT):
|
||||
zeta = zeta_bitRev[i]
|
||||
i -= 1
|
||||
for j in range(start, start + len_NTT):
|
||||
t = f[j]
|
||||
f[j] = (t + f[j + len_NTT]) % Q
|
||||
f[j + len_NTT] = (zeta * (f[j + len_NTT] - t)) % Q
|
||||
for i in range(len(f)):
|
||||
f[i] = (f[i] * N_INV) % Q
|
||||
return f
|
||||
|
||||
|
||||
class NttVectorGenerator(VectorGenerator):
|
||||
"""Generates test vectors for the ntt_sync module."""
|
||||
|
||||
def generate_one(self, params: dict) -> dict:
|
||||
mode = params.get('mode', 'forward')
|
||||
|
||||
if mode == 'forward':
|
||||
f = [random.randint(0, Q - 1) for _ in range(N)]
|
||||
f_hat = NTT(f)
|
||||
return {
|
||||
'input': {'mode': 'F', 'coeffs': f},
|
||||
'expected': {'coeffs': f_hat}
|
||||
}
|
||||
|
||||
elif mode == 'inverse':
|
||||
f_hat = [random.randint(0, Q - 1) for _ in range(N)]
|
||||
f = NTTInverse(f_hat)
|
||||
return {
|
||||
'input': {'mode': 'I', 'coeffs': f_hat},
|
||||
'expected': {'coeffs': f}
|
||||
}
|
||||
|
||||
elif mode == 'roundtrip':
|
||||
f = [random.randint(0, Q - 1) for _ in range(N)]
|
||||
f_hat = NTT(f)
|
||||
f_recover = NTTInverse(f_hat)
|
||||
assert f_recover == f, "Round-trip invariant failed!"
|
||||
return {
|
||||
'input': {'mode': 'I', 'coeffs': f_hat},
|
||||
'expected': {'coeffs': f}
|
||||
}
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unknown mode: {mode}")
|
||||
|
||||
def _format_coeffs(self, coeffs: list[int]) -> str:
|
||||
return ' '.join(f'{c:03X}' for c in coeffs)
|
||||
|
||||
def write_hex_file(self, vectors: list[dict], filepath: str) -> None:
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
with open(filepath, 'w') as f:
|
||||
for v in vectors:
|
||||
mode = v['input']['mode']
|
||||
coeffs = v['input']['coeffs']
|
||||
hex_str = self._format_coeffs(coeffs)
|
||||
f.write(f'{mode} {hex_str}\n')
|
||||
|
||||
def write_expected_file(self, vectors: list[dict], filepath: str) -> None:
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
with open(filepath, 'w') as f:
|
||||
for v in vectors:
|
||||
coeffs = v['expected']['coeffs']
|
||||
hex_str = self._format_coeffs(coeffs)
|
||||
f.write(f'{hex_str}\n')
|
||||
Reference in New Issue
Block a user