// poly_mul_sync.v - Synchronous NTT-domain polynomial multiplier (B2 ping-pong) // // Computes pointwise (Karatsuba-like base-case) multiplication of two // 256-coefficient NTT-domain polynomials. // // Double-buffered (ping-pong) load so the caller can stream the NEXT operation's // 256 A+B pairs into the idle buffer WHILE the current operation's results are // still draining out. This hides the 256-cycle load behind the ~256-cycle // compute/output of the previous multiply: back-to-back multiplies cost ~256 // cycles each instead of 512 (load THEN run). // // Contract (backward compatible with the single-shot testbench): // - ready_o is high whenever a buffer is free to accept a load. A fresh op is // accepted (load_active started) as soon as the first pair arrives. // - Each op streams 256 pairs on coeff_a_in/coeff_b_in (one per accepted cyc). // - When a buffer is full it is handed to the compute engine, which issues one // base-case every two cycles and emits 256 result coeffs on coeff_out via // valid_o/ready_i (c0 then c1 per pair). // - A caller that only ever loads-then-drains (never loads during RUN) sees // the exact same behaviour as before, just with an extra buffer. // // Interface (unchanged ports): // clk, rst_n - Clock, active-low reset // coeff_a_in[11:0]- Polynomial A coefficient (index = pairs accepted this load) // coeff_b_in[11:0]- Polynomial B coefficient // valid_i - Input valid // ready_o - Ready to accept a load pair (a buffer is free) // coeff_out[11:0] - Result coefficient output // valid_o - Output valid // 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 ); // Two coefficient buffers (ping-pong). reg [11:0] mem_A0 [0:255]; reg [11:0] mem_B0 [0:255]; reg [11:0] mem_A1 [0:255]; reg [11:0] mem_B1 [0:255]; // ---- Load side ---- reg ld_active; // currently accepting a load into ld_ptr reg ld_ptr; // FIFO write buffer (toggles each completed load) reg [8:0] load_cnt; // 0..256 pairs written this load reg buf_full [0:1]; // buffer holds a complete (unconsumed) operand set // ---- Compute side ---- reg run_active; // compute engine busy on rn_ptr reg rn_ptr; // FIFO read buffer (toggles each completed run) reg [7:0] issue_cnt; // 0..128 base-cases issued reg [7:0] out_cnt; // 0..255 output coeffs consumed reg issue_gap; // 2-cycle issue spacing reg out_phase; // 0=c0, 1=c1 reg out_valid_r; // Registered basecase inputs/results reg [11:0] bc_a0_reg, bc_a1_reg; reg [11:0] bc_b0_reg, bc_b1_reg; reg [11:0] bc_zeta_reg; reg bc_valid_reg; reg [11:0] c0_reg, c1_reg; // Compute-side memory read (from the buffer being consumed). wire [6:0] issue_k = issue_cnt[6:0]; wire [7:0] addr_even = {issue_k, 1'b0}; wire [7:0] addr_odd = {issue_k, 1'b1}; wire [11:0] mem_a0 = rn_ptr ? mem_A1[addr_even] : mem_A0[addr_even]; wire [11:0] mem_a1 = rn_ptr ? mem_A1[addr_odd] : mem_A0[addr_odd]; wire [11:0] mem_b0 = rn_ptr ? mem_B1[addr_even] : mem_B0[addr_even]; wire [11:0] mem_b1 = rn_ptr ? mem_B1[addr_odd] : mem_B0[addr_odd]; wire [11:0] zeta; poly_mul_zeta_rom u_zeta (.addr(issue_k), .zeta(zeta)); wire [11:0] bc_c0, bc_c1; wire bc_vo; basecase_mul_pipe u_bc ( .clk(clk), .rst_n(rst_n), .valid_i(bc_valid_reg), .a0(bc_a0_reg), .a1(bc_a1_reg), .b0(bc_b0_reg), .b1(bc_b1_reg), .zeta(bc_zeta_reg), .c0(bc_c0), .c1(bc_c1), .valid_o(bc_vo) ); // Accept a load pair whenever the FIFO write buffer (ld_ptr) is free. Once a // load is in progress it always finishes (ready stays high for its 256). assign ready_o = ld_active ? 1'b1 : !buf_full[ld_ptr]; assign valid_o = out_valid_r; assign coeff_out = out_phase ? c1_reg : c0_reg; wire ld_fire = valid_i && ready_o; integer i; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin ld_active <= 1'b0; ld_ptr <= 1'b0; load_cnt <= 9'd0; buf_full[0] <= 1'b0; buf_full[1] <= 1'b0; run_active <= 1'b0; rn_ptr <= 1'b0; issue_cnt <= 8'd0; out_cnt <= 8'd0; issue_gap <= 1'b0; out_phase <= 1'b0; out_valid_r <= 1'b0; bc_a0_reg <= 12'd0; bc_a1_reg <= 12'd0; bc_b0_reg <= 12'd0; bc_b1_reg <= 12'd0; bc_zeta_reg <= 12'd0; bc_valid_reg <= 1'b0; c0_reg <= 12'd0; c1_reg <= 12'd0; for (i = 0; i < 256; i = i + 1) begin mem_A0[i] = 12'd0; mem_B0[i] = 12'd0; mem_A1[i] = 12'd0; mem_B1[i] = 12'd0; end end else begin bc_valid_reg <= 1'b0; // ================= LOAD SIDE (FIFO write into ld_ptr) ================= if (!ld_active) begin if (ld_fire) begin if (!ld_ptr) begin mem_A0[0] <= coeff_a_in; mem_B0[0] <= coeff_b_in; end else begin mem_A1[0] <= coeff_a_in; mem_B1[0] <= coeff_b_in; end load_cnt <= 9'd1; ld_active <= 1'b1; end end else begin if (ld_fire) begin if (!ld_ptr) begin mem_A0[load_cnt[7:0]] <= coeff_a_in; mem_B0[load_cnt[7:0]] <= coeff_b_in; end else begin mem_A1[load_cnt[7:0]] <= coeff_a_in; mem_B1[load_cnt[7:0]] <= coeff_b_in; end if (load_cnt == 9'd255) begin buf_full[ld_ptr] <= 1'b1; // buffer complete ld_ptr <= ~ld_ptr; // FIFO advance ld_active <= 1'b0; load_cnt <= 9'd0; end else begin load_cnt <= load_cnt + 9'd1; end end end // ================= COMPUTE SIDE (FIFO read from rn_ptr) ============= if (!run_active) begin // Start compute on the FIFO head buffer once it is full (and not // still mid-load, which cannot happen for rn_ptr==ld_ptr while // ld_active, since buf_full is set only at load completion). if (buf_full[rn_ptr]) begin run_active <= 1'b1; issue_cnt <= 8'd0; out_cnt <= 8'd0; issue_gap <= 1'b0; out_phase <= 1'b0; out_valid_r <= 1'b0; end end else begin // Issue one base-case every two cycles. if (issue_cnt < 8'd128) begin if (!issue_gap) begin bc_a0_reg <= mem_a0; bc_a1_reg <= mem_a1; bc_b0_reg <= mem_b0; bc_b1_reg <= mem_b1; bc_zeta_reg <= zeta; bc_valid_reg <= 1'b1; issue_cnt <= issue_cnt + 8'd1; issue_gap <= 1'b1; end else begin issue_gap <= 1'b0; end end if (out_valid_r && ready_i) begin out_cnt <= out_cnt + 8'd1; if (out_cnt == 8'd255) begin out_valid_r <= 1'b0; out_phase <= 1'b0; end else if (!out_phase) begin out_phase <= 1'b1; end else begin out_valid_r <= 1'b0; out_phase <= 1'b0; end end if (bc_vo) begin c0_reg <= bc_c0; c1_reg <= bc_c1; out_valid_r <= 1'b1; out_phase <= 1'b0; end // Final output consumed: free this buffer, advance FIFO read ptr. if (out_valid_r && ready_i && out_cnt == 8'd255) begin buf_full[rn_ptr] <= 1'b0; rn_ptr <= ~rn_ptr; run_active <= 1'b0; issue_cnt <= 8'd0; out_cnt <= 8'd0; issue_gap <= 1'b0; out_phase <= 1'b0; out_valid_r <= 1'b0; end end end end endmodule