SNES-Emulatoren nur wenige Pixel von der absoluten Perfektion entfernt


Wir sind so nah dran, einen Emulator zu entwickeln, der alle Funktionen von echter Hardware und SNES-Software perfekt nachbilden kann.

In den letzten 15 Jahren habe ich als Codierer für den bsnes- Emulator versucht, die Super Nintendo-Emulation zu perfektionieren, aber jetzt stehen wir vor dem letzten Problem: dem genauen Timing der Taktzyklen der SNES-Videoprozessoren. Um diese letzte Stufe der Emulationsgenauigkeit zu erreichen, ist die Hilfe der gesamten Community erforderlich, und ich hoffe auf Ihre Unterstützung. Aber zuerst werde ich Ihnen sagen, was wir bereits erreicht haben.

Aktuellen Zustand


Heute ist die Situation mit der SNES-Emulation sehr gut. Abgesehen von den ungewöhnlichen Peripheriegeräten, die der Emulation widerstehen (z. B. einem Golfschläger mit Lichtsensor , einem Fahrradsimulator und einem DFÜ-Modem, die in Japan für Pferderennen in Japan verwendet werden), sind alle offiziell lizenzierten SNES-Spiele vollständig spielbar. und kein Spiel hat offensichtliche Probleme.

Die SNES-Emulation wurde so präzise, ​​dass ich den Emulator sogar in zwei Versionen aufteilen musste : higan , das absolute Genauigkeit und Konsistenz mit der Hardwaredokumentation anstrebt , und bsnes , das Geschwindigkeit, breite Funktionen und Benutzerfreundlichkeit anstrebt.

In jüngster Zeit wurden auf dem Gebiet der SNES-Emulation viele interessante Erfolge erzielt, darunter:


… und vieles mehr!

Also ist es fertig? Haben alle gut gearbeitet, tschüss, und danke für den Fisch? Nicht ganz.

Heute haben wir bei fast allen SNES-Komponenten eine Genauigkeit auf Schlagniveau erreicht. Die einzigen Ausnahmen waren die PPU (Bildverarbeitungseinheit, Bildverarbeitungsmodule), die zum Erzeugen von auf den Bildschirm übertragenen Videobildern verwendet wurden. Wir wissen meistens , wie PPUs funktionieren, aber für einige Funktionen müssen wir Vermutungen anstellen, was zu einer unvollständigen Genauigkeit führt.

Im Allgemeinen sind die verbleibenden Probleme ziemlich gering. Wenn Sie nicht nach der absolut perfekten Idealität der Emulation aus Liebe zur Kunst streben, kann ich Sie nicht von der Notwendigkeit überzeugen, die PPU-Emulation weiter zu verbessern. Wie in jedem Bereich ist die Rendite umso geringer, je näher wir dem Ideal sind.

Aber ich kann sagen, warum mir das wichtig ist : Dies ist die Arbeit meines ganzen Lebens, und ich möchte nicht, dass ich sage, dass ich so kurz vor dem Abschluss bin , ohne den letzten Schritt zu tun . Ich altern und ich bin nicht ewig. Ich möchte, dass das letzte Puzzleteil gelöst wird, damit ich nach meiner Pensionierung sicher bin, dass das SNES-Erbe dank der Emulation zuverlässig und vollständig erhalten bleibt. Ich möchte sagen, dass das Problem gelöst ist .

Wenn Sie immer noch fasziniert sind, lesen Sie weiter, um sich mit dem Hintergrund eines Problems und den von mir angebotenen Lösungen vertraut zu machen.

Modellierung der SNES-Architektur


Beginnen wir mit der Auflistung der Komponenten, aus denen SNES besteht:


Super NES Systemdiagramm.

Die Pfeile geben die Richtungen an, in denen verschiedene SNES-Prozessoren Daten miteinander austauschen können, und die gepunkteten Linien geben die Verbindungen zu den Speicherchips an.

Das Wichtigste für uns ist jetzt, dass die Ausgabe von Video und Ton direkt von PPU und DSP übertragen wird. Dies bedeutet, dass sie als "Black Boxes" fungieren und wir nicht sehen können, was in ihnen geschieht. Später wird es uns wichtig.

Richtigkeit


Stellen Sie sich vor, wir emulieren den CPU-Befehl "multiplizieren", der zwei Register (Variablen) verwendet, diese multipliziert, das Ergebnis und mehrere Flags empfängt, die den Status des Ergebnisses anzeigen (z. B. Überlauf ).

Wir können ein Programm schreiben, das jeden möglichen Wert von 0 bis 255 als Faktor und Multiplikator multipliziert. Dann können wir die numerischen und Flag-Multiplikationsergebnisse ableiten. Somit erhalten wir zwei Tabellen mit 65 536 Elementen.

Durch die Analyse dieser Tabellen können wir genau bestimmen, wie und wo die Ergebnisse von CPU-Berechnungen auf bestimmte Weise festgelegt werden. Dann können wir die Emulatoren so ändern, dass wir beim Ausführen des gleichen Tests genau die gleichen Tabellen zur gleichen Zeit erhalten.

Nehmen wir nun an, die CPU kann 16-Bit x 16-Bit-Multiplikation durchführen. Wenn jeder mögliche Wert getestet wird, werden 4 Milliarden Ergebnisse generiert, die in angemessener Zeit kaum zu testen sind. Wenn die CPU Multiplikationen von 32 Bit x 32 Bit hat, ist es in der Praxis nicht möglich, alle Kombinationen von Eingabewerten vor dem thermischen Tod des Universums zu testen (zumindest auf dem aktuellen Stand der Technik).

In solchen Fällen handeln wir bei den Tests selektiver und versuchen festzustellen, wann sich die Flags genau ändern können, wann die Ergebnisse überlaufen können und so weiter. Andernfalls müssten wir Tests durchführen, die niemals enden würden.

Die Multiplikation ist eine eher triviale Operation, aber das gleiche Prinzip kann auf den gesamten Prozess des Reverse Engineering ausgedehnt werden, einschließlich komplexerer Operationen, beispielsweise der Datenübertragung über DMA (direkter Speicherzugriff) während der horizontalen Rückgabe des Strahls. Wir erstellen Tests, die versuchen festzustellen, was in Grenzfällen passiert, und prüfen dann, ob sich unsere Emulation identisch mit dem Verhalten von echtem SNES verhält.

Signalgeneratoren und Beats


SNES verfügt über zwei Signalgeneratoren (Oszillatoren): einen Quarzoszillator, der mit einer Frequenz von ungefähr 21 MHz arbeitet (er steuert die CPU- und PPU-Module), und einen Keramikresonator, der mit einer Frequenz von ungefähr 24 MHz arbeitet und SMP und DSP steuert. In Cartridge-Coprozessoren wird manchmal ein 21-MHz-Quarzoszillator verwendet, und manchmal arbeiten eigene Signalgeneratoren mit anderen Frequenzen.


Das Wiederherstellen dieser Super Famicom-Platine im Code ist schwieriger als es sich anhört.

Die Uhr ist das Grundelement des Timings eines jeden Systems, und SNES ist dafür ausgelegt, verschiedene Aufgaben mit bestimmten Frequenzen und Zeitintervallen auszuführen.

Wenn Sie sich einen 100-Hertz-Takt vorstellen, ist dies ein Gerät mit einem Binärausgang, der 100 Mal pro Sekunde auf einen hohen logischen Zustand des Signals (z. B. +5 V) und dann auf einen niedrigen Zustand des Signals (0 V oder Masse) umschaltet. Das heißt, jede Sekunde schwankt die Spannung am Ausgang 200-mal: 100-mal erhöht und 100-mal die Vorderseite des Taktsignals abgesenkt.

Ein Taktzyklus wird normalerweise als ein vollständiger Übergang betrachtet, dh ein 100-Hz-Zyklus erzeugt 100 Taktzyklen pro Sekunde. Einige Systeme erfordern eine Unterscheidung zwischen ansteigenden und abfallenden Flanken, und für sie unterteilen wir den Zyklus in Halbzyklen, um jede Phase (hoch oder niedrig) des Taktsignals anzuzeigen.

Die wichtigste Aufgabe eines genauen Emulators besteht darin, Aufgaben genauso und in genau derselben Zeit wie bei realen Geräten zu erledigen. Es ist jedoch nicht sehr wichtig, wie die Aufgaben ausgeführt werden. Das einzige, was wichtig ist, ist, dass der Emulator, der dieselben Eingangssignale empfängt, dieselben Ausgangssignale zur gleichen Zeit wie auf realer Hardware erzeugt.

Timings


Manchmal brauchen Operationen Zeit. Nehmen wir zum Beispiel die Multiplikation in der SNES-CPU. Anstatt anzuhalten und auf den Abschluss der Multiplikation zu warten, berechnet die SNES-CPU das Ergebnis der Multiplikation bitweise im Hintergrund für acht Taktzyklen der CPU-Opcodes. Auf diese Weise kann der Code möglicherweise andere Aufgaben ausführen, während er auf den Abschluss der Multiplikation wartet.

Höchstwahrscheinlich wartet jede kommerzielle Software auf diese acht Zyklen. Wenn Sie versuchen, das Ergebnis zu lesen, bevor es fertig ist, erhalten wir ein teilweise abgeschlossenes Ergebnis. Bevor jedoch SNES-Emulatoren sofort korrekte Ergebnisse lieferten , ohne auf diese zusätzlichen Taktzyklen zu warten.

Als Konsolenfans begannen, selbst geschriebene Software in Emulatoren zu erstellen und zu testen, verursachte diese Diskrepanz bestimmte Probleme. Ein Teil der Software, zum Beispiel viele der ersten Super Mario World ROM-Hacks , funktionierte nur in diesen alten Emulatoren korrekt, nicht jedoch auf echter SNES-Hardware. Dies geschah, weil sie unter Berücksichtigung des Zeitpunkts (aus Sicht der realen Ausrüstung unzuverlässig) der Multiplikationsergebnisse entwickelt wurden.

Bei der Verbesserung der Emulatoren wurde die Kompatibilität alter Software beeinträchtigt, und daher mussten wir den neuen Emulatoren Kompatibilitätsoptionen hinzufügen, um diese Programme nicht zu verlieren. Ja, egal wie surreal es klingt, aber heute müssen Emulatoren andere Emulatoren emulieren!

Die Bequemlichkeit dieser Multiplikationsverzögerung in der CPU liegt in der Tatsache, dass sie sehr vorhersehbar ist: Acht Taktzyklen von Berechnungen beginnen unmittelbar nach der Anforderung der Multiplikationsoperation. Durch Schreiben von Code, der die Ergebnisse nach jedem Zyklus liest, konnten wir überprüfen, ob die SNES-CPU den Booth-Algorithmus zur Multiplikation verwendet .

Uhrensynchronisation


Andere Operationen sind nicht einfach zu modellieren, da sie im Hintergrund asynchron ausgeführt werden. Ein solcher Fall ist das DRAM-Update des zentralen SNES-Prozessors.

Während des Renderns jeder Rasterzeile unterbricht die gesamte SNES-CPU zu einem bestimmten Zeitpunkt ihren Betrieb für einen kurzen Zeitraum, während der Inhalt des RAM-Chips aktualisiert wird. Dies ist erforderlich, da zur Reduzierung der Kosten in SNES dynamischer (statt statischer) RAM als Hauptspeicher der CPU verwendet wurde. Um den Inhalt des dynamischen RAM zu speichern, muss dieser regelmäßig aktualisiert werden.


Es reicht nicht aus, einen wirklich perfekten Emulator zu erstellen, um die Spielbarkeit aller dreieinhalbtausend SNES-Spiele zu gewährleisten. Es ist auch notwendig, eine Simulation jeder Funktion des Systems mit perfekter Taktgenauigkeit zu erreichen.

Der Schlüsselfaktor bei der Analyse der genauen Zeitpunkte dieser Operationen war die Möglichkeit, horizontale und vertikale PPU-Zähler zu verwenden. Diese Zähler führen Inkremente durch und werden nach jeder umgekehrten horizontalen und vertikalen Strahlbewegung zurückgesetzt. Ihre Genauigkeit beträgt jedoch nur ein Viertel der Frequenz des SNES-CPU-Signalgenerators. Mit anderen Worten, der horizontale Zähler erhöht sich alle vier Taktzyklen.

Durch mehrmaliges Lesen der Werte der Zähler konnte ich feststellen, auf welches Viertel des Taktzyklus der Zähler ausgerichtet ist. Durch die Kombination dieses Wissens mit speziell erstellten Funktionen, die einen Schritt in Richtung der vom Benutzer angegebenen genauen Anzahl von Taktzyklen machen können, konnte ich die SNES-CPU perfekt auf jede genaue Position des von mir benötigten Taktzyklus abstimmen.

Dank eines iterativen Durchlaufs vieler Taktzyklen konnte ich feststellen, wann bestimmte Operationen genau ausgeführt werden (z. B. Aktualisieren des DRAM, Übertragen von HDMA, Abrufen von Interrupts usw.). Danach konnte ich all dies in der Emulation genau nachbilden.

SMP-ChipDie SNES-Konsole verfügt auch über eigene Timer, und für diesen Prozessor wurde auch ein erfolgreiches Reverse Engineering durchgeführt. Ich kann einen ganzen Artikel nur dem SMP TEST-Register widmen, mit dem Programmierer den SMP-Frequenzteiler und seinen Timer steuern können, ganz zu schweigen von anderen schrecklichen Dingen. Es wird ausreichen zu sagen, dass es kein einfacher und schneller Prozess war, aber am Ende haben wir gewonnen.

Wir sammeln Coprozessoren



Der SuperFX-Chip ist nur einer von vielen Cartridge-Coprozessoren, die der SNES-Emulator verarbeiten kann.

Es gibt eine ganze Reihe von SNES-Coprozessoren, die in verschiedenen Spielekassetten verwendet werden und die wir auch zähmen mussten. Von einzelnen Allzweck-CPUs wie SuperFX und SA-1 über digitale Signalprozessoren wie DSP-1 und Cx4 bis hin zu Dekompressionsbeschleunigern wie S-DD1 und SPC7110 oder Sharp- und Epson-Echtzeituhren und vielem mehr ...

Dies bedeutet, dass der SNES-Emulator mit SuperFX-Befehlen und Pixel-Caches umgehen muss. mit dem SA-1-Speicherbus-Konfliktlösungsschema (wodurch die SNES- und SA-1-CPUs gleichzeitig denselben ROM- und RAM-Chip verwenden können); mit integrierter Firmware DSP-1 und Cx4; mit prädiktionsbasierten arithmetischen Encodern S-DD1 und SPC7110; sowie mit ungeraden Grenzfällen von BCD (binär codierte Dezimalzahl) in Echtzeitgeneratoren. Langsam aber sicher haben wir mit allen oben beschriebenen Techniken zur Bestimmung der Korrektheit und des Timings gelernt, wie man all diese Chips nahezu perfekt emuliert.

Das Entfernen der Chipabdeckungen und der Firmware von den in verschiedenen Spielen verwendeten digitalen Signalprozessoren war mit viel Aufwand und Tausenden von Dollar verbunden. In einem Fall ist die NEC uPD772x-Emulation zulässigVerwenden Sie den Code von Higan, um die Stimme des verstorbenen Stephen Hawking zu retten! .

In einem anderen Fall mussten wir eine ganze Reihe von Anweisungen für die Hitachi HG51B-Architektur zurückentwickeln, da noch nie jemand die Dokumentation für diese Architektur veröffentlicht hatte. In einem anderen Fall stellte sich heraus, dass ein Spiel ( Hayazashi Nidan Morita Shougi 2 ) eine leistungsstarke 32-Bit-ARM6-CPU mit einer Frequenz von 21 MHz hat, was das japanische Shogi-Spiel beschleunigt!

Das Speichern aller SNES-Coprozessoren erwies sich als langfristiger Prozess voller Schwierigkeiten und Überraschungen.

Digitale Signalverarbeitung


Der Sony S-DSP-Chip (Digital Signal Processor), der nicht mit dem DSP-1-Kassetten-Coprozessor verwechselt werden darf, erzeugte einen einzigartigen SNES-Sound. In diesem Chip wurden acht Audiokanäle mit 4-Bit-ADPCM-Codierung angeschlossen, wodurch die Erzeugung eines 16-Bit-Stereosignals sichergestellt wurde.

Äußerlich und aus dem oben dargestellten Systemdiagramm scheint der DSP zunächst eine „Black Box“ zu sein: Wir passen die Tonkanäle und Mischerparameter an, wonach der Chip den an die Lautsprecher übertragenen Ton erzeugt.

Eine wichtige Funktion ermöglichte es dem Entwickler unter dem Spitznamen blargg, ein vollständiges Reverse Engineering dieses Chips durchzuführen: Es war ein Echopuffer. Der SNES-DSP verfügt über eine Funktion, die die Ausgabe vorheriger Samples mischt, um einen Echoeffekt zu erzeugen. Dies geschieht ganz am Ende des Klangerzeugungsprozesses (abgesehen von dem letzten Schallblockierungsflag, mit dem die gesamte Tonausgabe ausgeschaltet werden kann).

Durch Schreiben von Code mit dem richtigen Timing der Maßnahmen und Verfolgen des resultierenden Echos konnten wir die genaue Reihenfolge der vom DSP zu erzeugenden Operationen bestimmen von jedem Sample und perfekte Klang- und Schlaggenauigkeit.

PPU speichern


All dies führte uns zum letzten Teil des SNES-Architekturschemas: PPU-1- und PPU-2-Chips. Dank John McMaster haben wir Scans der Chips S-PPU1 (Revision 1) und S-PPU2 (Revision 3) mit einer zwanzigfachen Zunahme.


Zwanzigfacher Scan des Kristalls des ersten PPU-SNES ...


... und die zweite PPU.

Beide Crystal-Scans lassen uns wissen, dass es sich bei den Chips offensichtlich nicht um Allzweck-CPUs handelt, und es handelt sich auch nicht um spezialisierte Architekturen, die Betriebscodes aus dem internen ROM des Firmware-Programms ausführen. Hierbei handelt es sich um separate logische Schaltkreise mit fest codierter Logik, die eingehende Signale von verschiedenen Registern und Speichern empfangen und jeweils ein Rastersignal für den Monitor erzeugen.

PPUs bleiben das letzte Hindernis für die Emulation von SNES, da PPUs im Gegensatz zu allen oben beschriebenen Komponenten tatsächlich eine Black Box sind. Wir können sie für jeden Status konfigurieren, aber die SNES-CPU kann nicht direkt überwachen, was sie generieren.

Wenn wir unser vorheriges Beispiel mit Multiplikation als Analogie verwenden, stellen Sie sich vor, Sie hätten das Ergebnis 3 * 7 angefordert, aber anstelle der binären Antwort erhalten Sie ein unscharfes analoges Bild der Zahlen „21“ auf dem Bildschirm. Jeder, der Ihre Software ausführt , kann 21 sehen, aber Sie können kein Testprogramm schreiben, um automatisch zu überprüfen, ob er die richtige Antwort sieht. Die manuelle Überprüfung solcher Ergebnisse durch eine Person kann nicht auf mehr als mehrere tausend Tests skaliert werden, und Millionen sind erforderlich, um das PPU-Verhalten zu maximieren.

Ich weiß, was Sie gedacht haben: "Aber ist es einfacher, eine Aufnahmekarte zu verwenden, eine Bildverarbeitung durchzuführen, sie ungefähr mit dem Bild auf dem digitalen Bildschirm des Emulators zu vergleichen und darauf basierende Tests durchzuführen?"

Na ja, das ist möglich! Besonders wenn der Test darin besteht, zwei große Zahlen zu überprüfen, die den gesamten Bildschirm einnehmen.

Aber was ist, wenn das Testen viele Nuancen hat und wir versuchen, den Farbunterschied eines Halbtons von einem Pixel zu erkennen? Was ist, wenn wir eine Million Tests in der richtigen Reihenfolge ausführen möchten und nicht immer wissen, was wir generieren werden, aber dennoch das Ergebnis mit der Ausgabe unserer Emulation vergleichen möchten?

Nichts geht über Komfort und Genauigkeit bei digitalen Daten - ein genauer Bitstrom, der nur übereinstimmen kann oder nicht. Die analoge Natur eines CRT-Signals kann uns dies nicht liefern.

Warum ist es wichtig?


Mit Ausnahme eines Spiels ( Air Strike Patrol ) basiert die gesamte offiziell lizenzierte SNES-Software (sollte es gewesen sein) auf Rasterzeichenfolgen. Diese Spiele versuchen nicht, den Status des PPU-Renderings in der Mitte der aktuell gerenderten Rasterzeile zu ändern (ein solcher Trick wird von Programmierern als "Rastereffekt" bezeichnet). Dies bedeutet, dass die Ausführungszeiten der meisten Spiele nicht besonders genau sein müssen. Wenn Sie Zeit für die nächste vollständige Rasterzeile haben, ist alles in Ordnung.

Dies ist jedoch wichtig für ein einziges Spiel.




Diese Bilderserie zeigt einen komplexen Emulationseffekt, der in der Nachricht „Good Luck“ von Air Strike Patrol verwendet wird .

In den obigen Bildern sehen Sie den Frame-für-Frame-Text „Good Luck“ von Air Strike Patrol . Das Spiel implementiert es, indem es die Position des vertikalen Bildlaufs der Hintergrundebene 3 (BG3) ändert. Die Dashboard-Anzeige auf der linken Seite (wo Sie sehen können, dass der Spieler 39 Raketen hat) befindet sich jedoch ebenfalls auf derselben Hintergrundebene.

Das Spiel schafft es, diese Trennung durchzuführen, indem die Position der BG3-Schriftrolle in jeder Rasterzeile geändert wird, nachdem das linke Dashboard gerendert wurde, aber bevor der Text „Good Luck“ gerendert wird. Dies ist möglich, da BG3 außerhalb des Dashboards und des Texts transparent ist und zwischen diesen beiden Punkten unabhängig vom Wert des vertikalen Bildlaufregisters nichts zu zeichnen ist. Dieses Verhalten zeigt uns , dass Scrollen Register kann in jedem Stadium des Rendering verändert.


Dieser kleine Schatten unter dem Flugzeug bereitete dem präzisionsbesessenen Emulatorentwickler einige Kopfschmerzen.

Das Bild oben zeigt den berüchtigten Schatten eines Flugzeugs. Dieser Effekt wird durch Ändern des Bildschirmhelligkeitsregisters mit kurzen Wellen über fünf Rasterlinien gerendert.

Während des Spiels können Sie sehen, dass dieser Schatten ziemlich chaotisch ist. Im obigen Bild sieht es ein bisschen wie der Buchstabe "c" aus, aber seine Form in jeder Rasterlinie ändert sich in Länge und Startpunkt mit jedem Frame. Die Entwickler von Air Strike Patrol haben nur grob umrissen, wo der Schatten erscheinen soll, und dieses Problem direkt gelöst. In den meisten Fällen funktioniert dies.

Die korrekte Emulation eines solchen Verhaltens erfordert ein perfektes Timing, was im Emulator äußerst schwierig zu erreichen ist .


Auf dem Air Strike Patrol- Pausenbildschirm werden Rastereffekte verwendet, die in keinem anderen SNES-Spiel absichtlich verwendet wurden.

Lassen Sie uns nun über den Pausenbildschirm sprechen. Es schaltet BG3 ein, während links ein gelb-schwarzer Rand gezeichnet wird, und schaltet es während desselben Randes rechts wieder aus, um graue Linien auf dem Bildschirm zu zeichnen. Er wechselt auch abwechselnd durch den Rahmen die Rasterlinien, in denen diese grauen Linien angezeigt werden, um den Effekt eines Overlay-Jitters zu erzeugen.

Wenn Sie das oben gezeigte emulierte Bild vergrößern, werden Sie feststellen, dass während des Rasterlinienpaars in der linken Ecke dieser grauen Linien mehrere Pixel fehlen. Es ist passiert, weil meine PPU-Emulation in Taktzyklen zu 100% unvollständig ist. In diesem Fall wird BG3 etwas später aktiviert, als es sollte.

Ich kann die Timings sehr einfach ändern, damit dieses Bild korrekt wiedergegeben wird. Eine solche Änderung wirkt sich jedoch wahrscheinlich nachteilig auf andere Spiele aus, die die PPU-Anzeigeregister in der Mitte der Rasterzeile ändern. Obwohl Air Strike Patrol das einzige Spiel ist, das dies absichtlich tut, gibt es mindestens ein Dutzend Spiele, in denen dies zufällig geschieht (möglicherweise wird es früher oder später von IRQ ausgelöst).

Manchmal führt dies zu kurzen spürbaren Schäden am Bild, die bei der Entwicklung nicht berücksichtigt werden (z. B. bei Vollgasrennenwährend des Übergangs zwischen dem Laden und dem Spiel). Manchmal wird eine Aufzeichnung durchgeführt, während der Bildschirm gerendert wird, der im Rest transparent ist und daher keine visuellen Anomalien verursacht (z. B. bei der Anzeige des HP-Status in Dai Kaijuu Monogatari II ). Aber selbst solche „unsichtbaren“ Randfälle können Probleme beim weniger genauen Rendern von Rasterlinien verursachen die in den produktivsten Emulatoren verwendet werden.

Selbst wenn Sie Air Strike Patrol ignorieren , können Sie mit all diesen zufälligen (aber gültigen) Rastereffekten in der SNES-Software keinen PPU-Renderer funktional entwerfen, der die gesamte Rasterlinie mit perfekter Taktgenauigkeit generiert.

Im Fall von bsnes über die Jahre des Versuchs und Irrtums haben wir eine Liste solcher Spiele mit „Rastereffekten“ erstellt. Wir haben auch individuelle Rendering-Positionen erstellt, die ein viel schnelleres Rendern basierend auf Rasterlinien ermöglichen, um alle diese Spiele korrekt anzuzeigen (außer natürlich Air Strike Patrol ). Aber im Wesentlichen ist dies eine Reihe von Hacks, die für uns unangenehm sind und für bestimmte Spiele entwickelt wurden.

Ich habe auch einen uhrbasierten PPU-Renderer, der nicht alle diese Hacks benötigt, aber von Zeit zu Zeit kleine (ein bis vier Pixel) Unterschiede beim Rendern dieses Geräts erzeugt, wie im obigen Screenshot von Air Strike Patrol .

Interne Latch-Register


Der Grund für all diese kleinen Fehler liegt in der zeitlichen Abstimmung.

Angenommen, SNES rendert seinen berühmten Modus 7 , bei dem es sich um eine affine Texturtransformation mit Parameteränderungen in jeder Rasterzeile handelt. Um ein Bildschirmpixel zu bestimmen, müssen Sie ähnliche Berechnungen durchführen:

px = a * clip (hoffset - hcenter) + b * clip (voffset - vcenter) +
b * y + (hcenter << 8)

py = c * clip (hoffset - hcenter) + d * clip (voffset - vcenter) +
d * y + (vcenter << 8)

Real SNES kann nicht alle sechs Multiplikationen für jedes Pixel, das im Frame gerendert wird, schnell genug ausführen. Aber keiner dieser Werte ändert sich für jedes Pixel (oder sollte sich zumindest nicht ändern), sodass wir px und py nur einmal am Anfang jeder Rasterzeile berechnen müssen. Das heißt, PPU speichert statische Ergebnisse in Latches zwischen, die im Wesentlichen Kopien von PPU-Registern sind. In Zukunft können sie transformiert werden oder unverändert bleiben.

Dann werden die x, y-Koordinaten durch Modus 7 wie folgt transformiert:

ox = (px + a * x) >> 8

oy = (py + c * x) >> 8

Obwohl x für jedes Pixel variiert, wissen wir, dass das Inkrement jedes Mal um eins ausgeführt wird. Dank der Speicherung interner Laufwerke können wir ox und oy einfach für jedes Pixel konstante Werte a und c hinzufügen, anstatt zwei Multiplikationen für jedes Pixel durchzuführen.

Dann stellt sich vor uns die Frage: In welcher bestimmten Position des Taktzyklus liest die PPU die Werte von a und c aus den externen PPU-Registern, auf die die CPU Zugriff hat?

Wenn wir sie zu früh nehmen, kann dies einige Spiele brechen. Wenn wir es zu spät nehmen, kann es andere Spiele brechen.

Am einfachsten ist es, auf Fehlerberichte zu warten und diese Positionen anzupassen, um Probleme in den einzelnen Spielen zu beheben. In diesem Fall werden wir jedoch niemals die genauen Positionen finden, sondern nur deren Annäherungen.

Und jedes Mal, wenn wir eine dieser Variablen ändern, ist es für uns unrealistisch, alle dreieinhalbtausend Spiele aus der SNES-Bibliothek erneut zu testen, um festzustellen, welche Verschlechterung unsere Änderungen bewirken könnten.

Aus der Pfanne ins Feuer



Künstlerische Interpretation des Prozesses zur Beseitigung von Emulationsfehlern.

Eine ähnliche Art der Testmethode: "Wir machen nur das Spiel, an dem wir interessiert sind, um jeden Preis" führte zu dem Phänomen, das ich Emulation "vom Feuer, aber ins Feuer" nenne.

Zu Beginn der Entwicklung der SNES-Emulation, wenn Probleme im Spiel auftraten, wurde jede Korrektur in diesem Spiel, die es ermöglichte, akzeptiert und dem Emulator hinzugefügt. Dieser Fix hat notwendigerweise ein anderes Spiel kaputt gemacht. Und dann haben sie dieses Spiel korrigiert , woraufhin das dritte kaputt ging. Das dritte Spiel erneut zu reparieren, brach das erste. Dies dauerte viele Jahre.

Der Fehler dabei war, dass die Entwickler versuchten, jeweils nur eine Variable zu berücksichtigen. Angenommen, wir haben ein Spiel, und damit es funktioniert, müssen Ereignisse zwischen Takt 20 und 120 auftreten. Wir kennen den genauen Takt nicht, wählen Sie also einfach 70 genau in der Mitte.

Später erhalten wir einen Fehlerbericht in einem anderen Spiel und stellen fest, dass der Messwert für dieses Spiel zwischen 10 und 60 liegen sollte. Jetzt ändern wir ihn auf 40, was für beide Spiele funktioniert. Klingt logisch!

Aber dann erscheint das dritte Spiel, in dem das Ereignis zwischen Takt 80 und 160 funktionieren sollte! Jetzt können wir nicht alle drei Spiele gleichzeitig mit demselben Wert zum Laufen bringen.

Dies zwang Emulatorentwickler, Hacks für bestimmte Spiele zu erstellen. Codierer möchten keinen Emulator veröffentlichen, in dem Sie Mario , Zelda oder Metroid nicht ausführen können . Daher wird für den allgemeinen Fall der Taktzyklus 40 verwendet, aber beim Laden von Metroid erzwingen wir den Zeitwert auf 100.

Wie ist dies möglich, warum benötigen zwei Spiele unterschiedliche Werte? Dies geschieht, weil hier nicht nur eine Variable beteiligt ist. Das Timing, das Sie zuvor zum Auslösen eines anderen Ereignisses verwendet haben, kann sich auf den Timing-Wert auswirken, der für das nächste Ereignis erforderlich ist .

Stellen Sie sich dies in Form eines einfachen algebraischen Ausdrucks vor:

2x + y = 120

Sie können es lösen, indem Sie x = 10, y = 100 nehmen. Oder x = 20, y = 80. Oder x = 30, y = 60. Wenn wir nur an den Wert von x denken, mit dem Sie gleichzeitig eine Reihe von Spielen ausführen können, übersehen wir die Tatsache, dass das Problem möglicherweise im falschen y liegt!

Die ersten Versionen von Emulatoren zur Erhöhung der Kompatibilität haben den Wert von x je nach laufendem Spiel einfach neu definiert. Solche einzelnen Spiel-Hacks blieben bestehen, auch wenn später der richtige Einzelwert von x entdeckt wurde. Das y- Problem würde also niemals gelöst werden!

Bei SNES sind jedoch nicht eine oder zwei Variablen gleichzeitig beteiligt. Allein die SNES-Konsolen-PPU verfügt über 52 externe Register, was ungefähr 130 Parametern entspricht. Beim Rendern einer einzelnen Rasterzeile sind alle 130 dieser Parameter und eine unbekannte Anzahl interner Register und Latches beteiligt. Dies sind zu viele Informationen, als dass jemand außerhalb den Status der PPU zu einem bestimmten Zeitpunkt vollständig erfassen könnte.

Dieser Aspekt der Emulation ist für Uneingeweihte nicht offensichtlich, aber sehr fair: Genauigkeit ist nicht gleich Kompatibilität. Wir können einen Emulator mit einer Genauigkeit von 99 Prozent erstellen, der 10% der Spiele ausführen kann. Und Sie können einen 80% genauen Emulator schreiben, der 98% der Spiele ausführt. Manchmal bricht eine korrekte Implementierung kurzfristig beliebte Spiele. Dies ist ein notwendiges Opfer, wenn Sie versuchen, sowohl 100% Genauigkeit als auch 100% Kompatibilität zu erreichen.

Das Problem lösen


Dank deduktiver Überlegungen und Ergebnissen in der realen Welt haben wir die aktuelle Stufe der PPU-Emulation erreicht.

Wir wissen, dass zwei PPUs Zugriff auf zwei VRAM-Chips haben. Wir wissen, dass sie von jedem Chip eine bekannte Anzahl von Datenbytes pro Rasterzeile lesen können. Wir kennen die groben Details der Funktionsweise der einzelnen SNES-Videomodi. Auf dieser Grundlage können wir ein verallgemeinertes Muster für das Erscheinungsbild der Architektur skizzieren. Hier ist zum Beispiel ein kurzes Beispiel, wie die ersten drei SNES-Videomodi funktionieren können:

if (io.bgMode == 0) {

bg4.fetchNameTable ();

bg3.fetchNameTable ();

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg4.fetchCharacter (0);

bg3.fetchCharacter (0);

bg2.fetchCharacter (0);

bg1.fetchCharacter (0);

}}

if (io.bgMode == 1) {

bg3.fetchNameTable ();

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg3.fetchCharacter (0);

bg2.fetchCharacter (0);

bg2.fetchCharacter (1);

bg1.fetchCharacter (0);

bg1.fetchCharacter (1);

}}

if (io.bgMode == 2) {

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg3.fetchOffset(0);

bg3.fetchOffset(8);

bg2.fetchCharacter(0);

bg2.fetchCharacter(1);

bg1.fetchCharacter(0);

bg1.fetchCharacter(1);

}


Die PPU zeigt einem Drittbeobachter nur einen kleinen Teil ihres Zustands: horizontale / vertikale Austastflags, horizontale und vertikale Pixelanzahl und Kachelüberlagerungsflags im Intervall für Sprites. Das ist nicht so sehr, aber ich wiederhole - jedes winzige Element des Staates, das dem Beobachter zugänglich ist, hilft uns.

Der VRAM (Video-RAM, Videospeicher) des PPU-Chips während des Renderns ist auch zum Lesen für SNES-CPUs geschlossen. Wie sich herausstellte, sind OAM (Sprite-Speicher) und CGRAM (Palettenspeicher) geöffnet. Der Trick ist, dass zu diesem Zeitpunkt die PPU den Adressbus steuert. Daher kann ich beim Lesen von OAM und CGRAM während des Bildschirm-Renderings beobachten, was die PPU zu einem so kritischen Zeitpunkt von diesen beiden Speicherblöcken erhält.

Dies sind nicht alle Teile des Puzzles, aber sie reichen mir aus, um die praktisch richtigen Muster für das Erhalten von Sprites implementieren zu können.

Mithilfe von Zugriffsmustern für offenes OAM und CGRAM, PPU-Flags, allgemeinen Beobachtungen (d. H. Vermutungen) aus Fehlerberichten für verschiedene Spiele und deduktiven Überlegungen konnten wir uhrbasierte PPU-Renderer erstellen , mit denen alle veröffentlichten Spiele nahezu perfekt gestartet werden können.

Die Situation ist jedoch immer noch prekär: Wenn jemand anfängt, Homebrew-Spiele mit genauem Timing von Ticks und Rastereffekten zu erstellen, können alle unsere modernen Emulatoren damit nicht umgehen. Einschließlich auf FPGA basierender Software- und Hardware-Implementierungen.

Ich muss klar sagen: heute allesSie kennen nur die interne Reihenfolge der Operationen und das Fangverhalten in den PPU-Chips der SNES-Konsole. Niemand weiß, wie man sie perfekt emuliert. Zumindest für jetzt.

Mögliche Lösungen


Was sollen wir damit machen? Wie kann man die genaue Reihenfolge der Operationen in einer PPU bestimmen, wenn es sich aus Sicht der SNES-CPU um eine "Black Box" handelt?

Ich sehe vier mögliche Optionen: Logikanalysatoren, digitale Videoausgabe im Testmodus, Riser und Entfernen von Abdeckungen von Chips.

Logikanalysatoren


Wenn Sie sich die oben gezeigten Scans von PPU-Kristallen ansehen, werden Sie schwarze Bereiche an den Rändern des Chips bemerken. Dies sind die Plattformen, die mit den Kontakten der Chips verbunden sind.

Diese Pins speichern den Zustand der PPU-Chips während jedes Taktzyklus. Hier finden Sie die aktuelle Adresse, an die die Chips auf den Videospeicherchip zugreifen, die Werte der von einer PPU zur zweiten übertragenen Daten und vieles mehr.

Diese Informationen sind für Code, der auf der SNES-CPU ausgeführt wird, nicht verfügbar, liefern jedoch wertvolle Beobachtungen zur internen Reihenfolge der PPU-Operationen.


Das Anschließen von Super NES-Konsolen-PPUs an einen ähnlichen Logikanalysator kann der Schlüssel zur Black Box sein.

Das entscheidende Problem von Logikanalysatoren besteht darin, dass sie nicht sehr bequem zu verwalten sind: Wenn Sie versuchen, Live-Daten von einem funktionierenden System abzutasten, erhalten wir einen Strom von Ergebnissen, der ziemlich schwer zu entschlüsseln ist. Wenn Sie versuchen, die analoge RGB-Ausgabe des Systems zu analysieren, tritt dasselbe Problem auf: Um diese Daten zu erfassen, müssen Sie jeden der Tests manuell durchführen. Ein solches System ist nicht sehr gut für die Erstellung reproduzierbarer automatisierter Regressionstests.

Digitaler Videoausgang im Testmodus


Kürzlich wurde durch einen Scan von Kristallschnitten mit 20-facher Vergrößerung ein geheimer Testmodus in den PPU-Chips der SNES-Konsole entdeckt. Wenn Sie eine kleine Hardwaremodifikation vornehmen, gibt die PPU ein digitales 15-Bit-RGB-Signal aus !

Das ist fast was wir brauchen! Dieser Modus weist jedoch Probleme auf, da der berühmte Modus 7 nicht das richtige Bild anzeigen kann. Es scheint, dass diese Funktion nicht vollständig abgeschlossen wurde.

Um diese Methode zu implementieren, sind weiterhin manuelle Änderungen an SNES-Konsolen und ein geeigneter Mechanismus zum Erfassen und Analysieren der Ausgabe im Testmodus erforderlich. Im Gegensatz zu einer Lösung mit der Erfassung eines analogen RGB-Signals kann ein solches digitales Signal jedoch automatisch getestet werden, wodurch wir schnell einen großen Teil der Arbeit am PPU-Reverse Engineering erledigen können.

Riser


Da die PPUs statisch sind, können wir die PPU-Chips von einer funktionierenden SNES-Konsole entfernen und sie zusammen mit zwei VRAM-Chips an eine Prototyping-Karte oder eine maßgeschneiderte Leiterplatte anschließen. Danach können Sie einen Mikrocontroller zwischen der PPU und der USB-Schnittstelle platzieren und die Schnittstelle an den PC anschließen, sodass der Encoder alle externen Videospeicherregister und PPUs programmieren kann. Darüber hinaus kann der Codierer die PPU-Taktzyklen manuell steuern und die resultierenden Signale in jedem Taktzyklus auf den E / A-Anschlüssen, Registern und im PPU-Speicher lesen.

Indem Sie den Software-Emulator so modifizieren, dass er die gleichen internen Werte der E / A-Anschlüsse generiert, können Sie echte Hardware auch in Echtzeit direkt mit der Emulation vergleichen. Dies wird jedoch sehr harte Arbeit sein, da wir die internen PPU-Operationen noch nicht sehen können.

Entfernen der Abdeckung


Die extremste Lösung besteht darin, den Kristall weiter zu untersuchen, indem die Chipabdeckung entfernt wird. Wir haben bereits Kristall-Scans mit einer 20-fachen Vergrößerung, aber ihre Auflösung reicht nicht aus, um einzelne Logikschaltungen zu analysieren und neu zu erstellen, wie dies im Visual 6502-Projekt durchgeführt wurde . Wenn wir die Kristall-Scans beider PPUs mit einer 100-fachen Vergrößerung erhalten können, können wir mit der harten Arbeit beginnen, PPU-Schaltungen zu kompilieren und sie in Verbindungstabellen oder VHDL-Code zu konvertieren. Dann können sie direkt im FPGA verwendet sowie auf C ++ oder eine andere Programmiersprache portiert werden, die zum Erstellen von Softwareemulatoren geeignet ist.

Ein Spezialist, der dies zuvor getan hatte, gab mir eine grobe Schätzung: Es würde ungefähr 600 Stunden dauern, um beide PPUs abzubilden. Diese Aufgabe ist viel höher als das Niveau „Lasst uns Geld sammeln, indem wir Spenden sammeln und jemanden bezahlen“ und fällt idealerweise in die Kategorie „Hoffen wir, dass jemand, der sehr talentiert ist und über einzigartige Fähigkeiten verfügt, uns freiwillig helfen möchte“.

Das bedeutet natürlich nicht, dass ich nicht gerne jemanden für seine Hilfe finanziell belohnen würde, ich kann für die notwendigen Details und die Arbeit bezahlen.

Bitte um Hilfe


Zusammenfassend: Ich bin in meinem SNES-Emulatorprojekt so weit wie möglich gegangen und brauche Hilfe, um diese letzte Aufgabe zu erledigen. Wenn Sie bis zum Ende gelesen haben, möchten Sie vielleicht helfen! Jede Unterstützung, einschließlich der Teilnahme am bsnes-Projekt auf GitHub oder jeglicher Forschungsdokumentation zum internen Betrieb von PPU-Chips, ist für uns von unschätzbarem Wert!

Vielen Dank fürs Lesen und für Ihre Unterstützung! Es ist mir seit fünfzehn Jahren eine Ehre, Mitglied der SNES-Emulationsgemeinschaft zu sein.

All Articles