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,69 @@
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- STEP 3: banked activation SRAM
|
||||
// contention model. SIMULATION-ONLY, NEVER SYNTHESIZED.
|
||||
//
|
||||
// Models a single shared activation vector striped across N_BANKS
|
||||
// banks by tile index (bank = tile_idx % N_BANKS). Each bank can serve
|
||||
// ONE DISTINCT address per cycle, but BROADCASTS that address's data
|
||||
// to every requester currently wanting it (a shared-producer, many-
|
||||
// consumer read costs exactly one read, not one per consumer -- see
|
||||
// hardware/v2/nms/rtl.. STEP2's own architecture.log note and the
|
||||
// user's own NMS spec §11). A cycle where two+ requesters mapped to
|
||||
// the same bank want DIFFERENT tile indices serves only one of them
|
||||
// (lowest-index requester wins this round); the others simply retry
|
||||
// next cycle (idempotent -- an ideal SRAM read has no state cost).
|
||||
//
|
||||
// Read latency is modeled as ZERO cycles (grant and data-available
|
||||
// are the same cycle) -- this file isolates the BANK CONTENTION
|
||||
// question specifically, decoupled from backing-store latency, which
|
||||
// STEP1 (hardware/v2/nms/rtl/ideal_memory_model.v, EXP-0017) already
|
||||
// characterized separately. Fill-from-PSRAM is out of scope here too
|
||||
// (the vector is assumed already resident, i.e. steady-state
|
||||
// consumption after prefetch -- the question this file answers is
|
||||
// "does the on-chip organization itself let N_SLOTS scale", not
|
||||
// "how do we hide PSRAM latency" (already answered by STEP1).
|
||||
// ============================================================
|
||||
module ideal_banked_activation #(
|
||||
parameter NREQ = 4,
|
||||
parameter N_BANKS = 2,
|
||||
parameter AW = 32
|
||||
)(
|
||||
input [NREQ-1:0] req_valid,
|
||||
input [NREQ*AW-1:0] req_addr_flat,
|
||||
output reg [NREQ-1:0] ack
|
||||
);
|
||||
integer b, i;
|
||||
reg [AW-1:0] req_addr [0:NREQ-1];
|
||||
reg [AW-1:0] served_addr;
|
||||
reg have_served;
|
||||
integer bank_of_i;
|
||||
|
||||
always @* begin
|
||||
for (i = 0; i < NREQ; i = i + 1)
|
||||
req_addr[i] = req_addr_flat[i*AW +: AW];
|
||||
|
||||
ack = {NREQ{1'b0}};
|
||||
for (b = 0; b < N_BANKS; b = b + 1) begin
|
||||
have_served = 1'b0;
|
||||
served_addr = {AW{1'b0}};
|
||||
// first pass: lowest-index valid requester in this bank sets
|
||||
// the address served this cycle
|
||||
for (i = 0; i < NREQ; i = i + 1) begin
|
||||
bank_of_i = req_addr[i] % N_BANKS;
|
||||
if (req_valid[i] && (bank_of_i == b) && !have_served) begin
|
||||
served_addr = req_addr[i];
|
||||
have_served = 1'b1;
|
||||
end
|
||||
end
|
||||
// second pass: broadcast ack to every requester in this bank
|
||||
// that wants the SAME address (free, one read serves all)
|
||||
if (have_served) begin
|
||||
for (i = 0; i < NREQ; i = i + 1) begin
|
||||
bank_of_i = req_addr[i] % N_BANKS;
|
||||
if (req_valid[i] && (bank_of_i == b) && (req_addr[i] == served_addr))
|
||||
ack[i] = 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,195 @@
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- STEP 1 bandwidth requirement study.
|
||||
//
|
||||
// SIMULATION-ONLY, NEVER SYNTHESIZED, NOT A REAL MEMORY.
|
||||
//
|
||||
// Idealized single-port backing-store model: a shared pipe with two
|
||||
// independently configurable, RUNTIME (not just compile-time)
|
||||
// parameters:
|
||||
// cfg_latency -- fixed cycles from "bytes fully transferred" to
|
||||
// "response visible to the requester" (DRAM-style
|
||||
// round-trip latency, independent of throughput).
|
||||
// cfg_bw_bytes -- aggregate bytes/cycle the shared pipe can drain,
|
||||
// shared across however many TILE_BYTES-sized
|
||||
// transfers are queued (sustained bandwidth).
|
||||
//
|
||||
// Admission is effectively unconstrained (a deep FIFO, req_ready
|
||||
// combinationally follows req_valid) -- the real, modeled constraint
|
||||
// is entirely in the DRAIN stage: each cycle a shared byte budget of
|
||||
// cfg_bw_bytes is applied against the head of the queue, completing
|
||||
// as many whole TILE_BYTES-sized transfers as the budget allows
|
||||
// (zero, one, or several in the same cycle when cfg_bw_bytes is a
|
||||
// multiple of TILE_BYTES) before carrying any leftover partial
|
||||
// progress to the next cycle. This correctly separates BANDWIDTH
|
||||
// (how many bytes/cycle the shared pipe drains, aggregate across all
|
||||
// outstanding requests) from LATENCY (a fixed per-transfer delay
|
||||
// applied after draining, via a FIFO of pending completions -- see
|
||||
// hardware/v2/nms/logs, DEC for the first (broken) revision of this
|
||||
// module: EXP-0017 attempt 1 modeled only ONE transfer in service at
|
||||
// a time regardless of cfg_bw_bytes, which silently capped aggregate
|
||||
// system throughput at 1 transfer/cycle and made utilization collapse
|
||||
// to 1/N_SLOTS at every N_SLOTS>1 config -- a modeling bug, not a real
|
||||
// architectural finding; caught by the finding being suspiciously
|
||||
// exact (1/8, 1/4, 1/2 ...) instead of a physically-motivated curve).
|
||||
//
|
||||
// Requester ordering when several assert req_valid the same cycle is
|
||||
// a fixed, low-index-first tie-break -- not fairness-relevant here
|
||||
// since every requester in this study is symmetric.
|
||||
// ============================================================
|
||||
module ideal_memory_model #(
|
||||
parameter NREQ = 4,
|
||||
parameter TILE_BYTES = 16,
|
||||
parameter QDEPTH = 256, // max total outstanding across all requesters
|
||||
parameter MAX_ITERS = 16, // max queue pops/pushes processed in one cycle
|
||||
parameter TAGW = (NREQ <= 1) ? 1 : $clog2(NREQ)
|
||||
)(
|
||||
input clk,
|
||||
input rst,
|
||||
|
||||
input [15:0] cfg_latency, // cycles, runtime-configurable
|
||||
input [15:0] cfg_bw_bytes, // bytes/cycle, runtime-configurable, min 1
|
||||
|
||||
input [NREQ-1:0] req_valid,
|
||||
output wire [NREQ-1:0] req_ready, // combinational, essentially unconstrained
|
||||
output reg [NREQ-1:0] resp_valid // one-cycle completion pulse per requester
|
||||
);
|
||||
|
||||
// ---- global free-running cycle counter (absolute time base) ----
|
||||
reg [63:0] cycle_count;
|
||||
always @(posedge clk) begin
|
||||
if (rst) cycle_count <= 64'd0;
|
||||
else cycle_count <= cycle_count + 64'd1;
|
||||
end
|
||||
|
||||
wire [15:0] bw_eff = (cfg_bw_bytes == 16'd0) ? 16'd1 : cfg_bw_bytes;
|
||||
|
||||
// Admission is unconstrained (QDEPTH sized generously vs. the real
|
||||
// max outstanding this study ever drives: N_SLOTS*PREFETCH_DEPTH <= 64).
|
||||
assign req_ready = req_valid;
|
||||
|
||||
// ---- queue of admitted-but-not-yet-drained tags ----
|
||||
reg [TAGW-1:0] inq_tag [0:QDEPTH-1];
|
||||
reg [$clog2(QDEPTH+1)-1:0] inq_head, inq_count;
|
||||
|
||||
// ---- currently-draining head item (persists partial progress across cycles) ----
|
||||
reg head_active;
|
||||
reg [TAGW-1:0] head_tag;
|
||||
reg [31:0] head_remain;
|
||||
|
||||
// ---- latency tail FIFO (uniform latency -> strict FIFO completion order) ----
|
||||
reg [TAGW-1:0] lat_tag [0:QDEPTH-1];
|
||||
reg [63:0] lat_finish[0:QDEPTH-1];
|
||||
reg [$clog2(QDEPTH+1)-1:0] lat_head, lat_tail, lat_count;
|
||||
|
||||
integer i, k;
|
||||
|
||||
// ---- scratch (blocking-updated shadow state, committed via NBA at the end) ----
|
||||
reg [$clog2(QDEPTH+1)-1:0] s_inq_head, s_inq_count, s_lat_tail, s_lat_count;
|
||||
reg s_head_active;
|
||||
reg [TAGW-1:0] s_head_tag;
|
||||
reg [31:0] s_head_remain;
|
||||
integer s_budget;
|
||||
reg [TAGW-1:0] complete_tag [0:MAX_ITERS-1];
|
||||
integer n_complete;
|
||||
reg [$clog2(QDEPTH+1)-1:0] s_lat_head;
|
||||
integer n_popped;
|
||||
reg [TAGW-1:0] pop_tag [0:MAX_ITERS-1];
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
inq_head <= 0; inq_count <= 0;
|
||||
head_active <= 1'b0; head_tag <= {TAGW{1'b0}}; head_remain <= 32'd0;
|
||||
lat_head <= 0; lat_tail <= 0; lat_count <= 0;
|
||||
resp_valid <= {NREQ{1'b0}};
|
||||
end else begin
|
||||
// ================= 1. admission: append every asserted
|
||||
// requester this cycle to the queue tail, low-index-first =====
|
||||
s_inq_head = inq_head;
|
||||
s_inq_count = inq_count;
|
||||
// tail position for admission = (inq_head + inq_count) mod QDEPTH,
|
||||
// computed fresh per pushed item below.
|
||||
for (i = 0; i < NREQ; i = i + 1) begin
|
||||
if (req_valid[i]) begin
|
||||
inq_tag[(s_inq_head + s_inq_count) % QDEPTH] <= i[TAGW-1:0];
|
||||
s_inq_count = s_inq_count + 1;
|
||||
end
|
||||
end
|
||||
|
||||
// ================= 2. drain: shared byte budget consumes the
|
||||
// queue head (and the persisted in-progress head item), possibly
|
||||
// completing several TILE_BYTES-sized transfers in one cycle ====
|
||||
s_head_active = head_active;
|
||||
s_head_tag = head_tag;
|
||||
s_head_remain = head_remain;
|
||||
s_budget = bw_eff;
|
||||
n_complete = 0;
|
||||
|
||||
for (k = 0; k < MAX_ITERS; k = k + 1) begin
|
||||
if (!s_head_active && s_inq_count > 0) begin
|
||||
s_head_active = 1'b1;
|
||||
s_head_tag = inq_tag[s_inq_head];
|
||||
s_head_remain = TILE_BYTES;
|
||||
s_inq_head = (s_inq_head + 1) % QDEPTH;
|
||||
s_inq_count = s_inq_count - 1;
|
||||
end
|
||||
if (s_head_active && s_budget > 0) begin
|
||||
if (s_head_remain <= s_budget) begin
|
||||
s_budget = s_budget - s_head_remain;
|
||||
complete_tag[n_complete] = s_head_tag;
|
||||
n_complete = n_complete + 1;
|
||||
s_head_active = 1'b0;
|
||||
s_head_remain = 32'd0;
|
||||
end else begin
|
||||
s_head_remain = s_head_remain - s_budget;
|
||||
s_budget = 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
inq_head <= s_inq_head;
|
||||
inq_count <= s_inq_count;
|
||||
head_active <= s_head_active;
|
||||
head_tag <= s_head_tag;
|
||||
head_remain <= s_head_remain;
|
||||
|
||||
// push every completion from this cycle into the latency tail
|
||||
s_lat_tail = lat_tail;
|
||||
s_lat_count = lat_count;
|
||||
for (i = 0; i < MAX_ITERS; i = i + 1) begin
|
||||
if (i < n_complete) begin
|
||||
lat_tag[(s_lat_tail) % QDEPTH] <= complete_tag[i];
|
||||
lat_finish[(s_lat_tail) % QDEPTH] <= cycle_count + {48'd0, cfg_latency};
|
||||
s_lat_tail = s_lat_tail + 1;
|
||||
end
|
||||
end
|
||||
|
||||
// ================= 3. latency tail: pop every entry whose time
|
||||
// has come (uniform latency -> all due entries are contiguous
|
||||
// at the head, so a bounded scan suffices) =====================
|
||||
s_lat_head = lat_head;
|
||||
n_popped = 0;
|
||||
for (k = 0; k < MAX_ITERS; k = k + 1) begin
|
||||
if ((s_lat_count > 0) && (cycle_count >= lat_finish[s_lat_head])) begin
|
||||
pop_tag[n_popped] = lat_tag[s_lat_head];
|
||||
n_popped = n_popped + 1;
|
||||
s_lat_head = (s_lat_head + 1) % QDEPTH;
|
||||
s_lat_count = s_lat_count - 1;
|
||||
end
|
||||
end
|
||||
lat_head <= s_lat_head;
|
||||
lat_tail <= s_lat_tail % QDEPTH;
|
||||
// final count = old registered count + this cycle's new
|
||||
// completions (not yet poppable -- they only become visible
|
||||
// via lat_tag/lat_finish starting NEXT cycle, since those
|
||||
// array writes above are non-blocking) - this cycle's pops
|
||||
// (n_popped, drawn only from previously-registered entries).
|
||||
lat_count <= lat_count + n_complete - n_popped;
|
||||
|
||||
resp_valid <= {NREQ{1'b0}};
|
||||
for (i = 0; i < MAX_ITERS; i = i + 1) begin
|
||||
if (i < n_popped) resp_valid[pop_tag[i]] <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,164 @@
|
||||
// ============================================================
|
||||
// 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
|
||||
@@ -0,0 +1,184 @@
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- shared Activation fill controller.
|
||||
//
|
||||
// One instance per neural_memory_system (shared across all N_SLOTS),
|
||||
// backing nms_activation_replicated.v's single broadcast-write fill
|
||||
// port. Owns ONE prefetch_engine.v instance (real word-level PSRAM
|
||||
// fetch, DEC-0015 convention, reused verbatim -- it is generic
|
||||
// P_IN-byte-tile fetch logic, not weight-specific despite its name).
|
||||
//
|
||||
// Single-tag design (same honest limitation as the superseded
|
||||
// hardware/v2/rtl/activation_cache.v, DEC-0016): tracks ONE resident
|
||||
// x_base at a time. Refills (resident_count resets to 0, restarts
|
||||
// fetching from tile 0) whenever the lowest-indexed currently-active
|
||||
// slot's own x_base differs from what is resident -- correct always,
|
||||
// but can thrash under interleaved, genuinely-different-x_base
|
||||
// concurrent traffic; not exercised by this project's own realistic
|
||||
// dense-layer workloads (shared-producer dispatch, many slots given
|
||||
// the SAME x_base together).
|
||||
//
|
||||
// resident_count extends to the MAX n_tiles needed by any currently
|
||||
// active slot that shares resident_tag (not just the reference slot
|
||||
// that triggered the refill), so a later-joining slot with a deeper
|
||||
// need is served without a second refill.
|
||||
// ============================================================
|
||||
module nms_activation_fill_ctrl #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter MAX_TILES = 16,
|
||||
// TIW indexes the SRAM fill address (0..MAX_TILES-1); CNTW is for
|
||||
// resident_count, which must represent the VALUE MAX_TILES itself
|
||||
// (e.g. a fully-resident 16-tile vector with MAX_TILES=16) -- one
|
||||
// bit wider than TIW, same distinction/bug as
|
||||
// nms_memory_manager.v's own tile_idx/wgt_fetched (see that file's
|
||||
// header for the real deadlock this caused before the fix).
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// ---- per-slot job status (levels, held while that slot's job is active) ----
|
||||
input wire [N_SLOTS-1:0] job_active,
|
||||
input wire [N_SLOTS*ADDR_WIDTH-1:0] x_base_flat,
|
||||
input wire [N_SLOTS*16-1:0] n_tiles_flat,
|
||||
|
||||
// ---- broadcast status (every slot compares this against its own x_base) ----
|
||||
output reg [ADDR_WIDTH-1:0] resident_tag,
|
||||
output reg [CNTW-1:0] resident_count,
|
||||
|
||||
// ---- fill port into nms_activation_replicated.v ----
|
||||
output wire fill_we,
|
||||
output wire [TIW-1:0] fill_addr,
|
||||
output wire [DATA_WIDTH*P_IN-1:0] fill_data,
|
||||
|
||||
// ---- real word-level PSRAM backend (arbitrated externally) ----
|
||||
output wire mem_req,
|
||||
output wire mem_wr,
|
||||
output wire [ADDR_WIDTH-1:0] mem_addr,
|
||||
output wire [15:0] mem_wdata,
|
||||
output wire mem_lb_n,
|
||||
output wire mem_ub_n,
|
||||
input wire [15:0] mem_rdata,
|
||||
input wire mem_ready
|
||||
);
|
||||
|
||||
integer i;
|
||||
|
||||
// ---- desired x_base: lowest-indexed currently-active slot (fixed
|
||||
// priority -- simple, not fairness-critical here since this only
|
||||
// decides which TAG to chase, not who gets bandwidth) ----
|
||||
reg desired_valid;
|
||||
reg [ADDR_WIDTH-1:0] desired_x_base;
|
||||
reg [15:0] max_n_tiles;
|
||||
|
||||
always @* begin
|
||||
desired_valid = 1'b0;
|
||||
desired_x_base = {ADDR_WIDTH{1'b0}};
|
||||
max_n_tiles = 16'h0;
|
||||
for (i = N_SLOTS-1; i >= 0; i = i - 1) begin
|
||||
if (job_active[i]) begin
|
||||
desired_valid = 1'b1;
|
||||
desired_x_base = x_base_flat[i*ADDR_WIDTH +: ADDR_WIDTH];
|
||||
end
|
||||
end
|
||||
// max n_tiles across every ACTIVE slot that shares resident_tag
|
||||
// (recomputed every cycle so a later-joining deeper-need slot
|
||||
// extends the fill target without a second refill)
|
||||
for (i = 0; i < N_SLOTS; i = i + 1) begin
|
||||
if (job_active[i] && (x_base_flat[i*ADDR_WIDTH +: ADDR_WIDTH] == resident_tag)) begin
|
||||
if (n_tiles_flat[i*16 +: 16] > max_n_tiles)
|
||||
max_n_tiles = n_tiles_flat[i*16 +: 16];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
localparam ST_IDLE = 1'd0;
|
||||
localparam ST_FETCH = 1'd1;
|
||||
reg state;
|
||||
|
||||
reg pf_start;
|
||||
reg [ADDR_WIDTH-1:0] pf_addr;
|
||||
wire pf_busy, pf_done;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] pf_tile;
|
||||
|
||||
prefetch_engine #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH)
|
||||
) u_pf (
|
||||
.clk(clk), .rst(rst),
|
||||
.fetch_start(pf_start), .w_addr(pf_addr),
|
||||
.fetch_busy(pf_busy), .fetch_done(pf_done), .tile_w(pf_tile),
|
||||
.mem_req(mem_req), .mem_wr(mem_wr), .mem_addr(mem_addr), .mem_wdata(mem_wdata),
|
||||
.mem_lb_n(mem_lb_n), .mem_ub_n(mem_ub_n),
|
||||
.mem_rdata(mem_rdata), .mem_ready(mem_ready)
|
||||
);
|
||||
|
||||
reg fill_we_reg;
|
||||
reg [TIW-1:0] fill_addr_reg;
|
||||
reg [DATA_WIDTH*P_IN-1:0] fill_data_reg;
|
||||
assign fill_we = fill_we_reg;
|
||||
assign fill_addr = fill_addr_reg;
|
||||
assign fill_data = fill_data_reg;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
resident_tag <= {ADDR_WIDTH{1'b1}}; // sentinel: matches no real x_base at reset
|
||||
resident_count <= {CNTW{1'b0}};
|
||||
state <= ST_IDLE;
|
||||
pf_start <= 1'b0;
|
||||
fill_we_reg <= 1'b0;
|
||||
end else begin
|
||||
pf_start <= 1'b0;
|
||||
fill_we_reg <= 1'b0;
|
||||
|
||||
// latch a completed fetch into the replicated activation
|
||||
// memory's broadcast fill port
|
||||
if (pf_done) begin
|
||||
fill_we_reg <= 1'b1;
|
||||
fill_addr_reg <= resident_count[TIW-1:0]; // valid: gated < max_n_tiles <= MAX_TILES
|
||||
fill_data_reg <= pf_tile;
|
||||
resident_count <= resident_count + 1'b1;
|
||||
end
|
||||
|
||||
// refill trigger: the reference slot wants a DIFFERENT tag,
|
||||
// and the fetch engine is genuinely idle (never interrupt an
|
||||
// in-flight fetch -- same discipline as memory_manager.v's
|
||||
// own pf_pending guard, ERR-0006). Stays in ST_IDLE (not
|
||||
// ST_FETCH): only updates resident_tag/resident_count here;
|
||||
// the ST_IDLE case below is what actually issues pf_start,
|
||||
// reading the NEW resident_tag starting next cycle -- this
|
||||
// path must NOT itself jump to ST_FETCH without a matching
|
||||
// pf_start, or the engine would sit in ST_FETCH forever
|
||||
// waiting for a pf_done that was never triggered.
|
||||
if (desired_valid && (desired_x_base != resident_tag) && !pf_busy && (state == ST_IDLE)) begin
|
||||
resident_tag <= desired_x_base;
|
||||
resident_count <= {CNTW{1'b0}};
|
||||
end
|
||||
|
||||
case (state)
|
||||
ST_IDLE: begin
|
||||
// stay idle: fetching further tiles for the CURRENT
|
||||
// tag (if any active slot still needs more) is
|
||||
// handled below, symmetric to the refill case.
|
||||
if (!pf_busy && !pf_start && (resident_count < max_n_tiles) &&
|
||||
desired_valid && (desired_x_base == resident_tag)) begin
|
||||
pf_start <= 1'b1;
|
||||
pf_addr <= resident_tag + (resident_count * P_IN[ADDR_WIDTH-1:0]);
|
||||
state <= ST_FETCH;
|
||||
end
|
||||
end
|
||||
ST_FETCH: begin
|
||||
if (pf_done) begin
|
||||
// resident_count already bumped above this cycle;
|
||||
// decide whether more remain once back in IDLE.
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
end
|
||||
default: state <= ST_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,205 @@
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- shared Activation fill controller.
|
||||
//
|
||||
// One instance per neural_memory_system (shared across all N_SLOTS),
|
||||
// backing nms_activation_replicated.v's single broadcast-write fill
|
||||
// port. Owns ONE prefetch_engine.v instance (real word-level PSRAM
|
||||
// fetch, DEC-0015 convention, reused verbatim -- it is generic
|
||||
// P_IN-byte-tile fetch logic, not weight-specific despite its name).
|
||||
//
|
||||
// Single-tag design (same honest limitation as the superseded
|
||||
// hardware/v2/rtl/activation_cache.v, DEC-0016): tracks ONE resident
|
||||
// x_base at a time. Refills (resident_count resets to 0, restarts
|
||||
// fetching from tile 0) whenever the lowest-indexed currently-active
|
||||
// slot's own x_base differs from what is resident -- correct always,
|
||||
// but can thrash under interleaved, genuinely-different-x_base
|
||||
// concurrent traffic; not exercised by this project's own realistic
|
||||
// dense-layer workloads (shared-producer dispatch, many slots given
|
||||
// the SAME x_base together).
|
||||
//
|
||||
// resident_count extends to the MAX n_tiles needed by any currently
|
||||
// active slot that shares resident_tag (not just the reference slot
|
||||
// that triggered the refill), so a later-joining slot with a deeper
|
||||
// need is served without a second refill.
|
||||
// ============================================================
|
||||
module nms_activation_fill_ctrl_v2 #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter MAX_TILES = 16,
|
||||
// TIW indexes the SRAM fill address (0..MAX_TILES-1); CNTW is for
|
||||
// resident_count, which must represent the VALUE MAX_TILES itself
|
||||
// (e.g. a fully-resident 16-tile vector with MAX_TILES=16) -- one
|
||||
// bit wider than TIW, same distinction/bug as
|
||||
// nms_memory_manager.v's own tile_idx/wgt_fetched (see that file's
|
||||
// header for the real deadlock this caused before the fix).
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// ---- per-slot job status (levels, held while that slot's job is active) ----
|
||||
input wire [N_SLOTS-1:0] job_active,
|
||||
input wire [N_SLOTS*ADDR_WIDTH-1:0] x_base_flat,
|
||||
input wire [N_SLOTS*16-1:0] n_tiles_flat,
|
||||
|
||||
// ---- broadcast status (every slot compares this against its own x_base) ----
|
||||
output reg [ADDR_WIDTH-1:0] resident_tag,
|
||||
output reg [CNTW-1:0] resident_count,
|
||||
|
||||
// ---- fill port into nms_activation_replicated.v ----
|
||||
output wire fill_we,
|
||||
output wire [TIW-1:0] fill_addr,
|
||||
output wire [DATA_WIDTH*P_IN-1:0] fill_data,
|
||||
|
||||
// ---- real word-level PSRAM backend (arbitrated externally) ----
|
||||
output wire mem_req,
|
||||
output wire mem_wr,
|
||||
output wire [ADDR_WIDTH-1:0] mem_addr,
|
||||
output wire [15:0] mem_wdata,
|
||||
output wire mem_lb_n,
|
||||
output wire mem_ub_n,
|
||||
input wire [15:0] mem_rdata,
|
||||
input wire mem_ready
|
||||
);
|
||||
|
||||
integer i;
|
||||
|
||||
// ---- desired x_base: lowest-indexed currently-active slot (fixed
|
||||
// priority -- simple, not fairness-critical here since this only
|
||||
// decides which TAG to chase, not who gets bandwidth) ----
|
||||
reg desired_valid;
|
||||
reg [ADDR_WIDTH-1:0] desired_x_base;
|
||||
reg [15:0] max_n_tiles;
|
||||
|
||||
always @* begin
|
||||
desired_valid = 1'b0;
|
||||
desired_x_base = {ADDR_WIDTH{1'b0}};
|
||||
max_n_tiles = 16'h0;
|
||||
for (i = N_SLOTS-1; i >= 0; i = i - 1) begin
|
||||
if (job_active[i]) begin
|
||||
desired_valid = 1'b1;
|
||||
desired_x_base = x_base_flat[i*ADDR_WIDTH +: ADDR_WIDTH];
|
||||
end
|
||||
end
|
||||
// max n_tiles across every ACTIVE slot that shares resident_tag
|
||||
// (recomputed every cycle so a later-joining deeper-need slot
|
||||
// extends the fill target without a second refill)
|
||||
for (i = 0; i < N_SLOTS; i = i + 1) begin
|
||||
if (job_active[i] && (x_base_flat[i*ADDR_WIDTH +: ADDR_WIDTH] == resident_tag)) begin
|
||||
if (n_tiles_flat[i*16 +: 16] > max_n_tiles)
|
||||
max_n_tiles = n_tiles_flat[i*16 +: 16];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ---- STEP14/EXP-0029/DEC-0026: max_n_tiles registered ONE cycle
|
||||
// before use in the resident_count comparison below. Real post-
|
||||
// P&R critical path trace (EXP-0029, N_SLOTS=4, Fmax=55.22MHz
|
||||
// FAIL@80MHz) found max_n_tiles's own computation (a full 16-bit
|
||||
// running-max carry chain, above) feeding DIRECTLY, same-cycle,
|
||||
// into the resident_count < max_n_tiles comparison further below
|
||||
// -- two chained 16-bit magnitude comparisons in ONE combinational
|
||||
// cone, 6.25ns logic + 11.85ns routing, 18.11ns total. Breaking
|
||||
// them into separate clock edges is the minimum fix: this
|
||||
// refill-decision path is evaluated only at tile-fill-trigger
|
||||
// boundaries (gated by pf_busy/state==ST_IDLE below), never on
|
||||
// the real-time-critical per-tile-consumption path (already fully
|
||||
// decoupled by nms_memory_manager_stream.v's own read-ahead
|
||||
// pipeline, STEP13) -- one added cycle of latency here is
|
||||
// functionally free for steady-state throughput. ----
|
||||
reg [15:0] max_n_tiles_reg;
|
||||
always @(posedge clk) begin
|
||||
if (rst) max_n_tiles_reg <= 16'h0;
|
||||
else max_n_tiles_reg <= max_n_tiles;
|
||||
end
|
||||
|
||||
localparam ST_IDLE = 1'd0;
|
||||
localparam ST_FETCH = 1'd1;
|
||||
reg state;
|
||||
|
||||
reg pf_start;
|
||||
reg [ADDR_WIDTH-1:0] pf_addr;
|
||||
wire pf_busy, pf_done;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] pf_tile;
|
||||
|
||||
prefetch_engine #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH)
|
||||
) u_pf (
|
||||
.clk(clk), .rst(rst),
|
||||
.fetch_start(pf_start), .w_addr(pf_addr),
|
||||
.fetch_busy(pf_busy), .fetch_done(pf_done), .tile_w(pf_tile),
|
||||
.mem_req(mem_req), .mem_wr(mem_wr), .mem_addr(mem_addr), .mem_wdata(mem_wdata),
|
||||
.mem_lb_n(mem_lb_n), .mem_ub_n(mem_ub_n),
|
||||
.mem_rdata(mem_rdata), .mem_ready(mem_ready)
|
||||
);
|
||||
|
||||
reg fill_we_reg;
|
||||
reg [TIW-1:0] fill_addr_reg;
|
||||
reg [DATA_WIDTH*P_IN-1:0] fill_data_reg;
|
||||
assign fill_we = fill_we_reg;
|
||||
assign fill_addr = fill_addr_reg;
|
||||
assign fill_data = fill_data_reg;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
resident_tag <= {ADDR_WIDTH{1'b1}}; // sentinel: matches no real x_base at reset
|
||||
resident_count <= {CNTW{1'b0}};
|
||||
state <= ST_IDLE;
|
||||
pf_start <= 1'b0;
|
||||
fill_we_reg <= 1'b0;
|
||||
end else begin
|
||||
pf_start <= 1'b0;
|
||||
fill_we_reg <= 1'b0;
|
||||
|
||||
// latch a completed fetch into the replicated activation
|
||||
// memory's broadcast fill port
|
||||
if (pf_done) begin
|
||||
fill_we_reg <= 1'b1;
|
||||
fill_addr_reg <= resident_count[TIW-1:0]; // valid: gated < max_n_tiles <= MAX_TILES
|
||||
fill_data_reg <= pf_tile;
|
||||
resident_count <= resident_count + 1'b1;
|
||||
end
|
||||
|
||||
// refill trigger: the reference slot wants a DIFFERENT tag,
|
||||
// and the fetch engine is genuinely idle (never interrupt an
|
||||
// in-flight fetch -- same discipline as memory_manager.v's
|
||||
// own pf_pending guard, ERR-0006). Stays in ST_IDLE (not
|
||||
// ST_FETCH): only updates resident_tag/resident_count here;
|
||||
// the ST_IDLE case below is what actually issues pf_start,
|
||||
// reading the NEW resident_tag starting next cycle -- this
|
||||
// path must NOT itself jump to ST_FETCH without a matching
|
||||
// pf_start, or the engine would sit in ST_FETCH forever
|
||||
// waiting for a pf_done that was never triggered.
|
||||
if (desired_valid && (desired_x_base != resident_tag) && !pf_busy && (state == ST_IDLE)) begin
|
||||
resident_tag <= desired_x_base;
|
||||
resident_count <= {CNTW{1'b0}};
|
||||
end
|
||||
|
||||
case (state)
|
||||
ST_IDLE: begin
|
||||
// stay idle: fetching further tiles for the CURRENT
|
||||
// tag (if any active slot still needs more) is
|
||||
// handled below, symmetric to the refill case.
|
||||
if (!pf_busy && !pf_start && (resident_count < max_n_tiles_reg) &&
|
||||
desired_valid && (desired_x_base == resident_tag)) begin
|
||||
pf_start <= 1'b1;
|
||||
pf_addr <= resident_tag + (resident_count * P_IN[ADDR_WIDTH-1:0]);
|
||||
state <= ST_FETCH;
|
||||
end
|
||||
end
|
||||
ST_FETCH: begin
|
||||
if (pf_done) begin
|
||||
// resident_count already bumped above this cycle;
|
||||
// decide whether more remain once back in IDLE.
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
end
|
||||
default: state <= ST_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,223 @@
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- shared Activation fill controller.
|
||||
//
|
||||
// One instance per neural_memory_system (shared across all N_SLOTS),
|
||||
// backing nms_activation_replicated.v's single broadcast-write fill
|
||||
// port. Owns ONE prefetch_engine.v instance (real word-level PSRAM
|
||||
// fetch, DEC-0015 convention, reused verbatim -- it is generic
|
||||
// P_IN-byte-tile fetch logic, not weight-specific despite its name).
|
||||
//
|
||||
// Single-tag design (same honest limitation as the superseded
|
||||
// hardware/v2/rtl/activation_cache.v, DEC-0016): tracks ONE resident
|
||||
// x_base at a time. Refills (resident_count resets to 0, restarts
|
||||
// fetching from tile 0) whenever the lowest-indexed currently-active
|
||||
// slot's own x_base differs from what is resident -- correct always,
|
||||
// but can thrash under interleaved, genuinely-different-x_base
|
||||
// concurrent traffic; not exercised by this project's own realistic
|
||||
// dense-layer workloads (shared-producer dispatch, many slots given
|
||||
// the SAME x_base together).
|
||||
//
|
||||
// resident_count extends to the MAX n_tiles needed by any currently
|
||||
// active slot that shares resident_tag (not just the reference slot
|
||||
// that triggered the refill), so a later-joining slot with a deeper
|
||||
// need is served without a second refill.
|
||||
// ============================================================
|
||||
module nms_activation_fill_ctrl_v3 #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter MAX_TILES = 16,
|
||||
// TIW indexes the SRAM fill address (0..MAX_TILES-1); CNTW is for
|
||||
// resident_count, which must represent the VALUE MAX_TILES itself
|
||||
// (e.g. a fully-resident 16-tile vector with MAX_TILES=16) -- one
|
||||
// bit wider than TIW, same distinction/bug as
|
||||
// nms_memory_manager.v's own tile_idx/wgt_fetched (see that file's
|
||||
// header for the real deadlock this caused before the fix).
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// ---- per-slot job status (levels, held while that slot's job is active) ----
|
||||
input wire [N_SLOTS-1:0] job_active,
|
||||
input wire [N_SLOTS*ADDR_WIDTH-1:0] x_base_flat,
|
||||
input wire [N_SLOTS*16-1:0] n_tiles_flat,
|
||||
|
||||
// ---- broadcast status (every slot compares this against its own x_base) ----
|
||||
output reg [ADDR_WIDTH-1:0] resident_tag,
|
||||
output reg [CNTW-1:0] resident_count,
|
||||
|
||||
// ---- fill port into nms_activation_replicated.v ----
|
||||
output wire fill_we,
|
||||
output wire [TIW-1:0] fill_addr,
|
||||
output wire [DATA_WIDTH*P_IN-1:0] fill_data,
|
||||
|
||||
// ---- real word-level PSRAM backend (arbitrated externally) ----
|
||||
output wire mem_req,
|
||||
output wire mem_wr,
|
||||
output wire [ADDR_WIDTH-1:0] mem_addr,
|
||||
output wire [15:0] mem_wdata,
|
||||
output wire mem_lb_n,
|
||||
output wire mem_ub_n,
|
||||
input wire [15:0] mem_rdata,
|
||||
input wire mem_ready
|
||||
);
|
||||
|
||||
integer i;
|
||||
|
||||
// ---- desired x_base: lowest-indexed currently-active slot (fixed
|
||||
// priority -- simple, not fairness-critical here since this only
|
||||
// decides which TAG to chase, not who gets bandwidth) ----
|
||||
reg desired_valid;
|
||||
reg [ADDR_WIDTH-1:0] desired_x_base;
|
||||
|
||||
always @* begin
|
||||
desired_valid = 1'b0;
|
||||
desired_x_base = {ADDR_WIDTH{1'b0}};
|
||||
for (i = N_SLOTS-1; i >= 0; i = i - 1) begin
|
||||
if (job_active[i]) begin
|
||||
desired_valid = 1'b1;
|
||||
desired_x_base = x_base_flat[i*ADDR_WIDTH +: ADDR_WIDTH];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ---- STEP14/EXP-0029->EXP-0030: max_n_tiles computation split
|
||||
// into TWO pipeline stages, since registering ONLY its final use
|
||||
// (v2, DEC-0026) left the computation ITSELF as the new critical
|
||||
// path (EXP-0030, N=4 Fmax=72.78MHz, still FAIL@80MHz): the
|
||||
// original single-cycle logic mixed, PER SLOT, a 23-bit tag
|
||||
// equality check (x_base_flat[i]==resident_tag) together with an
|
||||
// N_SLOTS-wide SEQUENTIALLY-CHAINED 16-bit running-max fold (each
|
||||
// iteration's update depends on the previous one) -- both
|
||||
// combinational, both in the same cycle as the register that
|
||||
// captures the result.
|
||||
//
|
||||
// Stage 1 (independent per-slot work, no chain dependency between
|
||||
// slots): register a per-slot "counts toward this refill" mask
|
||||
// (job_active[i] && tag-match) and, gated by that mask, each
|
||||
// slot's own n_tiles value (0 if it doesn't count) -- N_SLOTS
|
||||
// independent 23-bit equality checks, no data dependency between
|
||||
// slots, so their combined depth does not grow with N_SLOTS the
|
||||
// way a sequential fold does.
|
||||
// Stage 2 (the actual reduction): fold the REGISTERED, already-
|
||||
// masked per-slot values into max_n_tiles_reg -- still an
|
||||
// N_SLOTS-wide sequential chain (same fold as before), but now
|
||||
// operating alone, without the equality check sharing the same
|
||||
// cycle.
|
||||
reg [15:0] n_tiles_masked [0:N_SLOTS-1];
|
||||
genvar gsi;
|
||||
generate
|
||||
for (gsi = 0; gsi < N_SLOTS; gsi = gsi + 1) begin : GEN_MASK
|
||||
wire slot_counts = job_active[gsi] &&
|
||||
(x_base_flat[gsi*ADDR_WIDTH +: ADDR_WIDTH] == resident_tag);
|
||||
always @(posedge clk) begin
|
||||
if (rst) n_tiles_masked[gsi] <= 16'h0;
|
||||
else n_tiles_masked[gsi] <= slot_counts ? n_tiles_flat[gsi*16 +: 16] : 16'h0;
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
|
||||
reg [15:0] max_n_tiles_reg;
|
||||
integer j;
|
||||
reg [15:0] max_n_tiles_comb;
|
||||
always @* begin
|
||||
max_n_tiles_comb = 16'h0;
|
||||
for (j = 0; j < N_SLOTS; j = j + 1)
|
||||
if (n_tiles_masked[j] > max_n_tiles_comb)
|
||||
max_n_tiles_comb = n_tiles_masked[j];
|
||||
end
|
||||
always @(posedge clk) begin
|
||||
if (rst) max_n_tiles_reg <= 16'h0;
|
||||
else max_n_tiles_reg <= max_n_tiles_comb;
|
||||
end
|
||||
|
||||
localparam ST_IDLE = 1'd0;
|
||||
localparam ST_FETCH = 1'd1;
|
||||
reg state;
|
||||
|
||||
reg pf_start;
|
||||
reg [ADDR_WIDTH-1:0] pf_addr;
|
||||
wire pf_busy, pf_done;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] pf_tile;
|
||||
|
||||
prefetch_engine #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH)
|
||||
) u_pf (
|
||||
.clk(clk), .rst(rst),
|
||||
.fetch_start(pf_start), .w_addr(pf_addr),
|
||||
.fetch_busy(pf_busy), .fetch_done(pf_done), .tile_w(pf_tile),
|
||||
.mem_req(mem_req), .mem_wr(mem_wr), .mem_addr(mem_addr), .mem_wdata(mem_wdata),
|
||||
.mem_lb_n(mem_lb_n), .mem_ub_n(mem_ub_n),
|
||||
.mem_rdata(mem_rdata), .mem_ready(mem_ready)
|
||||
);
|
||||
|
||||
reg fill_we_reg;
|
||||
reg [TIW-1:0] fill_addr_reg;
|
||||
reg [DATA_WIDTH*P_IN-1:0] fill_data_reg;
|
||||
assign fill_we = fill_we_reg;
|
||||
assign fill_addr = fill_addr_reg;
|
||||
assign fill_data = fill_data_reg;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
resident_tag <= {ADDR_WIDTH{1'b1}}; // sentinel: matches no real x_base at reset
|
||||
resident_count <= {CNTW{1'b0}};
|
||||
state <= ST_IDLE;
|
||||
pf_start <= 1'b0;
|
||||
fill_we_reg <= 1'b0;
|
||||
end else begin
|
||||
pf_start <= 1'b0;
|
||||
fill_we_reg <= 1'b0;
|
||||
|
||||
// latch a completed fetch into the replicated activation
|
||||
// memory's broadcast fill port
|
||||
if (pf_done) begin
|
||||
fill_we_reg <= 1'b1;
|
||||
fill_addr_reg <= resident_count[TIW-1:0]; // valid: gated < max_n_tiles <= MAX_TILES
|
||||
fill_data_reg <= pf_tile;
|
||||
resident_count <= resident_count + 1'b1;
|
||||
end
|
||||
|
||||
// refill trigger: the reference slot wants a DIFFERENT tag,
|
||||
// and the fetch engine is genuinely idle (never interrupt an
|
||||
// in-flight fetch -- same discipline as memory_manager.v's
|
||||
// own pf_pending guard, ERR-0006). Stays in ST_IDLE (not
|
||||
// ST_FETCH): only updates resident_tag/resident_count here;
|
||||
// the ST_IDLE case below is what actually issues pf_start,
|
||||
// reading the NEW resident_tag starting next cycle -- this
|
||||
// path must NOT itself jump to ST_FETCH without a matching
|
||||
// pf_start, or the engine would sit in ST_FETCH forever
|
||||
// waiting for a pf_done that was never triggered.
|
||||
if (desired_valid && (desired_x_base != resident_tag) && !pf_busy && (state == ST_IDLE)) begin
|
||||
resident_tag <= desired_x_base;
|
||||
resident_count <= {CNTW{1'b0}};
|
||||
end
|
||||
|
||||
case (state)
|
||||
ST_IDLE: begin
|
||||
// stay idle: fetching further tiles for the CURRENT
|
||||
// tag (if any active slot still needs more) is
|
||||
// handled below, symmetric to the refill case.
|
||||
if (!pf_busy && !pf_start && (resident_count < max_n_tiles_reg) &&
|
||||
desired_valid && (desired_x_base == resident_tag)) begin
|
||||
pf_start <= 1'b1;
|
||||
pf_addr <= resident_tag + (resident_count * P_IN[ADDR_WIDTH-1:0]);
|
||||
state <= ST_FETCH;
|
||||
end
|
||||
end
|
||||
ST_FETCH: begin
|
||||
if (pf_done) begin
|
||||
// resident_count already bumped above this cycle;
|
||||
// decide whether more remain once back in IDLE.
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
end
|
||||
default: state <= ST_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,54 @@
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- STEP4/5 candidate A: REPLICATED
|
||||
// activation memory.
|
||||
//
|
||||
// One full copy of the shared activation vector's tile storage per
|
||||
// slot (N_SLOTS independent single-write/single-read BRAMs). A shared
|
||||
// fill engine broadcasts each filled tile to EVERY copy on the same
|
||||
// cycle (one PSRAM-side write, N_SLOTS on-chip writes) -- after fill,
|
||||
// every slot's own read port is completely private: zero contention,
|
||||
// ever, by construction (no arbitration logic at all on the read
|
||||
// side). Real cost is N_SLOTS x the single-copy storage; this file
|
||||
// exists to MEASURE that real DP16KD/LUT/Fmax cost against Candidate
|
||||
// B (nms_activation_banked.v) rather than assume replication is too
|
||||
// expensive a priori (EXP-0019/DEC-0019).
|
||||
// ============================================================
|
||||
module nms_activation_replicated #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES)
|
||||
)(
|
||||
input clk,
|
||||
input rst,
|
||||
|
||||
// ---- fill port: one write, broadcast to every copy ----
|
||||
input fill_we,
|
||||
input [TIW-1:0] fill_addr,
|
||||
input [DATA_WIDTH*P_IN-1:0] fill_data,
|
||||
|
||||
// ---- per-slot private read port ----
|
||||
input [N_SLOTS-1:0] rd_en,
|
||||
input [N_SLOTS*TIW-1:0] rd_addr_flat,
|
||||
output [N_SLOTS*DATA_WIDTH*P_IN-1:0] rd_data_flat
|
||||
);
|
||||
|
||||
genvar g;
|
||||
generate
|
||||
for (g = 0; g < N_SLOTS; g = g + 1) begin : GEN_COPY
|
||||
reg [DATA_WIDTH*P_IN-1:0] mem [0:MAX_TILES-1];
|
||||
reg [DATA_WIDTH*P_IN-1:0] rd_data_reg;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (fill_we)
|
||||
mem[fill_addr] <= fill_data;
|
||||
if (rd_en[g])
|
||||
rd_data_reg <= mem[rd_addr_flat[g*TIW +: TIW]];
|
||||
end
|
||||
|
||||
assign rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN] = rd_data_reg;
|
||||
end
|
||||
endgenerate
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,260 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP8 full integration, mirrors
|
||||
// hardware/v2/rtl/dataflow_core.v's own scope exactly (M6 Dependency
|
||||
// Manager -> M5 Neural Director -> N_SLOTS x (memory manager + neural
|
||||
// processor)), but replaces the M4 memory_manager.v +
|
||||
// activation_cache.v cluster with the NMS's own decided pieces
|
||||
// (DEC-0019/DEC-0020):
|
||||
// - nms_activation_replicated.v: N_SLOTS private full-vector
|
||||
// activation copies, broadcast-filled by...
|
||||
// - nms_activation_fill_ctrl.v: the shared dedup/fetch controller
|
||||
// (single logical tag, same honest thrash-under-interleaved-
|
||||
// different-x_base limitation as the superseded activation_cache.v)
|
||||
// - nms_weight_packed.v: N_SLOTS private, per-MAC-lane packed weight
|
||||
// copies (never shared, no arbitration needed)
|
||||
// - nms_memory_manager.v: per-slot job FSM, reads directly from the
|
||||
// two SRAMs above instead of double-buffering 2 banks (the whole
|
||||
// vector is resident, not just 2 tiles worth)
|
||||
//
|
||||
// hardware/v2/rtl/dependency_manager.v and neural_director.v are
|
||||
// REUSED VERBATIM, unmodified -- the node-registration and slot-
|
||||
// dispatch protocol did not change at all; only what happens between
|
||||
// "job dispatched to a slot" and "job_done" changed.
|
||||
//
|
||||
// Memory Backend Interface: exposed N_SLOTS+1 wide exactly like
|
||||
// dataflow_core.v (indices [0,N_SLOTS) = per-slot memory managers'
|
||||
// own weight-fetch+result-write port, index [N_SLOTS] = the shared
|
||||
// activation fill controller's own port) -- arbitrated one level up,
|
||||
// reusing hardware/v2/rtl/slot_mem_arbiter.v unchanged.
|
||||
// ================================================================
|
||||
|
||||
module nms_dataflow_core #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
// Must match nms_memory_manager.v's/nms_activation_fill_ctrl.v's
|
||||
// own CNTW exactly -- this top-level wire connecting the two was
|
||||
// left at the narrower TIW after those modules were widened,
|
||||
// silently truncating resident_count's real value (16) back down
|
||||
// to 0 right when it should have reached MAX_TILES, deadlocking
|
||||
// the very last tile of any n_tiles==MAX_TILES job forever (found
|
||||
// via simulation: D-Stress's real 16-tile neurons hung 1 tile
|
||||
// short, act_resident_count visibly reset to 0 the exact cycle it
|
||||
// should have become 16).
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [N_SLOTS:0] slot_mem_req,
|
||||
output wire [N_SLOTS:0] slot_mem_wr,
|
||||
output wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr,
|
||||
output wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata,
|
||||
output wire [N_SLOTS:0] slot_mem_lb_n,
|
||||
output wire [N_SLOTS:0] slot_mem_ub_n,
|
||||
input wire [16*(N_SLOTS+1)-1:0] slot_mem_rdata,
|
||||
input wire [N_SLOTS:0] slot_mem_ready
|
||||
);
|
||||
|
||||
localparam NODE_IDW = $clog2(N_NODES);
|
||||
|
||||
wire dm_ready_valid;
|
||||
wire dm_ready_ready;
|
||||
wire [NODE_IDW-1:0] dm_ready_node_id;
|
||||
wire [ADDR_WIDTH-1:0] dm_ready_x_base, dm_ready_w_base, dm_ready_result_addr;
|
||||
wire [15:0] dm_ready_n_tiles;
|
||||
|
||||
wire dm_producer_done_valid;
|
||||
wire [NODE_IDW-1:0] dm_producer_done_node_id;
|
||||
|
||||
dependency_manager #(
|
||||
.N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .ADDR_WIDTH(ADDR_WIDTH)
|
||||
) u_dep_mgr (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.producer_done_valid(dm_producer_done_valid), .producer_done_node_id(dm_producer_done_node_id),
|
||||
.ready_valid(dm_ready_valid), .ready_ready(dm_ready_ready), .ready_node_id(dm_ready_node_id),
|
||||
.ready_x_base(dm_ready_x_base), .ready_w_base(dm_ready_w_base),
|
||||
.ready_n_tiles(dm_ready_n_tiles), .ready_result_addr(dm_ready_result_addr)
|
||||
);
|
||||
|
||||
wire [15:0] dm_ready_node_id_ext = {{(16-NODE_IDW){1'b0}}, dm_ready_node_id};
|
||||
|
||||
wire [N_SLOTS-1:0] dir_slot_job_start;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] dir_slot_x_base, dir_slot_w_base, dir_slot_result_addr;
|
||||
wire [16*N_SLOTS-1:0] dir_slot_n_tiles, dir_slot_node_id;
|
||||
wire [N_SLOTS-1:0] dir_slot_job_done;
|
||||
wire dir_job_out_done;
|
||||
wire [$clog2(N_SLOTS)-1:0] dir_job_out_slot;
|
||||
wire [3:0] dir_state;
|
||||
wire dir_error;
|
||||
|
||||
neural_director #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
|
||||
) u_director (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_in_valid(dm_ready_valid), .job_in_ready(dm_ready_ready),
|
||||
.job_in_x_base(dm_ready_x_base), .job_in_w_base(dm_ready_w_base),
|
||||
.job_in_n_tiles(dm_ready_n_tiles), .job_in_result_addr(dm_ready_result_addr),
|
||||
.job_in_node_id(dm_ready_node_id_ext),
|
||||
.slot_job_start(dir_slot_job_start), .slot_x_base(dir_slot_x_base), .slot_w_base(dir_slot_w_base),
|
||||
.slot_n_tiles(dir_slot_n_tiles), .slot_result_addr(dir_slot_result_addr),
|
||||
.slot_node_id(dir_slot_node_id), .slot_job_done(dir_slot_job_done),
|
||||
.job_out_done(dir_job_out_done), .job_out_slot(dir_job_out_slot),
|
||||
.dir_state(dir_state), .dir_error(dir_error)
|
||||
);
|
||||
|
||||
wire [15:0] completed_node_id_16 = dir_slot_node_id[dir_job_out_slot*16 +: 16];
|
||||
assign dm_producer_done_valid = dir_job_out_done;
|
||||
assign dm_producer_done_node_id = completed_node_id_16[NODE_IDW-1:0];
|
||||
|
||||
// ---- NMS memory: shared Activation SRAM (replicated) + private
|
||||
// Weight SRAM (packed), per DEC-0019/DEC-0020 ----
|
||||
wire fill_we;
|
||||
wire [TIW-1:0] fill_addr;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] fill_data;
|
||||
wire [ADDR_WIDTH-1:0] act_resident_tag;
|
||||
wire [CNTW-1:0] act_resident_count;
|
||||
|
||||
wire [N_SLOTS-1:0] act_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] act_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] act_rd_data_flat;
|
||||
|
||||
nms_activation_replicated #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_act_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.rd_en(act_rd_en), .rd_addr_flat(act_rd_addr_flat), .rd_data_flat(act_rd_data_flat)
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] job_active;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] job_x_base_flat;
|
||||
wire [16*N_SLOTS-1:0] job_n_tiles_flat;
|
||||
|
||||
nms_activation_fill_ctrl #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES)
|
||||
) u_act_fill (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_active(job_active), .x_base_flat(job_x_base_flat), .n_tiles_flat(job_n_tiles_flat),
|
||||
.resident_tag(act_resident_tag), .resident_count(act_resident_count),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.mem_req(slot_mem_req[N_SLOTS]), .mem_wr(slot_mem_wr[N_SLOTS]),
|
||||
.mem_addr(slot_mem_addr[N_SLOTS*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[N_SLOTS*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[N_SLOTS]), .mem_ub_n(slot_mem_ub_n[N_SLOTS]),
|
||||
.mem_rdata(slot_mem_rdata[N_SLOTS*16 +: 16]), .mem_ready(slot_mem_ready[N_SLOTS])
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] wgt_fill_we;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_fill_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_fill_data_flat;
|
||||
wire [N_SLOTS-1:0] wgt_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_rd_data_flat;
|
||||
|
||||
nms_weight_packed #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_wgt_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(wgt_fill_we), .fill_addr_flat(wgt_fill_addr_flat), .fill_data_flat(wgt_fill_data_flat),
|
||||
.rd_en(wgt_rd_en), .rd_addr_flat(wgt_rd_addr_flat), .rd_data_flat(wgt_rd_data_flat)
|
||||
);
|
||||
|
||||
genvar g;
|
||||
generate
|
||||
for (g = 0; g < N_SLOTS; g = g + 1) begin : GEN_SLOT
|
||||
|
||||
wire mm_operand_valid, mm_operand_ready;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] mm_input_data, mm_weight_data;
|
||||
wire mm_tile_last;
|
||||
wire mm_result_valid, mm_result_ready;
|
||||
wire signed [DATA_WIDTH-1:0] mm_result_data;
|
||||
|
||||
nms_memory_manager #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES)
|
||||
) u_mm (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_start(dir_slot_job_start[g]),
|
||||
.x_base(dir_slot_x_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.w_base(dir_slot_w_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.n_tiles(dir_slot_n_tiles[g*16 +: 16]),
|
||||
.result_addr(dir_slot_result_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_done(dir_slot_job_done[g]),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(mm_result_valid), .result_ready(mm_result_ready), .result_data(mm_result_data),
|
||||
.job_active(job_active[g]),
|
||||
.job_x_base(job_x_base_flat[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_n_tiles(job_n_tiles_flat[g*16 +: 16]),
|
||||
.act_resident_tag(act_resident_tag), .act_resident_count(act_resident_count),
|
||||
.act_rd_en(act_rd_en[g]),
|
||||
.act_rd_addr(act_rd_addr_flat[g*TIW +: TIW]),
|
||||
.act_rd_data(act_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_fill_we(wgt_fill_we[g]),
|
||||
.wgt_fill_addr(wgt_fill_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_fill_data(wgt_fill_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_rd_en(wgt_rd_en[g]),
|
||||
.wgt_rd_addr(wgt_rd_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_rd_data(wgt_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.mem_req(slot_mem_req[g]), .mem_wr(slot_mem_wr[g]),
|
||||
.mem_addr(slot_mem_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[g*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[g]), .mem_ub_n(slot_mem_ub_n[g]),
|
||||
.mem_rdata(slot_mem_rdata[g*16 +: 16]), .mem_ready(slot_mem_ready[g])
|
||||
);
|
||||
|
||||
reg job_valid_np;
|
||||
wire job_ready_np;
|
||||
wire result_valid_np;
|
||||
wire signed [DATA_WIDTH-1:0] result_data_np;
|
||||
wire [3:0] np_state;
|
||||
wire np_error;
|
||||
|
||||
neural_processor #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH)
|
||||
) u_np (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_valid(job_valid_np), .job_ready(job_ready_np),
|
||||
.job_node_id(16'h0), .job_bias(8'sd0), .job_activation(2'd1),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(result_valid_np), .result_ready(mm_result_ready),
|
||||
.result_data(result_data_np), .result_node_id(),
|
||||
.np_state(np_state), .np_error(np_error)
|
||||
);
|
||||
assign mm_result_valid = result_valid_np;
|
||||
assign mm_result_data = result_data_np;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) job_valid_np <= 1'b0;
|
||||
else if (dir_slot_job_start[g]) job_valid_np <= 1'b1;
|
||||
else if (job_valid_np && job_ready_np) job_valid_np <= 1'b0;
|
||||
end
|
||||
|
||||
end
|
||||
endgenerate
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,262 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP8 full integration, mirrors
|
||||
// hardware/v2/rtl/dataflow_core.v's own scope exactly (M6 Dependency
|
||||
// Manager -> M5 Neural Director -> N_SLOTS x (memory manager + neural
|
||||
// processor)), but replaces the M4 memory_manager.v +
|
||||
// activation_cache.v cluster with the NMS's own decided pieces
|
||||
// (DEC-0019/DEC-0020):
|
||||
// - nms_activation_replicated.v: N_SLOTS private full-vector
|
||||
// activation copies, broadcast-filled by...
|
||||
// - nms_activation_fill_ctrl.v: the shared dedup/fetch controller
|
||||
// (single logical tag, same honest thrash-under-interleaved-
|
||||
// different-x_base limitation as the superseded activation_cache.v)
|
||||
// - nms_weight_packed.v: N_SLOTS private, per-MAC-lane packed weight
|
||||
// copies (never shared, no arbitration needed)
|
||||
// - nms_memory_manager.v: per-slot job FSM, reads directly from the
|
||||
// two SRAMs above instead of double-buffering 2 banks (the whole
|
||||
// vector is resident, not just 2 tiles worth)
|
||||
//
|
||||
// hardware/v2/rtl/dependency_manager.v and neural_director.v are
|
||||
// REUSED VERBATIM, unmodified -- the node-registration and slot-
|
||||
// dispatch protocol did not change at all; only what happens between
|
||||
// "job dispatched to a slot" and "job_done" changed.
|
||||
//
|
||||
// Memory Backend Interface: exposed N_SLOTS+1 wide exactly like
|
||||
// dataflow_core.v (indices [0,N_SLOTS) = per-slot memory managers'
|
||||
// own weight-fetch+result-write port, index [N_SLOTS] = the shared
|
||||
// activation fill controller's own port) -- arbitrated one level up,
|
||||
// reusing hardware/v2/rtl/slot_mem_arbiter.v unchanged.
|
||||
// ================================================================
|
||||
|
||||
module nms_dataflow_core_actfix #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
// Must match nms_memory_manager.v's/nms_activation_fill_ctrl.v's
|
||||
// own CNTW exactly -- this top-level wire connecting the two was
|
||||
// left at the narrower TIW after those modules were widened,
|
||||
// silently truncating resident_count's real value (16) back down
|
||||
// to 0 right when it should have reached MAX_TILES, deadlocking
|
||||
// the very last tile of any n_tiles==MAX_TILES job forever (found
|
||||
// via simulation: D-Stress's real 16-tile neurons hung 1 tile
|
||||
// short, act_resident_count visibly reset to 0 the exact cycle it
|
||||
// should have become 16).
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [N_SLOTS:0] slot_mem_req,
|
||||
output wire [N_SLOTS:0] slot_mem_wr,
|
||||
output wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr,
|
||||
output wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata,
|
||||
output wire [N_SLOTS:0] slot_mem_lb_n,
|
||||
output wire [N_SLOTS:0] slot_mem_ub_n,
|
||||
input wire [16*(N_SLOTS+1)-1:0] slot_mem_rdata,
|
||||
input wire [N_SLOTS:0] slot_mem_ready
|
||||
);
|
||||
|
||||
localparam NODE_IDW = $clog2(N_NODES);
|
||||
|
||||
wire dm_ready_valid;
|
||||
wire dm_ready_ready;
|
||||
wire [NODE_IDW-1:0] dm_ready_node_id;
|
||||
wire [ADDR_WIDTH-1:0] dm_ready_x_base, dm_ready_w_base, dm_ready_result_addr;
|
||||
wire [15:0] dm_ready_n_tiles;
|
||||
|
||||
wire dm_producer_done_valid;
|
||||
wire [NODE_IDW-1:0] dm_producer_done_node_id;
|
||||
|
||||
dependency_manager #(
|
||||
.N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .ADDR_WIDTH(ADDR_WIDTH)
|
||||
) u_dep_mgr (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.producer_done_valid(dm_producer_done_valid), .producer_done_node_id(dm_producer_done_node_id),
|
||||
.ready_valid(dm_ready_valid), .ready_ready(dm_ready_ready), .ready_node_id(dm_ready_node_id),
|
||||
.ready_x_base(dm_ready_x_base), .ready_w_base(dm_ready_w_base),
|
||||
.ready_n_tiles(dm_ready_n_tiles), .ready_result_addr(dm_ready_result_addr)
|
||||
);
|
||||
|
||||
wire [15:0] dm_ready_node_id_ext = {{(16-NODE_IDW){1'b0}}, dm_ready_node_id};
|
||||
|
||||
wire [N_SLOTS-1:0] dir_slot_job_start;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] dir_slot_x_base, dir_slot_w_base, dir_slot_result_addr;
|
||||
wire [16*N_SLOTS-1:0] dir_slot_n_tiles, dir_slot_node_id;
|
||||
wire [N_SLOTS-1:0] dir_slot_job_done;
|
||||
wire dir_job_out_done;
|
||||
wire [$clog2(N_SLOTS)-1:0] dir_job_out_slot;
|
||||
wire [3:0] dir_state;
|
||||
wire dir_error;
|
||||
|
||||
neural_director #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
|
||||
) u_director (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_in_valid(dm_ready_valid), .job_in_ready(dm_ready_ready),
|
||||
.job_in_x_base(dm_ready_x_base), .job_in_w_base(dm_ready_w_base),
|
||||
.job_in_n_tiles(dm_ready_n_tiles), .job_in_result_addr(dm_ready_result_addr),
|
||||
.job_in_node_id(dm_ready_node_id_ext),
|
||||
.slot_job_start(dir_slot_job_start), .slot_x_base(dir_slot_x_base), .slot_w_base(dir_slot_w_base),
|
||||
.slot_n_tiles(dir_slot_n_tiles), .slot_result_addr(dir_slot_result_addr),
|
||||
.slot_node_id(dir_slot_node_id), .slot_job_done(dir_slot_job_done),
|
||||
.job_out_done(dir_job_out_done), .job_out_slot(dir_job_out_slot),
|
||||
.dir_state(dir_state), .dir_error(dir_error)
|
||||
);
|
||||
|
||||
wire [15:0] completed_node_id_16 = dir_slot_node_id[dir_job_out_slot*16 +: 16];
|
||||
assign dm_producer_done_valid = dir_job_out_done;
|
||||
assign dm_producer_done_node_id = completed_node_id_16[NODE_IDW-1:0];
|
||||
|
||||
// ---- NMS memory: shared Activation SRAM (replicated) + private
|
||||
// Weight SRAM (packed), per DEC-0019/DEC-0020 ----
|
||||
wire fill_we;
|
||||
wire [TIW-1:0] fill_addr;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] fill_data;
|
||||
wire [ADDR_WIDTH-1:0] act_resident_tag;
|
||||
wire [CNTW-1:0] act_resident_count;
|
||||
|
||||
wire [N_SLOTS-1:0] act_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] act_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] act_rd_data_flat;
|
||||
|
||||
nms_activation_replicated #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_act_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.rd_en(act_rd_en), .rd_addr_flat(act_rd_addr_flat), .rd_data_flat(act_rd_data_flat)
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] job_active;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] job_x_base_flat;
|
||||
wire [16*N_SLOTS-1:0] job_n_tiles_flat;
|
||||
|
||||
nms_activation_fill_ctrl_v2 #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES)
|
||||
) u_act_fill (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_active(job_active), .x_base_flat(job_x_base_flat), .n_tiles_flat(job_n_tiles_flat),
|
||||
.resident_tag(act_resident_tag), .resident_count(act_resident_count),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.mem_req(slot_mem_req[N_SLOTS]), .mem_wr(slot_mem_wr[N_SLOTS]),
|
||||
.mem_addr(slot_mem_addr[N_SLOTS*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[N_SLOTS*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[N_SLOTS]), .mem_ub_n(slot_mem_ub_n[N_SLOTS]),
|
||||
.mem_rdata(slot_mem_rdata[N_SLOTS*16 +: 16]), .mem_ready(slot_mem_ready[N_SLOTS])
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] wgt_fill_we;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_fill_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_fill_data_flat;
|
||||
wire [N_SLOTS-1:0] wgt_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_rd_data_flat;
|
||||
|
||||
nms_weight_packed #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_wgt_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(wgt_fill_we), .fill_addr_flat(wgt_fill_addr_flat), .fill_data_flat(wgt_fill_data_flat),
|
||||
.rd_en(wgt_rd_en), .rd_addr_flat(wgt_rd_addr_flat), .rd_data_flat(wgt_rd_data_flat)
|
||||
);
|
||||
|
||||
genvar g;
|
||||
generate
|
||||
for (g = 0; g < N_SLOTS; g = g + 1) begin : GEN_SLOT
|
||||
|
||||
wire mm_operand_valid, mm_operand_ready;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] mm_input_data, mm_weight_data;
|
||||
wire mm_tile_last;
|
||||
wire mm_result_valid, mm_result_ready;
|
||||
wire signed [DATA_WIDTH-1:0] mm_result_data;
|
||||
|
||||
nms_memory_manager_stream #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES),
|
||||
.PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_mm (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_start(dir_slot_job_start[g]),
|
||||
.x_base(dir_slot_x_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.w_base(dir_slot_w_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.n_tiles(dir_slot_n_tiles[g*16 +: 16]),
|
||||
.result_addr(dir_slot_result_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_done(dir_slot_job_done[g]),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(mm_result_valid), .result_ready(mm_result_ready), .result_data(mm_result_data),
|
||||
.job_active(job_active[g]),
|
||||
.job_x_base(job_x_base_flat[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_n_tiles(job_n_tiles_flat[g*16 +: 16]),
|
||||
.act_resident_tag(act_resident_tag), .act_resident_count(act_resident_count),
|
||||
.act_rd_en(act_rd_en[g]),
|
||||
.act_rd_addr(act_rd_addr_flat[g*TIW +: TIW]),
|
||||
.act_rd_data(act_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_fill_we(wgt_fill_we[g]),
|
||||
.wgt_fill_addr(wgt_fill_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_fill_data(wgt_fill_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_rd_en(wgt_rd_en[g]),
|
||||
.wgt_rd_addr(wgt_rd_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_rd_data(wgt_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.mem_req(slot_mem_req[g]), .mem_wr(slot_mem_wr[g]),
|
||||
.mem_addr(slot_mem_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[g*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[g]), .mem_ub_n(slot_mem_ub_n[g]),
|
||||
.mem_rdata(slot_mem_rdata[g*16 +: 16]), .mem_ready(slot_mem_ready[g])
|
||||
);
|
||||
|
||||
reg job_valid_np;
|
||||
wire job_ready_np;
|
||||
wire result_valid_np;
|
||||
wire signed [DATA_WIDTH-1:0] result_data_np;
|
||||
wire [3:0] np_state;
|
||||
wire np_error;
|
||||
|
||||
neural_processor #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH)
|
||||
) u_np (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_valid(job_valid_np), .job_ready(job_ready_np),
|
||||
.job_node_id(16'h0), .job_bias(8'sd0), .job_activation(2'd1),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(result_valid_np), .result_ready(mm_result_ready),
|
||||
.result_data(result_data_np), .result_node_id(),
|
||||
.np_state(np_state), .np_error(np_error)
|
||||
);
|
||||
assign mm_result_valid = result_valid_np;
|
||||
assign mm_result_data = result_data_np;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) job_valid_np <= 1'b0;
|
||||
else if (dir_slot_job_start[g]) job_valid_np <= 1'b1;
|
||||
else if (job_valid_np && job_ready_np) job_valid_np <= 1'b0;
|
||||
end
|
||||
|
||||
end
|
||||
endgenerate
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,262 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP8 full integration, mirrors
|
||||
// hardware/v2/rtl/dataflow_core.v's own scope exactly (M6 Dependency
|
||||
// Manager -> M5 Neural Director -> N_SLOTS x (memory manager + neural
|
||||
// processor)), but replaces the M4 memory_manager.v +
|
||||
// activation_cache.v cluster with the NMS's own decided pieces
|
||||
// (DEC-0019/DEC-0020):
|
||||
// - nms_activation_replicated.v: N_SLOTS private full-vector
|
||||
// activation copies, broadcast-filled by...
|
||||
// - nms_activation_fill_ctrl.v: the shared dedup/fetch controller
|
||||
// (single logical tag, same honest thrash-under-interleaved-
|
||||
// different-x_base limitation as the superseded activation_cache.v)
|
||||
// - nms_weight_packed.v: N_SLOTS private, per-MAC-lane packed weight
|
||||
// copies (never shared, no arbitration needed)
|
||||
// - nms_memory_manager.v: per-slot job FSM, reads directly from the
|
||||
// two SRAMs above instead of double-buffering 2 banks (the whole
|
||||
// vector is resident, not just 2 tiles worth)
|
||||
//
|
||||
// hardware/v2/rtl/dependency_manager.v and neural_director.v are
|
||||
// REUSED VERBATIM, unmodified -- the node-registration and slot-
|
||||
// dispatch protocol did not change at all; only what happens between
|
||||
// "job dispatched to a slot" and "job_done" changed.
|
||||
//
|
||||
// Memory Backend Interface: exposed N_SLOTS+1 wide exactly like
|
||||
// dataflow_core.v (indices [0,N_SLOTS) = per-slot memory managers'
|
||||
// own weight-fetch+result-write port, index [N_SLOTS] = the shared
|
||||
// activation fill controller's own port) -- arbitrated one level up,
|
||||
// reusing hardware/v2/rtl/slot_mem_arbiter.v unchanged.
|
||||
// ================================================================
|
||||
|
||||
module nms_dataflow_core_actfix2 #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
// Must match nms_memory_manager.v's/nms_activation_fill_ctrl.v's
|
||||
// own CNTW exactly -- this top-level wire connecting the two was
|
||||
// left at the narrower TIW after those modules were widened,
|
||||
// silently truncating resident_count's real value (16) back down
|
||||
// to 0 right when it should have reached MAX_TILES, deadlocking
|
||||
// the very last tile of any n_tiles==MAX_TILES job forever (found
|
||||
// via simulation: D-Stress's real 16-tile neurons hung 1 tile
|
||||
// short, act_resident_count visibly reset to 0 the exact cycle it
|
||||
// should have become 16).
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [N_SLOTS:0] slot_mem_req,
|
||||
output wire [N_SLOTS:0] slot_mem_wr,
|
||||
output wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr,
|
||||
output wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata,
|
||||
output wire [N_SLOTS:0] slot_mem_lb_n,
|
||||
output wire [N_SLOTS:0] slot_mem_ub_n,
|
||||
input wire [16*(N_SLOTS+1)-1:0] slot_mem_rdata,
|
||||
input wire [N_SLOTS:0] slot_mem_ready
|
||||
);
|
||||
|
||||
localparam NODE_IDW = $clog2(N_NODES);
|
||||
|
||||
wire dm_ready_valid;
|
||||
wire dm_ready_ready;
|
||||
wire [NODE_IDW-1:0] dm_ready_node_id;
|
||||
wire [ADDR_WIDTH-1:0] dm_ready_x_base, dm_ready_w_base, dm_ready_result_addr;
|
||||
wire [15:0] dm_ready_n_tiles;
|
||||
|
||||
wire dm_producer_done_valid;
|
||||
wire [NODE_IDW-1:0] dm_producer_done_node_id;
|
||||
|
||||
dependency_manager #(
|
||||
.N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .ADDR_WIDTH(ADDR_WIDTH)
|
||||
) u_dep_mgr (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.producer_done_valid(dm_producer_done_valid), .producer_done_node_id(dm_producer_done_node_id),
|
||||
.ready_valid(dm_ready_valid), .ready_ready(dm_ready_ready), .ready_node_id(dm_ready_node_id),
|
||||
.ready_x_base(dm_ready_x_base), .ready_w_base(dm_ready_w_base),
|
||||
.ready_n_tiles(dm_ready_n_tiles), .ready_result_addr(dm_ready_result_addr)
|
||||
);
|
||||
|
||||
wire [15:0] dm_ready_node_id_ext = {{(16-NODE_IDW){1'b0}}, dm_ready_node_id};
|
||||
|
||||
wire [N_SLOTS-1:0] dir_slot_job_start;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] dir_slot_x_base, dir_slot_w_base, dir_slot_result_addr;
|
||||
wire [16*N_SLOTS-1:0] dir_slot_n_tiles, dir_slot_node_id;
|
||||
wire [N_SLOTS-1:0] dir_slot_job_done;
|
||||
wire dir_job_out_done;
|
||||
wire [$clog2(N_SLOTS)-1:0] dir_job_out_slot;
|
||||
wire [3:0] dir_state;
|
||||
wire dir_error;
|
||||
|
||||
neural_director #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
|
||||
) u_director (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_in_valid(dm_ready_valid), .job_in_ready(dm_ready_ready),
|
||||
.job_in_x_base(dm_ready_x_base), .job_in_w_base(dm_ready_w_base),
|
||||
.job_in_n_tiles(dm_ready_n_tiles), .job_in_result_addr(dm_ready_result_addr),
|
||||
.job_in_node_id(dm_ready_node_id_ext),
|
||||
.slot_job_start(dir_slot_job_start), .slot_x_base(dir_slot_x_base), .slot_w_base(dir_slot_w_base),
|
||||
.slot_n_tiles(dir_slot_n_tiles), .slot_result_addr(dir_slot_result_addr),
|
||||
.slot_node_id(dir_slot_node_id), .slot_job_done(dir_slot_job_done),
|
||||
.job_out_done(dir_job_out_done), .job_out_slot(dir_job_out_slot),
|
||||
.dir_state(dir_state), .dir_error(dir_error)
|
||||
);
|
||||
|
||||
wire [15:0] completed_node_id_16 = dir_slot_node_id[dir_job_out_slot*16 +: 16];
|
||||
assign dm_producer_done_valid = dir_job_out_done;
|
||||
assign dm_producer_done_node_id = completed_node_id_16[NODE_IDW-1:0];
|
||||
|
||||
// ---- NMS memory: shared Activation SRAM (replicated) + private
|
||||
// Weight SRAM (packed), per DEC-0019/DEC-0020 ----
|
||||
wire fill_we;
|
||||
wire [TIW-1:0] fill_addr;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] fill_data;
|
||||
wire [ADDR_WIDTH-1:0] act_resident_tag;
|
||||
wire [CNTW-1:0] act_resident_count;
|
||||
|
||||
wire [N_SLOTS-1:0] act_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] act_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] act_rd_data_flat;
|
||||
|
||||
nms_activation_replicated #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_act_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.rd_en(act_rd_en), .rd_addr_flat(act_rd_addr_flat), .rd_data_flat(act_rd_data_flat)
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] job_active;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] job_x_base_flat;
|
||||
wire [16*N_SLOTS-1:0] job_n_tiles_flat;
|
||||
|
||||
nms_activation_fill_ctrl_v3 #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES)
|
||||
) u_act_fill (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_active(job_active), .x_base_flat(job_x_base_flat), .n_tiles_flat(job_n_tiles_flat),
|
||||
.resident_tag(act_resident_tag), .resident_count(act_resident_count),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.mem_req(slot_mem_req[N_SLOTS]), .mem_wr(slot_mem_wr[N_SLOTS]),
|
||||
.mem_addr(slot_mem_addr[N_SLOTS*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[N_SLOTS*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[N_SLOTS]), .mem_ub_n(slot_mem_ub_n[N_SLOTS]),
|
||||
.mem_rdata(slot_mem_rdata[N_SLOTS*16 +: 16]), .mem_ready(slot_mem_ready[N_SLOTS])
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] wgt_fill_we;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_fill_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_fill_data_flat;
|
||||
wire [N_SLOTS-1:0] wgt_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_rd_data_flat;
|
||||
|
||||
nms_weight_packed #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_wgt_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(wgt_fill_we), .fill_addr_flat(wgt_fill_addr_flat), .fill_data_flat(wgt_fill_data_flat),
|
||||
.rd_en(wgt_rd_en), .rd_addr_flat(wgt_rd_addr_flat), .rd_data_flat(wgt_rd_data_flat)
|
||||
);
|
||||
|
||||
genvar g;
|
||||
generate
|
||||
for (g = 0; g < N_SLOTS; g = g + 1) begin : GEN_SLOT
|
||||
|
||||
wire mm_operand_valid, mm_operand_ready;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] mm_input_data, mm_weight_data;
|
||||
wire mm_tile_last;
|
||||
wire mm_result_valid, mm_result_ready;
|
||||
wire signed [DATA_WIDTH-1:0] mm_result_data;
|
||||
|
||||
nms_memory_manager_stream #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES),
|
||||
.PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_mm (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_start(dir_slot_job_start[g]),
|
||||
.x_base(dir_slot_x_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.w_base(dir_slot_w_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.n_tiles(dir_slot_n_tiles[g*16 +: 16]),
|
||||
.result_addr(dir_slot_result_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_done(dir_slot_job_done[g]),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(mm_result_valid), .result_ready(mm_result_ready), .result_data(mm_result_data),
|
||||
.job_active(job_active[g]),
|
||||
.job_x_base(job_x_base_flat[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_n_tiles(job_n_tiles_flat[g*16 +: 16]),
|
||||
.act_resident_tag(act_resident_tag), .act_resident_count(act_resident_count),
|
||||
.act_rd_en(act_rd_en[g]),
|
||||
.act_rd_addr(act_rd_addr_flat[g*TIW +: TIW]),
|
||||
.act_rd_data(act_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_fill_we(wgt_fill_we[g]),
|
||||
.wgt_fill_addr(wgt_fill_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_fill_data(wgt_fill_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_rd_en(wgt_rd_en[g]),
|
||||
.wgt_rd_addr(wgt_rd_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_rd_data(wgt_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.mem_req(slot_mem_req[g]), .mem_wr(slot_mem_wr[g]),
|
||||
.mem_addr(slot_mem_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[g*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[g]), .mem_ub_n(slot_mem_ub_n[g]),
|
||||
.mem_rdata(slot_mem_rdata[g*16 +: 16]), .mem_ready(slot_mem_ready[g])
|
||||
);
|
||||
|
||||
reg job_valid_np;
|
||||
wire job_ready_np;
|
||||
wire result_valid_np;
|
||||
wire signed [DATA_WIDTH-1:0] result_data_np;
|
||||
wire [3:0] np_state;
|
||||
wire np_error;
|
||||
|
||||
neural_processor #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH)
|
||||
) u_np (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_valid(job_valid_np), .job_ready(job_ready_np),
|
||||
.job_node_id(16'h0), .job_bias(8'sd0), .job_activation(2'd1),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(result_valid_np), .result_ready(mm_result_ready),
|
||||
.result_data(result_data_np), .result_node_id(),
|
||||
.np_state(np_state), .np_error(np_error)
|
||||
);
|
||||
assign mm_result_valid = result_valid_np;
|
||||
assign mm_result_data = result_data_np;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) job_valid_np <= 1'b0;
|
||||
else if (dir_slot_job_start[g]) job_valid_np <= 1'b1;
|
||||
else if (job_valid_np && job_ready_np) job_valid_np <= 1'b0;
|
||||
end
|
||||
|
||||
end
|
||||
endgenerate
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,277 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP8 full integration, mirrors
|
||||
// hardware/v2/rtl/dataflow_core.v's own scope exactly (M6 Dependency
|
||||
// Manager -> M5 Neural Director -> N_SLOTS x (memory manager + neural
|
||||
// processor)), but replaces the M4 memory_manager.v +
|
||||
// activation_cache.v cluster with the NMS's own decided pieces
|
||||
// (DEC-0019/DEC-0020):
|
||||
// - nms_activation_replicated.v: N_SLOTS private full-vector
|
||||
// activation copies, broadcast-filled by...
|
||||
// - nms_activation_fill_ctrl.v: the shared dedup/fetch controller
|
||||
// (single logical tag, same honest thrash-under-interleaved-
|
||||
// different-x_base limitation as the superseded activation_cache.v)
|
||||
// - nms_weight_packed.v: N_SLOTS private, per-MAC-lane packed weight
|
||||
// copies (never shared, no arbitration needed)
|
||||
// - nms_memory_manager.v: per-slot job FSM, reads directly from the
|
||||
// two SRAMs above instead of double-buffering 2 banks (the whole
|
||||
// vector is resident, not just 2 tiles worth)
|
||||
//
|
||||
// hardware/v2/rtl/dependency_manager.v and neural_director.v are
|
||||
// REUSED VERBATIM, unmodified -- the node-registration and slot-
|
||||
// dispatch protocol did not change at all; only what happens between
|
||||
// "job dispatched to a slot" and "job_done" changed.
|
||||
//
|
||||
// Memory Backend Interface: exposed N_SLOTS+1 wide exactly like
|
||||
// dataflow_core.v (indices [0,N_SLOTS) = per-slot memory managers'
|
||||
// own weight-fetch+result-write port, index [N_SLOTS] = the shared
|
||||
// activation fill controller's own port) -- arbitrated one level up,
|
||||
// reusing hardware/v2/rtl/slot_mem_arbiter.v unchanged.
|
||||
// ================================================================
|
||||
|
||||
module nms_dataflow_core_dual32 #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
// Must match nms_memory_manager.v's/nms_activation_fill_ctrl.v's
|
||||
// own CNTW exactly -- this top-level wire connecting the two was
|
||||
// left at the narrower TIW after those modules were widened,
|
||||
// silently truncating resident_count's real value (16) back down
|
||||
// to 0 right when it should have reached MAX_TILES, deadlocking
|
||||
// the very last tile of any n_tiles==MAX_TILES job forever (found
|
||||
// via simulation: D-Stress's real 16-tile neurons hung 1 tile
|
||||
// short, act_resident_count visibly reset to 0 the exact cycle it
|
||||
// should have become 16).
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [N_SLOTS:0] slot_mem_req,
|
||||
output wire [N_SLOTS:0] slot_mem_wr,
|
||||
output wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr,
|
||||
output wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata,
|
||||
output wire [N_SLOTS:0] slot_mem_lb_n,
|
||||
output wire [N_SLOTS:0] slot_mem_ub_n,
|
||||
input wire [16*(N_SLOTS+1)-1:0] slot_mem_rdata,
|
||||
input wire [N_SLOTS:0] slot_mem_ready,
|
||||
|
||||
// ---- STEP15 (continuation): NEW, separate wide (32-bit logical)
|
||||
// weight-fetch backend interface, per slot -- N_SLOTS wide (NOT
|
||||
// N_SLOTS+1: the shared activation-fill controller stays on the
|
||||
// ORIGINAL 16-bit port above, unchanged). Arbitrated one level up
|
||||
// (slot_mem_arbiter_wide.v) down to the real dual-chip 32-bit
|
||||
// physical interface (psram_controller_dual32.v). ----
|
||||
output wire [N_SLOTS-1:0] wide_slot_mem_req,
|
||||
output wire [ADDR_WIDTH*N_SLOTS-1:0] wide_slot_mem_addr,
|
||||
input wire [32*N_SLOTS-1:0] wide_slot_mem_rdata,
|
||||
input wire [N_SLOTS-1:0] wide_slot_mem_ready
|
||||
);
|
||||
|
||||
localparam NODE_IDW = $clog2(N_NODES);
|
||||
|
||||
wire dm_ready_valid;
|
||||
wire dm_ready_ready;
|
||||
wire [NODE_IDW-1:0] dm_ready_node_id;
|
||||
wire [ADDR_WIDTH-1:0] dm_ready_x_base, dm_ready_w_base, dm_ready_result_addr;
|
||||
wire [15:0] dm_ready_n_tiles;
|
||||
|
||||
wire dm_producer_done_valid;
|
||||
wire [NODE_IDW-1:0] dm_producer_done_node_id;
|
||||
|
||||
dependency_manager #(
|
||||
.N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .ADDR_WIDTH(ADDR_WIDTH)
|
||||
) u_dep_mgr (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.producer_done_valid(dm_producer_done_valid), .producer_done_node_id(dm_producer_done_node_id),
|
||||
.ready_valid(dm_ready_valid), .ready_ready(dm_ready_ready), .ready_node_id(dm_ready_node_id),
|
||||
.ready_x_base(dm_ready_x_base), .ready_w_base(dm_ready_w_base),
|
||||
.ready_n_tiles(dm_ready_n_tiles), .ready_result_addr(dm_ready_result_addr)
|
||||
);
|
||||
|
||||
wire [15:0] dm_ready_node_id_ext = {{(16-NODE_IDW){1'b0}}, dm_ready_node_id};
|
||||
|
||||
wire [N_SLOTS-1:0] dir_slot_job_start;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] dir_slot_x_base, dir_slot_w_base, dir_slot_result_addr;
|
||||
wire [16*N_SLOTS-1:0] dir_slot_n_tiles, dir_slot_node_id;
|
||||
wire [N_SLOTS-1:0] dir_slot_job_done;
|
||||
wire dir_job_out_done;
|
||||
wire [$clog2(N_SLOTS)-1:0] dir_job_out_slot;
|
||||
wire [3:0] dir_state;
|
||||
wire dir_error;
|
||||
|
||||
neural_director #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
|
||||
) u_director (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_in_valid(dm_ready_valid), .job_in_ready(dm_ready_ready),
|
||||
.job_in_x_base(dm_ready_x_base), .job_in_w_base(dm_ready_w_base),
|
||||
.job_in_n_tiles(dm_ready_n_tiles), .job_in_result_addr(dm_ready_result_addr),
|
||||
.job_in_node_id(dm_ready_node_id_ext),
|
||||
.slot_job_start(dir_slot_job_start), .slot_x_base(dir_slot_x_base), .slot_w_base(dir_slot_w_base),
|
||||
.slot_n_tiles(dir_slot_n_tiles), .slot_result_addr(dir_slot_result_addr),
|
||||
.slot_node_id(dir_slot_node_id), .slot_job_done(dir_slot_job_done),
|
||||
.job_out_done(dir_job_out_done), .job_out_slot(dir_job_out_slot),
|
||||
.dir_state(dir_state), .dir_error(dir_error)
|
||||
);
|
||||
|
||||
wire [15:0] completed_node_id_16 = dir_slot_node_id[dir_job_out_slot*16 +: 16];
|
||||
assign dm_producer_done_valid = dir_job_out_done;
|
||||
assign dm_producer_done_node_id = completed_node_id_16[NODE_IDW-1:0];
|
||||
|
||||
// ---- NMS memory: shared Activation SRAM (replicated) + private
|
||||
// Weight SRAM (packed), per DEC-0019/DEC-0020 ----
|
||||
wire fill_we;
|
||||
wire [TIW-1:0] fill_addr;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] fill_data;
|
||||
wire [ADDR_WIDTH-1:0] act_resident_tag;
|
||||
wire [CNTW-1:0] act_resident_count;
|
||||
|
||||
wire [N_SLOTS-1:0] act_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] act_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] act_rd_data_flat;
|
||||
|
||||
nms_activation_replicated #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_act_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.rd_en(act_rd_en), .rd_addr_flat(act_rd_addr_flat), .rd_data_flat(act_rd_data_flat)
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] job_active;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] job_x_base_flat;
|
||||
wire [16*N_SLOTS-1:0] job_n_tiles_flat;
|
||||
|
||||
nms_activation_fill_ctrl_v3 #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES)
|
||||
) u_act_fill (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_active(job_active), .x_base_flat(job_x_base_flat), .n_tiles_flat(job_n_tiles_flat),
|
||||
.resident_tag(act_resident_tag), .resident_count(act_resident_count),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.mem_req(slot_mem_req[N_SLOTS]), .mem_wr(slot_mem_wr[N_SLOTS]),
|
||||
.mem_addr(slot_mem_addr[N_SLOTS*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[N_SLOTS*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[N_SLOTS]), .mem_ub_n(slot_mem_ub_n[N_SLOTS]),
|
||||
.mem_rdata(slot_mem_rdata[N_SLOTS*16 +: 16]), .mem_ready(slot_mem_ready[N_SLOTS])
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] wgt_fill_we;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_fill_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_fill_data_flat;
|
||||
wire [N_SLOTS-1:0] wgt_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_rd_data_flat;
|
||||
|
||||
nms_weight_packed #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_wgt_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(wgt_fill_we), .fill_addr_flat(wgt_fill_addr_flat), .fill_data_flat(wgt_fill_data_flat),
|
||||
.rd_en(wgt_rd_en), .rd_addr_flat(wgt_rd_addr_flat), .rd_data_flat(wgt_rd_data_flat)
|
||||
);
|
||||
|
||||
genvar g;
|
||||
generate
|
||||
for (g = 0; g < N_SLOTS; g = g + 1) begin : GEN_SLOT
|
||||
|
||||
wire mm_operand_valid, mm_operand_ready;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] mm_input_data, mm_weight_data;
|
||||
wire mm_tile_last;
|
||||
wire mm_result_valid, mm_result_ready;
|
||||
wire signed [DATA_WIDTH-1:0] mm_result_data;
|
||||
|
||||
nms_memory_manager_stream_wide #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES),
|
||||
.PREFETCH_DISTANCE(PREFETCH_DISTANCE), .MEM_DATA_WIDTH(32)
|
||||
) u_mm (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_start(dir_slot_job_start[g]),
|
||||
.x_base(dir_slot_x_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.w_base(dir_slot_w_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.n_tiles(dir_slot_n_tiles[g*16 +: 16]),
|
||||
.result_addr(dir_slot_result_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_done(dir_slot_job_done[g]),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(mm_result_valid), .result_ready(mm_result_ready), .result_data(mm_result_data),
|
||||
.job_active(job_active[g]),
|
||||
.job_x_base(job_x_base_flat[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_n_tiles(job_n_tiles_flat[g*16 +: 16]),
|
||||
.act_resident_tag(act_resident_tag), .act_resident_count(act_resident_count),
|
||||
.act_rd_en(act_rd_en[g]),
|
||||
.act_rd_addr(act_rd_addr_flat[g*TIW +: TIW]),
|
||||
.act_rd_data(act_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_fill_we(wgt_fill_we[g]),
|
||||
.wgt_fill_addr(wgt_fill_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_fill_data(wgt_fill_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_rd_en(wgt_rd_en[g]),
|
||||
.wgt_rd_addr(wgt_rd_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_rd_data(wgt_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.mem_req(slot_mem_req[g]), .mem_wr(slot_mem_wr[g]),
|
||||
.mem_addr(slot_mem_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[g*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[g]), .mem_ub_n(slot_mem_ub_n[g]),
|
||||
.mem_rdata(slot_mem_rdata[g*16 +: 16]), .mem_ready(slot_mem_ready[g]),
|
||||
.wide_mem_req(wide_slot_mem_req[g]),
|
||||
.wide_mem_addr(wide_slot_mem_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.wide_mem_rdata(wide_slot_mem_rdata[g*32 +: 32]),
|
||||
.wide_mem_ready(wide_slot_mem_ready[g])
|
||||
);
|
||||
|
||||
reg job_valid_np;
|
||||
wire job_ready_np;
|
||||
wire result_valid_np;
|
||||
wire signed [DATA_WIDTH-1:0] result_data_np;
|
||||
wire [3:0] np_state;
|
||||
wire np_error;
|
||||
|
||||
neural_processor #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH)
|
||||
) u_np (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_valid(job_valid_np), .job_ready(job_ready_np),
|
||||
.job_node_id(16'h0), .job_bias(8'sd0), .job_activation(2'd1),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(result_valid_np), .result_ready(mm_result_ready),
|
||||
.result_data(result_data_np), .result_node_id(),
|
||||
.np_state(np_state), .np_error(np_error)
|
||||
);
|
||||
assign mm_result_valid = result_valid_np;
|
||||
assign mm_result_data = result_data_np;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) job_valid_np <= 1'b0;
|
||||
else if (dir_slot_job_start[g]) job_valid_np <= 1'b1;
|
||||
else if (job_valid_np && job_ready_np) job_valid_np <= 1'b0;
|
||||
end
|
||||
|
||||
end
|
||||
endgenerate
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,262 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP8 full integration, mirrors
|
||||
// hardware/v2/rtl/dataflow_core.v's own scope exactly (M6 Dependency
|
||||
// Manager -> M5 Neural Director -> N_SLOTS x (memory manager + neural
|
||||
// processor)), but replaces the M4 memory_manager.v +
|
||||
// activation_cache.v cluster with the NMS's own decided pieces
|
||||
// (DEC-0019/DEC-0020):
|
||||
// - nms_activation_replicated.v: N_SLOTS private full-vector
|
||||
// activation copies, broadcast-filled by...
|
||||
// - nms_activation_fill_ctrl.v: the shared dedup/fetch controller
|
||||
// (single logical tag, same honest thrash-under-interleaved-
|
||||
// different-x_base limitation as the superseded activation_cache.v)
|
||||
// - nms_weight_packed.v: N_SLOTS private, per-MAC-lane packed weight
|
||||
// copies (never shared, no arbitration needed)
|
||||
// - nms_memory_manager.v: per-slot job FSM, reads directly from the
|
||||
// two SRAMs above instead of double-buffering 2 banks (the whole
|
||||
// vector is resident, not just 2 tiles worth)
|
||||
//
|
||||
// hardware/v2/rtl/dependency_manager.v and neural_director.v are
|
||||
// REUSED VERBATIM, unmodified -- the node-registration and slot-
|
||||
// dispatch protocol did not change at all; only what happens between
|
||||
// "job dispatched to a slot" and "job_done" changed.
|
||||
//
|
||||
// Memory Backend Interface: exposed N_SLOTS+1 wide exactly like
|
||||
// dataflow_core.v (indices [0,N_SLOTS) = per-slot memory managers'
|
||||
// own weight-fetch+result-write port, index [N_SLOTS] = the shared
|
||||
// activation fill controller's own port) -- arbitrated one level up,
|
||||
// reusing hardware/v2/rtl/slot_mem_arbiter.v unchanged.
|
||||
// ================================================================
|
||||
|
||||
module nms_dataflow_core_pf #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
// Must match nms_memory_manager.v's/nms_activation_fill_ctrl.v's
|
||||
// own CNTW exactly -- this top-level wire connecting the two was
|
||||
// left at the narrower TIW after those modules were widened,
|
||||
// silently truncating resident_count's real value (16) back down
|
||||
// to 0 right when it should have reached MAX_TILES, deadlocking
|
||||
// the very last tile of any n_tiles==MAX_TILES job forever (found
|
||||
// via simulation: D-Stress's real 16-tile neurons hung 1 tile
|
||||
// short, act_resident_count visibly reset to 0 the exact cycle it
|
||||
// should have become 16).
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [N_SLOTS:0] slot_mem_req,
|
||||
output wire [N_SLOTS:0] slot_mem_wr,
|
||||
output wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr,
|
||||
output wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata,
|
||||
output wire [N_SLOTS:0] slot_mem_lb_n,
|
||||
output wire [N_SLOTS:0] slot_mem_ub_n,
|
||||
input wire [16*(N_SLOTS+1)-1:0] slot_mem_rdata,
|
||||
input wire [N_SLOTS:0] slot_mem_ready
|
||||
);
|
||||
|
||||
localparam NODE_IDW = $clog2(N_NODES);
|
||||
|
||||
wire dm_ready_valid;
|
||||
wire dm_ready_ready;
|
||||
wire [NODE_IDW-1:0] dm_ready_node_id;
|
||||
wire [ADDR_WIDTH-1:0] dm_ready_x_base, dm_ready_w_base, dm_ready_result_addr;
|
||||
wire [15:0] dm_ready_n_tiles;
|
||||
|
||||
wire dm_producer_done_valid;
|
||||
wire [NODE_IDW-1:0] dm_producer_done_node_id;
|
||||
|
||||
dependency_manager #(
|
||||
.N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .ADDR_WIDTH(ADDR_WIDTH)
|
||||
) u_dep_mgr (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.producer_done_valid(dm_producer_done_valid), .producer_done_node_id(dm_producer_done_node_id),
|
||||
.ready_valid(dm_ready_valid), .ready_ready(dm_ready_ready), .ready_node_id(dm_ready_node_id),
|
||||
.ready_x_base(dm_ready_x_base), .ready_w_base(dm_ready_w_base),
|
||||
.ready_n_tiles(dm_ready_n_tiles), .ready_result_addr(dm_ready_result_addr)
|
||||
);
|
||||
|
||||
wire [15:0] dm_ready_node_id_ext = {{(16-NODE_IDW){1'b0}}, dm_ready_node_id};
|
||||
|
||||
wire [N_SLOTS-1:0] dir_slot_job_start;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] dir_slot_x_base, dir_slot_w_base, dir_slot_result_addr;
|
||||
wire [16*N_SLOTS-1:0] dir_slot_n_tiles, dir_slot_node_id;
|
||||
wire [N_SLOTS-1:0] dir_slot_job_done;
|
||||
wire dir_job_out_done;
|
||||
wire [$clog2(N_SLOTS)-1:0] dir_job_out_slot;
|
||||
wire [3:0] dir_state;
|
||||
wire dir_error;
|
||||
|
||||
neural_director #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
|
||||
) u_director (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_in_valid(dm_ready_valid), .job_in_ready(dm_ready_ready),
|
||||
.job_in_x_base(dm_ready_x_base), .job_in_w_base(dm_ready_w_base),
|
||||
.job_in_n_tiles(dm_ready_n_tiles), .job_in_result_addr(dm_ready_result_addr),
|
||||
.job_in_node_id(dm_ready_node_id_ext),
|
||||
.slot_job_start(dir_slot_job_start), .slot_x_base(dir_slot_x_base), .slot_w_base(dir_slot_w_base),
|
||||
.slot_n_tiles(dir_slot_n_tiles), .slot_result_addr(dir_slot_result_addr),
|
||||
.slot_node_id(dir_slot_node_id), .slot_job_done(dir_slot_job_done),
|
||||
.job_out_done(dir_job_out_done), .job_out_slot(dir_job_out_slot),
|
||||
.dir_state(dir_state), .dir_error(dir_error)
|
||||
);
|
||||
|
||||
wire [15:0] completed_node_id_16 = dir_slot_node_id[dir_job_out_slot*16 +: 16];
|
||||
assign dm_producer_done_valid = dir_job_out_done;
|
||||
assign dm_producer_done_node_id = completed_node_id_16[NODE_IDW-1:0];
|
||||
|
||||
// ---- NMS memory: shared Activation SRAM (replicated) + private
|
||||
// Weight SRAM (packed), per DEC-0019/DEC-0020 ----
|
||||
wire fill_we;
|
||||
wire [TIW-1:0] fill_addr;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] fill_data;
|
||||
wire [ADDR_WIDTH-1:0] act_resident_tag;
|
||||
wire [CNTW-1:0] act_resident_count;
|
||||
|
||||
wire [N_SLOTS-1:0] act_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] act_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] act_rd_data_flat;
|
||||
|
||||
nms_activation_replicated #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_act_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.rd_en(act_rd_en), .rd_addr_flat(act_rd_addr_flat), .rd_data_flat(act_rd_data_flat)
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] job_active;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] job_x_base_flat;
|
||||
wire [16*N_SLOTS-1:0] job_n_tiles_flat;
|
||||
|
||||
nms_activation_fill_ctrl #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES)
|
||||
) u_act_fill (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_active(job_active), .x_base_flat(job_x_base_flat), .n_tiles_flat(job_n_tiles_flat),
|
||||
.resident_tag(act_resident_tag), .resident_count(act_resident_count),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.mem_req(slot_mem_req[N_SLOTS]), .mem_wr(slot_mem_wr[N_SLOTS]),
|
||||
.mem_addr(slot_mem_addr[N_SLOTS*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[N_SLOTS*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[N_SLOTS]), .mem_ub_n(slot_mem_ub_n[N_SLOTS]),
|
||||
.mem_rdata(slot_mem_rdata[N_SLOTS*16 +: 16]), .mem_ready(slot_mem_ready[N_SLOTS])
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] wgt_fill_we;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_fill_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_fill_data_flat;
|
||||
wire [N_SLOTS-1:0] wgt_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_rd_data_flat;
|
||||
|
||||
nms_weight_packed #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_wgt_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(wgt_fill_we), .fill_addr_flat(wgt_fill_addr_flat), .fill_data_flat(wgt_fill_data_flat),
|
||||
.rd_en(wgt_rd_en), .rd_addr_flat(wgt_rd_addr_flat), .rd_data_flat(wgt_rd_data_flat)
|
||||
);
|
||||
|
||||
genvar g;
|
||||
generate
|
||||
for (g = 0; g < N_SLOTS; g = g + 1) begin : GEN_SLOT
|
||||
|
||||
wire mm_operand_valid, mm_operand_ready;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] mm_input_data, mm_weight_data;
|
||||
wire mm_tile_last;
|
||||
wire mm_result_valid, mm_result_ready;
|
||||
wire signed [DATA_WIDTH-1:0] mm_result_data;
|
||||
|
||||
nms_memory_manager_pf #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES),
|
||||
.PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_mm (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_start(dir_slot_job_start[g]),
|
||||
.x_base(dir_slot_x_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.w_base(dir_slot_w_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.n_tiles(dir_slot_n_tiles[g*16 +: 16]),
|
||||
.result_addr(dir_slot_result_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_done(dir_slot_job_done[g]),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(mm_result_valid), .result_ready(mm_result_ready), .result_data(mm_result_data),
|
||||
.job_active(job_active[g]),
|
||||
.job_x_base(job_x_base_flat[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_n_tiles(job_n_tiles_flat[g*16 +: 16]),
|
||||
.act_resident_tag(act_resident_tag), .act_resident_count(act_resident_count),
|
||||
.act_rd_en(act_rd_en[g]),
|
||||
.act_rd_addr(act_rd_addr_flat[g*TIW +: TIW]),
|
||||
.act_rd_data(act_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_fill_we(wgt_fill_we[g]),
|
||||
.wgt_fill_addr(wgt_fill_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_fill_data(wgt_fill_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_rd_en(wgt_rd_en[g]),
|
||||
.wgt_rd_addr(wgt_rd_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_rd_data(wgt_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.mem_req(slot_mem_req[g]), .mem_wr(slot_mem_wr[g]),
|
||||
.mem_addr(slot_mem_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[g*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[g]), .mem_ub_n(slot_mem_ub_n[g]),
|
||||
.mem_rdata(slot_mem_rdata[g*16 +: 16]), .mem_ready(slot_mem_ready[g])
|
||||
);
|
||||
|
||||
reg job_valid_np;
|
||||
wire job_ready_np;
|
||||
wire result_valid_np;
|
||||
wire signed [DATA_WIDTH-1:0] result_data_np;
|
||||
wire [3:0] np_state;
|
||||
wire np_error;
|
||||
|
||||
neural_processor #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH)
|
||||
) u_np (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_valid(job_valid_np), .job_ready(job_ready_np),
|
||||
.job_node_id(16'h0), .job_bias(8'sd0), .job_activation(2'd1),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(result_valid_np), .result_ready(mm_result_ready),
|
||||
.result_data(result_data_np), .result_node_id(),
|
||||
.np_state(np_state), .np_error(np_error)
|
||||
);
|
||||
assign mm_result_valid = result_valid_np;
|
||||
assign mm_result_data = result_data_np;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) job_valid_np <= 1'b0;
|
||||
else if (dir_slot_job_start[g]) job_valid_np <= 1'b1;
|
||||
else if (job_valid_np && job_ready_np) job_valid_np <= 1'b0;
|
||||
end
|
||||
|
||||
end
|
||||
endgenerate
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,290 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP8 full integration, mirrors
|
||||
// hardware/v2/rtl/dataflow_core.v's own scope exactly (M6 Dependency
|
||||
// Manager -> M5 Neural Director -> N_SLOTS x (memory manager + neural
|
||||
// processor)), but replaces the M4 memory_manager.v +
|
||||
// activation_cache.v cluster with the NMS's own decided pieces
|
||||
// (DEC-0019/DEC-0020):
|
||||
// - nms_activation_replicated.v: N_SLOTS private full-vector
|
||||
// activation copies, broadcast-filled by...
|
||||
// - nms_activation_fill_ctrl.v: the shared dedup/fetch controller
|
||||
// (single logical tag, same honest thrash-under-interleaved-
|
||||
// different-x_base limitation as the superseded activation_cache.v)
|
||||
// - nms_weight_packed.v: N_SLOTS private, per-MAC-lane packed weight
|
||||
// copies (never shared, no arbitration needed)
|
||||
// - nms_memory_manager.v: per-slot job FSM, reads directly from the
|
||||
// two SRAMs above instead of double-buffering 2 banks (the whole
|
||||
// vector is resident, not just 2 tiles worth)
|
||||
//
|
||||
// hardware/v2/rtl/dependency_manager.v and neural_director.v are
|
||||
// REUSED VERBATIM, unmodified -- the node-registration and slot-
|
||||
// dispatch protocol did not change at all; only what happens between
|
||||
// "job dispatched to a slot" and "job_done" changed.
|
||||
//
|
||||
// Memory Backend Interface: exposed N_SLOTS+1 wide exactly like
|
||||
// dataflow_core.v (indices [0,N_SLOTS) = per-slot memory managers'
|
||||
// own weight-fetch+result-write port, index [N_SLOTS] = the shared
|
||||
// activation fill controller's own port) -- arbitrated one level up,
|
||||
// reusing hardware/v2/rtl/slot_mem_arbiter.v unchanged.
|
||||
//
|
||||
// STEP16 Phase 5: forked from nms_dataflow_core_dual32.v (STEP15's
|
||||
// own dual-chip-32-bit variant) with ONLY the wide weight-fetch port
|
||||
// widened from 32 to 64 bits (MEM_DATA_WIDTH=64 in the per-slot
|
||||
// nms_memory_manager_stream_wide instance below) -- the natural
|
||||
// P_IN*DATA_WIDTH/16=4-word SDRAM burst identified in Phase 1 means
|
||||
// ONE mem_req now fetches exactly one whole tile (WORDS_PER_TILE=1),
|
||||
// same as the dual32 case fetched one whole tile per 32-bit request.
|
||||
// Everything else (activation path, dependency manager, neural
|
||||
// director, per-slot neural_processor) is BYTE-FOR-BYTE UNCHANGED --
|
||||
// per the governing spec's own "do not create an artificial
|
||||
// benchmark" instruction, only the piece under test (weight-fetch
|
||||
// physical memory) differs from the validated dual32 baseline.
|
||||
// ================================================================
|
||||
|
||||
module nms_dataflow_core_sdram #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
// Must match nms_memory_manager.v's/nms_activation_fill_ctrl.v's
|
||||
// own CNTW exactly -- this top-level wire connecting the two was
|
||||
// left at the narrower TIW after those modules were widened,
|
||||
// silently truncating resident_count's real value (16) back down
|
||||
// to 0 right when it should have reached MAX_TILES, deadlocking
|
||||
// the very last tile of any n_tiles==MAX_TILES job forever (found
|
||||
// via simulation: D-Stress's real 16-tile neurons hung 1 tile
|
||||
// short, act_resident_count visibly reset to 0 the exact cycle it
|
||||
// should have become 16).
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [N_SLOTS:0] slot_mem_req,
|
||||
output wire [N_SLOTS:0] slot_mem_wr,
|
||||
output wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr,
|
||||
output wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata,
|
||||
output wire [N_SLOTS:0] slot_mem_lb_n,
|
||||
output wire [N_SLOTS:0] slot_mem_ub_n,
|
||||
input wire [16*(N_SLOTS+1)-1:0] slot_mem_rdata,
|
||||
input wire [N_SLOTS:0] slot_mem_ready,
|
||||
|
||||
// ---- STEP16 Phase 5: wide (64-bit logical) weight-fetch backend
|
||||
// interface, per slot -- N_SLOTS wide (NOT N_SLOTS+1: the shared
|
||||
// activation-fill controller stays on the ORIGINAL 16-bit port
|
||||
// above, unchanged). Arbitrated one level up (slot_mem_arbiter_
|
||||
// wide.v, reused unchanged at DATA_WIDTH=64) down to the real
|
||||
// SDRAM physical interface (sdram_weight_backend.v). ----
|
||||
output wire [N_SLOTS-1:0] wide_slot_mem_req,
|
||||
output wire [ADDR_WIDTH*N_SLOTS-1:0] wide_slot_mem_addr,
|
||||
input wire [64*N_SLOTS-1:0] wide_slot_mem_rdata,
|
||||
input wire [N_SLOTS-1:0] wide_slot_mem_ready
|
||||
);
|
||||
|
||||
localparam NODE_IDW = $clog2(N_NODES);
|
||||
|
||||
wire dm_ready_valid;
|
||||
wire dm_ready_ready;
|
||||
wire [NODE_IDW-1:0] dm_ready_node_id;
|
||||
wire [ADDR_WIDTH-1:0] dm_ready_x_base, dm_ready_w_base, dm_ready_result_addr;
|
||||
wire [15:0] dm_ready_n_tiles;
|
||||
|
||||
wire dm_producer_done_valid;
|
||||
wire [NODE_IDW-1:0] dm_producer_done_node_id;
|
||||
|
||||
dependency_manager #(
|
||||
.N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .ADDR_WIDTH(ADDR_WIDTH)
|
||||
) u_dep_mgr (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.producer_done_valid(dm_producer_done_valid), .producer_done_node_id(dm_producer_done_node_id),
|
||||
.ready_valid(dm_ready_valid), .ready_ready(dm_ready_ready), .ready_node_id(dm_ready_node_id),
|
||||
.ready_x_base(dm_ready_x_base), .ready_w_base(dm_ready_w_base),
|
||||
.ready_n_tiles(dm_ready_n_tiles), .ready_result_addr(dm_ready_result_addr)
|
||||
);
|
||||
|
||||
wire [15:0] dm_ready_node_id_ext = {{(16-NODE_IDW){1'b0}}, dm_ready_node_id};
|
||||
|
||||
wire [N_SLOTS-1:0] dir_slot_job_start;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] dir_slot_x_base, dir_slot_w_base, dir_slot_result_addr;
|
||||
wire [16*N_SLOTS-1:0] dir_slot_n_tiles, dir_slot_node_id;
|
||||
wire [N_SLOTS-1:0] dir_slot_job_done;
|
||||
wire dir_job_out_done;
|
||||
wire [$clog2(N_SLOTS)-1:0] dir_job_out_slot;
|
||||
wire [3:0] dir_state;
|
||||
wire dir_error;
|
||||
|
||||
neural_director #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
|
||||
) u_director (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_in_valid(dm_ready_valid), .job_in_ready(dm_ready_ready),
|
||||
.job_in_x_base(dm_ready_x_base), .job_in_w_base(dm_ready_w_base),
|
||||
.job_in_n_tiles(dm_ready_n_tiles), .job_in_result_addr(dm_ready_result_addr),
|
||||
.job_in_node_id(dm_ready_node_id_ext),
|
||||
.slot_job_start(dir_slot_job_start), .slot_x_base(dir_slot_x_base), .slot_w_base(dir_slot_w_base),
|
||||
.slot_n_tiles(dir_slot_n_tiles), .slot_result_addr(dir_slot_result_addr),
|
||||
.slot_node_id(dir_slot_node_id), .slot_job_done(dir_slot_job_done),
|
||||
.job_out_done(dir_job_out_done), .job_out_slot(dir_job_out_slot),
|
||||
.dir_state(dir_state), .dir_error(dir_error)
|
||||
);
|
||||
|
||||
wire [15:0] completed_node_id_16 = dir_slot_node_id[dir_job_out_slot*16 +: 16];
|
||||
assign dm_producer_done_valid = dir_job_out_done;
|
||||
assign dm_producer_done_node_id = completed_node_id_16[NODE_IDW-1:0];
|
||||
|
||||
// ---- NMS memory: shared Activation SRAM (replicated) + private
|
||||
// Weight SRAM (packed), per DEC-0019/DEC-0020 ----
|
||||
wire fill_we;
|
||||
wire [TIW-1:0] fill_addr;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] fill_data;
|
||||
wire [ADDR_WIDTH-1:0] act_resident_tag;
|
||||
wire [CNTW-1:0] act_resident_count;
|
||||
|
||||
wire [N_SLOTS-1:0] act_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] act_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] act_rd_data_flat;
|
||||
|
||||
nms_activation_replicated #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_act_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.rd_en(act_rd_en), .rd_addr_flat(act_rd_addr_flat), .rd_data_flat(act_rd_data_flat)
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] job_active;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] job_x_base_flat;
|
||||
wire [16*N_SLOTS-1:0] job_n_tiles_flat;
|
||||
|
||||
nms_activation_fill_ctrl_v3 #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES)
|
||||
) u_act_fill (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_active(job_active), .x_base_flat(job_x_base_flat), .n_tiles_flat(job_n_tiles_flat),
|
||||
.resident_tag(act_resident_tag), .resident_count(act_resident_count),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.mem_req(slot_mem_req[N_SLOTS]), .mem_wr(slot_mem_wr[N_SLOTS]),
|
||||
.mem_addr(slot_mem_addr[N_SLOTS*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[N_SLOTS*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[N_SLOTS]), .mem_ub_n(slot_mem_ub_n[N_SLOTS]),
|
||||
.mem_rdata(slot_mem_rdata[N_SLOTS*16 +: 16]), .mem_ready(slot_mem_ready[N_SLOTS])
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] wgt_fill_we;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_fill_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_fill_data_flat;
|
||||
wire [N_SLOTS-1:0] wgt_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_rd_data_flat;
|
||||
|
||||
nms_weight_packed #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_wgt_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(wgt_fill_we), .fill_addr_flat(wgt_fill_addr_flat), .fill_data_flat(wgt_fill_data_flat),
|
||||
.rd_en(wgt_rd_en), .rd_addr_flat(wgt_rd_addr_flat), .rd_data_flat(wgt_rd_data_flat)
|
||||
);
|
||||
|
||||
genvar g;
|
||||
generate
|
||||
for (g = 0; g < N_SLOTS; g = g + 1) begin : GEN_SLOT
|
||||
|
||||
wire mm_operand_valid, mm_operand_ready;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] mm_input_data, mm_weight_data;
|
||||
wire mm_tile_last;
|
||||
wire mm_result_valid, mm_result_ready;
|
||||
wire signed [DATA_WIDTH-1:0] mm_result_data;
|
||||
|
||||
nms_memory_manager_stream_wide #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES),
|
||||
.PREFETCH_DISTANCE(PREFETCH_DISTANCE), .MEM_DATA_WIDTH(64)
|
||||
) u_mm (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_start(dir_slot_job_start[g]),
|
||||
.x_base(dir_slot_x_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.w_base(dir_slot_w_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.n_tiles(dir_slot_n_tiles[g*16 +: 16]),
|
||||
.result_addr(dir_slot_result_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_done(dir_slot_job_done[g]),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(mm_result_valid), .result_ready(mm_result_ready), .result_data(mm_result_data),
|
||||
.job_active(job_active[g]),
|
||||
.job_x_base(job_x_base_flat[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_n_tiles(job_n_tiles_flat[g*16 +: 16]),
|
||||
.act_resident_tag(act_resident_tag), .act_resident_count(act_resident_count),
|
||||
.act_rd_en(act_rd_en[g]),
|
||||
.act_rd_addr(act_rd_addr_flat[g*TIW +: TIW]),
|
||||
.act_rd_data(act_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_fill_we(wgt_fill_we[g]),
|
||||
.wgt_fill_addr(wgt_fill_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_fill_data(wgt_fill_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_rd_en(wgt_rd_en[g]),
|
||||
.wgt_rd_addr(wgt_rd_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_rd_data(wgt_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.mem_req(slot_mem_req[g]), .mem_wr(slot_mem_wr[g]),
|
||||
.mem_addr(slot_mem_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[g*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[g]), .mem_ub_n(slot_mem_ub_n[g]),
|
||||
.mem_rdata(slot_mem_rdata[g*16 +: 16]), .mem_ready(slot_mem_ready[g]),
|
||||
.wide_mem_req(wide_slot_mem_req[g]),
|
||||
.wide_mem_addr(wide_slot_mem_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.wide_mem_rdata(wide_slot_mem_rdata[g*64 +: 64]),
|
||||
.wide_mem_ready(wide_slot_mem_ready[g])
|
||||
);
|
||||
|
||||
reg job_valid_np;
|
||||
wire job_ready_np;
|
||||
wire result_valid_np;
|
||||
wire signed [DATA_WIDTH-1:0] result_data_np;
|
||||
wire [3:0] np_state;
|
||||
wire np_error;
|
||||
|
||||
neural_processor #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH)
|
||||
) u_np (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_valid(job_valid_np), .job_ready(job_ready_np),
|
||||
.job_node_id(16'h0), .job_bias(8'sd0), .job_activation(2'd1),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(result_valid_np), .result_ready(mm_result_ready),
|
||||
.result_data(result_data_np), .result_node_id(),
|
||||
.np_state(np_state), .np_error(np_error)
|
||||
);
|
||||
assign mm_result_valid = result_valid_np;
|
||||
assign mm_result_data = result_data_np;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) job_valid_np <= 1'b0;
|
||||
else if (dir_slot_job_start[g]) job_valid_np <= 1'b1;
|
||||
else if (job_valid_np && job_ready_np) job_valid_np <= 1'b0;
|
||||
end
|
||||
|
||||
end
|
||||
endgenerate
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,262 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP8 full integration, mirrors
|
||||
// hardware/v2/rtl/dataflow_core.v's own scope exactly (M6 Dependency
|
||||
// Manager -> M5 Neural Director -> N_SLOTS x (memory manager + neural
|
||||
// processor)), but replaces the M4 memory_manager.v +
|
||||
// activation_cache.v cluster with the NMS's own decided pieces
|
||||
// (DEC-0019/DEC-0020):
|
||||
// - nms_activation_replicated.v: N_SLOTS private full-vector
|
||||
// activation copies, broadcast-filled by...
|
||||
// - nms_activation_fill_ctrl.v: the shared dedup/fetch controller
|
||||
// (single logical tag, same honest thrash-under-interleaved-
|
||||
// different-x_base limitation as the superseded activation_cache.v)
|
||||
// - nms_weight_packed.v: N_SLOTS private, per-MAC-lane packed weight
|
||||
// copies (never shared, no arbitration needed)
|
||||
// - nms_memory_manager.v: per-slot job FSM, reads directly from the
|
||||
// two SRAMs above instead of double-buffering 2 banks (the whole
|
||||
// vector is resident, not just 2 tiles worth)
|
||||
//
|
||||
// hardware/v2/rtl/dependency_manager.v and neural_director.v are
|
||||
// REUSED VERBATIM, unmodified -- the node-registration and slot-
|
||||
// dispatch protocol did not change at all; only what happens between
|
||||
// "job dispatched to a slot" and "job_done" changed.
|
||||
//
|
||||
// Memory Backend Interface: exposed N_SLOTS+1 wide exactly like
|
||||
// dataflow_core.v (indices [0,N_SLOTS) = per-slot memory managers'
|
||||
// own weight-fetch+result-write port, index [N_SLOTS] = the shared
|
||||
// activation fill controller's own port) -- arbitrated one level up,
|
||||
// reusing hardware/v2/rtl/slot_mem_arbiter.v unchanged.
|
||||
// ================================================================
|
||||
|
||||
module nms_dataflow_core_stream #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
// Must match nms_memory_manager.v's/nms_activation_fill_ctrl.v's
|
||||
// own CNTW exactly -- this top-level wire connecting the two was
|
||||
// left at the narrower TIW after those modules were widened,
|
||||
// silently truncating resident_count's real value (16) back down
|
||||
// to 0 right when it should have reached MAX_TILES, deadlocking
|
||||
// the very last tile of any n_tiles==MAX_TILES job forever (found
|
||||
// via simulation: D-Stress's real 16-tile neurons hung 1 tile
|
||||
// short, act_resident_count visibly reset to 0 the exact cycle it
|
||||
// should have become 16).
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [N_SLOTS:0] slot_mem_req,
|
||||
output wire [N_SLOTS:0] slot_mem_wr,
|
||||
output wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr,
|
||||
output wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata,
|
||||
output wire [N_SLOTS:0] slot_mem_lb_n,
|
||||
output wire [N_SLOTS:0] slot_mem_ub_n,
|
||||
input wire [16*(N_SLOTS+1)-1:0] slot_mem_rdata,
|
||||
input wire [N_SLOTS:0] slot_mem_ready
|
||||
);
|
||||
|
||||
localparam NODE_IDW = $clog2(N_NODES);
|
||||
|
||||
wire dm_ready_valid;
|
||||
wire dm_ready_ready;
|
||||
wire [NODE_IDW-1:0] dm_ready_node_id;
|
||||
wire [ADDR_WIDTH-1:0] dm_ready_x_base, dm_ready_w_base, dm_ready_result_addr;
|
||||
wire [15:0] dm_ready_n_tiles;
|
||||
|
||||
wire dm_producer_done_valid;
|
||||
wire [NODE_IDW-1:0] dm_producer_done_node_id;
|
||||
|
||||
dependency_manager #(
|
||||
.N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .ADDR_WIDTH(ADDR_WIDTH)
|
||||
) u_dep_mgr (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.producer_done_valid(dm_producer_done_valid), .producer_done_node_id(dm_producer_done_node_id),
|
||||
.ready_valid(dm_ready_valid), .ready_ready(dm_ready_ready), .ready_node_id(dm_ready_node_id),
|
||||
.ready_x_base(dm_ready_x_base), .ready_w_base(dm_ready_w_base),
|
||||
.ready_n_tiles(dm_ready_n_tiles), .ready_result_addr(dm_ready_result_addr)
|
||||
);
|
||||
|
||||
wire [15:0] dm_ready_node_id_ext = {{(16-NODE_IDW){1'b0}}, dm_ready_node_id};
|
||||
|
||||
wire [N_SLOTS-1:0] dir_slot_job_start;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] dir_slot_x_base, dir_slot_w_base, dir_slot_result_addr;
|
||||
wire [16*N_SLOTS-1:0] dir_slot_n_tiles, dir_slot_node_id;
|
||||
wire [N_SLOTS-1:0] dir_slot_job_done;
|
||||
wire dir_job_out_done;
|
||||
wire [$clog2(N_SLOTS)-1:0] dir_job_out_slot;
|
||||
wire [3:0] dir_state;
|
||||
wire dir_error;
|
||||
|
||||
neural_director #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
|
||||
) u_director (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_in_valid(dm_ready_valid), .job_in_ready(dm_ready_ready),
|
||||
.job_in_x_base(dm_ready_x_base), .job_in_w_base(dm_ready_w_base),
|
||||
.job_in_n_tiles(dm_ready_n_tiles), .job_in_result_addr(dm_ready_result_addr),
|
||||
.job_in_node_id(dm_ready_node_id_ext),
|
||||
.slot_job_start(dir_slot_job_start), .slot_x_base(dir_slot_x_base), .slot_w_base(dir_slot_w_base),
|
||||
.slot_n_tiles(dir_slot_n_tiles), .slot_result_addr(dir_slot_result_addr),
|
||||
.slot_node_id(dir_slot_node_id), .slot_job_done(dir_slot_job_done),
|
||||
.job_out_done(dir_job_out_done), .job_out_slot(dir_job_out_slot),
|
||||
.dir_state(dir_state), .dir_error(dir_error)
|
||||
);
|
||||
|
||||
wire [15:0] completed_node_id_16 = dir_slot_node_id[dir_job_out_slot*16 +: 16];
|
||||
assign dm_producer_done_valid = dir_job_out_done;
|
||||
assign dm_producer_done_node_id = completed_node_id_16[NODE_IDW-1:0];
|
||||
|
||||
// ---- NMS memory: shared Activation SRAM (replicated) + private
|
||||
// Weight SRAM (packed), per DEC-0019/DEC-0020 ----
|
||||
wire fill_we;
|
||||
wire [TIW-1:0] fill_addr;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] fill_data;
|
||||
wire [ADDR_WIDTH-1:0] act_resident_tag;
|
||||
wire [CNTW-1:0] act_resident_count;
|
||||
|
||||
wire [N_SLOTS-1:0] act_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] act_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] act_rd_data_flat;
|
||||
|
||||
nms_activation_replicated #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_act_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.rd_en(act_rd_en), .rd_addr_flat(act_rd_addr_flat), .rd_data_flat(act_rd_data_flat)
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] job_active;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] job_x_base_flat;
|
||||
wire [16*N_SLOTS-1:0] job_n_tiles_flat;
|
||||
|
||||
nms_activation_fill_ctrl #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES)
|
||||
) u_act_fill (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_active(job_active), .x_base_flat(job_x_base_flat), .n_tiles_flat(job_n_tiles_flat),
|
||||
.resident_tag(act_resident_tag), .resident_count(act_resident_count),
|
||||
.fill_we(fill_we), .fill_addr(fill_addr), .fill_data(fill_data),
|
||||
.mem_req(slot_mem_req[N_SLOTS]), .mem_wr(slot_mem_wr[N_SLOTS]),
|
||||
.mem_addr(slot_mem_addr[N_SLOTS*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[N_SLOTS*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[N_SLOTS]), .mem_ub_n(slot_mem_ub_n[N_SLOTS]),
|
||||
.mem_rdata(slot_mem_rdata[N_SLOTS*16 +: 16]), .mem_ready(slot_mem_ready[N_SLOTS])
|
||||
);
|
||||
|
||||
wire [N_SLOTS-1:0] wgt_fill_we;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_fill_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_fill_data_flat;
|
||||
wire [N_SLOTS-1:0] wgt_rd_en;
|
||||
wire [N_SLOTS*TIW-1:0] wgt_rd_addr_flat;
|
||||
wire signed [DATA_WIDTH*P_IN*N_SLOTS-1:0] wgt_rd_data_flat;
|
||||
|
||||
nms_weight_packed #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .N_SLOTS(N_SLOTS), .MAX_TILES(MAX_TILES)
|
||||
) u_wgt_mem (
|
||||
.clk(clk), .rst(rst),
|
||||
.fill_we(wgt_fill_we), .fill_addr_flat(wgt_fill_addr_flat), .fill_data_flat(wgt_fill_data_flat),
|
||||
.rd_en(wgt_rd_en), .rd_addr_flat(wgt_rd_addr_flat), .rd_data_flat(wgt_rd_data_flat)
|
||||
);
|
||||
|
||||
genvar g;
|
||||
generate
|
||||
for (g = 0; g < N_SLOTS; g = g + 1) begin : GEN_SLOT
|
||||
|
||||
wire mm_operand_valid, mm_operand_ready;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] mm_input_data, mm_weight_data;
|
||||
wire mm_tile_last;
|
||||
wire mm_result_valid, mm_result_ready;
|
||||
wire signed [DATA_WIDTH-1:0] mm_result_data;
|
||||
|
||||
nms_memory_manager_stream #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH), .MAX_TILES(MAX_TILES),
|
||||
.PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_mm (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_start(dir_slot_job_start[g]),
|
||||
.x_base(dir_slot_x_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.w_base(dir_slot_w_base[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.n_tiles(dir_slot_n_tiles[g*16 +: 16]),
|
||||
.result_addr(dir_slot_result_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_done(dir_slot_job_done[g]),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(mm_result_valid), .result_ready(mm_result_ready), .result_data(mm_result_data),
|
||||
.job_active(job_active[g]),
|
||||
.job_x_base(job_x_base_flat[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.job_n_tiles(job_n_tiles_flat[g*16 +: 16]),
|
||||
.act_resident_tag(act_resident_tag), .act_resident_count(act_resident_count),
|
||||
.act_rd_en(act_rd_en[g]),
|
||||
.act_rd_addr(act_rd_addr_flat[g*TIW +: TIW]),
|
||||
.act_rd_data(act_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_fill_we(wgt_fill_we[g]),
|
||||
.wgt_fill_addr(wgt_fill_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_fill_data(wgt_fill_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.wgt_rd_en(wgt_rd_en[g]),
|
||||
.wgt_rd_addr(wgt_rd_addr_flat[g*TIW +: TIW]),
|
||||
.wgt_rd_data(wgt_rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN]),
|
||||
.mem_req(slot_mem_req[g]), .mem_wr(slot_mem_wr[g]),
|
||||
.mem_addr(slot_mem_addr[g*ADDR_WIDTH +: ADDR_WIDTH]),
|
||||
.mem_wdata(slot_mem_wdata[g*16 +: 16]),
|
||||
.mem_lb_n(slot_mem_lb_n[g]), .mem_ub_n(slot_mem_ub_n[g]),
|
||||
.mem_rdata(slot_mem_rdata[g*16 +: 16]), .mem_ready(slot_mem_ready[g])
|
||||
);
|
||||
|
||||
reg job_valid_np;
|
||||
wire job_ready_np;
|
||||
wire result_valid_np;
|
||||
wire signed [DATA_WIDTH-1:0] result_data_np;
|
||||
wire [3:0] np_state;
|
||||
wire np_error;
|
||||
|
||||
neural_processor #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH)
|
||||
) u_np (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_valid(job_valid_np), .job_ready(job_ready_np),
|
||||
.job_node_id(16'h0), .job_bias(8'sd0), .job_activation(2'd1),
|
||||
.operand_valid(mm_operand_valid), .operand_ready(mm_operand_ready),
|
||||
.input_data(mm_input_data), .weight_data(mm_weight_data), .tile_last(mm_tile_last),
|
||||
.result_valid(result_valid_np), .result_ready(mm_result_ready),
|
||||
.result_data(result_data_np), .result_node_id(),
|
||||
.np_state(np_state), .np_error(np_error)
|
||||
);
|
||||
assign mm_result_valid = result_valid_np;
|
||||
assign mm_result_data = result_data_np;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) job_valid_np <= 1'b0;
|
||||
else if (dir_slot_job_start[g]) job_valid_np <= 1'b1;
|
||||
else if (job_valid_np && job_ready_np) job_valid_np <= 1'b0;
|
||||
end
|
||||
|
||||
end
|
||||
endgenerate
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,332 @@
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- per-slot memory manager.
|
||||
//
|
||||
// Same EXTERNAL job/operand/result interface as
|
||||
// hardware/v2/rtl/memory_manager.v (drop-in for the Neural Director/
|
||||
// Dependency Manager side -- neither needs to change). Internally
|
||||
// simpler: since the on-chip Activation/Weight SRAMs
|
||||
// (nms_activation_replicated.v, nms_weight_packed.v) each hold the
|
||||
// ENTIRE shared/private vector (sized MAX_TILES deep), there is no
|
||||
// more double-buffering/bank-swap logic at all -- this module just
|
||||
// tracks a private WEIGHT fetch progress counter (own prefetch_engine
|
||||
// instance, exactly memory_manager.v's own proven pattern, writing
|
||||
// into this slot's own private SRAM lane instead of a bank register)
|
||||
// and reads the shared Activation fill controller's own
|
||||
// resident_tag/resident_count to know how many tiles of ITS x_base are
|
||||
// currently usable.
|
||||
//
|
||||
// A tile is presentable once tile_idx is below BOTH: this slot's own
|
||||
// weight-fetch progress, and (resident_tag==x_base_reg) ?
|
||||
// resident_count : 0. SRAM reads have 1-cycle registered latency
|
||||
// (matches nms_activation_replicated.v/nms_weight_packed.v exactly).
|
||||
// ============================================================
|
||||
module nms_memory_manager #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter MAX_TILES = 16,
|
||||
// TIW indexes SRAM addresses (0..MAX_TILES-1) -- matches
|
||||
// nms_activation_replicated.v/nms_weight_packed.v's own address
|
||||
// port width exactly, must stay in sync with them.
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
// CNTW is for COUNTERS (tile_idx, wgt_fetched) and the resident-
|
||||
// count status they compare against -- these must be able to
|
||||
// represent the VALUE MAX_TILES itself (e.g. n_tiles=16 with
|
||||
// MAX_TILES=16), one bit wider than an address index needs. Using
|
||||
// TIW for these counters was a real bug: a 4-bit wgt_fetched
|
||||
// (MAX_TILES=16) can count 0..15 but overflows 15->0 right when it
|
||||
// should reach 16, so "wgt_fetched < n_tiles" was NEVER false once
|
||||
// truly done -- the weight fetch looped forever, and separately
|
||||
// resident_count never advanced past its own analogous ceiling
|
||||
// (found via simulation: any n_tiles==MAX_TILES job -- exactly
|
||||
// D-Stress's real 16-tile neurons -- hung forever, while every
|
||||
// earlier n_tiles<MAX_TILES test in this project passed).
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// ---- job control (from Neural Director, unchanged interface) ----
|
||||
input wire job_start,
|
||||
input wire [ADDR_WIDTH-1:0] x_base,
|
||||
input wire [ADDR_WIDTH-1:0] w_base,
|
||||
input wire [15:0] n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] result_addr,
|
||||
output reg job_done,
|
||||
|
||||
// ---- Neural Processor-facing operand stream (unchanged) ----
|
||||
output reg operand_valid,
|
||||
input wire operand_ready,
|
||||
output reg signed [DATA_WIDTH*P_IN-1:0] input_data,
|
||||
output reg signed [DATA_WIDTH*P_IN-1:0] weight_data,
|
||||
output reg tile_last,
|
||||
|
||||
// ---- Neural Processor-facing result consumption (unchanged) ----
|
||||
input wire result_valid,
|
||||
output reg result_ready,
|
||||
input wire signed [DATA_WIDTH-1:0] result_data,
|
||||
|
||||
// ---- job status broadcast to the shared activation fill controller ----
|
||||
output wire job_active,
|
||||
output wire [ADDR_WIDTH-1:0] job_x_base,
|
||||
output wire [15:0] job_n_tiles,
|
||||
|
||||
// ---- shared activation fill controller status (broadcast, same for every slot) ----
|
||||
input wire [ADDR_WIDTH-1:0] act_resident_tag,
|
||||
input wire [CNTW-1:0] act_resident_count,
|
||||
|
||||
// ---- this slot's own private lane into nms_activation_replicated.v (READ only -- fill is owned by the shared controller) ----
|
||||
output reg act_rd_en,
|
||||
output reg [TIW-1:0] act_rd_addr,
|
||||
input wire signed [DATA_WIDTH*P_IN-1:0] act_rd_data,
|
||||
|
||||
// ---- this slot's own private lane into nms_weight_packed.v (fill AND read -- private) ----
|
||||
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 wgt_rd_en,
|
||||
output reg [TIW-1:0] wgt_rd_addr,
|
||||
input wire signed [DATA_WIDTH*P_IN-1:0] wgt_rd_data,
|
||||
|
||||
// ---- real word-level PSRAM backend for THIS slot's own weight
|
||||
// fetch + result write-back (arbitrated externally, exactly
|
||||
// memory_manager.v's own mem_* port) ----
|
||||
output wire mem_req,
|
||||
output wire mem_wr,
|
||||
output wire [ADDR_WIDTH-1:0] mem_addr,
|
||||
output wire [15:0] mem_wdata,
|
||||
output wire mem_lb_n,
|
||||
output wire mem_ub_n,
|
||||
input wire [15:0] mem_rdata,
|
||||
input wire mem_ready
|
||||
);
|
||||
|
||||
localparam ST_IDLE = 3'd0;
|
||||
localparam ST_RUN = 3'd1;
|
||||
localparam ST_WAIT_RESULT = 3'd2;
|
||||
localparam ST_WRITE_RES = 3'd3;
|
||||
localparam ST_DONE = 3'd4;
|
||||
|
||||
reg [2:0] state;
|
||||
reg job_active_reg;
|
||||
assign job_active = job_active_reg;
|
||||
assign job_x_base = x_base_reg;
|
||||
assign job_n_tiles = n_tiles_reg;
|
||||
|
||||
reg [ADDR_WIDTH-1:0] x_base_reg, w_base_reg, result_addr_reg;
|
||||
reg [15:0] n_tiles_reg;
|
||||
reg [CNTW-1:0] tile_idx; // consumption pointer (0..MAX_TILES)
|
||||
reg [CNTW-1:0] wgt_fetched; // this slot's own weight fetch progress (0..MAX_TILES)
|
||||
// Two-stage read pipeline, NOT one: both nms_activation_replicated.v
|
||||
// and nms_weight_packed.v register rd_en THEN register the memory
|
||||
// read off THAT (rd_data_reg <= mem[addr]) -- i.e. asserting rd_en
|
||||
// at cycle T makes the SRAM's own always block see it at cycle T+1
|
||||
// (scheduling the read for T+2), so rd_data is only valid starting
|
||||
// T+2, not T+1. A single "read_issued" flag capturing rd_data one
|
||||
// cycle after issuing it (T+1) grabbed the SRAM's PRE-read (stale)
|
||||
// output -- found via simulation: node0's own first tile computed
|
||||
// 0 instead of 48 (2*3*8) because input_data/weight_data were still
|
||||
// read AS ZERO the very cycle operand_valid first asserted (the
|
||||
// real act_rd_data/wgt_rd_data were correct by then, but the NBA
|
||||
// capture into input_data/weight_data was one cycle too early to
|
||||
// use them). Fixed with a genuine 2-stage pipeline: read_issued
|
||||
// (SRAM now computing) -> read_ready (SRAM output now valid,
|
||||
// capture NOW).
|
||||
reg read_issued;
|
||||
reg read_ready;
|
||||
|
||||
wire usable_act_count_valid = (act_resident_tag == x_base_reg);
|
||||
wire [CNTW-1:0] usable_act = usable_act_count_valid ? act_resident_count : {CNTW{1'b0}};
|
||||
// tile_idx/wgt_fetched/usable_act are CNTW-bit (able to represent
|
||||
// the value MAX_TILES itself, not just index it). First term
|
||||
// compares against the full 16-bit n_tiles_reg (Verilog zero-
|
||||
// extends automatically since CNTW<16 for any real MAX_TILES);
|
||||
// the other two compare tile_idx (what's NEEDED now) against
|
||||
// wgt_fetched/usable_act (what's actually AVAILABLE) -- NOT
|
||||
// against n_tiles_reg, which says nothing about availability. An
|
||||
// earlier revision of this fix mistakenly compared wgt_fetched
|
||||
// against n_tiles_reg here instead of against tile_idx: once
|
||||
// wgt_fetched legitimately reached n_tiles (fetch complete, no
|
||||
// more needed), that comparison went permanently false and
|
||||
// deadlocked consumption forever even though every tile was
|
||||
// genuinely ready -- caught because act_resident_count kept
|
||||
// climbing normally while operand_valid never once asserted.
|
||||
wire can_present = ({{(16-CNTW){1'b0}}, tile_idx} < n_tiles_reg) &&
|
||||
(tile_idx < wgt_fetched) &&
|
||||
(tile_idx < usable_act);
|
||||
|
||||
// ---- private weight prefetch (own prefetch_engine instance,
|
||||
// exactly memory_manager.v's own proven pattern -- fetch AS FAST AS
|
||||
// POSSIBLE up to n_tiles, no lookahead throttling needed since the
|
||||
// SRAM holds the whole vector, not just 2 double-buffered banks) ----
|
||||
reg pf_start;
|
||||
reg [ADDR_WIDTH-1:0] pf_w_addr;
|
||||
wire pf_busy, pf_done;
|
||||
wire signed [DATA_WIDTH*P_IN-1:0] pf_tile_w;
|
||||
|
||||
prefetch_engine #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH)
|
||||
) u_prefetch (
|
||||
.clk(clk), .rst(rst),
|
||||
.fetch_start(pf_start), .w_addr(pf_w_addr),
|
||||
.fetch_busy(pf_busy), .fetch_done(pf_done), .tile_w(pf_tile_w),
|
||||
.mem_req(pf_mem_req), .mem_wr(pf_mem_wr), .mem_addr(pf_mem_addr), .mem_wdata(pf_mem_wdata),
|
||||
.mem_lb_n(pf_mem_lb_n), .mem_ub_n(pf_mem_ub_n),
|
||||
.mem_rdata(mem_rdata), .mem_ready(mem_ready)
|
||||
);
|
||||
wire pf_mem_req, pf_mem_wr;
|
||||
wire [ADDR_WIDTH-1:0] pf_mem_addr;
|
||||
wire [15:0] pf_mem_wdata;
|
||||
wire pf_mem_lb_n, pf_mem_ub_n;
|
||||
|
||||
reg wr_mem_req;
|
||||
reg [ADDR_WIDTH-1:0] wr_mem_addr;
|
||||
reg [15:0] wr_mem_wdata;
|
||||
reg wr_mem_lb_n, wr_mem_ub_n;
|
||||
|
||||
wire wr_active = (state == ST_WRITE_RES) || (state == ST_DONE);
|
||||
assign mem_req = wr_active ? wr_mem_req : pf_mem_req;
|
||||
assign mem_wr = wr_active ? 1'b1 : pf_mem_wr;
|
||||
assign mem_addr = wr_active ? wr_mem_addr : pf_mem_addr;
|
||||
assign mem_wdata = wr_active ? wr_mem_wdata : pf_mem_wdata;
|
||||
assign mem_lb_n = wr_active ? wr_mem_lb_n : pf_mem_lb_n;
|
||||
assign mem_ub_n = wr_active ? wr_mem_ub_n : pf_mem_ub_n;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= ST_IDLE;
|
||||
job_active_reg <= 1'b0;
|
||||
job_done <= 1'b0;
|
||||
operand_valid <= 1'b0;
|
||||
tile_last <= 1'b0;
|
||||
result_ready <= 1'b0;
|
||||
tile_idx <= {CNTW{1'b0}};
|
||||
wgt_fetched <= {CNTW{1'b0}};
|
||||
read_issued <= 1'b0;
|
||||
read_ready <= 1'b0;
|
||||
pf_start <= 1'b0;
|
||||
act_rd_en <= 1'b0;
|
||||
wgt_rd_en <= 1'b0;
|
||||
wgt_fill_we <= 1'b0;
|
||||
wr_mem_req <= 1'b0;
|
||||
wr_mem_lb_n <= 1'b1;
|
||||
wr_mem_ub_n <= 1'b1;
|
||||
end else begin
|
||||
job_done <= 1'b0;
|
||||
pf_start <= 1'b0;
|
||||
act_rd_en <= 1'b0;
|
||||
wgt_rd_en <= 1'b0;
|
||||
wgt_fill_we <= 1'b0;
|
||||
result_ready <= 1'b0;
|
||||
|
||||
// latch a completed private weight fetch into this slot's
|
||||
// own SRAM lane
|
||||
if (pf_done) begin
|
||||
wgt_fill_we <= 1'b1;
|
||||
wgt_fill_addr <= wgt_fetched[TIW-1:0]; // valid: gated < n_tiles <= MAX_TILES
|
||||
wgt_fill_data <= pf_tile_w;
|
||||
wgt_fetched <= wgt_fetched + 1'b1;
|
||||
end
|
||||
|
||||
// keep fetching weight tiles as fast as the (single, private)
|
||||
// prefetch engine allows, up to n_tiles. The `!pf_done` guard
|
||||
// is required, not cosmetic: pf_done and the "wgt_fetched<=
|
||||
// wgt_fetched+1" increment above happen the SAME cycle
|
||||
// pf_busy also drops back to 0 (prefetch_engine.v's own
|
||||
// ST_DONE clears fetch_busy the same cycle it pulses
|
||||
// fetch_done) -- without this guard, THIS SAME cycle would
|
||||
// read the OLD (pre-increment) wgt_fetched to compute
|
||||
// pf_w_addr, re-issuing a fetch for the tile that JUST
|
||||
// completed instead of the next one, and that duplicate
|
||||
// fetch's own completion would then write into the NEXT
|
||||
// tile's SRAM slot using the WRONG (duplicated) source data
|
||||
// -- silently corrupting every other tile for any n_tiles>1
|
||||
// job (found via simulation once a >1-tile test was run;
|
||||
// every n_tiles=1 test in this file's own first pass never
|
||||
// exercised this path). Same bug class as ERR-0006's own
|
||||
// "don't gate solely on a signal with its own same-cycle
|
||||
// side effect" lesson.
|
||||
if (job_active_reg && !pf_busy && !pf_start && !pf_done &&
|
||||
({{(16-CNTW){1'b0}}, wgt_fetched} < n_tiles_reg)) begin
|
||||
pf_start <= 1'b1;
|
||||
pf_w_addr <= w_base_reg + (wgt_fetched * P_IN[ADDR_WIDTH-1:0]);
|
||||
end
|
||||
|
||||
case (state)
|
||||
ST_IDLE: begin
|
||||
if (job_start) begin
|
||||
x_base_reg <= x_base;
|
||||
w_base_reg <= w_base;
|
||||
n_tiles_reg <= n_tiles;
|
||||
result_addr_reg <= result_addr;
|
||||
tile_idx <= {CNTW{1'b0}};
|
||||
wgt_fetched <= {CNTW{1'b0}};
|
||||
read_issued <= 1'b0;
|
||||
read_ready <= 1'b0;
|
||||
operand_valid <= 1'b0;
|
||||
job_active_reg <= 1'b1;
|
||||
state <= ST_RUN;
|
||||
end
|
||||
end
|
||||
|
||||
ST_RUN: begin
|
||||
if (!operand_valid && !read_issued && !read_ready && can_present) begin
|
||||
act_rd_en <= 1'b1;
|
||||
act_rd_addr <= tile_idx[TIW-1:0]; // valid: gated < n_tiles <= MAX_TILES
|
||||
wgt_rd_en <= 1'b1;
|
||||
wgt_rd_addr <= tile_idx[TIW-1:0];
|
||||
read_issued <= 1'b1;
|
||||
end else if (read_issued) begin
|
||||
// SRAM's own always block has now seen rd_en
|
||||
// (this cycle) and scheduled rd_data_reg<=mem[addr]
|
||||
// for the NEXT cycle -- wait one more cycle before
|
||||
// trusting act_rd_data/wgt_rd_data.
|
||||
read_issued <= 1'b0;
|
||||
read_ready <= 1'b1;
|
||||
end else if (read_ready) begin
|
||||
operand_valid <= 1'b1;
|
||||
input_data <= act_rd_data;
|
||||
weight_data <= wgt_rd_data;
|
||||
tile_last <= ({{(16-CNTW){1'b0}}, tile_idx} == n_tiles_reg - 16'd1);
|
||||
read_ready <= 1'b0;
|
||||
end else if (operand_valid && operand_ready) begin
|
||||
operand_valid <= 1'b0;
|
||||
if ({{(16-CNTW){1'b0}}, tile_idx} + 16'd1 == n_tiles_reg) begin
|
||||
job_active_reg <= 1'b0;
|
||||
state <= ST_WAIT_RESULT;
|
||||
end else begin
|
||||
tile_idx <= tile_idx + 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ST_WAIT_RESULT: begin
|
||||
result_ready <= 1'b1;
|
||||
if (result_valid && result_ready) begin
|
||||
wr_mem_wdata <= result_addr_reg[0] ? {result_data, 8'h00} : {8'h00, result_data};
|
||||
wr_mem_lb_n <= result_addr_reg[0] ? 1'b1 : 1'b0;
|
||||
wr_mem_ub_n <= result_addr_reg[0] ? 1'b0 : 1'b1;
|
||||
state <= ST_WRITE_RES;
|
||||
end
|
||||
end
|
||||
|
||||
ST_WRITE_RES: begin
|
||||
wr_mem_req <= 1'b1;
|
||||
wr_mem_addr <= result_addr_reg[ADDR_WIDTH-1:1];
|
||||
state <= ST_DONE;
|
||||
end
|
||||
|
||||
ST_DONE: begin
|
||||
wr_mem_req <= 1'b0;
|
||||
if (mem_ready) begin
|
||||
job_done <= 1'b1;
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
default: state <= ST_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,232 @@
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- STEP11: per-slot memory manager, REAL
|
||||
// WEIGHT PREFETCH variant ("_pf").
|
||||
//
|
||||
// Identical external interface to nms_memory_manager.v (drop-in, same
|
||||
// Director/Dependency-Manager side, same Neural-Processor-facing
|
||||
// operand/result streams) -- the ONLY change is the private weight
|
||||
// path: prefetch_engine.v (single-shot, one tile in flight, real
|
||||
// per-tile control-restart overhead, ERR-0013/STEP11's own analysis)
|
||||
// is replaced by weight_prefetch_engine.v (continuous multi-tile
|
||||
// fetch stream, configurable PREFETCH_DISTANCE lookahead window).
|
||||
//
|
||||
// nms_memory_manager.v itself is UNTOUCHED -- this file exists
|
||||
// alongside it specifically so "Current NMS" (baseline) and "NMS +
|
||||
// weight prefetch" remain independently reproducible for the A/B
|
||||
// comparison STEP11 explicitly requires.
|
||||
// ============================================================
|
||||
module nms_memory_manager_pf #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire job_start,
|
||||
input wire [ADDR_WIDTH-1:0] x_base,
|
||||
input wire [ADDR_WIDTH-1:0] w_base,
|
||||
input wire [15:0] n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] result_addr,
|
||||
output reg job_done,
|
||||
|
||||
output reg operand_valid,
|
||||
input wire operand_ready,
|
||||
output reg signed [DATA_WIDTH*P_IN-1:0] input_data,
|
||||
output reg signed [DATA_WIDTH*P_IN-1:0] weight_data,
|
||||
output reg tile_last,
|
||||
|
||||
input wire result_valid,
|
||||
output reg result_ready,
|
||||
input wire signed [DATA_WIDTH-1:0] result_data,
|
||||
|
||||
output wire job_active,
|
||||
output wire [ADDR_WIDTH-1:0] job_x_base,
|
||||
output wire [15:0] job_n_tiles,
|
||||
|
||||
input wire [ADDR_WIDTH-1:0] act_resident_tag,
|
||||
input wire [CNTW-1:0] act_resident_count,
|
||||
|
||||
output reg act_rd_en,
|
||||
output reg [TIW-1:0] act_rd_addr,
|
||||
input wire signed [DATA_WIDTH*P_IN-1:0] act_rd_data,
|
||||
|
||||
// ---- weight SRAM fill port now driven by weight_prefetch_engine
|
||||
// (below), NOT by this FSM directly -- read port unchanged ----
|
||||
output wire wgt_fill_we,
|
||||
output wire [TIW-1:0] wgt_fill_addr,
|
||||
output wire [DATA_WIDTH*P_IN-1:0] wgt_fill_data,
|
||||
output reg wgt_rd_en,
|
||||
output reg [TIW-1:0] wgt_rd_addr,
|
||||
input wire signed [DATA_WIDTH*P_IN-1:0] wgt_rd_data,
|
||||
|
||||
output wire mem_req,
|
||||
output wire mem_wr,
|
||||
output wire [ADDR_WIDTH-1:0] mem_addr,
|
||||
output wire [15:0] mem_wdata,
|
||||
output wire mem_lb_n,
|
||||
output wire mem_ub_n,
|
||||
input wire [15:0] mem_rdata,
|
||||
input wire mem_ready
|
||||
);
|
||||
|
||||
localparam ST_IDLE = 3'd0;
|
||||
localparam ST_RUN = 3'd1;
|
||||
localparam ST_WAIT_RESULT = 3'd2;
|
||||
localparam ST_WRITE_RES = 3'd3;
|
||||
localparam ST_DONE = 3'd4;
|
||||
|
||||
reg [2:0] state;
|
||||
reg job_active_reg;
|
||||
assign job_active = job_active_reg;
|
||||
assign job_x_base = x_base_reg;
|
||||
assign job_n_tiles = n_tiles_reg;
|
||||
|
||||
reg [ADDR_WIDTH-1:0] x_base_reg, w_base_reg, result_addr_reg;
|
||||
reg [15:0] n_tiles_reg;
|
||||
reg [CNTW-1:0] tile_idx;
|
||||
|
||||
reg read_issued;
|
||||
reg read_ready;
|
||||
|
||||
wire [CNTW-1:0] wgt_ready_count;
|
||||
|
||||
wire usable_act_count_valid = (act_resident_tag == x_base_reg);
|
||||
wire [CNTW-1:0] usable_act = usable_act_count_valid ? act_resident_count : {CNTW{1'b0}};
|
||||
wire can_present = ({{(16-CNTW){1'b0}}, tile_idx} < n_tiles_reg) &&
|
||||
(tile_idx < wgt_ready_count) &&
|
||||
(tile_idx < usable_act);
|
||||
|
||||
// ---- REAL weight prefetch engine (STEP11): continuous multi-tile
|
||||
// fetch stream, PREFETCH_DISTANCE-bounded lookahead ahead of
|
||||
// tile_idx (this module's own consumption pointer) ----
|
||||
weight_prefetch_engine #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH),
|
||||
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_wpf (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_active(job_active_reg), .w_base(w_base_reg), .n_tiles(n_tiles_reg),
|
||||
.consumed_count(tile_idx),
|
||||
.wgt_fill_we(wgt_fill_we), .wgt_fill_addr(wgt_fill_addr), .wgt_fill_data(wgt_fill_data),
|
||||
.ready_count(wgt_ready_count),
|
||||
.mem_req(mem_req_wpf), .mem_wr(mem_wr_wpf), .mem_addr(mem_addr_wpf), .mem_wdata(mem_wdata_wpf),
|
||||
.mem_lb_n(mem_lb_n_wpf), .mem_ub_n(mem_ub_n_wpf),
|
||||
.mem_rdata(mem_rdata), .mem_ready(mem_ready)
|
||||
);
|
||||
wire mem_req_wpf, mem_wr_wpf;
|
||||
wire [ADDR_WIDTH-1:0] mem_addr_wpf;
|
||||
wire [15:0] mem_wdata_wpf;
|
||||
wire mem_lb_n_wpf, mem_ub_n_wpf;
|
||||
|
||||
reg wr_mem_req;
|
||||
reg [ADDR_WIDTH-1:0] wr_mem_addr;
|
||||
reg [15:0] wr_mem_wdata;
|
||||
reg wr_mem_lb_n, wr_mem_ub_n;
|
||||
|
||||
wire wr_active = (state == ST_WRITE_RES) || (state == ST_DONE);
|
||||
assign mem_req = wr_active ? wr_mem_req : mem_req_wpf;
|
||||
assign mem_wr = wr_active ? 1'b1 : mem_wr_wpf;
|
||||
assign mem_addr = wr_active ? wr_mem_addr : mem_addr_wpf;
|
||||
assign mem_wdata = wr_active ? wr_mem_wdata : mem_wdata_wpf;
|
||||
assign mem_lb_n = wr_active ? wr_mem_lb_n : mem_lb_n_wpf;
|
||||
assign mem_ub_n = wr_active ? wr_mem_ub_n : mem_ub_n_wpf;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= ST_IDLE;
|
||||
job_active_reg <= 1'b0;
|
||||
job_done <= 1'b0;
|
||||
operand_valid <= 1'b0;
|
||||
tile_last <= 1'b0;
|
||||
result_ready <= 1'b0;
|
||||
tile_idx <= {CNTW{1'b0}};
|
||||
read_issued <= 1'b0;
|
||||
read_ready <= 1'b0;
|
||||
act_rd_en <= 1'b0;
|
||||
wgt_rd_en <= 1'b0;
|
||||
wr_mem_req <= 1'b0;
|
||||
wr_mem_lb_n <= 1'b1;
|
||||
wr_mem_ub_n <= 1'b1;
|
||||
end else begin
|
||||
job_done <= 1'b0;
|
||||
act_rd_en <= 1'b0;
|
||||
wgt_rd_en <= 1'b0;
|
||||
result_ready <= 1'b0;
|
||||
|
||||
case (state)
|
||||
ST_IDLE: begin
|
||||
if (job_start) begin
|
||||
x_base_reg <= x_base;
|
||||
w_base_reg <= w_base;
|
||||
n_tiles_reg <= n_tiles;
|
||||
result_addr_reg <= result_addr;
|
||||
tile_idx <= {CNTW{1'b0}};
|
||||
read_issued <= 1'b0;
|
||||
read_ready <= 1'b0;
|
||||
operand_valid <= 1'b0;
|
||||
job_active_reg <= 1'b1;
|
||||
state <= ST_RUN;
|
||||
end
|
||||
end
|
||||
|
||||
ST_RUN: begin
|
||||
if (!operand_valid && !read_issued && !read_ready && can_present) begin
|
||||
act_rd_en <= 1'b1;
|
||||
act_rd_addr <= tile_idx[TIW-1:0];
|
||||
wgt_rd_en <= 1'b1;
|
||||
wgt_rd_addr <= tile_idx[TIW-1:0];
|
||||
read_issued <= 1'b1;
|
||||
end else if (read_issued) begin
|
||||
read_issued <= 1'b0;
|
||||
read_ready <= 1'b1;
|
||||
end else if (read_ready) begin
|
||||
operand_valid <= 1'b1;
|
||||
input_data <= act_rd_data;
|
||||
weight_data <= wgt_rd_data;
|
||||
tile_last <= ({{(16-CNTW){1'b0}}, tile_idx} == n_tiles_reg - 16'd1);
|
||||
read_ready <= 1'b0;
|
||||
end else if (operand_valid && operand_ready) begin
|
||||
operand_valid <= 1'b0;
|
||||
if ({{(16-CNTW){1'b0}}, tile_idx} + 16'd1 == n_tiles_reg) begin
|
||||
job_active_reg <= 1'b0;
|
||||
state <= ST_WAIT_RESULT;
|
||||
end else begin
|
||||
tile_idx <= tile_idx + 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ST_WAIT_RESULT: begin
|
||||
result_ready <= 1'b1;
|
||||
if (result_valid && result_ready) begin
|
||||
wr_mem_wdata <= result_addr_reg[0] ? {result_data, 8'h00} : {8'h00, result_data};
|
||||
wr_mem_lb_n <= result_addr_reg[0] ? 1'b1 : 1'b0;
|
||||
wr_mem_ub_n <= result_addr_reg[0] ? 1'b0 : 1'b1;
|
||||
state <= ST_WRITE_RES;
|
||||
end
|
||||
end
|
||||
|
||||
ST_WRITE_RES: begin
|
||||
wr_mem_req <= 1'b1;
|
||||
wr_mem_addr <= result_addr_reg[ADDR_WIDTH-1:1];
|
||||
state <= ST_DONE;
|
||||
end
|
||||
|
||||
ST_DONE: begin
|
||||
wr_mem_req <= 1'b0;
|
||||
if (mem_ready) begin
|
||||
job_done <= 1'b1;
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
default: state <= ST_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,310 @@
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- STEP13: per-slot memory manager,
|
||||
// PIPELINED CONTINUOUS TILE STREAM variant ("_stream").
|
||||
//
|
||||
// Identical external interface to nms_memory_manager_pf.v (drop-in,
|
||||
// same Director/Dependency-Manager side, same Neural-Processor-facing
|
||||
// operand/result streams, same weight_prefetch_engine.v instance) --
|
||||
// the ONLY change is INSIDE ST_RUN: the operand-delivery pipeline that
|
||||
// feeds neural_processor.v.
|
||||
//
|
||||
// EXP-0025 traced nms_memory_manager_pf.v's own ST_RUN state with a
|
||||
// zero-real-memory-latency configuration and found it costs EXACTLY 4
|
||||
// cycles/tile in steady state (read_issued -> read_ready -> present
|
||||
// -> consumed, a strictly sequential if/else-if chain with ZERO
|
||||
// overlap between consecutive tiles), even though neither side of the
|
||||
// interface requires it: the local activation/weight SRAMs
|
||||
// (nms_activation_replicated.v / nms_weight_packed.v) have only a
|
||||
// 1-cycle rd_en-to-data latency, and neural_processor.v's own
|
||||
// operand_ready is held continuously high through the whole
|
||||
// NP_WAIT_OPERANDS phase (its datapath is explicitly designed to
|
||||
// accept a new tile every cycle). That 4-cycles/tile serialization
|
||||
// was found to account for 93.4% of EXP-0024's real, measured
|
||||
// "non-memory" cycle floor (DEC-0024) -- the dominant real bottleneck,
|
||||
// NOT per-job dispatch overhead.
|
||||
//
|
||||
// This variant replaces ST_RUN's 4-state chain with a pipelined
|
||||
// read-ahead design:
|
||||
// - `rd_ptr` (CNTW bits): the tile index whose SRAM read has been
|
||||
// (or is about to be) ISSUED -- independent of, and normally one
|
||||
// tile AHEAD of, `tile_idx` (the CONSUMPTION pointer, i.e. how
|
||||
// many tiles neural_processor.v has actually accepted).
|
||||
// - a 1-deep skid buffer (`buf_valid`/`buf_input`/`buf_weight`/
|
||||
// `buf_last`) holds one tile's fully-read SRAM data, presented to
|
||||
// NP as `operand_valid`/`input_data`/`weight_data`/`tile_last`.
|
||||
// - every cycle: if a read was issued last cycle (`rd_pending`), its
|
||||
// data is now valid (1-cycle SRAM latency) and is captured into
|
||||
// the skid buffer; independently, a NEW read is issued for
|
||||
// `rd_ptr` whenever it is legal to do so (in bounds, weight+
|
||||
// activation ready) AND the skid buffer will not overflow (it is
|
||||
// empty, or being drained -- consumed -- this very same cycle).
|
||||
// Since NP's own operand_ready is continuously high through the tile-
|
||||
// loading phase, the skid buffer is drained every cycle it is full,
|
||||
// so a new read can be issued every cycle too: sustained ~1 cycle/
|
||||
// tile, down from 4 -- a real ~4x reduction in the dominant component
|
||||
// of EXP-0024's measured floor.
|
||||
//
|
||||
// `tile_idx` (the CONSUMPTION pointer) is still what is fed to
|
||||
// weight_prefetch_engine.v's own `consumed_count` port -- its
|
||||
// external contract (bound the lookahead window against how far the
|
||||
// CONSUMER has progressed) is unchanged; only the local SRAM
|
||||
// read-issue pointer (`rd_ptr`) is new, and it can run up to ONE tile
|
||||
// ahead of `tile_idx` (the skid buffer's own depth), same as before
|
||||
// conceptually (read_issued/read_ready already implied a similar
|
||||
// small lookahead, just serialized rather than pipelined).
|
||||
//
|
||||
// nms_memory_manager_pf.v itself is UNTOUCHED -- this file exists
|
||||
// alongside it (and alongside the original nms_memory_manager.v) so
|
||||
// all three ("Current NMS", "NMS + weight prefetch",
|
||||
// "NMS + weight prefetch + continuous tile stream") remain
|
||||
// independently reproducible for A/B/C comparison.
|
||||
// ============================================================
|
||||
module nms_memory_manager_stream #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire job_start,
|
||||
input wire [ADDR_WIDTH-1:0] x_base,
|
||||
input wire [ADDR_WIDTH-1:0] w_base,
|
||||
input wire [15:0] n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] result_addr,
|
||||
output reg job_done,
|
||||
|
||||
output wire operand_valid,
|
||||
input wire operand_ready,
|
||||
output wire signed [DATA_WIDTH*P_IN-1:0] input_data,
|
||||
output wire signed [DATA_WIDTH*P_IN-1:0] weight_data,
|
||||
output wire tile_last,
|
||||
|
||||
input wire result_valid,
|
||||
output reg result_ready,
|
||||
input wire signed [DATA_WIDTH-1:0] result_data,
|
||||
|
||||
output wire job_active,
|
||||
output wire [ADDR_WIDTH-1:0] job_x_base,
|
||||
output wire [15:0] job_n_tiles,
|
||||
|
||||
input wire [ADDR_WIDTH-1:0] act_resident_tag,
|
||||
input wire [CNTW-1:0] act_resident_count,
|
||||
|
||||
output reg act_rd_en,
|
||||
output reg [TIW-1:0] act_rd_addr,
|
||||
input wire signed [DATA_WIDTH*P_IN-1:0] act_rd_data,
|
||||
|
||||
// ---- weight SRAM fill port driven by weight_prefetch_engine
|
||||
// (below), NOT by this FSM directly -- read port unchanged ----
|
||||
output wire wgt_fill_we,
|
||||
output wire [TIW-1:0] wgt_fill_addr,
|
||||
output wire [DATA_WIDTH*P_IN-1:0] wgt_fill_data,
|
||||
output reg wgt_rd_en,
|
||||
output reg [TIW-1:0] wgt_rd_addr,
|
||||
input wire signed [DATA_WIDTH*P_IN-1:0] wgt_rd_data,
|
||||
|
||||
output wire mem_req,
|
||||
output wire mem_wr,
|
||||
output wire [ADDR_WIDTH-1:0] mem_addr,
|
||||
output wire [15:0] mem_wdata,
|
||||
output wire mem_lb_n,
|
||||
output wire mem_ub_n,
|
||||
input wire [15:0] mem_rdata,
|
||||
input wire mem_ready
|
||||
);
|
||||
|
||||
localparam ST_IDLE = 3'd0;
|
||||
localparam ST_RUN = 3'd1;
|
||||
localparam ST_WAIT_RESULT = 3'd2;
|
||||
localparam ST_WRITE_RES = 3'd3;
|
||||
localparam ST_DONE = 3'd4;
|
||||
|
||||
reg [2:0] state;
|
||||
reg job_active_reg;
|
||||
assign job_active = job_active_reg;
|
||||
assign job_x_base = x_base_reg;
|
||||
assign job_n_tiles = n_tiles_reg;
|
||||
|
||||
reg [ADDR_WIDTH-1:0] x_base_reg, w_base_reg, result_addr_reg;
|
||||
reg [15:0] n_tiles_reg;
|
||||
reg [CNTW-1:0] tile_idx; // CONSUMPTION pointer (tiles handed to NP so far)
|
||||
reg [CNTW-1:0] rd_ptr; // READ-ISSUE pointer (tiles whose SRAM read has been issued)
|
||||
|
||||
wire [CNTW-1:0] wgt_ready_count;
|
||||
|
||||
wire usable_act_count_valid = (act_resident_tag == x_base_reg);
|
||||
wire [CNTW-1:0] usable_act = usable_act_count_valid ? act_resident_count : {CNTW{1'b0}};
|
||||
|
||||
// Gating for the READ-ISSUE pointer (rd_ptr), same semantics as
|
||||
// the old can_present but evaluated against rd_ptr instead of
|
||||
// tile_idx, since reads may now run ahead of consumption.
|
||||
wire can_issue_rd = ({{(16-CNTW){1'b0}}, rd_ptr} < n_tiles_reg) &&
|
||||
(rd_ptr < wgt_ready_count) &&
|
||||
(rd_ptr < usable_act);
|
||||
|
||||
// ---- pipelined read-ahead + 1-deep skid buffer ----
|
||||
reg rd_pending; // a read issued last cycle; its data is valid THIS cycle
|
||||
reg [CNTW-1:0] rd_pending_tile;
|
||||
reg rd_pending_last;
|
||||
reg buf_valid;
|
||||
reg signed [DATA_WIDTH*P_IN-1:0] buf_input, buf_weight;
|
||||
reg buf_last;
|
||||
|
||||
assign operand_valid = buf_valid;
|
||||
assign input_data = buf_input;
|
||||
assign weight_data = buf_weight;
|
||||
assign tile_last = buf_last;
|
||||
|
||||
// May issue a new read this cycle iff the skid buffer will not
|
||||
// overflow: it's currently empty, or it is being drained
|
||||
// (consumed) THIS cycle.
|
||||
wire buf_will_be_free = !buf_valid || (operand_valid && operand_ready);
|
||||
wire issue_rd_now = (state == ST_RUN) && can_issue_rd && buf_will_be_free;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= ST_IDLE;
|
||||
job_active_reg <= 1'b0;
|
||||
job_done <= 1'b0;
|
||||
result_ready <= 1'b0;
|
||||
tile_idx <= {CNTW{1'b0}};
|
||||
rd_ptr <= {CNTW{1'b0}};
|
||||
rd_pending <= 1'b0;
|
||||
buf_valid <= 1'b0;
|
||||
act_rd_en <= 1'b0;
|
||||
wgt_rd_en <= 1'b0;
|
||||
wr_mem_req <= 1'b0;
|
||||
wr_mem_lb_n <= 1'b1;
|
||||
wr_mem_ub_n <= 1'b1;
|
||||
end else begin
|
||||
job_done <= 1'b0;
|
||||
act_rd_en <= 1'b0;
|
||||
wgt_rd_en <= 1'b0;
|
||||
result_ready <= 1'b0;
|
||||
|
||||
case (state)
|
||||
ST_IDLE: begin
|
||||
if (job_start) begin
|
||||
x_base_reg <= x_base;
|
||||
w_base_reg <= w_base;
|
||||
n_tiles_reg <= n_tiles;
|
||||
result_addr_reg <= result_addr;
|
||||
tile_idx <= {CNTW{1'b0}};
|
||||
rd_ptr <= {CNTW{1'b0}};
|
||||
rd_pending <= 1'b0;
|
||||
buf_valid <= 1'b0;
|
||||
job_active_reg <= 1'b1;
|
||||
state <= ST_RUN;
|
||||
end
|
||||
end
|
||||
|
||||
ST_RUN: begin
|
||||
// ---- Step 1: a read issued LAST cycle lands now ----
|
||||
if (rd_pending) begin
|
||||
buf_valid <= 1'b1;
|
||||
buf_input <= act_rd_data;
|
||||
buf_weight <= wgt_rd_data;
|
||||
buf_last <= rd_pending_last;
|
||||
end else if (operand_valid && operand_ready) begin
|
||||
// no new data arriving this cycle -- if the
|
||||
// buffer is being drained and nothing refills
|
||||
// it, it goes empty.
|
||||
buf_valid <= 1'b0;
|
||||
end
|
||||
|
||||
// ---- Step 2: consumption bookkeeping ----
|
||||
if (operand_valid && operand_ready) begin
|
||||
if ({{(16-CNTW){1'b0}}, tile_idx} + 16'd1 == n_tiles_reg) begin
|
||||
job_active_reg <= 1'b0;
|
||||
state <= ST_WAIT_RESULT;
|
||||
end else begin
|
||||
tile_idx <= tile_idx + 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
// ---- Step 3: issue the NEXT read, if legal ----
|
||||
if (issue_rd_now) begin
|
||||
act_rd_en <= 1'b1;
|
||||
act_rd_addr <= rd_ptr[TIW-1:0];
|
||||
wgt_rd_en <= 1'b1;
|
||||
wgt_rd_addr <= rd_ptr[TIW-1:0];
|
||||
rd_pending <= 1'b1;
|
||||
rd_pending_tile <= rd_ptr;
|
||||
rd_pending_last <= ({{(16-CNTW){1'b0}}, rd_ptr} == n_tiles_reg - 16'd1);
|
||||
rd_ptr <= rd_ptr + 1'b1;
|
||||
end else begin
|
||||
rd_pending <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
ST_WAIT_RESULT: begin
|
||||
result_ready <= 1'b1;
|
||||
if (result_valid && result_ready) begin
|
||||
wr_mem_wdata <= result_addr_reg[0] ? {result_data, 8'h00} : {8'h00, result_data};
|
||||
wr_mem_lb_n <= result_addr_reg[0] ? 1'b1 : 1'b0;
|
||||
wr_mem_ub_n <= result_addr_reg[0] ? 1'b0 : 1'b1;
|
||||
state <= ST_WRITE_RES;
|
||||
end
|
||||
end
|
||||
|
||||
ST_WRITE_RES: begin
|
||||
wr_mem_req <= 1'b1;
|
||||
wr_mem_addr <= result_addr_reg[ADDR_WIDTH-1:1];
|
||||
state <= ST_DONE;
|
||||
end
|
||||
|
||||
ST_DONE: begin
|
||||
wr_mem_req <= 1'b0;
|
||||
if (mem_ready) begin
|
||||
job_done <= 1'b1;
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
default: state <= ST_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ---- REAL weight prefetch engine (STEP11): unchanged from
|
||||
// nms_memory_manager_pf.v -- consumed_count is still the
|
||||
// CONSUMPTION pointer (tile_idx), not the read-issue pointer
|
||||
// (rd_ptr): the engine's own lookahead window is bounded by how
|
||||
// far the CONSUMER has progressed, exactly as before. ----
|
||||
weight_prefetch_engine #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH),
|
||||
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_wpf (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_active(job_active_reg), .w_base(w_base_reg), .n_tiles(n_tiles_reg),
|
||||
.consumed_count(tile_idx),
|
||||
.wgt_fill_we(wgt_fill_we), .wgt_fill_addr(wgt_fill_addr), .wgt_fill_data(wgt_fill_data),
|
||||
.ready_count(wgt_ready_count),
|
||||
.mem_req(mem_req_wpf), .mem_wr(mem_wr_wpf), .mem_addr(mem_addr_wpf), .mem_wdata(mem_wdata_wpf),
|
||||
.mem_lb_n(mem_lb_n_wpf), .mem_ub_n(mem_ub_n_wpf),
|
||||
.mem_rdata(mem_rdata), .mem_ready(mem_ready)
|
||||
);
|
||||
wire mem_req_wpf, mem_wr_wpf;
|
||||
wire [ADDR_WIDTH-1:0] mem_addr_wpf;
|
||||
wire [15:0] mem_wdata_wpf;
|
||||
wire mem_lb_n_wpf, mem_ub_n_wpf;
|
||||
|
||||
reg wr_mem_req;
|
||||
reg [ADDR_WIDTH-1:0] wr_mem_addr;
|
||||
reg [15:0] wr_mem_wdata;
|
||||
reg wr_mem_lb_n, wr_mem_ub_n;
|
||||
|
||||
wire wr_active = (state == ST_WRITE_RES) || (state == ST_DONE);
|
||||
assign mem_req = wr_active ? wr_mem_req : mem_req_wpf;
|
||||
assign mem_wr = wr_active ? 1'b1 : mem_wr_wpf;
|
||||
assign mem_addr = wr_active ? wr_mem_addr : mem_addr_wpf;
|
||||
assign mem_wdata = wr_active ? wr_mem_wdata : mem_wdata_wpf;
|
||||
assign mem_lb_n = wr_active ? wr_mem_lb_n : mem_lb_n_wpf;
|
||||
assign mem_ub_n = wr_active ? wr_mem_ub_n : mem_ub_n_wpf;
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,321 @@
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- STEP13: per-slot memory manager,
|
||||
// PIPELINED CONTINUOUS TILE STREAM variant ("_stream").
|
||||
//
|
||||
// Identical external interface to nms_memory_manager_pf.v (drop-in,
|
||||
// same Director/Dependency-Manager side, same Neural-Processor-facing
|
||||
// operand/result streams, same weight_prefetch_engine.v instance) --
|
||||
// the ONLY change is INSIDE ST_RUN: the operand-delivery pipeline that
|
||||
// feeds neural_processor.v.
|
||||
//
|
||||
// EXP-0025 traced nms_memory_manager_pf.v's own ST_RUN state with a
|
||||
// zero-real-memory-latency configuration and found it costs EXACTLY 4
|
||||
// cycles/tile in steady state (read_issued -> read_ready -> present
|
||||
// -> consumed, a strictly sequential if/else-if chain with ZERO
|
||||
// overlap between consecutive tiles), even though neither side of the
|
||||
// interface requires it: the local activation/weight SRAMs
|
||||
// (nms_activation_replicated.v / nms_weight_packed.v) have only a
|
||||
// 1-cycle rd_en-to-data latency, and neural_processor.v's own
|
||||
// operand_ready is held continuously high through the whole
|
||||
// NP_WAIT_OPERANDS phase (its datapath is explicitly designed to
|
||||
// accept a new tile every cycle). That 4-cycles/tile serialization
|
||||
// was found to account for 93.4% of EXP-0024's real, measured
|
||||
// "non-memory" cycle floor (DEC-0024) -- the dominant real bottleneck,
|
||||
// NOT per-job dispatch overhead.
|
||||
//
|
||||
// This variant replaces ST_RUN's 4-state chain with a pipelined
|
||||
// read-ahead design:
|
||||
// - `rd_ptr` (CNTW bits): the tile index whose SRAM read has been
|
||||
// (or is about to be) ISSUED -- independent of, and normally one
|
||||
// tile AHEAD of, `tile_idx` (the CONSUMPTION pointer, i.e. how
|
||||
// many tiles neural_processor.v has actually accepted).
|
||||
// - a 1-deep skid buffer (`buf_valid`/`buf_input`/`buf_weight`/
|
||||
// `buf_last`) holds one tile's fully-read SRAM data, presented to
|
||||
// NP as `operand_valid`/`input_data`/`weight_data`/`tile_last`.
|
||||
// - every cycle: if a read was issued last cycle (`rd_pending`), its
|
||||
// data is now valid (1-cycle SRAM latency) and is captured into
|
||||
// the skid buffer; independently, a NEW read is issued for
|
||||
// `rd_ptr` whenever it is legal to do so (in bounds, weight+
|
||||
// activation ready) AND the skid buffer will not overflow (it is
|
||||
// empty, or being drained -- consumed -- this very same cycle).
|
||||
// Since NP's own operand_ready is continuously high through the tile-
|
||||
// loading phase, the skid buffer is drained every cycle it is full,
|
||||
// so a new read can be issued every cycle too: sustained ~1 cycle/
|
||||
// tile, down from 4 -- a real ~4x reduction in the dominant component
|
||||
// of EXP-0024's measured floor.
|
||||
//
|
||||
// `tile_idx` (the CONSUMPTION pointer) is still what is fed to
|
||||
// weight_prefetch_engine.v's own `consumed_count` port -- its
|
||||
// external contract (bound the lookahead window against how far the
|
||||
// CONSUMER has progressed) is unchanged; only the local SRAM
|
||||
// read-issue pointer (`rd_ptr`) is new, and it can run up to ONE tile
|
||||
// ahead of `tile_idx` (the skid buffer's own depth), same as before
|
||||
// conceptually (read_issued/read_ready already implied a similar
|
||||
// small lookahead, just serialized rather than pipelined).
|
||||
//
|
||||
// nms_memory_manager_pf.v itself is UNTOUCHED -- this file exists
|
||||
// alongside it (and alongside the original nms_memory_manager.v) so
|
||||
// all three ("Current NMS", "NMS + weight prefetch",
|
||||
// "NMS + weight prefetch + continuous tile stream") remain
|
||||
// independently reproducible for A/B/C comparison.
|
||||
// ============================================================
|
||||
module nms_memory_manager_stream_wide #(
|
||||
parameter MEM_DATA_WIDTH = 64,
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
parameter CNTW = $clog2(MAX_TILES+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire job_start,
|
||||
input wire [ADDR_WIDTH-1:0] x_base,
|
||||
input wire [ADDR_WIDTH-1:0] w_base,
|
||||
input wire [15:0] n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] result_addr,
|
||||
output reg job_done,
|
||||
|
||||
output wire operand_valid,
|
||||
input wire operand_ready,
|
||||
output wire signed [DATA_WIDTH*P_IN-1:0] input_data,
|
||||
output wire signed [DATA_WIDTH*P_IN-1:0] weight_data,
|
||||
output wire tile_last,
|
||||
|
||||
input wire result_valid,
|
||||
output reg result_ready,
|
||||
input wire signed [DATA_WIDTH-1:0] result_data,
|
||||
|
||||
output wire job_active,
|
||||
output wire [ADDR_WIDTH-1:0] job_x_base,
|
||||
output wire [15:0] job_n_tiles,
|
||||
|
||||
input wire [ADDR_WIDTH-1:0] act_resident_tag,
|
||||
input wire [CNTW-1:0] act_resident_count,
|
||||
|
||||
output reg act_rd_en,
|
||||
output reg [TIW-1:0] act_rd_addr,
|
||||
input wire signed [DATA_WIDTH*P_IN-1:0] act_rd_data,
|
||||
|
||||
// ---- weight SRAM fill port driven by weight_prefetch_engine
|
||||
// (below), NOT by this FSM directly -- read port unchanged ----
|
||||
output wire wgt_fill_we,
|
||||
output wire [TIW-1:0] wgt_fill_addr,
|
||||
output wire [DATA_WIDTH*P_IN-1:0] wgt_fill_data,
|
||||
output reg wgt_rd_en,
|
||||
output reg [TIW-1:0] wgt_rd_addr,
|
||||
input wire signed [DATA_WIDTH*P_IN-1:0] wgt_rd_data,
|
||||
|
||||
// ---- result write-back only in this experimental variant (real
|
||||
// 16-bit-word protocol, matches memory_interface.v exactly) --
|
||||
// weight fetch uses the separate wide logical port below instead
|
||||
// of sharing this one, since this variant exists purely to
|
||||
// explore logical weight-path width in isolation (STEP14 Part A).
|
||||
output wire mem_req,
|
||||
output wire mem_wr,
|
||||
output wire [ADDR_WIDTH-1:0] mem_addr,
|
||||
output wire [15:0] mem_wdata,
|
||||
output wire mem_lb_n,
|
||||
output wire mem_ub_n,
|
||||
input wire [15:0] mem_rdata,
|
||||
input wire mem_ready,
|
||||
|
||||
// ---- separate wide logical weight-fetch port (ideal_memory_
|
||||
// model_wide.v or a real packing adapter, STEP14 Part A/A5) ----
|
||||
output wire wide_mem_req,
|
||||
output wire [ADDR_WIDTH-1:0] wide_mem_addr,
|
||||
input wire [MEM_DATA_WIDTH-1:0] wide_mem_rdata,
|
||||
input wire wide_mem_ready
|
||||
);
|
||||
|
||||
localparam ST_IDLE = 3'd0;
|
||||
localparam ST_RUN = 3'd1;
|
||||
localparam ST_WAIT_RESULT = 3'd2;
|
||||
localparam ST_WRITE_RES = 3'd3;
|
||||
localparam ST_DONE = 3'd4;
|
||||
|
||||
reg [2:0] state;
|
||||
reg job_active_reg;
|
||||
assign job_active = job_active_reg;
|
||||
assign job_x_base = x_base_reg;
|
||||
assign job_n_tiles = n_tiles_reg;
|
||||
|
||||
reg [ADDR_WIDTH-1:0] x_base_reg, w_base_reg, result_addr_reg;
|
||||
reg [15:0] n_tiles_reg;
|
||||
reg [CNTW-1:0] tile_idx; // CONSUMPTION pointer (tiles handed to NP so far)
|
||||
reg [CNTW-1:0] rd_ptr; // READ-ISSUE pointer (tiles whose SRAM read has been issued)
|
||||
|
||||
wire [CNTW-1:0] wgt_ready_count;
|
||||
|
||||
wire usable_act_count_valid = (act_resident_tag == x_base_reg);
|
||||
wire [CNTW-1:0] usable_act = usable_act_count_valid ? act_resident_count : {CNTW{1'b0}};
|
||||
|
||||
// Gating for the READ-ISSUE pointer (rd_ptr), same semantics as
|
||||
// the old can_present but evaluated against rd_ptr instead of
|
||||
// tile_idx, since reads may now run ahead of consumption.
|
||||
wire can_issue_rd = ({{(16-CNTW){1'b0}}, rd_ptr} < n_tiles_reg) &&
|
||||
(rd_ptr < wgt_ready_count) &&
|
||||
(rd_ptr < usable_act);
|
||||
|
||||
// ---- pipelined read-ahead + 1-deep skid buffer ----
|
||||
reg rd_pending; // a read issued last cycle; its data is valid THIS cycle
|
||||
reg [CNTW-1:0] rd_pending_tile;
|
||||
reg rd_pending_last;
|
||||
reg buf_valid;
|
||||
reg signed [DATA_WIDTH*P_IN-1:0] buf_input, buf_weight;
|
||||
reg buf_last;
|
||||
|
||||
assign operand_valid = buf_valid;
|
||||
assign input_data = buf_input;
|
||||
assign weight_data = buf_weight;
|
||||
assign tile_last = buf_last;
|
||||
|
||||
// May issue a new read this cycle iff the skid buffer will not
|
||||
// overflow: it's currently empty, or it is being drained
|
||||
// (consumed) THIS cycle.
|
||||
wire buf_will_be_free = !buf_valid || (operand_valid && operand_ready);
|
||||
wire issue_rd_now = (state == ST_RUN) && can_issue_rd && buf_will_be_free;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= ST_IDLE;
|
||||
job_active_reg <= 1'b0;
|
||||
job_done <= 1'b0;
|
||||
result_ready <= 1'b0;
|
||||
tile_idx <= {CNTW{1'b0}};
|
||||
rd_ptr <= {CNTW{1'b0}};
|
||||
rd_pending <= 1'b0;
|
||||
buf_valid <= 1'b0;
|
||||
act_rd_en <= 1'b0;
|
||||
wgt_rd_en <= 1'b0;
|
||||
wr_mem_req <= 1'b0;
|
||||
wr_mem_lb_n <= 1'b1;
|
||||
wr_mem_ub_n <= 1'b1;
|
||||
end else begin
|
||||
job_done <= 1'b0;
|
||||
act_rd_en <= 1'b0;
|
||||
wgt_rd_en <= 1'b0;
|
||||
result_ready <= 1'b0;
|
||||
|
||||
case (state)
|
||||
ST_IDLE: begin
|
||||
if (job_start) begin
|
||||
x_base_reg <= x_base;
|
||||
w_base_reg <= w_base;
|
||||
n_tiles_reg <= n_tiles;
|
||||
result_addr_reg <= result_addr;
|
||||
tile_idx <= {CNTW{1'b0}};
|
||||
rd_ptr <= {CNTW{1'b0}};
|
||||
rd_pending <= 1'b0;
|
||||
buf_valid <= 1'b0;
|
||||
job_active_reg <= 1'b1;
|
||||
state <= ST_RUN;
|
||||
end
|
||||
end
|
||||
|
||||
ST_RUN: begin
|
||||
// ---- Step 1: a read issued LAST cycle lands now ----
|
||||
if (rd_pending) begin
|
||||
buf_valid <= 1'b1;
|
||||
buf_input <= act_rd_data;
|
||||
buf_weight <= wgt_rd_data;
|
||||
buf_last <= rd_pending_last;
|
||||
end else if (operand_valid && operand_ready) begin
|
||||
// no new data arriving this cycle -- if the
|
||||
// buffer is being drained and nothing refills
|
||||
// it, it goes empty.
|
||||
buf_valid <= 1'b0;
|
||||
end
|
||||
|
||||
// ---- Step 2: consumption bookkeeping ----
|
||||
if (operand_valid && operand_ready) begin
|
||||
if ({{(16-CNTW){1'b0}}, tile_idx} + 16'd1 == n_tiles_reg) begin
|
||||
job_active_reg <= 1'b0;
|
||||
state <= ST_WAIT_RESULT;
|
||||
end else begin
|
||||
tile_idx <= tile_idx + 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
// ---- Step 3: issue the NEXT read, if legal ----
|
||||
if (issue_rd_now) begin
|
||||
act_rd_en <= 1'b1;
|
||||
act_rd_addr <= rd_ptr[TIW-1:0];
|
||||
wgt_rd_en <= 1'b1;
|
||||
wgt_rd_addr <= rd_ptr[TIW-1:0];
|
||||
rd_pending <= 1'b1;
|
||||
rd_pending_tile <= rd_ptr;
|
||||
rd_pending_last <= ({{(16-CNTW){1'b0}}, rd_ptr} == n_tiles_reg - 16'd1);
|
||||
rd_ptr <= rd_ptr + 1'b1;
|
||||
end else begin
|
||||
rd_pending <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
ST_WAIT_RESULT: begin
|
||||
result_ready <= 1'b1;
|
||||
if (result_valid && result_ready) begin
|
||||
wr_mem_wdata <= result_addr_reg[0] ? {result_data, 8'h00} : {8'h00, result_data};
|
||||
wr_mem_lb_n <= result_addr_reg[0] ? 1'b1 : 1'b0;
|
||||
wr_mem_ub_n <= result_addr_reg[0] ? 1'b0 : 1'b1;
|
||||
state <= ST_WRITE_RES;
|
||||
end
|
||||
end
|
||||
|
||||
ST_WRITE_RES: begin
|
||||
wr_mem_req <= 1'b1;
|
||||
wr_mem_addr <= result_addr_reg[ADDR_WIDTH-1:1];
|
||||
state <= ST_DONE;
|
||||
end
|
||||
|
||||
ST_DONE: begin
|
||||
wr_mem_req <= 1'b0;
|
||||
if (mem_ready) begin
|
||||
job_done <= 1'b1;
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
default: state <= ST_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ---- REAL weight prefetch engine (STEP11): unchanged from
|
||||
// nms_memory_manager_pf.v -- consumed_count is still the
|
||||
// CONSUMPTION pointer (tile_idx), not the read-issue pointer
|
||||
// (rd_ptr): the engine's own lookahead window is bounded by how
|
||||
// far the CONSUMER has progressed, exactly as before. ----
|
||||
weight_prefetch_engine_wide #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ADDR_WIDTH(ADDR_WIDTH),
|
||||
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE),
|
||||
.MEM_DATA_WIDTH(MEM_DATA_WIDTH)
|
||||
) u_wpf (
|
||||
.clk(clk), .rst(rst),
|
||||
.job_active(job_active_reg), .w_base(w_base_reg), .n_tiles(n_tiles_reg),
|
||||
.consumed_count(tile_idx),
|
||||
.wgt_fill_we(wgt_fill_we), .wgt_fill_addr(wgt_fill_addr), .wgt_fill_data(wgt_fill_data),
|
||||
.ready_count(wgt_ready_count),
|
||||
.mem_req(wide_mem_req), .mem_addr(wide_mem_addr),
|
||||
.mem_rdata(wide_mem_rdata), .mem_ready(wide_mem_ready)
|
||||
);
|
||||
|
||||
// Result write-back has the real 16-bit port entirely to itself
|
||||
// in this variant (no mux needed -- weight fetch lives on the
|
||||
// separate wide port above).
|
||||
reg wr_mem_req;
|
||||
reg [ADDR_WIDTH-1:0] wr_mem_addr;
|
||||
reg [15:0] wr_mem_wdata;
|
||||
reg wr_mem_lb_n, wr_mem_ub_n;
|
||||
|
||||
assign mem_req = wr_mem_req;
|
||||
assign mem_wr = 1'b1;
|
||||
assign mem_addr = wr_mem_addr;
|
||||
assign mem_wdata = wr_mem_wdata;
|
||||
assign mem_lb_n = wr_mem_lb_n;
|
||||
assign mem_ub_n = wr_mem_ub_n;
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,122 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP9 real hardware-facing top level.
|
||||
// Mirrors hardware/v2/rtl/neural_multiprocessor.v's own scope exactly
|
||||
// (M8): nms_dataflow_core.v's N_SLOTS+1 independent Memory Backend
|
||||
// Interface ports funneled through the SAME, UNMODIFIED
|
||||
// slot_mem_arbiter.v down to the SAME, UNMODIFIED real V1 PSRAM
|
||||
// backend chain (memory_interface.v -> psram_controller.v).
|
||||
//
|
||||
// Nothing about the arbiter or the real PSRAM chain changes for the
|
||||
// NMS -- only nms_dataflow_core.v's own internals (the memory
|
||||
// organization DEC-0019/DEC-0020 decided) differ from the frozen V2
|
||||
// dataflow_core.v this module's own structure is copied from.
|
||||
// ================================================================
|
||||
|
||||
module nms_neural_multiprocessor #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 2,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PSRAM_DATA_WIDTH = 16,
|
||||
parameter CLK_FREQ_MHZ = 80
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [ADDR_WIDTH-1:0] psram_a,
|
||||
inout wire [PSRAM_DATA_WIDTH-1:0] psram_dq,
|
||||
output wire psram_ce_n,
|
||||
output wire psram_oe_n,
|
||||
output wire psram_we_n,
|
||||
output wire psram_lb_n,
|
||||
output wire psram_ub_n,
|
||||
output wire psram_zz_n
|
||||
);
|
||||
|
||||
wire [N_SLOTS:0] slot_mem_req, slot_mem_wr;
|
||||
wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr;
|
||||
wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata, slot_mem_rdata;
|
||||
wire [N_SLOTS:0] slot_mem_lb_n, slot_mem_ub_n;
|
||||
wire [N_SLOTS:0] slot_mem_ready;
|
||||
|
||||
nms_dataflow_core #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
|
||||
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
|
||||
.MAX_TILES(MAX_TILES)
|
||||
) u_dataflow_core (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
|
||||
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
|
||||
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready)
|
||||
);
|
||||
|
||||
wire arb_m_req, arb_m_wr;
|
||||
wire [ADDR_WIDTH-1:0] arb_m_addr;
|
||||
wire [15:0] arb_m_wdata;
|
||||
wire arb_m_lb_n, arb_m_ub_n;
|
||||
wire [15:0] arb_m_rdata;
|
||||
wire arb_m_ready;
|
||||
|
||||
slot_mem_arbiter #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS+1)
|
||||
) u_arbiter (
|
||||
.clk(clk), .rst(rst),
|
||||
.s_req(slot_mem_req), .s_wr(slot_mem_wr), .s_addr(slot_mem_addr),
|
||||
.s_wdata(slot_mem_wdata), .s_lb_n(slot_mem_lb_n), .s_ub_n(slot_mem_ub_n),
|
||||
.s_rdata(slot_mem_rdata), .s_ready(slot_mem_ready),
|
||||
.m_req(arb_m_req), .m_wr(arb_m_wr), .m_addr(arb_m_addr), .m_wdata(arb_m_wdata),
|
||||
.m_lb_n(arb_m_lb_n), .m_ub_n(arb_m_ub_n),
|
||||
.m_rdata(arb_m_rdata), .m_ready(arb_m_ready)
|
||||
);
|
||||
|
||||
wire pc_mem_req, pc_mem_wr;
|
||||
wire [ADDR_WIDTH-1:0] pc_mem_addr;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_wdata;
|
||||
wire pc_mem_lb_n, pc_mem_ub_n;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_rdata;
|
||||
wire pc_mem_ready;
|
||||
|
||||
memory_interface #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH)) u_memif (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(arb_m_req), .wr(arb_m_wr), .addr(arb_m_addr), .wdata(arb_m_wdata),
|
||||
.lb_n(arb_m_lb_n), .ub_n(arb_m_ub_n),
|
||||
.rdata(arb_m_rdata), .ready(arb_m_ready),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready)
|
||||
);
|
||||
|
||||
psram_controller #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||
) u_psram_ctrl (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready),
|
||||
.psram_a(psram_a), .psram_dq(psram_dq),
|
||||
.psram_ce_n(psram_ce_n), .psram_oe_n(psram_oe_n), .psram_we_n(psram_we_n),
|
||||
.psram_lb_n(psram_lb_n), .psram_ub_n(psram_ub_n), .psram_zz_n(psram_zz_n)
|
||||
);
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,123 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP9 real hardware-facing top level.
|
||||
// Mirrors hardware/v2/rtl/neural_multiprocessor.v's own scope exactly
|
||||
// (M8): nms_dataflow_core.v's N_SLOTS+1 independent Memory Backend
|
||||
// Interface ports funneled through the SAME, UNMODIFIED
|
||||
// slot_mem_arbiter.v down to the SAME, UNMODIFIED real V1 PSRAM
|
||||
// backend chain (memory_interface.v -> psram_controller.v).
|
||||
//
|
||||
// Nothing about the arbiter or the real PSRAM chain changes for the
|
||||
// NMS -- only nms_dataflow_core.v's own internals (the memory
|
||||
// organization DEC-0019/DEC-0020 decided) differ from the frozen V2
|
||||
// dataflow_core.v this module's own structure is copied from.
|
||||
// ================================================================
|
||||
|
||||
module nms_neural_multiprocessor_actfix #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 2,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter PSRAM_DATA_WIDTH = 16,
|
||||
parameter CLK_FREQ_MHZ = 80
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [ADDR_WIDTH-1:0] psram_a,
|
||||
inout wire [PSRAM_DATA_WIDTH-1:0] psram_dq,
|
||||
output wire psram_ce_n,
|
||||
output wire psram_oe_n,
|
||||
output wire psram_we_n,
|
||||
output wire psram_lb_n,
|
||||
output wire psram_ub_n,
|
||||
output wire psram_zz_n
|
||||
);
|
||||
|
||||
wire [N_SLOTS:0] slot_mem_req, slot_mem_wr;
|
||||
wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr;
|
||||
wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata, slot_mem_rdata;
|
||||
wire [N_SLOTS:0] slot_mem_lb_n, slot_mem_ub_n;
|
||||
wire [N_SLOTS:0] slot_mem_ready;
|
||||
|
||||
nms_dataflow_core_actfix #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
|
||||
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
|
||||
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_dataflow_core (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
|
||||
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
|
||||
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready)
|
||||
);
|
||||
|
||||
wire arb_m_req, arb_m_wr;
|
||||
wire [ADDR_WIDTH-1:0] arb_m_addr;
|
||||
wire [15:0] arb_m_wdata;
|
||||
wire arb_m_lb_n, arb_m_ub_n;
|
||||
wire [15:0] arb_m_rdata;
|
||||
wire arb_m_ready;
|
||||
|
||||
slot_mem_arbiter #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS+1)
|
||||
) u_arbiter (
|
||||
.clk(clk), .rst(rst),
|
||||
.s_req(slot_mem_req), .s_wr(slot_mem_wr), .s_addr(slot_mem_addr),
|
||||
.s_wdata(slot_mem_wdata), .s_lb_n(slot_mem_lb_n), .s_ub_n(slot_mem_ub_n),
|
||||
.s_rdata(slot_mem_rdata), .s_ready(slot_mem_ready),
|
||||
.m_req(arb_m_req), .m_wr(arb_m_wr), .m_addr(arb_m_addr), .m_wdata(arb_m_wdata),
|
||||
.m_lb_n(arb_m_lb_n), .m_ub_n(arb_m_ub_n),
|
||||
.m_rdata(arb_m_rdata), .m_ready(arb_m_ready)
|
||||
);
|
||||
|
||||
wire pc_mem_req, pc_mem_wr;
|
||||
wire [ADDR_WIDTH-1:0] pc_mem_addr;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_wdata;
|
||||
wire pc_mem_lb_n, pc_mem_ub_n;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_rdata;
|
||||
wire pc_mem_ready;
|
||||
|
||||
memory_interface #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH)) u_memif (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(arb_m_req), .wr(arb_m_wr), .addr(arb_m_addr), .wdata(arb_m_wdata),
|
||||
.lb_n(arb_m_lb_n), .ub_n(arb_m_ub_n),
|
||||
.rdata(arb_m_rdata), .ready(arb_m_ready),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready)
|
||||
);
|
||||
|
||||
psram_controller #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||
) u_psram_ctrl (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready),
|
||||
.psram_a(psram_a), .psram_dq(psram_dq),
|
||||
.psram_ce_n(psram_ce_n), .psram_oe_n(psram_oe_n), .psram_we_n(psram_we_n),
|
||||
.psram_lb_n(psram_lb_n), .psram_ub_n(psram_ub_n), .psram_zz_n(psram_zz_n)
|
||||
);
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,123 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP9 real hardware-facing top level.
|
||||
// Mirrors hardware/v2/rtl/neural_multiprocessor.v's own scope exactly
|
||||
// (M8): nms_dataflow_core.v's N_SLOTS+1 independent Memory Backend
|
||||
// Interface ports funneled through the SAME, UNMODIFIED
|
||||
// slot_mem_arbiter.v down to the SAME, UNMODIFIED real V1 PSRAM
|
||||
// backend chain (memory_interface.v -> psram_controller.v).
|
||||
//
|
||||
// Nothing about the arbiter or the real PSRAM chain changes for the
|
||||
// NMS -- only nms_dataflow_core.v's own internals (the memory
|
||||
// organization DEC-0019/DEC-0020 decided) differ from the frozen V2
|
||||
// dataflow_core.v this module's own structure is copied from.
|
||||
// ================================================================
|
||||
|
||||
module nms_neural_multiprocessor_actfix2 #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 2,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter PSRAM_DATA_WIDTH = 16,
|
||||
parameter CLK_FREQ_MHZ = 80
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [ADDR_WIDTH-1:0] psram_a,
|
||||
inout wire [PSRAM_DATA_WIDTH-1:0] psram_dq,
|
||||
output wire psram_ce_n,
|
||||
output wire psram_oe_n,
|
||||
output wire psram_we_n,
|
||||
output wire psram_lb_n,
|
||||
output wire psram_ub_n,
|
||||
output wire psram_zz_n
|
||||
);
|
||||
|
||||
wire [N_SLOTS:0] slot_mem_req, slot_mem_wr;
|
||||
wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr;
|
||||
wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata, slot_mem_rdata;
|
||||
wire [N_SLOTS:0] slot_mem_lb_n, slot_mem_ub_n;
|
||||
wire [N_SLOTS:0] slot_mem_ready;
|
||||
|
||||
nms_dataflow_core_actfix2 #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
|
||||
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
|
||||
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_dataflow_core (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
|
||||
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
|
||||
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready)
|
||||
);
|
||||
|
||||
wire arb_m_req, arb_m_wr;
|
||||
wire [ADDR_WIDTH-1:0] arb_m_addr;
|
||||
wire [15:0] arb_m_wdata;
|
||||
wire arb_m_lb_n, arb_m_ub_n;
|
||||
wire [15:0] arb_m_rdata;
|
||||
wire arb_m_ready;
|
||||
|
||||
slot_mem_arbiter #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS+1)
|
||||
) u_arbiter (
|
||||
.clk(clk), .rst(rst),
|
||||
.s_req(slot_mem_req), .s_wr(slot_mem_wr), .s_addr(slot_mem_addr),
|
||||
.s_wdata(slot_mem_wdata), .s_lb_n(slot_mem_lb_n), .s_ub_n(slot_mem_ub_n),
|
||||
.s_rdata(slot_mem_rdata), .s_ready(slot_mem_ready),
|
||||
.m_req(arb_m_req), .m_wr(arb_m_wr), .m_addr(arb_m_addr), .m_wdata(arb_m_wdata),
|
||||
.m_lb_n(arb_m_lb_n), .m_ub_n(arb_m_ub_n),
|
||||
.m_rdata(arb_m_rdata), .m_ready(arb_m_ready)
|
||||
);
|
||||
|
||||
wire pc_mem_req, pc_mem_wr;
|
||||
wire [ADDR_WIDTH-1:0] pc_mem_addr;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_wdata;
|
||||
wire pc_mem_lb_n, pc_mem_ub_n;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_rdata;
|
||||
wire pc_mem_ready;
|
||||
|
||||
memory_interface #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH)) u_memif (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(arb_m_req), .wr(arb_m_wr), .addr(arb_m_addr), .wdata(arb_m_wdata),
|
||||
.lb_n(arb_m_lb_n), .ub_n(arb_m_ub_n),
|
||||
.rdata(arb_m_rdata), .ready(arb_m_ready),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready)
|
||||
);
|
||||
|
||||
psram_controller #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||
) u_psram_ctrl (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready),
|
||||
.psram_a(psram_a), .psram_dq(psram_dq),
|
||||
.psram_ce_n(psram_ce_n), .psram_oe_n(psram_oe_n), .psram_we_n(psram_we_n),
|
||||
.psram_lb_n(psram_lb_n), .psram_ub_n(psram_ub_n), .psram_zz_n(psram_zz_n)
|
||||
);
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,233 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP9 real hardware-facing top level.
|
||||
// Mirrors hardware/v2/rtl/neural_multiprocessor.v's own scope exactly
|
||||
// (M8): nms_dataflow_core.v's N_SLOTS+1 independent Memory Backend
|
||||
// Interface ports funneled through the SAME, UNMODIFIED
|
||||
// slot_mem_arbiter.v down to the SAME, UNMODIFIED real V1 PSRAM
|
||||
// backend chain (memory_interface.v -> psram_controller.v).
|
||||
//
|
||||
// Nothing about the arbiter or the real PSRAM chain changes for the
|
||||
// NMS -- only nms_dataflow_core.v's own internals (the memory
|
||||
// organization DEC-0019/DEC-0020 decided) differ from the frozen V2
|
||||
// dataflow_core.v this module's own structure is copied from.
|
||||
// ================================================================
|
||||
|
||||
module nms_neural_multiprocessor_dual32 #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 2,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter PSRAM_DATA_WIDTH = 16,
|
||||
parameter CLK_FREQ_MHZ = 80
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [ADDR_WIDTH-1:0] psram_a,
|
||||
inout wire [PSRAM_DATA_WIDTH-1:0] psram_dq,
|
||||
output wire psram_ce_n,
|
||||
output wire psram_oe_n,
|
||||
output wire psram_we_n,
|
||||
output wire psram_lb_n,
|
||||
output wire psram_ub_n,
|
||||
output wire psram_zz_n,
|
||||
|
||||
// ---- STEP15 (continuation): real dual-chip 32-bit physical
|
||||
// weight-fetch interface, TWO independent 16-bit PSRAM chips.
|
||||
//
|
||||
// Address and control (CE#/OE#/WE#/LB#/UB#/ZZ#) are SHARED, ONE
|
||||
// set of real FPGA pins, not duplicated per chip: both
|
||||
// psram_controller.v instances inside psram_controller_dual32.v
|
||||
// are fed byte-for-byte IDENTICAL mem_req/mem_wr/mem_addr/
|
||||
// mem_lb_n/mem_ub_n every cycle (that IS the whole synchronization
|
||||
// mechanism, EXP-0037/DEC-0029), so their own address/control
|
||||
// OUTPUTS are, by construction, always identical too -- a real
|
||||
// PCB ties ONE FPGA pin's own net to BOTH chips' corresponding
|
||||
// input pin (a simple fan-out trace, not a bus-contention
|
||||
// concern, since these are FPGA OUTPUTS driving PASSIVE chip
|
||||
// inputs). Only DQ (bidirectional, chip-specific data) genuinely
|
||||
// needs independent pins per chip.
|
||||
//
|
||||
// An earlier draft exposed FULLY separate psram0_*/psram1_*
|
||||
// address+control pins (90 total pins for this interface) --
|
||||
// real synthesis+P&R (EXP-0039) found this EXCEEDS the real
|
||||
// package's own I/O budget by exactly 2 pins (245 total
|
||||
// TRELLIS_IO on the LFE5U-45F-8CABGA381; 157 already committed by
|
||||
// the existing registration+single-chip-PSRAM interface, leaving
|
||||
// 88 free; 90 needed). Sharing address/control (a real, valid
|
||||
// PCB technique, not a synthesis trick) drops the requirement to
|
||||
// 23(addr)+6(ctrl)+16(chip0 dq)+16(chip1 dq) = 61 pins, which
|
||||
// fits comfortably (61 < 88).
|
||||
output wire [ADDR_WIDTH-1:0] psram01_a,
|
||||
output wire psram01_ce_n,
|
||||
output wire psram01_oe_n,
|
||||
output wire psram01_we_n,
|
||||
output wire psram01_lb_n,
|
||||
output wire psram01_ub_n,
|
||||
output wire psram01_zz_n,
|
||||
inout wire [15:0] psram0_dq,
|
||||
inout wire [15:0] psram1_dq
|
||||
);
|
||||
|
||||
wire [N_SLOTS:0] slot_mem_req, slot_mem_wr;
|
||||
wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr;
|
||||
wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata, slot_mem_rdata;
|
||||
wire [N_SLOTS:0] slot_mem_lb_n, slot_mem_ub_n;
|
||||
wire [N_SLOTS:0] slot_mem_ready;
|
||||
|
||||
wire [N_SLOTS-1:0] wide_slot_mem_req;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] wide_slot_mem_addr;
|
||||
wire [32*N_SLOTS-1:0] wide_slot_mem_rdata;
|
||||
wire [N_SLOTS-1:0] wide_slot_mem_ready;
|
||||
|
||||
nms_dataflow_core_dual32 #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
|
||||
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
|
||||
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_dataflow_core (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
|
||||
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
|
||||
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready),
|
||||
.wide_slot_mem_req(wide_slot_mem_req), .wide_slot_mem_addr(wide_slot_mem_addr),
|
||||
.wide_slot_mem_rdata(wide_slot_mem_rdata), .wide_slot_mem_ready(wide_slot_mem_ready)
|
||||
);
|
||||
|
||||
wire arb_m_req, arb_m_wr;
|
||||
wire [ADDR_WIDTH-1:0] arb_m_addr;
|
||||
wire [15:0] arb_m_wdata;
|
||||
wire arb_m_lb_n, arb_m_ub_n;
|
||||
wire [15:0] arb_m_rdata;
|
||||
wire arb_m_ready;
|
||||
|
||||
slot_mem_arbiter #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS+1)
|
||||
) u_arbiter (
|
||||
.clk(clk), .rst(rst),
|
||||
.s_req(slot_mem_req), .s_wr(slot_mem_wr), .s_addr(slot_mem_addr),
|
||||
.s_wdata(slot_mem_wdata), .s_lb_n(slot_mem_lb_n), .s_ub_n(slot_mem_ub_n),
|
||||
.s_rdata(slot_mem_rdata), .s_ready(slot_mem_ready),
|
||||
.m_req(arb_m_req), .m_wr(arb_m_wr), .m_addr(arb_m_addr), .m_wdata(arb_m_wdata),
|
||||
.m_lb_n(arb_m_lb_n), .m_ub_n(arb_m_ub_n),
|
||||
.m_rdata(arb_m_rdata), .m_ready(arb_m_ready)
|
||||
);
|
||||
|
||||
wire pc_mem_req, pc_mem_wr;
|
||||
wire [ADDR_WIDTH-1:0] pc_mem_addr;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_wdata;
|
||||
wire pc_mem_lb_n, pc_mem_ub_n;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_rdata;
|
||||
wire pc_mem_ready;
|
||||
|
||||
memory_interface #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH)) u_memif (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(arb_m_req), .wr(arb_m_wr), .addr(arb_m_addr), .wdata(arb_m_wdata),
|
||||
.lb_n(arb_m_lb_n), .ub_n(arb_m_ub_n),
|
||||
.rdata(arb_m_rdata), .ready(arb_m_ready),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready)
|
||||
);
|
||||
|
||||
psram_controller #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||
) u_psram_ctrl (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready),
|
||||
.psram_a(psram_a), .psram_dq(psram_dq),
|
||||
.psram_ce_n(psram_ce_n), .psram_oe_n(psram_oe_n), .psram_we_n(psram_we_n),
|
||||
.psram_lb_n(psram_lb_n), .psram_ub_n(psram_ub_n), .psram_zz_n(psram_zz_n)
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// STEP15 (continuation): real dual-chip 32-bit weight-fetch
|
||||
// backend -- a SEPARATE arbiter (slot_mem_arbiter_wide.v, N_PORTS=
|
||||
// N_SLOTS, no activation-fill port sharing this one) feeding the
|
||||
// real psram_controller_dual32.v (two independent, real,
|
||||
// UNMODIFIED psram_controller.v instances, DEC-0029's own
|
||||
// selected architecture). Weight fetch never writes: mem_wr/
|
||||
// mem_wdata tied to 0 at the arbiter's own per-port inputs, and
|
||||
// the dual32 controller's own 2-bit lane-enables tied to
|
||||
// "always both bytes of both chips" (2'b00), matching
|
||||
// weight_prefetch_engine_wide.v's own always-full-word read
|
||||
// behavior exactly (same convention as its real 16-bit
|
||||
// counterpart, weight_prefetch_engine.v).
|
||||
// ================================================================
|
||||
wire [N_SLOTS-1:0] wide_s_wr = {N_SLOTS{1'b0}};
|
||||
wire [32*N_SLOTS-1:0] wide_s_wdata = {(32*N_SLOTS){1'b0}};
|
||||
wire [N_SLOTS-1:0] wide_s_lb_n = {N_SLOTS{1'b0}};
|
||||
wire [N_SLOTS-1:0] wide_s_ub_n = {N_SLOTS{1'b0}};
|
||||
|
||||
wire wide_arb_m_req, wide_arb_m_wr;
|
||||
wire [ADDR_WIDTH-1:0] wide_arb_m_addr;
|
||||
wire [31:0] wide_arb_m_wdata;
|
||||
wire wide_arb_m_lb_n, wide_arb_m_ub_n;
|
||||
wire [31:0] wide_arb_m_rdata;
|
||||
wire wide_arb_m_ready;
|
||||
|
||||
slot_mem_arbiter_wide #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS), .DATA_WIDTH(32)
|
||||
) u_arbiter_wide (
|
||||
.clk(clk), .rst(rst),
|
||||
.s_req(wide_slot_mem_req), .s_wr(wide_s_wr), .s_addr(wide_slot_mem_addr),
|
||||
.s_wdata(wide_s_wdata), .s_lb_n(wide_s_lb_n), .s_ub_n(wide_s_ub_n),
|
||||
.s_rdata(wide_slot_mem_rdata), .s_ready(wide_slot_mem_ready),
|
||||
.m_req(wide_arb_m_req), .m_wr(wide_arb_m_wr), .m_addr(wide_arb_m_addr), .m_wdata(wide_arb_m_wdata),
|
||||
.m_lb_n(wide_arb_m_lb_n), .m_ub_n(wide_arb_m_ub_n),
|
||||
.m_rdata(wide_arb_m_rdata), .m_ready(wide_arb_m_ready)
|
||||
);
|
||||
|
||||
wire dual32_lane_sync_error;
|
||||
// chip1's own address/control outputs (identical in value to
|
||||
// chip0's, per the shared-input synchronization argument above) --
|
||||
// deliberately left unconnected to any top-level pin.
|
||||
wire [ADDR_WIDTH-1:0] unused_psram1_a;
|
||||
wire unused_psram1_ce_n, unused_psram1_oe_n, unused_psram1_we_n;
|
||||
wire unused_psram1_lb_n, unused_psram1_ub_n, unused_psram1_zz_n;
|
||||
|
||||
psram_controller_dual32 #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||
) u_psram_dual32 (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(wide_arb_m_req), .mem_wr(1'b0), .mem_addr(wide_arb_m_addr), .mem_wdata(32'h0),
|
||||
.mem_lb_n(2'b00), .mem_ub_n(2'b00),
|
||||
.mem_rdata(wide_arb_m_rdata), .mem_ready(wide_arb_m_ready),
|
||||
.lane_sync_error(dual32_lane_sync_error),
|
||||
.psram0_a(psram01_a), .psram0_dq(psram0_dq),
|
||||
.psram0_ce_n(psram01_ce_n), .psram0_oe_n(psram01_oe_n), .psram0_we_n(psram01_we_n),
|
||||
.psram0_lb_n(psram01_lb_n), .psram0_ub_n(psram01_ub_n), .psram0_zz_n(psram01_zz_n),
|
||||
// chip1's own address/control outputs are byte-for-byte
|
||||
// identical to chip0's (EXP-0037/DEC-0029) -- left as
|
||||
// internal-only wires here, not exposed as separate top-level
|
||||
// pins; a real PCB fans the SAME psram01_* net out to both
|
||||
// chips' corresponding input pin instead.
|
||||
.psram1_a(unused_psram1_a), .psram1_dq(psram1_dq),
|
||||
.psram1_ce_n(unused_psram1_ce_n), .psram1_oe_n(unused_psram1_oe_n), .psram1_we_n(unused_psram1_we_n),
|
||||
.psram1_lb_n(unused_psram1_lb_n), .psram1_ub_n(unused_psram1_ub_n), .psram1_zz_n(unused_psram1_zz_n)
|
||||
);
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,123 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP9 real hardware-facing top level.
|
||||
// Mirrors hardware/v2/rtl/neural_multiprocessor.v's own scope exactly
|
||||
// (M8): nms_dataflow_core.v's N_SLOTS+1 independent Memory Backend
|
||||
// Interface ports funneled through the SAME, UNMODIFIED
|
||||
// slot_mem_arbiter.v down to the SAME, UNMODIFIED real V1 PSRAM
|
||||
// backend chain (memory_interface.v -> psram_controller.v).
|
||||
//
|
||||
// Nothing about the arbiter or the real PSRAM chain changes for the
|
||||
// NMS -- only nms_dataflow_core.v's own internals (the memory
|
||||
// organization DEC-0019/DEC-0020 decided) differ from the frozen V2
|
||||
// dataflow_core.v this module's own structure is copied from.
|
||||
// ================================================================
|
||||
|
||||
module nms_neural_multiprocessor_pf #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 2,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter PSRAM_DATA_WIDTH = 16,
|
||||
parameter CLK_FREQ_MHZ = 80
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [ADDR_WIDTH-1:0] psram_a,
|
||||
inout wire [PSRAM_DATA_WIDTH-1:0] psram_dq,
|
||||
output wire psram_ce_n,
|
||||
output wire psram_oe_n,
|
||||
output wire psram_we_n,
|
||||
output wire psram_lb_n,
|
||||
output wire psram_ub_n,
|
||||
output wire psram_zz_n
|
||||
);
|
||||
|
||||
wire [N_SLOTS:0] slot_mem_req, slot_mem_wr;
|
||||
wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr;
|
||||
wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata, slot_mem_rdata;
|
||||
wire [N_SLOTS:0] slot_mem_lb_n, slot_mem_ub_n;
|
||||
wire [N_SLOTS:0] slot_mem_ready;
|
||||
|
||||
nms_dataflow_core_pf #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
|
||||
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
|
||||
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_dataflow_core (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
|
||||
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
|
||||
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready)
|
||||
);
|
||||
|
||||
wire arb_m_req, arb_m_wr;
|
||||
wire [ADDR_WIDTH-1:0] arb_m_addr;
|
||||
wire [15:0] arb_m_wdata;
|
||||
wire arb_m_lb_n, arb_m_ub_n;
|
||||
wire [15:0] arb_m_rdata;
|
||||
wire arb_m_ready;
|
||||
|
||||
slot_mem_arbiter #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS+1)
|
||||
) u_arbiter (
|
||||
.clk(clk), .rst(rst),
|
||||
.s_req(slot_mem_req), .s_wr(slot_mem_wr), .s_addr(slot_mem_addr),
|
||||
.s_wdata(slot_mem_wdata), .s_lb_n(slot_mem_lb_n), .s_ub_n(slot_mem_ub_n),
|
||||
.s_rdata(slot_mem_rdata), .s_ready(slot_mem_ready),
|
||||
.m_req(arb_m_req), .m_wr(arb_m_wr), .m_addr(arb_m_addr), .m_wdata(arb_m_wdata),
|
||||
.m_lb_n(arb_m_lb_n), .m_ub_n(arb_m_ub_n),
|
||||
.m_rdata(arb_m_rdata), .m_ready(arb_m_ready)
|
||||
);
|
||||
|
||||
wire pc_mem_req, pc_mem_wr;
|
||||
wire [ADDR_WIDTH-1:0] pc_mem_addr;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_wdata;
|
||||
wire pc_mem_lb_n, pc_mem_ub_n;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_rdata;
|
||||
wire pc_mem_ready;
|
||||
|
||||
memory_interface #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH)) u_memif (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(arb_m_req), .wr(arb_m_wr), .addr(arb_m_addr), .wdata(arb_m_wdata),
|
||||
.lb_n(arb_m_lb_n), .ub_n(arb_m_ub_n),
|
||||
.rdata(arb_m_rdata), .ready(arb_m_ready),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready)
|
||||
);
|
||||
|
||||
psram_controller #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||
) u_psram_ctrl (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready),
|
||||
.psram_a(psram_a), .psram_dq(psram_dq),
|
||||
.psram_ce_n(psram_ce_n), .psram_oe_n(psram_oe_n), .psram_we_n(psram_we_n),
|
||||
.psram_lb_n(psram_lb_n), .psram_ub_n(psram_ub_n), .psram_zz_n(psram_zz_n)
|
||||
);
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,205 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP9 real hardware-facing top level.
|
||||
// Mirrors hardware/v2/rtl/neural_multiprocessor.v's own scope exactly
|
||||
// (M8): nms_dataflow_core.v's N_SLOTS+1 independent Memory Backend
|
||||
// Interface ports funneled through the SAME, UNMODIFIED
|
||||
// slot_mem_arbiter.v down to the SAME, UNMODIFIED real V1 PSRAM
|
||||
// backend chain (memory_interface.v -> psram_controller.v).
|
||||
//
|
||||
// Nothing about the arbiter or the real PSRAM chain changes for the
|
||||
// NMS -- only nms_dataflow_core.v's own internals (the memory
|
||||
// organization DEC-0019/DEC-0020 decided) differ from the frozen V2
|
||||
// dataflow_core.v this module's own structure is copied from.
|
||||
//
|
||||
// STEP16 Phase 5: forked from nms_neural_multiprocessor_dual32.v
|
||||
// (STEP15's real dual-chip-32-bit weight-fetch variant). The ORIGINAL
|
||||
// single-chip 16-bit PSRAM activation+writeback path (memory_
|
||||
// interface.v -> psram_controller.v) is BYTE-FOR-BYTE UNCHANGED --
|
||||
// STEP15 itself established this path sits at only 4.4% utilization
|
||||
// and was never the bandwidth bottleneck under test. ONLY the wide
|
||||
// weight-fetch backend changes: nms_dataflow_core_sdram.v (64-bit
|
||||
// wide port) -> slot_mem_arbiter_wide.v (reused unchanged at
|
||||
// DATA_WIDTH=64) -> sdram_weight_backend.v (the real, isolated-and-
|
||||
// validated AS4C4M16SA-6TIN SDRAM controller, BURST_LEN=4), replacing
|
||||
// psram_controller_dual32.v exactly at this one point in the design.
|
||||
// ================================================================
|
||||
|
||||
module nms_neural_multiprocessor_sdram #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 2,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter PSRAM_DATA_WIDTH = 16,
|
||||
parameter CLK_FREQ_MHZ = 80
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [ADDR_WIDTH-1:0] psram_a,
|
||||
inout wire [PSRAM_DATA_WIDTH-1:0] psram_dq,
|
||||
output wire psram_ce_n,
|
||||
output wire psram_oe_n,
|
||||
output wire psram_we_n,
|
||||
output wire psram_lb_n,
|
||||
output wire psram_ub_n,
|
||||
output wire psram_zz_n,
|
||||
|
||||
// ---- STEP16 Phase 5: real single-chip SDRAM physical weight-
|
||||
// fetch interface (AS4C4M16SA-6TIN, x16, real JEDEC SDR SDRAM
|
||||
// command pins). Real pin count: 2(BA)+12(A)+1(CKE)+1(CS#)+1(RAS#)
|
||||
// +1(CAS#)+1(WE#)+2(DQM)+16(DQ) = 37 pins, confirmed by real P&R
|
||||
// in Phase 6 (see the STEP16 report's own I/O analysis section) --
|
||||
// far fewer than the dual32 baseline's own 61-pin weight-fetch
|
||||
// interface, since this is a single chip, not two.
|
||||
output wire sdram_cke,
|
||||
output wire sdram_cs_n,
|
||||
output wire sdram_ras_n,
|
||||
output wire sdram_cas_n,
|
||||
output wire sdram_we_n,
|
||||
output wire [1:0] sdram_ba,
|
||||
output wire [11:0] sdram_a,
|
||||
inout wire [15:0] sdram_dq,
|
||||
output wire [1:0] sdram_dqm
|
||||
);
|
||||
|
||||
wire [N_SLOTS:0] slot_mem_req, slot_mem_wr;
|
||||
wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr;
|
||||
wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata, slot_mem_rdata;
|
||||
wire [N_SLOTS:0] slot_mem_lb_n, slot_mem_ub_n;
|
||||
wire [N_SLOTS:0] slot_mem_ready;
|
||||
|
||||
wire [N_SLOTS-1:0] wide_slot_mem_req;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] wide_slot_mem_addr;
|
||||
wire [64*N_SLOTS-1:0] wide_slot_mem_rdata;
|
||||
wire [N_SLOTS-1:0] wide_slot_mem_ready;
|
||||
|
||||
nms_dataflow_core_sdram #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
|
||||
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
|
||||
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_dataflow_core (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
|
||||
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
|
||||
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready),
|
||||
.wide_slot_mem_req(wide_slot_mem_req), .wide_slot_mem_addr(wide_slot_mem_addr),
|
||||
.wide_slot_mem_rdata(wide_slot_mem_rdata), .wide_slot_mem_ready(wide_slot_mem_ready)
|
||||
);
|
||||
|
||||
wire arb_m_req, arb_m_wr;
|
||||
wire [ADDR_WIDTH-1:0] arb_m_addr;
|
||||
wire [15:0] arb_m_wdata;
|
||||
wire arb_m_lb_n, arb_m_ub_n;
|
||||
wire [15:0] arb_m_rdata;
|
||||
wire arb_m_ready;
|
||||
|
||||
slot_mem_arbiter #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS+1)
|
||||
) u_arbiter (
|
||||
.clk(clk), .rst(rst),
|
||||
.s_req(slot_mem_req), .s_wr(slot_mem_wr), .s_addr(slot_mem_addr),
|
||||
.s_wdata(slot_mem_wdata), .s_lb_n(slot_mem_lb_n), .s_ub_n(slot_mem_ub_n),
|
||||
.s_rdata(slot_mem_rdata), .s_ready(slot_mem_ready),
|
||||
.m_req(arb_m_req), .m_wr(arb_m_wr), .m_addr(arb_m_addr), .m_wdata(arb_m_wdata),
|
||||
.m_lb_n(arb_m_lb_n), .m_ub_n(arb_m_ub_n),
|
||||
.m_rdata(arb_m_rdata), .m_ready(arb_m_ready)
|
||||
);
|
||||
|
||||
wire pc_mem_req, pc_mem_wr;
|
||||
wire [ADDR_WIDTH-1:0] pc_mem_addr;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_wdata;
|
||||
wire pc_mem_lb_n, pc_mem_ub_n;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_rdata;
|
||||
wire pc_mem_ready;
|
||||
|
||||
memory_interface #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH)) u_memif (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(arb_m_req), .wr(arb_m_wr), .addr(arb_m_addr), .wdata(arb_m_wdata),
|
||||
.lb_n(arb_m_lb_n), .ub_n(arb_m_ub_n),
|
||||
.rdata(arb_m_rdata), .ready(arb_m_ready),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready)
|
||||
);
|
||||
|
||||
psram_controller #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||
) u_psram_ctrl (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready),
|
||||
.psram_a(psram_a), .psram_dq(psram_dq),
|
||||
.psram_ce_n(psram_ce_n), .psram_oe_n(psram_oe_n), .psram_we_n(psram_we_n),
|
||||
.psram_lb_n(psram_lb_n), .psram_ub_n(psram_ub_n), .psram_zz_n(psram_zz_n)
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// STEP16 Phase 5: real single-chip SDRAM weight-fetch backend --
|
||||
// a SEPARATE arbiter (slot_mem_arbiter_wide.v, N_PORTS=N_SLOTS, no
|
||||
// activation-fill port sharing this one, reused UNCHANGED from the
|
||||
// dual32 baseline at DATA_WIDTH=64) feeding sdram_weight_backend.v
|
||||
// (the real, isolated-and-validated AS4C4M16SA-6TIN controller).
|
||||
// Weight fetch never writes: mem_wr/mem_wdata tied to 0 at the
|
||||
// arbiter's own per-port inputs, matching weight_prefetch_engine_
|
||||
// wide.v's own always-full-word read behavior exactly (same
|
||||
// convention as the dual32 baseline).
|
||||
// ================================================================
|
||||
wire [N_SLOTS-1:0] wide_s_wr = {N_SLOTS{1'b0}};
|
||||
wire [64*N_SLOTS-1:0] wide_s_wdata = {(64*N_SLOTS){1'b0}};
|
||||
wire [N_SLOTS-1:0] wide_s_lb_n = {N_SLOTS{1'b0}};
|
||||
wire [N_SLOTS-1:0] wide_s_ub_n = {N_SLOTS{1'b0}};
|
||||
|
||||
wire wide_arb_m_req, wide_arb_m_wr;
|
||||
wire [ADDR_WIDTH-1:0] wide_arb_m_addr;
|
||||
wire [63:0] wide_arb_m_wdata;
|
||||
wire wide_arb_m_lb_n, wide_arb_m_ub_n;
|
||||
wire [63:0] wide_arb_m_rdata;
|
||||
wire wide_arb_m_ready;
|
||||
|
||||
slot_mem_arbiter_wide #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS), .DATA_WIDTH(64)
|
||||
) u_arbiter_wide (
|
||||
.clk(clk), .rst(rst),
|
||||
.s_req(wide_slot_mem_req), .s_wr(wide_s_wr), .s_addr(wide_slot_mem_addr),
|
||||
.s_wdata(wide_s_wdata), .s_lb_n(wide_s_lb_n), .s_ub_n(wide_s_ub_n),
|
||||
.s_rdata(wide_slot_mem_rdata), .s_ready(wide_slot_mem_ready),
|
||||
.m_req(wide_arb_m_req), .m_wr(wide_arb_m_wr), .m_addr(wide_arb_m_addr), .m_wdata(wide_arb_m_wdata),
|
||||
.m_lb_n(wide_arb_m_lb_n), .m_ub_n(wide_arb_m_ub_n),
|
||||
.m_rdata(wide_arb_m_rdata), .m_ready(wide_arb_m_ready)
|
||||
);
|
||||
|
||||
sdram_weight_backend #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||
) u_sdram_backend (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(wide_arb_m_req), .mem_wr(1'b0), .mem_addr(wide_arb_m_addr), .mem_wdata(64'h0),
|
||||
.mem_rdata(wide_arb_m_rdata), .mem_ready(wide_arb_m_ready),
|
||||
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
|
||||
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
|
||||
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm)
|
||||
);
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,205 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP9 real hardware-facing top level.
|
||||
// Mirrors hardware/v2/rtl/neural_multiprocessor.v's own scope exactly
|
||||
// (M8): nms_dataflow_core.v's N_SLOTS+1 independent Memory Backend
|
||||
// Interface ports funneled through the SAME, UNMODIFIED
|
||||
// slot_mem_arbiter.v down to the SAME, UNMODIFIED real V1 PSRAM
|
||||
// backend chain (memory_interface.v -> psram_controller.v).
|
||||
//
|
||||
// Nothing about the arbiter or the real PSRAM chain changes for the
|
||||
// NMS -- only nms_dataflow_core.v's own internals (the memory
|
||||
// organization DEC-0019/DEC-0020 decided) differ from the frozen V2
|
||||
// dataflow_core.v this module's own structure is copied from.
|
||||
//
|
||||
// STEP16 Phase 5: forked from nms_neural_multiprocessor_dual32.v
|
||||
// (STEP15's real dual-chip-32-bit weight-fetch variant). The ORIGINAL
|
||||
// single-chip 16-bit PSRAM activation+writeback path (memory_
|
||||
// interface.v -> psram_controller.v) is BYTE-FOR-BYTE UNCHANGED --
|
||||
// STEP15 itself established this path sits at only 4.4% utilization
|
||||
// and was never the bandwidth bottleneck under test. ONLY the wide
|
||||
// weight-fetch backend changes: nms_dataflow_core_sdram.v (64-bit
|
||||
// wide port) -> slot_mem_arbiter_wide.v (reused unchanged at
|
||||
// DATA_WIDTH=64) -> sdram_weight_backend.v (the real, isolated-and-
|
||||
// validated AS4C4M16SA-6TIN SDRAM controller, BURST_LEN=4), replacing
|
||||
// psram_controller_dual32.v exactly at this one point in the design.
|
||||
// ================================================================
|
||||
|
||||
module nms_neural_multiprocessor_sdram_pack128 #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 2,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter PSRAM_DATA_WIDTH = 16,
|
||||
parameter CLK_FREQ_MHZ = 80
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [ADDR_WIDTH-1:0] psram_a,
|
||||
inout wire [PSRAM_DATA_WIDTH-1:0] psram_dq,
|
||||
output wire psram_ce_n,
|
||||
output wire psram_oe_n,
|
||||
output wire psram_we_n,
|
||||
output wire psram_lb_n,
|
||||
output wire psram_ub_n,
|
||||
output wire psram_zz_n,
|
||||
|
||||
// ---- STEP16 Phase 5: real single-chip SDRAM physical weight-
|
||||
// fetch interface (AS4C4M16SA-6TIN, x16, real JEDEC SDR SDRAM
|
||||
// command pins). Real pin count: 2(BA)+12(A)+1(CKE)+1(CS#)+1(RAS#)
|
||||
// +1(CAS#)+1(WE#)+2(DQM)+16(DQ) = 37 pins, confirmed by real P&R
|
||||
// in Phase 6 (see the STEP16 report's own I/O analysis section) --
|
||||
// far fewer than the dual32 baseline's own 61-pin weight-fetch
|
||||
// interface, since this is a single chip, not two.
|
||||
output wire sdram_cke,
|
||||
output wire sdram_cs_n,
|
||||
output wire sdram_ras_n,
|
||||
output wire sdram_cas_n,
|
||||
output wire sdram_we_n,
|
||||
output wire [1:0] sdram_ba,
|
||||
output wire [11:0] sdram_a,
|
||||
inout wire [15:0] sdram_dq,
|
||||
output wire [1:0] sdram_dqm
|
||||
);
|
||||
|
||||
wire [N_SLOTS:0] slot_mem_req, slot_mem_wr;
|
||||
wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr;
|
||||
wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata, slot_mem_rdata;
|
||||
wire [N_SLOTS:0] slot_mem_lb_n, slot_mem_ub_n;
|
||||
wire [N_SLOTS:0] slot_mem_ready;
|
||||
|
||||
wire [N_SLOTS-1:0] wide_slot_mem_req;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] wide_slot_mem_addr;
|
||||
wire [64*N_SLOTS-1:0] wide_slot_mem_rdata;
|
||||
wire [N_SLOTS-1:0] wide_slot_mem_ready;
|
||||
|
||||
nms_dataflow_core_sdram #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
|
||||
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
|
||||
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_dataflow_core (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
|
||||
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
|
||||
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready),
|
||||
.wide_slot_mem_req(wide_slot_mem_req), .wide_slot_mem_addr(wide_slot_mem_addr),
|
||||
.wide_slot_mem_rdata(wide_slot_mem_rdata), .wide_slot_mem_ready(wide_slot_mem_ready)
|
||||
);
|
||||
|
||||
wire arb_m_req, arb_m_wr;
|
||||
wire [ADDR_WIDTH-1:0] arb_m_addr;
|
||||
wire [15:0] arb_m_wdata;
|
||||
wire arb_m_lb_n, arb_m_ub_n;
|
||||
wire [15:0] arb_m_rdata;
|
||||
wire arb_m_ready;
|
||||
|
||||
slot_mem_arbiter #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS+1)
|
||||
) u_arbiter (
|
||||
.clk(clk), .rst(rst),
|
||||
.s_req(slot_mem_req), .s_wr(slot_mem_wr), .s_addr(slot_mem_addr),
|
||||
.s_wdata(slot_mem_wdata), .s_lb_n(slot_mem_lb_n), .s_ub_n(slot_mem_ub_n),
|
||||
.s_rdata(slot_mem_rdata), .s_ready(slot_mem_ready),
|
||||
.m_req(arb_m_req), .m_wr(arb_m_wr), .m_addr(arb_m_addr), .m_wdata(arb_m_wdata),
|
||||
.m_lb_n(arb_m_lb_n), .m_ub_n(arb_m_ub_n),
|
||||
.m_rdata(arb_m_rdata), .m_ready(arb_m_ready)
|
||||
);
|
||||
|
||||
wire pc_mem_req, pc_mem_wr;
|
||||
wire [ADDR_WIDTH-1:0] pc_mem_addr;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_wdata;
|
||||
wire pc_mem_lb_n, pc_mem_ub_n;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_rdata;
|
||||
wire pc_mem_ready;
|
||||
|
||||
memory_interface #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH)) u_memif (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(arb_m_req), .wr(arb_m_wr), .addr(arb_m_addr), .wdata(arb_m_wdata),
|
||||
.lb_n(arb_m_lb_n), .ub_n(arb_m_ub_n),
|
||||
.rdata(arb_m_rdata), .ready(arb_m_ready),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready)
|
||||
);
|
||||
|
||||
psram_controller #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||
) u_psram_ctrl (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready),
|
||||
.psram_a(psram_a), .psram_dq(psram_dq),
|
||||
.psram_ce_n(psram_ce_n), .psram_oe_n(psram_oe_n), .psram_we_n(psram_we_n),
|
||||
.psram_lb_n(psram_lb_n), .psram_ub_n(psram_ub_n), .psram_zz_n(psram_zz_n)
|
||||
);
|
||||
|
||||
// ================================================================
|
||||
// STEP16 Phase 5: real single-chip SDRAM weight-fetch backend --
|
||||
// a SEPARATE arbiter (slot_mem_arbiter_wide.v, N_PORTS=N_SLOTS, no
|
||||
// activation-fill port sharing this one, reused UNCHANGED from the
|
||||
// dual32 baseline at DATA_WIDTH=64) feeding sdram_weight_backend.v
|
||||
// (the real, isolated-and-validated AS4C4M16SA-6TIN controller).
|
||||
// Weight fetch never writes: mem_wr/mem_wdata tied to 0 at the
|
||||
// arbiter's own per-port inputs, matching weight_prefetch_engine_
|
||||
// wide.v's own always-full-word read behavior exactly (same
|
||||
// convention as the dual32 baseline).
|
||||
// ================================================================
|
||||
wire [N_SLOTS-1:0] wide_s_wr = {N_SLOTS{1'b0}};
|
||||
wire [64*N_SLOTS-1:0] wide_s_wdata = {(64*N_SLOTS){1'b0}};
|
||||
wire [N_SLOTS-1:0] wide_s_lb_n = {N_SLOTS{1'b0}};
|
||||
wire [N_SLOTS-1:0] wide_s_ub_n = {N_SLOTS{1'b0}};
|
||||
|
||||
wire wide_arb_m_req, wide_arb_m_wr;
|
||||
wire [ADDR_WIDTH-1:0] wide_arb_m_addr;
|
||||
wire [63:0] wide_arb_m_wdata;
|
||||
wire wide_arb_m_lb_n, wide_arb_m_ub_n;
|
||||
wire [63:0] wide_arb_m_rdata;
|
||||
wire wide_arb_m_ready;
|
||||
|
||||
slot_mem_arbiter_wide #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS), .DATA_WIDTH(64)
|
||||
) u_arbiter_wide (
|
||||
.clk(clk), .rst(rst),
|
||||
.s_req(wide_slot_mem_req), .s_wr(wide_s_wr), .s_addr(wide_slot_mem_addr),
|
||||
.s_wdata(wide_s_wdata), .s_lb_n(wide_s_lb_n), .s_ub_n(wide_s_ub_n),
|
||||
.s_rdata(wide_slot_mem_rdata), .s_ready(wide_slot_mem_ready),
|
||||
.m_req(wide_arb_m_req), .m_wr(wide_arb_m_wr), .m_addr(wide_arb_m_addr), .m_wdata(wide_arb_m_wdata),
|
||||
.m_lb_n(wide_arb_m_lb_n), .m_ub_n(wide_arb_m_ub_n),
|
||||
.m_rdata(wide_arb_m_rdata), .m_ready(wide_arb_m_ready)
|
||||
);
|
||||
|
||||
sdram_weight_backend_pack128 #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||
) u_sdram_backend (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(wide_arb_m_req), .mem_wr(1'b0), .mem_addr(wide_arb_m_addr), .mem_wdata(64'h0),
|
||||
.mem_rdata(wide_arb_m_rdata), .mem_ready(wide_arb_m_ready),
|
||||
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
|
||||
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
|
||||
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm)
|
||||
);
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,167 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP19 real hardware-facing top level.
|
||||
//
|
||||
// SINGLE EXTERNAL SDRAM ONLY. Forked from nms_neural_multiprocessor_
|
||||
// sdram_pack128.v (STEP18) with the ONE change this step's own
|
||||
// governing spec mandates: the real hardware/v1/rtl/psram_controller.v
|
||||
// + memory_interface.v pairing (activation-fill + result-writeback,
|
||||
// 16-bit) is REMOVED from the V2 physical path entirely and replaced
|
||||
// by sdram_unified_backend.v's own AR port, sharing the SAME single
|
||||
// physical AS4C4M16SA-6TIN SDRAM chip and the SAME single sdram_
|
||||
// controller.v instance the weight-fetch path (W port) already uses.
|
||||
//
|
||||
// slot_mem_arbiter.v (16-bit, activation+result) and slot_mem_
|
||||
// arbiter_wide.v (64-bit, weight) are BOTH reused completely
|
||||
// UNCHANGED -- their own downstream ports now both terminate at
|
||||
// sdram_unified_backend.v instead of two separate physical chains.
|
||||
// nms_dataflow_core_sdram.v, nms_activation_fill_ctrl_v3.v, nms_
|
||||
// memory_manager_stream_wide.v, weight_prefetch_engine_wide.v, and
|
||||
// neural_processor.v are ALL byte-for-byte unchanged -- this is a
|
||||
// pure memory-side substitution, per the governing spec's own
|
||||
// explicit instruction.
|
||||
//
|
||||
// V1 (hardware/v1/**) is untouched -- psram_controller.v and memory_
|
||||
// interface.v simply are no longer INSTANTIATED by this top-level;
|
||||
// neither file was modified, and V1's own golden-reference status is
|
||||
// unaffected.
|
||||
//
|
||||
// Real pin count (weight+activation+result, ALL through ONE chip):
|
||||
// 2(BA)+12(A)+1(CKE)+1(CS#)+1(RAS#)+1(CAS#)+1(WE#)+2(DQM)+16(DQ) = 37
|
||||
// pins total -- the SAME 37 pins the weight-only path already used in
|
||||
// STEP16-18 (no NEW physical SDRAM pins are needed to add activation/
|
||||
// result traffic, since it shares the identical physical bus).
|
||||
// ================================================================
|
||||
|
||||
module nms_neural_multiprocessor_sdram_unified #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 2,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter CLK_FREQ_MHZ = 80
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
// ---- STEP19: ONE physical SDRAM interface, ALL traffic
|
||||
// (weights + activations + results) ----
|
||||
output wire sdram_cke,
|
||||
output wire sdram_cs_n,
|
||||
output wire sdram_ras_n,
|
||||
output wire sdram_cas_n,
|
||||
output wire sdram_we_n,
|
||||
output wire [1:0] sdram_ba,
|
||||
output wire [11:0] sdram_a,
|
||||
inout wire [15:0] sdram_dq,
|
||||
output wire [1:0] sdram_dqm
|
||||
);
|
||||
|
||||
wire [N_SLOTS:0] slot_mem_req, slot_mem_wr;
|
||||
wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr;
|
||||
wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata, slot_mem_rdata;
|
||||
wire [N_SLOTS:0] slot_mem_lb_n, slot_mem_ub_n;
|
||||
wire [N_SLOTS:0] slot_mem_ready;
|
||||
|
||||
wire [N_SLOTS-1:0] wide_slot_mem_req;
|
||||
wire [ADDR_WIDTH*N_SLOTS-1:0] wide_slot_mem_addr;
|
||||
wire [64*N_SLOTS-1:0] wide_slot_mem_rdata;
|
||||
wire [N_SLOTS-1:0] wide_slot_mem_ready;
|
||||
|
||||
nms_dataflow_core_sdram #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
|
||||
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
|
||||
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_dataflow_core (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
|
||||
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
|
||||
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready),
|
||||
.wide_slot_mem_req(wide_slot_mem_req), .wide_slot_mem_addr(wide_slot_mem_addr),
|
||||
.wide_slot_mem_rdata(wide_slot_mem_rdata), .wide_slot_mem_ready(wide_slot_mem_ready)
|
||||
);
|
||||
|
||||
// ---- AR: activation-fill (shared, 1 port) + per-slot result
|
||||
// writeback (N_SLOTS ports), arbitrated exactly as before ----
|
||||
wire arb_m_req, arb_m_wr;
|
||||
wire [ADDR_WIDTH-1:0] arb_m_addr;
|
||||
wire [15:0] arb_m_wdata;
|
||||
wire arb_m_lb_n, arb_m_ub_n;
|
||||
wire [15:0] arb_m_rdata;
|
||||
wire arb_m_ready;
|
||||
|
||||
slot_mem_arbiter #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS+1)
|
||||
) u_arbiter (
|
||||
.clk(clk), .rst(rst),
|
||||
.s_req(slot_mem_req), .s_wr(slot_mem_wr), .s_addr(slot_mem_addr),
|
||||
.s_wdata(slot_mem_wdata), .s_lb_n(slot_mem_lb_n), .s_ub_n(slot_mem_ub_n),
|
||||
.s_rdata(slot_mem_rdata), .s_ready(slot_mem_ready),
|
||||
.m_req(arb_m_req), .m_wr(arb_m_wr), .m_addr(arb_m_addr), .m_wdata(arb_m_wdata),
|
||||
.m_lb_n(arb_m_lb_n), .m_ub_n(arb_m_ub_n),
|
||||
.m_rdata(arb_m_rdata), .m_ready(arb_m_ready)
|
||||
);
|
||||
|
||||
// ---- W: weight fetch (N_SLOTS ports), arbitrated exactly as
|
||||
// before -- weight fetch never writes, same tie-off convention
|
||||
// as STEP16-18 ----
|
||||
wire [N_SLOTS-1:0] wide_s_wr = {N_SLOTS{1'b0}};
|
||||
wire [64*N_SLOTS-1:0] wide_s_wdata = {(64*N_SLOTS){1'b0}};
|
||||
wire [N_SLOTS-1:0] wide_s_lb_n = {N_SLOTS{1'b0}};
|
||||
wire [N_SLOTS-1:0] wide_s_ub_n = {N_SLOTS{1'b0}};
|
||||
|
||||
wire wide_arb_m_req, wide_arb_m_wr;
|
||||
wire [ADDR_WIDTH-1:0] wide_arb_m_addr;
|
||||
wire [63:0] wide_arb_m_wdata;
|
||||
wire wide_arb_m_lb_n, wide_arb_m_ub_n;
|
||||
wire [63:0] wide_arb_m_rdata;
|
||||
wire wide_arb_m_ready;
|
||||
|
||||
slot_mem_arbiter_wide #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS), .DATA_WIDTH(64)
|
||||
) u_arbiter_wide (
|
||||
.clk(clk), .rst(rst),
|
||||
.s_req(wide_slot_mem_req), .s_wr(wide_s_wr), .s_addr(wide_slot_mem_addr),
|
||||
.s_wdata(wide_s_wdata), .s_lb_n(wide_s_lb_n), .s_ub_n(wide_s_ub_n),
|
||||
.s_rdata(wide_slot_mem_rdata), .s_ready(wide_slot_mem_ready),
|
||||
.m_req(wide_arb_m_req), .m_wr(wide_arb_m_wr), .m_addr(wide_arb_m_addr), .m_wdata(wide_arb_m_wdata),
|
||||
.m_lb_n(wide_arb_m_lb_n), .m_ub_n(wide_arb_m_ub_n),
|
||||
.m_rdata(wide_arb_m_rdata), .m_ready(wide_arb_m_ready)
|
||||
);
|
||||
|
||||
// ---- STEP19: ONE physical SDRAM backend, both W and AR ports ----
|
||||
sdram_unified_backend #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||
) u_sdram_backend (
|
||||
.clk(clk), .rst(rst),
|
||||
.w_req(wide_arb_m_req), .w_addr(wide_arb_m_addr),
|
||||
.w_rdata(wide_arb_m_rdata), .w_ready(wide_arb_m_ready),
|
||||
.ar_req(arb_m_req), .ar_wr(arb_m_wr), .ar_addr(arb_m_addr), .ar_wdata(arb_m_wdata),
|
||||
.ar_lb_n(arb_m_lb_n), .ar_ub_n(arb_m_ub_n),
|
||||
.ar_rdata(arb_m_rdata), .ar_ready(arb_m_ready),
|
||||
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
|
||||
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
|
||||
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm)
|
||||
);
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,123 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ================================================================
|
||||
// Neural Memory System (NMS) -- STEP9 real hardware-facing top level.
|
||||
// Mirrors hardware/v2/rtl/neural_multiprocessor.v's own scope exactly
|
||||
// (M8): nms_dataflow_core.v's N_SLOTS+1 independent Memory Backend
|
||||
// Interface ports funneled through the SAME, UNMODIFIED
|
||||
// slot_mem_arbiter.v down to the SAME, UNMODIFIED real V1 PSRAM
|
||||
// backend chain (memory_interface.v -> psram_controller.v).
|
||||
//
|
||||
// Nothing about the arbiter or the real PSRAM chain changes for the
|
||||
// NMS -- only nms_dataflow_core.v's own internals (the memory
|
||||
// organization DEC-0019/DEC-0020 decided) differ from the frozen V2
|
||||
// dataflow_core.v this module's own structure is copied from.
|
||||
// ================================================================
|
||||
|
||||
module nms_neural_multiprocessor_stream #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ACC_WIDTH = 32,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter N_SLOTS = 2,
|
||||
parameter N_NODES = 16,
|
||||
parameter MAX_DEPS = 4,
|
||||
parameter QUEUE_DEPTH = 8,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter PSRAM_DATA_WIDTH = 16,
|
||||
parameter CLK_FREQ_MHZ = 80
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire reg_valid,
|
||||
output wire reg_ready,
|
||||
input wire [$clog2(N_NODES)-1:0] reg_node_id,
|
||||
input wire [$clog2(MAX_DEPS+1)-1:0] reg_required,
|
||||
input wire [MAX_DEPS*$clog2(N_NODES)-1:0] reg_producer_ids,
|
||||
input wire [ADDR_WIDTH-1:0] reg_x_base,
|
||||
input wire [ADDR_WIDTH-1:0] reg_w_base,
|
||||
input wire [15:0] reg_n_tiles,
|
||||
input wire [ADDR_WIDTH-1:0] reg_result_addr,
|
||||
|
||||
output wire [ADDR_WIDTH-1:0] psram_a,
|
||||
inout wire [PSRAM_DATA_WIDTH-1:0] psram_dq,
|
||||
output wire psram_ce_n,
|
||||
output wire psram_oe_n,
|
||||
output wire psram_we_n,
|
||||
output wire psram_lb_n,
|
||||
output wire psram_ub_n,
|
||||
output wire psram_zz_n
|
||||
);
|
||||
|
||||
wire [N_SLOTS:0] slot_mem_req, slot_mem_wr;
|
||||
wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr;
|
||||
wire [16*(N_SLOTS+1)-1:0] slot_mem_wdata, slot_mem_rdata;
|
||||
wire [N_SLOTS:0] slot_mem_lb_n, slot_mem_ub_n;
|
||||
wire [N_SLOTS:0] slot_mem_ready;
|
||||
|
||||
nms_dataflow_core_stream #(
|
||||
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH), .ADDR_WIDTH(ADDR_WIDTH),
|
||||
.N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .QUEUE_DEPTH(QUEUE_DEPTH),
|
||||
.MAX_TILES(MAX_TILES), .PREFETCH_DISTANCE(PREFETCH_DISTANCE)
|
||||
) u_dataflow_core (
|
||||
.clk(clk), .rst(rst),
|
||||
.reg_valid(reg_valid), .reg_ready(reg_ready), .reg_node_id(reg_node_id),
|
||||
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
|
||||
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
|
||||
.reg_result_addr(reg_result_addr),
|
||||
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
|
||||
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
|
||||
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready)
|
||||
);
|
||||
|
||||
wire arb_m_req, arb_m_wr;
|
||||
wire [ADDR_WIDTH-1:0] arb_m_addr;
|
||||
wire [15:0] arb_m_wdata;
|
||||
wire arb_m_lb_n, arb_m_ub_n;
|
||||
wire [15:0] arb_m_rdata;
|
||||
wire arb_m_ready;
|
||||
|
||||
slot_mem_arbiter #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .N_PORTS(N_SLOTS+1)
|
||||
) u_arbiter (
|
||||
.clk(clk), .rst(rst),
|
||||
.s_req(slot_mem_req), .s_wr(slot_mem_wr), .s_addr(slot_mem_addr),
|
||||
.s_wdata(slot_mem_wdata), .s_lb_n(slot_mem_lb_n), .s_ub_n(slot_mem_ub_n),
|
||||
.s_rdata(slot_mem_rdata), .s_ready(slot_mem_ready),
|
||||
.m_req(arb_m_req), .m_wr(arb_m_wr), .m_addr(arb_m_addr), .m_wdata(arb_m_wdata),
|
||||
.m_lb_n(arb_m_lb_n), .m_ub_n(arb_m_ub_n),
|
||||
.m_rdata(arb_m_rdata), .m_ready(arb_m_ready)
|
||||
);
|
||||
|
||||
wire pc_mem_req, pc_mem_wr;
|
||||
wire [ADDR_WIDTH-1:0] pc_mem_addr;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_wdata;
|
||||
wire pc_mem_lb_n, pc_mem_ub_n;
|
||||
wire [PSRAM_DATA_WIDTH-1:0] pc_mem_rdata;
|
||||
wire pc_mem_ready;
|
||||
|
||||
memory_interface #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH)) u_memif (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(arb_m_req), .wr(arb_m_wr), .addr(arb_m_addr), .wdata(arb_m_wdata),
|
||||
.lb_n(arb_m_lb_n), .ub_n(arb_m_ub_n),
|
||||
.rdata(arb_m_rdata), .ready(arb_m_ready),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready)
|
||||
);
|
||||
|
||||
psram_controller #(
|
||||
.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(PSRAM_DATA_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)
|
||||
) u_psram_ctrl (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(pc_mem_req), .mem_wr(pc_mem_wr), .mem_addr(pc_mem_addr), .mem_wdata(pc_mem_wdata),
|
||||
.mem_lb_n(pc_mem_lb_n), .mem_ub_n(pc_mem_ub_n),
|
||||
.mem_rdata(pc_mem_rdata), .mem_ready(pc_mem_ready),
|
||||
.psram_a(psram_a), .psram_dq(psram_dq),
|
||||
.psram_ce_n(psram_ce_n), .psram_oe_n(psram_oe_n), .psram_we_n(psram_we_n),
|
||||
.psram_lb_n(psram_lb_n), .psram_ub_n(psram_ub_n), .psram_zz_n(psram_zz_n)
|
||||
);
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,48 @@
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- Weight SRAM candidate W1 "direct":
|
||||
// N_SLOTS private, single-port, natively P_IN*DATA_WIDTH-wide memories
|
||||
// (one per slot). Weights are NEVER shared across neurons (STEP2's own
|
||||
// analytical conclusion), so there is no arbitration to design at all
|
||||
// here -- every slot's own fill+read port is fully private. This
|
||||
// candidate mirrors hardware/v2/rtl/weight_buffer.v's own original
|
||||
// width/depth structure (EXP-0004/M3) exactly, replicated N_SLOTS
|
||||
// times, to measure the REAL total DP16KD cost of that replication
|
||||
// rather than assume it from the single-copy number alone.
|
||||
// ============================================================
|
||||
module nms_weight_direct #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES)
|
||||
)(
|
||||
input clk,
|
||||
input rst,
|
||||
|
||||
input [N_SLOTS-1:0] fill_we,
|
||||
input [N_SLOTS*TIW-1:0] fill_addr_flat,
|
||||
input [N_SLOTS*DATA_WIDTH*P_IN-1:0] fill_data_flat,
|
||||
|
||||
input [N_SLOTS-1:0] rd_en,
|
||||
input [N_SLOTS*TIW-1:0] rd_addr_flat,
|
||||
output [N_SLOTS*DATA_WIDTH*P_IN-1:0] rd_data_flat
|
||||
);
|
||||
|
||||
genvar g;
|
||||
generate
|
||||
for (g = 0; g < N_SLOTS; g = g + 1) begin : GEN_SLOT
|
||||
reg [DATA_WIDTH*P_IN-1:0] mem [0:MAX_TILES-1];
|
||||
reg [DATA_WIDTH*P_IN-1:0] rd_data_reg;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (fill_we[g])
|
||||
mem[fill_addr_flat[g*TIW +: TIW]] <= fill_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN];
|
||||
if (rd_en[g])
|
||||
rd_data_reg <= mem[rd_addr_flat[g*TIW +: TIW]];
|
||||
end
|
||||
|
||||
assign rd_data_flat[g*DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN] = rd_data_reg;
|
||||
end
|
||||
endgenerate
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,53 @@
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- Weight SRAM candidate W2 "packed":
|
||||
// same private-per-slot semantics as nms_weight_direct.v, but each
|
||||
// slot's P_IN*DATA_WIDTH-wide tile storage is decomposed into P_IN
|
||||
// separate, narrow (DATA_WIDTH=8-bit-wide) single-port memories (one
|
||||
// per MAC lane) instead of one wide 64-bit memory. Reassembly into the
|
||||
// full tile word is a static concatenation of P_IN registered
|
||||
// per-lane outputs -- no runtime mux, no real LUT cost expected there.
|
||||
// Exists to measure whether narrower-but-more-numerous memories pack
|
||||
// into fewer total DP16KD blocks than nms_weight_direct.v's wider-but-
|
||||
// fewer instances, per the user's own explicit ask (NMS spec S6) to
|
||||
// measure width/depth/packing real DP16KD cost rather than assume it.
|
||||
// ============================================================
|
||||
module nms_weight_packed #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter N_SLOTS = 4,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES)
|
||||
)(
|
||||
input clk,
|
||||
input rst,
|
||||
|
||||
input [N_SLOTS-1:0] fill_we,
|
||||
input [N_SLOTS*TIW-1:0] fill_addr_flat,
|
||||
input [N_SLOTS*DATA_WIDTH*P_IN-1:0] fill_data_flat,
|
||||
|
||||
input [N_SLOTS-1:0] rd_en,
|
||||
input [N_SLOTS*TIW-1:0] rd_addr_flat,
|
||||
output [N_SLOTS*DATA_WIDTH*P_IN-1:0] rd_data_flat
|
||||
);
|
||||
|
||||
genvar g, p;
|
||||
generate
|
||||
for (g = 0; g < N_SLOTS; g = g + 1) begin : GEN_SLOT
|
||||
for (p = 0; p < P_IN; p = p + 1) begin : GEN_LANE
|
||||
reg [DATA_WIDTH-1:0] mem [0:MAX_TILES-1];
|
||||
reg [DATA_WIDTH-1:0] rd_data_reg;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (fill_we[g])
|
||||
mem[fill_addr_flat[g*TIW +: TIW]] <=
|
||||
fill_data_flat[g*DATA_WIDTH*P_IN + p*DATA_WIDTH +: DATA_WIDTH];
|
||||
if (rd_en[g])
|
||||
rd_data_reg <= mem[rd_addr_flat[g*TIW +: TIW]];
|
||||
end
|
||||
|
||||
assign rd_data_flat[g*DATA_WIDTH*P_IN + p*DATA_WIDTH +: DATA_WIDTH] = rd_data_reg;
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,182 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ============================================================
|
||||
// NMS STEP15 (continuation) -- REAL 32-bit physical memory
|
||||
// interface: two independent, real, UNMODIFIED
|
||||
// hardware/v1/rtl/psram_controller.v instances (each driving its
|
||||
// own physical 16-bit ISSI IS66WVE4M16EBLL-70BLI chip), presenting
|
||||
// a single, coherent 32-bit mem_req/mem_addr/mem_wdata/mem_rdata/
|
||||
// mem_ready interface to the rest of the NMS.
|
||||
//
|
||||
// ARCHITECTURE DECISION (not assumed -- evaluated against the
|
||||
// alternatives STEP15's own governing spec explicitly listed):
|
||||
// - "one widened controller": REJECTED. psram_controller.v's own
|
||||
// internal state machine (STATE_INIT/CR_INIT/IDLE/READ/
|
||||
// PAGE_OPEN/PAGE_CLOSE/PAGE_REOPEN/WRITE/WRITE_WAIT) is written
|
||||
// around a DATA_WIDTH-parametrized single physical DQ bus
|
||||
// (psram_dq is `inout [DATA_WIDTH-1:0]`) -- widening DATA_WIDTH
|
||||
// to 32 in a SINGLE instance would drive ONE 32-bit inout bus,
|
||||
// which does not correspond to two SEPARATE physical chips each
|
||||
// with their OWN independent DQ pins, CE#, OE#, WE# etc. Two
|
||||
// real, separate physical chips cannot share one inout bus.
|
||||
// - "interleaved controllers" (alternate words to alternate
|
||||
// chips): REJECTED. This would double the ADDRESSABLE space
|
||||
// seen by the LOGICAL 32-bit interface without doubling the
|
||||
// WIDTH of a single transaction -- it solves a different
|
||||
// problem (more capacity at the same per-transfer width) than
|
||||
// what STEP15 asked for (wider per-transfer width at the same
|
||||
// tile-fetch granularity).
|
||||
// - "duplicated controller instances, shared control, duplicated
|
||||
// data path" -- SELECTED. Two full, real, byte-for-byte
|
||||
// UNMODIFIED psram_controller.v instances, each wired to its own
|
||||
// physical chip. Both instances receive IDENTICAL clk/rst/
|
||||
// mem_req/mem_wr/mem_addr every cycle (broadcast) -- since both
|
||||
// instances are the exact same RTL executing the exact same real
|
||||
// timing FSM against the exact same inputs, they are
|
||||
// STRUCTURALLY, CYCLE-EXACT synchronized by construction, not by
|
||||
// any added synchronization logic. This preserves
|
||||
// psram_controller.v's own real page/open/close timing
|
||||
// semantics EXACTLY (DEC requirement: "preserve existing memory
|
||||
// timing behavior") -- neither instance's own internal state
|
||||
// machine is touched at all.
|
||||
//
|
||||
// Address mapping: this module's own external mem_addr is the BYTE
|
||||
// address of the 32-bit transaction (matching
|
||||
// weight_prefetch_engine_wide.v's own established byte-address
|
||||
// convention, STEP14 -- so it plugs in without modifying that
|
||||
// already-validated module). Internally, mem_addr[ADDR_WIDTH-1:2]
|
||||
// (a right-shift by 2, i.e. divide by 4 bytes/32-bit-word) is fed
|
||||
// IDENTICALLY to both real psram_controller.v instances as THEIR own
|
||||
// required per-chip WORD address (2 bytes/word each) -- i.e. logical
|
||||
// 32-bit word W is stored as chip0's own word W (bits [15:0]) and
|
||||
// chip1's own word W (bits [31:16]). This is the standard byte-lane
|
||||
// "bus-widening" mapping (NOT address interleaving): the combined
|
||||
// interface holds the SAME NUMBER of 32-bit words as either chip
|
||||
// alone holds 16-bit words (matching real DEC-0029's own "2 parallel
|
||||
// chips, shared address bus" architecture). An earlier draft fed
|
||||
// mem_addr to both instances UNSHIFTED (treating a byte address as if
|
||||
// it were already a word address) -- a real bug, caught by the bit-
|
||||
// exact regression (tb_psram_dual32.v): every byte beyond the very
|
||||
// first tile's own lane 0 read back as 0 (uninitialized), because the
|
||||
// real controllers were being addressed ~4x further out than
|
||||
// intended. Fixed by adding the explicit >>2 conversion here.
|
||||
//
|
||||
// mem_ready: both instances' own mem_ready are asserted the SAME
|
||||
// cycle by construction (see above) -- output is chip0's own
|
||||
// mem_ready, with a real, synthesizable assertion checking chip1's
|
||||
// mem_ready never diverges (STEP15's own explicit "verify
|
||||
// simultaneous read/write timing, latency matching" requirement).
|
||||
// ============================================================
|
||||
module psram_controller_dual32 #(
|
||||
parameter ADDR_WIDTH = 23, // word address width, PER CHIP (same value fed to both)
|
||||
parameter CLK_FREQ_MHZ = 80
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// ---- 32-bit logical interface ----
|
||||
input wire mem_req,
|
||||
input wire mem_wr,
|
||||
input wire [ADDR_WIDTH-1:0] mem_addr,
|
||||
input wire [31:0] mem_wdata,
|
||||
input wire [1:0] mem_lb_n, // [0]=chip0 low-byte enable#, [1]=chip1 low-byte enable#
|
||||
input wire [1:0] mem_ub_n, // [0]=chip0 high-byte enable#, [1]=chip1 high-byte enable#
|
||||
|
||||
output wire [31:0] mem_rdata,
|
||||
output wire mem_ready,
|
||||
output reg lane_sync_error, // latched, real assertion: should NEVER go high
|
||||
|
||||
// ---- physical interface, chip 0 (bits [15:0]) ----
|
||||
output wire [ADDR_WIDTH-1:0] psram0_a,
|
||||
inout wire [15:0] psram0_dq,
|
||||
output wire psram0_ce_n,
|
||||
output wire psram0_oe_n,
|
||||
output wire psram0_we_n,
|
||||
output wire psram0_lb_n,
|
||||
output wire psram0_ub_n,
|
||||
output wire psram0_zz_n,
|
||||
|
||||
// ---- physical interface, chip 1 (bits [31:16]) ----
|
||||
output wire [ADDR_WIDTH-1:0] psram1_a,
|
||||
inout wire [15:0] psram1_dq,
|
||||
output wire psram1_ce_n,
|
||||
output wire psram1_oe_n,
|
||||
output wire psram1_we_n,
|
||||
output wire psram1_lb_n,
|
||||
output wire psram1_ub_n,
|
||||
output wire psram1_zz_n
|
||||
);
|
||||
|
||||
wire [15:0] rdata0, rdata1;
|
||||
wire ready0, ready1;
|
||||
|
||||
// mem_addr (this module's own external contract) is the BYTE
|
||||
// address of the 32-bit transaction -- matching
|
||||
// weight_prefetch_engine_wide.v's own established convention
|
||||
// (STEP14), so it plugs in without modifying that already-
|
||||
// validated module. The REAL psram_controller.v instances, in
|
||||
// contrast, each expect a per-chip WORD address (2 bytes/word) --
|
||||
// exactly like weight_prefetch_engine.v's own real, original
|
||||
// 16-bit usage, which explicitly converts via `w_base[ADDR_WIDTH-
|
||||
// 1:1]` before using it as mem_addr. For this 32-bit interface
|
||||
// (4 bytes/logical word, mapped straight across both 16-bit
|
||||
// chips at the SAME per-chip word index), the equivalent
|
||||
// conversion is a right-shift by 2, not 1 -- byte address bits
|
||||
// [1:0] select which of the 4 bytes within the 32-bit word (not
|
||||
// meaningful to the per-chip word address itself, only to
|
||||
// mem_lb_n/mem_ub_n lane selection, already handled separately
|
||||
// by the caller).
|
||||
wire [ADDR_WIDTH-1:0] chip_word_addr = mem_addr[ADDR_WIDTH-1:2];
|
||||
|
||||
psram_controller #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(16), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)) u_ctrl0 (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(mem_req), .mem_wr(mem_wr), .mem_addr(chip_word_addr),
|
||||
.mem_wdata(mem_wdata[15:0]), .mem_lb_n(mem_lb_n[0]), .mem_ub_n(mem_ub_n[0]),
|
||||
.mem_rdata(rdata0), .mem_ready(ready0),
|
||||
.psram_a(psram0_a), .psram_dq(psram0_dq),
|
||||
.psram_ce_n(psram0_ce_n), .psram_oe_n(psram0_oe_n), .psram_we_n(psram0_we_n),
|
||||
.psram_lb_n(psram0_lb_n), .psram_ub_n(psram0_ub_n), .psram_zz_n(psram0_zz_n)
|
||||
);
|
||||
|
||||
psram_controller #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(16), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)) u_ctrl1 (
|
||||
.clk(clk), .rst(rst),
|
||||
.mem_req(mem_req), .mem_wr(mem_wr), .mem_addr(chip_word_addr),
|
||||
.mem_wdata(mem_wdata[31:16]), .mem_lb_n(mem_lb_n[1]), .mem_ub_n(mem_ub_n[1]),
|
||||
.mem_rdata(rdata1), .mem_ready(ready1),
|
||||
.psram_a(psram1_a), .psram_dq(psram1_dq),
|
||||
.psram_ce_n(psram1_ce_n), .psram_oe_n(psram1_oe_n), .psram_we_n(psram1_we_n),
|
||||
.psram_lb_n(psram1_lb_n), .psram_ub_n(psram1_ub_n), .psram_zz_n(psram1_zz_n)
|
||||
);
|
||||
|
||||
assign mem_rdata = {rdata1, rdata0};
|
||||
|
||||
// mem_ready must be COMBINATIONAL, passed straight through from
|
||||
// ready0 -- NOT registered. An earlier draft registered it
|
||||
// (`mem_ready <= ready0`), adding one cycle of spurious latency
|
||||
// relative to mem_rdata (which reflects the controllers' own
|
||||
// CURRENT output combinationally) -- a real timing misalignment
|
||||
// caught by the bit-exact regression (every byte beyond tile 0's
|
||||
// own coincidental zero read back wrong): the caller sampled
|
||||
// mem_rdata one cycle before the (delayed) ready pulse told it to,
|
||||
// capturing stale/settling data. Fixed by making mem_ready a
|
||||
// simple continuous assignment, matching the real, single-chip
|
||||
// psram_controller.v's own timing exactly (which this module must
|
||||
// preserve, per its own design goal).
|
||||
assign mem_ready = ready0;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
lane_sync_error <= 1'b0;
|
||||
end else begin
|
||||
// Real, synthesizable cross-check: both instances are fed
|
||||
// byte-for-byte identical control/address every cycle, so
|
||||
// their own real timing FSMs MUST assert ready the same
|
||||
// cycle. This is not expected to ever fire; if it does,
|
||||
// the two physical chips have gone out of lockstep (e.g.
|
||||
// a real hardware fault or a genuine RTL bug), and
|
||||
// lane_sync_error latches permanently until reset.
|
||||
if (ready0 !== ready1) lane_sync_error <= 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,445 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ============================================================
|
||||
// NMS STEP16 -- minimal, CORRECT-FIRST SDR SDRAM controller for
|
||||
// Alliance Memory AS4C4M16SA-6TIN (64Mbit/8MB, x16, -6 speed grade:
|
||||
// tCK=6ns/166MHz max, CAS latency 3).
|
||||
//
|
||||
// Design priority explicitly stated by the governing spec:
|
||||
// correctness > performance > elegance. This controller therefore:
|
||||
// - ALWAYS uses auto-precharge (A10=1 on every READ/WRITE) --
|
||||
// every transaction activates a row, bursts BURST_LEN words, and
|
||||
// closes the row again before the next transaction. This is NOT
|
||||
// the fastest possible design (no page-hit/keep-row-open
|
||||
// optimization, unlike psram_controller.v's own real page-mode),
|
||||
// but it is trivially correct: no per-row state to track, no
|
||||
// risk of a stale-open-row bug, exactly one code path for every
|
||||
// transaction regardless of address history.
|
||||
// - Real JEDEC SDR SDRAM command encoding (CS#/RAS#/CAS#/WE#),
|
||||
// real power-up sequence (200us wait, PRECHARGE ALL, 8x AUTO
|
||||
// REFRESH, LOAD MODE REGISTER), real periodic AUTO REFRESH
|
||||
// insertion between transactions (tREFI = 4096 rows / 64ms).
|
||||
// - Real, standard -6-speed-grade SDR SDRAM timing (datasheet-
|
||||
// standard values, not vendor-specific tuning): tRCD=3cyc,
|
||||
// tRP=3cyc, tRAS(min)=7cyc, tRC=10cyc, tMRD=2cyc @166MHz -- all
|
||||
// re-derived per CLK_FREQ_MHZ so the same RTL is reused across
|
||||
// the Phase 4 100/133/166MHz sweep (STEP16's own explicit
|
||||
// "measure, do not estimate" requirement).
|
||||
//
|
||||
// Address format: word address (16-bit words), decomposed as
|
||||
// {bank[1:0], row[11:0], col[7:0]} -- matches the REAL AS4C4M16SA's
|
||||
// own 4-bank x 4096-row x 256-column x16 organization (4*4096*256 =
|
||||
// 4M words = 8MB, confirmed against the real datasheet capacity).
|
||||
//
|
||||
// External protocol matches this project's own established
|
||||
// mem_req/mem_wr/mem_addr/mem_wdata/mem_rdata/mem_ready convention
|
||||
// (same idiom as psram_controller.v), generalized to a BURST: one
|
||||
// req initiates a full BURST_LEN-word transaction (the natural unit
|
||||
// for this workload -- one weight TILE = P_IN*DATA_WIDTH/16 = 4
|
||||
// words at BURST_LEN=4, an exact match, not a coincidence chosen
|
||||
// after the fact -- STEP16 Phase 1 identified this exact byte count
|
||||
// per tile before any RTL was written).
|
||||
// ============================================================
|
||||
module sdram_controller #(
|
||||
parameter CLK_FREQ_MHZ = 166,
|
||||
parameter BURST_LEN = 4, // 1, 4, or 8 -- Phase 4 sweep parameter
|
||||
parameter ADDR_WIDTH = 22 // word address: 2 bank + 12 row + 8 col
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire req,
|
||||
input wire wr,
|
||||
input wire [ADDR_WIDTH-1:0] addr, // burst-aligned word address
|
||||
input wire [16*BURST_LEN-1:0] wdata, // BURST_LEN words, word0 first
|
||||
// STEP19: per-burst-word DQM write mask, 2 bits/word (bit0=low
|
||||
// byte, bit1=high byte, real SDR SDRAM DQM polarity: 1=masked/
|
||||
// NOT written, memory array retains its old value for that byte;
|
||||
// 0=written). Ties to {2*BURST_LEN{1'b0}} (never mask, i.e.
|
||||
// "always write full word") reproduces this module's own STEP16
|
||||
// behavior exactly -- every existing caller (sdram_weight_
|
||||
// backend.v, sdram_weight_backend_pack128.v, tb_sdram_controller.v)
|
||||
// was updated to pass that literal tie-off, so read/weight-fetch
|
||||
// behavior is byte-for-byte unchanged. Only meaningful for `wr`
|
||||
// transactions; ignored for reads (dqm is forced 0 during reads
|
||||
// regardless, since real SDR SDRAM masks READ OUTPUT with DQM too,
|
||||
// and this controller always wants valid read data back).
|
||||
input wire [2*BURST_LEN-1:0] wmask,
|
||||
output reg [16*BURST_LEN-1:0] rdata, // valid the same cycle `ready` pulses
|
||||
output reg ready, // pulses once, whole burst transaction done
|
||||
output reg busy,
|
||||
|
||||
// ---- real SDRAM physical pins ----
|
||||
output reg sdram_cke,
|
||||
output reg sdram_cs_n,
|
||||
output reg sdram_ras_n,
|
||||
output reg sdram_cas_n,
|
||||
output reg sdram_we_n,
|
||||
output reg [1:0] sdram_ba,
|
||||
output reg [11:0] sdram_a,
|
||||
inout wire [15:0] sdram_dq,
|
||||
output reg [1:0] sdram_dqm
|
||||
);
|
||||
|
||||
localparam BURST_IDXW = (BURST_LEN <= 1) ? 1 : $clog2(BURST_LEN);
|
||||
|
||||
// ---- real, standard -6-speed-grade timing, re-derived per
|
||||
// CLK_FREQ_MHZ (ceiling division: never UNDER-count a real ns
|
||||
// requirement) ----
|
||||
function integer ns_to_cycles;
|
||||
input integer ns;
|
||||
begin
|
||||
ns_to_cycles = (ns * CLK_FREQ_MHZ + 999) / 1000;
|
||||
end
|
||||
endfunction
|
||||
localparam T_RCD = ns_to_cycles(18); // ACTIVE -> READ/WRITE
|
||||
localparam T_RP = ns_to_cycles(18); // PRECHARGE -> ACTIVE
|
||||
// ACTIVE->PRECHARGE minimum (tRAS=42ns=7cyc@166MHz) is not
|
||||
// separately waited on: this design's own fixed sequencing
|
||||
// (tRCD + CAS_LATENCY + BURST_LEN data cycles, always >= 3+3+1=7
|
||||
// even at the narrowest BURST_LEN=1) already comfortably exceeds
|
||||
// it by construction before auto-precharge can begin internally.
|
||||
localparam T_MRD = ns_to_cycles(12); // LOAD MODE REGISTER -> any command
|
||||
localparam T_INIT_US= 200; // power-up wait, real datasheet value
|
||||
localparam T_INIT = T_INIT_US * CLK_FREQ_MHZ;
|
||||
localparam CAS_LATENCY = 3; // fixed for this part/speed grade
|
||||
// real refresh interval: 4096 rows must each be refreshed within
|
||||
// 64ms -> one AUTO REFRESH at least every 64e6ns/4096 = 15625ns
|
||||
localparam T_REFI = ns_to_cycles(15625);
|
||||
|
||||
localparam CNTW = $clog2((T_INIT>T_REFI ? T_INIT : T_REFI) + 1);
|
||||
|
||||
// JEDEC SDR SDRAM commands are encoded directly in the FSM below
|
||||
// via named signal drives (cs_n/ras_n/cas_n/we_n), not a lookup
|
||||
// table -- clearer to review against the real datasheet's own
|
||||
// command truth table line by line.
|
||||
|
||||
// tRC (ACTIVATE-to-ACTIVATE minimum, same bank), used by both the
|
||||
// init-refresh and steady-state refresh wait.
|
||||
function [CNTW-1:0] T_RC_MINUS1;
|
||||
localparam integer T_RC = ns_to_cycles(60);
|
||||
begin
|
||||
T_RC_MINUS1 = T_RC[CNTW-1:0] - 1'b1;
|
||||
end
|
||||
endfunction
|
||||
|
||||
localparam
|
||||
S_INIT_WAIT = 5'd0,
|
||||
S_INIT_PRE_WAIT = 5'd2,
|
||||
S_INIT_REF = 5'd3,
|
||||
S_INIT_REF_WAIT = 5'd4,
|
||||
S_INIT_MRS_WAIT = 5'd6,
|
||||
S_IDLE = 5'd7,
|
||||
S_REFRESH_WAIT = 5'd9,
|
||||
S_ACTIVATE_WAIT = 5'd11,
|
||||
S_CAS_WAIT = 5'd13,
|
||||
S_BURST_READ = 5'd14,
|
||||
S_BURST_WRITE = 5'd15,
|
||||
S_PRECHARGE_WAIT = 5'd16;
|
||||
|
||||
reg [4:0] state;
|
||||
reg [CNTW-1:0] wait_cnt;
|
||||
reg [3:0] init_ref_cnt;
|
||||
reg [CNTW-1:0] refresh_timer;
|
||||
reg [BURST_IDXW-1:0] burst_idx;
|
||||
reg req_wr_reg;
|
||||
reg [1:0] req_bank_reg;
|
||||
reg [11:0] req_row_reg;
|
||||
reg [7:0] req_col_reg;
|
||||
reg [16*BURST_LEN-1:0] wdata_reg;
|
||||
reg [2*BURST_LEN-1:0] wmask_reg;
|
||||
|
||||
wire [1:0] addr_bank = addr[ADDR_WIDTH-1:ADDR_WIDTH-2];
|
||||
wire [11:0] addr_row = addr[ADDR_WIDTH-3:8];
|
||||
wire [7:0] addr_col = addr[7:0];
|
||||
|
||||
// req_pending: latches a req that arrives in S_IDLE on the SAME
|
||||
// cycle a periodic AUTO REFRESH is also due. Without this, a
|
||||
// single-cycle req pulse (this project's own established
|
||||
// mem_req convention -- see weight_prefetch_engine.v's own header
|
||||
// comment) would be silently dropped whenever refresh wins
|
||||
// arbitration that cycle: the caller only holds req high for one
|
||||
// cycle, has no idea refresh was chosen instead, and then waits
|
||||
// forever for a `ready` that will never come -- a real,
|
||||
// frequency/burst-alignment-dependent deadlock found by STEP16's
|
||||
// own Phase 4 100/133/166MHz sweep (reproduced at BURST_LEN=1,
|
||||
// CLK_FREQ_MHZ=133, but the race is general, not specific to that
|
||||
// combination -- it is a matter of which absolute cycle each test
|
||||
// vector's req happens to land on).
|
||||
reg req_pending;
|
||||
wire eff_wr = req ? wr : req_wr_reg;
|
||||
wire [1:0] eff_bank = req ? addr_bank : req_bank_reg;
|
||||
wire [11:0] eff_row = req ? addr_row : req_row_reg;
|
||||
wire [7:0] eff_col = req ? addr_col : req_col_reg;
|
||||
wire [16*BURST_LEN-1:0] eff_wdata = req ? wdata : wdata_reg;
|
||||
wire [2*BURST_LEN-1:0] eff_wmask = req ? wmask : wmask_reg;
|
||||
|
||||
// tri-state DQ: driven only during a write burst
|
||||
reg dq_out_en;
|
||||
reg [15:0] dq_out;
|
||||
assign sdram_dq = dq_out_en ? dq_out : 16'hzzzz;
|
||||
|
||||
// Mode register value: burst length code + sequential burst type
|
||||
// (A3=0) + CAS latency 3 (A6:4=011) + standard write burst (A9=0).
|
||||
function [11:0] mrs_value;
|
||||
input integer burst_len;
|
||||
reg [2:0] bl_code;
|
||||
begin
|
||||
bl_code = (burst_len==1) ? 3'b000 :
|
||||
(burst_len==2) ? 3'b001 :
|
||||
(burst_len==4) ? 3'b010 :
|
||||
(burst_len==8) ? 3'b011 : 3'b111; // 111 = full page, unused here
|
||||
mrs_value = {3'b000, 1'b0, 3'b011, 1'b0, bl_code};
|
||||
end
|
||||
endfunction
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S_INIT_WAIT;
|
||||
wait_cnt <= T_INIT[CNTW-1:0];
|
||||
init_ref_cnt <= 4'd0;
|
||||
refresh_timer <= T_REFI[CNTW-1:0];
|
||||
sdram_cke <= 1'b1; // held high throughout, real part supports CKE-always-high operation
|
||||
sdram_cs_n <= 1'b1;
|
||||
sdram_ras_n <= 1'b1;
|
||||
sdram_cas_n <= 1'b1;
|
||||
sdram_we_n <= 1'b1;
|
||||
sdram_ba <= 2'b00;
|
||||
sdram_a <= 12'h000;
|
||||
sdram_dqm <= 2'b00; // both byte lanes always enabled (weight/tile fetch always full-word)
|
||||
dq_out_en <= 1'b0;
|
||||
ready <= 1'b0;
|
||||
busy <= 1'b1;
|
||||
req_pending <= 1'b0;
|
||||
end else begin
|
||||
// default: NOP every cycle unless a state below overrides it
|
||||
sdram_cs_n <= 1'b0;
|
||||
sdram_ras_n <= 1'b1;
|
||||
sdram_cas_n <= 1'b1;
|
||||
sdram_we_n <= 1'b1;
|
||||
ready <= 1'b0;
|
||||
dq_out_en <= 1'b0;
|
||||
sdram_dqm <= 2'b00; // default: no mask (reads always want valid data; writes override below per-word)
|
||||
|
||||
if (refresh_timer != 0) refresh_timer <= refresh_timer - 1'b1;
|
||||
|
||||
// Latch a fresh req's fields UNCONDITIONALLY, every cycle,
|
||||
// regardless of what state the controller is currently in
|
||||
// -- not just while in S_IDLE. ERR-0019's own fix only
|
||||
// covered "refresh wins arbitration the SAME cycle S_IDLE
|
||||
// sees req" -- but a real caller (e.g. slot_mem_arbiter_
|
||||
// wide.v) can pulse req for exactly one cycle at ANY time,
|
||||
// including a cycle where the controller is mid-refresh
|
||||
// (S_REFRESH_WAIT) or finishing a PREVIOUS transaction's
|
||||
// own PRECHARGE_WAIT tail -- i.e. NOT in S_IDLE at all that
|
||||
// cycle. The old S_IDLE-only latch silently missed those,
|
||||
// permanently starving whichever requester's pulse landed
|
||||
// there (found via the real N=2 D-Stress integration
|
||||
// benchmark, EXP-0042: both slots' memory managers hung
|
||||
// forever at tile_idx=0 while the arbiter's own `owner`
|
||||
// stayed locked on a grant the controller had already
|
||||
// forgotten -- a real, reproducible full-system deadlock,
|
||||
// not merely a slower run).
|
||||
if (req) begin
|
||||
req_wr_reg <= wr;
|
||||
req_bank_reg <= addr_bank;
|
||||
req_row_reg <= addr_row;
|
||||
req_col_reg <= addr_col;
|
||||
wdata_reg <= wdata;
|
||||
wmask_reg <= wmask;
|
||||
req_pending <= 1'b1;
|
||||
end
|
||||
|
||||
case (state)
|
||||
S_INIT_WAIT: begin
|
||||
busy <= 1'b1;
|
||||
if (wait_cnt != 0) wait_cnt <= wait_cnt - 1'b1;
|
||||
else begin
|
||||
// PRECHARGE ALL: RAS#=0,CAS#=1,WE#=0, A10=1
|
||||
sdram_ras_n <= 1'b0; sdram_we_n <= 1'b0;
|
||||
sdram_a[10] <= 1'b1;
|
||||
wait_cnt <= T_RP[CNTW-1:0] - 1'b1;
|
||||
state <= S_INIT_PRE_WAIT;
|
||||
end
|
||||
end
|
||||
S_INIT_PRE_WAIT: begin
|
||||
if (wait_cnt != 0) wait_cnt <= wait_cnt - 1'b1;
|
||||
else begin
|
||||
state <= S_INIT_REF;
|
||||
end
|
||||
end
|
||||
S_INIT_REF: begin
|
||||
// AUTO REFRESH: RAS#=0,CAS#=0,WE#=1
|
||||
sdram_ras_n <= 1'b0; sdram_cas_n <= 1'b0;
|
||||
wait_cnt <= T_RC_MINUS1();
|
||||
state <= S_INIT_REF_WAIT;
|
||||
end
|
||||
S_INIT_REF_WAIT: begin
|
||||
if (wait_cnt != 0) wait_cnt <= wait_cnt - 1'b1;
|
||||
else if (init_ref_cnt < 4'd7) begin
|
||||
init_ref_cnt <= init_ref_cnt + 1'b1;
|
||||
state <= S_INIT_REF;
|
||||
end else begin
|
||||
// LOAD MODE REGISTER: RAS#=0,CAS#=0,WE#=0, addr=mode value
|
||||
sdram_ras_n <= 1'b0; sdram_cas_n <= 1'b0; sdram_we_n <= 1'b0;
|
||||
sdram_ba <= 2'b00;
|
||||
sdram_a <= mrs_value(BURST_LEN);
|
||||
wait_cnt <= T_MRD[CNTW-1:0] - 1'b1;
|
||||
state <= S_INIT_MRS_WAIT;
|
||||
end
|
||||
end
|
||||
S_INIT_MRS_WAIT: begin
|
||||
if (wait_cnt != 0) wait_cnt <= wait_cnt - 1'b1;
|
||||
else begin
|
||||
busy <= 1'b0;
|
||||
state <= S_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
S_IDLE: begin
|
||||
busy <= 1'b0;
|
||||
// req (if any) was already latched into req_pending
|
||||
// unconditionally above, regardless of state -- see
|
||||
// that latch's own comment for why it must not be
|
||||
// scoped to only this state.
|
||||
if (refresh_timer == 0) begin
|
||||
// periodic AUTO REFRESH -- no row is ever left
|
||||
// open between transactions (auto-precharge
|
||||
// always used), so we can refresh immediately,
|
||||
// no PRECHARGE-ALL needed here.
|
||||
busy <= 1'b1;
|
||||
sdram_ras_n <= 1'b0; sdram_cas_n <= 1'b0;
|
||||
wait_cnt <= T_RC_MINUS1();
|
||||
refresh_timer <= T_REFI[CNTW-1:0];
|
||||
state <= S_REFRESH_WAIT;
|
||||
end else if (req || req_pending) begin
|
||||
busy <= 1'b1;
|
||||
req_wr_reg <= eff_wr;
|
||||
req_bank_reg <= eff_bank;
|
||||
req_row_reg <= eff_row;
|
||||
req_col_reg <= eff_col;
|
||||
wdata_reg <= eff_wdata;
|
||||
wmask_reg <= eff_wmask;
|
||||
req_pending <= 1'b0;
|
||||
// ACTIVATE: RAS#=0,CAS#=1,WE#=1, ba=bank, a=row
|
||||
sdram_ras_n <= 1'b0;
|
||||
sdram_ba <= eff_bank;
|
||||
sdram_a <= eff_row;
|
||||
wait_cnt <= T_RCD[CNTW-1:0] - 1'b1;
|
||||
state <= S_ACTIVATE_WAIT;
|
||||
end
|
||||
end
|
||||
|
||||
S_REFRESH_WAIT: begin
|
||||
if (wait_cnt != 0) wait_cnt <= wait_cnt - 1'b1;
|
||||
else state <= S_IDLE;
|
||||
end
|
||||
|
||||
S_ACTIVATE_WAIT: begin
|
||||
if (wait_cnt != 0) begin
|
||||
wait_cnt <= wait_cnt - 1'b1;
|
||||
end else begin
|
||||
// READ or WRITE with auto-precharge (A10=1):
|
||||
// CAS#=0, WE#=(0 for write /1 for read), ba=bank,
|
||||
// a[7:0]=col, a[10]=1
|
||||
sdram_cas_n <= 1'b0;
|
||||
sdram_we_n <= req_wr_reg ? 1'b0 : 1'b1;
|
||||
sdram_ba <= req_bank_reg;
|
||||
sdram_a <= {4'b0100, req_col_reg}; // a[11]=0,a[10]=1(auto-precharge),a[9:8]=0
|
||||
burst_idx <= {BURST_IDXW{1'b0}};
|
||||
if (req_wr_reg) begin
|
||||
dq_out_en <= 1'b1;
|
||||
dq_out <= wdata_reg[15:0];
|
||||
sdram_dqm <= wmask_reg[1:0];
|
||||
state <= S_BURST_WRITE;
|
||||
end else begin
|
||||
// Cycle-exact derivation (not assumed --
|
||||
// see the module's own design log /
|
||||
// EXP-0040 for the full walkthrough):
|
||||
// cas_n=0 becomes VISIBLE to the real chip
|
||||
// one cycle after this NBA (call that
|
||||
// cycle "C"). Entering S_CAS_WAIT also
|
||||
// takes effect at cycle C, with wait_cnt
|
||||
// set here. The state's own "wait_cnt==0"
|
||||
// capture branch first fires at cycle
|
||||
// C + wait_cnt_initial. We want that to be
|
||||
// C + CAS_LATENCY (data must be valid
|
||||
// exactly CAS_LATENCY real clocks after
|
||||
// the command is sampled) -- so
|
||||
// wait_cnt_initial = CAS_LATENCY exactly,
|
||||
// no adjustment.
|
||||
wait_cnt <= CAS_LATENCY[CNTW-1:0];
|
||||
state <= S_CAS_WAIT;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
S_CAS_WAIT: begin
|
||||
if (wait_cnt != 0) begin
|
||||
wait_cnt <= wait_cnt - 1'b1;
|
||||
end else begin
|
||||
rdata[0 +: 16] <= sdram_dq;
|
||||
// BURST_LEN==1 is a real, distinct edge case:
|
||||
// word0 IS the whole (only) burst -- go
|
||||
// straight to precharge-wait. Routing it
|
||||
// through S_BURST_READ instead (burst_idx
|
||||
// already at 1, one past the only valid
|
||||
// index) was a real deadlock, found and fixed
|
||||
// via the Phase 3 burst=1 test (EXP-0040):
|
||||
// S_BURST_READ's own "burst_idx==BURST_LEN-1"
|
||||
// exit check (==0) can never be true again
|
||||
// once burst_idx has already advanced to 1.
|
||||
if (BURST_LEN == 1) begin
|
||||
ready <= 1'b1;
|
||||
wait_cnt <= T_RP[CNTW-1:0] - 1'b1;
|
||||
state <= S_PRECHARGE_WAIT;
|
||||
end else begin
|
||||
burst_idx <= burst_idx + 1'b1;
|
||||
state <= S_BURST_READ;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
S_BURST_READ: begin
|
||||
// burst_idx's own width (BURST_IDXW=clog2(BURST_
|
||||
// LEN)) can only ever represent 0..BURST_LEN-1 --
|
||||
// capture therefore happens unconditionally every
|
||||
// cycle spent in this state (an explicit "<
|
||||
// BURST_LEN" guard here would always be true by
|
||||
// construction and was removed as dead logic).
|
||||
rdata[burst_idx*16 +: 16] <= sdram_dq;
|
||||
if (burst_idx == BURST_LEN[BURST_IDXW-1:0] - 1'b1) begin
|
||||
ready <= 1'b1;
|
||||
// auto-precharge already running internally;
|
||||
// enforce tRP before the next ACTIVATE.
|
||||
wait_cnt <= T_RP[CNTW-1:0] - 1'b1;
|
||||
state <= S_PRECHARGE_WAIT;
|
||||
end else begin
|
||||
burst_idx <= burst_idx + 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
S_BURST_WRITE: begin
|
||||
if (burst_idx < BURST_LEN[BURST_IDXW-1:0] - 1'b1) begin
|
||||
burst_idx <= burst_idx + 1'b1;
|
||||
dq_out_en <= 1'b1;
|
||||
dq_out <= wdata_reg[(burst_idx+1'b1)*16 +: 16];
|
||||
sdram_dqm <= wmask_reg[(burst_idx+1'b1)*2 +: 2];
|
||||
end else begin
|
||||
ready <= 1'b1;
|
||||
wait_cnt <= T_RP[CNTW-1:0] + 1'b1; // tWR folded in conservatively
|
||||
state <= S_PRECHARGE_WAIT;
|
||||
end
|
||||
end
|
||||
|
||||
S_PRECHARGE_WAIT: begin
|
||||
if (wait_cnt != 0) wait_cnt <= wait_cnt - 1'b1;
|
||||
else state <= S_IDLE;
|
||||
end
|
||||
|
||||
default: state <= S_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,322 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ============================================================
|
||||
// NMS STEP19 -- UNIFIED single-SDRAM memory backend.
|
||||
//
|
||||
// Replaces BOTH physical memory paths that existed through STEP18
|
||||
// (sdram_weight_backend_pack128.v for weights, and hardware/v1/rtl/
|
||||
// memory_interface.v + psram_controller.v for activation-fill/result-
|
||||
// writeback) with ONE physical AS4C4M16SA-6TIN SDRAM chip, ONE
|
||||
// sdram_controller.v instance (BURST_LEN=8), serving THREE logical
|
||||
// traffic classes through TWO external ports that exactly match what
|
||||
// the existing, UNCHANGED consumers already drive:
|
||||
//
|
||||
// W port (64-bit): weight_prefetch_engine_wide.v's own real
|
||||
// traffic, via slot_mem_arbiter_wide.v -- IDENTICAL external
|
||||
// contract to STEP18's sdram_weight_backend_pack128.v (byte
|
||||
// address in, 64-bit mem_rdata out), and internally reuses that
|
||||
// module's own validated N_ENTRIES=4 "other half" cache
|
||||
// unchanged (EXP-0046/ERR-0022's own fix, not re-derived here).
|
||||
//
|
||||
// AR port (16-bit, byte-maskable): nms_activation_fill_ctrl_v3.v's
|
||||
// own activation reads AND every per-slot nms_memory_manager_
|
||||
// stream_wide.v's own result writes, via slot_mem_arbiter.v --
|
||||
// IDENTICAL external contract to the real V1 psram_controller.v
|
||||
// port it replaces (word address in, 16-bit mem_wdata/mem_rdata,
|
||||
// mem_lb_n/mem_ub_n byte-lane write masking). Neither
|
||||
// nms_activation_fill_ctrl_v3.v nor nms_memory_manager_stream_
|
||||
// wide.v needed ANY change -- they already produce a WORD
|
||||
// address and already drive lb_n/ub_n exactly as the real V1
|
||||
// PSRAM controller expected.
|
||||
//
|
||||
// Neither weight_prefetch_engine_wide.v, nms_activation_fill_ctrl_v3.
|
||||
// v, nms_memory_manager_stream_wide.v, nor neural_processor.v changed
|
||||
// AT ALL for this step -- this is a pure memory-side substitution,
|
||||
// per the governing spec's own explicit instruction.
|
||||
//
|
||||
// KEY ENABLING FACT: real SDR SDRAM's own DQM pins are a per-BYTE
|
||||
// write mask (STEP19's own real, tested extension to sdram_
|
||||
// controller.v's `wmask` port) -- this lets a single-BYTE result
|
||||
// write happen INSIDE a shared BURST_LEN=8 (128-bit) transaction by
|
||||
// masking out every byte except the one/two the caller actually wants
|
||||
// written, with NO read-modify-write needed at all (the real SDRAM
|
||||
// chip itself leaves masked bytes untouched, by JEDEC definition).
|
||||
// Activation reads need no such trick -- a full 128-bit block is
|
||||
// fetched and the caller's own requested 16-bit word is extracted
|
||||
// combinationally from it.
|
||||
//
|
||||
// Arbitration: simple, correctness-first 2-way priority (weight
|
||||
// traffic strongly dominates real measured traffic -- STEP17 showed
|
||||
// the activation/result path at <=7.2% of all external-memory
|
||||
// activity -- so W is granted priority when both are pending, AR is
|
||||
// never starved since W's own real traffic pattern always eventually
|
||||
// idles between tiles/jobs). Exactly one physical SDRAM transaction
|
||||
// in flight at a time (matches sdram_controller.v's own inherent
|
||||
// single-transaction design, STEP18 Part E's own documented, accepted
|
||||
// scope boundary -- not revisited here).
|
||||
// ============================================================
|
||||
module sdram_unified_backend #(
|
||||
parameter ADDR_WIDTH = 23, // byte address width (W port convention)
|
||||
parameter CLK_FREQ_MHZ = 80,
|
||||
parameter W_ENTRIES = 4 // weight-cache depth, >= real N_SLOTS
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// ---- W: weight fetch (64-bit, byte address, read-only) ----
|
||||
input wire w_req,
|
||||
input wire [ADDR_WIDTH-1:0] w_addr,
|
||||
output reg [63:0] w_rdata,
|
||||
output reg w_ready,
|
||||
|
||||
// ---- AR: activation-fill (read) + result-writeback (write),
|
||||
// 16-bit, WORD address (matches the real V1 psram_controller.v
|
||||
// convention this port replaces exactly) ----
|
||||
input wire ar_req,
|
||||
input wire ar_wr,
|
||||
input wire [ADDR_WIDTH-1:0] ar_addr, // word address, low 22 bits meaningful
|
||||
// (matches slot_mem_arbiter.v's own
|
||||
// m_addr width convention exactly --
|
||||
// that arbiter's real callers only ever
|
||||
// drive a 22-bit-significant word
|
||||
// address into an ADDR_WIDTH-wide bus)
|
||||
input wire [15:0] ar_wdata,
|
||||
input wire ar_lb_n,
|
||||
input wire ar_ub_n,
|
||||
output reg [15:0] ar_rdata,
|
||||
output reg ar_ready,
|
||||
|
||||
output wire sdram_cke,
|
||||
output wire sdram_cs_n,
|
||||
output wire sdram_ras_n,
|
||||
output wire sdram_cas_n,
|
||||
output wire sdram_we_n,
|
||||
output wire [1:0] sdram_ba,
|
||||
output wire [11:0] sdram_a,
|
||||
inout wire [15:0] sdram_dq,
|
||||
output wire [1:0] sdram_dqm
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// W-port cache (identical logic to sdram_weight_backend_pack128.v
|
||||
// -- an N_ENTRIES-deep, fully-associative "other half" cache,
|
||||
// round-robin allocated; safe under any sizing, see that module's
|
||||
// own header/ERR-0022 for the full rationale, not repeated here)
|
||||
// ============================================================
|
||||
localparam WEIDXW = (W_ENTRIES <= 1) ? 1 : $clog2(W_ENTRIES);
|
||||
reg w_cache_valid [0:W_ENTRIES-1];
|
||||
reg [ADDR_WIDTH-1:0] w_cache_addr [0:W_ENTRIES-1];
|
||||
reg [63:0] w_cache_data [0:W_ENTRIES-1];
|
||||
reg [WEIDXW-1:0] w_alloc_ptr;
|
||||
|
||||
reg w_hit_found_c;
|
||||
reg [WEIDXW-1:0] w_hit_idx_c;
|
||||
integer ei;
|
||||
always @(*) begin
|
||||
w_hit_found_c = 1'b0;
|
||||
w_hit_idx_c = {WEIDXW{1'b0}};
|
||||
for (ei = 0; ei < W_ENTRIES; ei = ei + 1) begin
|
||||
if (w_cache_valid[ei] && w_cache_addr[ei] == w_addr) begin
|
||||
w_hit_found_c = 1'b1;
|
||||
w_hit_idx_c = ei[WEIDXW-1:0];
|
||||
end
|
||||
end
|
||||
end
|
||||
wire w_cache_hit = w_hit_found_c && w_req;
|
||||
|
||||
// ============================================================
|
||||
// Shared physical controller, BURST_LEN=8 (128-bit/16-byte real
|
||||
// SDRAM transactions), reused UNCHANGED from STEP16-18.
|
||||
// ============================================================
|
||||
reg ctrl_req;
|
||||
reg ctrl_wr;
|
||||
reg [21:0] ctrl_addr;
|
||||
reg [127:0] ctrl_wdata;
|
||||
reg [15:0] ctrl_wmask;
|
||||
wire [127:0] ctrl_rdata;
|
||||
wire ctrl_ready;
|
||||
wire ctrl_busy;
|
||||
|
||||
sdram_controller #(
|
||||
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(8), .ADDR_WIDTH(22)
|
||||
) u_sdram_ctrl (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr),
|
||||
.wdata(ctrl_wdata), .wmask(ctrl_wmask),
|
||||
.rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy),
|
||||
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
|
||||
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
|
||||
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm)
|
||||
);
|
||||
|
||||
localparam S_IDLE = 3'd0,
|
||||
S_W_WAIT = 3'd1,
|
||||
S_AR_RD_WAIT = 3'd2,
|
||||
S_AR_WR_WAIT = 3'd3;
|
||||
reg [2:0] state;
|
||||
reg w_pending_upper_half;
|
||||
reg [ADDR_WIDTH-1:0] w_pending_addr;
|
||||
reg [2:0] ar_pending_word;
|
||||
|
||||
// ---- req_pending latches (same fix class as sdram_controller.v's
|
||||
// own ERR-0019/ERR-0020): this backend's own top-level S_IDLE
|
||||
// arbitration can only START a new transaction when it is
|
||||
// genuinely idle. A single-cycle w_req/ar_req pulse (this
|
||||
// project's own established mem_req convention) arriving on a
|
||||
// cycle this backend happens to be busy servicing the OTHER port
|
||||
// would otherwise be silently dropped -- the caller has no idea,
|
||||
// waits forever for a `ready` that never comes. Found the hard way
|
||||
// (STEP19, EXP-0048): the first real N=4 D-Stress run deadlocked
|
||||
// at 0/256 neurons, jobs_allocated stuck at 12, because the very
|
||||
// first activation-fill read raced against weight-prefetch traffic
|
||||
// and was lost exactly this way. Fix: latch EVERY req's own fields
|
||||
// unconditionally, every cycle, regardless of current state (not
|
||||
// just from S_IDLE), mirroring sdram_controller.v's own corrected
|
||||
// fix exactly (ERR-0020: the FIRST attempt only latched from
|
||||
// S_IDLE, which was still not enough -- latch unconditionally).
|
||||
reg w_req_pending;
|
||||
reg [ADDR_WIDTH-1:0] w_req_addr_lat;
|
||||
reg ar_req_pending;
|
||||
reg ar_req_wr_lat;
|
||||
reg [ADDR_WIDTH-1:0] ar_req_addr_lat;
|
||||
reg [15:0] ar_req_wdata_lat;
|
||||
reg ar_req_lbn_lat, ar_req_ubn_lat;
|
||||
|
||||
wire w_eff_req = w_req || w_req_pending;
|
||||
wire [ADDR_WIDTH-1:0] w_eff_addr = w_req ? w_addr : w_req_addr_lat;
|
||||
wire ar_eff_req = ar_req || ar_req_pending;
|
||||
wire ar_eff_wr = ar_req ? ar_wr : ar_req_wr_lat;
|
||||
wire [ADDR_WIDTH-1:0] ar_eff_addr = ar_req ? ar_addr : ar_req_addr_lat;
|
||||
wire [15:0] ar_eff_wdata= ar_req ? ar_wdata : ar_req_wdata_lat;
|
||||
wire ar_eff_lbn = ar_req ? ar_lb_n : ar_req_lbn_lat;
|
||||
wire ar_eff_ubn = ar_req ? ar_ub_n : ar_req_ubn_lat;
|
||||
|
||||
wire [21:0] w_eff_aligned_word_addr = {w_eff_addr[ADDR_WIDTH-1:4], 3'b000};
|
||||
wire w_eff_addr_is_upper_half = w_eff_addr[3];
|
||||
wire [21:0] ar_eff_block_base = {ar_eff_addr[21:3], 3'b000};
|
||||
wire [2:0] ar_eff_word_in_blk = ar_eff_addr[2:0];
|
||||
|
||||
integer ri;
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S_IDLE;
|
||||
for (ri = 0; ri < W_ENTRIES; ri = ri + 1) w_cache_valid[ri] <= 1'b0;
|
||||
w_alloc_ptr <= {WEIDXW{1'b0}};
|
||||
ctrl_req <= 1'b0; ctrl_wr <= 1'b0; ctrl_addr <= 22'h0;
|
||||
ctrl_wdata <= 128'h0; ctrl_wmask <= 16'hFFFF;
|
||||
w_ready <= 1'b0; w_rdata <= 64'h0;
|
||||
ar_ready <= 1'b0; ar_rdata <= 16'h0;
|
||||
w_pending_upper_half <= 1'b0; w_pending_addr <= {ADDR_WIDTH{1'b0}};
|
||||
ar_pending_word <= 3'h0;
|
||||
w_req_pending <= 1'b0; w_req_addr_lat <= {ADDR_WIDTH{1'b0}};
|
||||
ar_req_pending <= 1'b0; ar_req_wr_lat <= 1'b0;
|
||||
ar_req_addr_lat <= {ADDR_WIDTH{1'b0}}; ar_req_wdata_lat <= 16'h0;
|
||||
ar_req_lbn_lat <= 1'b1; ar_req_ubn_lat <= 1'b1;
|
||||
end else begin
|
||||
ctrl_req <= 1'b0;
|
||||
w_ready <= 1'b0;
|
||||
ar_ready <= 1'b0;
|
||||
|
||||
// latch fresh requests unconditionally, every cycle,
|
||||
// regardless of state (see req_pending's own comment above)
|
||||
if (w_req) begin
|
||||
w_req_addr_lat <= w_addr;
|
||||
w_req_pending <= 1'b1;
|
||||
end
|
||||
if (ar_req) begin
|
||||
ar_req_wr_lat <= ar_wr;
|
||||
ar_req_addr_lat <= ar_addr;
|
||||
ar_req_wdata_lat <= ar_wdata;
|
||||
ar_req_lbn_lat <= ar_lb_n;
|
||||
ar_req_ubn_lat <= ar_ub_n;
|
||||
ar_req_pending <= 1'b1;
|
||||
end
|
||||
|
||||
case (state)
|
||||
S_IDLE: begin
|
||||
// W has priority when both are pending (real
|
||||
// measured traffic: weight >>> activation+result,
|
||||
// STEP17 EXP-0045 -- AR is never starved since W's
|
||||
// own real access pattern idles between tiles).
|
||||
if (w_cache_hit) begin
|
||||
// fully serviced THIS cycle -- must also cancel
|
||||
// the unconditional latch above, which just set
|
||||
// w_req_pending<=1 for this SAME w_req pulse
|
||||
// (real bug found via full regression, EXP-0048
|
||||
// /ERR-0023: without this the latch survives
|
||||
// uncontested, and next cycle w_eff_req reads
|
||||
// true from STALE w_req_pending/w_req_addr_lat,
|
||||
// issuing a bogus extra fetch that shifts every
|
||||
// subsequent response by one).
|
||||
w_rdata <= w_cache_data[w_hit_idx_c];
|
||||
w_ready <= 1'b1;
|
||||
w_cache_valid[w_hit_idx_c] <= 1'b0;
|
||||
w_req_pending <= 1'b0;
|
||||
end else if (w_eff_req) begin
|
||||
ctrl_req <= 1'b1;
|
||||
ctrl_wr <= 1'b0;
|
||||
ctrl_addr <= w_eff_aligned_word_addr;
|
||||
ctrl_wmask <= 16'h0000;
|
||||
w_pending_upper_half <= w_eff_addr_is_upper_half;
|
||||
w_pending_addr <= w_eff_addr;
|
||||
w_req_pending <= 1'b0;
|
||||
state <= S_W_WAIT;
|
||||
end else if (ar_eff_req && !ar_eff_wr) begin
|
||||
ctrl_req <= 1'b1;
|
||||
ctrl_wr <= 1'b0;
|
||||
ctrl_addr <= ar_eff_block_base;
|
||||
ctrl_wmask <= 16'h0000;
|
||||
ar_pending_word <= ar_eff_word_in_blk;
|
||||
ar_req_pending <= 1'b0;
|
||||
state <= S_AR_RD_WAIT;
|
||||
end else if (ar_eff_req && ar_eff_wr) begin
|
||||
// mask every word except the target one; within
|
||||
// the target word, pass ar_lb_n/ar_ub_n through
|
||||
// directly (same active-low "write this byte"
|
||||
// polarity as real SDRAM DQM: lb_n=0 -> DQM=0
|
||||
// -> byte written; lb_n=1 -> DQM=1 -> masked).
|
||||
ctrl_req <= 1'b1;
|
||||
ctrl_wr <= 1'b1;
|
||||
ctrl_addr <= ar_eff_block_base;
|
||||
ctrl_wdata <= {8{ar_eff_wdata}}; // replicate; only the target word's mask bits matter
|
||||
ctrl_wmask <= {16{1'b1}} & ~(16'h0003 << (ar_eff_word_in_blk*2)) | ({14'b0, ar_eff_ubn, ar_eff_lbn} << (ar_eff_word_in_blk*2));
|
||||
ar_req_pending <= 1'b0;
|
||||
state <= S_AR_WR_WAIT;
|
||||
end
|
||||
end
|
||||
S_W_WAIT: begin
|
||||
if (ctrl_ready) begin
|
||||
if (w_pending_upper_half) begin
|
||||
w_rdata <= ctrl_rdata[127:64];
|
||||
w_cache_data[w_alloc_ptr] <= ctrl_rdata[63:0];
|
||||
w_cache_addr[w_alloc_ptr] <= w_pending_addr - {{(ADDR_WIDTH-4){1'b0}}, 4'd8};
|
||||
end else begin
|
||||
w_rdata <= ctrl_rdata[63:0];
|
||||
w_cache_data[w_alloc_ptr] <= ctrl_rdata[127:64];
|
||||
w_cache_addr[w_alloc_ptr] <= w_pending_addr + {{(ADDR_WIDTH-4){1'b0}}, 4'd8};
|
||||
end
|
||||
w_cache_valid[w_alloc_ptr] <= 1'b1;
|
||||
w_alloc_ptr <= (w_alloc_ptr == W_ENTRIES[WEIDXW-1:0]-1'b1) ? {WEIDXW{1'b0}} : w_alloc_ptr + 1'b1;
|
||||
w_ready <= 1'b1;
|
||||
state <= S_IDLE;
|
||||
end
|
||||
end
|
||||
S_AR_RD_WAIT: begin
|
||||
if (ctrl_ready) begin
|
||||
ar_rdata <= ctrl_rdata[ar_pending_word*16 +: 16];
|
||||
ar_ready <= 1'b1;
|
||||
state <= S_IDLE;
|
||||
end
|
||||
end
|
||||
S_AR_WR_WAIT: begin
|
||||
if (ctrl_ready) begin
|
||||
ar_ready <= 1'b1;
|
||||
state <= S_IDLE;
|
||||
end
|
||||
end
|
||||
default: state <= S_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,68 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ============================================================
|
||||
// NMS STEP16 Phase 5 -- SDRAM weight-fetch backend. Wraps the real,
|
||||
// isolated-and-validated sdram_controller.v (Phase 1-4, EXP-0040/0041,
|
||||
// ERR-0016..0019) with BURST_LEN=4 (P_IN*DATA_WIDTH/16 = 4 words/tile,
|
||||
// the exact natural match identified in Phase 1) and presents the
|
||||
// same mem_req/mem_wr/mem_addr/mem_wdata/mem_rdata/mem_ready
|
||||
// convention as psram_controller_dual32.v, so it drops into
|
||||
// nms_neural_multiprocessor_sdram.v's wide weight-fetch port exactly
|
||||
// where psram_controller_dual32.v sits in the dual32 baseline --
|
||||
// same external contract, different physical memory underneath, for
|
||||
// a direct, apples-to-apples STEP16 comparison.
|
||||
//
|
||||
// Address conversion: `mem_addr` is a BYTE address (this project's
|
||||
// own established convention, matching weight_prefetch_engine_wide.v
|
||||
// and psram_controller_dual32.v's own external contract). The real
|
||||
// AS4C4M16SA-6TIN is x16 (2 bytes/word), so the SDRAM controller's
|
||||
// own word address is `mem_addr >> 1` -- exactly analogous to
|
||||
// STEP15's own real ">>2" byte-to-32-bit-word fix (EXP-0037 bug #1),
|
||||
// here ">>1" for a 16-bit-word device.
|
||||
//
|
||||
// Weight fetch never writes (same convention as psram_controller_
|
||||
// dual32.v's own top-level instantiation, which ties mem_wr=0):
|
||||
// `mem_wr` is exposed here for interface symmetry but the underlying
|
||||
// sdram_controller is only ever driven with wr=0 by this wrapper's
|
||||
// own real usage in nms_neural_multiprocessor_sdram.v.
|
||||
// ============================================================
|
||||
module sdram_weight_backend #(
|
||||
parameter ADDR_WIDTH = 23, // byte address width (project convention)
|
||||
parameter CLK_FREQ_MHZ = 80
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire mem_req,
|
||||
input wire mem_wr,
|
||||
input wire [ADDR_WIDTH-1:0] mem_addr,
|
||||
input wire [63:0] mem_wdata,
|
||||
output wire [63:0] mem_rdata,
|
||||
output wire mem_ready,
|
||||
|
||||
output wire sdram_cke,
|
||||
output wire sdram_cs_n,
|
||||
output wire sdram_ras_n,
|
||||
output wire sdram_cas_n,
|
||||
output wire sdram_we_n,
|
||||
output wire [1:0] sdram_ba,
|
||||
output wire [11:0] sdram_a,
|
||||
inout wire [15:0] sdram_dq,
|
||||
output wire [1:0] sdram_dqm
|
||||
);
|
||||
|
||||
wire busy;
|
||||
wire [21:0] sdram_word_addr = mem_addr[ADDR_WIDTH-1:1]; // byte -> 16-bit-word address
|
||||
|
||||
sdram_controller #(
|
||||
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(4), .ADDR_WIDTH(22)
|
||||
) u_sdram_ctrl (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(mem_req), .wr(mem_wr), .addr(sdram_word_addr),
|
||||
.wdata(mem_wdata), .wmask(8'h00), .rdata(mem_rdata), .ready(mem_ready), .busy(busy),
|
||||
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
|
||||
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
|
||||
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm)
|
||||
);
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,171 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ============================================================
|
||||
// NMS STEP18 Part C/D/F -- SDRAM weight-fetch backend, PACKED variant.
|
||||
//
|
||||
// Same external contract as sdram_weight_backend.v (STEP16): byte
|
||||
// address in, 64-bit mem_rdata out, mem_req/mem_wr/mem_ready --
|
||||
// weight_prefetch_engine_wide.v (MEM_DATA_WIDTH=64) plugs in UNCHANGED,
|
||||
// and neural_processor.v is never touched. ONLY the physical transfer
|
||||
// underneath changes: BURST_LEN=8 (128 bits/16 bytes per real SDRAM
|
||||
// transaction, 2 P8 tiles' worth) instead of STEP16's BURST_LEN=4 (64
|
||||
// bits/8 bytes, 1 tile).
|
||||
//
|
||||
// Design: an N_ENTRIES-deep, fully-associative "other half" cache
|
||||
// holds the not-yet-requested half of each real 128-bit fetch still
|
||||
// issued, tagged by its own byte address. On mem_req(addr):
|
||||
// - cache HIT (some entry's addr matches): serve instantly, no new
|
||||
// SDRAM transaction, invalidate that entry.
|
||||
// - cache MISS: issue a real BURST_LEN=8 fetch at the 16-byte-
|
||||
// aligned base covering addr, serve the HALF the caller actually
|
||||
// asked for, and cache the OTHER half (round-robin-allocated
|
||||
// entry) for a possible future hit.
|
||||
//
|
||||
// WHY N_ENTRIES>1 IS REQUIRED (found the hard way, EXP-0046): a first
|
||||
// draft used a single-entry cache, correct in isolation (see
|
||||
// tb_sdram_weight_backend_pack128.v, 20/20 PASS for one requester) but
|
||||
// a REAL regression in the full N=4 system (74004 cycles vs the
|
||||
// STEP16 baseline's 49430 -- WORSE, not better). Root cause: the real
|
||||
// system's N_SLOTS independent memory managers all share ONE physical
|
||||
// backend through slot_mem_arbiter_wide.v, which interleaves their
|
||||
// requests round-robin. A single cache entry gets overwritten by
|
||||
// ANOTHER slot's own "other half" before the ORIGINAL slot's own next
|
||||
// (paired) request ever arrives, so nearly every access became a real
|
||||
// 16-cycle miss instead of the intended ~50% instant-hit rate --
|
||||
// WORSE than the STEP16 baseline's 10-cycle BURST_LEN=4 transactions.
|
||||
// Fix: size the cache to N_ENTRIES (>= N_SLOTS, the real worst-case
|
||||
// number of simultaneously-pending "other halves" -- each slot has at
|
||||
// most ONE outstanding request at a time, by the existing memory
|
||||
// manager's own design, so N_SLOTS entries can never be exceeded in
|
||||
// real traffic). Round-robin eviction (not LRU) is used for
|
||||
// simplicity; it is SAFE regardless of sizing accuracy, since an
|
||||
// evicted-too-early entry only costs an extra real fetch (a
|
||||
// performance effect), never incorrect data (a cache MISS always
|
||||
// falls back to a real, address-exact fetch).
|
||||
// ============================================================
|
||||
module sdram_weight_backend_pack128 #(
|
||||
parameter ADDR_WIDTH = 23, // byte address width (project convention)
|
||||
parameter CLK_FREQ_MHZ = 80,
|
||||
parameter N_ENTRIES = 4 // >= real N_SLOTS in the system using this backend
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
input wire mem_req,
|
||||
input wire mem_wr,
|
||||
input wire [ADDR_WIDTH-1:0] mem_addr,
|
||||
input wire [63:0] mem_wdata,
|
||||
output reg [63:0] mem_rdata,
|
||||
output reg mem_ready,
|
||||
|
||||
output wire sdram_cke,
|
||||
output wire sdram_cs_n,
|
||||
output wire sdram_ras_n,
|
||||
output wire sdram_cas_n,
|
||||
output wire sdram_we_n,
|
||||
output wire [1:0] sdram_ba,
|
||||
output wire [11:0] sdram_a,
|
||||
inout wire [15:0] sdram_dq,
|
||||
output wire [1:0] sdram_dqm
|
||||
);
|
||||
|
||||
localparam EIDXW = (N_ENTRIES <= 1) ? 1 : $clog2(N_ENTRIES);
|
||||
|
||||
reg cache_valid [0:N_ENTRIES-1];
|
||||
reg [ADDR_WIDTH-1:0] cache_addr [0:N_ENTRIES-1];
|
||||
reg [63:0] cache_data [0:N_ENTRIES-1];
|
||||
reg [EIDXW-1:0] alloc_ptr;
|
||||
|
||||
reg hit_found_c;
|
||||
reg [EIDXW-1:0] hit_idx_c;
|
||||
integer ei;
|
||||
always @(*) begin
|
||||
hit_found_c = 1'b0;
|
||||
hit_idx_c = {EIDXW{1'b0}};
|
||||
for (ei = 0; ei < N_ENTRIES; ei = ei + 1) begin
|
||||
if (cache_valid[ei] && cache_addr[ei] == mem_addr) begin
|
||||
hit_found_c = 1'b1;
|
||||
hit_idx_c = ei[EIDXW-1:0];
|
||||
end
|
||||
end
|
||||
end
|
||||
wire cache_hit = hit_found_c && mem_req && !mem_wr;
|
||||
|
||||
// ---- real SDRAM controller, BURST_LEN=8 (128-bit/16-byte txns) ----
|
||||
reg ctrl_req;
|
||||
reg ctrl_wr;
|
||||
reg [21:0] ctrl_addr; // 22-bit word address (16-bit words)
|
||||
wire [127:0] ctrl_rdata;
|
||||
wire ctrl_ready;
|
||||
wire ctrl_busy;
|
||||
|
||||
wire [21:0] aligned_word_addr = {mem_addr[ADDR_WIDTH-1:4], 3'b000}; // 16-byte-aligned word address
|
||||
wire addr_is_upper_half = mem_addr[3]; // 1 = caller wants bytes [aligned+8 .. aligned+15]
|
||||
|
||||
sdram_controller #(
|
||||
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(8), .ADDR_WIDTH(22)
|
||||
) u_sdram_ctrl (
|
||||
.clk(clk), .rst(rst),
|
||||
.req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr),
|
||||
.wdata(64'h0), .wmask(16'h0000), .rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy),
|
||||
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
|
||||
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
|
||||
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm)
|
||||
);
|
||||
|
||||
localparam S_IDLE = 2'd0, S_WAIT = 2'd1;
|
||||
reg [1:0] state;
|
||||
reg pending_upper_half;
|
||||
reg [ADDR_WIDTH-1:0] pending_addr;
|
||||
|
||||
integer ri;
|
||||
always @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= S_IDLE;
|
||||
for (ri = 0; ri < N_ENTRIES; ri = ri + 1) cache_valid[ri] <= 1'b0;
|
||||
alloc_ptr <= {EIDXW{1'b0}};
|
||||
ctrl_req <= 1'b0; ctrl_wr <= 1'b0; ctrl_addr <= 22'h0;
|
||||
mem_ready <= 1'b0; mem_rdata <= 64'h0;
|
||||
pending_upper_half <= 1'b0; pending_addr <= {ADDR_WIDTH{1'b0}};
|
||||
end else begin
|
||||
ctrl_req <= 1'b0;
|
||||
mem_ready <= 1'b0;
|
||||
|
||||
case (state)
|
||||
S_IDLE: begin
|
||||
if (cache_hit) begin
|
||||
mem_rdata <= cache_data[hit_idx_c];
|
||||
mem_ready <= 1'b1;
|
||||
cache_valid[hit_idx_c] <= 1'b0;
|
||||
end else if (mem_req && !mem_wr) begin
|
||||
ctrl_req <= 1'b1;
|
||||
ctrl_wr <= 1'b0;
|
||||
ctrl_addr <= aligned_word_addr;
|
||||
pending_upper_half <= addr_is_upper_half;
|
||||
pending_addr <= mem_addr;
|
||||
state <= S_WAIT;
|
||||
end
|
||||
end
|
||||
S_WAIT: begin
|
||||
if (ctrl_ready) begin
|
||||
if (pending_upper_half) begin
|
||||
mem_rdata <= ctrl_rdata[127:64];
|
||||
cache_data[alloc_ptr] <= ctrl_rdata[63:0];
|
||||
cache_addr[alloc_ptr] <= pending_addr - {{(ADDR_WIDTH-4){1'b0}}, 4'd8};
|
||||
end else begin
|
||||
mem_rdata <= ctrl_rdata[63:0];
|
||||
cache_data[alloc_ptr] <= ctrl_rdata[127:64];
|
||||
cache_addr[alloc_ptr] <= pending_addr + {{(ADDR_WIDTH-4){1'b0}}, 4'd8};
|
||||
end
|
||||
cache_valid[alloc_ptr] <= 1'b1;
|
||||
alloc_ptr <= (alloc_ptr == N_ENTRIES[EIDXW-1:0]-1'b1) ? {EIDXW{1'b0}} : alloc_ptr + 1'b1;
|
||||
mem_ready <= 1'b1;
|
||||
state <= S_IDLE;
|
||||
end
|
||||
end
|
||||
default: state <= S_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,181 @@
|
||||
`timescale 1ns/1ps
|
||||
|
||||
// ============================================================
|
||||
// Neural Memory System (NMS) -- STEP11: real weight prefetch engine.
|
||||
//
|
||||
// Replaces prefetch_engine.v's per-tile single-shot usage inside
|
||||
// nms_memory_manager.v with a CONTINUOUS, multi-tile fetch stream.
|
||||
//
|
||||
// Real analysis (see docs/architecture/nms_weight_prefetch.md and
|
||||
// hardware/v2/logs/development.log): the real backend
|
||||
// (hardware/v1/rtl/memory_interface.v -> psram_controller.v) is a
|
||||
// fire-and-forget, ONE-transaction-in-flight-at-a-time protocol (a
|
||||
// single mem_req pulse, wait for mem_ready, that IS the transaction --
|
||||
// no wire-level pipelining is physically possible against this real
|
||||
// backend, matching the real PSRAM's own single physical port). So
|
||||
// "multiple outstanding requests" cannot mean multiple simultaneous
|
||||
// WORD transactions -- it means eliminating the CONTROL-PLANE
|
||||
// overhead the old design paid at every tile boundary (prefetch_engine
|
||||
// return-to-IDLE, fetch_done pulse, nms_memory_manager's own
|
||||
// !pf_done-gated restart, ERR-0013) and letting the fetch stream run
|
||||
// CONTINUOUSLY across tile boundaries, queueing up to
|
||||
// PREFETCH_DISTANCE tiles' worth of lookahead ahead of consumption
|
||||
// instead of restarting control state once per tile.
|
||||
//
|
||||
// Tiles are always fetched in strict sequential order (0..n_tiles-1,
|
||||
// never reordered, never re-fetched) -- so no per-tile state array is
|
||||
// needed; two monotonic counters (fetch progress, consumption
|
||||
// progress) fully describe the system, exactly like the superseded
|
||||
// design, but the FETCH counter now advances continuously instead of
|
||||
// stalling at each tile boundary.
|
||||
// ============================================================
|
||||
module weight_prefetch_engine #(
|
||||
parameter DATA_WIDTH = 8,
|
||||
parameter P_IN = 8,
|
||||
parameter ADDR_WIDTH = 23,
|
||||
parameter MAX_TILES = 16,
|
||||
parameter PREFETCH_DISTANCE = 8,
|
||||
parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES),
|
||||
parameter CNTW = $clog2(MAX_TILES+1),
|
||||
parameter WORDS_PER_TILE = P_IN/2,
|
||||
parameter WIW = $clog2(WORDS_PER_TILE+1)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// ---- job control (level-held while a job is running; the
|
||||
// consumer -- nms_memory_manager.v -- resets its OWN tile_idx to 0
|
||||
// on job_start, this engine mirrors that via job_active falling/
|
||||
// rising) ----
|
||||
input wire job_active,
|
||||
input wire [ADDR_WIDTH-1:0] w_base, // byte address, word-aligned
|
||||
input wire [15:0] n_tiles,
|
||||
input wire [CNTW-1:0] consumed_count, // consumer's own tile_idx, bounds lookahead
|
||||
|
||||
// ---- fill port into nms_weight_packed.v (this slot's own private lane) ----
|
||||
output reg wgt_fill_we,
|
||||
output reg [TIW-1:0] wgt_fill_addr,
|
||||
output reg [DATA_WIDTH*P_IN-1:0] wgt_fill_data,
|
||||
|
||||
// ---- status to consumer: tiles 0..ready_count-1 are fully resident ----
|
||||
output reg [CNTW-1:0] ready_count,
|
||||
|
||||
// ---- real word-level PSRAM backend (matches
|
||||
// hardware/v1/rtl/memory_interface.v's contract exactly, same as
|
||||
// prefetch_engine.v's own real, proven usage) ----
|
||||
output reg mem_req,
|
||||
output wire mem_wr,
|
||||
output reg [ADDR_WIDTH-1:0] mem_addr,
|
||||
output wire [15:0] mem_wdata,
|
||||
output wire mem_lb_n,
|
||||
output wire mem_ub_n,
|
||||
input wire [15:0] mem_rdata,
|
||||
input wire mem_ready
|
||||
);
|
||||
|
||||
assign mem_wr = 1'b0;
|
||||
assign mem_wdata = 16'h0000;
|
||||
assign mem_lb_n = 1'b0; // fetch the whole word, both byte lanes
|
||||
assign mem_ub_n = 1'b0;
|
||||
|
||||
// fetch_tile/fetch_word: the NEXT word to be requested (or, while
|
||||
// req_outstanding, the word CURRENTLY in flight).
|
||||
reg [CNTW-1:0] fetch_tile;
|
||||
reg [WIW-1:0] fetch_word;
|
||||
reg req_outstanding;
|
||||
reg [DATA_WIDTH*P_IN-1:0] tile_buf;
|
||||
|
||||
wire [ADDR_WIDTH-1:0] w_word_base = w_base[ADDR_WIDTH-1:1];
|
||||
|
||||
// Don't fetch past n_tiles, and don't get more than
|
||||
// PREFETCH_DISTANCE tiles ahead of the consumer's own progress --
|
||||
// the real, configurable lookahead window (STEP11's own
|
||||
// requirement). consumed_count is the consumer's tile_idx
|
||||
// (registered, one cycle old at most -- fine, this only bounds a
|
||||
// SOFT bandwidth-shaping window, not a correctness-critical value:
|
||||
// over-fetching by one extra tile due to a one-cycle-stale compare
|
||||
// is harmless, the SRAM has room for the whole vector regardless).
|
||||
//
|
||||
// window_limit/the comparison below are computed in a FIXED 32-bit
|
||||
// width, wide enough to hold PREFETCH_DISTANCE undamaged for any
|
||||
// realistic parameter value -- an earlier version truncated
|
||||
// PREFETCH_DISTANCE down to CNTW bits before adding it
|
||||
// (PREFETCH_DISTANCE[CNTW-1:0]), which silently wrapped PFD=32 to 0
|
||||
// at MAX_TILES=16 (CNTW=5 bits), making window_limit==consumed_count
|
||||
// and more_to_fetch permanently false -- a full, real deadlock (see
|
||||
// errors.log ERR-0015, same TIW/CNTW-truncation bug class as
|
||||
// ERR-0014, this time on the newly-introduced PREFETCH_DISTANCE
|
||||
// parameter itself rather than a tile counter).
|
||||
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
|
||||
// mirrors the consumer's own job_start reset (nms_
|
||||
// memory_manager.v resets its tile_idx the same way)
|
||||
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
|
||||
// A word just completed. Commit it, THEN -- same
|
||||
// cycle, not next -- decide the very next request
|
||||
// (same tile's next word, or the following tile's
|
||||
// first word): this is what makes the fetch stream
|
||||
// genuinely continuous across tile boundaries, not
|
||||
// just within one tile the way prefetch_engine.v's own
|
||||
// design already was. Computed with blocking-style
|
||||
// "next state" locals so both the commit and the next
|
||||
// request land in a single, unambiguous set of NBAs.
|
||||
req_outstanding <= 1'b0;
|
||||
tile_buf[fetch_word*16 +: 16] <= 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 <= {mem_rdata, tile_buf[DATA_WIDTH*P_IN-17:0]};
|
||||
ready_count <= ready_count + 1'b1;
|
||||
fetch_tile <= fetch_tile + 1'b1;
|
||||
fetch_word <= {WIW{1'b0}};
|
||||
// start the NEXT tile's first word immediately if
|
||||
// the (post-increment) tile is still within bounds
|
||||
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_word_base + (fetch_tile + 1'b1) * WORDS_PER_TILE[CNTW-1:0];
|
||||
req_outstanding <= 1'b1;
|
||||
end
|
||||
end else begin
|
||||
fetch_word <= fetch_word + 1'b1;
|
||||
mem_req <= 1'b1;
|
||||
mem_addr <= w_word_base + fetch_tile*WORDS_PER_TILE[CNTW-1:0] + {{(ADDR_WIDTH-WIW){1'b0}}, fetch_word} + 1'b1;
|
||||
req_outstanding <= 1'b1;
|
||||
end
|
||||
end else if (!req_outstanding && more_to_fetch) begin
|
||||
// reached only when nothing has ever been requested
|
||||
// yet for this job (the very first word) -- every
|
||||
// subsequent request is issued from the branch above,
|
||||
// in the same cycle its predecessor's mem_ready
|
||||
// arrives, with zero gap, including across tile
|
||||
// boundaries.
|
||||
mem_req <= 1'b1;
|
||||
mem_addr <= w_word_base + fetch_tile*WORDS_PER_TILE[CNTW-1:0] + {{(ADDR_WIDTH-WIW){1'b0}}, fetch_word};
|
||||
req_outstanding <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -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