added README documentation

This commit is contained in:
black
2025-07-09 22:13:59 +02:00
parent 47889cb5dc
commit e311ae0d7d
2 changed files with 148 additions and 1 deletions

148
README.md
View File

@@ -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

View File

@@ -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