Tachibana

Phosphoric › Artikelen › Phosphoric 2.0

Phosphoric 2.0: een cyclusgestuurde machine — en de claim die we intrekken

Wat we hadden gezegd en waarom het onjuist was, wat V2 veranderde, wat zichtbaar verandert — en wat we nog steeds niet claimen.

Door bmarty · Phosphoric 2.0.4 · 12 september 2026

Tot 1.120.0-alpha presenteerde Phosphoric zich als „cycle-accurate”. Dat was niet waar in de zin die referentie-emulatoren aan dat woord geven. Dit artikel zegt wat is ingetrokken, wat in de plaats is gebouwd en waar de claim ophoudt. Namen van tests en opties zijn die van de repository.

1. Wat we hadden gezegd, en waarom het onjuist was

De 6502-kern voerde een instructie als één blok uit: de bustoegangen kwamen in de juiste volgorde en het totaal aantal cycli per opcode klopte, maar de interne cycli werden aan het einde van de instructie opgevuld, de NMOS-dummytoegangen bestonden niet, en interrupts werden op instructiegrenzen genomen. De VIA kreeg batches cycli, de ULA renderde een hele lijn in één keer, de PSG draaide op de audiobemonsteringsfrequentie.

Dat is een eerbaar niveau — we noemden het N2, „geordend op buscyclus” — maar het is niet „cyclusexact”. We hebben de formulering ingetrokken, een verifieerbare schaal geschreven (docs/ACCURACY.md: N1 tot N4, elk niveau gestaafd door de test die het bewijst), en een automatische bewaker (make test-docs-claims) die elke ongekwalificeerde „cycle-accurate” in de etalagedocumenten weigert. Daarna hebben we V2 gemaakt.

NiveauNaamOperationele definitie
N1Geteld per instructieHet totaal aantal cycli per opcode is exact; de periferie gaat na de instructie in batches vooruit.
N2Geordend op buscyclusElke echte bustoegang valt op de juiste cyclus binnen de instructie; interne cycli worden aan het einde opgevuld; IRQ op instructiegrenzen.
N3Stap voor stap per cyclusDe eenheid van vooruitgang is de cyclus: één busactie per cyclus (dummytoegangen inbegrepen), periferie in gelijke tred; IRQ/NMI op de voorlaatste cyclus.
N4Subcyclus (φ1/φ2)De cyclus is onderverdeeld; setup/hold-races tussen kaarten en bus worden gemodelleerd.

2. Wat V2 veranderde

Eerst een orakel

Vóór de kern werd aangeraakt speelt make test-cycle de SingleStepTests/65x02-vectoren af — 10 000 gevallen per opcode, met de verwachte busspoor cyclus per cyclus — en scoort vier eigenschappen apart (eindtoestand, cyclustotaal, bussubsequentie, exacte bussequentie). Het gemeten en gepubliceerde vertrekpunt: 44,26 % exacte bussequenties. Het orakel bracht bovendien vijf logische fouten in de kern aan het licht, verholpen nog vóór het begin.

Een micro-gesequencede kern

Elke instructie wordt een plan van micro-operaties, één per cyclus, die elk precies hun bustoegang doen — inclusief de dummytoegangen (zero-page-indexering, paginaovergang, RMW-terugschrijven, dode stacklezingen). Resultaat: 100,00 % over 2 440 000 gevallen. Interrupts worden bemonsterd op de voorlaatste cyclus, wat gratis de vertraagde I-vlag van CLI/SEI/PLP en het kapen van een BRK door een NMI oplevert. Beide kernen delen dezelfde berekening (vlaggen, BCD, illegale opcodes): alleen de sequencing verschilt. De oude blijft beschikbaar (--cpu-legacy).

Een masterklok

emu_cycle() laat de hele machine één cyclus vooruitgaan, in een vaste volgorde: de CPU doet zijn bustoegang, de periferie gaat één cyclus vooruit — nooit een batch —, dan fetcht de ULA de cel van diezelfde cyclus. Dat is de volgorde die Mike Brown op de hardware heeft gemeten; tot 2.0.1 ging de ULA eerst, en viel elke split één cel te ver naar rechts. De hoofdlus berekent niets meer: ze vraagt.

De componenten, één voor één

3. Wat zichtbaar verandert

4. Wat we onderweg hebben geleerd

Twee fouten zouden nooit zijn gezien zonder van methode te veranderen.

De PSG-envelop was „door herberekening” conform verklaard — een herberekening die 16 toestanden aannam in plaats van 32. Een onjuiste aanname is onzichtbaar bij herlezing; ze toont zich alleen door het signaal te meten. De audiotests meten nu frequenties en duren in plaats van bytes te vergelijken.

De tweede is nog leerzamer. Een niet-genomen sprong nam zijn beslissing één cyclus te laat, in een micro-op zonder bustoegang. De CPU-teller bleef juist, dus het orakel was 100 % groen. Maar de masterklok was één keer extra aangeroepen: de ULA ging een cyclus vooruit die noch de CPU noch de VIA had beleefd — ongeveer 410 keer per frame op de BASIC-ROM, ofwel één frame drift per seconde tussen het beeld en de rest van de machine. Een orakel dat alleen naar de CPU kijkt bewijst de synchronisatie van de machine niet. Wat het aan het licht bracht: een determinismetest van de savestates, die eiste dat rasterstops op exact dezelfde cyclus vielen.

5. Wat we niet zeggen

„Phosphoric is cyclusexact” — nee. De FDC wordt nog steeds getimed door vaste vertragingen op een plat beeld (geen MFM-stroom, dus geen echt byteverlies of CRC). De horizontale referentie is daarentegen geen conventie meer: de (gemeten) ULA-teller zet kolom 0 op count 0 — maar de absolute fase tussen die teller en de CPU is op een echte ORIC alleen waarneembaar met de „VSYNC hack”, die we niet emuleren. De halve cyclus van de VIA-one-shot is niet gemodelleerd. Het standaard laden van cassettes blijft de ROM-patch, uit keuze: dezelfde inhoud geladen, 2,4× minder cycli.

De exacte toegestane formulering, en de test die elke regel ervan zou laten vallen, staan in docs/ACCURACY.md.

6. Kosten

De overstap naar de cyclus kostte, op de referentiemachine op volle snelheid, 491 → 611 µs per geëmuleerd frame: 3 % van het budget van 20 ms. make test-bench weigert nu elke overschrijding boven 5 %.

7. Sinds 2.0.0

Testsuite: 1 208 tests in 60 suites, 100 % groen; automatische release bij tag (Linux-binary, Windows-zip); browserversie online.

Bronnen & links

← Phosphoric · Functies →