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.
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.
| Stufe | Name | Operative Definition |
|---|---|---|
| N1 | Pro Instruktion gezählt | Die Zyklensumme pro Opcode ist exakt; die Peripherie rückt nach der Instruktion paketweise vor. |
| N2 | Buszyklus-geordnet | Jeder echte Buszugriff fällt in den richtigen Zyklus innerhalb der Instruktion; interne Zyklen werden am Ende nachgeholt; IRQ an Instruktionsgrenzen. |
| N3 | Zyklusweise | Die Fortschrittseinheit der Maschine ist der Zyklus: eine Busaktion pro Zyklus (Dummy-Zugriffe eingeschlossen), Peripherie im Gleichschritt; IRQ/NMI im vorletzten Zyklus. |
| N4 | Subzyklus (φ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
- VIA 6522: zählt im Zyklustakt; Timer 1 hat seine Periode N+2 zurück (das alte Modell lieferte N: 20 % Fehler bei N=10).
- ULA: holt eine Zelle von 6 Pixeln pro Zyklus, in dem Moment, in dem der Strahl sie liest — ein Schreibzugriff mitten in der Zeile erreicht nur die noch nicht abgetasteten Zellen; Raster-Splits werden möglich.
- PSG AY-3-8910: läuft mit Takt/8 (125 kHz) und integriertem Ausgang — ein oberhalb von Nyquist programmierter Ton wird gedämpft statt gespiegelt; die Hüllkurve, seit jeher doppelt zu langsam, läuft in der richtigen Geschwindigkeit; die analoge Ausgangsstufe wurde vom offiziellen Schaltplan übernommen.
- WD1793: meldet
LOST DATAund Schreibschutz. - Kassette auf Signalebene: ungerade Rahmenparität, nicht gerade.
3. Was sichtbar anders wird
- Raster-Splits: ein Programm, das mitten in der Zeile Tinte oder Modus wechselt, bekommt, was die Hardware liefern würde, keine gleichförmige Zeile.
- Der Klang: Hüllkurven in richtiger Dauer, kein Phantompfeifen mehr bei sehr hohen Tönen, zentriertes Signal (Gleichanteil gesperrt wie auf der Platine).
- Die IRQs: ein Interrupt kann nicht mehr vor der laufenden Instruktion angenommen
werden;
SEIschützt die folgende Instruktion nicht vor einem bereits anstehenden IRQ. - Register mit zerstörendem Lesen: ein BASIC-
POKEin das Datenregister einer ACIA verliert ein Byte, weilSTA (zp),Yvor dem Schreiben einen Dummy-Lesezugriff macht — im Emulator wie an der Maschine. Phosphoric war nachsichtiger als die Hardware; das ist es nicht mehr. - Savestates: ein mitten im Bild aufgenommener Zustand setzt genau dort fort, wo er angehalten hat (Strahlposition, Timer, Interrupt-Abtastung).
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
- 2.0.1 — WebAssembly-Build repariert (seit dem macOS-Port defekt), Online-Seite in 2.0 neu gebaut.
- 2.0.2 — Reihenfolge CPU/ULA innerhalb des Zyklus nach Mike Browns Unofficial ULA Guide korrigiert: der 6502 greift zuerst zu, die ULA holt danach denselben Count; jeder Raster-Split lag eine Zelle (6 Pixel) zu weit rechts. Der Vorbehalt „horizontaler Bezug nicht kalibriert“ ist aufgehoben.
- 2.0.3 — Hauptschleife in benannte Schritte zerlegt (1 296 → 126 Zeilen), keine sichtbare Änderung.
- 2.0.4 — LOCI-Kosimulation: die F8-Taste hat nur noch einen Besitzer (die kosimulierte Firmware); langer Druck = Diagnose-ROM von Mike Brown.
Testsuite: 1 208 Tests in 60 Suiten, 100 % grün; automatisches Release bei Tag (Linux-Binary, Windows-Zip); Browserversion online.
Quellen & Links
- Phosphoric-Repository: Codeberg · Framagit · GitHub
- Genauigkeitsskala und Stand je Komponente: docs/ACCURACY.md
- Ursprüngliche technische Notiz (Französisch): docs/articles/v2-cycle-stepped.md
- 6502-Orakel: SingleStepTests/65x02; Funktionstest von Klaus Dormann.
- Mike Brown, ORIC 1/ATMOS Unofficial ULA Guide: oric.free.fr · oric.signal11.org.uk
- Online ausprobieren: WebAssembly-Version