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
177 lines
5.8 KiB
Verilog
177 lines
5.8 KiB
Verilog
`timescale 1ns/1ps
|
|
|
|
// ================================================================
|
|
// FLASH_COPY_ENGINE TESTBENCH -- DIR_ERASE (F5's standalone
|
|
// FLASH_ERASE opcode primitive)
|
|
//
|
|
// Flash-side only (DIR_ERASE never touches PSRAM/Port D at all --
|
|
// d_req stays low throughout, checked explicitly below), so no
|
|
// PSRAM stack needed, mirroring F1's minimal testbench style.
|
|
//
|
|
// TEST 1 (happy path): a sector pre-poisoned with 0x33 is erased
|
|
// and read back (independent flash_model.mem[] peek) as all
|
|
// 0xFF -- §8.2.18 p.56.
|
|
// TEST 2 (adversarial §A.3): non-sector-aligned flash_addr -> err,
|
|
// no SE ever attempted (flash_model.v's own alignment guard,
|
|
// which would $fatal, never fires).
|
|
// TEST 3: an ADJACENT sector, deliberately left poisoned, must
|
|
// survive untouched -- confirms DIR_ERASE erases EXACTLY one
|
|
// sector, not a wider or narrower range.
|
|
// ================================================================
|
|
|
|
module tb;
|
|
|
|
localparam CLK_PERIOD = 12.5; // 80 MHz
|
|
|
|
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_dir;
|
|
reg [23:0] flash_addr;
|
|
reg [22:0] psram_addr;
|
|
reg [23:0] len;
|
|
wire busy, done, err;
|
|
|
|
wire d_req, d_wr;
|
|
wire [22:0] d_addr;
|
|
wire signed [7:0] d_wdata;
|
|
reg signed [7:0] d_rdata;
|
|
reg d_ready;
|
|
|
|
localparam DIR_ERASE = 2'd2;
|
|
|
|
flash_copy_engine #(
|
|
.PSRAM_ADDR_WIDTH(23), .CLK_FREQ_MHZ(80), .SCLK_DIV(2)
|
|
) dut (
|
|
.clk(clk), .rst(rst),
|
|
.mosi(mosi), .miso(miso), .cs_n(cs_n), .sclk(sclk_w),
|
|
.op_start(op_start), .op_dir(op_dir),
|
|
.flash_addr(flash_addr), .psram_addr(psram_addr), .len(len),
|
|
.busy(busy), .done(done), .err(err),
|
|
.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)
|
|
);
|
|
|
|
// Port D must NEVER be requested by DIR_ERASE.
|
|
integer d_req_count;
|
|
always @(posedge clk) if (d_req) d_req_count = d_req_count + 1;
|
|
|
|
integer errors;
|
|
integer i;
|
|
|
|
task automatic do_op(input [23:0] p_addr);
|
|
integer wd;
|
|
begin
|
|
@(posedge clk);
|
|
op_start <= 1'b1;
|
|
op_dir <= DIR_ERASE;
|
|
flash_addr <= p_addr;
|
|
psram_addr <= 23'h0;
|
|
len <= 24'h0;
|
|
@(posedge clk);
|
|
op_start <= 1'b0;
|
|
wd = 0;
|
|
while (!done) begin
|
|
@(posedge clk);
|
|
wd = wd + 1;
|
|
if (wd > 2_000_000) begin
|
|
$display("FATAL: do_op watchdog timeout");
|
|
$finish;
|
|
end
|
|
end
|
|
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;
|
|
d_req_count = 0;
|
|
rst = 1'b1;
|
|
op_start = 1'b0; op_dir = DIR_ERASE; flash_addr = 24'h0;
|
|
psram_addr = 23'h0; len = 24'h0;
|
|
d_rdata = 8'sd0; d_ready = 1'b0;
|
|
|
|
repeat (5) @(posedge clk);
|
|
rst = 1'b0;
|
|
repeat (5) @(posedge clk);
|
|
|
|
// ========================================================
|
|
// TEST 1: happy path
|
|
// ========================================================
|
|
$display("--- TEST 1 starting ---");
|
|
for (i = 0; i < 4096; i = i + 1)
|
|
dut_flash.mem[24'h00A000 + i] = 8'h33;
|
|
|
|
do_op(24'h00A000);
|
|
if (err) begin $display("FAIL: TEST1 unexpected err"); errors = errors + 1; end
|
|
|
|
for (i = 0; i < 4096; i = i + 1) begin
|
|
if (dut_flash.mem[24'h00A000 + i] !== 8'hFF) begin
|
|
$display("FAIL: TEST1 not fully erased at offset %0d, got=%02h", i, dut_flash.mem[24'h00A000+i]);
|
|
errors = errors + 1;
|
|
i = 4096;
|
|
end
|
|
end
|
|
|
|
// ========================================================
|
|
// TEST 2 (adversarial §A.3): unaligned flash_addr
|
|
// ========================================================
|
|
$display("--- TEST 2 starting ---");
|
|
do_op(24'h00A100); // not a multiple of 0x1000
|
|
if (!err) begin $display("FAIL: TEST2 expected err for unaligned erase, got none"); errors = errors + 1; end
|
|
|
|
// ========================================================
|
|
// TEST 3: adjacent sector untouched
|
|
// ========================================================
|
|
$display("--- TEST 3 starting ---");
|
|
for (i = 0; i < 4096; i = i + 1)
|
|
dut_flash.mem[24'h00B000 + i] = 8'h44; // sector right after the one erased in TEST1
|
|
|
|
do_op(24'h00A000); // re-erase the SAME sector as TEST1 (already 0xFF, harmless)
|
|
if (err) begin $display("FAIL: TEST3 unexpected err"); errors = errors + 1; end
|
|
|
|
for (i = 0; i < 4096; i = i + 1)
|
|
check_byte(dut_flash.mem[24'h00B000 + i], 8'h44, "TEST3 adjacent sector untouched");
|
|
|
|
if (d_req_count != 0) begin
|
|
$display("FAIL: DIR_ERASE issued %0d Port D request(s), expected 0", d_req_count);
|
|
errors = errors + 1;
|
|
end
|
|
|
|
// ========================================================
|
|
if (errors == 0)
|
|
$display("ALL TESTS PASSED");
|
|
else
|
|
$display("FAILED: %0d error(s)", errors);
|
|
|
|
$finish;
|
|
end
|
|
|
|
initial begin
|
|
#50_000_000;
|
|
$display("FATAL: global simulation timeout");
|
|
$finish;
|
|
end
|
|
|
|
endmodule
|