sd_fat_rewrite¶
Source: Verilog/SD-FAT/circuit/sd_fat_rewrite.v
Hierarchy: not instantiated by any of the 9 build tops (elaborated by yosys).
Module hierarchy - All modules

Schematic¶
Drawn from the Verilog: no build top uses this module, so it was elaborated from its own file with no defines and default parameters. Sub-modules are boxes (click the picture to open it full size; there every sub-module box links to its page, and every wire shows its Verilog name).
Description¶
FAT16/FAT32 in-place file replacement ("delete + recreate") Given a file's directory entry location and the filesystem geometry (all exported by the modified sd_file_reader after a successful scan), this module makes the file exactly new_size bytes long: realloc = 0 (target's allocation already big enough): - patch the SIZE field of the directory entry only create = 1 (the file does not exist yet): - scan the root directory for a free 32-byte slot (FAT16 linear region / FAT32 root cluster chain) and write a fresh 8.3 entry (name, Archive attribute, fixed date, zero size/cluster), then proceed exactly like realloc=1 realloc = 1 (full replacement): - FREE the file's old cluster chain (both FAT copies) - SCAN the FAT for a CONTIGUOUS run of enough free clusters - WRITE the new chain (both FAT copies) - patch the directory entry: size + first cluster The caller then writes the data sectors (the new chain is contiguous, first data sector = data_base_sector + cluster_size * new_first). All sector traffic goes through one sd_writer engine (CMD17 reads + CMD24 writes) on an already-initialized card; an internal 512-byte buffer holds the sector being read-modify-written. Scope limits (documented in SD-FAT/README.md): - the new chain is contiguous (fails with err if no contiguous run of free clusters exists - "no space") - FAT32: the FSInfo free-cluster count is NOT updated (fsck reports and fixes the summary; the filesystem itself stays consistent), and files beyond cluster 65535 are out of reach (reader limit) - directory entry name/date are left untouched This file is ORIGINAL ND-120 project code (MIT, like the repository). Last reviewed: 11-JUL-2026 Ronny Hansen
Ports¶
| Direction | Width | Name | Description |
|---|---|---|---|
| input | 1 |
clk |
|
| input | 1 |
rst_n (active low) |
|
| input | 1 |
start |
1-cycle pulse; only when busy=0 |
| input | 1 |
realloc |
0 = size patch only, 1 = free + allocate + patch |
| output | 1 |
busy |
|
| output | 1 |
done |
1-cycle pulse |
| output | 1 |
err |
1-cycle pulse (engine error, chain loop, no space) |
| output | [1:0] |
err_kind |
valid with err, held until the next start: |
| input | 1 |
fs_is_fat32 |
|
| input | [7:0] |
cluster_size |
sectors per cluster (power of two) |
| input | [31:0] |
fat0_sector |
|
| input | [31:0] |
sectors_per_fat |
|
| input | [7:0] |
num_fats |
1 or 2 (2 on every real card) |
| input | [31:0] |
data_base_sector |
BIASED: cluster c starts at data_base + cluster_size*c |
| input | [31:0] |
total_sectors |
|
| input | [31:0] |
root_start |
FAT16: first root dir sector |
| input | [31:0] |
root_secs |
FAT16: number of root dir sectors |
| input | [31:0] |
root_cluster |
FAT32: first cluster of the root dir |
| input | 1 |
create |
1 = no entry exists: make one first |
| input | [87:0] |
name83 |
11-char 8.3 name, e.g. "IO DAT" |
| input | [15:0] |
fdate |
FAT date word stamped on creation |
| input | [31:0] |
dir_sector |
absolute sector of the directory entry |
| input | [3:0] |
dir_index |
entry index within that sector (0-15) |
| input | [31:0] |
old_first_cluster |
|
| input | [31:0] |
new_size |
bytes |
| output | [31:0] |
new_first_cluster |
= old_first_cluster when realloc=0 |
| output | 1 |
eng_start |
|
| output | 1 |
eng_rd |
1 = CMD17 read, 0 = CMD24 write |
| output | [31:0] |
eng_sector |
|
| input | 1 |
eng_busy |
|
| input | 1 |
eng_done |
|
| input | 1 |
eng_err |
|
| input | 1 |
eng_rx_we |
read data stream into the buffer |
| input | [8:0] |
eng_rx_addr |
|
| input | [7:0] |
eng_rx_data |
|
| input | [8:0] |
eng_tx_addr |
write data source from the buffer |
| output | [7:0] |
eng_tx_data |
Verilog source¶
Verilog/SD-FAT/circuit/sd_fat_rewrite.v on GitHub.
Show the Verilog of sd_fat_rewrite (670 lines)
/****************************************************************************
** FAT16/FAT32 in-place file replacement ("delete + recreate") **
** **
** Given a file's directory entry location and the filesystem geometry **
** (all exported by the modified sd_file_reader after a successful scan), **
** this module makes the file exactly new_size bytes long: **
** **
** realloc = 0 (target's allocation already big enough): **
** - patch the SIZE field of the directory entry only **
** create = 1 (the file does not exist yet): **
** - scan the root directory for a free 32-byte slot (FAT16 linear **
** region / FAT32 root cluster chain) and write a fresh 8.3 entry **
** (name, Archive attribute, fixed date, zero size/cluster), then **
** proceed exactly like realloc=1 **
** realloc = 1 (full replacement): **
** - FREE the file's old cluster chain (both FAT copies) **
** - SCAN the FAT for a CONTIGUOUS run of enough free clusters **
** - WRITE the new chain (both FAT copies) **
** - patch the directory entry: size + first cluster **
** **
** The caller then writes the data sectors (the new chain is contiguous, **
** first data sector = data_base_sector + cluster_size * new_first). **
** **
** All sector traffic goes through one sd_writer engine (CMD17 reads + **
** CMD24 writes) on an already-initialized card; an internal 512-byte **
** buffer holds the sector being read-modify-written. **
** **
** Scope limits (documented in SD-FAT/README.md): **
** - the new chain is contiguous (fails with err if no contiguous run **
** of free clusters exists - "no space") **
** - FAT32: the FSInfo free-cluster count is NOT updated (fsck reports **
** and fixes the summary; the filesystem itself stays consistent), **
** and files beyond cluster 65535 are out of reach (reader limit) **
** - directory entry name/date are left untouched **
** **
** This file is ORIGINAL ND-120 project code (MIT, like the repository). **
** **
** Last reviewed: 11-JUL-2026 **
** Ronny Hansen **
*****************************************************************************/
module sd_fat_rewrite (
input wire clk,
input wire rst_n,
// command
input wire start, // 1-cycle pulse; only when busy=0
input wire realloc, // 0 = size patch only, 1 = free + allocate + patch
output wire busy,
output reg done, // 1-cycle pulse
output reg err, // 1-cycle pulse (engine error, chain loop, no space)
output reg [1:0] err_kind, // valid with err, held until the next start:
// 0 = resource verdict (no contiguous free
// run / root directory full),
// 1 = engine READ failed (FAT/dir sector),
// 2 = engine WRITE failed,
// 3 = FAT chain corrupt (loop while freeing)
// filesystem geometry (from sd_file_reader, stable once file_found=1)
input wire fs_is_fat32,
input wire [7:0] cluster_size, // sectors per cluster (power of two)
input wire [31:0] fat0_sector,
input wire [31:0] sectors_per_fat,
input wire [7:0] num_fats, // 1 or 2 (2 on every real card)
input wire [31:0] data_base_sector, // BIASED: cluster c starts at data_base + cluster_size*c
input wire [31:0] total_sectors,
// root directory location (for create): FAT16 linear region and the
// FAT32 root cluster
input wire [31:0] root_start, // FAT16: first root dir sector
input wire [31:0] root_secs, // FAT16: number of root dir sectors
input wire [31:0] root_cluster, // FAT32: first cluster of the root dir
// target file
input wire create, // 1 = no entry exists: make one first
input wire [87:0] name83, // 11-char 8.3 name, e.g. "IO DAT"
input wire [15:0] fdate, // FAT date word stamped on creation
input wire [31:0] dir_sector, // absolute sector of the directory entry
input wire [3:0] dir_index, // entry index within that sector (0-15)
input wire [31:0] old_first_cluster,
input wire [31:0] new_size, // bytes
// result
output reg [31:0] new_first_cluster, // = old_first_cluster when realloc=0
// sd_writer engine (exclusive use while busy)
output reg eng_start,
output reg eng_rd, // 1 = CMD17 read, 0 = CMD24 write
output reg [31:0] eng_sector,
input wire eng_busy,
input wire eng_done,
input wire eng_err,
input wire eng_rx_we, // read data stream into the buffer
input wire [8:0] eng_rx_addr,
input wire [7:0] eng_rx_data,
input wire [8:0] eng_tx_addr, // write data source from the buffer
output reg [7:0] eng_tx_data
);
// ------------------------------------------------------------- 512 B buffer
reg [7:0] sbuf[0:511];
reg fsm_we;
reg [8:0] fsm_addr;
reg [7:0] fsm_wdata;
reg [7:0] fsm_q;
always @(posedge clk) begin
if (eng_rx_we) sbuf[eng_rx_addr] <= eng_rx_data;
else if (fsm_we) sbuf[fsm_addr] <= fsm_wdata;
end
always @(posedge clk) fsm_q <= sbuf[fsm_addr];
always @(posedge clk) eng_tx_data <= sbuf[eng_tx_addr];
// ------------------------------------------------------------- helpers
// FAT sector and byte offset of a cluster's entry
function [31:0] fat_sec(input [31:0] c);
fat_sec = fat0_sector + (fs_is_fat32 ? (c >> 7) : (c >> 8));
endfunction
function [8:0] fat_off(input [31:0] c);
fat_off = fs_is_fat32 ? {c[6:0], 2'b00} : {c[7:0], 1'b0};
endfunction
// end-of-chain / invalid-cluster test (masked value)
function is_eoc(input [31:0] c);
is_eoc = fs_is_fat32 ? (c[27:0] >= 28'hFFFFFF7) : (c[15:0] >= 16'hFFF7);
endfunction
function [4:0] log2cs(input [7:0] cs);
casez (cs)
8'b1???????: log2cs = 5'd7;
8'b01??????: log2cs = 5'd6;
8'b001?????: log2cs = 5'd5;
8'b0001????: log2cs = 5'd4;
8'b00001???: log2cs = 5'd3;
8'b000001??: log2cs = 5'd2;
8'b0000001?: log2cs = 5'd1;
default: log2cs = 5'd0;
endcase
endfunction
// last addressable cluster: clusters 2..max_cluster
wire [31:0] usable_sectors = total_sectors - (data_base_sector + {24'b0, cluster_size} * 2);
wire [31:0] max_cluster = (usable_sectors >> log2cs(cluster_size)) + 32'd1;
// ------------------------------------------------------------- FSM
localparam S_IDLE = 6'd0;
localparam S_KCALC = 6'd1; // K = clusters needed (subtract loop)
localparam S_FREE_CHK = 6'd2; // old chain: next hop valid?
localparam S_FREE_RD = 6'd3; // read the FAT sector holding entry(c)
localparam S_FREE_E0 = 6'd4; // fetch entry bytes (2 or 4) -> nextc
localparam S_FREE_E1 = 6'd5;
localparam S_FREE_Z = 6'd6; // zero the entry bytes in the buffer
localparam S_FREE_W1 = 6'd7; // write FAT #0
localparam S_FREE_W2 = 6'd8; // write FAT #1
localparam S_SCAN_RD = 6'd9; // read the FAT sector holding entry(scan_c)
localparam S_SCAN_E0 = 6'd10; // fetch entry bytes -> val
localparam S_SCAN_E1 = 6'd11;
localparam S_SCAN_DEC = 6'd12; // free-run bookkeeping
localparam S_CH_RD = 6'd13; // chain write: read FAT sector
localparam S_CH_P = 6'd14; // patch all new entries within this sector
localparam S_CH_W1 = 6'd15; // write FAT #0
localparam S_CH_W2 = 6'd16; // write FAT #1
localparam S_DIR_RD = 6'd17; // read the directory sector
localparam S_DIR_P = 6'd18; // patch size (+ first cluster)
localparam S_DIR_W = 6'd19; // write it back
localparam S_DONE = 6'd20;
localparam S_ERR = 6'd21;
localparam S_EGO = 6'd22; // engine op: pulse start
localparam S_EWAIT = 6'd23; // engine op: wait done/err
localparam S_BRD = 6'd24; // buffered byte read: wait 1 cycle
localparam S_DIR_P2 = 6'd26; // patch the first-cluster fields
localparam S_CH_NEXT = 6'd27; // chain write: more entries or done?
localparam S_CR_RD = 6'd28; // create: read a root directory sector
localparam S_CR_SCAN = 6'd29; // create: hunt for a free entry slot
localparam S_CR_WR = 6'd30; // create: write the fresh entry bytes
localparam S_CR_WSEC = 6'd31; // create: flush the directory sector
localparam S_CR_FE0 = 6'd32; // create: FAT32 root chain hop
localparam S_CR_FE1 = 6'd33;
reg [5:0] state, e_ret, b_ret;
reg realloc_r;
reg [31:0] tmp; // K subtract scratch
reg [12:0] kclust; // clusters needed (up to 4096: 2 MB file at 512 B clusters)
reg [31:0] cur, nextc; // chain walk
reg [16:0] guard;
reg [31:0] scan_c, run_start;
reg [31:0] dsec_r; // directory entry location actually used
reg [3:0] didx_r;
reg [31:0] oldc_r; // first cluster of the chain to free (0 for create)
reg [31:0] sl_sec, sl_clu, sl_rem; // slot search position
reg [7:0] sl_off; // sector offset inside the FAT32 root cluster
reg [4:0] sl_i; // entry index inside the sector
reg [5:0] cr_i; // entry byte counter
reg [8:0] run_len;
reg [31:0] cur_sec; // FAT sector currently in the buffer
reg [12:0] chain_i; // chain write index
reg [31:0] ent_val; // assembled entry value / value to write
reg [1:0] byte_i;
wire [17:0] bytes_per_cluster = {1'b0, cluster_size, 9'b0};
assign busy = (state != S_IDLE);
// a fresh directory entry: 8.3 name, Archive, fixed date, empty file
function [7:0] crbyte(input [5:0] i);
begin
if (i < 6'd11) crbyte = name83[8*(10-i)+:8];
else case (i)
6'd11: crbyte = 8'h20; // ATTR_ARCHIVE
6'd16: crbyte = fdate[7:0]; // create date
6'd17: crbyte = fdate[15:8];
6'd18: crbyte = fdate[7:0]; // access date
6'd19: crbyte = fdate[15:8];
6'd24: crbyte = fdate[7:0]; // write date
6'd25: crbyte = fdate[15:8];
default: crbyte = 8'h00; // times, cluster, size: zero
endcase
end
endfunction
// entry value for chain writing: next cluster or end-of-chain
wire [31:0] chain_val = (chain_i == kclust - 13'd1)
? (fs_is_fat32 ? 32'h0FFFFFFF : 32'h0000FFFF)
: (new_first_cluster + {19'b0, chain_i} + 32'd1);
always @(posedge clk) begin
if (!rst_n) begin
state <= S_IDLE;
e_ret <= S_IDLE;
b_ret <= S_IDLE;
done <= 1'b0;
err <= 1'b0;
err_kind <= 2'd0;
eng_start <= 1'b0;
eng_rd <= 1'b0;
eng_sector <= 0;
fsm_we <= 1'b0;
fsm_addr <= 0;
fsm_wdata <= 0;
realloc_r <= 1'b0;
tmp <= 0;
kclust <= 0;
cur <= 0;
nextc <= 0;
guard <= 0;
scan_c <= 0;
run_start <= 0;
run_len <= 0;
cur_sec <= 0;
chain_i <= 0;
ent_val <= 0;
byte_i <= 0;
new_first_cluster <= 0;
dsec_r <= 0;
didx_r <= 0;
oldc_r <= 0;
sl_sec <= 0;
sl_clu <= 0;
sl_rem <= 0;
sl_off <= 0;
sl_i <= 0;
cr_i <= 0;
end else begin
done <= 1'b0;
err <= 1'b0;
eng_start <= 1'b0;
fsm_we <= 1'b0;
case (state)
S_IDLE:
if (start) begin
err_kind <= 2'd0;
realloc_r <= realloc | create;
dsec_r <= dir_sector;
didx_r <= dir_index;
oldc_r <= create ? 32'd0 : old_first_cluster;
new_first_cluster <= old_first_cluster;
guard <= 0;
if (create) begin
// hunt for a free root directory slot first
if (fs_is_fat32) begin
sl_clu <= root_cluster;
sl_off <= 0;
sl_sec <= data_base_sector + {24'b0, cluster_size} * root_cluster;
end else begin
sl_sec <= root_start;
sl_rem <= root_secs;
end
state <= S_CR_RD;
end else if (realloc) begin
tmp <= new_size;
kclust <= 0;
state <= S_KCALC;
end else state <= S_DIR_RD;
end
// ---- clusters needed --------------------------------------------
S_KCALC:
if (tmp == 0) begin
if (kclust == 0) kclust <= 13'd1; // never allocate an empty chain
cur <= fs_is_fat32 ? {4'b0, oldc_r[27:0]} : {16'b0, oldc_r[15:0]};
state <= S_FREE_CHK;
end else if (tmp >= {14'b0, bytes_per_cluster}) begin
tmp <= tmp - {14'b0, bytes_per_cluster};
kclust <= kclust + 13'd1;
end else begin
tmp <= 0;
kclust <= kclust + 13'd1;
end
// ---- free the old chain -----------------------------------------
S_FREE_CHK:
if (cur < 32'd2 || is_eoc(cur) || cur > max_cluster) begin
scan_c <= 32'd2; // chain freed: hunt for a contiguous run
run_len <= 0;
cur_sec <= 32'hFFFFFFFF;
state <= S_SCAN_RD;
end else if (guard[16]) begin
err_kind <= 2'd3;
state <= S_ERR; // chain loop (corrupt FAT)
end else begin
eng_sector <= fat_sec(cur);
eng_rd <= 1'b1;
e_ret <= S_FREE_E0;
state <= S_EGO;
end
S_FREE_E0: begin // read entry bytes -> nextc (little endian)
fsm_addr <= fat_off(cur);
byte_i <= 0;
ent_val <= 0;
b_ret <= S_FREE_E1;
state <= S_BRD;
end
S_FREE_E1: begin
ent_val <= ent_val | ({24'b0, fsm_q} << {byte_i, 3'b000});
if ({1'b0, byte_i} == (fs_is_fat32 ? 3'd3 : 3'd1)) begin
state <= S_FREE_Z;
fsm_addr <= fat_off(cur);
byte_i <= 0;
end else begin
byte_i <= byte_i + 2'd1;
fsm_addr <= fsm_addr + 9'd1;
b_ret <= S_FREE_E1;
state <= S_BRD;
end
end
S_FREE_Z: begin // zero the entry (FAT32: preserve the reserved nibble)
// NOTE the write latency: fsm_we/addr/wdata registered here take
// effect one clock later, so the address for byte k is advanced
// WITH byte k (k>=1), never on the first byte
fsm_we <= 1'b1;
fsm_wdata <= (fs_is_fat32 && byte_i == 2'd3) ? (ent_val[31:24] & 8'hF0) : 8'h00;
if (byte_i != 2'd0) fsm_addr <= fsm_addr + 9'd1;
if ({1'b0, byte_i} == (fs_is_fat32 ? 3'd3 : 3'd1)) begin
nextc <= fs_is_fat32 ? {4'b0, ent_val[27:0]} : {16'b0, ent_val[15:0]};
eng_sector <= fat_sec(cur);
eng_rd <= 1'b0;
e_ret <= S_FREE_W2;
state <= S_EGO;
end else begin
byte_i <= byte_i + 2'd1;
end
end
S_FREE_W2:
if (num_fats > 8'd1) begin
eng_sector <= fat_sec(cur) + sectors_per_fat;
eng_rd <= 1'b0;
e_ret <= S_FREE_W1;
state <= S_EGO;
end else state <= S_FREE_W1;
S_FREE_W1: begin
cur <= nextc;
guard <= guard + 17'd1;
state <= S_FREE_CHK;
end
// ---- scan for a contiguous free run -----------------------------
S_SCAN_RD:
if (scan_c + {19'b0, kclust} - 32'd1 > max_cluster) begin
err_kind <= 2'd0;
state <= S_ERR; // no contiguous space (the scan itself succeeded)
end else if (fat_sec(scan_c) == cur_sec) begin
state <= S_SCAN_E0;
end else begin
eng_sector <= fat_sec(scan_c);
eng_rd <= 1'b1;
e_ret <= S_SCAN_E0;
state <= S_EGO;
end
S_SCAN_E0: begin
cur_sec <= fat_sec(scan_c);
fsm_addr <= fat_off(scan_c);
byte_i <= 0;
ent_val <= 0;
b_ret <= S_SCAN_E1;
state <= S_BRD;
end
S_SCAN_E1: begin
ent_val <= ent_val | ({24'b0, fsm_q} << {byte_i, 3'b000});
if ({1'b0, byte_i} == (fs_is_fat32 ? 3'd3 : 3'd1)) state <= S_SCAN_DEC;
else begin
byte_i <= byte_i + 2'd1;
fsm_addr <= fsm_addr + 9'd1;
b_ret <= S_SCAN_E1;
state <= S_BRD;
end
end
S_SCAN_DEC: begin
if ((fs_is_fat32 ? {4'b0, ent_val[27:0]} : {16'b0, ent_val[15:0]}) == 32'd0) begin
if (run_len == 0) run_start <= scan_c;
if ({4'b0, run_len} + 13'd1 == kclust) begin
new_first_cluster <= (run_len == 0) ? scan_c : run_start;
chain_i <= 0;
cur_sec <= 32'hFFFFFFFF;
state <= S_CH_RD;
end else begin
run_len <= run_len + 9'd1;
scan_c <= scan_c + 32'd1;
state <= S_SCAN_RD;
end
end else begin
run_len <= 0;
scan_c <= scan_c + 32'd1;
state <= S_SCAN_RD;
end
end
// ---- write the new chain (batched per FAT sector) ---------------
S_CH_RD:
if (fat_sec(new_first_cluster + {19'b0, chain_i}) == cur_sec) begin
state <= S_CH_P;
fsm_addr <= fat_off(new_first_cluster + {19'b0, chain_i});
byte_i <= 0;
ent_val <= chain_val;
end else begin
eng_sector <= fat_sec(new_first_cluster + {19'b0, chain_i});
eng_rd <= 1'b1;
e_ret <= S_CH_RD;
cur_sec <= fat_sec(new_first_cluster + {19'b0, chain_i});
state <= S_EGO;
end
S_CH_P: begin // write the entry bytes (little endian)
// (same write-latency rule as S_FREE_Z: advance the address WITH
// byte k for k>=1, never on the first byte)
fsm_we <= 1'b1;
fsm_wdata <= ent_val[7:0];
ent_val <= {8'b0, ent_val[31:8]};
if (byte_i != 2'd0) fsm_addr <= fsm_addr + 9'd1;
if ({1'b0, byte_i} == (fs_is_fat32 ? 3'd3 : 3'd1)) begin
chain_i <= chain_i + 13'd1;
// flush when this was the last entry, or the next one lives in
// another FAT sector; otherwise patch on in the same buffer
if (chain_i + 13'd1 == kclust ||
fat_sec(new_first_cluster + {19'b0, chain_i} + 32'd1) != cur_sec)
state <= S_CH_W1;
else state <= S_CH_RD; // same sector: re-enter to set up the next entry
end else begin
byte_i <= byte_i + 2'd1;
end
end
S_CH_W1: begin
eng_sector <= cur_sec;
eng_rd <= 1'b0;
e_ret <= S_CH_W2;
state <= S_EGO;
end
S_CH_W2:
if (num_fats > 8'd1) begin
eng_sector <= cur_sec + sectors_per_fat;
eng_rd <= 1'b0;
e_ret <= S_CH_NEXT;
state <= S_EGO;
end else state <= S_CH_NEXT;
S_CH_NEXT: state <= (chain_i >= kclust) ? S_DIR_RD : S_CH_RD;
// ---- patch the directory entry ----------------------------------
S_DIR_RD: begin
eng_sector <= dsec_r;
eng_rd <= 1'b1;
e_ret <= S_DIR_P;
byte_i <= 0;
state <= S_EGO;
end
S_DIR_P: begin // size dword at +0x1C; first cluster at +0x1A / +0x14
fsm_we <= 1'b1;
case (byte_i)
2'd0: begin fsm_addr <= {didx_r, 5'h1C}; fsm_wdata <= new_size[7:0]; end
2'd1: begin fsm_addr <= {didx_r, 5'h1D}; fsm_wdata <= new_size[15:8]; end
2'd2: begin fsm_addr <= {didx_r, 5'h1E}; fsm_wdata <= new_size[23:16]; end
2'd3: begin fsm_addr <= {didx_r, 5'h1F}; fsm_wdata <= new_size[31:24]; end
endcase
if (byte_i == 2'd3) begin
byte_i <= 0;
state <= realloc_r ? S_DIR_P2 : S_DIR_W;
end else byte_i <= byte_i + 2'd1;
end
S_DIR_P2: begin // patch the first-cluster fields (realloc only)
fsm_we <= 1'b1;
case (byte_i)
2'd0: begin fsm_addr <= {didx_r, 5'h1A}; fsm_wdata <= new_first_cluster[7:0]; end
2'd1: begin fsm_addr <= {didx_r, 5'h1B}; fsm_wdata <= new_first_cluster[15:8]; end
2'd2: begin fsm_addr <= {didx_r, 5'h14}; fsm_wdata <= fs_is_fat32 ? new_first_cluster[23:16] : 8'h00; end
2'd3: begin fsm_addr <= {didx_r, 5'h15}; fsm_wdata <= fs_is_fat32 ? new_first_cluster[31:24] : 8'h00; end
endcase
// (FAT16: 0x14/0x15 is reserved - writing zeros is correct)
if (byte_i == 2'd3) state <= S_DIR_W;
else byte_i <= byte_i + 2'd1;
end
S_DIR_W: begin
// MUST be dsec_r, not the dir_sector input: on the create path the
// entry lives in the root slot found by S_CR_SCAN (dsec_r), while
// dir_sector is meaningless (a file that did not exist has no known
// entry location - the caller passes stale/zero). Writing to
// dir_sector here put a root-directory sector image at sector 0,
// destroying the MBR/boot sector (field failure: WRITE test on a
// fresh card, then every mount died with FS SCAN TIMEOUT).
eng_sector <= dsec_r;
eng_rd <= 1'b0;
e_ret <= S_DONE;
state <= S_EGO;
end
S_DONE: begin
done <= 1'b1;
state <= S_IDLE;
end
S_ERR: begin
err <= 1'b1;
state <= S_IDLE;
end
// ---- create: find a free slot, write a fresh entry ---------------
S_CR_RD: begin
eng_sector <= sl_sec;
eng_rd <= 1'b1;
sl_i <= 0;
fsm_addr <= 9'd0; // entry 0, byte 0
b_ret <= S_CR_SCAN;
e_ret <= S_BRD;
state <= S_EGO;
end
S_CR_SCAN:
if (fsm_q == 8'hE5 || fsm_q == 8'h00) begin
// free slot: this becomes the file's directory entry
dsec_r <= sl_sec;
didx_r <= sl_i[3:0];
cr_i <= 0;
fsm_addr <= {sl_i[3:0], 5'd0};
state <= S_CR_WR;
end else if (sl_i == 5'd15) begin
// sector exhausted: next root sector
if (fs_is_fat32) begin
if (sl_off + 8'd1 < cluster_size) begin
sl_off <= sl_off + 8'd1;
sl_sec <= sl_sec + 32'd1;
state <= S_CR_RD;
end else begin
// follow the root cluster chain
eng_sector <= fat_sec(sl_clu);
eng_rd <= 1'b1;
e_ret <= S_CR_FE0;
state <= S_EGO;
end
end else begin
if (sl_rem <= 32'd1) begin
err_kind <= 2'd0;
state <= S_ERR; // root directory full
end
else begin
sl_rem <= sl_rem - 32'd1;
sl_sec <= sl_sec + 32'd1;
state <= S_CR_RD;
end
end
end else begin
sl_i <= sl_i + 5'd1;
fsm_addr <= {sl_i[3:0] + 4'd1, 5'd0};
b_ret <= S_CR_SCAN;
state <= S_BRD;
end
S_CR_FE0: begin // fetch the FAT32 root chain entry
fsm_addr <= fat_off(sl_clu);
byte_i <= 0;
ent_val <= 0;
b_ret <= S_CR_FE1;
state <= S_BRD;
end
S_CR_FE1: begin
ent_val <= ent_val | ({24'b0, fsm_q} << {byte_i, 3'b000});
if ({1'b0, byte_i} == 3'd3) begin
if (is_eoc((ent_val | ({24'b0, fsm_q} << {byte_i, 3'b000})) & 32'h0FFFFFFF) ||
((ent_val | ({24'b0, fsm_q} << {byte_i, 3'b000})) & 32'h0FFFFFFF) < 32'd2) begin
err_kind <= 2'd0;
state <= S_ERR; // root directory full
end
else begin
sl_clu <= (ent_val | ({24'b0, fsm_q} << {byte_i, 3'b000})) & 32'h0FFFFFFF;
sl_off <= 0;
sl_sec <= data_base_sector + {24'b0, cluster_size} *
((ent_val | ({24'b0, fsm_q} << {byte_i, 3'b000})) & 32'h0FFFFFFF);
state <= S_CR_RD;
end
end else begin
byte_i <= byte_i + 2'd1;
fsm_addr <= fsm_addr + 9'd1;
b_ret <= S_CR_FE1;
state <= S_BRD;
end
end
S_CR_WR: begin // 32 fresh entry bytes (same write-latency rule)
fsm_we <= 1'b1;
fsm_wdata <= crbyte(cr_i[5:0] - 6'd0);
if (cr_i != 6'd0) fsm_addr <= fsm_addr + 9'd1;
if (cr_i == 6'd31) begin
state <= S_CR_WSEC;
end else cr_i <= cr_i + 6'd1;
end
S_CR_WSEC: begin // flush; the root dir has no mirror copy
eng_sector <= dsec_r;
eng_rd <= 1'b0;
e_ret <= S_KCALC;
tmp <= new_size;
kclust <= 0;
state <= S_EGO;
end
// ---- engine op helper -------------------------------------------
S_EGO: begin
eng_start <= 1'b1;
state <= S_EWAIT;
end
S_EWAIT:
if (eng_done) state <= e_ret;
else if (eng_err) begin
err_kind <= eng_rd ? 2'd1 : 2'd2; // truthful read-vs-write verdict
state <= S_ERR;
end
// ---- buffered byte read helper (1-cycle BRAM latency) -----------
S_BRD: state <= b_ret; // one wait cycle while fsm_q catches up
default: state <= S_IDLE;
endcase
end
end
endmodule