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