The cold-boot generator runs and dispatches CALL $FDB0 for the Videx

5 min read
6502z80cpmsoftcardvidexapple-iiemulationreverse-engineeringretrocomputingcpm-videx-series

Stage-3 detection devlog confirmed the slot scanner correctly identifies the Videx — as device 6 (Pascal 1.1) in 2.23, as device 4 in 2.20. The next question: when the Z-80 cold-boot generator processes that slot info, does it actually take the new code path? Today’s experiment: yes, and the runtime trace shows exactly which Z-80 handler it dispatches to.

What was missing

Up through Stage 2 the Z-80 emulator halted on any DD- or FD-prefixed opcode. The cold-boot generator at $FB3A (in bios_223.bin) and at $1A82 (the runtime location after PREP_HANDOFF #2) doesn’t itself use IX/IY — but the per-device init routines it calls do. Without DD/FD support I couldn’t run past the generator’s first CALL.

So I added a generic IX/IY dispatch table to nibbler/z80_cpu.py. The implementation is shared between DD and FD via a factory function: same opcodes, just substituting ix for iy as the index register. 40 opcodes per prefix (LD reg,(IX+d) / LD (IX+d),reg variants for B/C/D/E/H/L/A, LD IX,nn / LD (nn),IX / LD IX,(nn), INC IX / DEC IX, ADD IX,rr, INC/DEC (IX+d), ALU ops on (IX+d), PUSH/POP IX, JP (IX), LD SP,IX, EX (SP),IX, plus the DDCB bit-op subdispatcher).

That covers the BIOS init code’s full IX/IY usage based on a static survey of bios_223.bin.

Two setup steps no one writes

With DD/FD in place I tried again — starting Z-80 at $1A82 (the generator’s actual runtime address, found by a memory search for the LD HL,$F3B8 signature). It immediately reads from $F3B8 looking for slot info. That region is empty in our emulator after the 6502 boot. The generator iterates 7 times, finds nothing, returns.

So I checked: does any 6502 instruction in the loader write to $F3xx? Grep across the entire 3 KB loader_223.bin binary: zero STA $F3xx instructions. Zero LDA $03B8 / STA $F3B8 copy patterns. Whatever populates $F3B8 on real hardware doesn’t show up in the 6502 boot.

Same question for the BIOS image at $FAB8-$FFFF (the 1352 bytes that become bios_223.bin on disk). Static analysis says the cold-boot generator’s CALL $FE81 / CALL $FD83 / CALL $FDB0 targets land in this region. Our 6502 boot never writes there either. Yet the bytes must come from somewhere.

Both are runtime-only mechanisms that the static analysis didn’t fully trace and Stage 1’s 6502-only emulator confirmed weren’t 6502-driven.

Manual workaround + the actual finding

I forced both setups manually — copied $03B8-$03BF (the 6502-populated slot info) to $F3B8-$F3BF, and loaded bios_223.bin at $FAB8. Then started the Z-80 at $1A82 and traced.

The cold-boot generator runs cleanly. Iteration sequence (DE counts down 7→1):

DE=7 (slot 7 = $F3BF):  device $00 -- skip
DE=6 (slot 6 = $F3BE):  device $00 -- skip
DE=5 (slot 5 = $F3BD):  device $00 -- skip
DE=4 (slot 4 = $F3BC):  device $00 -- skip
DE=3 (slot 3 = $F3BB):  device $06 (Videx, Pascal 1.1)
  -> A=6, SUB 3 = 3, JR NZ taken
  -> DEC A = 2, JR NZ taken
  -> CP $02, Z set, JR NZ not taken
  -> LD HL,$0DD0
  -> CALL $FDB0   <-- THE 2.23 PASCAL 1.1 DISPATCH PATH
  -> returns immediately ($FDB0 is a one-byte RET stub on disk)
DE=2 (slot 2 = $F3BA):  device $00 -- skip
DE=1 (slot 1 = $F3B9):  device $02 -- not 3, 4, or 6, skip

The generator successfully reaches $FDB0 — the new Pascal-1.1 device-6 dispatch path that 2.20 doesn’t have. This is the runtime confirmation of the entire investigation’s central architectural claim. The 11-byte slot-scanner branch in 2.23 (the static delta from Part 1) routes through the cold-boot generator’s device-6 case (the static delta from Part 6), reaching $FDB0 end-to-end.

What $FDB0 is

The static disasm comment on $FDB0 in gen_223_bios.py is straightforward:

NOTE: as it appears on disk, $FDB0 is ONE BYTE: a “RET” stub. The actual driver-install path lives elsewhere.

That’s exactly what the runtime trace shows — CALL $FDB0 returns immediately. So the real Videx driver install is somewhere else, populated at runtime via a mechanism the cold-boot generator alone doesn’t cover.

Open

Two interlocking mechanisms remain unexplained even after this success:

  1. How $F3B8-$F3BF gets the slot info (we put it there manually). Real hardware must do this; neither 6502 boot nor the cold-boot generator we just traced does it.
  2. Where bios_223.bin’s 1352 bytes actually load to $FAB8 from. The disk image contains these bytes (find_223_layout.py finds the BIOS jump-table signature at file offset $2400 in DOS3.3-interleaved order), but the loader code never does the read.

Likely answer for both: there’s a separate boot path that does these copies via the cooperative-CPU disk model, after the SoftCard CPU switch but before the cold-boot generator runs. That requires a bidirectional CPU switch model and possibly LC-RAM bank selection, both of which Stage 4 will need to add.

Status

Cold-boot generator works in the emulator. The 2.23 Pascal-1.1 dispatch to $FDB0 is runtime-confirmed end-to-end. Two setup mechanisms still untracked: slot-info copy to $F3B8 and BIOS-image load to $FAB8. Both fit into Stage 4’s bidirectional CPU switch + LC-RAM-bank model.