sd_file_reader¶
Source: Verilog/SD-FAT/circuit/sd_file_reader.v
Where it sits (Simulation): ND120_TOP > nd_storage_devices > nd_storage > sd_file_reader
- instance path: TAPE_SDFAT_SOURCE.u_nd_storage.u_reader
Used in: nd_storage (Simulation, Tang, Nexys, QMTECH)
Contains: sd_card_ctrl
Module hierarchy - All modules

Schematic¶
Schematic not generated: netlistsvg failed: RangeError: Maximum call stack size exceeded.
Description¶
SD card init + FAT16/FAT32 mount + root-directory scan + file stream ORIGINAL ND-120 project code (MIT, like the repository). CLEAN-ROOM implementation from public specifications only: the SD Physical Layer Simplified Specification (command framing, init sequence, response formats, data-block framing) and the Microsoft FAT specification (MBR/BPB layout, FAT12/16/32 thresholds, directory entries, VFAT long file names). The command/data bit engine reuses the proven idiom of this project's own sd_writer.v (tick-based bit clock, CRC functions, 48-bit command shifter). One run per release of rstn (the consumers hold the module in reset and release it per command - the rewind / card-swap recovery pattern): 1. card init at the identification clock (~137 kHz at 27 MHz): 74+ dummy clocks, CMD0, CMD8 (SDv2 detect), CMD55+ACMD41 loop (HCS; CCS in the OCR selects SDHC block addressing), CMD2 (CID), CMD3 (RCA), CMD9 (CSD - card_capacity_mb is decoded from it, both CSD v1 and v2; a CMD9 timeout is tolerated and leaves it 0), CMD7 (select) - then the DATA clock (clk/(2CLK_DIV), 13.5 MHz at 27 MHz / CLK_DIV=1) - CMD16 (512-byte blocks). card_stat counts the init steps; >= 8 means the card is ready. 2. mount: sector 0 is a DBR (superfloppy) or an MBR (the four partition entries are walked for the first plausible volume); the BPB gives the geometry. Cluster count < 4085 (FAT12) or a malformed BPB = unmountable: scan_done without filesystem_type. 3. root directory scan (FAT16 linear region / FAT32 root cluster chain, CMD17 per sector): every plain file and subdirectory is reported on dir_entry_ (VFAT long name when a valid LFN chain with a matching SFN checksum precedes the entry, else the 8.3 name, no trailing dot on extension-less names); deleted entries, volume labels and hidden/system/read-only entries are skipped; a 0x00 name byte ends the directory. target_name/target_len is matched case-insensitively (target_len = 0 never matches: LIST mode). 4. file streaming: the FAT chain is walked run by run (consecutive clusters merge into one CMD18 multi-block read - the speed path; a single-sector tail uses CMD17); every block's CRC16 is verified; found_file_size bytes are emitted in order on outen/outbyte. 5. scan_done (sticky level): file streamed, file not found after the full scan, or unmountable filesystem. No tristates (repo rule): CMD is exposed as sdcmd_i/_o/_oe; DAT0 is never driven by this module (read-only core - writes live in sd_writer.v). CLK_DIV: data-phase half-period in clk cycles. 1 = clk/2 (13.5 MHz at 27 MHz, inside the mandatory 25 MHz default-speed limit); legacy values >1 divide further (2 = clk/4 for 25-50 MHz clocks). The identification clock is fixed near 137 kHz at 27 MHz (100-400 kHz band). SIMULATE=1 shortens only the identification clock and the power-up dummy-clock run (simulation card models answer immediately). Last reviewed: 11-JUL-2026 Ronny Hansen
Parameters¶
| Parameter | Default |
|---|---|
CLK_DIV |
3'd1 |
SIMULATE |
0 |
Ports¶
| Direction | Width | Name | Description |
|---|---|---|---|
| input | 1 |
rstn |
|
| input | 1 |
clk |
|
| output | 1 |
sdclk |
|
| input | 1 |
sdcmd_i |
|
| output | 1 |
sdcmd_o |
|
| output | 1 |
sdcmd_oe |
|
| input | 1 |
sddat0 |
|
| output | [3:0] |
card_stat |
init step counter; >= 8 = ready |
| output | [1:0] |
card_type |
0 unknown, 1 SDv1, 2 SDv2, 3 SDHCv2 |
| output | [1:0] |
filesystem_type |
0 unknown, 2 FAT16, 3 FAT32 |
| output | 1 |
file_found |
level: target matched (found_* valid) |
| output | 1 |
outen |
1-cycle pulse per file byte |
| output | [7:0] |
outbyte |
|
| output | 1 |
scan_done |
sticky level: run finished |
| output | [31:0] |
found_file_size |
|
| input | [52*8-1:0] |
target_name |
|
| input | [7:0] |
target_len |
|
| input | 1 |
no_stream |
tied to 1 (in nd_storage) |
| output | 1 |
dir_entry_valid |
|
| output | [52*8-1:0] |
dir_entry_name |
byte 0 in the low byte |
| output | [7:0] |
dir_entry_len |
|
| output | [31:0] |
dir_entry_size |
|
| output | [15:0] |
dir_entry_date |
raw FAT write-date word |
| output | [31:0] |
dir_entry_cluster |
|
| output | 1 |
dir_entry_is_dir |
|
| output | [31:0] |
found_file_first_sector |
|
| output | [7:0] |
fs_cluster_size |
sectors per cluster |
| output | [31:0] |
fs_fat0_sector |
absolute first FAT sector |
| output | [31:0] |
fs_sectors_per_fat |
|
| output | [7:0] |
fs_num_fats |
|
| output | [31:0] |
fs_data_base_sector |
BIASED: cluster c at base + cs*c |
| output | [31:0] |
fs_total_sectors |
absolute end of the volume |
| output | [31:0] |
fs_root_cluster |
FAT32 root dir first cluster |
| output | [31:0] |
found_dir_entry_sector |
|
| output | [3:0] |
found_dir_entry_index |
|
| output | [31:0] |
found_file_cluster |
full 32-bit (top nibble masked) |
| output | [31:0] |
card_capacity_mb |
|
| output | [15:0] |
card_rca |
Verilog source¶
Verilog/SD-FAT/circuit/sd_file_reader.v on GitHub.
Show the Verilog of sd_file_reader (1241 lines)
/****************************************************************************
** SD card init + FAT16/FAT32 mount + root-directory scan + file stream **
** **
** ORIGINAL ND-120 project code (MIT, like the repository). CLEAN-ROOM **
** implementation from public specifications only: the SD Physical Layer **
** Simplified Specification (command framing, init sequence, response **
** formats, data-block framing) and the Microsoft FAT specification **
** (MBR/BPB layout, FAT12/16/32 thresholds, directory entries, VFAT long **
** file names). The command/data bit engine reuses the proven idiom of **
** this project's own sd_writer.v (tick-based bit clock, CRC functions, **
** 48-bit command shifter). **
** **
** One run per release of rstn (the consumers hold the module in reset **
** and release it per command - the rewind / card-swap recovery pattern): **
** **
** 1. card init at the identification clock (~137 kHz at 27 MHz): **
** 74+ dummy clocks, CMD0, CMD8 (SDv2 detect), CMD55+ACMD41 loop **
** (HCS; CCS in the OCR selects SDHC block addressing), CMD2 (CID), **
** CMD3 (RCA), CMD9 (CSD - card_capacity_mb is decoded from it, both **
** CSD v1 and v2; a CMD9 timeout is tolerated and leaves it 0), **
** CMD7 (select) - then the DATA clock (clk/(2*CLK_DIV), **
** 13.5 MHz at 27 MHz / CLK_DIV=1) - CMD16 (512-byte blocks). **
** card_stat counts the init steps; >= 8 means the card is ready. **
** 2. mount: sector 0 is a DBR (superfloppy) or an MBR (the four **
** partition entries are walked for the first plausible volume); **
** the BPB gives the geometry. Cluster count < 4085 (FAT12) or a **
** malformed BPB = unmountable: scan_done without filesystem_type. **
** 3. root directory scan (FAT16 linear region / FAT32 root cluster **
** chain, CMD17 per sector): every plain file and subdirectory is **
** reported on dir_entry_* (VFAT long name when a valid LFN chain **
** with a matching SFN checksum precedes the entry, else the 8.3 **
** name, no trailing dot on extension-less names); deleted entries, **
** volume labels and hidden/system/read-only entries are skipped; **
** a 0x00 name byte ends the directory. target_name/target_len is **
** matched case-insensitively (target_len = 0 never matches: LIST **
** mode). **
** 4. file streaming: the FAT chain is walked run by run (consecutive **
** clusters merge into one CMD18 multi-block read - the speed path; **
** a single-sector tail uses CMD17); every block's CRC16 is **
** verified; found_file_size bytes are emitted in order on **
** outen/outbyte. **
** 5. scan_done (sticky level): file streamed, file not found after **
** the full scan, or unmountable filesystem. **
** **
** No tristates (repo rule): CMD is exposed as sdcmd_i/_o/_oe; DAT0 is **
** never driven by this module (read-only core - writes live in **
** sd_writer.v). **
** **
** CLK_DIV: data-phase half-period in clk cycles. 1 = clk/2 (13.5 MHz at **
** 27 MHz, inside the mandatory 25 MHz default-speed limit); legacy **
** values >1 divide further (2 = clk/4 for 25-50 MHz clocks). The **
** identification clock is fixed near 137 kHz at 27 MHz (100-400 kHz **
** band). SIMULATE=1 shortens only the identification clock and the **
** power-up dummy-clock run (simulation card models answer immediately). **
** **
** Last reviewed: 11-JUL-2026 **
** Ronny Hansen **
*****************************************************************************/
module sd_file_reader #(
parameter [2:0] CLK_DIV = 3'd1, // data sdclk = clk / (2*CLK_DIV)
parameter SIMULATE = 0
) (
input wire rstn,
input wire clk,
// SD pins (no tristate here - repo rule)
output wire sdclk,
input wire sdcmd_i,
output wire sdcmd_o,
output wire sdcmd_oe,
input wire sddat0,
// status
output reg [3:0] card_stat, // init step counter; >= 8 = ready
output reg [1:0] card_type, // 0 unknown, 1 SDv1, 2 SDv2, 3 SDHCv2
output reg [1:0] filesystem_type, // 0 unknown, 2 FAT16, 3 FAT32
// scan / stream results
output reg file_found, // level: target matched (found_* valid)
output reg outen, // 1-cycle pulse per file byte
output reg [7:0] outbyte,
output reg scan_done, // sticky level: run finished
output reg [31:0] found_file_size,
// target file name, byte 0 in the low byte; target_len 0 = LIST mode
input wire [52*8-1:0] target_name,
input wire [7:0] target_len,
// 1 = stop after the DIRECTORY match: found_* are published and the run
// ends at H_FINISH without reading a single file byte. For a consumer
// that only wants geometry (nd_storage's mount, which then fetches
// blocks on demand) this makes an open cost the same for a 64 KB file
// and a 75 MB one, and - the reason it exists - it ends the run at a
// CLEAN card boundary. Killing the reader mid-transfer instead leaves
// the card streaming and the next user of the card fails.
input wire no_stream, //! tied to 1 (in nd_storage)
// one pulse per reported root entry
output reg dir_entry_valid,
output reg [52*8-1:0] dir_entry_name, // byte 0 in the low byte
output reg [7:0] dir_entry_len,
output reg [31:0] dir_entry_size,
output reg [15:0] dir_entry_date, // raw FAT write-date word
output reg [31:0] dir_entry_cluster,
output reg dir_entry_is_dir,
// found-file details (valid with file_found)
output reg [31:0] found_file_first_sector,
// filesystem geometry (valid once filesystem_type[1] = mounted)
output reg [7:0] fs_cluster_size, // sectors per cluster
output reg [31:0] fs_fat0_sector, // absolute first FAT sector
output reg [31:0] fs_sectors_per_fat,
output reg [7:0] fs_num_fats,
output reg [31:0] fs_data_base_sector, // BIASED: cluster c at base + cs*c
output reg [31:0] fs_total_sectors, // absolute end of the volume
output reg [31:0] fs_root_cluster, // FAT32 root dir first cluster
output reg [31:0] found_dir_entry_sector,
output reg [3:0] found_dir_entry_index,
output reg [31:0] found_file_cluster, // full 32-bit (top nibble masked)
// card identity (valid once card_stat >= 6): capacity decoded from the
// CMD9 CSD register (v1 and v2 layouts; 0 = CMD9 unanswered) and the
// CMD3-published RCA (the sd_writer engine needs it for CMD55)
output reg [31:0] card_capacity_mb,
output wire [15:0] card_rca
);
// identification-phase half period: ~137 kHz at 27 MHz on hardware,
// shortened in simulation (the card models answer at fixed clock counts)
localparam [7:0] INIT_HALF = SIMULATE ? 8'd2 : 8'd99;
localparam [7:0] DATA_HALF = (CLK_DIV == 3'd0) ? 8'd1 : {5'd0, CLK_DIV};
// ------------------------------------------------------------- bit engine
localparam [1:0] K_DUMMY = 2'd0; // op_ndum clocks with CMD driven high
localparam [1:0] K_CMD = 2'd1; // 48-bit command + response
localparam [1:0] K_CMDN = 2'd2; // 48-bit command, no response (CMD0)
localparam [1:0] K_READ = 2'd3; // CMD17/CMD18 + op_nblk data blocks
reg e_start;
reg [1:0] e_kind;
reg [5:0] e_cmd;
reg [31:0] e_arg;
reg e_r2;
reg [7:0] e_ndum;
reg [12:0] e_nblk;
wire e_busy;
wire e_done, e_err;
wire [31:0] e_resp;
wire [127:0] e_resp_r2; // R2 payload (CID/CSD bits 127:1; bit 0 = end bit)
wire e_rx_we;
wire [7:0] e_rx_byte;
wire [8:0] e_rx_idx;
reg e_slow;
sd_card_ctrl #(
.DATA_DIV(DATA_HALF),
.INIT_DIV(INIT_HALF)
) u_ctrl (
.clk (clk),
.rstn (rstn),
.slow (e_slow),
.op_start(e_start),
.op_kind (e_kind),
.op_cmd (e_cmd),
.op_arg (e_arg),
.op_r2 (e_r2),
.op_ndum (e_ndum),
.op_nblk (e_nblk),
.busy (e_busy),
.done (e_done),
.err (e_err),
.resp_arg(e_resp),
.resp_r2 (e_resp_r2),
.rx_we (e_rx_we),
.rx_byte (e_rx_byte),
.rx_idx (e_rx_idx),
.sdclk (sdclk),
.sdcmd_i (sdcmd_i),
.sdcmd_o (sdcmd_o),
.sdcmd_oe(sdcmd_oe),
.sddat0 (sddat0)
);
// ------------------------------------------------------------- helpers
function [2:0] log2cs(input [7:0] cs); // sectors/cluster is a power of two
casez (cs)
8'b1???????: log2cs = 3'd7;
8'b01??????: log2cs = 3'd6;
8'b001?????: log2cs = 3'd5;
8'b0001????: log2cs = 3'd4;
8'b00001???: log2cs = 3'd3;
8'b000001??: log2cs = 3'd2;
8'b0000001?: log2cs = 3'd1;
default: log2cs = 3'd0;
endcase
endfunction
// case-insensitive fold (ASCII lower -> upper)
function [7:0] ucase(input [7:0] b);
ucase = (b >= 8'h61 && b <= 8'h7A) ? (b - 8'h20) : b;
endfunction
`ifndef SDFAT_NO_LFN
// byte offset of LFN name unit u's LOW byte inside a directory entry
function [4:0] lfn_off(input [3:0] u);
case (u)
4'd0: lfn_off = 5'd1;
4'd1: lfn_off = 5'd3;
4'd2: lfn_off = 5'd5;
4'd3: lfn_off = 5'd7;
4'd4: lfn_off = 5'd9;
4'd5: lfn_off = 5'd14;
4'd6: lfn_off = 5'd16;
4'd7: lfn_off = 5'd18;
4'd8: lfn_off = 5'd20;
4'd9: lfn_off = 5'd22;
4'd10: lfn_off = 5'd24;
4'd11: lfn_off = 5'd28;
default: lfn_off = 5'd30;
endcase
endfunction
`endif // SDFAT_NO_LFN
// ------------------------------------------------------------- registers
reg is32;
reg [2:0] l2cs;
reg [31:0] max_cluster;
reg ccs; // 1 = SDHC/SDXC block addressing, 0 = byte addressing
reg v2; // CMD8 answered
reg [15:0] rca;
assign card_rca = rca;
// CSD capacity decode (e_resp_r2[k] = CSD[k] after the CMD9 R2):
// v2 (CSD_STRUCTURE = 1): capacity = (C_SIZE+1) * 512 KB, C_SIZE[21:0]
// at CSD[69:48] -> MB = (C_SIZE+1) / 2
// v1 (CSD_STRUCTURE = 0): capacity = (C_SIZE+1) *
// 2^(C_SIZE_MULT+2) * 2^READ_BL_LEN bytes, C_SIZE[11:0] at CSD[73:62],
// C_SIZE_MULT[2:0] at CSD[49:47], READ_BL_LEN[3:0] at CSD[83:80]
wire [22:0] s_csd2_hblk = {1'b0, e_resp_r2[69:48]} + 23'd1; // 512KB units
wire [12:0] s_csd1_blks = {1'b0, e_resp_r2[73:62]} + 13'd1;
wire [4:0] s_csd1_exp = {2'b0, e_resp_r2[49:47]} +
{1'b0, e_resp_r2[83:80]} + 5'd2;
// the v1 2^exp scaling runs one bit per clock in H_CSD1: a variable
// barrel shifter would cost ~200 LUTs for a once-per-init calculation
reg [19:0] csd_w; // v1 work value (v1 tops out at 4 GB = 4096 MB)
reg [4:0] csd_n; // shift budget, balanced against the /2^20 (bytes->MB)
// BPB fields as parsed
reg [31:0] vol_base;
reg mbr_done;
reg jmp_ok;
reg [15:0] p_bps, p_rsvd, p_rootn, p_tot16, p_spf16;
reg [7:0] p_spc, p_nfat;
reg [31:0] p_tot32, p_spf32;
reg [1:0] mbr_p;
// directory scan position
reg [31:0] dsec; // current root directory sector
reg [31:0] drem; // FAT16: root sectors left
reg [31:0] dclu; // FAT32: current root cluster
reg [7:0] doff; // FAT32: sector offset inside the root cluster
reg [15:0] dscan; // scanned-sector guard
reg dirend; // 0x00 entry seen: directory over
// FAT sector cache + entry fetch
reg [31:0] cache_sec;
reg [31:0] qc; // cluster whose FAT entry is wanted
reg [31:0] fat_val; // masked entry value
reg [5:0] fat_ret;
// streaming
reg [31:0] remaining;
reg [31:0] cur, nxt, run_start;
reg [12:0] run_secs;
// buffered field fetch (1-clk BRAM latency)
reg [8:0] f_off;
reg [1:0] f_idx, f_cnt; // f_cnt = nbytes-1
reg [31:0] f_val;
reg [5:0] f_ret;
// engine-op helper
reg [5:0] e_ret, e_eret;
reg e_err_seen; // the last op ended in err (e_eret was taken)
reg [1:0] route;
localparam [1:0] R_NONE = 2'd0;
localparam [1:0] R_BUF = 2'd1;
localparam [1:0] R_DIR = 2'd2;
localparam [1:0] R_STREAM = 2'd3;
reg [15:0] a41_guard;
reg cmd8_ok;
// ------------------------------------------------------------- sector buffer
reg [7:0] secbuf[0:511];
reg [7:0] fsm_q;
reg [8:0] fsm_addr;
always @(posedge clk) begin
if (e_rx_we && route == R_BUF) secbuf[e_rx_idx] <= e_rx_byte;
fsm_q <= secbuf[fsm_addr];
end
// ------------------------------------------------------------- dir parser
// 32-byte entries are collected live (raw), then processed at leisure in
// proc while the next entry's bytes arrive (one byte per >= 16 clk at
// CLK_DIV=1, so a whole entry takes >= 512 clk - the process FSM below
// needs well under 200).
reg [247:0] raw; // entry bytes 0..30 (byte 31 joins at the copy)
reg [255:0] proc;
reg pbusy;
reg [3:0] pent_snap; // entry index inside the sector
reg [31:0] psec_snap; // sector holding the entry
localparam [3:0] P_CLASS = 4'd1;
localparam [3:0] P_LFNU = 4'd2;
localparam [3:0] P_SFN = 4'd3;
localparam [3:0] P_PICK = 4'd4;
localparam [3:0] P_83B = 4'd5;
localparam [3:0] P_83E = 4'd6;
localparam [3:0] P_83X = 4'd7;
localparam [3:0] P_CMP = 4'd8;
localparam [3:0] P_EMIT = 4'd9;
reg [3:0] pstate;
reg [3:0] pu; // LFN unit index 0..12
reg [3:0] pi; // SFN byte index
reg [5:0] pbase; // (ord-1)*13
reg plast; // this LFN entry carries the 0x40 last flag
reg eterm; // terminator seen inside THIS LFN entry
reg [52*8-1:0] lfn_buf;
reg [7:0] lfn_len;
reg [7:0] lfn_ck; // checksum byte carried by the LFN chain
reg [3:0] lfn_next; // expected ordinal of the next LFN entry
reg lfn_have;
reg [7:0] ck; // SFN checksum accumulator
reg [7:0] nci; // name-build / compare index
reg pmatch;
wire [7:0] pb0 = proc[7:0];
wire [7:0] pattr = proc[8*11+:8];
wire [7:0] pck13 = proc[8*13+:8];
`ifndef SDFAT_NO_LFN
wire [4:0] pulo = lfn_off(pu);
wire [7:0] plo = proc[8*pulo+:8]; // variable part-select: wide LFN mux
wire [7:0] phi = proc[8*(pulo+5'd1)+:8];
`endif // SDFAT_NO_LFN
wire [3:0] pord = pb0[3:0]; // LFN ordinal (valid range here is 1..4)
wire [5:0] pux = pbase + {2'd0, pu};
// ------------------------------------------------------------- main FSM
localparam [5:0] H_BOOT = 6'd0; // power-up dummy clocks
localparam [5:0] H_CMD0 = 6'd1;
localparam [5:0] H_CMD8 = 6'd2;
localparam [5:0] H_C55 = 6'd3;
localparam [5:0] H_A41 = 6'd4;
localparam [5:0] H_A41W = 6'd5; // not-ready pause before the retry
localparam [5:0] H_CMD2 = 6'd6;
localparam [5:0] H_CMD3 = 6'd7;
localparam [5:0] H_CMD7 = 6'd8;
localparam [5:0] H_CMD16 = 6'd9;
localparam [5:0] H_RDBOOT = 6'd10; // read the DBR/MBR candidate sector
localparam [5:0] H_P_SIG = 6'd11;
localparam [5:0] H_P_JMP = 6'd12;
localparam [5:0] H_P_BPS = 6'd13;
localparam [5:0] H_P_SPC = 6'd14;
localparam [5:0] H_P_RSVD = 6'd15;
localparam [5:0] H_P_NFAT = 6'd16;
localparam [5:0] H_P_ROOTN = 6'd17;
localparam [5:0] H_P_TOT16 = 6'd18;
localparam [5:0] H_P_SPF16 = 6'd19;
localparam [5:0] H_P_TOT32 = 6'd20;
localparam [5:0] H_P_SPF32 = 6'd21;
localparam [5:0] H_P_ROOTC = 6'd22;
localparam [5:0] H_GEOM = 6'd23;
localparam [5:0] H_MBR_T = 6'd24; // partition entry: type byte
localparam [5:0] H_MBR_L = 6'd25; // partition entry: start LBA
localparam [5:0] H_DIR_RD = 6'd26;
localparam [5:0] H_DIR_NX = 6'd27;
localparam [5:0] H_DIR_HOP = 6'd28; // FAT32 root chain hop
localparam [5:0] H_STREAM0 = 6'd29;
localparam [5:0] H_RUN0 = 6'd30;
localparam [5:0] H_RUNX = 6'd31;
localparam [5:0] H_RUNJ = 6'd32;
localparam [5:0] H_RDRUN = 6'd33;
localparam [5:0] H_RUNEND = 6'd34;
localparam [5:0] H_FINISH = 6'd35; // wait for the parser, then scan_done
localparam [5:0] H_HALT = 6'd36;
localparam [5:0] H_FAT_RD = 6'd37; // FAT entry fetch (cached sector)
localparam [5:0] H_FAT_OFF = 6'd38;
localparam [5:0] H_FAT_V = 6'd39;
localparam [5:0] E_GO = 6'd40; // engine op: pulse start
localparam [5:0] E_WT = 6'd41; // engine op: wait done/err
localparam [5:0] F_RD0 = 6'd42; // little-endian field fetch (secbuf)
localparam [5:0] F_RD1 = 6'd43;
localparam [5:0] F_RD2 = 6'd44;
localparam [5:0] H_UNMNT = 6'd45;
localparam [5:0] H_CMD9 = 6'd46; // SEND_CSD (capacity; timeout tolerated)
localparam [5:0] H_CSD = 6'd47; // decode card_capacity_mb
localparam [5:0] H_CSD1 = 6'd48; // CSD v1: scale by 2^exp, bit by bit
reg [5:0] state;
// end-of-chain test on a masked FAT entry value
wire fat_eoc = is32 ? (fat_val[27:0] >= 28'hFFFFFF7)
: (fat_val[15:0] >= 16'hFFF7);
wire nxt_eoc = is32 ? (nxt[27:0] >= 28'hFFFFFF7)
: (nxt[15:0] >= 16'hFFF7);
wire [31:0] spf_v = (p_spf16 != 16'd0) ? {16'd0, p_spf16} : p_spf32;
wire [31:0] tot_v = (p_tot16 != 16'd0) ? {16'd0, p_tot16} : p_tot32;
wire [31:0] rootsec = {20'd0, p_rootn[15:4]}; // rootn*32/512
wire [31:0] fatarea = {24'd0, p_nfat} * spf_v;
wire bpb_ok = jmp_ok && (p_bps == 16'd512) && (p_spc != 8'd0) &&
((p_spc & (p_spc - 8'd1)) == 8'd0) &&
(p_rsvd != 16'd0) && (p_nfat != 8'd0) &&
(spf_v != 32'd0) && (tot_v != 32'd0);
// sectors needed for the rest of the file, capped to the current run
wire [31:0] need_secs = (remaining + 32'd511) >> 9;
wire [12:0] nblk_now = (need_secs < {19'd0, run_secs}) ? need_secs[12:0]
: run_secs;
always @(posedge clk) begin
if (!rstn) begin
state <= H_BOOT;
e_start <= 1'b0;
e_kind <= K_DUMMY;
e_cmd <= 6'd0;
e_arg <= 32'd0;
e_r2 <= 1'b0;
e_ndum <= 8'd0;
e_nblk <= 13'd0;
e_slow <= 1'b1;
e_ret <= H_BOOT;
e_eret <= H_BOOT;
route <= R_NONE;
card_stat <= 4'd0;
card_type <= 2'd0;
filesystem_type <= 2'd0;
file_found <= 1'b0;
outen <= 1'b0;
outbyte <= 8'd0;
scan_done <= 1'b0;
found_file_size <= 32'd0;
dir_entry_valid <= 1'b0;
dir_entry_name <= {52 * 8{1'b0}};
dir_entry_len <= 8'd0;
dir_entry_size <= 32'd0;
dir_entry_date <= 16'd0;
dir_entry_cluster <= 32'd0;
dir_entry_is_dir <= 1'b0;
found_file_first_sector <= 32'd0;
fs_cluster_size <= 8'd0;
fs_fat0_sector <= 32'd0;
fs_sectors_per_fat <= 32'd0;
fs_num_fats <= 8'd0;
fs_data_base_sector <= 32'd0;
fs_total_sectors <= 32'd0;
fs_root_cluster <= 32'd0;
found_dir_entry_sector <= 32'd0;
found_dir_entry_index <= 4'd0;
found_file_cluster <= 32'd0;
card_capacity_mb <= 32'd0;
csd_w <= 20'd0;
csd_n <= 5'd0;
is32 <= 1'b0;
l2cs <= 3'd0;
max_cluster <= 32'd0;
ccs <= 1'b0;
v2 <= 1'b0;
rca <= 16'd0;
vol_base <= 32'd0;
mbr_done <= 1'b0;
jmp_ok <= 1'b0;
p_bps <= 16'd0;
p_rsvd <= 16'd0;
p_rootn <= 16'd0;
p_tot16 <= 16'd0;
p_spf16 <= 16'd0;
p_spc <= 8'd0;
p_nfat <= 8'd0;
p_tot32 <= 32'd0;
p_spf32 <= 32'd0;
mbr_p <= 2'd0;
dsec <= 32'd0;
drem <= 32'd0;
dclu <= 32'd0;
doff <= 8'd0;
dscan <= 16'd0;
dirend <= 1'b0;
cache_sec <= 32'hFFFF_FFFF;
qc <= 32'd0;
fat_val <= 32'd0;
fat_ret <= H_BOOT;
remaining <= 32'd0;
cur <= 32'd0;
nxt <= 32'd0;
run_start <= 32'd0;
run_secs <= 13'd0;
f_off <= 9'd0;
f_idx <= 2'd0;
f_cnt <= 2'd0;
f_val <= 32'd0;
f_ret <= H_BOOT;
a41_guard <= 16'd0;
cmd8_ok <= 1'b0;
e_err_seen <= 1'b0;
raw <= 248'd0;
proc <= 256'd0;
pbusy <= 1'b0;
pent_snap <= 4'd0;
psec_snap <= 32'd0;
pstate <= P_CLASS;
pu <= 4'd0;
pi <= 4'd0;
pbase <= 6'd0;
plast <= 1'b0;
eterm <= 1'b0;
lfn_buf <= {52 * 8{1'b0}};
lfn_len <= 8'd0;
lfn_ck <= 8'd0;
lfn_next <= 4'd0;
lfn_have <= 1'b0;
ck <= 8'd0;
nci <= 8'd0;
pmatch <= 1'b0;
end else begin
e_start <= 1'b0;
outen <= 1'b0;
dir_entry_valid <= 1'b0;
// ---------------- stream datapath (parallel to the FSM) -------------
if (e_rx_we && route == R_STREAM && remaining != 32'd0) begin
outen <= 1'b1;
outbyte <= e_rx_byte;
remaining <= remaining - 32'd1;
end
// ---------------- directory entry collector -------------------------
if (e_rx_we && route == R_DIR) begin
if (e_rx_idx[4:0] == 5'd31) begin
proc <= {e_rx_byte, raw};
pent_snap <= e_rx_idx[8:5];
psec_snap <= dsec;
pbusy <= 1'b1;
pstate <= P_CLASS;
end else begin
raw[8*e_rx_idx[4:0]+:8] <= e_rx_byte;
end
end
// ---------------- directory entry process FSM -----------------------
if (pbusy) begin
case (pstate)
P_CLASS: begin
if (pb0 == 8'h00) begin
dirend <= 1'b1;
pbusy <= 1'b0;
end else if (pb0 == 8'hE5) begin
lfn_have <= 1'b0; // deleted: an LFN chain cannot span it
pbusy <= 1'b0;
end else if (pattr == 8'h0F) begin
`ifdef SDFAT_NO_LFN
// SDFAT_NO_LFN (tape-only cut): VFAT long-name parsing stripped.
// Skip the LFN entry outright; the file is matched by its 8.3
// short name on the directory entry that follows.
lfn_have <= 1'b0;
pbusy <= 1'b0;
`else
// VFAT long-name entry (reverse order, 13 UTF-16 units each)
eterm <= 1'b0;
pu <= 4'd0;
if (pb0[6]) begin // 0x40: last (highest-ordinal) entry
if (pord >= 4'd1 && pord <= 4'd4 && !pb0[5] && !pb0[4]) begin
lfn_have <= 1'b1;
lfn_buf <= {52 * 8{1'b0}}; // no residue past the new name
lfn_ck <= pck13;
lfn_len <= {1'd0, pord, 3'd0} + {2'd0, pord, 2'd0} +
{4'd0, pord}; // 13*ord (full, no terminator)
lfn_next <= pord - 4'd1;
plast <= 1'b1;
case (pord)
4'd1: pbase <= 6'd0;
4'd2: pbase <= 6'd13;
4'd3: pbase <= 6'd26;
default: pbase <= 6'd39;
endcase
pstate <= P_LFNU;
end else begin
lfn_have <= 1'b0; // name longer than 52 chars / malformed
pbusy <= 1'b0;
end
end else begin // continuation entry
if (lfn_have && !pb0[5] && !pb0[4] && pord == lfn_next &&
pord >= 4'd1 && pck13 == lfn_ck) begin
lfn_next <= pord - 4'd1;
plast <= 1'b0;
case (pord)
4'd1: pbase <= 6'd0;
4'd2: pbase <= 6'd13;
default: pbase <= 6'd26; // ordinal 3 (4 is always last)
endcase
pstate <= P_LFNU;
end else begin
lfn_have <= 1'b0; // broken chain
pbusy <= 1'b0;
end
end
`endif // SDFAT_NO_LFN
end else if ((pattr & 8'h0F) != 8'h00) begin
// volume label / hidden / system / read-only: skipped
lfn_have <= 1'b0;
pbusy <= 1'b0;
end else begin
// plain file or subdirectory
ck <= 8'd0;
pi <= 4'd0;
pstate <= P_SFN;
end
end
`ifndef SDFAT_NO_LFN
P_LFNU: begin // one UTF-16 unit per cycle
if (!eterm) begin
if (plo == 8'h00 && phi == 8'h00) begin
if (plast) begin
lfn_len <= {2'd0, pux};
eterm <= 1'b1;
end else begin
lfn_have <= 1'b0; // terminator outside the last entry
end
end else if (phi != 8'h00) begin
lfn_have <= 1'b0; // non-ASCII unit: fall back to 8.3
end else begin
lfn_buf[8*pux+:8] <= plo;
end
end
if (pu == 4'd12 || !lfn_have) pbusy <= 1'b0;
else pu <= pu + 4'd1;
end
`endif // SDFAT_NO_LFN
P_SFN: begin // checksum over the 11 SFN bytes
ck <= {ck[0], ck[7:1]} + proc[8*pi+:8];
if (pi == 4'd10) pstate <= P_PICK;
else pi <= pi + 4'd1;
end
P_PICK: begin
dir_entry_size <= {proc[8*31+:8], proc[8*30+:8],
proc[8*29+:8], proc[8*28+:8]};
dir_entry_date <= {proc[8*25+:8], proc[8*24+:8]};
dir_entry_cluster <= is32
? {4'd0, proc[8*21+:4], proc[8*20+:8],
proc[8*27+:8], proc[8*26+:8]}
: {16'd0, proc[8*27+:8], proc[8*26+:8]};
dir_entry_is_dir <= pattr[4];
`ifndef SDFAT_NO_LFN
if (lfn_have && lfn_next == 4'd0 && ck == lfn_ck &&
lfn_len != 8'd0 && lfn_len <= 8'd52) begin
dir_entry_name <= lfn_buf;
dir_entry_len <= lfn_len;
pstate <= P_CMP;
nci <= 8'd0;
pmatch <= (target_len != 8'd0) && (target_len == lfn_len) &&
!pattr[4] && !file_found;
end else begin
`endif // SDFAT_NO_LFN
// SDFAT_NO_LFN forces this 8.3 short-name path unconditionally
dir_entry_name <= {52 * 8{1'b0}}; // NUL-padded past the name
pi <= 4'd0;
nci <= 8'd0;
pstate <= P_83B;
`ifndef SDFAT_NO_LFN
end
`endif // SDFAT_NO_LFN
end
P_83B: begin // 8.3 base name (strip trailing spaces)
if (proc[8*pi+:8] == 8'h20) begin
pstate <= P_83E;
end else begin
dir_entry_name[8*nci[5:0]+:8] <=
(pi == 4'd0 && proc[7:0] == 8'h05) ? 8'hE5 : proc[8*pi+:8];
nci <= nci + 8'd1;
if (pi == 4'd7) pstate <= P_83E;
else pi <= pi + 4'd1;
end
end
P_83E: begin // '.' only when an extension exists
if (proc[8*8+:8] == 8'h20) begin
dir_entry_len <= nci;
pmatch <= (target_len != 8'd0) && (target_len == nci) &&
!pattr[4] && !file_found;
nci <= 8'd0;
pstate <= P_CMP;
end else begin
dir_entry_name[8*nci[5:0]+:8] <= 8'h2E; // '.'
nci <= nci + 8'd1;
pi <= 4'd8;
pstate <= P_83X;
end
end
P_83X: begin // extension (strip trailing spaces)
if (proc[8*pi+:8] == 8'h20) begin
dir_entry_len <= nci;
pmatch <= (target_len != 8'd0) && (target_len == nci) &&
!pattr[4] && !file_found;
nci <= 8'd0;
pstate <= P_CMP;
end else begin
dir_entry_name[8*nci[5:0]+:8] <= proc[8*pi+:8];
nci <= nci + 8'd1;
if (pi == 4'd10) begin
dir_entry_len <= nci + 8'd1;
pmatch <= (target_len != 8'd0) &&
(target_len == nci + 8'd1) &&
!pattr[4] && !file_found;
nci <= 8'd0;
pstate <= P_CMP;
end else pi <= pi + 4'd1;
end
end
P_CMP: begin // case-insensitive target compare, one byte per cycle
if (!pmatch || nci >= dir_entry_len) begin
pstate <= P_EMIT;
end else begin
if (ucase(dir_entry_name[8*nci[5:0]+:8]) !=
ucase(target_name[8*nci[5:0]+:8])) pmatch <= 1'b0;
nci <= nci + 8'd1;
end
end
P_EMIT: begin
dir_entry_valid <= 1'b1;
if (pmatch) begin
file_found <= 1'b1;
found_file_size <= dir_entry_size;
found_file_cluster <= dir_entry_cluster;
found_file_first_sector <= fs_data_base_sector +
(dir_entry_cluster << l2cs);
found_dir_entry_sector <= psec_snap;
found_dir_entry_index <= pent_snap;
end
pmatch <= 1'b0;
lfn_have <= 1'b0;
pbusy <= 1'b0;
end
default: pbusy <= 1'b0;
endcase
end
// ---------------- main FSM ------------------------------------------
case (state)
// ======== card init (identification clock) ========================
H_BOOT: begin
card_stat <= 4'd0;
e_slow <= 1'b1;
route <= R_NONE;
e_kind <= K_DUMMY;
e_ndum <= 8'd80; // >= 74 power-up clocks with CMD high
e_ret <= H_CMD0;
e_eret <= H_BOOT;
state <= E_GO;
end
H_CMD0: begin
card_stat <= 4'd1;
e_kind <= K_CMDN;
e_cmd <= 6'd0;
e_arg <= 32'd0;
e_ret <= H_CMD8;
e_eret <= H_BOOT;
state <= E_GO;
end
H_CMD8: begin
card_stat <= 4'd2;
e_kind <= K_CMD;
e_r2 <= 1'b0;
e_cmd <= 6'd8;
e_arg <= 32'h0000_01AA; // 2.7-3.6 V + check pattern
cmd8_ok <= 1'b1;
e_ret <= H_C55; // response: SDv2
e_eret <= H_C55; // timeout: SDv1 (via cmd8_ok clear)
a41_guard <= 16'd0;
v2 <= 1'b0;
state <= E_GO;
end
H_C55: begin
// arriving from H_CMD8: e_done set v2, e_err means no CMD8 answer
if (cmd8_ok) begin
v2 <= !e_err_seen && (e_resp[7:0] == 8'hAA);
cmd8_ok <= 1'b0;
end
card_stat <= 4'd3;
e_kind <= K_CMD;
e_cmd <= 6'd55;
e_arg <= 32'd0; // RCA 0 before CMD3
e_ret <= H_A41;
e_eret <= H_BOOT;
state <= E_GO;
end
H_A41: begin
e_kind <= K_CMD;
e_cmd <= 6'd41; // ACMD41: HCS + voltage window
e_arg <= v2 ? 32'h40FF_8000 : 32'h00FF_8000;
e_ret <= H_A41W;
e_eret <= H_BOOT;
state <= E_GO;
end
H_A41W: begin
if (e_resp[31]) begin // OCR busy bit set = ready
ccs <= v2 & e_resp[30];
card_type <= v2 ? (e_resp[30] ? 2'd3 : 2'd2) : 2'd1;
state <= H_CMD2;
end else if (a41_guard == 16'hFFFF) begin
state <= H_BOOT; // card never ready: start over
end else begin
a41_guard <= a41_guard + 16'd1;
e_kind <= K_DUMMY; // idle clocks between the retries
e_ndum <= 8'd8;
e_ret <= H_C55;
e_eret <= H_BOOT;
state <= E_GO;
end
end
H_CMD2: begin
card_stat <= 4'd4;
e_kind <= K_CMD;
e_r2 <= 1'b1; // 136-bit CID
e_cmd <= 6'd2;
e_arg <= 32'd0;
e_ret <= H_CMD3;
e_eret <= H_BOOT;
state <= E_GO;
end
H_CMD3: begin
card_stat <= 4'd5;
e_kind <= K_CMD;
e_r2 <= 1'b0;
e_cmd <= 6'd3;
e_arg <= 32'd0;
e_ret <= H_CMD9;
e_eret <= H_BOOT;
state <= E_GO;
end
H_CMD9: begin
rca <= e_resp[31:16]; // R6: the card's published RCA
e_kind <= K_CMD;
e_r2 <= 1'b1; // 136-bit CSD
e_cmd <= 6'd9;
e_arg <= {e_resp[31:16], 16'd0};
e_ret <= H_CSD;
e_eret <= H_CSD; // no CSD answer: capacity stays 0
state <= E_GO;
end
H_CSD:
if (e_err_seen) state <= H_CMD7; // no CSD answer: capacity stays 0
else if (e_resp_r2[127:126] == 2'b01) begin
card_capacity_mb <= {10'd0, s_csd2_hblk[22:1]}; // v2: (C_SIZE+1)/2
state <= H_CMD7;
end else begin
csd_w <= {7'd0, s_csd1_blks}; // v1: (C_SIZE+1) * 2^exp bytes
csd_n <= s_csd1_exp;
state <= H_CSD1;
end
H_CSD1: // MB = blocks << (exp-20), one bit per clock either way
if (csd_n == 5'd20) begin
card_capacity_mb <= {12'd0, csd_w};
state <= H_CMD7;
end else if (csd_n > 5'd20) begin
csd_w <= {csd_w[18:0], 1'b0};
csd_n <= csd_n - 5'd1;
end else begin
csd_w <= {1'b0, csd_w[19:1]};
csd_n <= csd_n + 5'd1;
end
H_CMD7: begin
card_stat <= 4'd6;
e_kind <= K_CMD;
e_r2 <= 1'b0;
e_cmd <= 6'd7;
e_arg <= {rca, 16'd0};
e_ret <= H_CMD16;
e_eret <= H_BOOT;
state <= E_GO;
end
H_CMD16: begin
card_stat <= 4'd7;
e_slow <= 1'b0; // data clock from here on
e_kind <= K_CMD;
e_cmd <= 6'd16;
e_arg <= 32'd512;
e_ret <= H_RDBOOT;
e_eret <= H_BOOT;
state <= E_GO;
end
// ======== mount ====================================================
H_RDBOOT: begin
card_stat <= 4'd8; // card ready (consumer threshold)
route <= R_BUF;
cache_sec <= 32'hFFFF_FFFF; // the FAT cache buffer is reused
e_kind <= K_READ;
e_cmd <= 6'd17;
e_arg <= ccs ? vol_base : (vol_base << 9);
e_nblk <= 13'd1;
e_ret <= H_P_SIG;
e_eret <= H_UNMNT;
state <= E_GO;
end
H_P_SIG: begin
f_off <= 9'd510; f_cnt <= 2'd1; f_ret <= H_P_JMP; state <= F_RD0;
end
H_P_JMP:
if (f_val[15:0] != 16'hAA55) state <= H_UNMNT;
else begin
f_off <= 9'd0; f_cnt <= 2'd0; f_ret <= H_P_BPS; state <= F_RD0;
end
H_P_BPS: begin
jmp_ok <= (f_val[7:0] == 8'hEB) || (f_val[7:0] == 8'hE9);
f_off <= 9'd11; f_cnt <= 2'd1; f_ret <= H_P_SPC; state <= F_RD0;
end
H_P_SPC: begin
p_bps <= f_val[15:0];
f_off <= 9'd13; f_cnt <= 2'd0; f_ret <= H_P_RSVD; state <= F_RD0;
end
H_P_RSVD: begin
p_spc <= f_val[7:0];
f_off <= 9'd14; f_cnt <= 2'd1; f_ret <= H_P_NFAT; state <= F_RD0;
end
H_P_NFAT: begin
p_rsvd <= f_val[15:0];
f_off <= 9'd16; f_cnt <= 2'd0; f_ret <= H_P_ROOTN; state <= F_RD0;
end
H_P_ROOTN: begin
p_nfat <= f_val[7:0];
f_off <= 9'd17; f_cnt <= 2'd1; f_ret <= H_P_TOT16; state <= F_RD0;
end
H_P_TOT16: begin
p_rootn <= f_val[15:0];
f_off <= 9'd19; f_cnt <= 2'd1; f_ret <= H_P_SPF16; state <= F_RD0;
end
H_P_SPF16: begin
p_tot16 <= f_val[15:0];
f_off <= 9'd22; f_cnt <= 2'd1; f_ret <= H_P_TOT32; state <= F_RD0;
end
H_P_TOT32: begin
p_spf16 <= f_val[15:0];
f_off <= 9'd32; f_cnt <= 2'd3; f_ret <= H_P_SPF32; state <= F_RD0;
end
H_P_SPF32: begin
p_tot32 <= f_val;
f_off <= 9'd36; f_cnt <= 2'd3; f_ret <= H_P_ROOTC; state <= F_RD0;
end
H_P_ROOTC: begin
p_spf32 <= f_val;
f_off <= 9'd44; f_cnt <= 2'd3; f_ret <= H_GEOM; state <= F_RD0;
end
H_GEOM: begin : geom
reg [31:0] g_fat0, g_first, g_dbase, g_cc, g_rootc;
if (!bpb_ok) begin
// not a BPB: sector 0 may be an MBR - walk its partition table
if (!mbr_done && vol_base == 32'd0) begin
mbr_p <= 2'd0;
f_off <= 9'd450; // 446 + 4: partition 0 type byte
f_cnt <= 2'd0;
f_ret <= H_MBR_T;
state <= F_RD0;
end else state <= H_UNMNT;
end else begin
g_rootc = f_val & 32'h0FFF_FFFF;
g_fat0 = vol_base + {16'd0, p_rsvd};
g_first = g_fat0 + fatarea + rootsec;
g_dbase = g_first - {23'd0, p_spc, 1'b0}; // bias: -2*spc
g_cc = (tot_v - (g_first - vol_base)) >> log2cs(p_spc);
is32 <= (p_spf16 == 16'd0);
l2cs <= log2cs(p_spc);
fs_cluster_size <= p_spc;
fs_fat0_sector <= g_fat0;
fs_sectors_per_fat <= spf_v;
fs_num_fats <= p_nfat;
fs_data_base_sector <= g_dbase;
fs_total_sectors <= vol_base + tot_v;
fs_root_cluster <= (p_spf16 == 16'd0) ? g_rootc : 32'd0;
max_cluster <= g_cc + 32'd1;
dscan <= 16'd0;
dirend <= 1'b0;
if (p_spf16 != 16'd0 && g_cc < 32'd4085) begin
state <= H_UNMNT; // FAT12 territory: not supported
end else if (p_spf16 == 16'd0) begin
filesystem_type <= 2'd3;
dclu <= g_rootc;
doff <= 8'd0;
dsec <= g_dbase + (g_rootc << log2cs(p_spc));
if (g_rootc < 32'd2) state <= H_UNMNT;
else state <= H_DIR_RD;
end else begin
filesystem_type <= 2'd2;
dsec <= g_fat0 + fatarea;
drem <= rootsec;
if (rootsec == 32'd0) state <= H_FINISH;
else state <= H_DIR_RD;
end
end
end
H_MBR_T: begin
// f_val[7:0] = partition type; 0 = empty slot
if (f_val[7:0] != 8'h00) begin
f_off <= 9'd446 + {1'd0, mbr_p, 4'd0} + 9'd8; // start LBA
f_cnt <= 2'd3;
f_ret <= H_MBR_L;
state <= F_RD0;
end else if (mbr_p == 2'd3) state <= H_UNMNT;
else begin
mbr_p <= mbr_p + 2'd1;
f_off <= 9'd450 + {1'd0, mbr_p + 2'd1, 4'd0};
f_cnt <= 2'd0;
f_ret <= H_MBR_T;
state <= F_RD0;
end
end
H_MBR_L:
if (f_val == 32'd0) state <= H_UNMNT;
else begin
vol_base <= f_val;
mbr_done <= 1'b1;
state <= H_RDBOOT;
end
// ======== root directory scan =====================================
H_DIR_RD:
if (!pbusy) begin
route <= R_DIR;
e_kind <= K_READ;
e_cmd <= 6'd17;
e_arg <= ccs ? dsec : (dsec << 9);
e_nblk <= 13'd1;
e_ret <= H_DIR_NX;
e_eret <= H_FINISH;
state <= E_GO;
end
H_DIR_NX:
if (!pbusy) begin
if (file_found) state <= no_stream ? H_FINISH : H_STREAM0;
else if (dirend || dscan == 16'hFFFF) state <= H_FINISH;
else begin
dscan <= dscan + 16'd1;
if (!is32) begin
if (drem <= 32'd1) state <= H_FINISH;
else begin
drem <= drem - 32'd1;
dsec <= dsec + 32'd1;
state <= H_DIR_RD;
end
end else begin
if ({24'd0, doff} + 32'd1 < {24'd0, fs_cluster_size}) begin
doff <= doff + 8'd1;
dsec <= dsec + 32'd1;
state <= H_DIR_RD;
end else begin
qc <= dclu; // follow the root cluster chain
fat_ret <= H_DIR_HOP;
state <= H_FAT_RD;
end
end
end
end
H_DIR_HOP:
if (fat_eoc || fat_val < 32'd2 || fat_val > max_cluster)
state <= H_FINISH;
else begin
dclu <= fat_val;
doff <= 8'd0;
dsec <= fs_data_base_sector + (fat_val << l2cs);
state <= H_DIR_RD;
end
// ======== FAT entry fetch (with a one-sector cache) ===============
H_FAT_RD: begin
if ((fs_fat0_sector + (is32 ? (qc >> 7) : (qc >> 8))) == cache_sec)
state <= H_FAT_OFF;
else begin
route <= R_BUF;
cache_sec <= fs_fat0_sector + (is32 ? (qc >> 7) : (qc >> 8));
e_kind <= K_READ;
e_cmd <= 6'd17;
e_arg <= ccs ? (fs_fat0_sector + (is32 ? (qc >> 7) : (qc >> 8)))
: ((fs_fat0_sector +
(is32 ? (qc >> 7) : (qc >> 8))) << 9);
e_nblk <= 13'd1;
e_ret <= H_FAT_OFF;
e_eret <= H_FINISH;
state <= E_GO;
end
end
H_FAT_OFF: begin
f_off <= is32 ? {qc[6:0], 2'b00} : {qc[7:0], 1'b0};
f_cnt <= is32 ? 2'd3 : 2'd1;
f_ret <= H_FAT_V;
state <= F_RD0;
end
H_FAT_V: begin
fat_val <= is32 ? (f_val & 32'h0FFF_FFFF) : {16'd0, f_val[15:0]};
state <= fat_ret;
end
// ======== file streaming ==========================================
H_STREAM0: begin
remaining <= found_file_size;
cur <= found_file_cluster;
if (found_file_size == 32'd0 || found_file_cluster < 32'd2 ||
found_file_cluster > max_cluster) state <= H_FINISH;
else state <= H_RUN0;
end
H_RUN0: begin
run_start <= cur;
run_secs <= {5'd0, fs_cluster_size};
state <= H_RUNX;
end
H_RUNX: begin
qc <= cur;
fat_ret <= H_RUNJ;
state <= H_FAT_RD;
end
H_RUNJ: // merge consecutive clusters into one multi-block read
if (fat_val == cur + 32'd1 && fat_val <= max_cluster &&
({1'b0, run_secs} + {6'd0, fs_cluster_size} <= 14'd4096)) begin
cur <= fat_val;
run_secs <= run_secs + {5'd0, fs_cluster_size};
state <= H_RUNX;
end else begin
nxt <= fat_val;
state <= H_RDRUN;
end
H_RDRUN: begin
route <= R_STREAM;
e_kind <= K_READ;
e_cmd <= (nblk_now > 13'd1) ? 6'd18 : 6'd17;
e_arg <= ccs ? (fs_data_base_sector + (run_start << l2cs))
: ((fs_data_base_sector + (run_start << l2cs)) << 9);
e_nblk <= nblk_now;
e_ret <= H_RUNEND;
e_eret <= H_FINISH; // CRC / timeout: end the stream early
state <= E_GO;
end
H_RUNEND:
if (remaining == 32'd0) state <= H_FINISH;
else if (nxt_eoc || nxt < 32'd2 || nxt > max_cluster)
state <= H_FINISH; // size says more, chain says over: stop
else begin
cur <= nxt;
state <= H_RUN0;
end
// ======== common tails ============================================
H_UNMNT: state <= H_FINISH; // filesystem_type stays < 2
H_FINISH:
if (!pbusy) begin
route <= R_NONE;
scan_done <= 1'b1;
state <= H_HALT;
end
H_HALT: state <= H_HALT; // parked until the next rstn cycle
// ======== engine-op helper ========================================
E_GO: begin
e_start <= 1'b1;
state <= E_WT;
end
E_WT: begin
if (e_done) begin
e_err_seen <= 1'b0;
state <= e_ret;
end else if (e_err) begin
e_err_seen <= 1'b1;
state <= e_eret;
end
end
// ======== little-endian field fetch from secbuf ===================
F_RD0: begin
f_val <= 32'd0;
f_idx <= 2'd0;
fsm_addr <= f_off;
state <= F_RD1;
end
F_RD1: state <= F_RD2; // registered-read latency
F_RD2: begin
f_val <= f_val | ({24'd0, fsm_q} << {f_idx, 3'b000});
if (f_idx == f_cnt) state <= f_ret;
else begin
f_idx <= f_idx + 2'd1;
fsm_addr <= fsm_addr + 9'd1;
state <= F_RD1;
end
end
default: state <= H_HALT;
endcase
end
end
endmodule