Add Renode interface
Add Renode interface to build a shared library of kelvin_sim that can run with Renode framework as an external CPU
PiperOrigin-RevId: 559857693
diff --git a/sim/renode/BUILD b/sim/renode/BUILD
new file mode 100644
index 0000000..f87d6be
--- /dev/null
+++ b/sim/renode/BUILD
@@ -0,0 +1,70 @@
+# Build Renode interface for kelvin
+
+package(
+ default_visibility = ["//visibility:public"],
+)
+
+cc_library(
+ name = "renode_mpact",
+ srcs = [
+ "renode_mpact.cc",
+ ],
+ hdrs = [
+ "renode_mpact.h",
+ ],
+ deps = [
+ ":renode_debug_interface",
+ "@com_google_absl//absl/container:flat_hash_map",
+ "@com_google_absl//absl/log",
+ "@com_google_absl//absl/strings",
+ "@com_google_mpact-sim//mpact/sim/generic:core_debug_interface",
+ "@com_google_mpact-sim//mpact/sim/util/program_loader:elf_loader",
+ ],
+ alwayslink = True,
+)
+
+cc_library(
+ name = "renode_debug_interface",
+ hdrs = ["renode_debug_interface.h"],
+ deps = [
+ "@com_google_absl//absl/status",
+ "@com_google_absl//absl/status:statusor",
+ "@com_google_mpact-sim//mpact/sim/generic:core_debug_interface",
+ ],
+)
+
+cc_library(
+ name = "kelvin_renode",
+ srcs = [
+ "kelvin_renode.cc",
+ "kelvin_renode_reigster_info.cc",
+ ],
+ hdrs = [
+ "kelvin_renode.h",
+ "kelvin_renode_register_info.h",
+ ],
+ deps = [
+ ":renode_debug_interface",
+ "//sim:kelvin_top",
+ "@com_google_absl//absl/status",
+ "@com_google_absl//absl/status:statusor",
+ "@com_google_absl//absl/strings",
+ "@com_google_mpact-riscv//riscv:riscv_debug_info",
+ "@com_google_mpact-sim//mpact/sim/generic:core",
+ "@com_google_mpact-sim//mpact/sim/generic:core_debug_interface",
+ "@com_google_mpact-sim//mpact/sim/generic:instruction",
+ "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+ ],
+ alwayslink = True,
+)
+
+# This rule generates the shared library that is loaded and used by Renode to instantiate and
+# control kelvin sim
+cc_binary(
+ name = "renode_kelvin",
+ linkshared = True,
+ deps = [
+ ":kelvin_renode",
+ ":renode_mpact",
+ ],
+)
diff --git a/sim/renode/kelvin_renode.cc b/sim/renode/kelvin_renode.cc
new file mode 100644
index 0000000..a1df4e7
--- /dev/null
+++ b/sim/renode/kelvin_renode.cc
@@ -0,0 +1,144 @@
+#include "sim/renode/kelvin_renode.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <string>
+
+#include "sim/kelvin_top.h"
+#include "sim/renode/kelvin_renode_register_info.h"
+#include "sim/renode/renode_debug_interface.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "riscv/riscv_debug_info.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/instruction.h"
+
+kelvin::sim::renode::RenodeDebugInterface *CreateKelvinSim(std::string name) {
+ auto *top = new kelvin::sim::KelvinRenode(name);
+ return top;
+}
+
+namespace kelvin::sim {
+
+using HaltReason = mpact::sim::generic::CoreDebugInterface::HaltReason;
+using RunStatus = mpact::sim::generic::CoreDebugInterface::RunStatus;
+using Instruction = mpact::sim::generic::Instruction;
+using RiscVDebugInfo = mpact::sim::riscv::RiscVDebugInfo;
+
+KelvinRenode::KelvinRenode(std::string name) {
+ kelvin_top_ = new KelvinTop(name);
+}
+
+KelvinRenode::~KelvinRenode() { delete kelvin_top_; }
+
+absl::Status KelvinRenode::Halt() { return kelvin_top_->Halt(); }
+absl::StatusOr<int> KelvinRenode::Step(int num_steps) {
+ return kelvin_top_->Step(num_steps);
+}
+absl::Status KelvinRenode::Run() { return kelvin_top_->Run(); }
+absl::Status KelvinRenode::Wait() { return kelvin_top_->Wait(); }
+absl::StatusOr<RunStatus> KelvinRenode::GetRunStatus() {
+ return kelvin_top_->GetRunStatus();
+}
+absl::StatusOr<HaltReason> KelvinRenode::GetLastHaltReason() {
+ return kelvin_top_->GetLastHaltReason();
+}
+
+absl::StatusOr<uint64_t> KelvinRenode::ReadRegister(const std::string &name) {
+ return kelvin_top_->ReadRegister(name);
+}
+
+absl::Status KelvinRenode::WriteRegister(const std::string &name,
+ uint64_t value) {
+ return kelvin_top_->WriteRegister(name, value);
+}
+
+absl::StatusOr<size_t> KelvinRenode::ReadMemory(uint64_t address, void *buf,
+ size_t length) {
+ return kelvin_top_->ReadMemory(address, buf, length);
+}
+
+absl::StatusOr<size_t> KelvinRenode::WriteMemory(uint64_t address,
+ const void *buf,
+ size_t length) {
+ return kelvin_top_->WriteMemory(address, buf, length);
+}
+
+absl::StatusOr<mpact::sim::generic::DataBuffer *>
+KelvinRenode::GetRegisterDataBuffer(const std::string &name) {
+ return kelvin_top_->GetRegisterDataBuffer(name);
+}
+
+bool KelvinRenode::HasBreakpoint(uint64_t address) {
+ return kelvin_top_->HasBreakpoint(address);
+}
+
+absl::Status KelvinRenode::SetSwBreakpoint(uint64_t address) {
+ return kelvin_top_->SetSwBreakpoint(address);
+}
+
+absl::Status KelvinRenode::ClearSwBreakpoint(uint64_t address) {
+ return kelvin_top_->ClearSwBreakpoint(address);
+}
+
+absl::Status KelvinRenode::ClearAllSwBreakpoints() {
+ return kelvin_top_->ClearAllSwBreakpoints();
+}
+
+absl::StatusOr<mpact::sim::generic::Instruction *> KelvinRenode::GetInstruction(
+ uint64_t address) {
+ return kelvin_top_->GetInstruction(address);
+}
+
+absl::StatusOr<std::string> KelvinRenode::GetDisassembly(uint64_t address) {
+ return kelvin_top_->GetDisassembly(address);
+}
+
+absl::StatusOr<uint64_t> KelvinRenode::ReadRegister(uint32_t reg_id) {
+ auto ptr = RiscVDebugInfo::Instance()->debug_register_map().find(reg_id);
+ if (ptr == RiscVDebugInfo::Instance()->debug_register_map().end()) {
+ return absl::NotFoundError(
+ absl::StrCat("Not found reg id: ", absl::Hex(reg_id)));
+ }
+
+ return ReadRegister(ptr->second);
+}
+
+absl::Status KelvinRenode::WriteRegister(uint32_t reg_id, uint64_t value) {
+ auto ptr = RiscVDebugInfo::Instance()->debug_register_map().find(reg_id);
+ if (ptr == RiscVDebugInfo::Instance()->debug_register_map().end()) {
+ return absl::NotFoundError(
+ absl::StrCat("Not found reg id: ", absl::Hex(reg_id)));
+ }
+
+ return WriteRegister(ptr->second, value);
+}
+
+int32_t KelvinRenode::GetRenodeRegisterInfoSize() const {
+ return KelvinRenodeRegisterInfo::GetRenodeRegisterInfo().size();
+}
+
+absl::Status KelvinRenode::GetRenodeRegisterInfo(int32_t index, int32_t max_len,
+ char *name,
+ RenodeCpuRegister &info) {
+ auto const ®ister_info = KelvinRenodeRegisterInfo::GetRenodeRegisterInfo();
+ if ((index < 0 || index >= register_info.size())) {
+ return absl::OutOfRangeError(
+ absl::StrCat("Register info index (", index, ") out of range"));
+ }
+ info = register_info[index];
+ auto const ®_map = RiscVDebugInfo::Instance()->debug_register_map();
+ auto ptr = reg_map.find(info.index);
+ if (ptr == reg_map.end()) {
+ name[0] = '\0';
+ } else {
+ strncpy(name, ptr->second.c_str(), max_len);
+ }
+
+ return absl::OkStatus();
+}
+
+} // namespace kelvin::sim
diff --git a/sim/renode/kelvin_renode.h b/sim/renode/kelvin_renode.h
new file mode 100644
index 0000000..e2a5591
--- /dev/null
+++ b/sim/renode/kelvin_renode.h
@@ -0,0 +1,84 @@
+#ifndef LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_KELVIN_RENODE_H_
+#define LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_KELVIN_RENODE_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+
+#include "sim/kelvin_top.h"
+#include "sim/renode/renode_debug_interface.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/instruction.h"
+
+// This file defines a wrapper class for the KelvinTop. In addition, the .cc
+// class defines a global namespace function that is used by the renode wrapper
+// to create a top simulator instance.
+
+extern kelvin::sim::renode::RenodeDebugInterface *CreateKelvinSim(
+ std::string name);
+
+namespace kelvin::sim {
+
+class KelvinRenode : public renode::RenodeDebugInterface {
+ public:
+ using mpact::sim::generic::CoreDebugInterface::HaltReason;
+ using mpact::sim::generic::CoreDebugInterface::RunStatus;
+ using RenodeCpuRegister = kelvin::sim::renode::RenodeCpuRegister;
+
+ explicit KelvinRenode(std::string name);
+ ~KelvinRenode() override;
+
+ // Request that core stop running override;
+ absl::Status Halt() override;
+ // Step the core by num instructions.
+ absl::StatusOr<int> Step(int num) override;
+ // Allow the core to free-run. The loop to run the instructions should be
+ // in a separate thread so that this method can return. This allows a user
+ // interface built on top of this interface to handle multiple cores running
+ // at the same time.
+ absl::Status Run() override;
+ // Wait until the current core halts execution.
+ absl::Status Wait() override;
+ // Returns the current run status.
+ absl::StatusOr<RunStatus> GetRunStatus() override;
+ // Returns the reason for the most recent halt.
+ absl::StatusOr<HaltReason> GetLastHaltReason() override;
+ // Read/write the named registers.
+ absl::StatusOr<uint64_t> ReadRegister(const std::string &name) override;
+ absl::Status WriteRegister(const std::string &name, uint64_t value) override;
+
+ // Read/write the numeric id registers.
+ absl::StatusOr<uint64_t> ReadRegister(uint32_t reg_id) override;
+ absl::Status WriteRegister(uint32_t reg_id, uint64_t value) override;
+ absl::StatusOr<mpact::sim::generic::DataBuffer *> GetRegisterDataBuffer(
+ const std::string &name) override;
+ // Read/write the buffers to memory.
+ absl::StatusOr<size_t> ReadMemory(uint64_t address, void *buf,
+ size_t length) override;
+ absl::StatusOr<size_t> WriteMemory(uint64_t address, const void *buf,
+ size_t length) override;
+ bool HasBreakpoint(uint64_t address) override;
+ // Set/Clear software breakpoints at the given addresses.
+ absl::Status SetSwBreakpoint(uint64_t address) override;
+ absl::Status ClearSwBreakpoint(uint64_t address) override;
+ // Remove all software breakpoints.
+ absl::Status ClearAllSwBreakpoints() override;
+ absl::StatusOr<mpact::sim::generic::Instruction *> GetInstruction(
+ uint64_t address) override;
+ // Return the string representation for the instruction at the given address.
+ absl::StatusOr<std::string> GetDisassembly(uint64_t address) override;
+ // Return register information.
+ int32_t GetRenodeRegisterInfoSize() const override;
+ absl::Status GetRenodeRegisterInfo(int32_t index, int32_t max_len, char *name,
+ RenodeCpuRegister &info) override;
+
+ private:
+ KelvinTop *kelvin_top_ = nullptr;
+};
+
+} // namespace kelvin::sim
+
+#endif // LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_KELVIN_RENODE_H_
diff --git a/sim/renode/kelvin_renode_register_info.h b/sim/renode/kelvin_renode_register_info.h
new file mode 100644
index 0000000..b54ff85
--- /dev/null
+++ b/sim/renode/kelvin_renode_register_info.h
@@ -0,0 +1,30 @@
+#ifndef LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_KELVIN_RENODE_REGISTER_INFO_H_
+#define LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_KELVIN_RENODE_REGISTER_INFO_H_
+
+#include <vector>
+
+#include "sim/renode/renode_debug_interface.h"
+
+namespace kelvin::sim {
+
+// This file defines a class that is used to store the register information
+// that needs to be provided to renode for the Kelvin registers.
+
+class KelvinRenodeRegisterInfo {
+ public:
+ using RenodeRegisterInfo = std::vector<renode::RenodeCpuRegister>;
+ static const RenodeRegisterInfo &GetRenodeRegisterInfo();
+
+ private:
+ KelvinRenodeRegisterInfo();
+ static KelvinRenodeRegisterInfo *Instance();
+ void InitializeRenodeRegisterInfo();
+ const RenodeRegisterInfo &GetRenodeRegisterInfoPrivate();
+
+ static KelvinRenodeRegisterInfo *instance_;
+ RenodeRegisterInfo renode_register_info_;
+};
+
+} // namespace kelvin::sim
+
+#endif // LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_KELVIN_RENODE_REGISTER_INFO_H_
diff --git a/sim/renode/kelvin_renode_reigster_info.cc b/sim/renode/kelvin_renode_reigster_info.cc
new file mode 100644
index 0000000..8a3be56
--- /dev/null
+++ b/sim/renode/kelvin_renode_reigster_info.cc
@@ -0,0 +1,54 @@
+#include "sim/renode/kelvin_renode_register_info.h"
+#include "riscv/riscv_debug_info.h"
+#include "mpact/sim/generic/type_helpers.h"
+
+namespace kelvin::sim {
+
+KelvinRenodeRegisterInfo *KelvinRenodeRegisterInfo::instance_ = nullptr;
+
+void KelvinRenodeRegisterInfo::InitializeRenodeRegisterInfo() {
+ using DbgReg = mpact::sim::riscv::DebugRegisterEnum;
+
+ renode_register_info_ = {
+ {*DbgReg::kPc, 32, true, false}, {*DbgReg::kX0, 32, true, true},
+ {*DbgReg::kX1, 32, true, false}, {*DbgReg::kX2, 32, true, false},
+ {*DbgReg::kX3, 32, true, false}, {*DbgReg::kX4, 32, true, false},
+ {*DbgReg::kX5, 32, true, false}, {*DbgReg::kX6, 32, true, false},
+ {*DbgReg::kX7, 32, true, false}, {*DbgReg::kX8, 32, true, false},
+ {*DbgReg::kX9, 32, true, false}, {*DbgReg::kX10, 32, true, false},
+ {*DbgReg::kX11, 32, true, false}, {*DbgReg::kX12, 32, true, false},
+ {*DbgReg::kX13, 32, true, false}, {*DbgReg::kX14, 32, true, false},
+ {*DbgReg::kX15, 32, true, false}, {*DbgReg::kX16, 32, true, false},
+ {*DbgReg::kX17, 32, true, false}, {*DbgReg::kX18, 32, true, false},
+ {*DbgReg::kX19, 32, true, false}, {*DbgReg::kX20, 32, true, false},
+ {*DbgReg::kX21, 32, true, false}, {*DbgReg::kX22, 32, true, false},
+ {*DbgReg::kX23, 32, true, false}, {*DbgReg::kX24, 32, true, false},
+ {*DbgReg::kX25, 32, true, false}, {*DbgReg::kX26, 32, true, false},
+ {*DbgReg::kX27, 32, true, false}, {*DbgReg::kX28, 32, true, false},
+ {*DbgReg::kX29, 32, true, false}, {*DbgReg::kX30, 32, true, false},
+ {*DbgReg::kX31, 32, true, false},
+ };
+}
+
+KelvinRenodeRegisterInfo::KelvinRenodeRegisterInfo() {
+ InitializeRenodeRegisterInfo();
+}
+
+const KelvinRenodeRegisterInfo::RenodeRegisterInfo &
+KelvinRenodeRegisterInfo::GetRenodeRegisterInfo() {
+ return Instance()->GetRenodeRegisterInfoPrivate();
+}
+
+KelvinRenodeRegisterInfo *KelvinRenodeRegisterInfo::Instance() {
+ if (instance_ == nullptr) {
+ instance_ = new KelvinRenodeRegisterInfo();
+ }
+ return instance_;
+}
+
+const KelvinRenodeRegisterInfo::RenodeRegisterInfo &
+KelvinRenodeRegisterInfo::GetRenodeRegisterInfoPrivate() {
+ return renode_register_info_;
+}
+
+} // namespace kelvin::sim
diff --git a/sim/renode/renode_debug_interface.h b/sim/renode/renode_debug_interface.h
new file mode 100644
index 0000000..4368763
--- /dev/null
+++ b/sim/renode/renode_debug_interface.h
@@ -0,0 +1,41 @@
+#ifndef LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_RENODE_DEBUG_INTERFACE_H_
+#define LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_RENODE_DEBUG_INTERFACE_H_
+
+#include <cstdint>
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+
+namespace kelvin::sim::renode {
+
+// This structure mirrors the one defined in renode to provide information
+// about the target registers. Do not change, as it maps to the marshaling
+// structure used by renode.
+struct RenodeCpuRegister {
+ int index;
+ int width;
+ bool is_general;
+ bool is_read_only;
+};
+
+class RenodeDebugInterface : public mpact::sim::generic::CoreDebugInterface {
+ public:
+ // These using declarations are required to tell the compiler that these
+ // methods are not overridden nor hidden by the two virtual methods declared
+ // in this class.
+ using mpact::sim::generic::CoreDebugInterface::ReadRegister;
+ using mpact::sim::generic::CoreDebugInterface::WriteRegister;
+ // Read/write the numeric id registers.
+ virtual absl::StatusOr<uint64_t> ReadRegister(uint32_t reg_id) = 0;
+ virtual absl::Status WriteRegister(uint32_t reg_id, uint64_t value) = 0;
+ // Get register information.
+ virtual int32_t GetRenodeRegisterInfoSize() const = 0;
+ virtual absl::Status GetRenodeRegisterInfo(int32_t index, int32_t max_len,
+ char *name,
+ RenodeCpuRegister &info) = 0;
+};
+
+} // namespace kelvin::sim::renode
+
+#endif // LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_RENODE_DEBUG_INTERFACE_H_
diff --git a/sim/renode/renode_mpact.cc b/sim/renode/renode_mpact.cc
new file mode 100644
index 0000000..51e111e
--- /dev/null
+++ b/sim/renode/renode_mpact.cc
@@ -0,0 +1,383 @@
+#include "sim/renode/renode_mpact.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <fstream>
+#include <ios>
+#include <limits>
+#include <string>
+
+#include "sim/renode/renode_debug_interface.h"
+#include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+#include "mpact/sim/util/program_loader/elf_program_loader.h"
+
+// This function must be defined in the library.
+extern kelvin::sim::renode::RenodeDebugInterface *CreateKelvinSim(std::string);
+
+// External "C" functions visible to Renode.
+using kelvin::sim::renode::RenodeAgent;
+using kelvin::sim::renode::RenodeCpuRegister;
+
+// Implementation of the C interface functions. They each forward the call to
+// the corresponding method in RenodeAgent.
+int32_t construct(int32_t max_name_length) {
+ return RenodeAgent::Instance()->Construct(max_name_length);
+}
+int32_t destruct(int32_t id) { return RenodeAgent::Instance()->Destroy(id); }
+int32_t reset(int32_t id) { return RenodeAgent::Instance()->Reset(id); }
+int32_t get_reg_info_size(int32_t id) {
+ return RenodeAgent::Instance()->GetRegisterInfoSize(id);
+}
+int32_t get_reg_info(int32_t id, int32_t index, char *name, void *info) {
+ if (info == nullptr) return -1;
+ return RenodeAgent::Instance()->GetRegisterInfo(
+ id, index, name, static_cast<RenodeCpuRegister *>(info));
+}
+uint64_t load_executable(int32_t id, const char *elf_file_name,
+ int32_t *status) {
+ return RenodeAgent::Instance()->LoadExecutable(id, elf_file_name, status);
+}
+int32_t load_image(int32_t id, const char *file_name, uint64_t address) {
+ return RenodeAgent::Instance()->LoadImage(id, file_name, address);
+}
+int32_t read_register(int32_t id, uint32_t reg_id, uint64_t *value) {
+ return RenodeAgent::Instance()->ReadRegister(id, reg_id, value);
+}
+int32_t write_register(int32_t id, uint32_t reg_id, uint64_t value) {
+ return RenodeAgent::Instance()->WriteRegister(id, reg_id, value);
+}
+uint64_t read_memory(int32_t id, uint64_t address, char *buffer,
+ uint64_t length) {
+ return RenodeAgent::Instance()->ReadMemory(id, address, buffer, length);
+}
+uint64_t write_memory(int32_t id, uint64_t address, const char *buffer,
+ uint64_t length) {
+ return RenodeAgent::Instance()->WriteMemory(id, address, buffer, length);
+}
+uint64_t step(int32_t id, uint64_t num_to_step, int32_t *status) {
+ return RenodeAgent::Instance()->Step(id, num_to_step, status);
+}
+int32_t halt(int32_t id, int32_t *status) {
+ return RenodeAgent::Instance()->Halt(id, status);
+}
+
+namespace kelvin::sim::renode {
+
+RenodeAgent *RenodeAgent::instance_ = nullptr;
+uint32_t RenodeAgent::count_ = 0;
+
+// Create the debug instance by calling the factory function.
+int32_t RenodeAgent::Construct(int32_t max_name_length) {
+ std::string name = absl::StrCat("renode", count_);
+ auto *dbg = CreateKelvinSim(name);
+ if (dbg == nullptr) {
+ return -1;
+ }
+ core_dbg_instances_.emplace(RenodeAgent::count_, dbg);
+ name_length_map_.emplace(RenodeAgent::count_, max_name_length);
+ return RenodeAgent::count_++;
+}
+
+// Destroy the debug instance.
+int32_t RenodeAgent::Destroy(int32_t id) {
+ // Check for valid instance.
+ auto dbg_iter = core_dbg_instances_.find(id);
+ if (dbg_iter == core_dbg_instances_.end()) return -1;
+ delete dbg_iter->second;
+ core_dbg_instances_.erase(dbg_iter);
+ return 0;
+}
+
+int32_t RenodeAgent::Reset(int32_t id) {
+ // Check for valid instance.
+ auto dbg_iter = core_dbg_instances_.find(id);
+ if (dbg_iter == core_dbg_instances_.end()) return -1;
+ // For now, do nothing.
+ return 0;
+}
+
+int32_t RenodeAgent::GetRegisterInfoSize(int32_t id) const {
+ // Check for valid instance.
+ auto dbg_iter = core_dbg_instances_.find(id);
+ if (dbg_iter == core_dbg_instances_.end()) return -1;
+ auto *dbg = dbg_iter->second;
+ return dbg->GetRenodeRegisterInfoSize();
+}
+
+int32_t RenodeAgent::GetRegisterInfo(int32_t id, int32_t index, char *name,
+ RenodeCpuRegister *info) {
+ // Check for valid instance.
+ if (info == nullptr) return -1;
+ auto dbg_iter = core_dbg_instances_.find(id);
+ if (dbg_iter == core_dbg_instances_.end()) return -1;
+ auto *dbg = dbg_iter->second;
+ int32_t max_len = name_length_map_.at(id);
+ auto result = dbg->GetRenodeRegisterInfo(index, max_len, name, *info);
+ if (!result.ok()) return -1;
+ return 0;
+}
+
+// Read the register given by the id.
+int32_t RenodeAgent::ReadRegister(int32_t id, uint32_t reg_id,
+ uint64_t *value) {
+ // Check for valid instance.
+ if (value == nullptr) return -1;
+ auto dbg_iter = core_dbg_instances_.find(id);
+ if (dbg_iter == core_dbg_instances_.end()) return -1;
+ // Read register.
+ auto *dbg = dbg_iter->second;
+ auto result = dbg->ReadRegister(reg_id);
+ if (!result.ok()) return -1;
+ *value = result.value();
+ return 0;
+}
+
+int32_t RenodeAgent::WriteRegister(int32_t id, uint32_t reg_id,
+ uint64_t value) {
+ // Check for valid instance.
+ auto dbg_iter = core_dbg_instances_.find(id);
+ if (dbg_iter == core_dbg_instances_.end()) return -1;
+ // Write register.
+ auto *dbg = dbg_iter->second;
+ auto result = dbg->WriteRegister(reg_id, value);
+ if (!result.ok()) return -1;
+ return 0;
+}
+
+uint64_t RenodeAgent::ReadMemory(int32_t id, uint64_t address, char *buffer,
+ uint64_t length) {
+ // Check for valid desktop.
+ auto dbg_iter = core_dbg_instances_.find(id);
+ if (dbg_iter == core_dbg_instances_.end()) {
+ LOG(ERROR) << "No such core dbg instance: " << id;
+ return 0;
+ }
+ auto *dbg = dbg_iter->second;
+ auto res = dbg->ReadMemory(address, buffer, length);
+ if (!res.ok()) return 0;
+ return res.value();
+}
+
+uint64_t RenodeAgent::WriteMemory(int32_t id, uint64_t address,
+ const char *buffer, uint64_t length) {
+ // Check for valid desktop.
+ auto dbg_iter = core_dbg_instances_.find(id);
+ if (dbg_iter == core_dbg_instances_.end()) {
+ LOG(ERROR) << "No such core dbg instance: " << id;
+ return 0;
+ }
+ auto *dbg = dbg_iter->second;
+ auto res = dbg->WriteMemory(address, buffer, length);
+ if (!res.ok()) return 0;
+ return res.value();
+}
+
+uint64_t RenodeAgent::LoadExecutable(int32_t id, const char *file_name,
+ int32_t *status) {
+ // Check for valid desktop.
+ auto dbg_iter = core_dbg_instances_.find(id);
+ if (dbg_iter == core_dbg_instances_.end()) {
+ LOG(ERROR) << "No such core dbg instance: " << id;
+ *status = -1;
+ return 0;
+ }
+ // Instantiate loader and try to load the file.
+ auto *dbg = dbg_iter->second;
+ mpact::sim::util::ElfProgramLoader loader(dbg);
+ auto load_res = loader.LoadProgram(file_name);
+ if (!load_res.ok()) {
+ LOG(ERROR) << "Failed to load program: " << load_res.status().message();
+ *status = -1;
+ return 0;
+ }
+ uint64_t entry = load_res.value();
+ if (!dbg->WriteRegister("pc", entry).ok()) {
+ LOG(ERROR) << "Failed to write to the pc: " << load_res.status().message();
+ *status = -1;
+ return 0;
+ }
+ *status = 0;
+ return entry;
+}
+
+int32_t RenodeAgent::LoadImage(int32_t id, const char *file_name,
+ uint64_t address) {
+ // Get the debug interface.
+ auto dbg_iter = core_dbg_instances_.find(id);
+ if (dbg_iter == core_dbg_instances_.end()) {
+ LOG(ERROR) << "No such core dbg instance: " << id;
+ return -1;
+ }
+ auto *dbg = dbg_iter->second;
+ // Open up the image file.
+ std::ifstream image_file;
+ image_file.open(file_name, std::ios::in | std::ios::binary);
+ if (!image_file.good()) {
+ LOG(ERROR) << "LoadImage: Input file not in good state";
+ return -1;
+ }
+ char buffer[kBufferSize];
+ size_t gcount = 0;
+ uint64_t load_address = address;
+ do {
+ // Fill buffer.
+ image_file.read(buffer, kBufferSize);
+ // Get the number of bytes that was read.
+ gcount = image_file.gcount();
+ // Write to the simulator memory.
+ auto res = dbg->WriteMemory(load_address, buffer, gcount);
+ // Check that the write succeeded, increment address if it did.
+ if (!res.ok()) {
+ LOG(ERROR) << "LoadImage: Memory write failed";
+ return -1;
+ }
+ if (res.value() != gcount) {
+ LOG(ERROR) << "LoadImage: Memory write failed to write all the bytes";
+ return -1;
+ }
+ load_address += gcount;
+ } while (image_file.good() && (gcount > 0));
+ image_file.close();
+ return 0;
+}
+
+uint64_t RenodeAgent::Step(int32_t id, uint64_t num_to_step, int32_t *status) {
+ // Set the default execution status
+ if (status != nullptr) {
+ *status = static_cast<int32_t>(ExecutionResult::kAborted);
+ }
+
+ // Get the core debug if object.
+ auto *dbg = RenodeAgent::Instance()->core_dbg(id);
+ // Is the debug interface valid?
+ if (dbg == nullptr) {
+ return 0;
+ }
+ if (num_to_step == 0) {
+ if (status != nullptr) {
+ *status = static_cast<int32_t>(ExecutionResult::kOk);
+ }
+ return 0;
+ }
+ // Check the previous halt reason.
+ auto halt_res = dbg->GetLastHaltReason();
+ if (!halt_res.ok()) {
+ return 0;
+ }
+ // If the previous halt reason was a semihost halt request, then we shouldn't
+ // step any further. Just return with "waiting for interrupt" code.
+ using HaltReason = RenodeDebugInterface::HaltReason;
+ if (halt_res.value() == HaltReason::kSemihostHaltRequest) {
+ if (status != nullptr) {
+ *status = static_cast<int32_t>(ExecutionResult::kAborted);
+ }
+ return 0;
+ }
+ // Perform the stepping.
+ uint32_t total_executed = 0;
+ while (num_to_step > 0) {
+ // Check how far to step, and make multiple calls if the number
+ // is greater than <int>::max();
+ int step_count = (num_to_step > std::numeric_limits<int>::max())
+ ? std::numeric_limits<int>::max()
+ : static_cast<int>(num_to_step);
+ auto res = dbg->Step(step_count);
+ // An error occurred.
+ if (!res.ok()) {
+ return total_executed;
+ }
+ int num_executed = res.value();
+ total_executed += num_executed;
+ // Check if the execution was halted due to a semihosting halt request,
+ // i.e., program exit.
+ halt_res = dbg->GetLastHaltReason();
+ if (!halt_res.ok()) {
+ return total_executed;
+ }
+ switch (halt_res.value()) {
+ case HaltReason::kSemihostHaltRequest:
+ return total_executed;
+ break;
+ case HaltReason::kSoftwareBreakpoint:
+ case HaltReason::kHardwareBreakpoint:
+ if (status != nullptr) {
+ *status = static_cast<int32_t>(ExecutionResult::kStoppedAtBreakpoint);
+ }
+ return total_executed;
+ break;
+ case HaltReason::kUserRequest:
+ if (status != nullptr) {
+ *status = static_cast<int32_t>(ExecutionResult::kOk);
+ }
+ return total_executed;
+ break;
+ default:
+ break;
+ }
+
+ // If we stepped fewer instructions than anticipated, stop stepping and
+ // return with no error.
+ if (num_executed < step_count) {
+ if (status != nullptr) {
+ *status = static_cast<int32_t>(ExecutionResult::kOk);
+ }
+ return total_executed;
+ }
+ num_to_step -= num_executed;
+ }
+ if (status != nullptr) {
+ *status = static_cast<int32_t>(ExecutionResult::kOk);
+ }
+ return total_executed;
+}
+
+// Signal the simulator to halt.
+int32_t RenodeAgent::Halt(int32_t id, int32_t *status) {
+ // Get the core debug if object.
+ auto *dbg = RenodeAgent::Instance()->core_dbg(id);
+ // Is the debug interface valid?
+ if (dbg == nullptr) {
+ return -1;
+ }
+ // Request halt.
+ auto halt_status = dbg->Halt();
+ if (!halt_status.ok()) {
+ if (status != nullptr) {
+ *status = static_cast<int32_t>(ExecutionResult::kAborted);
+ }
+ return -1;
+ }
+ // Get the halt status.
+ auto halt_res = dbg->GetLastHaltReason();
+ if (!halt_res.ok()) {
+ if (status != nullptr) {
+ *status = static_cast<int32_t>(ExecutionResult::kAborted);
+ }
+ return -1;
+ }
+ // Map the halt status appropriately.
+ using HaltReason = RenodeDebugInterface::HaltReason;
+ if (status != nullptr) {
+ switch (halt_res.value()) {
+ case HaltReason::kSemihostHaltRequest:
+ *status = static_cast<int32_t>(ExecutionResult::kAborted);
+ break;
+ case HaltReason::kSoftwareBreakpoint:
+ *status = static_cast<int32_t>(ExecutionResult::kStoppedAtBreakpoint);
+ break;
+ case HaltReason::kUserRequest:
+ *status = static_cast<int32_t>(ExecutionResult::kInterrupted);
+ break;
+ case HaltReason::kNone:
+ *status = static_cast<int32_t>(ExecutionResult::kOk);
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+} // namespace kelvin::sim::renode
diff --git a/sim/renode/renode_mpact.h b/sim/renode/renode_mpact.h
new file mode 100644
index 0000000..a295478
--- /dev/null
+++ b/sim/renode/renode_mpact.h
@@ -0,0 +1,111 @@
+#ifndef LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_RENODE_MPACT_H_
+#define LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_RENODE_MPACT_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "sim/renode/renode_debug_interface.h"
+#include "absl/container/flat_hash_map.h"
+
+// This file defines the interface that Renode uses to communicate with the
+// simulator as well as support classes to implement the interface.
+// C interface used by Renode.
+extern "C" {
+// Construct a debug instance connected to a simulator. Returns the non-zero
+// id of the created instance. A return value of zero indicates an error.
+int32_t construct(int32_t max_name_length);
+// Destruct the given debug instance. A negative return value indicates an
+// error.
+int32_t destruct(int32_t id);
+// Return the number of register entries.
+int32_t get_reg_info_size(int32_t id);
+// Return the register entry with the given index. The info pointer should
+// store an object of type RenodeCpuRegister.
+int32_t get_reg_info(int32_t id, int32_t index, char *name, void *info);
+// Load the given executable into the instance with the given id. Return the
+// entry point.
+uint64_t load_executable(int32_t id, const char *elf_file_name,
+ int32_t *status);
+// Load the content of the given file into memory, starting at the given
+// address.
+int32_t load_image(int32_t id, const char *file_name, uint64_t address);
+// Read register reg_id in the instance id, store the value in the pointer
+// given. A return value < 0 is an error.
+int32_t read_register(int32_t id, uint32_t reg_id, uint64_t *value);
+// Write register reg_id in the instance id. A return value < 0 is an error.
+int32_t write_register(int32_t id, uint32_t reg_id, uint64_t value);
+
+uint64_t read_memory(int32_t id, uint64_t address, char *buffer,
+ uint64_t length);
+uint64_t write_memory(int32_t id, uint64_t address, const char *buffer,
+ uint64_t length);
+// Reset the instance. A return value < 0 is an error.
+int32_t reset(int32_t id);
+// Step the instance id by num_to_step instructions. Return the number of
+// instructions stepped. The status is written to the pointer *status.
+uint64_t step(int32_t id, uint64_t num_to_step, int32_t *status);
+// Halt a free running simulator.
+int32_t halt(int32_t id, int32_t *status);
+}
+
+namespace kelvin::sim::renode {
+
+// Execution results.
+enum class ExecutionResult : int32_t {
+ kOk = 0,
+ kInterrupted = 1,
+ kWaitingForInterrupt = 2,
+ kStoppedAtBreakpoint = 3,
+ kStoppedAtWatchpoint = 4,
+ kExternalMmuFault = 5,
+ kAborted = -1,
+};
+// Intermediary between the C interface above and the actual debug interface
+// of the simulator.
+class RenodeAgent {
+ public:
+ using RenodeCpuRegister = kelvin::sim::renode::RenodeCpuRegister;
+ constexpr static size_t kBufferSize = 64 * 1024;
+ // This is a singleton class, so need a static Instance method.
+ static RenodeAgent *Instance() {
+ if (instance_ != nullptr) return instance_;
+ instance_ = new RenodeAgent();
+ return instance_;
+ }
+ // These methods correspond to the C methods defined above.
+ int32_t Construct(int32_t max_name_length);
+ int32_t Destroy(int32_t id);
+ int32_t Reset(int32_t id);
+ int32_t GetRegisterInfoSize(int32_t id) const;
+ int32_t GetRegisterInfo(int32_t id, int32_t index, char *name,
+ RenodeCpuRegister *info);
+ int32_t ReadRegister(int32_t id, uint32_t reg_id, uint64_t *value);
+ int32_t WriteRegister(int32_t id, uint32_t reg_id, uint64_t value);
+ uint64_t ReadMemory(int32_t id, uint64_t address, char *buffer,
+ uint64_t length);
+ uint64_t WriteMemory(int32_t id, uint64_t address, const char *buffer,
+ uint64_t length);
+ uint64_t LoadExecutable(int32_t id, const char *elf_file_name,
+ int32_t *status);
+ int32_t LoadImage(int32_t id, const char *file_name, uint64_t address);
+ uint64_t Step(int32_t id, uint64_t num_to_step, int32_t *status);
+ int32_t Halt(int32_t id, int32_t *status);
+ // Accessor.
+ kelvin::sim::renode::RenodeDebugInterface *core_dbg(int32_t id) const {
+ auto ptr = core_dbg_instances_.find(id);
+ if (ptr != core_dbg_instances_.end()) return ptr->second;
+ return nullptr;
+ }
+
+ private:
+ static RenodeAgent *instance_;
+ static uint32_t count_;
+ RenodeAgent() = default;
+ absl::flat_hash_map<uint32_t, kelvin::sim::renode::RenodeDebugInterface *>
+ core_dbg_instances_;
+ absl::flat_hash_map<uint32_t, int32_t> name_length_map_;
+};
+
+} // namespace kelvin::sim::renode
+
+#endif // LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_RENODE_MPACT_H_
diff --git a/sim/renode/test/BUILD b/sim/renode/test/BUILD
new file mode 100644
index 0000000..50538d3
--- /dev/null
+++ b/sim/renode/test/BUILD
@@ -0,0 +1,43 @@
+# This project contains a small set of tests to test the functionality of
+# the MPACT-Sim/Renode interface.
+
+cc_test(
+ name = "renode_mpact_test",
+ size = "small",
+ srcs = ["renode_mpact_test.cc"],
+ data = [
+ "//sim/test:testfiles/hello_world_mpause.bin",
+ "//sim/test:testfiles/hello_world_mpause.elf",
+ ],
+ deps = [
+ "//sim/renode:kelvin_renode",
+ "//sim/renode:renode_mpact",
+ "@com_google_absl//absl/log:check",
+ "@com_google_absl//absl/strings",
+ "@com_google_googletest//:gtest_main",
+ "@com_google_mpact-riscv//riscv:riscv_debug_info",
+ ],
+)
+
+cc_test(
+ name = "kelvin_renode_test",
+ size = "small",
+ srcs = ["kelvin_renode_test.cc"],
+ data = [
+ "//sim/test:testfiles/hello_world_mpause.bin",
+ "//sim/test:testfiles/hello_world_mpause.elf",
+ ],
+ deps = [
+ "//sim:kelvin_top",
+ "//sim/renode:kelvin_renode",
+ "//sim/renode:renode_debug_interface",
+ "@com_google_absl//absl/log:check",
+ "@com_google_absl//absl/status",
+ "@com_google_absl//absl/strings",
+ "@com_google_googletest//:gtest_main",
+ "@com_google_mpact-riscv//riscv:riscv_debug_info",
+ "@com_google_mpact-sim//mpact/sim/generic:core",
+ "@com_google_mpact-sim//mpact/sim/generic:core_debug_interface",
+ "@com_google_mpact-sim//mpact/sim/util/program_loader:elf_loader",
+ ],
+)
diff --git a/sim/renode/test/kelvin_renode_test.cc b/sim/renode/test/kelvin_renode_test.cc
new file mode 100644
index 0000000..82c01e7
--- /dev/null
+++ b/sim/renode/test/kelvin_renode_test.cc
@@ -0,0 +1,119 @@
+#include "sim/renode/kelvin_renode.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <fstream>
+#include <ios>
+#include <string>
+
+#include "sim/kelvin_top.h"
+#include "sim/renode/renode_debug_interface.h"
+#include "googletest/include/gtest/gtest.h"
+#include "absl/log/check.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "riscv/riscv_debug_info.h"
+#include "mpact/sim/generic/core_debug_interface.h"
+#include "mpact/sim/util/program_loader/elf_program_loader.h"
+
+namespace {
+
+using kelvin::sim::KelvinTop;
+using kelvin::sim::renode::RenodeDebugInterface;
+
+constexpr char kFileName[] = "hello_world_mpause.elf";
+constexpr char kBinFileName[] = "hello_world_mpause.bin";
+// The depot path to the test directory.
+constexpr char kDepotPath[] = "sim/test/";
+constexpr char kTopName[] = "test";
+
+class KelvinRenodeTest : public testing::Test {
+ protected:
+ KelvinRenodeTest() { top_ = new kelvin::sim::KelvinRenode(kTopName); }
+
+ ~KelvinRenodeTest() override { delete top_; }
+
+ RenodeDebugInterface *top_ = nullptr;
+};
+
+// Test the implementation of the added methods in the RenodeDebugInterface.
+TEST_F(KelvinRenodeTest, RegisterIds) {
+ uint32_t word_value;
+ for (int i = 0; i < 32; i++) {
+ uint32_t reg_id =
+ i + static_cast<uint32_t>(mpact::sim::riscv::DebugRegisterEnum::kX0);
+ auto result = top_->ReadRegister(reg_id);
+ EXPECT_TRUE(result.status().ok());
+ word_value = result.value();
+ EXPECT_TRUE(top_->WriteRegister(reg_id, word_value + 1).ok());
+ result = top_->ReadRegister(reg_id);
+ EXPECT_TRUE(result.status().ok());
+ EXPECT_EQ(result.value(), word_value + 1);
+ }
+ // Not found.
+ EXPECT_EQ(top_->ReadRegister(0xfff).status().code(),
+ absl::StatusCode::kNotFound);
+ EXPECT_EQ(top_->WriteRegister(0xfff, word_value).code(),
+ absl::StatusCode::kNotFound);
+}
+
+TEST_F(KelvinRenodeTest, RunElfProgram) {
+ std::string file_name = absl::StrCat(kDepotPath, "testfiles/", kFileName);
+ // Load the program.
+ auto *loader = new mpact::sim::util::ElfProgramLoader(top_);
+ auto result = loader->LoadProgram(file_name);
+ CHECK_OK(result);
+ auto entry_point = result.value();
+ // Run the program.
+ testing::internal::CaptureStdout();
+ EXPECT_TRUE(top_->WriteRegister("pc", entry_point).ok());
+ EXPECT_TRUE(top_->Run().ok());
+ EXPECT_TRUE(top_->Wait().ok());
+ // Check the results.
+ auto halt_result = top_->GetLastHaltReason();
+ CHECK_OK(halt_result);
+ EXPECT_EQ(static_cast<int>(halt_result.value()),
+ static_cast<int>(KelvinTop::HaltReason::kUserRequest));
+ const std::string stdout_str = testing::internal::GetCapturedStdout();
+ EXPECT_EQ("Program exits properly\n", stdout_str);
+ // Clean up.
+ delete loader;
+}
+
+TEST_F(KelvinRenodeTest, RunBinProgram) {
+ std::string file_name = absl::StrCat(kDepotPath, "testfiles/", kBinFileName);
+ constexpr uint32_t kBufferSize = 1024;
+ constexpr uint64_t kBinFileAddress = 0x0;
+ constexpr uint64_t kBinFileEntryPoint = 0x0;
+
+ char buffer[kBufferSize];
+ size_t gcount = 0;
+ uint64_t load_address = kBinFileAddress;
+ std::ifstream image_file;
+ image_file.open(file_name, std::ios::in | std::ios::binary);
+ EXPECT_TRUE(image_file.good());
+ do {
+ image_file.read(buffer, kBufferSize);
+ gcount = image_file.gcount();
+ auto result = top_->WriteMemory(load_address, buffer, gcount);
+ EXPECT_TRUE(result.ok());
+ EXPECT_EQ(result.value(), gcount);
+ load_address += gcount;
+ } while (image_file.good() && (gcount > 0));
+ image_file.close();
+
+ // Run the program.
+ testing::internal::CaptureStdout();
+ EXPECT_TRUE(top_->WriteRegister("pc", kBinFileEntryPoint).ok());
+ EXPECT_TRUE(top_->Run().ok());
+ EXPECT_TRUE(top_->Wait().ok());
+ // Check the results.
+ auto halt_result = top_->GetLastHaltReason();
+ CHECK_OK(halt_result);
+ EXPECT_EQ(static_cast<int>(halt_result.value()),
+ static_cast<int>(KelvinTop::HaltReason::kUserRequest));
+ const std::string stdout_str = testing::internal::GetCapturedStdout();
+ EXPECT_EQ("Program exits properly\n", stdout_str);
+}
+
+} // namespace
diff --git a/sim/renode/test/renode_mpact_test.cc b/sim/renode/test/renode_mpact_test.cc
new file mode 100644
index 0000000..c4fa7d1
--- /dev/null
+++ b/sim/renode/test/renode_mpact_test.cc
@@ -0,0 +1,214 @@
+#include "sim/renode/renode_mpact.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <fstream>
+#include <ios>
+#include <string>
+
+#include "googletest/include/gtest/gtest.h"
+#include "absl/log/check.h"
+#include "absl/strings/str_cat.h"
+#include "riscv/riscv_debug_info.h"
+
+// This file contains a test of the RenodeMpact interface using kelvin.
+
+namespace {
+
+using kelvin::sim::renode::ExecutionResult;
+using mpact::sim::riscv::DebugRegisterEnum;
+
+constexpr char kExecutableFileName[] = "hello_world_mpause.elf";
+constexpr char kBinFileName[] = "hello_world_mpause.bin";
+constexpr uint64_t kBinFileAddress = 0x0;
+constexpr uint64_t kBinFileEntryPoint = 0x0;
+constexpr size_t kBufferSize = 1024;
+
+// The depot path to the test directory.
+constexpr char kDepotPath[] = "sim/test/";
+
+class RenodeMpactTest : public testing::Test {
+ protected:
+ RenodeMpactTest() {
+ sim_id_ = construct(1);
+ CHECK_GE(sim_id_, 0) << sim_id_;
+ }
+
+ ~RenodeMpactTest() override {
+ int32_t result = destruct(sim_id_);
+ CHECK_EQ(result, 0);
+ }
+ int32_t sim_id_;
+};
+
+// Test the reset call.
+TEST_F(RenodeMpactTest, Reset) {
+ CHECK_EQ(reset(sim_id_), 0);
+ CHECK_EQ(reset(10), -1);
+}
+
+// Test that registers can be read and written using the id numbers.
+TEST_F(RenodeMpactTest, RegisterId) {
+ uint32_t xreg = static_cast<uint32_t>(DebugRegisterEnum::kX0);
+ uint64_t value = 0;
+ for (int i = 0; i < 8; i++) {
+ auto error = write_register(sim_id_, xreg + i, value++);
+ EXPECT_EQ(error, 0);
+ }
+ value = 0;
+ uint64_t reg_value;
+ for (int i = 0; i < 8; i++) {
+ auto error = read_register(sim_id_, xreg + i, ®_value);
+ EXPECT_EQ(error, 0);
+ EXPECT_EQ(reg_value, value);
+ value++;
+ }
+ // Wrong debug ids.
+ auto error = read_register(10, xreg, ®_value);
+ EXPECT_EQ(error, -1);
+ error = write_register(10, xreg, reg_value);
+ EXPECT_EQ(error, -1);
+ // Wrong register numbers.
+ error = read_register(sim_id_, xreg - 1, ®_value);
+ EXPECT_EQ(error, -1);
+ error = write_register(sim_id_, xreg - 1, reg_value);
+ EXPECT_EQ(error, -1);
+ // Nullptr.
+ error = read_register(sim_id_, xreg, nullptr);
+ EXPECT_EQ(error, -1);
+}
+
+TEST_F(RenodeMpactTest, LoadExecutable) {
+ const std::string input_file_name =
+ absl::StrCat(kDepotPath, "testfiles/", kExecutableFileName);
+ // Fails - wrong sim id.
+ int32_t status;
+ uint64_t entry_pt;
+ entry_pt = load_executable(100, input_file_name.c_str(), &status);
+ EXPECT_EQ(status, -1);
+ // Fails - wrong file name.
+ entry_pt = load_executable(sim_id_, "wrong_file_name", &status);
+ EXPECT_EQ(status, -1);
+ // Succeeds - correct sim id and file name.
+ entry_pt = load_executable(sim_id_, input_file_name.c_str(), &status);
+ EXPECT_EQ(status, 0);
+ // Read the pc, verify that the entry point matches.
+ uint64_t pc_value;
+ status = read_register(sim_id_, static_cast<uint32_t>(DebugRegisterEnum::kPc),
+ &pc_value);
+ EXPECT_EQ(pc_value, entry_pt);
+ EXPECT_EQ(status, 0);
+}
+
+TEST_F(RenodeMpactTest, ReadWriteMem) {
+ constexpr uint8_t kBytes[] = {0x01, 0x02, 0x03, 0x04, 0xff, 0xfe, 0xfd, 0xfc};
+ int res =
+ write_memory(sim_id_, 0x100, reinterpret_cast<const char *>(kBytes), 8);
+ EXPECT_EQ(res, 8);
+ uint8_t mem_bytes[8] = {0xde, 0xad, 0xbe, 0xef, 0x5a, 0xa5, 0xff, 0x00};
+ res = read_memory(sim_id_, 0x104, reinterpret_cast<char *>(mem_bytes), 1);
+ EXPECT_EQ(res, 1);
+ EXPECT_EQ(mem_bytes[0], kBytes[4]);
+ res = read_memory(sim_id_, 0x100, reinterpret_cast<char *>(mem_bytes), 8);
+ for (int i = 0; i < 8; i++) EXPECT_EQ(kBytes[i], mem_bytes[i]);
+}
+
+TEST_F(RenodeMpactTest, LoadImage) {
+ const std::string input_file_name =
+ absl::StrCat(kDepotPath, "testfiles/", kBinFileName);
+ int32_t ret =
+ load_image(sim_id_, (input_file_name + "xyz").c_str(), kBinFileAddress);
+ EXPECT_LT(ret, 0);
+ ret = load_image(sim_id_, input_file_name.c_str(), kBinFileAddress);
+ EXPECT_EQ(ret, 0);
+ std::ifstream bin_file;
+ bin_file.open(input_file_name, std::ios::in | std::ios::binary);
+ CHECK_EQ(bin_file.good(), true);
+ char file_data[kBufferSize];
+ char memory_data[kBufferSize];
+ uint64_t data_address = kBinFileAddress;
+ size_t gcount;
+ size_t total = 0;
+ do {
+ bin_file.read(file_data, kBufferSize);
+ gcount = bin_file.gcount();
+ auto res = read_memory(sim_id_, data_address, memory_data, gcount);
+ EXPECT_GT(res, 0);
+ EXPECT_EQ(res, gcount);
+ for (int i = 0; i < kBufferSize; ++i)
+ EXPECT_EQ(file_data[i], memory_data[i]) << "Byte: " << total + i;
+ data_address += gcount;
+ total += gcount;
+ } while (gcount == kBufferSize);
+ EXPECT_TRUE(bin_file.eof());
+ bin_file.close();
+}
+
+TEST_F(RenodeMpactTest, StepExecutableProgram) {
+ testing::internal::CaptureStdout();
+ const std::string input_file_name =
+ absl::StrCat(kDepotPath, "testfiles/", kExecutableFileName);
+ int32_t status;
+ (void)load_executable(sim_id_, input_file_name.c_str(), &status);
+ CHECK_EQ(status, 0);
+ // Fail if you use the wrong sim_id_.
+ auto count = step(10, 1, &status);
+ EXPECT_EQ(status, -1);
+ EXPECT_EQ(count, 0);
+ // Zero steps, but no failure if step count is == 0.
+ count = step(sim_id_, 0, &status);
+ EXPECT_EQ(status, static_cast<int32_t>(ExecutionResult::kOk));
+ EXPECT_EQ(count, 0);
+ // Null status should work.
+ count = step(10, 1, nullptr);
+ EXPECT_EQ(count, 0);
+ count = step(10, 0, nullptr);
+ EXPECT_EQ(count, 0);
+ count = step(sim_id_, 1, nullptr);
+ EXPECT_EQ(count, 1);
+ // Counts from 1 to 10.
+ for (int i = 0; i < 10; i++) {
+ count = step(sim_id_, i, &status);
+ EXPECT_EQ(count, i);
+ EXPECT_EQ(status, static_cast<int32_t>(ExecutionResult::kOk));
+ }
+ constexpr uint64_t kStepCount = 500;
+ while (true) {
+ count = step(sim_id_, kStepCount, &status);
+ if (count != kStepCount) break;
+ EXPECT_EQ(status, static_cast<int32_t>(ExecutionResult::kOk));
+ }
+ // Execution should now have completed and the program has printed the proper
+ // exit message.
+ EXPECT_GT(kStepCount, count);
+ EXPECT_EQ(status, static_cast<int32_t>(ExecutionResult::kOk));
+ const std::string stdout_str = testing::internal::GetCapturedStdout();
+ EXPECT_EQ("Program exits properly\n", stdout_str);
+}
+
+TEST_F(RenodeMpactTest, StepImageProgram) {
+ const std::string input_file_name =
+ absl::StrCat(kDepotPath, "testfiles/", kBinFileName);
+ testing::internal::CaptureStdout();
+ auto ret = load_image(sim_id_, input_file_name.c_str(), kBinFileAddress);
+ EXPECT_EQ(ret, 0);
+ auto xreg = static_cast<uint32_t>(DebugRegisterEnum::kPc);
+ auto error = write_register(sim_id_, xreg, kBinFileEntryPoint);
+ EXPECT_EQ(error, 0);
+ constexpr uint64_t kStepCount = 1000;
+ int32_t status;
+ int32_t count;
+ while (true) {
+ count = step(sim_id_, kStepCount, &status);
+ if (count != kStepCount) break;
+ EXPECT_EQ(status, static_cast<int32_t>(ExecutionResult::kOk));
+ }
+ // Execution should now have completed and the program has printed the proper
+ // exit message.
+ EXPECT_GT(kStepCount, count);
+ EXPECT_EQ(status, static_cast<int32_t>(ExecutionResult::kOk));
+ const std::string stdout_str = testing::internal::GetCapturedStdout();
+ EXPECT_EQ("Program exits properly\n", stdout_str);
+}
+
+} // namespace
diff --git a/sim/test/BUILD b/sim/test/BUILD
index 76ad1e3..5ea7564 100644
--- a/sim/test/BUILD
+++ b/sim/test/BUILD
@@ -1,13 +1,18 @@
# Unit tests for kelvin simulator.
-exports_files([
- "testfiles/hello_world_rv32imf.elf",
- "testfiles/kelvin_vldvst.elf",
- "testfiles/rv32i.elf",
- "testfiles/rv32m.elf",
- "testfiles/rv32soft_fp.elf",
- "testfiles/rv32uf_fadd.elf",
-])
+exports_files(
+ srcs = [
+ "testfiles/hello_world_mpause.bin",
+ "testfiles/hello_world_mpause.elf",
+ "testfiles/hello_world_rv32imf.elf",
+ "testfiles/kelvin_vldvst.elf",
+ "testfiles/rv32i.elf",
+ "testfiles/rv32m.elf",
+ "testfiles/rv32soft_fp.elf",
+ "testfiles/rv32uf_fadd.elf",
+ ],
+ visibility = ["//visibility:public"],
+)
cc_test(
name = "kelvin_encoding_test",
diff --git a/sim/test/testfiles/hello_world_mpause.bin b/sim/test/testfiles/hello_world_mpause.bin
new file mode 100644
index 0000000..d30b157
--- /dev/null
+++ b/sim/test/testfiles/hello_world_mpause.bin
Binary files differ