1281 lines
46 KiB
C++
1281 lines
46 KiB
C++
#ifndef LIBCPU_RISCV_PRIVILEGE_MODULE_HH
|
|
#define LIBCPU_RISCV_PRIVILEGE_MODULE_HH
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <climits>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <libcpu/memory.hh>
|
|
#include <libcpu/riscv/riscv.hh>
|
|
#include <libcpu/riscv/user_core.hh>
|
|
#include <libvio/bus.hh>
|
|
#include <libanemo/width.hh>
|
|
#include <optional>
|
|
#include <type_traits>
|
|
|
|
namespace libcpu::riscv {
|
|
|
|
/**
|
|
* @brief RISC-V Privilege Module
|
|
*
|
|
* This class implements the RISC-V privilege architecture as defined in the
|
|
* RISC-V Privileged Specification. It manages:
|
|
* - Privilege levels (User, Supervisor, Machine)
|
|
* - Control/Status Registers (CSRs)
|
|
* - Address translation (via SATP register)
|
|
* - Exception and interrupt handling
|
|
* - Privilege transitions
|
|
*
|
|
* The module works in conjunction with the user core to handle all privileged
|
|
* operations while maintaining separation of concerns.
|
|
*
|
|
* @tparam WORD_T The word type for the architecture (uint32_t for RV32, uint64_t for RV64)
|
|
*
|
|
* @note All CSR accesses and privilege transitions must go through this module
|
|
* @note Memory operations are delegated to this module
|
|
* @see riscv_user_core for the unprivileged counterpart
|
|
*/
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2=12>
|
|
class privilege_module {
|
|
public:
|
|
using dispatch_t = riscv::dispatch_t;
|
|
using decode_t = riscv::decode_t;
|
|
using exec_result_type_t = riscv::exec_result_type_t;
|
|
using exec_result_t = riscv::exec_result_t<WORD_T>;
|
|
using satp_mode_t = riscv::satp_mode_t;
|
|
|
|
using tlb_entry_t = struct {
|
|
uint64_t ppn; ///< guest physical page number
|
|
WORD_T vpn; ///< guest virtual page number
|
|
}; ///< a virtual tlb entry translating guest virtual page number to host address
|
|
|
|
using address_translation_t = struct {
|
|
uint64_t paddr;
|
|
bool access_fault;
|
|
bool page_fault;
|
|
};
|
|
|
|
priv_level_t priv_level;
|
|
WORD_T mepc, mtvec, mcause, mtval, mscratch, mie, mip, medeleg, mideleg;
|
|
WORD_T sepc, stvec, scause, stval, sscratch, sie, sip;
|
|
|
|
memory_view *instr_bus;
|
|
memory_view *data_bus;
|
|
libvio::io_agent *mmio_bus;
|
|
|
|
struct {
|
|
priv_level_t mpp;
|
|
bool mxr; bool sum; bool spp; bool mpie; bool spie; bool mie; bool sie;
|
|
} status;
|
|
|
|
struct {
|
|
satp_mode_t mode;
|
|
uint16_t asid;
|
|
WORD_T ppn;
|
|
} satp;
|
|
|
|
tlb_entry_t tlb_r[1<<TLB_SIZE_LOG2]; ///< virtual tlb for load
|
|
tlb_entry_t tlb_w[1<<TLB_SIZE_LOG2]; ///< virtual tlb for store
|
|
tlb_entry_t tlb_x[1<<TLB_SIZE_LOG2]; ///< virtual tlb for instruction fetching
|
|
|
|
/**
|
|
* @brief Physical address of the last load-reserved, with a reservation flag in the low 3 bits.
|
|
*
|
|
* If (reserved_set & 0x7) == 0, no address is reserved.
|
|
* Otherwise, the 64-bit data at (reserved_set & ~0x7) is reserved.
|
|
*/
|
|
WORD_T reserved_set = 0;
|
|
|
|
/**
|
|
* @brief Translate virtual address to physical address
|
|
*
|
|
* Performs address translation based on current privilege level and SATP register.
|
|
* This function returns a 64-bit address regardless of WORD_T since the physical address
|
|
* in sv32 is also wider than 32 bits.
|
|
*
|
|
* This function is used for debugging. It ignores the page permission and do not modify the PTE.
|
|
*
|
|
* @param vaddr Virtual address to translate
|
|
* @return Physical address if translation succeeds, empty if fails
|
|
*/
|
|
std::optional<uint64_t> vaddr_to_paddr(WORD_T vaddr) const;
|
|
|
|
/**
|
|
* @brief Fetch instruction using physical address
|
|
*
|
|
* This function is designed for simulating processors without virtual memory.
|
|
* `op` will be populated with the `fetch` variant if successful, or `trap` if not.
|
|
*
|
|
* @param op Execution result structure to populate with fetch details
|
|
*/
|
|
void paddr_fetch_instruction(exec_result_t &op) const;
|
|
|
|
/**
|
|
* @brief Fetch instruction using virtual address
|
|
*
|
|
* Handles instruction fetch with virtual address translation.
|
|
* `op` will be populated with the `fetch` variant if successful, or `trap` if not.
|
|
*
|
|
* @param op Execution result structure to populate with fetch details
|
|
*/
|
|
void vaddr_fetch_instruction(exec_result_t &op);
|
|
|
|
/**
|
|
* @brief Perform physical address load operation
|
|
*
|
|
* This function is designed for simulating processors without virtual memory.
|
|
* `op` will be populated with the `retire` variant if successful, or the `trap` variant if not.
|
|
*
|
|
* @param op Execution result structure containing load details
|
|
*/
|
|
void paddr_load(exec_result_t &op);
|
|
|
|
/**
|
|
* @brief Perform physical address store operation
|
|
*
|
|
* This function is designed for simulating processors without virtual memory.
|
|
* `op` will be populated with the `retire` variant if successful, or the `trap` variant if not.
|
|
*
|
|
* @param op Execution result structure containing store details
|
|
*/
|
|
void paddr_store(exec_result_t &op);
|
|
|
|
/**
|
|
* @brief Perform virtual address load operation
|
|
*
|
|
* Handles memory load operations using virtual addresses, including full address translation
|
|
* and privilege checks according to the current memory management configuration.
|
|
* `op` will be populated with the `retire` variant if successful, or the `trap` variant
|
|
* if translation fails or access privileges are violated.
|
|
*
|
|
* @param op Execution result structure containing load details
|
|
*/
|
|
void vaddr_load(exec_result_t &op);
|
|
|
|
/**
|
|
* @brief Perform virtual address store operation
|
|
*
|
|
* Handles memory store operations using virtual addresses, including full address translation
|
|
* and privilege checks according to the current memory management configuration.
|
|
* `op` will be populated with the `retire` variant if successful, or the `trap` variant
|
|
* if translation fails or access privileges are violated.
|
|
*
|
|
* @param op Execution result structure containing store details
|
|
*/
|
|
void vaddr_store(exec_result_t &op);
|
|
|
|
WORD_T amo_op(amo_type_t type, libanemo::width_t width, WORD_T rs_value, WORD_T load_value) const;
|
|
|
|
void paddr_amo(exec_result_t &op);
|
|
|
|
void vaddr_amo(exec_result_t &op);
|
|
|
|
/**
|
|
* @brief Reset privilege module
|
|
*
|
|
* Initializes all privilege-related registers and state to their reset values.
|
|
*/
|
|
void reset(void);
|
|
|
|
/**
|
|
* @brief Raise an interrupt
|
|
*
|
|
* Records interrupt cause and updates interrupt pending bits.
|
|
*
|
|
* @param cause Interrupt cause code
|
|
*/
|
|
void raise_interrupt(WORD_T cause);
|
|
|
|
/**
|
|
* @brief Handle exception
|
|
*
|
|
* Processes exceptions by updating CSRs and potentially changing privilege level.
|
|
* `op` will be populated with the `retire` varient, and `op.next_pc` will be changed
|
|
* if there is an exception to handle.
|
|
*
|
|
* @param op Execution result structure containing exception details
|
|
*/
|
|
void handle_exception(exec_result_t &op);
|
|
|
|
/**
|
|
* @brief Handle interrupt
|
|
*
|
|
* Processes interrupts by updating CSRs and potentially changing privilege level.
|
|
* `op` will be populated with the `retire` varient, and `op.next_pc` will be changed
|
|
* if there is an interrupt to handle.
|
|
*
|
|
* @param op Execution result structure containing interrupt details
|
|
*/
|
|
void handle_interrupt(exec_result_t &op);
|
|
|
|
/**
|
|
* @brief Perform CSR operation
|
|
*
|
|
* Executes CSR read/write/set/clear operations with proper privilege checks.
|
|
* `op` will be populated with the `retire` varient if successful, or the `trap` varient if not.
|
|
*
|
|
* @param op Execution result structure containing CSR operation details
|
|
*/
|
|
void csr_op(exec_result_t &op);
|
|
|
|
/**
|
|
* @brief Handle ecall, mret, sret instructions
|
|
*
|
|
* @param op Execution result structure containing execution details
|
|
*/
|
|
void sys_op(exec_result_t &op);
|
|
|
|
/**
|
|
* @brief get the physical address of a leaf page table entry
|
|
*
|
|
* @param vaddr the virtual address
|
|
* @param read whether to load from the virtual address
|
|
* @param write whether to store the virtual address
|
|
* @param exec whether to fetch instruction from from the virtual address
|
|
* @return the physical address
|
|
*/
|
|
address_translation_t page_walk(WORD_T vaddr, bool read, bool write, bool exec);
|
|
|
|
/**
|
|
* @brief invalidate the entire TLB
|
|
*/
|
|
void flush_tlb(void);
|
|
};
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::reset(void) {
|
|
priv_level = priv_level_t::m;
|
|
mepc = 0; sepc = 0;
|
|
mtvec = 0; stvec = 0;
|
|
mcause = 0; scause = 0;
|
|
mtval = 0; stval = 0;
|
|
mscratch = 0; sscratch = 0;
|
|
medeleg = 0; mideleg = 0;
|
|
mie = 0; sie = 0;
|
|
mip = 0; sip = 0;
|
|
status = {
|
|
.mpp=priv_level_t::m, .spp=false,
|
|
.mpie=false, .spie=false,
|
|
.mie=false, .sie=false,
|
|
};
|
|
satp = {
|
|
.mode=satp_mode_t::bare,
|
|
.asid=0, .ppn=0,
|
|
};
|
|
reserved_set = 0;
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
std::optional<uint64_t> privilege_module<WORD_T, TLB_SIZE_LOG2>::vaddr_to_paddr(WORD_T vaddr) const {
|
|
if (priv_level==priv_level_t::m) {
|
|
return vaddr;
|
|
}
|
|
int n_levels;
|
|
int vaddr_size;
|
|
int paddr_size;
|
|
int top_ppn_size;
|
|
// Size of one level virtual page number and non-root level physical page number
|
|
constexpr int vpn_size = sizeof(WORD_T)*CHAR_BIT==32 ? 10 : 9;
|
|
// Mask of one level virtual page number
|
|
constexpr WORD_T vpn_mask = libanemo::bit_mask<WORD_T>(vpn_size, 0);
|
|
switch (satp.mode) {
|
|
case satp_mode_t::bare: {
|
|
return vaddr;
|
|
break;
|
|
}
|
|
case satp_mode_t::sv32: {
|
|
assert(sizeof(WORD_T)*CHAR_BIT == 32);
|
|
n_levels = 2;
|
|
vaddr_size = 32;
|
|
paddr_size = 34;
|
|
top_ppn_size = 12;
|
|
break;
|
|
}
|
|
case satp_mode_t::sv39: {
|
|
assert(sizeof(WORD_T)*CHAR_BIT == 64);
|
|
n_levels = 3;
|
|
vaddr_size = 39;
|
|
paddr_size = 56;
|
|
top_ppn_size = 26;
|
|
break;
|
|
}
|
|
case satp_mode_t::sv48: {
|
|
assert(sizeof(WORD_T)*CHAR_BIT == 64);
|
|
n_levels = 4;
|
|
vaddr_size = 48;
|
|
paddr_size = 56;
|
|
top_ppn_size = 17;
|
|
break;
|
|
}
|
|
case satp_mode_t::sv57: {
|
|
assert(sizeof(WORD_T)*CHAR_BIT == 64);
|
|
n_levels = 5;
|
|
vaddr_size = 57;
|
|
paddr_size = 56;
|
|
top_ppn_size = 8;
|
|
break;
|
|
}
|
|
case satp_mode_t::sv64: {
|
|
// This is not yet implemented
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
// This avoids silly mistakes
|
|
// PTE size here mean PTE size without the top reserved bits
|
|
int pte_size = (top_ppn_size+vpn_size*(n_levels-1)+10);
|
|
assert(vpn_size*n_levels+12 == vaddr_size);
|
|
assert(top_ppn_size+vpn_size*(n_levels-1)+12 == paddr_size);
|
|
assert(sizeof(WORD_T)*CHAR_BIT==32&&pte_size==32 || sizeof(WORD_T)*CHAR_BIT==64&&pte_size==54);
|
|
// check whether the virtual address is sign extended
|
|
if constexpr (sizeof(WORD_T)*CHAR_BIT == 64) {
|
|
if (vaddr&libanemo::bit_mask<WORD_T>(vaddr_size+1, vaddr_size)) {
|
|
if (vaddr>>vaddr_size != libanemo::bit_mask<WORD_T>(64-vaddr_size, 0)) {
|
|
return std::nullopt;
|
|
}
|
|
} else {
|
|
if (vaddr>>vaddr_size != 0) {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
}
|
|
WORD_T ppn = satp.ppn;
|
|
for (int i=n_levels-1; i>=0; --i) {
|
|
// construct the address of the current level page table
|
|
WORD_T vpn = vaddr>>(i*vpn_size+12) & vpn_mask;
|
|
WORD_T pte_paddr = (ppn<<12) | (vpn<<(12-vpn_size));
|
|
std::optional<WORD_T> pte_opt = data_bus->read(pte_paddr, libanemo::int_type_to_width_t<WORD_T>());
|
|
if (!pte_opt.has_value()) {
|
|
// the physical addressof the PTE is not accessible
|
|
return std::nullopt;
|
|
}
|
|
WORD_T pte = pte_opt.value();
|
|
bool pte_v = pte & pte_mask<WORD_T>::v;
|
|
bool pte_r = pte & pte_mask<WORD_T>::r;
|
|
bool pte_w = pte & pte_mask<WORD_T>::w;
|
|
bool pte_x = pte & pte_mask<WORD_T>::x;
|
|
bool pte_u = pte & pte_mask<WORD_T>::u;
|
|
bool pte_a = pte & pte_mask<WORD_T>::a;
|
|
bool pte_d = pte & pte_mask<WORD_T>::d;
|
|
// Check the V bit and whether the permission fields are invalid
|
|
if (!pte_v || !pte_r&&pte_w) {
|
|
return std::nullopt;
|
|
}
|
|
// A, U and D fields of a non-leaf PTE is reserved
|
|
if (!(pte_r||pte_x) && (pte_u||pte_a||pte_d)) {
|
|
return std::nullopt;
|
|
}
|
|
// Reserved bits of the PTE should be zero
|
|
if constexpr (sizeof(WORD_T)*CHAR_BIT != 32) {
|
|
if (pte>>pte_size != 0) {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
// Now the PTE is valid, get the current level of PPN from it
|
|
ppn = pte >> 10;
|
|
if (pte_r || pte_x) {
|
|
// PTE is leaf
|
|
// Check if the large page is aligned
|
|
bool large_page_aligned = true;
|
|
for (int j=i-1; j>=0; --j) {
|
|
if (((ppn>>(j*vpn_size))&vpn_mask) != 0) {
|
|
large_page_aligned = false;
|
|
}
|
|
}
|
|
if (! large_page_aligned) {
|
|
return std::nullopt;
|
|
}
|
|
return (ppn<<12) | (vaddr&libanemo::bit_mask<WORD_T>(i*vpn_size+12, 0));
|
|
} else if (i == 0) {
|
|
// Level 0 page table must be a leaf
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
// Should not reach here
|
|
assert(false);
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::paddr_fetch_instruction(exec_result_t &op) const {
|
|
assert(op.type==exec_result_type_t::retire || op.type==exec_result_type_t::trap);
|
|
WORD_T paddr = op.pc;
|
|
std::optional<uint32_t> instr_opt = instr_bus->read(paddr, libanemo::width_t::word);
|
|
if (instr_opt.has_value()) {
|
|
op.type = exec_result_type_t::fetch;
|
|
op.instr = instr_opt.value();
|
|
} else {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {
|
|
.cause = riscv::mcause<WORD_T>::except_instr_fault,
|
|
.tval = paddr,
|
|
};
|
|
}
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::vaddr_fetch_instruction(exec_result_t &op) {
|
|
assert(op.type==exec_result_type_t::retire || op.type==exec_result_type_t::trap);
|
|
WORD_T vaddr = op.pc;
|
|
WORD_T vpn = vaddr >> 12;
|
|
size_t tlb_index = vpn & libanemo::bit_mask<WORD_T>(TLB_SIZE_LOG2, 0);
|
|
tlb_entry_t tlb_entry = tlb_x[tlb_index];
|
|
uint64_t paddr;
|
|
if (tlb_entry.vpn != vpn) {
|
|
address_translation_t translation = page_walk(vaddr, false, false, true);
|
|
if (translation.access_fault) {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {
|
|
.cause = riscv::mcause<WORD_T>::except_instr_fault,
|
|
.tval = vaddr,
|
|
};
|
|
return;
|
|
} else if (translation.page_fault) {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {
|
|
.cause = riscv::mcause<WORD_T>::except_instr_page_fault,
|
|
.tval = vaddr,
|
|
};
|
|
return;
|
|
}
|
|
// now address translation successful
|
|
paddr = translation.paddr;
|
|
// now physical address is valid, update TLB
|
|
tlb_entry.vpn = vpn;
|
|
tlb_entry.ppn = paddr >> 12;
|
|
tlb_x[tlb_index] = tlb_entry;
|
|
} else {
|
|
paddr = (tlb_entry.ppn<<12) | (vaddr&libanemo::bit_mask<WORD_T>(12, 0));
|
|
}
|
|
// instruction fetch uses the instruction bus, not the data bus;
|
|
// no MMIO fallback for instruction fetch
|
|
std::optional<uint64_t> instr_opt = instr_bus->read(paddr, libanemo::width_t::word);
|
|
if (instr_opt.has_value()) {
|
|
op.type = exec_result_type_t::fetch;
|
|
op.instr = instr_opt.value();
|
|
} else {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {
|
|
.cause = riscv::mcause<WORD_T>::except_instr_fault,
|
|
.tval = vaddr,
|
|
};
|
|
}
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::paddr_load(exec_result_t &op) {
|
|
assert(op.type == exec_result_type_t::load);
|
|
auto [paddr, width, sign_extend, rd, reserved] = op.load;
|
|
std::optional<uint64_t> data_opt = data_bus->read(paddr, width);
|
|
// fall back to MMIO if the address is out of RAM
|
|
if (!data_opt.has_value() && mmio_bus!=nullptr) {
|
|
data_opt = mmio_bus->read(paddr, width);
|
|
}
|
|
if (data_opt.has_value()) {
|
|
// `data` is zero extended
|
|
WORD_T data = data_opt.value();
|
|
if (sign_extend) {
|
|
data = libanemo::sign_extend<WORD_T>(data, width);
|
|
}
|
|
if (reserved) {
|
|
reserved_set = paddr | 0x7;
|
|
}
|
|
op.type = exec_result_type_t::retire;
|
|
op.retire = {
|
|
.rd=rd, .value=data,
|
|
};
|
|
} else {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {
|
|
.cause = riscv::mcause<WORD_T>::except_load_fault,
|
|
.tval = paddr,
|
|
};
|
|
}
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::paddr_store(exec_result_t &op) {
|
|
assert(op.type == exec_result_type_t::store);
|
|
auto [paddr, width, data, rd, conditional] = op.store;
|
|
bool reservation_match = (reserved_set&0x7)!=0 && (reserved_set|0x7)==(paddr|0x7);
|
|
// The reservation will always be invalidated if it matches or this is sc
|
|
// - If there is no memory access trap, the memory access invalidates it.
|
|
// - If there is a trap, the trap invalidates it
|
|
if (reservation_match || conditional) {
|
|
reserved_set = 0;
|
|
}
|
|
// If store conditional does not have a matched reservation, skip memory access
|
|
if (conditional && !reservation_match) {
|
|
op.type = exec_result_type_t::retire;
|
|
op.retire = {.rd=rd, .value=1};
|
|
return;
|
|
}
|
|
bool success = data_bus->write(paddr, width, data);
|
|
// fall back to MMIO
|
|
if (!success && mmio_bus!=nullptr) {
|
|
success = mmio_bus->write(paddr, width, data);
|
|
}
|
|
if (success) {
|
|
op.type = exec_result_type_t::retire;
|
|
op.retire = {
|
|
.rd=rd, .value=0,
|
|
};
|
|
} else {
|
|
// both RAM and MMIO failed
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {
|
|
.cause = riscv::mcause<WORD_T>::except_store_fault,
|
|
.tval = paddr,
|
|
};
|
|
}
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::vaddr_load(exec_result_t &op) {
|
|
assert(op.type == exec_result_type_t::load);
|
|
auto [vaddr, width, sign_extend, rd, reserved] = op.load;
|
|
WORD_T vpn = vaddr >> 12;
|
|
size_t tlb_index = vpn & libanemo::bit_mask<WORD_T>(TLB_SIZE_LOG2, 0);
|
|
tlb_entry_t tlb_entry = tlb_r[tlb_index];
|
|
uint64_t paddr;
|
|
if (tlb_entry.vpn != vpn) {
|
|
address_translation_t translation = page_walk(vaddr, true, false, false);
|
|
if (translation.access_fault) {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {
|
|
.cause = riscv::mcause<WORD_T>::except_load_fault,
|
|
.tval = vaddr,
|
|
};
|
|
return;
|
|
} else if (translation.page_fault) {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {
|
|
.cause = riscv::mcause<WORD_T>::except_load_page_fault,
|
|
.tval = vaddr,
|
|
};
|
|
return;
|
|
}
|
|
// now address translation successful
|
|
paddr = translation.paddr;
|
|
// now physical address is valid, update TLB
|
|
tlb_entry.vpn = vpn;
|
|
tlb_entry.ppn = paddr >> 12;
|
|
tlb_r[tlb_index] = tlb_entry;
|
|
} else {
|
|
paddr = (tlb_entry.ppn<<12) | (vaddr&libanemo::bit_mask<WORD_T>(12, 0));
|
|
}
|
|
std::optional<uint64_t> data_opt = data_bus->read(paddr, width);
|
|
// fall back to MMIO if the address is out of RAM
|
|
if (!data_opt.has_value() && mmio_bus!=nullptr) {
|
|
data_opt = mmio_bus->read(paddr, width);
|
|
}
|
|
if (data_opt.has_value()) {
|
|
// `data` is zero extended
|
|
WORD_T data = data_opt.value();
|
|
if (sign_extend) {
|
|
data = libanemo::sign_extend<WORD_T>(data, width);
|
|
}
|
|
if (reserved) {
|
|
reserved_set = paddr | 0x7;
|
|
}
|
|
op.type = exec_result_type_t::retire;
|
|
op.retire = {
|
|
.rd=rd, .value=data,
|
|
};
|
|
} else {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {
|
|
.cause = riscv::mcause<WORD_T>::except_load_fault,
|
|
.tval = vaddr,
|
|
};
|
|
}
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::vaddr_store(exec_result_t &op) {
|
|
assert(op.type == exec_result_type_t::store);
|
|
auto [vaddr, width, value, rd, conditional] = op.store;
|
|
WORD_T vpn = vaddr >> 12;
|
|
size_t tlb_index = vpn & libanemo::bit_mask<WORD_T>(TLB_SIZE_LOG2, 0);
|
|
tlb_entry_t tlb_entry = tlb_w[tlb_index];
|
|
uint64_t paddr;
|
|
if (tlb_entry.vpn != vpn) {
|
|
address_translation_t translation = page_walk(vaddr, false, true, false);
|
|
if (translation.access_fault) {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {
|
|
.cause = riscv::mcause<WORD_T>::except_store_fault,
|
|
.tval = vaddr,
|
|
};
|
|
return;
|
|
} else if (translation.page_fault) {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {
|
|
.cause = riscv::mcause<WORD_T>::except_store_page_fault,
|
|
.tval = vaddr,
|
|
};
|
|
return;
|
|
}
|
|
// now address translation successful
|
|
paddr = translation.paddr;
|
|
// now physical address is valid, update TLB
|
|
tlb_entry.vpn = vpn;
|
|
tlb_entry.ppn = paddr >> 12;
|
|
tlb_w[tlb_index] = tlb_entry;
|
|
} else {
|
|
paddr = (tlb_entry.ppn<<12) | (vaddr&libanemo::bit_mask<WORD_T>(12, 0));
|
|
}
|
|
bool reservation_match = (reserved_set&0x7)!=0 && (reserved_set|0x7)==(paddr|0x7);
|
|
// The reservation will always be invalidated if it matches or this is sc
|
|
// - If there is no memory access trap, the memory access invalidates it.
|
|
// - If there is a trap, the trap invalidates it
|
|
if (reservation_match || conditional) {
|
|
reserved_set = 0;
|
|
}
|
|
// If store conditional does not have a matched reservation, skip memory access
|
|
if (conditional && !reservation_match) {
|
|
op.type = exec_result_type_t::retire;
|
|
op.retire = {.rd=rd, .value=1};
|
|
return;
|
|
}
|
|
bool success = data_bus->write(paddr, width, value);
|
|
// fall back to MMIO if the address is out of RAM
|
|
if (!success && mmio_bus!=nullptr) {
|
|
success = mmio_bus->write(paddr, width, value);
|
|
}
|
|
if (success) {
|
|
op.type = exec_result_type_t::retire;
|
|
op.retire = {.rd=rd, .value=0};
|
|
} else {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {
|
|
.cause = riscv::mcause<WORD_T>::except_store_fault,
|
|
.tval = vaddr,
|
|
};
|
|
}
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::raise_interrupt(WORD_T cause) {
|
|
WORD_T cause_mask = 1 << (cause & ~riscv::mcause<WORD_T>::intr_mask);
|
|
if (mideleg & cause_mask) {
|
|
sip |= cause_mask;
|
|
} else {
|
|
mip |= cause_mask;
|
|
}
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::handle_interrupt(exec_result_t &op) {
|
|
constexpr size_t word_size = sizeof(WORD_T) * CHAR_BIT;
|
|
|
|
assert(op.type == exec_result_type_t::retire);
|
|
WORD_T pc = op.next_pc;
|
|
priv_level_t target_priv_level;
|
|
WORD_T cause;
|
|
|
|
if ((mie&mip) && (status.mie||priv_level!=priv_level_t::m)) {
|
|
for (size_t i=0; i<word_size; ++i) {
|
|
if (((mie&mip)>>i)&1) {
|
|
target_priv_level = priv_level_t::m;
|
|
cause = i;
|
|
break;
|
|
}
|
|
}
|
|
} else if ((sie&sip) && priv_level!=priv_level_t::m && (status.sie||priv_level==priv_level_t::u)) {
|
|
for (size_t i=0; i<word_size; ++i) {
|
|
if (((sie&sip)>>i)&1) {
|
|
target_priv_level = priv_level_t::s;
|
|
cause = i;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
WORD_T vector_base;
|
|
bool is_vectord;
|
|
if (target_priv_level == priv_level_t::m) {
|
|
vector_base = mtvec & ~riscv::mtvec<WORD_T>::vectored;
|
|
is_vectord = mtvec & riscv::mtvec<WORD_T>::vectored;
|
|
} else {
|
|
vector_base = stvec & ~riscv::mtvec<WORD_T>::vectored;
|
|
is_vectord = stvec & riscv::mtvec<WORD_T>::vectored;
|
|
}
|
|
WORD_T target_addr;
|
|
if (is_vectord) {
|
|
target_addr = vector_base + 4*cause;
|
|
} else {
|
|
target_addr = vector_base;
|
|
}
|
|
|
|
if (target_priv_level == priv_level_t::m) {
|
|
mcause = cause | riscv::mcause<WORD_T>::intr_mask;
|
|
mtval = 0;
|
|
mepc = pc;
|
|
status.mpp = priv_level;
|
|
status.mpie = status.mie;
|
|
status.mie = false;
|
|
} else {
|
|
assert(priv_level!=priv_level_t::m);
|
|
scause = cause | riscv::mcause<WORD_T>::intr_mask;
|
|
stval = 0;
|
|
sepc = pc;
|
|
status.spp = priv_level==priv_level_t::s;
|
|
status.spie = status.sie;
|
|
status.sie = false;
|
|
}
|
|
priv_level = target_priv_level;
|
|
|
|
reserved_set = 0;
|
|
|
|
op.type = exec_result_type_t::retire;
|
|
op.next_pc = target_addr;
|
|
op.retire.rd = 0;
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::handle_exception(exec_result_t &op) {
|
|
assert(op.type == exec_result_type_t::trap);
|
|
WORD_T pc = op.pc;
|
|
WORD_T cause = op.trap.cause;
|
|
WORD_T tval = op.trap.tval;
|
|
WORD_T trap_no = cause & ~riscv::mcause<WORD_T>::intr_mask;
|
|
|
|
priv_level_t target_priv_level;
|
|
if (priv_level!=priv_level_t::m && (medeleg & (1 << cause))) {
|
|
target_priv_level = priv_level_t::s;
|
|
} else {
|
|
target_priv_level = priv_level_t::m;
|
|
}
|
|
|
|
WORD_T target_addr;
|
|
if (target_priv_level == priv_level_t::m) {
|
|
target_addr = mtvec & ~riscv::mtvec<WORD_T>::vectored;
|
|
} else {
|
|
target_addr = stvec & ~riscv::mtvec<WORD_T>::vectored;
|
|
}
|
|
|
|
if (target_priv_level == priv_level_t::m) {
|
|
mcause = cause;
|
|
mtval = tval;
|
|
mepc = pc;
|
|
status.mpp = priv_level;
|
|
status.mpie = status.mie;
|
|
status.mie = false;
|
|
} else {
|
|
assert(priv_level!=priv_level_t::m);
|
|
scause = cause;
|
|
stval = tval;
|
|
sepc = pc;
|
|
status.spp = priv_level==priv_level_t::s;
|
|
status.spie = status.sie;
|
|
status.sie = false;
|
|
}
|
|
priv_level = target_priv_level;
|
|
|
|
reserved_set = 0;
|
|
|
|
op.type = exec_result_type_t::retire;
|
|
op.next_pc = target_addr;
|
|
op.retire.rd = 0;
|
|
}
|
|
|
|
#define CSRRCS(name) \
|
|
case riscv::csr_addr::name: { \
|
|
op.retire.value = name; \
|
|
if (write) { \
|
|
name = value; \
|
|
} else if (set) { \
|
|
name |= value; \
|
|
} else if (clear) { \
|
|
name &= ~value; \
|
|
} \
|
|
break; \
|
|
}
|
|
|
|
#define STATUSRCS(csr_name, bit_name) \
|
|
if (status.bit_name) { \
|
|
op.retire.value |= riscv::csr_name<WORD_T>::bit_name; \
|
|
} \
|
|
if (write) { \
|
|
status.bit_name = value & riscv::csr_name<WORD_T>::bit_name; \
|
|
} else if (set) { \
|
|
status.bit_name = value&riscv::csr_name<WORD_T>::bit_name ? true : status.bit_name; \
|
|
} else if (clear) { \
|
|
status.bit_name = value&riscv::csr_name<WORD_T>::bit_name ? false : status.bit_name; \
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::csr_op(exec_result_t &op) {
|
|
constexpr bool is_rv64 = sizeof(WORD_T) * CHAR_BIT == 64;
|
|
assert(op.type == exec_result_type_t::csr_op);
|
|
|
|
auto [addr, rd, read, write, set, clear, value] = op.csr_op;
|
|
// check whether it has access
|
|
bool read_access = static_cast<int>(priv_level) >= (addr>>8 & 0x3);
|
|
if (!read_access && read) {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap.cause = riscv::mcause<WORD_T>::except_illegal_instr;
|
|
op.trap.tval = op.instr;
|
|
return;
|
|
}
|
|
bool write_access = read_access && (addr>>10)!=0x3;
|
|
if (!write_access && (write||set||clear)) {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap.cause = riscv::mcause<WORD_T>::except_illegal_instr;
|
|
op.trap.tval = op.instr;
|
|
return;
|
|
}
|
|
|
|
op.type = exec_result_type_t::retire;
|
|
op.retire.rd = rd;
|
|
op.retire.value = 0;
|
|
switch (addr) {
|
|
case riscv::csr_addr::misa: {
|
|
if constexpr (is_rv64) {
|
|
op.retire.value = uint64_t(2)<<62 | 0x101100;
|
|
} else {
|
|
op.retire.value = 0x40101100;
|
|
}
|
|
break;
|
|
}
|
|
case riscv::csr_addr::mhartid: {
|
|
op.retire.value = 0;
|
|
}
|
|
CSRRCS(mepc);
|
|
CSRRCS(sepc);
|
|
CSRRCS(mtvec);
|
|
CSRRCS(stvec);
|
|
CSRRCS(mcause);
|
|
CSRRCS(scause);
|
|
CSRRCS(mtval);
|
|
CSRRCS(stval);
|
|
CSRRCS(mscratch);
|
|
CSRRCS(sscratch);
|
|
CSRRCS(medeleg);
|
|
CSRRCS(mideleg);
|
|
CSRRCS(mie);
|
|
CSRRCS(sie);
|
|
CSRRCS(mip);
|
|
CSRRCS(sip);
|
|
case riscv::csr_addr::mstatus: {
|
|
WORD_T mpp = static_cast<WORD_T>(status.mpp);
|
|
op.retire.value |= mpp << 11;
|
|
if (write) {
|
|
mpp = (value>>11) & 3;
|
|
} else if (set) {
|
|
mpp |= (value>>11) & 3;
|
|
} else if (clear) {
|
|
mpp &= ~((value>>11) & 3);
|
|
}
|
|
STATUSRCS(mstatus, mxr);
|
|
STATUSRCS(mstatus, sum);
|
|
status.mpp = static_cast<priv_level_t>(mpp);
|
|
// prevent invalid values
|
|
status.mpp = status.mpp!=priv_level_t::u && status.mpp!=priv_level_t::s ? priv_level_t::m : status.mpp;
|
|
STATUSRCS(mstatus, spp);
|
|
STATUSRCS(mstatus, mpie);
|
|
STATUSRCS(mstatus, spie);
|
|
STATUSRCS(mstatus, mie);
|
|
STATUSRCS(mstatus, sie);
|
|
break;
|
|
}
|
|
case riscv::csr_addr::sstatus: {
|
|
STATUSRCS(sstatus, mxr);
|
|
STATUSRCS(sstatus, sum);
|
|
STATUSRCS(sstatus, spp);
|
|
STATUSRCS(sstatus, spie);
|
|
STATUSRCS(sstatus, sie);
|
|
break;
|
|
}
|
|
case riscv::csr_addr::satp: {
|
|
WORD_T ppn = satp.ppn;
|
|
uint16_t asid = satp.asid;
|
|
uint8_t mode_val = static_cast<uint8_t>(satp.mode);
|
|
WORD_T new_ppn; uint16_t new_asid; uint8_t new_mode_val;
|
|
if constexpr (is_rv64) {
|
|
op.retire.value = static_cast<WORD_T>(satp.mode)<<60 | WORD_T(satp.asid)<<44 | satp.ppn;
|
|
new_ppn = value & libanemo::bit_mask<uint64_t>(44, 0);
|
|
new_asid = (value>>44) & libanemo::bit_mask<WORD_T>(16, 0);
|
|
new_mode_val = value >> 60;
|
|
} else {
|
|
op.retire.value = static_cast<WORD_T>(satp.mode)<<31 | WORD_T(satp.asid)<<22 | satp.ppn;
|
|
new_ppn = value & libanemo::bit_mask<uint64_t>(22, 0);
|
|
new_asid = (value>>22) & libanemo::bit_mask<WORD_T>(9, 0);
|
|
new_mode_val = value >> 31;
|
|
}
|
|
if (write) {
|
|
ppn = new_ppn;
|
|
asid = new_asid;
|
|
mode_val = new_mode_val;
|
|
flush_tlb();
|
|
} else if (set) {
|
|
ppn |= new_ppn;
|
|
asid |= new_asid;
|
|
mode_val |= new_mode_val;
|
|
flush_tlb();
|
|
} else if (clear) {
|
|
ppn &= ~new_ppn;
|
|
asid &= ~new_asid;
|
|
mode_val &= ~new_mode_val;
|
|
flush_tlb();
|
|
}
|
|
satp_mode_t mode;
|
|
switch (mode_val) {
|
|
case static_cast<uint64_t>(satp_mode_t::bare):
|
|
mode = satp_mode_t::bare;
|
|
break;
|
|
case static_cast<uint64_t>(satp_mode_t::sv32):
|
|
if constexpr (is_rv64) {
|
|
// unsupported mode, do not change anything
|
|
return;
|
|
} else {
|
|
mode = satp_mode_t::sv32;
|
|
}
|
|
break;
|
|
// mode_val can only be bare (0) or sv32 (1) on 32 bit
|
|
case static_cast<uint64_t>(satp_mode_t::sv39):
|
|
mode = satp_mode_t::sv39;
|
|
break;
|
|
case static_cast<uint64_t>(satp_mode_t::sv48):
|
|
mode = satp_mode_t::sv48;
|
|
break;
|
|
case static_cast<uint64_t>(satp_mode_t::sv57):
|
|
mode = satp_mode_t::sv57;
|
|
break;
|
|
default:
|
|
// unsupported mode, do not change anything
|
|
return;
|
|
}
|
|
satp.ppn = ppn;
|
|
satp.asid = asid;
|
|
satp.mode = mode;
|
|
break;
|
|
}
|
|
default: {
|
|
// Attempt of accessing not implemented CSR
|
|
// raise an illegal instruction trap
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap.cause = riscv::mcause<WORD_T>::except_illegal_instr;
|
|
op.trap.tval = op.instr;
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef CSRRCS
|
|
#undef STATUSRCS
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::sys_op(exec_result_t &op) {
|
|
assert(op.type == exec_result_type_t::sys_op);
|
|
if (op.sys_op.ecall) {
|
|
op.type = exec_result_type_t::trap;
|
|
if (priv_level == priv_level_t::u) {
|
|
op.trap.cause = riscv::mcause<WORD_T>::except_env_call_u;
|
|
} else if (priv_level == priv_level_t::s) {
|
|
op.trap.cause = riscv::mcause<WORD_T>::except_env_call_s;
|
|
} else if (priv_level == priv_level_t::m) {
|
|
op.trap.cause = riscv::mcause<WORD_T>::except_env_call_m;
|
|
}
|
|
op.trap.tval = 0;
|
|
} else if (op.sys_op.mret) {
|
|
if (priv_level != priv_level_t::m) {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {.cause=riscv::mcause<WORD_T>::except_illegal_instr, .tval=op.instr};
|
|
} else {
|
|
priv_level = status.mpp;
|
|
status.mie = status.mpie;
|
|
status.mpie = 1;
|
|
status.mpp = priv_level_t::u;
|
|
op.type = exec_result_type_t::retire;
|
|
op.retire.rd = 0;
|
|
op.next_pc = mepc;
|
|
}
|
|
} else if (op.sys_op.sret) {
|
|
if (priv_level == priv_level_t::u) {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {.cause=riscv::mcause<WORD_T>::except_illegal_instr, .tval=op.instr};
|
|
} else {
|
|
priv_level = status.spp ? priv_level_t::s : priv_level_t::u;
|
|
status.sie = status.spie;
|
|
status.spie = 1;
|
|
status.spp = 0;
|
|
op.type = exec_result_type_t::retire;
|
|
op.retire.rd = 0;
|
|
op.next_pc = sepc;
|
|
}
|
|
} else if (op.sys_op.sfence_vma) {
|
|
if (priv_level == priv_level_t::u) {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {.cause=riscv::mcause<WORD_T>::except_illegal_instr, .tval=op.instr};
|
|
} else {
|
|
flush_tlb();
|
|
op.type = exec_result_type_t::retire;
|
|
op.retire.rd = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
privilege_module<WORD_T, TLB_SIZE_LOG2>::address_translation_t privilege_module<WORD_T, TLB_SIZE_LOG2>::page_walk(WORD_T vaddr, bool read, bool write, bool exec) {
|
|
if (priv_level==priv_level_t::m) {
|
|
return {.paddr=vaddr, .access_fault=false, .page_fault=false};
|
|
}
|
|
int n_levels;
|
|
int vaddr_size;
|
|
int paddr_size;
|
|
int top_ppn_size;
|
|
// Size of one level virtual page number and non-root level physical page number
|
|
constexpr int vpn_size = sizeof(WORD_T)*CHAR_BIT==32 ? 10 : 9;
|
|
// Mask of one level virtual page number
|
|
constexpr WORD_T vpn_mask = libanemo::bit_mask<WORD_T>(vpn_size, 0);
|
|
switch (satp.mode) {
|
|
case satp_mode_t::bare: {
|
|
return {.paddr=vaddr, .access_fault=false, .page_fault=false};
|
|
break;
|
|
}
|
|
case satp_mode_t::sv32: {
|
|
assert(sizeof(WORD_T)*CHAR_BIT == 32);
|
|
n_levels = 2;
|
|
vaddr_size = 32;
|
|
paddr_size = 34;
|
|
top_ppn_size = 12;
|
|
break;
|
|
}
|
|
case satp_mode_t::sv39: {
|
|
assert(sizeof(WORD_T)*CHAR_BIT == 64);
|
|
n_levels = 3;
|
|
vaddr_size = 39;
|
|
paddr_size = 56;
|
|
top_ppn_size = 26;
|
|
break;
|
|
}
|
|
case satp_mode_t::sv48: {
|
|
assert(sizeof(WORD_T)*CHAR_BIT == 64);
|
|
n_levels = 4;
|
|
vaddr_size = 48;
|
|
paddr_size = 56;
|
|
top_ppn_size = 17;
|
|
break;
|
|
}
|
|
case satp_mode_t::sv57: {
|
|
assert(sizeof(WORD_T)*CHAR_BIT == 64);
|
|
n_levels = 5;
|
|
vaddr_size = 57;
|
|
paddr_size = 56;
|
|
top_ppn_size = 8;
|
|
break;
|
|
}
|
|
case satp_mode_t::sv64: {
|
|
// This is not yet implemented
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
// This avoids silly mistakes
|
|
// PTE size here mean PTE size without the top reserved bits
|
|
int pte_size = (top_ppn_size+vpn_size*(n_levels-1)+10);
|
|
assert(vpn_size*n_levels+12 == vaddr_size);
|
|
assert(top_ppn_size+vpn_size*(n_levels-1)+12 == paddr_size);
|
|
assert(sizeof(WORD_T)*CHAR_BIT==32&&pte_size==32 || sizeof(WORD_T)*CHAR_BIT==64&&pte_size==54);
|
|
// check whether the virtual address is sign extended
|
|
if constexpr (sizeof(WORD_T)*CHAR_BIT == 64) {
|
|
if (vaddr&libanemo::bit_mask<WORD_T>(vaddr_size+1, vaddr_size)) {
|
|
if (vaddr>>vaddr_size != libanemo::bit_mask<WORD_T>(64-vaddr_size, 0)) {
|
|
return {.paddr=0, .access_fault=false, .page_fault=true};
|
|
}
|
|
} else {
|
|
if (vaddr>>vaddr_size != 0) {
|
|
return {.paddr=0, .access_fault=false, .page_fault=true};
|
|
}
|
|
}
|
|
}
|
|
WORD_T ppn = satp.ppn;
|
|
for (int i=n_levels-1; i>=0; --i) {
|
|
// construct the address of the current level page table
|
|
WORD_T vpn = vaddr>>(i*vpn_size+12) & vpn_mask;
|
|
WORD_T pte_paddr = (ppn<<12) | (vpn<<(12-vpn_size));
|
|
std::optional<WORD_T> pte_opt = data_bus->read(pte_paddr, libanemo::int_type_to_width_t<WORD_T>());
|
|
if (!pte_opt.has_value()) {
|
|
// the physical addressof the PTE is not accessible
|
|
return {.paddr=0, .access_fault=true, .page_fault=false};
|
|
}
|
|
WORD_T pte = pte_opt.value();
|
|
bool pte_v = pte & pte_mask<WORD_T>::v;
|
|
bool pte_r = pte & pte_mask<WORD_T>::r;
|
|
bool pte_w = pte & pte_mask<WORD_T>::w;
|
|
bool pte_x = pte & pte_mask<WORD_T>::x;
|
|
bool pte_u = pte & pte_mask<WORD_T>::u;
|
|
bool pte_a = pte & pte_mask<WORD_T>::a;
|
|
bool pte_d = pte & pte_mask<WORD_T>::d;
|
|
// Check the V bit and whether the permission fields are invalid
|
|
if (!pte_v || !pte_r&&pte_w) {
|
|
return {.paddr=0, .access_fault=false, .page_fault=true};
|
|
}
|
|
// A, U and D fields of a non-leaf PTE is reserved
|
|
if (!(pte_r||pte_x) && (pte_u||pte_a||pte_d)) {
|
|
return {.paddr=0, .access_fault=false, .page_fault=true};
|
|
}
|
|
// Reserved bits of the PTE should be zero
|
|
if constexpr (sizeof(WORD_T)*CHAR_BIT != 32) {
|
|
if (pte>>pte_size != 0) {
|
|
return {.paddr=0, .access_fault=false, .page_fault=true};
|
|
}
|
|
}
|
|
// Now the PTE is valid, get the current level of PPN from it
|
|
ppn = pte >> 10;
|
|
if (pte_r || pte_x) {
|
|
// PTE is leaf
|
|
// Check if the large page is aligned
|
|
bool large_page_aligned = true;
|
|
for (int j=i-1; j>=0; --j) {
|
|
if (((ppn>>(j*vpn_size))&vpn_mask) != 0) {
|
|
large_page_aligned = false;
|
|
}
|
|
}
|
|
// Check page permissions
|
|
bool page_fault =
|
|
!large_page_aligned || // the page is not aligned
|
|
(!status.sum||exec)&&pte_u&&priv_level==priv_level_t::s || !pte_u&&priv_level==priv_level_t::u || // access not allowd by u bit
|
|
!(pte_r||pte_x&&status.mxr)&&read || !pte_w&&write || !pte_x&&exec; // do not have permission
|
|
// Now set the bit flags in the PTE
|
|
if (!page_fault) {
|
|
if (read || exec) {
|
|
pte |= pte_mask<WORD_T>::a;
|
|
}
|
|
if (write) {
|
|
pte |= pte_mask<WORD_T>::d;
|
|
}
|
|
bool result = data_bus->write(pte_paddr, libanemo::int_type_to_width_t<WORD_T>(), pte);
|
|
if (!result) {
|
|
return {.paddr=0, .access_fault=true, .page_fault=false};
|
|
}
|
|
}
|
|
return {
|
|
.paddr = (ppn<<12) | (vaddr&libanemo::bit_mask<WORD_T>(i*vpn_size+12, 0)),
|
|
.access_fault = false,
|
|
.page_fault = page_fault,
|
|
};
|
|
} else if (i == 0) {
|
|
// Level 0 page table must be a leaf
|
|
return {.paddr=0, .access_fault=false, .page_fault=true};
|
|
}
|
|
}
|
|
// Should not reach here
|
|
assert(false);
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::flush_tlb(void) {
|
|
for (tlb_entry_t& entry: tlb_r) {
|
|
entry.vpn = ~WORD_T(0);
|
|
}
|
|
for (tlb_entry_t& entry: tlb_w) {
|
|
entry.vpn = ~WORD_T(0);
|
|
}
|
|
for (tlb_entry_t& entry: tlb_x) {
|
|
entry.vpn = ~WORD_T(0);
|
|
}
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
WORD_T privilege_module<WORD_T, TLB_SIZE_LOG2>::amo_op(amo_type_t type, libanemo::width_t width, WORD_T rs_value, WORD_T load_value) const {
|
|
switch (type) {
|
|
case amo_type_t::swap:
|
|
return rs_value;
|
|
case amo_type_t::add:
|
|
return rs_value + load_value;
|
|
case amo_type_t::xor_:
|
|
return rs_value ^ load_value;
|
|
case amo_type_t::and_:
|
|
return rs_value & load_value;
|
|
case amo_type_t::or_:
|
|
return rs_value | load_value;
|
|
case amo_type_t::min:
|
|
return std::min(std::make_signed_t<WORD_T>(libanemo::sign_extend(rs_value, width)), std::make_signed_t<WORD_T>(load_value));
|
|
case amo_type_t::max:
|
|
return std::max(std::make_signed_t<WORD_T>(libanemo::sign_extend(rs_value, width)), std::make_signed_t<WORD_T>(load_value));
|
|
case amo_type_t::min_u:
|
|
return std::min(libanemo::zero_truncate(rs_value, width), libanemo::zero_truncate(load_value, width));
|
|
case amo_type_t::max_u:
|
|
return std::max(libanemo::zero_truncate(rs_value, width), libanemo::zero_truncate(load_value, width));
|
|
}
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::paddr_amo(exec_result_t &op) {
|
|
assert(op.type == exec_result_type_t::amo);
|
|
auto [paddr, width, data, rd, type] = op.amo;
|
|
// load the data
|
|
exec_result_t load_op = {
|
|
.type = exec_result_type_t::load,
|
|
.load = {.addr=paddr, .width=width, .sign_extend=true, .rd=0, .reserved=false},
|
|
};
|
|
paddr_load(load_op);
|
|
// convert load trap to store/amo trap
|
|
if (load_op.type == exec_result_type_t::trap) {
|
|
op.type = exec_result_type_t::trap;
|
|
op.trap = {.cause=riscv::mcause<WORD_T>::except_store_fault, .tval=paddr};
|
|
return;
|
|
}
|
|
assert(load_op.type == exec_result_type_t::retire);
|
|
// do computation
|
|
WORD_T result = amo_op(type, width, data, load_op.retire.value);
|
|
op.type = exec_result_type_t::store;
|
|
op.store = {.addr=paddr, .width=width, .data=result, .rd=0, .conditional=false};
|
|
// store the result back
|
|
paddr_store(op);
|
|
// set rd to thle loaded value
|
|
if (op.type == exec_result_type_t::retire) {
|
|
op.retire.rd = rd;
|
|
op.retire.value = load_op.retire.value;
|
|
} else {
|
|
assert(op.type == exec_result_type_t::trap);
|
|
}
|
|
}
|
|
|
|
template <typename WORD_T, size_t TLB_SIZE_LOG2>
|
|
void privilege_module<WORD_T, TLB_SIZE_LOG2>::vaddr_amo(exec_result_t &op) {
|
|
assert(op.type == exec_result_type_t::amo);
|
|
auto [vaddr, width, data, rd, type] = op.amo;
|
|
// load the data
|
|
exec_result_t load_op = {
|
|
.type = exec_result_type_t::load,
|
|
.load = {.addr=vaddr, .width=width, .sign_extend=true, .rd=0, .reserved=false},
|
|
};
|
|
vaddr_load(load_op);
|
|
// convert load trap to store/amo trap
|
|
if (load_op.type == exec_result_type_t::trap) {
|
|
op.type = exec_result_type_t::trap;
|
|
if (load_op.trap.cause == riscv::mcause<WORD_T>::except_load_fault) {
|
|
op.trap.cause=riscv::mcause<WORD_T>::except_store_fault;
|
|
} else if (load_op.trap.cause == riscv::mcause<WORD_T>::except_load_page_fault) {
|
|
op.trap.cause=riscv::mcause<WORD_T>::except_store_page_fault;
|
|
} else {
|
|
assert(0);
|
|
}
|
|
op.trap.tval=vaddr;
|
|
return;
|
|
}
|
|
assert(load_op.type == exec_result_type_t::retire);
|
|
// do the computation
|
|
WORD_T result = amo_op(type, width, data, load_op.retire.value);
|
|
op.type = exec_result_type_t::store;
|
|
op.store = {.addr=vaddr, .width=width, .data=result, .rd=0, .conditional=false};
|
|
// store the result back
|
|
vaddr_store(op);
|
|
// set rd to loaded value
|
|
if (op.type == exec_result_type_t::retire) {
|
|
op.retire.rd = rd;
|
|
op.retire.value = load_op.retire.value;
|
|
} else {
|
|
assert(op.type == exec_result_type_t::trap);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|