aboutsummaryrefslogtreecommitdiff
path: root/rv64cpu/tb
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-11-07 02:59:33 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-11-07 02:59:33 +0800
commitbc3ec25107314be3cd816c0b669d82338e892806 (patch)
treeff3e69ce6be1218be8bd0dd04e0c75760776d7dc /rv64cpu/tb
parentc67ba709482821f37d63601b001ac07e6db867b6 (diff)
downloadrcc-project-bc3ec25107314be3cd816c0b669d82338e892806.tar.gz
rcc-project-bc3ec25107314be3cd816c0b669d82338e892806.tar.xz
checkpoint: preserve current RV64 CPU development state
Diffstat (limited to 'rv64cpu/tb')
-rw-r--r--rv64cpu/tb/CMakeLists.txt29
-rw-r--r--rv64cpu/tb/compliance/CMakeLists.txt7
-rw-r--r--rv64cpu/tb/compliance/compliance_runner.cc238
-rw-r--r--rv64cpu/tb/core/tb_core.cc40
-rw-r--r--rv64cpu/tb/unit/tb_alu.cc45
-rw-r--r--rv64cpu/tb/unit/tb_csr.cc225
-rw-r--r--rv64cpu/tb/unit/tb_decoder.cc51
-rw-r--r--rv64cpu/tb/unit/tb_hazard_unit.cc21
-rw-r--r--rv64cpu/tb/unit/tb_mem_stage.cc97
-rw-r--r--rv64cpu/tb/unit/tb_mmu_top.cc236
-rw-r--r--rv64cpu/tb/unit/tb_ptw.cc206
-rw-r--r--rv64cpu/tb/unit/tb_tlb.cc421
-rw-r--r--rv64cpu/tb/unit/tb_trap_ctrl.cc98
13 files changed, 1281 insertions, 433 deletions
diff --git a/rv64cpu/tb/CMakeLists.txt b/rv64cpu/tb/CMakeLists.txt
index 1312860..33a6224 100644
--- a/rv64cpu/tb/CMakeLists.txt
+++ b/rv64cpu/tb/CMakeLists.txt
@@ -81,6 +81,7 @@ add_verilator_target(
SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
${RTL_CORE_DIR}/decoder.sv
${RTL_CORE_DIR}/regfile.sv
+ ${RTL_CORE_DIR}/fp_regfile.sv
${RTL_CORE_DIR}/id_stage.sv
INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_id_stage.cc
@@ -189,6 +190,27 @@ add_verilator_target(
TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_tlb.cc
)
+add_verilator_target(
+ NAME tb_ptw
+ TOP_MODULE ptw
+ SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
+ ${RTL_CORE_DIR}/mmu/ptw.sv
+ INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
+ TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_ptw.cc
+)
+
+add_verilator_target(
+ NAME tb_mmu_top
+ TOP_MODULE mmu_top
+ SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
+ ${RTL_CORE_DIR}/mmu/tlb.sv
+ ${RTL_CORE_DIR}/mmu/ptw.sv
+ ${RTL_CORE_DIR}/mmu/mmu_top.sv
+ INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
+ TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_mmu_top.cc
+ EXTRA_FLAGS -Wno-VARHIDDEN
+)
+
# ═══════════════════════════════════════════════════════════════════════════════════
# Core Integration Test
# ═══════════════════════════════════════════════════════════════════════════════════
@@ -199,13 +221,16 @@ add_verilator_target(
SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
${RTL_CORE_DIR}/alu.sv
${RTL_CORE_DIR}/branch_unit.sv
+ ${RTL_CORE_DIR}/compressed_decoder.sv
${RTL_CORE_DIR}/decoder.sv
${RTL_CORE_DIR}/regfile.sv
+ ${RTL_CORE_DIR}/fp_regfile.sv
${RTL_CORE_DIR}/if_stage.sv
${RTL_CORE_DIR}/if_id_reg.sv
${RTL_CORE_DIR}/id_stage.sv
${RTL_CORE_DIR}/id_ex_reg.sv
${RTL_CORE_DIR}/ex_stage.sv
+ ${RTL_CORE_DIR}/fpu/fpu_unit.sv
${RTL_CORE_DIR}/ex_mem_reg.sv
${RTL_CORE_DIR}/mem_stage.sv
${RTL_CORE_DIR}/mem_wb_reg.sv
@@ -213,6 +238,10 @@ add_verilator_target(
${RTL_CORE_DIR}/hazard_unit.sv
${RTL_CORE_DIR}/include/csr_pkg.sv
${RTL_CORE_DIR}/csr_unit.sv
+ ${RTL_CORE_DIR}/pmp_checker.sv
+ ${RTL_CORE_DIR}/mmu/tlb.sv
+ ${RTL_CORE_DIR}/mmu/ptw.sv
+ ${RTL_CORE_DIR}/mmu/mmu_top.sv
${RTL_CORE_DIR}/trap_ctrl.sv
${RTL_CORE_DIR}/richard_core.sv
INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
diff --git a/rv64cpu/tb/compliance/CMakeLists.txt b/rv64cpu/tb/compliance/CMakeLists.txt
index 0139f54..677cae6 100644
--- a/rv64cpu/tb/compliance/CMakeLists.txt
+++ b/rv64cpu/tb/compliance/CMakeLists.txt
@@ -12,13 +12,16 @@ add_verilator_target(
SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv
${RTL_CORE_DIR}/alu.sv
${RTL_CORE_DIR}/branch_unit.sv
+ ${RTL_CORE_DIR}/compressed_decoder.sv
${RTL_CORE_DIR}/decoder.sv
${RTL_CORE_DIR}/regfile.sv
+ ${RTL_CORE_DIR}/fp_regfile.sv
${RTL_CORE_DIR}/if_stage.sv
${RTL_CORE_DIR}/if_id_reg.sv
${RTL_CORE_DIR}/id_stage.sv
${RTL_CORE_DIR}/id_ex_reg.sv
${RTL_CORE_DIR}/ex_stage.sv
+ ${RTL_CORE_DIR}/fpu/fpu_unit.sv
${RTL_CORE_DIR}/ex_mem_reg.sv
${RTL_CORE_DIR}/mem_stage.sv
${RTL_CORE_DIR}/mem_wb_reg.sv
@@ -26,6 +29,10 @@ add_verilator_target(
${RTL_CORE_DIR}/hazard_unit.sv
${RTL_CORE_DIR}/include/csr_pkg.sv
${RTL_CORE_DIR}/csr_unit.sv
+ ${RTL_CORE_DIR}/pmp_checker.sv
+ ${RTL_CORE_DIR}/mmu/tlb.sv
+ ${RTL_CORE_DIR}/mmu/ptw.sv
+ ${RTL_CORE_DIR}/mmu/mmu_top.sv
${RTL_CORE_DIR}/trap_ctrl.sv
${RTL_CORE_DIR}/richard_core.sv
INCLUDE_DIRS ${RTL_COMMON_INCLUDES}
diff --git a/rv64cpu/tb/compliance/compliance_runner.cc b/rv64cpu/tb/compliance/compliance_runner.cc
index b29662a..ba52647 100644
--- a/rv64cpu/tb/compliance/compliance_runner.cc
+++ b/rv64cpu/tb/compliance/compliance_runner.cc
@@ -27,10 +27,12 @@
#include <iostream>
#include <map>
#include <string>
+#include <sstream>
#include <vector>
#include <verilated.h>
#include "Vrichard_core.h"
+#include "Vrichard_core___024root.h"
namespace {
@@ -38,6 +40,7 @@ struct Options {
uint64_t max_cycles = 2000;
std::string elf_path;
std::string signature_path;
+ uint64_t trace_pc_count = 0;
bool show_help = false;
};
@@ -49,11 +52,21 @@ struct Symbols {
class ByteMemory {
public:
+ static constexpr uint64_t UART_THR = 0x10000000ULL;
+ static constexpr uint64_t UART_LSR = 0x10000005ULL;
+ static constexpr uint64_t CLINT_MTIME = 0x0200BFF8ULL;
+
void write_byte(uint64_t addr, uint8_t value) {
+ if (addr == UART_THR) {
+ return;
+ }
mem_[addr] = value;
}
uint8_t read_byte(uint64_t addr) const {
+ if (addr == UART_LSR) {
+ return 0x20;
+ }
auto it = mem_.find(addr);
if (it == mem_.end()) {
return 0;
@@ -186,6 +199,10 @@ Options parse_args(int argc, char** argv) {
opts.signature_path = argv[++i];
continue;
}
+ if (arg == "--trace-pc" && (i + 1) < argc) {
+ opts.trace_pc_count = static_cast<uint64_t>(std::stoull(argv[++i]));
+ continue;
+ }
}
return opts;
}
@@ -194,7 +211,46 @@ void print_help() {
std::cout
<< "compliance_runner (bootstrap)\n"
<< "Usage:\n"
- << " compliance_runner --elf PATH [--max-cycles N] [--signature PATH]\n";
+ << " compliance_runner --elf PATH [--max-cycles N] [--signature PATH] [--trace-pc N]\n";
+}
+
+std::string hex64(uint64_t value) {
+ std::ostringstream out;
+ out << "0x" << std::hex << std::setfill('0') << std::setw(16) << value;
+ return out.str();
+}
+
+void dump_selected_gprs(const Vrichard_core* top, const char* prefix) {
+ const auto& regs = top->rootp->richard_core__DOT__u_id_stage__DOT__u_regfile__DOT__regs;
+ std::cerr << prefix << ": selected GPRs\n"
+ << " x2/sp=" << hex64(regs[2])
+ << " x3/gp=" << hex64(regs[3])
+ << " x4/tp=" << hex64(regs[4])
+ << " x5/t0=" << hex64(regs[5]) << "\n"
+ << " x6/t1=" << hex64(regs[6])
+ << " x7/t2=" << hex64(regs[7])
+ << " x8/s0=" << hex64(regs[8])
+ << " x9/s1=" << hex64(regs[9]) << "\n"
+ << " x10/a0=" << hex64(regs[10])
+ << " x11/a1=" << hex64(regs[11])
+ << " x20/s4=" << hex64(regs[20])
+ << " x21/s5=" << hex64(regs[21]) << "\n";
+ std::cerr << " mstatus=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mstatus_reg)
+ << " sstatus=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__sstatus_reg)
+ << " mcause=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mcause_reg)
+ << " mepc=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mepc_reg)
+ << "\n";
+ std::cerr << " priv=" << static_cast<unsigned>(top->rootp->richard_core__DOT__u_csr_unit__DOT__priv_mode_q)
+ << " medeleg=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__medeleg_reg)
+ << " mideleg=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mideleg_reg)
+ << " mtvec=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mtvec_reg)
+ << " stvec=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__stvec_reg)
+ << "\n";
+ std::cerr << " mstateen0=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mstateen_reg[0])
+ << " sstateen0=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__sstateen_reg[0])
+ << " menvcfg=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__menvcfg_reg)
+ << " senvcfg=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__senvcfg_reg)
+ << "\n";
}
bool load_elf64(const std::string& path, ByteMemory* memory, Symbols* symbols) {
@@ -303,20 +359,34 @@ bool load_elf64(const std::string& path, ByteMemory* memory, Symbols* symbols) {
return true;
}
-void tick(Vrichard_core* top, ByteMemory* memory) {
+void drive_memory_response(Vrichard_core* top, ByteMemory* memory) {
top->imem_rdata = memory->read_u32(top->imem_addr);
top->dmem_rdata = top->dmem_read_en ? memory->read_u64(top->dmem_addr & ~0x7ULL) : 0;
- top->ext_timer_interrupt = 0;
- top->ext_software_interrupt = 0;
- top->ext_external_interrupt = 0;
+}
+
+void tick(Vrichard_core* top, ByteMemory* memory) {
+ drive_memory_response(top, memory);
+ top->m_timer_interrupt = 0;
+ top->m_software_interrupt = 0;
+ top->m_external_interrupt = 0;
+ top->s_timer_interrupt = 0;
+ top->s_software_interrupt = 0;
+ top->s_external_interrupt = 0;
top->clk = 0;
top->eval();
+ drive_memory_response(top, memory);
+ top->eval();
top->clk = 1;
top->eval();
if (top->dmem_write_en) {
memory->write_store(top->dmem_addr, top->dmem_wdata, top->dmem_size);
+ if ((top->dmem_addr <= ByteMemory::CLINT_MTIME + 7ULL) &&
+ (top->dmem_addr + 7ULL >= ByteMemory::CLINT_MTIME)) {
+ top->rootp->richard_core__DOT__u_csr_unit__DOT__time_reg =
+ memory->read_u64(ByteMemory::CLINT_MTIME);
+ }
}
}
@@ -377,10 +447,152 @@ int main(int argc, char** argv) {
bool saw_tohost = false;
bool test_pass = false;
+ uint64_t store_trace_printed = 0;
+ uint64_t read_trace_printed = 0;
+ uint64_t wb_trace_printed = 0;
+ uint64_t trap_trace_printed = 0;
+ std::string uart_trace;
+ std::vector<uint64_t> pc_trace;
+ if (opts.trace_pc_count != 0) {
+ pc_trace.reserve(static_cast<size_t>(opts.trace_pc_count));
+ }
+
for (uint64_t i = 0; i < opts.max_cycles; ++i) {
+ if (opts.trace_pc_count != 0) {
+ if (pc_trace.size() == opts.trace_pc_count) {
+ pc_trace.erase(pc_trace.begin());
+ }
+ pc_trace.push_back(top->imem_addr);
+ }
tick(top, &memory);
+ if (opts.trace_pc_count != 0 &&
+ top->dmem_read_en &&
+ (top->dmem_addr >= 0x000000008004cd20ULL) &&
+ (top->dmem_addr < 0x000000008004cd40ULL) &&
+ read_trace_printed < 32) {
+ std::cerr << "load cycle=" << i
+ << " pc=" << hex64(top->imem_addr)
+ << " addr=" << hex64(top->dmem_addr)
+ << " rdata=" << hex64(top->dmem_rdata)
+ << " size=" << static_cast<unsigned>(top->dmem_size)
+ << "\n";
+ ++read_trace_printed;
+ }
+ if (opts.trace_pc_count != 0 && top->dmem_write_en && top->dmem_addr == ByteMemory::UART_THR) {
+ uart_trace.push_back(static_cast<char>(top->dmem_wdata & 0xFFULL));
+ }
+ if (opts.trace_pc_count != 0 &&
+ top->dmem_write_en &&
+ top->dmem_addr >= 0x00000000800115e0ULL &&
+ top->dmem_addr < 0x0000000080011608ULL) {
+ std::cerr << "scratch-store cycle=" << i
+ << " pc=" << hex64(top->imem_addr)
+ << " addr=" << hex64(top->dmem_addr)
+ << " data=" << hex64(top->dmem_wdata)
+ << " size=" << static_cast<unsigned>(top->dmem_size)
+ << "\n";
+ }
+ if (opts.trace_pc_count != 0 &&
+ top->rootp->richard_core__DOT__csr_req &&
+ (((top->rootp->richard_core__DOT__wb_instr_in >> 20) & 0xFFFU) == 0x341 ||
+ ((top->rootp->richard_core__DOT__wb_instr_in >> 20) & 0xFFFU) == 0x141 ||
+ ((top->rootp->richard_core__DOT__wb_instr_in >> 20) & 0xFFFU) == 0x300 ||
+ ((top->rootp->richard_core__DOT__wb_instr_in >> 20) & 0xFFFU) == 0x100)) {
+ std::cerr << "csr cycle=" << i
+ << " pc=" << hex64(top->rootp->richard_core__DOT__wb_pc_in)
+ << " addr=0x" << std::hex << std::setw(3) << std::setfill('0')
+ << ((top->rootp->richard_core__DOT__wb_instr_in >> 20) & 0xFFFU)
+ << " op=" << std::dec
+ << static_cast<unsigned>((top->rootp->richard_core__DOT__wb_instr_in >> 12) & 0x7U)
+ << " rs1=" << hex64(top->rootp->richard_core__DOT__csr_rs1_data)
+ << " mepc_old=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mepc_reg)
+ << " sepc_old=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__sepc_reg)
+ << " instr=0x" << std::hex << std::setw(8) << std::setfill('0')
+ << top->rootp->richard_core__DOT__wb_instr_in
+ << std::dec << std::setfill(' ') << "\n";
+ }
+ if (opts.trace_pc_count != 0 &&
+ (top->rootp->richard_core__DOT__trap_ctrl_mret_en ||
+ top->rootp->richard_core__DOT__trap_ctrl_sret_en)) {
+ std::cerr << "xret cycle=" << i
+ << " pc=" << hex64(top->rootp->richard_core__DOT__wb_pc_in)
+ << " mret=" << static_cast<unsigned>(top->rootp->richard_core__DOT__trap_ctrl_mret_en)
+ << " sret=" << static_cast<unsigned>(top->rootp->richard_core__DOT__trap_ctrl_sret_en)
+ << " target=" << hex64(top->rootp->richard_core__DOT__hu_pc_redirect_target)
+ << " mepc=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mepc_reg)
+ << " sepc=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__sepc_reg)
+ << "\n";
+ }
+ if (opts.trace_pc_count != 0 &&
+ top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000e1b4ULL &&
+ trap_trace_printed < 128) {
+ const auto& regs = top->rootp->richard_core__DOT__u_id_stage__DOT__u_regfile__DOT__regs;
+ const uint64_t sig_ptr = regs[6];
+ std::cerr << "trap-check cycle=" << i
+ << " t1=" << hex64(sig_ptr)
+ << " cause=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mcause_reg)
+ << " mepc=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__mepc_reg)
+ << " stored_msig=" << hex64(memory.read_u64(0x00000000800115f0ULL))
+ << " exp_status=" << hex64(memory.read_u64(sig_ptr))
+ << " exp_cause=" << hex64(memory.read_u64(sig_ptr + 8))
+ << " exp_epc=" << hex64(memory.read_u64(sig_ptr + 16))
+ << "\n";
+ ++trap_trace_printed;
+ }
+ if (opts.trace_pc_count != 0 &&
+ (top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000eb84ULL ||
+ top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000eb88ULL ||
+ top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000ebc8ULL ||
+ top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000ebccULL ||
+ top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000ebecULL ||
+ top->rootp->richard_core__DOT__wb_pc_in == 0x000000008000ec60ULL) &&
+ trap_trace_printed < 256) {
+ const auto& regs = top->rootp->richard_core__DOT__u_id_stage__DOT__u_regfile__DOT__regs;
+ std::cerr << "strap-debug cycle=" << i
+ << " pc=" << hex64(top->rootp->richard_core__DOT__wb_pc_in)
+ << " sp=" << hex64(regs[2])
+ << " t1=" << hex64(regs[6])
+ << " t2=" << hex64(regs[7])
+ << " s0=" << hex64(regs[8])
+ << " s1=" << hex64(regs[9])
+ << " a0=" << hex64(regs[10])
+ << " a1=" << hex64(regs[11])
+ << " sscratch=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__sscratch_reg)
+ << " scause=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__scause_reg)
+ << " sepc=" << hex64(top->rootp->richard_core__DOT__u_csr_unit__DOT__sepc_reg)
+ << " mem[Strap_sig]=" << hex64(memory.read_u64(0x00000000800115f0ULL))
+ << " mem[Ssig_bgn]=" << hex64(memory.read_u64(0x00000000800115d0ULL))
+ << " mem[Sbase]=" << hex64(memory.read_u64(0x00000000800115b0ULL))
+ << "\n";
+ ++trap_trace_printed;
+ }
+ if (opts.trace_pc_count != 0 &&
+ top->rootp->richard_core__DOT__wb_reg_write_en_out &&
+ (top->rootp->richard_core__DOT__wb_rd_addr_in == 8 ||
+ top->rootp->richard_core__DOT__wb_rd_addr_in == 4 ||
+ top->rootp->richard_core__DOT__wb_rd_addr_in == 13 ||
+ top->rootp->richard_core__DOT__wb_rd_addr_in == 20) &&
+ wb_trace_printed < 64) {
+ std::cerr << "wb cycle=" << i
+ << " pc=" << hex64(top->rootp->richard_core__DOT__wb_pc_in)
+ << " rd=x" << static_cast<unsigned>(top->rootp->richard_core__DOT__wb_rd_addr_in)
+ << " data=" << hex64(top->rootp->richard_core__DOT__wb_rd_wdata)
+ << " instr=0x" << std::hex << std::setw(8) << std::setfill('0')
+ << top->rootp->richard_core__DOT__wb_instr_in
+ << std::dec << std::setfill(' ') << "\n";
+ ++wb_trace_printed;
+ }
if (top->dmem_write_en && symbols.tohost != 0) {
const uint64_t addr = top->dmem_addr;
+ if (opts.trace_pc_count != 0 && store_trace_printed < 128) {
+ std::cerr << "store cycle=" << i
+ << " pc=" << hex64(top->imem_addr)
+ << " addr=" << hex64(addr)
+ << " data=" << hex64(top->dmem_wdata)
+ << " size=" << static_cast<unsigned>(top->dmem_size)
+ << "\n";
+ ++store_trace_printed;
+ }
const bool overlaps_tohost = (addr <= symbols.tohost + 3ULL) && (addr + 7ULL >= symbols.tohost);
if (overlaps_tohost) {
const uint32_t status = static_cast<uint32_t>(memory.read_u64(symbols.tohost) & 0xFFFFFFFFULL);
@@ -400,9 +612,23 @@ int main(int argc, char** argv) {
dump_signature(memory, symbols, opts.signature_path);
- delete top;
+ if (opts.trace_pc_count != 0 && !uart_trace.empty()) {
+ std::cerr << "uart output:\n" << uart_trace << "\n";
+ }
if (!saw_tohost) {
+ if (!pc_trace.empty()) {
+ std::cerr << "timeout: last " << pc_trace.size() << " fetch PCs\n";
+ for (uint64_t pc : pc_trace) {
+ std::cerr << " " << hex64(pc) << "\n";
+ }
+ dump_selected_gprs(top, "timeout");
+ }
+ delete top;
return 3;
}
+ if (opts.trace_pc_count != 0 && !test_pass) {
+ dump_selected_gprs(top, "fail");
+ }
+ delete top;
return test_pass ? 0 : 1;
}
diff --git a/rv64cpu/tb/core/tb_core.cc b/rv64cpu/tb/core/tb_core.cc
index 6877d8d..da758c1 100644
--- a/rv64cpu/tb/core/tb_core.cc
+++ b/rv64cpu/tb/core/tb_core.cc
@@ -352,17 +352,23 @@ public:
Vrichard_core* top;
MemoryModel mem;
uint64_t sim_time;
- bool ext_timer_interrupt_level;
- bool ext_software_interrupt_level;
- bool ext_external_interrupt_level;
+ bool m_timer_interrupt_level;
+ bool m_software_interrupt_level;
+ bool m_external_interrupt_level;
+ bool s_timer_interrupt_level;
+ bool s_software_interrupt_level;
+ bool s_external_interrupt_level;
#if VM_TRACE
VerilatedVcdC* tfp;
#endif
CoreSim() : sim_time(0),
- ext_timer_interrupt_level(false),
- ext_software_interrupt_level(false),
- ext_external_interrupt_level(false) {
+ m_timer_interrupt_level(false),
+ m_software_interrupt_level(false),
+ m_external_interrupt_level(false),
+ s_timer_interrupt_level(false),
+ s_software_interrupt_level(false),
+ s_external_interrupt_level(false) {
top = new Vrichard_core;
#if VM_TRACE
Verilated::traceEverOn(true);
@@ -401,9 +407,12 @@ public:
top->dmem_rdata = 0;
}
- top->ext_timer_interrupt = ext_timer_interrupt_level;
- top->ext_software_interrupt = ext_software_interrupt_level;
- top->ext_external_interrupt = ext_external_interrupt_level;
+ top->m_timer_interrupt = m_timer_interrupt_level;
+ top->m_software_interrupt = m_software_interrupt_level;
+ top->m_external_interrupt = m_external_interrupt_level;
+ top->s_timer_interrupt = s_timer_interrupt_level;
+ top->s_software_interrupt = s_software_interrupt_level;
+ top->s_external_interrupt = s_external_interrupt_level;
// Rising edge
top->clk = 0;
@@ -432,13 +441,20 @@ public:
}
void set_interrupt_lines(bool timer, bool software, bool external) {
- ext_timer_interrupt_level = timer;
- ext_software_interrupt_level = software;
- ext_external_interrupt_level = external;
+ m_timer_interrupt_level = timer;
+ m_software_interrupt_level = software;
+ m_external_interrupt_level = external;
}
void clear_interrupts() {
set_interrupt_lines(false, false, false);
+ set_supervisor_interrupt_lines(false, false, false);
+ }
+
+ void set_supervisor_interrupt_lines(bool timer, bool software, bool external) {
+ s_timer_interrupt_level = timer;
+ s_software_interrupt_level = software;
+ s_external_interrupt_level = external;
}
};
diff --git a/rv64cpu/tb/unit/tb_alu.cc b/rv64cpu/tb/unit/tb_alu.cc
index 04eaaed..0ed5059 100644
--- a/rv64cpu/tb/unit/tb_alu.cc
+++ b/rv64cpu/tb/unit/tb_alu.cc
@@ -22,16 +22,24 @@
#include <verilated.h>
enum AluOp {
- ALU_ADD = 0b0000,
- ALU_SUB = 0b1000,
- ALU_SLL = 0b0001,
- ALU_SLT = 0b0010,
- ALU_SLTU = 0b0011,
- ALU_XOR = 0b0100,
- ALU_SRL = 0b0101,
- ALU_SRA = 0b1101,
- ALU_OR = 0b0110,
- ALU_AND = 0b0111
+ ALU_ADD = 0,
+ ALU_SUB = 8,
+ ALU_SLL = 1,
+ ALU_SLT = 2,
+ ALU_SLTU = 3,
+ ALU_XOR = 4,
+ ALU_SRL = 5,
+ ALU_SRA = 13,
+ ALU_OR = 6,
+ ALU_AND = 7,
+ ALU_MUL = 16,
+ ALU_MULH = 17,
+ ALU_MULHSU = 18,
+ ALU_MULHU = 19,
+ ALU_DIV = 20,
+ ALU_DIVU = 21,
+ ALU_REM = 22,
+ ALU_REMU = 23
};
void test_alu(Valu* alu, uint8_t op, uint64_t rs1, uint64_t rs2, uint64_t expected, const char* test_name) {
@@ -104,10 +112,25 @@ int main(int argc, char** argv) {
// SLT Edge Cases
test_alu(alu, ALU_SLT, 0x8000000000000000ULL, 0x7FFFFFFFFFFFFFFFULL, 1, "SLT_Min_max");
test_alu(alu, ALU_SLT, 0x7FFFFFFFFFFFFFFFULL, 0x8000000000000000ULL, 0, "SLT_Max_Min");
+
+ // M extension
+ test_alu(alu, ALU_MUL, 7, 6, 42, "MUL_Positive");
+ test_alu(alu, ALU_MUL, 0xFFFFFFFFFFFFFFFFULL, 2, 0xFFFFFFFFFFFFFFFEULL, "MUL_NegativeLow");
+ test_alu(alu, ALU_MULH, 0xFFFFFFFFFFFFFFFFULL, 2, 0xFFFFFFFFFFFFFFFFULL, "MULH_Negative");
+ test_alu(alu, ALU_MULHU, 0xFFFFFFFFFFFFFFFFULL, 2, 1, "MULHU_UnsignedHigh");
+ test_alu(alu, ALU_DIV, (uint64_t)-21, 4, (uint64_t)-5, "DIV_SignedTruncTowardZero");
+ test_alu(alu, ALU_DIVU, 21, 4, 5, "DIVU");
+ test_alu(alu, ALU_REM, (uint64_t)-21, 4, (uint64_t)-1, "REM_Signed");
+ test_alu(alu, ALU_REMU, 21, 4, 1, "REMU");
+ test_alu(alu, ALU_DIV, 123, 0, 0xFFFFFFFFFFFFFFFFULL, "DIV_ByZero");
+ test_alu(alu, ALU_REM, 123, 0, 123, "REM_ByZero");
+ test_alu(alu, ALU_DIV, 0x8000000000000000ULL, 0xFFFFFFFFFFFFFFFFULL,
+ 0x8000000000000000ULL, "DIV_Overflow");
+ test_alu(alu, ALU_REM, 0x8000000000000000ULL, 0xFFFFFFFFFFFFFFFFULL, 0, "REM_Overflow");
std::cout << "----------------------------------" << std::endl;
std::cout << "\033[32mAll tests passed!\033[0m" << std::endl;
std::cout << "----------------------------------" << std::endl;
delete alu;
return 0;
-} \ No newline at end of file
+}
diff --git a/rv64cpu/tb/unit/tb_csr.cc b/rv64cpu/tb/unit/tb_csr.cc
index 8391759..e86ad9b 100644
--- a/rv64cpu/tb/unit/tb_csr.cc
+++ b/rv64cpu/tb/unit/tb_csr.cc
@@ -37,16 +37,21 @@ constexpr uint16_t CSR_MIDELEG = 0x303;
constexpr uint16_t CSR_MIE = 0x304;
constexpr uint16_t CSR_MTVEC = 0x305;
constexpr uint16_t CSR_MCOUNTEREN= 0x306;
+constexpr uint16_t CSR_MENVCFG = 0x30A;
+constexpr uint16_t CSR_MSTATEEN0 = 0x30C;
+constexpr uint16_t CSR_MCOUNTINHIBIT = 0x320;
constexpr uint16_t CSR_MSCRATCH = 0x340;
constexpr uint16_t CSR_MEPC = 0x341;
constexpr uint16_t CSR_MCAUSE = 0x342;
constexpr uint16_t CSR_MTVAL = 0x343;
+constexpr uint16_t CSR_MIP = 0x344;
constexpr uint16_t CSR_MCYCLE = 0xB00;
constexpr uint16_t CSR_MINSTRET = 0xB02;
constexpr uint16_t CSR_SSTATUS = 0x100;
constexpr uint16_t CSR_SIE = 0x104;
constexpr uint16_t CSR_STVEC = 0x105;
constexpr uint16_t CSR_SCOUNTEREN= 0x106;
+constexpr uint16_t CSR_SENVCFG = 0x10A;
constexpr uint16_t CSR_SSCRATCH = 0x140;
constexpr uint16_t CSR_SEPC = 0x141;
constexpr uint16_t CSR_SCAUSE = 0x142;
@@ -58,6 +63,7 @@ constexpr uint16_t CSR_INSTRET = 0xC02;
constexpr uint16_t CSR_FFLAGS = 0x001;
constexpr uint16_t CSR_FRM = 0x002;
constexpr uint16_t CSR_FCSR = 0x003;
+constexpr uint16_t CSR_MVENDORID = 0xF11;
constexpr uint64_t EXC_INST_ILLEGAL = 2ULL;
constexpr uint64_t EXC_ECALL_FROM_U = 8ULL;
@@ -69,18 +75,49 @@ constexpr uint64_t INTERRUPT_BIT = 1ULL << 63;
constexpr uint64_t INTERRUPT_M_SOFTWARE = 3ULL;
constexpr uint64_t INTERRUPT_M_TIMER = 7ULL;
constexpr uint64_t INTERRUPT_M_EXTERNAL = 11ULL;
+constexpr uint64_t INTERRUPT_S_SOFTWARE = 1ULL;
+constexpr uint64_t INTERRUPT_S_TIMER = 5ULL;
+constexpr uint64_t INTERRUPT_S_EXTERNAL = 9ULL;
constexpr int MSTATUS_MIE_BIT = 3;
constexpr int MSTATUS_MPIE_BIT = 7;
constexpr int MSTATUS_MPP_LO = 11;
constexpr int MSTATUS_MPP_HI = 12;
+constexpr int MSTATUS_TVM_BIT = 20;
+constexpr int MSTATUS_TW_BIT = 21;
+constexpr int MSTATUS_TSR_BIT = 22;
+constexpr int MSTATUS_UXL_LO = 32;
+constexpr int MSTATUS_SXL_LO = 34;
constexpr int SSTATUS_SIE_BIT = 1;
constexpr int SSTATUS_SPIE_BIT = 5;
constexpr int SSTATUS_SPP_BIT = 8;
+constexpr int MCOUNTINHIBIT_CY_BIT = 0;
+constexpr int MCOUNTINHIBIT_IR_BIT = 2;
+constexpr int STATEEN0_ENVCFG_BIT = 62;
+
+constexpr uint64_t STATUS_XLEN_64 = 2ULL;
+constexpr uint64_t MSTATUS_RESET_VALUE = (STATUS_XLEN_64 << MSTATUS_UXL_LO) |
+ (STATUS_XLEN_64 << MSTATUS_SXL_LO);
+constexpr uint64_t SSTATUS_RESET_VALUE = (STATUS_XLEN_64 << MSTATUS_UXL_LO);
constexpr uint64_t PRIV_ENCODE_U = 0ULL;
constexpr uint64_t PRIV_ENCODE_S = 1ULL;
constexpr uint64_t PRIV_ENCODE_M = 3ULL;
+constexpr uint64_t MEDELEG_WRITABLE_MASK =
+ (1ULL << EXC_ECALL_FROM_U) |
+ (1ULL << EXC_ECALL_FROM_S) |
+ (1ULL << EXC_LOAD_FAULT) |
+ (1ULL << EXC_STORE_FAULT);
+constexpr uint64_t MEDELEG_READ_ONLY_ONE_MASK = (1ULL << EXC_INST_ILLEGAL);
+constexpr uint64_t MIDELEG_WRITABLE_MASK =
+ (1ULL << INTERRUPT_S_SOFTWARE) |
+ (1ULL << INTERRUPT_S_TIMER) |
+ (1ULL << INTERRUPT_S_EXTERNAL);
+constexpr uint64_t MCOUNTINHIBIT_WRITABLE_MASK =
+ (1ULL << MCOUNTINHIBIT_CY_BIT) |
+ (1ULL << MCOUNTINHIBIT_IR_BIT) |
+ (0x1FFFFFFFULL << 3);
+constexpr uint64_t COUNTEREN_WRITABLE_MASK = 0xFFFFFFFFULL;
}
void tick(Vcsr_unit* dut) {
@@ -106,6 +143,12 @@ void drive_defaults(Vcsr_unit* dut) {
dut->trap_ctrl_mtval = 0;
dut->trap_ctrl_mret_en = 0;
dut->trap_ctrl_sret_en = 0;
+ dut->m_timer_interrupt = 0;
+ dut->m_software_interrupt = 0;
+ dut->m_external_interrupt = 0;
+ dut->s_timer_interrupt = 0;
+ dut->s_software_interrupt = 0;
+ dut->s_external_interrupt = 0;
}
void apply_reset(Vcsr_unit* dut) {
@@ -185,6 +228,9 @@ void advance_cycles(Vcsr_unit* dut, int cycles) {
}
void force_priv_mode_via_mret(Vcsr_unit* dut, uint64_t encoded_mode) {
+ if (dut->csr_priv_mode != PRIV_ENCODE_M) {
+ trigger_trap(dut, 0x100ULL, EXC_INST_ILLEGAL, 0ULL, false);
+ }
uint64_t mstatus = read_csr(dut, CSR_MSTATUS);
mstatus &= ~(0x3ULL << MSTATUS_MPP_LO);
mstatus |= (encoded_mode << MSTATUS_MPP_LO);
@@ -231,10 +277,10 @@ int main(int argc, char** argv) {
std::cout << "----------------------------------\n";
// Reset sanity
- assert(read_csr(dut, CSR_MSTATUS) == 0);
+ assert(read_csr(dut, CSR_MSTATUS) == MSTATUS_RESET_VALUE);
assert(read_csr(dut, CSR_MIE) == 0);
assert(read_csr(dut, CSR_MEPC) == 0);
- assert(read_csr(dut, CSR_SSTATUS) == 0);
+ assert(read_csr(dut, CSR_SSTATUS) == SSTATUS_RESET_VALUE);
assert(read_csr(dut, CSR_SATP) == 0);
std::cout << "[PASS] Reset clears CSR state" << std::endl;
@@ -247,30 +293,62 @@ int main(int argc, char** argv) {
// CSRRS / CSRRC behavior
csr_op(dut, CSR_MIE, CSR_OP_WRITE, 0);
- csr_op(dut, CSR_MIE, CSR_OP_SET, 0xBuLL);
- assert(read_csr(dut, CSR_MIE) == 0xBuLL);
- csr_op(dut, CSR_MIE, CSR_OP_CLEAR, 0x3ULL);
- assert(read_csr(dut, CSR_MIE) == 0x8ULL);
+ csr_op(dut, CSR_MIE, CSR_OP_SET, (1ULL << INTERRUPT_M_SOFTWARE) | (1ULL << INTERRUPT_M_TIMER));
+ assert(read_csr(dut, CSR_MIE) == ((1ULL << INTERRUPT_M_SOFTWARE) | (1ULL << INTERRUPT_M_TIMER)));
+ csr_op(dut, CSR_MIE, CSR_OP_CLEAR, (1ULL << INTERRUPT_M_SOFTWARE));
+ assert(read_csr(dut, CSR_MIE) == (1ULL << INTERRUPT_M_TIMER));
csr_op(dut, CSR_MIE, CSR_OP_SET, 0x0ULL);
- assert(read_csr(dut, CSR_MIE) == 0x8ULL);
+ assert(read_csr(dut, CSR_MIE) == (1ULL << INTERRUPT_M_TIMER));
std::cout << "[PASS] CSRRS/CSRRC honor masks and ignore zero data" << std::endl;
- // Read-only CSRs ignore write attempts
+ csr_op(
+ dut,
+ CSR_MSTATUS,
+ CSR_OP_WRITE,
+ (1ULL << MSTATUS_TVM_BIT) | (1ULL << MSTATUS_TW_BIT) | (1ULL << MSTATUS_TSR_BIT)
+ );
+ assert((read_csr(dut, CSR_MSTATUS) & ((1ULL << MSTATUS_TVM_BIT) |
+ (1ULL << MSTATUS_TW_BIT) |
+ (1ULL << MSTATUS_TSR_BIT))) == 0ULL);
+ std::cout << "[PASS] Unsupported mstatus trap-control bits are read-zero" << std::endl;
+
+ // Hardwired WARL CSRs may ignore writes without taking an illegal-instruction trap.
const uint64_t misa_ro_snapshot = read_csr(dut, CSR_MISA);
csr_op(dut, CSR_MISA, CSR_OP_WRITE, 0ULL);
assert(read_csr(dut, CSR_MISA) == misa_ro_snapshot);
- std::cout << "[PASS] Read-only CSRs reject writes" << std::endl;
+ std::cout << "[PASS] Hardwired WARL CSRs ignore unsupported writes" << std::endl;
- // Canonical aliases for cycle/minstret
- csr_op(dut, CSR_CYCLE, CSR_OP_WRITE, 0x40ULL);
+ // Machine counters are writable through machine CSR addresses; user aliases are read-only.
+ csr_op(dut, CSR_MCYCLE, CSR_OP_WRITE, 0x40ULL);
assert(read_csr(dut, CSR_MCYCLE) == 0x40ULL);
+ csr_op(dut, CSR_CYCLE, CSR_OP_WRITE, 0x100ULL);
+ assert(read_csr(dut, CSR_MCYCLE) == 0x41ULL);
advance_cycles(dut, 5);
- assert(read_csr(dut, CSR_MCYCLE) == 0x45ULL);
+ assert(read_csr(dut, CSR_MCYCLE) == 0x46ULL);
const uint64_t instret_before = read_csr(dut, CSR_MINSTRET);
advance_cycles(dut, 3);
assert(read_csr(dut, CSR_MINSTRET) == instret_before + 3);
std::cout << "[PASS] Cycle/instret counters tick correctly" << std::endl;
+ csr_op(dut, CSR_MCOUNTINHIBIT, CSR_OP_WRITE, ~0ULL);
+ assert(read_csr(dut, CSR_MCOUNTINHIBIT) == MCOUNTINHIBIT_WRITABLE_MASK);
+ const uint64_t inhibited_cycle = read_csr(dut, CSR_MCYCLE);
+ const uint64_t inhibited_instret = read_csr(dut, CSR_MINSTRET);
+ advance_cycles(dut, 3);
+ assert(read_csr(dut, CSR_MCYCLE) == inhibited_cycle);
+ assert(read_csr(dut, CSR_MINSTRET) == inhibited_instret);
+ csr_op(dut, CSR_MCOUNTINHIBIT, CSR_OP_WRITE, 0ULL);
+ advance_cycles(dut, 1);
+ assert(read_csr(dut, CSR_MCYCLE) == inhibited_cycle + 1);
+ assert(read_csr(dut, CSR_MINSTRET) == inhibited_instret + 1);
+ std::cout << "[PASS] mcountinhibit controls machine counters" << std::endl;
+
+ csr_op(dut, CSR_MCOUNTEREN, CSR_OP_WRITE, ~0ULL);
+ assert(read_csr(dut, CSR_MCOUNTEREN) == COUNTEREN_WRITABLE_MASK);
+ csr_op(dut, CSR_SCOUNTEREN, CSR_OP_WRITE, ~0ULL);
+ assert(read_csr(dut, CSR_SCOUNTEREN) == COUNTEREN_WRITABLE_MASK);
+ std::cout << "[PASS] counteren CSRs keep only implemented counter bits" << std::endl;
+
// Trap should freeze minstret for that cycle
uint64_t mstatus_cfg = read_csr(dut, CSR_MSTATUS) | (1ULL << MSTATUS_MIE_BIT);
csr_op(dut, CSR_MSTATUS, CSR_OP_WRITE, mstatus_cfg);
@@ -346,21 +424,54 @@ int main(int argc, char** argv) {
std::cout << "[PASS] medeleg routes ecall to supervisor" << std::endl;
force_priv_mode_via_mret(dut, PRIV_ENCODE_M);
+ csr_op(dut, CSR_MEDELEG, CSR_OP_WRITE, ~0ULL);
+ assert((read_csr(dut, CSR_MEDELEG) & MEDELEG_WRITABLE_MASK) == MEDELEG_WRITABLE_MASK);
+ assert((read_csr(dut, CSR_MEDELEG) & MEDELEG_READ_ONLY_ONE_MASK) == MEDELEG_READ_ONLY_ONE_MASK);
+ assert((read_csr(dut, CSR_MEDELEG) & (1ULL << 11)) == 0ULL);
+ csr_op(dut, CSR_MEDELEG, CSR_OP_WRITE, 0ULL);
+ assert((read_csr(dut, CSR_MEDELEG) & MEDELEG_READ_ONLY_ONE_MASK) == MEDELEG_READ_ONLY_ONE_MASK);
+ csr_op(dut, CSR_MIDELEG, CSR_OP_WRITE, ~0ULL);
+ assert((read_csr(dut, CSR_MIDELEG) & MIDELEG_WRITABLE_MASK) == MIDELEG_WRITABLE_MASK);
+ assert((read_csr(dut, CSR_MIDELEG) & (1ULL << INTERRUPT_M_TIMER)) == 0ULL);
+ std::cout << "[PASS] Delegation CSRs expose only writable standard bits" << std::endl;
+
// Basic supervisor CSRs touching for completeness
- csr_op(dut, CSR_SIE, CSR_OP_WRITE, 0x55ULL);
+ const uint64_t s_interrupt_mask = (1ULL << INTERRUPT_S_SOFTWARE) |
+ (1ULL << INTERRUPT_S_TIMER) |
+ (1ULL << INTERRUPT_S_EXTERNAL);
+ csr_op(dut, CSR_SIE, CSR_OP_WRITE, ~0ULL);
csr_op(dut, CSR_SSCRATCH, CSR_OP_WRITE, 0xAAULL);
csr_op(dut, CSR_SEPC, CSR_OP_WRITE, 0x1234ULL);
csr_op(dut, CSR_SCAUSE, CSR_OP_WRITE, 0x21ULL);
- csr_op(dut, CSR_SIP, CSR_OP_WRITE, 0x3ULL);
+ csr_op(dut, CSR_SIP, CSR_OP_WRITE, ~0ULL);
csr_op(dut, CSR_SATP, CSR_OP_WRITE, 0x8000000000000000ULL);
- assert(read_csr(dut, CSR_SIE) == 0x55ULL);
+ assert(read_csr(dut, CSR_SIE) == s_interrupt_mask);
assert(read_csr(dut, CSR_SSCRATCH) == 0xAAULL);
assert(read_csr(dut, CSR_SEPC) == 0x1234ULL);
assert(read_csr(dut, CSR_SCAUSE) == 0x21ULL);
- assert(read_csr(dut, CSR_SIP) == 0x3ULL);
+ assert(read_csr(dut, CSR_SIP) == s_interrupt_mask);
assert(read_csr(dut, CSR_SATP) == 0x8000000000000000ULL);
std::cout << "[PASS] Supervisor space CSR read/write" << std::endl;
+ csr_op(dut, CSR_MIE, CSR_OP_WRITE, 0ULL);
+ csr_op(dut, CSR_SIE, CSR_OP_WRITE, (1ULL << INTERRUPT_S_TIMER));
+ assert(read_csr(dut, CSR_SIE) == (1ULL << INTERRUPT_S_TIMER));
+ assert(read_csr(dut, CSR_MIE) == (1ULL << INTERRUPT_S_TIMER));
+
+ csr_op(dut, CSR_MIP, CSR_OP_WRITE, 0ULL);
+ dut->s_timer_interrupt = 1;
+ dut->eval();
+ assert(read_csr(dut, CSR_SIP) == (1ULL << INTERRUPT_S_TIMER));
+ assert(read_csr(dut, CSR_MIP) == (1ULL << INTERRUPT_S_TIMER));
+ dut->s_timer_interrupt = 0;
+
+ dut->m_external_interrupt = 1;
+ dut->eval();
+ assert((read_csr(dut, CSR_MIP) & (1ULL << INTERRUPT_M_EXTERNAL)) != 0ULL);
+ assert((read_csr(dut, CSR_SIP) & (1ULL << INTERRUPT_M_EXTERNAL)) == 0ULL);
+ dut->m_external_interrupt = 0;
+ std::cout << "[PASS] Interrupt pending and enable CSRs expose delegated views" << std::endl;
+
// Writing SSTATUS should mirror MSTATUS subset bits
csr_op(dut, CSR_SSTATUS, CSR_OP_WRITE, (1ULL << SSTATUS_SIE_BIT));
assert(((read_csr(dut, CSR_MSTATUS) >> SSTATUS_SIE_BIT) & 1ULL) == 1ULL);
@@ -457,8 +568,9 @@ int main(int argc, char** argv) {
});
run_csr_scenario(dut, "nested_return_sequence", [&](Vcsr_unit* target) {
- force_priv_mode_via_mret(target, PRIV_ENCODE_U);
+ force_priv_mode_via_mret(target, PRIV_ENCODE_S);
csr_op(target, CSR_SSTATUS, CSR_OP_WRITE, (1ULL << SSTATUS_SIE_BIT));
+ force_priv_mode_via_mret(target, PRIV_ENCODE_U);
const uint64_t first_trap_pc = 0xB00ULL;
trigger_trap(target, first_trap_pc, EXC_ECALL_FROM_U, 0ULL, true);
@@ -477,7 +589,9 @@ int main(int argc, char** argv) {
assert(target->csr_priv_mode == PRIV_ENCODE_U);
// Enable machine interrupts so the subsequent machine trap captures pre-trap MIE=1
+ force_priv_mode_via_mret(target, PRIV_ENCODE_M);
csr_op(target, CSR_MSTATUS, CSR_OP_SET, (1ULL << MSTATUS_MIE_BIT));
+ force_priv_mode_via_mret(target, PRIV_ENCODE_U);
const uint64_t second_trap_pc = 0xB80ULL;
trigger_trap(target, second_trap_pc, EXC_INST_ILLEGAL, 0ULL, false);
uint64_t mstatus_snapshot = read_csr(target, CSR_MSTATUS);
@@ -499,8 +613,8 @@ int main(int argc, char** argv) {
run_csr_scenario(dut, "illegal_csr_access_guard", [&](Vcsr_unit* target) {
force_priv_mode_via_mret(target, PRIV_ENCODE_M);
assert(target->csr_priv_mode == PRIV_ENCODE_M);
- const uint64_t misa_snapshot = read_csr(target, CSR_MISA);
- target->csr_addr = CSR_MISA;
+ const uint64_t vendor_snapshot = read_csr(target, CSR_MVENDORID);
+ target->csr_addr = CSR_MVENDORID;
target->csr_op = CSR_OP_WRITE;
target->csr_rs1_data = 0x1234ULL;
target->csr_req = 1;
@@ -510,12 +624,79 @@ int main(int argc, char** argv) {
target->csr_rs1_data = 0;
target->csr_addr = 0;
assert(target->csr_illegal_access);
- assert(read_csr(target, CSR_MISA) == misa_snapshot);
+ assert(read_csr(target, CSR_MVENDORID) == vendor_snapshot);
assert(!target->csr_trap_to_s_mode);
assert(target->csr_priv_mode == PRIV_ENCODE_M);
assert(target->csr_trap_vector_next == read_csr(target, CSR_MTVEC));
tick(target);
assert(!target->csr_illegal_access);
+
+ force_priv_mode_via_mret(target, PRIV_ENCODE_S);
+ assert(target->csr_priv_mode == PRIV_ENCODE_S);
+ target->csr_addr = CSR_MSTATUS;
+ target->csr_op = CSR_OP_WRITE;
+ target->csr_rs1_data = 0ULL;
+ target->csr_req = 1;
+ target->eval();
+ assert(target->csr_illegal_access);
+ tick(target);
+ target->csr_req = 0;
+ target->csr_op = 0;
+ target->csr_rs1_data = 0;
+ target->csr_addr = 0;
+ target->eval();
+ assert(!target->csr_illegal_access);
+
+ force_priv_mode_via_mret(target, PRIV_ENCODE_M);
+ csr_op(target, CSR_MCOUNTEREN, CSR_OP_WRITE, 0ULL);
+ force_priv_mode_via_mret(target, PRIV_ENCODE_S);
+ target->csr_addr = CSR_CYCLE;
+ target->csr_op = 0;
+ target->csr_rs1_data = 0ULL;
+ target->csr_req = 1;
+ target->eval();
+ assert(target->csr_illegal_access);
+ tick(target);
+ target->csr_req = 0;
+ target->csr_addr = 0;
+ target->eval();
+
+ force_priv_mode_via_mret(target, PRIV_ENCODE_M);
+ csr_op(target, CSR_MCOUNTEREN, CSR_OP_WRITE, 1ULL);
+ csr_op(target, CSR_SCOUNTEREN, CSR_OP_WRITE, 0ULL);
+ force_priv_mode_via_mret(target, PRIV_ENCODE_U);
+ target->csr_addr = CSR_CYCLE;
+ target->csr_op = 0;
+ target->csr_rs1_data = 0ULL;
+ target->csr_req = 1;
+ target->eval();
+ assert(target->csr_illegal_access);
+ tick(target);
+ target->csr_req = 0;
+ target->csr_addr = 0;
+ target->eval();
+
+ force_priv_mode_via_mret(target, PRIV_ENCODE_M);
+ csr_op(target, CSR_MSTATEEN0, CSR_OP_WRITE, 0ULL);
+ force_priv_mode_via_mret(target, PRIV_ENCODE_S);
+ target->csr_addr = CSR_SENVCFG;
+ target->csr_op = CSR_OP_WRITE;
+ target->csr_rs1_data = 1ULL;
+ target->csr_req = 1;
+ target->eval();
+ assert(target->csr_illegal_access);
+ tick(target);
+ target->csr_req = 0;
+ target->csr_op = 0;
+ target->csr_rs1_data = 0;
+ target->csr_addr = 0;
+ target->eval();
+
+ force_priv_mode_via_mret(target, PRIV_ENCODE_M);
+ csr_op(target, CSR_MSTATEEN0, CSR_OP_WRITE, (1ULL << STATEEN0_ENVCFG_BIT));
+ force_priv_mode_via_mret(target, PRIV_ENCODE_S);
+ csr_op(target, CSR_SENVCFG, CSR_OP_WRITE, 1ULL);
+ assert((read_csr(target, CSR_SENVCFG) & 1ULL) == 1ULL);
});
run_csr_scenario(dut, "delegation_cross_over", [&](Vcsr_unit* target) {
@@ -535,8 +716,8 @@ int main(int argc, char** argv) {
const std::array<DelegationProbe, 4> probes = {{
{"medeleg_set", false, EXC_ECALL_FROM_U, true, 0xD000ULL},
{"medeleg_clear", false, EXC_ECALL_FROM_U, false, 0xD040ULL},
- {"mideleg_set", true, INTERRUPT_M_TIMER, true, 0xD080ULL},
- {"mideleg_clear", true, INTERRUPT_M_TIMER, false, 0xD0C0ULL}
+ {"mideleg_set", true, INTERRUPT_S_TIMER, true, 0xD080ULL},
+ {"mideleg_clear", true, INTERRUPT_S_TIMER, false, 0xD0C0ULL}
}};
for (const auto& probe : probes) {
diff --git a/rv64cpu/tb/unit/tb_decoder.cc b/rv64cpu/tb/unit/tb_decoder.cc
index 25a3d69..1dcfb82 100644
--- a/rv64cpu/tb/unit/tb_decoder.cc
+++ b/rv64cpu/tb/unit/tb_decoder.cc
@@ -225,6 +225,55 @@ int main(int argc, char** argv) {
CHECK(dut, alu_op, 8, "SUBW"); // ALU_SUB
CHECK(dut, is_word_op, 1, "SUBW");
+ decode(dut, 0x023100B3, "MUL x1, x2, x3");
+ CHECK(dut, alu_op, 16, "MUL");
+ CHECK(dut, reg_write_en, 1, "MUL");
+ CHECK(dut, illegal_instr,0, "MUL");
+
+ decode(dut, 0x023140B3, "DIV x1, x2, x3");
+ CHECK(dut, alu_op, 20, "DIV");
+ CHECK(dut, illegal_instr,0, "DIV");
+
+ decode(dut, 0x023160B3, "REM x1, x2, x3");
+ CHECK(dut, alu_op, 22, "REM");
+ CHECK(dut, illegal_instr,0, "REM");
+
+ decode(dut, 0x023100BB, "MULW x1, x2, x3");
+ CHECK(dut, alu_op, 16, "MULW");
+ CHECK(dut, is_word_op, 1, "MULW");
+ CHECK(dut, illegal_instr,0, "MULW");
+
+ decode(dut, 0x0021A0AF, "AMOADD.W x1, x2, (x3)");
+ CHECK(dut, is_amo, 1, "AMOADD.W");
+ CHECK(dut, amo_op, 4, "AMOADD.W");
+ CHECK(dut, mem_size, 2, "AMOADD.W");
+ CHECK(dut, wb_sel, 1, "AMOADD.W");
+ CHECK(dut, reg_write_en, 1, "AMOADD.W");
+ CHECK(dut, illegal_instr,0, "AMOADD.W");
+
+ decode(dut, 0x100332AF, "LR.D x5, (x6)");
+ CHECK(dut, is_amo, 1, "LR.D");
+ CHECK(dut, amo_op, 1, "LR.D");
+ CHECK(dut, mem_size, 3, "LR.D");
+ CHECK(dut, rs2_addr, 0, "LR.D");
+ CHECK(dut, illegal_instr,0, "LR.D");
+
+ decode(dut, 0x1884B3AF, "SC.D x7, x8, (x9)");
+ CHECK(dut, is_amo, 1, "SC.D");
+ CHECK(dut, amo_op, 2, "SC.D");
+ CHECK(dut, mem_size, 3, "SC.D");
+ CHECK(dut, illegal_instr,0, "SC.D");
+
+ decode(dut, 0x0EB6252F, "AMOSWAP.W.AQRL x10, x11, (x12)");
+ CHECK(dut, is_amo, 1, "AMOSWAP.W.AQRL");
+ CHECK(dut, amo_op, 3, "AMOSWAP.W.AQRL");
+ CHECK(dut, illegal_instr,0, "AMOSWAP.W.AQRL");
+
+ decode(dut, 0x105332AF, "Illegal LR.D with rs2 != x0");
+ CHECK(dut, is_amo, 0, "Illegal LR.D");
+ CHECK(dut, reg_write_en, 0, "Illegal LR.D");
+ CHECK(dut, illegal_instr,1, "Illegal LR.D");
+
// 19. CSRRW x1, mstatus(0x300), x2
// imm=001100000000 rs1=00010 funct3=001 rd=00001 op=1110011
// Tests: is_csr, csr_op=funct3, reg_write=1
@@ -301,4 +350,4 @@ int main(int argc, char** argv) {
delete dut;
return (fail_count > 0) ? 1 : 0;
-} \ No newline at end of file
+}
diff --git a/rv64cpu/tb/unit/tb_hazard_unit.cc b/rv64cpu/tb/unit/tb_hazard_unit.cc
index d43ccc5..6314ab8 100644
--- a/rv64cpu/tb/unit/tb_hazard_unit.cc
+++ b/rv64cpu/tb/unit/tb_hazard_unit.cc
@@ -45,6 +45,7 @@ void reset(Vhazard_unit* dut) {
dut->ex_branch_target = 0;
dut->mem_rd_addr = 0;
dut->mem_reg_write_en = 0;
+ dut->mem_result_late = 0;
dut->mem_stall_req = 0;
dut->trap_flush_req = 0;
dut->trap_pc_sel = 0;
@@ -139,6 +140,26 @@ int main(int argc, char** argv) {
dut->eval();
check(dut->forward_b_sel == 1, "FwdB_MEM_Priority_Over_WB");
+ reset(dut);
+ dut->ex_rs1_addr = 3;
+ dut->mem_rd_addr = 3;
+ dut->mem_reg_write_en = 1;
+ dut->mem_result_late = 1;
+ dut->wb_rd_addr = 3;
+ dut->wb_reg_write_en = 1;
+ dut->eval();
+ check(dut->forward_a_sel == 2, "LateResult_ForwardA_From_WB");
+
+ reset(dut);
+ dut->ex_rs2_addr = 3;
+ dut->mem_rd_addr = 3;
+ dut->mem_reg_write_en = 1;
+ dut->mem_result_late = 1;
+ dut->wb_rd_addr = 3;
+ dut->wb_reg_write_en = 1;
+ dut->eval();
+ check(dut->forward_b_sel == 2, "LateResult_ForwardB_From_WB");
+
// -----------------------
// 7. No forward to x0 (hardwired zero)
// -----------------------
diff --git a/rv64cpu/tb/unit/tb_mem_stage.cc b/rv64cpu/tb/unit/tb_mem_stage.cc
index 2747ca9..417cb95 100644
--- a/rv64cpu/tb/unit/tb_mem_stage.cc
+++ b/rv64cpu/tb/unit/tb_mem_stage.cc
@@ -28,6 +28,15 @@ void tick(Vmem_stage* dut) {
dut->eval();
}
+void clock_tick(Vmem_stage* dut) {
+ dut->clk = 0;
+ dut->eval();
+ dut->clk = 1;
+ dut->eval();
+ dut->clk = 0;
+ dut->eval();
+}
+
void check(bool cond, const char* name) {
if (cond) {
std::cout << "\033[32m[PASS] " << name << "\033[0m" << std::endl;
@@ -47,15 +56,21 @@ int main(int argc, char** argv) {
std::cout << "----------------------------------" << std::endl;
// 1. Initial / Default states
+ dut->clk = 0;
+ dut->rst = 1;
dut->mem_alu_result = 0;
dut->mem_rs2_rdata = 0;
dut->mem_is_load = 0;
dut->mem_is_store = 0;
+ dut->mem_is_amo = 0;
+ dut->mem_amo_op = 0;
dut->mem_mem_size = 0;
dut->dmem_req_ready = 1;
dut->dmem_rsp_valid = 1;
dut->dmem_rdata = 0;
+ clock_tick(dut);
+ dut->rst = 0;
tick(dut);
check(dut->dmem_read_req == 0, "No_Read_Req");
@@ -186,7 +201,87 @@ int main(int argc, char** argv) {
tick(dut);
check(dut->mem_wb_mem_rdata == 0xAABBCCDDEEFF0011ULL, "LD_Data");
- // 5. Stall Logic
+ dut->mem_is_load = 0;
+
+ // 5. Atomic memory operations
+ dut->mem_is_amo = 1;
+ dut->mem_mem_size = 2; // AMO.W
+ dut->mem_amo_op = 4; // AMO_ADD
+ dut->mem_alu_result = 0x3004;
+ dut->mem_rs2_rdata = 3;
+ dut->dmem_rdata = 0x0000000500000000ULL;
+ dut->dmem_req_ready = 1;
+ dut->dmem_rsp_valid = 1;
+ tick(dut);
+ check(dut->dmem_read_req == 1, "AMOADDW_ReadReq");
+ check(dut->dmem_write_req == 0, "AMOADDW_NoWriteDuringRead");
+ check(dut->mem_stall_req == 1, "AMOADDW_StallDuringRead");
+ clock_tick(dut);
+
+ tick(dut);
+ check(dut->dmem_read_req == 0, "AMOADDW_NoReadDuringWrite");
+ check(dut->dmem_write_req == 1, "AMOADDW_WriteReq");
+ check(dut->dmem_wdata == 0x0000000800000000ULL, "AMOADDW_WriteData");
+ check(dut->dmem_wstrb == 0xF0, "AMOADDW_Strobe");
+ check(dut->mem_wb_mem_rdata == 5, "AMOADDW_ReturnOldValue");
+ check(dut->mem_stall_req == 0, "AMOADDW_NoStallDuringWrite");
+ clock_tick(dut);
+
+ dut->mem_mem_size = 3; // AMO.D
+ dut->mem_amo_op = 1; // AMO_LR
+ dut->mem_alu_result = 0x4000;
+ dut->dmem_rdata = 0x123456789ABCDEF0ULL;
+ tick(dut);
+ check(dut->dmem_read_req == 1, "LRD_ReadReq");
+ check(dut->dmem_write_req == 0, "LRD_NoWrite");
+ check(dut->mem_wb_mem_rdata == 0x123456789ABCDEF0ULL, "LRD_ReturnData");
+ clock_tick(dut);
+
+ dut->mem_amo_op = 2; // AMO_SC
+ dut->mem_rs2_rdata = 0xCAFEBABE11223344ULL;
+ tick(dut);
+ check(dut->dmem_read_req == 0, "SCD_NoRead");
+ check(dut->dmem_write_req == 1, "SCD_SuccessWrite");
+ check(dut->dmem_wdata == 0xCAFEBABE11223344ULL, "SCD_WriteData");
+ check(dut->mem_wb_mem_rdata == 0, "SCD_SuccessStatus");
+ clock_tick(dut);
+
+ tick(dut);
+ check(dut->dmem_write_req == 0, "SCD_FailNoWrite");
+ check(dut->mem_wb_mem_rdata == 1, "SCD_FailStatus");
+
+ dut->mem_amo_op = 1; // AMO_LR
+ dut->mem_alu_result = 0x5000;
+ dut->dmem_rdata = 0x1122334455667788ULL;
+ tick(dut);
+ check(dut->dmem_read_req == 1, "LRD_BeforeStore_ReadReq");
+ clock_tick(dut);
+
+ dut->mem_is_amo = 0;
+ dut->mem_is_store = 1;
+ dut->mem_mem_size = 0; // SB
+ dut->mem_alu_result = 0x5003;
+ dut->mem_rs2_rdata = 0xAA;
+ tick(dut);
+ check(dut->dmem_write_req == 1, "StoreAfterLR_WriteReq");
+ clock_tick(dut);
+
+ dut->mem_is_store = 0;
+ dut->mem_is_amo = 1;
+ dut->mem_mem_size = 3; // AMO.D
+ dut->mem_amo_op = 2; // AMO_SC
+ dut->mem_alu_result = 0x5000;
+ dut->mem_rs2_rdata = 0x0102030405060708ULL;
+ tick(dut);
+ check(dut->dmem_write_req == 1, "SCD_AfterStore_WriteReq");
+ check(dut->mem_wb_mem_rdata == 0, "SCD_AfterStore_SuccessStatus");
+
+ dut->mem_is_amo = 0;
+ dut->mem_is_store = 0;
+
+ // 6. Stall Logic
+ dut->mem_is_load = 1;
+ dut->mem_mem_size = 3;
dut->dmem_req_ready = 0; // Cache miss / AXI busy
dut->dmem_rsp_valid = 0;
tick(dut);
diff --git a/rv64cpu/tb/unit/tb_mmu_top.cc b/rv64cpu/tb/unit/tb_mmu_top.cc
new file mode 100644
index 0000000..5df90ef
--- /dev/null
+++ b/rv64cpu/tb/unit/tb_mmu_top.cc
@@ -0,0 +1,236 @@
+#include <cassert>
+#include <cstdint>
+#include <iostream>
+#include <unordered_map>
+
+#include <verilated.h>
+
+#include "Vmmu_top.h"
+
+namespace {
+
+constexpr uint8_t MODE_BARE = 0;
+constexpr uint8_t MODE_SV39 = 8;
+constexpr uint8_t MODE_SV48 = 9;
+
+constexpr uint64_t FLAG_V = 1ULL << 0;
+constexpr uint64_t FLAG_R = 1ULL << 1;
+constexpr uint64_t FLAG_W = 1ULL << 2;
+constexpr uint64_t FLAG_X = 1ULL << 3;
+constexpr uint64_t FLAG_A = 1ULL << 6;
+constexpr uint64_t FLAG_D = 1ULL << 7;
+
+int g_pass_count = 0;
+
+void check(bool cond, const char* name) {
+ if (cond) {
+ std::cout << "\033[32m[PASS] " << name << "\033[0m\n";
+ ++g_pass_count;
+ } else {
+ std::cout << "\033[31m[FAIL] " << name << "\033[0m\n";
+ assert(false);
+ }
+}
+
+uint64_t make_satp(uint8_t mode, uint16_t asid, uint64_t ppn) {
+ return (uint64_t(mode) << 60) | (uint64_t(asid) << 44) | (ppn & ((1ULL << 44) - 1ULL));
+}
+
+uint64_t make_pte(uint64_t ppn, uint64_t flags) {
+ return ((ppn & ((1ULL << 44) - 1ULL)) << 10) | (flags & 0x3ffULL);
+}
+
+void tick(Vmmu_top* dut) {
+ dut->clk = 0;
+ dut->eval();
+ dut->clk = 1;
+ dut->eval();
+}
+
+void reset(Vmmu_top* dut) {
+ dut->rst = 1;
+ dut->i_req_valid = 0;
+ dut->d_req_valid = 0;
+ dut->d_req_is_store = 0;
+ dut->flush_all = 0;
+ dut->flush_asid_valid = 0;
+ dut->flush_asid = 0;
+ dut->flush_vpn_valid = 0;
+ dut->flush_vpn = 0;
+ dut->ptw_mem_req_ready = 1;
+ dut->ptw_mem_resp_valid = 0;
+ dut->ptw_mem_resp_data = 0;
+ tick(dut);
+ tick(dut);
+ dut->rst = 0;
+ tick(dut);
+}
+
+struct StepResult {
+ bool issued;
+ uint64_t req_addr;
+ bool i_resp_valid;
+ bool i_resp_hit;
+ bool i_resp_page_fault;
+ uint64_t i_resp_paddr;
+ bool d_resp_valid;
+ bool d_resp_hit;
+ bool d_resp_page_fault;
+ uint64_t d_resp_paddr;
+};
+
+StepResult step(Vmmu_top* dut, bool inject_resp, uint64_t resp_data) {
+ dut->ptw_mem_req_ready = 1;
+ dut->ptw_mem_resp_valid = inject_resp ? 1 : 0;
+ dut->ptw_mem_resp_data = resp_data;
+
+ dut->clk = 0;
+ dut->eval();
+ const bool issued = dut->ptw_mem_req_valid && dut->ptw_mem_req_ready;
+ const uint64_t req_addr = dut->ptw_mem_req_addr;
+ StepResult result{
+ issued,
+ req_addr,
+ bool(dut->i_resp_valid),
+ bool(dut->i_resp_hit),
+ bool(dut->i_resp_page_fault),
+ dut->i_resp_paddr,
+ bool(dut->d_resp_valid),
+ bool(dut->d_resp_hit),
+ bool(dut->d_resp_page_fault),
+ dut->d_resp_paddr,
+ };
+
+ dut->clk = 1;
+ dut->eval();
+
+ return result;
+}
+
+void add_sv39_mapping(
+ std::unordered_map<uint64_t, uint64_t>& memory,
+ uint64_t root_ppn,
+ uint64_t vaddr,
+ uint64_t l1_ppn,
+ uint64_t l0_ppn,
+ uint64_t leaf_ppn,
+ uint64_t leaf_flags
+) {
+ const uint64_t vpn2 = (vaddr >> 30) & 0x1ffULL;
+ const uint64_t vpn1 = (vaddr >> 21) & 0x1ffULL;
+ const uint64_t vpn0 = (vaddr >> 12) & 0x1ffULL;
+ memory[(root_ppn << 12) + (vpn2 << 3)] = make_pte(l1_ppn, FLAG_V);
+ memory[(l1_ppn << 12) + (vpn1 << 3)] = make_pte(l0_ppn, FLAG_V);
+ memory[(l0_ppn << 12) + (vpn0 << 3)] = make_pte(leaf_ppn, leaf_flags);
+}
+
+StepResult run_until_response(
+ Vmmu_top* dut,
+ const std::unordered_map<uint64_t, uint64_t>& memory,
+ bool want_d
+) {
+ bool inject_resp = false;
+ uint64_t next_resp = 0;
+ StepResult last{};
+
+ for (int cycle = 0; cycle < 100; ++cycle) {
+ last = step(dut, inject_resp, next_resp);
+ if ((!want_d && last.i_resp_valid) || (want_d && last.d_resp_valid)) {
+ return last;
+ }
+
+ inject_resp = false;
+ next_resp = 0;
+ if (last.issued) {
+ const auto it = memory.find(last.req_addr);
+ next_resp = (it == memory.end()) ? 0 : it->second;
+ inject_resp = true;
+ }
+ }
+
+ check(false, "MMU_ResponseTimeout");
+ return last;
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ Verilated::commandArgs(argc, argv);
+ auto* dut = new Vmmu_top;
+
+ std::cout << "----------------------------------\n";
+ std::cout << " MMU Top Unit Test\n";
+ std::cout << "----------------------------------\n";
+
+ dut->satp_value = make_satp(MODE_BARE, 0, 0);
+ dut->priv_mode = 1;
+ dut->sum = 0;
+ dut->mxr = 0;
+ reset(dut);
+
+ dut->i_req_valid = 1;
+ dut->i_req_vaddr = 0x80001234ULL;
+ dut->d_req_valid = 1;
+ dut->d_req_vaddr = 0x90005678ULL;
+ dut->d_req_is_store = 0;
+ dut->eval();
+ check(dut->i_resp_valid && dut->i_resp_hit && dut->i_resp_paddr == 0x80001234ULL, "Bare_I_Bypass");
+ check(dut->d_resp_valid && dut->d_resp_hit && dut->d_resp_paddr == 0x90005678ULL, "Bare_D_Bypass");
+ dut->i_req_valid = 0;
+ dut->d_req_valid = 0;
+ tick(dut);
+
+ dut->satp_value = make_satp(MODE_SV48, 1, 0x100);
+ dut->i_req_valid = 1;
+ dut->i_req_vaddr = 0x0000000040401000ULL;
+ dut->eval();
+ check(dut->i_resp_valid && dut->i_resp_page_fault, "Sv48_Stub_IFault");
+ dut->i_req_valid = 0;
+ tick(dut);
+
+ const uint64_t root_ppn = 0x100;
+ const uint64_t i_va = 0x0000000040401234ULL;
+ const uint64_t d_va = 0x0000000080505678ULL;
+ const uint64_t i_leaf_ppn = 0x45670;
+ const uint64_t d_leaf_ppn = 0x56780;
+ std::unordered_map<uint64_t, uint64_t> memory;
+ add_sv39_mapping(memory, root_ppn, i_va, 0x210, 0x220, i_leaf_ppn,
+ FLAG_V | FLAG_R | FLAG_X | FLAG_A);
+ add_sv39_mapping(memory, root_ppn, d_va, 0x230, 0x240, d_leaf_ppn,
+ FLAG_V | FLAG_R | FLAG_W | FLAG_A | FLAG_D);
+
+ dut->satp_value = make_satp(MODE_SV39, 1, root_ppn);
+ dut->i_req_valid = 1;
+ dut->i_req_vaddr = i_va;
+ StepResult i_resp = run_until_response(dut, memory, false);
+ check(i_resp.i_resp_hit && !i_resp.i_resp_page_fault, "Sv39_I_Walk_Hit");
+ check(i_resp.i_resp_paddr == ((i_leaf_ppn << 12) | (i_va & 0xfffULL)), "Sv39_I_Walk_Paddr");
+ dut->i_req_valid = 0;
+ tick(dut);
+
+ dut->d_req_valid = 1;
+ dut->d_req_vaddr = d_va;
+ dut->d_req_is_store = 1;
+ StepResult d_resp = run_until_response(dut, memory, true);
+ check(d_resp.d_resp_hit && !d_resp.d_resp_page_fault, "Sv39_D_Walk_Hit");
+ check(d_resp.d_resp_paddr == ((d_leaf_ppn << 12) | (d_va & 0xfffULL)), "Sv39_D_Walk_Paddr");
+
+ dut->d_req_valid = 0;
+ tick(dut);
+ dut->i_req_valid = 1;
+ dut->i_req_vaddr = i_va;
+ dut->d_req_valid = 1;
+ dut->d_req_vaddr = d_va;
+ dut->d_req_is_store = 1;
+ dut->eval();
+ check(dut->i_resp_valid && dut->i_resp_hit, "Sv39_I_TLB_Hit");
+ check(dut->d_resp_valid && dut->d_resp_hit, "Sv39_D_TLB_Hit");
+ check(dut->ptw_mem_req_valid == 0, "Sv39_TLB_Hit_NoPTW");
+
+ std::cout << "----------------------------------\n";
+ std::cout << "All " << g_pass_count << " MMU top tests passed!\n";
+ std::cout << "----------------------------------\n";
+
+ delete dut;
+ return 0;
+}
diff --git a/rv64cpu/tb/unit/tb_ptw.cc b/rv64cpu/tb/unit/tb_ptw.cc
new file mode 100644
index 0000000..f32de41
--- /dev/null
+++ b/rv64cpu/tb/unit/tb_ptw.cc
@@ -0,0 +1,206 @@
+#include <cassert>
+#include <cstdint>
+#include <iostream>
+#include <unordered_map>
+
+#include <verilated.h>
+
+#include "Vptw.h"
+
+namespace {
+
+constexpr uint8_t MODE_BARE = 0;
+constexpr uint8_t MODE_SV39 = 8;
+constexpr uint8_t MODE_SV48 = 9;
+constexpr uint8_t MODE_SV57 = 10;
+constexpr uint8_t MODE_SV64 = 11;
+
+constexpr uint64_t FLAG_V = 1ULL << 0;
+constexpr uint64_t FLAG_R = 1ULL << 1;
+constexpr uint64_t FLAG_W = 1ULL << 2;
+constexpr uint64_t FLAG_X = 1ULL << 3;
+constexpr uint64_t FLAG_A = 1ULL << 6;
+constexpr uint64_t FLAG_D = 1ULL << 7;
+
+int g_pass_count = 0;
+
+void check(bool cond, const char* name) {
+ if (cond) {
+ std::cout << "\033[32m[PASS] " << name << "\033[0m\n";
+ ++g_pass_count;
+ } else {
+ std::cout << "\033[31m[FAIL] " << name << "\033[0m\n";
+ assert(false);
+ }
+}
+
+uint64_t make_satp(uint8_t mode, uint16_t asid, uint64_t ppn) {
+ return (uint64_t(mode) << 60) | (uint64_t(asid) << 44) | (ppn & ((1ULL << 44) - 1ULL));
+}
+
+uint64_t make_pte(uint64_t ppn, uint64_t flags) {
+ return ((ppn & ((1ULL << 44) - 1ULL)) << 10) | (flags & 0x3ffULL);
+}
+
+void tick(Vptw* dut) {
+ dut->clk = 0;
+ dut->eval();
+ dut->clk = 1;
+ dut->eval();
+}
+
+void reset(Vptw* dut) {
+ dut->rst = 1;
+ dut->req_valid = 0;
+ dut->mem_req_ready = 1;
+ dut->mem_resp_valid = 0;
+ dut->mem_resp_data = 0;
+ tick(dut);
+ tick(dut);
+ dut->rst = 0;
+ tick(dut);
+}
+
+void launch(
+ Vptw* dut,
+ uint64_t vaddr,
+ uint8_t mode,
+ bool fetch,
+ bool store,
+ uint64_t satp
+) {
+ for (int i = 0; i < 8 && !dut->req_ready; ++i) {
+ dut->req_valid = 0;
+ dut->mem_resp_valid = 0;
+ tick(dut);
+ }
+
+ dut->req_valid = 1;
+ dut->req_vaddr = vaddr;
+ dut->req_asid = 1;
+ dut->req_mode = mode;
+ dut->req_priv = 1;
+ dut->req_is_fetch = fetch;
+ dut->req_is_store = store;
+ dut->req_sum = 0;
+ dut->req_mxr = 0;
+ dut->satp_value = satp;
+ tick(dut);
+ dut->req_valid = 0;
+}
+
+void run_walk(Vptw* dut, const std::unordered_map<uint64_t, uint64_t>& memory) {
+ bool inject_resp = false;
+ uint64_t next_resp_data = 0;
+
+ for (int cycle = 0; cycle < 80; ++cycle) {
+ dut->mem_req_ready = 1;
+ dut->mem_resp_valid = inject_resp ? 1 : 0;
+ dut->mem_resp_data = next_resp_data;
+
+ dut->clk = 0;
+ dut->eval();
+
+ if (dut->resp_valid) {
+ dut->mem_resp_valid = 0;
+ return;
+ }
+
+ const bool issued = dut->mem_req_valid && dut->mem_req_ready;
+ const uint64_t req_addr = dut->mem_req_addr;
+
+ dut->clk = 1;
+ dut->eval();
+
+ inject_resp = false;
+ next_resp_data = 0;
+ if (issued) {
+ const auto it = memory.find(req_addr);
+ next_resp_data = (it == memory.end()) ? 0 : it->second;
+ inject_resp = true;
+ }
+ }
+
+ check(false, "PTW_Timeout");
+}
+
+void add_sv39_mapping(
+ std::unordered_map<uint64_t, uint64_t>& memory,
+ uint64_t root_ppn,
+ uint64_t vaddr,
+ uint64_t l1_ppn,
+ uint64_t l0_ppn,
+ uint64_t leaf_ppn,
+ uint64_t leaf_flags
+) {
+ const uint64_t vpn2 = (vaddr >> 30) & 0x1ffULL;
+ const uint64_t vpn1 = (vaddr >> 21) & 0x1ffULL;
+ const uint64_t vpn0 = (vaddr >> 12) & 0x1ffULL;
+ memory[(root_ppn << 12) + (vpn2 << 3)] = make_pte(l1_ppn, FLAG_V);
+ memory[(l1_ppn << 12) + (vpn1 << 3)] = make_pte(l0_ppn, FLAG_V);
+ memory[(l0_ppn << 12) + (vpn0 << 3)] = make_pte(leaf_ppn, leaf_flags);
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ Verilated::commandArgs(argc, argv);
+ auto* dut = new Vptw;
+
+ std::cout << "----------------------------------\n";
+ std::cout << " PTW Unit Test\n";
+ std::cout << "----------------------------------\n";
+
+ reset(dut);
+
+ launch(dut, 0x0000000040401000ULL, MODE_SV48, false, false, make_satp(MODE_BARE, 1, 0x100));
+ run_walk(dut, {});
+ check(dut->resp_fault == 1, "Sv48_Stub_Fault");
+
+ launch(dut, 0x0000000040401000ULL, MODE_SV57, false, false, make_satp(MODE_BARE, 1, 0x100));
+ run_walk(dut, {});
+ check(dut->resp_fault == 1, "Sv57_Stub_Fault");
+
+ launch(dut, 0x0000000040401000ULL, MODE_SV64, false, false, make_satp(MODE_BARE, 1, 0x100));
+ run_walk(dut, {});
+ check(dut->resp_fault == 1, "Sv64_Stub_Fault");
+
+ const uint64_t root_ppn = 0x100;
+ const uint64_t va = 0x0000000040401234ULL;
+ const uint64_t leaf_ppn = 0x45670;
+ std::unordered_map<uint64_t, uint64_t> mem_ok;
+ add_sv39_mapping(mem_ok, root_ppn, va, 0x210, 0x220, leaf_ppn,
+ FLAG_V | FLAG_R | FLAG_W | FLAG_A | FLAG_D);
+
+ launch(dut, va, MODE_SV39, false, false, make_satp(MODE_SV39, 1, root_ppn));
+ run_walk(dut, mem_ok);
+ check(dut->resp_fault == 0, "Sv39_Success_NoFault");
+ check(dut->resp_paddr == ((leaf_ppn << 12) | (va & 0xfffULL)), "Sv39_Success_Paddr");
+ check(dut->refill_valid == 1, "Sv39_Success_Refill");
+ check(dut->refill_level == 0, "Sv39_Success_Level0");
+
+ std::unordered_map<uint64_t, uint64_t> mem_no_x;
+ add_sv39_mapping(mem_no_x, root_ppn, va, 0x230, 0x240, 0x50000, FLAG_V | FLAG_R | FLAG_A);
+ launch(dut, va, MODE_SV39, true, false, make_satp(MODE_SV39, 1, root_ppn));
+ run_walk(dut, mem_no_x);
+ check(dut->resp_fault == 1, "Sv39_Fetch_NoX_Fault");
+ check(dut->refill_valid == 0, "Sv39_Fetch_NoX_NoRefill");
+
+ std::unordered_map<uint64_t, uint64_t> mem_store_d0;
+ add_sv39_mapping(mem_store_d0, root_ppn, va, 0x250, 0x260, 0x60000,
+ FLAG_V | FLAG_R | FLAG_W | FLAG_A);
+ launch(dut, va, MODE_SV39, false, true, make_satp(MODE_SV39, 1, root_ppn));
+ run_walk(dut, mem_store_d0);
+ check(dut->resp_fault == 1, "Sv39_Store_D0_Fault");
+
+ launch(dut, 0x0000008000000000ULL, MODE_SV39, false, false, make_satp(MODE_SV39, 1, root_ppn));
+ run_walk(dut, {});
+ check(dut->resp_fault == 1, "Sv39_NonCanonical_Fault");
+
+ std::cout << "----------------------------------\n";
+ std::cout << "All " << g_pass_count << " PTW tests passed!\n";
+ std::cout << "----------------------------------\n";
+
+ delete dut;
+ return 0;
+}
diff --git a/rv64cpu/tb/unit/tb_tlb.cc b/rv64cpu/tb/unit/tb_tlb.cc
index f244fbe..0a57738 100644
--- a/rv64cpu/tb/unit/tb_tlb.cc
+++ b/rv64cpu/tb/unit/tb_tlb.cc
@@ -1,19 +1,3 @@
-// Richard CPU
-// Copyright (C) 2026 Richard Qin
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see <https://www.gnu.org/licenses/>.
-
-
#include <cassert>
#include <cstdint>
#include <iostream>
@@ -25,9 +9,10 @@
namespace {
constexpr uint8_t MODE_BARE = 0;
-constexpr uint8_t MODE_SV32 = 1;
+constexpr uint8_t MODE_SV39 = 8;
constexpr uint8_t MODE_SV48 = 9;
constexpr uint8_t MODE_SV57 = 10;
+constexpr uint8_t MODE_SV64 = 11;
int g_pass_count = 0;
@@ -41,10 +26,6 @@ void check(bool cond, const char* name) {
}
}
-void eval_comb(Vtlb* dut) {
- dut->eval();
-}
-
void tick(Vtlb* dut) {
dut->clk = 0;
dut->eval();
@@ -52,27 +33,11 @@ void tick(Vtlb* dut) {
dut->eval();
}
-uint64_t make_paddr(uint64_t ppn, uint64_t vaddr) {
- return (ppn << 12) | (vaddr & 0xFFFULL);
-}
-
-uint64_t sv48_vpn(uint64_t vaddr) {
- return (vaddr >> 12) & ((1ULL << 36) - 1ULL);
-}
-
-uint64_t sv32_vpn(uint64_t vaddr) {
- return (vaddr >> 12) & ((1ULL << 20) - 1ULL);
-}
-
-uint64_t sv57_vpn(uint64_t vaddr) {
- return (vaddr >> 12) & ((1ULL << 45) - 1ULL);
-}
-
void clear_req(Vtlb* dut) {
dut->req_valid = 0;
dut->req_vaddr = 0;
dut->req_asid = 0;
- dut->req_mode = MODE_SV48;
+ dut->req_mode = MODE_SV39;
dut->req_priv = 1;
dut->req_is_fetch = 0;
dut->req_is_store = 0;
@@ -85,7 +50,7 @@ void clear_refill(Vtlb* dut) {
dut->refill_vpn = 0;
dut->refill_ppn = 0;
dut->refill_level = 0;
- dut->refill_mode = MODE_SV48;
+ dut->refill_mode = MODE_SV39;
dut->refill_asid = 0;
dut->refill_g = 0;
dut->refill_u = 0;
@@ -102,34 +67,7 @@ void clear_flush(Vtlb* dut) {
dut->flush_asid = 0;
dut->flush_vpn_valid = 0;
dut->flush_vpn = 0;
- dut->flush_mode = MODE_SV48;
-}
-
-void apply_flush_all(Vtlb* dut) {
- clear_flush(dut);
- dut->flush_all = 1;
- tick(dut);
- dut->flush_all = 0;
- eval_comb(dut);
-}
-
-void apply_flush_asid(Vtlb* dut, uint16_t asid) {
- clear_flush(dut);
- dut->flush_asid_valid = 1;
- dut->flush_asid = asid;
- tick(dut);
- clear_flush(dut);
- eval_comb(dut);
-}
-
-void apply_flush_vpn(Vtlb* dut, uint64_t vpn, uint8_t mode) {
- clear_flush(dut);
- dut->flush_vpn_valid = 1;
- dut->flush_vpn = vpn;
- dut->flush_mode = mode;
- tick(dut);
- clear_flush(dut);
- eval_comb(dut);
+ dut->flush_mode = MODE_SV39;
}
void reset(Vtlb* dut) {
@@ -140,77 +78,42 @@ void reset(Vtlb* dut) {
tick(dut);
tick(dut);
dut->rst = 0;
- eval_comb(dut);
+ dut->eval();
+}
+
+uint64_t sv39_vpn(uint64_t vaddr) {
+ return (vaddr >> 12) & ((1ULL << 27) - 1ULL);
}
-void do_refill(
- Vtlb* dut,
- uint64_t vpn,
- uint64_t ppn,
- uint8_t level,
- uint8_t mode,
- uint16_t asid,
- bool g,
- bool u,
- bool r,
- bool w,
- bool x,
- bool a,
- bool d
-) {
+void refill(Vtlb* dut, uint64_t vpn, uint64_t ppn, bool r, bool w, bool x, bool a, bool d) {
clear_refill(dut);
dut->refill_valid = 1;
dut->refill_vpn = vpn;
dut->refill_ppn = ppn;
- dut->refill_level = level;
- dut->refill_mode = mode;
- dut->refill_asid = asid;
- dut->refill_g = g;
- dut->refill_u = u;
+ dut->refill_level = 0;
+ dut->refill_mode = MODE_SV39;
+ dut->refill_asid = 1;
dut->refill_r = r;
dut->refill_w = w;
dut->refill_x = x;
dut->refill_a = a;
dut->refill_d = d;
tick(dut);
- dut->refill_valid = 0;
- eval_comb(dut);
-}
-
-void issue_load(Vtlb* dut, uint64_t vaddr, uint16_t asid, uint8_t mode) {
- clear_req(dut);
- dut->req_valid = 1;
- dut->req_vaddr = vaddr;
- dut->req_asid = asid;
- dut->req_mode = mode;
- dut->req_priv = 1; // S mode
- dut->req_is_fetch = 0;
- dut->req_is_store = 0;
- eval_comb(dut);
+ clear_refill(dut);
+ dut->eval();
}
-void issue_req(
- Vtlb* dut,
- uint64_t vaddr,
- uint16_t asid,
- uint8_t mode,
- uint8_t priv,
- bool is_fetch,
- bool is_store,
- bool sum,
- bool mxr
-) {
+void request(Vtlb* dut, uint64_t vaddr, uint8_t mode, bool fetch, bool store, bool mxr = false) {
clear_req(dut);
dut->req_valid = 1;
dut->req_vaddr = vaddr;
- dut->req_asid = asid;
+ dut->req_asid = 1;
dut->req_mode = mode;
- dut->req_priv = priv;
- dut->req_is_fetch = is_fetch;
- dut->req_is_store = is_store;
- dut->req_sum = sum;
+ dut->req_priv = 1;
+ dut->req_is_fetch = fetch;
+ dut->req_is_store = store;
dut->req_mxr = mxr;
- eval_comb(dut);
+ dut->eval();
}
} // namespace
@@ -225,255 +128,43 @@ int main(int argc, char** argv) {
reset(dut);
- // 1) Bare mode bypass
- clear_req(dut);
- dut->req_valid = 1;
- dut->req_mode = MODE_BARE;
- dut->req_vaddr = 0x1234'5678'9ABC'DEF0ULL;
- dut->req_priv = 1;
- eval_comb(dut);
+ request(dut, 0x123456789abcdef0ULL, MODE_BARE, false, false);
check(dut->resp_hit == 1, "Bare_Bypass_Hit");
check(dut->resp_page_fault == 0, "Bare_Bypass_NoFault");
- check(dut->resp_need_ptw == 0, "Bare_Bypass_NoPTW");
- check(dut->resp_paddr == dut->req_vaddr, "Bare_Bypass_PaddrEqualsVaddr");
-
- // 2) Unsupported mode faults
- clear_req(dut);
- dut->req_valid = 1;
- dut->req_mode = 4;
- dut->req_vaddr = 0x8000'0000ULL;
- eval_comb(dut);
- check(dut->resp_page_fault == 1, "UnsupportedMode_PageFault");
-
- // 3) Miss => need PTW
- issue_load(dut, 0x0000'0000'8040'1000ULL, 1, MODE_SV48);
- check(dut->resp_hit == 0, "Miss_NoHit");
- check(dut->resp_need_ptw == 1, "Miss_NeedPTW");
- check(dut->resp_page_fault == 0, "Miss_NoFault");
-
- // 4) Refill then hit
- const uint64_t va1 = 0x0000'0000'8040'1234ULL;
- const uint64_t vpn1 = sv48_vpn(va1);
- const uint64_t ppn1 = 0x12345ULL;
- do_refill(dut, vpn1, ppn1, 0, MODE_SV48, 1, false, false, true, true, false, true, true);
- issue_load(dut, va1, 1, MODE_SV48);
- check(dut->resp_hit == 1, "Refill_Hit");
- check(dut->resp_page_fault == 0, "Refill_NoFault");
- check(dut->resp_paddr == make_paddr(ppn1, va1), "Refill_PaddrMatch");
-
- // 5) ASID mismatch miss when non-global
- issue_load(dut, va1, 2, MODE_SV48);
- check(dut->resp_hit == 0, "AsidMismatch_Miss");
- check(dut->resp_need_ptw == 1, "AsidMismatch_NeedPTW");
-
- // 6) Global entry ignores ASID
- const uint64_t va2 = 0x0000'0000'9000'3456ULL;
- const uint64_t vpn2 = sv48_vpn(va2);
- const uint64_t ppn2 = 0x23456ULL;
- do_refill(dut, vpn2, ppn2, 0, MODE_SV48, 9, true, false, true, false, true, true, true);
- issue_load(dut, va2, 99, MODE_SV48);
- check(dut->resp_hit == 1, "Global_AsidIgnore_Hit");
-
- // 7) Store permission requires W and D
- const uint64_t va3 = 0x0000'0000'A000'2222ULL;
- const uint64_t vpn3 = sv48_vpn(va3);
- do_refill(dut, vpn3, 0x34567ULL, 0, MODE_SV48, 3, false, false, true, true, false, true, false);
- clear_req(dut);
- dut->req_valid = 1;
- dut->req_vaddr = va3;
- dut->req_asid = 3;
- dut->req_mode = MODE_SV48;
- dut->req_priv = 1;
- dut->req_is_store = 1;
- eval_comb(dut);
- check(dut->resp_page_fault == 1, "Store_DBitFault");
-
- // 8) MXR allows load from execute-only page
- const uint64_t va4 = 0x0000'0000'B000'0100ULL;
- const uint64_t vpn4 = sv48_vpn(va4);
- do_refill(dut, vpn4, 0x45678ULL, 0, MODE_SV48, 4, false, false, false, false, true, true, true);
- clear_req(dut);
- dut->req_valid = 1;
- dut->req_vaddr = va4;
- dut->req_asid = 4;
- dut->req_mode = MODE_SV48;
- dut->req_priv = 1;
- dut->req_mxr = 1;
- eval_comb(dut);
- check(dut->resp_hit == 1, "MXR_LoadFromXOnly_Hit");
- check(dut->resp_page_fault == 0, "MXR_LoadFromXOnly_NoFault");
+ check(dut->resp_paddr == 0x123456789abcdef0ULL, "Bare_Bypass_Paddr");
+
+ request(dut, 0x0000000040401000ULL, MODE_SV48, false, false);
+ check(dut->resp_page_fault == 1, "Sv48_Stub_Fault");
+ request(dut, 0x0000000040401000ULL, MODE_SV57, false, false);
+ check(dut->resp_page_fault == 1, "Sv57_Stub_Fault");
+ request(dut, 0x0000000040401000ULL, MODE_SV64, false, false);
+ check(dut->resp_page_fault == 1, "Sv64_Stub_Fault");
+
+ const uint64_t va = 0x0000000040401234ULL;
+ const uint64_t ppn = 0x45670ULL;
+ request(dut, va, MODE_SV39, false, false);
+ check(dut->resp_hit == 0, "Sv39_Miss_NoHit");
+ check(dut->resp_need_ptw == 1, "Sv39_Miss_NeedPTW");
+ check(dut->resp_page_fault == 0, "Sv39_Miss_NoFault");
+
+ refill(dut, sv39_vpn(va), ppn, true, true, false, true, true);
+ request(dut, va, MODE_SV39, false, false);
+ check(dut->resp_hit == 1, "Sv39_Refill_Hit");
+ check(dut->resp_paddr == ((ppn << 12) | (va & 0xfffULL)), "Sv39_Refill_Paddr");
+
+ refill(dut, sv39_vpn(0x0000000040500000ULL), 0x50000ULL, true, true, false, true, false);
+ request(dut, 0x0000000040500000ULL, MODE_SV39, false, true);
+ check(dut->resp_page_fault == 1, "Sv39_Store_D0_Fault");
+
+ refill(dut, sv39_vpn(0x0000000040600000ULL), 0x60000ULL, false, false, true, true, false);
+ request(dut, 0x0000000040600000ULL, MODE_SV39, false, false, true);
+ check(dut->resp_hit == 1, "Sv39_MXR_LoadFromXOnly");
+
+ request(dut, 0x0000008000000000ULL, MODE_SV39, false, false);
+ check(dut->resp_page_fault == 1, "Sv39_NonCanonical_Fault");
- // 9) Superpage match (Sv48 level=1) ignores low VPN[8:0]
- const uint64_t va5a = 0x0000'0000'C123'4000ULL;
- const uint64_t va5b = 0x0000'0000'C123'8ABCULL; // same high VPN, different low VPN bits
- const uint64_t vpn5 = sv48_vpn(va5a);
- do_refill(dut, vpn5, 0x56780ULL, 1, MODE_SV48, 5, false, false, true, true, true, true, true);
- issue_load(dut, va5b, 5, MODE_SV48);
- check(dut->resp_hit == 1, "Superpage_Level1_Hit");
-
- // 10) Flush all invalidates
- apply_flush_all(dut);
- issue_load(dut, va1, 1, MODE_SV48);
- check(dut->resp_hit == 0, "FlushAll_Invalidates");
-
- // 11) Flush by ASID only invalidates matching non-global entries
- const uint64_t va6a = 0x0000'0000'D000'1111ULL;
- const uint64_t va6b = 0x0000'0000'D000'2222ULL;
- do_refill(dut, sv48_vpn(va6a), 0x60100ULL, 0, MODE_SV48, 7, false, false, true, true, false, true, true);
- do_refill(dut, sv48_vpn(va6b), 0x60200ULL, 0, MODE_SV48, 8, false, false, true, true, false, true, true);
- apply_flush_asid(dut, 7);
- issue_load(dut, va6a, 7, MODE_SV48);
- check(dut->resp_hit == 0, "FlushAsid_TargetInvalidated");
- issue_load(dut, va6b, 8, MODE_SV48);
- check(dut->resp_hit == 1, "FlushAsid_OtherAsidPreserved");
-
- // 12) Flush by VPN only invalidates matching VPN in the selected mode
- const uint64_t va7a = 0x0000'0000'E100'0123ULL;
- const uint64_t va7b = 0x0000'0000'E200'0456ULL;
- do_refill(dut, sv48_vpn(va7a), 0x70100ULL, 0, MODE_SV48, 9, false, false, true, true, false, true, true);
- do_refill(dut, sv48_vpn(va7b), 0x70200ULL, 0, MODE_SV48, 9, false, false, true, true, false, true, true);
- apply_flush_vpn(dut, sv48_vpn(va7a), MODE_SV48);
- issue_load(dut, va7a, 9, MODE_SV48);
- check(dut->resp_hit == 0, "FlushVpn_TargetInvalidated");
- issue_load(dut, va7b, 9, MODE_SV48);
- check(dut->resp_hit == 1, "FlushVpn_OtherVpnPreserved");
-
- // 13) Sv32 superpage boundary: level=1 ignores low VPN[9:0]
- const uint64_t va32a = 0x0000000081234000ULL;
- const uint64_t va32b = 0x00000000813FFABCULL;
- do_refill(dut, sv32_vpn(va32a), 0x31000ULL, 1, MODE_SV32, 11, false, false, true, true, false, true, true);
- issue_load(dut, va32b, 11, MODE_SV32);
- check(dut->resp_hit == 1, "Sv32_Superpage_Level1_Hit");
- check(dut->resp_page_fault == 0, "Sv32_Superpage_NoFault");
-
- // 14) Sv57 superpage boundary: level=2 ignores low VPN[17:0]
- const uint64_t va57a = 0x0012'3456'7800'4000ULL;
- const uint64_t va57b = 0x0012'3456'7BFF'8ABCULL;
- do_refill(dut, sv57_vpn(va57a), 0x4A1234ULL, 2, MODE_SV57, 12, false, false, true, true, false, true, true);
- issue_load(dut, va57b, 12, MODE_SV57);
- check(dut->resp_hit == 1, "Sv57_Superpage_Level2_Hit");
- check(dut->resp_page_fault == 0, "Sv57_Superpage_NoFault");
-
- // 15) Flush VPN mode filter: Sv32 flush must not touch Sv48 entry with same low VPN
- const uint64_t va8 = 0x0000'0000'8123'4567ULL;
- const uint64_t vpn32 = sv32_vpn(va8);
- const uint64_t vpn48_same_low = vpn32;
- do_refill(dut, vpn32, 0x81000ULL, 0, MODE_SV32, 13, false, false, true, true, false, true, true);
- do_refill(dut, vpn48_same_low, 0x82000ULL, 0, MODE_SV48, 13, false, false, true, true, false, true, true);
- apply_flush_vpn(dut, vpn32, MODE_SV32);
- issue_load(dut, va8, 13, MODE_SV32);
- check(dut->resp_hit == 0, "FlushVpn_ModeFilter_Sv32Invalidated");
- issue_load(dut, va8, 13, MODE_SV48);
- check(dut->resp_hit == 1, "FlushVpn_ModeFilter_Sv48Preserved");
-
- // 16) S-mode access to U=1 page requires SUM for data access
- const uint64_t va9 = 0x0000'0000'8333'0100ULL;
- do_refill(dut, sv48_vpn(va9), 0x83000ULL, 0, MODE_SV48, 14, false, true, true, true, false, true, true);
- issue_req(dut, va9, 14, MODE_SV48, 1, false, false, false, false);
- check(dut->resp_page_fault == 1, "SUM_SmodeLoadUPage_Sum0_Fault");
- issue_req(dut, va9, 14, MODE_SV48, 1, false, false, true, false);
- check(dut->resp_hit == 1, "SUM_SmodeLoadUPage_Sum1_Hit");
- check(dut->resp_page_fault == 0, "SUM_SmodeLoadUPage_Sum1_NoFault");
-
- // 17) SUM must not allow S-mode instruction fetch from U page
- issue_req(dut, va9, 14, MODE_SV48, 1, true, false, true, false);
- check(dut->resp_page_fault == 1, "SUM_SmodeFetchUPage_AlwaysFault");
-
- // 18) U-mode cannot access U=0 page
- const uint64_t va10 = 0x0000'0000'8444'0200ULL;
- do_refill(dut, sv48_vpn(va10), 0x84000ULL, 0, MODE_SV48, 15, false, false, true, true, true, true, true);
- issue_req(dut, va10, 15, MODE_SV48, 0, false, false, false, false);
- check(dut->resp_page_fault == 1, "Umode_LoadSupervisorPage_Fault");
-
- // 19) U-mode can load U=1 page when permissions allow
- const uint64_t va11 = 0x0000'0000'8555'0300ULL;
- do_refill(dut, sv48_vpn(va11), 0x85000ULL, 0, MODE_SV48, 16, false, true, true, false, false, true, true);
- issue_req(dut, va11, 16, MODE_SV48, 0, false, false, false, false);
- check(dut->resp_hit == 1, "Umode_LoadUserPage_Hit");
- check(dut->resp_page_fault == 0, "Umode_LoadUserPage_NoFault");
-
- // 20) MXR matrix: execute-only page load faults when mxr=0, passes when mxr=1
- const uint64_t va12 = 0x0000'0000'8666'0400ULL;
- do_refill(dut, sv48_vpn(va12), 0x86000ULL, 0, MODE_SV48, 17, false, false, false, false, true, true, true);
- issue_req(dut, va12, 17, MODE_SV48, 1, false, false, false, false);
- check(dut->resp_page_fault == 1, "MXR_LoadXOnly_Mxr0_Fault");
- issue_req(dut, va12, 17, MODE_SV48, 1, false, false, false, true);
- check(dut->resp_hit == 1, "MXR_LoadXOnly_Mxr1_Hit");
- check(dut->resp_page_fault == 0, "MXR_LoadXOnly_Mxr1_NoFault");
-
- // 21) A-bit required for any access type (load/fetch)
- const uint64_t va13 = 0x0000'0000'8777'0500ULL;
- do_refill(dut, sv48_vpn(va13), 0x87000ULL, 0, MODE_SV48, 18, false, false, true, true, true, false, true);
- issue_req(dut, va13, 18, MODE_SV48, 1, false, false, false, false);
- check(dut->resp_page_fault == 1, "ABit_LoadFault_WhenA0");
- issue_req(dut, va13, 18, MODE_SV48, 1, true, false, false, false);
- check(dut->resp_page_fault == 1, "ABit_FetchFault_WhenA0");
-
- // 22) ASID-targeted flush must preserve global entries
- const uint64_t va14 = 0x0000'0000'8888'0600ULL;
- do_refill(dut, sv48_vpn(va14), 0x88000ULL, 0, MODE_SV48, 33, true, false, true, true, false, true, true);
- apply_flush_asid(dut, 33);
- issue_load(dut, va14, 99, MODE_SV48);
- check(dut->resp_hit == 1, "FlushAsid_GlobalPreserved");
-
- // 23) Refill pressure should eventually evict oldest entry (round-robin)
- apply_flush_all(dut);
- const uint64_t va15 = 0x0000'0000'9000'1000ULL;
- do_refill(dut, sv48_vpn(va15), 0x90001ULL, 0, MODE_SV48, 40, false, false, true, true, false, true, true);
- for (int n = 0; n < 32; ++n) {
- const uint64_t va_fill = 0x0000'0001'0000'0000ULL + (static_cast<uint64_t>(n) << 12);
- const uint64_t ppn_fill = 0x91000ULL + static_cast<uint64_t>(n);
- do_refill(dut, sv48_vpn(va_fill), ppn_fill, 0, MODE_SV48, static_cast<uint16_t>(50 + n), false, false, true, true, false, true, true);
- }
- issue_load(dut, va15, 40, MODE_SV48);
- check(dut->resp_hit == 0, "ReplacementPressure_OldestEvicted");
-
- // 24) Newest refill remains accessible after pressure
- const uint64_t va16 = 0x0000'0001'0001'F000ULL;
- do_refill(dut, sv48_vpn(va16), 0xA001FULL, 0, MODE_SV48, 77, false, false, true, true, false, true, true);
- issue_load(dut, va16, 77, MODE_SV48);
- check(dut->resp_hit == 1, "ReplacementPressure_NewestEntryHits");
-
- // 25) flush_vpn should invalidate Sv48 level-1 superpage for any VPN within same range
- apply_flush_all(dut);
- const uint64_t va17_base = 0x0000'0001'2340'0000ULL;
- const uint64_t va17_same_superpage = 0x0000'0001'2341'2ABCULL;
- const uint64_t va17_other_superpage = 0x0000'0001'2440'0555ULL;
- do_refill(dut, sv48_vpn(va17_base), 0xB1000ULL, 1, MODE_SV48, 88, false, false, true, true, false, true, true);
- do_refill(dut, sv48_vpn(va17_other_superpage), 0xB2000ULL, 1, MODE_SV48, 88, false, false, true, true, false, true, true);
- apply_flush_vpn(dut, sv48_vpn(va17_same_superpage), MODE_SV48);
- issue_load(dut, va17_base, 88, MODE_SV48);
- check(dut->resp_hit == 0, "FlushVpn_Sv48L1_SameSuperpageInvalidated");
- issue_load(dut, va17_other_superpage, 88, MODE_SV48);
- check(dut->resp_hit == 1, "FlushVpn_Sv48L1_OtherSuperpagePreserved");
-
- // 26) flush_vpn should invalidate Sv57 level-2 superpage with 18-bit VPN wildcard
- apply_flush_all(dut);
- const uint64_t va18_base = 0x0012'3456'7800'1000ULL;
- const uint64_t va18_same_superpage = 0x0012'3456'7BFF'8FFFULL;
- const uint64_t va18_other_superpage = 0x0012'345A'0000'2000ULL;
- do_refill(dut, sv57_vpn(va18_base), 0xC1234ULL, 2, MODE_SV57, 99, false, false, true, true, false, true, true);
- do_refill(dut, sv57_vpn(va18_other_superpage), 0xC5678ULL, 2, MODE_SV57, 99, false, false, true, true, false, true, true);
- apply_flush_vpn(dut, sv57_vpn(va18_same_superpage), MODE_SV57);
- issue_load(dut, va18_base, 99, MODE_SV57);
- check(dut->resp_hit == 0, "FlushVpn_Sv57L2_SameSuperpageInvalidated");
- issue_load(dut, va18_other_superpage, 99, MODE_SV57);
- check(dut->resp_hit == 1, "FlushVpn_Sv57L2_OtherSuperpagePreserved");
-
- // 27) flush_vpn with exact-page entry must not over-invalidate neighboring pages
- apply_flush_all(dut);
- const uint64_t va19_a = 0x0000'0000'9900'1000ULL;
- const uint64_t va19_b = 0x0000'0000'9900'2000ULL;
- do_refill(dut, sv48_vpn(va19_a), 0xD1000ULL, 0, MODE_SV48, 120, false, false, true, true, false, true, true);
- do_refill(dut, sv48_vpn(va19_b), 0xD2000ULL, 0, MODE_SV48, 120, false, false, true, true, false, true, true);
- apply_flush_vpn(dut, sv48_vpn(va19_a), MODE_SV48);
- issue_load(dut, va19_a, 120, MODE_SV48);
- check(dut->resp_hit == 0, "FlushVpn_Level0_OnlyTargetInvalidated");
- issue_load(dut, va19_b, 120, MODE_SV48);
- check(dut->resp_hit == 1, "FlushVpn_Level0_NeighborPreserved");
-
- std::cout << "\n----------------------------------\n";
- std::cout << "TLB tests passed: " << g_pass_count << "\n";
+ std::cout << "----------------------------------\n";
+ std::cout << "All " << g_pass_count << " TLB tests passed!\n";
std::cout << "----------------------------------\n";
delete dut;
diff --git a/rv64cpu/tb/unit/tb_trap_ctrl.cc b/rv64cpu/tb/unit/tb_trap_ctrl.cc
index f0b667c..62867df 100644
--- a/rv64cpu/tb/unit/tb_trap_ctrl.cc
+++ b/rv64cpu/tb/unit/tb_trap_ctrl.cc
@@ -35,12 +35,19 @@ constexpr uint64_t EXC_STORE_FAULT = 7ULL;
constexpr uint64_t INTERRUPT_M_SOFTWARE = 3ULL;
constexpr uint64_t INTERRUPT_M_TIMER = 7ULL;
constexpr uint64_t INTERRUPT_M_EXTERNAL = 11ULL;
+constexpr uint64_t INTERRUPT_S_SOFTWARE = 1ULL;
+constexpr uint64_t INTERRUPT_S_TIMER = 5ULL;
+constexpr uint64_t INTERRUPT_S_EXTERNAL = 9ULL;
constexpr uint64_t INTERRUPT_BIT = 1ULL << 63;
constexpr uint64_t PRIV_MODE_U = 0ULL;
constexpr uint64_t PRIV_MODE_S = 1ULL;
constexpr uint64_t PRIV_MODE_M = 3ULL;
+uint64_t mie_bit(uint64_t interrupt_cause) {
+ return 1ULL << interrupt_cause;
+}
+
void drive_defaults(Vtrap_ctrl* dut) {
dut->wb_pc = 0;
dut->trap_illegal_instr = 0;
@@ -56,11 +63,11 @@ void drive_defaults(Vtrap_ctrl* dut) {
dut->trap_bad_addr = 0;
dut->trap_bad_instr = 0;
- dut->ext_timer_interrupt = 0;
- dut->ext_software_interrupt = 0;
- dut->ext_external_interrupt = 0;
-
dut->csr_mstatus_mie = 0;
+ dut->csr_mie = 0;
+ dut->csr_sie = 0;
+ dut->csr_mip = 0;
+ dut->csr_sip = 0;
dut->csr_mtvec = 0x800ULL;
dut->csr_stvec = 0x400ULL;
dut->csr_mepc = 0x1000ULL;
@@ -150,16 +157,19 @@ int main(int argc, char** argv) {
check(dut->trap_mtval == 0xABCDEF0020ULL, "StoreFault_Mtval");
// ---------------------------------------------------------------------
- // 2) Interrupt priority + delegation to supervisor
+ // 2) Interrupt priority + enable masking
// ---------------------------------------------------------------------
drive_defaults(dut);
dut->csr_mstatus_mie = 1;
+ dut->csr_mie = mie_bit(INTERRUPT_M_EXTERNAL) |
+ mie_bit(INTERRUPT_M_SOFTWARE) |
+ mie_bit(INTERRUPT_M_TIMER);
dut->csr_priv_mode = PRIV_MODE_M;
dut->csr_mtvec = 0x300ULL | 0x1ULL; // vectored mode
dut->wb_pc = 0x4000ULL;
- dut->ext_timer_interrupt = 1;
- dut->ext_software_interrupt = 1;
- dut->ext_external_interrupt = 1;
+ dut->csr_mip = mie_bit(INTERRUPT_M_EXTERNAL) |
+ mie_bit(INTERRUPT_M_SOFTWARE) |
+ mie_bit(INTERRUPT_M_TIMER);
dut->eval();
const uint64_t expected_vectored_pc = 0x300ULL + (INTERRUPT_M_EXTERNAL << 2);
check(dut->trap_trigger, "InterruptPriority_Triggers");
@@ -169,22 +179,80 @@ int main(int argc, char** argv) {
drive_defaults(dut);
dut->csr_mstatus_mie = 0;
- dut->ext_external_interrupt = 1;
+ dut->csr_mie = mie_bit(INTERRUPT_M_EXTERNAL);
+ dut->csr_mip = mie_bit(INTERRUPT_M_EXTERNAL);
dut->eval();
- check(!dut->trap_trigger, "InterruptMasked_NoTrigger");
+ check(!dut->trap_trigger, "InterruptMaskedByGlobalMie_NoTrigger");
+
+ drive_defaults(dut);
+ dut->csr_priv_mode = PRIV_MODE_M;
+ dut->csr_mstatus_mie = 1;
+ dut->csr_mie = 0;
+ dut->csr_mip = mie_bit(INTERRUPT_M_EXTERNAL);
+ dut->eval();
+ check(!dut->trap_trigger, "InterruptMaskedByMie_NoTrigger");
+
+ drive_defaults(dut);
+ dut->csr_priv_mode = PRIV_MODE_S;
+ dut->csr_mstatus_mie = 0;
+ dut->csr_mie = mie_bit(INTERRUPT_M_EXTERNAL);
+ dut->csr_mtvec = 0x440ULL;
+ dut->wb_pc = 0x6780ULL;
+ dut->csr_mip = mie_bit(INTERRUPT_M_EXTERNAL);
+ dut->eval();
+ check(dut->trap_trigger, "MachineInterruptBelowM_IgnoresMstatusMie");
+ check(!dut->trap_to_s_mode, "MachineInterruptBelowM_NoDelegationWithoutMideleg");
+ check(dut->trap_mcause == (INTERRUPT_BIT | INTERRUPT_M_EXTERNAL), "MachineInterruptBelowM_Mcause");
+ check(dut->trap_next_pc == 0x440ULL, "MachineInterruptBelowM_JumpsToMtvec");
drive_defaults(dut);
dut->csr_priv_mode = PRIV_MODE_S;
dut->csr_mstatus_mie = 1;
- dut->csr_stvec = 0x880ULL | 0x1ULL; // vectored supervisor mode
+ dut->csr_mie = mie_bit(INTERRUPT_M_TIMER);
+ dut->csr_mtvec = 0x880ULL | 0x1ULL; // vectored machine mode
+ dut->csr_stvec = 0x400ULL | 0x1ULL;
dut->csr_mideleg = (1ULL << INTERRUPT_M_TIMER);
dut->wb_pc = 0xABCDULL;
- dut->ext_timer_interrupt = 1;
+ dut->csr_mip = mie_bit(INTERRUPT_M_TIMER);
dut->eval();
const uint64_t expected_sv_pc = 0x880ULL + (INTERRUPT_M_TIMER << 2);
- check(dut->trap_to_s_mode, "InterruptDelegation_ToSupervisor");
- check(dut->trap_next_pc == expected_sv_pc, "InterruptDelegation_UsesStvec");
- check(dut->trap_mcause == (INTERRUPT_BIT | INTERRUPT_M_TIMER), "InterruptDelegation_Mcause");
+ check(!dut->trap_to_s_mode, "MachineInterrupt_NoDelegation");
+ check(dut->trap_next_pc == expected_sv_pc, "MachineInterrupt_UsesMtvec");
+ check(dut->trap_mcause == (INTERRUPT_BIT | INTERRUPT_M_TIMER), "MachineInterrupt_Mcause");
+
+ drive_defaults(dut);
+ dut->csr_priv_mode = PRIV_MODE_S;
+ dut->csr_mstatus = (1ULL << 1);
+ dut->csr_sie = mie_bit(INTERRUPT_S_TIMER);
+ dut->csr_sip = mie_bit(INTERRUPT_S_TIMER);
+ dut->csr_mideleg = mie_bit(INTERRUPT_S_TIMER);
+ dut->csr_stvec = 0x900ULL | 0x1ULL;
+ dut->wb_pc = 0xABCEULL;
+ dut->eval();
+ const uint64_t expected_s_timer_pc = 0x900ULL + (INTERRUPT_S_TIMER << 2);
+ check(dut->trap_to_s_mode, "SupervisorInterruptDelegated_ToSupervisor");
+ check(dut->trap_next_pc == expected_s_timer_pc, "SupervisorInterruptDelegated_UsesStvec");
+ check(dut->trap_mcause == (INTERRUPT_BIT | INTERRUPT_S_TIMER), "SupervisorInterruptDelegated_Mcause");
+
+ drive_defaults(dut);
+ dut->csr_priv_mode = PRIV_MODE_S;
+ dut->csr_mstatus = 0;
+ dut->csr_sie = mie_bit(INTERRUPT_S_TIMER);
+ dut->csr_sip = mie_bit(INTERRUPT_S_TIMER);
+ dut->csr_mideleg = mie_bit(INTERRUPT_S_TIMER);
+ dut->eval();
+ check(!dut->trap_trigger, "SupervisorInterruptMaskedBySstatusSie_NoTrigger");
+
+ drive_defaults(dut);
+ dut->csr_priv_mode = PRIV_MODE_U;
+ dut->csr_mstatus = 0;
+ dut->csr_sie = mie_bit(INTERRUPT_S_EXTERNAL);
+ dut->csr_sip = mie_bit(INTERRUPT_S_EXTERNAL);
+ dut->csr_mideleg = mie_bit(INTERRUPT_S_EXTERNAL);
+ dut->csr_stvec = 0x940ULL;
+ dut->eval();
+ check(dut->trap_to_s_mode, "SupervisorInterruptBelowS_IgnoresSstatusSie");
+ check(dut->trap_next_pc == 0x940ULL, "SupervisorInterruptBelowS_UsesStvec");
// ---------------------------------------------------------------------
// 3) MRET/SRET redirect without new traps