| // 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. |
| |
| #ifndef MPACT_RISCV_RISCV_RISCV_STATE_H_ |
| #define MPACT_RISCV_RISCV_RISCV_STATE_H_ |
| |
| #include <cstdint> |
| #include <limits> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/functional/any_invocable.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "mpact/sim/generic/arch_state.h" |
| #include "mpact/sim/generic/counters.h" |
| #include "mpact/sim/generic/data_buffer.h" |
| #include "mpact/sim/generic/instruction.h" |
| #include "mpact/sim/generic/operand_interface.h" |
| #include "mpact/sim/generic/ref_count.h" |
| #include "mpact/sim/util/memory/memory_interface.h" |
| #include "riscv/riscv_csr.h" |
| #include "riscv/riscv_misa.h" |
| #include "riscv/riscv_register.h" |
| #include "riscv/riscv_vector_state.h" |
| #include "riscv/riscv_xip_xie.h" |
| #include "riscv/riscv_xstatus.h" |
| |
| namespace mpact { |
| namespace sim { |
| namespace riscv { |
| |
| using ArchState = ::mpact::sim::generic::ArchState; |
| using DataBuffer = ::mpact::sim::generic::DataBuffer; |
| using Instruction = ::mpact::sim::generic::Instruction; |
| using ReferenceCount = ::mpact::sim::generic::ReferenceCount; |
| |
| // Exception codes. |
| enum class ExceptionCode : uint64_t { |
| kInstructionAddressMisaligned = 0, |
| kInstructionAccessFault = 1, |
| kIllegalInstruction = 2, |
| kBreakpoint = 3, |
| kLoadAddressMisaligned = 4, |
| kLoadAccessFault = 5, |
| kStoreAddressMisaligned = 6, |
| kStoreAccessFault = 7, |
| kEnvCallFromUMode = 8, |
| kEnvCallFromSMode = 9, |
| kEnvCallFromMMode = 11, |
| kInstructionPageFault = 12, |
| kLoadPageFault = 13, |
| kStorePageFault = 15, |
| }; |
| |
| // Interrupt codes. |
| enum class InterruptCode : uint64_t { |
| kUserSoftwareInterrupt = 0, |
| kSupervisorSoftwareInterrupt = 1, |
| kMachineSoftwareInterrupt = 3, |
| kUserTimerInterrupt = 4, |
| kSupervisorTimerInterrupt = 5, |
| kMachineTimerInterrupt = 7, |
| kUserExternalInterrupt = 8, |
| kSupervisorExternalInterrupt = 9, |
| kMachineExternalInterrupt = 11, |
| kNone = std::numeric_limits<uint64_t>::max(), |
| }; |
| |
| // Isa extensions. |
| enum class IsaExtension : uint64_t { |
| kAtomic = 1 << 0, |
| kBitManipulation = 1 << 1, |
| kCompressed = 1 << 2, |
| kDoublePrecisionFp = 1 << 3, |
| kRV32EBase = 1 << 4, |
| kSinglePrecisionFp = 1 << 5, |
| kGExtension = 1 << 6, |
| kHypervisor = 1 << 7, |
| kRVIBaseIsa = 1 << 8, |
| kJExtension = 1 << 9, |
| kKReserved = 1 << 10, |
| kLExtension = 1 << 11, |
| kIntegerMulDiv = 1 << 12, |
| kUserLevelInterrupts = 1 << 13, |
| kOReserved = 1 << 14, |
| kPExtension = 1 << 15, |
| kQuadPrecisionFp = 1 << 16, |
| kRReserved = 1 << 17, |
| kSupervisorMode = 1 << 18, |
| kTExtension = 1 << 19, |
| kUserMode = 1 << 20, |
| kVectorExtension = 1 << 21, |
| kWReserved = 1 << 22, |
| kNonStandardExtension = 1 << 23, |
| kYReserved = 1 << 24, |
| kZReserved = 1 << 25, |
| kNone = std::numeric_limits<uint64_t>::max(), |
| }; |
| |
| // Privilege modes. |
| enum class PrivilegeMode : uint64_t { |
| kUser = 0b00, |
| kSupervisor = 0b01, |
| kMachine = 0b11, |
| }; |
| |
| // A simple load context class for convenience. |
| struct LoadContext : public generic::ReferenceCount { |
| explicit LoadContext(DataBuffer* vdb) : value_db(vdb) {} |
| ~LoadContext() override { |
| if (value_db != nullptr) value_db->DecRef(); |
| } |
| |
| // Override the base class method so that the data buffer can be DecRef'ed |
| // when the context object is recycled. |
| void OnRefCountIsZero() override { |
| if (value_db != nullptr) value_db->DecRef(); |
| value_db = nullptr; |
| // Call the base class method. |
| generic::ReferenceCount::OnRefCountIsZero(); |
| } |
| // Data buffers for the value loaded from memory (byte, half, word, etc.). |
| DataBuffer* value_db = nullptr; |
| }; |
| |
| // Vector load context class. |
| struct VectorLoadContext : public generic::ReferenceCount { |
| VectorLoadContext(DataBuffer* vdb, DataBuffer* mdb, int element_width_, |
| int vstart_, int vlength_) |
| : value_db(vdb), |
| mask_db(mdb), |
| element_width(element_width_), |
| vstart(vstart_), |
| vlength(vlength_) {} |
| ~VectorLoadContext() override { |
| if (value_db != nullptr) value_db->DecRef(); |
| if (mask_db != nullptr) mask_db->DecRef(); |
| } |
| // Override the base class method so that the data buffers can be DecRef'ed |
| // when the context object is recycled. |
| void OnRefCountIsZero() override { |
| if (value_db != nullptr) value_db->DecRef(); |
| value_db = nullptr; |
| if (mask_db != nullptr) mask_db->DecRef(); |
| mask_db = nullptr; |
| // Call the base class method. |
| generic::ReferenceCount::OnRefCountIsZero(); |
| } |
| // DataBuffer instances for the value loaded from memory. |
| DataBuffer* value_db = nullptr; |
| // Mask data buffer. |
| DataBuffer* mask_db = nullptr; |
| // Vector element width. |
| int element_width; |
| // Starting element index. |
| int vstart; |
| // Vector length. |
| int vlength; |
| }; |
| |
| // Supported values of Xlen. |
| enum class RiscVXlen : uint64_t { |
| RV32 = 0b01, |
| RV64 = 0b10, |
| RVUnknown = 4, |
| }; |
| |
| // Forward declare a template function defined in the .cc file. |
| template <typename T> |
| void CreateCsrs(RiscVState*, std::vector<RiscVCsrInterface*>&); |
| |
| class RiscVFPState; |
| class RiscVPmp; |
| |
| // Class that extends ArchState with RiscV specific methods. These methods |
| // implement RiscV specific memory operations, memory/IO fencing, system |
| // calls and software breakpoints. |
| class RiscVState : public ArchState { |
| public: |
| friend void CreateCsrs<uint32_t>(RiscVState*, |
| std::vector<RiscVCsrInterface*>&); |
| friend void CreateCsrs<uint64_t>(RiscVState*, |
| std::vector<RiscVCsrInterface*>&); |
| |
| static constexpr char kFregPrefix[] = "f"; |
| static constexpr char kXregPrefix[] = "x"; |
| static constexpr char kVregPrefix[] = "v"; |
| static constexpr char kCsrName[] = "csr"; |
| static constexpr char kNextPcName[] = "next_pc"; |
| static constexpr char kPcName[] = "pc"; |
| |
| RiscVState(absl::string_view id, RiscVXlen xlen, |
| util::MemoryInterface* memory, |
| util::AtomicMemoryOpInterface* atomic_memory); |
| RiscVState(absl::string_view id, RiscVXlen xlen, |
| util::MemoryInterface* memory) |
| : RiscVState(id, xlen, memory, nullptr) {} |
| ~RiscVState() override; |
| |
| // Deleted Constructors and operators. |
| RiscVState(const RiscVState&) = delete; |
| RiscVState(RiscVState&&) = delete; |
| RiscVState& operator=(const RiscVState&) = delete; |
| RiscVState& operator=(RiscVState&&) = delete; |
| |
| // Return a pair consisting of pointer to the named register and a bool that |
| // is true if the register had to be created, and false if it was found |
| // in the register map (or if nullptr is returned). |
| template <typename RegisterType> |
| std::pair<RegisterType*, bool> GetRegister(absl::string_view name) { |
| // If the register already exists, return a pointer to the register. |
| auto ptr = registers()->find(std::string(name)); |
| if (ptr != registers()->end()) |
| return std::make_pair(static_cast<RegisterType*>(ptr->second), false); |
| // Create a new register and return a pointer to the object. |
| return std::make_pair(AddRegister<RegisterType>(name), true); |
| } |
| |
| // Add register alias. |
| template <typename RegisterType> |
| absl::Status AddRegisterAlias(absl::string_view current_name, |
| absl::string_view new_name) { |
| auto ptr = registers()->find(std::string(current_name)); |
| if (ptr == registers()->end()) { |
| return absl::NotFoundError( |
| absl::StrCat("Register '", current_name, "' does not exist.")); |
| } |
| AddRegister(new_name, ptr->second); |
| return absl::OkStatus(); |
| } |
| |
| // Methods called by instruction semantic functions to load from memory. |
| void LoadMemory(const Instruction* inst, uint64_t address, DataBuffer* db, |
| Instruction* child_inst, ReferenceCount* context); |
| void LoadMemory(const Instruction* inst, DataBuffer* address_db, |
| DataBuffer* mask_db, int el_size, DataBuffer* db, |
| Instruction* child_inst, ReferenceCount* context); |
| // Methods called by instruction semantic functions to store to memory. |
| void StoreMemory(const Instruction* inst, uint64_t address, DataBuffer* db); |
| void StoreMemory(const Instruction* inst, DataBuffer* address_db, |
| DataBuffer* mask_db, int el_size, DataBuffer* db); |
| // Called by the fence instruction semantic function to signal a fence |
| // operation. |
| void Fence(const Instruction* inst, int fm, int predecessor, int successor); |
| // Synchronize instruction and data streams. |
| void FenceI(const Instruction* inst); |
| // System call. |
| void ECall(const Instruction* inst); |
| // Breakpoint. |
| void EBreak(const Instruction* inst); |
| // WFI |
| void WFI(const Instruction* inst); |
| // Ceases execution on the core. This is a non-standard instruction that |
| // quiesces traffic for embedded cores before halting. The core must be reset |
| // to come out of this state. |
| void Cease(const Instruction* inst); |
| // Trap. |
| void Trap(bool is_interrupt, uint64_t trap_value, uint64_t exception_code, |
| uint64_t epc, const Instruction* inst); |
| // Add ebreak handler. |
| void AddEbreakHandler(absl::AnyInvocable<bool(const Instruction*)> handler) { |
| on_ebreak_.emplace_back(std::move(handler)); |
| } |
| // This function is called after any event that may have caused an interrupt |
| // to be registered as pending or enabled. If the interrupt can be taken |
| // it registers it as available. |
| void CheckForInterrupt() override; |
| // This function is called when the return pc for the available interrupt |
| // is known. If there is no available interrupt, it just returns. |
| void TakeAvailableInterrupt(uint64_t epc); |
| |
| // Indicates that the program has returned from handling an interrupt. This |
| // decrements the interrupt handler depth and should be called by the |
| // implementations of mret, sret, and uret. |
| void SignalReturnFromInterrupt() { counter_interrupt_returns_.Increment(1); } |
| |
| // Returns the depth of the interrupt handler currently being executed, or |
| // zero if no interrupt handler is being executed. |
| int InterruptHandlerDepth() const { |
| return counter_interrupts_taken_.GetValue() - |
| counter_interrupt_returns_.GetValue(); |
| } |
| |
| // Accessors. |
| void set_memory(util::MemoryInterface* memory) { memory_ = memory; } |
| util::MemoryInterface* memory() const { return memory_; } |
| util::AtomicMemoryOpInterface* atomic_memory() const { |
| return atomic_memory_; |
| } |
| void set_atomic_memory(util::AtomicMemoryOpInterface* atomic_memory) { |
| atomic_memory_ = atomic_memory; |
| } |
| |
| void set_max_physical_address(uint64_t max_physical_address); |
| uint64_t max_physical_address() const { return max_physical_address_; } |
| |
| // Setters for handlers for ecall, and trap. The handler returns true |
| // if the instruction/event was handled, and false otherwise. |
| |
| void set_on_ecall(absl::AnyInvocable<bool(const Instruction*)> callback) { |
| on_ecall_ = std::move(callback); |
| } |
| |
| void set_on_wfi(absl::AnyInvocable<bool(const Instruction*)> callback) { |
| on_wfi_ = std::move(callback); |
| } |
| |
| void set_on_cease(absl::AnyInvocable<bool(const Instruction*)> callback) { |
| on_cease_ = std::move(callback); |
| } |
| |
| void set_on_trap( |
| absl::AnyInvocable<bool(bool /*is_interrupt*/, uint64_t /*trap_value*/, |
| uint64_t /*exception_code*/, uint64_t /*epc*/, |
| const Instruction*)> |
| callback) { |
| on_trap_ = std::move(callback); |
| } |
| |
| int flen() const { return flen_; } |
| RiscVXlen xlen() const { return xlen_; } |
| RiscVVectorState* rv_vector() const { return rv_vector_; } |
| void set_rv_vector(RiscVVectorState* value) { rv_vector_ = value; } |
| RiscVFPState* rv_fp() const { return rv_fp_; } |
| void set_rv_fp(RiscVFPState* value) { rv_fp_ = value; } |
| void set_vector_register_width(int value) { vector_register_width_ = value; } |
| int vector_register_width() const { return vector_register_width_; } |
| |
| RiscVCsrSet* csr_set() const { return csr_set_; } |
| |
| PrivilegeMode privilege_mode() const { return privilege_mode_; } |
| void set_privilege_mode(PrivilegeMode privilege_mode) { |
| privilege_mode_ = privilege_mode; |
| } |
| |
| // Returns true if an interrupt is available for the core to take or false |
| // otherwise. |
| inline bool is_interrupt_available() const { return is_interrupt_available_; } |
| // Resets the is_interrupt_available flag to false. This should only be called |
| // when resetting the RISCV core, as 'is_interrupt_available' is Normally |
| // reset during the interrupt handling flow. |
| inline void reset_is_interrupt_available() { |
| is_interrupt_available_ = false; |
| } |
| |
| void set_branch(bool value) { branch_ = value; } |
| bool branch() const { return branch_; } |
| |
| // Getters for select CSRs. |
| RiscVMStatus* mstatus() const { return mstatus_; } |
| RiscVMIsa* misa() const { return misa_; } |
| RiscVMIp* mip() const { return mip_; } |
| RiscVMIe* mie() const { return mie_; } |
| RiscVCsrInterface* jvt() const { return jvt_; } |
| RiscVCsrInterface* mtvec() const { return mtvec_; } |
| RiscVCsrInterface* mepc() const { return mepc_; } |
| RiscVCsrInterface* mcause() const { return mcause_; } |
| RiscVCsrInterface* medeleg() const { return medeleg_; } |
| RiscVCsrInterface* mideleg() const { return mideleg_; } |
| RiscVSIp* sip() const { return sip_; } |
| RiscVSIe* sie() const { return sie_; } |
| RiscVCsrInterface* stvec() const { return stvec_; } |
| RiscVCsrInterface* sepc() const { return sepc_; } |
| RiscVCsrInterface* scause() const { return scause_; } |
| RiscVCsrInterface* sideleg() const { return sideleg_; } |
| |
| private: |
| InterruptCode PickInterrupt(uint32_t interrupts); |
| RiscVXlen xlen_; |
| uint64_t max_physical_address_; |
| RiscVVectorState* rv_vector_ = nullptr; |
| RiscVFPState* rv_fp_ = nullptr; |
| // Program counter register. |
| generic::RegisterBase* pc_; |
| // Operands used to access pc values generically. Note, the pc value may read |
| // as the address of the next instruction during execution of an instruction, |
| // so the address of the instruction executing should be used instead. |
| generic::SourceOperandInterface* pc_src_operand_ = nullptr; |
| generic::DestinationOperandInterface* pc_dst_operand_ = nullptr; |
| int vector_register_width_ = 0; |
| int flen_ = 0; |
| util::MemoryInterface* memory_ = nullptr; |
| util::AtomicMemoryOpInterface* atomic_memory_ = nullptr; |
| RiscVCsrSet* csr_set_ = nullptr; |
| std::vector<absl::AnyInvocable<bool(const Instruction*)>> on_ebreak_; |
| absl::AnyInvocable<bool(const Instruction*)> on_ecall_; |
| absl::AnyInvocable<bool(bool, uint64_t, uint64_t, uint64_t, |
| const Instruction*)> |
| on_trap_; |
| absl::AnyInvocable<bool(const Instruction*)> on_wfi_; |
| absl::AnyInvocable<bool(const Instruction*)> on_cease_; |
| std::vector<RiscVCsrInterface*> csr_vec_; |
| // For interrupt handling. |
| bool is_interrupt_available_ = false; |
| InterruptCode available_interrupt_code_ = InterruptCode::kNone; |
| // By default, execute in machine mode. |
| PrivilegeMode privilege_mode_ = PrivilegeMode::kMachine; |
| // Flag set on branch instructions. |
| bool branch_ = false; |
| // Handles to frequently used CSRs. |
| RiscVMStatus* mstatus_ = nullptr; |
| RiscVMIsa* misa_ = nullptr; |
| RiscVMIp* mip_ = nullptr; |
| RiscVMIe* mie_ = nullptr; |
| RiscVPmp* pmp_ = nullptr; |
| RiscVCsrInterface* jvt_ = nullptr; |
| RiscVCsrInterface* mtvec_ = nullptr; |
| RiscVCsrInterface* mepc_ = nullptr; |
| RiscVCsrInterface* mcause_ = nullptr; |
| RiscVCsrInterface* medeleg_ = nullptr; |
| RiscVCsrInterface* mideleg_ = nullptr; |
| RiscVSIp* sip_ = nullptr; |
| RiscVSIe* sie_ = nullptr; |
| RiscVCsrInterface* stvec_ = nullptr; |
| RiscVCsrInterface* sepc_ = nullptr; |
| RiscVCsrInterface* scause_ = nullptr; |
| RiscVCsrInterface* sideleg_ = nullptr; |
| generic::SimpleCounter<int64_t> counter_interrupts_taken_; |
| generic::SimpleCounter<int64_t> counter_interrupt_returns_; |
| }; |
| |
| // Specialization for RiscV vector registers. |
| template <> |
| inline std::pair<RVVectorRegister*, bool> |
| RiscVState::GetRegister<RVVectorRegister>(absl::string_view name) { |
| int vector_byte_width = vector_register_width(); |
| if (vector_byte_width == 0) return std::make_pair(nullptr, false); |
| auto ptr = registers()->find(std::string(name)); |
| if (ptr != registers()->end()) |
| return std::make_pair(static_cast<RVVectorRegister*>(ptr->second), false); |
| // Create a new register and return a pointer to the object. |
| return std::make_pair(AddRegister<RVVectorRegister>(name, vector_byte_width), |
| true); |
| } |
| |
| } // namespace riscv |
| } // namespace sim |
| } // namespace mpact |
| |
| #endif // MPACT_RISCV_RISCV_RISCV_STATE_H_ |