Vielleicht haben sich schon manche hier gefragt wie sie sich einen eigenen Emulator programmieren können. Oft bekommt man bei solch einer Frage Antworten wie "Lern Programmieren" "Kauf dir Bücher, Lern einige Jahre Programmieren und dann weißt du es schon". Das ist nicht sonderlich hilfreich weil es nicht spezifisch auf die Hauptfrage eingeht wie sowas funktioniert.
Es gibt zwar Tutorials die zeigen wie man z.b einen Gameboy Emulator bastelt, aber ich fande das nie wirklich Informativ genug.. das wäre dann vielmehr ein Copy Paste gewesen hätte ich mir damit einen Emulator zusammengebaut. Deshalb entschloss ich damals es sein zu lassen.
Gestern kam mir aber dann die Idee weil ich mich grade mit Assembler befasse. Mir kam der Gedanke "Ich hätte gern eine Art eigenen Interpreter für eine eigene Assemblerähnliche Sprache..!". Meine Idee war es, eine Art Interpreter zu basteln mit eigenem Befehlssatz der Assembler angelehnt war.. jedoch so das ich das ganze konkret auf meine Bedürfnisse anpassen konnte.
Als ich kurz davor war fertig zu werden mit der Planung merkte ich.. "Hey.. das ist doch ne Software CPU was ich hier grade Plane.. Das ist doch wie Emulatoren aufgebaut sind..!". Ich habe also ne CPU im Softwareformat geplant ohne es zu merken zunächst.
Was sind nun aber die Vorteile von einer eigenen (Softwarebasierten) "CPU"?
- Die dafür geschriebenen Programme sind sehr klein (Dateigröße)
- Es ist sehr portabel (man benötigt nur die cpu als Binary)
- Man lernt etwas mehr wie Computer und Emulatoren funktionieren
Die Idee stand nun also..
Nun musste ich entscheiden "Was benötige ich?"..
Ich entschied mich für folgende Eigenschaften:
Code:
Daten-Register: - C0 - C1 - C2 - C3 - C4 - C5 System-Register: - IP (Instruction Pointer) - EC (Error Code - benutzt um Errorcodes zurückzuliefern) - RC (Run Cycles - Anzahl bisher ausgeführte Befehle) - Memory (Programmcode der ausgeführt werden soll)
Doch nur mit Registern allein kann man nichts anfangen.. es fehlen noch die Befehle! Ich habe also überlegt was ich benötigen werde an funktionen..
Ich habe diese Befehle so Basic gehalten wie nur möglich - keine String Manipulationen, Kein Netzwerk Kram oder sonstwas.. sondern Grundlegende Basic Dinge auf denen man weitere Funktionen aufbauen kann.
Beispiel:
Ich habe keine For oder While Schleife implentiert als Befehlssatz, sondern Funktionen die Multifunktionell sind und mit denen man dann auch For, While etc. realisieren kann - und mehr.
Am Ende kam folgender Befehlssatz herraus (Die Zahlen sind die Opcodes):
Code:
Arithmetik: ADD = 00 DEC = 01 MUL = 02 DIV = 03 Register Funktionen: CNST = 04 POP = 05 INSNUM = 06 INSSTR = 07 CHECK = 08 Sprünge: JMPLE = 09 JMPEQ = 10 JMPGE = 11 Interactive: PRINT = 12 INPUT = 13 Other: NOP = 14 EXIT = 15 Register: C0 = 00 C1 = 01 C2 = 02 C3 = 03 C4 = 04 C5 = 05 IP = 06 EC = 07 RC = 08 MEMORY = 09
Nun haben wir also unsere Register um unsere Daten zu speichern die wir in unseren Programmen benötigen, und auch die Funktionen die wir benötigen um unsere Programme aufzubauen.
Wie funktioniert nun unsere CPU?
Ich erkläre euch mal den Grundlegenden Ablauf wie das ganze vom Ablauf ist..
1)
Wir tragen unser Programm das in Form von Opcodes vorliegt in unser MEMORY Register. Das Memory Register enthält unseren gesamten Programmcode.
Unser Opcode sieht Beispielsweise so aus:
Code:
06 00 00 //INSNUM 0 0 06 01 01 //INSNUM 1 1 06 02 09 //INSNUM 2 9 06 04 12 //INSNUM 4 12 00 00 01 //ADD 0 1 08 03 00 02 //CHECK 3 0 2 09 03 04 //JMPLE 3 4 15 //EXIT
Unser Opcode sieht nun folgendermaßen aus:
Code:
new Array(6,0,0,6,1,1,6,02,9,6,4,12,0,0,1,8,3,0,2,9,3,4,15);
Nun sagen wir unserer CPU "Hey, ich habe hier ein Programm..lauf das Programm mal durch und führe es aus".
Doch wie macht unsere CPU dies? Nun.. unsere CPU hat die gesamten Funktionen die wir als Befehlsatz haben bereits vorhanden, und da diese Befehle jeweils bestimmten Opcodes zugeordnet sind weiß es welche Befehle ausgeführt werden sollen.
In unserem Beispiel steht als Beispiel am Anfang
Code:
6 0 0
Und da wir wissen das unser Befehl INSNUM 2 Werte annimmt, weiß unsere CPU auch das diese Werte jeweils auf IP+1 und IP+2 liegen (Instruction Pointer).
Der Instruction Pointer könnt ihr euch Vorstellen wie eine Positionsangabe für die Position an der wir uns zur Zeit in unserem Programm befinden.
Befinden wir uns beim Start des Programms, steht der IP auf 0.
Führen wir nun aber unser erstes INSNUM aus, wird der IP um 3 erhöht.
Wieso? Nunja, der Opcode für INSNUM ist 06.. das ist die erste Stelle.
Die 2 Werte die INSNUM benötigt stehen jeweils an IP+1 (0) und IP+2 (0).
Diese 2 Werte nehmen weitere 2 Stellen in unserem Programm ein.
2+1 = 3.. Deshalb 3 Stellen.
Wir wissen nun also das wir beim nächsten Sprung nicht nur den Instruction Pointer um eins erhöhen dürfen, weil wir sonst ein Crash in unserem Programm erzeugen würden. Wir müssen zum nächsten Befehl springen, und dieser liegt 3 Schritte vorne. Das erhöhen des Instruction Pointer müssen wir aber nicht manuell in unserem späteren Programm das in Opcode vorliegt erledigen - das macht die CPU für uns sobald sie den Befehl ausgeführt hat.
Wenn wir nun unseren ersten Befehl abgearbeitet haben, springen wir anschließend zum nächsten Befehl.. dieser wird dann auch wieder ausgeführt. Das macht unsere CPU mit allen Befehlen bis sie am Ende des Programmes angekommen ist (Opcode 15, Exit).
Das ist Grundsätzlich was hierbei passiert.
Die einzelnen Funktionen die wir im Befehlssatz stehen haben erfüllen verschiedene Funktionen. Wir können mit ihnen an andere Stellen im Programm springen, Daten speichern, Rechnungen ausführen, Text einlesen und ausgeben und weitere Dinge.
Lassen wir uns doch mal anschauen wie das ganze nun implentiert ausschaut, okay? Damit ihr auch ein Verständniss davon habt wie das ganze dann in Code ausschauen kann
Unser Befehlssatz:
Programm Befehle parsen & ausführen (Opcode):
Wir haben hier noch zusätzlich einige Dinge im Code wie Beispielsweise die Errorcodes.. das ist deshalb vorhanden damit das Debuggen des Programmcodes erleichtert wird. Es ist enorm Dämlich wenn man nicht weiß wo der Fehler steckt.. deshalb gibt es Errorcodes die bei bekannten Fehlern dann jeweils das Error-Code Register befüllen.
Mit dem bisher vorhandenen können wir nun schon unser erstes Programm schreiben das unsere CPU ausführen kann
Was kann das Programm? Es ist eine For-Schleife die von 0 bis 9 zählt
Zunächst bauen wir das Programm für Menschen lesbar auf damit wir es einfach haben.. wir benutzen hierfür keine Opcodes sondern die normalen Befehle die wir zur Verfügung haben.
Code:
INSNUM C0, 0 //var a = 0 INSNUM C1, 1 //var b = 1 INSNUM C2, 9 //for(x<9) INSNUM C4, 12 //Speicherstelle 12 vormerken ADD C0, C1 //C0 = C0 + C1 CHECK C3, C0, C2 //Vergleiche C0 mit C2 um festzustellen ob schon 9-10 Durchläufe durchgelaufen sind. Ergebniss in C3 packen JMPLE C3, C4 //C3 kleiner? Springe zu Speicherstelle die in C4 vorgemerkt ist EXIT //Schleife fertig? Programm beenden
Code:
for(int x=0;x<9;x++)
{
}
Kurz gesagt definieren wir 2 Werte.. einmal die Zahl 0 und einmal die Zahl 1.
Wir zählen diese Werte nun immer wieder zusammen ..
0+1
1+1
2+1
3+1
...
Dadurch zählt unser Programm immer um eins höher.
Und unser Programm kontrolliert nach jedem Durchlauf "haben wir schon die 9 erreicht?". Wenn die 9 erreicht ist, beendet sich unser Programm.
Da unsere CPU aber mit dem oben geschriebenen Programm so nichts anfangen kann, müssen wir das ganze in Opcode umwandeln.
Opcode steht für Operationscode.. kurzgesagt ist dies einfach eine Zahl die einem Befehl zugeordnet ist (siehe oben der Befehlssatz inklusive der Opcodes). Fangen wir also mal an..
Code:
06 00 00 //INSNUM 0 0 06 01 01 //INSNUM 1 1 06 02 09 //INSNUM 2 9 06 04 12 //INSNUM 4 12 00 00 01 //ADD 0 1 08 03 00 02 //CHECK 3 0 2 09 03 04 //JMPLE 3 4 15 //EXIT
Code:
Schritt 1) 06 00 00 06 01 01 06 02 09 06 04 12 00 00 01 08 03 00 02 09 03 04 15 Schritt 2) new Array(6,0,0,6,1,1,6,02,9,6,4,12,0,0,1,8,3,0,2,9,3,4,15);
Und.. es klappt! Wuhu *freu*
Unser Programm wird abgearbeitet, und zählt bis 9
Wir haben nun also eine eigene kleine Software CPU womit wir Mini Programme schreiben können die enorm klein von der Dateigröße her sind und freuen uns einen Ast
Für alle die der Gesamte Code, die Dokumentation etc. Interessiert habe ich hier mal einen Download-Link für die Javascript Dateien inklusive Dokumentationen des Befehlssatzes.

Virustotal:

Es wäre möglich das sich noch der ein oder andere Bug darin befindet.. solltet ihr einen finden könnt ihr mir gerne ne PM schicken. Ich werde den Befehlssatz sicher auch in Zukunft vielleicht noch erweitern.
Zudem noch als Erwähnung.. der Quellcode ist nicht auf Schnelligkeit ausgelegt oder sonstwie Optimiert. Das ganze diente mir nur als Lern-Hilfe um dahinter zu steigen wie sowas funktioniert & ist bisher meine erste CPU Emulation. Seit also bitte nicht zu Streng wegen dem Code
grüße







