blob: 85d2491d13c3ee1e10bba97556828cde206a98a9 [file] [log] [blame]
#include "sim/kelvin_top.h"
#include <cstdint>
#include <string>
#include <tuple>
#include "sim/kelvin_state.h"
#include "googlemock/include/gmock/gmock.h"
#include "googletest/include/gtest/gtest.h"
#include "absl/flags/flag.h"
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "mpact/sim/generic/core_debug_interface.h"
#include "mpact/sim/util/memory/flat_demand_memory.h"
#include "mpact/sim/util/program_loader/elf_program_loader.h"
namespace {
#ifndef EXPECT_OK
#define EXPECT_OK(x) EXPECT_TRUE(x.ok())
#endif
using ::kelvin::sim::KelvinTop;
using ::mpact::sim::util::ElfProgramLoader;
using ::mpact::sim::util::FlatDemandMemory;
using HaltReason = ::mpact::sim::generic::CoreDebugInterface::HaltReason;
constexpr char kEbreakElfFileName[] = "kelvin_ebreak.elf";
constexpr char kMpauseBinaryFileName[] = "hello_world_mpause.bin";
constexpr char kMpauseElfFileName[] = "hello_world_mpause.elf";
constexpr char kRV32imfElfFileName[] = "hello_world_rv32imf.elf";
constexpr char kRV32iElfFileName[] = "rv32i.elf";
constexpr char kRV32mElfFileName[] = "rv32m.elf";
constexpr char kRV32SoftFloatElfFileName[] = "rv32soft_fp.elf";
constexpr char kRV32fElfFileName[] = "rv32uf_fadd.elf";
constexpr char kKelvinVldVstFileName[] = "kelvin_vldvst.elf";
// The depot path to the test directory.
constexpr char kDepotPath[] = "sim/test/";
// Maximum memory size used by riscv programs build for userspace.
constexpr uint64_t kRiscv32MaxAddress = 0x3'ffff'ffffULL;
constexpr uint64_t kBinaryAddress = 0;
class KelvinTopTest : public testing::Test {
protected:
KelvinTopTest() {
memory_ = new FlatDemandMemory();
kelvin_top_ = new KelvinTop("Kelvin");
// Set up the elf loader.
loader_ = new ElfProgramLoader(kelvin_top_->memory());
}
~KelvinTopTest() override {
delete loader_;
delete kelvin_top_;
delete memory_;
}
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;
FlatDemandMemory *memory_ = nullptr;
};
// Check the max memory size
TEST_F(KelvinTopTest, CheckDefaultMaxMemorySize) {
EXPECT_EQ(kelvin_top_->state()->max_physical_address(),
kelvin::sim::kKelvinMaxMemoryAddress);
}
// Run a program exceeds the default memory setting
TEST_F(KelvinTopTest, RunProgramExceedDefaultMemory) {
LoadFile(kRV32imfElfFileName);
testing::internal::CaptureStderr();
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(static_cast<int>(halt_result.value()),
static_cast<int>(HaltReason::kUserRequest));
const std::string stderr_str = testing::internal::GetCapturedStderr();
EXPECT_THAT(stderr_str, testing::HasSubstr("Memory store access fault"));
}
// Runs the program from has ebreak (from syscall).
TEST_F(KelvinTopTest, RunEbreakProgram) {
LoadFile(kEbreakElfFileName);
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(), kelvin::sim::kHaltAbort);
const std::string stdout_str = testing::internal::GetCapturedStdout();
EXPECT_EQ("Program exits with fault\n", stdout_str);
}
// Runs the program from beginning to end. Enable arm semihosting.
TEST_F(KelvinTopTest, RunHelloProgramSemihost) {
absl::SetFlag(&FLAGS_use_semihost, true);
kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
LoadFile(kRV32imfElfFileName);
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(static_cast<int>(halt_result.value()),
static_cast<int>(HaltReason::kSemihostHaltRequest));
const std::string stdout_str = testing::internal::GetCapturedStdout();
EXPECT_EQ("Hello, World! 7\n", stdout_str);
absl::SetFlag(&FLAGS_use_semihost, false);
}
// Runs the program ended with mpause from beginning to end.
TEST_F(KelvinTopTest, RunHelloMpauseProgram) {
LoadFile(kMpauseElfFileName);
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(static_cast<int>(halt_result.value()),
static_cast<int>(HaltReason::kUserRequest));
const std::string stdout_str = testing::internal::GetCapturedStdout();
EXPECT_EQ("Program exits properly\n", stdout_str);
}
TEST_F(KelvinTopTest, LoadImageFailed) {
const std::string input_file_name =
absl::StrCat(kDepotPath, "testfiles/", kMpauseBinaryFileName);
auto result = kelvin_top_->LoadImage("wrong_file", kBinaryAddress);
EXPECT_FALSE(result.ok());
// Set the memory to be smaller than the loaded image
kelvin_top_->state()->set_max_physical_address(0);
result = kelvin_top_->LoadImage(input_file_name, kBinaryAddress);
EXPECT_FALSE(result.ok());
kelvin_top_->state()->set_max_physical_address(0xf);
result = kelvin_top_->LoadImage(input_file_name, kBinaryAddress);
EXPECT_FALSE(result.ok());
}
// Directly read/write to memory addresses that are out-of-bound
TEST_F(KelvinTopTest, ReadWriteOutOfBoundMemory) {
// Set the machine to have 16-byte physical memory
constexpr uint64_t kTestMemerySize = 0x10;
constexpr uint64_t kMaxPhysicalAddress = kTestMemerySize - 1;
kelvin_top_->state()->set_max_physical_address(kMaxPhysicalAddress);
uint8_t mem_bytes[kTestMemerySize + 4] = {0};
// Read the memory with the length greater than the physical memory size. The
// read operation is successful within the physical memory size range.
auto result =
kelvin_top_->ReadMemory(kBinaryAddress, mem_bytes, sizeof(mem_bytes));
EXPECT_OK(result);
EXPECT_EQ(result.value(), kTestMemerySize);
// Read at the maximum physical address, so only one byte can be read.
result = kelvin_top_->ReadMemory(kMaxPhysicalAddress, mem_bytes,
sizeof(mem_bytes));
EXPECT_OK(result);
EXPECT_EQ(result.value(), 1);
// Read the memory with the staring address out of the physical memory range.
// The read operation returns error.
result = kelvin_top_->ReadMemory(kTestMemerySize + 4, mem_bytes,
sizeof(mem_bytes));
EXPECT_FALSE(result.ok());
// Write the memory with the length greater than the physical memory size. The
// write operation is successful within the physical memory size range.
result =
kelvin_top_->WriteMemory(kBinaryAddress, mem_bytes, sizeof(mem_bytes));
EXPECT_OK(result);
EXPECT_EQ(result.value(), kTestMemerySize);
// Write at the maximum physical address, so only one byte can be written.
result = kelvin_top_->WriteMemory(kMaxPhysicalAddress, mem_bytes,
sizeof(mem_bytes));
EXPECT_OK(result);
EXPECT_EQ(result.value(), 1);
// Write the memory with the staring address out of the physical memory range.
// The write operation returns error.
result = kelvin_top_->WriteMemory(kTestMemerySize + 4, mem_bytes,
sizeof(mem_bytes));
EXPECT_FALSE(result.ok());
}
// Runs the binary program from beginning to end
TEST_F(KelvinTopTest, RunHelloMpauseBinaryProgram) {
const std::string input_file_name =
absl::StrCat(kDepotPath, "testfiles/", kMpauseBinaryFileName);
constexpr uint32_t kBinaryEntryPoint = 0;
testing::internal::CaptureStdout();
auto result = kelvin_top_->LoadImage(input_file_name, kBinaryAddress);
CHECK_OK(result);
EXPECT_OK(kelvin_top_->WriteRegister("pc", kBinaryEntryPoint));
EXPECT_OK(kelvin_top_->Run());
EXPECT_OK(kelvin_top_->Wait());
auto halt_result = kelvin_top_->GetLastHaltReason();
CHECK_OK(halt_result);
EXPECT_EQ(static_cast<int>(halt_result.value()),
static_cast<int>(HaltReason::kUserRequest));
const std::string stdout_str = testing::internal::GetCapturedStdout();
EXPECT_EQ("Program exits properly\n", stdout_str);
}
// Runs the rv32i program with arm semihosting.
TEST_F(KelvinTopTest, RunRV32IProgram) {
absl::SetFlag(&FLAGS_use_semihost, true);
kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
LoadFile(kRV32iElfFileName);
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(static_cast<int>(halt_result.value()),
static_cast<int>(HaltReason::kSemihostHaltRequest));
const std::string stdout_str = testing::internal::GetCapturedStdout();
EXPECT_EQ("5+5=10;5-5=0\n", stdout_str);
absl::SetFlag(&FLAGS_use_semihost, false);
}
// Runs the rv32m program with arm semihosting.
TEST_F(KelvinTopTest, RunRV32MProgram) {
absl::SetFlag(&FLAGS_use_semihost, true);
kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
LoadFile(kRV32mElfFileName);
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(static_cast<int>(halt_result.value()),
static_cast<int>(HaltReason::kSemihostHaltRequest));
const std::string stdout_str = testing::internal::GetCapturedStdout();
EXPECT_EQ("5*5=25;5/5=1\n", stdout_str);
absl::SetFlag(&FLAGS_use_semihost, false);
}
// Runs the rv32 soft float program with arm semihosting.
TEST_F(KelvinTopTest, RunRV32SoftFProgram) {
absl::SetFlag(&FLAGS_use_semihost, true);
kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
LoadFile(kRV32SoftFloatElfFileName);
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(static_cast<int>(halt_result.value()),
static_cast<int>(HaltReason::kSemihostHaltRequest));
const std::string stdout_str = testing::internal::GetCapturedStdout();
EXPECT_EQ("7.00+3.00=10.00;7.00-3.00=4.00;7.00*3.00=21.00;7.00/3.00=2.33\n",
stdout_str);
absl::SetFlag(&FLAGS_use_semihost, false);
}
// Runs the rv32f program (not supported)
TEST_F(KelvinTopTest, RunIllegalRV32FProgram) {
LoadFile(kRV32fElfFileName);
testing::internal::CaptureStderr();
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(static_cast<int>(halt_result.value()),
static_cast<int>(HaltReason::kUserRequest));
const std::string stderr_str = testing::internal::GetCapturedStderr();
EXPECT_THAT(stderr_str, testing::HasSubstr("Illegal instruction at 0x"));
}
// Steps through the program from beginning to end.
TEST_F(KelvinTopTest, StepProgram) {
LoadFile(kEbreakElfFileName);
testing::internal::CaptureStdout();
EXPECT_OK(kelvin_top_->WriteRegister("pc", entry_point_));
auto res = kelvin_top_->Step(10000);
EXPECT_OK(res.status());
auto halt_result = kelvin_top_->GetLastHaltReason();
CHECK_OK(halt_result);
EXPECT_EQ(halt_result.value(), kelvin::sim::kHaltAbort);
EXPECT_EQ("Program exits with fault\n",
testing::internal::GetCapturedStdout());
}
// Sets/Clears breakpoints without executing the program.
TEST_F(KelvinTopTest, SetAndClearBreakpoint) {
LoadFile(kRV32imfElfFileName);
kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
auto result = loader_->GetSymbol("printf");
EXPECT_OK(result);
auto address = result.value().first;
EXPECT_EQ(kelvin_top_->ClearSwBreakpoint(address).code(),
absl::StatusCode::kNotFound);
EXPECT_OK(kelvin_top_->SetSwBreakpoint(address));
EXPECT_EQ(kelvin_top_->SetSwBreakpoint(address).code(),
absl::StatusCode::kAlreadyExists);
EXPECT_OK(kelvin_top_->ClearSwBreakpoint(address));
EXPECT_EQ(kelvin_top_->ClearSwBreakpoint(address).code(),
absl::StatusCode::kNotFound);
EXPECT_OK(kelvin_top_->SetSwBreakpoint(address));
EXPECT_OK(kelvin_top_->ClearAllSwBreakpoints());
EXPECT_EQ(kelvin_top_->ClearSwBreakpoint(address).code(),
absl::StatusCode::kNotFound);
}
// Runs program with breakpoint at printf with arm semihosting.
TEST_F(KelvinTopTest, RunWithBreakpoint) {
absl::SetFlag(&FLAGS_use_semihost, true);
kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
LoadFile(kRV32imfElfFileName);
// Set breakpoint at printf.
auto result = loader_->GetSymbol("printf");
EXPECT_OK(result);
auto address = result.value().first;
EXPECT_OK(kelvin_top_->SetSwBreakpoint(address));
testing::internal::CaptureStdout();
EXPECT_OK(kelvin_top_->WriteRegister("pc", entry_point_));
// Run to printf.
EXPECT_OK(kelvin_top_->Run());
EXPECT_OK(kelvin_top_->Wait());
// Should be stopped at breakpoint, but nothing printed.
auto halt_result = kelvin_top_->GetLastHaltReason();
CHECK_OK(halt_result);
EXPECT_EQ(static_cast<int>(halt_result.value()),
static_cast<int>(HaltReason::kSoftwareBreakpoint));
EXPECT_EQ(testing::internal::GetCapturedStdout().size(), 0);
// Run to the end of the program.
testing::internal::CaptureStdout();
EXPECT_OK(kelvin_top_->Run());
EXPECT_OK(kelvin_top_->Wait());
// Should be stopped due to semihost halt request. Captured 'Hello World!
// 7\n'.
halt_result = kelvin_top_->GetLastHaltReason();
CHECK_OK(halt_result);
EXPECT_EQ(static_cast<int>(halt_result.value()),
static_cast<int>(HaltReason::kSemihostHaltRequest));
EXPECT_EQ("Hello, World! 7\n", testing::internal::GetCapturedStdout());
absl::SetFlag(&FLAGS_use_semihost, false);
}
// Memory read/write test.
TEST_F(KelvinTopTest, Memory) {
uint8_t byte_data = 0xab;
uint16_t half_data = 0xabcd;
uint32_t word_data = 0xba5eba11;
uint64_t dword_data = 0x5ca1ab1e'0ddball;
EXPECT_OK(kelvin_top_->WriteMemory(0x1000, &byte_data, sizeof(byte_data)));
EXPECT_OK(kelvin_top_->WriteMemory(0x1004, &half_data, sizeof(half_data)));
EXPECT_OK(kelvin_top_->WriteMemory(0x1008, &word_data, sizeof(word_data)));
EXPECT_OK(kelvin_top_->WriteMemory(0x1010, &dword_data, sizeof(dword_data)));
uint8_t byte_value;
uint16_t half_value;
uint32_t word_value;
uint64_t dword_value;
EXPECT_OK(kelvin_top_->ReadMemory(0x1000, &byte_value, sizeof(byte_value)));
EXPECT_OK(kelvin_top_->ReadMemory(0x1004, &half_value, sizeof(half_value)));
EXPECT_OK(kelvin_top_->ReadMemory(0x1008, &word_value, sizeof(word_value)));
EXPECT_OK(kelvin_top_->ReadMemory(0x1010, &dword_value, sizeof(dword_value)));
EXPECT_EQ(byte_data, byte_value);
EXPECT_EQ(half_data, half_value);
EXPECT_EQ(word_data, word_value);
EXPECT_EQ(dword_data, dword_value);
EXPECT_OK(kelvin_top_->ReadMemory(0x1000, &byte_value, sizeof(byte_value)));
EXPECT_OK(kelvin_top_->ReadMemory(0x1000, &half_value, sizeof(half_value)));
EXPECT_OK(kelvin_top_->ReadMemory(0x1000, &word_value, sizeof(word_value)));
EXPECT_OK(kelvin_top_->ReadMemory(0x1000, &dword_value, sizeof(dword_value)));
EXPECT_EQ(byte_data, byte_value);
EXPECT_EQ(byte_data, half_value);
EXPECT_EQ(byte_data, word_value);
EXPECT_EQ(0x0000'abcd'0000'00ab, dword_value);
}
// Register name test.
TEST_F(KelvinTopTest, RegisterNames) {
// Test x-names and numbers.
uint32_t word_value;
for (int i = 0; i < 32; i++) {
std::string name = absl::StrCat("x", i);
auto result = kelvin_top_->ReadRegister(name);
EXPECT_OK(result.status());
word_value = result.value();
EXPECT_OK(kelvin_top_->WriteRegister(name, word_value));
}
// Not found.
EXPECT_EQ(kelvin_top_->ReadRegister("x32").status().code(),
absl::StatusCode::kNotFound);
EXPECT_EQ(kelvin_top_->WriteRegister("x32", word_value).code(),
absl::StatusCode::kNotFound);
// Aliases.
for (auto &[name, alias] : {std::tuple<std::string, std::string>{"x1", "ra"},
{"x4", "tp"},
{"x8", "s0"}}) {
uint32_t write_value = 0xba5eba11;
EXPECT_OK(kelvin_top_->WriteRegister(name, write_value));
uint32_t read_value;
auto result = kelvin_top_->ReadRegister(alias);
EXPECT_OK(result.status());
read_value = result.value();
EXPECT_EQ(read_value, write_value);
}
}
TEST_F(KelvinTopTest, 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(static_cast<int>(halt_result.value()),
static_cast<int>(HaltReason::kUserRequest));
const std::string stdout_str = testing::internal::GetCapturedStdout();
EXPECT_THAT(stdout_str, testing::HasSubstr("vld_vst test passed!"));
}
} // namespace