Files
FPGA-Neural/hardware/v2/nms/rtl/nms_activation_banked.v
T
micheleandClaude Sonnet 5 8e014d8d49 V2.0.0 hardware freeze - single SDRAM
FASE #1 hardware freeze for FPGA-Neural V2, N4/P8, single external
SDRAM (Alliance Memory AS4C4M16SA-6TIN) serving weights, activations,
and results through one physical sdram_controller.v instance. Removes
the PSRAM dependency (hardware/v1/rtl/psram_controller.v +
memory_interface.v) from the V2 physical path entirely -- V1 itself
remains fully unmodified, the golden reference.

New RTL: sdram_unified_backend.v (2-way W/AR arbitration over one
SDRAM controller, real per-byte DQM write masking added to
sdram_controller.v for correct single-byte result writes with no
read-modify-write), nms_neural_multiprocessor_sdram_unified.v (the
frozen top-level). Two real bugs found and fixed via full-system
testing before being accepted (ERR-0023): a deadlock and an off-by-one
data-shift bug in the new arbitration logic.

Real results: N=4 and N=2 D-Stress bit-exact (256/256 neurons), 40
real AUTO REFRESH events interleaved with zero corruption, real
Yosys+nextpnr-ecp5 synthesis/P&R for LFE5U-45F-8CABGA381 (149/245
TRELLIS_IO, a real 45-pin reduction from the prior dual-memory
design). Timing is MARGINAL (1/8 P&R seeds >=80MHz), reported honestly
rather than masked by the best seed.

Real, sourced ball-level pinout for the SDRAM bus + clk/rst (39/149
signals, P&R-verified) using the official Lattice ECP5U-45 pinout CSV
found on disk during this step's own pre-commit review -- corrects an
earlier draft that wrongly assumed no real pinout data was available.

Chip readiness: NO. Real, disclosed blockers remain (no physical host
interface exists yet -- the RTL's own reg_* ports are a 110-pin raw
test-harness bus; clock source/PLL decision; power/configuration
component selection) -- see hardware/v2/docs/{HARDWARE_FREEZE,
CHIP_READINESS,OPEN_ITEMS}.md for the complete, itemized status.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
2026-09-06 13:39:55 +02:00

165 lines
6.7 KiB
Verilog

// ============================================================
// Neural Memory System (NMS) -- STEP4/5 candidate B: BANKED activation
// memory with broadcast-on-same-address and round-robin arbitration.
//
// ONE logical copy of the shared activation vector, striped across
// N_BANKS single-port BRAMs by tile index (bank = tile_idx % N_BANKS).
// Real per-bank round-robin arbitration (same policy validated in
// simulation, EXP-0018): the lowest-index requester currently pointed
// to by that bank's own rotating pointer wins ties; every requester
// wanting the SAME tile index as the winner is broadcast-acked for
// free (one read serves them all).
//
// Two register stages (request -> arbitration decision -> BRAM
// address; BRAM address -> BRAM data -> crossbar mux), register-to-
// register throughout -- deliberately NOT the single-cycle
// combinational hit-detection/broadcast structure that cost
// activation_cache.v its Fmax margin at N_SLOTS=4 (DEC-0016). Request
// accepted at cycle T; ack + data both become valid at cycle T+2.
//
// Real per-bank storage depth is MAX_TILES/N_BANKS -- N_BANKS times
// LESS total on-chip storage than nms_activation_replicated.v's
// N_SLOTS full copies, at the cost of the arbitration/crossbar logic
// below. Both candidates are measured (EXP-0019/DEC-0019), not chosen
// a priori.
// ============================================================
module nms_activation_banked #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter N_SLOTS = 4,
parameter N_BANKS = 4,
parameter MAX_TILES = 16,
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
parameter BDEPTH = (MAX_TILES + N_BANKS - 1) / N_BANKS,
parameter BAW = (BDEPTH <= 1) ? 1 : $clog2(BDEPTH),
parameter SLOTW = (N_SLOTS <= 1) ? 1 : $clog2(N_SLOTS),
parameter BANKW = (N_BANKS <= 1) ? 1 : $clog2(N_BANKS)
)(
input clk,
input rst,
// ---- fill port: one tile write, routed to its own bank ----
input fill_we,
input [TIW-1:0] fill_tile_idx,
input [DATA_WIDTH*P_IN-1:0] fill_data,
// ---- per-slot request/response (2-cycle latency: ack+data valid
// at T+2 for a request presented at T) ----
input [N_SLOTS-1:0] req_valid,
input [N_SLOTS*TIW-1:0] req_tile_idx_flat,
output reg [N_SLOTS-1:0] ack,
output [N_SLOTS*DATA_WIDTH*P_IN-1:0] rd_data_flat
);
integer i, b;
// ---- bank storage (N_BANKS separate 1D arrays -> N_BANKS BRAMs) ----
genvar gb;
wire [DATA_WIDTH*P_IN-1:0] bank_rd_data_reg [0:N_BANKS-1];
reg [BAW-1:0] bank_rd_addr_stage1 [0:N_BANKS-1];
reg bank_rd_en_stage1 [0:N_BANKS-1];
generate
for (gb = 0; gb < N_BANKS; gb = gb + 1) begin : GEN_BANK
reg [DATA_WIDTH*P_IN-1:0] mem [0:BDEPTH-1];
reg [DATA_WIDTH*P_IN-1:0] rd_data_r;
wire this_bank_we = fill_we && ((fill_tile_idx % N_BANKS) == gb);
always @(posedge clk) begin
if (this_bank_we)
mem[fill_tile_idx / N_BANKS] <= fill_data;
if (bank_rd_en_stage1[gb])
rd_data_r <= mem[bank_rd_addr_stage1[gb]];
end
assign bank_rd_data_reg[gb] = rd_data_r;
end
endgenerate
// ---- stage 0 (combinational): per-bank round-robin arbitration +
// broadcast-ack decision, using THIS cycle's req_valid/req_tile_idx ----
reg [TIW-1:0] req_tile_idx [0:N_SLOTS-1];
reg [BANKW-1:0] req_bank [0:N_SLOTS-1];
reg [SLOTW-1:0] rr_ptr [0:N_BANKS-1];
reg win_valid [0:N_BANKS-1];
reg [TIW-1:0] win_tile_idx [0:N_BANKS-1];
reg [SLOTW-1:0] win_first [0:N_BANKS-1];
reg [N_SLOTS-1:0] ack_comb;
reg [BANKW-1:0] slot_bank_comb [0:N_SLOTS-1];
integer scan_i, cand;
always @* begin
for (i = 0; i < N_SLOTS; i = i + 1) begin
req_tile_idx[i] = req_tile_idx_flat[i*TIW +: TIW];
req_bank[i] = req_tile_idx[i] % N_BANKS;
slot_bank_comb[i] = req_bank[i];
end
ack_comb = {N_SLOTS{1'b0}};
for (b = 0; b < N_BANKS; b = b + 1) begin
win_valid[b] = 1'b0;
win_tile_idx[b] = {TIW{1'b0}};
win_first[b] = {SLOTW{1'b0}};
for (scan_i = 0; scan_i < N_SLOTS; scan_i = scan_i + 1) begin
cand = (rr_ptr[b] + scan_i) % N_SLOTS;
if (req_valid[cand] && (req_bank[cand] == b) && !win_valid[b]) begin
win_valid[b] = 1'b1;
win_tile_idx[b] = req_tile_idx[cand];
win_first[b] = cand[SLOTW-1:0];
end
end
if (win_valid[b]) begin
for (i = 0; i < N_SLOTS; i = i + 1) begin
if (req_valid[i] && (req_bank[i] == b) && (req_tile_idx[i] == win_tile_idx[b]))
ack_comb[i] = 1'b1;
end
end
end
end
// ---- stage 1 registers: arbitration decision -> BRAM address,
// plus the per-slot bookkeeping needed to route data back 1 cycle
// later (stage 2) ----
reg [N_SLOTS-1:0] ack_stage1;
reg [BANKW-1:0] slot_bank_stage1 [0:N_SLOTS-1];
always @(posedge clk) begin
if (rst) begin
for (b = 0; b < N_BANKS; b = b + 1) begin
rr_ptr[b] <= {SLOTW{1'b0}};
bank_rd_en_stage1[b] <= 1'b0;
bank_rd_addr_stage1[b] <= {BAW{1'b0}};
end
ack_stage1 <= {N_SLOTS{1'b0}};
ack <= {N_SLOTS{1'b0}};
for (i = 0; i < N_SLOTS; i = i + 1) begin
slot_bank_stage1[i] <= {BANKW{1'b0}};
end
end else begin
for (b = 0; b < N_BANKS; b = b + 1) begin
bank_rd_en_stage1[b] <= win_valid[b];
bank_rd_addr_stage1[b] <= win_tile_idx[b] / N_BANKS;
if (win_valid[b]) rr_ptr[b] <= win_first[b] + 1'b1;
end
ack_stage1 <= ack_comb;
for (i = 0; i < N_SLOTS; i = i + 1)
slot_bank_stage1[i] <= slot_bank_comb[i];
// stage 2: ack becomes valid the cycle the BRAM's own
// registered read output (bank_rd_data_reg) is valid
ack <= ack_stage1;
end
end
// ---- stage 2 (combinational crossbar): route each bank's
// registered read output to whichever slot(s) it was serving,
// using the stage1-registered bank assignment ----
genvar gs;
generate
for (gs = 0; gs < N_SLOTS; gs = gs + 1) begin : GEN_XBAR
assign rd_data_flat[gs*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN] =
bank_rd_data_reg[slot_bank_stage1[gs]];
end
endgenerate
endmodule