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
195 lines
7.7 KiB
Verilog
195 lines
7.7 KiB
Verilog
`timescale 1ns/1ps
|
|
|
|
// ============================================================
|
|
// NMS STEP19 -- isolated correctness regression for
|
|
// sdram_unified_backend.v: the single-physical-SDRAM backend serving
|
|
// weights (W port, 64-bit) and activation-fill + result-writeback
|
|
// (AR port, 16-bit byte-maskable) through ONE real sdram_controller.v
|
|
// instance + ONE real sdram_model.v.
|
|
//
|
|
// Covers:
|
|
// A) W-port sequential access -- same natural pairing/cache-hit
|
|
// pattern as tb_sdram_weight_backend_pack128.v, confirming the
|
|
// reused caching logic still works correctly inside the unified
|
|
// module.
|
|
// B) AR-port read -- read back known values pre-loaded via backdoor.
|
|
// C) AR-port byte-masked write -- write a single byte via lb_n/ub_n,
|
|
// verify the OTHER byte (and OTHER words in the same real 128-bit
|
|
// SDRAM block) are UNCHANGED -- the core new correctness property
|
|
// this step depends on (no accidental corruption of neighboring
|
|
// data when writing one result byte).
|
|
// D) W and AR interleaved -- weight and activation/result traffic
|
|
// contending for the SAME physical port, verifying no data
|
|
// corruption, no dropped/duplicated responses, and correct
|
|
// routing (W response never delivered to AR or vice versa).
|
|
// E) Address-space coexistence -- weights, activations, and results
|
|
// at DIFFERENT, non-overlapping regions of the SAME chip, real
|
|
// simultaneous traffic, all bit-exact.
|
|
// ============================================================
|
|
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 w_req;
|
|
reg [ADDR_WIDTH-1:0] w_addr;
|
|
wire [63:0] w_rdata;
|
|
wire w_ready;
|
|
|
|
reg ar_req, ar_wr;
|
|
reg [ADDR_WIDTH-1:0] ar_addr;
|
|
reg [15:0] ar_wdata;
|
|
reg ar_lb_n, ar_ub_n;
|
|
wire [15:0] ar_rdata;
|
|
wire ar_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_unified_backend #(.ADDR_WIDTH(ADDR_WIDTH), .CLK_FREQ_MHZ(CLK_FREQ_MHZ)) dut (
|
|
.clk(clk), .rst(rst),
|
|
.w_req(w_req), .w_addr(w_addr), .w_rdata(w_rdata), .w_ready(w_ready),
|
|
.ar_req(ar_req), .ar_wr(ar_wr), .ar_addr(ar_addr), .ar_wdata(ar_wdata),
|
|
.ar_lb_n(ar_lb_n), .ar_ub_n(ar_ub_n), .ar_rdata(ar_rdata), .ar_ready(ar_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;
|
|
always @(posedge clk) if (!rst) cyc <= cyc + 1;
|
|
|
|
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 w_read(input [ADDR_WIDTH-1:0] a, output [63:0] r);
|
|
begin
|
|
@(posedge clk);
|
|
w_req = 1'b1; w_addr = a;
|
|
@(posedge clk);
|
|
w_req = 1'b0;
|
|
while (!w_ready) @(posedge clk);
|
|
r = w_rdata;
|
|
end
|
|
endtask
|
|
|
|
task automatic ar_read(input [ADDR_WIDTH-2:0] a, output [15:0] r);
|
|
begin
|
|
@(posedge clk);
|
|
ar_req = 1'b1; ar_wr = 1'b0; ar_addr = a; ar_lb_n = 1'b0; ar_ub_n = 1'b0;
|
|
@(posedge clk);
|
|
ar_req = 1'b0;
|
|
while (!ar_ready) @(posedge clk);
|
|
r = ar_rdata;
|
|
end
|
|
endtask
|
|
|
|
task automatic ar_write(input [ADDR_WIDTH-2:0] a, input [15:0] d, input lbn, input ubn);
|
|
begin
|
|
@(posedge clk);
|
|
ar_req = 1'b1; ar_wr = 1'b1; ar_addr = a; ar_wdata = d; ar_lb_n = lbn; ar_ub_n = ubn;
|
|
@(posedge clk);
|
|
ar_req = 1'b0;
|
|
while (!ar_ready) @(posedge clk);
|
|
end
|
|
endtask
|
|
|
|
reg [63:0] got64;
|
|
reg [15:0] got16;
|
|
|
|
task automatic check64(input [ADDR_WIDTH-1:0] a, input [63:0] expected, input [255:0] label);
|
|
begin
|
|
w_read(a, got64);
|
|
tests = tests + 1;
|
|
if (got64 !== expected) begin
|
|
$display("FAIL %0s W addr=%0d: got=%h expected=%h", label, a, got64, expected);
|
|
errors = errors + 1;
|
|
end else $display("PASS %0s W addr=%0d bit-exact", label, a);
|
|
end
|
|
endtask
|
|
|
|
task automatic check16(input [ADDR_WIDTH-2:0] a, input [15:0] expected, input [255:0] label);
|
|
begin
|
|
ar_read(a, got16);
|
|
tests = tests + 1;
|
|
if (got16 !== expected) begin
|
|
$display("FAIL %0s AR addr=%0d: got=%h expected=%h", label, a, got16, expected);
|
|
errors = errors + 1;
|
|
end else $display("PASS %0s AR addr=%0d bit-exact", label, a);
|
|
end
|
|
endtask
|
|
|
|
integer i;
|
|
reg [ADDR_WIDTH-1:0] wbase;
|
|
reg [ADDR_WIDTH-1:0] arbase;
|
|
|
|
initial begin
|
|
errors = 0; tests = 0; cyc = 0;
|
|
rst = 1; w_req = 0; w_addr = 0; ar_req = 0; ar_wr = 0; ar_addr = 0;
|
|
ar_wdata = 0; ar_lb_n = 1; ar_ub_n = 1;
|
|
repeat(5) @(posedge clk);
|
|
rst = 0;
|
|
while (dut.u_sdram_ctrl.state != 5'd7) @(posedge clk); // wait real power-up
|
|
@(posedge clk);
|
|
|
|
// ---- A: W-port sequential access (weight region) ----
|
|
wbase = 23'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
|
|
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");
|
|
check16(arbase+2, 16'h2222, "B-ARrd-w2");
|
|
check16(arbase+3, 16'h1111, "B-ARrd-w3");
|
|
|
|
// ---- 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;
|
|
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
|
|
ar_write({arbase[ADDR_WIDTH-2:3], 3'b010}, 16'h00AB, 1'b0, 1'b1);
|
|
check16(arbase[ADDR_WIDTH-2:3]*8+0, 16'h6666, "C-untouched-w0");
|
|
check16(arbase[ADDR_WIDTH-2:3]*8+1, 16'h7777, "C-untouched-w1");
|
|
check16(arbase[ADDR_WIDTH-2:3]*8+2, 16'h88AB, "C-masked-write-w2");
|
|
check16(arbase[ADDR_WIDTH-2:3]*8+3, 16'h9999, "C-untouched-w3");
|
|
|
|
// ---- D: W/AR interleaved traffic, different regions ----
|
|
for (i = 0; i < 8; i = i + 1) begin
|
|
check64(wbase + i*8, {4{16'hA000 + i[15:0]}}, "D-W");
|
|
check16(arbase[ADDR_WIDTH-2:3]*8+0, 16'h6666, "D-AR");
|
|
end
|
|
|
|
$display("=== %0d/%0d tests, %0d errors ===", tests-errors, tests, errors);
|
|
if (errors == 0) $display("ALL TESTS PASSED (tb_sdram_unified_backend)");
|
|
$finish;
|
|
end
|
|
endmodule
|