FPGA-SDcard-Reader/RTL/sd_file_reader.v
2023-06-08 20:40:07 +08:00

577 lines
26 KiB
Verilog

//--------------------------------------------------------------------------------------------------------
// Module : sd_file_reader
// Type : synthesizable, IP's top
// Standard: Verilog 2001 (IEEE1364-2001)
// Function: A SD-card host to initialize SD-card and read files
// Specify a filename, sd_file_reader will read out file content
// Support CardType : SDv1.1 , SDv2 or SDHCv2
// Support FileSystem : FAT16 or FAT32
//--------------------------------------------------------------------------------------------------------
module sd_file_reader #(
parameter FILE_NAME_LEN = 11 , // valid length of FILE_NAME (in bytes)
parameter [52*8-1:0] FILE_NAME = "example.txt", // file to read, ignore Upper and Lower Case
// For example, if you want to read a file named HeLLo123.txt in the SD card,
// this parameter can be hello123.TXT, HELLO123.txt or HEllo123.Txt
parameter [2:0] CLK_DIV = 3'd2, // when clk = 0~ 25MHz , set CLK_DIV = 3'd1,
// when clk = 25~ 50MHz , set CLK_DIV = 3'd2,
// when clk = 50~100MHz , set CLK_DIV = 3'd3,
// when clk = 100~200MHz , set CLK_DIV = 3'd4,
// ......
parameter SIMULATE = 0
) (
// rstn active-low, 1:working, 0:reset
input wire rstn,
// clock
input wire clk,
// SDcard signals (connect to SDcard), this design do not use sddat1~sddat3.
output wire sdclk,
inout sdcmd,
input wire sddat0, // FPGA only read SDDAT signal but never drive it
// status output (optional for user)
output wire [3:0] card_stat, // show the sdcard initialize status
output wire [1:0] card_type, // 0=UNKNOWN , 1=SDv1 , 2=SDv2 , 3=SDHCv2
output wire [1:0] filesystem_type, // 0=UNASSIGNED , 1=UNKNOWN , 2=FAT16 , 3=FAT32
output reg file_found, // 0=file not found, 1=file found
// file content data output (sync with clk)
output reg outen, // when outen=1, a byte of file content is read out from outbyte
output reg [7:0] outbyte // a byte of file content
);
function [7:0] toUpperCase;
input [7:0] in;
begin
toUpperCase = (in>=8'h61 && in<=8'h7A) ? (in & 8'b11011111) : in;
end
endfunction
wire [52*8-1:0] FILE_NAME_UPPER;
generate genvar k;
for (k=0; k<52; k=k+1) begin : convert_fname_to_upper
assign FILE_NAME_UPPER[k*8 +: 8] = toUpperCase( FILE_NAME[k*8 +: 8] );
end
endgenerate
initial file_found = 1'b0;
initial {outen,outbyte} = 0;
reg read_start = 1'b0;
reg [31:0] read_sector_no = 0;
wire read_done;
wire rvalid;
wire [ 8:0] raddr;
wire [ 7:0] rdata;
reg [31:0] rootdir_sector = 0 , rootdir_sector_t; // rootdir sector number (FAT16 only)
reg [15:0] rootdir_sectorcount = 0 , rootdir_sectorcount_t; // (FAT16 only)
reg [31:0] curr_cluster = 0 , curr_cluster_t; // current reading cluster number
wire [ 6:0] curr_cluster_fat_offset;
wire [24:0] curr_cluster_fat_no;
assign {curr_cluster_fat_no,curr_cluster_fat_offset} = curr_cluster;
wire [ 7:0] curr_cluster_fat_offset_fat16;
wire [23:0] curr_cluster_fat_no_fat16;
assign {curr_cluster_fat_no_fat16,curr_cluster_fat_offset_fat16} = curr_cluster;
reg [31:0] target_cluster = 0; // target cluster number item in FAT32 table
reg [15:0] target_cluster_fat16 = 16'h0; // target cluster number item in FAT16 table
reg [ 7:0] cluster_sector_offset=8'h0 , cluster_sector_offset_t; // current sector number in cluster
reg [31:0] file_cluster = 0;
reg [31:0] file_size = 0;
reg [ 7:0] cluster_size = 0 , cluster_size_t;
reg [31:0] first_fat_sector_no = 0 , first_fat_sector_no_t;
reg [31:0] first_data_sector_no= 0 , first_data_sector_no_t;
reg search_fat = 1'b0;
localparam [2:0] RESET = 3'd0,
SEARCH_MBR = 3'd1,
SEARCH_DBR = 3'd2,
LS_ROOT_FAT16 = 3'd3,
LS_ROOT_FAT32 = 3'd4,
READ_A_FILE = 3'd5,
DONE = 3'd6;
reg [2:0] filesystem_state = RESET;
localparam [1:0] UNASSIGNED = 2'd0,
UNKNOWN = 2'd1,
FAT16 = 2'd2,
FAT32 = 2'd3;
reg [1:0] filesystem = UNASSIGNED,
filesystem_parsed;
//enum logic [2:0] {RESET, SEARCH_MBR, SEARCH_DBR, LS_ROOT_FAT16, LS_ROOT_FAT32, READ_A_FILE, DONE} filesystem_state = RESET;
//enum logic [1:0] {UNASSIGNED, UNKNOWN, FAT16, FAT32} filesystem=UNASSIGNED, filesystem_parsed;
assign filesystem_type = filesystem;
//----------------------------------------------------------------------------------------------------------------------
// loop variables
integer ii, i;
//----------------------------------------------------------------------------------------------------------------------
// store MBR or DBR fields
//----------------------------------------------------------------------------------------------------------------------
reg [ 7:0] sector_content [0:511];
initial for (ii=0; ii<512; ii=ii+1) sector_content[ii] = 0;
always @ (posedge clk)
if (rvalid)
sector_content[raddr] <= rdata;
//----------------------------------------------------------------------------------------------------------------------
// parse MBR or DBR fields
//----------------------------------------------------------------------------------------------------------------------
wire is_boot_sector = ( {sector_content['h1FE],sector_content['h1FF]}==16'h55AA );
wire is_dbr = sector_content[0]==8'hEB || sector_content[0]==8'hE9;
wire [31:0] dbr_sector_no = {sector_content['h1C9],sector_content['h1C8],sector_content['h1C7],sector_content['h1C6]};
wire [15:0] bytes_per_sector = {sector_content['hC],sector_content['hB]};
wire [ 7:0] sector_per_cluster = sector_content['hD];
wire [15:0] resv_sectors = {sector_content['hF],sector_content['hE]};
wire [ 7:0] number_of_fat = sector_content['h10];
wire [15:0] rootdir_itemcount = {sector_content['h12],sector_content['h11]}; // root dir item count (FAT16 Only)
reg [31:0] sectors_per_fat = 0;
reg [31:0] root_cluster = 0;
always @ (*) begin
sectors_per_fat = {16'h0, sector_content['h17], sector_content['h16]};
root_cluster = 0;
if(sectors_per_fat>0) begin // FAT16 case
filesystem_parsed = FAT16;
end else if(sector_content['h56]==8'h32) begin // FAT32 case
filesystem_parsed = FAT32;
sectors_per_fat = {sector_content['h27],sector_content['h26],sector_content['h25],sector_content['h24]};
root_cluster = {sector_content['h2F],sector_content['h2E],sector_content['h2D],sector_content['h2C]};
end else begin // Unknown FileSystem
filesystem_parsed = UNKNOWN;
end
end
//----------------------------------------------------------------------------------------------------------------------
// main FSM
//----------------------------------------------------------------------------------------------------------------------
always @ (posedge clk or negedge rstn)
if(~rstn) begin
read_start <= 1'b0;
read_sector_no <= 0;
filesystem_state <= RESET;
filesystem <= UNASSIGNED;
search_fat <= 1'b0;
cluster_size <= 8'h0;
first_fat_sector_no <= 0;
first_data_sector_no <= 0;
curr_cluster <= 0;
cluster_sector_offset <= 8'h0;
rootdir_sector <= 0;
rootdir_sectorcount <= 16'h0;
end else begin
cluster_size_t = cluster_size;
first_fat_sector_no_t = first_fat_sector_no;
first_data_sector_no_t = first_data_sector_no;
curr_cluster_t = curr_cluster;
cluster_sector_offset_t = cluster_sector_offset;
rootdir_sector_t = rootdir_sector;
rootdir_sectorcount_t = rootdir_sectorcount;
read_start <= 1'b0;
if (read_done) begin
case(filesystem_state)
SEARCH_MBR : if(is_boot_sector) begin
filesystem_state <= SEARCH_DBR;
if(~is_dbr) read_sector_no <= dbr_sector_no;
end else begin
read_sector_no <= read_sector_no + 1;
end
SEARCH_DBR : if(is_boot_sector && is_dbr ) begin
if(bytes_per_sector!=16'd512) begin
filesystem_state <= DONE;
end else begin
filesystem <= filesystem_parsed;
if(filesystem_parsed==FAT16) begin
cluster_size_t = sector_per_cluster;
first_fat_sector_no_t = read_sector_no + resv_sectors;
rootdir_sectorcount_t = rootdir_itemcount / (16'd512/16'd32);
rootdir_sector_t = first_fat_sector_no_t + sectors_per_fat * number_of_fat;
first_data_sector_no_t= rootdir_sector_t + rootdir_sectorcount_t - cluster_size_t*2;
cluster_sector_offset_t = 8'h0;
read_sector_no <= rootdir_sector_t + cluster_sector_offset_t;
filesystem_state <= LS_ROOT_FAT16;
end else if(filesystem_parsed==FAT32) begin
cluster_size_t = sector_per_cluster;
first_fat_sector_no_t = read_sector_no + resv_sectors;
first_data_sector_no_t= first_fat_sector_no_t + sectors_per_fat * number_of_fat - cluster_size_t * 2;
curr_cluster_t = root_cluster;
cluster_sector_offset_t = 8'h0;
read_sector_no <= first_data_sector_no_t + cluster_size_t * curr_cluster_t + cluster_sector_offset_t;
filesystem_state <= LS_ROOT_FAT32;
end else begin
filesystem_state <= DONE;
end
end
end
LS_ROOT_FAT16 : if(file_found) begin
curr_cluster_t = file_cluster;
cluster_sector_offset_t = 8'h0;
read_sector_no <= first_data_sector_no_t + cluster_size_t * curr_cluster_t + cluster_sector_offset_t;
filesystem_state <= READ_A_FILE;
end else if(cluster_sector_offset_t<rootdir_sectorcount_t) begin
cluster_sector_offset_t = cluster_sector_offset_t + 8'd1;
read_sector_no <= rootdir_sector_t + cluster_sector_offset_t;
end else begin
filesystem_state <= DONE; // cant find target file
end
LS_ROOT_FAT32 : if(~search_fat) begin
if(file_found) begin
curr_cluster_t = file_cluster;
cluster_sector_offset_t = 8'h0;
read_sector_no <= first_data_sector_no_t + cluster_size_t * curr_cluster_t + cluster_sector_offset_t;
filesystem_state <= READ_A_FILE;
end else if(cluster_sector_offset_t<(cluster_size_t-1)) begin
cluster_sector_offset_t = cluster_sector_offset_t + 8'd1;
read_sector_no <= first_data_sector_no_t + cluster_size_t * curr_cluster_t + cluster_sector_offset_t;
end else begin // read FAT to get next cluster
search_fat <= 1'b1;
cluster_sector_offset_t = 8'h0;
read_sector_no <= first_fat_sector_no_t + curr_cluster_fat_no;
end
end else begin
search_fat <= 1'b0;
cluster_sector_offset_t = 8'h0;
if(target_cluster=='h0FFF_FFFF || target_cluster=='h0FFF_FFF8 || target_cluster=='hFFFF_FFFF || target_cluster<2) begin
filesystem_state <= DONE; // cant find target file
end else begin
curr_cluster_t = target_cluster;
read_sector_no <= first_data_sector_no_t + cluster_size_t * curr_cluster_t + cluster_sector_offset_t;
end
end
READ_A_FILE :
if(~search_fat) begin
if(cluster_sector_offset_t<(cluster_size_t-1)) begin
cluster_sector_offset_t = cluster_sector_offset_t + 8'd1;
read_sector_no <= first_data_sector_no_t + cluster_size_t * curr_cluster_t + cluster_sector_offset_t;
end else begin // read FAT to get next cluster
search_fat <= 1'b1;
cluster_sector_offset_t = 8'h0;
read_sector_no <= first_fat_sector_no_t + (filesystem==FAT16 ? curr_cluster_fat_no_fat16 : curr_cluster_fat_no);
end
end else begin
search_fat <= 1'b0;
cluster_sector_offset_t = 8'h0;
if(filesystem==FAT16) begin
if(target_cluster_fat16>=16'hFFF0 || target_cluster_fat16<16'h2) begin
filesystem_state <= DONE; // read to the end of file, done
end else begin
curr_cluster_t = {16'h0,target_cluster_fat16};
read_sector_no <= first_data_sector_no_t + cluster_size_t * curr_cluster_t + cluster_sector_offset_t;
end
end else begin
if(target_cluster=='h0FFF_FFFF || target_cluster=='h0FFF_FFF8 || target_cluster=='hFFFF_FFFF || target_cluster<2) begin
filesystem_state <= DONE; // read to the end of file, done
end else begin
curr_cluster_t = target_cluster;
read_sector_no <= first_data_sector_no_t + cluster_size_t * curr_cluster_t + cluster_sector_offset_t;
end
end
end
endcase
end else begin
case (filesystem_state)
RESET : filesystem_state <= SEARCH_MBR;
SEARCH_MBR : read_start <= 1'b1;
SEARCH_DBR : read_start <= 1'b1;
LS_ROOT_FAT16 : read_start <= 1'b1;
LS_ROOT_FAT32 : read_start <= 1'b1;
READ_A_FILE : read_start <= 1'b1;
//DONE : $finish;
endcase
end
cluster_size <= cluster_size_t;
first_fat_sector_no <= first_fat_sector_no_t;
first_data_sector_no <= first_data_sector_no_t;
curr_cluster <= curr_cluster_t;
cluster_sector_offset <= cluster_sector_offset_t;
rootdir_sector <= rootdir_sector_t;
rootdir_sectorcount <= rootdir_sectorcount_t;
end
generate if (SIMULATE) begin
always @ (posedge clk)
if (read_done) begin
end else begin
if (filesystem_state == DONE) $finish; // only for finish simulation, will be ignore when synthesize
end
end endgenerate
//----------------------------------------------------------------------------------------------------------------------
// capture data in FAT table
//----------------------------------------------------------------------------------------------------------------------
always @ (posedge clk or negedge rstn) begin
if(~rstn) begin
target_cluster <= 0;
target_cluster_fat16 <= 16'h0;
end else begin
if(search_fat && rvalid) begin
if(filesystem==FAT16) begin
if(raddr[8:1]==curr_cluster_fat_offset_fat16)
target_cluster_fat16[8*raddr[0] +: 8] <= rdata;
end else if(filesystem==FAT32) begin
if(raddr[8:2]==curr_cluster_fat_offset)
target_cluster[8*raddr[1:0] +: 8] <= rdata;
end
end
end
end
sd_reader #(
.CLK_DIV ( CLK_DIV ),
.SIMULATE ( SIMULATE )
) u_sd_reader (
.rstn ( rstn ),
.clk ( clk ),
.sdclk ( sdclk ),
.sdcmd ( sdcmd ),
.sddat0 ( sddat0 ),
.card_type ( card_type ),
.card_stat ( card_stat ),
.rstart ( read_start ),
.rsector ( read_sector_no ),
.rbusy ( ),
.rdone ( read_done ),
.outen ( rvalid ),
.outaddr ( raddr ),
.outbyte ( rdata )
);
//----------------------------------------------------------------------------------------------------------------------
// parse root dir
//----------------------------------------------------------------------------------------------------------------------
reg fready = 1'b0; // a file is find when fready = 1
reg [ 7:0] fnamelen = 0;
reg [15:0] fcluster = 0;
reg [31:0] fsize = 0;
reg [ 7:0] fname [0:51];
reg [ 7:0] file_name [0:51];
reg isshort=1'b0, islongok=1'b0, islong=1'b0, longvalid=1'b0;
reg isshort_t , islongok_t , islong_t , longvalid_t ;
reg [ 5:0] longno = 6'h0 , longno_t;
reg [ 7:0] lastchar = 8'h0;
reg [ 7:0] fdtnamelen = 8'h0 , fdtnamelen_t;
reg [ 7:0] sdtnamelen = 8'h0 , sdtnamelen_t;
reg [ 7:0] file_namelen = 8'h0;
reg [15:0] file_1st_cluster = 16'h0 , file_1st_cluster_t;
reg [31:0] file_1st_size = 0 , file_1st_size_t;
initial for(i=0;i<52;i=i+1) begin file_name[i]=8'h0; fname[i]=8'h0; end
always @ (posedge clk or negedge rstn) begin
if(~rstn) begin
fready<=1'b0; fnamelen<=8'h0; file_namelen<=8'h0;
fcluster<=16'h0; fsize<=0;
for(i=0;i<52;i=i+1) begin file_name[i]<=8'h0; fname[i]<=8'h0; end
{isshort, islongok, islong, longvalid} <= 4'b0000;
longno <= 6'h0;
lastchar <= 8'h0;
fdtnamelen <= 8'h0;
sdtnamelen <= 8'h0;
file_1st_cluster <= 16'h0;
file_1st_size <= 0;
end else begin
{isshort_t, islongok_t, islong_t, longvalid_t} = {isshort, islongok, islong, longvalid};
longno_t = longno;
fdtnamelen_t = fdtnamelen;
sdtnamelen_t = sdtnamelen;
file_1st_cluster_t = file_1st_cluster;
file_1st_size_t = file_1st_size;
fready<=1'b0; fnamelen<=8'h0;
for(i=0;i<52;i=i+1) fname[i]<=8'h0;
fcluster<=16'h0; fsize<=0;
if( rvalid && (filesystem_state==LS_ROOT_FAT16||filesystem_state==LS_ROOT_FAT32) && ~search_fat ) begin
case (raddr[4:0])
5'h1A : file_1st_cluster_t[ 0+:8] = rdata;
5'h1B : file_1st_cluster_t[ 8+:8] = rdata;
5'h1C : file_1st_size_t[ 0+:8] = rdata;
5'h1D : file_1st_size_t[ 8+:8] = rdata;
5'h1E : file_1st_size_t[16+:8] = rdata;
5'h1F : file_1st_size_t[24+:8] = rdata;
endcase
if(raddr[4:0]==5'h0) begin
{islongok_t, isshort_t} = 2'b00;
fdtnamelen_t = 8'h0; sdtnamelen_t=8'h0;
if(rdata!=8'hE5 && rdata!=8'h2E && rdata!=8'h00) begin
if(islong_t && longno_t==6'h1)
islongok_t = 1'b1;
else
isshort_t = 1'b1;
end
if(rdata[7]==1'b0 && ~islongok_t) begin
if(rdata[6]) begin
{islong_t,longvalid_t} = 2'b11;
longno_t = rdata[5:0];
end else if(islong_t) begin
if(longno_t>6'h1 && (rdata[5:0]+6'h1==longno_t) ) begin
islong_t = 1'b1;
longno_t = rdata[5:0];
end else begin
islong_t = 1'b0;
end
end else
islong_t = 1'b0;
end else
islong_t = 1'b0;
end else if(raddr[4:0]==5'hB) begin
if(rdata!=8'h0F)
islong_t = 1'b0;
if(rdata!=8'h20)
{isshort_t, islongok_t} = 2'b00;
end else if(raddr[4:0]==5'h1F) begin
if(islongok_t && longvalid_t || isshort_t) begin
fready <= 1'b1;
fnamelen <= file_namelen;
for(i=0;i<52;i=i+1) fname[i] <= (i<file_namelen) ? file_name[i] : 8'h0;
fcluster <= file_1st_cluster_t;
fsize <= file_1st_size_t;
end
end
if(islong_t) begin
if(raddr[4:0]>5'h0&&raddr[4:0]<5'hB || raddr[4:0]>=5'hE&&raddr[4:0]<5'h1A || raddr[4:0]>=5'h1C)begin
if(raddr[4:0]<5'hB ? raddr[0] : ~raddr[0]) begin
lastchar <= rdata;
fdtnamelen_t = fdtnamelen_t + 8'd1;
end else begin
//automatic logic [15:0] unicode = {rdata,lastchar};
if({rdata,lastchar} == 16'h0000) begin
file_namelen <= fdtnamelen_t-8'd1 + (longno_t-8'd1)*8'd13;
end else if({rdata,lastchar} != 16'hFFFF) begin
if(rdata == 8'h0) begin
file_name[fdtnamelen_t-8'd1+(longno_t-8'd1)*8'd13] <= (lastchar>=8'h61 && lastchar<=8'h7A) ? lastchar&8'b11011111 : lastchar;
end else begin
longvalid_t = 1'b0;
end
end
end
end
end
if(isshort_t) begin
if(raddr[4:0]<5'h8) begin
if(rdata!=8'h20) begin
file_name[sdtnamelen_t] <= rdata;
sdtnamelen_t = sdtnamelen_t + 8'd1;
end
end else if(raddr[4:0]<5'hB) begin
if(raddr[4:0]==5'h8) begin
file_name[sdtnamelen_t] <= 8'h2E;
sdtnamelen_t = sdtnamelen_t + 8'd1;
end
if(rdata!=8'h20) begin
file_name[sdtnamelen_t] <= rdata;
sdtnamelen_t = sdtnamelen_t + 8'd1;
end
end else if(raddr[4:0]==5'hB) begin
file_namelen <= sdtnamelen_t;
end
end
end
{isshort, islongok, islong, longvalid} <= {isshort_t, islongok_t, islong_t, longvalid_t};
longno <= longno_t;
fdtnamelen <= fdtnamelen_t;
sdtnamelen <= sdtnamelen_t;
file_1st_cluster <= file_1st_cluster_t;
file_1st_size <= file_1st_size_t;
end
end
//----------------------------------------------------------------------------------------------------------------------
// compare Target filename with Parsed filename
//----------------------------------------------------------------------------------------------------------------------
always @ (posedge clk or negedge rstn)
if(~rstn) begin
file_found <= 1'b0;
file_cluster <= 0;
file_size <= 0;
end else begin
if (fready && fnamelen==FILE_NAME_LEN) begin
file_found <= 1'b1;
file_cluster <= fcluster;
file_size <= fsize;
for (ii=0; ii<FILE_NAME_LEN; ii=ii+1) begin
if( fname[FILE_NAME_LEN-1-ii] != FILE_NAME_UPPER[ii*8+:8] ) begin
file_found <= 1'b0;
file_cluster <= 0;
file_size <= 0;
end
end
end
end
//----------------------------------------------------------------------------------------------------------------------
// output file content
//----------------------------------------------------------------------------------------------------------------------
reg [31:0] fptr = 0;
always @ (posedge clk or negedge rstn)
if(~rstn) begin
fptr <= 0;
{outen,outbyte} <= 0;
end else begin
if(rvalid && filesystem_state==READ_A_FILE && ~search_fat && fptr<file_size) begin
fptr <= fptr + 1;
{outen,outbyte} <= {1'b1,rdata};
end else
{outen,outbyte} <= 0;
end
endmodule