114 lines
4.5 KiB
Verilog
114 lines
4.5 KiB
Verilog
// coeff_bram_4way.v - 4-way conflict-free banked storage for NTT coefficients
|
|
//
|
|
// Replaces the 256x12 LUT register file (reg [11:0] cr [0:255]) with 4 BRAM
|
|
// banks (each 128x12 via sd_bram), removing the large multi-port LUT mux tree.
|
|
//
|
|
// Bank function: bank(a) = { a[0], ^a[7:1] } (bit0 XOR parity of upper 7 bits)
|
|
// local(a) = a[7:1] (7-bit index into the 128-deep bank)
|
|
//
|
|
// This function makes a dual-butterfly's four simultaneous coefficient accesses
|
|
// {j, j+1, j+len, j+1+len} land in four DISTINCT banks for every ML-KEM NTT
|
|
// layer_len in {2,4,...,128}: j is even and len is even, so
|
|
// j -> {0, P }
|
|
// j+1 -> {1, P }
|
|
// j+len -> {0, P'} (flipping bit m>=1 flips the upper-bits parity)
|
|
// j+1+len -> {1, P'}
|
|
// with P != P'. Four distinct banks => 4 conflict-free reads/cycle from four
|
|
// single-read-port banks, and likewise 4 conflict-free writes/cycle. Each bank
|
|
// therefore needs only 1 read + 1 write per cycle (an sd_bram), no ping-pong.
|
|
//
|
|
// Read latency is 1 cycle (sd_bram registered read): present rd_addr_k this
|
|
// cycle, rd_data_k valid next cycle. Writes commit on the presenting cycle.
|
|
//
|
|
// NOTE: the caller must guarantee the four read addresses map to four distinct
|
|
// banks (true for the NTT butterfly pattern) and the four write addresses too.
|
|
// Sequential access (load/output) uses a single port and is always safe.
|
|
|
|
module coeff_bram_4way (
|
|
input clk, rst_n,
|
|
// Four read addresses (global 8-bit); data returns 1 cycle later.
|
|
input [7:0] rd_addr_0, rd_addr_1, rd_addr_2, rd_addr_3,
|
|
output [11:0] rd_data_0, rd_data_1, rd_data_2, rd_data_3,
|
|
// Four write ports.
|
|
input wr_en_0, wr_en_1, wr_en_2, wr_en_3,
|
|
input [7:0] wr_addr_0, wr_addr_1, wr_addr_2, wr_addr_3,
|
|
input [11:0] wr_data_0, wr_data_1, wr_data_2, wr_data_3
|
|
);
|
|
|
|
// Bank id and local index for an address.
|
|
function [1:0] bank_id;
|
|
input [7:0] a;
|
|
bank_id = {a[0], ^a[7:1]};
|
|
endfunction
|
|
|
|
wire [1:0] rb0 = bank_id(rd_addr_0);
|
|
wire [1:0] rb1 = bank_id(rd_addr_1);
|
|
wire [1:0] rb2 = bank_id(rd_addr_2);
|
|
wire [1:0] rb3 = bank_id(rd_addr_3);
|
|
|
|
wire [1:0] wb0 = bank_id(wr_addr_0);
|
|
wire [1:0] wb1 = bank_id(wr_addr_1);
|
|
wire [1:0] wb2 = bank_id(wr_addr_2);
|
|
wire [1:0] wb3 = bank_id(wr_addr_3);
|
|
|
|
// Per-bank read-address crossbar: bank b reads the local index of whichever
|
|
// read port targets it (exactly one, by construction).
|
|
wire [6:0] bank_rd_local [0:3];
|
|
wire [6:0] bank_wr_local [0:3];
|
|
wire bank_wr_en [0:3];
|
|
wire [11:0] bank_wr_data [0:3];
|
|
wire [11:0] bank_rd_data [0:3];
|
|
|
|
genvar b;
|
|
generate
|
|
for (b = 0; b < 4; b = b + 1) begin : gen_xbar
|
|
// read address route (priority: 3>2>1>0; ties impossible for NTT)
|
|
assign bank_rd_local[b] =
|
|
(rb3 == b[1:0]) ? rd_addr_3[7:1] :
|
|
(rb2 == b[1:0]) ? rd_addr_2[7:1] :
|
|
(rb1 == b[1:0]) ? rd_addr_1[7:1] :
|
|
rd_addr_0[7:1];
|
|
// write route
|
|
wire m0 = wr_en_0 && (wb0 == b[1:0]);
|
|
wire m1 = wr_en_1 && (wb1 == b[1:0]);
|
|
wire m2 = wr_en_2 && (wb2 == b[1:0]);
|
|
wire m3 = wr_en_3 && (wb3 == b[1:0]);
|
|
assign bank_wr_en[b] = m0 | m1 | m2 | m3;
|
|
assign bank_wr_local[b] =
|
|
m3 ? wr_addr_3[7:1] :
|
|
m2 ? wr_addr_2[7:1] :
|
|
m1 ? wr_addr_1[7:1] : wr_addr_0[7:1];
|
|
assign bank_wr_data[b] =
|
|
m3 ? wr_data_3 :
|
|
m2 ? wr_data_2 :
|
|
m1 ? wr_data_1 : wr_data_0;
|
|
|
|
sd_bram #(.W(12), .D(128), .A(7)) u_bank (
|
|
.clk(clk),
|
|
.rd_addr(bank_rd_local[b]),
|
|
.rd_data(bank_rd_data[b]),
|
|
.wr_en(bank_wr_en[b]),
|
|
.wr_addr(bank_wr_local[b]),
|
|
.wr_data(bank_wr_data[b])
|
|
);
|
|
end
|
|
endgenerate
|
|
|
|
// Register the read bank-ids to align with sd_bram's 1-cycle read latency,
|
|
// then route each bank's output back to the requesting read port.
|
|
reg [1:0] rb0_r, rb1_r, rb2_r, rb3_r;
|
|
always @(posedge clk or negedge rst_n) begin
|
|
if (!rst_n) begin
|
|
rb0_r <= 2'd0; rb1_r <= 2'd0; rb2_r <= 2'd0; rb3_r <= 2'd0;
|
|
end else begin
|
|
rb0_r <= rb0; rb1_r <= rb1; rb2_r <= rb2; rb3_r <= rb3;
|
|
end
|
|
end
|
|
|
|
assign rd_data_0 = bank_rd_data[rb0_r];
|
|
assign rd_data_1 = bank_rd_data[rb1_r];
|
|
assign rd_data_2 = bank_rd_data[rb2_r];
|
|
assign rd_data_3 = bank_rd_data[rb3_r];
|
|
|
|
endmodule
|