I have given the problem of saving files on flash some thought, and slept on this idea. FPGA boards almost always use a typical W25 series SPI NOR Flash. The iCEBreaker fpga board that I am using has a generous 16MB W25Q128JV.
I am leaning towards something dead simple like a copy-on-write circular buffer in the flash. The savegames are fixed 1KB, as @Pharap said – since it’s what the size of the EEPROM is. I am thinking of a scheme where the savegame, if modified is written back (committed) to the flash at most once every second.
Assume, each “file” takes up one 4KB block (min erase sector on a typical w25-series spi nor flash = 4KB). A certain number of blocks forms the “filesystem” (FS). When starting the game, I’d O(N) search the blocks for its savegame and load it into the ram. No index block required (as you’d expect in CoW or for that matter, most FS), and the search cost is no biggie.
As the game runs, the savegame in the ram is modified. If the savegame remains modified for some time – indicating it is stable, it is copied and a write-back starts. I will search for the next free block starting from the current location in the circular buffer, write the new savegame into the free block, and issue an erase for the current one.
The find (block allocation), write (0.7~3ms per page of 256B) and erase sequence (45~400ms) would be sequenced across game-frames or soft-timers and would thus be non-blocking (and totally handled in the background). Since, I only need to write-back at most once per second, the sequence would be done and the flash would be ready for a new write. Usually, we’d not expect to write back that often.
I think this would be the cheapest code solution for the job. Very application specific, and I get wear-levelling. Power-off resilience can be ensured with some metadata and atomic sequence of the write+erase op (maybe? – need to think about this more). I’d need some code to check the blocks upon boot and ensure the bad blocks (half erased/half written) are cleared out, i.e. some garbage collection. That’s a stretch goal though, I am not strongly aiming for power-resilience. But, overall, this would be cheap.
I can do things a little smarter by committing 3 full writes (256B header + 3 x 1KB data) to the same 4KB block before moving onto a different block and issuing an erase. A bit mask in the header can keep this status. When moving to a new block, the header has to be copied over and started fresh. The important part is that a savegame uses the whole block (4KB) and does not share. In this scheme I can mess with the logical block size to decrease the overhead of the header – though at the cost of erase time (of physical blocks – for example 32KB erase takes 120ms~1.6s). This scheme is a hybrid CoW + Logging FS.
However, if I were to chose a real FS – littleFS is seriously looking to be the best choice. Very informative presentation - https://www.youtube.com/watch?v=ogdqeaO-83s - from the author of littleFS.
EDIT: I approached this wrong. I should spin this - Flash cart(ridge)