Introducing hg80
hg80 (“Mercury”) is the CPU at the bottom of the toolchain — a Z80 and Z80N core, stepped one clock edge at a time. It’s the processor Bizmuth will run on, and as of 1.0.0 it’s a public Rust crate on crates.io.
What it is
hg80 is a hardware model rather than an interpreter. It follows the shape of the T80/T80N FPGA core it is derived from: a combinational microcode decode and ALU feeding a machine-cycle and T-state sequencer, with register writes committed on the clock edge.
That’s what tick() exposes. One call advances a single clock edge — half a T-state — so the bus transitions a real Z80 presents are each visible on their own tick: the address becoming valid, the control lines asserting, the data bus being latched. Anything watching the bus mid-instruction needs exactly that, whether it’s a contended-memory host applying wait states at the T-state the hardware would, a debugger stepping a bus cycle instead of an instruction, or a comparison against an HDL simulation signal by signal.
When you don’t need that detail, step() runs to the next instruction boundary and returns the T-states taken. It’s the optimised path and the one a machine should run on — across the benchmark workloads it costs roughly ten to thirty times less than tick(), the ratio depending on which instructions the workload runs.
Both flavours of the instruction set are there, switchable at run time. With Z80N off, the extended ED-prefixed opcodes decode as the no-operation forms a real Z80 gives them, so one instance models either part. The Z80N instructions with effects outside the processor surface as command tokens carrying their operand, leaving the core free of any particular machine’s memory-management or register-file details.
The core holds processor state and nothing else — no memory, no ports, no machine. Those come through a Host trait you implement. It’s no_std, brings no allocator, and is #![forbid(unsafe_code)].
How it’s checked
Four independent oracles, which is the part that took the time:
- Published test vectors — 1350 of 1356 pass. The other six are cases where the vectors’ expected event log omits a memory read the processor really performs, or disagrees with the hardware about the program counter during a halt. Each is named in the harness with its reason.
- T-state-for-T-state against an HDL simulation — 18 programs at four wait-state depths, comparing the machine cycle, T-state, address, cycle kind, the byte written, the halt output and the interrupt enable.
- Final register state — 185 seeded probes, each running one instruction and pushing every register pair so both sides read the result off the bus.
- Save and restore — a core paused at any half-T-state round-trips and resumes identically.
It also isn’t a core written to a spec and left there. It was written for the Bizmuth ZX Spectrum Next emulator and is developed alongside it, and it will be the CPU Bizmuth runs on — no released version of the emulator uses it yet.
Get it
[dependencies]
hg80 = "1"- crates.io/crates/hg80 — the crate.
- docs.rs/hg80 — the API documentation.
- codeberg.org/TwistedRaven/hg80 — the source.
Licensed MIT OR Apache-2.0, with the parts derived from the T80 core and its T80N modifications redistributed under a three-clause BSD licence. hg80 rests on work other people did first — Daniel Wallner’s T80 core, and the T80N modifications from the ZX Spectrum Next Project — and the repository’s NOTICE is the licence statement that says so properly.