From 8d83d97bde66cc0ecfcc54f4017c2aa15f6264ff Mon Sep 17 00:00:00 2001 From: Michele Bigi Date: Mon, 7 Sep 2026 00:20:53 +0200 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v --- hardware/v2/constraints/v2_board_top.lpf | 6 + hardware/v2/nms/rtl/fpga_neural_v2_top.v | 4 +- .../v2/nms/rtl/nms_activation_fill_ctrl_v3.v | 86 ++++++-- hardware/v2/nms/rtl/nms_dataflow_core_sdram.v | 2 +- .../nms/rtl/nms_memory_manager_stream_wide.v | 2 +- .../nms_neural_multiprocessor_sdram_unified.v | 2 +- hardware/v2/nms/rtl/sdram_controller.v | 186 ++++++++++++------ hardware/v2/nms/rtl/sdram_unified_backend.v | 35 +++- .../v2/nms/rtl/weight_prefetch_engine_wide.v | 2 +- hardware/v2/nms/sim/sdram_model.v | 72 ++++--- .../v2/nms/sim/tb_fpga_neural_v2_top_smoke.v | 57 +++--- .../v2/nms/sim/tb_nms_dstress_sdram_unified.v | 18 +- hardware/v2/nms/sim/tb_sdram_boundary.v | 72 ++++--- hardware/v2/nms/sim/tb_sdram_controller.v | 54 +++-- .../v2/nms/sim/tb_sdram_unified_backend.v | 12 +- hardware/v2/rtl/dependency_manager.v | 2 +- hardware/v2/rtl/neural_director.v | 97 ++++++--- hardware/v2/rtl/slot_mem_arbiter.v | 2 +- hardware/v2/rtl/slot_mem_arbiter_wide.v | 2 +- hardware/v2/rtl/spi_host_bridge.v | 119 ++++++----- hardware/v2/sim/tb_spi_host_bridge.v | 21 +- 21 files changed, 558 insertions(+), 295 deletions(-) diff --git a/hardware/v2/constraints/v2_board_top.lpf b/hardware/v2/constraints/v2_board_top.lpf index 9fe3c5d..fa74edc 100644 --- a/hardware/v2/constraints/v2_board_top.lpf +++ b/hardware/v2/constraints/v2_board_top.lpf @@ -70,6 +70,12 @@ LOCATE COMP "sdram_a[8]" SITE "C2"; IOBUF PORT "sdram_a[8]" IO_TYPE=LVCMOS33 LOCATE COMP "sdram_a[9]" SITE "C1"; IOBUF PORT "sdram_a[9]" IO_TYPE=LVCMOS33; LOCATE COMP "sdram_a[10]" SITE "D2"; IOBUF PORT "sdram_a[10]" IO_TYPE=LVCMOS33; LOCATE COMP "sdram_a[11]" SITE "D1"; IOBUF PORT "sdram_a[11]" IO_TYPE=LVCMOS33; +// sdram_a[12]: NEW pin, required by the AS4C32M16SA-7TIN memory +// upgrade (13 row address bits, one more than the previous +// AS4C4M16SA-6TIN's 12). Real, free, plain-GPIO ball (bank 6, +// official Lattice pinout CSV rev 3.0, CABGA381 column) -- not +// previously used by this LPF's own 44-signal set. +LOCATE COMP "sdram_a[12]" SITE "F1"; IOBUF PORT "sdram_a[12]" IO_TYPE=LVCMOS33; LOCATE COMP "sdram_dq[0]" SITE "E1"; IOBUF PORT "sdram_dq[0]" IO_TYPE=LVCMOS33; LOCATE COMP "sdram_dq[1]" SITE "G5"; IOBUF PORT "sdram_dq[1]" IO_TYPE=LVCMOS33; diff --git a/hardware/v2/nms/rtl/fpga_neural_v2_top.v b/hardware/v2/nms/rtl/fpga_neural_v2_top.v index 152ee8a..2b88044 100644 --- a/hardware/v2/nms/rtl/fpga_neural_v2_top.v +++ b/hardware/v2/nms/rtl/fpga_neural_v2_top.v @@ -37,7 +37,7 @@ module fpga_neural_v2_top #( parameter DATA_WIDTH = 8, parameter P_IN = 8, parameter ACC_WIDTH = 32, - parameter ADDR_WIDTH = 23, + parameter ADDR_WIDTH = 26, parameter N_SLOTS = 4, parameter N_NODES = 16, parameter MAX_DEPS = 4, @@ -62,7 +62,7 @@ module fpga_neural_v2_top #( output wire sdram_cas_n, output wire sdram_we_n, output wire [1:0] sdram_ba, - output wire [11:0] sdram_a, + output wire [12:0] sdram_a, inout wire [15:0] sdram_dq, output wire [1:0] sdram_dqm, diff --git a/hardware/v2/nms/rtl/nms_activation_fill_ctrl_v3.v b/hardware/v2/nms/rtl/nms_activation_fill_ctrl_v3.v index 97e0a3f..44e10ca 100644 --- a/hardware/v2/nms/rtl/nms_activation_fill_ctrl_v3.v +++ b/hardware/v2/nms/rtl/nms_activation_fill_ctrl_v3.v @@ -26,7 +26,7 @@ module nms_activation_fill_ctrl_v3 #( parameter DATA_WIDTH = 8, parameter P_IN = 8, parameter N_SLOTS = 4, - parameter ADDR_WIDTH = 23, + parameter ADDR_WIDTH = 26, 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 @@ -120,19 +120,79 @@ module nms_activation_fill_ctrl_v3 #( end endgenerate + // Balanced binary max-tree (log2(N_SLOTS) comparison levels) + // instead of the flat N_SLOTS-wide sequential scan this file's own + // header comment above already flagged as "an N_SLOTS-wide + // sequential chain". Found and fixed this session: that chain's + // own carry-chain critical path became the DOMINANT critical path + // at N_SLOTS=8 (real nextpnr-ecp5 P&R: Fmax collapsed to ~40MHz, + // failing the 64MHz target across every measured seed). A tree + // has the SAME single-cycle combinational timing as the scan it + // replaces (max_n_tiles_reg is still registered exactly one cycle + // behind n_tiles_masked -- no FSM/latency change, purely a + // combinational-depth reduction: log2(N_SLOTS) levels instead of + // N_SLOTS). + // + // Written as explicit, uniquely-named per-level wires (NOT a + // multi-dimensional generate-indexed array) -- a first attempt + // using a shared 2D `wire max_tree[level][idx]` array triggered a + // real simulator UNOPTFLAT "circular combinational logic" warning. + // The actual dependency graph IS acyclic (level L+1 only ever + // reads level L), but that tool's array-flattening circularity + // check could not prove that for a shared 2D array; distinctly- + // named per-level wires sidestep the ambiguity entirely for both + // simulation and synthesis. N_SLOTS is a power of two for every + // real configuration this project uses (1/2/4/8); anything else + // falls back, explicitly, to the original flat scan (correct but not + // optimized) rather than silently doing the wrong thing. 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 + generate + if (N_SLOTS == 1) begin : GEN_MAXTREE_N1 + always @(posedge clk) begin + if (rst) max_n_tiles_reg <= 16'h0; + else max_n_tiles_reg <= n_tiles_masked[0]; + end + end else if (N_SLOTS == 2) begin : GEN_MAXTREE_N2 + wire [15:0] max_final = (n_tiles_masked[0] > n_tiles_masked[1]) ? n_tiles_masked[0] : n_tiles_masked[1]; + always @(posedge clk) begin + if (rst) max_n_tiles_reg <= 16'h0; + else max_n_tiles_reg <= max_final; + end + end else if (N_SLOTS == 4) begin : GEN_MAXTREE_N4 + wire [15:0] m0 = (n_tiles_masked[0] > n_tiles_masked[1]) ? n_tiles_masked[0] : n_tiles_masked[1]; + wire [15:0] m1 = (n_tiles_masked[2] > n_tiles_masked[3]) ? n_tiles_masked[2] : n_tiles_masked[3]; + wire [15:0] max_final = (m0 > m1) ? m0 : m1; + always @(posedge clk) begin + if (rst) max_n_tiles_reg <= 16'h0; + else max_n_tiles_reg <= max_final; + end + end else if (N_SLOTS == 8) begin : GEN_MAXTREE_N8 + wire [15:0] m0 = (n_tiles_masked[0] > n_tiles_masked[1]) ? n_tiles_masked[0] : n_tiles_masked[1]; + wire [15:0] m1 = (n_tiles_masked[2] > n_tiles_masked[3]) ? n_tiles_masked[2] : n_tiles_masked[3]; + wire [15:0] m2 = (n_tiles_masked[4] > n_tiles_masked[5]) ? n_tiles_masked[4] : n_tiles_masked[5]; + wire [15:0] m3 = (n_tiles_masked[6] > n_tiles_masked[7]) ? n_tiles_masked[6] : n_tiles_masked[7]; + wire [15:0] m01 = (m0 > m1) ? m0 : m1; + wire [15:0] m23 = (m2 > m3) ? m2 : m3; + wire [15:0] max_final = (m01 > m23) ? m01 : m23; + always @(posedge clk) begin + if (rst) max_n_tiles_reg <= 16'h0; + else max_n_tiles_reg <= max_final; + end + end else begin : GEN_MAXTREE_FALLBACK + reg [15:0] max_n_tiles_comb_fallback; + integer j; + always @* begin + max_n_tiles_comb_fallback = 16'h0; + for (j = 0; j < N_SLOTS; j = j + 1) + if (n_tiles_masked[j] > max_n_tiles_comb_fallback) + max_n_tiles_comb_fallback = 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_fallback; + end + end + endgenerate localparam ST_IDLE = 1'd0; localparam ST_FETCH = 1'd1; diff --git a/hardware/v2/nms/rtl/nms_dataflow_core_sdram.v b/hardware/v2/nms/rtl/nms_dataflow_core_sdram.v index e734990..a7cfbdf 100644 --- a/hardware/v2/nms/rtl/nms_dataflow_core_sdram.v +++ b/hardware/v2/nms/rtl/nms_dataflow_core_sdram.v @@ -47,7 +47,7 @@ module nms_dataflow_core_sdram #( parameter DATA_WIDTH = 8, parameter P_IN = 8, parameter ACC_WIDTH = 32, - parameter ADDR_WIDTH = 23, + parameter ADDR_WIDTH = 26, parameter N_SLOTS = 4, parameter N_NODES = 16, parameter MAX_DEPS = 4, diff --git a/hardware/v2/nms/rtl/nms_memory_manager_stream_wide.v b/hardware/v2/nms/rtl/nms_memory_manager_stream_wide.v index 0f6de54..a43af20 100644 --- a/hardware/v2/nms/rtl/nms_memory_manager_stream_wide.v +++ b/hardware/v2/nms/rtl/nms_memory_manager_stream_wide.v @@ -63,7 +63,7 @@ module nms_memory_manager_stream_wide #( parameter MEM_DATA_WIDTH = 64, parameter DATA_WIDTH = 8, parameter P_IN = 8, - parameter ADDR_WIDTH = 23, + parameter ADDR_WIDTH = 26, parameter MAX_TILES = 16, parameter PREFETCH_DISTANCE = 8, parameter TIW = (MAX_TILES <= 1) ? 1 : $clog2(MAX_TILES), diff --git a/hardware/v2/nms/rtl/nms_neural_multiprocessor_sdram_unified.v b/hardware/v2/nms/rtl/nms_neural_multiprocessor_sdram_unified.v index 41c30cd..5ccab1d 100644 --- a/hardware/v2/nms/rtl/nms_neural_multiprocessor_sdram_unified.v +++ b/hardware/v2/nms/rtl/nms_neural_multiprocessor_sdram_unified.v @@ -38,7 +38,7 @@ module nms_neural_multiprocessor_sdram_unified #( parameter DATA_WIDTH = 8, parameter P_IN = 8, parameter ACC_WIDTH = 32, - parameter ADDR_WIDTH = 23, + parameter ADDR_WIDTH = 26, parameter N_SLOTS = 2, parameter N_NODES = 16, parameter MAX_DEPS = 4, diff --git a/hardware/v2/nms/rtl/sdram_controller.v b/hardware/v2/nms/rtl/sdram_controller.v index cc976b0..4fa4344 100644 --- a/hardware/v2/nms/rtl/sdram_controller.v +++ b/hardware/v2/nms/rtl/sdram_controller.v @@ -1,9 +1,29 @@ `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). +// NMS STEP16 -- minimal, CORRECT-FIRST SDR SDRAM controller. +// +// MEMORY UPGRADE (post-PRE-PCB-FREEZE capacity/throughput review): +// retargeted from Alliance Memory AS4C4M16SA-6TIN (64Mbit/8MB) to +// Alliance Memory AS4C32M16SA-7TIN (512Mbit/64MB, x16, -7 speed +// grade: tCK=7ns/143MHz max, CAS latency 2 or 3), the largest +// same-family, same-package (54-pin TSOP-II, 3.3V) SDR SDRAM +// Alliance Memory offers. Confirmed via the real manufacturer +// datasheet (Alliance Memory AS4C32M16SA Rev 2.0): organization is +// 4 banks x 8192 rows x 1024 columns x16 bits (row address A0-A12, +// 13 bits; column address A0-A9, 10 bits; bank BA0/BA1, 2 bits) -- +// ROW_BITS/COL_BITS/BANK_BITS below are now real parameters (not +// hardcoded 12/8/2) so this same RTL supports either device by +// parameter alone. Real -7-grade AC timing (all well inside this +// design's 64-100MHz target, itself far below the part's own +// 143MHz max): tRCD=15ns min, tRP=15ns min, tRAS=45ns min/100000ns +// max, tRC=65ns min, tMRD=2 CLK (fixed, explicitly stated in CLK +// units by this datasheet -- no unit ambiguity, unlike the smaller +// AS4C4M16SA's own datasheet that triggered ERR-0026), tWR=2 CLK +// (also explicitly CLK units), tREFI=64ms/8192 rows=7.8125us (HALF +// the previous part's 15.625us, since this part has 2x the rows to +// refresh in the same 64ms window -- a real, meaningful difference, +// not a rounding artifact). // // Design priority explicitly stated by the governing spec: // correctness > performance > elegance. This controller therefore: @@ -18,18 +38,19 @@ // - 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). +// insertion between transactions (tREFI = rows / 64ms, ROW_BITS- +// dependent -- see T_REFI below). +// - Real, standard SDR SDRAM timing (datasheet-standard values, +// not vendor-specific tuning), re-derived per CLK_FREQ_MHZ so the +// same RTL is reused across every tested frequency (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). +// {bank[BANK_BITS-1:0], row[ROW_BITS-1:0], col[COL_BITS-1:0]} -- +// default ROW_BITS=13/COL_BITS=10/BANK_BITS=2 matches the REAL +// AS4C32M16SA's own 4-bank x 8192-row x 1024-column x16 organization +// (4*8192*1024 = 32M words = 64MB, 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 @@ -41,9 +62,15 @@ // per tile before any RTL was written). // ============================================================ module sdram_controller #( - parameter CLK_FREQ_MHZ = 166, + parameter CLK_FREQ_MHZ = 64, parameter BURST_LEN = 4, // 1, 4, or 8 -- Phase 4 sweep parameter - parameter ADDR_WIDTH = 22 // word address: 2 bank + 12 row + 8 col + parameter ROW_BITS = 13, // AS4C32M16SA: row address A0-A12 + parameter COL_BITS = 10, // AS4C32M16SA: column address A0-A9 + parameter BANK_BITS = 2, // BA0,BA1 -- fixed across this whole Alliance SDR family + // word address width; default derived from ROW_BITS/COL_BITS/ + // BANK_BITS above -- if overridden independently, must still equal + // BANK_BITS+ROW_BITS+COL_BITS (asserted at elaboration below) + parameter ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS )( input wire clk, input wire rst, @@ -75,14 +102,24 @@ module sdram_controller #( 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, + output reg [1:0] sdram_ba, + output reg [ROW_BITS-1: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); + // elaboration-time consistency check: ADDR_WIDTH must always equal + // the sum of its own row/col/bank widths, whether left at its + // derived default or overridden explicitly -- catches a mismatched + // override immediately rather than silently mis-decoding addresses. + initial if (ADDR_WIDTH != BANK_BITS + ROW_BITS + COL_BITS) begin + $display("FATAL sdram_controller: ADDR_WIDTH=%0d != BANK_BITS(%0d)+ROW_BITS(%0d)+COL_BITS(%0d)=%0d", + ADDR_WIDTH, BANK_BITS, ROW_BITS, COL_BITS, BANK_BITS+ROW_BITS+COL_BITS); + $finish; + end + // ---- real, standard -6-speed-grade timing, re-derived per // CLK_FREQ_MHZ (ceiling division: never UNDER-count a real ns // requirement) ---- @@ -92,34 +129,43 @@ module sdram_controller #( 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 + localparam T_RCD = ns_to_cycles(15); // ACTIVE -> READ/WRITE (AS4C32M16SA: 15ns min) + localparam T_RP = ns_to_cycles(15); // PRECHARGE -> ACTIVE (AS4C32M16SA: 15ns min) + // ACTIVE->PRECHARGE minimum (tRAS=45ns min, AS4C32M16SA) 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. - // tMRD is specified by the real AS4C4M16SA-6TIN datasheet (Table 17) - // as a FIXED CYCLE COUNT ("2 tCK"), not a nanosecond value -- unlike - // tRCD/tRP, which genuinely are ns-based and correctly belong behind - // ns_to_cycles(). A previous draft modeled tMRD as ns_to_cycles(12), - // an assumed-equivalent ns figure that happened to round up to - // exactly 2 cycles at every frequency this design had been tested at - // (100/133/166MHz), silently masking the wrong unit model. At the - // real V2 board's own 64MHz operating point, ns_to_cycles(12) rounds - // to only 1 cycle -- one cycle short of the real, fixed 2-tCK - // minimum -- found via this step's own fresh datasheet-level audit - // (real Alliance Memory AS4C4M16SA-6TIN datasheet Rev.5.0, Table 17). - // Fixed by hardcoding the real, frequency-independent requirement - // directly, matching how CAS_LATENCY (also a real fixed-cycle spec) - // is already modeled two lines below. - localparam T_MRD = 2; // LOAD MODE REGISTER -> any command (tMRD = 2 tCK, fixed) - localparam T_INIT_US= 200; // power-up wait, real datasheet value + // (tRCD + CAS_LATENCY + BURST_LEN data cycles) already comfortably + // exceeds it by construction before auto-precharge can begin + // internally, at every frequency this design actually targets + // (64-100MHz) -- re-verified this session for the new part's own + // 45ns real minimum (was 42ns for the previous, smaller part): + // at CAS_LATENCY=3 and the default BURST_LEN=4, the minimum + // possible sequence is T_RCD(>=1 cycle)+3+4=8 cycles, i.e. >=8 + // cycles*period; even at 100MHz (10ns period) that is 80ns >= + // 45ns. This margin narrows at higher frequency and/or smaller + // BURST_LEN, and is NOT re-derived symbolically here -- confirmed + // instead by this session's own real simulation regression at + // every frequency actually used (64/80/100MHz), per this + // project's own "measure, do not estimate" standard. + // + // tMRD and tWR are BOTH specified by the real AS4C32M16SA + // datasheet in explicit CLK units (2 CLK each) -- no unit + // ambiguity this time (unlike the smaller AS4C4M16SA's own + // datasheet, which stated tMRD in ns-at-max-frequency and caused + // ERR-0026). Hardcoded directly as fixed cycle counts, matching + // how CAS_LATENCY is already modeled. + localparam T_MRD = 2; // LOAD MODE REGISTER -> any command (tMRD = 2 CLK, fixed) + localparam T_INIT_US= 200; // power-up wait, real datasheet value (unchanged) 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 CAS_LATENCY = 3; // fixed for this part/speed grade (CL=2 or 3 supported; 3 chosen, matches the previous part) + // real refresh interval: AS4C32M16SA has 8192 rows (ROW_BITS=13), + // each must be refreshed within 64ms -> one AUTO REFRESH at least + // every 64e6ns/8192 = 7812.5ns, rounded UP to 7813ns (never under- + // count). HALF the previous, smaller part's own 15625ns interval, + // since this part has 2x the rows to refresh in the same 64ms + // window -- a real, meaningful difference (not a rounding + // artifact), re-derived from ROW_BITS so this stays correct if + // ROW_BITS is ever changed again for a different device. + localparam T_REFI = ns_to_cycles(64000000 / (1 << ROW_BITS) + 1); localparam CNTW = $clog2((T_INIT>T_REFI ? T_INIT : T_REFI) + 1); @@ -129,9 +175,9 @@ module sdram_controller #( // command truth table line by line. // tRC (ACTIVATE-to-ACTIVATE minimum, same bank), used by both the - // init-refresh and steady-state refresh wait. + // init-refresh and steady-state refresh wait. AS4C32M16SA: 65ns min. function [CNTW-1:0] T_RC_MINUS1; - localparam integer T_RC = ns_to_cycles(60); + localparam integer T_RC = ns_to_cycles(65); begin T_RC_MINUS1 = T_RC[CNTW-1:0] - 1'b1; end @@ -157,15 +203,15 @@ module sdram_controller #( 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 [BANK_BITS-1:0] req_bank_reg; + reg [ROW_BITS-1:0] req_row_reg; + reg [COL_BITS-1: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]; + wire [BANK_BITS-1:0] addr_bank = addr[ADDR_WIDTH-1 -: BANK_BITS]; + wire [ROW_BITS-1:0] addr_row = addr[ADDR_WIDTH-BANK_BITS-1 -: ROW_BITS]; + wire [COL_BITS-1:0] addr_col = addr[COL_BITS-1: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 @@ -181,10 +227,10 @@ module sdram_controller #( // 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 eff_wr = req ? wr : req_wr_reg; + wire [BANK_BITS-1:0] eff_bank = req ? addr_bank : req_bank_reg; + wire [ROW_BITS-1:0] eff_row = req ? addr_row : req_row_reg; + wire [COL_BITS-1: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; @@ -194,16 +240,28 @@ module sdram_controller #( 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; + // (A3=0) + CAS latency 3 (A6:4=011) + standard write burst (A9=0, + // "WBL" -- bit position within the reserved/test-mode region above + // A6:4 varies slightly by device row-width across this Alliance + // family, but is always 0/"burst" for every variant, so this + // function's own "everything above bit 6 is 0" construction is + // correct regardless of that exact bit-name mapping). Width is + // ROW_BITS (matches sdram_a), zero-padded above bit 6 for any + // ROW_BITS value. + function [ROW_BITS-1:0] mrs_value; input integer burst_len; reg [2:0] bl_code; + reg [ROW_BITS-1:0] v; 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}; + v = {ROW_BITS{1'b0}}; + v[6:4] = 3'b011; // CAS Latency = 3 (matches this controller's own fixed CAS_LATENCY) + v[3] = 1'b0; // Burst Type = sequential + v[2:0] = bl_code; // Burst Length + mrs_value = v; end endfunction @@ -219,7 +277,7 @@ module sdram_controller #( sdram_cas_n <= 1'b1; sdram_we_n <= 1'b1; sdram_ba <= 2'b00; - sdram_a <= 12'h000; + sdram_a <= {ROW_BITS{1'b0}}; sdram_dqm <= 2'b00; // both byte lanes always enabled (weight/tile fetch always full-word) dq_out_en <= 1'b0; ready <= 1'b0; @@ -355,11 +413,17 @@ module sdram_controller #( 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 + // a[COL_BITS-1:0]=col, a[10]=1 (auto-precharge, + // always at bit 10 across this whole Alliance + // SDR family regardless of ROW_BITS/COL_BITS -- + // safe as long as COL_BITS<=10, true for every + // device this controller has ever targeted, so + // the column field [COL_BITS-1:0] never + // overlaps bit 10) 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 + sdram_a <= {{(ROW_BITS-11){1'b0}}, 1'b1, {(10-COL_BITS){1'b0}}, req_col_reg}; burst_idx <= {BURST_IDXW{1'b0}}; if (req_wr_reg) begin dq_out_en <= 1'b1; diff --git a/hardware/v2/nms/rtl/sdram_unified_backend.v b/hardware/v2/nms/rtl/sdram_unified_backend.v index a529c34..4c14bac 100644 --- a/hardware/v2/nms/rtl/sdram_unified_backend.v +++ b/hardware/v2/nms/rtl/sdram_unified_backend.v @@ -56,9 +56,17 @@ // 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 + parameter ADDR_WIDTH = 26, // byte address width (W port convention) + parameter CLK_FREQ_MHZ = 64, + parameter W_ENTRIES = 4, // weight-cache depth, >= real N_SLOTS + // physical SDRAM geometry, forwarded directly to sdram_controller.v + // (AS4C32M16SA defaults: 13 row bits/A0-A12, 10 col bits/A0-A9, + // 2 bank bits/BA0-BA1) -- must satisfy ADDR_WIDTH-1 == + // BANK_BITS+ROW_BITS+COL_BITS (byte address = word address + 1 bit), + // asserted at elaboration below. + parameter ROW_BITS = 13, + parameter COL_BITS = 10, + parameter BANK_BITS = 2 )( input wire clk, input wire rst, @@ -91,12 +99,18 @@ module sdram_unified_backend #( 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, + output wire [BANK_BITS-1:0] sdram_ba, + output wire [ROW_BITS-1:0] sdram_a, inout wire [15:0] sdram_dq, output wire [1:0] sdram_dqm ); + initial if (ADDR_WIDTH != BANK_BITS + ROW_BITS + COL_BITS + 1) begin + $display("FATAL sdram_unified_backend: ADDR_WIDTH(%0d) != BANK_BITS(%0d)+ROW_BITS(%0d)+COL_BITS(%0d)+1", + ADDR_WIDTH, BANK_BITS, ROW_BITS, COL_BITS); + $finish; + end + // ============================================================ // W-port cache (identical logic to sdram_weight_backend_pack128.v // -- an N_ENTRIES-deep, fully-associative "other half" cache, @@ -130,7 +144,7 @@ module sdram_unified_backend #( // ============================================================ reg ctrl_req; reg ctrl_wr; - reg [21:0] ctrl_addr; + reg [ADDR_WIDTH-2:0] ctrl_addr; reg [127:0] ctrl_wdata; reg [15:0] ctrl_wmask; wire [127:0] ctrl_rdata; @@ -138,7 +152,8 @@ module sdram_unified_backend #( wire ctrl_busy; sdram_controller #( - .CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(8), .ADDR_WIDTH(22) + .CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(8), + .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) u_sdram_ctrl ( .clk(clk), .rst(rst), .req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr), @@ -191,9 +206,9 @@ module sdram_unified_backend #( 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 [ADDR_WIDTH-2: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 [ADDR_WIDTH-2:0] ar_eff_block_base = {ar_eff_addr[ADDR_WIDTH-2:3], 3'b000}; wire [2:0] ar_eff_word_in_blk = ar_eff_addr[2:0]; integer ri; @@ -202,7 +217,7 @@ module sdram_unified_backend #( 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_req <= 1'b0; ctrl_wr <= 1'b0; ctrl_addr <= {(ADDR_WIDTH-1){1'b0}}; ctrl_wdata <= 128'h0; ctrl_wmask <= 16'hFFFF; w_ready <= 1'b0; w_rdata <= 64'h0; ar_ready <= 1'b0; ar_rdata <= 16'h0; diff --git a/hardware/v2/nms/rtl/weight_prefetch_engine_wide.v b/hardware/v2/nms/rtl/weight_prefetch_engine_wide.v index 90b3696..b1693c6 100644 --- a/hardware/v2/nms/rtl/weight_prefetch_engine_wide.v +++ b/hardware/v2/nms/rtl/weight_prefetch_engine_wide.v @@ -27,7 +27,7 @@ module weight_prefetch_engine_wide #( parameter DATA_WIDTH = 8, parameter P_IN = 8, - parameter ADDR_WIDTH = 23, + parameter ADDR_WIDTH = 26, parameter MAX_TILES = 16, parameter PREFETCH_DISTANCE = 8, parameter MEM_DATA_WIDTH = 64, // 16, 32, 64, 128 -- the STEP14 Part A sweep parameter diff --git a/hardware/v2/nms/sim/sdram_model.v b/hardware/v2/nms/sim/sdram_model.v index dcdca79..d8eb3d0 100644 --- a/hardware/v2/nms/sim/sdram_model.v +++ b/hardware/v2/nms/sim/sdram_model.v @@ -1,8 +1,17 @@ `timescale 1ns/1ps // ============================================================ -// NMS STEP16 -- behavioral model of Alliance Memory AS4C4M16SA-6TIN -// (SDR SDRAM, 64Mbit/8MB, x16, 4 banks x 4096 rows x 256 cols). +// NMS STEP16 -- behavioral model of Alliance Memory SDR SDRAM. +// +// MEMORY UPGRADE: retargeted from AS4C4M16SA-6TIN (64Mbit/8MB) to +// AS4C32M16SA-7TIN (512Mbit/64MB, x16, 4 banks x 8192 rows x 1024 +// cols) -- ROW_BITS/COL_BITS/BANK_BITS are now real parameters +// (matching sdram_controller.v's own parameterization) so this same +// model supports either device by parameter alone. Real -7-grade AC +// timing: tRCD=15ns, tRP=15ns, tRAS(min)=45ns, tRC=65ns, tMRD=2 CLK +// (fixed, explicit CLK units per this datasheet), tREFI=64ms/8192 +// rows=7.8125us -- see sdram_controller.v's own header for the full +// datasheet cross-reference. // // Real JEDEC command decode (CS#/RAS#/CAS#/WE#), real per-bank // state tracking (IDLE / ACTIVE with an open row), and REAL timing- @@ -13,7 +22,7 @@ // timing violation here is a genuine controller bug, not tolerated // silently. // -// Refresh is tracked per-row (a real 4096-row array of "last +// Refresh is tracked per-row (a real ROWS-row array of "last // refreshed at cycle N" timestamps) and checked against tREFI -- // data itself is not modeled as decaying (unnecessary complexity for // this validation), but an insufficiently-refreshed row is flagged @@ -26,7 +35,10 @@ // bug in the MRS encoding would be caught here too. // ============================================================ module sdram_model #( - parameter CLK_FREQ_MHZ = 166 + parameter CLK_FREQ_MHZ = 64, + parameter ROW_BITS = 13, // AS4C32M16SA: row address A0-A12 + parameter COL_BITS = 10, // AS4C32M16SA: column address A0-A9 + parameter BANK_BITS = 2 // BA0,BA1 -- fixed across this whole Alliance SDR family )( input wire clk, input wire cke, @@ -34,14 +46,14 @@ module sdram_model #( input wire ras_n, input wire cas_n, input wire we_n, - input wire [1:0] ba, - input wire [11:0] a, + input wire [BANK_BITS-1:0] ba, + input wire [ROW_BITS-1:0] a, inout wire [15:0] dq, input wire [1:0] dqm ); - localparam BANKS = 4; - localparam ROWS = 4096; - localparam COLS = 256; + localparam BANKS = 1 << BANK_BITS; + localparam ROWS = 1 << ROW_BITS; + localparam COLS = 1 << COL_BITS; function integer ns_to_cycles; input integer ns; @@ -49,18 +61,18 @@ module sdram_model #( ns_to_cycles = (ns * CLK_FREQ_MHZ + 999) / 1000; end endfunction - localparam T_RCD = ns_to_cycles(18); - localparam T_RP = ns_to_cycles(18); - localparam T_RAS_MIN= ns_to_cycles(42); - localparam T_RC = ns_to_cycles(60); - localparam T_MRD = ns_to_cycles(12); - localparam T_REFI = ns_to_cycles(15625); + localparam T_RCD = ns_to_cycles(15); + localparam T_RP = ns_to_cycles(15); + localparam T_RAS_MIN= ns_to_cycles(45); + localparam T_RC = ns_to_cycles(65); + localparam T_MRD = 2; // tMRD = 2 CLK, fixed (see sdram_controller.v's own header) + localparam T_REFI = ns_to_cycles(64000000 / ROWS + 1); reg [15:0] mem [0:BANKS*ROWS*COLS-1]; // per-bank state reg bank_active [0:BANKS-1]; - reg [11:0] bank_row [0:BANKS-1]; + reg [ROW_BITS-1:0] bank_row [0:BANKS-1]; integer bank_active_since [0:BANKS-1]; // cycle ACTIVATE was issued integer bank_precharge_since [0:BANKS-1]; // cycle last PRECHARGE completed @@ -103,16 +115,16 @@ module sdram_model #( // active read-burst tracking (for auto-precharge/address auto-increment) reg rd_burst_active; - reg [1:0] rd_bank; - reg [11:0] rd_row; - reg [7:0] rd_col; + reg [BANK_BITS-1:0] rd_bank; + reg [ROW_BITS-1:0] rd_row; + reg [COL_BITS-1:0] rd_col; integer rd_remaining; reg rd_autoprecharge; reg wr_burst_active; - reg [1:0] wr_bank; - reg [11:0] wr_row; - reg [7:0] wr_col; + reg [BANK_BITS-1:0] wr_bank; + reg [ROW_BITS-1:0] wr_row; + reg [COL_BITS-1:0] wr_col; integer wr_remaining; reg wr_autoprecharge; @@ -210,7 +222,7 @@ module sdram_model #( if (cmd_read || cmd_write) begin if (!bank_active[ba]) $display("SDRAM_MODEL VIOLATION @%0t: %s to bank %0d with no active row", $time, cmd_read?"READ":"WRITE", ba); - else if (bank_row[ba] !== a[11:0] && 1'b0) begin + else if (bank_row[ba] !== a[ROW_BITS-1:0] && 1'b0) begin // column command doesn't carry a row -- nothing to // check here beyond bank-active, real row match is // implicit (the address IS the column within the @@ -230,7 +242,7 @@ module sdram_model #( // (the same bug class as the write-side fix below, // found by tracing the cycle-exact mismatch against // the controller's own CAS_LATENCY-cycle wait_cnt) - rd_pipe[cas_latency-1] <= mem[ba*ROWS*COLS + bank_row[ba]*COLS + a[7:0]]; + rd_pipe[cas_latency-1] <= mem[ba*ROWS*COLS + bank_row[ba]*COLS + a[COL_BITS-1:0]]; rd_valid_pipe[cas_latency-1] <= 1'b1; if (burst_len == 1) begin rd_burst_active <= 1'b0; @@ -239,7 +251,7 @@ module sdram_model #( bank_precharge_since[ba] <= cycle; end end else begin - rd_bank <= ba; rd_row <= bank_row[ba]; rd_col <= a[7:0] + 1'b1; + rd_bank <= ba; rd_row <= bank_row[ba]; rd_col <= a[COL_BITS-1:0] + 1'b1; rd_remaining <= burst_len - 1'b1; rd_autoprecharge <= a[10]; rd_burst_active <= 1'b1; end @@ -249,8 +261,8 @@ module sdram_model #( // cycle later -- capture it right here (in the same // cycle the command is decoded) instead of waiting for // wr_burst_active, which would silently drop word0 - if (dqm[0] == 1'b0) mem[ba*ROWS*COLS + bank_row[ba]*COLS + a[7:0]][7:0] <= dq[7:0]; - if (dqm[1] == 1'b0) mem[ba*ROWS*COLS + bank_row[ba]*COLS + a[7:0]][15:8] <= dq[15:8]; + if (dqm[0] == 1'b0) mem[ba*ROWS*COLS + bank_row[ba]*COLS + a[COL_BITS-1:0]][7:0] <= dq[7:0]; + if (dqm[1] == 1'b0) mem[ba*ROWS*COLS + bank_row[ba]*COLS + a[COL_BITS-1:0]][15:8] <= dq[15:8]; if (burst_len == 1) begin wr_burst_active <= 1'b0; if (a[10]) begin @@ -258,7 +270,7 @@ module sdram_model #( bank_precharge_since[ba] <= cycle; end end else begin - wr_bank <= ba; wr_row <= bank_row[ba]; wr_col <= a[7:0] + 1'b1; + wr_bank <= ba; wr_row <= bank_row[ba]; wr_col <= a[COL_BITS-1:0] + 1'b1; wr_remaining <= burst_len - 1'b1; wr_autoprecharge <= a[10]; wr_burst_active <= 1'b1; end @@ -302,12 +314,12 @@ module sdram_model #( // testbench-only backdoor access (poke/peek), matching this // project's own established convention elsewhere (psram_model.v) - task automatic backdoor_write(input [1:0] tb_bank, input [11:0] tb_row, input [7:0] tb_col, input [15:0] val); + task automatic backdoor_write(input [BANK_BITS-1:0] tb_bank, input [ROW_BITS-1:0] tb_row, input [COL_BITS-1:0] tb_col, input [15:0] val); begin mem[tb_bank*ROWS*COLS + tb_row*COLS + tb_col] = val; end endtask - function automatic [15:0] backdoor_read(input [1:0] tb_bank, input [11:0] tb_row, input [7:0] tb_col); + function automatic [15:0] backdoor_read(input [BANK_BITS-1:0] tb_bank, input [ROW_BITS-1:0] tb_row, input [COL_BITS-1:0] tb_col); begin backdoor_read = mem[tb_bank*ROWS*COLS + tb_row*COLS + tb_col]; end diff --git a/hardware/v2/nms/sim/tb_fpga_neural_v2_top_smoke.v b/hardware/v2/nms/sim/tb_fpga_neural_v2_top_smoke.v index 60b938b..e3abf06 100644 --- a/hardware/v2/nms/sim/tb_fpga_neural_v2_top_smoke.v +++ b/hardware/v2/nms/sim/tb_fpga_neural_v2_top_smoke.v @@ -56,13 +56,19 @@ module tb_fpga_neural_v2_top_smoke; - localparam ADDR_WIDTH = 23; + localparam ADDR_WIDTH = 26; // AS4C32M16SA memory upgrade localparam N_SLOTS = 2; localparam N_NODES = 16; localparam MAX_DEPS = 4; reg osc_clk = 0; - always #31.25 osc_clk = ~osc_clk; // 16MHz (bypassed 1:1 to clk_sys under `SIM) + // Driven at the REAL 64MHz clk_sys rate (not the board's own 16MHz + // osc_clk) -- under the `SIM PLL bypass (clk_sys = osc_clk + // directly, see ecp5_pll_sys_clk.v), this reproduces the real + // board's actual system-clock rate for this test, matching + // CLK_FREQ_MHZ(64) above (a previous draft left both this and the + // controller's own CLK_FREQ_MHZ at a stale, pre-freeze value). + always #7.8125 osc_clk = ~osc_clk; // 64MHz reg ext_rst_n = 0; @@ -71,14 +77,14 @@ module tb_fpga_neural_v2_top_smoke; 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; wire pll_locked; fpga_neural_v2_top #( .ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), - .CLK_FREQ_MHZ(80) + .CLK_FREQ_MHZ(64) ) dut ( .osc_clk(osc_clk), .ext_rst_n(ext_rst_n), .spi_sclk(spi_sclk), .spi_mosi(spi_mosi), .spi_miso(spi_miso), .spi_cs_n(spi_cs_n), @@ -88,7 +94,7 @@ module tb_fpga_neural_v2_top_smoke; .pll_locked(pll_locked) ); - sdram_model #(.CLK_FREQ_MHZ(80)) u_sdram ( + sdram_model #(.CLK_FREQ_MHZ(64)) u_sdram ( .clk(dut.clk_sys), .cke(sdram_cke), .cs_n(sdram_cs_n), .ras_n(sdram_ras_n), .cas_n(sdram_cas_n), .we_n(sdram_we_n), .ba(sdram_ba), .a(sdram_a), .dq(sdram_dq), .dqm(sdram_dqm) @@ -103,7 +109,7 @@ module tb_fpga_neural_v2_top_smoke; endfunction task poke_byte(input [ADDR_WIDTH-1:0] byte_addr, input signed [7:0] val); - reg [21:0] word_addr; + reg [24:0] word_addr; begin word_addr = byte_addr[ADDR_WIDTH-1:1]; if (byte_addr[0] == 1'b0) u_sdram.mem[word_addr][7:0] = val; @@ -112,7 +118,7 @@ module tb_fpga_neural_v2_top_smoke; endtask function automatic signed [7:0] peek_byte(input [ADDR_WIDTH-1:0] byte_addr); - reg [21:0] word_addr; + reg [24:0] word_addr; begin word_addr = byte_addr[ADDR_WIDTH-1:1]; peek_byte = (byte_addr[0] == 1'b0) ? u_sdram.mem[word_addr][7:0] : u_sdram.mem[word_addr][15:8]; @@ -134,8 +140,8 @@ module tb_fpga_neural_v2_top_smoke; endtask task write_job(input [3:0] node_id, input [2:0] required, input [15:0] producer_ids, - input [22:0] x_base, input [22:0] w_base, input [15:0] n_tiles, - input [22:0] result_addr); + input [ADDR_WIDTH-1:0] x_base, input [ADDR_WIDTH-1:0] w_base, input [15:0] n_tiles, + input [ADDR_WIDTH-1:0] result_addr); reg [7:0] rxb; begin spi_cs_n = 0; #20; @@ -144,15 +150,18 @@ module tb_fpga_neural_v2_top_smoke; spi_byte({5'b0, required}, rxb); spi_byte(producer_ids[15:8], rxb); spi_byte(producer_ids[7:0], rxb); - spi_byte({1'b0, x_base[22:16]}, rxb); + spi_byte({6'b0, x_base[25:24]}, rxb); + spi_byte(x_base[23:16], rxb); spi_byte(x_base[15:8], rxb); spi_byte(x_base[7:0], rxb); - spi_byte({1'b0, w_base[22:16]}, rxb); + spi_byte({6'b0, w_base[25:24]}, rxb); + spi_byte(w_base[23:16], rxb); spi_byte(w_base[15:8], rxb); spi_byte(w_base[7:0], rxb); spi_byte(n_tiles[15:8], rxb); spi_byte(n_tiles[7:0], rxb); - spi_byte({1'b0, result_addr[22:16]}, rxb); + spi_byte({6'b0, result_addr[25:24]}, rxb); + spi_byte(result_addr[23:16], rxb); spi_byte(result_addr[15:8], rxb); spi_byte(result_addr[7:0], rxb); // hold CS through the reg_valid/reg_ready handshake (may @@ -194,10 +203,10 @@ module tb_fpga_neural_v2_top_smoke; integer k, n; begin x_base = region; - w0 = region + 23'h100; - w1 = region + 23'h110; - res0 = region + 23'h200; - res1 = region + 23'h201; + w0 = region + 26'h100; + w1 = region + 26'h110; + res0 = region + 26'h200; + res1 = region + 26'h201; for (k = 0; k < 8; k = k + 1) poke_byte(x_base + k, k[7:0] + 1); for (n = 0; n < 2; n = n + 1) @@ -225,8 +234,8 @@ module tb_fpga_neural_v2_top_smoke; integer k; begin x_base = region; - w0 = region + 23'h100; - res0 = region + 23'h200; + w0 = region + 26'h100; + res0 = region + 26'h200; for (k = 0; k < 8; k = k + 1) poke_byte(x_base + k, k[7:0] + 3); for (k = 0; k < 8; k = k + 1) poke_byte(w0 + k, ((k) % 3) + 1); poke_byte(res0, 8'sd0); @@ -250,19 +259,19 @@ module tb_fpga_neural_v2_top_smoke; @(posedge dut.clk_sys); // B) single job, alone - run_single(23'h001000, "B-single-neuron0"); + run_single(26'h001000, "B-single-neuron0"); // A/D) two jobs, realistic wide SPI pacing (~85us worth of SPI // framing plus an explicit extra gap -- the original failing case) - run_pair(23'h004000, 20000, "A-wide-gap"); + run_pair(26'h004000, 20000, "A-wide-gap"); // C) two jobs back-to-back (minimal CS-high gap between them) - run_pair(23'h007000, 0, "C-back-to-back"); + run_pair(26'h007000, 0, "C-back-to-back"); // G) parametric sweep across several distinct inter-job gaps - run_pair(23'h00A000, 100, "G-gap100ns"); - run_pair(23'h00D000, 5000, "G-gap5000ns"); - run_pair(23'h010000, 50000, "G-gap50000ns"); + run_pair(26'h00A000, 100, "G-gap100ns"); + run_pair(26'h00D000, 5000, "G-gap5000ns"); + run_pair(26'h010000, 50000, "G-gap50000ns"); $display("=== tb_fpga_neural_v2_top_smoke: %0d/%0d PASS ===", tests-errors, tests); if (errors != 0) $display("*** %0d FAILURES ***", errors); diff --git a/hardware/v2/nms/sim/tb_nms_dstress_sdram_unified.v b/hardware/v2/nms/sim/tb_nms_dstress_sdram_unified.v index f6d9c62..f041c3d 100644 --- a/hardware/v2/nms/sim/tb_nms_dstress_sdram_unified.v +++ b/hardware/v2/nms/sim/tb_nms_dstress_sdram_unified.v @@ -75,7 +75,7 @@ module tb #( parameter PFD_CFG = 8 ); - localparam ADDR_WIDTH = 23; + localparam ADDR_WIDTH = 26; // AS4C32M16SA: 25-bit word address + 1 byte-select bit localparam DATA_WIDTH = 8; localparam P_IN = 8; localparam ACC_WIDTH = 32; @@ -149,7 +149,7 @@ module tb #( // backing array via the same byte_addr>>1 / byte_addr[0] pattern. // ============================================================ task automatic poke_byte(input [ADDR_WIDTH-1:0] byte_addr, input signed [7:0] val); - reg [21:0] word_addr; + reg [24:0] word_addr; begin word_addr = byte_addr[ADDR_WIDTH-1:1]; if (byte_addr[0] == 1'b0) u_sdram.mem[word_addr][7:0] = val; @@ -158,24 +158,24 @@ module tb #( endtask function automatic signed [7:0] peek_byte(input [ADDR_WIDTH-1:0] byte_addr); - reg [21:0] word_addr; + reg [24:0] word_addr; begin word_addr = byte_addr[ADDR_WIDTH-1:1]; peek_byte = (byte_addr[0] == 1'b0) ? u_sdram.mem[word_addr][7:0] : u_sdram.mem[word_addr][15:8]; end endfunction - // sdram_model.v's own `mem` array is flat-indexed by the 22-bit + // sdram_model.v's own `mem` array is flat-indexed by the 25-bit // word address directly (bank*ROWS*COLS + row*COLS + col, which, - // given ROWS=4096/COLS=256 are both powers of 2, is numerically - // IDENTICAL to treating the address as one flat 22-bit integer -- + // given ROWS=8192/COLS=1024 are both powers of 2, is numerically + // IDENTICAL to treating the address as one flat 25-bit integer -- // confirmed against sdram_model.v's own BANKS/ROWS/COLS localparams // before writing this, not assumed) -- so this is the exact same // byte_addr>>1 / byte_addr[0] pattern as the original single-chip // poke_byte/peek_byte above, just against u_sdram.mem instead of // u_psram.mem. task automatic poke_byte_weight(input [ADDR_WIDTH-1:0] byte_addr, input signed [7:0] val); - reg [21:0] word_addr; + reg [24:0] word_addr; begin word_addr = byte_addr[ADDR_WIDTH-1:1]; if (byte_addr[0] == 1'b0) u_sdram.mem[word_addr][7:0] = val; @@ -184,7 +184,7 @@ module tb #( endtask function automatic signed [7:0] peek_byte_weight(input [ADDR_WIDTH-1:0] byte_addr); - reg [21:0] word_addr; + reg [24:0] word_addr; begin word_addr = byte_addr[ADDR_WIDTH-1:1]; peek_byte_weight = (byte_addr[0] == 1'b0) ? u_sdram.mem[word_addr][7:0] : u_sdram.mem[word_addr][15:8]; @@ -853,7 +853,7 @@ module tb #( // STEP19 official memory map (hardware/v2/docs/MEMORY_ARCHITECTURE.md): // weights @ 0x010000, activations @ 0x200000, results @ 0x300000 -- // non-overlapping 1MB-aligned regions in the single 8MB SDRAM. - run_dense_layer("D-Stress", 256, 16, 16'd400, 23'h200000, 23'h010000, 23'h300000, 1'b0); + run_dense_layer("D-Stress", 256, 16, 16'd400, 26'h200000, 26'h010000, 26'h300000, 1'b0); $display("========================================"); if (errors == 0) diff --git a/hardware/v2/nms/sim/tb_sdram_boundary.v b/hardware/v2/nms/sim/tb_sdram_boundary.v index a220169..e142e30 100644 --- a/hardware/v2/nms/sim/tb_sdram_boundary.v +++ b/hardware/v2/nms/sim/tb_sdram_boundary.v @@ -12,9 +12,10 @@ // (all 4 banks), the real V2 memory-map region boundaries // (weights/activations/results), and every DQM byte-mask combination // with an explicit read-after-write check. Real Alliance Memory -// AS4C4M16SA-6TIN geometry (confirmed against sdram_controller.v's -// own address decode): word address = {bank[1:0], row[11:0], -// col[7:0]}, 4 banks x 4096 rows x 256 cols x 16 bits = 4M words = 8MB. +// AS4C32M16SA-7TIN geometry (confirmed against sdram_controller.v's +// own address decode, post-PRE-PCB-FREEZE memory upgrade): word +// address = {bank[1:0], row[12:0], col[9:0]}, 4 banks x 8192 rows x +// 1024 cols x 16 bits = 32M words = 64MB. // // BURST_LEN=1 is used throughout (not the default 4) so every address // in this test names an exact, single physical word -- burst-wrap @@ -28,7 +29,12 @@ module tb_sdram_boundary #( parameter CLK_FREQ_MHZ = 64 ); localparam BURST_LEN = 1; - localparam ADDR_WIDTH = 22; + // AS4C32M16SA-7TIN (64MB): 13 row bits (A0-A12), 10 col bits + // (A0-A9), 2 bank bits (BA0-BA1). + localparam ROW_BITS = 13; + localparam COL_BITS = 10; + localparam BANK_BITS = 2; + localparam ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS; localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ; reg clk = 0; @@ -43,12 +49,15 @@ module tb_sdram_boundary #( wire ready, busy; 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 [BANK_BITS-1:0] sdram_ba; + wire [ROW_BITS-1:0] sdram_a; wire [15:0] sdram_dq; wire [1:0] sdram_dqm; - sdram_controller #(.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)) dut ( + sdram_controller #( + .CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN), + .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) + ) dut ( .clk(clk), .rst(rst), .req(req), .wr(wr), .addr(addr), .wdata(wdata), .wmask(wmask), .rdata(rdata), .ready(ready), .busy(busy), .sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n), @@ -56,7 +65,10 @@ module tb_sdram_boundary #( .sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm) ); - sdram_model #(.CLK_FREQ_MHZ(CLK_FREQ_MHZ)) mem ( + sdram_model #( + .CLK_FREQ_MHZ(CLK_FREQ_MHZ), + .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) + ) mem ( .clk(clk), .cke(sdram_cke), .cs_n(sdram_cs_n), .ras_n(sdram_ras_n), .cas_n(sdram_cas_n), .we_n(sdram_we_n), .ba(sdram_ba), .a(sdram_a), .dq(sdram_dq), .dqm(sdram_dqm) @@ -95,11 +107,11 @@ module tb_sdram_boundary #( tests = tests + 1; if (got !== expected) begin $display("FAIL %0s addr=0x%06h (bank=%0d row=%0d col=%0d): expected=%h actual=%h", - label, a, a[21:20], a[19:8], a[7:0], expected, got); + label, a, a[24:23], a[22:10], a[9:0], expected, got); errors = errors + 1; end else begin $display("PASS %0s addr=0x%06h (bank=%0d row=%0d col=%0d): data=%h", - label, a, a[21:20], a[19:8], a[7:0], got); + label, a, a[24:23], a[22:10], a[9:0], got); end end endtask @@ -113,11 +125,11 @@ module tb_sdram_boundary #( // ---- the real V2 memory map (BYTE addresses) converted to this // controller's own WORD addresses (word = byte>>1) ---- - localparam [ADDR_WIDTH-1:0] WEIGHTS_BASE_W = 22'h008000; // byte 0x010000 - localparam [ADDR_WIDTH-1:0] ACT_BASE_W = 22'h100000; // byte 0x200000 - localparam [ADDR_WIDTH-1:0] RESULTS_BASE_W = 22'h180000; // byte 0x300000 - localparam [ADDR_WIDTH-1:0] WEIGHTS_LAST_W = ACT_BASE_W - 22'd1; // last word before activations - localparam [ADDR_WIDTH-1:0] ACT_LAST_W = RESULTS_BASE_W - 22'd1; // last word before results + localparam [ADDR_WIDTH-1:0] WEIGHTS_BASE_W = 25'h008000; // byte 0x010000 + localparam [ADDR_WIDTH-1:0] ACT_BASE_W = 25'h100000; // byte 0x200000 + localparam [ADDR_WIDTH-1:0] RESULTS_BASE_W = 25'h180000; // byte 0x300000 + localparam [ADDR_WIDTH-1:0] WEIGHTS_LAST_W = ACT_BASE_W - 25'd1; // last word before activations + localparam [ADDR_WIDTH-1:0] ACT_LAST_W = RESULTS_BASE_W - 25'd1; // last word before results // ---- the 17-address boundary/adjacency set. All written first // (each a distinct addr_pat value), THEN all read back in a @@ -133,18 +145,18 @@ module tb_sdram_boundary #( integer ai; initial begin - a_set[0] = 22'h000000; a_label[0] = "addr-0"; - a_set[1] = 22'h000001; a_label[1] = "addr-1"; - a_set[2] = 22'h3FFFFF; a_label[2] = "addr-last"; - a_set[3] = 22'h3FFFFE; a_label[3] = "addr-last-1"; - a_set[4] = {2'd0, 12'd10, 8'd255}; a_label[4] = "row10-lastcol"; - a_set[5] = {2'd0, 12'd11, 8'd0}; a_label[5] = "row11-firstcol"; - a_set[6] = {2'd0, 12'd4095, 8'd255}; a_label[6] = "bank0-last"; - a_set[7] = {2'd1, 12'd0, 8'd0}; a_label[7] = "bank1-first"; - a_set[8] = {2'd1, 12'd4095, 8'd255}; a_label[8] = "bank1-last"; - a_set[9] = {2'd2, 12'd0, 8'd0}; a_label[9] = "bank2-first"; - a_set[10] = {2'd2, 12'd4095, 8'd255}; a_label[10] = "bank2-last"; - a_set[11] = {2'd3, 12'd0, 8'd0}; a_label[11] = "bank3-first"; + a_set[0] = {ADDR_WIDTH{1'b0}}; a_label[0] = "addr-0"; + a_set[1] = {{(ADDR_WIDTH-1){1'b0}}, 1'b1}; a_label[1] = "addr-1"; + a_set[2] = {ADDR_WIDTH{1'b1}}; a_label[2] = "addr-last"; + a_set[3] = {ADDR_WIDTH{1'b1}} - 1'b1; a_label[3] = "addr-last-1"; + a_set[4] = {2'd0, 13'd10, 10'd1023}; a_label[4] = "row10-lastcol"; + a_set[5] = {2'd0, 13'd11, 10'd0}; a_label[5] = "row11-firstcol"; + a_set[6] = {2'd0, 13'd8191, 10'd1023}; a_label[6] = "bank0-last"; + a_set[7] = {2'd1, 13'd0, 10'd0}; a_label[7] = "bank1-first"; + a_set[8] = {2'd1, 13'd8191, 10'd1023}; a_label[8] = "bank1-last"; + a_set[9] = {2'd2, 13'd0, 10'd0}; a_label[9] = "bank2-first"; + a_set[10] = {2'd2, 13'd8191, 10'd1023}; a_label[10] = "bank2-last"; + a_set[11] = {2'd3, 13'd0, 10'd0}; a_label[11] = "bank3-first"; a_set[12] = WEIGHTS_BASE_W; a_label[12] = "weights-base"; a_set[13] = WEIGHTS_LAST_W; a_label[13] = "weights-last(pre-act)"; a_set[14] = ACT_BASE_W; a_label[14] = "activations-base"; @@ -173,7 +185,7 @@ module tb_sdram_boundary #( // using the requested deterministic patterns (0x0000, 0xFFFF, // 0xAAAA, 0x5555) ---- begin : mask_tests - localparam [ADDR_WIDTH-1:0] MADDR = 22'h001000; + localparam [ADDR_WIDTH-1:0] MADDR = 25'h001000; // lower-byte-only write (wmask=2'b10: upper masked/ // retained, lower written) @@ -196,8 +208,8 @@ module tb_sdram_boundary #( // (0x5555 alone, both bytes, at a different address) to // exercise all four requested literal patterns at least // once each in this test - write_word(MADDR + 22'd1, 16'h5555, 2'b00); - check(MADDR + 22'd1, 16'h5555, "pattern-5555-plain"); + write_word(MADDR + 25'd1, 16'h5555, 2'b00); + check(MADDR + 25'd1, 16'h5555, "pattern-5555-plain"); end $display("=== %0d/%0d tests, %0d errors (tb_sdram_boundary, CLK_FREQ_MHZ=%0d) ===", diff --git a/hardware/v2/nms/sim/tb_sdram_controller.v b/hardware/v2/nms/sim/tb_sdram_controller.v index bcf240c..f4147ba 100644 --- a/hardware/v2/nms/sim/tb_sdram_controller.v +++ b/hardware/v2/nms/sim/tb_sdram_controller.v @@ -18,9 +18,15 @@ // ============================================================ module tb #( parameter BURST_LEN = 4, - parameter CLK_FREQ_MHZ = 166 + parameter CLK_FREQ_MHZ = 64 ); - localparam ADDR_WIDTH = 22; + // AS4C32M16SA-7TIN (64MB): 13 row bits (A0-A12), 10 col bits + // (A0-A9), 2 bank bits (BA0-BA1) -- see sdram_controller.v's own + // header for the full datasheet cross-reference. + localparam ROW_BITS = 13; + localparam COL_BITS = 10; + localparam BANK_BITS = 2; + localparam ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS; localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ; reg clk = 0; @@ -35,12 +41,15 @@ module tb #( wire ready, busy; 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 [BANK_BITS-1:0] sdram_ba; + wire [ROW_BITS-1:0] sdram_a; wire [15:0] sdram_dq; wire [1:0] sdram_dqm; - sdram_controller #(.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)) dut ( + sdram_controller #( + .CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN), + .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) + ) dut ( .clk(clk), .rst(rst), .req(req), .wr(wr), .addr(addr), .wdata(wdata), .wmask(wmask), .rdata(rdata), .ready(ready), .busy(busy), .sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n), @@ -48,7 +57,10 @@ module tb #( .sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm) ); - sdram_model #(.CLK_FREQ_MHZ(CLK_FREQ_MHZ)) mem ( + sdram_model #( + .CLK_FREQ_MHZ(CLK_FREQ_MHZ), + .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) + ) mem ( .clk(clk), .cke(sdram_cke), .cs_n(sdram_cs_n), .ras_n(sdram_ras_n), .cas_n(sdram_cas_n), .we_n(sdram_we_n), .ba(sdram_ba), .a(sdram_a), .dq(sdram_dq), .dqm(sdram_dqm) @@ -123,7 +135,7 @@ module tb #( while (busy) @(posedge clk); // real power-up/init sequence // ---- A: write -> read single ---- - check_word(22'd0, 16'hA5A5); + check_word({ADDR_WIDTH{1'b0}}, 16'hA5A5); // ---- B: sequential addresses ---- trace_on = 1'b1; @@ -134,25 +146,25 @@ module tb #( check_word(i*BURST_LEN, 16'h1000 + i); // ---- E: row change (same bank 0, different row) ---- - check_word({2'b00, 12'd0, 8'd0}, 16'h2000); - check_word({2'b00, 12'd1, 8'd0}, 16'h2001); - check_word({2'b00, 12'd100, 8'd0}, 16'h2002); + check_word({2'b00, 13'd0, 10'd0}, 16'h2000); + check_word({2'b00, 13'd1, 10'd0}, 16'h2001); + check_word({2'b00, 13'd100, 10'd0}, 16'h2002); // ---- F: bank change ---- - check_word({2'b00, 12'd5, 8'd0}, 16'h3000); - check_word({2'b01, 12'd5, 8'd0}, 16'h3001); - check_word({2'b10, 12'd5, 8'd0}, 16'h3002); - check_word({2'b11, 12'd5, 8'd0}, 16'h3003); + check_word({2'b00, 13'd5, 10'd0}, 16'h3000); + check_word({2'b01, 13'd5, 10'd0}, 16'h3001); + check_word({2'b10, 13'd5, 10'd0}, 16'h3002); + check_word({2'b11, 13'd5, 10'd0}, 16'h3003); - // ---- I: address limits ---- - check_word({2'b00, 12'd0, 8'd0}, 16'h4000); // row 0, col 0 - check_word({2'b11, 12'd4095, 8'(256-BURST_LEN)}, 16'h4001); // max bank/row, last valid burst-aligned col - check_word({2'b00, 12'd4095, 8'd0}, 16'h4002); - check_word({2'b11, 12'd0, 8'd0}, 16'h4003); + // ---- I: address limits (AS4C32M16SA: 8192 rows, 1024 cols) ---- + check_word({2'b00, 13'd0, 10'd0}, 16'h4000); // row 0, col 0 + check_word({2'b11, 13'd8191, 10'(1024-BURST_LEN)}, 16'h4001); // max bank/row, last valid burst-aligned col + check_word({2'b00, 13'd8191, 10'd0}, 16'h4002); + check_word({2'b11, 13'd0, 10'd0}, 16'h4003); // ---- H: pseudo-random pattern ---- for (i = 0; i < 32; i = i + 1) begin - rnd_addr = ($random(seed) % (4*4096*256/BURST_LEN)) * BURST_LEN; + rnd_addr = ($random(seed) % (4*8192*1024/BURST_LEN)) * BURST_LEN; check_word(rnd_addr, 16'h5000 + i); end @@ -176,7 +188,7 @@ module tb #( reg [2*BURST_LEN-1:0] m; integer w, elapsed_j; reg [ADDR_WIDTH-1:0] addr_j; - addr_j = 22'd50000; + addr_j = 25'd50000; // seed a known full pattern first (no masking) for (w = 0; w < BURST_LEN; w = w + 1) full_pat[w*16 +: 16] = 16'h7000 + w[15:0]; wmask = {(2*BURST_LEN){1'b0}}; diff --git a/hardware/v2/nms/sim/tb_sdram_unified_backend.v b/hardware/v2/nms/sim/tb_sdram_unified_backend.v index feaffde..24e4c81 100644 --- a/hardware/v2/nms/sim/tb_sdram_unified_backend.v +++ b/hardware/v2/nms/sim/tb_sdram_unified_backend.v @@ -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 diff --git a/hardware/v2/rtl/dependency_manager.v b/hardware/v2/rtl/dependency_manager.v index a30fd84..5bbb788 100644 --- a/hardware/v2/rtl/dependency_manager.v +++ b/hardware/v2/rtl/dependency_manager.v @@ -43,7 +43,7 @@ module dependency_manager #( parameter N_NODES = 16, parameter MAX_DEPS = 4, - parameter ADDR_WIDTH = 23 + parameter ADDR_WIDTH = 26 )( input wire clk, input wire rst, diff --git a/hardware/v2/rtl/neural_director.v b/hardware/v2/rtl/neural_director.v index 51c3aa9..a0ece4c 100644 --- a/hardware/v2/rtl/neural_director.v +++ b/hardware/v2/rtl/neural_director.v @@ -30,7 +30,7 @@ // ================================================================ module neural_director #( - parameter ADDR_WIDTH = 23, + parameter ADDR_WIDTH = 26, parameter N_SLOTS = 4, parameter QUEUE_DEPTH = 8 )( @@ -48,11 +48,16 @@ module neural_director #( input wire [15:0] job_in_node_id, // ---- per-slot memory_manager job control (arrayed, §9) ---- - output reg [N_SLOTS-1:0] slot_job_start, - output reg [ADDR_WIDTH*N_SLOTS-1:0] slot_x_base, - output reg [ADDR_WIDTH*N_SLOTS-1:0] slot_w_base, - output reg [16*N_SLOTS-1:0] slot_n_tiles, - output reg [ADDR_WIDTH*N_SLOTS-1:0] slot_result_addr, + // Ports are `wire`, driven by the GEN_SLOT_OUT generate block below + // from internal unpacked-array registers (see that block's own + // comment for why -- a real, measured timing regression found this + // session when ADDR_WIDTH grew from 23 to 26 bits, section + // "post-PRE-PCB-FREEZE memory upgrade"). + output wire [N_SLOTS-1:0] slot_job_start, + output wire [ADDR_WIDTH*N_SLOTS-1:0] slot_x_base, + output wire [ADDR_WIDTH*N_SLOTS-1:0] slot_w_base, + output wire [16*N_SLOTS-1:0] slot_n_tiles, + output wire [ADDR_WIDTH*N_SLOTS-1:0] slot_result_addr, // slot_node_id: which node_id is currently occupying each slot -- // not needed by memory_manager itself (it has no notion of node // ids), but needed by a caller (dataflow_core.v, M7) that must @@ -60,7 +65,7 @@ module neural_director #( // to notify the Dependency Manager (M6). Purely additive: existing // callers (hardware/v2/sim/tb_neural_director.v, M5) that don't // connect it are unaffected. - output reg [16*N_SLOTS-1:0] slot_node_id, + output wire [16*N_SLOTS-1:0] slot_node_id, input wire [N_SLOTS-1:0] slot_job_done, // ---- completion notification (§9 "rilevamento dei completamenti") ---- @@ -121,6 +126,45 @@ module neural_director #( end end + // ---- per-slot output storage (unpacked arrays, one real register + // set per slot) + constant-indexed generate wiring out to the + // packed ports above. Found and fixed this session (post-PRE-PCB- + // FREEZE memory upgrade, ADDR_WIDTH 23->26): the PREVIOUS design + // used one wide packed `output reg` per field and wrote it with a + // RUNTIME-computed part-select (`slot_x_base[free_slot_idx* + // ADDR_WIDTH +: ADDR_WIDTH] <= ...`). A variable-indexed write into + // a wide packed register is not free logic -- Yosys/synth_ecp5 + // synthesized the index computation (`free_slot_idx*ADDR_WIDTH`) + // as an actual MULT18X18D hard multiplier feeding a wide demux/ + // crossbar into the destination slot, and this got measurably + // worse as ADDR_WIDTH grew (real nextpnr-ecp5 P&R: worst-seed Fmax + // collapsed from 68.51MHz at ADDR_WIDTH=23 to ~40-47MHz at + // ADDR_WIDTH=26, confirmed across 8 seeds, all failing the 64MHz + // target). The fix below replaces the runtime-indexed demux write + // with N_SLOTS parallel CONSTANT-indexed comparisons (`fi == + // free_slot_idx`, each a cheap few-bit compare, no multiply) each + // gating its own slot's own narrow register -- functionally + // IDENTICAL behavior, bit-exact same external port semantics, only + // the internal implementation changed. + reg slot_job_start_r [0:N_SLOTS-1]; + reg [ADDR_WIDTH-1:0] slot_x_base_r [0:N_SLOTS-1]; + reg [ADDR_WIDTH-1:0] slot_w_base_r [0:N_SLOTS-1]; + reg [15:0] slot_n_tiles_r [0:N_SLOTS-1]; + reg [ADDR_WIDTH-1:0] slot_result_addr_r [0:N_SLOTS-1]; + reg [15:0] slot_node_id_r [0:N_SLOTS-1]; + + genvar gs; + generate + for (gs = 0; gs < N_SLOTS; gs = gs + 1) begin : GEN_SLOT_OUT + assign slot_job_start[gs] = slot_job_start_r[gs]; + assign slot_x_base[gs*ADDR_WIDTH +: ADDR_WIDTH] = slot_x_base_r[gs]; + assign slot_w_base[gs*ADDR_WIDTH +: ADDR_WIDTH] = slot_w_base_r[gs]; + assign slot_n_tiles[gs*16 +: 16] = slot_n_tiles_r[gs]; + assign slot_result_addr[gs*ADDR_WIDTH +: ADDR_WIDTH] = slot_result_addr_r[gs]; + assign slot_node_id[gs*16 +: 16] = slot_node_id_r[gs]; + end + endgenerate + // Priority-encoded lowest-indexed slot reporting job_done this // cycle (combinational, so it reflects THIS cycle's slot_job_done // bus directly -- a register-based "already reported one" flag @@ -143,16 +187,18 @@ module neural_director #( q_tail <= {Q_ADDR_WIDTH{1'b0}}; q_count <= {(Q_ADDR_WIDTH+1){1'b0}}; slot_busy <= {N_SLOTS{1'b0}}; - slot_job_start <= {N_SLOTS{1'b0}}; - slot_x_base <= {(ADDR_WIDTH*N_SLOTS){1'b0}}; - slot_w_base <= {(ADDR_WIDTH*N_SLOTS){1'b0}}; - slot_n_tiles <= {(16*N_SLOTS){1'b0}}; - slot_result_addr <= {(ADDR_WIDTH*N_SLOTS){1'b0}}; - slot_node_id <= {(16*N_SLOTS){1'b0}}; + for (fi = 0; fi < N_SLOTS; fi = fi + 1) begin + slot_job_start_r[fi] <= 1'b0; + slot_x_base_r[fi] <= {ADDR_WIDTH{1'b0}}; + slot_w_base_r[fi] <= {ADDR_WIDTH{1'b0}}; + slot_n_tiles_r[fi] <= 16'b0; + slot_result_addr_r[fi] <= {ADDR_WIDTH{1'b0}}; + slot_node_id_r[fi] <= 16'b0; + end job_out_done <= 1'b0; job_out_slot <= '0; end else begin - slot_job_start <= {N_SLOTS{1'b0}}; + for (fi = 0; fi < N_SLOTS; fi = fi + 1) slot_job_start_r[fi] <= 1'b0; job_out_done <= 1'b0; // ---- Accept a new job into the ready queue (independent @@ -201,14 +247,21 @@ module neural_director #( end DIR_ALLOCATE: begin - // Dispatch the head of the queue to the first - // free slot found this cycle. - slot_job_start[free_slot_idx] <= 1'b1; - slot_x_base[free_slot_idx*ADDR_WIDTH +: ADDR_WIDTH] <= q_x_base[q_head]; - slot_w_base[free_slot_idx*ADDR_WIDTH +: ADDR_WIDTH] <= q_w_base[q_head]; - slot_n_tiles[free_slot_idx*16 +: 16] <= q_n_tiles[q_head]; - slot_result_addr[free_slot_idx*ADDR_WIDTH +: ADDR_WIDTH] <= q_result_addr[q_head]; - slot_node_id[free_slot_idx*16 +: 16] <= q_node_id[q_head]; + // Dispatch the head of the queue to the first free + // slot found this cycle -- N_SLOTS parallel + // constant-indexed compares (cheap) instead of one + // runtime-indexed wide demux write (see + // slot_x_base_r's own declaration comment for why). + for (fi = 0; fi < N_SLOTS; fi = fi + 1) begin + if (fi[$clog2(N_SLOTS)-1:0] == free_slot_idx) begin + slot_job_start_r[fi] <= 1'b1; + slot_x_base_r[fi] <= q_x_base[q_head]; + slot_w_base_r[fi] <= q_w_base[q_head]; + slot_n_tiles_r[fi] <= q_n_tiles[q_head]; + slot_result_addr_r[fi] <= q_result_addr[q_head]; + slot_node_id_r[fi] <= q_node_id[q_head]; + end + end slot_busy[free_slot_idx] <= 1'b1; q_head <= (q_head == QUEUE_DEPTH[Q_ADDR_WIDTH-1:0]-1'b1) ? {Q_ADDR_WIDTH{1'b0}} : q_head + 1'b1; dir_state <= DIR_SCAN_READY; diff --git a/hardware/v2/rtl/slot_mem_arbiter.v b/hardware/v2/rtl/slot_mem_arbiter.v index 3cc40f4..cbdc3d2 100644 --- a/hardware/v2/rtl/slot_mem_arbiter.v +++ b/hardware/v2/rtl/slot_mem_arbiter.v @@ -52,7 +52,7 @@ // ================================================================ module slot_mem_arbiter #( - parameter ADDR_WIDTH = 23, + parameter ADDR_WIDTH = 26, parameter N_PORTS = 4 )( input wire clk, diff --git a/hardware/v2/rtl/slot_mem_arbiter_wide.v b/hardware/v2/rtl/slot_mem_arbiter_wide.v index 4e21f85..925b9bd 100644 --- a/hardware/v2/rtl/slot_mem_arbiter_wide.v +++ b/hardware/v2/rtl/slot_mem_arbiter_wide.v @@ -53,7 +53,7 @@ module slot_mem_arbiter_wide #( parameter DATA_WIDTH = 32, - parameter ADDR_WIDTH = 23, + parameter ADDR_WIDTH = 26, parameter N_PORTS = 4 )( input wire clk, diff --git a/hardware/v2/rtl/spi_host_bridge.v b/hardware/v2/rtl/spi_host_bridge.v index 0a6038c..36d2e2f 100644 --- a/hardware/v2/rtl/spi_host_bridge.v +++ b/hardware/v2/rtl/spi_host_bridge.v @@ -26,15 +26,17 @@ // 0x00 NOP -- 0 payload bytes. // 0x0F RESET -- 0 payload bytes. Pulses soft_rst_pulse for // one clk cycle after CS rises. -// 0x10 WRITE_JOB -- 15 payload bytes, registers one dependency- -// manager job (== one reg_valid/reg_* handshake): +// 0x10 WRITE_JOB -- 18 payload bytes (widened from 15 -- see +// "ADDRESS WIDTH" note below), registers one +// dependency-manager job (== one reg_valid/ +// reg_* handshake): // byte0 = {4'b0,node_id[3:0]} // byte1 = {5'b0,required[2:0]} // byte2:3 = producer_ids[15:0] -// byte4:6 = x_base[22:0] (byte4 msb={1'b0,x_base[22:16]}) -// byte7:9 = w_base[22:0] -// byte10:11= n_tiles[15:0] -// byte12:14= result_addr[22:0] +// byte4:7 = x_base[25:0] (byte4 msb={6'b0,x_base[25:24]}) +// byte8:11 = w_base[25:0] +// byte12:13= n_tiles[15:0] +// byte14:17= result_addr[25:0] // reg_valid is asserted and HELD until the // cycle reg_ready also reads 1 (same-cycle // valid&&ready acceptance, matching @@ -46,31 +48,42 @@ // bit1 = mem_busy (WRITE_MEM/READ_MEM waiting on mem_ready) // bit2 = last_job_accepted (sticky, cleared by next WRITE_JOB) // bits[7:3] = 0 (reserved) -// 0x01 WRITE_MEM -- 5 header bytes + 2*len_words payload bytes: -// byte0:2 = addr[22:0] (WORD address, matches +// 0x01 WRITE_MEM -- 6 header bytes (widened from 5) + 2*len_words +// payload bytes: +// byte0:3 = addr[25:0] (WORD address, matches // sdram_unified_backend's AR port -// convention -- NOT a byte address) -// byte3:4 = len_words[15:0] (number of 16-bit +// convention -- NOT a byte address; +// byte0 msb={6'b0,addr[25:24]}) +// byte4:5 = len_words[15:0] (number of 16-bit // words to write, len_words>=1) // then len_words * 2 bytes of data, MSB-first // per word; each word is written via one // mem_req/mem_ready handshake (lb_n=ub_n=0, // full 16-bit write) before the next word's // bytes are accepted. -// 0x02 READ_MEM -- 5 header bytes (addr + len_words, same shape +// 0x02 READ_MEM -- 6 header bytes (addr + len_words, same shape // as WRITE_MEM), 0 further MOSI payload; the // 2*len_words response bytes are clocked out -// on MISO starting at payload byte 6, MSB- +// on MISO starting at payload byte 7, MSB- // first per word, one mem_req/mem_ready // read per word. // +// ADDRESS WIDTH (post-PRE-PCB-FREEZE memory upgrade): ADDR_WIDTH grew +// from 23 to 26 bits (SDRAM capacity upgrade, AS4C4M16SA-6TIN 8MB -> +// AS4C32M16SA-7TIN 64MB -- see sdram_controller.v's own header). A +// 26-bit address no longer fits in 3 bytes (24 bits) with a spare +// reserved bit the way the old 23-bit address did -- every address +// field below therefore widened from 3 to 4 bytes (6 reserved bits in +// the new top byte instead of 1), growing WRITE_JOB from 15 to 18 +// payload bytes and the WRITE_MEM/READ_MEM header from 5 to 6 bytes. +// // Any opcode byte not listed above is treated as NOP (0 payload, // MISO drives 0x00) -- matches spi_engine.v's own "unknown opcode is // inert, never wedges the bus" precedent. // ================================================================ module spi_host_bridge #( - parameter ADDR_WIDTH = 23, + parameter ADDR_WIDTH = 26, parameter N_NODES = 16, parameter MAX_DEPS = 4 )( @@ -226,9 +239,9 @@ module spi_host_bridge #( localparam OP_STATUS = 8'h20; localparam ST_OPCODE = 4'd0; - localparam ST_JOB = 4'd1; // collecting 15 WRITE_JOB payload bytes + localparam ST_JOB = 4'd1; // collecting 18 WRITE_JOB payload bytes localparam ST_JOB_WAIT= 4'd2; // reg_valid held, waiting reg_ready - localparam ST_MEM_ADDR= 4'd3; // collecting 3 addr bytes + localparam ST_MEM_ADDR= 4'd3; // collecting 4 addr bytes localparam ST_MEM_LEN = 4'd4; // collecting 2 length bytes localparam ST_MEM_WD = 4'd5; // WRITE_MEM: collecting 2 data bytes/word localparam ST_MEM_WISS= 4'd6; // WRITE_MEM: issue+wait mem_req @@ -238,7 +251,7 @@ module spi_host_bridge #( reg [3:0] state; reg [7:0] opcode; - reg [3:0] byte_idx; // generic byte counter within a field + reg [4:0] byte_idx; // generic byte counter within a field (up to 17, WRITE_JOB) reg [15:0] len_words; reg [15:0] word_cnt; reg [15:0] cur_word; // WRITE_MEM: assembling MSB,LSB; READ_MEM: holding readback @@ -252,13 +265,13 @@ module spi_host_bridge #( if (opcode == OP_STATUS) tx_mux = {5'b0, last_job_accepted_r, mem_busy_r, job_busy_r}; else if (opcode == OP_READ_MEM && state == ST_MEM_ROUT) - tx_mux = (byte_idx == 4'd0) ? cur_word[15:8] : cur_word[7:0]; + tx_mux = (byte_idx == 5'd0) ? cur_word[15:8] : cur_word[7:0]; end assign tx_byte = tx_mux; always @(posedge clk) begin if (rst) begin - state <= ST_OPCODE; opcode <= 8'h00; byte_idx <= 4'd0; + state <= ST_OPCODE; opcode <= 8'h00; byte_idx <= 5'd0; len_words <= 16'd0; word_cnt <= 16'd0; cur_word <= 16'd0; reg_valid <= 1'b0; reg_node_id <= {NODEW{1'b0}}; reg_required <= {REQW{1'b0}}; reg_producer_ids <= {(MAX_DEPS*NODEW){1'b0}}; @@ -292,12 +305,12 @@ module spi_host_bridge #( // to cs_rose below. if (cs_fell && state != ST_JOB_WAIT && state != ST_MEM_WISS && state != ST_MEM_RISS) begin state <= ST_OPCODE; - byte_idx <= 4'd0; + byte_idx <= 5'd0; end else if (!cs_fell && rx_valid) begin case (state) ST_OPCODE: begin opcode <= rx_byte; - byte_idx <= 4'd0; + byte_idx <= 5'd0; case (rx_byte) OP_WRITE_JOB: state <= ST_JOB; OP_WRITE_MEM: state <= ST_MEM_ADDR; @@ -309,59 +322,63 @@ module spi_host_bridge #( ST_JOB: begin case (byte_idx) - 4'd0: reg_node_id <= rx_byte[NODEW-1:0]; - 4'd1: reg_required <= rx_byte[REQW-1:0]; - 4'd2: reg_producer_ids[15:8] <= rx_byte; - 4'd3: reg_producer_ids[7:0] <= rx_byte; - 4'd4: reg_x_base[22:16] <= rx_byte[6:0]; - 4'd5: reg_x_base[15:8] <= rx_byte; - 4'd6: reg_x_base[7:0] <= rx_byte; - 4'd7: reg_w_base[22:16] <= rx_byte[6:0]; - 4'd8: reg_w_base[15:8] <= rx_byte; - 4'd9: reg_w_base[7:0] <= rx_byte; - 4'd10: reg_n_tiles[15:8] <= rx_byte; - 4'd11: reg_n_tiles[7:0] <= rx_byte; - 4'd12: reg_result_addr[22:16] <= rx_byte[6:0]; - 4'd13: reg_result_addr[15:8] <= rx_byte; - 4'd14: begin + 5'd0: reg_node_id <= rx_byte[NODEW-1:0]; + 5'd1: reg_required <= rx_byte[REQW-1:0]; + 5'd2: reg_producer_ids[15:8] <= rx_byte; + 5'd3: reg_producer_ids[7:0] <= rx_byte; + 5'd4: reg_x_base[25:24] <= rx_byte[1:0]; + 5'd5: reg_x_base[23:16] <= rx_byte; + 5'd6: reg_x_base[15:8] <= rx_byte; + 5'd7: reg_x_base[7:0] <= rx_byte; + 5'd8: reg_w_base[25:24] <= rx_byte[1:0]; + 5'd9: reg_w_base[23:16] <= rx_byte; + 5'd10: reg_w_base[15:8] <= rx_byte; + 5'd11: reg_w_base[7:0] <= rx_byte; + 5'd12: reg_n_tiles[15:8] <= rx_byte; + 5'd13: reg_n_tiles[7:0] <= rx_byte; + 5'd14: reg_result_addr[25:24] <= rx_byte[1:0]; + 5'd15: reg_result_addr[23:16] <= rx_byte; + 5'd16: reg_result_addr[15:8] <= rx_byte; + 5'd17: begin reg_result_addr[7:0] <= rx_byte; reg_valid <= 1'b1; last_job_accepted_r <= 1'b0; state <= ST_JOB_WAIT; end endcase - if (byte_idx != 4'd14) byte_idx <= byte_idx + 4'd1; + if (byte_idx != 5'd17) byte_idx <= byte_idx + 5'd1; end ST_MEM_ADDR: begin case (byte_idx) - 4'd0: mem_addr[22:16] <= rx_byte[6:0]; - 4'd1: mem_addr[15:8] <= rx_byte; - 4'd2: begin + 5'd0: mem_addr[25:24] <= rx_byte[1:0]; + 5'd1: mem_addr[23:16] <= rx_byte; + 5'd2: mem_addr[15:8] <= rx_byte; + 5'd3: begin mem_addr[7:0] <= rx_byte; state <= ST_MEM_LEN; end endcase - if (byte_idx != 4'd2) byte_idx <= byte_idx + 4'd1; - else byte_idx <= 4'd0; + if (byte_idx != 5'd3) byte_idx <= byte_idx + 5'd1; + else byte_idx <= 5'd0; end ST_MEM_LEN: begin - if (byte_idx == 4'd0) begin + if (byte_idx == 5'd0) begin len_words[15:8] <= rx_byte; - byte_idx <= 4'd1; + byte_idx <= 5'd1; end else begin len_words[7:0] <= rx_byte; word_cnt <= {len_words[15:8], rx_byte}; - byte_idx <= 4'd0; + byte_idx <= 5'd0; state <= (opcode == OP_WRITE_MEM) ? ST_MEM_WD : ST_MEM_RISS; end end ST_MEM_WD: begin - if (byte_idx == 4'd0) begin + if (byte_idx == 5'd0) begin cur_word[15:8] <= rx_byte; - byte_idx <= 4'd1; + byte_idx <= 5'd1; end else begin cur_word[7:0] <= rx_byte; state <= ST_MEM_WISS; @@ -390,7 +407,7 @@ module spi_host_bridge #( mem_busy_r <= 1'b0; mem_addr <= mem_addr + 1'b1; word_cnt <= word_cnt - 1'b1; - byte_idx <= 4'd0; + byte_idx <= 5'd0; state <= (word_cnt == 16'd1) ? ST_IGNORE : ST_MEM_WD; end @@ -403,7 +420,7 @@ module spi_host_bridge #( end else if (state == ST_MEM_RISS && mem_busy_r && mem_ready) begin mem_busy_r <= 1'b0; cur_word <= mem_rdata; - byte_idx <= 4'd0; + byte_idx <= 5'd0; state <= ST_MEM_ROUT; end if (state == ST_MEM_ROUT && rx_valid) begin @@ -412,12 +429,12 @@ module spi_host_bridge #( // it is also the correct "advance" event for MISO-side // bookkeeping (mirrors spi_slave's own documented // rx_valid-drives-advancement convention). - if (byte_idx == 4'd0) begin - byte_idx <= 4'd1; + if (byte_idx == 5'd0) begin + byte_idx <= 5'd1; end else begin mem_addr <= mem_addr + 1'b1; word_cnt <= word_cnt - 1'b1; - byte_idx <= 4'd0; + byte_idx <= 5'd0; state <= (word_cnt == 16'd1) ? ST_IGNORE : ST_MEM_RISS; end end diff --git a/hardware/v2/sim/tb_spi_host_bridge.v b/hardware/v2/sim/tb_spi_host_bridge.v index 22fbb30..ee394fc 100644 --- a/hardware/v2/sim/tb_spi_host_bridge.v +++ b/hardware/v2/sim/tb_spi_host_bridge.v @@ -20,7 +20,7 @@ module tb_spi_host_bridge; - localparam ADDR_WIDTH = 23; + localparam ADDR_WIDTH = 26; // AS4C32M16SA memory upgrade: 25-bit word address + 1 byte-select bit localparam N_NODES = 16; localparam MAX_DEPS = 4; localparam NODEW = $clog2(N_NODES); @@ -135,15 +135,18 @@ module tb_spi_host_bridge; spi_byte(8'h02, rxb); // required=2 spi_byte(8'hAB, rxb); // producer_ids[15:8] spi_byte(8'hCD, rxb); // producer_ids[7:0] - spi_byte(8'h00, rxb); // x_base[22:16] + spi_byte(8'h00, rxb); // x_base[25:24] + spi_byte(8'h00, rxb); // x_base[23:16] spi_byte(8'h10, rxb); // x_base[15:8] spi_byte(8'h00, rxb); // x_base[7:0] -> x_base=0x001000 - spi_byte(8'h00, rxb); // w_base[22:16] + spi_byte(8'h00, rxb); // w_base[25:24] + spi_byte(8'h00, rxb); // w_base[23:16] spi_byte(8'h20, rxb); // w_base[15:8] spi_byte(8'h00, rxb); // w_base[7:0] -> w_base=0x002000 spi_byte(8'h00, rxb); // n_tiles[15:8] spi_byte(8'h04, rxb); // n_tiles[7:0] -> n_tiles=4 - spi_byte(8'h00, rxb); // result_addr[22:16] + spi_byte(8'h00, rxb); // result_addr[25:24] + spi_byte(8'h00, rxb); // result_addr[23:16] spi_byte(8'h30, rxb); // result_addr[15:8] spi_byte(8'h00, rxb); // result_addr[7:0] -> result_addr=0x003000 @@ -154,10 +157,10 @@ module tb_spi_host_bridge; check(reg_node_id == 5, "A: reg_node_id"); check(reg_required == 2, "A: reg_required"); check(reg_producer_ids == 16'hABCD, "A: reg_producer_ids"); - check(reg_x_base == 23'h001000, "A: reg_x_base"); - check(reg_w_base == 23'h002000, "A: reg_w_base"); + check(reg_x_base == 26'h001000, "A: reg_x_base"); + check(reg_w_base == 26'h002000, "A: reg_w_base"); check(reg_n_tiles == 16'h0004, "A: reg_n_tiles"); - check(reg_result_addr == 23'h003000, "A: reg_result_addr"); + check(reg_result_addr == 26'h003000, "A: reg_result_addr"); repeat (3) begin @(posedge clk); @@ -182,7 +185,7 @@ module tb_spi_host_bridge; // ================= Test C: WRITE_MEM, single word =========== cs_n = 0; #20; spi_byte(8'h01, rxb); // opcode WRITE_MEM - spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h55, rxb); // addr=0x000055 + spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h55, rxb); // addr=0x000055 spi_byte(8'h00, rxb); spi_byte(8'h01, rxb); // len_words=1 spi_byte(8'h12, rxb); spi_byte(8'h34, rxb); // data=0x1234 // hold CS low, idle SCLK, while the memory model latency elapses @@ -193,7 +196,7 @@ module tb_spi_host_bridge; // ================= Test D: READ_MEM, single word ============= cs_n = 0; #20; spi_byte(8'h02, rxb); // opcode READ_MEM - spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h55, rxb); // addr=0x000055 + spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h00, rxb); spi_byte(8'h55, rxb); // addr=0x000055 spi_byte(8'h00, rxb); spi_byte(8'h01, rxb); // len_words=1 #200; // idle SCLK while the read latency elapses spi_byte(8'h00, rxb); exp_addr = rxb; // MSB