From bc3ec25107314be3cd816c0b669d82338e892806 Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Sat, 7 Nov 2026 02:59:33 +0800 Subject: checkpoint: preserve current RV64 CPU development state --- rv64cpu/tb/CMakeLists.txt | 29 ++ rv64cpu/tb/compliance/CMakeLists.txt | 7 + rv64cpu/tb/compliance/compliance_runner.cc | 238 +++++++++++++++- rv64cpu/tb/core/tb_core.cc | 40 ++- rv64cpu/tb/unit/tb_alu.cc | 45 ++- rv64cpu/tb/unit/tb_csr.cc | 225 +++++++++++++-- rv64cpu/tb/unit/tb_decoder.cc | 51 +++- rv64cpu/tb/unit/tb_hazard_unit.cc | 21 ++ rv64cpu/tb/unit/tb_mem_stage.cc | 97 ++++++- rv64cpu/tb/unit/tb_mmu_top.cc | 236 ++++++++++++++++ rv64cpu/tb/unit/tb_ptw.cc | 206 ++++++++++++++ rv64cpu/tb/unit/tb_tlb.cc | 421 ++++------------------------- rv64cpu/tb/unit/tb_trap_ctrl.cc | 98 ++++++- 13 files changed, 1281 insertions(+), 433 deletions(-) create mode 100644 rv64cpu/tb/unit/tb_mmu_top.cc create mode 100644 rv64cpu/tb/unit/tb_ptw.cc (limited to 'rv64cpu/tb') 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 #include #include +#include #include #include #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(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(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 pc_trace; + if (opts.trace_pc_count != 0) { + pc_trace.reserve(static_cast(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(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(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(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((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(top->rootp->richard_core__DOT__trap_ctrl_mret_en) + << " sret=" << static_cast(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(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(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(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 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 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 +#include +#include +#include + +#include + +#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& 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& 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 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 +#include +#include +#include + +#include + +#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& 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& 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 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 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 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 . - - #include #include #include @@ -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(n) << 12); - const uint64_t ppn_fill = 0x91000ULL + static_cast(n); - do_refill(dut, sv48_vpn(va_fill), ppn_fill, 0, MODE_SV48, static_cast(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 -- cgit v1.2.3