| // Copyright 2024 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 | |
| // | |
| // http://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 "cheriot/riscv_cheriot_zicsr_instructions.h" | |
| #include <cstdint> | |
| #include <string> | |
| #include <tuple> | |
| #include <vector> | |
| #include "absl/log/check.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/types/span.h" | |
| #include "cheriot/cheriot_register.h" | |
| #include "cheriot/cheriot_state.h" | |
| #include "cheriot/riscv_cheriot_csr_enum.h" | |
| #include "googlemock/include/gmock/gmock.h" | |
| #include "mpact/sim/generic/immediate_operand.h" | |
| #include "mpact/sim/generic/instruction.h" | |
| #include "mpact/sim/util/memory/tagged_flat_demand_memory.h" | |
| #include "riscv//riscv_csr.h" | |
| // This file contains tests for individual Zicsr instructions. | |
| namespace { | |
| using EC = ::mpact::sim::cheriot::ExceptionCode; | |
| using PB = ::mpact::sim::cheriot::CheriotRegister::PermissionBits; | |
| using ::mpact::sim::cheriot::CheriotRegister; | |
| using ::mpact::sim::cheriot::CheriotState; | |
| using ::mpact::sim::cheriot::RiscVCheriotCsrEnum; | |
| using ::mpact::sim::generic::ImmediateOperand; | |
| using ::mpact::sim::generic::Instruction; | |
| using ::mpact::sim::riscv::RiscV32SimpleCsr; | |
| using ::mpact::sim::util::TaggedFlatDemandMemory; | |
| constexpr uint32_t kInstAddress = 0x2468; | |
| constexpr char kX1[] = "x1"; | |
| constexpr char kX3[] = "x3"; | |
| constexpr uint32_t kMScratchValue = | |
| static_cast<uint32_t>(RiscVCheriotCsrEnum::kMScratch); | |
| constexpr uint32_t kCycleValue = | |
| static_cast<uint32_t>(RiscVCheriotCsrEnum::kCycle); | |
| // The test fixture allocates a machine state object and an instruction object. | |
| // It also contains convenience methods for interacting with the instruction | |
| // object in a more short hand form. | |
| class ZicsrInstructionsTest : public testing::Test { | |
| protected: | |
| ZicsrInstructionsTest() { | |
| mem_ = new TaggedFlatDemandMemory(8); | |
| state_ = new CheriotState("test", mem_, nullptr); | |
| instruction_ = new Instruction(kInstAddress, state_); | |
| instruction_->set_size(4); | |
| state_->set_on_trap([this](bool is_interrupt, uint64_t trap_value, | |
| uint64_t exception_code, uint64_t epc, | |
| const Instruction *inst) { | |
| return TrapHandler(is_interrupt, trap_value, exception_code, epc, inst); | |
| }); | |
| } | |
| ~ZicsrInstructionsTest() override { | |
| delete instruction_; | |
| delete mem_; | |
| delete state_; | |
| } | |
| // Appends the source and destination operands for the register names | |
| // given in the two vectors. | |
| void AppendRegisterOperands(Instruction *inst, | |
| absl::Span<const std::string> sources, | |
| absl::Span<const std::string> destinations) { | |
| for (auto ®_name : sources) { | |
| auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first; | |
| inst->AppendSource(reg->CreateSourceOperand()); | |
| } | |
| for (auto ®_name : destinations) { | |
| auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first; | |
| inst->AppendDestination(reg->CreateDestinationOperand(0)); | |
| } | |
| } | |
| // Appends immediate source operands with the given values. | |
| template <typename T> | |
| void AppendImmediateOperands(Instruction *inst, | |
| const std::vector<T> &values) { | |
| for (auto value : values) { | |
| auto *src = new ImmediateOperand<T>(value); | |
| inst->AppendSource(src); | |
| } | |
| } | |
| // Takes a vector of tuples of register names and values. Fetches each | |
| // named register and sets it to the corresponding value. | |
| template <typename T> | |
| void SetRegisterValues(const std::vector<std::tuple<std::string, T>> values) { | |
| for (auto &[reg_name, value] : values) { | |
| auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first; | |
| auto *db = state_->db_factory()->Allocate<CheriotRegister::ValueType>(1); | |
| db->Set<T>(0, value); | |
| reg->SetDataBuffer(db); | |
| db->DecRef(); | |
| } | |
| } | |
| // Initializes the semantic function of the instruction object. | |
| void SetSemanticFunction(Instruction::SemanticFunction fcn) { | |
| instruction_->set_semantic_function(fcn); | |
| } | |
| // Returns the value of the named register. | |
| template <typename T> | |
| T GetRegisterValue(absl::string_view reg_name) { | |
| auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first; | |
| return reg->data_buffer()->Get<T>(0); | |
| } | |
| // Handler for any traps that are raised. It is used to capture the trap | |
| // information for checks. | |
| bool TrapHandler(bool is_interrupt, uint64_t trap_value, | |
| uint64_t exception_code, uint64_t epc, | |
| const Instruction *inst); | |
| TaggedFlatDemandMemory *mem_; | |
| RiscV32SimpleCsr *csr_; | |
| CheriotState *state_; | |
| Instruction *instruction_; | |
| bool trap_taken_ = false; | |
| bool trap_is_interrupt_ = false; | |
| uint64_t trap_value_ = 0; | |
| uint64_t trap_exception_code_ = 0; | |
| uint64_t trap_epc_ = 0; | |
| const Instruction *trap_inst_ = nullptr; | |
| }; | |
| bool ZicsrInstructionsTest::TrapHandler(bool is_interrupt, uint64_t trap_value, | |
| uint64_t exception_code, uint64_t epc, | |
| const Instruction *inst) { | |
| trap_taken_ = true; | |
| trap_is_interrupt_ = is_interrupt; | |
| trap_value_ = trap_value; | |
| trap_exception_code_ = exception_code; | |
| trap_epc_ = epc; | |
| trap_inst_ = inst; | |
| return true; | |
| } | |
| constexpr uint32_t kCsrValue1 = 0xaaaa5555; | |
| constexpr uint32_t kCsrValue2 = 0xa5a5a5a5; | |
| // The following tests all follow the same pattern. First the CSR and any | |
| // registers that are used are initialized with known values. Then the | |
| // instruction is initialized with the proper operands. The instruction is | |
| // executed, before checking the values of registers and CSR for correctness. | |
| // Tests the plain Csrrw/Csrrwi function. | |
| TEST_F(ZicsrInstructionsTest, RiscVZiCsrrw) { | |
| auto result = state_->csr_set()->GetCsr(kMScratchValue); | |
| CHECK_OK(result); | |
| auto *csr = result.value(); | |
| CHECK_NE(csr, nullptr); | |
| csr->Set(kCsrValue1); | |
| SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}}); | |
| AppendRegisterOperands(instruction_, {kX1}, {kX3}); | |
| AppendImmediateOperands(instruction_, std::vector<uint32_t>{kMScratchValue}); | |
| SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrw); | |
| instruction_->Execute(nullptr); | |
| EXPECT_EQ(GetRegisterValue<uint32_t>(kX1), kCsrValue2); | |
| EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kCsrValue1); | |
| EXPECT_EQ(csr->AsUint32(), kCsrValue2); | |
| } | |
| // Tests the plain Csrrs/Csrrsi function. | |
| TEST_F(ZicsrInstructionsTest, RiscVZiCsrrs) { | |
| auto result = state_->csr_set()->GetCsr(kMScratchValue); | |
| CHECK_OK(result); | |
| auto *csr = result.value(); | |
| CHECK_NE(csr, nullptr); | |
| csr->Set(kCsrValue1); | |
| SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}}); | |
| AppendRegisterOperands(instruction_, {kX1}, {kX3}); | |
| AppendImmediateOperands(instruction_, std::vector<uint32_t>{kMScratchValue}); | |
| SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrs); | |
| instruction_->Execute(nullptr); | |
| EXPECT_EQ(GetRegisterValue<uint32_t>(kX1), kCsrValue2); | |
| EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kCsrValue1); | |
| EXPECT_EQ(csr->AsUint32(), kCsrValue1 | kCsrValue2); | |
| } | |
| // Tests the plain Cssrrc/Csrrci function. | |
| TEST_F(ZicsrInstructionsTest, RiscVZiCsrrc) { | |
| auto result = state_->csr_set()->GetCsr(kMScratchValue); | |
| CHECK_OK(result); | |
| auto *csr = result.value(); | |
| CHECK_NE(csr, nullptr); | |
| csr->Set(kCsrValue1); | |
| SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}}); | |
| AppendRegisterOperands(instruction_, {kX1}, {kX3}); | |
| AppendImmediateOperands(instruction_, std::vector<uint32_t>{kMScratchValue}); | |
| SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrc); | |
| instruction_->Execute(nullptr); | |
| EXPECT_EQ(GetRegisterValue<uint32_t>(kX1), kCsrValue2); | |
| EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kCsrValue1); | |
| EXPECT_EQ(csr->AsUint32(), kCsrValue1 & ~kCsrValue2); | |
| } | |
| // Tests Cssrw when the CSR register isn't read (register source is x0). | |
| TEST_F(ZicsrInstructionsTest, RiscVZiCsrrwNr) { | |
| auto result = state_->csr_set()->GetCsr(kMScratchValue); | |
| CHECK_OK(result); | |
| auto *csr = result.value(); | |
| CHECK_NE(csr, nullptr); | |
| csr->Set(kCsrValue1); | |
| SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}}); | |
| AppendRegisterOperands(instruction_, {kX1}, {kX3}); | |
| AppendImmediateOperands(instruction_, std::vector<uint32_t>{kMScratchValue}); | |
| SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrwNr); | |
| instruction_->Execute(nullptr); | |
| EXPECT_EQ(GetRegisterValue<uint32_t>(kX1), kCsrValue2); | |
| EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), 0); | |
| EXPECT_EQ(csr->AsUint32(), kCsrValue2); | |
| } | |
| // Tests Cssr[wcs]i when the CSR register isn't written (immediate is 0). | |
| TEST_F(ZicsrInstructionsTest, RiscVZiCsrrNw) { | |
| auto result = state_->csr_set()->GetCsr(kMScratchValue); | |
| CHECK_OK(result); | |
| auto *csr = result.value(); | |
| CHECK_NE(csr, nullptr); | |
| csr->Set(kCsrValue1); | |
| SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}}); | |
| AppendImmediateOperands(instruction_, std::vector<uint32_t>{kMScratchValue}); | |
| AppendRegisterOperands(instruction_, {}, {kX3}); | |
| SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrNw); | |
| instruction_->Execute(nullptr); | |
| EXPECT_EQ(GetRegisterValue<uint32_t>(kX1), kCsrValue2); | |
| EXPECT_EQ(GetRegisterValue<uint32_t>(kX3), kCsrValue1); | |
| EXPECT_EQ(csr->AsUint32(), kCsrValue1); | |
| } | |
| // Test for trap if accessing without required permission in pcc. | |
| TEST_F(ZicsrInstructionsTest, RiscVZiCsrrNwTrap) { | |
| state_->pcc()->ClearPermissions(PB::kPermitAccessSystemRegisters); | |
| SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}}); | |
| AppendImmediateOperands(instruction_, std::vector<uint32_t>{kMScratchValue}); | |
| AppendRegisterOperands(instruction_, {kX1}, {kX3}); | |
| SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrNw); | |
| instruction_->Execute(nullptr); | |
| EXPECT_TRUE(trap_taken_); | |
| EXPECT_FALSE(trap_is_interrupt_); | |
| EXPECT_EQ(trap_value_, | |
| (0b1'00000 << 5) | *EC::kCapExPermitAccessSystemRegistersViolation); | |
| EXPECT_EQ(trap_exception_code_, CheriotState::kCheriExceptionCode); | |
| EXPECT_EQ(trap_epc_, instruction_->address()); | |
| EXPECT_EQ(trap_inst_, trap_inst_); | |
| } | |
| // Test for no trap if accessing 'cycle' without required permission in pcc, as | |
| // a small subset of CSRs are user mode accessible, and thus does not require | |
| // pcc permission bit. | |
| TEST_F(ZicsrInstructionsTest, RiscVZiCsrrNwNoTrap) { | |
| state_->pcc()->ClearPermissions(PB::kPermitAccessSystemRegisters); | |
| SetRegisterValues<uint32_t>({{kX1, kCsrValue2}, {kX3, 0}}); | |
| AppendImmediateOperands(instruction_, std::vector<uint32_t>{kCycleValue}); | |
| AppendRegisterOperands(instruction_, {kX1}, {kX3}); | |
| SetSemanticFunction(&::mpact::sim::cheriot::RiscVZiCsrrNw); | |
| instruction_->Execute(nullptr); | |
| EXPECT_FALSE(trap_taken_); | |
| } | |
| } // namespace |