`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