A cycle-accurate Zilog Z80 CPU emulator library in Go. Zero dependencies, machine-agnostic, with per-T-state timing hooks precise enough to power machines that use the CPU itself for video generation (Galaksija, ZX80/81) or contended memory (ZX Spectrum).
▶ Try it in your browser — the emulator compiled to WebAssembly, stepping through the example program with live registers and memory.
▶ See it power a full machine — vremeplov, a Galaksija emulator built on gozilog, running in the browser complete with a built-in monitor/debugger.
The project is complete. Every documented and undocumented opcode implemented; all 1604 SingleStepTests files pass with full per-T-state cycle-trace assertions (address/data bus and control pins at every T-state); ZEXDOC and ZEXALL report all CRCs OK. See SPEC.md for the design, and AGENTS.md for how to build, test and continue development — if AI is your cup of tea.
go get github.com/mtrisic/gozilog # the library
go install github.com/mtrisic/gozilog/cmd/zrun@latest # headless runner
go install github.com/mtrisic/gozilog/cmd/zstep@latest # TUI stepper
npm install gozilog # JS/WASM binding (~120 KB gzipped)The npm package (gozilog,
see bindings/npm) wraps the emulator compiled to
WebAssembly with TinyGo and is differentially verified against the
reference Go build on every release.
Development happens exclusively inside the devcontainer: open the folder
in VSCode, "Reopen in Container" (first build downloads the ~280 MB test
suite), then press F5 — it assembles examples/hello_from_z80.asm
with pasmo and runs it in the emulator under the debugger.
From a terminal inside the container:
mkdir -p build
pasmo examples/hello_from_z80.asm build/hello_from_z80.bin
go run ./cmd/zrun -org 0x8000 build/hello_from_z80.bin # run to HALT, print RAM dump
go run ./cmd/zstep -org 0x8000 build/hello_from_z80.bin # interactive TUI stepper
go test ./... -short # tests incl. SingleStepTests
go test ./... # …plus ZEXDOC/ZEXALL (~4 min)
(cd cmd && go test ./...) # runner + golden-dump testzstep shows live registers (including WZ/MEMPTR, I, R, IM, IFFs and
decoded flags) and a memory view that can follow PC, HL or SP while you
single-step, run bursts, or run to HALT.
import "github.com/mtrisic/gozilog/z80"
// A machine is anything implementing z80.Bus (and optionally
// z80.Ticker for per-T-state timing, z80.IntAcker for INT vectors).
type Machine struct{ mem [65536]byte }
func (m *Machine) MemRead(a uint16) byte { return m.mem[a] }
func (m *Machine) MemWrite(a uint16, v byte) { m.mem[a] = v }
func (m *Machine) IORead(p uint16) byte { return 0xFF }
func (m *Machine) IOWrite(p uint16, v byte) {}
func main() {
m := &Machine{}
cpu := z80.New(m)
for !cpu.Halted() {
cpu.Step()
}
}The full embedder contract — including the timing model that lets a
machine implement video generation and memory contention — is documented
in the godoc of z80.Bus, z80.Ticker and in SPEC.md.
The sibling package
z80/disasm
turns machine code into mnemonic text for debugger frontends. It decodes
every byte sequence — documented and undocumented opcodes, all prefix
combinations — into exactly one instruction with a correct byte length,
so a disassembly window pointed at arbitrary memory never panics, stalls
or mis-frames what follows:
import "github.com/mtrisic/gozilog/z80/disasm"
ins := disasm.Decode(m.MemRead, cpu.State().PC)
fmt.Printf("%04X %s\n", ins.Addr, ins.Text) // e.g. "8003 LD A,(IX+05)"Decoded lengths are verified instruction-by-instruction against the CPU core's own PC advance, and the disassembly of the complete opcode space is pinned by a committed golden file.
This emulator would not be verifiable without the work of the retro-computing community:
- SingleStepTests/z80 — ~1.6 million per-instruction test cases with per-T-state bus traces; the primary correctness gate for this project.
- ZEXDOC / ZEXALL by Frank D. Cringle (binaries fetched from agn453/ZEXALL) — the classic instruction exercisers with CRCs recorded from real hardware.
- MEMPTR, esoteric register of the Zilog Z80 CPU by the zx.pk.ru research group ("Boo-boo"), English translation by Vladimir Kladov.
- The Undocumented Z80 Documented by Sean Young and Jan Wilmans.
- Tony Brewer's research on the Z80 special reset.
- redcode/Z80, ha1tch/zen80 and remogatto/z80, studied as behavioral references (no code was ported or copied from them).
This project is not affiliated with or endorsed by Zilog, Inc. Zilog and Z80 are trademarks of their respective owners. The name "gozilog" refers to the CPU this library emulates, not to the company.
This software is provided "as is", without warranty of any kind — see LICENSE for the full terms. Use at your own risk.
The library is a first-class WASM citizen: it compiles unmodified for
both official Go targets, and the full verification suite (all 1604
SingleStepTests files including per-T-state cycle traces) passes under
both runtimes. bash tools/check-wasm.sh (inside the devcontainer) is
the gate: build checks, the test suite under wasmtime (wasip1) and
Node (js), a cross-architecture determinism proof (the wasm-compiled
zrun reproduces the committed golden RAM dump), and a headless check
of the browser demo.
The browser demo is hosted at https://mtrisic.github.io/gozilog/demo/ — or run it locally:
cd examples/wasm
bash build.sh # assemble example, copy wasm_exec.js, compile to WASM
go run ./serve # → http://localhost:8080The demo page steps the CPU through hello_from_z80.asm with live
registers and memory (z80Load/z80Step/z80Run/z80State/z80Mem
exposed via syscall/js) — the seed of a browser-based machine emulator.
Only cmd/zstep stays native-only (it needs a real terminal).
The project site deploys automatically to GitHub Pages on every push
to master (.github/workflows/pages.yml + tools/build-site.sh:
this README rendered as the homepage, the demo under /demo/; the
workflow enables the Pages site itself on first run). The demo page
must be served over HTTP; opening index.html from disk is blocked by
browsers, and the page says so if you try.
Contributions are welcome — human and AI alike. The repo is built for both: AGENTS.md gives any contributor (carbon- or silicon-based) the working rules, the test suites make correctness non-negotiable, and the devcontainer means there is exactly one "works on my machine". Open an issue or a PR.
If gozilog is useful to you, star it so others can find it — and a fork is how the next machine emulator gets born. If you build something on top of it (a Galaksija? a Spectrum? something stranger?), please do tell.
MIT — see LICENSE. The test data downloaded by the devcontainer (SingleStepTests JSON, ZEX binaries) is not part of this repository and carries its authors' own licenses.
