added README documentation
This commit is contained in:
148
README.md
148
README.md
@@ -1,2 +1,148 @@
|
||||
# riscv-emulator
|
||||
# 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
|
||||
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ Manager &Manager::getInstance() {
|
||||
[[noreturn]] void Manager::run() {
|
||||
if (!m_programFile.is_open()) {
|
||||
std::cerr << "Bitte zuerst die init() Methode aufrufen!" << "\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
ProgramLoader::getInstance().indexFile(m_programFile);
|
||||
// Die Position des Streams (virtueller Lesekopf) muss nach dem Indexen wieder zurückgesetzt werden
|
||||
|
||||
Reference in New Issue
Block a user