Conway's Multiverse: a soft 6502 on the card runs eight Life universes while the IIe watches
What I tried
The PicoPort’s PIO is driven by a small internal RISC-V, and that planted the question of whether the Byte Hamr could run its own processor next to the host. The ECP5 had the room. A 6502 made sense for this machine, so I put Arlet Ottens’ verilog-6502 in with its own block RAM and a $E000 write window that posts one SDRAM write through a two-client priority arbiter, monitor first, coprocessor second. Everything is one 25 MHz domain, no CDC.
C0 on 2026-06-08 was the steel thread: the coprocessor runs LDA #$42, STA $E000 from its own BRAM, the byte lands in SDRAM, and from the monitor B 000 then R 0040 prints 0040: 42. C1 the same day was a resident, hardware-protected kernel the host registers tasks into. C2 was a cooperative scheduler: two Merlin tasks race, the host reads 0102, retune the budgets and it reads 0201. C3 added a preemptive tick timer and an IRQ into the Arlet core, and a third task with no yields still gets sliced. C3.1 made the GO trigger a nonzero write so a stale count could not re-arm across runs. C-flash on 06-09 saves the task registry to flash and restores it on boot, with a magic-last header so a torn write fails safe. C4 the same day was async skill dispatch: a control-plane register block, a dispatcher inside the ISR, a slot mailbox ABI so a skill is reentrant, and CPLIB on the host with CP_CALL, POLL, WAIT and RESULT. Then a two-step $E004 to $E008 auto-increment read window, so the coprocessor can read SDRAM as well as write it. The demo needs that.
The demo is Conway’s Life, eight of them. LIFE8 is one registered skill that loops over eight independent universes in SDRAM forever, bit-packed cells, a three-row sliding window so each row is read once, wrap-around edges. The IIe picks a universe with the 0 to 7 keys, draws it, and otherwise does nothing. Switch away, come back, it has moved on.

What I measured
- C0 build: about 2% of LUTs, 4 EBR, logic Fmax around 72 MHz against the 25 MHz clock.
- Double Hi-Res grid, 560 by 192, about 107,000 cells: a few seconds per generation per universe on a 25 MHz 6502 doing one cell’s neighbors at a time.
- Lo-Res grid, 40 by 48, about 56 times fewer cells: near real time with fat blocks. Both ship.
- Gliders hold shape and wrap the torus. In simulation one crossing a packed-byte boundary lands one over and one down every four generations. The R-pentomino on channel 3 settles into beehives, blocks, blinkers and beacons.
The Lo-Res version, since it is the one you can actually watch:




What broke or surprised me
Yosys will not infer a DP16KD with two write ports, so the kernel BRAM has one shared write port and a count-last rule. The Arlet core needed a registered synchronous data-in and a gate on SDRAM ready. A cooperative task has to end with JMP DONE and not RTS. Re-arming the count register is a level, so wait for DONE before re-arming or the kernel hangs.
The Gosper glider gun I seeded on channel 0 collapsed. On a torus the gliders come all the way round and hit the gun. Guns need an open field.
Lo-Res and Double Hi-Res are different memory formats. DHGR packs 7 pixels per byte so the render was a copy; Lo-Res is two stacked 4-bit color blocks per byte, so the renderer expands each cell bit into a $0 or $F nibble. And the double buffer that kept DHGR clean tears at Lo-Res speed, because the core laps the host inside one frame. The fix is a generation-stamped snapshot: copy the grid, check the generation counter did not change, retry if it did. Cloning the engine also broke on the row stride, which was a hand-written MUL80 and had to become MUL5 for 5-byte rows.
Next
Other things that run on the card without the host asking. A live database the IIe queries, or a game where the day passes and crops grow whether or not you are looking. That became the farm.