Reading FAT32 inside a FPGA core

Current FPGA boards have support for a sdcard but not all cores support it. There’re some classic cores (like Acorn Atom or the Spectrum) which do it with an extension ROM. But often the sdcard needs to be in a special format not readable by a PC which is also a problem for the MiST because it needs to load the core from a FAT filesystem and currently its not supported to change the sd card during runtime.
Another solution is to instantiate a second CPU which does the FAT reading and injects the code into RAM addressable by the core. But this comes with extra code because its like a second machine with own bios inside the fpga. Examples for that are the MSX and PC Engine port for the MiST.
Wouldn’t it be nice to keep requirements small and read a FAT filesystem directly in the core?

The FPGAmstrad core from Renaud HΓ©lias raised the idea but I wanted to code my own module for learning purpose. But where to start? First I needed a library to easily read the sdcard block by block. I found code from XESS which was a good start (https://github.com/xesscorp/VHDL_Lib/blob/master/SDCard.vhd).
Next was a comprehend guide to understand FAT. A good summary with the most important bits are Paul’s 8051 Code Library.

The code is written around a FSM (finite state machine). I started with reading the first sector of the sdcard to determine the first partition. Before reading the first sector of the first partition the code checks if its a valid FAT filesystem and what type it is. The current code supports only FAT32 and one partition. But this should cope with most situations as today you can only buy SDHC cards but even SD cards can be formatted with FAT32.

Once we have the LBA of the first partition we read the first sector and derive some important values from there:

sd_factor is 512 for SD cards (byte addressing) and 1 for SDHC cards (block addressing).

With those values we know where to start reading the root directory and can search for the file we want to load. Subdirectories are also located here but the current code only supports reading from the root directory and from small directories as FAT reading for dirs isn’t supported yet πŸ™‚
For FAT32 the directory entries are 32 bytes long and we jump from entry to entry until we’ve found our file.

Once found the entry contains the file size and the starting cluster. From there we can read the file sector by sector and cluster by cluster. For my 4GB SDHC card the cluster size was 4KB (8 sectors * 512byte). After reading a full cluster we need to find the next cluster via the FAT and so on.

The current code is still work in progress as it contains a lot of debug stuff and is missing some important bits:
– Reading longer directories via FAT
– Optimize code as it currently takes up to 12.000 LE’s on a Cyclone III (and I have no idea why)
– Support subdirectories
– Support writing would be nice but I don’t think that I get to it soon

At least it works which was a good start for me πŸ™‚
The code is in my github repo as usual if you want to give it a try.
https://github.com/wsoltys/mist-cores/tree/master/misc/sdcard

Disclaimer: The code was written for learning purpose and might be highly inefficient. I’m open for any suggestions to optimize the code.

3 thoughts on “Reading FAT32 inside a FPGA core

  1. Thanks for the nice tutorial.

    In my experience designs with excessive logic requirements typically include some memory portions which for some reason the sythesis tool doesn’t place in dedicated block ram. The reason often is combinatorics which drive the memory. Running input and output via registers might help as well as the explicit usage of dedicated block ram.

  2. In FPGAmstrad I does use also FSM (finite state machine), but as I have too much states, I do use integer (with a range) and comments πŸ™‚
    But as it does evolve, line numbers are now mixed, like in a Amstrad Basic program listing πŸ˜€

    I have several state machines, each time one is master of another slave one. Each state machine is in it’s own process.

    I does use some internal process functions to redondant stuff like… launching a command (in another process (in a slave process, from master process)) or computing a CRC.
    Functions are here for readability, for sample :
    * fillRAM(file_sector_pointer,file_address,conv_integer(file_size));
    * send_cmd(SET_BLOCKLEN,conv_std_logic_vector(BLOCK_SIZE,32-9) & “0” & x”00″);
    That’s the force of my implementation.

    I’ve got four layers :
    * SDRAM_SPIMASTER.vhd->native_send_cmd (execute one SPI CMD)
    * SDRAM_SPIMASTER.vhd->sangoku (read/write one byte at a certain address calling SPI CMD functions)
    * SDRAM_FAT32_LOADER.vhd->process spi_to_loader (read/write several bytes (1 2 4 or transmit to RAM/dump from RAM) at a certain address)
    * SDRAM_FAT32_LOADER.vhd->process tortue_geniale (FAT32 hello and use of files)

    Synchronous between process are done using 2 booleans (one for “do action” and another for “done confirmation”), surrounding each FSM (“do” for slave FSM, “done” for master FSM). I add then a process’s function (in master FSM’s process) about “master asking slave to do something”… in a clear way.

    For debug, I use the state machine key : an integer (with a range), displayed on leds, for sample :
    leds_tortue_geniale<=conv_std_logic_vector(step_var,8);
    (step_* is the name I give in fact to my state machine key (integer with a range))
    For debug, I use also error states in the state machine, for sample :
    when 27=>NULL; — bad root folder cluster
    This is another reason about using integer instead of NAMED_STATES in states machines.

Leave a Reply

Your email address will not be published. Required fields are marked *