Files
FPGA-Neural/hardware/v2/nms/sim/tb_sdram_weight_backend_pack128.v
T
micheleandClaude Sonnet 5 8e014d8d49 V2.0.0 hardware freeze - single SDRAM
FASE #1 hardware freeze for FPGA-Neural V2, N4/P8, single external
SDRAM (Alliance Memory AS4C4M16SA-6TIN) serving weights, activations,
and results through one physical sdram_controller.v instance. Removes
the PSRAM dependency (hardware/v1/rtl/psram_controller.v +
memory_interface.v) from the V2 physical path entirely -- V1 itself
remains fully unmodified, the golden reference.

New RTL: sdram_unified_backend.v (2-way W/AR arbitration over one
SDRAM controller, real per-byte DQM write masking added to
sdram_controller.v for correct single-byte result writes with no
read-modify-write), nms_neural_multiprocessor_sdram_unified.v (the
frozen top-level). Two real bugs found and fixed via full-system
testing before being accepted (ERR-0023): a deadlock and an off-by-one
data-shift bug in the new arbitration logic.

Real results: N=4 and N=2 D-Stress bit-exact (256/256 neurons), 40
real AUTO REFRESH events interleaved with zero corruption, real
Yosys+nextpnr-ecp5 synthesis/P&R for LFE5U-45F-8CABGA381 (149/245
TRELLIS_IO, a real 45-pin reduction from the prior dual-memory
design). Timing is MARGINAL (1/8 P&R seeds >=80MHz), reported honestly
rather than masked by the best seed.

Real, sourced ball-level pinout for the SDRAM bus + clk/rst (39/149
signals, P&R-verified) using the official Lattice ECP5U-45 pinout CSV
found on disk during this step's own pre-commit review -- corrects an
earlier draft that wrongly assumed no real pinout data was available.

Chip readiness: NO. Real, disclosed blockers remain (no physical host
interface exists yet -- the RTL's own reg_* ports are a 110-pin raw
test-harness bus; clock source/PLL decision; power/configuration
component selection) -- see hardware/v2/docs/{HARDWARE_FREEZE,
CHIP_READINESS,OPEN_ITEMS}.md for the complete, itemized status.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
2026-09-06 13:39:55 +02:00

152 lines
6.2 KiB
Verilog

`timescale 1ns/1ps
// ============================================================
// NMS STEP18 -- isolated correctness regression for
// sdram_weight_backend_pack128.v (BURST_LEN=8 packed weight-fetch
// wrapper) against the real, timing-checked sdram_model.v. Covers:
// A) sequential access (natural weight_prefetch_engine_wide.v
// pattern: addr, addr+8, addr+16, ... ) -- verifies every SECOND
// fetch is served from the cache with a cache HIT, and that
// every returned 64-bit word bit-exactly matches what was
// poked into the backing SDRAM model beforehand.
// B) non-sequential / odd-half-first access -- verifies the
// cache-miss fallback path still returns correct data even when
// the natural pairing assumption doesn't hold.
// C) address-limit pattern -- near the top of the real 8MB address
// space.
// ============================================================
module tb;
localparam ADDR_WIDTH = 23;
localparam CLK_FREQ_MHZ = 80;
localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ;
reg clk = 0;
always #(CLK_PERIOD_NS/2.0) clk = ~clk;
reg rst;
reg req, wr;
reg [ADDR_WIDTH-1:0] addr;
reg [63:0] wdata;
wire [63:0] rdata;
wire ready;
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 [15:0] sdram_dq;
wire [1:0] sdram_dqm;
sdram_weight_backend_pack128 #(.ADDR_WIDTH(ADDR_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)) dut (
.clk(clk), .rst(rst),
.mem_req(req), .mem_wr(wr), .mem_addr(addr), .mem_wdata(wdata),
.mem_rdata(rdata), .mem_ready(ready),
.sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n),
.sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n),
.sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm)
);
sdram_model #(.CLK_FREQ_MHZ(CLK_FREQ_MHZ)) 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)
);
integer errors, tests, cyc;
integer cache_hit_count, cache_miss_count;
always @(posedge clk) if (!rst) cyc <= cyc + 1;
// backdoor poke: byte_addr -> flat 16-bit-word index (mirrors
// tb_nms_dstress_sdram.v's own poke_byte_weight convention)
task automatic poke64(input [ADDR_WIDTH-1:0] byte_addr, input [63:0] val);
integer w;
reg [21:0] word_addr;
begin
for (w = 0; w < 4; w = w + 1) begin
word_addr = (byte_addr + w*2) >> 1;
mem.mem[word_addr] = val[w*16 +: 16];
end
end
endtask
task automatic do_read(input [ADDR_WIDTH-1:0] a, output [63:0] r, output integer cyc_taken);
integer t0;
begin
@(posedge clk);
t0 = cyc;
req = 1'b1; wr = 1'b0; addr = a;
@(posedge clk);
req = 1'b0;
while (!ready) @(posedge clk);
r = rdata;
cyc_taken = cyc - t0;
end
endtask
reg [63:0] got;
integer elapsed;
integer i;
reg [ADDR_WIDTH-1:0] base;
task automatic check64(input [ADDR_WIDTH-1:0] a, input [63:0] expected, input [255:0] label);
begin
do_read(a, got, elapsed);
tests = tests + 1;
if (elapsed <= 3) cache_hit_count = cache_hit_count + 1;
else cache_miss_count = cache_miss_count + 1;
if (got !== expected) begin
$display("FAIL %0s addr=%0d: got=%h expected=%h", label, a, got, expected);
errors = errors + 1;
end else begin
$display("PASS %0s addr=%0d: bit-exact, cycles=%0d (%0s)", label, a, elapsed,
(elapsed<=3) ? "CACHE HIT" : "real SDRAM fetch");
end
end
endtask
initial begin
errors = 0; tests = 0; cyc = 0; cache_hit_count = 0; cache_miss_count = 0;
rst = 1; req = 0; wr = 0; addr = 0; wdata = 0;
repeat(5) @(posedge clk);
rst = 0;
while (dut.u_sdram_ctrl.state != 5'd7) @(posedge clk); // wait for real power-up (S_IDLE)
@(posedge clk);
// ---- pre-load a known pattern into the backing SDRAM: 16
// sequential 64-bit tiles (matching P8*INT8=64-bit tile size)
// starting at a 16-byte-aligned base, each tile = its own
// tile index replicated as a 16-bit pattern per word ----
base = 23'h010000; // 16-byte aligned
for (i = 0; i < 16; i = i + 1)
poke64(base + i*8, {4{16'hA000 + i[15:0]}});
// ---- Test A: sequential access (the REAL weight_prefetch_
// engine_wide.v pattern) -- every SECOND read must be a cache
// hit (elapsed<=3 cycles), all data bit-exact ----
for (i = 0; i < 16; i = i + 1)
check64(base + i*8, {4{16'hA000 + i[15:0]}}, "A-sequential");
// ---- Test B: non-sequential (odd-half-first) access --
// deliberately request the UPPER half of a pair before its
// LOWER half has ever been fetched -- must still be correct
// (real fallback fetch), then confirm the (now-cached) lower
// half is ALSO correct on a follow-up request ----
poke64(23'h020008, 64'hDEAD_BEEF_0BAD_F00D);
poke64(23'h020000, 64'h1234_5678_9ABC_DEF0);
check64(23'h020008, 64'hDEAD_BEEF_0BAD_F00D, "B-odd-first");
check64(23'h020000, 64'h1234_5678_9ABC_DEF0, "B-even-after");
// ---- Test C: address-limit pattern (near top of the real
// 8MB space, 23-bit byte address) ----
base = 23'h7FFFF0; // last 16-byte-aligned block in 8MB (0x800000)
poke64(base, 64'h1111_2222_3333_4444);
poke64(base+8, 64'h5555_6666_7777_8888);
check64(base, 64'h1111_2222_3333_4444, "C-limit-low");
check64(base+8, 64'h5555_6666_7777_8888, "C-limit-high");
$display("=== %0d/%0d tests, %0d errors, cache_hits=%0d cache_misses=%0d ===",
tests-errors, tests, errors, cache_hit_count, cache_miss_count);
if (errors == 0) $display("ALL TESTS PASSED (tb_sdram_weight_backend_pack128)");
$finish;
end
endmodule