Adds FPGA-exclusive access to the onboard W25Q128JV SPI NOR flash for weights/bias/network persistence, layered as spi_flash_master (raw SPI, USRMCLK-driven) -> flash_copy_engine (flash<->PSRAM streaming, erase- before-write, Page Program loop) -> flash_slot_manager (16-slot catalog with CRC32), exposed via 8 new SPI opcodes (0x40-0x47). Fixes two pre-existing bugs found during bring-up: a psram_controller.v request lost during power-up, and a one-cycle-pulse race in the PSRAM arbiter request handshake. Full simulation + real Yosys/nextpnr-ecp5 synthesis verification (0 errors, Fmax 66.68MHz) in WORKLOG.md and docs/FPGA-Neural-Flash-Subsystem-Verification.md. Also updates docs/pinout to reflect the 56-signal real .lpf (3 new flash pins) and documents the WRITE_RAM/READ_RAM host backpressure risk found while testing this subsystem. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
448 lines
18 KiB
Verilog
448 lines
18 KiB
Verilog
`timescale 1ns/1ps
|
|
|
|
// ================================================================
|
|
// FLASH_SLOT_MANAGER TESTBENCH -- Phase F4
|
|
//
|
|
// Real stack: mem_arbiter -> int8_memory_access -> memory_interface
|
|
// -> psram_controller -> psram_model on the PSRAM side (same as
|
|
// F2/F3's testbenches), flash_model.v on the flash side.
|
|
// flash_slot_manager owns Port D exclusively in this testbench (its
|
|
// own internal flash_copy_engine's traffic plus its own
|
|
// catalog-staging traffic, muxed internally -- see the module's own
|
|
// header); Port A is driven directly by this testbench to seed/
|
|
// verify PSRAM content, mirroring F2/F3's own convention.
|
|
//
|
|
// Independent oracle (§A.1): tools/flash_catalog/oracle.py. Exact
|
|
// expected bytes/CRC used below were generated by running:
|
|
// python3 -c "from tools.flash_catalog.oracle import pack_entry, crc32; ..."
|
|
// (see WORKLOG.md's F4 entry for the precise invocation and output)
|
|
// -- not hand-derived from this RTL's own algorithm.
|
|
//
|
|
// TEST 1 (CAT_WRITE_SLOT + persist + CAT_READ round-trip):
|
|
// registers slot 2 (offset=0x001000, length=0, type=0x01,
|
|
// valid=0 since no data yet). Checks (a) the raw persisted flash
|
|
// bytes against the oracle's pack_entry(...) output byte-for-
|
|
// byte (independent oracle, not the RTL's own decode), and
|
|
// (b) a fresh CAT_READ (simulating a reboot) correctly
|
|
// reconstructs the same on-chip entry.
|
|
//
|
|
// TEST 2 (SAVE_SLOT, independent CRC oracle): seeds PSRAM with a
|
|
// known 32-byte pattern, SAVE_SLOTs it into slot 2, and checks
|
|
// the persisted catalog entry's raw bytes against the oracle's
|
|
// pack_entry(..., valid=True, data=pattern) output -- including
|
|
// the CRC32 field, computed by a completely independent
|
|
// implementation (Python zlib), not by reading rtl/crc32.v back.
|
|
//
|
|
// TEST 3 (LOAD_SLOT, byte-exact + CRC accepted): loads slot 2 back
|
|
// into a different PSRAM region, checks byte-exact content and
|
|
// err==0 (valid CRC).
|
|
//
|
|
// TEST 4 (adversarial §A.3, CRC corrotto -> invalido): after
|
|
// TEST 2/3's successful save, ONE flash byte belonging to slot
|
|
// 2's data is corrupted directly (hierarchical poke into
|
|
// flash_model.mem[], independent of the RTL under test) and
|
|
// LOAD_SLOT is retried -- must report err==1 (CRC mismatch).
|
|
//
|
|
// TEST 5 (adversarial §A.3, slot mai salvato -> invalido):
|
|
// LOAD_SLOT on a slot that was CAT_WRITE_SLOT'd (registered)
|
|
// but never SAVE_SLOT'd -- must report err==1 immediately, with
|
|
// no flash/PSRAM transaction attempted at all (checked via a
|
|
// PSRAM sentinel at the target address surviving untouched).
|
|
//
|
|
// TEST 6 (adversarial §A.3, power-loss simulato): a SAVE_SLOT's
|
|
// underlying Sector Erase is aborted mid-flight using
|
|
// flash_model.v's own documented power-loss hook (forcing
|
|
// `pending_se` and `busy` low via hierarchical reference before
|
|
// the erase's commit loop runs -- see flash_model.v's header).
|
|
// The target sector was pre-poisoned with a DIFFERENT pattern
|
|
// than the one being saved, so the abort leaves stale bytes
|
|
// un-erased; flash_copy_engine's WIP poll (fooled by the forced
|
|
// `busy=0`) proceeds to "successfully" finish the whole
|
|
// SAVE_SLOT and marks the catalog valid -- but with a CRC
|
|
// computed from the INTENDED data, which no longer matches the
|
|
// ACTUAL (corrupted, AND-of-poison-and-intended) flash bytes.
|
|
// A subsequent LOAD_SLOT must therefore report err==1 -- this
|
|
// is the mechanism (CRC over real committed bytes, not trust in
|
|
// a completion signal) that makes "regione invalida rilevata"
|
|
// work even when the underlying op silently didn't do what it
|
|
// claimed.
|
|
// ================================================================
|
|
|
|
module tb;
|
|
|
|
localparam CLK_PERIOD = 12.5; // 80 MHz
|
|
localparam ADDR_WIDTH = 23;
|
|
|
|
reg clk;
|
|
reg rst;
|
|
|
|
initial begin
|
|
clk = 1'b0;
|
|
forever #(CLK_PERIOD / 2.0) clk = ~clk;
|
|
end
|
|
|
|
wire mosi, miso, cs_n, sclk_w;
|
|
|
|
reg op_start;
|
|
reg [1:0] op_code;
|
|
reg [3:0] slot_id;
|
|
reg [23:0] new_offset, new_length;
|
|
reg [7:0] new_type;
|
|
reg [ADDR_WIDTH-1:0] ext_psram_addr;
|
|
reg [23:0] ext_length;
|
|
wire busy, done, err;
|
|
|
|
reg [3:0] cat_read_sel;
|
|
wire [23:0] cat_out_offset, cat_out_length;
|
|
wire [7:0] cat_out_type;
|
|
wire cat_out_valid;
|
|
wire [31:0] cat_out_crc;
|
|
|
|
localparam OP_CAT_READ = 2'd0;
|
|
localparam OP_CAT_WRITE_SLOT = 2'd1;
|
|
localparam OP_LOAD_SLOT = 2'd2;
|
|
localparam OP_SAVE_SLOT = 2'd3;
|
|
|
|
wire d_req, d_wr;
|
|
wire [ADDR_WIDTH-1:0] d_addr;
|
|
wire signed [7:0] d_wdata;
|
|
wire signed [7:0] d_rdata;
|
|
wire d_ready;
|
|
|
|
reg a_req, a_wr;
|
|
reg [ADDR_WIDTH-1:0] a_addr;
|
|
reg signed [7:0] a_wdata;
|
|
wire signed [7:0] a_rdata;
|
|
wire a_ready;
|
|
|
|
flash_slot_manager #(
|
|
.PSRAM_ADDR_WIDTH(ADDR_WIDTH),
|
|
.CLK_FREQ_MHZ(80),
|
|
.SCLK_DIV(2),
|
|
.CATALOG_PSRAM_ADDR(23'h000000)
|
|
) dut (
|
|
.clk(clk), .rst(rst),
|
|
|
|
.mosi(mosi), .miso(miso), .cs_n(cs_n),
|
|
.sclk_sim(sclk_w),
|
|
|
|
.op_start(op_start), .op_code(op_code), .slot_id(slot_id),
|
|
.new_offset(new_offset), .new_length(new_length), .new_type(new_type),
|
|
.ext_psram_addr(ext_psram_addr), .ext_length(ext_length),
|
|
.busy(busy), .done(done), .err(err),
|
|
|
|
.cat_read_sel(cat_read_sel),
|
|
.cat_out_offset(cat_out_offset), .cat_out_length(cat_out_length),
|
|
.cat_out_type(cat_out_type), .cat_out_valid(cat_out_valid), .cat_out_crc(cat_out_crc),
|
|
|
|
.d_req(d_req), .d_wr(d_wr), .d_addr(d_addr), .d_wdata(d_wdata),
|
|
.d_rdata(d_rdata), .d_ready(d_ready)
|
|
);
|
|
|
|
flash_model #(
|
|
.DEPTH(32'h0002_0000),
|
|
.TIME_SCALE(100000)
|
|
) dut_flash (
|
|
.sclk(sclk_w), .mosi(mosi), .miso(miso), .cs_n(cs_n)
|
|
);
|
|
|
|
// ------------------------------------------------------------
|
|
// Real PSRAM stack
|
|
// ------------------------------------------------------------
|
|
wire arb_req, arb_wr;
|
|
wire [ADDR_WIDTH-1:0] arb_addr;
|
|
wire signed [7:0] arb_wdata;
|
|
wire signed [7:0] arb_rdata;
|
|
wire arb_ready;
|
|
|
|
mem_arbiter #(.ADDR_WIDTH(ADDR_WIDTH)) u_arbiter (
|
|
.clk(clk), .rst(rst),
|
|
|
|
.a_req(a_req), .a_wr(a_wr), .a_addr(a_addr), .a_wdata(a_wdata),
|
|
.a_rdata(a_rdata), .a_ready(a_ready),
|
|
|
|
.b_req(1'b0), .b_wr(1'b0), .b_addr({ADDR_WIDTH{1'b0}}), .b_wdata(8'sd0),
|
|
.b_rdata(), .b_ready(),
|
|
|
|
.c_req(1'b0), .c_wr(1'b0), .c_addr({ADDR_WIDTH{1'b0}}), .c_wdata(8'sd0),
|
|
.c_rdata(), .c_ready(),
|
|
|
|
.d_req(d_req), .d_wr(d_wr), .d_addr(d_addr), .d_wdata(d_wdata),
|
|
.d_rdata(d_rdata), .d_ready(d_ready),
|
|
|
|
.m_req(arb_req), .m_wr(arb_wr), .m_addr(arb_addr), .m_wdata(arb_wdata),
|
|
.m_rdata(arb_rdata), .m_ready(arb_ready)
|
|
);
|
|
|
|
wire i8_req, i8_wr;
|
|
wire [ADDR_WIDTH-1:0] i8_addr;
|
|
wire [15:0] i8_wdata;
|
|
wire i8_lb_n, i8_ub_n;
|
|
wire [15:0] i8_rdata;
|
|
wire i8_ready;
|
|
|
|
int8_memory_access #(.ADDR_WIDTH(ADDR_WIDTH)) u_i8 (
|
|
.clk(clk), .rst(rst),
|
|
.req(arb_req), .wr(arb_wr), .addr(arb_addr), .wdata(arb_wdata),
|
|
.rdata(arb_rdata), .ready(arb_ready),
|
|
.mem_req(i8_req), .mem_wr(i8_wr), .mem_addr(i8_addr), .mem_wdata(i8_wdata),
|
|
.mem_lb_n(i8_lb_n), .mem_ub_n(i8_ub_n),
|
|
.mem_rdata(i8_rdata), .mem_ready(i8_ready)
|
|
);
|
|
|
|
wire mi_req, mi_wr;
|
|
wire [ADDR_WIDTH-1:0] mi_addr;
|
|
wire [15:0] mi_wdata;
|
|
wire mi_lb_n, mi_ub_n;
|
|
wire [15:0] mi_rdata;
|
|
wire mi_ready;
|
|
|
|
memory_interface #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(16)) u_mi (
|
|
.clk(clk), .rst(rst),
|
|
.req(i8_req), .wr(i8_wr), .addr(i8_addr), .wdata(i8_wdata),
|
|
.lb_n(i8_lb_n), .ub_n(i8_ub_n),
|
|
.rdata(i8_rdata), .ready(i8_ready),
|
|
.mem_req(mi_req), .mem_wr(mi_wr), .mem_addr(mi_addr), .mem_wdata(mi_wdata),
|
|
.mem_lb_n(mi_lb_n), .mem_ub_n(mi_ub_n),
|
|
.mem_rdata(mi_rdata), .mem_ready(mi_ready)
|
|
);
|
|
|
|
wire [ADDR_WIDTH-1:0] psram_a;
|
|
wire [15:0] psram_dq;
|
|
wire psram_ce_n, psram_oe_n, psram_we_n, psram_lb_n, psram_ub_n, psram_zz_n;
|
|
|
|
psram_controller #(.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(16), .CLK_FREQ_MHZ(80)) u_psram_ctrl (
|
|
.clk(clk), .rst(rst),
|
|
.mem_req(mi_req), .mem_wr(mi_wr), .mem_addr(mi_addr), .mem_wdata(mi_wdata),
|
|
.mem_lb_n(mi_lb_n), .mem_ub_n(mi_ub_n),
|
|
.mem_rdata(mi_rdata), .mem_ready(mi_ready),
|
|
.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(16), .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)
|
|
);
|
|
|
|
// ============================================================
|
|
// Helper tasks
|
|
// ============================================================
|
|
integer errors;
|
|
|
|
task automatic do_op(
|
|
input [1:0] p_code,
|
|
input [3:0] p_slot,
|
|
input [23:0] p_new_offset,
|
|
input [23:0] p_new_length,
|
|
input [7:0] p_new_type,
|
|
input [ADDR_WIDTH-1:0] p_ext_psram_addr,
|
|
input [23:0] p_ext_length
|
|
);
|
|
integer wd;
|
|
begin
|
|
@(posedge clk);
|
|
op_start <= 1'b1;
|
|
op_code <= p_code;
|
|
slot_id <= p_slot;
|
|
new_offset <= p_new_offset;
|
|
new_length <= p_new_length;
|
|
new_type <= p_new_type;
|
|
ext_psram_addr <= p_ext_psram_addr;
|
|
ext_length <= p_ext_length;
|
|
@(posedge clk);
|
|
op_start <= 1'b0;
|
|
wd = 0;
|
|
while (!done) begin
|
|
@(posedge clk);
|
|
wd = wd + 1;
|
|
if (wd > 5_000_000) begin
|
|
$display("FATAL: do_op watchdog timeout");
|
|
$finish;
|
|
end
|
|
end
|
|
end
|
|
endtask
|
|
|
|
task automatic psram_read_byte(input [ADDR_WIDTH-1:0] a, output [7:0] v);
|
|
begin
|
|
@(posedge clk);
|
|
a_req <= 1'b1;
|
|
a_wr <= 1'b0;
|
|
a_addr <= a;
|
|
@(posedge clk);
|
|
a_req <= 1'b0;
|
|
while (!a_ready) @(posedge clk);
|
|
v = a_rdata;
|
|
@(posedge clk);
|
|
end
|
|
endtask
|
|
|
|
task automatic psram_write_byte(input [ADDR_WIDTH-1:0] a, input [7:0] v);
|
|
begin
|
|
@(posedge clk);
|
|
a_req <= 1'b1;
|
|
a_wr <= 1'b1;
|
|
a_addr <= a;
|
|
a_wdata <= $signed(v);
|
|
@(posedge clk);
|
|
a_req <= 1'b0;
|
|
while (!a_ready) @(posedge clk);
|
|
@(posedge clk);
|
|
end
|
|
endtask
|
|
|
|
task automatic check_byte(input [7:0] got, input [7:0] exp, input [255:0] label);
|
|
begin
|
|
if (got !== exp) begin
|
|
$display("FAIL: %0s got=%02h exp=%02h", label, got, exp);
|
|
errors = errors + 1;
|
|
end
|
|
end
|
|
endtask
|
|
|
|
integer i;
|
|
reg [7:0] rb;
|
|
reg [7:0] exp_entry [0:15];
|
|
|
|
initial begin
|
|
errors = 0;
|
|
rst = 1'b1;
|
|
op_start = 1'b0;
|
|
op_code = OP_CAT_READ;
|
|
slot_id = 4'h0;
|
|
new_offset = 24'h0; new_length = 24'h0; new_type = 8'h0;
|
|
ext_psram_addr = {ADDR_WIDTH{1'b0}};
|
|
ext_length = 24'h0;
|
|
cat_read_sel = 4'h0;
|
|
a_req = 1'b0; a_wr = 1'b0; a_addr = {ADDR_WIDTH{1'b0}}; a_wdata = 8'sd0;
|
|
|
|
repeat (5) @(posedge clk);
|
|
rst = 1'b0;
|
|
repeat (5) @(posedge clk);
|
|
|
|
// ========================================================
|
|
// TEST 1: CAT_WRITE_SLOT + persist + CAT_READ round-trip
|
|
// ========================================================
|
|
$display("--- TEST 1 starting ---");
|
|
do_op(OP_CAT_WRITE_SLOT, 4'd2, 24'h001000, 24'd0, 8'h01, {ADDR_WIDTH{1'b0}}, 24'h0);
|
|
if (err) begin $display("FAIL: TEST1 unexpected err"); errors = errors + 1; end
|
|
|
|
// Independent oracle: python3 pack_entry(offset=0x1000, length=0,
|
|
// type=1, valid=False, data=b"") = 00100000000001000000000000000000
|
|
exp_entry[0]=8'h00; exp_entry[1]=8'h10; exp_entry[2]=8'h00; exp_entry[3]=8'h00;
|
|
exp_entry[4]=8'h00; exp_entry[5]=8'h00; exp_entry[6]=8'h01; exp_entry[7]=8'h00;
|
|
exp_entry[8]=8'h00; exp_entry[9]=8'h00; exp_entry[10]=8'h00; exp_entry[11]=8'h00;
|
|
exp_entry[12]=8'h00; exp_entry[13]=8'h00; exp_entry[14]=8'h00; exp_entry[15]=8'h00;
|
|
for (i = 0; i < 16; i = i + 1)
|
|
check_byte(dut_flash.mem[24'h000000 + 2*16 + i], exp_entry[i], "TEST1 persisted catalog entry vs oracle");
|
|
|
|
do_op(OP_CAT_READ, 4'h0, 24'h0, 24'h0, 8'h0, {ADDR_WIDTH{1'b0}}, 24'h0);
|
|
cat_read_sel = 4'd2;
|
|
#1;
|
|
if (cat_out_offset !== 24'h001000 || cat_out_length !== 24'd0 ||
|
|
cat_out_type !== 8'h01 || cat_out_valid !== 1'b0) begin
|
|
$display("FAIL: TEST1 CAT_READ reconstruction mismatch: offset=%06h length=%0d type=%02h valid=%b",
|
|
cat_out_offset, cat_out_length, cat_out_type, cat_out_valid);
|
|
errors = errors + 1;
|
|
end
|
|
|
|
// ========================================================
|
|
// TEST 2/3: SAVE_SLOT (independent CRC oracle) + LOAD_SLOT
|
|
// ========================================================
|
|
$display("--- TEST 2/3 starting ---");
|
|
for (i = 0; i < 32; i = i + 1)
|
|
psram_write_byte(23'h003000 + i, 8'h60 + i[7:0]);
|
|
|
|
do_op(OP_SAVE_SLOT, 4'd2, 24'h0, 24'h0, 8'h0, 23'h003000, 24'd32);
|
|
if (err) begin $display("FAIL: TEST2 unexpected err"); errors = errors + 1; end
|
|
|
|
// Independent oracle: python3 pack_entry(offset=0x1000, length=32,
|
|
// type=1, valid=True, data=bytes(0x60..0x7F))
|
|
// = 001000000020010139a6f63100000000
|
|
exp_entry[0]=8'h00; exp_entry[1]=8'h10; exp_entry[2]=8'h00; exp_entry[3]=8'h00;
|
|
exp_entry[4]=8'h00; exp_entry[5]=8'h20; exp_entry[6]=8'h01; exp_entry[7]=8'h01;
|
|
exp_entry[8]=8'h39; exp_entry[9]=8'ha6; exp_entry[10]=8'hf6; exp_entry[11]=8'h31;
|
|
exp_entry[12]=8'h00; exp_entry[13]=8'h00; exp_entry[14]=8'h00; exp_entry[15]=8'h00;
|
|
for (i = 0; i < 16; i = i + 1)
|
|
check_byte(dut_flash.mem[24'h000000 + 2*16 + i], exp_entry[i], "TEST2 persisted catalog entry (SAVE_SLOT) vs oracle");
|
|
|
|
for (i = 0; i < 32; i = i + 1)
|
|
check_byte(dut_flash.mem[24'h001000 + i], 8'h60 + i[7:0], "TEST2 slot data in flash vs oracle pattern");
|
|
|
|
do_op(OP_LOAD_SLOT, 4'd2, 24'h0, 24'h0, 8'h0, 23'h004000, 24'h0);
|
|
if (err) begin $display("FAIL: TEST3 unexpected err (valid CRC)"); errors = errors + 1; end
|
|
for (i = 0; i < 32; i = i + 1) begin
|
|
psram_read_byte(23'h004000 + i, rb);
|
|
check_byte(rb, 8'h60 + i[7:0], "TEST3 LOAD_SLOT byte-exact");
|
|
end
|
|
|
|
// ========================================================
|
|
// TEST 4 (adversarial §A.3): CRC corrotto -> invalido
|
|
// ========================================================
|
|
$display("--- TEST 4 starting ---");
|
|
dut_flash.mem[24'h001005] = dut_flash.mem[24'h001005] ^ 8'h01; // flip one bit of slot data
|
|
|
|
do_op(OP_LOAD_SLOT, 4'd2, 24'h0, 24'h0, 8'h0, 23'h005000, 24'h0);
|
|
if (!err) begin $display("FAIL: TEST4 expected err for corrupted CRC, got none"); errors = errors + 1; end
|
|
|
|
dut_flash.mem[24'h001005] = dut_flash.mem[24'h001005] ^ 8'h01; // restore for later tests
|
|
|
|
// ========================================================
|
|
// TEST 5 (adversarial §A.3): slot mai salvato -> invalido
|
|
// ========================================================
|
|
$display("--- TEST 5 starting ---");
|
|
do_op(OP_CAT_WRITE_SLOT, 4'd5, 24'h008000, 24'd0, 8'h02, {ADDR_WIDTH{1'b0}}, 24'h0);
|
|
|
|
psram_write_byte(23'h006000, 8'h5A); // sentinel
|
|
do_op(OP_LOAD_SLOT, 4'd5, 24'h0, 24'h0, 8'h0, 23'h006000, 24'h0);
|
|
if (!err) begin $display("FAIL: TEST5 expected err for never-saved slot, got none"); errors = errors + 1; end
|
|
psram_read_byte(23'h006000, rb);
|
|
check_byte(rb, 8'h5A, "TEST5 sentinel untouched (no transaction attempted)");
|
|
|
|
// ========================================================
|
|
// TEST 6 (adversarial §A.3): power-loss simulato durante
|
|
// l'erase di una SAVE_SLOT.
|
|
// ========================================================
|
|
$display("--- TEST 6 starting ---");
|
|
do_op(OP_CAT_WRITE_SLOT, 4'd7, 24'h009000, 24'd0, 8'h01, {ADDR_WIDTH{1'b0}}, 24'h0);
|
|
|
|
for (i = 0; i < 4096; i = i + 1)
|
|
dut_flash.mem[24'h009000 + i] = 8'h77; // poison, different from the intended save
|
|
|
|
for (i = 0; i < 16; i = i + 1)
|
|
psram_write_byte(23'h007000 + i, 8'hD0 + i[7:0]); // intended (different) data
|
|
|
|
fork
|
|
do_op(OP_SAVE_SLOT, 4'd7, 24'h0, 24'h0, 8'h0, 23'h007000, 24'd16);
|
|
begin
|
|
@(posedge dut_flash.pending_se);
|
|
dut_flash.pending_se = 1'b0;
|
|
dut_flash.busy = 1'b0;
|
|
end
|
|
join
|
|
|
|
if (err) begin $display("FAIL: TEST6 unexpected err on the (fooled) SAVE_SLOT itself"); errors = errors + 1; end
|
|
|
|
do_op(OP_LOAD_SLOT, 4'd7, 24'h0, 24'h0, 8'h0, 23'h008100, 24'h0);
|
|
if (!err) begin $display("FAIL: TEST6 expected err for power-loss-corrupted slot, got none"); errors = errors + 1; end
|
|
|
|
// ========================================================
|
|
if (errors == 0)
|
|
$display("ALL TESTS PASSED");
|
|
else
|
|
$display("FAILED: %0d error(s)", errors);
|
|
|
|
$finish;
|
|
end
|
|
|
|
initial begin
|
|
#200_000_000;
|
|
$display("FATAL: global simulation timeout");
|
|
$finish;
|
|
end
|
|
|
|
endmodule
|