Phase 2.2: NTT-domain polynomial pointwise multiplication. - basecase_mul.v: degree-1 base-case multiply (c0,c1) with Barrett - poly_mul_zeta_rom.v: 128-entry zeta ROM for PolyMul - poly_mul_sync.v: FSM (IDLE→LOAD 256 cycles→COMPUTE 256 cycles→DONE) Verified: 5/5 vectors bit-exact vs Python PolyMul reference
167 lines
5.4 KiB
Verilog
167 lines
5.4 KiB
Verilog
// poly_mul_sync.v - Synchronous NTT-domain polynomial multiplier
|
||
//
|
||
// Computes pointwise (Karatsuba-like base-case) multiplication of two
|
||
// 256-coefficient NTT-domain polynomials.
|
||
//
|
||
// Operation flow:
|
||
// IDLE → LOAD (256× A+B pairs) → COMP_CALC (read+compute, 1 cycle)
|
||
// → COMP_C0 (output c0) → COMP_C1 (output c1) → DONE → IDLE
|
||
//
|
||
// The LOAD phase accepts both A and B coefficients simultaneously
|
||
// (one pair per cycle) on coeff_a_in/coeff_b_in.
|
||
//
|
||
// The COMPUTE phase outputs the 256 result coefficients one per cycle
|
||
// via valid/ready handshake on coeff_out/valid_o/ready_i.
|
||
//
|
||
// Memory: 256×12-bit register arrays for A and B coefficients.
|
||
//
|
||
// Interface:
|
||
// clk, rst_n - Clock, active-low reset
|
||
// coeff_a_in[11:0]- Polynomial A coefficient input
|
||
// coeff_b_in[11:0]- Polynomial B coefficient input
|
||
// valid_i - Input valid
|
||
// ready_o - Ready to accept input (high in IDLE/LOAD)
|
||
// coeff_out[11:0] - Result coefficient output
|
||
// valid_o - Output valid (high in COMP_C0/COMP_C1)
|
||
// ready_i - Output consumer ready
|
||
|
||
module poly_mul_sync (
|
||
input clk, rst_n,
|
||
input [11:0] coeff_a_in,
|
||
input [11:0] coeff_b_in,
|
||
input valid_i,
|
||
output ready_o,
|
||
output [11:0] coeff_out,
|
||
output valid_o,
|
||
input ready_i
|
||
);
|
||
|
||
// State definitions
|
||
localparam S_IDLE = 3'd0;
|
||
localparam S_LOAD = 3'd1;
|
||
localparam S_COMP_CALC = 3'd2;
|
||
localparam S_COMP_C0 = 3'd3;
|
||
localparam S_COMP_C1 = 3'd4;
|
||
localparam S_DONE = 3'd5;
|
||
|
||
reg [2:0] state, next_state;
|
||
|
||
// Coefficient storage (register arrays)
|
||
reg [11:0] mem_A [0:255];
|
||
reg [11:0] mem_B [0:255];
|
||
|
||
// Counters
|
||
reg [7:0] load_cnt; // 0..256 for loading 256 pairs
|
||
reg [6:0] comp_k; // 0..127, current base-case index
|
||
|
||
// Registered basecase_mul results
|
||
reg [11:0] c0_reg, c1_reg;
|
||
|
||
// Combinational read signals for COMP_CALC
|
||
wire [7:0] addr_even = {comp_k, 1'b0}; // comp_k * 2 (7+1 = 8 bits)
|
||
wire [7:0] addr_odd = {comp_k, 1'b1}; // comp_k * 2 + 1
|
||
wire [11:0] mem_a0 = mem_A[addr_even];
|
||
wire [11:0] mem_a1 = mem_A[addr_odd];
|
||
wire [11:0] mem_b0 = mem_B[addr_even];
|
||
wire [11:0] mem_b1 = mem_B[addr_odd];
|
||
|
||
// Zeta ROM
|
||
wire [11:0] zeta;
|
||
poly_mul_zeta_rom u_zeta (
|
||
.addr (comp_k),
|
||
.zeta (zeta)
|
||
);
|
||
|
||
// Basecase multiply
|
||
wire [11:0] bc_c0, bc_c1;
|
||
basecase_mul u_bc (
|
||
.a0 (mem_a0),
|
||
.a1 (mem_a1),
|
||
.b0 (mem_b0),
|
||
.b1 (mem_b1),
|
||
.zeta(zeta),
|
||
.c0 (bc_c0),
|
||
.c1 (bc_c1)
|
||
);
|
||
|
||
// Output interface
|
||
assign ready_o = (state == S_IDLE) || (state == S_LOAD);
|
||
assign valid_o = (state == S_COMP_C0) || (state == S_COMP_C1);
|
||
assign coeff_out = (state == S_COMP_C0) ? c0_reg : c1_reg;
|
||
|
||
// State transition logic (combinational)
|
||
always @* begin
|
||
next_state = state;
|
||
case (state)
|
||
S_IDLE: if (valid_i && ready_o) next_state = S_LOAD;
|
||
S_LOAD: if (load_cnt >= 255 && valid_i && ready_o)
|
||
next_state = S_COMP_CALC;
|
||
S_COMP_CALC: next_state = S_COMP_C0;
|
||
S_COMP_C0: if (valid_o && ready_i) next_state = S_COMP_C1;
|
||
S_COMP_C1: if (valid_o && ready_i) begin
|
||
if (comp_k >= 127) next_state = S_DONE;
|
||
else next_state = S_COMP_CALC;
|
||
end
|
||
S_DONE: next_state = S_IDLE;
|
||
default: next_state = S_IDLE;
|
||
endcase
|
||
end
|
||
|
||
// Sequential logic
|
||
integer i;
|
||
always @(posedge clk or negedge rst_n) begin
|
||
if (!rst_n) begin
|
||
state <= S_IDLE;
|
||
load_cnt <= 8'd0;
|
||
comp_k <= 7'd0;
|
||
c0_reg <= 12'd0;
|
||
c1_reg <= 12'd0;
|
||
for (i = 0; i < 256; i = i + 1) begin
|
||
mem_A[i] <= 12'd0;
|
||
mem_B[i] <= 12'd0;
|
||
end
|
||
end else begin
|
||
state <= next_state;
|
||
|
||
// ---- LOAD phase ----
|
||
// First coefficient captured on IDLE → LOAD transition
|
||
if (state == S_IDLE && valid_i && ready_o) begin
|
||
mem_A[0] <= coeff_a_in;
|
||
mem_B[0] <= coeff_b_in;
|
||
load_cnt <= 8'd1;
|
||
end
|
||
|
||
// Subsequent coefficients in LOAD state
|
||
if (state == S_LOAD && valid_i && ready_o) begin
|
||
mem_A[load_cnt] <= coeff_a_in;
|
||
mem_B[load_cnt] <= coeff_b_in;
|
||
load_cnt <= load_cnt + 8'd1;
|
||
end
|
||
|
||
// ---- COMPUTE phase ----
|
||
// COMP_CALC: capture basecase_mul results
|
||
if (state == S_COMP_CALC) begin
|
||
c0_reg <= bc_c0;
|
||
c1_reg <= bc_c1;
|
||
end
|
||
|
||
// COMP_C0 → COMP_C1: c0 was consumed, increment comp_k
|
||
if (state == S_COMP_C0 && valid_o && ready_i) begin
|
||
// comp_k stays same, c1 still to output
|
||
end
|
||
|
||
// COMP_C1 → COMP_CALC: c1 was consumed, advance to next pair
|
||
if (state == S_COMP_C1 && valid_o && ready_i) begin
|
||
comp_k <= comp_k + 7'd1;
|
||
end
|
||
|
||
// ---- DONE → IDLE: reset counters ----
|
||
if (state == S_DONE) begin
|
||
load_cnt <= 8'd0;
|
||
comp_k <= 7'd0;
|
||
end
|
||
end
|
||
end
|
||
|
||
endmodule
|