Accuracy, word by word
In September 2026, Phosphoric withdrew a claim it could not keep. It replaced it with a verifiable scale, where each word is backed by a test that would make it fall.
On this page
A withdrawn claim
Up to version 1.120, the project presented itself as “exact to the cycle”. An audit of 10 September 2026 showed that this was not true in the sense reference emulators give the word. The 6502 core ran an instruction in one block: accesses came out in the right order and the cycle total was right, but internal cycles were caught up at the end of the instruction, dummy accesses did not exist, and interrupts were taken between two instructions. The VIA received batches of cycles, the ULA drew a line in one go, the PSG ran at the audio output rate.
That is an honourable level, which the project named N2. But it is not “exact to the cycle”. The project withdrew the wording, wrote a scale, added an automatic safeguard, then made version 2.0.
The scale
| Level | Name | Definition | The test that proves it |
|---|---|---|---|
| N1 | counted per instruction | the cycle total per opcode is right; peripherals move in batches | compare the counters with the official table |
| N2 | ordered per bus cycle | each real access falls on the right cycle; internal cycles are caught up; IRQ at boundaries | observe a register with side effects |
| N3 | stepped per cycle | the machine moves one cycle: one access per cycle, dummy accesses included; everything moves together; IRQ on the penultimate cycle | compare the bus trace, cycle by cycle, with an external oracle |
| N4 | below the cycle | the cycle is split into φ1 and φ2 phases; races between cards and bus are modelled | a race predicate and reproducible edge cases |
Each component, at its level
| Component | Level | What is missing |
|---|---|---|
| 6502 | N3 | nothing identified: 100.00% on 2,440,000 cases; the only accepted divergence, the 12 JAM opcodes stop the processor instead of locking the bus |
| VIA 6522 | N3 for the timers | the half cycle of the one-shot mode (N + 1.5) |
| ULA | N3 for the fetch | border and blanking; the absolute scan/processor phase; the full-screen ULA-NG modes, rendered per line |
| PSG | clocked at hardware rate | the value of the coupling capacitor, unreadable on the schematic |
| WD1793 | N1+ | fixed delays, flat image: no real byte loss nor CRC |
| Tape | N3 at signal level | the default remains the patched ROM, by choice |
| LOCI bus | partial N4 | the constants are not yet calibrated on hardware |
The VIA, measured on a real 6522
Since version 2.6.0, the VIA’s behaviour takes six fixes measured on a real 6522, brought over
from the Neo6502Vic20 project. There they were validated by 61 VIC-20 test programs out of 61, taken
from the VICE emulator, whose reference results come from the hardware: countdown starting on the
cycle after the write, one-shot reloaded, delayed T2 mode, 8-bit T2 for the shift register, PB7
without DDRB. These programs do not yet run in Phosphoric’s CI; there the rules are locked by
make test-io.
Version 2.7.0 added an exact lazy path: a cycle where nothing can happen is only counted, then
applied in one block before the next access. The behaviour stays that of stepping cycle by cycle:
make test-via-lazy replays 116 command lines against a build without this path, with no difference.
The gain is 20 to 30%.
Allowed words, refused words
| Wording | Status |
|---|---|
| “CPU core exact to the cycle” | ✅ since 1.124: 100% exact bus sequence, interrupts on the penultimate cycle |
| “cycle-stepped machine” | ✅ since 2.0.0-alpha.8: the master clock moves everything, never empty, and a save state is an exact resume point |
| “bus-cycle accurate” for the whole machine | ✅ |
| “exact cycle counters per opcode (256/256)” | ✅ |
| “exact to the cycle” alone, for the whole machine | ❌ the floppy controller is N1+, the VIA half cycle and the absolute phase are not modelled |
| “WD1793 exact to the cycle” | ❌ say “WD1793 clocked in cycles, flat image model” |
The make test-docs-claims safeguard refuses any unqualified wording in the repository’s showcase
documents. This wiki follows the same rule.
Dummy accesses reach the registers
This is the most surprising consequence of level N3, and it is intended: a dummy cycle is a real bus cycle, and a chip does not know that it is a dummy.
The same precautions apply to any register with destructive reads: ACIA 6551 and 6850, FIFO registers. Before 2.0, Phosphoric did not issue these accesses: it was more lenient than the hardware.
What was learned along the way
Two defects would not have been seen without changing method:
- The PSG envelope, declared conformant by a recomputation that assumed 16 states instead of 32. A wrong assumption does not show on re-reading, only when the signal is measured.
- The phantom cycle of a branch not taken. The processor oracle was green at 100%, but the master clock was called once too often, about 410 times per frame. An oracle that only looks at the processor does not prove the machine is synchronised. A save-state determinism test revealed it.
The cost
Going cycle by cycle cost, on the reference machine at full speed, from 491 to 611 µs per emulated
frame: 3% of the 20 ms budget. make test-bench now refuses any overrun beyond 5% of that budget.
Datasheet audits
On top of the scale, the repository keeps an audit of conformance to datasheets
(docs/HARDWARE_CONFORMANCE.md): each core is re-read line by line against the manufacturer’s
datasheet. Each difference is classed as fixed (with its test) or accepted deviation (with its
reason). For example, for the WD1793: Read Address wrote the wrong byte into the sector register;
the step commands ignored the track update flag; Force Interrupt always raised an interrupt, whereas
only the D8 variant does. All three are fixed.