Ich habe vor ein paar Tagen angefangen, einen einfachen Emulator für den GameBoy zu schreiben. Einfach so zum Spaß, und weil ich schon immer mal einen Emulator schreiben wollte 🙂
Der erste Schritt ist nun, einen CPU-Emulator zu bauen. Die CPU des Gameboy (LR35902) basiert auf dem Intel 8080 und dem Zilog Z80, kann aber nicht alle Instruktionen, dafür aber ein paar extra… Glücklicherweise gibt es aber unglaublich viele Ressourcen zum Gameboy im Netz, wie zum Beispiel diese tolle Tabelle mit allen Instruktionen.
Wie man dort sieht, ist der Instruktionssatz einigermaßen orthogonal aufgebaut. Trotzdem muss man erst mal ein bisschen hinschauen, um die Zusammenhänge in den Bitmustern zu sehen. Ich habe daher lange auf die Tabelle gestarrt, meine Erinnerungen an KV-Diagramme wieder rausgekramt und diese Excel-Tabelle gebaut, in der die Kodierung der Instruktionen (hoffentlich) übersichtlich zusammengefasst ist. Im Rest des Beitrags werde ich diese Tabelle ein bisschen kommentieren
Grundsätzlich kann man die Instruktionen Anhand der ersten beiden Bits in vier Klassen einteilen. Unter 00xx_xxxx finden sich verschiedene allgemeine Instruktionen, unter 01xx_xxxx ein orthogonales Set an Load-Instruktionen, unter 10xx_xxxx Alu-Operationen (ebenfalls wunderbar orthogonal), und 11xx_xxxx enthält schließlich die etwas spezielleren Instruktionen.
Unter 0x00 finden wir NOP, sicherlich keine schlechte Wahl, so „fällt“ die CPU durch genullten Speicher. Interessant ist, das NOP explizit implementiert ist, da es auch ausreichend impotente Loads (z.b. LD A,A) gibt, die mal als NOP gebrauchen kann. Ich denke, man wollte hier wirklich 0x00 explizit freihalten. Die HALT Instruktion ist, 0x76, was nach dem Schema der umgebenden Instruktionen LD (HL), (HL) wäre. Das ist sehr geschickt gewählt: Die Operation hätte keinen Effekt, da ja Quelle und Ziel identisch sind. Auf der anderen Seite brauchen alle Loads mit (HL), also einer Indirektion als Quelle oder Ziel einen Takt länger. LD (HL), (HL) würde also vermutlich zwei Takte brauchen, und den Decoder unnötig verkomplizieren, weil es die einzige Instruktion ist, die zwei indirekte Zugriffe hat. Auf diese Weise hat man Implementierungsaufwand für eine völlig nutzlose Instruktion gespart und hatte gleich noch einen OpCode für HALT frei. Oder aber man hat den Sonderfall übersehen, und die CPU hat sich einfach bei LD (HL), (HL) aufgehängt, und schon war HALT geboren 🙂
Weitere interessante Design-Entscheidungen: Es gibt SCF, Set Carry Flag und CCF, Complement Carry Flag. Das Flag explizit zu löschen ist nicht möglich. Zudem siehtdas Mnemonik CCF verdammt nach Clear Carry Flag aus :(. Es gibt die Instruktionen LD HL, SP+r8 und LD SP, HL, was es vereinfacht HL als Link-Register für Calls zu benutzen.
Als letztes bleiben noch die speziellen Instruktionen, die nur die Gameboy-CPU kennt, die sich hinter dem Präfix 0xCB verstecken. Diese Set beinhaltet alle Arten von Shifts und Bitoperationen, wie man sie wohl für Spiele gut gebrauchen kann. Dieses Set an Instruktionen ist komplett orthogonal, was den Decoder vereinfacht.
Mit diesen Erkenntnissen werde ich jetzt anfangen, die CPU zu implementieren. Ich habe da schon eine interessante Idee, wie man so einen Emulator sehr elegant in C# schreiben kann. Mal sehen, ob das funktioniert. Wenn ich ein bisschen weiter bin, wird es den Code auch auf GitHub geben.