2.20 hang settled in the emulator: Case B (Z-80 stack overflow on $E5 spam)
The v2.20 hang byte-trace devlog settled the dispatch path through $DFBE → CALL $DAC5 and identified two static-evidence-consistent failure modes:
- Case A: Cold-boot generator populates
$DAC5+with code that calls$Cn07on the Videx; 6502 enters Videx ROM without the V-flag preamble; hangs inBASOUTwaiting for an uninitialized status bit; Z-80 polls forever. - Case B: Generator leaves
$DAC5+and$DFBE+as$E5filler; Z-80 executesPUSH HLrepeatedly; stack overflows; system loses coherence.
Without runtime, both stayed plausible. The emulator settles it.
Setup
After the slot-scanner detects the Videx as device 4 (Pascal 1.0; 2.20 doesn’t check $Cn0B), I loaded bios_220.bin (2 KB, the disk-staged BIOS image) at Apple $DA00 (the address 2.20’s planted Z-80 reset vector points to with JP $DA00).
The bytes at the supposed dispatch handler $DFBE:
$DFBE: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5
Pure $E5 fill — the runtime-generator zone marker for 2.20. Same for $DAFF onward (the dispatch table) and most of the supposed BIOS-handler region.
$DAFF: B2 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5
$DB0F: E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5 E5
... (fill continues)
So statically, on disk, $DFBE is not a real handler. The cold-boot generator is supposed to populate it at runtime with device-specific code. For a Videx (device 4 in 2.20), that population doesn’t happen — because 2.20’s generator doesn’t have a Pascal-1.1 path, and the device-4 generator path either targets a different region or generates incorrect code for this device.
Running it
I jumped the Z-80 directly to $DFBE (simulating what CONOUT dispatch via the device-4 path would do), with SP=$0080 (per the BIOS init convention) and HL=$1234 as a marker. Tracing the SP register over 500 instructions:
First 10 SPs: $0080, $007E, $007C, $007A, $0078, $0076, $0074, $0072, $0070, $006E
Last 10 SPs: $FEE4, $FEE2, $FEE0, $FEDE, $FEDC, $FEDA, $FED8, $FED6, $FED4, $FED2
Two PUSH HLs underflow the available stack ($0080-$0000 = 64 PUSHes). After that SP wraps to $FFFE and PUSH HL keeps going. The BIOS region ($FA00-$FFFF) gets overwritten:
$FFE0: 34 12 34 12 34 12 34 12 34 12 34 12 34 12 34 12
$FFF0: 34 12 34 12 34 12 34 12 34 12 34 12 34 12 34 12
Pure $1234 fill — the marker HL value, written 2 bytes per PUSH, 16 PUSHes per row.
What this means
Case B confirmed. The 2.20 hang isn’t the 6502 stuck inside the Videx ROM (Case A); it’s the Z-80 stack-overflowing through $E5-filled handler space. After ~32K iterations the entire 64K Z-80 address space gets corrupted with whatever HL contained on entry. The 6502, still alive on its own and running its warm-boot loop at $03C0, eventually has its state overwritten too once SP wraps low enough.
This happens because:
- The 2.20 slot scanner (without the Pascal-1.1 check) tags the Videx as device 4 (Pascal 1.0 generic).
- 2.20’s BIOS dispatch table at
$DAFFmaps device 4 to the handler at$DFBE. $DFBEis in the runtime-generator zone —$E5fill on disk.- The cold-boot generator that would populate
$DFBEwith real handler code doesn’t have a working device-4-with-Videx path. (For device 3 or no card, the generator either fills it correctly or leaves it deliberately$E5-filled because the BIOS isn’t expected to dispatch via that handler.) - CP/M boots, displays the cold-boot banner, then calls CONOUT for the first character of the
A>prompt. CONOUT goes through$DFBE. PUSH HL spam.
2.23 fixes this by detecting the Videx as device 6 (Pascal 1.1) and routing through $FDB0 — which the Stage-3 cold-boot generator devlog showed is dispatched correctly. ($FDB0 itself is a one-byte RET stub on disk; the real Pascal-1.1 driver gets installed via a separate runtime mechanism.)
What this closes
The investigation’s central question — why does CP/M 2.20 hang with a Videx in slot 3 — is now end-to-end settled in the emulator:
| Path | 2.20 | 2.23 |
|---|---|---|
| Slot scanner detects Videx | ✓ device 4 | ✓ device 6 |
| Cold-boot generator routes | ✗ device 4 → $DFBE | ✓ device 6 → $FDB0 |
| Handler bytes at target | $E5 fill | One-byte RET stub (placeholder for runtime install) |
| Behavior on first CONOUT | PUSH HL spam, stack overflow | RETs cleanly |
| Outcome | hangs | works |
The Case A vs Case B distinction is now unambiguously Case B in the emulator.
Status
The 2.20 hang failure mode is settled: Case B, Z-80 stack overflow on $E5 PUSH-HL spam at $DFBE. Reproduced byte-for-byte in the Stage-3 emulator. Closes the byte-trace devlog’s open Case A vs B question.