This repository focuses on a compact Z80 CPU core intended for experimenting with a CP/M environment. The goal is to provide a clean foundation for building a disk-backed CP/M emulator while keeping the code small enough to understand and extend. This is currently a simple command-line tool that executes Z80 binaries.
The current state includes:
- A CPU core that implements the full 8080 instruction set along with the Z80 rotate/bit (
CB) and block transfer/compare (ED) groups required by CP/M system binaries, including recent additions such asNEG,RETN/RETI, interrupt mode selection (IM n), register transfers withI/R, the decimal rotate helpersRRD/RLD, and comprehensive IX/IY-prefixed arithmetic, load, and block instructions. - A flat 64 KiB memory map suitable for early CP/M programs.
- A configurable disk subsystem with multi-drive support wired into BIOS trap handlers. Each mounted image tracks per-drive geometry, exposes CP/M-compatible drive tables, sector translation tables, and disk parameter headers for
SELDSK, caches recently accessed sectors, persists allocation vector updates alongside directory metadata, reports detailed status codes so CP/M filesystem routines can react to I/O faults while the full FDC emulation evolves, and can infer geometry directly from optional CPMI headers without an explicit--disk-geomoverride while honouring any embedded directory-buffer sizing hints so BIOS workspace reservations line up with curated media. - CP/M-style BIOS warm boot and BDOS entry points that translate console and file calls into host operations so simple programs can interact with the environment.
- BDOS trampolines covering sequential and random record file I/O alongside directory search helpers (
SEARCH FIRST/SEARCH NEXT) so console utilities can enumerate the mounted disk images without custom host shims while remaining compatible with extent sequencing, and a reader-device shim so paper-tape oriented programs can ingest host data without custom BIOS patches. - Console device shims that drive the standard console, punch, and list streams so host tooling can observe printer or paper-tape output without custom BIOS patches, now including BDOS console-status polling against host input readiness and BIOS port handlers so
--no-cpm-trapsruns still capture punch and list output while exposing Altair-style console-status readiness to unmodified system BIOS code.
Expect to extend the instruction coverage and peripheral behaviour as CP/M functionality is implemented.
- A C11-compatible compiler (tested with
gcc). make.
Run ./configure to confirm the prerequisites are available on your system.
./configure
make
The build produces the z80 executable in the project root.
To quickly verify the emulator, the repository ships with a minimal CP/M-compatible program in examples/hello.asm. A matching
hex listing is converted into a runnable binary using Python. Generate it with:
make example
This creates examples/hello.bin, which loads at the CP/M transient program area and is used by the smoke tests below.
The emulator currently accepts a raw binary to load at the standard CP/M transient program area (0x0100). It executes instructions until a HALT is encountered or a cycle budget is exhausted.
./z80 path/to/program.bin
For the bundled sample program:
./z80 examples/hello.bin
The BDOS shim prints the greeting stored in the sample program and then returns to the host.
To experiment with a full CP/M environment instead of a transient program, boot the bundled CP/M 2.2 images and let the emulated BIOS handle console I/O. The quickest path is to reuse the regression helper that wires everything together:
./scripts/run_cpm_system_image_test.shThe script decodes the CCP/BDOS and BIOS binaries stored under third_party/cpm22/, loads them at the same addresses used by the tests, disables the host BDOS and BIOS traps, and runs the machine long enough to reach the CP/M prompt. When the supervisor finishes booting you can issue standard commands such as DIR or TYPE against any mounted disk images.
If you need the raw CP/M system images outside of the helper script, decode the bundled Base64 assets yourself:
base64 -d third_party/cpm22/cpm.bin.base64 > /tmp/cpm.bin
base64 -d third_party/cpm22/bios.bin.base64 > /tmp/bios.binBoth commands emit the same cpm.bin (CCP/BDOS) and bios.bin blobs that the regression suite boots.
To wire up the console session manually, mimic the options used by the helper script. First decode the base64 resources into raw binaries (for example, /tmp/cpm.bin and /tmp/bios.bin), then launch the emulator with:
./z80 \
--no-cpm-traps \
--entry 0xFA00 \
--load 0xDC00:/tmp/cpm.bin \
--load 0xFA00:/tmp/bios.bin \
--disk A:path/to/disk.img \
--cycles 0Key flags:
--no-cpm-trapshands console and disk services back to the CP/M BIOS and BDOS so you see the native prompt.--entry 0xFA00jumps into the BIOS warm-boot vector packaged with the supervisor image.- Each
--loadpair copies the decoded system binaries into the addresses expected by the supervisor. - Add
--diskoptions to mount CP/M disk images that should appear as drives A:, B:, and so on.
Once the machine reaches the CCP prompt, you can run any CP/M commands available on the mounted media.
Mounting a disk image with --disk is optional—omit the flag entirely if you only need to confirm that the supervisor stack boots. When you do want storage attached but don't already have a CP/M disk handy, the scripts/run_cpm_system_image_test.sh helper shows how to fabricate a minimal one: it writes a single-track file containing 26 sectors of 128 bytes each and preloads a short message for the BIOS regression. You can reuse that geometry to build a blank image of the same size with your preferred tooling (for example, via a short Python or dd snippet) and pass its path to --disk A: to have it appear as drive A: in the CP/M session.
When no disk is supplied alongside the supervisor images, the emulator now creates a temporary, blank system disk in /tmp that contains the preloaded CCP/BDOS and BIOS bytes. This synthetic volume lets the bundled CP/M stack reach the A> prompt without requiring a separate media image on disk. The file is unlinked immediately after the emulator opens it, so runs without an explicit --disk flag leave no artefacts behind, and subsequent warm boots continue to reload the resident system from the same in-memory media.
When one or more disk images are mounted, the emulator reserves a BIOS workspace near the top of memory so CP/M software can interrogate the host geometry without custom patches. The word stored at 0xF000 contains the pointer to the drive table, which in turn stores one disk parameter header (DPH) pointer per drive. The byte at 0xF002 reports how many of those entries correspond to mounted drives. Each DPH references a drive-specific disk parameter block (DPB), allocation vector, and scratch buffers. The layout matches the CP/M 2.2 conventions, so SELDSK returns the same DPH pointer and utilities can walk the table directly to discover the sector size, tracks-per-disk, and reserved-directory allocation for each image.
Words beginning at 0xF004 expose the default DMA address that each mounted drive advertises. Entries are encoded in little-endian order and contain zero for unmounted drives, the CP/M default (0x0080) when no override is available, or the value parsed from a CPMI header. BIOS or BDOS components can consult that table before issuing SETDMA so multi-profile disk sets preserve their intended buffering behaviour automatically.
Read-only host permissions now surface through both the BDOS read-only vector and individual directory entries, so CP/M utilities will display the R/O attribute and avoid attempting to reclaim allocation blocks on protected media. The BDOS attribute handler also honours the system and archive bits, ensuring tools such as STAT or PIP see the same protection state that the directory encodes. Host-backed file handles now treat any of those attribute bits as write barriers, so BDOS refuses to persist records once a file is marked protected, and attribute updates performed from CP/M are written back into the directory metadata whenever BDOS writes succeed, keeping the on-disk flags synchronised across reboots.
Run the regression suite to ensure the CP/M system exercise and sample transient program both still behave, and that disk shims continue to match CP/M conventions:
make test
The harness first invokes a Python integration test that assembles examples/hello.asm on the fly, runs the resulting binary under the emulator, and asserts on the captured console output so the recent ED-prefixed flag fixes stay covered. It then assembles a transient program that exercises the BDOS random read/write helpers against a host-backed file, confirming that extent sequencing and random record updates stay synchronised with the new helpers. Dedicated regressions next stream curated bytes through BDOS function 3 via the --reader flag to ensure the tape-ingest path continues to track CLI plumbing, capture the punch and list devices into host-managed files, poll BDOS function 0x0B to confirm console-status requests reflect host input readiness, and toggle the system/archive bits on a synthetic directory entry before inspecting the disk image to confirm the flags persist and now block host writes immediately. A CPMI-focused helper runs a transient program that reads the BIOS DMA table, while a companion C harness mounts a deliberately skewed disk image via the disk.c API to verify that directory enumeration continues to respect CP/M translation tables, that CPMI headers can steer the BIOS directory-buffer reservation, and that read-only hints embedded in headers seed the BDOS protection vector. Additional BIOS-port checks emit punch and list bytes with --no-cpm-traps enabled to ensure raw system images can still spool output into the configured capture files, a dedicated supervisor-spool harness now streams a PIP-style payload directly through the punch and list ports to confirm the port handlers capture traffic end-to-end, and a companion status poll confirms the Altair console-status port now mirrors host input readiness for unmodified BIOS loops. After those targeted checks, the suite boots a curated CP/M 2.2 supervisor image with the emulator running in "no traps" mode. Successful runs show the Hello from CP/M! greeting, demonstrating that the command console trampolines are still wired correctly and that the expanded Z80 ED and IX/IY-prefixed helpers behave as expected in both a transient program and the supervisor stack itself.
After validating the supervisor image, the regression mounts a generated single-track diskette and runs examples/bios_disk.bin. That transient program issues BIOS SELDSK, SETTRK, SETSEC, SETDMA, and READ calls, confirming that the host-side FDC abstraction can service sector reads through the CP/M BIOS entry point. The BIOS shims now surface explicit DISK_STATUS_* return codes, so the program can distinguish "not ready" from "bad address" failures while echoing the sector payload through BDOS function 9.
Useful command-line options:
--cycles N– Limit execution toNT-states before halting automatically (default: 1,000,000). Pass0to run without a cap until the guest HALTs.--disk DRIVE:path– Mount a raw disk image on CP/M drive letterDRIVE(for example,--disk B:disks/work.img). The legacy shorthand--disk-ais still accepted for convenience.--disk-geom DRIVE:spt:ssize[:tracks]– Override the sectors per track, sector size in bytes, and optional track count before mounting a drive. Geometry defaults to 26×128-byte sectors when unspecified.--disk-xlt DRIVE:map– Provide a comma-separated list of 1-based sector numbers describing the BIOS translation order for each track (for example,--disk-xlt A:1,5,9,13,17,21,25,2,6,...).--reader path– Supply bytes for the BDOS reader device frompath(use-to read from standard input).--punch-out path– Capture BDOS punch device output inpath(use-to forward to the host standard output stream).--list-out path– Capture BDOS list device output inpath(use-to continue emitting on the host standard error stream).--load addr:file– Copy an additional binary into memory ataddr(for example, to preload a ROM or BIOS image). This flag may be repeated.--load-hex path– Ingest an Intel HEX file at the addresses encoded in the records, which is convenient for CP/M system distributions that ship in textual form.--entry addr– Override the initial program counter. When absent, it defaults to the transient program area (0x0100) if a standalone program is supplied.--no-cpm-traps– Disable the host-side BDOS and BIOS shims so that genuine system images can run their own handlers.
Because most peripheral behaviours and a handful of less common opcodes are still incomplete, running an arbitrary CP/M program can still terminate with an "Unimplemented opcode" message. This is intentional at this stage so the remaining gaps can be filled in incrementally.
When mounting images that begin with the 16-byte CPMI header, the emulator now extracts the encoded sector size, sectors per track, and optional track count automatically. The three highest bits of the track-count field act as feature flags: bit 31 indicates that the header is followed by a translation-table length and one byte per logical sector describing the BIOS skew order (using the familiar 1-based numbering), bit 30 signals that a 16-bit little-endian default DMA address is stored immediately after the header, bit 29 advertises a 16-bit little-endian directory-buffer size that the BIOS uses when reserving scratch space for that drive, and bit 28 appends a single-byte attribute hint whose low bits seed the BDOS read-only vector before any directory traversal occurs. Optional sections appear in that order, and the payload immediately following the header (and any optional sections) is treated as the first sector, so existing raw media remain compatible while curated images can embed geometry, translation, DMA, attribute, and BIOS workspace metadata for convenience.
Directory updates issued by BDOS now reapply those CPMI attribute hints before writing sectors back to disk, so the seeded read-only bits persist across cold and warm boots even when directory entries churn.
To validate the IX/IY-prefixed instruction paths against real system software, the test suite stores base64-encoded CP/M 2.2 supervisor images sourced from the z80pack reconstruction. The helper script invoked by make test decodes the CCP/BDOS bundle (cpm.bin.base64) and BIOS stub (bios.bin.base64) into temporary binaries, maps them at 0xDC00 and 0xFA00, disables the host BDOS/BIOS shims, and executes the machine for 500,000 T-states. When the emulator completes without reporting unimplemented opcodes, the IX/IY-prefixed execution paths have been exercised against the full CP/M supervisor stack. Mount a disk image with --disk A:path to extend the experiment to filesystem and console integration as new device emulation features land.
Contributions that expand opcode coverage, improve testing, or add CP/M-compatible peripherals are welcome.