blob: 29f6ea5004a31f288154899e62de15a6067c2937 [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.
#include "riscv/riscv_state.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <string>
#include <vector>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "mpact/sim/generic/arch_state.h"
#include "mpact/sim/generic/type_helpers.h"
#include "mpact/sim/util/memory/memory_interface.h"
#include "riscv/riscv_counter_csr.h"
#include "riscv/riscv_csr.h"
#include "riscv/riscv_jvt.h"
#include "riscv/riscv_misa.h"
#include "riscv/riscv_pmp.h"
#include "riscv/riscv_register.h"
#include "riscv/riscv_sim_csrs.h"
#include "riscv/riscv_xip_xie.h"
#include "riscv/riscv_xstatus.h"
namespace mpact {
namespace sim {
namespace riscv {
using ::mpact::sim::generic::operator*; // NOLINT: used below (clang error).
// These helper templates are used to store information about the CSR registers
// for 32 and 64 bit versions of RiscV.
template <typename T>
struct CsrInfo {};
template <>
struct CsrInfo<uint32_t> {
using T = uint32_t;
static constexpr T kMhartidRMask = std::numeric_limits<T>::max();
static constexpr T kMhartidWMask = 0;
static constexpr T kMstatusInitialValue = 2000;
static constexpr T kUstatusRMask = 0x11;
static constexpr T kUstatusWMask = 0x11;
static constexpr T kSstatusRMask = 0x800d'e133;
static constexpr T kSstatusWMask = 0x800d'e133;
static constexpr T kMisaInitialValue =
(*RiscVXlen::RV32 << 30) | *IsaExtension::kIntegerMulDiv |
*IsaExtension::kRVIBaseIsa | *IsaExtension::kGExtension |
*IsaExtension::kSinglePrecisionFp | *IsaExtension::kDoublePrecisionFp |
*IsaExtension::kCompressed | *IsaExtension::kAtomic |
*IsaExtension::kSupervisorMode;
static constexpr T kMisaRMask = 0xc3ff'ffff;
static constexpr T kMisaWMask = 0x0;
// Can't delegate env call from M-mode.
static constexpr T kMEdelegRMask = 0x0000'b3ff;
static constexpr T kMEdelegWMask = 0x0000'b3ff;
static constexpr T kMIdelegRMask = 0x0bbb;
static constexpr T kMIdelegWMask = 0x0bbb;
};
template <>
struct CsrInfo<uint64_t> {
using T = uint64_t;
static constexpr T kMhartidRMask = std::numeric_limits<T>::max();
static constexpr T kMhartidWMask = 0;
static constexpr T kMstatusInitialValue = 0x0000'000a'0000'2000ULL;
static constexpr T kUstatusRMask = 0x11;
static constexpr T kUstatusWMask = 0x11;
static constexpr T kSstatusRMask = 0x8000'0003'000d'e133ULL;
static constexpr T kSstatusWMask = 0x8000'0003'000d'e133ULL;
static constexpr T kMisaInitialValue =
(*RiscVXlen::RV64 << 62) | *IsaExtension::kIntegerMulDiv |
*IsaExtension::kRVIBaseIsa | *IsaExtension::kGExtension |
*IsaExtension::kSinglePrecisionFp | *IsaExtension::kDoublePrecisionFp |
*IsaExtension::kCompressed | *IsaExtension::kAtomic |
*IsaExtension::kSupervisorMode;
static constexpr T kMisaRMask = 0xc000'0000'03ff'ffffULL;
static constexpr T kMisaWMask = 0x0;
// Can't delegate env call from M-mode.
static constexpr T kMEdelegRMask = 0x0000'0000'0000'b3ffULL;
static constexpr T kMEdelegWMask = 0x0000'0000'0000'b3ffULL;
static constexpr T kMIdelegRMask = 0x0bbb;
static constexpr T kMIdelegWMask = 0x0bbb;
};
// Three templated helper functions used to create individual CSRs.
// This creates the CSR and assigns it to a pointer in the state object. Type
// can be inferred from the state object pointer.
template <typename T, typename... Ps>
T* CreateCsr(RiscVState* state, T*& ptr,
std::vector<RiscVCsrInterface*>& csr_vec, Ps... pargs) {
auto* csr = new T(pargs...);
auto result = state->csr_set()->AddCsr(csr);
if (!result.ok()) {
LOG(ERROR) << absl::StrCat("Failed to add csr '", csr->name(),
"': ", result.message());
delete csr;
return nullptr;
}
csr_vec.push_back(csr);
ptr = csr;
return csr;
}
// This creates the CSR and assigns it to a pointer in the state object, however
// that pointer is of abstract type, so the CSR type cannot be inferred, but
// has to be specified in the call.
template <typename T, typename... Ps>
T* CreateCsr(RiscVState* state, RiscVCsrInterface*& ptr,
std::vector<RiscVCsrInterface*>& csr_vec, Ps... pargs) {
auto* csr = new T(pargs...);
auto result = state->csr_set()->AddCsr(csr);
if (!result.ok()) {
LOG(ERROR) << absl::StrCat("Failed to add csr '", csr->name(),
"': ", result.message());
delete csr;
return nullptr;
}
csr_vec.push_back(csr);
ptr = csr;
return csr;
}
// This creates the CSR, but does not assign it to a pointer in the state
// object. That means the type cannot be inferred, but has to be specified
// in the call.
template <typename T, typename... Ps>
T* CreateCsr(RiscVState* state, std::vector<RiscVCsrInterface*>& csr_vec,
Ps... pargs) {
auto* csr = new T(pargs...);
auto result = state->csr_set()->AddCsr(csr);
if (!result.ok()) {
LOG(ERROR) << absl::StrCat("Failed to add csr '", csr->name(),
"': ", result.message());
delete csr;
return nullptr;
}
csr_vec.push_back(csr);
return csr;
}
// Templated helper function that is used to create the set of CSRs needed
// for simulation.
template <typename T>
void CreateCsrs(RiscVState* state, std::vector<RiscVCsrInterface*>& csr_vec) {
absl::Status result;
// Create CSRs.
// menvcfg
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, csr_vec, "menvcfg",
RiscVCsrEnum::kMenvcfg, 0, state),
nullptr);
// misa
auto* misa = CreateCsr(state, state->misa_, csr_vec,
CsrInfo<T>::kMisaInitialValue, state);
CHECK_NE(misa, nullptr);
// mtvec
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, state->mtvec_, csr_vec, "mtvec",
RiscVCsrEnum::kMTvec, 0, state),
nullptr);
// mcause
CHECK_NE(
CreateCsr<RiscVSimpleCsr<T>>(state, state->mcause_, csr_vec, "mcause",
RiscVCsrEnum::kMCause, 0, state),
nullptr);
// Mip and Mie are always 32 bit.
// mip
auto* mip = CreateCsr(state, state->mip_, csr_vec, 0, state);
CHECK_NE(mip, nullptr);
// mie
auto* mie = CreateCsr(state, state->mie_, csr_vec, 0, state);
CHECK_NE(mie, nullptr);
// mhartid
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(
state, csr_vec, "mhartid", RiscVCsrEnum::kMHartId, 0,
CsrInfo<T>::kMhartidRMask, CsrInfo<T>::kMhartidWMask, state),
nullptr);
// mepc
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, state->mepc_, csr_vec, "mepc",
RiscVCsrEnum::kMEpc, 0, state),
nullptr);
// mscratch
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, csr_vec, "mscratch",
RiscVCsrEnum::kMScratch, 0, state),
nullptr);
// medeleg - machine mode exception delegation register.
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, state->medeleg_, csr_vec,
"medeleg", RiscVCsrEnum::kMEDeleg, 0,
CsrInfo<T>::kMEdelegRMask,
CsrInfo<T>::kMEdelegWMask, state),
nullptr);
// mideleg - machine mode interrupt delegation register.
auto* mideleg = CreateCsr<RiscVSimpleCsr<T>>(
state, state->mideleg_, csr_vec, "mideleg", RiscVCsrEnum::kMIDeleg, 0,
CsrInfo<T>::kMIdelegRMask, CsrInfo<T>::kMIdelegWMask, state);
CHECK_NE(mideleg, nullptr);
// mstatus
auto* mstatus =
CreateCsr(state, state->mstatus_, csr_vec,
CsrInfo<uint64_t>::kMstatusInitialValue, state, misa);
CHECK_NE(mstatus, nullptr);
// mtval
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, state->mtval_, csr_vec, "mtval",
RiscVCsrEnum::kMTval, 0, state),
nullptr);
// minstret/minstreth
auto* minstret = CreateCsr<RiscVPerformanceCounterCsr<T, RiscVState>>(
state, csr_vec, "minstret", RiscVCsrEnum ::kMInstret, state);
CHECK_NE(minstret, nullptr);
if (std::is_same_v<T, uint32_t>) {
CHECK_NE(
CreateCsr<RiscVPerformanceCounterCsrHigh<RiscVState>>(
state, csr_vec, "minstreth", RiscVCsrEnum::kMInstretH, state,
reinterpret_cast<RiscVCounterCsr<uint32_t, RiscVState>*>(minstret)),
nullptr);
}
// mcycle/mcycleh
auto* mcycle = CreateCsr<RiscVPerformanceCounterCsr<T, RiscVState>>(
state, csr_vec, "mcycle", RiscVCsrEnum::kMCycle, state);
CHECK_NE(mcycle, nullptr);
if (std::is_same_v<T, uint32_t>) {
CHECK_NE(
CreateCsr<RiscVPerformanceCounterCsrHigh<RiscVState>>(
state, csr_vec, "mcycleh", RiscVCsrEnum::kMCycleH, state,
reinterpret_cast<RiscVCounterCsr<uint32_t, RiscVState>*>(mcycle)),
nullptr);
}
// cycle / cycleh
auto* cycle = CreateCsr<RiscVPerformanceCounterCsr<T, RiscVState>>(
state, csr_vec, "cycle", RiscVCsrEnum::kCycle, state);
CHECK_NE(cycle, nullptr);
if (std::is_same_v<T, uint32_t>) {
CHECK_NE(
CreateCsr<RiscVPerformanceCounterCsrHigh<RiscVState>>(
state, csr_vec, "cycleh", RiscVCsrEnum::kCycleH, state,
reinterpret_cast<RiscVCounterCsr<uint32_t, RiscVState>*>(cycle)),
nullptr);
}
// time / timeh
auto* time = CreateCsr<RiscVCounterCsr<T, RiscVState>>(
state, csr_vec, "time", RiscVCsrEnum::kTime, state);
CHECK_NE(time, nullptr);
if (std::is_same_v<T, uint32_t>) {
CHECK_NE(
CreateCsr<RiscVCounterCsrHigh<RiscVState>>(
state, csr_vec, "timeh", RiscVCsrEnum::kTimeH, state,
reinterpret_cast<RiscVCounterCsr<uint32_t, RiscVState>*>(time)),
nullptr);
}
// instret / instreth
auto* instret = CreateCsr<RiscVPerformanceCounterCsr<T, RiscVState>>(
state, csr_vec, "instret", RiscVCsrEnum::kInstret, state);
CHECK_NE(instret, nullptr);
if (std::is_same_v<T, uint32_t>) {
CHECK_NE(
CreateCsr<RiscVPerformanceCounterCsrHigh<RiscVState>>(
state, csr_vec, "instreth", RiscVCsrEnum::kInstretH, state,
reinterpret_cast<RiscVCounterCsr<uint32_t, RiscVState>*>(instret)),
nullptr);
}
// hpmcounterN + hpmcounterNh (N=3..31)
uint32_t hpmcounter_base = static_cast<uint32_t>(RiscVCsrEnum::kCycle);
uint32_t hpmcounter_base_high = static_cast<uint32_t>(RiscVCsrEnum::kCycleH);
for (int i = 0; i < kNumHardwarePerfCounters; i++) {
RiscVCounterCsr<T, RiscVState>* hpmcounter =
CreateCsr<RiscVCounterCsr<T, RiscVState>>(
state, csr_vec,
absl::StrCat("hpmcounter", i + kMinimumHardwarePerfIndex),
static_cast<RiscVCsrEnum>(hpmcounter_base + i +
kMinimumHardwarePerfIndex),
state);
CHECK_NE(hpmcounter, nullptr);
if (std::is_same_v<T, uint32_t>) {
CHECK_NE(
CreateCsr<RiscVCounterCsrHigh<RiscVState>>(
state, csr_vec,
absl::StrCat("hpmcounter", i + kMinimumHardwarePerfIndex, "h"),
static_cast<RiscVCsrEnum>(hpmcounter_base_high + i +
kMinimumHardwarePerfIndex),
state,
reinterpret_cast<RiscVCounterCsr<uint32_t, RiscVState>*>(
hpmcounter)),
nullptr);
}
}
// mhpmcounterN + mhpmcounterNh (N=3..31)
uint32_t mhpmcounter_base = static_cast<uint32_t>(RiscVCsrEnum::kMCycle);
uint32_t mhpmcounter_base_high =
static_cast<uint32_t>(RiscVCsrEnum::kMCycleH);
for (int i = 0; i < kNumHardwarePerfCounters; i++) {
RiscVCounterCsr<T, RiscVState>* mhpmcounter =
CreateCsr<RiscVCounterCsr<T, RiscVState>>(
state, csr_vec,
absl::StrCat("mhpmcounter", i + kMinimumHardwarePerfIndex),
static_cast<RiscVCsrEnum>(mhpmcounter_base + i +
kMinimumHardwarePerfIndex),
state);
CHECK_NE(mhpmcounter, nullptr);
if (std::is_same_v<T, uint32_t>) {
CHECK_NE(
CreateCsr<RiscVCounterCsrHigh<RiscVState>>(
state, csr_vec,
absl::StrCat("mhpmcounter", i + kMinimumHardwarePerfIndex, "h"),
static_cast<RiscVCsrEnum>(mhpmcounter_base_high + i +
kMinimumHardwarePerfIndex),
state,
reinterpret_cast<RiscVCounterCsr<uint32_t, RiscVState>*>(
mhpmcounter + kMinimumHardwarePerfIndex)),
nullptr);
}
}
// Hypervisor level CSRs
// henvcfg
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, csr_vec, "henvcfg",
RiscVCsrEnum::kHenvcfg, 0, state),
nullptr);
// Supervisor level CSRs
// senvcfg
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, csr_vec, "senvcfg",
RiscVCsrEnum::kSenvcfg, 0, state),
nullptr);
// scounteren
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, csr_vec, "scounteren",
RiscVCsrEnum::kSCounteren, 0, state),
nullptr);
// sstatus
CHECK_NE(CreateCsr<RiscVSStatus>(state, csr_vec, mstatus, state), nullptr);
// sip and sie are always 32 bit.
// sip - supervisor interrupt pending register.
CHECK_NE(CreateCsr(state, state->sip_, csr_vec, mip, mideleg, state),
nullptr);
// sie - supervisor interrupt enable register.
CHECK_NE(CreateCsr(state, state->sie_, csr_vec, mie, mideleg, state),
nullptr);
// stvec - supervisor trap vector register.
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, state->stvec_, csr_vec, "stvec",
RiscVCsrEnum::kSTvec, 0, state),
nullptr);
// scause - supervisor trap cause register.
CHECK_NE(
CreateCsr<RiscVSimpleCsr<T>>(state, state->scause_, csr_vec, "scause",
RiscVCsrEnum::kSCause, 0, state),
nullptr);
// sepc - supervisor exception pc register.
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, state->sepc_, csr_vec, "sepc",
RiscVCsrEnum::kSEpc, 0, state),
nullptr);
// stval
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, state->stval_, csr_vec, "stval",
RiscVCsrEnum::kSTval, 0, state),
nullptr);
// sscratch
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, csr_vec, "sscratch",
RiscVCsrEnum::kSScratch, 0, state),
nullptr);
// sideleg - machine mode interrupt delegation register.
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, state->sideleg_, csr_vec,
"sideleg", RiscVCsrEnum::kSIDeleg, 0,
CsrInfo<T>::kMIdelegRMask,
CsrInfo<T>::kMIdelegWMask, state),
nullptr);
// User level CSRs
// ustatus
CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(
state, csr_vec, "ustatus", RiscVCsrEnum::kUStatus, 0,
CsrInfo<T>::kUstatusRMask, CsrInfo<T>::kUstatusWMask, state),
nullptr);
// PMP CSRs
state->pmp_ = new RiscVPmp(state);
state->pmp_->CreatePmpCsrs<T, RiscVCsrEnum>(state->csr_set());
// Jump base vector and control register (for Zcmt instructions).
auto* jvt_csr = CreateCsr<RiscVJvtCsr<T>>(state, state->jvt_, csr_vec, "jvt",
RiscVCsrEnum::kJvt, 0, state);
CHECK_NE(jvt_csr, nullptr);
state->jvt_ = jvt_csr;
// Simulator CSRs
// Access current privilege mode.
CHECK_NE(CreateCsr<RiscVSimModeCsr>(state, csr_vec, "$mode",
RiscVCsrEnum::kSimMode, state),
nullptr);
}
// This value is in the RV32ISA manual to support MMU, although in "BARE" mode
// only the bottom 32-bit is valid.
constexpr uint64_t kRiscv32MaxMemorySize = 0x3f'ffff'ffffULL;
constexpr uint64_t kRiscv64MaxMemorySize = 0x00ff'ffff'ffff'ffffULL;
RiscVState::RiscVState(absl::string_view id, RiscVXlen xlen,
util::MemoryInterface* memory,
util::AtomicMemoryOpInterface* atomic_memory)
: ArchState(id),
xlen_(xlen),
memory_(memory),
atomic_memory_(atomic_memory),
csr_set_(new RiscVCsrSet()),
counter_interrupts_taken_("interrupts_taken", 0),
counter_interrupt_returns_("interrupt_returns", 0) {
CHECK_OK(AddCounter(&counter_interrupt_returns_));
CHECK_OK(AddCounter(&counter_interrupts_taken_));
DataBuffer* db = nullptr;
switch (xlen_) {
case RiscVXlen::RV32: {
auto* pc32 = GetRegister<RV32Register>(kPcName).first;
pc_src_operand_ = pc32->CreateSourceOperand();
pc_dst_operand_ = pc32->CreateDestinationOperand(0);
pc_ = pc32;
db = db_factory()->Allocate<RV32Register::ValueType>(1);
db->Set<uint32_t>(0, 0);
CreateCsrs<uint32_t>(this, csr_vec_);
max_physical_address_ = kRiscv32MaxMemorySize;
break;
}
case RiscVXlen::RV64: {
auto* pc64 = GetRegister<RV64Register>(kPcName).first;
pc_src_operand_ = pc64->CreateSourceOperand();
pc_dst_operand_ = pc64->CreateDestinationOperand(0);
pc_ = pc64;
db = db_factory()->Allocate<RV64Register::ValueType>(1);
db->Set<uint64_t>(0, 0);
CreateCsrs<uint64_t>(this, csr_vec_);
max_physical_address_ = kRiscv64MaxMemorySize;
break;
}
default:
LOG(ERROR) << "Unsupported xlen";
return;
}
set_pc_operand(pc_src_operand_);
pc_->SetDataBuffer(db);
db->DecRef();
// Set the flen value based on the ISA features.
// Note, the FP register class must be set appropriately as well.
auto result = csr_set()->GetCsr("misa");
if (!result.ok()) {
LOG(ERROR) << "Failed to get misa register";
return;
}
auto* misa = result.value();
auto misa_value = misa->AsUint32();
if (misa_value & *IsaExtension::kSinglePrecisionFp) {
flen_ = 32;
}
if (misa_value & *IsaExtension::kDoublePrecisionFp) {
flen_ = 64;
}
if (misa_value & *IsaExtension::kQuadPrecisionFp) {
flen_ = 128;
}
}
RiscVState::~RiscVState() {
delete pc_src_operand_;
delete pc_dst_operand_;
delete csr_set_;
delete pmp_;
for (auto* csr : csr_vec_) {
delete csr;
}
csr_vec_.clear();
}
void RiscVState::set_max_physical_address(uint64_t max_physical_address) {
switch (xlen_) {
case RiscVXlen::RV32:
max_physical_address_ =
std::min(max_physical_address, kRiscv32MaxMemorySize);
break;
case RiscVXlen::RV64:
max_physical_address_ =
std::min(max_physical_address, kRiscv64MaxMemorySize);
break;
default:
break;
}
}
void RiscVState::LoadMemory(const Instruction* inst, uint64_t address,
DataBuffer* db, Instruction* child_inst,
ReferenceCount* context) {
if (address > max_physical_address_) {
Trap(/*is_interrupt*/ false, address, *ExceptionCode::kLoadAccessFault,
inst->address(), inst);
return;
}
memory_->Load(address, db, child_inst, context);
}
void RiscVState::LoadMemory(const Instruction* inst, DataBuffer* address_db,
DataBuffer* mask_db, int el_size, DataBuffer* db,
Instruction* child_inst, ReferenceCount* context) {
for (auto address : address_db->Get<uint64_t>()) {
if (address > max_physical_address_) {
Trap(/*is_interrupt*/ false, address, *ExceptionCode::kLoadAccessFault,
inst->address(), inst);
return;
}
}
memory_->Load(address_db, mask_db, el_size, db, child_inst, context);
}
void RiscVState::StoreMemory(const Instruction* inst, uint64_t address,
DataBuffer* db) {
if (address > max_physical_address_) {
Trap(/*is_interrupt*/ false, address, *ExceptionCode::kStoreAccessFault,
inst->address(), inst);
return;
}
memory_->Store(address, db);
}
void RiscVState::StoreMemory(const Instruction* inst, DataBuffer* address_db,
DataBuffer* mask_db, int el_size, DataBuffer* db) {
for (auto address : address_db->Get<uint64_t>()) {
if (address > max_physical_address_) {
Trap(/*is_interrupt*/ false, address, *ExceptionCode::kStoreAccessFault,
inst->address(), inst);
return;
}
}
memory_->Store(address_db, mask_db, el_size, db);
}
void RiscVState::Fence(const Instruction* inst, int fm, int predecessor,
int successor) {
// TODO: Add fence operation once operations have non-zero latency.
}
void RiscVState::FenceI(const Instruction* inst) {
// TODO: Add instruction fence operation when needed.
}
void RiscVState::ECall(const Instruction* inst) {
if (on_ecall_ != nullptr) {
auto res = on_ecall_(inst);
if (res) return;
}
std::string where = (inst != nullptr)
? absl::StrCat(absl::Hex(inst->address()))
: "unknown location";
ExceptionCode code;
switch (privilege_mode()) {
case PrivilegeMode::kUser:
code = ExceptionCode::kEnvCallFromUMode;
break;
case PrivilegeMode::kSupervisor:
code = ExceptionCode::kEnvCallFromSMode;
break;
case PrivilegeMode::kMachine:
code = ExceptionCode::kEnvCallFromMMode;
break;
default:
LOG(ERROR) << "Unknown privilege mode";
return;
}
uint64_t epc = inst->address();
Trap(/*is_interrupt*/ false, 0, *code, epc, inst);
}
void RiscVState::EBreak(const Instruction* inst) {
for (auto& handler : on_ebreak_) {
bool res = handler(inst);
if (res) return;
}
// Set the return address to the current instruction.
auto epc = (inst != nullptr) ? inst->address() : 0;
Trap(/*is_interrupt=*/false, 0, 3, epc, inst);
}
void RiscVState::WFI(const Instruction* inst) {
if (on_wfi_ != nullptr) {
bool res = on_wfi_(inst);
if (res) return;
}
std::string where = (inst != nullptr)
? absl::StrCat(absl::Hex(inst->address()))
: "unknown location";
LOG(INFO) << "No handler for wfi: treating as nop: " << where;
}
void RiscVState::Cease(const Instruction* inst) {
if (on_cease_ != nullptr) {
const bool res = on_cease_(inst);
if (res) return;
}
// If no handler is specified, then CEASE is treated as an infinite loop.
auto current_xlen = xlen();
auto* db = pc_dst_operand_->AllocateDataBuffer();
if (current_xlen == RiscVXlen::RV32) {
db->SetSubmit<uint32_t>(0, static_cast<uint32_t>(inst->address()));
set_branch(true);
} else if (current_xlen == RiscVXlen::RV64) {
db->SetSubmit<uint64_t>(0, inst->address());
set_branch(true);
} else {
LOG(ERROR) << "Unknown xlen";
}
const std::string where = (inst != nullptr)
? absl::StrCat(absl::Hex(inst->address()))
: "unknown location";
LOG(INFO) << "No handler for cease: treating as an infinite loop: " << where;
}
void RiscVState::Trap(bool is_interrupt, uint64_t trap_value,
uint64_t exception_code, uint64_t epc,
const Instruction* inst) {
if (!is_interrupt) {
auto minstret_res =
csr_set()->GetCsr(static_cast<uint64_t>(RiscVCsrEnum::kMInstret));
if (minstret_res.ok()) {
// If an exception causes a trap, the instruction did not retire.
// The minstret counter is implemented using RiscVPerformanceCounterCsr
// which increments its value as part of instruction processing before it
// is known if it will cause a trap. The write below corrects for this
// by decrementing the minstret value by 1. This is a side-effect of
// RiscVPerformanceCounterCsr::Set().
(*minstret_res)->Set((*minstret_res)->AsUint64());
}
}
if (on_trap_ != nullptr) {
bool res = on_trap_(is_interrupt, trap_value, exception_code, epc, inst);
if (res) return;
}
// Default behavior.
// Determine where the interrupt should be taken. Default is machine mode,
// but if the interrupt/exception is delegated, it may be taken in supervisor
// mode (or if supervisor mode delegates it, in user mode). Traps cannot be
// delegated lower than the current level of execution. E.g., a trap in
// machine mode, cannot be delegated to supervisor mode. Additionally, an
// interrupt that is delegated, is masked at the level of the delegator, i.e.,
// an interrupt delegated to supervisor mode will never be taken while
// executing machine mode code. This, however, is handled in the
// CheckForInterrupt method.
PrivilegeMode destination_mode = PrivilegeMode::kMachine;
// Determine the destination execution mode.
if (is_interrupt) {
if (mideleg_->AsUint32() & (1U << exception_code)) {
destination_mode = PrivilegeMode::kSupervisor;
if (sideleg_->AsUint32() & (1U << exception_code)) {
LOG(ERROR)
<< "Full support for user mode interrupts not implemented yet";
destination_mode = PrivilegeMode::kUser;
}
}
} else {
if ((privilege_mode() != PrivilegeMode::kMachine) &&
(medeleg_->AsUint64() & (1ULL << exception_code))) {
destination_mode = PrivilegeMode::kSupervisor;
// There is no support for user level exceptions.
}
}
// Based on the destination privilege mode, select the CSRs that will be
// used.
RiscVCsrInterface* epc_csr = nullptr;
RiscVCsrInterface* cause_csr = nullptr;
RiscVCsrInterface* tvec_csr = nullptr;
RiscVCsrInterface* tval_csr = nullptr;
if (destination_mode == PrivilegeMode::kMachine) {
epc_csr = mepc_;
cause_csr = mcause_;
tvec_csr = mtvec_;
tval_csr = mtval_;
} else if (destination_mode == PrivilegeMode::kSupervisor) {
epc_csr = sepc_;
cause_csr = scause_;
tvec_csr = stvec_;
tval_csr = stval_;
} else {
LOG(ERROR) << "Invalid destination execution mode";
return;
}
// Get trap destination.
int trap_vector_mode = tvec_csr->AsUint64() & 0x3ULL;
uint64_t trap_target = tvec_csr->AsUint64() & ~0x3ULL;
if (trap_vector_mode == 1) {
trap_target += 4 * exception_code;
}
// Set xepc.
epc_csr->Set(epc);
// Set xtval.
tval_csr->Set(trap_value);
// Set xcause.
cause_csr->Set(exception_code);
auto current_xlen = xlen();
if (is_interrupt) {
if (current_xlen == RiscVXlen::RV32) {
cause_csr->SetBits(static_cast<uint32_t>(0x8000'0000));
} else if (current_xlen == RiscVXlen::RV64) {
cause_csr->SetBits(static_cast<uint64_t>(0x8000'0000'0000'0000ULL));
} else {
LOG(ERROR) << "Unknown xlen";
}
}
// Set mstatus bits accordingly.
if (destination_mode == PrivilegeMode::kMachine) {
// Set the privilege mode to return to after the interrupt.
mstatus_->set_mpp(*(privilege_mode()));
// Save the current interrupt enable to mpie.
mstatus_->set_mpie(mstatus_->mie());
// Disable further interrupts.
mstatus_->set_mie(0);
} else if (destination_mode == PrivilegeMode::kSupervisor) {
// Set the privilege mode to return to after the interrupt.
mstatus_->set_spp(*privilege_mode() & 0x1);
// Save the current interrupt enable to mpie.
mstatus_->set_spie(mstatus_->sie());
// Disable further interrupts.
mstatus_->set_sie(0);
}
// Advance data buffer delay line until empty. Flush pending writes to
// register and possibly pc.
while (!data_buffer_delay_line()->IsEmpty()) {
data_buffer_delay_line()->Advance();
}
// Update the PC.
auto* db = pc_dst_operand_->AllocateDataBuffer();
if (current_xlen == RiscVXlen::RV32) {
db->SetSubmit<uint32_t>(0, static_cast<uint32_t>(trap_target));
set_branch(true);
} else if (current_xlen == RiscVXlen::RV64) {
db->SetSubmit<uint64_t>(0, trap_target);
set_branch(true);
} else {
LOG(ERROR) << "Unknown xlen";
}
set_privilege_mode(destination_mode);
mstatus_->Submit();
}
// CheckForInterrupt is called whenever any relevant bits in the interrupt
// enable and set bits are changed. It should always be scheduled to execute
// from the function_delay_line, that way it is executed after an instruction
// has completed execution.
void RiscVState::CheckForInterrupt() {
// Compute interrupts enabled at each level using [ms]ideleg.
uint32_t interrupts = mip_->AsUint32() & mie_->AsUint32();
if (interrupts == 0) return;
uint32_t m_interrupts = interrupts & ~mideleg_->AsUint32();
interrupts = interrupts & mideleg_->AsUint32();
uint32_t s_interrupts = interrupts & ~sideleg_->AsUint32();
uint32_t u_interrupts = interrupts & sideleg_->AsUint32();
auto priv_mode = privilege_mode();
// Interrupt at level L is enabled if current priv level is < L, or if
// mstatus->Lie is set. If priv level > L, then enable is off.
bool mie = (priv_mode != PrivilegeMode::kMachine) || mstatus_->mie();
bool sie = ((*priv_mode < *PrivilegeMode::kSupervisor) || mstatus_->sie()) &&
(priv_mode != PrivilegeMode::kMachine);
bool uie = mstatus_->uie() && (priv_mode == PrivilegeMode::kUser);
// No interrupts can be taken.
if (!(mie || sie || uie)) return;
InterruptCode code;
if (mie && (m_interrupts != 0)) {
// Take an interrupt to machine mode.
code = PickInterrupt(m_interrupts);
} else if (sie && (s_interrupts != 0)) {
// Take an interrupt to supervisor mode.
code = PickInterrupt(s_interrupts);
} else if (uie && (u_interrupts != 0)) {
// Take an interrupt to user mode.
code = PickInterrupt(u_interrupts);
LOG(ERROR) << "User mode interrupts not supported yet";
return;
} else {
// No eligible interrupts to take.
return;
}
available_interrupt_code_ = code;
is_interrupt_available_ = true;
}
// Take the interrupt that is pending.
void RiscVState::TakeAvailableInterrupt(uint64_t epc) {
// Make sure an interrupt is set as pending by CheckForInterrupt.
if (!is_interrupt_available_) return;
// Initiate the interrupt.
Trap(/*is_interrupt*/ true, 0, *available_interrupt_code_, epc, nullptr);
// Clear pending interrupt.
is_interrupt_available_ = false;
counter_interrupts_taken_.Increment(1);
available_interrupt_code_ = InterruptCode::kNone;
}
// The priority order of the interrupts are as follows:
// mei, msi, mti, sei, ssi, sti, uei, usi, uti.
InterruptCode RiscVState::PickInterrupt(uint32_t interrupts) {
if (interrupts & (1 << *InterruptCode::kMachineExternalInterrupt))
return InterruptCode::kMachineExternalInterrupt;
if (interrupts & (1 << *InterruptCode::kMachineSoftwareInterrupt))
return InterruptCode::kMachineSoftwareInterrupt;
if (interrupts & (1 << *InterruptCode::kMachineTimerInterrupt))
return InterruptCode::kMachineTimerInterrupt;
if (interrupts & (1 << *InterruptCode::kSupervisorExternalInterrupt))
return InterruptCode::kSupervisorExternalInterrupt;
if (interrupts & (1 << *InterruptCode::kSupervisorSoftwareInterrupt))
return InterruptCode::kSupervisorSoftwareInterrupt;
if (interrupts & (1 << *InterruptCode::kSupervisorTimerInterrupt))
return InterruptCode::kSupervisorTimerInterrupt;
if (interrupts & (1 << *InterruptCode::kUserExternalInterrupt))
return InterruptCode::kUserExternalInterrupt;
if (interrupts & (1 << *InterruptCode::kUserSoftwareInterrupt))
return InterruptCode::kUserSoftwareInterrupt;
if (interrupts & (1 << *InterruptCode::kUserTimerInterrupt))
return InterruptCode::kUserTimerInterrupt;
return InterruptCode::kNone;
}
} // namespace riscv
} // namespace sim
} // namespace mpact