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
+2 -2
View File
@@ -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,
@@ -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;
@@ -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,
@@ -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),
@@ -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,
+125 -61
View File
@@ -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;
+25 -10
View File
@@ -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;
@@ -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
+42 -30
View File
@@ -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
@@ -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);
@@ -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)
+42 -30
View File
@@ -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) ===",
+33 -21
View File
@@ -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}};
@@ -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