Initial commit for Ripes command line mode
This commit introduces initial support for command-line execution of Ripes.
Command-line mode is enabled when providing the "--mode sh" argument. Based on an input source file, Ripes will assemble the program, execute it, and report back any requested telemetry.
MODIFY
main.cpp
main.cpp
#include <QApplication>
+#include <QCommandLineParser>
#include <QFileInfo>
#include <QMessageBox>
#include <QResource>
#include <QTimer>
#include <iostream>
+#include "src/cmd/cmdoptions.h"
+#include "src/cmd/cmdrunner.h"
#include "src/mainwindow.h"
using namespace std;
-int main(int argc, char** argv) {
- Q_INIT_RESOURCE(icons);
- Q_INIT_RESOURCE(examples);
- Q_INIT_RESOURCE(layouts);
- Q_INIT_RESOURCE(fonts);
+void initParser(QCommandLineParser &parser) {
+ QString helpText =
+ "The command line interface allows for assembling/compiling/executing a "
+ "\n"
+ "program on an arbitrary processor model and subsequent reporting of \n"
+ "execution telemetry.\nCommand line mode is enabled when the '--mode "
+ "sh' argument is provided.";
- QApplication app(argc, argv);
+ helpText.prepend("Ripes command line interface.\n");
+ parser.setApplicationDescription(helpText);
+ QCommandLineOption modeOption("mode", "Ripes mode [gui, sh]", "mode", "gui");
+ parser.addOption(modeOption);
+ Ripes::addCmdOptions(parser);
+}
+
+enum CommandLineParseResult {
+ CommandLineError,
+ CommandLineHelpRequested,
+ CommandLineGUI,
+ CommandLineCMD
+};
+
+CommandLineParseResult parseCommandLine(QCommandLineParser &parser,
+ QString &errorMessage) {
+ const QCommandLineOption helpOption = parser.addHelpOption();
+ if (!parser.parse(QCoreApplication::arguments())) {
+ errorMessage = parser.errorText();
+ return CommandLineError;
+ }
+
+ if (parser.isSet(helpOption))
+ return CommandLineHelpRequested;
+
+ if (parser.value("mode") == "gui")
+ return CommandLineGUI;
+ else if (parser.value("mode") == "sh")
+ return CommandLineCMD;
+ else {
+ errorMessage = "Invalid mode: " + parser.value("mode");
+ return CommandLineError;
+ }
+}
+
+int guiMode(QApplication &app) {
Ripes::MainWindow m;
- // The following sequence of events manages to successfully start the application as maximized, with the processor
- // at a reasonable size. This has been found to be specially a problem on windows.
+ // The following sequence of events manages to successfully start the
+ // application as maximized, with the processor at a reasonable size. This has
+ // been found to be specially a problem on windows.
m.resize(800, 600);
m.showMaximized();
m.setWindowState(Qt::WindowMaximized);
@@ -27,3 +68,41 @@ int main(int argc, char** argv) {
return app.exec();
}
+
+int cmdMode(QCommandLineParser &parser, QApplication &) {
+ QString err;
+ Ripes::CmdModeOptions options;
+ if (!Ripes::parseCmdOptions(parser, err, options)) {
+ std::cerr << "ERROR: " << err.toStdString() << std::endl;
+ parser.showHelp();
+ return 0;
+ }
+ return Ripes::CmdRunner(options).run();
+}
+
+int main(int argc, char **argv) {
+ Q_INIT_RESOURCE(icons);
+ Q_INIT_RESOURCE(examples);
+ Q_INIT_RESOURCE(layouts);
+ Q_INIT_RESOURCE(fonts);
+
+ QApplication app(argc, argv);
+ QCoreApplication::setApplicationName("Ripes");
+
+ QCommandLineParser parser;
+ initParser(parser);
+ QString err;
+ switch (parseCommandLine(parser, err)) {
+ case CommandLineError:
+ std::cerr << "ERROR: " << err.toStdString() << std::endl;
+ parser.showHelp();
+ return 0;
+ case CommandLineHelpRequested:
+ parser.showHelp();
+ return 0;
+ case CommandLineGUI:
+ return guiMode(app);
+ case CommandLineCMD:
+ return cmdMode(parser, app);
+ }
+}

MODIFY
src/CMakeLists.txt
src/CMakeLists.txt
@@ -65,6 +65,7 @@ add_subdirectory(io)
add_subdirectory(utilities)
add_subdirectory(processors)
add_subdirectory(version)
+add_subdirectory(cmd)
# Also link Qt and VSRTL libraries.
target_link_libraries(${RIPES_LIB} PUBLIC

ADD
src/cmd/CMakeLists.txt
+create_ripes_lib(cmd LINK_TO_RIPES_LIB)

ADD
src/cmd/cmdoptions.cpp
+#include "cmdoptions.h"
+#include "processorregistry.h"
+#include <QMetaEnum>
+
+namespace Ripes {
+
+template <typename T>
+QString enumToString(T value) {
+ int castValue = static_cast<int>(value);
+ return QMetaEnum::fromType<T>().valueToKey(castValue);
+}
+
+void addCmdOptions(QCommandLineParser &parser) {
+
+ QCommandLineOption srcOption("src", "Source file", "src");
+ parser.addOption(srcOption);
+
+ QCommandLineOption srcTypeOption("t", "Source type. Options: [c, asm, bin]",
+ "type", "asm");
+ srcTypeOption.setDefaultValue("asm");
+ parser.addOption(srcTypeOption);
+
+ // Processor models. Generate information from processor registry.
+ QStringList processorOptions;
+ for (int i = 0; i < ProcessorID::NUM_PROCESSORS; i++)
+ processorOptions.push_back(
+ enumToString<ProcessorID>(static_cast<ProcessorID>(i)));
+ QString desc =
+ "Processor model. Options: [" + processorOptions.join(", ") + "]";
+ QCommandLineOption processorOption("proc", desc, "proc");
+ parser.addOption(processorOption);
+
+ // ISA extensions.
+ QCommandLineOption isaExtensionsOptions("isaexts",
+ "ISA extensions to enable (comma "
+ "separated)",
+ "isaexts", "");
+ parser.addOption(isaExtensionsOptions);
+
+ QCommandLineOption verboseOption("v", "Verbose output");
+ parser.addOption(verboseOption);
+
+ // Telemtry reporting
+ QCommandLineOption cycles("cycles", "Print # of cycles executed");
+ parser.addOption(cycles);
+ QCommandLineOption cpi("cpi", "Print CPI");
+ parser.addOption(cpi);
+ QCommandLineOption ipc("ipc", "Print IPC");
+ parser.addOption(ipc);
+}
+
+bool parseCmdOptions(QCommandLineParser &parser, QString &errorMessage,
+ CmdModeOptions &options) {
+ options.verbose = parser.isSet("v");
+
+ if (!parser.isSet("src")) {
+ errorMessage = "No source file specified (--src)";
+ return false;
+ }
+ options.src = parser.value("src");
+
+ if (!parser.isSet("t")) {
+ errorMessage = "No source type specified (--t)";
+ return false;
+ }
+
+ if (parser.value("t") == "c") {
+ options.srcType = SourceType::C;
+ } else if (parser.value("t") == "asm") {
+ options.srcType = SourceType::Assembly;
+ } else if (parser.value("t") == "bin") {
+ options.srcType = SourceType::FlatBinary;
+ } else if (parser.value("t") == "elf") {
+ options.srcType = SourceType::ExternalELF;
+ } else {
+ errorMessage = "Invalid source type (--t)";
+ return false;
+ }
+
+ if (!parser.isSet("proc")) {
+ errorMessage = "No processor specified (-proc).";
+ return false;
+ }
+ bool ok;
+ int procID = QMetaEnum::fromType<ProcessorID>().keyToValue(
+ parser.value("proc").toStdString().c_str(), &ok);
+ if (!ok) {
+ errorMessage = "Invalid processor model specified '" +
+ parser.value("proc") + "' (--proc).";
+ return false;
+ }
+ options.proc = static_cast<ProcessorID>(procID);
+
+ if (parser.isSet("isaexts")) {
+ options.isaExtensions = parser.value("isaexts").split(",");
+
+ // Validate the ISA extensions with respect to the selected processor.
+ auto exts = ProcessorRegistry::getDescription(options.proc)
+ .isaInfo()
+ .supportedExtensions;
+
+ for (auto &ext : qAsConst(options.isaExtensions)) {
+ if (!exts.contains(ext)) {
+ errorMessage =
+ "Invalid ISA extension '" + ext + "' specified (--isaexts).";
+ errorMessage +=
+ " Processor '" + enumToString<ProcessorID>(options.proc) + "'";
+ errorMessage += " supports extensions: " + exts.join(", ");
+ return false;
+ }
+ }
+ }
+
+ // Telemtry reporting
+ options.cycles = parser.isSet("cycles");
+ options.cpi = parser.isSet("cpi");
+ options.ipc = parser.isSet("ipc");
+
+ return true;
+}
+
+} // namespace Ripes

ADD
src/cmd/cmdoptions.h
+#pragma once
+
+#include "assembler/program.h"
+#include "processorregistry.h"
+#include <QCommandLineParser>
+
+namespace Ripes {
+
+struct CmdModeOptions {
+ QString src;
+ SourceType srcType;
+ ProcessorID proc;
+ QStringList isaExtensions;
+ bool verbose;
+
+ // telemetry
+ bool cycles = false;
+ bool cpi = false;
+ bool ipc = false;
+};
+
+/// Adds Ripes command-line mode options to a parser.
+void addCmdOptions(QCommandLineParser &parser);
+/// Parses Ripes command-line mode options to a CmdModeOptions struct. Returns
+/// true if options were parsed successfully.
+bool parseCmdOptions(QCommandLineParser &parser, QString &errorMessage,
+ CmdModeOptions &options);
+
+} // namespace Ripes

ADD
src/cmd/cmdrunner.cpp
+#include "cmdrunner.h"
+#include "io/iomanager.h"
+#include "processorhandler.h"
+#include "programutilities.h"
+#include "syscall/systemio.h"
+
+namespace Ripes {
+
+CmdRunner::CmdRunner(const CmdModeOptions &options)
+ : QObject(), m_options(options) {
+ info("Ripes command-line mode", false, true);
+ ProcessorHandler::selectProcessor(m_options.proc);
+
+ // Connect systemIO output to stdout.
+ connect(&SystemIO::get(), &SystemIO::doPrint, this, [&](auto text) {
+ std::cout << text.toStdString();
+ std::flush(std::cout);
+ });
+
+ // TODO: how to handle system input?
+}
+
+int CmdRunner::run() {
+ if (processInput())
+ return 1;
+
+ if (runModel())
+ return 1;
+
+ if (postRun())
+ return 1;
+
+ return 0;
+}
+
+int CmdRunner::processInput() {
+ info("Processing input file", false, true);
+
+ switch (m_options.srcType) {
+ case SourceType::Assembly: {
+ info("Assembling input file '" + m_options.src + "'");
+ QFile inputFile(m_options.src);
+ if (!inputFile.open(QIODevice::ReadOnly)) {
+ error("Failed to open input file");
+ return 1;
+ }
+ auto res = ProcessorHandler::getAssembler()->assembleRaw(
+ inputFile.readAll(), &IOManager::get().assemblerSymbols());
+ if (res.errors.size() == 0)
+ ProcessorHandler::loadProgram(std::make_shared<Program>(res.program));
+ else {
+ error("Error during assembly:");
+ for (auto &err : res.errors)
+ info(err.errorMessage(), true);
+ return 1;
+ }
+ break;
+ };
+ case SourceType::FlatBinary: {
+ info("Loading binary file '" + m_options.src + "'");
+ Program p;
+ QString err = loadFlatBinaryFile(p, m_options.src, 0, 0);
+ if (!err.isEmpty()) {
+ error(err);
+ return 1;
+ }
+ ProcessorHandler::loadProgram(std::make_shared<Program>(p));
+ break;
+ }
+ default:
+ assert(false &&
+ "Command-line support for this source type is not yet implemented");
+ }
+
+ return 0;
+}
+
+int CmdRunner::runModel() {
+ info("Running model", false, true);
+ ProcessorHandler::run();
+
+ // Wait until receiving ProcessorHandler::runFinished signal
+ // before proceeding.
+ QEventLoop loop;
+ QObject::connect(ProcessorHandler::get(), &ProcessorHandler::runFinished,
+ &loop, &QEventLoop::quit);
+ loop.exec();
+ return 0;
+}
+
+int CmdRunner::postRun() {
+ info("Post-run", false, true);
+
+ const auto cycleCount = ProcessorHandler::getProcessor()->getCycleCount();
+ const auto instrsRetired =
+ ProcessorHandler::getProcessor()->getInstructionsRetired();
+ const double cpi =
+ static_cast<double>(cycleCount) / static_cast<double>(instrsRetired);
+ const double ipc = 1 / cpi;
+
+ if (m_options.cycles)
+ info("Cycles: " + QString::number(cycleCount), true);
+
+ if (m_options.cpi)
+ info("CPI: " + QString::number(cpi), true);
+
+ if (m_options.ipc)
+ info("IPC: " + QString::number(ipc), true);
+
+ return 0;
+}
+
+void CmdRunner::info(QString msg, bool alwaysPrint, bool header) {
+ if (m_options.verbose || alwaysPrint) {
+ if (header) {
+ int headerWidth = 80;
+ int headerLength = msg.length();
+ int headerSpaces = (headerWidth - headerLength) / 2 - 2;
+ msg.prepend(" ");
+ msg.append(" ");
+ for (int i = 0; i < headerSpaces; i++) {
+ msg.prepend("=");
+ msg.append("=");
+ }
+ } else
+ msg.prepend("INFO: ");
+ std::cout << msg.toStdString() << std::endl;
+ }
+}
+
+void CmdRunner::error(const QString &msg) {
+ info("ERROR: " + msg, true, false);
+}
+
+} // namespace Ripes

ADD
src/cmd/cmdrunner.h
+#pragma once
+
+#include "cmdoptions.h"
+#include <QObject>
+
+namespace Ripes {
+
+/// The CmdRunner class is used to run Ripes in command-line mode.
+/// Based on a CmdModeOptions struct, it will run the appropriate combination
+/// of source processing (assembler/compiler/...), processor model execution
+/// as well as telemtry gathering and reporting.
+class CmdRunner : public QObject {
+ Q_OBJECT
+public:
+ CmdRunner(const CmdModeOptions &options);
+
+ /// Runs the command-line mode.
+ int run();
+
+private:
+ /// Process the provided source file (assembling, compiling, loading, ...)
+ int processInput();
+
+ /// Runs the processor model until the program is finished.
+ int runModel();
+
+ /// Prints requested telemetry to the console/output file.
+ int postRun();
+ void info(QString msg, bool alwaysPrint = false, bool header = false);
+ void error(const QString &msg);
+
+ CmdModeOptions m_options;
+};
+
+} // namespace Ripes

ADD
src/cmd/programutilities.cpp
+#include "programutilities.h"
+
+namespace Ripes {
+
+QString loadFlatBinaryFile(Program &program, const QString &filepath,
+ unsigned long entryPoint, unsigned long loadAt) {
+ QFile file(filepath);
+ if (!file.open(QIODevice::ReadOnly)) {
+ return "Error: Could not open file " + file.fileName();
+ }
+ ProgramSection section;
+ section.name = TEXT_SECTION_NAME;
+ section.address = loadAt;
+ section.data = file.readAll();
+
+ program.sections[TEXT_SECTION_NAME] = section;
+ program.entryPoint = entryPoint;
+ return QString();
+}
+
+} // namespace Ripes

ADD
src/cmd/programutilities.h
+#pragma once
+
+#include "assembler/program.h"
+#include <QFile>
+
+namespace Ripes {
+
+QString loadFlatBinaryFile(Program &program, const QString &filepath,
+ unsigned long entryPoint, unsigned long loadAt);
+
+} // namespace Ripes

MODIFY
src/edittab.cpp
src/edittab.cpp
#include "assembler/program.h"
#include "ccmanager.h"
+#include "cmd/programutilities.h"
#include "compilererrordialog.h"
#include "editor/codeeditor.h"
#include "io/iomanager.h"
@@ -189,24 +190,36 @@ bool EditTab::loadFile(const LoadFileParams &fileParams) {
auto loadedProgram = std::make_shared<Program>();
switch (fileParams.type) {
case SourceType::C:
- case SourceType::Assembly:
+ case SourceType::Assembly: {
success &= loadSourceFile(*loadedProgram, file);
break;
- case SourceType::FlatBinary:
- success &=
- loadFlatBinaryFile(*loadedProgram, file, fileParams.binaryEntryPoint,
+ }
+ case SourceType::FlatBinary: {
+ QString err = loadFlatBinaryFile(*loadedProgram, file.fileName(),
+ fileParams.binaryEntryPoint,
fileParams.binaryLoadAt);
+ success = err.isEmpty();
+ if (!success) {
+ QMessageBox::warning(this, "Error", err);
+ } else {
+ m_ui->curInputSrcLabel->setText("Flat binary");
+ m_ui->inputSrcPath->setText(file.fileName());
+ disableEditor();
+ }
break;
- case SourceType::InternalELF:
+ }
+ case SourceType::InternalELF: {
success &= loadElfFile(*loadedProgram, file);
break;
- case SourceType::ExternalELF:
+ }
+ case SourceType::ExternalELF: {
// Since there is no related source code for an externally compiled ELF, the
// editor is disabled
disableEditor();
success &= loadElfFile(*loadedProgram, file);
break;
}
+ }
if (success) {
// Move the shared pointer to be the current active program
@@ -368,31 +381,6 @@ void EditTab::disableEditor() {
void EditTab::on_disassembledViewButton_toggled() { updateProgramViewer(); }
-bool EditTab::loadFlatBinaryFile(Program &program, QFile &file,
- unsigned long entryPoint,
- unsigned long loadAt) {
- // Reopen the file, ensuring that we're reading it as a binary (non-text)
- // file:
- file.close();
- if (!file.open(QIODevice::ReadOnly)) {
- QMessageBox::warning(this, "Error",
- "Error: Could not open file " + file.fileName());
- return false;
- }
- ProgramSection section;
- section.name = TEXT_SECTION_NAME;
- section.address = loadAt;
- section.data = file.readAll();
-
- program.sections[TEXT_SECTION_NAME] = section;
- program.entryPoint = entryPoint;
-
- m_ui->curInputSrcLabel->setText("Flat binary");
- m_ui->inputSrcPath->setText(file.fileName());
- disableEditor();
- return true;
-}
-
void EditTab::loadSourceText(const QString &text) {
enableEditor();
setSourceText(text);

MODIFY
src/edittab.h
src/edittab.h
@@ -76,8 +76,6 @@ private:
void compile();
void updateProgramViewer();
- bool loadFlatBinaryFile(Program &program, QFile &file,
- unsigned long entryPoint, unsigned long loadAt);
bool loadSourceFile(Program &program, QFile &file);
bool loadElfFile(Program &program, QFile &file);

MODIFY
src/processorregistry.h
src/processorregistry.h
#pragma once
#include <QMetaType>
+#include <QPointF>
#include <map>
#include <memory>
@@ -27,7 +28,7 @@ enum ProcessorID {
RV64_6S_DUAL,
NUM_PROCESSORS
};
-Q_ENUM_NS(Ripes::ProcessorID); // Register with the metaobject system
+Q_ENUM_NS(ProcessorID); // Register with the metaobject system
// ============================================================================
using RegisterInitialization = std::map<unsigned, VInt>;

MODIFY
src/syscall/control.h
src/syscall/control.h
@@ -15,7 +15,7 @@ class ExitSyscall : public BaseSyscall {
public:
ExitSyscall() : BaseSyscall("Exit", "Exits the program with code 0") {}
void execute() {
- SystemIO::printString("\nProgram exited with code: 0");
+ SystemIO::printString("\nProgram exited with code: 0\n");
ProcessorHandler::getProcessorNonConst()->finalize(
RipesProcessor::FinalizeReason::exitSyscall);
}
@@ -32,7 +32,7 @@ public:
void execute() {
SystemIO::printString(
"\nProgram exited with code: " +
- QString::number(BaseSyscall::getArg(RegisterFileType::GPR, 0)));
+ QString::number(BaseSyscall::getArg(RegisterFileType::GPR, 0)) + "\n");
ProcessorHandler::getProcessorNonConst()->finalize(
RipesProcessor::FinalizeReason::exitSyscall);
}

Initial commit for Ripes command line mode

This commit introduces initial support for command-line execution of Ripes.
Command-line mode is enabled when providing the "--mode sh" argument. Based on an input source file, Ripes will assemble the program, execute it, and report back any requested telemetry.