|  | // 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 |