The runtime-code marker: FF FF 00 00 / F7 F7 00 00

5 min read
z80cpmsoftcardreverse-engineeringretrocomputingcpm-videx-series

Detail for Part 5 — The BIOS That Half-Exists.

Update (2026-04-28): “Cold-boot routine at $FB70” is wrong — $FB70 is the LIST jump-table entry, and the actual BOOT vector points to $FED1. The marker-pattern observation in this devlog is still valid: the FF FF 00 00 / F7 F7 00 00 pattern marks runtime-generated regions, and populated code does call into those regions. What needs correcting is the implied geometry — pages aren’t split cleanly into “first KB code, second KB markers”; the BIOS uses a 256-byte interleave, and 2.20 uses $E5 instead of the FF/F7 pattern. See the BIOS jump-table correction.

Following up on the cold-boot location finding: the cold-boot routine at $FB70 only sets up architectural fixed points (stack, BDOS vector, reset vector) — it doesn’t visibly contain the per-device code generation. The actual generation must happen elsewhere.

Disassembling more of the populated 1 KB at $FD60-$FDB0 reveals real Z-80 code that calls into the supposedly-empty regions:

$FD60-$FD82: an address-manipulation routine (writes to (HL))
$FD83: real entry point with multiple jumps:
  $FD84: JP M,$FBD0           ; jumps to "data" area
  $FD88: CALL $FBC4           ; calls "data" area
  $FD92: JP P,$FBE2           ; jumps to "data" area
  $FDA9: JP $FCA4              ; jumps elsewhere into BIOS

$FDB1: ED 43 ... LD (nn),BC   ; (an ED-prefix instruction my
                              ; disassembler doesn't decode)

The targets $FBC4, $FBD0, $FBE2 are all in the byte range I had previously classified as “data” — the FF FF 00 00 / F7 F7 00 00 pattern visible in static disassembly. But the populated BIOS code is CALLing those addresses. So those bytes must be valid Z-80 code at runtime — they’re runtime-generated, not data.

The FF FF 00 00 / F7 F7 00 00 pattern appears in several places:

  • $FA00-$FAB7 (cold-boot area, before the jump table)
  • $FBB9-$FBFF (between cold-boot routine end and the next populated code)
  • $FE81-$FEB7 (just before the second-half boundary)
  • $FEB8-$FFFF (the second half, all-zero on disk; contains the F7 F7 00 00 pattern at runtime)

It’s a consistent runtime-code marker. The cold-boot generator allocates slots in these regions, marks them with the pattern, then writes real per-device code over the markers based on the slot scanner’s device-code table. As Z-80 instructions, FF FF 00 00 decodes as RST $38; RST $38; NOP; NOP and F7 F7 00 00 as RST $30; RST $30; NOP; NOP — both are RST instructions that, if executed before the generator runs, would cause traps. Likely an intentional safety measure: any premature execution lands in a defined trap rather than an undefined garbage path.

The implication for our extraction strategy: the BIOS first 1 KB at $FAB8-$FEB7 isn’t pure code. It’s interleaved — populated code (jump table, dispatch table, CONOUT, control-char dispatch, cold-boot routine, address manipulation routines) interleaved with code-generation slots awaiting runtime population. The slots at $FBB9-$FBFF are within the populated 1 KB; the code at addresses just before and after them IS real, but the byte ranges $FBB9-$FBFF themselves are placeholder markers.

So the static structure of the BIOS is more like:

$FAB8-$FAE4  jump table              (static)
$FAE5-$FAEA  inline LISTST/SECTRAN    (static)
$FAEB-$FB2A  per-device dispatch     (mixed: data + slots within entry padding)
$FB2B-$FB39  control-char data table (static)
$FB3A-$FB68  control-char dispatch   (static)
$FB69-$FB6F  small helper            (static)
$FB70-$FBB6  cold-boot routine       (static)
$FBB9-$FBFF  RUNTIME GENERATED       (marker: FF FF 00 00 / F7 F7 00 00)
$FC00-$FE6B  more populated code     (static, partly disassembled)
$FE6C-$FE9F  HOME / SETTRK / SELDSK  (static)
$FEA0-$FEB7  RUNTIME GENERATED       (marker)
$FEB8-$FFFF  RUNTIME GENERATED       (per-device handlers + state)

Approximately 2/3 of the 2 KB BIOS region is statically loaded; 1/3 is runtime-generated. The static portion calls into the runtime-generated portion at multiple points. So the cold-boot generator MUST run before any BIOS routine is invoked — including before the very first instruction the Z-80 fetches, if that’s at $FA00.

This raises a sharper version of the earlier puzzle: the Z-80 reset vector points to $FA00 (in the runtime-generated cold-boot area). Either (a) the reset vector points somewhere ELSE in practice, (b) the SoftCard hardware injects an initial value into $FA00-$FAB7 before flipping to Z-80, or (c) the very first call to a BIOS routine triggers some “first-call trap” that runs the generator. Without a Z-80 emulator to actually boot the system, this is bounded but not yet resolved.

Status: the runtime-code marker pattern is identified and consistent. The cold-boot generator must populate slots interleaved throughout the BIOS region, not just the bookend halves. The exact mechanism by which generation runs before the first BIOS call (given the Z-80 reset vector lands in the unpopulated $FA00 area) remains the next concrete piece. Adding CB/DD/ED/FD prefix decoding to z80disasm would also help — the ED 43 at $FDB1 already shows we’re missing real instructions.