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
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- STEP14 Part A: parameterized-width
|
||||
// experimental weight prefetch engine.
|
||||
//
|
||||
// SIMULATION-ONLY / EXPLORATORY (same status as ideal_memory_model.v,
|
||||
// STEP11's own EXP-0017/23): establishes the ARCHITECTURAL requirement
|
||||
// (what logical weight-path width removes the fetch-rate bottleneck
|
||||
// EXP-0026/0027 identified) BEFORE committing to any specific real
|
||||
// hardware implementation. Generalizes weight_prefetch_engine.v's own
|
||||
// continuous cross-tile-boundary streaming design (STEP11, unchanged
|
||||
// in spirit) to an arbitrary MEM_DATA_WIDTH instead of the real V1
|
||||
// PSRAM's fixed 16 bits. Byte-lane enables (mem_lb_n/mem_ub_n) are
|
||||
// dropped at this level of abstraction -- not meaningful for a
|
||||
// logical bus wider than 16 bits; the real 16-bit interface (with
|
||||
// lane enables) is reintroduced separately by the A5 packing adapter
|
||||
// (weight_fetch_pack_adapter.v) that connects this engine's logical
|
||||
// wide requests to the REAL, unmodified V1 PSRAM chain.
|
||||
//
|
||||
// WORDS_PER_TILE generalizes to
|
||||
// ceil(DATA_WIDTH*P_IN / MEM_DATA_WIDTH), clamped to a minimum of 1
|
||||
// (a bus wider than one full tile still costs exactly 1 transaction,
|
||||
// with the surplus bits simply unused -- this experiment does not
|
||||
// attempt multi-tile-per-transaction bursting).
|
||||
// ============================================================
|
||||
module weight_prefetch_engine_wide #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter MEM_DATA_WIDTH = 64, // 16, 32, 64, 128 -- the STEP14 Part A sweep parameter
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
parameter CNTW = $clog2(MAX_TILES+1),
|
||||
// ceil(TILE_BITS / MEM_DATA_WIDTH), minimum 1
|
||||
parameter TILE_BITS = DATA_WIDTH*P_IN,
|
||||
parameter WORDS_PER_TILE = (TILE_BITS + MEM_DATA_WIDTH - 1) / MEM_DATA_WIDTH,
|
||||
parameter WIW = $clog2(WORDS_PER_TILE+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire job_active,
|
||||
input wire [ADDR_WIDTH-1:0] w_base, // byte address
|
||||
input wire [15:0] n_tiles,
|
||||
input wire [CNTW-1:0] consumed_count,
|
||||
|
||||
output reg wgt_fill_we,
|
||||
output reg [TIW-1:0] wgt_fill_addr,
|
||||
output reg [DATA_WIDTH*P_IN-1:0] wgt_fill_data,
|
||||
|
||||
output reg [CNTW-1:0] ready_count,
|
||||
|
||||
// ---- logical wide memory port (ideal_memory_model_wide.v) ----
|
||||
output reg mem_req,
|
||||
output reg [ADDR_WIDTH-1:0] mem_addr, // byte address of this transaction's first byte
|
||||
input wire [MEM_DATA_WIDTH-1:0] mem_rdata,
|
||||
input wire mem_ready
|
||||
);
|
||||
|
||||
localparam BYTES_PER_WORD = MEM_DATA_WIDTH/8;
|
||||
// The backing store is packed at the tile's OWN natural byte size
|
||||
// (TILE_BITS/8 = P_IN*DATA_WIDTH/8, e.g. 8 bytes for P_IN=8/
|
||||
// DATA_WIDTH=8), regardless of MEM_DATA_WIDTH. This equals
|
||||
// WORDS_PER_TILE*BYTES_PER_WORD whenever MEM_DATA_WIDTH<=TILE_BITS
|
||||
// (no waste, e.g. 16/32/64-bit busses), but NOT when
|
||||
// MEM_DATA_WIDTH>TILE_BITS (e.g. a 128-bit bus fetching a 64-bit
|
||||
// tile in one transaction, using only its low half) -- using
|
||||
// WORDS_PER_TILE*BYTES_PER_WORD as the inter-tile address stride
|
||||
// in that case would double-count the unused surplus bits as real
|
||||
// address space and skip over the next tile's actual data in the
|
||||
// packed backing store. TILE_BYTES is the correct stride always.
|
||||
localparam TILE_BYTES = TILE_BITS/8;
|
||||
|
||||
reg [CNTW-1:0] fetch_tile;
|
||||
reg [WIW-1:0] fetch_word;
|
||||
reg req_outstanding;
|
||||
reg [WORDS_PER_TILE*MEM_DATA_WIDTH-1:0] tile_buf; // oversized scratch, only low TILE_BITS used
|
||||
|
||||
// Final-word tile assembly, selected at ELABORATION time
|
||||
// (WORDS_PER_TILE is a parameter) via generate -- avoids an
|
||||
// invalid zero/negative-width part-select on tile_buf when
|
||||
// WORDS_PER_TILE==1 (bus wider than one full tile: no "earlier
|
||||
// words" exist at all, the ternary-operator alternative would
|
||||
// still be elaborated structurally by most tools even though
|
||||
// never selected at runtime).
|
||||
wire [TILE_BITS-1:0] final_word_tile_data;
|
||||
generate
|
||||
if (WORDS_PER_TILE == 1) begin : GEN_ASSEMBLE_SINGLE
|
||||
assign final_word_tile_data = mem_rdata[TILE_BITS-1:0];
|
||||
end else begin : GEN_ASSEMBLE_MULTI
|
||||
// Yosys' Verilog frontend rejects a part-select applied
|
||||
// directly to a concatenation ({a,b}[msb:lsb]); Verilator
|
||||
// accepts it, but real synthesis requires an intermediate
|
||||
// signal instead.
|
||||
wire [WORDS_PER_TILE*MEM_DATA_WIDTH-1:0] assembled_full;
|
||||
assign assembled_full = {mem_rdata, tile_buf[(WORDS_PER_TILE-1)*MEM_DATA_WIDTH-1:0]};
|
||||
assign final_word_tile_data = assembled_full[TILE_BITS-1:0];
|
||||
end
|
||||
endgenerate
|
||||
|
||||
wire [31:0] window_limit = {{(32-CNTW){1'b0}}, consumed_count} + PREFETCH_DISTANCE;
|
||||
wire more_to_fetch = job_active &&
|
||||
({{(16-CNTW){1'b0}}, fetch_tile} < n_tiles) &&
|
||||
({{(32-CNTW){1'b0}}, fetch_tile} < window_limit);
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
fetch_tile <= {CNTW{1'b0}};
|
||||
fetch_word <= {WIW{1'b0}};
|
||||
ready_count <= {CNTW{1'b0}};
|
||||
req_outstanding <= 1'b0;
|
||||
mem_req <= 1'b0;
|
||||
wgt_fill_we <= 1'b0;
|
||||
end else begin
|
||||
mem_req <= 1'b0;
|
||||
wgt_fill_we <= 1'b0;
|
||||
|
||||
if (!job_active) begin
|
||||
fetch_tile <= {CNTW{1'b0}};
|
||||
fetch_word <= {WIW{1'b0}};
|
||||
ready_count <= {CNTW{1'b0}};
|
||||
req_outstanding <= 1'b0;
|
||||
end else if (mem_ready && req_outstanding) begin
|
||||
req_outstanding <= 1'b0;
|
||||
tile_buf[fetch_word*MEM_DATA_WIDTH +: MEM_DATA_WIDTH] <= mem_rdata;
|
||||
|
||||
if (fetch_word == WORDS_PER_TILE[WIW-1:0] - 1'b1) begin
|
||||
wgt_fill_we <= 1'b1;
|
||||
wgt_fill_addr <= fetch_tile[TIW-1:0];
|
||||
wgt_fill_data <= final_word_tile_data;
|
||||
ready_count <= ready_count + 1'b1;
|
||||
fetch_tile <= fetch_tile + 1'b1;
|
||||
fetch_word <= {WIW{1'b0}};
|
||||
if (({{(16-CNTW){1'b0}}, fetch_tile + 1'b1} < n_tiles) &&
|
||||
({{(32-CNTW){1'b0}}, fetch_tile + 1'b1} < window_limit)) begin
|
||||
mem_req <= 1'b1;
|
||||
mem_addr <= w_base + (fetch_tile + 1'b1) * TILE_BYTES[CNTW-1:0];
|
||||
req_outstanding <= 1'b1;
|
||||
end
|
||||
end else begin
|
||||
fetch_word <= fetch_word + 1'b1;
|
||||
mem_req <= 1'b1;
|
||||
// next word within the SAME tile (only reached when
|
||||
// WORDS_PER_TILE>1, i.e. MEM_DATA_WIDTH<=TILE_BITS,
|
||||
// where WORDS_PER_TILE*BYTES_PER_WORD==TILE_BYTES
|
||||
// exactly -- no surplus/waste in that regime):
|
||||
// byte offset = fetch_tile*TILE_BYTES + (fetch_word+1)*BYTES_PER_WORD
|
||||
mem_addr <= w_base + fetch_tile*TILE_BYTES[CNTW-1:0] +
|
||||
({{(CNTW-WIW){1'b0}}, fetch_word} + 1'b1) * BYTES_PER_WORD[CNTW-1:0];
|
||||
req_outstanding <= 1'b1;
|
||||
end
|
||||
end else if (!req_outstanding && more_to_fetch) begin
|
||||
mem_req <= 1'b1;
|
||||
mem_addr <= w_base + fetch_tile*TILE_BYTES[CNTW-1:0];
|
||||
req_outstanding <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
Reference in New Issue
Block a user