Files
FPGA-Neural/rtl/flash_slot_manager.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

652 lines
29 KiB
Verilog

`timescale 1ns/1ps
// ================================================================
// FLASH_SLOT_MANAGER
//
// Fixed-slot catalog layer on top of rtl/flash_copy_engine.v (F2/F3,
// reused UNCHANGED -- zero modifications, zero regression risk to
// its already-verified LOAD/SAVE paths). This is the module the
// flash-subsystem phase-plan's §4 describes: NOT a filesystem --
// a small fixed-size table mapping slot_id -> (offset, length,
// type, valid, CRC), read at boot and resolved before every
// LOAD_SLOT/SAVE_SLOT, no dynamic allocation, no GC.
//
// ----------------------------------------------------------------
// Catalog layout (matches tools/flash_catalog/oracle.py exactly --
// that script is the independent §A.1 oracle for both the byte
// layout and the CRC32 value; kept in sync by hand, cross-checked
// by the testbenches comparing actual bytes, not by either side
// trusting the other's prose)
// ----------------------------------------------------------------
// - N_SLOTS = 16 entries x ENTRY_BYTES = 16 bytes = 256 bytes
// total, comfortably inside one 4KB sector (CATALOG_SECTOR_ADDR,
// flash sector 0 -- RESERVED, never used for slot data).
// - Per-entry layout, MSB-first: offset[24b] | length[24b] |
// type[8b] | valid[8b] (0x01=valid, else invalid) | crc32[32b]
// | reserved[32b, always 0].
// - A freshly-erased catalog sector is all-0xFF, so every
// unwritten slot decodes as valid=0xFF != 0x01 => invalid,
// without needing any format/init step -- matches flash_model.v
// and the real Winbond erase behavior (§8.2.18 p.56).
//
// ----------------------------------------------------------------
// How the catalog gets to/from flash without touching PSRAM as user
// data (design decision, documented per the phase-plan's own
// "traccia ogni scelta" requirement):
// ----------------------------------------------------------------
// flash_copy_engine already knows how to move bytes flash<->PSRAM
// (F2/F3). Rather than teach it a THIRD destination (on-chip
// registers), this module stages the 256-byte catalog through a
// small reserved PSRAM region (CATALOG_PSRAM_ADDR) using
// flash_copy_engine's EXISTING, unmodified DIR_LOAD/DIR_SAVE, then
// itself reads/writes that staging region byte-by-byte via the
// SAME Port D connection flash_copy_engine uses -- safe because
// the two uses are TEMPORALLY DISJOINT by construction (this
// module's own sequential FSM never drives Port D itself while an
// internal flash_copy_engine operation is in flight, and vice
// versa): a static mux on `fce_busy` selects the owner, no new
// arbiter port needed.
// ASSUMPTION flagged (§A.5): CATALOG_PSRAM_ADDR is a convention,
// not enforced anywhere else -- nothing else in this design may
// use that PSRAM range while a catalog operation is in flight.
//
// ----------------------------------------------------------------
// CRC32 (rtl/crc32.v, IEEE 802.3/zlib -- verified independently
// against tools/flash_catalog/oracle.py + a textbook check-value,
// see sim/crc32_tb.v / WORKLOG.md's F4 entry) is computed by TAPPING
// flash_copy_engine's own Port D traffic while it is the active
// owner during a LOAD_SLOT or SAVE_SLOT (fce_d_wdata for a LOAD's
// PSRAM writes, fce_d_rdata for a SAVE's PSRAM reads) -- again zero
// modification to flash_copy_engine.v itself.
// ----------------------------------------------------------------
//
// Command interface:
// op_start, op_code (OP_CAT_READ / OP_CAT_WRITE_SLOT /
// OP_LOAD_SLOT / OP_SAVE_SLOT), slot_id, and per-opcode fields
// below. busy/done/err mirror flash_copy_engine's own convention.
//
// OP_CAT_READ: reloads the on-chip catalog register file from the
// flash catalog sector (e.g. at boot, or to force a refresh).
// OP_CAT_WRITE_SLOT: registers/updates slot_id's (offset, length,
// type) in the on-chip table, marks it INVALID (no verified
// data behind it yet -- SAVE_SLOT is what marks it valid), and
// persists the WHOLE catalog table to flash.
// OP_LOAD_SLOT(slot_id, psram_addr): resolves slot_id's offset+
// length from the on-chip catalog; if invalid, `err`+`done`
// immediately, no flash/PSRAM touched. Otherwise streams the
// slot's data flash->PSRAM (reusing flash_copy_engine's DIR_LOAD
// verbatim) while recomputing the CRC32 live; if it does not
// match the catalog's stored CRC, `err` is asserted (the data
// still lands in PSRAM -- the host is being told "unreliable",
// not given a transactional rollback, matching this project's
// existing STATUS-flag error convention rather than introducing
// a new one).
// OP_SAVE_SLOT(slot_id, psram_addr, length): resolves slot_id's
// already-registered offset (from a prior CAT_WRITE_SLOT);
// streams `length` bytes PSRAM->flash at that offset (reusing
// flash_copy_engine's DIR_SAVE verbatim) while computing the
// CRC32 live; on completion updates the on-chip entry (length,
// crc, valid=1) and persists the whole catalog to flash.
// ================================================================
module flash_slot_manager #(
parameter PSRAM_ADDR_WIDTH = 23,
parameter CLK_FREQ_MHZ = 80,
parameter SCLK_DIV = 2,
parameter [PSRAM_ADDR_WIDTH-1:0] CATALOG_PSRAM_ADDR = {PSRAM_ADDR_WIDTH{1'b0}}
)(
input wire clk,
input wire rst,
// ------------------------------------------------------------
// Physical flash pins (owns flash_copy_engine, which owns
// spi_flash_master -- same ownership chain as F2/F3)
// ------------------------------------------------------------
output wire mosi,
input wire miso,
output wire cs_n,
output wire sclk, // ordinary GPIO, real in both sim and synthesis
// ------------------------------------------------------------
// Command interface. op_code 0-3 are the F4 catalog/slot ops;
// 4-6 (added in F5) are raw, non-slot, non-CRC block ops that
// just forward straight to the internal flash_copy_engine --
// the phase-plan's §5 FLASH_READ_BLOCK/FLASH_WRITE_BLOCK/
// FLASH_ERASE, which take an explicit flash address rather than
// resolving one from the catalog.
// ------------------------------------------------------------
input wire op_start,
input wire [2:0] op_code,
input wire [3:0] slot_id,
input wire [23:0] new_offset, // CAT_WRITE_SLOT
input wire [23:0] new_length, // CAT_WRITE_SLOT
input wire [7:0] new_type, // CAT_WRITE_SLOT
input wire [PSRAM_ADDR_WIDTH-1:0] ext_psram_addr, // LOAD_SLOT / SAVE_SLOT / raw ops
input wire [23:0] ext_length, // SAVE_SLOT / raw block ops
input wire [23:0] raw_flash_addr, // FLASH_READ_BLOCK / FLASH_WRITE_BLOCK / FLASH_ERASE
output wire busy,
output reg done, // one-cycle pulse
output reg err, // held until next op_start
// ------------------------------------------------------------
// Catalog inspection (combinational read port, e.g. for a
// future CAT_READ SPI response -- F5 -- or this module's own
// testbench)
// ------------------------------------------------------------
input wire [3:0] cat_read_sel,
output wire [23:0] cat_out_offset,
output wire [23:0] cat_out_length,
output wire [7:0] cat_out_type,
output wire cat_out_valid,
output wire [31:0] cat_out_crc,
// ------------------------------------------------------------
// PSRAM arbiter master port (mem_arbiter.v Port D) -- muxed
// between the internal flash_copy_engine's own use and this
// module's own catalog-staging-buffer access, see header.
// ------------------------------------------------------------
output wire d_req,
output wire d_wr,
output wire [PSRAM_ADDR_WIDTH-1:0] d_addr,
output wire signed [7:0] d_wdata,
input wire signed [7:0] d_rdata,
input wire d_ready
);
localparam OP_CAT_READ = 3'd0;
localparam OP_CAT_WRITE_SLOT = 3'd1;
localparam OP_LOAD_SLOT = 3'd2;
localparam OP_SAVE_SLOT = 3'd3;
localparam OP_FLASH_READ_BLOCK = 3'd4; // F5, raw: flash -> PSRAM, explicit flash_addr
localparam OP_FLASH_WRITE_BLOCK = 3'd5; // F5, raw: PSRAM -> flash, explicit flash_addr
localparam OP_FLASH_ERASE = 3'd6; // F5, raw: standalone sector erase
localparam N_SLOTS = 16;
localparam ENTRY_BYTES = 16;
localparam CATALOG_BYTES = N_SLOTS * ENTRY_BYTES; // 256
localparam [23:0] CATALOG_SECTOR_ADDR = 24'h000000; // reserved, see header
localparam [7:0] VALID_MARK = 8'h01;
// ============================================================
// On-chip catalog register file
// ============================================================
reg [23:0] cat_offset [0:N_SLOTS-1];
reg [23:0] cat_length [0:N_SLOTS-1];
reg [7:0] cat_type [0:N_SLOTS-1];
reg cat_valid [0:N_SLOTS-1];
reg [31:0] cat_crc [0:N_SLOTS-1];
assign cat_out_offset = cat_offset[cat_read_sel];
assign cat_out_length = cat_length[cat_read_sel];
assign cat_out_type = cat_type[cat_read_sel];
assign cat_out_valid = cat_valid[cat_read_sel];
assign cat_out_crc = cat_crc[cat_read_sel];
// ============================================================
// flash_copy_engine instance (F2/F3 primitive, unmodified)
// ============================================================
reg fce_start;
reg [1:0] fce_dir;
reg [23:0] fce_flash_addr;
reg [PSRAM_ADDR_WIDTH-1:0] fce_psram_addr;
reg [23:0] fce_len;
wire fce_busy, fce_done, fce_err;
wire fce_d_req, fce_d_wr;
wire [PSRAM_ADDR_WIDTH-1:0] fce_d_addr;
wire signed [7:0] fce_d_wdata;
localparam FCE_DIR_LOAD = 2'd0;
localparam FCE_DIR_SAVE = 2'd1;
localparam FCE_DIR_ERASE = 2'd2;
flash_copy_engine #(
.PSRAM_ADDR_WIDTH(PSRAM_ADDR_WIDTH),
.CLK_FREQ_MHZ(CLK_FREQ_MHZ),
.SCLK_DIV(SCLK_DIV)
) u_fce (
.clk(clk), .rst(rst),
.mosi(mosi), .miso(miso), .cs_n(cs_n), .sclk(sclk),
.op_start(fce_start), .op_dir(fce_dir),
.flash_addr(fce_flash_addr), .psram_addr(fce_psram_addr), .len(fce_len),
.busy(fce_busy), .done(fce_done), .err(fce_err),
.d_req(fce_d_req), .d_wr(fce_d_wr), .d_addr(fce_d_addr), .d_wdata(fce_d_wdata),
.d_rdata(d_rdata), .d_ready(d_ready)
);
// ============================================================
// Port D mux: fce owns it whenever busy; otherwise this module
// drives it directly for catalog-staging-buffer access. See
// header for why this is safe without a new arbiter port.
// ============================================================
reg fsm_d_req;
reg fsm_d_wr;
reg [PSRAM_ADDR_WIDTH-1:0] fsm_d_addr;
reg signed [7:0] fsm_d_wdata;
assign d_req = fce_busy ? fce_d_req : fsm_d_req;
assign d_wr = fce_busy ? fce_d_wr : fsm_d_wr;
assign d_addr = fce_busy ? fce_d_addr : fsm_d_addr;
assign d_wdata = fce_busy ? fce_d_wdata : fsm_d_wdata;
// ============================================================
// CRC32 accumulator, tapped from fce's own Port D traffic while
// it is the active owner (see header). Active only during
// LOAD_SLOT/SAVE_SLOT's data-transfer phase (`crc_active`).
// ============================================================
reg crc_active;
reg [31:0] crc_acc;
wire [31:0] crc_next;
wire [7:0] crc_tap_byte = fce_d_wr ? fce_d_wdata : d_rdata; // LOAD writes vs SAVE reads
crc32_byte u_crc32 (
.crc_in(crc_acc),
.data(crc_tap_byte),
.crc_out(crc_next)
);
// NOTE: fce's own `d_req` is combinationally defined (in
// flash_copy_engine.v) as `(waiting_state) && !d_ready` -- so
// `fce_d_req && d_ready` is a contradiction, always false, and
// would never fire. The correct "a byte transfer through fce
// just completed" condition is simply `fce_busy && d_ready`: at
// that exact cycle fce's own state hasn't advanced out of its
// waiting state yet (same one-cycle-late FSM update reasoning
// documented in flash_copy_engine.v), so fce_busy is still 1,
// and this is the only reason d_ready would be high while fce
// owns Port D (see the mux above).
always @(posedge clk) begin
if (rst) begin
crc_acc <= 32'hFFFFFFFF;
end else if (crc_active && fce_busy && d_ready) begin
crc_acc <= crc_next;
end else if (!crc_active) begin
crc_acc <= 32'hFFFFFFFF; // re-armed for the next operation
end
end
// ============================================================
// Main FSM
// ============================================================
localparam ST_IDLE = 4'd0;
localparam ST_CATRD_FCE_GO = 4'd1;
localparam ST_CATRD_FCE_WAIT = 4'd2;
localparam ST_CATRD_RD_ISSUE = 4'd3;
localparam ST_CATRD_RD_WAIT = 4'd4;
localparam ST_CATWR_SER_ISSUE = 4'd5;
localparam ST_CATWR_SER_WAIT = 4'd6;
localparam ST_CATWR_FCE_GO = 4'd7;
localparam ST_CATWR_FCE_WAIT = 4'd8;
localparam ST_SLOT_FCE_GO = 4'd9;
localparam ST_SLOT_FCE_WAIT = 4'd10;
localparam ST_SLOT_CATWR_KICK = 4'd11; // SAVE_SLOT only: fall into the CAT_WRITE_SLOT persist sequence
localparam ST_DONE = 4'd15;
reg [3:0] state;
reg [8:0] byte_idx; // 0..255, position within the 256B catalog
reg [3:0] cur_slot; // slot_id of the entry currently being (de)serialized
reg [127:0] entry_shift; // 16-byte (de)serialization shift register, see header
reg [2:0] active_op;
reg [3:0] active_slot;
integer rst_i;
assign busy = (state != ST_IDLE);
always @(posedge clk) begin
if (rst) begin
state <= ST_IDLE;
done <= 1'b0;
err <= 1'b0;
fce_start <= 1'b0;
fce_dir <= 2'd0;
fce_flash_addr <= 24'h0;
fce_psram_addr <= {PSRAM_ADDR_WIDTH{1'b0}};
fce_len <= 24'h0;
fsm_d_req <= 1'b0;
fsm_d_wr <= 1'b0;
fsm_d_addr <= {PSRAM_ADDR_WIDTH{1'b0}};
fsm_d_wdata <= 8'sd0;
crc_active <= 1'b0;
byte_idx <= 9'h0;
cur_slot <= 4'h0;
entry_shift <= 128'h0;
active_op <= 2'd0;
active_slot <= 4'h0;
for (rst_i = 0; rst_i < N_SLOTS; rst_i = rst_i + 1) begin
cat_offset[rst_i] <= 24'h0;
cat_length[rst_i] <= 24'h0;
cat_type[rst_i] <= 8'h0;
cat_valid[rst_i] <= 1'b0;
cat_crc[rst_i] <= 32'h0;
end
end else begin
fce_start <= 1'b0;
fsm_d_req <= 1'b0;
done <= 1'b0;
case (state)
// ========================================
ST_IDLE: begin
if (op_start) begin
active_op <= op_code;
active_slot <= slot_id;
case (op_code)
OP_CAT_READ: begin
err <= 1'b0;
fce_start <= 1'b1;
fce_dir <= FCE_DIR_LOAD;
fce_flash_addr <= CATALOG_SECTOR_ADDR;
fce_psram_addr <= CATALOG_PSRAM_ADDR;
fce_len <= CATALOG_BYTES[23:0];
state <= ST_CATRD_FCE_WAIT;
end
OP_CAT_WRITE_SLOT: begin
err <= 1'b0;
cat_offset[slot_id] <= new_offset;
cat_length[slot_id] <= new_length;
cat_type[slot_id] <= new_type;
cat_valid[slot_id] <= 1'b0; // no verified data yet, see header
cat_crc[slot_id] <= 32'h0;
byte_idx <= 9'h0;
cur_slot <= 4'h0;
state <= ST_CATWR_SER_ISSUE;
end
OP_LOAD_SLOT: begin
if (!cat_valid[slot_id]) begin
err <= 1'b1;
done <= 1'b1;
end else begin
err <= 1'b0;
crc_active <= 1'b1;
fce_start <= 1'b1;
fce_dir <= FCE_DIR_LOAD;
fce_flash_addr <= cat_offset[slot_id];
fce_psram_addr <= ext_psram_addr;
fce_len <= cat_length[slot_id];
state <= ST_SLOT_FCE_WAIT;
end
end
OP_SAVE_SLOT: begin
err <= 1'b0;
crc_active <= 1'b1;
fce_start <= 1'b1;
fce_dir <= FCE_DIR_SAVE;
fce_flash_addr <= cat_offset[slot_id]; // registered by a prior CAT_WRITE_SLOT
fce_psram_addr <= ext_psram_addr;
fce_len <= ext_length;
state <= ST_SLOT_FCE_WAIT;
end
// F5: raw block ops, no catalog/CRC
// involvement at all -- straight pass-
// through to flash_copy_engine with an
// explicit flash address from the host.
OP_FLASH_READ_BLOCK: begin
err <= 1'b0;
fce_start <= 1'b1;
fce_dir <= FCE_DIR_LOAD;
fce_flash_addr <= raw_flash_addr;
fce_psram_addr <= ext_psram_addr;
fce_len <= ext_length;
state <= ST_SLOT_FCE_WAIT;
end
OP_FLASH_WRITE_BLOCK: begin
err <= 1'b0;
fce_start <= 1'b1;
fce_dir <= FCE_DIR_SAVE;
fce_flash_addr <= raw_flash_addr;
fce_psram_addr <= ext_psram_addr;
fce_len <= ext_length;
state <= ST_SLOT_FCE_WAIT;
end
OP_FLASH_ERASE: begin
err <= 1'b0;
fce_start <= 1'b1;
fce_dir <= FCE_DIR_ERASE;
fce_flash_addr <= raw_flash_addr;
state <= ST_SLOT_FCE_WAIT;
end
default: begin
err <= 1'b1;
done <= 1'b1;
end
endcase
end
end
// ========================================
// CAT_READ: load the catalog sector into the
// PSRAM staging buffer (fce, unmodified), then
// parse it 16 bytes (one entry) at a time.
// ========================================
ST_CATRD_FCE_WAIT: begin
if (fce_done) begin
byte_idx <= 9'h0;
state <= ST_CATRD_RD_ISSUE;
end
end
ST_CATRD_RD_ISSUE: begin
fsm_d_req <= 1'b1;
fsm_d_wr <= 1'b0;
fsm_d_addr <= CATALOG_PSRAM_ADDR + byte_idx;
state <= ST_CATRD_RD_WAIT;
end
ST_CATRD_RD_WAIT: begin
if (d_ready) begin
if (byte_idx[3:0] == 4'hF) begin
// 16th byte of this entry (bytes 12-15
// are reserved/ignored, so `d_rdata`
// itself -- this 16th byte -- never
// actually feeds any decoded field, only
// the PRE-update `entry_shift`, which at
// this point holds bytes 0-14 from the
// 15 shifts so far: entry_shift[119:112]
// = byte0 ... entry_shift[7:0] = byte14.
// See the module header for the full
// derivation.
cat_offset[byte_idx[8:4]] <= entry_shift[119:96]; // bytes 0-2
cat_length[byte_idx[8:4]] <= entry_shift[95:72]; // bytes 3-5
cat_type[byte_idx[8:4]] <= entry_shift[71:64]; // byte 6
cat_valid[byte_idx[8:4]] <= (entry_shift[63:56] == VALID_MARK); // byte 7
cat_crc[byte_idx[8:4]] <= entry_shift[55:24]; // bytes 8-11
end
entry_shift <= {entry_shift[119:0], d_rdata};
if (byte_idx == 9'd255) begin
state <= ST_DONE;
end else begin
byte_idx <= byte_idx + 9'd1;
state <= ST_CATRD_RD_ISSUE;
end
end
end
// ========================================
// CAT_WRITE_SLOT: serialize all N_SLOTS entries
// (from the just-updated on-chip table) into the
// PSRAM staging buffer, then persist via fce's
// unmodified DIR_SAVE (erase + page-program loop
// + WIP poll, all F3).
// ========================================
ST_CATWR_SER_ISSUE: begin
fsm_d_req <= 1'b1;
fsm_d_wr <= 1'b1;
fsm_d_addr <= CATALOG_PSRAM_ADDR + byte_idx;
// Byte to send: byte_idx[3:0] selects which of the
// 16 bytes of cur_slot's entry, MSB-first, same
// layout as the CAT_READ decode.
case (byte_idx[3:0])
4'h0: fsm_d_wdata <= cat_offset[cur_slot][23:16];
4'h1: fsm_d_wdata <= cat_offset[cur_slot][15:8];
4'h2: fsm_d_wdata <= cat_offset[cur_slot][7:0];
4'h3: fsm_d_wdata <= cat_length[cur_slot][23:16];
4'h4: fsm_d_wdata <= cat_length[cur_slot][15:8];
4'h5: fsm_d_wdata <= cat_length[cur_slot][7:0];
4'h6: fsm_d_wdata <= cat_type[cur_slot];
4'h7: fsm_d_wdata <= cat_valid[cur_slot] ? VALID_MARK : 8'h00;
4'h8: fsm_d_wdata <= cat_crc[cur_slot][31:24];
4'h9: fsm_d_wdata <= cat_crc[cur_slot][23:16];
4'hA: fsm_d_wdata <= cat_crc[cur_slot][15:8];
4'hB: fsm_d_wdata <= cat_crc[cur_slot][7:0];
default: fsm_d_wdata <= 8'h00; // reserved, bytes C-F
endcase
state <= ST_CATWR_SER_WAIT;
end
ST_CATWR_SER_WAIT: begin
if (d_ready) begin
if (byte_idx == 9'd255) begin
fce_start <= 1'b1;
fce_dir <= FCE_DIR_SAVE;
fce_flash_addr <= CATALOG_SECTOR_ADDR;
fce_psram_addr <= CATALOG_PSRAM_ADDR;
fce_len <= CATALOG_BYTES[23:0];
state <= ST_CATWR_FCE_WAIT;
end else begin
byte_idx <= byte_idx + 9'd1;
cur_slot <= (byte_idx[3:0] == 4'hF) ? (byte_idx[8:4] + 4'd1) : byte_idx[8:4];
state <= ST_CATWR_SER_ISSUE;
end
end
end
ST_CATWR_FCE_WAIT: begin
// Reached either directly from a CAT_WRITE_SLOT
// op, or via ST_SLOT_CATWR_KICK after a
// SAVE_SLOT's own data transfer already updated
// the catalog entry -- either way, this state is
// just "persist the catalog sector, then done".
if (fce_done)
state <= ST_DONE;
end
// ========================================
// LOAD_SLOT / SAVE_SLOT: run fce's existing
// DIR_LOAD/DIR_SAVE verbatim, CRC accumulated via
// the tap above.
// ========================================
ST_SLOT_FCE_WAIT: begin
if (fce_done) begin
crc_active <= 1'b0;
case (active_op)
OP_LOAD_SLOT: begin
// fce_err here would mean the
// catalog's own (offset,length) is
// somehow out of range -- shouldn't
// happen for a slot that passed the
// CAT_WRITE_SLOT/SAVE_SLOT bounds
// checks, but checked anyway rather
// than assumed.
if (fce_err || ((crc_acc ^ 32'hFFFFFFFF) != cat_crc[active_slot]))
err <= 1'b1;
state <= ST_DONE;
end
OP_SAVE_SLOT: begin
// BUG FIXED HERE (found during F5
// integration review, before ever
// running the F5 testbench): this
// branch previously updated+persisted
// the catalog as "valid" unconditionally
// on fce_done, without checking
// fce_err first -- a SAVE_SLOT whose
// underlying flash_copy_engine call
// itself failed (e.g. an out-of-range
// length) would still have been
// marked valid in the catalog. Now
// skips the catalog update entirely
// on a failed transfer.
if (fce_err) begin
err <= 1'b1;
state <= ST_DONE;
end else begin
cat_length[active_slot] <= fce_len;
cat_crc[active_slot] <= crc_acc ^ 32'hFFFFFFFF;
cat_valid[active_slot] <= 1'b1;
byte_idx <= 9'h0;
cur_slot <= 4'h0;
state <= ST_SLOT_CATWR_KICK;
end
end
default: begin
// F5 raw block ops (FLASH_READ_BLOCK/
// FLASH_WRITE_BLOCK/FLASH_ERASE): no
// catalog/CRC involvement, just
// forward fce's own error status.
err <= fce_err;
state <= ST_DONE;
end
endcase
end
end
// One-cycle bridge: the cat_* writes above need a
// cycle to land before ST_CATWR_SER_ISSUE reads them
// back out for serialization.
ST_SLOT_CATWR_KICK: begin
state <= ST_CATWR_SER_ISSUE;
end
// ========================================
ST_DONE: begin
done <= 1'b1;
state <= ST_IDLE;
end
default: state <= ST_IDLE;
endcase
end
end
endmodule