Stage 1 emulator: 6502 boot finishes without ever writing $FA00
The static analysis from Parts 1-10 reached a wall at the cold-boot generator at Apple $FA00. The disassembled boot loader has three documented PAGE_COPY calls ($0A00-$0FFF → $BA00-$BFFF, $9700-$9CFF → $0A00-$0FFF, $8000-$96FF → $A300-$B9FF) and one LOAD_CPM call that reads 29 sectors into $8000-$9CFF. None of those targets $FA00. The user clarified that the SoftCard has no on-board ROM, so $FA00 cannot be ROM-mapped either. So how does the Z-80 see executable code at $FA00 when its planted reset vector says JP $FA00?
This devlog runs the 6502 boot in an emulator and watches every memory write. The result settles half the question: 6502 doesn’t do it.
What the emulator looks like
cpm-investigation/emu_softcard.py ties together three pieces:
nibbler.cpu.CPU6502— pre-existing 6502 emulator with Apple soft-switch handling (Disk II controller, keyboard, LC RAM stubs).nibbler.dsk_disk.DSKDisk— new this session. Mirrors theWOZDiskAPI (read_nibble, step_phase, motor_on, current_qtrack) but synthesizes its nibble stream from a.dsk/.posector-order image. Each track gets a synthetic GCR layout:40×$FFsync, address prologD5 AA 96, 4-and-4 vol/trk/sec/cks, address epilogDE AA EB, sync gap, data prologD5 AA AD, 343 6-and-2 nibbles, data epilog. Validated by reading back known sectors via the standard ROM decoder.- A P6 PROM hook at
$Cn5Cthat synthesizes sector-loads — the boot stub’sJMP ($003E)lands there, and rather than emulate ROM execution, the hook reads the requested sector from the .dsk file directly and increments$27to mimic the PROM’s tail behavior.
I also extracted the real Disk II P6 PROM bytes from docs/DiskII_BootROM.asm and dropped them into the slot-6 ROM page ($C600-$C6FF) so the slot scanner’s CKSUM_SLOT at $114E can actually see something stable.
What works
The boot stub’s 11 sector-load iterations all complete (120 instructions), filling $0A00-$13FF. Stage-2 enters at $1000, runs the install loops, hits the slot scanner at $1060. PREP_HANDOFF copies all execute. LOAD_CPM reads its 29 sectors. After ~20M instructions the 6502 ends up looping in the warm-boot routine at $03C0-$03D2 (where JSR $0E36 would trigger the SoftCard CPU switch on real hardware).
End-of-boot snapshot:
| Region | Non-zero bytes | Expected source |
|---|---|---|
$0A00-$0FFF (1536 B) | 1443 | PREP_HANDOFF #2 from staging $9700-$9CFF |
$A300-$B9FF (5888 B) | 5596 | PREP_HANDOFF #3 from staging $8000-$96FF |
$BA00-$BFFF (1536 B) | 1483 | PREP_HANDOFF #1 from $0A00-$0FFF |
$1000-$1002 | C3 00 FA | Z-80 reset vector (JP $FA00) |
$FFF9-$FFFF (6 B) | 6 (from log) | Apple monitor reset-vector patch |
$FA00-$FFF8 | 0 (zero) | nothing wrote here |
What doesn’t work, and what it tells us
Two things didn’t work, and both are informative:
1. The slot scanner has an apparent dead-code path. SCAN_INIT_SLOT at $1086 is the only place that increments $3E (the iteration counter the post-scan check LDA $3E; CMP #$01; BEQ DISPATCH_OK reads). The path to $1086 is BEQ $1086 at $106C, which fires when $3E = 0. But $3E is initialized to $FF at $105E (via DEY from 0 then STY $3E), and no other instruction in the loader writes $3E. So $3E never reaches 0, SCAN_INIT_SLOT never runs, $3E stays at $FF, and the post-scan check always falls into the multi-card error path. On real hardware this works, so something must clear $3E — maybe a SoftCard hardware side effect on a specific memory access. For now I patch $3E = 0 externally at $1060 and document the open question.
2. No 6502 write to $FA00-$FFFF. The write logger ran the entire boot and recorded exactly 6 writes to that region: the Apple monitor reset-vector patch at $FFF9-$FFFF. Zero writes anywhere in $FA00-$FFF8. So the BIOS proper at $FA00-$FFB7 is never written by the 6502 during the boot.
Where this leaves the BIOS-population question
The factual settlement: in 2.23, the 6502 doesn’t put the BIOS at $FA00. So either:
- (a) The Z-80 starts somewhere other than
$FA00, runs setup code that generates the BIOS at$FA00, then jumps to it. The “JP $FA00” at the planted reset vector might be for warm-boots after the cold-boot generator finishes. - (b) The CPU switch hardware doesn’t reset the Z-80 PC to
$0000. It might pick up at the Apple-side address the 6502 was attempting to execute ($0E36), which the Z-80 reads as$1E36. The Z-80 callbacks installed at$0A00-$0FFFmap to Z-80$1A00-$1FFF, so$1E36is in the BIOS first 1 KB region — and that’s where the dispatch / cold-boot-generator code lives.
Option (b) is consistent with the resume-prompt’s note that the polling loop is at “Z-80 $1E39” and with $1E36 being the Z-80 view of Apple $0E36. Stage 2 will add a Z-80 emulator and a SoftCard CPU-switch model, then verify by tracing the actual transition.
Files
nibbler/dsk_disk.py— synthetic GCR streams from sector-order imagescpm-investigation/emu_softcard.py— 6502 boot harness with P6 PROM hook + write-loggercpm-investigation/extract_p6_rom.py— pulls real Disk II PROM bytes from the disasm doc
Status
Stage-1 emulator boots reliably to handoff. $FA00-$FFFF stays at zero through the entire 6502 phase. The BIOS proper must be Z-80-generated post-handoff. Stage 2 (Z-80 emulator + SoftCard switch) is next.