Load anything over serial, and 1974 finds five CPU bugs the self-test missed
What I tried
L streams Intel HEX from the UART into RAM, with checksum, resync at the next colon, a ‘.’ or ‘?’ per record and an OK or ERR verdict. G runs an address through a JMP trampoline at 0x3F80, since the 8008 has no indirect jump. Monitor scratch moved to the 0x3F00 page so a payload owns 0x2000 to 0x3EFF. RST 1 through 7 ROM vectors forward to 8-byte RAM slots at 0x3FC0 plus n times 8, and an uninstalled slot reads 0x00, which is HLT. Senders have to pace: the USART holds one RX byte and the echo path costs about 4 ms per character.
Then five programs from Mike Willegal’s SCELBI apps page. The recipe for each: relocate into 0x2000 to 0x3EFF, swap the bit-banged serial for OUT 9 and IN 1, exit with jmp 0 instead of HLT. Everything else stays the author’s bytes, and each change is in the commit.
Mandelbrot. org 2040h so the page-zero data offsets stay valid on page 0x20. That’s the whole change.

Pi. Page-zero variables moved out of what is now ROM, and its RST 7 handler reaches the output routine through a forwarded RAM vector slot. The port also surfaced a bug in the original: the 2013-era bit-bang cout ends its delay loop with B at 0, and prbcd keeps its BCD digit pair in B across that call, so every second digit prints as ‘0’ on any hardware wired that way. My USART version paces on A and leaves B alone.

HEXPAWN (1973). Learns by modifying its own move lists, which is why it has to run from RAM. Page literals bumped by 0x20. These programs compare against MSB-set ASCII, and the monitor’s ready byte is already MSB plus character, so input needed nothing. It beat me repeatedly and got better at it.

Calc (1974). SCELBI’s 23-bit floating point package with self-modifying result dispatch. Moving its work area from page 0 to 0x2000 found a bug in the original FININP: one site sets the destination page with xra a then mov d,a, hardcoding page zero instead of loading it from H. The converted mantissa was being written into monitor ROM, the write died silently, and every result printed +0.000000E+00. The fix had to be mov d,h, same byte count, because the self-modifying code around it depends on intra-page addresses staying put.

Shooting Stars (Byte, May 1976). Talks through RST 6 and RST 7. A small boot shim installs forwarding stubs in the RAM vector slots and jumps to the unmodified game. It wants DEL (0x7F) for rubout, and echoes a backslash per deleted character, because there’s no cursor addressing in 1976.

What broke
Five things in the CPU, each found by one of those programs, each fixed in one module and re-proven on the board.
- INR and DCR must preserve carry. The FP package does multi-byte arithmetic with increment-driven loops between carry-chained adds, and my ALU clobbered carry on every increment.
- Rotates write carry only. Z, S and P must be preserved.
- A real WAIT state. READY parks the CPU between T2 and T3. My first attempt was a PC freeze on the side, and it double-fetched.
- Interrupts at instruction boundaries only. Figure 2 of the User’s Manual reaches the INTERRUPTED? decision only when execution is complete. Mine could hijack a multi-cycle instruction between its own machine cycles. An interrupt storm of RST 7s into a spinning taken-jump loop crashed on silicon until this, with the post-handler resume landing one byte past the jump target.
- The PC lives in the address stack. The real chip has no separate program counter. The PC is whichever of the eight 14-bit stack registers SP points at. CALL is SP moving on and the old slot keeps the return address, with no copy. RET is SP moving back. I rebuilt it that way and converted fetch to post-increment. The
pc_was_loadedflag and the pre-computed return addresses deleted themselves. The stack wrap now emerges from the structure. One consequence: the bootstrap RST-0 jam consumes one level for good, so programs get six safe nesting levels, same as real silicon booted the same way.
What else went in
The self-test grew to 46 with rotate flag preservation and INR/DCR carry cases. A parameterized 8 KB RAM behind an address decoder. The USART’s RX read became an atomic snapshot-and-pop after a race. Front-panel switches: sw(6) holds READY low to freeze the CPU in WAIT, sw(5) fires an interrupt with sw(7) picking RST 5 or RST 7, both with hltwake and intstorm test programs. A dead-board regression from a vector-mux race and inverted switch polarity got fixed the same day.
Next
Cycle-exact T-states, then SCELBAL.