Emulation ist ein facettenreicher Bereich. Hier sind die Grundideen und Funktionskomponenten. Ich werde es in Stücke zerbrechen und dann die Details über Änderungen ausfüllen. Viele der Dinge, die ich beschreiben werde, erfordern Kenntnisse über das Innenleben von Prozessoren - Montagekenntnisse sind erforderlich. Wenn ich in bestimmten Dingen etwas zu vage bin, stellen Sie bitte Fragen, damit ich diese Antwort weiter verbessern kann.
Grundidee:
Bei der Emulation wird das Verhalten des Prozessors und der einzelnen Komponenten behandelt. Sie bauen jedes einzelne Teil des Systems und verbinden die Teile dann ähnlich wie Drähte in der Hardware.
Prozessoremulation:
Es gibt drei Möglichkeiten, mit der Prozessoremulation umzugehen:
- Deutung
- Dynamische Neukompilierung
- Statische Neukompilierung
Mit all diesen Pfaden haben Sie das gleiche allgemeine Ziel: Führen Sie einen Code aus, um den Prozessorstatus zu ändern und mit 'Hardware' zu interagieren. Der Prozessorstatus ist ein Konglomerat der Prozessorregister, Interrupt-Handler usw. für ein bestimmtes Prozessorziel. Für die 6502, dann würden Sie eine Reihe von 8-Bit - Integer haben repräsentiert Register: A
, X
, Y
, P
, und S
; Sie hätten auch ein 16-Bit- PC
Register.
Bei der Interpretation beginnen Sie am IP
(Befehlszeiger - auch PC
Programmzähler genannt) und lesen den Befehl aus dem Speicher. Ihr Code analysiert diese Anweisung und verwendet diese Informationen, um den von Ihrem Prozessor angegebenen Prozessorstatus zu ändern. Das Kernproblem bei der Interpretation ist, dass es sehr langsam ist; Jedes Mal, wenn Sie eine bestimmte Anweisung bearbeiten, müssen Sie sie dekodieren und die erforderliche Operation ausführen.
Bei der dynamischen Neukompilierung iterieren Sie ähnlich wie bei der Interpretation über den Code, aber anstatt nur Opcodes auszuführen, erstellen Sie eine Liste von Operationen. Sobald Sie eine Verzweigungsanweisung erreicht haben, kompilieren Sie diese Liste von Vorgängen, um Code für Ihre Hostplattform zu verarbeiten. Anschließend speichern Sie diesen kompilierten Code im Cache und führen ihn aus. Wenn Sie dann eine bestimmte Anweisungsgruppe erneut treffen, müssen Sie nur den Code aus dem Cache ausführen. (Übrigens erstellen die meisten Leute keine Liste von Anweisungen, sondern kompilieren sie im laufenden Betrieb zu Maschinencode - dies erschwert die Optimierung, aber das liegt außerhalb des Rahmens dieser Antwort, es sei denn, es sind genügend Leute interessiert.)
Bei der statischen Neukompilierung machen Sie dasselbe wie bei der dynamischen Neukompilierung, folgen jedoch Zweigen. Am Ende erstellen Sie einen Codeabschnitt, der den gesamten Code im Programm darstellt und dann ohne weitere Eingriffe ausgeführt werden kann. Dies wäre ein großartiger Mechanismus, wenn nicht die folgenden Probleme auftreten würden:
- Code, der zunächst nicht im Programm enthalten ist (z. B. komprimiert, verschlüsselt, zur Laufzeit generiert / geändert usw.), wird nicht neu kompiliert und daher nicht ausgeführt
- Es ist erwiesen, dass das Finden des gesamten Codes in einer bestimmten Binärdatei dem Problem des Anhaltens entspricht
Diese kombinieren, um eine statische Neukompilierung in 99% der Fälle vollständig unmöglich zu machen. Für weitere Informationen hat Michael Steil einige großartige Untersuchungen zur statischen Neukompilierung durchgeführt - das Beste, was ich je gesehen habe.
Die andere Seite der Prozessoremulation ist die Art und Weise, wie Sie mit Hardware interagieren. Das hat wirklich zwei Seiten:
- Prozessor-Timing
- Behandlung unterbrechen
Prozessor-Timing:
Bestimmte Plattformen - insbesondere ältere Konsolen wie NES, SNES usw. - erfordern ein striktes Timing Ihres Emulators, um vollständig kompatibel zu sein. Mit dem NES haben Sie die PPU (Pixel Processing Unit), die erfordert, dass die CPU Pixel zu bestimmten Zeitpunkten in ihren Speicher legt. Wenn Sie die Interpretation verwenden, können Sie Zyklen leicht zählen und das richtige Timing emulieren. Bei der dynamischen / statischen Neukompilierung sind die Dinge viel komplexer.
Interrupt-Behandlung:
Interrupts sind der primäre Mechanismus, den die CPU mit der Hardware kommuniziert. Im Allgemeinen teilen Ihre Hardwarekomponenten der CPU mit, um welche Interrupts es sich handelt. Dies ist ziemlich einfach: Wenn Ihr Code einen bestimmten Interrupt auslöst, sehen Sie sich die Interrupt-Handler-Tabelle an und rufen den richtigen Rückruf auf.
Hardware-Emulation:
Das Emulieren eines bestimmten Hardwaregeräts hat zwei Seiten:
- Emulieren der Funktionalität des Geräts
- Emulieren der tatsächlichen Geräteschnittstellen
Nehmen Sie den Fall einer Festplatte. Die Funktionalität wird durch Erstellen des Sicherungsspeichers, der Lese- / Schreib- / Formatierungsroutinen usw. emuliert. Dieser Teil ist im Allgemeinen sehr einfach.
Die eigentliche Schnittstelle des Gerätes ist etwas komplexer. Dies ist im Allgemeinen eine Kombination aus speicherabgebildeten Registern (z. B. Teilen des Speichers, die das Gerät auf Änderungen überwacht, um die Signalisierung durchzuführen) und Interrupts. Für eine Festplatte verfügen Sie möglicherweise über einen Speicherbereich, in dem Sie Lesebefehle, Schreibvorgänge usw. platzieren und diese Daten dann zurücklesen.
Ich würde näher darauf eingehen, aber es gibt eine Million Möglichkeiten, wie Sie damit umgehen können. Wenn Sie hier spezielle Fragen haben, können Sie diese gerne stellen und ich werde die Informationen hinzufügen.
Ressourcen:
Ich denke, ich habe hier ein ziemlich gutes Intro gegeben, aber es gibt eine Menge zusätzlicher Bereiche. Bei Fragen stehe ich Ihnen gerne zur Verfügung. Ich war in den meisten Fällen sehr vage, einfach wegen der immensen Komplexität.
Obligatorische Wikipedia-Links:
Allgemeine Emulationsressourcen:
- Zophar - Hier begann ich mit der Emulation, indem ich zuerst Emulatoren herunterlud und schließlich ihre riesigen Dokumentationsarchive plünderte. Dies ist die absolut beste Ressource, die Sie möglicherweise haben können.
- NGEmu - Nicht viele direkte Ressourcen, aber ihre Foren sind unschlagbar.
- RomHacking.net - Der Abschnitt Dokumente enthält Ressourcen zur Maschinenarchitektur für gängige Konsolen
Emulator-Projekte als Referenz:
- IronBabel - Dies ist eine Emulationsplattform für .NET, die in Nemerle geschrieben wurde und Code im laufenden Betrieb in C # neu kompiliert. Haftungsausschluss: Dies ist mein Projekt, also entschuldigen Sie den schamlosen Stecker.
- BSnes - Ein großartiger SNES-Emulator mit dem Ziel einer zyklisch perfekten Genauigkeit.
- MAME - Der Arcade-Emulator. Tolle Referenz.
- 6502asm.com - Dies ist ein JavaScript 6502-Emulator mit einem coolen kleinen Forum.
- dynarec'd 6502asm - Dies ist ein kleiner Hack, den ich über ein oder zwei Tage gemacht habe. Ich habe den vorhandenen Emulator von 6502asm.com übernommen und ihn geändert, um den Code für massive Geschwindigkeitssteigerungen dynamisch in JavaScript neu zu kompilieren.
Referenzen zur Prozessorkompilierung:
- Die von Michael Steil (siehe oben) durchgeführten Untersuchungen zur statischen Rekompilierung gipfelten in diesem Artikel. Eine Quelle und dergleichen finden Sie hier .
Nachtrag:
Es ist weit über ein Jahr her, seit diese Antwort eingereicht wurde, und mit all der Aufmerksamkeit, die sie bekommen hat, dachte ich, es ist Zeit, einige Dinge zu aktualisieren.
Das vielleicht aufregendste im Moment in der Emulation ist libcpu , das von dem oben genannten Michael Steil gestartet wurde. Es handelt sich um eine Bibliothek, die eine große Anzahl von CPU-Kernen unterstützen soll, die LLVM für die Neukompilierung verwenden (statisch und dynamisch!). Es hat ein riesiges Potenzial und ich denke, es wird großartige Dinge für die Emulation tun.
Ich wurde auch auf emu-docs aufmerksam gemacht, das ein großes Repository an Systemdokumentation enthält, das für Emulationszwecke sehr nützlich ist. Ich habe dort nicht viel Zeit verbracht, aber es sieht so aus, als hätten sie viele großartige Ressourcen.
Ich bin froh, dass dieser Beitrag hilfreich war, und ich hoffe, dass ich mich bis Ende des Jahres / Anfang nächsten Jahres von meinem Arsch lösen und mein Buch zu diesem Thema fertigstellen kann.