Refactor syscall infrastructure
Syscalls are now modularized and can be easily added as well as grouped
together for a specific ISA/ABI.
MODIFY
src/CMakeLists.txt
src/CMakeLists.txt
@@ -6,15 +6,18 @@ file(GLOB LIB_SOURCES
*.cpp
cachesim/*.cpp
editor/*.cpp
+ syscall/*.cpp
version/version.cpp)
file(GLOB LIB_HEADERS
*.h
cachesim/*.h
editor/*.h
+ syscall/*.h
graphics/*.h)
file(GLOB LIB_UIS
*.ui
cachesim/*.ui
+ syscall/*.ui
)
set(RIPES_LIB ripes_lib)

MODIFY
src/isainfo.h
src/isainfo.h
@@ -120,6 +120,18 @@ const static std::map<RVElfFlags, QString> RVELFFlagStrings {{RVC, "RVC"}, {Floa
template <>
class ISAInfo<ISA::RV32IM> : public ISAInfoBase {
public:
+ enum SysCall {
+ None = 0,
+ PrintInt = 1,
+ PrintFloat = 2,
+ PrintStr = 4,
+ Exit = 10,
+ PrintChar = 11,
+ PrintIntHex = 34,
+ PrintIntBinary = 35,
+ PrintIntUnsigned = 36,
+ Exit2 = 93
+ };
static const ISAInfo<ISA::RV32IM>* instance() {
static ISAInfo<ISA::RV32IM> pr;
return &pr;

MODIFY
src/mainwindow.cpp
src/mainwindow.cpp
#include "ripessettings.h"
#include "savedialog.h"
#include "settingsdialog.h"
+#include "syscall/systemio.h"
#include "version/version.h"
#include "fancytabbar/fancytabbar.h"
@@ -83,10 +84,11 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), m_ui(new Ui::Main
connect(m_editTab, &EditTab::programChanged, ProcessorHandler::get(), &ProcessorHandler::loadProgram);
connect(m_editTab, &EditTab::editorStateChanged, [=] { this->m_hasSavedFile = false; });
- connect(ProcessorHandler::get(), &ProcessorHandler::print, m_processorTab, &ProcessorTab::printToLog);
connect(ProcessorHandler::get(), &ProcessorHandler::exit, m_processorTab, &ProcessorTab::processorFinished);
connect(ProcessorHandler::get(), &ProcessorHandler::runFinished, m_processorTab, &ProcessorTab::runFinished);
+ connect(&SystemIO::get(), &SystemIO::doPrint, m_processorTab, &ProcessorTab::printToLog);
+
// Reset and program reload signals
connect(m_memoryTab, &MemoryTab::reqProcessorReset, m_processorTab, &ProcessorTab::reset);
connect(ProcessorHandler::get(), &ProcessorHandler::reqProcessorReset, m_processorTab, &ProcessorTab::reset);

MODIFY
src/processorhandler.cpp
src/processorhandler.cpp
#include "program.h"
#include "ripessettings.h"
+#include "syscall/riscv_syscall.h"
+
#include <QMessageBox>
#include <QtConcurrent/QtConcurrent>
@@ -19,6 +21,8 @@ ProcessorHandler::ProcessorHandler() {
[=](const auto& size) { m_currentProcessor->setReverseStackSize(size.toUInt()); });
// Update VSRTL reverse stack size to reflect current settings
m_currentProcessor->setReverseStackSize(RipesSettings::value(RIPES_SETTING_REWINDSTACKSIZE).toUInt());
+
+ m_syscallManager = std::make_unique<RISCVSyscallManager>();
}
void ProcessorHandler::loadProgram(std::shared_ptr<Program> p) {
@@ -185,64 +189,8 @@ QString ProcessorHandler::parseInstrAt(const uint32_t addr) const {
}
void ProcessorHandler::handleSysCall() {
- const int argumentRegister = 17; // a7
- const int valueRegister = 10; // a0
- const unsigned int arg = m_currentProcessor->getRegister(argumentRegister);
- const auto val = m_currentProcessor->getRegister(valueRegister);
- switch (arg) {
- case SysCall::None:
- return;
- case SysCall::PrintInt: {
- emit print(QString::number(static_cast<int>(val)));
- return;
- }
- case SysCall::PrintFloat: {
- auto* v_f = reinterpret_cast<const float*>(&val);
- emit print(QString::number(static_cast<double>(*v_f)));
- return;
- }
- case SysCall::PrintStr: {
- QByteArray string;
- char byte;
- unsigned int address = val;
- do {
- byte = static_cast<char>(m_currentProcessor->getMemory().readMem(address++) & 0xFF);
- string.append(byte);
- } while (byte != '\0');
- emit print(QString::fromUtf8(string));
- return;
- }
- case SysCall::Exit2:
- case SysCall::Exit: {
- FinalizeReason fr;
- fr.exitSyscall = true;
- m_currentProcessor->finalize(fr);
- return;
- }
- case SysCall::PrintChar: {
- QString ch = QChar(val);
- emit print(ch);
- break;
- }
- case SysCall::PrintIntHex: {
- emit print("0x" + QString::number(val, 16).rightJustified(currentISA()->bytes(), '0'));
- return;
- }
- case SysCall::PrintIntBinary: {
- emit print("0b" + QString::number(val, 2).rightJustified(currentISA()->bits(), '0'));
- return;
- }
- case SysCall::PrintIntUnsigned: {
- emit print(QString::number(static_cast<unsigned>(val)));
- return;
- }
- default: {
- QMessageBox::warning(nullptr, "Error",
- "Unknown system call argument in register " +
- currentISA()->regAlias(argumentRegister) + ": " + QString::number(arg));
- return;
- }
- }
+ const unsigned int function = m_currentProcessor->getRegister(currentISA()->syscallReg());
+ m_syscallManager->execute(function);
}
void ProcessorHandler::checkProcessorFinished() {

MODIFY
src/processorhandler.h
src/processorhandler.h
#include "processorregistry.h"
#include "program.h"
+#include "syscall/ripes_syscall.h"
#include "vsrtl_widget.h"
@@ -134,12 +135,6 @@ signals:
*/
void reqReloadProgram();
- /**
- * @brief print
- * Print string to log
- */
- void print(const QString&);
-
/**
* @brief exit
* end the current simulation, disallowing further clocking of the processor unless the processor is reset.
@@ -165,6 +160,7 @@ private:
ProcessorID m_currentID = ProcessorID::RV5S;
std::unique_ptr<vsrtl::core::RipesProcessor> m_currentProcessor;
+ std::unique_ptr<SyscallManager> m_syscallManager;
/**
* @brief m_vsrtlWidget

MODIFY
src/processors/ripesprocessor.h
src/processors/ripesprocessor.h
#include "../isainfo.h"
namespace Ripes {
-enum SysCall {
- None = 0,
- PrintInt = 1,
- PrintFloat = 2,
- PrintStr = 4,
- Exit = 10,
- PrintChar = 11,
- PrintIntHex = 34,
- PrintIntBinary = 35,
- PrintIntUnsigned = 36,
- Exit2 = 93
-};
/**
* @brief The StageInfo struct

ADD
src/syscall/control.h
+#pragma once
+
+#include <type_traits>
+
+#include "processorhandler.h"
+#include "ripes_syscall.h"
+#include "systemio.h"
+
+namespace Ripes {
+
+template <typename BaseSyscall>
+class ExitSyscall : public BaseSyscall {
+ static_assert(std::is_base_of<Syscall, BaseSyscall>::value);
+
+public:
+ ExitSyscall() : BaseSyscall("Exit", "Exits the program with code 0") {}
+ void execute() {
+ SystemIO::printString("\nProgram exited with code: 0");
+ FinalizeReason fr;
+ fr.exitSyscall = true;
+ ProcessorHandler::get()->getProcessorNonConst()->finalize(fr);
+ }
+};
+
+template <typename BaseSyscall>
+class Exit2Syscall : public BaseSyscall {
+ static_assert(std::is_base_of<Syscall, BaseSyscall>::value);
+
+public:
+ Exit2Syscall() : BaseSyscall("Exit2", "Exits the program with a code", {{0, "the number to exit with"}}) {}
+ void execute() {
+ SystemIO::printString("\nProgram exited with code: " + QString::number(BaseSyscall::getArg(0)));
+ FinalizeReason fr;
+ fr.exitSyscall = true;
+ ProcessorHandler::get()->getProcessorNonConst()->finalize(fr);
+ }
+};
+
+} // namespace Ripes

ADD
src/syscall/print.h
+#pragma once
+
+#include <type_traits>
+
+#include "processorhandler.h"
+#include "ripes_syscall.h"
+#include "systemio.h"
+
+namespace Ripes {
+
+template <typename BaseSyscall>
+class PrintIntSyscall : public BaseSyscall {
+ static_assert(std::is_base_of<Syscall, BaseSyscall>::value);
+
+public:
+ PrintIntSyscall() : BaseSyscall("PrintInt", "Prints an integer", {{0, "integer to print"}}) {}
+ void execute() {
+ const uint32_t arg0 = BaseSyscall::getArg(0);
+ SystemIO::printString(QString::number(static_cast<int>(arg0)));
+ }
+};
+
+template <typename BaseSyscall>
+class PrintFloatSyscall : public BaseSyscall {
+ static_assert(std::is_base_of<Syscall, BaseSyscall>::value);
+
+public:
+ PrintFloatSyscall() : BaseSyscall("PrintFloat", "Prints a floating point number", {{0, "float to print"}}) {}
+ void execute() {
+ const uint32_t arg0 = BaseSyscall::getArg(0);
+ auto* v_f = reinterpret_cast<const float*>(&arg0);
+ SystemIO::printString(QString::number(static_cast<double>(*v_f)));
+ }
+};
+
+template <typename BaseSyscall>
+class PrintStrSyscall : public BaseSyscall {
+ static_assert(std::is_base_of<Syscall, BaseSyscall>::value);
+
+public:
+ PrintStrSyscall() : BaseSyscall("PrintString", "Prints a null-terminated string", {{0, "address of the string"}}) {}
+ void execute() {
+ const uint32_t arg0 = BaseSyscall::getArg(0);
+ QByteArray string;
+ char byte;
+ unsigned int address = arg0;
+ do {
+ byte = static_cast<char>(ProcessorHandler::get()->getMemory().readMemConst(address++) & 0xFF);
+ string.append(byte);
+ } while (byte != '\0');
+ SystemIO::printString(QString::fromUtf8(string));
+ }
+};
+
+template <typename BaseSyscall>
+class PrintCharSyscall : public BaseSyscall {
+ static_assert(std::is_base_of<Syscall, BaseSyscall>::value);
+
+public:
+ PrintCharSyscall()
+ : BaseSyscall("PrintChar", "Prints an ascii character",
+ {{0, "character to print (only lowest byte is considered)"}}) {}
+ void execute() {
+ const uint32_t arg0 = BaseSyscall::getArg(0);
+ SystemIO::printString(QChar(arg0));
+ }
+};
+
+template <typename BaseSyscall>
+class PrintHexSyscall : public BaseSyscall {
+ static_assert(std::is_base_of<Syscall, BaseSyscall>::value);
+
+public:
+ PrintHexSyscall()
+ : BaseSyscall("PrintIntHex", "Prints an integer (in hexdecimal format left-padded with zeroes)",
+ {{0, "integer to print"}}) {}
+ void execute() {
+ const uint32_t arg0 = BaseSyscall::getArg(0);
+ SystemIO::printString(
+ "0x" + QString::number(arg0, 16).rightJustified(ProcessorHandler::get()->currentISA()->bytes(), '0'));
+ }
+};
+
+template <typename BaseSyscall>
+class PrintBinarySyscall : public BaseSyscall {
+ static_assert(std::is_base_of<Syscall, BaseSyscall>::value);
+
+public:
+ PrintBinarySyscall()
+ : BaseSyscall("PrintIntBinary", "Prints an integer (in binary format left-padded with zeroes)",
+ {{0, "integer to print"}}) {}
+ void execute() {
+ const uint32_t arg0 = BaseSyscall::getArg(0);
+ SystemIO::printString(
+ "0b" + QString::number(arg0, 2).rightJustified(ProcessorHandler::get()->currentISA()->bits(), '0'));
+ }
+};
+
+template <typename BaseSyscall>
+class PrintUnsignedSyscall : public BaseSyscall {
+ static_assert(std::is_base_of<Syscall, BaseSyscall>::value);
+
+public:
+ PrintUnsignedSyscall()
+ : BaseSyscall("PrintIntUnsigned", "Prints an integer (unsigned)", {{0, "integer to print"}}) {}
+ void execute() {
+ const uint32_t arg0 = BaseSyscall::getArg(0);
+ SystemIO::printString(QString::number(static_cast<unsigned>(arg0)));
+ }
+};
+
+} // namespace Ripes

ADD
src/syscall/ripes_syscall.h
+#pragma once
+
+#include <QMessageBox>
+#include <QString>
+#include <functional>
+#include <map>
+#include <memory>
+
+namespace Ripes {
+
+/**
+ * @brief The Syscall class
+ * Base class for all system calls. Must be specialized by an ISA/ABI specific system call class. This class shall
+ * define getArg() based on the given ABI.
+ */
+class Syscall {
+public:
+ Syscall(const QString& name, const QString& description = QString(),
+ const std::map<unsigned, QString>& argumentDescriptions = std::map<unsigned, QString>())
+ : m_name(name), m_description(description), m_argumentDescriptions(argumentDescriptions) {}
+ virtual ~Syscall() {}
+ const QString& name() const { return m_name; }
+ const QString& description() const { return m_description; }
+
+ virtual void execute() = 0;
+
+ virtual uint32_t getArg(unsigned i) const = 0;
+
+protected:
+ const QString m_name;
+ const QString m_description;
+ const std::map<unsigned, QString> m_argumentDescriptions;
+};
+
+class SyscallManager {
+public:
+ /**
+ * @brief execute
+ * Executes the syscall identified by id. Throws out of range exception if syscall @p id is unknown.
+ */
+ void execute(int id) {
+ if (m_syscalls.count(id) == 0) {
+ QMessageBox::warning(nullptr, "Error", "Unknown system call: " + QString::number(id));
+ } else {
+ m_syscalls.at(id)->execute();
+ }
+ }
+
+protected:
+ SyscallManager() {}
+ std::map<int, std::unique_ptr<Syscall>> m_syscalls;
+};
+
+template <class T>
+class SyscallManagerT : public SyscallManager {
+ static_assert(std::is_base_of<Syscall, T>::value);
+
+public:
+ template <class T_Syscall>
+ void emplace(int id) {
+ static_assert(std::is_base_of<T, T_Syscall>::value);
+ assert(m_syscalls.count(id) == 0);
+ m_syscalls.emplace(id, std::make_unique<T_Syscall>());
+ }
+};
+
+} // namespace Ripes

ADD
src/syscall/riscv_syscall.h
+#pragma once
+
+#include "processorhandler.h"
+#include "ripes_syscall.h"
+
+// Syscall headers
+#include "control.h"
+#include "print.h"
+
+namespace Ripes {
+
+class RISCVSyscall : public Syscall {
+public:
+ RISCVSyscall(const QString& name, const QString& description = QString(),
+ const std::map<unsigned, QString>& argumentDescriptions = std::map<unsigned, QString>())
+ : Syscall(name, description, argumentDescriptions) {}
+ ~RISCVSyscall() override {}
+ uint32_t getArg(unsigned i) const override {
+ // RISC-V arguments range from a0-a6
+ assert(i < 7);
+ const int regIdx = 10 + i; // a0 = x10
+ return ProcessorHandler::get()->getRegisterValue(regIdx);
+ }
+};
+
+class RISCVSyscallManager : public SyscallManagerT<RISCVSyscall> {
+public:
+ RISCVSyscallManager() {
+ // Print syscalls
+ emplace<PrintIntSyscall<RISCVSyscall>>(ISAInfo<ISA::RV32IM>::PrintInt);
+ emplace<PrintFloatSyscall<RISCVSyscall>>(ISAInfo<ISA::RV32IM>::PrintFloat);
+ emplace<PrintStrSyscall<RISCVSyscall>>(ISAInfo<ISA::RV32IM>::PrintStr);
+ emplace<PrintCharSyscall<RISCVSyscall>>(ISAInfo<ISA::RV32IM>::PrintChar);
+ emplace<PrintHexSyscall<RISCVSyscall>>(ISAInfo<ISA::RV32IM>::PrintIntHex);
+ emplace<PrintBinarySyscall<RISCVSyscall>>(ISAInfo<ISA::RV32IM>::PrintIntBinary);
+ emplace<PrintUnsignedSyscall<RISCVSyscall>>(ISAInfo<ISA::RV32IM>::PrintIntUnsigned);
+
+ // Control syscalls
+ emplace<ExitSyscall<RISCVSyscall>>(ISAInfo<ISA::RV32IM>::Exit);
+ emplace<Exit2Syscall<RISCVSyscall>>(ISAInfo<ISA::RV32IM>::Exit2);
+ }
+};
+
+} // namespace Ripes

ADD
src/syscall/systemio.h
+#pragma once
+
+#include <QObject>
+
+namespace Ripes {
+
+class SystemIO : public QObject {
+ Q_OBJECT
+public:
+ static SystemIO& get() {
+ static SystemIO sio;
+ return sio;
+ }
+
+ static void printString(const QString& string) { emit get().doPrint(string); }
+
+signals:
+ void doPrint(const QString&);
+
+private:
+ SystemIO() {}
+};
+
+} // namespace Ripes

Refactor syscall infrastructure

Syscalls are now modularized and can be easily added as well as grouped
together for a specific ISA/ABI.