Skip to main content

z80-python

CI Oracles

A readable, pure-Python Z80 instruction-core reference implementation.

z80-python is a processor core built to be read, learned from, embedded in real machines, and inspected by humans and AI tools. It passes every oracle it is checked against, from values captured on real Zilog silicon (z80test, ZEXALL) to the emulator-derived SingleStepTests corpus, each named with its tier below. It implements the Z80 instruction set and deterministic processor lifecycle at instruction boundaries while leaving memory maps, devices, and machine scheduling to the host.

The project is deliberately:

  • readable — every instruction is an ordinary Python method, not generated code, a native extension, or an opaque optimization; tables only route each opcode to its method, which cites the manual page its rule comes from;
  • pure Python — the core has no runtime dependencies and supports CPython and PyPy;
  • independently validated — correctness claims come from external test oracles, not from code-generation confidence;
  • embeddable — a host passes in its memory (and, optionally, I/O) as callables and controls when the processor advances; and
  • inspectable — processor state, disassembly, bounded debugging with breakpoints, watchpoints and bus-access tracking, structured traces, and python -m z80_python for stepping a binary make execution explainable without contaminating the hot core path.

Validation

Each oracle below is named with its tier: where its expected values came from. A lower tier detects; the highest tier that checks a claim decides it (oracle tiers). The instruction core passes:

  • hardware-captured: three of raxoft/z80test's programs (z80full, z80memptr, z80ccf), whose values were recorded on a real 48K ZX Spectrum's Zilog NMOS Z80: every flag including the undocumented X/Y, WZ (MEMPTR), and SCF/CCF's Q-dependent behavior, for the instructions it covers;
  • hardware-captured, coarse: ZEXDOC and ZEXALL, whose per-group CRCs were "found empirically on a real Z80", under CPython and PyPy; they cannot see WZ, Q, R, I/O or T-states;
  • emulator-derived: all 1,604 files of the pinned SingleStepTests/z80 corpus, 1,604,000 cases covering registers, flags, memory, I/O, T-states, R, WZ, Q and the order of memory accesses, generated by a port of the Ares emulator;
  • emulator-derived: FUSE 1.6.0's Z80 core test set, 1,356 cases, of which 1,350 agree and the six that do not are each decided by a higher-tier source the core follows instead; and an interrupt-lifecycle cross-check against the independent superzazu/z80 core;
  • documentation: runs of DD/FD prefixes, from Young's The Undocumented Z80 Documented; and every handler's rule, cited to a page of Zilog's manual or a section of Young's in its docstring.

The order of memory accesses has no hardware-captured oracle at all: it rests on SingleStepTests and FUSE agreeing.

Release 0.4.0 was certified against every one of these, on both CPython and PyPy, at commit b1720b6. Exact source revisions, hashes, commands, timings, and scope limits are recorded in the validation evidence.

This is an instruction-level semantic and lifecycle claim. It is not a claim of cycle-accurate bus-pin behavior or of a complete computer.

CI coverage

The two badges cover different things, and neither covers everything. A green CI badge alone would not mean the oracle gates above passed, so they are separated rather than implied:

Badge Runs When
CI the fast suite, Ruff check and format, the example host, a wheel build and installed-API smoke test, on CPython 3.12/3.13/3.14 and PyPy 3.11; plus the FUSE oracle every push and pull request
Oracles SingleStepTests (1,604 files, 1,604,000 cases), z80test (z80full, z80ccf, z80memptr), FUSE, and the superzazu/z80 interrupt cross-check, each against its pinned corpus weekly, and on demand

ZEXDOC and ZEXALL are certified locally, not in CI. They need zexdoc.com/zexall.com, which no script here fetches, and take roughly 90 minutes each under CPython. Their revisions, file hashes, commands and timings are in the validation evidence; treat that record, not a badge, as the citation for those two gates.

Vibe coded, oracle validated

This project was developed with extensive AI assistance—“vibe coded” in the colloquial sense. That history is intentional and worth stating plainly: the implementation demonstrates what AI-assisted engineering can produce when the feedback loop is stronger than the model's confidence.

Generated emulator code can be plausible and wrong, especially around prefixes, undocumented flags, WZ/MEMPTR, Q, and refresh behavior. The project therefore does not treat AI output or code review as proof. Independent SingleStep vectors, focused regressions, ZEX CRCs, and real emulator integrations are the authority. See AI-assisted development and validation.

Version status

The current release is 0.4.0 (see its release note and the changelog). It changes how a host embeds the CPU; the changelog's Breaking section has the one-line migration.

Source Status Contents
PyPI and GitHub v0.4.0 Current release Callable embedding contract, 2.5x speed, every handler cited, oracle tiers, bus-access tracking and watchpoints
PyPI and GitHub v0.3.0 Previous release API hardening and advanced trace diagnostics
GitHub v0.2.0 Milestone RESET, CPU state, disassembly, and debugger foundations

Install from PyPI

python -m pip install z80-python

Install the source tree

git clone https://github.com/alewman/z80-python.git
cd z80-python
python -m venv .venv && . .venv/bin/activate   # Windows: .venv\Scripts\activate
python -m pip install -e .

Recent Debian, Ubuntu, Fedora, and Homebrew Pythons refuse pip install into the system interpreter (PEP 668); the virtual environment line above is the supported way around that.

For tests, linting, and development tools:

python -m pip install -e ".[dev]"

The distribution is named z80-python; its public import is z80_python to avoid ambiguity with the unrelated z80 distribution on PyPI.

Minimal host

The host owns memory and devices and passes the CPU its buses as callables. The core always passes a 16-bit address and an 8-bit value, so a bytearray's own methods are a complete memory:

from z80_python import Z80CPU

memory = bytearray(0x10000)
cpu = Z80CPU(memory.__getitem__, memory.__setitem__)  # read_byte, write_byte
memory[:3] = bytes((0x3E, 0x2A, 0x3C))  # LD A,2Ah; INC A
assert cpu.step() == 7
assert cpu.step() == 4
assert cpu.a == 0x2B

Devices on the I/O bus come in as read_port= and write_port=; without them IN reads 0xFF and OUT goes nowhere. This is the embedding contract of the whole family: m6800-python takes its bus the same way. start-here has the rest. Until 0.4.0 a host subclassed Z80CPU and defined the four bus methods; that form now raises a TypeError naming this one.

step() advances one instruction or accepted lifecycle boundary and returns its documented T-state total. Registers and modeled processor state are directly readable and writable. decode_and_execute() remains the historical instruction-only entry point; new hosts should normally use step().

Processor lifecycle

The core models external processor inputs as deterministic transitions between instructions:

  • RESET is host-controlled and level-sensitive. While asserted, each step() applies the documented processor-state effects in 3 T-states without fetching an instruction or accessing the stack.
  • NMI is accepted at the next available boundary, wakes HALT, preserves the prior IFF1 in IFF2, pushes PC, and enters 0x0066 in 11 T-states.
  • Maskable interrupts honor IFF1, the one-instruction EI delay, HALT, and interrupt modes 0, 1, and 2. IM 0 is intentionally bounded to device-supplied RST opcodes.

RESET has priority over NMI; NMI has priority over an acceptable maskable interrupt. Hosts schedule devices from returned T-state totals and request interrupts through the public lifecycle API rather than mutating PC, SP, or interrupt flip-flops to synthesize entry.

See the interrupt lifecycle contract for exact state transitions and exclusions.

Reference-core boundary

z80-python owns:

  • Z80 instruction semantics and documented instruction T-state totals;
  • registers, flags, alternate registers, and modeled internal processor state;
  • RESET, NMI, maskable interrupts, EI delay, RETN, and HALT behavior; and
  • processor-level observation and debugging values.

A host machine owns:

  • ROM, RAM, memory maps, ports, mappers, and open-bus behavior;
  • video, audio, input, and other devices;
  • frame, scanline, clock, and interrupt scheduling;
  • device events and side-effect-free memory peeking; and
  • complete machine save states, deterministic replay, and rewind.

Accordingly, the project does not claim:

  • cycle-accurate bus cycles, contention, WAIT, BUSREQ, or pin timing;
  • interrupt-acknowledge bus callbacks, daisy chains, or arbitrary IM 0 injected instructions;
  • a complete CP/M system, arcade board, console, or computer; or
  • a universal machine scheduler or save-state format.

These are scope boundaries, not unfinished promises. They keep the core readable, portable, and useful across different machines.

Learning and inspection

New to the Z80? Read Start here first: the register file, the flag byte, the opcode bit fields every handler decodes, the prefix model, and the three internal registers (WZ, Q, R) that only emulator authors meet.

The implementation is organized by instruction family behind a small public Z80CPU facade. Every opcode handler's docstring starts with its Zilog mnemonic, so grep DJNZ src/ lands on the implementation, and ends with where its rule comes from: a page of Zilog's Z80 CPU User Manual or a section of Young's Undocumented Z80 Documented, plus the SingleStepTests file (and, where one exists, the z80test program) that pins any WZ or Q rule neither manual covers. The hardware reason behind each undocumented flag or MEMPTR effect is a comment on the line that encodes it. A test (tests/test_readability.py) enforces the mnemonic and the citations the same way the vector gate enforces correctness.

The development tree also provides immutable CPUState capture and restoration for processor-owned state and a complete structured disassembler for every opcode form supported by the core. Disassembly requires an explicit side-effect-free byte reader: debugging must not accidentally acknowledge or mutate a mapped device.

These APIs make the project useful as:

  • a reference while learning Z80 instructions and flags;
  • a tested foundation for emulators and machine experiments;
  • a source of deterministic examples and regression witnesses; and
  • an executable environment where an AI agent can inspect real state instead of guessing from source code alone.

See CPU state, disassembly, and undocumented behavior.

Conformance for ports in other languages

A manifest pins a machine (memory, initial state, host profile, interrupt events, stop rule); the reference core produces a trace for it, and a core in any language can be diffed against that trace after every instruction, with T-states and all 28 processor-state fields compared:

python -m z80_python.conformance trace examples/conformance/interrupts.json --out ref.jsonl
python -m z80_python.conformance diff  examples/conformance/interrupts.json yours.jsonl

See conformance for the host contract and the certification ladder, and the trace schema for the record format.

Advanced diagnostics and tooling

The following development-tree features support emulator diagnosis but are secondary to the instruction core itself:

  • DebugSession wraps an existing host with bounded execution, execute breakpoints, lifecycle-aware records, T-state totals, and bounded history. It adds no callback or history overhead when unused.
  • CommandDebugger is a dependency-free text-stream frontend for registers, stepping, bounded runs, breakpoints, disassembly, memory display, and history.
  • Trace comparison finds the first differing instruction, lifecycle boundary, timing result, or individual CPU-state field across two executions.
  • Versioned JSON Lines persistence allows large traces to be written, reloaded, and compared incrementally without buffering complete runs.
  • Live lockstep comparison can advance two machines under a finite budget and stop at the causal processor-visible difference while preserving bounded pre-divergence context.

A Galaxian integration proved the intended diagnostic behavior: two otherwise identical boards differed only in their vblank NMI-enable latch. Comparison stopped at the exact boundary where one scheduler first asserted pending NMI—one step before the later control-flow divergence into 0x0066.

These are deterministic tools, not an embedded LLM or AI provider. Any human UI, agent, MCP adapter, or model can consume the same structured contracts. See debug sessions, trace comparison, and the tooling roadmap.

Development

Run the ordinary quality gate:

python -m pytest -q
python -m ruff check .
python examples/minimal_z80_host.py

The vector corpus and ZEX binaries are external artifacts and are intentionally not bundled. Fetch the pinned vector corpus with:

python scripts/fetch_test_vectors.py
python -m pytest -q

ZEX recertification is reserved for release candidates and semantic-core changes; reproduction instructions and finite execution budgets are documented in the validation evidence.

Project records

License

MIT. External validation artifacts retain their own licenses and are not bundled in this distribution.

Metadata

Release files for z80-python 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for z80-python 0.4.0
File Size Uploaded
z80_python-0.4.0.tar.gz 214.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for z80-python 0.4.0
File Interpreter ABI Platform
z80_python-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 279.1 kB

Release files / z80_python-0.4.0.tar.gz

Download URL z80_python-0.4.0.tar.gz
Size 214.0 kB
Tags Source
SHA-256 checksum
How to use checksums
8bdb3052d8f1a409a07f5f92022a5af65f0d5a59b4e30b10f4f83431a43bd59e
BLAKE2b-256 checksum
How to use checksums
a8045a0409f0bfc7fb553ba3d1f375902db832f2b2157fa2357cb5825f121c78
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / z80_python-0.4.0-py3-none-any.whl

Download URL z80_python-0.4.0-py3-none-any.whl
Size 65.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a06a7136ff2aa86df9d3843d5cff99df63ba165061c38dd2dcdaa1a506169866
BLAKE2b-256 checksum
How to use checksums
aafe13e6234a9f6977a37bc96e0a23e11314a2f1c9d19e0afa6151820488c32a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page