blob: a0666cf614cae8aca8b98dad936486a506f902c4 [file]
// 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 {
static constexpr int kNumHardwarePerfCounters = 29;
static constexpr int kMinimumHardwarePerfIndex = 3;
using ArchState = ::mpact::sim::generic::ArchState;
using DataBuffer = ::mpact::sim::generic::DataBuffer;
using Instruction = ::mpact::sim::generic::Instruction;
using ReferenceCount = ::mpact::sim::generic::ReferenceCount;
enum class IsaExtensions : uint64_t {
kAtomic = 1 << 0,
kBitManipulation = 1 << 1,
kCompressed = 1 << 2,
kDoublePrecisionFp = 1 << 3,
kEBaseIsa = 1 << 4,
kSinglePrecisionFp = 1 << 5,
kGReserved = 1 << 6,
kHypervisor = 1 << 7,
kIBaseIsa = 1 << 8,
kJReserved = 1 << 9,
kKReserved = 1 << 10,
kLReserved = 1 << 11,
kIntegerMulDiv = 1 << 12,
kUserLevelInterrupts = 1 << 13,
kOReserved = 1 << 14,
kPReserved = 1 << 15,
kQuadPrecisionFp = 1 << 16,
kRReserved = 1 << 17,
kSupervisorMode = 1 << 18,
kTReserved = 1 << 19,
kUserMode = 1 << 20,
kVector = 1 << 21,
kWReserved = 1 << 22,
kNonStandardExtension = 1 << 23,
kYReserved = 1 << 24,
kZReserved = 1 << 25,
};
constexpr uint64_t kCompressedExtension = 1 << 2;
// 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* mtval() const { return mtval_; }
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* stval() const { return stval_; }
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* mtval_ = nullptr;
RiscVCsrInterface* mepc_ = nullptr;
RiscVCsrInterface* mcause_ = nullptr;
RiscVCsrInterface* medeleg_ = nullptr;
RiscVCsrInterface* mideleg_ = nullptr;
RiscVSIp* sip_ = nullptr;
RiscVSIe* sie_ = nullptr;
RiscVCsrInterface* stvec_ = nullptr;
RiscVCsrInterface* stval_ = 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_