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