# hg80 — Mercury
A Z80 and Z80N CPU core, stepped one clock edge at a time.
hg80 is a hardware model, not 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.
`tick()` advances one clock edge — half a T-state. That's what makes the bus edges visible at the
granularity real hardware presents them. An address becomes valid, `MREQ` and `RD` assert, the data
bus is latched on the falling edge, each on its own tick.
`step()` runs to the next instruction boundary and returns the T-states taken, for callers that
don't need that detail.
**Drive with `step()` unless you need the bus edges.** The two paths cost very different amounts:
`tick()` works at half-T-state granularity and pays for it, while `step()` runs an instruction at
a time and is the path that has been optimised. Across the benchmark workloads — the same
programs, the same T-state counts — `step()` costs roughly ten to thirty times less, the ratio
depending on which instructions the workload runs. Absolute figures vary with the machine; the gap
does not close. `tick()` is what a debugger or a contended-memory host needs; `step()` is what a
machine runs on.
## Using it
```toml
[dependencies]
hg80 = "1"
```
The core holds processor state and nothing else. Memory, ports and the signals reaching the CPU
come through the `Host` trait, which you implement:
```rust
use hg80::{Cpu, Host};
struct Machine {
memory: [u8; 0x10000],
}
impl Host for Machine {
fn read(&mut self, address: u16, _at: u32) -> u8 {
self.memory[address as usize]
}
fn write(&mut self, address: u16, value: u8, _at: u32) {
self.memory[address as usize] = value;
}
fn input(&mut self, _port: u16, _at: u32) -> u8 {
0xFF
}
fn output(&mut self, _port: u16, _value: u8, _at: u32) {}
}
let mut cpu = Cpu::new();
let mut machine = Machine { memory: [0; 0x10000] };
cpu.reset();
let t_states = cpu.step(&mut machine);
```
`at` is the transfer's position within its machine cycle, in T-states. That is what lets a host
apply contention or wait states at the moment the hardware would. `fetch()` takes opcode fetches
and defaults to `read()`, so override it only if you need to tell them apart.
`serde` is an optional feature, adding `Serialize` and `Deserialize` to the state types.
## What this crate does not do
No memory, no ports, no machine — those are yours, behind `Host`. Nothing here knows about a
Spectrum, a Next, or any other hardware, and the Z80N instructions with effects outside the CPU
surface as tokens for the host rather than being acted on.
The core is `no_std` and `#![forbid(unsafe_code)]`, so it brings no allocator and no platform
assumptions.
## Why edge granularity
Anything that watches the bus mid-instruction needs the CPU to show the same intermediate states
the hardware does.
- **Contention and wait states.** A host that stalls the CPU on particular addresses at particular
moments can only do it if it's asked at the exact T-state of the access.
- **Cycle-exact comparison.** A core that advances an edge at a time can be diffed against an HDL
simulation signal for signal, instead of only at instruction boundaries.
- **Debuggers.** Stepping a bus cycle, not just an instruction.
## Z80 and Z80N
`set_z80n_enabled()` switches between a plain Z80 and the Z80N superset 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 can model either part.
The Z80N instructions with effects outside the CPU don't reach outside the core themselves. They
surface as command tokens carrying their operand, and the host does the work. That keeps the core
free of any particular machine's memory-management or register-file details.
See [docs/Z80N.md](docs/Z80N.md) for what the extended set does, measured rather than quoted.
## Status
Production ready. The decode, ALU, register file and sequencer are all in place, along with the
Z80N instructions and their tokens, and the core runs real software rather than test programs: it
is the CPU of the Bizmuth ZX Spectrum Next emulator, written for it and maintained alongside it —
a sibling project rather than a dependency picked off the shelf.
Four oracles check it:
- **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 where it disagrees with the
hardware about the program counter during a halt. Each is named in the harness with its reason.
- **Cycle-by-cycle 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.
## What isn't distributed
The test vectors are GPL-2 licensed, so they aren't distributed here. That test reports itself
skipped when they are absent. See [CONTRIBUTING.md](CONTRIBUTING.md) for how to supply them.
The HDL comparison needs the reference VHDL, which also isn't distributed here. Its results are
committed, so the comparison runs either way.
## Acknowledgements
hg80 rests on work other people did first.
- **Daniel Wallner**, for the T80 Z80-compatible core (2001–2002, OpenCores) — the design this one
is derived from, and the reason a hardware model was a reasonable thing to attempt at all.
- **The ZX Spectrum Next Project** — Fabio Belavenuto, Victor Trucco, Charlie Ingley, Garry
Lancaster and ACX — for the T80N modifications (2020), which are where the Z80N instructions and
the Next's timing come from.
- **The [Fuse](https://sourceforge.net/projects/fuse-emulator/) emulator project**, whose published
Z80 test vectors are one of the four oracles this core is checked against. Nothing here is
derived from Fuse: the vectors are used as expected-output data, and are not redistributed with
this crate.
The T80 and T80N work is redistributed under a three-clause BSD licence — see [NOTICE](NOTICE),
which is the licence statement this section does not replace.
## Licence
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
- MIT license ([LICENSE-MIT](LICENSE-MIT))
at your option.
This crate contains work derived from the T80 core and its T80N modifications, redistributed under
a three-clause BSD licence — see [NOTICE](NOTICE).
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the
work by you, as defined in the Apache-2.0 licence, shall be dual licensed as above, without any
additional terms or conditions.
[CONTRIBUTING.md](CONTRIBUTING.md) describes how to submit one, and what happens to it afterwards —
which is not quite what most projects do, so it is worth reading before you spend effort.