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:
195
sync_rtl/ntt/TB/tb_ntt.cpp
Normal file
195
sync_rtl/ntt/TB/tb_ntt.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
// tb_ntt.cpp - Verilator C++ testbench for ntt_sync
|
||||
//
|
||||
// Reads test vectors from +VECTOR_FILE= plusarg.
|
||||
// Format: "MODE COEFF0 COEFF1 ... COEFF255"
|
||||
// MODE: "F" = forward NTT, "I" = inverse NTT
|
||||
// COEFFx: 3-digit hex (12-bit, 000..CFF)
|
||||
//
|
||||
// Drives DUT with coefficients one per cycle, waits for output,
|
||||
// prints "RESULT: COEFF0 COEFF1 ... COEFF255\n" to stdout.
|
||||
//
|
||||
// Clock: 10ns period. Reset: 2 cycles.
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include "Vntt_sync.h"
|
||||
#include "verilated.h"
|
||||
|
||||
#define CLK_PERIOD_NS 10.0
|
||||
#define TIMEOUT_CYCLES 500000
|
||||
|
||||
static vluint64_t main_time = 0;
|
||||
|
||||
double sc_time_stamp() {
|
||||
return main_time;
|
||||
}
|
||||
|
||||
// Toggle clock: both edges + eval (one full cycle)
|
||||
static void posedge(Vntt_sync* dut) {
|
||||
dut->clk = 1;
|
||||
main_time += (vluint64_t)(CLK_PERIOD_NS / 2.0);
|
||||
dut->eval();
|
||||
dut->clk = 0;
|
||||
main_time += (vluint64_t)(CLK_PERIOD_NS / 2.0);
|
||||
dut->eval();
|
||||
}
|
||||
|
||||
static int hex_char_to_nibble(char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Parse 3-char hex token to 12-bit value.
|
||||
static uint16_t hex3_to_val(const std::string& tok) {
|
||||
uint16_t val = 0;
|
||||
for (size_t i = 0; i < tok.length() && i < 3; i++) {
|
||||
val = (val << 4) | hex_char_to_nibble(tok[i]);
|
||||
}
|
||||
return val & 0xFFF;
|
||||
}
|
||||
|
||||
// Format 12-bit value as 3-char hex (lowercase for consistency).
|
||||
static std::string val_to_hex3(uint16_t val) {
|
||||
char buf[4];
|
||||
snprintf(buf, sizeof(buf), "%03X", val & 0xFFF);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
Verilated::commandArgs(argc, argv);
|
||||
|
||||
// Parse +VECTOR_FILE= plusarg
|
||||
const char* vector_file = NULL;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::string arg(argv[i]);
|
||||
if (arg.rfind("+VECTOR_FILE=", 0) == 0) {
|
||||
vector_file = argv[i] + 13;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vector_file) {
|
||||
std::cerr << "ERROR: +VECTOR_FILE= not specified" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::ifstream infile(vector_file);
|
||||
if (!infile.is_open()) {
|
||||
std::cerr << "ERROR: Cannot open vector file: " << vector_file << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Instantiate DUT
|
||||
Vntt_sync* dut = new Vntt_sync;
|
||||
|
||||
// Initialize
|
||||
dut->clk = 0;
|
||||
dut->rst_n = 0;
|
||||
dut->mode = 0;
|
||||
dut->coeff_in = 0;
|
||||
dut->valid_i = 0;
|
||||
dut->ready_i = 0;
|
||||
|
||||
// Reset: 2 full cycles
|
||||
for (int i = 0; i < 2; i++) posedge(dut);
|
||||
dut->rst_n = 1;
|
||||
|
||||
std::string line;
|
||||
vluint64_t cycle = 0;
|
||||
int vec_count = 0;
|
||||
|
||||
while (std::getline(infile, line)) {
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
|
||||
// Parse: MODE COEFF0 COEFF1 ... COEFF255
|
||||
std::istringstream iss(line);
|
||||
std::string mode_str;
|
||||
if (!(iss >> mode_str)) continue;
|
||||
|
||||
int mode_val = 0;
|
||||
if (mode_str == "I") mode_val = 1;
|
||||
|
||||
// Parse 256 coefficients into vector
|
||||
std::vector<uint16_t> input_coeffs(256);
|
||||
std::string tok;
|
||||
int coeff_idx = 0;
|
||||
while (iss >> tok && coeff_idx < 256) {
|
||||
input_coeffs[coeff_idx] = hex3_to_val(tok);
|
||||
coeff_idx++;
|
||||
}
|
||||
if (coeff_idx != 256) {
|
||||
std::cerr << "ERROR: Expected 256 coefficients, got " << coeff_idx
|
||||
<< " (vec " << vec_count << ")" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set mode
|
||||
dut->mode = mode_val;
|
||||
|
||||
// ---- Load 256 coefficients ----
|
||||
while (!dut->ready_o) {
|
||||
posedge(dut); cycle++;
|
||||
if (cycle > TIMEOUT_CYCLES) goto timeout_err;
|
||||
}
|
||||
for (int i = 0; i < 256; i++) {
|
||||
dut->coeff_in = input_coeffs[i];
|
||||
dut->valid_i = 1;
|
||||
posedge(dut); cycle++;
|
||||
dut->valid_i = 0;
|
||||
if (cycle > TIMEOUT_CYCLES) goto timeout_err;
|
||||
}
|
||||
|
||||
// At this point, the DUT has captured all 256 coeffs and
|
||||
// transitioned to S_COMPUTE_RD (ready_o went low).
|
||||
|
||||
// ---- Wait for valid_o (DUT computing) ----
|
||||
dut->ready_i = 1;
|
||||
while (!dut->valid_o) {
|
||||
posedge(dut);
|
||||
cycle++;
|
||||
if (cycle > TIMEOUT_CYCLES) goto timeout_err;
|
||||
}
|
||||
|
||||
// ---- Read 256 output coefficients ----
|
||||
printf("RESULT: ");
|
||||
for (int i = 0; i < 256; i++) {
|
||||
// Wait for valid_o to be asserted (data is valid NOW)
|
||||
while (!dut->valid_o) {
|
||||
posedge(dut);
|
||||
cycle++;
|
||||
if (cycle > TIMEOUT_CYCLES) goto timeout_err;
|
||||
}
|
||||
|
||||
// Capture coefficient BEFORE consuming posedge
|
||||
uint16_t coeff_val = (uint16_t)(dut->coeff_out & 0xFFF);
|
||||
printf("%s%s", val_to_hex3(coeff_val).c_str(),
|
||||
(i < 255) ? " " : "");
|
||||
|
||||
// Consume this coefficient: posedge with ready_i=1
|
||||
posedge(dut);
|
||||
cycle++;
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
vec_count++;
|
||||
}
|
||||
|
||||
std::cout << "Processed " << vec_count << " vectors" << std::endl;
|
||||
infile.close();
|
||||
delete dut;
|
||||
return (vec_count > 0) ? 0 : 1;
|
||||
|
||||
timeout_err:
|
||||
std::cerr << "ERROR: Timeout at cycle " << cycle
|
||||
<< " (vec " << vec_count << ")" << std::endl;
|
||||
infile.close();
|
||||
delete dut;
|
||||
return 1;
|
||||
}
|
||||
53
sync_rtl/ntt/barrett_mul.v
Normal file
53
sync_rtl/ntt/barrett_mul.v
Normal file
@@ -0,0 +1,53 @@
|
||||
// barrett_mul.v - Barrett modular multiplication (a * b mod Q)
|
||||
//
|
||||
// Computes product = a * b mod Q using Barrett reduction.
|
||||
// Q = 3329, k = floor(2^24 / Q) = 5039
|
||||
//
|
||||
// Pure combinational, single-cycle latency.
|
||||
// All multiplication widths explicitly controlled to avoid Verilog truncation.
|
||||
|
||||
module barrett_mul (
|
||||
input [11:0] a,
|
||||
input [11:0] b,
|
||||
output [11:0] product
|
||||
);
|
||||
localparam Q = 3329;
|
||||
localparam K = 5039; // floor(2^24 / 3329)
|
||||
localparam R = 24;
|
||||
|
||||
// Full product: a * b (both < 3329, product < 11,082,241 < 2^24)
|
||||
// Force 24-bit evaluation by extending operand
|
||||
wire [23:0] p;
|
||||
assign p = {12'd0, a} * b;
|
||||
|
||||
// Extend p to 37 bits for multiplication with K
|
||||
wire [36:0] p_ext;
|
||||
assign p_ext = {13'd0, p};
|
||||
|
||||
// Compute t_shifted = (p * K) >> 24
|
||||
// Use explicit wire for the product to control width
|
||||
/* verilator lint_off UNUSEDSIGNAL */
|
||||
wire [36:0] t_product;
|
||||
/* verilator lint_on UNUSEDSIGNAL */
|
||||
assign t_product = p_ext * K;
|
||||
wire [12:0] t_shifted;
|
||||
assign t_shifted = t_product[36:R];
|
||||
|
||||
// q_approx = t_shifted * Q
|
||||
wire [24:0] q_approx;
|
||||
assign q_approx = t_shifted * Q;
|
||||
|
||||
// r = p - q_approx
|
||||
wire [24:0] r0;
|
||||
assign r0 = {1'b0, p} - q_approx;
|
||||
|
||||
// Conditional subtract Q (at most twice)
|
||||
wire [24:0] r1;
|
||||
assign r1 = (r0 >= Q) ? (r0 - Q) : r0;
|
||||
|
||||
wire [11:0] r2;
|
||||
assign r2 = (r1[11:0] >= Q) ? (r1[11:0] - Q) : r1[11:0];
|
||||
|
||||
assign product = (r1 >= Q) ? r2 : r1[11:0];
|
||||
|
||||
endmodule
|
||||
67
sync_rtl/ntt/butterfly_unit.v
Normal file
67
sync_rtl/ntt/butterfly_unit.v
Normal file
@@ -0,0 +1,67 @@
|
||||
// butterfly_unit.v - Cooley-Tukey / Gentleman-Sande butterfly
|
||||
//
|
||||
// Computes one butterfly operation for NTT or inverse NTT.
|
||||
//
|
||||
// Parameters:
|
||||
// Q = 3329 (prime modulus)
|
||||
//
|
||||
// Forward NTT (mode=0):
|
||||
// t = zeta * b mod Q (via barrett_mul)
|
||||
// a_out = (a + t) mod Q
|
||||
// b_out = (a - t) mod Q
|
||||
//
|
||||
// Inverse NTT (mode=1):
|
||||
// a_out = (a + b) mod Q
|
||||
// diff = (b - a) mod Q (handled as: if b >= a: b-a; else: b-a+Q)
|
||||
// b_out = zeta * diff mod Q (via barrett_mul)
|
||||
//
|
||||
// Pure combinational.
|
||||
|
||||
module butterfly_unit (
|
||||
input [11:0] a,
|
||||
input [11:0] b,
|
||||
input [11:0] zeta,
|
||||
input mode, // 0 = forward NTT, 1 = inverse NTT
|
||||
output [11:0] a_out,
|
||||
output [11:0] b_out
|
||||
);
|
||||
localparam Q = 3329;
|
||||
|
||||
// Barrett modular multiplication: zeta * mul_data mod Q
|
||||
wire [11:0] mul_data; // what to multiply with zeta
|
||||
wire [11:0] mul_result; // result of barrett_mul(zeta, mul_data)
|
||||
|
||||
// Forward: mul_data = b, t = zeta * b mod Q
|
||||
// Inverse: mul_data = (b - a) mod Q positive
|
||||
assign mul_data = (mode == 1'b0) ? b : ((b >= a) ? (b - a) : (b - a + Q));
|
||||
|
||||
barrett_mul u_barrett (
|
||||
.a (zeta),
|
||||
.b (mul_data),
|
||||
.product (mul_result)
|
||||
);
|
||||
|
||||
// ---- a_out computation ----
|
||||
// Forward: a_out = (a + t) mod Q
|
||||
// Inverse: a_out = (a + b) mod Q
|
||||
wire [12:0] a_sum;
|
||||
wire [11:0] add_val;
|
||||
assign add_val = (mode == 1'b0) ? mul_result : b;
|
||||
assign a_sum = {1'b0, a} + {1'b0, add_val};
|
||||
// a_sum - Q produces 13-bit result; we only need lower 12 bits since
|
||||
// a_sum >= Q guarantees the result < Q < 2^12
|
||||
wire [11:0] a_sub_12;
|
||||
assign a_sub_12 = a_sum[11:0] - Q[11:0];
|
||||
wire [11:0] a_result = (a_sum >= Q) ? a_sub_12 : a_sum[11:0];
|
||||
assign a_out = a_result;
|
||||
|
||||
// ---- b_out computation ----
|
||||
// Forward: b_out = (a - t) mod Q → if a >= t: a-t; else: a-t+Q
|
||||
// Inverse: b_out = t (mul_result, which is zeta * (b-a) mod Q)
|
||||
wire [11:0] sub_val;
|
||||
assign sub_val = mul_result;
|
||||
wire [11:0] sub_result;
|
||||
assign sub_result = (a >= sub_val) ? (a - sub_val) : (a - sub_val + Q);
|
||||
assign b_out = (mode == 1'b0) ? sub_result : mul_result;
|
||||
|
||||
endmodule
|
||||
132
sync_rtl/ntt/ntt_core.v
Normal file
132
sync_rtl/ntt/ntt_core.v
Normal file
@@ -0,0 +1,132 @@
|
||||
// ntt_core.v - NTT core with individual coefficient registers
|
||||
//
|
||||
// Uses 256 individual 12-bit registers and generate-based muxing
|
||||
// to avoid any part-select simulation issues.
|
||||
// 3-cycle butterfly: SetAddr -> Read -> Compute+Write
|
||||
|
||||
module ntt_core (
|
||||
input clk, rst_n,
|
||||
input [11:0] coeff_in,
|
||||
input valid_i,
|
||||
output ready_o,
|
||||
input mode,
|
||||
output [11:0] coeff_out,
|
||||
output valid_o,
|
||||
input ready_i,
|
||||
output done_o
|
||||
);
|
||||
localparam N = 256, LAYERS = 7, DW = 12;
|
||||
|
||||
// Individual coefficient registers
|
||||
reg [DW-1:0] cr [0:N-1];
|
||||
integer ci;
|
||||
|
||||
// State machine
|
||||
localparam S_IDLE=3'd0, S_LOAD=3'd1, S_CMP_A=3'd2, S_CMP_B=3'd3,
|
||||
S_CMP_C=3'd4, S_OUTPUT=3'd5, S_DONE=3'd6;
|
||||
reg [2:0] state, next_state;
|
||||
|
||||
reg [7:0] load_cnt, out_cnt;
|
||||
reg [7:0] j, start, layer_len;
|
||||
reg [6:0] zeta_idx;
|
||||
reg [2:0] layer;
|
||||
reg bf_done;
|
||||
|
||||
// Pipeline registers
|
||||
reg [DW-1:0] r_a, r_b;
|
||||
reg [7:0] r_wa, r_wb;
|
||||
|
||||
// Zeta
|
||||
wire [DW-1:0] zeta;
|
||||
zeta_rom u_z (.addr(zeta_idx), .zeta(zeta));
|
||||
|
||||
// Butterfly
|
||||
wire [DW-1:0] bf_a_out, bf_b_out;
|
||||
butterfly_unit u_bf (
|
||||
.a(r_a), .b(r_b), .zeta(zeta), .mode(mode),
|
||||
.a_out(bf_a_out), .b_out(bf_b_out));
|
||||
|
||||
// Output scaling
|
||||
wire [DW-1:0] coeff_scaled;
|
||||
barrett_mul u_scl (.a(cr[out_cnt]), .b(12'd3303), .product(coeff_scaled));
|
||||
assign coeff_out = mode ? coeff_scaled : cr[out_cnt];
|
||||
|
||||
|
||||
assign ready_o = (state == S_IDLE) || (state == S_LOAD);
|
||||
assign valid_o = (state == S_OUTPUT);
|
||||
assign done_o = (state == S_DONE);
|
||||
|
||||
always @* begin
|
||||
next_state = state;
|
||||
case (state)
|
||||
S_IDLE: if (valid_i) next_state = S_LOAD;
|
||||
S_LOAD: if (load_cnt >= 255 && valid_i) next_state = S_CMP_A;
|
||||
S_CMP_A: if (bf_done) next_state = S_OUTPUT; else next_state = S_CMP_B;
|
||||
S_CMP_B: if (bf_done) next_state = S_OUTPUT; else next_state = S_CMP_C;
|
||||
S_CMP_C: if (bf_done) next_state = S_OUTPUT; else next_state = S_CMP_A;
|
||||
S_OUTPUT:if (out_cnt >= 255 && ready_i) next_state = S_DONE;
|
||||
S_DONE: next_state = S_IDLE;
|
||||
default: next_state = S_IDLE;
|
||||
endcase
|
||||
end
|
||||
|
||||
always @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) begin
|
||||
state<=S_IDLE; load_cnt<=0; out_cnt<=0; j<=0; start<=0; layer_len<=0;
|
||||
zeta_idx<=0; layer<=0; bf_done<=0; r_a<=0; r_b<=0; r_wa<=0; r_wb<=0;
|
||||
for (ci=0; ci<N; ci=ci+1) cr[ci] <= 0;
|
||||
end else begin
|
||||
state <= next_state;
|
||||
|
||||
if (state == S_IDLE && valid_i) begin
|
||||
cr[0] <= coeff_in;
|
||||
load_cnt<=1; out_cnt<=0; j<=0; start<=0; layer<=0; bf_done<=0;
|
||||
if (!mode) begin layer_len<=128; zeta_idx<=1; end
|
||||
else begin layer_len<=2; zeta_idx<=127; end
|
||||
end
|
||||
|
||||
if (state == S_LOAD && valid_i) begin
|
||||
cr[load_cnt] <= coeff_in;
|
||||
load_cnt <= load_cnt + 8'd1;
|
||||
end
|
||||
|
||||
// S_CMP_A: set read addresses (j, j+len)
|
||||
if (state == S_CMP_A) begin
|
||||
r_wa <= j;
|
||||
r_wb <= j + layer_len;
|
||||
end
|
||||
|
||||
// S_CMP_B: capture read data
|
||||
if (state == S_CMP_B) begin
|
||||
r_a <= cr[j];
|
||||
r_b <= cr[j + layer_len];
|
||||
end
|
||||
|
||||
// S_CMP_C: write butterfly results, advance counters
|
||||
if (state == S_CMP_C) begin
|
||||
cr[r_wa] <= bf_a_out;
|
||||
cr[r_wb] <= bf_b_out;
|
||||
|
||||
j <= j + 8'd1;
|
||||
if (j + 8'd1 >= start + layer_len) begin
|
||||
if (!mode) zeta_idx <= zeta_idx + 7'd1;
|
||||
else zeta_idx <= zeta_idx - 7'd1;
|
||||
|
||||
if ({1'b0,start} + {1'b0,layer_len} + {1'b0,layer_len} >= 256) begin
|
||||
layer <= layer + 3'd1;
|
||||
layer_len <= mode ? (layer_len<<1) : (layer_len>>1);
|
||||
start <= 0; j <= 0;
|
||||
if (layer + 3'd1 >= LAYERS) bf_done <= 1'b1;
|
||||
end else begin
|
||||
start <= start + layer_len + layer_len;
|
||||
j <= start + layer_len + layer_len;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (state == S_OUTPUT && ready_i)
|
||||
out_cnt <= (out_cnt>=255) ? 0 : (out_cnt+8'd1);
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
19
sync_rtl/ntt/ntt_sync.v
Normal file
19
sync_rtl/ntt/ntt_sync.v
Normal file
@@ -0,0 +1,19 @@
|
||||
module ntt_sync (
|
||||
input clk, rst_n,
|
||||
input [11:0] coeff_in,
|
||||
input valid_i,
|
||||
output ready_o,
|
||||
output [11:0] coeff_out,
|
||||
output valid_o,
|
||||
input ready_i,
|
||||
input mode,
|
||||
output done_o
|
||||
);
|
||||
ntt_core u_ntt_core (
|
||||
.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)
|
||||
);
|
||||
endmodule
|
||||
141
sync_rtl/ntt/zeta_rom.v
Normal file
141
sync_rtl/ntt/zeta_rom.v
Normal file
@@ -0,0 +1,141 @@
|
||||
// zeta_rom.v - Precomputed Zeta ROM for ML-KEM NTT
|
||||
//
|
||||
// Stores 128 bit-reversed roots of unity for Z_q (q=3329).
|
||||
// Address 0..127, each entry is a 12-bit value.
|
||||
// Pure combinational, single continuous assignment.
|
||||
|
||||
module zeta_rom (
|
||||
input [6:0] addr, // 0..127
|
||||
output [11:0] zeta
|
||||
);
|
||||
assign zeta =
|
||||
(addr == 7'd0) ? 12'd1 :
|
||||
(addr == 7'd1) ? 12'd1729 :
|
||||
(addr == 7'd2) ? 12'd2580 :
|
||||
(addr == 7'd3) ? 12'd3289 :
|
||||
(addr == 7'd4) ? 12'd2642 :
|
||||
(addr == 7'd5) ? 12'd630 :
|
||||
(addr == 7'd6) ? 12'd1897 :
|
||||
(addr == 7'd7) ? 12'd848 :
|
||||
(addr == 7'd8) ? 12'd1062 :
|
||||
(addr == 7'd9) ? 12'd1919 :
|
||||
(addr == 7'd10) ? 12'd193 :
|
||||
(addr == 7'd11) ? 12'd797 :
|
||||
(addr == 7'd12) ? 12'd2786 :
|
||||
(addr == 7'd13) ? 12'd3260 :
|
||||
(addr == 7'd14) ? 12'd569 :
|
||||
(addr == 7'd15) ? 12'd1746 :
|
||||
(addr == 7'd16) ? 12'd296 :
|
||||
(addr == 7'd17) ? 12'd2447 :
|
||||
(addr == 7'd18) ? 12'd1339 :
|
||||
(addr == 7'd19) ? 12'd1476 :
|
||||
(addr == 7'd20) ? 12'd3046 :
|
||||
(addr == 7'd21) ? 12'd56 :
|
||||
(addr == 7'd22) ? 12'd2240 :
|
||||
(addr == 7'd23) ? 12'd1333 :
|
||||
(addr == 7'd24) ? 12'd1426 :
|
||||
(addr == 7'd25) ? 12'd2094 :
|
||||
(addr == 7'd26) ? 12'd535 :
|
||||
(addr == 7'd27) ? 12'd2882 :
|
||||
(addr == 7'd28) ? 12'd2393 :
|
||||
(addr == 7'd29) ? 12'd2879 :
|
||||
(addr == 7'd30) ? 12'd1974 :
|
||||
(addr == 7'd31) ? 12'd821 :
|
||||
(addr == 7'd32) ? 12'd289 :
|
||||
(addr == 7'd33) ? 12'd331 :
|
||||
(addr == 7'd34) ? 12'd3253 :
|
||||
(addr == 7'd35) ? 12'd1756 :
|
||||
(addr == 7'd36) ? 12'd1197 :
|
||||
(addr == 7'd37) ? 12'd2304 :
|
||||
(addr == 7'd38) ? 12'd2277 :
|
||||
(addr == 7'd39) ? 12'd2055 :
|
||||
(addr == 7'd40) ? 12'd650 :
|
||||
(addr == 7'd41) ? 12'd1977 :
|
||||
(addr == 7'd42) ? 12'd2513 :
|
||||
(addr == 7'd43) ? 12'd632 :
|
||||
(addr == 7'd44) ? 12'd2865 :
|
||||
(addr == 7'd45) ? 12'd33 :
|
||||
(addr == 7'd46) ? 12'd1320 :
|
||||
(addr == 7'd47) ? 12'd1915 :
|
||||
(addr == 7'd48) ? 12'd2319 :
|
||||
(addr == 7'd49) ? 12'd1435 :
|
||||
(addr == 7'd50) ? 12'd807 :
|
||||
(addr == 7'd51) ? 12'd452 :
|
||||
(addr == 7'd52) ? 12'd1438 :
|
||||
(addr == 7'd53) ? 12'd2868 :
|
||||
(addr == 7'd54) ? 12'd1534 :
|
||||
(addr == 7'd55) ? 12'd2402 :
|
||||
(addr == 7'd56) ? 12'd2647 :
|
||||
(addr == 7'd57) ? 12'd2617 :
|
||||
(addr == 7'd58) ? 12'd1481 :
|
||||
(addr == 7'd59) ? 12'd648 :
|
||||
(addr == 7'd60) ? 12'd2474 :
|
||||
(addr == 7'd61) ? 12'd3110 :
|
||||
(addr == 7'd62) ? 12'd1227 :
|
||||
(addr == 7'd63) ? 12'd910 :
|
||||
(addr == 7'd64) ? 12'd17 :
|
||||
(addr == 7'd65) ? 12'd2761 :
|
||||
(addr == 7'd66) ? 12'd583 :
|
||||
(addr == 7'd67) ? 12'd2649 :
|
||||
(addr == 7'd68) ? 12'd1637 :
|
||||
(addr == 7'd69) ? 12'd723 :
|
||||
(addr == 7'd70) ? 12'd2288 :
|
||||
(addr == 7'd71) ? 12'd1100 :
|
||||
(addr == 7'd72) ? 12'd1409 :
|
||||
(addr == 7'd73) ? 12'd2662 :
|
||||
(addr == 7'd74) ? 12'd3281 :
|
||||
(addr == 7'd75) ? 12'd233 :
|
||||
(addr == 7'd76) ? 12'd756 :
|
||||
(addr == 7'd77) ? 12'd2156 :
|
||||
(addr == 7'd78) ? 12'd3015 :
|
||||
(addr == 7'd79) ? 12'd3050 :
|
||||
(addr == 7'd80) ? 12'd1703 :
|
||||
(addr == 7'd81) ? 12'd1651 :
|
||||
(addr == 7'd82) ? 12'd2789 :
|
||||
(addr == 7'd83) ? 12'd1789 :
|
||||
(addr == 7'd84) ? 12'd1847 :
|
||||
(addr == 7'd85) ? 12'd952 :
|
||||
(addr == 7'd86) ? 12'd1461 :
|
||||
(addr == 7'd87) ? 12'd2687 :
|
||||
(addr == 7'd88) ? 12'd939 :
|
||||
(addr == 7'd89) ? 12'd2308 :
|
||||
(addr == 7'd90) ? 12'd2437 :
|
||||
(addr == 7'd91) ? 12'd2388 :
|
||||
(addr == 7'd92) ? 12'd733 :
|
||||
(addr == 7'd93) ? 12'd2337 :
|
||||
(addr == 7'd94) ? 12'd268 :
|
||||
(addr == 7'd95) ? 12'd641 :
|
||||
(addr == 7'd96) ? 12'd1584 :
|
||||
(addr == 7'd97) ? 12'd2298 :
|
||||
(addr == 7'd98) ? 12'd2037 :
|
||||
(addr == 7'd99) ? 12'd3220 :
|
||||
(addr == 7'd100) ? 12'd375 :
|
||||
(addr == 7'd101) ? 12'd2549 :
|
||||
(addr == 7'd102) ? 12'd2090 :
|
||||
(addr == 7'd103) ? 12'd1645 :
|
||||
(addr == 7'd104) ? 12'd1063 :
|
||||
(addr == 7'd105) ? 12'd319 :
|
||||
(addr == 7'd106) ? 12'd2773 :
|
||||
(addr == 7'd107) ? 12'd757 :
|
||||
(addr == 7'd108) ? 12'd2099 :
|
||||
(addr == 7'd109) ? 12'd561 :
|
||||
(addr == 7'd110) ? 12'd2466 :
|
||||
(addr == 7'd111) ? 12'd2594 :
|
||||
(addr == 7'd112) ? 12'd2804 :
|
||||
(addr == 7'd113) ? 12'd1092 :
|
||||
(addr == 7'd114) ? 12'd403 :
|
||||
(addr == 7'd115) ? 12'd1026 :
|
||||
(addr == 7'd116) ? 12'd1143 :
|
||||
(addr == 7'd117) ? 12'd2150 :
|
||||
(addr == 7'd118) ? 12'd2775 :
|
||||
(addr == 7'd119) ? 12'd886 :
|
||||
(addr == 7'd120) ? 12'd1722 :
|
||||
(addr == 7'd121) ? 12'd1212 :
|
||||
(addr == 7'd122) ? 12'd1874 :
|
||||
(addr == 7'd123) ? 12'd1029 :
|
||||
(addr == 7'd124) ? 12'd2110 :
|
||||
(addr == 7'd125) ? 12'd2935 :
|
||||
(addr == 7'd126) ? 12'd885 :
|
||||
12'd2154;
|
||||
|
||||
endmodule
|
||||
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')
|
||||
36
test_framework/modules/ntt/test_plan.json
Normal file
36
test_framework/modules/ntt/test_plan.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"module": "ntt",
|
||||
"rtl_top": "sync_rtl/ntt/ntt_sync.v",
|
||||
"rtl_deps": [
|
||||
"sync_rtl/ntt/ntt_core.v",
|
||||
"sync_rtl/ntt/butterfly_unit.v",
|
||||
"sync_rtl/ntt/barrett_mul.v",
|
||||
"sync_rtl/ntt/zeta_rom.v"
|
||||
],
|
||||
"tb_cpp": "sync_rtl/ntt/TB/tb_ntt.cpp",
|
||||
"simulator": "verilator",
|
||||
"timeout_s": 300,
|
||||
"cases": [
|
||||
{
|
||||
"id": "forward",
|
||||
"description": "Forward NTT: 256-coeff polynomial -> NTT domain, vs Python NTT()",
|
||||
"params": {"mode": "forward"},
|
||||
"num_vectors": 5,
|
||||
"tolerance": "bit_exact"
|
||||
},
|
||||
{
|
||||
"id": "inverse",
|
||||
"description": "Inverse NTT: 256-coeff NTT-domain -> normal domain, vs Python NTTInverse()",
|
||||
"params": {"mode": "inverse"},
|
||||
"num_vectors": 5,
|
||||
"tolerance": "bit_exact"
|
||||
},
|
||||
{
|
||||
"id": "roundtrip",
|
||||
"description": "Round-trip: NTT(INTT(f)) == f",
|
||||
"params": {"mode": "roundtrip"},
|
||||
"num_vectors": 3,
|
||||
"tolerance": "bit_exact"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user