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
152 lines
6.2 KiB
Verilog
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
|