Files
FPGA-Neural/hardware/v3/rtl/sdram_arbiter_n.v
T
micheleandClaude Sonnet 5 9dead54ebf feat: real 32-bit DDR3 channel widening - functionally complete, timing NOT yet closed (EXP-0084)
Real 32-bit DDR3 widening (2x MT41J128M16JT-125:K chips ganged in
parallel, user's own MIG wizard session). Full RTL adaptation across
the shared ctrl bus (16-bit word -> 32-bit word, BURST_LEN=8 unchanged,
burst payload 128->256 bits):

- mig_native_adapter.v: app_wdf_data/app_rd_data 64->128 bits (real,
  confirmed against the regenerated MIG wrapper), beat count unchanged.
- act_tile_fetch.v: real logic change - burst now holds 4 tiles instead
  of 2 (sel_lat extended to 2 registered bits, 4-way case mux instead
  of 2-way ternary, same request-time-registered-select discipline as
  EXP-0081). Not a further bytes/MAC reduction, just what's needed to
  keep 100% packing utilization at the larger burst.
- host_mem_bridge.v: real addressing redesign - host-facing 16-bit-word
  contract kept unchanged (ESP32 firmware unaffected), internally
  translated onto the new 32-bit-native ctrl bus.
- sdram_arbiter_n.v, layer_prefetch_ctrl.v, packed_slot.v,
  ddr_prefetch_mgr.v, n2_system_ddr3_top.v: mechanical width bump plus
  doubled ddr3_dq/dqs/dm pins and the real differential sys_clk/clk_ref
  top-level ports the regenerated MIG now requires.

New burst_mem_model32.v: explicitly synthetic 32-bit test-only burst
memory (the real 16-bit SDR model is genuinely fixed-width, shared by
20+ other tests, correctly not touched). Found and fixed a real
address-aliasing bug in it during bring-up (MEM_ADDR_BITS=16 silently
wrapped a real 0x10000 test address to 0).

Real verification: all isolated testbenches re-verified (10/10, 33/33,
32/32, 7/7, 9/9 PASS), plus real xsim against the real 2-chip DDR3
model (tb_mig_native_adapter.v 12/12 PASS, tb_n2_system_ddr3.v 8/8
PASS, both chips visibly returning different real data).

Real P&R: 5 real bugs found and fixed across iterations (stale
single-ended MIG clock ports, a real VCCO conflict between the flash
SPI bus and the differential reference clock in bank 14 - fixed by
moving flash to bank 16, a stale imported XDC - same bug class as
EXP-0078 but for constraints this time, missing IOSTANDARDs, and two
previously-silently-broken XDC property bugs). Route completes 100%,
but real timing does NOT close: WNS -0.618ns, 213 failing endpoints.

Honest root cause: the violation is inside neural_processor_packed.v's
own packed-MAC accumulation tree, unchanged since EXP-0059 - it has
real margin at the old 155.039MHz ui_clk but not at the new 172.414MHz
the paired clock-period change produced. This is NOT caused by the
32-bit width change itself. Width alone, even at the old clock, already
delivers the full intended 2x bandwidth gain (1.24 -> ~2.48 GB/s) -
width and clock rate are separable levers. Current trustworthy timing
signoff remains EXP-0083 (16-bit, +0.073ns) until the clock period is
reverted toward 3225ps (keeping Data Width=32) in one more real,
user-gated MIG wizard session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-20 16:28:44 +02:00

146 lines
5.5 KiB
Verilog

`timescale 1ns/1ps
// ============================================================
// V3 -- generalized N-way arbiter for a shared memory controller
// port (SDRAM placeholder today, DDR3/mig_native_adapter.v tomorrow
// -- this arbiter sits on the req/wr/addr/wdata/wmask->rdata/ready/
// busy side, identical on either backend).
//
// Generalizes sdram_slot_arbiter2.v (EXP-0066) to NUM_REQ requesters
// instead of a hardcoded 2, for (a) scaling the compute system past
// N=2 packed slots, and (b) adding a HOST raw-memory-access requester
// (the still-missing SPI WRITE_MEM/READ_MEM equivalent for V3,
// flagged when re-auditing spi_host_bridge.v's own opcode set against
// this project's actual V3 architecture).
//
// Preserves EXACTLY the combinational-first-grant mechanism EXP-0066
// found necessary the hard way: layer_prefetch_ctrl.v (and any other
// requester built the same way, e.g. a future host-access engine)
// issues its own ctrl_req as a genuine ONE-SHOT pulse with no retry,
// so a requester must see ITS OWN grant asserted the SAME cycle its
// own `active` first goes high, or that first request is silently
// lost forever (a real, previously-hit bug, not a hypothetical one --
// see EXP-0066's own writeup). `locked`/`grant_reg` below only LATCH
// a decision already available combinationally, purely to keep it
// sticky once made (no mid-fetch grant switching), never to delay
// the first grant.
//
// Priority: lowest-indexed active requester wins on first grant (same
// policy as sdram_slot_arbiter2.v -- a documented, simple, first-
// come-by-index scheme, not fairness-optimized; matches this
// project's own "correctness first" precedent of choosing the
// simplest policy that is provably correct before optimizing).
// ============================================================
module sdram_arbiter_n #(
parameter NUM_REQ = 3,
parameter ADDR_WIDTH = 25,
parameter BURST_LEN = 8
)(
input wire clk,
input wire rst,
input wire [NUM_REQ-1:0] req_active,
output wire [NUM_REQ-1:0] req_grant,
input wire [NUM_REQ-1:0] req_req,
input wire [NUM_REQ-1:0] req_wr,
input wire [NUM_REQ*ADDR_WIDTH-1:0] req_addr,
input wire [NUM_REQ*32*BURST_LEN-1:0] req_wdata,
input wire [NUM_REQ*4*BURST_LEN-1:0] req_wmask,
output wire [NUM_REQ*32*BURST_LEN-1:0] req_rdata,
output wire [NUM_REQ-1:0] req_ready,
output wire [NUM_REQ-1:0] req_busy,
output wire ctrl_req,
output wire ctrl_wr,
output wire [ADDR_WIDTH-1:0] ctrl_addr,
output wire [32*BURST_LEN-1:0] ctrl_wdata,
output wire [4*BURST_LEN-1:0] ctrl_wmask,
input wire [32*BURST_LEN-1:0] ctrl_rdata,
input wire ctrl_ready,
input wire ctrl_busy
);
localparam SELW = (NUM_REQ <= 1) ? 1 : $clog2(NUM_REQ);
wire any_active = |req_active;
// combinational lowest-index-active picker -- available with zero
// cycle latency relative to req_active first asserting (see header).
reg [SELW-1:0] pick_idx;
integer pi;
always @(*) begin
pick_idx = {SELW{1'b0}};
for (pi = NUM_REQ-1; pi >= 0; pi = pi - 1)
if (req_active[pi]) pick_idx = pi[SELW-1:0];
end
reg locked;
reg [SELW-1:0] grant_idx_r;
wire [SELW-1:0] grant_idx_now = locked ? grant_idx_r : pick_idx;
always @(posedge clk) begin
if (rst) begin
locked <= 1'b0;
grant_idx_r <= {SELW{1'b0}};
end else begin
if (!locked) begin
if (any_active) begin
locked <= 1'b1;
grant_idx_r <= grant_idx_now;
end
end else begin
if (!req_active[grant_idx_r]) locked <= 1'b0;
end
end
end
wire [NUM_REQ-1:0] sel;
genvar gs;
generate
for (gs = 0; gs < NUM_REQ; gs = gs + 1) begin : GEN_SEL
assign sel[gs] = any_active && (grant_idx_now == gs[SELW-1:0]);
end
endgenerate
assign req_grant = sel;
// mux request-side signals from the granted requester -> shared ctrl
reg m_req, m_wr;
reg [ADDR_WIDTH-1:0] m_addr;
reg [32*BURST_LEN-1:0] m_wdata;
reg [4*BURST_LEN-1:0] m_wmask;
integer mi;
always @(*) begin
m_req = 1'b0;
m_wr = 1'b0;
m_addr = {ADDR_WIDTH{1'b0}};
m_wdata = {(32*BURST_LEN){1'b0}};
m_wmask = {(4*BURST_LEN){1'b0}};
for (mi = 0; mi < NUM_REQ; mi = mi + 1) begin
if (sel[mi]) begin
m_req = req_req[mi];
m_wr = req_wr[mi];
m_addr = req_addr[mi*ADDR_WIDTH +: ADDR_WIDTH];
m_wdata = req_wdata[mi*32*BURST_LEN +: 32*BURST_LEN];
m_wmask = req_wmask[mi*4*BURST_LEN +: 4*BURST_LEN];
end
end
end
assign ctrl_req = m_req;
assign ctrl_wr = m_wr;
assign ctrl_addr = m_addr;
assign ctrl_wdata = m_wdata;
assign ctrl_wmask = m_wmask;
// demux response back to whichever requester is currently granted
genvar gd;
generate
for (gd = 0; gd < NUM_REQ; gd = gd + 1) begin : GEN_DEMUX
assign req_rdata[gd*32*BURST_LEN +: 32*BURST_LEN] = ctrl_rdata;
assign req_ready[gd] = sel[gd] ? ctrl_ready : 1'b0;
assign req_busy[gd] = sel[gd] ? ctrl_busy : 1'b1;
end
endgenerate
endmodule