Im diesem Artikel beginne ich, den ersten Teil des Emulators zu implementieren.
Der aktuelle Projektstand befindet sich in meinem Gitlab-Projekt TSC64Emu.
Der virtuelle Arbeitsspeicher
Als erstes benötigen wir eine Klasse, die den virtuellen Speicher darstellt (src/memory/Memory.ts).
Das einzig besondere an der Klasse ist die konfigurierbare Byte-Reihenfolge. Beim erstellen eines Speicherobjekts gibt man die Größe in Bytes und die Byte-Reihenfolge (Memory.LITTLE_ENDIAN oder Memory.BIG_ENDIAN) an, in der WORD-Elemente abgelegt werden.
Um die Funktion der Klasse zu testen, habe ich einige Tests angelegt, die die grundlegenden Funktionen testet:
- Byte lesen
- Byte schreiben
- Word lesen
- Word schreiben
Die Tests sind in src/test/01_Memory.spec.ts implementiert.
Die virtuelle CPU
Als nächstes benötigen wird die virtuelle CPU. Diese besteht aus den Registern PC (Program Counter), SP (Stack Pointer), AC (Accumulator), X, Y und dem Status-Register.
Die Register habe ich in eine eigene Klasse ausgelagert (src/cpu/Register.ts).
Dort sind auch zwei Funktionen, die ein Flag im Status-Register setzen oder löschen.
In der eigentlichen CPU Klasse (src/cpu/CPU.ts) wird zur Zeit nicht viel getan.
Die Step-Funktion wird immer aufgerufen, wenn wir in der HTML-Seite den STEP-Button anklicken:
/**
* executes one cpu cycle (fetch - decode - execute)
*/
public step() : void {
const opcode = this.fetch();
this.execute(opcode);
}
Es wird von der fetch Funktion der Inhalt des Speichers an der Addresse gelesen, die im PC Register steht. Danach wird das PC-Register um eins erhöht.
Der gelesene Inhalt der Speicherstelle ist der nächste auszuführende Opcode.
In der execute Funktion wird mit dem switch-Statement der auszuführende Opcode ermittelt.
/* LDA */
case 0xa9:
this.reg.ac = this.mem.readByte(this.reg.pc++);
this.reg.ac == 0 ? this.reg.setFlag(Register.FLAG_Z) : this.reg.clearFlag(Register.FLAG_Z);
this.reg.ac & 0x80 ? this.reg.setFlag(Register.FLAG_N) : this.reg.clearFlag(Register.FLAG_N);
break;
Der LDA Befehl mit dem Opcode 0xa9 liest den Inhalt der nächsten Speicherstelle auf den der PC zeigt in den Akkumulator. Danach wird geprüft ob der gelesene Wert Negativ oder Null ist und das Status-Register entsprechend angepasst.
/* STA */
case 0x85:
this.mem.writeByte(this.mem.readByte(this.reg.pc++), this.reg.ac);
break;
Der STA Befehl schreibt den Inhalt des Akkumulators an die Speicherstelle, auf die das PC Register zeigt.
Die CPU-Testfälle zeigen die Anwendung der beiden Befehle, da dort erst der Speicher befüllt wird
a9 20 => LDA #20
85 08 => STA $08
Damit man die Funktion des Emulator in dieser einfachen Form prüfen kann, gibt es die CPUView Klasse. Diese Klasse fügt den HTML-Inhalt mit dem CPU und Speicher-Status in die HTML-Vorlage ein. Dazu wird das div-Elements mit der id root verwendet.
Man hätte hier den HTML Inhalt auch in die Vorlage schreiben können, aber ich wollte flexibel sein, wenn ich irgendwann die View-Klasse durch eine Alternative austauschen möchte.
Am Ende wird noch in der index.ts Datei der Controller definiert, der CPU und Speicher mit der Ausgabe verbindet. Letztlich legt die Einstiegsfunktion die Objekte an und übergibt sie der Controller-Klasse.
Dies war nur ein kurzer Beitrag, mit dem ich die Entwicklung gestartet habe. Die lauffähige Version befindet sich hier.
Als nächstes implementiere ich die Addressierungsarten der CPU und eine Opcode-Tabelle. Darin sind alle Opcodes mit Namen, Länge in Bytes und Dauer in CPU Zyklen hinterlegt.