z80-python
A readable, pure-Python Z80 instruction-core reference implementation.
z80-python is deliberately a CPU core rather than a complete computer or arcade
emulator. A host subclass supplies 16-bit memory and I/O access; the core executes
one instruction at a time and returns its documented T-state total.
Validation status
The extracted core has passed:
- all 1,604 local
SingleStepTests/z80opcode/prefix vector files, comprising 1,604,000 state transitions; and - ZEXDOC and ZEXALL long-sequence CRC exercisers under both CPython 3.12 and PyPy 3.11.
See validation evidence for exact hashes, commands, results, and scope limits. This does not claim cycle-accurate bus behavior, a complete Z80 machine, CP/M, or a Galaxian board.
Install
python -m pip install z80-python
The public import is z80_python, intentionally distinct from the unrelated
existing z80 distribution on PyPI.
Minimal host
from z80_python import Z80CPU
class Machine(Z80CPU):
def __init__(self) -> None:
super().__init__()
self.memory = bytearray(0x10000)
self.ports = bytearray(0x10000)
def read_byte(self, addr: int) -> int:
return self.memory[addr & 0xFFFF]
def write_byte(self, addr: int, value: int) -> None:
self.memory[addr & 0xFFFF] = value & 0xFF
def read_port(self, addr: int) -> int:
return self.ports[addr & 0xFFFF]
def write_port(self, addr: int, value: int) -> None:
self.ports[addr & 0xFFFF] = value & 0xFF
cpu = Machine()
cpu.memory[:3] = bytes((0x3E, 0x2A, 0x3C)) # LD A,2Ah; INC A
assert cpu.step() == 7
assert cpu.step() == 4
assert cpu.a == 0x2B
step() is the public one-instruction entry point. decode_and_execute() is
retained as a supported historical name. CPU registers and modeled state are
directly readable and writable; the host owns memory, devices, and reset policy.
Maskable interrupts
The core provides a deterministic instruction-boundary model for external maskable
interrupts. A host asserts a request with request_maskable_interrupt() and calls
step() normally. Once IFF1 permits it (including the real one-instruction EI
delay), step() returns the interrupt lifecycle timing and transfers control:
- IM 1 enters
0x0038in 13 T-states, which is the standard arcade-board case; - IM 2 reads a two-byte target through
Iplus the supplied device vector byte in 19 T-states; and - IM 0 intentionally supports device-supplied
RSTopcodes only.
Acceptance pushes the instruction-boundary PC, clears both interrupt flip-flops, and
wakes a halted CPU. A masked request remains pending until accepted or explicitly
removed with clear_maskable_interrupt(). This is a lifecycle abstraction, not a
cycle-accurate interrupt-acknowledge bus model.
See the interrupt lifecycle contract for exact mode, timing, and scope details.
Non-maskable interrupts
Hosts request an NMI with request_non_maskable_interrupt(). It is accepted at the
next instruction boundary regardless of IFF1 or EI delay, takes priority over a
maskable request, wakes HALT, pushes the boundary PC, copies IFF1 to IFF2,
clears IFF1, and enters 0x0066 in 11 T-states. A pending NMI can be inspected
through non_maskable_interrupt_pending or cancelled with
clear_non_maskable_interrupt().
Development and validation
python -m pip install -e ".[dev]"
python -m pytest -q
python -m ruff check .
python examples/minimal_z80_host.py
The vector corpus and ZEX binaries are external artifacts, intentionally not
bundled in releases. Fetch the pinned vector corpus with
python scripts/fetch_test_vectors.py, then rerun the test suite. See
validation evidence for ZEX instructions.
Project records
- Validation evidence and scope
- Undocumented behavior notes
- AI-assisted development and validation
- Extraction provenance
- Contribution guidance
License
MIT. External validation artifacts have their own licenses and are not bundled.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file z80_python-0.1.3.tar.gz.
File metadata
- Download URL: z80_python-0.1.3.tar.gz
- Upload date:
- Size: 99.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0885588c295453877800983ac3273cd3c1fc8900ba58e0ac482dafd03e279f4e
|
|
| MD5 |
23b3493075e34e5d456ab3e3c55da710
|
|
| BLAKE2b-256 |
9635a1739bb90941ce93a3190e8251b9c44e06a0d05648a7beddc379656e6f24
|
File details
Details for the file z80_python-0.1.3-py3-none-any.whl.
File metadata
- Download URL: z80_python-0.1.3-py3-none-any.whl
- Upload date:
- Size: 21.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b8bf15cf1065e162098b7927f0fefb09bebe2d2fc3b076d4cfba6996aef72b0a
|
|
| MD5 |
baecbe282149b0b6acee5849e2c1f70b
|
|
| BLAKE2b-256 |
83c1fabf5065107c8e7fc6e45272920d90df9f402f92e796ce6d43f397f0ff8d
|