Tachibana

Phosphoric › Artikel › Phosphoric 2.0

Phosphoric 2.0: eine zyklusgetaktete Maschine — und die Aussage, die wir zurücknehmen

Was wir gesagt hatten und warum es falsch war, was V2 geändert hat, was sichtbar anders wird — und was wir weiterhin nicht behaupten.

Von bmarty · Phosphoric 2.0.4 · 12. September 2026

Bis 1.120.0-alpha stellte sich Phosphoric als „cycle-accurate“ vor. Das war nicht wahr in dem Sinn, den Referenzemulatoren diesem Wort geben. Dieser Artikel sagt, was zurückgenommen wurde, was stattdessen gebaut wurde und wo die Aussage endet. Test- und Optionsnamen sind die des Repositorys.

1. Was wir gesagt hatten, und warum es falsch war

Der 6502-Kern führte eine Instruktion als Block aus: die Buszugriffe kamen in der richtigen Reihenfolge und die Zyklensumme pro Opcode stimmte, aber die internen Zyklen wurden am Ende der Instruktion durch Auffüllen nachgeholt, die NMOS-Dummy-Zugriffe existierten nicht, und Interrupts wurden an Instruktionsgrenzen angenommen. Die VIA erhielt Zyklenpakete, die ULA renderte eine ganze Zeile auf einmal, der PSG lief mit der Audio-Abtastrate.

Das ist ein achtbares Niveau — wir nannten es N2, „buszyklus-geordnet“ — aber es ist nicht „zyklusexakt“. Wir haben die Formulierung zurückgenommen, eine überprüfbare Skala geschrieben (docs/ACCURACY.md: N1 bis N4, jede Stufe gestützt auf den Test, der sie beweist), und eine automatische Sperre (make test-docs-claims), die jedes unqualifizierte „cycle-accurate“ in den Schaufensterdokumenten ablehnt. Dann haben wir V2 gebaut.

StufeNameOperative Definition
N1Pro Instruktion gezähltDie Zyklensumme pro Opcode ist exakt; die Peripherie rückt nach der Instruktion paketweise vor.
N2Buszyklus-geordnetJeder echte Buszugriff fällt in den richtigen Zyklus innerhalb der Instruktion; interne Zyklen werden am Ende nachgeholt; IRQ an Instruktionsgrenzen.
N3ZyklusweiseDie Fortschrittseinheit der Maschine ist der Zyklus: eine Busaktion pro Zyklus (Dummy-Zugriffe eingeschlossen), Peripherie im Gleichschritt; IRQ/NMI im vorletzten Zyklus.
N4Subzyklus (φ1/φ2)Der Zyklus ist unterteilt; Setup/Hold-Wettläufe zwischen Karten und Bus werden modelliert.

2. Was V2 geändert hat

Zuerst ein Orakel

Bevor der Kern angefasst wurde, spielt make test-cycle die SingleStepTests/65x02-Vektoren ab — 10 000 Fälle pro Opcode, mit der erwarteten Busspur Zyklus für Zyklus — und bewertet vier Eigenschaften getrennt (Endzustand, Zyklensumme, Bus-Teilsequenz, exakte Bussequenz). Der gemessene, veröffentlichte Ausgangspunkt: 44,26 % exakte Bussequenzen. Das Orakel deckte zudem fünf Logikfehler des Kerns auf, die noch vor dem Start behoben wurden.

Ein mikrosequenzierter Kern

Jede Instruktion wird ein Plan aus Mikrooperationen, eine pro Zyklus, jede mit genau ihrem Buszugriff — einschließlich der Dummy-Zugriffe (Zero-Page-Indizierung, Seitenüberschreitung, RMW-Rückschreiben, tote Stack-Lesezugriffe). Ergebnis: 100,00 % über 2 440 000 Fälle. Interrupts werden im vorletzten Zyklus abgetastet, was das verzögerte I-Flag von CLI/SEI/PLP und das Kapern eines BRK durch einen NMI gratis liefert. Beide Kerne teilen dieselbe Berechnung (Flags, BCD, illegale Opcodes): nur die Sequenzierung unterscheidet sich. Der alte bleibt verfügbar (--cpu-legacy).

Ein Master-Takt

emu_cycle() lässt die ganze Maschine um einen Zyklus vorrücken, in fester Reihenfolge: die CPU macht ihren Buszugriff, die Peripherie rückt um einen Zyklus vor — nie um ein Paket —, dann holt die ULA die Zelle ebendieses Zyklus. Das ist die von Mike Brown an der Hardware gemessene Reihenfolge; bis 2.0.1 kam die ULA zuerst, und jeder Split lag eine Zelle zu weit rechts. Die Hauptschleife rechnet nichts mehr: sie fragt.

Die Komponenten, eine nach der anderen

3. Was sichtbar anders wird

4. Was wir unterwegs gelernt haben

Zwei Fehler wären ohne Methodenwechsel nie gesehen worden.

Die PSG-Hüllkurve galt „durch Nachrechnen“ als konform — ein Nachrechnen, das 16 statt 32 Zustände annahm. Eine falsche Annahme ist beim Wiederlesen unsichtbar; sie zeigt sich nur beim Messen des Signals. Die Audiotests messen jetzt Frequenzen und Dauern, statt Bytes zu vergleichen.

Der zweite ist noch lehrreicher. Ein nicht genommener Sprung traf seine Entscheidung einen Zyklus zu spät, in einer Mikrooperation ohne Buszugriff. Der CPU-Zähler blieb richtig, also war das Orakel zu 100 % grün. Aber der Master-Takt war einmal mehr aufgerufen worden: die ULA rückte um einen Zyklus vor, den weder CPU noch VIA erlebt hatten — etwa 410-mal pro Bild auf der BASIC-ROM, also ein Bild Drift pro Sekunde zwischen dem Bild und dem Rest der Maschine. Ein Orakel, das nur auf die CPU schaut, beweist die Synchronisation der Maschine nicht. Aufgedeckt hat es ein Determinismustest der Savestates, der verlangte, dass Raster-Stopps in exakt demselben Zyklus landen.

5. Was wir nicht sagen

„Phosphoric ist zyklusexakt“ — nein. Der FDC wird weiterhin durch feste Verzögerungen auf einem flachen Abbild getaktet (kein MFM-Strom, also kein echter Byteverlust und keine CRC). Der horizontale Bezug dagegen ist keine Konvention mehr: der (gemessene) ULA-Zähler setzt Spalte 0 auf Count 0 — aber die absolute Phase zwischen diesem Zähler und der CPU ist an einem echten ORIC nur mit dem „VSYNC-Hack“ beobachtbar, den wir nicht emulieren. Der halbe Zyklus des VIA-One-Shots ist nicht abgebildet. Das Kassettenladen bleibt standardmäßig der ROM-Patch, aus Entscheidung: gleicher Inhalt geladen, 2,4× weniger Zyklen.

Die genaue zulässige Formulierung und der Test, der jede ihrer Zeilen widerlegen würde, stehen in docs/ACCURACY.md.

6. Kosten

Der Übergang zum Zyklus kostete auf der Referenzmaschine bei voller Geschwindigkeit 491 → 611 µs pro emuliertem Bild: 3 % des 20-ms-Budgets. make test-bench lehnt jetzt jede Überschreitung über 5 % ab.

7. Seit 2.0.0

Testsuite: 1 208 Tests in 60 Suiten, 100 % grün; automatisches Release bei Tag (Linux-Binary, Windows-Zip); Browserversion online.

Quellen & Links

← Phosphoric · Funktionen →