Add Renode memory interface
Add a memory interface class to access memory allocated by Renode. KelvinTop only accesses the memory via load/store, but it does not control the life cycle of the memory block.
PiperOrigin-RevId: 565185383
diff --git a/sim/BUILD b/sim/BUILD
index de6e67c..8ea30ec 100644
--- a/sim/BUILD
+++ b/sim/BUILD
@@ -131,6 +131,7 @@
":kelvin_decoder",
":kelvin_isa",
":kelvin_state",
+ "//sim/renode:kelvin_renode_memory",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/flags:flag",
"@com_google_absl//absl/functional:bind_front",
diff --git a/sim/kelvin_top.cc b/sim/kelvin_top.cc
index 5ec7fa9..0862639 100644
--- a/sim/kelvin_top.cc
+++ b/sim/kelvin_top.cc
@@ -18,6 +18,7 @@
#include "sim/decoder.h"
#include "sim/kelvin_enums.h"
#include "sim/kelvin_state.h"
+#include "sim/renode/kelvin_renode_memory.h"
#include "absl/flags/flag.h"
#include "absl/functional/bind_front.h"
#include "absl/log/check.h"
@@ -81,6 +82,18 @@
Initialize();
}
+KelvinTop::KelvinTop(std::string name, uint64_t memory_block_size_bytes,
+ uint64_t memory_size_bytes,
+ uint8_t **memory_block_ptr_list)
+ : Component(std::move(name)),
+ counter_num_instructions_{"num_instructions", 0},
+ counter_num_cycles_{"num_cycles", 0} {
+ // Use Kelvin renode memory for this core.
+ memory_ = new renode::KelvinRenodeMemory(
+ memory_block_size_bytes, memory_size_bytes, memory_block_ptr_list);
+ Initialize();
+}
+
KelvinTop::~KelvinTop() {
// If the simulator is still running, request a halt (set halted_ to true),
// and wait until the simulator finishes before continuing the destructor.
diff --git a/sim/kelvin_top.h b/sim/kelvin_top.h
index 3f8aace..0ab6792 100644
--- a/sim/kelvin_top.h
+++ b/sim/kelvin_top.h
@@ -46,6 +46,9 @@
using RunStatus = mpact::sim::generic::CoreDebugInterface::RunStatus;
explicit KelvinTop(std::string name);
+ KelvinTop(std::string name, uint64_t memory_block_size_bytes,
+ uint64_t memory_size_bytes, uint8_t **memory_block_ptr_list);
+
~KelvinTop() override;
// Methods inherited from CoreDebugInterface.
diff --git a/sim/renode/BUILD b/sim/renode/BUILD
index a215a01..e19ac2e 100644
--- a/sim/renode/BUILD
+++ b/sim/renode/BUILD
@@ -36,6 +36,24 @@
)
cc_library(
+ name = "kelvin_renode_memory",
+ srcs = [
+ "kelvin_renode_memory.cc",
+ ],
+ hdrs = [
+ "kelvin_renode_memory.h",
+ ],
+ deps = [
+ "//sim:kelvin_state",
+ "@com_google_absl//absl/base:core_headers",
+ "@com_google_absl//absl/numeric:bits",
+ "@com_google_mpact-sim//mpact/sim/generic:core",
+ "@com_google_mpact-sim//mpact/sim/generic:instruction",
+ "@com_google_mpact-sim//mpact/sim/util/memory",
+ ],
+)
+
+cc_library(
name = "kelvin_renode",
srcs = [
"kelvin_renode.cc",
diff --git a/sim/renode/kelvin_renode.cc b/sim/renode/kelvin_renode.cc
index 618a08f..700feaa 100644
--- a/sim/renode/kelvin_renode.cc
+++ b/sim/renode/kelvin_renode.cc
@@ -21,6 +21,14 @@
return top;
}
+kelvin::sim::renode::RenodeDebugInterface *CreateKelvinSim(
+ std::string name, uint64_t memory_block_size_bytes,
+ uint64_t memory_size_bytes, uint8_t **block_ptr_list) {
+ auto *top = new kelvin::sim::KelvinRenode(name, memory_block_size_bytes,
+ memory_size_bytes, block_ptr_list);
+ return top;
+}
+
namespace kelvin::sim {
using HaltReasonValueType =
@@ -33,6 +41,13 @@
kelvin_top_ = new KelvinTop(name);
}
+KelvinRenode::KelvinRenode(std::string name, uint64_t memory_block_size_bytes,
+ uint64_t memory_size_bytes,
+ uint8_t **block_ptr_list) {
+ kelvin_top_ = new KelvinTop(name, memory_block_size_bytes, memory_size_bytes,
+ block_ptr_list);
+}
+
KelvinRenode::~KelvinRenode() { delete kelvin_top_; }
absl::Status KelvinRenode::Halt() { return kelvin_top_->Halt(); }
diff --git a/sim/renode/kelvin_renode.h b/sim/renode/kelvin_renode.h
index 331d912..0763f34 100644
--- a/sim/renode/kelvin_renode.h
+++ b/sim/renode/kelvin_renode.h
@@ -20,6 +20,10 @@
extern kelvin::sim::renode::RenodeDebugInterface *CreateKelvinSim(
std::string name);
+extern kelvin::sim::renode::RenodeDebugInterface *CreateKelvinSim(
+ std::string name, uint64_t memory_block_size_bytes,
+ uint64_t memory_size_bytes, uint8_t **block_ptr_list);
+
namespace kelvin::sim {
class KelvinRenode : public renode::RenodeDebugInterface {
@@ -29,6 +33,9 @@
using RenodeCpuRegister = kelvin::sim::renode::RenodeCpuRegister;
explicit KelvinRenode(std::string name);
+ explicit KelvinRenode(std::string name, uint64_t memory_block_size_bytes,
+ uint64_t memory_size_bytes, uint8_t **block_ptr_list);
+
~KelvinRenode() override;
// Request that core stop running override;
diff --git a/sim/renode/kelvin_renode_memory.cc b/sim/renode/kelvin_renode_memory.cc
new file mode 100644
index 0000000..15f5d37
--- /dev/null
+++ b/sim/renode/kelvin_renode_memory.cc
@@ -0,0 +1,165 @@
+#include "sim/renode/kelvin_renode_memory.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+
+#include "sim/kelvin_state.h"
+#include "absl/base/macros.h"
+#include "absl/numeric/bits.h"
+
+namespace kelvin::sim::renode {
+
+KelvinRenodeMemory::KelvinRenodeMemory(uint64_t block_size_bytes,
+ uint64_t memory_size_bytes,
+ uint8_t **block_ptr_list,
+ uint64_t base_address,
+ unsigned addressable_unit_size)
+ : addressable_unit_size_(addressable_unit_size),
+ allocation_byte_size_(block_size_bytes * addressable_unit_size),
+ memory_block_size_bytes_(block_size_bytes),
+ base_address_(base_address),
+ max_address_(base_address + memory_size_bytes) {
+ // Available memory should be greater than Kelvin's default 4MB address space.
+ ABSL_HARDENING_ASSERT(max_address_ > kelvin::sim::kKelvinMaxMemoryAddress);
+ uint64_t num_block = (max_address_ + block_size_bytes - 1) / block_size_bytes;
+ // Build the block map.
+ for (int i = 0; i < num_block; ++i) {
+ block_map_.push_back(block_ptr_list[i]);
+ }
+ addressable_unit_shift_ = absl::bit_width(addressable_unit_size) - 1;
+}
+
+bool KelvinRenodeMemory::IsValidAddress(uint64_t address,
+ uint64_t high_address) {
+ return (address >= base_address_) && (high_address <= max_address_);
+}
+
+void KelvinRenodeMemory::Load(uint64_t address, DataBuffer *db,
+ Instruction *inst, ReferenceCount *context) {
+ int size_in_units = db->size<uint8_t>() / addressable_unit_size_;
+ uint64_t high = address + size_in_units;
+ ABSL_HARDENING_ASSERT(IsValidAddress(address, high));
+ ABSL_HARDENING_ASSERT(size_in_units > 0);
+ uint8_t *byte_ptr = static_cast<uint8_t *>(db->raw_ptr());
+ // Load the data into the data buffer.
+ LoadStoreHelper(address, byte_ptr, size_in_units, true);
+ // Execute the instruction to process and write back the load data.
+ if (nullptr != inst) {
+ if (db->latency() > 0) {
+ inst->IncRef();
+ if (context != nullptr) context->IncRef();
+ inst->state()->function_delay_line()->Add(db->latency(),
+ [inst, context]() {
+ inst->Execute(context);
+ if (context != nullptr)
+ context->DecRef();
+ inst->DecRef();
+ });
+ } else {
+ inst->Execute(context);
+ }
+ }
+}
+
+void KelvinRenodeMemory::Load(DataBuffer *address_db, DataBuffer *mask_db,
+ int el_size, DataBuffer *db, Instruction *inst,
+ ReferenceCount *context) {
+ auto mask_span = mask_db->Get<bool>();
+ auto address_span = address_db->Get<uint64_t>();
+ uint8_t *byte_ptr = static_cast<uint8_t *>(db->raw_ptr());
+ int size_in_units = el_size / addressable_unit_size_;
+ ABSL_HARDENING_ASSERT(size_in_units > 0);
+ // This is either a gather load, or a unit stride load depending on size of
+ // the address span.
+ bool gather = address_span.size() > 1;
+ for (unsigned i = 0; i < mask_span.size(); i++) {
+ if (!mask_span[i]) continue;
+ uint64_t address = gather ? address_span[i] : address_span[0] + i * el_size;
+ uint64_t high = address + size_in_units;
+ ABSL_HARDENING_ASSERT(IsValidAddress(address, high));
+ LoadStoreHelper(address, &byte_ptr[el_size * i], size_in_units, true);
+ }
+ // Execute the instruction to process and write back the load data.
+ if (nullptr != inst) {
+ if (db->latency() > 0) {
+ inst->IncRef();
+ if (context != nullptr) context->IncRef();
+ inst->state()->function_delay_line()->Add(db->latency(),
+ [inst, context]() {
+ inst->Execute(context);
+ if (context != nullptr)
+ context->DecRef();
+ inst->DecRef();
+ });
+ } else {
+ inst->Execute(context);
+ }
+ }
+}
+
+void KelvinRenodeMemory::Store(uint64_t address, DataBuffer *db) {
+ int size_in_units = db->size<uint8_t>() / addressable_unit_size_;
+ uint64_t high = address + size_in_units;
+ ABSL_HARDENING_ASSERT(IsValidAddress(address, high));
+ ABSL_HARDENING_ASSERT(size_in_units > 0);
+ uint8_t *byte_ptr = static_cast<uint8_t *>(db->raw_ptr());
+ LoadStoreHelper(address, byte_ptr, size_in_units, /*is_load*/ false);
+}
+
+void KelvinRenodeMemory::Store(DataBuffer *address_db, DataBuffer *mask_db,
+ int el_size, DataBuffer *db) {
+ auto mask_span = mask_db->Get<bool>();
+ auto address_span = address_db->Get<uint64_t>();
+ uint8_t *byte_ptr = static_cast<uint8_t *>(db->raw_ptr());
+ int size_in_units = el_size / addressable_unit_size_;
+ ABSL_HARDENING_ASSERT(size_in_units > 0);
+ // If the address_span.size() > 1, then this is a scatter store, otherwise
+ // it's a unit stride store.
+ bool scatter = address_span.size() > 1;
+ for (unsigned i = 0; i < mask_span.size(); i++) {
+ if (!mask_span[i]) continue;
+ uint64_t address =
+ scatter ? address_span[i] : address_span[0] + i * el_size;
+ uint64_t high = address + size_in_units;
+ ABSL_HARDENING_ASSERT(IsValidAddress(address, high));
+ LoadStoreHelper(address, &byte_ptr[el_size * i], size_in_units,
+ /*is_load*/ false);
+ }
+}
+
+void KelvinRenodeMemory::LoadStoreHelper(uint64_t address, uint8_t *byte_ptr,
+ int size_in_units, bool is_load) {
+ ABSL_HARDENING_ASSERT(address < max_address_);
+ do {
+ // Find the block in the map.
+ uint64_t block_idx = address / memory_block_size_bytes_;
+
+ uint8_t *block = block_map_[block_idx];
+
+ int block_unit_offset = (address - block_idx * memory_block_size_bytes_);
+
+ // Compute how many addressable units to load/store from/to the current
+ // block.
+ int store_size_in_units =
+ std::min(size_in_units, allocation_byte_size_ - block_unit_offset);
+
+ // Translate from unit size to byte size.
+ int store_size_in_bytes = store_size_in_units << addressable_unit_shift_;
+ int block_byte_offset = block_unit_offset << addressable_unit_shift_;
+
+ if (is_load) {
+ std::memcpy(byte_ptr, &block[block_byte_offset], store_size_in_bytes);
+ } else {
+ std::memcpy(&block[block_byte_offset], byte_ptr, store_size_in_bytes);
+ }
+
+ // Adjust address, data pointer and the remaining data left to be
+ // loaded/stored.
+ size_in_units -= store_size_in_units;
+ byte_ptr += store_size_in_bytes;
+ address += store_size_in_units;
+ } while (size_in_units > 0);
+}
+
+} // namespace kelvin::sim::renode
diff --git a/sim/renode/kelvin_renode_memory.h b/sim/renode/kelvin_renode_memory.h
new file mode 100644
index 0000000..83e0354
--- /dev/null
+++ b/sim/renode/kelvin_renode_memory.h
@@ -0,0 +1,82 @@
+#ifndef LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_KELVIN_RENODE_MEMORY_H_
+#define LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_KELVIN_RENODE_MEMORY_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/ref_count.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+
+namespace kelvin::sim::renode {
+
+using ::mpact::sim::generic::DataBuffer;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::generic::ReferenceCount;
+
+// A memory interface class with memory blocks created, Initialize, and shared
+// by Renode's MappedMemory module as an array of memory block pointers, the
+// size of each block, and the total size of the memory (uint64_t
+// memory_block_size_bytes, uint64_t memory_size_byte, uint8_t **
+// memory_block_pointer_list). The class is tied to the Kelvin configuration
+// with memory size check.
+class KelvinRenodeMemory : public mpact::sim::util::MemoryInterface {
+ public:
+ KelvinRenodeMemory(uint64_t block_size_bytes, uint64_t memory_size_bytes,
+ uint8_t **block_ptr_list, uint64_t base_address,
+ unsigned addressable_unit_size);
+ KelvinRenodeMemory(uint64_t block_size_bytes, uint64_t memory_size_bytes,
+ uint8_t **block_ptr_list)
+ : KelvinRenodeMemory(block_size_bytes, memory_size_bytes, block_ptr_list,
+ 0, 1) {}
+
+ // The memory is not allocated by this class, so there is nothing to release
+ // in the destructor.
+ ~KelvinRenodeMemory() override = default;
+
+ // Implementation of the MemoryInterface methods.
+ void Load(uint64_t address, DataBuffer *db, Instruction *inst,
+ ReferenceCount *context) override;
+
+ void Load(DataBuffer *address_db, DataBuffer *mask_db, int el_size,
+ DataBuffer *db, Instruction *inst,
+ ReferenceCount *context) override;
+
+ // Convenience template function that calls the above function with the
+ // element size as the sizeof() the template parameter type.
+ template <typename T>
+ void Load(DataBuffer *address_db, DataBuffer *mask_db, DataBuffer *db,
+ Instruction *inst, ReferenceCount *context) {
+ Load(address_db, mask_db, sizeof(T), db, inst, context);
+ }
+
+ void Store(uint64_t address, DataBuffer *db) override;
+ void Store(DataBuffer *address_db, DataBuffer *mask_db, int el_size,
+ DataBuffer *db) override;
+
+ // Convenience template function that calls the above function with the
+ // element size as the sizeof() the template parameter type.
+ template <typename T>
+ void Store(DataBuffer *address_db, DataBuffer *mask_db, DataBuffer *db) {
+ Store(address_db, mask_db, sizeof(T), db);
+ }
+
+ private:
+ void LoadStoreHelper(uint64_t address, uint8_t *byte_ptr, int size_in_units,
+ bool is_load);
+ bool IsValidAddress(uint64_t address, uint64_t high_address);
+
+ int addressable_unit_shift_;
+ int addressable_unit_size_;
+ int allocation_byte_size_;
+ uint64_t memory_block_size_bytes_;
+ uint64_t base_address_;
+ uint64_t max_address_;
+
+ std::vector<uint8_t *> block_map_;
+};
+
+} // namespace kelvin::sim::renode
+
+#endif // LEARNING_BRAIN_RESEARCH_KELVIN_SIM_RENODE_KELVIN_RENODE_MEMORY_H_
diff --git a/sim/renode/renode_mpact.cc b/sim/renode/renode_mpact.cc
index 3c25082..3d11cb7 100644
--- a/sim/renode/renode_mpact.cc
+++ b/sim/renode/renode_mpact.cc
@@ -8,13 +8,17 @@
#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/generic/type_helpers.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);
+extern kelvin::sim::renode::RenodeDebugInterface *CreateKelvinSim(std::string,
+ uint64_t,
+ uint64_t,
+ uint8_t **);
+
// External "C" functions visible to Renode.
using kelvin::sim::renode::RenodeAgent;
using kelvin::sim::renode::RenodeCpuRegister;
@@ -24,6 +28,16 @@
int32_t construct(int32_t max_name_length) {
return RenodeAgent::Instance()->Construct(max_name_length);
}
+
+int32_t construct_with_memory(int32_t max_name_length,
+ uint64_t memory_block_size_bytes,
+ uint64_t memory_size_bytes,
+ uint8_t **mem_block_ptr_list) {
+ return RenodeAgent::Instance()->Construct(
+ max_name_length, memory_block_size_bytes, memory_size_bytes,
+ mem_block_ptr_list);
+}
+
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) {
@@ -79,6 +93,21 @@
return RenodeAgent::count_++;
}
+int32_t RenodeAgent::Construct(int32_t max_name_length,
+ uint64_t memory_block_size_bytes,
+ uint64_t memory_size_bytes,
+ uint8_t **mem_block_ptr_list) {
+ std::string name = absl::StrCat("renode", count_);
+ auto *dbg = CreateKelvinSim(name, memory_block_size_bytes, memory_size_bytes,
+ mem_block_ptr_list);
+ 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.
diff --git a/sim/renode/renode_mpact.h b/sim/renode/renode_mpact.h
index a295478..edb583b 100644
--- a/sim/renode/renode_mpact.h
+++ b/sim/renode/renode_mpact.h
@@ -14,6 +14,14 @@
// 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);
+// Construct a debug instance connected to a simulator with memory passed from
+// renode. Returns the non-zero id of the created instance. A return value of
+// zero indicates an error. Note the pointer array needs to be the last argument
+// to comply with renode's import binding signature.
+int32_t construct_with_memory(int32_t max_name_length,
+ uint64_t memory_block_size_bytes,
+ uint64_t memory_size_bytes,
+ uint8_t **mem_block_ptr_list);
// Destruct the given debug instance. A negative return value indicates an
// error.
int32_t destruct(int32_t id);
@@ -74,6 +82,8 @@
}
// These methods correspond to the C methods defined above.
int32_t Construct(int32_t max_name_length);
+ int32_t Construct(int32_t max_name_length, uint64_t memory_block_size_bytes,
+ uint64_t memory_size_bytes, uint8_t **mem_block_ptr_list);
int32_t Destroy(int32_t id);
int32_t Reset(int32_t id);
int32_t GetRegisterInfoSize(int32_t id) const;
diff --git a/sim/renode/test/BUILD b/sim/renode/test/BUILD
index b1d3ac2..0d6c7b0 100644
--- a/sim/renode/test/BUILD
+++ b/sim/renode/test/BUILD
@@ -39,6 +39,7 @@
"@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
index a64c689..d5f157a 100644
--- a/sim/renode/test/kelvin_renode_test.cc
+++ b/sim/renode/test/kelvin_renode_test.cc
@@ -1,6 +1,7 @@
#include "sim/renode/kelvin_renode.h"
#include <cstdint>
+#include <cstring>
#include <string>
#include "sim/kelvin_top.h"
@@ -10,12 +11,14 @@
#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;
+using RunStatus = mpact::sim::generic::CoreDebugInterface::RunStatus;
constexpr char kFileName[] = "hello_world_mpause.elf";
constexpr char kBinFileName[] = "hello_world_mpause.bin";
@@ -114,12 +117,59 @@
EXPECT_TRUE(top_->Run().ok());
EXPECT_TRUE(top_->Wait().ok());
// Check the results.
+ auto run_status = top_->GetRunStatus();
+ EXPECT_TRUE(run_status.ok());
+ EXPECT_EQ(run_status.value(), RunStatus::kHalted);
auto halt_result = top_->GetLastHaltReason();
- CHECK_OK(halt_result);
+ EXPECT_TRUE(halt_result.ok());
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);
}
+// Setup external memory to run a binary program
+TEST_F(KelvinRenodeTest, RunBinProgramWithExternalMemory) {
+ std::string file_name = absl::StrCat(kDepotPath, "testfiles/", kBinFileName);
+ constexpr uint64_t kBinFileAddress = 0x0;
+ constexpr uint64_t kBinFileEntryPoint = 0x0;
+
+ // Setup the external memory.
+ constexpr uint64_t kMemoryBlockSize = 0x40000; // 256KB
+ constexpr uint64_t kNumBlock = 16; // 4MB / 256KB
+ uint8_t *memory_block[kNumBlock] = {nullptr};
+ // Allocate memory blocks.
+ for (int i = 0; i < kNumBlock; ++i) {
+ memory_block[i] = new uint8_t[kMemoryBlockSize];
+ memset(memory_block[i], 0, kMemoryBlockSize);
+ }
+
+ // Reset top with external memory.
+ delete top_;
+ top_ = new kelvin::sim::KelvinRenode(
+ kTopName, kMemoryBlockSize, kNumBlock * kMemoryBlockSize, memory_block);
+
+ auto res = top_->LoadImage(file_name, kBinFileAddress);
+ EXPECT_TRUE(res.ok());
+ // 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 run_status = top_->GetRunStatus();
+ EXPECT_TRUE(run_status.ok());
+ EXPECT_EQ(run_status.value(), RunStatus::kHalted);
+ auto halt_result = top_->GetLastHaltReason();
+ EXPECT_TRUE(halt_result.ok());
+ EXPECT_EQ(halt_result.value(), *KelvinTop::HaltReason::kUserRequest);
+ const std::string stdout_str = testing::internal::GetCapturedStdout();
+ EXPECT_EQ("Program exits properly\n", stdout_str);
+
+ // Release the memory blocks.
+ for (int i = 0; i < kNumBlock; ++i) {
+ delete[] memory_block[i];
+ }
+}
+
} // namespace
diff --git a/sim/renode/test/renode_mpact_test.cc b/sim/renode/test/renode_mpact_test.cc
index 5eda348..6c88bef 100644
--- a/sim/renode/test/renode_mpact_test.cc
+++ b/sim/renode/test/renode_mpact_test.cc
@@ -2,6 +2,7 @@
#include <cstddef>
#include <cstdint>
+#include <cstring>
#include <fstream>
#include <ios>
#include <string>
@@ -243,4 +244,52 @@
EXPECT_EQ("Program exits properly\n", stdout_str);
}
+// Test stepping over a binary image program with external memory.
+TEST_F(RenodeMpactTest, StepImageProgramWithExternalMemory) {
+ const std::string input_file_name =
+ absl::StrCat(kDepotPath, "testfiles/", kBinFileName);
+
+ // Setup the external memory.
+ constexpr uint64_t kMemoryBlockSize = 0x40000; // 256KB
+ constexpr uint64_t kNumBlock = 16; // 4MB / 256KB
+ uint8_t *memory_block[kNumBlock] = {nullptr};
+ // Allocate memory blocks.
+ for (int i = 0; i < kNumBlock; ++i) {
+ memory_block[i] = new uint8_t[kMemoryBlockSize];
+ memset(memory_block[i], 0, kMemoryBlockSize);
+ }
+
+ // Reset the simulator with the external memory.
+ destruct(sim_id_);
+ sim_id_ = construct_with_memory(1, kMemoryBlockSize,
+ kNumBlock * kMemoryBlockSize, memory_block);
+ EXPECT_GE(sim_id_, 1) << sim_id_; // the agent count keeps incrementing.
+
+ 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);
+
+ // Release the memory blocks.
+ for (int i = 0; i < kNumBlock; ++i) {
+ delete[] memory_block[i];
+ }
+}
+
} // namespace
diff --git a/sim/test/kelvin_top_test.cc b/sim/test/kelvin_top_test.cc
index 85d2491..66686ff 100644
--- a/sim/test/kelvin_top_test.cc
+++ b/sim/test/kelvin_top_test.cc
@@ -1,6 +1,8 @@
#include "sim/kelvin_top.h"
+#include <algorithm>
#include <cstdint>
+#include <cstring>
#include <string>
#include <tuple>
@@ -445,4 +447,113 @@
EXPECT_THAT(stdout_str, testing::HasSubstr("vld_vst test passed!"));
}
+constexpr int kMemoryBlockSize = 256 * 1024; // 256KB
+// Default max memory address is 4MB - 1. Round up to find the number of memory
+// blocks.
+constexpr int kNumMemoryBlocks =
+ (kelvin::sim::kKelvinMaxMemoryAddress + kMemoryBlockSize) /
+ kMemoryBlockSize;
+
+class KelvinTopExternalMemoryTest : public testing::Test {
+ protected:
+ KelvinTopExternalMemoryTest()
+ : memory_size_(kMemoryBlockSize * kNumMemoryBlocks) {
+ // Set the memory blocks outside of KelvinTop.
+ for (int i = 0; i < kNumMemoryBlocks; ++i) {
+ memory_blocks_[i] = new uint8_t[kMemoryBlockSize];
+ memset(memory_blocks_[i], 0, kMemoryBlockSize);
+ }
+ kelvin_top_ =
+ new KelvinTop("Kelvin", kMemoryBlockSize, memory_size_, memory_blocks_);
+ // Set up the elf loader.
+ loader_ = new ElfProgramLoader(kelvin_top_->memory());
+ }
+
+ ~KelvinTopExternalMemoryTest() override {
+ delete loader_;
+ delete kelvin_top_;
+ for (int i = 0; i < kNumMemoryBlocks; ++i) {
+ delete[] memory_blocks_[i];
+ }
+ }
+
+ void LoadFile(const std::string file_name) {
+ const std::string input_file_name =
+ absl::StrCat(kDepotPath, "testfiles/", file_name);
+ auto result = loader_->LoadProgram(input_file_name);
+ CHECK_OK(result);
+ entry_point_ = result.value();
+ }
+
+ uint32_t entry_point_;
+ KelvinTop *kelvin_top_ = nullptr;
+ ElfProgramLoader *loader_ = nullptr;
+ uint8_t *memory_blocks_[kNumMemoryBlocks] = {nullptr};
+ uint64_t memory_size_;
+};
+
+// Run a vector program from beginning to end.
+TEST_F(KelvinTopExternalMemoryTest, RunKelvinVectorProgram) {
+ LoadFile(kKelvinVldVstFileName);
+ testing::internal::CaptureStdout();
+ EXPECT_OK(kelvin_top_->WriteRegister("pc", entry_point_));
+
+ EXPECT_OK(kelvin_top_->Run());
+ EXPECT_OK(kelvin_top_->Wait());
+ auto halt_result = kelvin_top_->GetLastHaltReason();
+ CHECK_OK(halt_result);
+ EXPECT_EQ(halt_result.value(), *HaltReason::kUserRequest);
+ const std::string stdout_str = testing::internal::GetCapturedStdout();
+ EXPECT_THAT(stdout_str, testing::HasSubstr("vld_vst test passed!"));
+}
+
+// Step a regular program from beginning to end.
+TEST_F(KelvinTopExternalMemoryTest, StepMPauseProgram) {
+ LoadFile(kMpauseElfFileName);
+ testing::internal::CaptureStdout();
+ EXPECT_OK(kelvin_top_->WriteRegister("pc", entry_point_));
+
+ constexpr int kStep = 2000;
+ absl::StatusOr<kelvin::sim::HaltReasonValueType> halt_result;
+ do {
+ auto res = kelvin_top_->Step(kStep);
+ EXPECT_OK(res.status());
+ halt_result = kelvin_top_->GetLastHaltReason();
+ EXPECT_OK(halt_result);
+ } while (halt_result.value() == *HaltReason::kNone);
+
+ EXPECT_EQ(halt_result.value(), *HaltReason::kUserRequest);
+ const std::string stdout_str = testing::internal::GetCapturedStdout();
+ EXPECT_EQ("Program exits properly\n", stdout_str);
+}
+
+// Read/Write memory
+TEST_F(KelvinTopExternalMemoryTest, AccessMemory) {
+ constexpr uint64_t kTestMemerySize = 8;
+ const uint64_t test_access_address[] = {
+ 0x1000, kMemoryBlockSize - 4, kMemoryBlockSize + 4,
+ kelvin::sim::kKelvinMaxMemoryAddress - 4};
+ uint8_t mem_bytes[kTestMemerySize] = {1, 2, 3, 4, 5, 6, 7, 8};
+ uint8_t mem_bytes_return[kTestMemerySize] = {0};
+
+ // Write new values to the memory.
+ for (int i = 0; i < sizeof(test_access_address) / sizeof(uint64_t); ++i) {
+ auto result = kelvin_top_->WriteMemory(test_access_address[i], mem_bytes,
+ sizeof(mem_bytes));
+ uint64_t expected_length =
+ std::min(kTestMemerySize, memory_size_ - test_access_address[i]);
+ EXPECT_OK(result);
+ EXPECT_EQ(result.value(), expected_length);
+ // Read back the content from the external memory.
+ result = kelvin_top_->ReadMemory(test_access_address[i], mem_bytes_return,
+ sizeof(mem_bytes_return));
+ EXPECT_OK(result);
+ EXPECT_EQ(result.value(), expected_length);
+ for (int i = 0; i < result.value(); ++i) {
+ EXPECT_EQ(mem_bytes[i], mem_bytes_return[i]);
+ }
+ memset(mem_bytes_return, 0, sizeof(mem_bytes_return));
+ }
+}
+
} // namespace