| // Copyright 2023 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "riscv/riscv_top.h" |
| |
| #include <cstdint> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/log/check.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_join.h" |
| #include "googlemock/include/gmock/gmock.h" |
| #include "mpact/sim/generic/core_debug_interface.h" |
| #include "mpact/sim/generic/decoder_interface.h" |
| #include "mpact/sim/generic/instruction.h" |
| #include "mpact/sim/generic/type_helpers.h" |
| #include "mpact/sim/util/memory/flat_demand_memory.h" |
| #include "mpact/sim/util/memory/memory_interface.h" |
| #include "mpact/sim/util/memory/memory_watcher.h" |
| #include "mpact/sim/util/program_loader/elf_program_loader.h" |
| #include "riscv/riscv32_decoder.h" |
| #include "riscv/riscv32_htif_semihost.h" |
| #include "riscv/riscv64_decoder.h" |
| #include "riscv/riscv_arm_semihost.h" |
| #include "riscv/riscv_register.h" |
| #include "riscv/riscv_register_aliases.h" |
| #include "riscv/riscv_state.h" |
| |
| namespace { |
| |
| using ::mpact::sim::generic::operator*; // NOLINT: is used below (clang error). |
| |
| #ifndef EXPECT_OK |
| #define EXPECT_OK(x) EXPECT_TRUE(x.ok()) |
| #endif |
| |
| using ::mpact::sim::generic::DecoderInterface; |
| using ::mpact::sim::generic::Instruction; |
| using ::mpact::sim::riscv::RiscV32Decoder; |
| using ::mpact::sim::riscv::RiscV64Decoder; |
| using ::mpact::sim::riscv::RiscVArmSemihost; |
| using ::mpact::sim::riscv::RiscVFPState; |
| using ::mpact::sim::riscv::RiscVState; |
| using ::mpact::sim::riscv::RiscVTop; |
| using ::mpact::sim::riscv::RiscVXlen; |
| using ::mpact::sim::riscv::RV32Register; |
| using ::mpact::sim::riscv::RV64Register; |
| using ::mpact::sim::riscv::RVFpRegister; |
| using ::mpact::sim::util::FlatDemandMemory; |
| |
| using HaltReason = ::mpact::sim::generic::CoreDebugInterface::HaltReason; |
| constexpr char kHtifFileName[] = "hello_world.elf"; |
| constexpr char kArmFileName[] = "hello_world_arm.elf"; |
| constexpr char kArm64FileName[] = "hello_world_64.elf"; |
| |
| // The depot path to the test directory. |
| constexpr char kDepotPath[] = "riscv/test/"; |
| |
| // Helper function to get symbol addresses from the loader. |
| static bool GetMagicAddresses( |
| mpact::sim::util::ElfProgramLoader* loader, |
| mpact::sim::riscv::RiscV32HtifSemiHost::SemiHostAddresses* magic) { |
| auto result = loader->GetSymbol("tohost_ready"); |
| if (!result.ok()) return false; |
| magic->tohost_ready = result.value().first; |
| |
| result = loader->GetSymbol("tohost"); |
| if (!result.ok()) return false; |
| magic->tohost = result.value().first; |
| |
| result = loader->GetSymbol("fromhost_ready"); |
| if (!result.ok()) return false; |
| magic->fromhost_ready = result.value().first; |
| |
| result = loader->GetSymbol("fromhost"); |
| if (!result.ok()) return false; |
| magic->fromhost = result.value().first; |
| |
| return true; |
| } |
| |
| // Helper RAII style class for setting up HTIF semihosting. |
| class HtifSemihostSetup { |
| public: |
| HtifSemihostSetup(RiscVTop* top, mpact::sim::util::ElfProgramLoader* loader, |
| mpact::sim::util::MemoryInterface* memory) |
| : memory_(memory), state_(top->state()) { |
| mpact::sim::riscv::RiscV32HtifSemiHost::SemiHostAddresses magic; |
| if (GetMagicAddresses(loader, &magic)) { |
| watcher_ = new mpact::sim::util::MemoryWatcher(memory); |
| semihost_ = new mpact::sim::riscv::RiscV32HtifSemiHost( |
| watcher_, memory, magic, |
| [top]() { |
| top->RequestHalt(RiscVTop::HaltReason::kSemihostHaltRequest, |
| nullptr); |
| }, |
| [top](std::string) { |
| top->RequestHalt(RiscVTop::HaltReason::kSemihostHaltRequest, |
| nullptr); |
| }); |
| top->state()->set_memory(watcher_); |
| } |
| } |
| |
| ~HtifSemihostSetup() { |
| state_->set_memory(memory_); |
| delete semihost_; |
| delete watcher_; |
| } |
| |
| private: |
| mpact::sim::util::MemoryInterface* memory_ = nullptr; |
| mpact::sim::riscv::RiscVState* state_ = nullptr; |
| mpact::sim::util::MemoryWatcher* watcher_ = nullptr; |
| mpact::sim::riscv::RiscV32HtifSemiHost* semihost_ = nullptr; |
| }; |
| |
| // Helper RAII style class for setting up ARM semihosting. |
| class ArmSemihostSetup { |
| public: |
| explicit ArmSemihostSetup(RiscVTop* top, |
| mpact::sim::util::MemoryInterface* memory) { |
| auto xlen = top->state()->xlen(); |
| if (xlen == RiscVXlen::RV64) { |
| semihost_ = new RiscVArmSemihost(RiscVArmSemihost::BitWidth::kWord64, |
| memory, memory); |
| } else { |
| semihost_ = new RiscVArmSemihost(RiscVArmSemihost::BitWidth::kWord32, |
| memory, memory); |
| } |
| auto semihost = semihost_; |
| top->state()->AddEbreakHandler([semihost, |
| top](const Instruction* inst) -> bool { |
| if (semihost->IsSemihostingCall(inst)) { |
| semihost->OnEBreak(inst); |
| } else { |
| top->RequestHalt(RiscVTop::HaltReason::kSoftwareBreakpoint, nullptr); |
| } |
| return true; |
| }); |
| semihost_->set_exit_callback([top]() { |
| top->RequestHalt(RiscVTop::HaltReason::kSemihostHaltRequest, nullptr); |
| }); |
| } |
| |
| ~ArmSemihostSetup() { delete semihost_; } |
| |
| private: |
| mpact::sim::riscv::RiscVArmSemihost* semihost_ = nullptr; |
| }; |
| |
| class RiscVTopTest : public testing::Test { |
| protected: |
| RiscVTopTest() { |
| memory_ = new FlatDemandMemory(); |
| state_ = new RiscVState("RV32", RiscVXlen::RV32, memory_); |
| fp_state_ = new RiscVFPState(state_->csr_set(), state_); |
| state_->set_rv_fp(fp_state_); |
| decoder_ = new RiscV32Decoder(state_, memory_); |
| |
| // Make sure the architectural and abi register aliases are added. |
| std::string reg_name; |
| for (int i = 0; i < 32; i++) { |
| reg_name = absl::StrCat(RiscVState::kXregPrefix, i); |
| (void)state_->AddRegister<RV32Register>(reg_name); |
| (void)state_->AddRegisterAlias<RV32Register>( |
| reg_name, ::mpact::sim::riscv::kXRegisterAliases[i]); |
| } |
| for (int i = 0; i < 32; i++) { |
| reg_name = absl::StrCat(RiscVState::kFregPrefix, i); |
| (void)state_->AddRegister<RVFpRegister>(reg_name); |
| (void)state_->AddRegisterAlias<RVFpRegister>( |
| reg_name, ::mpact::sim::riscv::kFRegisterAliases[i]); |
| } |
| |
| riscv_top_ = new RiscVTop("RV32", state_, decoder_); |
| // Set up the elf loader. |
| loader_ = new mpact::sim::util::ElfProgramLoader(memory_); |
| } |
| |
| ~RiscVTopTest() override { |
| delete loader_; |
| delete decoder_; |
| delete fp_state_; |
| delete riscv_top_; |
| delete state_; |
| 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(); |
| } |
| |
| uint64_t entry_point_; |
| RiscVTop* riscv_top_ = nullptr; |
| mpact::sim::util::ElfProgramLoader* loader_ = nullptr; |
| FlatDemandMemory* memory_ = nullptr; |
| RiscVState* state_ = nullptr; |
| RiscVFPState* fp_state_ = nullptr; |
| DecoderInterface* decoder_ = nullptr; |
| }; |
| |
| // Runs the program from beginning to end using HTIF semihosting. |
| TEST_F(RiscVTopTest, RunProgramHtif) { |
| LoadFile(kHtifFileName); |
| HtifSemihostSetup htif_semihost(riscv_top_, loader_, memory_); |
| testing::internal::CaptureStdout(); |
| EXPECT_OK(riscv_top_->WriteRegister("pc", entry_point_)); |
| // Initialize stack pointer. |
| EXPECT_OK(riscv_top_->WriteRegister("sp", 0x200000)); |
| EXPECT_OK(riscv_top_->Run()); |
| EXPECT_OK(riscv_top_->Wait()); |
| auto halt_result = riscv_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("Arch: RV32\nHello World!\n", stdout_str); |
| } |
| |
| // Runs the program from beginning to end using ARM semihosting. |
| TEST_F(RiscVTopTest, RunProgramArm) { |
| LoadFile(kArmFileName); |
| ArmSemihostSetup arm_semihost(riscv_top_, memory_); |
| testing::internal::CaptureStdout(); |
| EXPECT_OK(riscv_top_->WriteRegister("pc", entry_point_)); |
| // Initialize stack pointer. |
| EXPECT_OK(riscv_top_->WriteRegister("sp", 0x200000)); |
| EXPECT_OK(riscv_top_->Run()); |
| EXPECT_OK(riscv_top_->Wait()); |
| auto halt_result = riscv_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! 5\n", stdout_str); |
| } |
| |
| // Steps through the program from beginning to end. |
| TEST_F(RiscVTopTest, StepProgramHtif) { |
| LoadFile(kHtifFileName); |
| HtifSemihostSetup htif_semihost(riscv_top_, loader_, memory_); |
| testing::internal::CaptureStdout(); |
| EXPECT_OK(riscv_top_->WriteRegister("pc", entry_point_)); |
| // Initialize stack pointer. |
| EXPECT_OK(riscv_top_->WriteRegister("sp", 0x200000)); |
| |
| auto res = riscv_top_->Step(10000); |
| EXPECT_OK(res.status()); |
| auto halt_result = riscv_top_->GetLastHaltReason(); |
| CHECK_OK(halt_result); |
| EXPECT_EQ(static_cast<int>(halt_result.value()), |
| static_cast<int>(HaltReason::kSemihostHaltRequest)); |
| |
| EXPECT_EQ("Arch: RV32\nHello World!\n", |
| testing::internal::GetCapturedStdout()); |
| } |
| |
| // Steps through the program from beginning to end. |
| TEST_F(RiscVTopTest, StepProgramArm) { |
| LoadFile(kArmFileName); |
| ArmSemihostSetup arm_semihost(riscv_top_, memory_); |
| testing::internal::CaptureStdout(); |
| EXPECT_OK(riscv_top_->WriteRegister("pc", entry_point_)); |
| // Initialize stack pointer. |
| EXPECT_OK(riscv_top_->WriteRegister("sp", 0x200000)); |
| |
| auto res = riscv_top_->Step(10000); |
| EXPECT_OK(res.status()); |
| auto halt_result = riscv_top_->GetLastHaltReason(); |
| CHECK_OK(halt_result); |
| EXPECT_EQ(static_cast<int>(halt_result.value()), |
| static_cast<int>(HaltReason::kSemihostHaltRequest)); |
| |
| EXPECT_EQ("Hello World! 5\n", testing::internal::GetCapturedStdout()); |
| } |
| |
| // Sets/Clears breakpoints without executing the program. |
| TEST_F(RiscVTopTest, SetAndClearBreakpoint) { |
| LoadFile(kHtifFileName); |
| auto result = loader_->GetSymbol("printf"); |
| EXPECT_OK(result); |
| auto address = result.value().first; |
| EXPECT_EQ(riscv_top_->ClearSwBreakpoint(address).code(), |
| absl::StatusCode::kNotFound); |
| EXPECT_OK(riscv_top_->SetSwBreakpoint(address)); |
| EXPECT_EQ(riscv_top_->SetSwBreakpoint(address).code(), |
| absl::StatusCode::kAlreadyExists); |
| EXPECT_OK(riscv_top_->ClearSwBreakpoint(address)); |
| EXPECT_EQ(riscv_top_->ClearSwBreakpoint(address).code(), |
| absl::StatusCode::kNotFound); |
| EXPECT_OK(riscv_top_->SetSwBreakpoint(address)); |
| EXPECT_OK(riscv_top_->ClearAllSwBreakpoints()); |
| EXPECT_EQ(riscv_top_->ClearSwBreakpoint(address).code(), |
| absl::StatusCode::kNotFound); |
| } |
| |
| // Runs program with breakpint at printf with htif semihosting. |
| TEST_F(RiscVTopTest, RunWithBreakpointHtif) { |
| LoadFile(kHtifFileName); |
| HtifSemihostSetup htif_semihost(riscv_top_, loader_, memory_); |
| // Set breakpoint at printf. |
| auto result = loader_->GetSymbol("printf"); |
| EXPECT_OK(result); |
| auto address = result.value().first; |
| EXPECT_OK(riscv_top_->SetSwBreakpoint(address)); |
| EXPECT_OK(riscv_top_->WriteRegister("pc", entry_point_)); |
| // Initialize stack pointer. |
| EXPECT_OK(riscv_top_->WriteRegister("sp", 0x200000)); |
| |
| // Run to printf. Capture stdout |
| testing::internal::CaptureStdout(); |
| EXPECT_OK(riscv_top_->Run()); |
| EXPECT_OK(riscv_top_->Wait()); |
| // Should be stopped at breakpoint, but nothing printed. |
| auto halt_result = riscv_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 printf. Capture stdout. |
| testing::internal::CaptureStdout(); |
| EXPECT_OK(riscv_top_->Run()); |
| EXPECT_OK(riscv_top_->Wait()); |
| // Should be stopped at breakpoint. Captured 'Arch: RV32\n'. |
| halt_result = riscv_top_->GetLastHaltReason(); |
| CHECK_OK(halt_result); |
| EXPECT_EQ(static_cast<int>(halt_result.value()), |
| static_cast<int>(HaltReason::kSoftwareBreakpoint)); |
| EXPECT_STREQ("Arch: RV32\n", testing::internal::GetCapturedStdout().c_str()); |
| |
| // Run to the end of the program, and capture stdout. |
| testing::internal::CaptureStdout(); |
| EXPECT_OK(riscv_top_->Run()); |
| EXPECT_OK(riscv_top_->Wait()); |
| // Should be stopped due to semihost halt request. Captured 'Hello World! |
| // 5\n'. |
| halt_result = riscv_top_->GetLastHaltReason(); |
| CHECK_OK(halt_result); |
| EXPECT_EQ(static_cast<int>(halt_result.value()), |
| static_cast<int>(HaltReason::kSemihostHaltRequest)); |
| EXPECT_EQ("Hello World!\n", testing::internal::GetCapturedStdout()); |
| } |
| |
| // Runs program with breakpint at printf with arm semihosting. |
| TEST_F(RiscVTopTest, RunWithBreakpointArm) { |
| LoadFile(kArmFileName); |
| ArmSemihostSetup arm_semihost(riscv_top_, memory_); |
| // Set breakpoint at printf. |
| auto result = loader_->GetSymbol("printf"); |
| EXPECT_OK(result); |
| auto address = result.value().first; |
| EXPECT_OK(riscv_top_->SetSwBreakpoint(address)); |
| EXPECT_OK(riscv_top_->WriteRegister("pc", entry_point_)); |
| // Initialize stack pointer. |
| EXPECT_OK(riscv_top_->WriteRegister("sp", 0x200000)); |
| |
| // Run to printf. Capture stdout. |
| testing::internal::CaptureStdout(); |
| EXPECT_OK(riscv_top_->Run()); |
| EXPECT_OK(riscv_top_->Wait()); |
| |
| // Should be stopped at breakpoint, but nothing printed. |
| auto halt_result = riscv_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. Capture stdout. |
| testing::internal::CaptureStdout(); |
| EXPECT_OK(riscv_top_->Run()); |
| EXPECT_OK(riscv_top_->Wait()); |
| |
| // Should be stopped due to semihost halt request. Captured 'Hello World! |
| // 5\n'. |
| halt_result = riscv_top_->GetLastHaltReason(); |
| CHECK_OK(halt_result); |
| EXPECT_EQ(static_cast<int>(halt_result.value()), |
| static_cast<int>(HaltReason::kSemihostHaltRequest)); |
| EXPECT_EQ("Hello World! 5\n", testing::internal::GetCapturedStdout()); |
| } |
| |
| // Memory read/write test. |
| TEST_F(RiscVTopTest, Memory) { |
| uint8_t byte_data = 0xab; |
| uint16_t half_data = 0xabcd; |
| uint32_t word_data = 0xba5eba11; |
| uint64_t dword_data = 0x5ca1ab1e'0ddball; |
| EXPECT_OK(riscv_top_->WriteMemory(0x1000, &byte_data, sizeof(byte_data))); |
| EXPECT_OK(riscv_top_->WriteMemory(0x1004, &half_data, sizeof(half_data))); |
| EXPECT_OK(riscv_top_->WriteMemory(0x1008, &word_data, sizeof(word_data))); |
| EXPECT_OK(riscv_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(riscv_top_->ReadMemory(0x1000, &byte_value, sizeof(byte_value))); |
| EXPECT_OK(riscv_top_->ReadMemory(0x1004, &half_value, sizeof(half_value))); |
| EXPECT_OK(riscv_top_->ReadMemory(0x1008, &word_value, sizeof(word_value))); |
| EXPECT_OK(riscv_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(riscv_top_->ReadMemory(0x1000, &byte_value, sizeof(byte_value))); |
| EXPECT_OK(riscv_top_->ReadMemory(0x1000, &half_value, sizeof(half_value))); |
| EXPECT_OK(riscv_top_->ReadMemory(0x1000, &word_value, sizeof(word_value))); |
| EXPECT_OK(riscv_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(RiscVTopTest, 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 = riscv_top_->ReadRegister(name); |
| EXPECT_OK(result.status()); |
| word_value = result.value(); |
| EXPECT_OK(riscv_top_->WriteRegister(name, word_value)); |
| } |
| // Test d-names and numbers. |
| uint64_t dword_value; |
| for (int i = 0; i < 32; i++) { |
| std::string name = absl::StrCat("f", i); |
| auto result = riscv_top_->ReadRegister(name); |
| EXPECT_OK(result.status()); |
| dword_value = result.value(); |
| EXPECT_OK(riscv_top_->WriteRegister(name, dword_value)); |
| } |
| // Not found. |
| EXPECT_EQ(riscv_top_->ReadRegister("x32").status().code(), |
| absl::StatusCode::kNotFound); |
| EXPECT_EQ(riscv_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(riscv_top_->WriteRegister(name, write_value)); |
| uint32_t read_value; |
| auto result = riscv_top_->ReadRegister(alias); |
| EXPECT_OK(result.status()); |
| read_value = result.value(); |
| EXPECT_EQ(read_value, write_value); |
| } |
| } |
| |
| // Directly read/write the memory addresses that are out-of-bound. |
| TEST_F(RiscVTopTest, ReadWriteOutOfBoundMemory) { |
| // Set the machine to have 16-byte memory |
| constexpr uint64_t kTestMemerySize = 0x10; |
| constexpr uint64_t kMaxPhysicalAddress = kTestMemerySize - 1; |
| constexpr uint64_t kBinaryAddress = 0; |
| riscv_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 = |
| riscv_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 = |
| riscv_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 = |
| riscv_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 = |
| riscv_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 = riscv_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 = riscv_top_->WriteMemory(kTestMemerySize + 4, mem_bytes, |
| sizeof(mem_bytes)); |
| EXPECT_FALSE(result.ok()); |
| } |
| |
| // This test will verify that the 64 bit version executes a program properly. |
| // No need to test other aspects of the top. |
| TEST_F(RiscVTopTest, RiscV64) { |
| delete riscv_top_; |
| delete loader_; |
| delete decoder_; |
| delete state_; |
| |
| // New top with 64 bit registers. |
| state_ = new RiscVState("RV64", RiscVXlen::RV64, memory_); |
| state_->set_rv_fp(fp_state_); |
| decoder_ = new RiscV64Decoder(state_, memory_); |
| |
| // Make sure the architectural and abi register aliases are added. |
| std::string reg_name; |
| for (int i = 0; i < 32; i++) { |
| reg_name = absl::StrCat(RiscVState::kXregPrefix, i); |
| (void)state_->AddRegister<RV64Register>(reg_name); |
| (void)state_->AddRegisterAlias<RV64Register>( |
| reg_name, ::mpact::sim::riscv::kXRegisterAliases[i]); |
| } |
| for (int i = 0; i < 32; i++) { |
| reg_name = absl::StrCat(RiscVState::kFregPrefix, i); |
| (void)state_->AddRegister<RVFpRegister>(reg_name); |
| (void)state_->AddRegisterAlias<RVFpRegister>( |
| reg_name, ::mpact::sim::riscv::kFRegisterAliases[i]); |
| } |
| |
| riscv_top_ = new RiscVTop("RV64", state_, decoder_); |
| // Set up the elf loader. |
| loader_ = new mpact::sim::util::ElfProgramLoader(memory_); |
| |
| LoadFile(kArm64FileName); |
| ArmSemihostSetup arm_semihost(riscv_top_, memory_); |
| // Set breakpoint at printf. |
| auto result = loader_->GetSymbol("printf"); |
| EXPECT_OK(result); |
| auto address = result.value().first; |
| EXPECT_OK(riscv_top_->SetSwBreakpoint(address)); |
| EXPECT_OK(riscv_top_->WriteRegister("pc", entry_point_)); |
| // Initialize stack pointer. |
| EXPECT_OK(riscv_top_->WriteRegister("sp", 0x200000)); |
| |
| // Run to printf. |
| testing::internal::CaptureStdout(); |
| EXPECT_OK(riscv_top_->Run()); |
| EXPECT_OK(riscv_top_->Wait()); |
| |
| // Should be stopped at breakpoint, but nothing printed. |
| auto halt_result = riscv_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(riscv_top_->Run()); |
| EXPECT_OK(riscv_top_->Wait()); |
| |
| // Should be stopped due to semihost halt request. Captured 'Hello World! |
| // 5\n'. |
| halt_result = riscv_top_->GetLastHaltReason(); |
| CHECK_OK(halt_result); |
| EXPECT_EQ(static_cast<int>(halt_result.value()), |
| static_cast<int>(HaltReason::kSemihostHaltRequest)); |
| EXPECT_EQ("Hello world! 5\n", testing::internal::GetCapturedStdout()); |
| |
| // Verify that the counter CSRs are non-zero. Mutation testing found the |
| // coverage gap. |
| EXPECT_NE(riscv_top_->ReadRegister("cycle").value(), 0); |
| EXPECT_NE(riscv_top_->ReadRegister("time").value(), 0); |
| EXPECT_NE(riscv_top_->ReadRegister("instret").value(), 0); |
| |
| // Verify that the hardware perf counters are initialized. These checks are |
| // based on coverage gaps found in mutation testing. |
| std::vector<int> uninitialized_perf_counter_indices; |
| for (int i = 0; i < riscv_top_->counter_hardware_perf()->size(); ++i) { |
| if (!riscv_top_->counter_hardware_perf()->at(i).IsInitialized()) { |
| uninitialized_perf_counter_indices.push_back(i); |
| } |
| } |
| EXPECT_EQ(uninitialized_perf_counter_indices.size(), 0) |
| << "Uninitialized perf counters: " |
| << absl::StrJoin(uninitialized_perf_counter_indices, ","); |
| |
| // Verify that the hardware perf counters are connected to the CSRs. |
| // This is based on a mutation testing finding. |
| // Get the CSR value, increment the underlying counter, and verify that the |
| // CSR value changes. |
| std::vector<std::string> failed_perf_counter_csr_names; |
| for (int i = 0; i < riscv_top_->counter_hardware_perf()->size(); ++i) { |
| std::string csr_name = absl::StrCat("hpmcounter", i + 3); |
| std::string machine_csr_name = absl::StrCat("mhpmcounter", i + 3); |
| auto csr_value = riscv_top_->ReadRegister(csr_name).value(); |
| riscv_top_->counter_hardware_perf()->at(i).Increment(1); |
| if (riscv_top_->ReadRegister(csr_name).value() == csr_value) { |
| failed_perf_counter_csr_names.push_back(csr_name); |
| } |
| if (riscv_top_->ReadRegister(machine_csr_name).value() == csr_value) { |
| failed_perf_counter_csr_names.push_back(machine_csr_name); |
| } |
| } |
| EXPECT_EQ(failed_perf_counter_csr_names.size(), 0) |
| << "Failed CSR names: " |
| << absl::StrJoin(failed_perf_counter_csr_names, ","); |
| } |
| |
| } // namespace |