feat: SDRAM 8MB->64MB upgrade (AS4C32M16SB-7BIN) + N_SLOTS=8 support

Memory upgrade, at the user's own explicit request: Alliance Memory
AS4C4M16SA-6TIN (64Mbit/8MB) -> AS4C32M16SB-7BIN (512Mbit/64MB, 54-ball
TFBGA), the largest same-family SDR SDRAM Alliance Memory offers.
Real-datasheet-driven (whole AS4C4M16SA/AS4C8M16SA/AS4C16M16SA/
AS4C32M16SA family investigated): 13 row bits (was 12, one new FPGA
pin sdram_a[12]/ball F1), 10 column bits (was 8), real -7-grade AC
timing (tRCD/tRP improved to 15ns, tREFI halved to 7.8us for the
doubled row count). sdram_controller.v and sdram_model.v gained real
ROW_BITS/COL_BITS/BANK_BITS parameters (was hardcoded 12/8/2).

ADDR_WIDTH widened 23->26 bits across the live instantiation tree.
This required a real SPI protocol change (spi_host_bridge.v): a 26-bit
byte address no longer fits in 3 bytes -- every address field widened
3->4 bytes (WRITE_JOB 15->18 payload bytes, WRITE_MEM/READ_MEM header
5->6 bytes).

Found and fixed two real timing regressions via nextpnr-ecp5 P&R
(not assumed): neural_director.v's own runtime-indexed demux write
(ERR-0027, was silently synthesizing an extra MULT18X18D) and
nms_activation_fill_ctrl_v3.v's own linear N_SLOTS-wide max-scan
(ERR-0028, became dominant at N_SLOTS=8) -- both replaced with
constant-indexed/tree-based equivalents, bit-exact same behavior,
confirmed via full D-Stress N=2/4/8 regression (identical cycle
counts). N_SLOTS=4 now fully closes timing at 64MHz (8/8 seeds);
N_SLOTS=8 significantly improved but not yet fully reliable (5/8
seeds) -- honestly disclosed, not claimed complete.

Full regression re-verified: sdram_controller (461/461, 18 configs),
tb_sdram_boundary (21/21), D-Stress N=2/4/8 (bit-exact), spi_host_bridge
(18/18), board-level SPI smoke test (11/11), unified backend (40/40).

See hardware/v2/docs/MEMORY_UPGRADE_64MB_N8.md for the full
investigation, and errors.log/decisions.log (ERR-0027, ERR-0028,
DEC-0039) for the complete root-cause writeups.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
2026-09-07 00:20:53 +02:00
co-authored by Claude Sonnet 5
parent 9b5d1055b8
commit 8d83d97bde
21 changed files with 558 additions and 295 deletions
@@ -27,7 +27,7 @@
// simultaneous traffic, all bit-exact.
// ============================================================
module tb;
localparam ADDR_WIDTH = 23;
localparam ADDR_WIDTH = 26; // AS4C32M16SA memory upgrade
localparam CLK_FREQ_MHZ = 80;
localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ;
@@ -49,7 +49,7 @@ module tb;
wire sdram_cke, sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n;
wire [1:0] sdram_ba;
wire [11:0] sdram_a;
wire [12:0] sdram_a;
wire [15:0] sdram_dq;
wire [1:0] sdram_dqm;
@@ -74,7 +74,7 @@ module tb;
task automatic poke64(input [ADDR_WIDTH-1:0] byte_addr, input [63:0] val);
integer w;
reg [21:0] word_addr;
reg [24:0] word_addr;
begin
for (w = 0; w < 4; w = w + 1) begin
word_addr = (byte_addr + w*2) >> 1;
@@ -154,14 +154,14 @@ module tb;
@(posedge clk);
// ---- A: W-port sequential access (weight region) ----
wbase = 23'h010000;
wbase = 26'h010000;
for (i = 0; i < 16; i = i + 1)
poke64(wbase + i*8, {4{16'hA000 + i[15:0]}});
for (i = 0; i < 16; i = i + 1)
check64(wbase + i*8, {4{16'hA000 + i[15:0]}}, "A-Wseq");
// ---- B: AR-port read (activation region, disjoint from W) ----
arbase = 23'h200000 >> 1; // word address
arbase = 26'h200000 >> 1; // word address
poke64({arbase, 1'b0}, 64'h1111_2222_3333_4444);
check16(arbase+0, 16'h4444, "B-ARrd-w0");
check16(arbase+1, 16'h3333, "B-ARrd-w1");
@@ -171,7 +171,7 @@ module tb;
// ---- C: AR-port byte-masked write (result region) --
// pre-seed a known 128-bit block, write ONE byte, verify
// every OTHER byte in the same real SDRAM block is untouched.
arbase = 23'h300000 >> 1;
arbase = 26'h300000 >> 1;
poke64({arbase[ADDR_WIDTH-2:3], 4'b0000}, 64'h9999_8888_7777_6666);
poke64({arbase[ADDR_WIDTH-2:3], 4'b1000}, 64'h5555_4444_3333_2222);
// write only the LOW byte of word 2 (within the 8-word block) to 8'hAB