Files
FPGA-Neural/sim/spi_neuron_top_flash_tb.v
T
micheleandClaude Sonnet 5 b029e3d95a fix: make flash SPI bus electrically independent, drop USRMCLK/CCLK reuse (Phase F7)
The flash subsystem's SCLK previously reused the boot config-SPI's CCLK
pad via the ECP5 USRMCLK primitive to save one pin. This made the
"exclusive flash bus" claim misleading (SCLK still depended on the
config engine's own pad electrically) and carried an unresolved
verification gap (USRMCLKTS pad-enable timing never checked against
the primary Lattice sysCONFIG Usage Guide).

flash_sclk is now a genuine 4th ordinary GPIO pin (E3, bank 7), added
purely additively to the real .lpf (git diff: one new line, no existing
ball moved). The flash bus is now 4 fully independent wires
(sclk/mosi/miso/cs_n), zero pins shared with any ECP5 config primitive
-- confirmed by the full-system synthesis reporting USRMCLK 0/1 (0%)
utilisation.

All 33 project testbenches re-run clean after the port rename (no
functional change, only sclk_sim -> sclk). Full-system real synthesis
re-verified: 0 constraint errors, Fmax 67.91MHz (up slightly from
66.68MHz, same critical path, not a regression).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
2026-09-04 10:16:31 +02:00

681 lines
27 KiB
Verilog

`timescale 1ns/1ps
// ================================================================
// SPI_NEURON_TOP FLASH-SUBSYSTEM END-TO-END TESTBENCH (Phase F5)
//
// Same real-stack BFM style as sim/spi_neuron_top_graph_tb.v (SPI
// master bit-banging tasks copied verbatim from there), driving the
// FULL integrated top level -- spi_slave + spi_engine + graph_engine
// + flash_slot_manager (-> flash_copy_engine -> spi_flash_master) +
// mem_arbiter + int8_memory_access + memory_interface +
// psram_controller + psram_model + flash_model -- purely over
// simulated host SPI and simulated flash SPI.
//
// TEST 1: FLASH_ERASE + FLASH_WRITE_BLOCK + FLASH_READ_BLOCK
// opcode-level smoke test over real SPI framing (deep
// correctness of these primitives already covered by
// sim/flash_copy_engine_erase_tb.v and
// sim/flash_slot_manager_raw_tb.v -- this proves the SPI byte
// framing in spi_engine.v decodes them correctly, which those
// module-level tests cannot).
// TEST 2: CAT_WRITE_SLOT + CAT_READ + CAT_INSPECT over real SPI.
// TEST 3 (adversarial §A.3): LOAD_SLOT on a never-saved slot ->
// STATUS.bit3 (flash_err) observed over real SPI.
// TEST 4 (the phase-plan's explicit §6 end-to-end requirement):
// netasm -> WRITE_RAM -> SAVE_SLOT -> (PSRAM region overwritten
// with garbage, proving the reload is real) -> LOAD_SLOT ->
// RUN_NETWORK -> READ_RAM matches the hand-computed expected
// output (126), the SAME independently-derived value already
// used in sim/graph_engine_tb.v / sim/spi_neuron_top_graph_tb.v
// (spec §3's worked example, x=[10,1,4,0] -> n4=49, n5=126) --
// not re-derived here, reused as the existing independent
// oracle. The exact WRITE_RAM/SET_BASE/RUN_NETWORK byte sequence
// below was generated by netasm itself (not hand-typed):
// python3 tools/netasm/cli.py tools/netasm/examples/graph_example.netasm \
// -o /tmp/netasm_out/graph --table-base 0x000000 \
// --edges-base 0x000100 --x-base 0x000400 --out-base 0x000500
// (see that command's own .debug.txt output, quoted inline below
// at each step for traceability).
// ================================================================
module tb;
localparam ADDR_WIDTH = 23;
localparam DATA_WIDTH = 8;
localparam N_INPUTS = 4;
localparam N_NEURONS = 4;
localparam PARALLEL = 2;
localparam ACC_WIDTH = 32;
localparam MEM_DATA_WIDTH = 16;
localparam N_LAYERS = 4;
localparam GRAPH_MAX_CONN = 4;
localparam GRAPH_N_TOTAL = 4096;
localparam CLK_PERIOD = 12.5; // 80 MHz
reg clk;
reg rst;
initial begin
clk = 1'b0;
forever #(CLK_PERIOD / 2.0) clk = ~clk;
end
reg sclk;
reg mosi;
wire miso;
reg cs_n;
wire flash_mosi, flash_miso, flash_cs_n, flash_sclk;
wire [ADDR_WIDTH-1:0] psram_a;
wire [MEM_DATA_WIDTH-1:0] psram_dq;
wire psram_ce_n, psram_oe_n, psram_we_n, psram_lb_n, psram_ub_n, psram_zz_n;
spi_neuron_top #(
.ADDR_WIDTH(ADDR_WIDTH),
.DATA_WIDTH(DATA_WIDTH),
.N_INPUTS(N_INPUTS),
.N_NEURONS(N_NEURONS),
.PARALLEL(PARALLEL),
.ACC_WIDTH(ACC_WIDTH),
.MEM_DATA_WIDTH(MEM_DATA_WIDTH),
.CLK_FREQ_MHZ(80),
.N_LAYERS(N_LAYERS),
.GRAPH_MAX_CONN(GRAPH_MAX_CONN),
.GRAPH_N_TOTAL(GRAPH_N_TOTAL)
) dut (
.clk(clk), .rst(rst),
.sclk(sclk), .mosi(mosi), .miso(miso), .cs_n(cs_n),
.flash_mosi(flash_mosi), .flash_miso(flash_miso), .flash_cs_n(flash_cs_n),
.flash_sclk(flash_sclk),
.psram_a(psram_a), .psram_dq(psram_dq),
.psram_ce_n(psram_ce_n), .psram_oe_n(psram_oe_n), .psram_we_n(psram_we_n),
.psram_lb_n(psram_lb_n), .psram_ub_n(psram_ub_n), .psram_zz_n(psram_zz_n)
);
psram_model #(
.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(MEM_DATA_WIDTH), .DEPTH(16384)
) u_psram (
.clk(clk), .a(psram_a), .dq(psram_dq),
.ce_n(psram_ce_n), .oe_n(psram_oe_n), .we_n(psram_we_n),
.lb_n(psram_lb_n), .ub_n(psram_ub_n), .zz_n(psram_zz_n)
);
flash_model #(
.DEPTH(32'h0002_0000), .TIME_SCALE(100000)
) u_flash (
.sclk(flash_sclk), .mosi(flash_mosi), .miso(flash_miso), .cs_n(flash_cs_n)
);
// ============================================================
// SPI MASTER BFM (identical to sim/spi_neuron_top_graph_tb.v)
// ============================================================
task clk_wait;
input integer n;
integer k;
begin
for (k = 0; k < n; k = k + 1)
@(posedge clk);
end
endtask
task spi_begin;
input integer half_bit_cycles;
begin
cs_n = 1'b1;
sclk = 1'b0;
mosi = 1'b0;
clk_wait(half_bit_cycles * 2);
cs_n = 1'b0;
clk_wait(half_bit_cycles * 2);
end
endtask
task spi_end;
input integer half_bit_cycles;
begin
clk_wait(half_bit_cycles * 2);
cs_n = 1'b1;
clk_wait(half_bit_cycles * 2);
end
endtask
task spi_xfer_byte;
input [7:0] tx;
input integer half_bit_cycles;
output [7:0] rx;
integer i;
reg [7:0] rx_acc;
begin
rx_acc = 8'h00;
for (i = 7; i >= 0; i = i - 1) begin
mosi = tx[i];
clk_wait(half_bit_cycles);
sclk = 1'b1;
rx_acc[i] = miso;
clk_wait(half_bit_cycles);
sclk = 1'b0;
clk_wait(half_bit_cycles);
end
rx = rx_acc;
end
endtask
localparam HB_RAM = 40;
localparam HB_REG = 8;
reg [7:0] rx_tmp;
integer errors;
integer poll_count;
reg signed [7:0] payload [0:63];
reg signed [7:0] readback [0:63];
// ============================================================
// HELPER TASKS (existing opcodes, same convention as
// sim/spi_neuron_top_graph_tb.v)
// ============================================================
task do_reset;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h0F, HB_REG, rx_tmp);
spi_end(HB_REG);
end
endtask
task set_net_type;
input [7:0] t;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h11, HB_REG, rx_tmp);
spi_xfer_byte(t, HB_REG, rx_tmp);
spi_end(HB_REG);
end
endtask
task set_base;
input [7:0] sel;
input [ADDR_WIDTH-1:0] addr;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h10, HB_REG, rx_tmp);
spi_xfer_byte(sel, HB_REG, rx_tmp);
spi_xfer_byte(addr[23:16], HB_REG, rx_tmp);
spi_xfer_byte(addr[15:8], HB_REG, rx_tmp);
spi_xfer_byte(addr[7:0], HB_REG, rx_tmp);
spi_end(HB_REG);
end
endtask
task write_ram_bytes;
input [ADDR_WIDTH-1:0] addr;
input integer len;
integer k;
begin
spi_begin(HB_RAM);
spi_xfer_byte(8'h01, HB_RAM, rx_tmp);
spi_xfer_byte(addr[23:16], HB_RAM, rx_tmp);
spi_xfer_byte(addr[15:8], HB_RAM, rx_tmp);
spi_xfer_byte(addr[7:0], HB_RAM, rx_tmp);
spi_xfer_byte(len[15:8], HB_RAM, rx_tmp);
spi_xfer_byte(len[7:0], HB_RAM, rx_tmp);
for (k = 0; k < len; k = k + 1)
spi_xfer_byte(payload[k], HB_RAM, rx_tmp);
spi_end(HB_RAM);
end
endtask
task read_ram_bytes;
input [ADDR_WIDTH-1:0] addr;
input integer len;
integer k;
begin
spi_begin(HB_RAM);
spi_xfer_byte(8'h02, HB_RAM, rx_tmp);
spi_xfer_byte(addr[23:16], HB_RAM, rx_tmp);
spi_xfer_byte(addr[15:8], HB_RAM, rx_tmp);
spi_xfer_byte(addr[7:0], HB_RAM, rx_tmp);
spi_xfer_byte(len[15:8], HB_RAM, rx_tmp);
spi_xfer_byte(len[7:0], HB_RAM, rx_tmp);
for (k = 0; k < len; k = k + 1)
spi_xfer_byte(8'h00, HB_RAM, readback[k]);
spi_end(HB_RAM);
end
endtask
task read_status;
output [7:0] status;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h21, HB_REG, rx_tmp);
spi_xfer_byte(8'h00, HB_REG, status);
spi_end(HB_REG);
end
endtask
task run_network;
input [7:0] payload_byte;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h23, HB_REG, rx_tmp);
spi_xfer_byte(payload_byte, HB_REG, rx_tmp);
spi_end(HB_REG);
end
endtask
reg [7:0] last_status;
// Polls until STATUS.bit1(done) OR bit2(graph_err) OR bit3(flash_err)
// latches, or timeout -- covers both inference and flash completions.
task wait_done_or_err;
begin
poll_count = 0;
last_status = 8'h00;
while (!last_status[1] && !last_status[2] && !last_status[3] && poll_count < 200000) begin
clk_wait(20);
read_status(last_status);
poll_count = poll_count + 1;
end
end
endtask
task read_output_bytes;
input integer n;
integer k;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h22, HB_REG, rx_tmp);
for (k = 0; k < n; k = k + 1)
spi_xfer_byte(8'h00, HB_REG, readback[k]);
spi_end(HB_REG);
end
endtask
// ============================================================
// HELPER TASKS (new F5 flash opcodes)
// ============================================================
task flash_erase;
input [23:0] sector_addr;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h42, HB_REG, rx_tmp); // FLASH_ERASE
spi_xfer_byte(sector_addr[23:16], HB_REG, rx_tmp);
spi_xfer_byte(sector_addr[15:8], HB_REG, rx_tmp);
spi_xfer_byte(sector_addr[7:0], HB_REG, rx_tmp);
spi_end(HB_REG);
end
endtask
task flash_write_block;
input [ADDR_WIDTH-1:0] psram_addr;
input [23:0] flash_addr;
input [23:0] len;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h41, HB_REG, rx_tmp); // FLASH_WRITE_BLOCK
spi_xfer_byte(psram_addr[23:16], HB_REG, rx_tmp);
spi_xfer_byte(psram_addr[15:8], HB_REG, rx_tmp);
spi_xfer_byte(psram_addr[7:0], HB_REG, rx_tmp);
spi_xfer_byte(flash_addr[23:16], HB_REG, rx_tmp);
spi_xfer_byte(flash_addr[15:8], HB_REG, rx_tmp);
spi_xfer_byte(flash_addr[7:0], HB_REG, rx_tmp);
spi_xfer_byte(len[23:16], HB_REG, rx_tmp);
spi_xfer_byte(len[15:8], HB_REG, rx_tmp);
spi_xfer_byte(len[7:0], HB_REG, rx_tmp);
spi_end(HB_REG);
end
endtask
task flash_read_block;
input [23:0] flash_addr;
input [ADDR_WIDTH-1:0] psram_addr;
input [23:0] len;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h40, HB_REG, rx_tmp); // FLASH_READ_BLOCK
spi_xfer_byte(flash_addr[23:16], HB_REG, rx_tmp);
spi_xfer_byte(flash_addr[15:8], HB_REG, rx_tmp);
spi_xfer_byte(flash_addr[7:0], HB_REG, rx_tmp);
spi_xfer_byte(psram_addr[23:16], HB_REG, rx_tmp);
spi_xfer_byte(psram_addr[15:8], HB_REG, rx_tmp);
spi_xfer_byte(psram_addr[7:0], HB_REG, rx_tmp);
spi_xfer_byte(len[23:16], HB_REG, rx_tmp);
spi_xfer_byte(len[15:8], HB_REG, rx_tmp);
spi_xfer_byte(len[7:0], HB_REG, rx_tmp);
spi_end(HB_REG);
end
endtask
task cat_write_slot;
input [3:0] slot;
input [23:0] offset;
input [23:0] length;
input [7:0] slot_type;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h44, HB_REG, rx_tmp); // CAT_WRITE_SLOT
spi_xfer_byte({4'h0, slot}, HB_REG, rx_tmp);
spi_xfer_byte(offset[23:16], HB_REG, rx_tmp);
spi_xfer_byte(offset[15:8], HB_REG, rx_tmp);
spi_xfer_byte(offset[7:0], HB_REG, rx_tmp);
spi_xfer_byte(length[23:16], HB_REG, rx_tmp);
spi_xfer_byte(length[15:8], HB_REG, rx_tmp);
spi_xfer_byte(length[7:0], HB_REG, rx_tmp);
spi_xfer_byte(slot_type, HB_REG, rx_tmp);
spi_end(HB_REG);
end
endtask
task load_slot;
input [3:0] slot;
input [ADDR_WIDTH-1:0] psram_addr;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h45, HB_REG, rx_tmp); // LOAD_SLOT
spi_xfer_byte({4'h0, slot}, HB_REG, rx_tmp);
spi_xfer_byte(psram_addr[23:16], HB_REG, rx_tmp);
spi_xfer_byte(psram_addr[15:8], HB_REG, rx_tmp);
spi_xfer_byte(psram_addr[7:0], HB_REG, rx_tmp);
spi_end(HB_REG);
end
endtask
task save_slot;
input [3:0] slot;
input [ADDR_WIDTH-1:0] psram_addr;
input [23:0] length;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h46, HB_REG, rx_tmp); // SAVE_SLOT
spi_xfer_byte({4'h0, slot}, HB_REG, rx_tmp);
spi_xfer_byte(psram_addr[23:16], HB_REG, rx_tmp);
spi_xfer_byte(psram_addr[15:8], HB_REG, rx_tmp);
spi_xfer_byte(psram_addr[7:0], HB_REG, rx_tmp);
spi_xfer_byte(length[23:16], HB_REG, rx_tmp);
spi_xfer_byte(length[15:8], HB_REG, rx_tmp);
spi_xfer_byte(length[7:0], HB_REG, rx_tmp);
spi_end(HB_REG);
end
endtask
task cat_read;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h43, HB_REG, rx_tmp); // CAT_READ
spi_end(HB_REG);
end
endtask
task cat_inspect;
input [3:0] slot;
begin
spi_begin(HB_REG);
spi_xfer_byte(8'h47, HB_REG, rx_tmp); // CAT_INSPECT
spi_xfer_byte({4'h0, slot}, HB_REG, rx_tmp);
spi_xfer_byte(8'h00, HB_REG, readback[0]); // offset[23:16]
spi_xfer_byte(8'h00, HB_REG, readback[1]); // offset[15:8]
spi_xfer_byte(8'h00, HB_REG, readback[2]); // offset[7:0]
spi_xfer_byte(8'h00, HB_REG, readback[3]); // length[23:16]
spi_xfer_byte(8'h00, HB_REG, readback[4]); // length[15:8]
spi_xfer_byte(8'h00, HB_REG, readback[5]); // length[7:0]
spi_xfer_byte(8'h00, HB_REG, readback[6]); // type
spi_xfer_byte(8'h00, HB_REG, readback[7]); // valid
spi_xfer_byte(8'h00, HB_REG, readback[8]); // crc[31:24]
spi_xfer_byte(8'h00, HB_REG, readback[9]); // crc[23:16]
spi_xfer_byte(8'h00, HB_REG, readback[10]); // crc[15:8]
spi_xfer_byte(8'h00, HB_REG, readback[11]); // crc[7:0]
spi_end(HB_REG);
end
endtask
task check_byte;
input [255:0] label;
input signed [7:0] got, exp;
begin
if (got !== exp) begin
$display("FAIL: %0s got=%0d exp=%0d", label, got, exp);
errors = errors + 1;
end
end
endtask
integer i;
initial begin
errors = 0;
rst = 1'b1;
sclk = 1'b0; mosi = 1'b0; cs_n = 1'b1;
repeat (5) @(posedge clk);
rst = 1'b0;
// Wait for psram_controller's own power-up (STATE_INIT +
// STATE_CR_INIT, ~150us+ @ 80MHz) to fully complete before
// the FIRST WRITE_RAM/READ_RAM -- same established pattern
// sim/spi_neuron_top_graph_tb.v and friends already use
// (`wait (dut.u_psram_ctrl.state == ...STATE_IDLE)`), just
// not one this file had copied initially.
//
// REAL FINDING (see WORKLOG.md's F5 entry): WRITE_RAM/
// READ_RAM have NO backpressure to the SPI master (documented
// as a known "v1 limitation" in spi_engine.v's own header --
// predates this session). Skipping this wait does not hang or
// error -- it SILENTLY CORRUPTS DATA: the host's un-
// backpressured SPI clocking drifts ahead of spi_engine while
// spi_engine is stuck waiting the FULL ~150us for the very
// first PSRAM access to complete, and bytes received during
// that wait are dropped without any error signaled. Verified
// directly with a minimal WRITE_RAM-only reproduction with NO
// flash opcodes involved at all -- this is a general PSRAM
// access hazard, not specific to the flash subsystem, but
// real host software (and every testbench touching PSRAM)
// MUST account for it explicitly.
wait (dut.u_psram_ctrl.state == dut.u_psram_ctrl.STATE_IDLE);
do_reset;
// ========================================================
// TEST 1: FLASH_ERASE + FLASH_WRITE_BLOCK + FLASH_READ_BLOCK
// ========================================================
$display("--- TEST 1 starting ---");
flash_erase(24'h010000);
wait_done_or_err;
if (last_status[3]) begin $display("FAIL: TEST1 erase unexpected flash_err"); errors = errors + 1; end
for (i = 0; i < 16; i = i + 1)
payload[i] = 8'h40 + i;
write_ram_bytes(23'h000800, 16);
flash_write_block(23'h000800, 24'h010000, 24'd16);
wait_done_or_err;
if (last_status[3]) begin $display("FAIL: TEST1 write_block unexpected flash_err"); errors = errors + 1; end
flash_read_block(24'h010000, 23'h000900, 24'd16);
wait_done_or_err;
if (last_status[3]) begin $display("FAIL: TEST1 read_block unexpected flash_err"); errors = errors + 1; end
read_ram_bytes(23'h000900, 16);
for (i = 0; i < 16; i = i + 1)
check_byte("TEST1 FLASH_WRITE_BLOCK/READ_BLOCK round-trip", readback[i], 8'h40 + i);
// ========================================================
// TEST 2: CAT_WRITE_SLOT + CAT_READ + CAT_INSPECT
// ========================================================
$display("--- TEST 2 starting ---");
cat_write_slot(4'd2, 24'h011000, 24'd0, 8'h05);
wait_done_or_err;
if (last_status[3]) begin $display("FAIL: TEST2 cat_write_slot unexpected flash_err"); errors = errors + 1; end
cat_read; // force a fresh reload from flash
wait_done_or_err;
if (last_status[3]) begin $display("FAIL: TEST2 cat_read unexpected flash_err"); errors = errors + 1; end
cat_inspect(4'd2);
check_byte("TEST2 CAT_INSPECT offset[23:16]", readback[0], 8'h01);
check_byte("TEST2 CAT_INSPECT offset[15:8]", readback[1], 8'h10);
check_byte("TEST2 CAT_INSPECT offset[7:0]", readback[2], 8'h00);
check_byte("TEST2 CAT_INSPECT type", readback[6], 8'h05);
check_byte("TEST2 CAT_INSPECT valid (never saved)", readback[7], 8'h00);
// ========================================================
// TEST 3 (adversarial §A.3): LOAD_SLOT on a never-saved slot
// ========================================================
$display("--- TEST 3 starting ---");
load_slot(4'd2, 23'h000A00); // slot 2 registered but never SAVE_SLOT'd
wait_done_or_err;
if (!last_status[3]) begin $display("FAIL: TEST3 expected flash_err for never-saved slot, got none"); errors = errors + 1; end
// ========================================================
// TEST 4: netasm -> WRITE_RAM -> SAVE_SLOT -> LOAD_SLOT ->
// RUN_NETWORK, independent oracle = 126 (spec §3 worked
// example, already used in sim/graph_engine_tb.v /
// sim/spi_neuron_top_graph_tb.v).
// ========================================================
$display("--- TEST 4 starting ---");
do_reset;
// From netasm's own debug dump, regenerated TWICE during
// bring-up (see WORKLOG.md's F5 entry for both findings):
// 1) --parallel 2 --max-conn 4, to MATCH this testbench's
// own RTL parameters (PARALLEL=2 above -- same as
// sim/spi_neuron_top_graph_tb.v, whose header explains
// why: n_conn=2 for both neurons exactly equals
// PARALLEL=2, so n_conn_padded=2, no padding needed).
// The default --parallel (8) instead produces padded=8
// edge blocks this GRAPH_MAX_CONN=4 build cannot
// correctly consume.
// 2) --table-base/--edges-base matching where the blob is
// ACTUALLY placed in PSRAM below (0x002000/0x002100).
// netasm bakes each neuron's edge-block address into the
// descriptor table as an ABSOLUTE PSRAM address AT
// COMPILE TIME (not an offset relative to table_base) --
// placing the compiled blob at a different PSRAM address
// than the one netasm was told about leaves those
// embedded pointers stale, silently pointing at
// whatever (unrelated, here all-zero) bytes happen to
// sit at the ORIGINAL address instead of the real edges.
// This does not error or hang -- graph_engine reads
// zeroed/garbage edges and computes a wrong (here, 0)
// result with STATUS reporting a completely normal,
// error-free completion. First discovered by getting
// output=0 instead of 126 with table/edges compiled for
// table_base=0x000000 but placed at 0x002000 (needed
// anyway to dodge the CATALOG_PSRAM_ADDR collision noted
// below) -- every other signal (STATUS, CRC, byte-exact
// PSRAM/flash content checks) looked perfectly correct,
// which is exactly why this is worth calling out: wrong
// base-address arguments to a code generator can produce
// a fully "successful", fully wrong result with no
// hardware-visible symptom at all.
// Final, correct invocation:
// python3 tools/netasm/cli.py tools/netasm/examples/graph_example.netasm \
// -o /tmp/netasm_out3/graph --parallel 2 --max-conn 4 \
// --table-base 0x002000 --edges-base 0x002100 --x-base 0x000400 --out-base 0x000500
//
// Addresses AS GENERATED (table_base=0x002000):
// descriptor table (22 bytes) @ 0x002000
// edges for n4 (8 bytes) @ 0x002100
// edges for n5 (8 bytes) @ 0x002108
// -> spans 0x002000-0x00210F (0x110 = 272 bytes), with an
// unused gap between the table and the edges (harmless,
// included verbatim in the SAVE_SLOT/CRC below -- a slot's
// saved range need not be semantically packed, only byte-
// exact on round-trip).
//
// Placed at PSRAM 0x002000 specifically (not netasm's own
// default 0x000000) because 0x000000 collides with
// flash_slot_manager's own CATALOG_PSRAM_ADDR staging region
// (default, also 0x000000) -- confirmed by direct trace
// during bring-up (see WORKLOG.md's F5 entry): the catalog's
// own serialization step during SAVE_SLOT's persist phase
// overwrote that exact PSRAM range out from under the network
// blob. Not an RTL bug -- exactly the limitation
// rtl/flash_slot_manager.v's own header already documents
// ("nothing else in this design may use that PSRAM range").
payload[0]=8'h00; payload[1]=8'h21; payload[2]=8'h00; payload[3]=8'h00;
payload[4]=8'h02; payload[5]=8'h00; payload[6]=8'h04; payload[7]=8'h01;
payload[8]=8'h02; payload[9]=8'h00; payload[10]=8'h00; payload[11]=8'h00;
payload[12]=8'h21; payload[13]=8'h08; payload[14]=8'h00; payload[15]=8'h02;
payload[16]=8'h00; payload[17]=8'h05; payload[18]=8'h00; payload[19]=8'h00;
payload[20]=8'h00; payload[21]=8'h00;
write_ram_bytes(23'h002000, 22); // descriptor table
payload[0]=8'h00; payload[1]=8'h00; payload[2]=8'h05; payload[3]=8'h00;
payload[4]=8'h00; payload[5]=8'h01; payload[6]=8'hfd; payload[7]=8'h00;
write_ram_bytes(23'h002100, 8); // edges for n4
payload[0]=8'h00; payload[1]=8'h04; payload[2]=8'h02; payload[3]=8'h00;
payload[4]=8'h00; payload[5]=8'h02; payload[6]=8'h07; payload[7]=8'h00;
write_ram_bytes(23'h002108, 8); // edges for n5
// Persist that 0x110-byte (272B) blob to flash as slot 4,
// sector-aligned target offset 0x012000 (arbitrary, unrelated
// to the catalog's own reserved sector 0).
cat_write_slot(4'd4, 24'h012000, 24'd0, 8'h02);
wait_done_or_err;
if (last_status[3]) begin $display("FAIL: TEST4 cat_write_slot unexpected flash_err"); errors = errors + 1; end
save_slot(4'd4, 23'h002000, 24'd272);
wait_done_or_err;
if (last_status[3]) begin $display("FAIL: TEST4 save_slot unexpected flash_err"); errors = errors + 1; end
// Overwrite the PSRAM region with garbage -- proves the
// network the RUN_NETWORK below actually computes with comes
// from the flash reload, not leftover PSRAM content.
for (i = 0; i < 64; i = i + 1) payload[i] = 8'h7E;
write_ram_bytes(23'h002000, 32); // covers the 22-byte descriptor table
write_ram_bytes(23'h002100, 16); // covers both 8-byte edge blocks exactly
load_slot(4'd4, 23'h002000);
wait_done_or_err;
if (last_status[3]) begin $display("FAIL: TEST4 load_slot unexpected flash_err (CRC mismatch?)"); errors = errors + 1; end
// Rest of netasm's own load/run sequence (debug dump, see
// file header), unchanged -- inputs x=[10,1,4,0] at x_base,
// Type#2 dispatch, RUN_NETWORK.
set_net_type(8'h02);
set_base(8'h00, 24'h000400); // x_base
set_base(8'h03, 24'h002000); // table_base
set_base(8'h04, 24'h000500); // buf_a_base (== out_base for graph)
set_base(8'h07, 24'h000004); // n_inputs_real = 4
set_base(8'h09, 24'h000002); // num_neurons_graph = 2
set_base(8'h0a, 24'h000001); // n_out = 1
payload[0] = 8'sd10; payload[1] = 8'sd1; payload[2] = 8'sd4; payload[3] = 8'sd0;
write_ram_bytes(23'h000400, 4);
run_network(8'h00);
wait_done_or_err;
if (last_status[3]) begin $display("FAIL: TEST4 RUN_NETWORK unexpected graph err"); errors = errors + 1; end
if (!last_status[1]) begin $display("FAIL: TEST4 RUN_NETWORK never completed (done)"); errors = errors + 1; end
read_ram_bytes(23'h000500, 1);
check_byte("TEST4 end-to-end netasm->SAVE_SLOT->LOAD_SLOT->RUN_NETWORK output", readback[0], 8'sd126);
// ========================================================
if (errors == 0)
$display("ALL TESTS PASSED");
else
$display("FAILED: %0d error(s)", errors);
$finish;
end
initial begin
#300_000_000;
$display("FATAL: global simulation timeout");
$finish;
end
endmodule