From e311ae0d7dffb7fb06f2a6ba913c9e174c36d90d Mon Sep 17 00:00:00 2001 From: black Date: Wed, 9 Jul 2025 22:13:59 +0200 Subject: [PATCH] added README documentation --- README.md | 148 +++++++++++++++++++++++++++++++++++++++++++++++- src/Manager.cpp | 1 + 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e8b92bd..7c5a274 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,148 @@ -# riscv-emulator +# RISC-V Emulator + +--- + +## Dokumentation für den Emulator + +- Das Programm kann mit `./riscv-emulator ` gestartet werden. +- Dabei ist 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 +
+ 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: + ` , [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 + diff --git a/src/Manager.cpp b/src/Manager.cpp index 598c2ee..92a4ec1 100644 --- a/src/Manager.cpp +++ b/src/Manager.cpp @@ -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