83 lines
4.4 KiB
Markdown
83 lines
4.4 KiB
Markdown
|
|
# Z80 Reference Implementations — Cross-Validation
|
|||
|
|
|
|||
|
|
Three permissively-licensed Z80 cores verified live. **No code is being copied — references only.** GPL projects (MAME, RetroArch z80 cores, redcode/Z80) excluded.
|
|||
|
|
|
|||
|
|
## A. `floooh/chips` — `chips/z80.h`
|
|||
|
|
|
|||
|
|
- URL: https://github.com/floooh/chips/blob/master/chips/z80.h
|
|||
|
|
- License: **zlib/libpng** (SPDX `Zlib`, © 2018 Andre Weissflog) — Apache-compatible.
|
|||
|
|
- Single-header C99, **2,838 LOC**. Cycle-accurate (tick-by-tick `uint64_t` pin-bus). Decoder is **table-driven, generated by an offline Python script** (`codegen/z80_gen.py`).
|
|||
|
|
- ZEXDOC + ZEXALL pass via `chips-test`.
|
|||
|
|
- Caveat: pin-bitmask API is unusual. Lifting flag tables / undoc rules is fine; structure is non-idiomatic.
|
|||
|
|
|
|||
|
|
## B. `superzazu/z80`
|
|||
|
|
|
|||
|
|
- URL: https://github.com/superzazu/z80
|
|||
|
|
- License: **MIT** (© 2019 Nicolas Allemand).
|
|||
|
|
- C99, `z80.c` is **1,773 LOC**, conventional read/write callback API.
|
|||
|
|
- **prelim, ZEXDOC (PASS), ZEXALL (PASS)** with cycle counts identical to hardware (5,764,169,747 instructions, 0 diff). Same author as `superzazu/8080`.
|
|||
|
|
- Caveat: cycles counted at instruction granularity, not per-T-state.
|
|||
|
|
|
|||
|
|
## C. `kosarev/z80`
|
|||
|
|
|
|||
|
|
- URL: https://github.com/kosarev/z80
|
|||
|
|
- License: **MIT** (© 2017–2026 Ivan Kosarev).
|
|||
|
|
- C++11 single-header, optional Python bindings. Machine-cycle level; all undoc opcodes/flags/registers. Passes `cputest`, `8080pre`, `8080exer`, `8080exm`, `prelim`, **`zexall`**.
|
|||
|
|
- Caveat: heavy CRTP; harder to read for "where does this flag get set" — good cross-check authority, poor teaching reference.
|
|||
|
|
|
|||
|
|
(Rejected: `kirjavascript/z80` MIT but small/less-tested; redcode GPL; MAME GPLv2; RetroArch GPL.)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Edge cases
|
|||
|
|
|
|||
|
|
### α. DAA + N flag
|
|||
|
|
|
|||
|
|
All three sources agree: select `diff` from (C, H, N, A_high, A_low) lookup; **add if N=0, subtract if N=1**. Canonical form (matches [Y] §4.7 tables and zexall):
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
t = 0
|
|||
|
|
if H or (A & 0x0F) > 9: t |= 0x06
|
|||
|
|
if C or A > 0x99: t |= 0x60; C' = 1
|
|||
|
|
if N == 0: H' = ((A & 0x0F) > 9); A' = A + t
|
|||
|
|
else: H' = (H and (A & 0x0F) < 6); A' = A − t
|
|||
|
|
S' = A'.7; Z' = (A'==0); P' = parity(A'); Y' = A'.5; X' = A'.3; N' = N
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Disagreement: [Y]'s H rule for N=1 differs from [U] p. 166's plain "borrow from bit 4" — [Y] is what zexall checks.
|
|||
|
|
|
|||
|
|
### β. Block-instruction P/V on the LAST iteration
|
|||
|
|
|
|||
|
|
LDIR (and LDI/LDD/LDDR/CPI/CPD/CPIR/CPDR): **P/V = (BC ≠ 0 after decrement)**. Final iteration BC=0 → P/V=0; intermediate iterations P/V=1. [U] p. 78: "if the byte counter decrements to 0, the flag is cleared to 0, otherwise the flag is set to 1." Both refs match. **No real disagreement** — the trap is forgetting LDIR re-executes (PC -= 2), so P/V is updated every iteration.
|
|||
|
|
|
|||
|
|
### γ. NEG aliases
|
|||
|
|
|
|||
|
|
Confirmed 8 ED-prefixed NEG opcodes ([Y] §3.4 p. 12): **ED 44, 4C, 54, 5C, 64, 6C, 74, 7C**. Only ED 44 documented; the seven others are aliases (marked `**` in [Y]) with identical behaviour.
|
|||
|
|
|
|||
|
|
### δ. Undocumented IXH / IXL (and IY equivalents)
|
|||
|
|
|
|||
|
|
[Y] §3.2 p. 11–12: with DD prefix, **H → IXH, L → IXL** *unless* the same instruction also references (IX+d) (then H/L revert to themselves). Specifics:
|
|||
|
|
|
|||
|
|
- `LD r,r'` with r/r' ∈ {B,C,D,E,A,IXH,IXL} works (e.g. `LD B,IXH`, `LD IXH,IXL`); H/L cannot mix with IXH/IXL in one op.
|
|||
|
|
- `ADD/ADC/SUB/SBC/AND/OR/XOR/CP/INC/DEC` accept IXH or IXL.
|
|||
|
|
- 16-bit ops on HL, ops on (HL), and ops on (IX+d) leave IX unsplit.
|
|||
|
|
- FD prefix is the IY counterpart.
|
|||
|
|
- DDCB / FDCB ([Y] §3.5 p. 13): the low 3 bits of the second opcode byte select B/C/D/E/H/L/A as a *secondary* destination alongside (IX+d). BIT variants don't store back.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Discrepancies to watch in YOUR implementation
|
|||
|
|
|
|||
|
|
1. **F bits 3 & 5** are X / Y undoc flags; zexall tests them. [U] p. 76 calling them "Not Used" is misleading.
|
|||
|
|
2. **NMI does not copy IFF1 → IFF2** — [Y] §5.1 corrects [U]; only IFF1 clears.
|
|||
|
|
3. **IM 2 vector LSB is not forced to 0** — [Y] §7.1 corrects [U] Fig. 16.
|
|||
|
|
4. **Block I/O flags fully deterministic** ([Y] §4.3) despite [U] p. 272/282 saying "unknown".
|
|||
|
|
5. **R increments per prefix M1**: DDCB / FDCB still total +2 (the inner CB is not an M1).
|
|||
|
|
|
|||
|
|
## Verified URLs
|
|||
|
|
- https://raw.githubusercontent.com/floooh/chips/master/LICENSE — zlib/libpng
|
|||
|
|
- https://raw.githubusercontent.com/floooh/chips/master/chips/z80.h — exists, 2838 LOC
|
|||
|
|
- https://raw.githubusercontent.com/superzazu/z80/master/LICENSE — MIT
|
|||
|
|
- https://raw.githubusercontent.com/superzazu/z80/master/z80.c — 1773 LOC
|
|||
|
|
- https://raw.githubusercontent.com/kosarev/z80/master/LICENSE — MIT
|