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() {
|
[[noreturn]] void Manager::run() {
|
||||||
if (!m_programFile.is_open()) {
|
if (!m_programFile.is_open()) {
|
||||||
std::cerr << "Bitte zuerst die init() Methode aufrufen!" << "\n" << std::flush;
|
std::cerr << "Bitte zuerst die init() Methode aufrufen!" << "\n" << std::flush;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
ProgramLoader::getInstance().indexFile(m_programFile);
|
ProgramLoader::getInstance().indexFile(m_programFile);
|
||||||
// Die Position des Streams (virtueller Lesekopf) muss nach dem Indexen wieder zurückgesetzt werden
|
// Die Position des Streams (virtueller Lesekopf) muss nach dem Indexen wieder zurückgesetzt werden
|
||||||
|
|||||||
Reference in New Issue
Block a user