Flash Hamr: 4,800 lines of FAT32 Verilog lose to a PicoRV32 running FatFS

/ Byte Hamr / updated 2026-09-11 / from project_byte_hamr flash-hamr picorv32 fatfs sd-card fat32

What I tried

Block Hamr served one image baked into flash. The images were always going to come from an SD card: FAT32, drop a .po on it, plug it in.

First attempt was pure RTL. Verilog state machines parsing FAT32, chasing cluster chains, talking SPI to the card. The commit message was “Attempt one, no softcore.” About 4,800 lines across fat32_reader.v, sd_controller.v, sd_mount.v, sd_persist.v and friends. It kind of worked. Every FAT32 edge case, long filenames, directory parsing, sector math, was another state machine branch and another week.

Then PicoRV32: a RISC-V RV32I soft core, one Verilog file, 3,049 lines, written by Claire Xenia Wolf, who also wrote Yosys. It targets the toolchain I already use, with first-class ECP5 support. With a real CPU I could use elm-chan’s FatFS. The firmware is about 970 lines of C in main.c, diskio.c and spi_sd.c. It opens a file, reads blocks on demand, and writes them back.

The Apple side did not change: a 6502 driver ROM in the slot space talks to the register interface. A block request goes to the PicoRV32 through a toggle-based mailbox for the clock crossing. A block already cached in SDRAM gets copied to the block buffer. A miss reads from SD through FatFS, lands in SDRAM, then gets served. Writes go to SDRAM first so the IIe does not wait, then the CPU persists them to the .po on the card.

The picker in the slot ROM, listing the .PO and .2MG images on the card with their block counts.
The picker in the slot ROM, listing the .PO and .2MG images on the card with their block counts.
Bitsy Bye on a 32 MB ProDOS volume served from the SD card.
Bitsy Bye on a 32 MB ProDOS volume served from the SD card.
Oregon Trail off the SD card. Wagon leader: BYTE HAMR.
Oregon Trail off the SD card. Wagon leader: BYTE HAMR.

What I measured

What broke or surprised me

The worst one. I spent days on what looked like CDC timing, SDRAM cache misses and stretch pulses. hamr_rom.mem, the 6502 driver ROM, was stale. The build never called the assembler to regenerate it, so the RTL was right the whole time and the ROM did not match the register interface. The Makefile now rebuilds it from the Merlin source.

Next

Multiple drives and a picker. That became v6 through v8 over the next two days.