Files
FPGA-Neural/sim/flash_slot_manager_raw_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

287 lines
11 KiB
Verilog

`timescale 1ns/1ps
// ================================================================
// FLASH_SLOT_MANAGER TESTBENCH -- F5 raw block ops (op_code 4-6:
// OP_FLASH_READ_BLOCK / OP_FLASH_WRITE_BLOCK / OP_FLASH_ERASE),
// straight pass-through to the internal flash_copy_engine, no
// catalog/CRC involvement. Same real stack as flash_slot_manager_tb.v.
//
// TEST 1: FLASH_READ_BLOCK, byte-exact, independent oracle (data
// planted directly in flash_model.mem[]).
// TEST 2: FLASH_WRITE_BLOCK, byte-exact, independent oracle
// (direct flash_model.mem[] peek after the write).
// TEST 3: FLASH_ERASE, independent oracle (direct peek, all 0xFF).
// TEST 4 (adversarial §A.3): FLASH_ERASE on a non-sector-aligned
// address -> err (forwarded from flash_copy_engine's own DIR_ERASE
// bounds check, verified reaching this module's own `err` output).
// ================================================================
module tb;
localparam CLK_PERIOD = 12.5;
localparam ADDR_WIDTH = 23;
reg clk, 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 [2: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;
reg [23:0] raw_flash_addr;
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_FLASH_READ_BLOCK = 3'd4;
localparam OP_FLASH_WRITE_BLOCK = 3'd5;
localparam OP_FLASH_ERASE = 3'd6;
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(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),
.raw_flash_addr(raw_flash_addr),
.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)
);
wire arb_req, arb_wr;
wire [ADDR_WIDTH-1:0] arb_addr;
wire signed [7:0] arb_wdata, 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)
);
integer errors;
integer i;
reg [7:0] rb;
task automatic do_op(
input [2:0] p_code,
input [23:0] p_raw_flash_addr,
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;
raw_flash_addr <= p_raw_flash_addr;
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
initial begin
errors = 0;
rst = 1'b1;
op_start = 1'b0; op_code = 3'h0; 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; raw_flash_addr = 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);
// ========================================================
$display("--- TEST 1 starting ---");
for (i = 0; i < 20; i = i + 1)
dut_flash.mem[24'h00C000 + i] = 8'h90 + i[7:0];
do_op(OP_FLASH_READ_BLOCK, 24'h00C000, 23'h001500, 24'd20);
if (err) begin $display("FAIL: TEST1 unexpected err"); errors = errors + 1; end
for (i = 0; i < 20; i = i + 1) begin
psram_read_byte(23'h001500 + i, rb);
check_byte(rb, 8'h90 + i[7:0], "TEST1 FLASH_READ_BLOCK byte-exact");
end
// ========================================================
$display("--- TEST 2 starting ---");
for (i = 0; i < 20; i = i + 1)
psram_write_byte(23'h001600 + i, 8'hB0 + i[7:0]);
do_op(OP_FLASH_WRITE_BLOCK, 24'h00D000, 23'h001600, 24'd20);
if (err) begin $display("FAIL: TEST2 unexpected err"); errors = errors + 1; end
for (i = 0; i < 20; i = i + 1)
check_byte(dut_flash.mem[24'h00D000 + i], 8'hB0 + i[7:0], "TEST2 FLASH_WRITE_BLOCK vs independent flash peek");
// ========================================================
$display("--- TEST 3 starting ---");
for (i = 0; i < 4096; i = i + 1)
dut_flash.mem[24'h00E000 + i] = 8'h22;
do_op(OP_FLASH_ERASE, 24'h00E000, {ADDR_WIDTH{1'b0}}, 24'h0);
if (err) begin $display("FAIL: TEST3 unexpected err"); errors = errors + 1; end
for (i = 0; i < 4096; i = i + 1) begin
if (dut_flash.mem[24'h00E000 + i] !== 8'hFF) begin
$display("FAIL: TEST3 not erased at offset %0d", i);
errors = errors + 1;
i = 4096;
end
end
// ========================================================
$display("--- TEST 4 starting ---");
do_op(OP_FLASH_ERASE, 24'h00E100, {ADDR_WIDTH{1'b0}}, 24'h0); // not sector-aligned
if (!err) begin $display("FAIL: TEST4 expected err for unaligned erase, 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