Historical machine · Intel 8088

IBM PC/XT: native firmware startup

Open the XT laboratory →

Executable source ↗

This executable chapter assembles the selected IBM 5160 board around the 8088. The original 11/08/82 BIOS can start ROM BASIC or boot MS-DOS 3.30 from drive A. During POST, the CPU tests itself, DMA and the first 16 KiB of RAM. It discovers 640 KiB, initializes its interrupt vectors, and resets the XT keyboard. It then programs CGA, tests video RAM and retrace status, and clears the display. The BIOS tests the remaining RAM, resets and seeks the diskette drive, resets the keyboard a second time, and attempts a disk boot. With no disk inserted, the BIOS's own timeout leads to ROM BASIC C1.10. BASIC prints its prompt and accepts immediate commands and stored programs through the keyboard receiver and IRQ1. No BIOS service is replaced by host code and no BIOS data area is preloaded. The session checks cover editing, Ctrl-Break, warm boot, and saved input. With the DOS disk inserted before power-on, the BIOS loads the boot sector through DMA and starts the command interpreter. The browser laboratory runs this machine with locally supplied ROMs.

Physical storage and firmware

The board has 640 KiB of conventional RAM, separate 16 KiB CGA backing storage, and externally supplied BIOS, BASIC, and character ROM images. The CGA storage aliases at B8000 and BC000; the character ROM connects only to the display and is not visible on the CPU bus. Missing memory reads as FF, and writes to missing memory or ROM are ignored. This fixed unanswered-bus value is a model convention, not an electrical floating-bus model.

The original XT's decoder repeats the first 8 KiB of BASIC at F0000, F2000, and F4000; BASIC's full image occupies F6000–FDFFF, and BIOS occupies FE000–FFFFF. Windows below express those aliases without copying ROM bytes. IBM's Technical Reference and the ROM layout notes describe the selected board and sockets. The target audit records the pinned reference emulator and later acceptance milestones.

components {
    ram = ram A0000
    video = ram 4000
    cga = ibm-cga
    font = rom 2000
    bios = rom 2000
    basic = rom 8000
    io = xt-io
}

map 100000 {
    00000 = ram
    B8000 = video
    BC000 = video
    F0000 = window 2000 {
        read = basic
        write = discard
    }
    F2000 = window 2000 {
        read = basic
        write = discard
    }
    F4000 = window 2000 {
        read = basic
        write = discard
    }
    F6000 = basic
    FE000 = bios
    unmapped = FF
}

image bios external sha256 2798f8a9a7dcab651ede40373d35269ad573b75acf811115c263e034ef80a1f2
image basic external sha256 3033d1a54c99d7e2aa1fc7c8c2e51a56ae1b61bf4e70e8aa580f43c43e37a63e
image font external sha256 37527f661580e5a09710051cec67422abfadf31c61b841537a91e7f27be19304

The generated create8088PcXt({ bios, basic, font }) accepts independently verified RomImage bindings or explicit null for absent firmware. Installation does not reset or execute the machine. Snapshots store ROM identities, not ROM bytes; restoration requires the same bindings. The optional ROM-backed test uses XT_BIOS_ROM and XT_BASIC_ROM; XT_CGA_ROM additionally checks the rendered firmware test bar. Firmware stays external to the repository. The selected 8 KiB IBM MDA/CGA character ROM uses its default double-dot CGA bank at offset 1800; the font is unnecessary for CPU execution but required for visible text rendering. Missing bindings are not replaced with a host font.

Power-on and reset

Construction allocates zeroed RAM and deterministic inactive CPU/device state. Call reset() to reset CPU and board, then stepTimedXtCpu() to execute from FFFF:0000 (physical FFFF0). The BIOS far jump enters F000:E05B. RAM and installed ROMs survive reset. cpu.reset() alone preserves the board; fresh construction supplies fresh RAM and device state. Hardware leaves some register/RAM power-on values unspecified; this model chooses zero explicitly.

cpu 8088 {
    AX = 0000  BX = 0000  CX = 0000  DX = 0000
    SP = 0000  BP = 0000  SI = 0000  DI = 0000
    CS = 0000  DS = 0000  SS = 0000  ES = 0000  IP = 0000
    halted = false waiting = false interruptDeferred = false recognitionDeferred = false trapPending = false
    flags { CF = 0  PF = 0  AF = 0  ZF = 0  SF = 0  TF = 0  IF = 0  DF = 0  OF = 0 }
}
reset { cpu io cga }

Board ports, checks, and interrupts

The port map connects DMA, PIC, PIT, PPI, CGA, the diskette adapter, and the write-only NMI-enable latch. Unpopulated ports return FF and ignore output, including the absent expansion box at 0213. CGA's index/data aliases are declared; extra system-board port aliases are not yet decoded. The selected switch byte is 2D: 256 KiB planar RAM, no 8087, 80-column colour, one drive, and no manufacturing loop.

ports {
    in 00 = io 00
    out 00 = io 00
    in 01 = io 01
    out 01 = io 01
    in 02 = io 02
    out 02 = io 02
    in 03 = io 03
    out 03 = io 03
    in 04 = io 04
    out 04 = io 04
    in 05 = io 05
    out 05 = io 05
    in 06 = io 06
    out 06 = io 06
    in 07 = io 07
    out 07 = io 07
    in 08 = io 08
    out 08 = io 08
    out 09 = io 09
    out 0A = io 0A
    out 0B = io 0B
    out 0C = io 0C
    in 0D = io 0D
    out 0D = io 0D
    out 0E = io 0E
    out 0F = io 0F
    in 20 = io 20
    out 20 = io 20
    in 21 = io 21
    out 21 = io 21
    in 40 = io 40
    out 40 = io 40
    in 41 = io 41
    out 41 = io 41
    in 42 = io 42
    out 42 = io 42
    out 43 = io 43
    in 60 = io 60
    out 60 = io 60
    in 61 = io 61
    out 61 = io 61
    in 62 = io 62
    out 62 = io 62
    out 63 = io 63
    out 81 = io 81
    out A0 = io A0
    out 3D0 = cga 0
    in 3D1 = cga 1
    out 3D1 = cga 1
    out 3D2 = cga 2
    in 3D3 = cga 3
    out 3D3 = cga 3
    out 3D4 = cga 4
    in 3D5 = cga 5
    out 3D5 = cga 5
    out 3D6 = cga 6
    in 3D7 = cga 7
    out 3D7 = cga 7
    out 3D8 = cga 8
    out 3D9 = cga 9
    in 3DA = cga A
    out 3F2 = io F2
    in 3F4 = io F4
    in 3F5 = io F5
    out 3F5 = io F5
    unmapped = FF
}

The system-control specification owns NMI enable and parity/channel-check latch behavior. PPI PB4/PB5 clear and disable the corresponding checks; PC7/PC6 report them. Normal RAM has no parity storage, so inputs remain inactive unless diagnostics explicitly call io.setCheckErrors(parity, channel). A pending NMI is offered before INTR and consumed after successful CPU entry; a held error does not retrigger it. CPU recognition delays remain CPU decisions. Port A0 writes use only bit 7 and have no guest readback.

The timer and refresh chapters own oscillator, device ordering, and DMA grants. The same execution helper services this board and the small examples. It grants one pending DMA transfer, offers interrupts, executes one CPU boundary, and advances devices using declared instruction clocks. Repeated strings use the CPU chapter's explicitly bounded one-element re-entry timing; this does not reproduce an uninterrupted hardware REP's throughput. Timing covers POST, empty-drive bootstrap, the BASIC sessions below, and the selected BIOS services. Unsigned multiplication and division use the CPU chapter's documented published-range ceilings, including memory and word-transfer costs; operand-dependent arithmetic microcode remains unmodeled.

When does the 8088 see INTR?

The PIC can have a timer request pending while its mask keeps INT low. Suppose the CPU unmasks that request with OUT 21h,AL and the next instruction reads the mask. Does the read happen before the timer handler can change it?

Intel describes INTR as sampled in the clock preceding the end of the current instruction (8086 Family User's Manual, figure 4-19 and accompanying text). Our board approximates this ordering at instruction boundaries: a PIC output that goes high because of the current CPU bus access misses that instruction's sample. The following instruction can run, then the CPU can accept INTR. The PIC pin itself changes immediately; the board saves the missed sample separately. An IRQ already asserted before the instruction, or one raised by a later timer tick or host key between steps, follows the normal interrupt-offer path. This is a board timing convention, not a new interrupt shadow on every OUT; it does not simulate propagation delays or individual bus clocks.

The distinction matters on warm boot: timer 0 is still running when POST tests the PIC mask (F000:E331). If IRQ0 is accepted before the following IN, the diagnostic handler masks it and the BIOS reads back the handler's mask instead of the one it wrote. The board timing test checks the same ordering with an independent IRQ and a save immediately after OUT.

Empty diskette drive

The diskette chapter owns the adapter latch, µPD765 command/result phases, one-drive scope, mechanical head position, and explicit timing limits. Board ports 3F2, 3F4, and 3F5 route to local XtIo offsets F2, F4, and F5. The controller advances from the same elapsed microseconds as the keyboard, including refresh grants and halted CPU intervals. IRQ6 is a settled device output, alongside timer IRQ0 and keyboard IRQ1; callers cannot independently drive those three lines. PIC acknowledgement/EOI does not acknowledge the diskette controller.

The drive starts empty. Reset, recalibration, and seeks can complete without media. Reads wait for index/data pulses. The original BIOS times out while the controller waits and the channel-2 address and count remain unchanged.

Snapshots retain partial commands/results, reset and seek timing, physical head position, and the controller's cylinder estimate. CPU reset preserves them. Board reset cancels in-flight work and retains the disk, head position, and Specify timing registers.

The board tests check port routing, fractional clock continuation, native IRQ6 entry from HLT, separate controller/PIC acknowledgement, and the empty drive’s absence of DMA or memory side effects. The original-ROM boot tests start at reset and require the exact reset/Specify/recalibrate/seek sequence, four IRQ6 entries, seeks to cylinders 1 and 34, and F000:E597 without entering the 601 diskette-error path at F000:E58A.

After that native setup, small RAM callers invoke original INT 13h. Reset must return success and reprogram Specify. A one-sector read must enter the no-index wait, return carry set and AH=80 through the BIOS timeout, and leave the sector buffer and channel-2 address/count unchanged. A snapshot taken inside that wait must resume to identical complete state. These callers change their own code and entry registers; they never patch BIOS, its vectors, or its data.

Reading a diskette through INT 13h

Insert a raw, write-protected 360 KiB image with io.setDiskette(bytes). The drive keeps its own copy. Eject it with io.setDiskette(null). The controller matches the sector ID on the selected track and side. It requests channel 2 for each byte and the 8237A arbitrates between disk and refresh.

The BIOS writes the upper four physical address bits to port 81. Each channel-2 grant combines this page with the current 16-bit DMA address, writes one byte through the board's memory map, and takes five CPU clocks (15 oscillator ticks). The 16-bit address can wrap without changing the page. On the final grant the 8237A signals terminal count. The controller raises IRQ6 and makes seven result bytes available. The PIC accepts IRQ6 separately from the BIOS reading the controller results. IBM Technical Reference

The ROM acceptance starts after the original POST. A RAM caller requests cylinder 2, side 1, sector 3 into 2000:7C00 through INT 13h. The check follows all 512 DMA writes, checks the neighboring RAM bytes, and requires carry clear and AH zero on return. In the 11/08/82 ROM, NUM_TRANS computes one sector in AL at F000:EFC4. The final call to GET_PARM at F000:EC6C looks up the motor timeout and clears AX in the process. The caller therefore sees AL zero even after a successful read. IBM BIOS listing, appendix A

Restoring after byte 256 must reproduce the remaining transfer and the final machine state. The checkpoint includes the disk bytes, DMA page, controller byte position, and delay until its next request.

A second check inserts a synthetic boot disk after POST and lets the BIOS continue unaided. Its INT 19h bootstrap calls INT 13h for sector one, jumps to 0000:7C00, and executes the first instruction from the sector. The ROM’s default diskette parameter table at F000:EFC7 sets EOT to eight. The disk image has nine sectors per track. The controller uses the EOT supplied in each command, which matters when following a DOS loader beyond the boot sector.

Writing a diskette through INT 13h

Insert a disposable data disk with io.setDiskette(bytes, { writable: true }). The drive writes to its own copy. The original bytes and earlier checkpoints keep their contents. Disks inserted without that option are write-protected.

For a one-sector write, the caller sets AH to 03, AL to one, and ES:BX to the source buffer. The original BIOS programs channel 2 with mode byte 4A (single DMA read from memory) and sends Write Data (C5) to the controller. Each DREQ2/DACK2 exchange moves one byte from RAM onto disk. Terminal count on the 512th byte completes the sector. IRQ6 wakes the BIOS to collect the seven result bytes. IBM BIOS listing, appendix A

The ROM acceptance test writes from 2000:7C00 to cylinder 2, head 1, sector 3, then uses INT 13h function 02 to read that sector back into 2000:8000. It checks every DMA address and byte, compares the whole disk against the expected image, and resumes a checkpoint after byte 256 to the same final state. With protection enabled, Write Data reports Not Writable before requesting any bytes. The BIOS returns carry set and AH=03, and the disk stays intact.

Cold-booting MS-DOS 3.30

Drive A takes Microsoft MS-DOS 3.30 Disk 1, volume MS330PP01. This write-protected 360 KiB FAT12 image has 40 cylinders, two heads and nine 512-byte sectors per track. The root directory contains the hidden system files IO.SYS and MSDOS.SYS, and the command interpreter COMMAND.COM. The media identity table gives the SHA-256 for this edition.

The cold-boot test inserts the disk before power-on and runs the original BIOS from reset. All 512 bytes transferred by DMA channel 2 must match sector one at physical 07C00. The PIC delivers IRQ6 and the BIOS jumps to 0000:7C00.

The boot sector asks the BIOS for cylinder 0, head 0, sector 6. That read places the first root directory sector at physical 00500, with the entry for IO.SYS first. The next read starts at cylinder 0, head 1, sector 4. It puts the first six sectors of IO.SYS at 00700. DOS continues loading from disk until its command interpreter takes input. Enter a date and time, or press Enter at each prompt to accept the defaults. To read a two-digit field, DOS uses the 8088's AAD instruction to combine the tens and units.

At the keyboard What appears on the CGA display
VER at the first A> prompt MS-DOS Version 3.30, followed by another A>.
DIR Volume MS330PP01, directory A:\, 34 files, 5,120 bytes free, then A>.

The test sends make and break codes through the XT keyboard. It reads completed lines from CGA memory before they scroll away and compares every listed name and size, in order, with the image's root directory. To run it, set XT_BIOS_ROM, XT_BASIC_ROM and XT_DOS_DISK to the three local files, then use npm test. With current compiled output, the focused check is node --test dist/tests/machines/8088/pc-xt/rom-dos.test.js.

Changing disks at the DOS prompt

After listing the system disk, leave DOS running and wait for the drive light to go out before exchanging disks. The original drive has no disk-change line. An immediate exchange in this session gave the new filenames but the old disk's 5,120 bytes free. The test lets the BIOS timer interrupt clear the drive A motor bit at port 3F2h before exchanging disks. DOS then reports the new disk's free space. Microsoft's account of the floppy disk cache

Insert the DROMAIOS data disk. The disk builder creates this 360 KiB FAT12 image from scratch. To make a local copy, run node scripts/make-xt-data-disk.ts dromaios-data.img from the repository root.

A>TYPE README.TXT
Dromaios XT data disk.
DOS read this file from drive A.

A>HELLO.COM
Hello from HELLO.COM on drive A.

A>

TYPE is built into the command interpreter. It opens the new disk's README.TXT and displays its two lines. HELLO.COM exercises the DOS program loader. DOS starts its 12 bytes of code at offset 0100h. The program calls INT 21h with AH=09h to print the string immediately after its code, then with AX=4C00h to exit with status zero. The MS-DOS Encyclopedia, section II

The acceptance test issues one more DIR after the program exits. The interpreter must list volume DROMAIOS, its two files and 360,448 bytes free. Both disks stay write-protected for these commands.

Saving a DOS file across a fresh boot

At the A> prompt, wait for Motor off. Choose Insert writable copy… in Drive A and select the DROMAIOS data disk. DOS can now change the copy in the drive. The file you selected keeps its original bytes. If the file picker paused the XT, choose Run. Click the screen and type these commands.

A>ECHO HELLO>SAVE.TXT
A>TYPE SAVE.TXT
HELLO

The > sends the output of ECHO to a file. DOS creates a directory entry for SAVE.TXT and allocates one 1,024-byte cluster. The file's seven bytes are HELLO followed by carriage return and line feed. Both copies of the FAT mark that cluster as the end of the file.

  1. Leave the XT running until Motor off, then choose Export disk image. This downloads a raw 360 KiB .img containing the current sectors.
  2. Pause the XT. Choose Insert… and select the MS-DOS system disk. Choose Fresh power-on, then Run. Press Enter at the date and time prompts.
  3. At A>, wait for Motor off, then use Insert writable copy… to mount the exported data disk. DIR shows three files and 359,424 bytes free. TYPE SAVE.TXT prints HELLO from the exported sectors.
  4. Type DEL SAVE.TXT, then DIR. The two original files remain and free space returns to 360,448 bytes. The download still contains SAVE.TXT.

The DOS acceptance test boots the original BIOS and MS-DOS twice. Between boots it independently checks the directory entry's date, hour and minute against the values typed at startup, reads the allocated cluster in both FATs, and verifies the seven data bytes. After deletion it checks that DOS marked the entry deleted and freed the cluster.

CGA text output

The CGA device chapter owns the 6845 register masks, scan/status signals, display-start latch, cursor, and attribute/blink rules. This board selects 40/80-column, 25-row RGBI text with eight-line characters. Graphics, composite colour, and the CRTC's internal cursor-blink modes are outside this first profile.

stepTimedXtCpu() advances CGA and the system board from the same oscillator: DMA grants advance both before CPU accesses, and instruction/interrupt clocks advance both after the CPU boundary. An idle halt advances both to the next PIT pulse. For explicit device-only advancement, call advanceXtOscillatorTicks(machine, ticks); calling io.advanceOscillatorTicks() alone advances only the system-board devices. Snapshots retain CGA's divider, scan and blink phase alongside the board clock, so inspection and restoration do not restart the display.

The pure text renderer, renderXtTextFrame(machine), returns a detached 320×200 or 640×200 array of RGBI colour indices. It reads physical video RAM and the character ROM without CPU bus accesses or elapsed time. cgaColours supplies the 16-colour monitor palette. The laboratory fits both widths to a 4:3 monitor. The renderer rejects unsupported profiles and a missing font when video is enabled, and returns black when configured video is disabled.

Original BIOS text services

The ROM's INT 10h handler is ordinary 8088 code. It converts row/column positions to video-memory offsets, polls CGA status, writes characters and attributes, and programs the CRTC cursor and display start. The BIOS data area stores the active page, page size, and each page's cursor. IBM describes the interface on printed pages A-46–A-48 of the Technical Reference; the teletype routine is on A-68–A-69.

The original-ROM text tests establish these cases through timed execution:

BIOS service (AH, hexadecimal) Acceptance
00, 0F: set/read mode Modes 0–3 initialize 40/80-column text, report the correct geometry, and retain the RGBI text profile.
01–03: cursor shape, position, and query Cursor registers and per-page BIOS positions agree; a programmed shape and hidden cursor reach the rendered pixels.
05: select active page All eight 40-column pages and four 80-column pages retain independent text/cursors; hidden-page writes survive selection. Display start takes effect at the next frame boundary.
06, 07: scroll up/down A bounded window copies rows in the correct direction, leaves its border and cursor untouched, and fills vacated cells with the supplied attribute. A zero count clears the window.
08–0A: read/write character and attribute Repeated writes preserve cursor position; character-only writes retain the existing attribute, and reads return both bytes.
0E: teletype Ordinary characters retain cell attributes; carriage return, line feed, and backspace update the active page's cursor. Backspace at column zero stays there; wrapping at the bottom right scrolls the screen.

These tests first run reset through F000:E30A, then start small RAM callers of the installed BIOS handler. The original POST supplies their IVT, BIOS data area, and display state. This is a service-level acceptance fixture, not a claim that later POST or disk boot has completed. It adds no host BIOS services or firmware patches. The same suite saves a scroll during a REP MOVSW element sequence and requires restored execution to reproduce RAM, CPU, device state, clock phase, and pixels. XT_CGA_ROM enables the original-font pixel checks.

Acceptance boundary

The machine tests check storage bounds, ROM identities/aliases, protection, unanswered ports, reset, and detached restoration. Synthetic firmware tests exercise the same map and CPU path without distributing historical ROM bytes.

With the selected external ROMs, the startup test begins at reset, checks F000:E242 before video initialization, and continues to F000:E30A after the video tests and immediately before the option-ROM scan. It requires 640 KiB in the BIOS data area, the selected equipment switches, firmware-owned interrupt vectors, successful DMA refresh, and the keyboard's native AA self-test response through IRQ1. The video continuation requires the ROM's temporary test bar, successful retrace polling and video-memory tests, the mode-3 BIOS data area and CRTC values, and cleared video RAM. With the font installed, it checks the bar's pixels without changing machine state. It rejects unsupported execution, failure halts, and unexpected diagnostics. A saved continuation must reach the same boundary with identical CPU, RAM, devices, and oscillator time. Independent rendering tests cover text pixels, attributes, cursor, wrapping, and saved blink phase. The test never patches firmware, seeds RAM, or shortcuts a BIOS routine.

ROM BASIC boot and input

The boot tests continue from reset through the remaining POST and empty-drive bootstrap. Both keyboard resets must deliver AA through IRQ1, with a nonzero timeout counter after the second reset at F000:E3B7. Neither the keyboard 301 path nor diskette 601 path may be entered. The keyboard chapter owns the response delay; it allows the original ROM to clear its interrupt flag before the response arrives.

Without any host input, the BIOS must exhaust its disk boot attempt, retain the disk timeout status 80, and enter BASIC at F600:0000. The selected ROM then displays these first four rows and waits in the original INT 16h input loop:

The IBM Personal Computer Basic
Version C1.10 Copyright IBM Corp 1981
62940 Bytes free
Ok

BASIC uses the CGA character FF as a blank in some text; screen assertions treat it as a space, retaining the original video-memory bytes. Host make/break codes type PRINT 2+2, including Shift and its release. The command must echo, print 4, and return to Ok; every supplied byte must traverse native IRQ1. The host retains rejected offers rather than overwriting an unconsumed byte.

ROM BASIC sessions

The session tests continue from that authentic prompt. The host supplies one line at a time, as XT scan codes, and waits for BASIC to consume it. The assertions read CGA character cells; they never inspect or modify BASIC's internal program representation.

At the keyboard What the ROM displays
Type PRINT 2+3. Type PRINT 77, Backspace twice, type 12, and press Enter. The first answer is 5; the edited line reads PRINT 12 and produces 12. The repeated digits and Backspaces have separate makes and breaks.
Enter the three lines below, then LIST and RUN. The listing reproduces the three lines in order. The run prints 5 and returns to Ok.
Replace line 20 with 20 A=A+4, list, and run again. Only line 20 changes; the run prints 6. NEW followed by LIST returns straight to Ok with no program lines.
Run 10 GOTO 10 and press Ctrl-Break (Ctrl + the XT's Scroll Lock/Break key). BASIC prints ^C, Break in 10, and Ok. After the releases, PRINT 2+3 still produces 5.
10 A=2
20 A=A+3
30 PRINT A

While typing RUN for the edited program, save after R has been entered, Shift is held, and U's make is pending in the receiver and PIC. The machine snapshot owns the accepted byte and the emulated hardware; the host saves the unaccepted suffix separately. A mismatched BASIC ROM must not restore that session. Resuming from serialized data with batches of 37 or 4096 boundaries must match single stepping: the same interrupt and boundary counts, output, CPU, RAM, video, devices, and oscillator phase.

Restarting a session. Ctrl-Alt-Del is firmware behavior on the XT. Its IRQ1 handler writes 1234 to 0040:0072 and jumps to F000:E05B without resetting the board clock. The test holds the chord through POST and releases it at the new BASIC prompt. POST reaches BASIC again through the empty-drive bootstrap; the banner and free space match cold boot, LIST is empty, and an immediate command works. This schedule matters: the BIOS checks the keyboard for stuck keys during POST. Break bytes offered during that check can produce a 301 diagnostic. This byte-level fixture does not model the keyboard's internal pre-reset queue or a browser's key-release policy.

By contrast, CPU-only reset returns to physical FFFF0 and clears IF, preserving the general registers, RAM, video, devices, pending key, and clock. Fresh power-on starts at the same vector with zeroed RAM/video, inactive devices, and oscillator time zero.

Using the laboratory

In Firmware, choose the 8 KiB XT BIOS dated 11/08/82, the 32 KiB IBM BASIC C1.10 ROM, and the 8 KiB IBM MDA/CGA character ROM. Each file is checked against the identity in this chapter and remembered locally. The disk in drive A determines what boots.

  • ROM BASIC. Leave Drive A empty and press Run. At Ok, click the screen, paste the three-line program with a final Enter, then type LIST and RUN.
  • DOS. In Drive A, choose Insert… and select the raw 360 KiB MS-DOS 3.30 system disk. Its filename appears in the panel. Choose Fresh power-on, then Run. Click the screen and press Enter at each date and time prompt. At A>, try VER and DIR.

The first part of POST runs with video disabled. The address and emulated seconds in Execution show the board advancing. Step advances one timed CPU boundary.

For a shorter startup, choose Quick boot in Execution. It starts fresh RAM and devices, keeps the mounted disk, and sets the BIOS warm-start word at 0040:0072 to 1234h before running from the reset vector. The original BIOS then takes its own warm POST path, skipping the long RAM pattern test. Fresh power-on and Run perform the full diagnostic.

To exchange disks at the DOS prompt, let the XT run until Drive A shows Motor off, then choose Insert… again. The motor light follows the XT's drive control register. Selecting a file of the wrong size leaves the current disk in place. Insert… mounts the file write-protected. Use Insert writable copy… and Export disk image to save files from DOS. Eject leaves the drive empty. Fresh power-on clears RAM and resets the controller while leaving the current disk in the drive.

To resume later, pause and choose Save snapshot. On a later visit, choose Restore snapshot with the same three ROMs loaded. The checkpoint restores the CPU, RAM, screen, board devices, and pending keyboard input. It also restores the saved disk's name, contents, and write permission, even if you have since ejected it or inserted another disk. A checkpoint saved with an empty drive restores it empty.

From fresh power-on, Disassembly shows EA 5B E0 00 F0 at FFFF:0000 (physical FFFF0). The far jump takes its offset from the first word and its segment from the second. Step once. Registers highlights CS changing to F000 and IP to E05B. The current disassembly row moves to physical FE05B, with the jump above it. Recent rows use the bytes the CPU fetched. The current row and the rows below it read the instruction stream from RAM or loaded ROM. Fetching across IP=FFFF wraps the offset within CS. A missing byte, unsupported encoding, or the inspector's 32-byte preview limit ends the forward listing.

Memory scrolls through physical addresses 00000–FFFFF. Enter a hexadecimal address or segment:offset in its header. FFFF:0000 and F000:FFF0 both select FFFF0, showing the same EA 5B E0 00 F0 bytes. The field normalizes the entry to its physical address. A projected address wraps at 1 MiB, so FFFF:0010 selects 00000. Each segment and offset must fit sixteen bits.

Select CS:IP, then Step from fresh power-on. Memory and Disassembly both move to FE05B. SS:SP follows the physical stack pointer. FIX, entering an address, or scrolling keeps the position fixed. The 8 / 16 buttons change bytes per row. The listing stays in physical address order across 0FFFF–10000 and ends at FFFFF. RAM is visible before firmware is loaded. Missing ROM sockets and unmapped ranges show --, and CGA's two ranges show the same backing storage. Changed bytes compare successive observations.

Each inspector has its own Live switch during Run. Pause or Step refreshes the visible panels. A frozen Memory panel retains its full captured image when scrolled, resized, or switched between eight and sixteen bytes per row. Reset, fresh power-on, and restoration clear its change comparison. Register and flag highlights compare the two snapshots from the latest CPU boundary. Inspection leaves the board clock and devices alone.

Debugging execution

In Debugger, enter F000:E05B under Run to from fresh power-on. Execution stops at that exact CS:IP after the reset jump and before CLI. Entering FE05B matches any segment alias of the same physical byte. The same notation applies to persistent Breakpoints. At reset, FFFF0 and FFFF:0000 both match. F000:FFF0 names the same bytes but a different CS:IP, so it does not match the reset state. Both stop reports include logical and physical addresses. Breakpoint checks observe CPU state before the next timed boundary, preserving pending input, interrupt requests, and board time.

Run after a breakpoint crosses its current encounter once. A loop back stops again. Step crosses the current breakpoint and completes one native CPU boundary. An unfinished REP keeps CS:IP at its first prefix, so continuing from a breakpoint there executes one more element before the next encounter. Run to is a temporary request with a limit of 2,000,000 boundaries. Breakpoints, watchpoints, Pause, and execution errors end that request early. An already-reached target stops immediately, and an unreachable target reports the limit. Halted or waiting boundaries also count toward it.

RAM write watchpoints select physical bytes in 00000–9FFFF. An entry such as 0040:0017 selects physical 00417, through any segment alias. A watch reports the first watched store with its before/after byte and originating CS:IP, including a store of the same value. It stops after the complete CPU boundary, so both bytes of a word store or all six bytes of an interrupt frame are complete. REP stops after the watched element. External interrupt writes are identified as interrupt entry. Show written byte in Memory selects the captured address. These watches observe conventional RAM stores during CPU execution. Video, ROM, I/O, reads, and host edits are outside their scope.

Each list holds up to 64 enabled or disabled stops and is remembered separately from machine checkpoints. Reset, fresh power-on, and checkpoint restoration clear the stop report and any temporary request while retaining these lists. Check a RAM watch after restoration to verify that it follows the restored hardware. Panel updates and navigation use observed state and perform no guest bus or port accesses.

Changes and watched RAM history

Changes uses the shared laboratory log. Each retained event identifies its original CS:IP and 20-bit physical address, fetched instruction bytes, and register, flag, and stored-byte changes. Relative branch targets are decoded from that captured segment pair. The CS:IP filter covers both fields of the logical instruction pointer. External interrupts, pending traps, and halted boundaries retain their own identity rather than borrowing the instruction at that address. Each REP iteration is a separate native boundary.

Memory changes come from storage observers, including conventional RAM and CGA RAM. The two CGA windows share storage, so changes name its first physical window at B8000, including writes made through the BC000 alias. Unchanged stores, ROM writes, and port I/O do not appear as memory changes. Reading or repainting the log does not read devices. A failed boundary keeps its completed changes and is marked interrupted.

The log retains the newest 500 boundaries. Record changes pauses capture; Live pauses rendering during Run. Closing the panel does not stop capture. Filters affect presentation only. Clear log restarts its numbering without clearing watched-memory history.

In Debugger, each configured RAM write watch has a History button. Its checkbox controls stopping; both enabled and disabled watches retain their last 256 writes, including unchanged values and interrupt stack writes. History continues with Changes recording paused, Live off, or the panel hidden. It excludes reads, CGA RAM, ROM, port I/O, and host edits. A failed boundary does not append a completed watch event. Removing a watch clears that watch's history. Clear all clears all retained watch events, leaving the watches set.

A history entry links the written physical byte to Memory and the writer's captured CS:IP to Disassembly. Disassembly then browses current storage at that logical location; Follow CS:IP returns to the running CPU. The history's instruction text continues to use the original fetched bytes even if the program has since changed them.

The BIOS data byte selector uses the chapter's workspace labels below. These follow the original listing's keyboard and timer data area at segment 0040 (appendix A, pages A-3–A-4). Word labels offer their low and high bytes separately, with +1 identifying the high byte. The addresses describe the selected firmware's layout; POST and its interrupt handlers initialize and maintain the values. For example, after booting, watch KB_FLAG, BUFFER_TAIL, and TIMER_LOW, disable stopping, then type while running. Their histories show the BIOS's keyboard and timer stores. IBM Technical Reference

Reset CPU, fresh power-on, restoration, and successful ROM replacement clear both histories while preserving stop preferences and recording controls. These debugging observations are not part of a saved hardware checkpoint.

Following reset and early POST

The Instruction panel previews the next native CPU boundary on private CPU, board-I/O, and keyboard copies. Its purpose and conditional-branch tests come from the 8088 chapter. It explains explicit memory operands as an effective offset, selected segment, and physical address, then shows predicted register, flag, and memory effects. Port I/O, TEST, and ESC stop the preview with an explanation. Unavailable storage and long prefix sequences also stop it. When a boundary has no modeled timing, the panel reports that the XT cannot yet execute it. Step executes the real boundary. The panel's Live switch controls updates during Run, and the previewed physical addresses link to Memory.

Choose Fresh power-on, then open Walkthrough. Read each checkpoint's preparation and expected observations, and choose Run to checkpoint. Next and Previous select the guide without executing. Breakpoints and watchpoints can stop the run earlier. The current address alone does not verify the setup. The final checkpoint completes the BIOS checksum and leaves the machine ready for its DMA and timer tests. The screen is still dark during these early tests.

These labels and comments follow the selected 11/08/82 listing in IBM's Technical Reference, appendix A, pages A-6–A-7 and A-73. RESET, C8, C10, ERR01, C11, C1, and ROS_CHECKSUM retain IBM's names. RESET_VECTOR is our label for the hardware entry. The instruction comments additionally check the fetched bytes, including retained disassembly history. The ROM identity below ties the guide to this chapter's BIOS socket. IBM Technical Reference

Follow the first SAHF and JAE to see CF set and the error branch declined. The register test then passes an all-ones pattern and a zero pattern through the general and segment registers. After its first device writes, the BIOS sets DS and SS to F000, BX to E000, and SP to E016. ROS_CHECKSUM adds the 8,192 BIOS bytes modulo 256. Its RET reads D7 E0 from physical FE016, returning to F000:E0D7 through an existing ROM word. That lets this test use a return instruction before establishing a working RAM stack.

{
  "sha256": "2798f8a9a7dcab651ede40373d35269ad573b75acf811115c263e034ef80a1f2",
  "regions": [
    {"start": "FE000", "end": "FFFFF", "name": "IBM XT BIOS · 11/08/82"}
  ],
  "routines": [
    {"address": "FFFF0", "name": "RESET_VECTOR", "description": "Hardware entry at FFFF:0000. Its far jump selects the BIOS code segment and RESET offset."},
    {"address": "FE05B", "name": "RESET", "description": "Start of the 8088 flag, branch, and register tests. Maskable interrupts are disabled first."},
    {"address": "FE08C", "name": "C8", "description": "Pass the current AX pattern through DS, BX, ES, CX, SS, DX, SP, BP, SI, and DI."},
    {"address": "FE0AD", "name": "ERR01", "description": "HLT on an early processor or BIOS-checksum failure."},
    {"address": "FE0AE", "name": "C10", "description": "Disable NMI and video, initialize board ports, and prepare the BIOS checksum."},
    {"address": "FF8F2", "name": "ROS_CHECKSUM", "description": "Add the 8,192 bytes starting at DS:BX into AL. OR AL,AL sets ZF from the checksum before RET."},
    {"address": "FE0D7", "name": "C11", "description": "Check the BIOS checksum result and continue to DMA/timer initialization when ZF is set."}
  ],
  "labels": [
    {"address": "00417", "name": "KB_FLAG", "scope": "workspace", "description": "0040:0017. Keyboard shift and toggle flags, maintained by the BIOS IRQ1 routine."},
    {"address": "00418", "name": "KB_FLAG_1", "scope": "workspace", "description": "0040:0018. Secondary keyboard status, including held toggle keys and the suspend state."},
    {"address": "0041A", "name": "BUFFER_HEAD", "scope": "workspace", "bytes": 2, "description": "0040:001A. Little-endian offset of the next keyboard entry to read, relative to segment 0040."},
    {"address": "0041C", "name": "BUFFER_TAIL", "scope": "workspace", "bytes": 2, "description": "0040:001C. Little-endian offset of the next free keyboard entry. Equal head and tail offsets mean an empty buffer."},
    {"address": "0041E", "name": "KB_BUFFER", "scope": "workspace", "bytes": 2, "description": "0040:001E. First word of the 16-word keyboard ring. The low byte is the character code and the high byte is its scan code; one slot remains empty."},
    {"address": "0046C", "name": "TIMER_LOW", "scope": "workspace", "bytes": 2, "description": "0040:006C. Low little-endian word of the BIOS tick count, updated by IRQ0."},
    {"address": "0046E", "name": "TIMER_HIGH", "scope": "workspace", "bytes": 2, "description": "0040:006E. High little-endian word of the BIOS tick count. TIMER_HIGH:TIMER_LOW forms one 32-bit counter."},
    {"address": "00470", "name": "TIMER_OFL", "scope": "workspace", "description": "0040:0070. BIOS midnight rollover flag, reported and cleared when the clock is read through INT 1Ah."},
    {"address": "FE016", "name": "C1", "scope": "rom", "description": "ROM word D7 E0 used as the return address for the first checksum. SS:SP points here before the jump."}
  ],
  "notes": [
    {"address": "FFFF0", "bytes": "EA 5B E0 00 F0", "text": "RESET_VECTOR selects CS=F000 and IP=E05B. The physical destination is FE05B."},
    {"address": "FE05B", "bytes": "FA", "text": "Disable maskable interrupts before testing the CPU and initializing board devices."},
    {"address": "FE05C", "bytes": "B4 D5", "text": "Prepare AH=D5 so SAHF sets SF, ZF, AF, PF, and CF."},
    {"address": "FE05E", "bytes": "9E", "text": "Copy the five selected AH bits into FLAGS. OF, IF, DF, and TF retain their existing values."},
    {"address": "FE05F", "bytes": "73 4C", "text": "JAE is IBM's JNC spelling here. CF should be 1, so continue to E061 instead of ERR01."},
    {"address": "FE061", "bytes": "75 4A", "text": "ZF should also be 1. JNE must decline the error branch."},
    {"address": "FE088", "bytes": "B8 FF FF", "text": "Begin the all-ones register pattern. A second pass checks zeroes."},
    {"address": "FE08C", "bytes": "8E D8", "text": "Start passing the AX pattern through the data and segment registers."},
    {"address": "FE0AD", "bytes": "F4", "text": "An early self-test failed. The expected walkthrough path does not reach this HLT."},
    {"address": "FE0AE", "bytes": "E6 A0", "text": "AL is zero after the successful register test. Writing it to A0 disables the NMI latch."},
    {"address": "FE0CB", "bytes": "8E D8", "text": "Set DS=F000 so the checksum reads the BIOS through DS:BX."},
    {"address": "FE0CD", "bytes": "FC", "text": "Clear the direction flag for subsequent forward string operations."},
    {"address": "FE0D1", "bytes": "BC 16 E0", "text": "Point SP at ROM word C1. With SS=F000, RET will read D7 E0 at FE016."},
    {"address": "FE0D4", "bytes": "E9 1B 18", "text": "Jump to ROS_CHECKSUM. The return word is already in ROM at SS:SP."},
    {"address": "FF8F2", "bytes": "B9 00 20", "text": "Count all 2000h bytes of the 8 KiB BIOS image."},
    {"address": "FF8F7", "bytes": "02 07", "text": "Add the byte at DS:BX into AL. Incrementing BX and LOOP visit each BIOS byte."},
    {"address": "FF8FE", "bytes": "C3", "text": "Pop the existing ROM word E0D7 into IP. SP advances from E016 to E018."},
    {"address": "FE0D7", "bytes": "75 D4", "text": "The byte sum is zero, so ZF=1 and JNE declines ERR01. DMA and timer tests follow."}
  ],
  "walkthroughs": [{
    "id": "reset-post", "title": "From reset to the BIOS checksum",
    "setup": "Load the three verified ROMs and choose Fresh power-on. Follow checkpoints in order. Disassembly, Instruction, Registers, and Memory explain each stop. Existing debugger stops may interrupt a run earlier.",
    "steps": [
      {"title": "The hardware entry", "address": "FFFF0", "execution": "FFFF:0000", "routine": "RESET_VECTOR",
       "prepare": "Choose Fresh power-on. Run to checkpoint is immediate at the reset address.",
       "observe": "The five bytes are EA 5B E0 00 F0. Instruction predicts CS:IP F000:E05B and physical FE05B. Step once to take the far jump."},
      {"title": "A pattern for the flags", "address": "FE05E", "execution": "F000:E05E", "routine": "RESET",
       "prepare": "Run from the reset jump through CLI and MOV AH,D5.",
       "observe": "AH is D5. Instruction predicts SF, ZF, AF, PF, and CF becoming 1. Step SAHF and watch the flag highlights."},
      {"title": "A branch that should be declined", "address": "FE05F", "execution": "F000:E05F", "routine": "RESET",
       "prepare": "Continue from SAHF. If you already stepped it, this checkpoint is immediate.",
       "observe": "JAE tests CF=0. CF is 1, so the branch is not taken. ERR01 labels its destination. Step to E061, where JNE tests ZF."},
      {"title": "Testing the registers", "address": "FE088", "execution": "F000:E088", "routine": "C8",
       "prepare": "Run through the remaining flag and branch tests.",
       "observe": "MOV AX,FFFF prepares the ones pattern. C8 passes it through the registers, then checks zeroes on a second pass. Follow a few Steps or continue to the next checkpoint."},
      {"title": "The first board output", "address": "FE0AE", "execution": "F000:E0AE", "routine": "C10",
       "prepare": "Run until both register patterns have passed.",
       "observe": "AX is zero. OUT A0h,AL will disable NMI. Instruction shows the output and stops its preview at the port. Step performs the actual device write."},
      {"title": "A return address in ROM", "address": "FE0D4", "execution": "F000:E0D4", "routine": "ROS_CHECKSUM",
       "prepare": "Run through the initial board outputs and checksum setup.",
       "observe": "DS=SS=F000, BX=E000, and SP=E016. Select SS:SP in Memory to see D7 E0 at FE016. JMP enters ROS_CHECKSUM without writing a return word."},
      {"title": "Read the first BIOS byte", "address": "FF8F7", "execution": "F000:F8F7", "routine": "ROS_CHECKSUM",
       "prepare": "Run through MOV CX,2000h and XOR AL,AL.",
       "observe": "Instruction resolves DS:BX to F000:E000, physical FE000. CX counts 8192 bytes. The ADD, INC, and LOOP sequence accumulates an eight-bit sum."},
      {"title": "Return through the ROM word", "address": "FF8FE", "execution": "F000:F8FE", "routine": "ROS_CHECKSUM",
       "prepare": "Run through the checksum loop and OR AL,AL.",
       "observe": "AL=00 and ZF=1. RET reads D7 E0 from SS:SP and predicts F000:E0D7. Its two reads are visible under Previewed storage accesses. Step to return."},
      {"title": "Checksum passed", "address": "FE0D7", "execution": "F000:E0D7", "routine": "C11",
       "prepare": "Continue from RET. This checkpoint is immediate if you already stepped it.",
       "observe": "SP is E018. JNE tests ZF=0 and is not taken. Step reaches E0D9, ready for the DMA and timer tests. This completes the initial walkthrough."}
    ]
  }]
}

Keyboard and restart controls

The browser maps US physical keys to XT makes and breaks. Tab moves focus out of the screen. Leaving the screen releases held keys, and leaving the window also pauses execution. Pasting supports US keyboard text and line breaks. It paces characters by the emulated oscillator and allows extra time after Enter for BASIC to scroll. The paste position and delay survive a checkpoint.

Ctrl-Break and Ctrl-Alt-Del send the corresponding XT key chords through the receiver. When the BIOS resets the keyboard, its self-test takes precedence and the host discards typing and releases queued before that reset. This keeps a stale release from reaching POST's stuck-key check. Reset CPU preserves RAM and devices. Fresh power-on starts a new machine with the loaded ROMs and any disk in drive A.