RSSAmplifier

Blog

Wise Owl

Deep technical dives into retro computing, FPGA hardware, emulation, and language implementation.

wiseowl.comRSS feed ↗42 posts

Latest posts

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

Why does Microsoft SoftCard CP/M 2.20 hang with a Videx Videoterm while 2.23 boots fine? To answer that question, I built two disassemblers and reverse engineered the boot diskette's first three tracks to produce the full CP/M OS commented source code.

The Components of 2.20

A tour of the four pieces that make up SoftCard CP/M 2.20: the 6502 boot loader, the BIOS, the BDOS, and the CCP. What each is for, the interface it presents to the parts above it, and how it works, with links to the recovered source for every one.

How the Source Is Organized

One source file per operating-system component, each decoded at its true runtime address, with shared include files for the contracts between them and a sector de-interleave nobody warns you about. How the recovered SoftCard CP/M 2.20 source tree is laid out, and why.

Compiling the Sources

The toolchain that turns the recovered SoftCard CP/M source files into binaries: ca65 and ld65 for the 6502 parts, sjasmplus for the Z-80, and a thin pipeline that knows each file's load address, include path, and embedded dependencies. One source in, one fixed-size binary out.

From Binaries to Boot Tracks

How the four compiled binaries map to physical sectors on the first three tracks of the floppy, why each 256-byte page lands where it does, and the tooling that scatters them back through the sector interleave to produce a disk image byte-identical to the 1980 original.

The Rest of the Disk

Three boot tracks rebuilt from source is an operating system, not yet a disk. What stands between the reconstructed system area and a floppy you can boot from nothing but source: the file system, the directory, and the shipped utilities, most of which already have annotated source of their own.

Two Disassemblers, and Telling Code From Data

A trustworthy disassembler in two stages: a round-trip byte-identity check wired in as a permanent regression test, and the harder problem of telling instructions from data on a disk that holds both 6502 and Z-80 machine code with no boundary marked.

Decompiling One Program by Running It

A CP/M .COM file is raw Z-80 bytes with no marker for code versus data and no way to follow a computed jump statically. Run it under an emulator with a BDOS shim, let execution mark the code bytes, and seed a recursive-descent disassembler from there. The decompile-file verb, worked on CPM60.COM.

Decompiling the Whole Disk

Scaling the decompiler from one .COM file to an entire SoftCard CP/M floppy: fingerprinting the image, the CP/M sector skew and the .dsk/.po interleave, splitting 6502 from Z-80 by who fetched the byte, and decompile-os / decompile-disk / list-files.

As-Shipped, Not Snapshotted: the Doctrine

Every source byte in the decompiled CP/M distribution is a byte that shipped on the disk, not a byte captured from a machine already running and rewriting itself. The 924-byte forensics behind the rule, and why the "185 patches" count was an extraction artifact.

One Source, Two Addresses: Single-Source Rebuilds and Diffing Versions

Single-source rebuilds with sjasmplus DISP/MODULE/INCBIN/IFNDEF (GBASIC and CPM60.COM worked), plus diffing 2.20 against 2.23 down to the 11-byte device check and the two console ownership paths.

The Whole Distribution, Rebuilt From Source

Part 6 of the Decompiler series: the whole Microsoft SoftCard CP/M distribution (three releases, 2.23 44K, 2.23 60K, 2.20) recovered as annotated source that rebuilds the original disk images byte for byte, completing the series.

The Card That Wouldn't Boot CP/M

I added a re-creation of a 1980 Apple ][ display card to the A2FPGA board. It worked, then a report said it stopped CP/M from starting. The same failure showed up on a real card too. The hunt for why, and what the wrong answer left behind once I finally had the right one.

The Answer That Agreed With Itself

I handed the CP/M boot failure to an AI assistant. It came back fast and certain, then built test after test that all confirmed it. The catch was that every test rested on the same wrong assumption, so none of them could ever disagree.

Two Right Parts, One Dead Machine

The real reason CP/M 2.20 hangs with an 80-column card. The expansion cards share one memory window and take turns owning it, and the SoftCard's processor switch knocks the owner loose one beat too soon. Two correct parts, fatal only together.

Two Tools and a Whole Distribution

Chasing one boot failure produced two tools that outgrew it: an emulator that boots the real operating system, and a decompiler that turns a 1980 floppy into readable source and rebuilds it byte-for-byte. Then the project took apart the whole CP/M distribution.

Why a Replica, Not a Reading

Static disassembly reads the SoftCard CP/M bytes perfectly and still cannot say which CPU owns the $C800 window at the instant of a fetch. That timing is where the 2.20 hang lives. So I built a whole emulated Apple ][ to find it, and held it to a hard bar before I believed it.

One Memory, Two CPUs, and a Bus Switch

The core architecture of the SoftCard replica: a single 64KB address space, two instruction-complete CPU cores that never run at once, the real four-window Z-80 to Apple address map, and a bus switch built from one memory access at $C700.

Hooking the Contracts: Disk, Monitor ROM, and the Keyboard the Z-80 Reads Itself

Part 3 of The Emulator series. Booting CP/M means servicing four peripheral interfaces from outside: the $BE11 sector-read hook, the monitor SAVE/RESTORE entries as PC hooks, the cross-CPU RPC channel through $45-$48 and the $03D0/$03D1 operand, and the Z-80 reading $E000/$E010 directly.

Modeling the Videx Until Its Ownership Rules Become Visible

Model the Videx Videoterm faithfully enough that its expansion-ROM window ownership becomes observable: the $C800-$CFFF claim and release, the 6845, the paged VRAM, the real firmware, and an arbiter that can log a fault.

What the Replica Found, and the Wrong Answer It Gave First

The emulator first booted 2.20 with a Videoterm because the $C800 window switch-out went unmodeled. Add that one bus rule and the field reports reproduce: 38 million window faults for 2.20, zero for 2.23. Plus the earlier run that confirmed a wrong theory, and why it agreed with garbage.

Emulating the SoftCard, Part 1: Why Static Analysis Wasn't Enough

Disassembly told me everything the code said and almost nothing about what the system did. Here are the four things in a SoftCard CP/M system I could only see by running a replica, plus the bar I made my emulator clear before I'd trust a word it told me.

Emulating the SoftCard, Part 2: Building a Two-CPU Machine in Python

How I built the machine in software: two CPU cores, one shared 64 KB, a bus switch made of a Python exception, a disk controller that cheats honestly, monitor ROM as breakpoints, a Videx with real ownership rules, and a language card, plus the bugs of mine that impersonated the system.

Emulating the SoftCard, Part 3: What a Working Replica Discovered

Once my replica booted, it told me things disassembly couldn't: a live trace of 2.20's console path, the bug rendered as a two-line rhythm in the fault log, the BIOS's origin settled in two probes, a boot-time patcher caught in the act, and the old failure matrix reproduced cell for cell.

What the CP/M pipeline does, and how to run it

Hand it a Microsoft SoftCard CP/M disk image and it maps how the disk boots, diffs one version against another, turns the disk into readable annotated assembly, and rebuilds a byte-identical image from that source. What each verb does, how to run it, and what it shows you.

Two disassemblers, and the trouble with telling code from data

To find why a 1980 CP/M disk wouldn't boot, I had to read its 6502 and Z-80 machine code, and no tool gave me source I could trust. So I wrote two disassemblers. Getting valid source back took a few tries. Teaching them to tell code from data took far longer.

Four Bugs That Lived in the Dark

Adding a second expansion-ROM-capable card to the A2FPGA exposed four latent bus timing bugs in the upstream codebase — bugs that had been present for approximately two years and went undetected because only one emulated card had ever used C8-space at the same time.

The Clock Card That Just Works

The ThunderClock Plus is the only Apple II clock card with a built-in ProDOS driver. Emulating it means implementing a NEC uPD1990AC serial calendar clock in SystemVerilog — and exploiting a USB-C power trick to give the FPGA battery-like time persistence.

Building the Rendering Pipeline

Getting 80 columns of text from VRAM to pixels on a modern HDMI display: the Videx VideoTerm rendering pipeline in SystemVerilog, C8-space ownership, and the Pascal boot hang that required reading a 1983 firmware disassembly to fix.

The Card That Made the Apple II Serious

The Videx VideoTerm was the 80-column card that turned the Apple II into a business machine. Emulating it on an FPGA means reverse-engineering a 1981 MC6845 CRTC design and understanding why slot 3 is unlike every other slot in the machine.

Forty Years of BitBlt

The decision to replace Smalltalk-80's 1980 display model with GPU-accelerated rendering: deleting Form, BitBlt, and Pen from the codebase and building Wise Owl Smalltalk on SDL2 and SkiaSharp.

First Pixels

Getting a Smalltalk-80 system to display something requires implementing BitBlt, the 1980 graphics primitive at the foundation of everything visible. Then saving the live heap so you never have to do cold start again.

The Ghost in the Source File

The Smalltalk-80 file-out is not a complete description of a running system. Some globals only ever existed interactively — they're not in any source file, but the code that uses them was written assuming they exist.

The Machine That Runs the Machine

The Smalltalk-80 bytecode interpreter: execution contexts, method lookup through the metaclass chain, non-local block returns, and the BlockClosure bug that took weeks to find.

Building a Universe from a Blueprint

The cold-start loader reads a JSON description of every Smalltalk class and builds a complete, correctly-linked heap from scratch. The garbage collector tells you when you got it wrong.

The Compiler That Couldn't Run Its Own Output

To build a Smalltalk-80 VM you need a compiler. But Smalltalk bytecode is useless without a running Smalltalk. The escape: a JSON intermediate representation and an empirically-discovered grammar.

The Object at the End of the Universe

Smalltalk-80's object model is the most consistent in computing history. Understanding it — metaclasses, live images, and all — is the first step toward building one from scratch.

nibbler: A WOZ Disk Analysis Toolkit

Every nibbler command demonstrated against Apple Panic — actual outputs, what each line means, and how to read the results. The reference guide for analysing a copy-protected Apple II WOZ disk image from first scan to disassembled source.

Forty-Five Kilobytes

What a recursive-descent disassembler finds in 27KB of 1981 Apple II game code: an XOR sprite engine, speaker timing loops, enemy AI state machines with three behavioral trees, BCD scoring, 104 subroutines, and a memory layout where every byte has a reason.

The Bug Was in Our Own Code

Sector 1 fails its checksum every time. Three theories, each plausible, each wrong. The real cause turns out to be a fundamental property of how WOZ files represent circular disk tracks — and it was in our own bit-stream reader the whole time.

Reading Code That Lies

Disassembling the Apple Panic boot loader reveals self-modifying code, a mathematically elegant GCR table corruption, a custom post-decode permutation, and a per-track marker scheme that makes the disk illegible unless you already know the key.

The Disk That Couldn't Be Copied

How the Disk II's radical hardware design made Apple II copy protection possible — and what a flux-level disk image reveals about a 1981 floppy that defeated nearly every copy tool of its era.