RSSAmplifier

Wise Owl · Jun 24, 2026

Why Microsoft SoftCard CP/M Wouldn't Boot With a Videx Videoterm

0
Sign in to vote or save

Brent Rector · Wise Owl

I have an Apple ][+ with a Microsoft Z-80 SoftCard and a Videoterm Videx 80-column board. The SoftCard is an add-on circuit board hosting a Z-80 processor that allows the Apple to run CP/M, the dominant business operating system of the time. I have the Videx Videoterm because PASCAL, and CP/M software work better with 80-text column displays. The Apple natively displays only 40 text columns. For the Apple ]+, Microsoft shipped two versions of SoftCard CP/M: 2.20 and 2.23. Version 2.23 boots to the A> prompt with the Videoterm installed. Version 2.20 hangs. Same machine, same card, same floppy drive. One works, one does not.

I wanted to know exactly why. To find out, I started reverse engineering and disassembling the CP/M boot process for each OS version. By the time I discovered why, I had created so much tooling, I kept going and recreated the source code for the entire CP/M OS and its utilities. This first article is the why and the how-much. The rest of the series is the source.

What “boots” even means here

If you have never watched an Apple ][+ start CP/M, the sequence is stranger than it sounds, because two completely different processors are involved and they take turns.

When you power on, the Apple’s own built-in 6502 processor runs. It reads the first sector off the floppy, that sector loads more code, and that code eventually loads the actual operating system into memory. But CP/M is is an operating system for a different processor - the Z-80. The 6502 cannot run Z-80 code. The 6502 runs the boot code up to the point where the Z-80 code begins, then flips a switch on the SoftCard. The switch stops the 6502, wakes up the Z-80, and from then on the Z-80 is in charge. But when CP/M aka the Z-80 need the Apple ][+ to do something - display text on the screen, play sound, read or write the disk, etc. - the Z-80 sets some flags informing the 6502 of the request, then flips the switch back. The Z-80 stops running and the 6502 resume where it left off. The two processors share the same memory and the same screen and the same disk drive. They just never run at the same time.

The hang happened after much of the disk was read but before any screen output appeared. That implied it was likely crashing during a processor switch. But only when the Videoterm 80-column card was present. So likely during a handoff to display something on the screen. And, only when running CP/M version 2.20, as 2.23 worked fine. The failure was tangled up in the most confusing part of the machine: the seam between two processors, the moment one hands control to the other, and a third device, the display card, sitting in the middle of it. And Microsoft had found and fixed the issue in 2.23, but never published what the fix was.

Microsoft never published how SoftCard CP/M worked at this level, and the parts that mattered, the console driver and the card detection, were exactly the parts that differed between 2.20 and 2.23. The answer was in the bytes on the disk, and nowhere else.

The first wrong answer

I actually found an answer very quickly. I found a code difference between 2.20 and 2.23, in roughly the right place, that looked like it explained the hang. It was a satisfying answer. It agreed with itself. I was using Claude Code and it definitively stated, yes, we’ve found the problem.

It was wrong. The real cause was somewhere else entirely. Claude was arguing that control was running off into random memory, thus causing the hang. And Claude was point at the point where it did so. But, I kept insisting on why. Why was control executing random memory. Claude and I did not have that answer. And, at that time, I hadn’t yet looked into version 2.23.

By this time, I’d mostly implemented in Python a full Apple ][+ emulator. If it was truly running off into random memory, I should be able to emulate the boot process accurately enough to trace exactly what went wrong. I wanted to watch a running model that would reproduce the hang and let me watch it happen. The full story of the wrong answer and the real one is its own series, The Card That Wouldn’t Boot CP/M. The short version is the the first attempt failed in the oddest way: version 2.20 booted to the A> CP/M prompt on my emulator. It didn’t crashed. It turned out, my emulator did not quite precisely emulate the hardware. When I fixed that, the emulation crashed as expected on 2.20 and ran ran successfully on version 2.23. ANd I found the precise reason why.

That decision, to keep going past the first confidently wrong answer, is reason I created the tooling. Much of it was actually authored by Claude. I will be specific about which half was which as the series goes, because the division of labor is unusually clean and I think it is interesting. Claude provided the unabashed confidence. But the continual skepticism was mine.

What it took: two disassemblers

Bask to the beginning. I had a image of the CP/M boot disks. But, as alluded to previously, the machine code on those disks was both 6502 and Z-80 code. To translate this machine code to assembly language source, first, you need two disassemblers, and second, some way to detect when 6502 ends and Z-80 begins and vice-versa.

I created two disassemblers: one for the 6502 code that runs on the Apple, one for the Z-80 code that runs on the Softcard. As a consistency check, we re-compiled the output of the disassembler, using a third-party assembler, and compared the result against the original bytes. If a single byte differs, the source code is lying somewhere (basically the disassembler has a bug). You find out immediately instead of a week into reasoning from bad source code. That round-trip check became the backbone of the whole project. Two Disassemblers, and Telling Code From Data is the detail.

A disassembler turns raw bytes back into source code a programmer can read. But the same byte means different things to different processors. The byte $C3 is a jump on both the 6502 and the Z-80, but most bytes are not so lucky, and a long run of bytes will decode into perfectly plausible, completely different programs depending on which processor you assume. There is no header on the disk saying “Z-80 code starts here.” You have to figure out the boundaries yourself. As it turned out, the only solid way to distinguish the code was to run it under an emulator and follow the 6502 execution until it switches to the Z-80, then follow the Z-80 emulation until it switches back to the 6502, ad infinitum.

What it took: an emulator that is two CPUs in one

The Apple ][ has plenty of emulators, but they model a 6502. The SoftCard puts a Z-80 in the same machine, sharing the same memory through a hardware switch, and no off-the-shelf emulator modeled that arrangement. So I built one: a single emulated machine with a 6502 and a Z-80 both wired to one memory bus, with the SoftCard’s switch deciding which processor is awake and how each one sees the same physical memory at different addresses.

The emulator boots the real disk images, via an emulated Disk II controller. It has 64K of memory, via the emulated language card. It has 80-column text output, via the emulated Videx Videoterm video adapter. It contains images of the genuine Apple ROMs, in order to provide system services. And finally, reproduces the CP/M version 2.20-with-Videoterm hang exactly. And, as a final proof of concept, when I disabled the Videoterm emulation, the emulator booted version 2.20 and run all the way to the A> prompt on the 40-column screen. Entering ‘DIR’ even displayed the disk catalog.

That last part is what made it worth building. A model that reproduces the bug is a model you can trust to explain the bug, because you can stop it mid-hang and look. The emulator is its own series, starting at Why a Replica, Not a Reading. For this tour, the thing to take away is that running the system, not just reading it, is what separated the wrong answer from the right one.

What it took: reverse engineering the first three tracks

A floppy disk is divided into tracks, and tracks into sectors. On this disk the operating system lives in the first three tracks, the reserved “system area” the Apple reads at boot before it ever shows you a file. Tracks three and up hold the file system, the programs you would see if you typed DIR. The operating system itself, the part that runs the machine, is tracks zero through two.

Three tracks with 16 sectors per track and 256 bytes per sector. That’s 12,288 bytes to decode. That’s seemed manageable. Oh so naive. Reverse engineering those three tracks meant decoding every byte in them, across both processors, and working out not just what each instruction is but what each routine is for: this block is the keyboard handler, that one selects a disk drive, this table is the list of console device drivers. The disk does not tell you any of that. It’s just bytes. Recovering the meaning is the slow part, and it is the part that turns “I decoded the bytes” into “I understand the operating system.”

And there is a twist in those three tracks that took an embarrassingly long time to see. The operating system is not stored on disk in the order the processor runs it. The boot loader reads the sectors in one order and lays them into memory in another, so the bytes execute in a different sequence than they sit on the disk. For most of the project I had the bytes right and the addresses wrong, which is a special kind of trap: everything reassembles to the correct disk, so every automated check passes, while the labels point at the wrong places. Sorting that out, decoding the operating system at the address the processor actually runs it, is the correction that made the source genuinely describe the machine.

What fell out: the source files

Put all of that together, the two disassemblers, the running model to check against, the slow recovery of meaning, the de-interleaving of the boot tracks, and what you get is a set of source files. One per component of the operating system, each one ORG’d to the exact address the processor runs it at, each one carrying named routines and commentary instead of anonymous hex, and each one assembling with standard tools back into the exact bytes on the original 1980 disk. They are all public, in softcard/CPMV220-44K/os/; this series is the guided walk through them.

Note: there may be some existing misinterpretations. I never had the original source code so the source code I provide has names I invented for functionality I believe the code provides. However, it assembles to the original disk, byte for byte, then it is the operating system, written down in a form you can read.

Those source files are what the rest of this series is about:

Read the original on wiseowl.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.