158 lines
8.0 KiB
Markdown
158 lines
8.0 KiB
Markdown
# RISC-V Emulator
|
|
|
|
---
|
|
|
|
## Dokumentation für den Emulator
|
|
|
|
- Das Programm kann mit `./riscv-emulator <ASSEMBLY.txt>` gestartet werden.
|
|
- Dabei ist <ASSEMBLY.txt> ein absoluter oder relativer Pfad zum Quellcode eines RV32I (RISC-V) Programmes. Wie die
|
|
Syntax aussehen muss, ist in der [Dokumentation für den Assembly Code](#dokumentation-für-den-assembly-code)
|
|
beschrieben.
|
|
- Der Emulator führt dann Zeile für Zeile den Quellcode aus.
|
|
- Dabei wird **nach** jeder berechneten Zeile diese ausgegeben und nach weiteren Anweisungen gefragt.
|
|
Es gibt folgende Anweisungen:
|
|
|
|
| Befehl | Abkürzung für | Aktion |
|
|
|:------:|:-------------:|:-------------------------------------------:|
|
|
| s | sprint | Führt den Sourcecode bis zum Ende aus |
|
|
| l | line | Führe die nächste Zeile des Sourcecodes aus |
|
|
| e | end | Beende den Emulator direkt |
|
|
| m | memory | Gibt einen Memorydump in der Konsole aus |
|
|
| r | register | Gibt einen Registerdump in der Konsole aus |
|
|
- Am Ende des Sourcecodes stehen nur noch `e`, `m`, `r` zur Verfügung.
|
|
- Der Emulator wurde von und für Linux implementiert. Das Verhalten auf Windows wurde nicht getestet und kann
|
|
undefiniert
|
|
sein.
|
|
- Es wurden folgende angeforderte Befehle implementiert:
|
|
- add
|
|
- sub
|
|
- and
|
|
- or
|
|
- xor
|
|
- addi
|
|
- andi
|
|
- ori
|
|
- lw
|
|
- sw
|
|
- beq
|
|
- bne
|
|
- jal
|
|
- jalr
|
|
<br>
|
|
Folgende Befehle sind zusätzlich im Hinblick auf
|
|
die [Beispiel Programme](https://moodle.hs-kempten.de/pluginfile.php/463700/mod_resource/content/7/Beispiel_Programme.pdf)
|
|
implementiert worden:
|
|
- j
|
|
- slli
|
|
- Die Beispielprogramme sind im entsprechenden Verzeichnis.
|
|
|
|
## Dokumentation für den Assembly Code
|
|
|
|
- Der Assembly Code Parser wurde auf Grundlage des Cheatsheets
|
|
von [Project F](https://projectf.io/posts/riscv-cheat-sheet/)
|
|
implementiert und stellen die Syntax einer RISC-V (RV32I) Architektur dar.
|
|
- Die Syntax hat damit folgender Struktur zu folgen:
|
|
`<Befehl> <Register/Label>, [Register/Label/Immediate(Register)], [Register/Label/Immediate]`
|
|
- Es gilt folgende Legende:
|
|
`<>`: Verpflichtend
|
|
`/`: Entweder oder
|
|
`[]`: Optional
|
|
- Leerzeichen zwischen z.B. Register und Komma (`x5 , x3`) müssen vermieden werden und führen zu undefiniertem
|
|
Verhalten.
|
|
- Leerzeilen sind erlaubt.
|
|
- Kommentare sind mit `#` beginnend, auch inline, erlaubt.
|
|
- Der Assemblycode muss in einer `.txt` Datei gespeichert werden.
|
|
- Speziell für Beispielprogramm 8: In diesem wird in Zeile 12 der Wert des Pointers auf das Array um 4 erhöht, um somit
|
|
zum nächsten Element zu springen. Aufgrund der Funktionsweise dieses Emulators ist das nicht nötig und der Wert
|
|
braucht
|
|
lediglich um 1 erhöht zu werden. In den beigefügten Beispielprogrammen ist das bereits berücksichtigt.
|
|
|
|
## Funktionsweise
|
|
|
|
- Der Emulator ist Objektorientiert programmiert und implementiert die einzelnen Komponenten in Klassen.
|
|
- Folgende Klassen sind mit folgenden Komponenten implementiert:
|
|
|
|
| Klasse | Komponente | Funktion |
|
|
|:-------------:|:---------------:|:----------------------------------------------------------------------------------------:|
|
|
| ALU | ALU | Die haupt Rechneneinheit, in der die Berechnungen stattfinden. |
|
|
| Memory | RAM | Speichert den Inhalt des RAM in einem Vektor. |
|
|
| Register | Register | Speichert den Inhalt der Register in einem Vektor. |
|
|
| Manager | Program counter | Sorgt für die schrittweise Bearbeitung des Programmes und kombiniert die Nutzereingaben. |
|
|
| ProgramLoader | | Parst die einzelnen Codezeilen und indexiert das Programm nach Labeln |
|
|
|
|
- zusätzlich gibt es folgende Dateien:
|
|
|
|
| Datei | Funktion |
|
|
|:--------:|:----------------------------------------------------------------:|
|
|
| main.cpp | Der Emulator-Starter. Startet die Indexierung und die Emulation. |
|
|
|
|
### Der Emulator arbeitet nach folgender Funktionsweise:
|
|
|
|
Anstatt eines virtuellen program counters wird dieser als Zeilennummern implementiert. Hierbei wird die Funktionsweise
|
|
des
|
|
Einlesen von Dateien in der C++ standard library genutzt. In dieser wird in dem file Objekt die aktuelle Position des
|
|
Lese/
|
|
Schreibkopfes genau gespeichert, Zeile + aktuelles Zeichen. Wird also der PC um 4 erhöht (es wird zum nächsten Befehl
|
|
gesprungen), wird lediglich der Lesekopf um eine Zeile nach vorne verschoben. Für Sprünge zu Labels
|
|
kann der Lesekopf direkt auf eine angegebene Position gesetzt werden.
|
|
|
|
1. Der Manager startet eine Indexierung der Datei in ProgramLoader. Dabei werden die Positionen, an denen der Lesekopf
|
|
auf ein Label stößt, in einer Map gespeichert.
|
|
2. Nachdem der Lesekopf wieder auf den Anfang der Datei gesetzt wurde, wird der Quellcode, vom Manager gesteuert, Zeile
|
|
für Zeile bis zum Ende der Datei im ProgramLoader eingelesen.
|
|
3. Dieser wandelt den Befehl und seine Argumente in einen Vektor um, damit die ALU später einheitlich auf alle Daten
|
|
zugreifen kann.
|
|
4. Die ALU beginnt nun mit dem Aufbereiten der Argumente. Dabei werden führende `x` von Registern entfernt und Labels
|
|
sowie immediate values erkannt.
|
|
5. Nun wird mithilfe eines großen (unschönen) if/else Block zwischen den verschiedenen Befehlen unterschieden und diese
|
|
dann ausgeführt. Dabei kann mithilfe von Getter- und Setter-Methoden der Inhalt des RAM und der Register gelesen und
|
|
verändert werden.
|
|
Sprünge werden mithilfe der in Schritt 1 erstellen Map durchgeführt werden. Eine Angabe der Sprungweite durch einen
|
|
immediate value wird mithilfe eines einfachen for-loops mit dem mehrmaligen Springen in die nächste Zeile des
|
|
Quellcodes
|
|
bewerkstelligt. Eine einfache Addition der Zahl auf die aktuelle Position des Lesekopfes ist nicht möglich, da dessen
|
|
Position wie oben erwähnt auch das Zeilenoffset enthält.
|
|
|
|
Aufgrund der Funktionsweise des Emulators kann dieser nicht mit sehr großen Assembly Dateien umgehen. Das liegt daran,
|
|
dass Sprungadressen nicht als program counter sondern als offset in der jeweiligen Quellcodedatei gespeichert werden.
|
|
Bei sehr großen Dateien könnte das integer Limit überschritten werden.
|
|
|
|
## Kompilierung
|
|
|
|
Der Emulator wurde mithilfe von JetBrains CLion geschrieben. Für einen einfachen Kompilierprozess, code insights
|
|
und weitere Hilfestellungen wird die Verwendung (von zumindest einer C++ fähigen IDE) empfohlen.
|
|
|
|
### Kompilierung in CLion:
|
|
|
|
1. Beim öffnen des Projektordners sollte Clion automatisch das CMake buildsystem erkennen, andernfalls nach der
|
|
Initialisierung fragen.
|
|
2. Nachdem CLion das Projekt ge-indext hat, ist das Kompilieren des Emulators in der rechten, oberen Programmecke mit
|
|
dem Hammer Symbol möglich.
|
|
3. Der fertige Emulator ist nun in, je nach build mode, `./cmake-build-(debug/release)` zu finden.
|
|
|
|
### Kompilierung mit CMake
|
|
|
|
Für die Kompilierung wird CMake benötigt. Das ist ein buildsystem für C/C++ und übernimmt das Kompilieren von großen
|
|
Projekten mit vielen Quellcodedateien. Evtl. muss cmake manuell installiert werden, falls es nicht bereits vorhanden
|
|
ist.
|
|
Hierfür wird das `cmake` Paket benötigt. `make` ist in den meisten Fällen bereits vorinstalliert.
|
|
|
|
1. Im Projektordner einen build Ordner erstellen: `mkdir build && cd build`
|
|
2. CMake konfigurieren: `cmake ..`
|
|
3. Projekt kompilieren: `make`
|
|
|
|
### Kompilierung mit G++ (nicht empfohlen)
|
|
|
|
Bei der manuellen Kompilierung mit g++ müssen alle Quellcodedateien manuell angegeben werden. Aufgrund der
|
|
Unübersichtlichkeit und Fehleranfälligkeit ist dies nicht empfohlen!
|
|
|
|
1. Im Projektordner einen build Ordner erstellen: `mkdir build && cd build`
|
|
2. Projekt kompilieren: `g++ main.cpp Manager.cpp ProgramLoader.cpp ./components/Alu.cpp ./components/Memory.cpp
|
|
./components/Register.cpp`
|
|
|
|
---
|
|
|
|
J. Anders @HS Kempten für VL Rechnerarchitektur, 2025
|
|
|
|
|