Block Hamr: a ProDOS block device that survives a power cycle
What I tried
Rev 2 was in fabrication, so I picked up the boot-to-RAM idea from the PicoPort log. I called it IWM Hamr: the full IWM emulation from Smart Hamr, with the SDRAM on the PCB as the device instead of an ESP32 on the GPIO wires. A boot loader copies a .po from SPI flash into SDRAM, the IWM serializes block data in and out, and the SmartPort protocol runs as a Verilog state machine behind it. I was knee-deep in it before it sank in that the IWM exists to serialize data across a cable. FM encoding, bit cell timing, shift registers and handshakes are there to move bytes over a pair of wires to an external device. With the storage in 25 MHz SDRAM on the same PCB there is no cable and nothing to serialize. So I ripped it out.
The IWM and SmartPort emulation had eaten February. A block device does not need any of it, so on 2026-03-26 I replaced the whole stack with a register file at $C0C0: status and command, block number low and high, a data-read port and a data-write port with auto-increment, and a block count pair filled in by the boot loader. The 6502 driver fits in the 256-byte slot ROM, so there is no $C800 to fight over. The boot loader copies the disk image from flash at 0x400000 into SDRAM, reads the ProDOS volume header at bytes 1065 and 1066 to learn how many blocks the image has, and raises boot_done.
Persistence came on 2026-03-30 in three commits: v3 synchronous write-through to flash, v3.1 8 MB volumes, v3.2 auto-detected volume size with a 12 MB ceiling, which is what is left of the 16 MB flash after the bitstream.
What I measured
- SAVE, CREATE, SAVE into a nested directory, CATALOG, warm and cold reboot all work against SDRAM.
- 12 MB maximum image, 24,576 blocks of 512 bytes.
- Modified blocks flush to flash asynchronously and are there after a power cycle.
- Streaming 8 MB from SPI flash at 12.5 MHz takes about 5 s at power-on.
What broke or surprised me
STA abs,X is five cycles and the fourth is a dummy read of the effective address, so a store to the data port raised DEVICE SELECT twice and the auto-increment counted twice. LDA abs,X is four cycles and only pulses once. The fix was separate read and write addresses: the dummy read lands on the write port with R/W high and the increment condition rejects it. Write data is captured on the rising edge of DEVICE SELECT, the same point AppleIISd and the original IWM use, since the 6502 holds data for 10 ns to 30 ns after PHI0 falls.
Adding persist states to the SDRAM arbiter’s state machine broke block reads and writes on hardware, even when the new states were unreachable, and simulation passed the whole time. Persistence went into its own module, persist_controller, and the arbiter has not been touched since. The final shape is write_through for gating, flash_persist for the flash operations, flash_writer as the SPI engine, and a top-level mux that hands SDRAM to whichever of the arbiter or flash_persist claims it.
The one that had me chasing ghosts was a 5-bit sector address. Flash is organized in 4 KB sectors, 8 ProDOS blocks each, and the sector number came from block_num[7:3]: five bits, maximum 31. A 280-block ProDOS disk needs sector 34. The test image, a full ProDOS 2.4.1 distribution, had 7 free blocks left, all at blocks 273 to 279, which is sector 34. The truncation wrapped sector 34 to sector 2. Directory entries live in sector 0 and persisted fine. File data lived in sector 34 and vanished on every reboot. CATALOG looked right and RUN gave nothing, until I looked at which blocks ProDOS was allocating and did the math.
Next
An SD card instead of a fixed image in flash. That became Flash Hamr. Before that I was thinking about a boot menu in the slot ROM: the 12 MB of flash left after the bitstream holds about 87 140K volumes or 15 800K images.