diff options
| -rw-r--r-- | rv64cpu/rtl/core/trap_ctrl.sv | 204 | ||||
| -rw-r--r-- | rv64cpu/tb/CMakeLists.txt | 10 | ||||
| -rw-r--r-- | rv64cpu/tb/unit/tb_trap_ctrl.cc | 192 |
3 files changed, 406 insertions, 0 deletions
diff --git a/rv64cpu/rtl/core/trap_ctrl.sv b/rv64cpu/rtl/core/trap_ctrl.sv new file mode 100644 index 0000000..c5593ea --- /dev/null +++ b/rv64cpu/rtl/core/trap_ctrl.sv @@ -0,0 +1,204 @@ +// 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/>. + +// File path: rtl/core/trap_ctrl.sv +// Description: Privileged-level Trap Controller (Exception and Interrupt Handling) +// Detects exceptions/interrupts, triggers context saving, and generates global pipeline flush and PC jump signals + +module trap_ctrl + import rv64_pkg::*; + import csr_pkg::*; +( + // Submission signals from the WB stage (guarantee of precise exceptions) + input logic [XLEN-1:0] wb_pc, + input logic trap_illegal_instr, + input logic trap_is_ecall, + input logic trap_is_ebreak, + input logic trap_is_mret, + input logic trap_is_sret, + input logic trap_load_fault, + input logic trap_store_fault, + input logic trap_instr_page_fault, + input logic trap_load_page_fault, + input logic trap_store_page_fault, + input logic [XLEN-1:0] trap_bad_addr, + input logic [INST_WIDTH-1:0] trap_bad_instr, + + // Interrupt pin from external source (asynchronous signal) + input logic ext_timer_int, // M-Mode Machine Timer Interrupt (MTIP) + input logic ext_software_int, // M-Mode Software Interrupt (MSIP) + input logic ext_external_int, // M-Mode External Interrupt (MEIP) + + // Status read from CSR Unit + input logic csr_mstatus_mie, // Global interrupt enable (MIE) + input logic [XLEN-1:0] csr_mtvec, // Machine trap vector + input logic [XLEN-1:0] csr_stvec, // Supervisor trap vector + input logic [XLEN-1:0] csr_mepc, // Machine exception PC (for MRET) + input logic [XLEN-1:0] csr_sepc, // Supervisor exception PC (for SRET) + input logic [XLEN-1:0] csr_medeleg, // Exception delegation mask + input logic [XLEN-1:0] csr_mideleg, // Interrupt delegation mask + input logic [1:0] csr_priv_mode, // Current privilege level + + // Status update signal sent to the CSR Unit + output logic trap_trigger, // Trigger exception/interrupt context save + output logic [XLEN-1:0] trap_mepc, // Saved PC (MEPC/SEPC selected by trap_to_s_mode) + output logic [XLEN-1:0] trap_mcause, // Cause code (bit63 indicates interrupt) + output logic [XLEN-1:0] trap_mtval, // Additional information saved to mtval/stval + output logic trap_to_s_mode, // 1: delegated to Supervisor mode + output logic mret_en, // Trigger MRET context restore + output logic sret_en, // Trigger SRET context restore + + // Control flow interference sent to the Hazard Unit and IF stage + output logic trap_flush_req, // Global pipeline flush request + output logic trap_pc_sel, // Forcibly take over PC MUX (0: normal, 1: Trap/Mret/Sret) + output logic [XLEN-1:0] trap_next_pc // Target address for forced jump +); + + typedef enum logic [1:0] { + PRIV_MODE_U = 2'b00, + PRIV_MODE_S = 2'b01, + PRIV_MODE_M = 2'b11 + } priv_mode_t; + + localparam logic [XLEN-1:0] INTERRUPT_BIT = {1'b1, {(XLEN-1){1'b0}}}; + + logic has_sync_exception; + logic [XLEN-1:0] sync_cause; + logic [XLEN-1:0] sync_mtval; + logic [XLEN-1:0] illegal_instr_value; + + assign illegal_instr_value = {{(XLEN-INST_WIDTH){1'b0}}, trap_bad_instr}; + + always_comb begin + has_sync_exception = 1'b0; + sync_cause = '0; + sync_mtval = '0; + + if (trap_illegal_instr) begin + has_sync_exception = 1'b1; + sync_cause = EXC_INST_ILLEGAL; + sync_mtval = illegal_instr_value; + end else if (trap_is_ebreak) begin + has_sync_exception = 1'b1; + sync_cause = EXC_BREAKPOINT; + sync_mtval = wb_pc; + end else if (trap_is_ecall) begin + has_sync_exception = 1'b1; + case (priv_mode_t'(csr_priv_mode)) + PRIV_MODE_U: sync_cause = EXC_ECALL_FROM_U; + PRIV_MODE_S: sync_cause = EXC_ECALL_FROM_S; + default: sync_cause = EXC_ECALL_FROM_M; + endcase + sync_mtval = '0; + end else if (trap_load_fault) begin + has_sync_exception = 1'b1; + sync_cause = EXC_LOAD_FAULT; + sync_mtval = trap_bad_addr; + end else if (trap_store_fault) begin + has_sync_exception = 1'b1; + sync_cause = EXC_STORE_FAULT; + sync_mtval = trap_bad_addr; + end else if (trap_instr_page_fault) begin + has_sync_exception = 1'b1; + sync_cause = EXC_INSTR_PAGE_FAULT; + sync_mtval = trap_bad_addr; + end else if (trap_load_page_fault) begin + has_sync_exception = 1'b1; + sync_cause = EXC_LOAD_PAGE_FAULT; + sync_mtval = trap_bad_addr; + end else if (trap_store_page_fault) begin + has_sync_exception = 1'b1; + sync_cause = EXC_STORE_PAGE_FAULT; + sync_mtval = trap_bad_addr; + end + end + + // Interrupt Detection - Asynchronous, Protected by MIE + logic has_interrupt; + logic [XLEN-1:0] int_cause; + + always_comb begin + has_interrupt = 1'b0; + int_cause = '0; + + if (csr_mstatus_mie) begin + if (ext_external_int) begin + has_interrupt = 1'b1; + int_cause = INTERRUPT_BIT | INT_M_EXTERNAL; + end else if (ext_software_int) begin + has_interrupt = 1'b1; + int_cause = INTERRUPT_BIT | INT_M_SOFTWARE; + end else if (ext_timer_int) begin + has_interrupt = 1'b1; + int_cause = INTERRUPT_BIT | INT_M_TIMER; + end + end + end + + logic [XLEN-1:0] trap_cause_value; + logic [XLEN-1:0] trap_mtval_value; + logic trap_is_interrupt; + + assign trap_is_interrupt = has_interrupt; + assign trap_cause_value = has_interrupt ? int_cause : sync_cause; + assign trap_mtval_value = has_interrupt ? '0 : sync_mtval; + + assign trap_trigger = has_interrupt | has_sync_exception; + + logic [5:0] trap_cause_index; + logic delegate_to_s; + logic [XLEN-1:0] deleg_mask; + + assign trap_cause_index = trap_cause_value[5:0]; + assign deleg_mask = trap_is_interrupt ? csr_mideleg : csr_medeleg; + assign delegate_to_s = trap_trigger && (priv_mode_t'(csr_priv_mode) != PRIV_MODE_M) && deleg_mask[trap_cause_index]; + assign trap_to_s_mode = delegate_to_s; + + assign trap_mcause = trap_trigger ? trap_cause_value : '0; + assign trap_mtval = trap_trigger ? trap_mtval_value : '0; + assign trap_mepc = trap_trigger ? wb_pc : '0; + + assign mret_en = trap_is_mret & ~trap_trigger; + assign sret_en = trap_is_sret & ~trap_trigger; + + assign trap_flush_req = trap_trigger | mret_en | sret_en; + assign trap_pc_sel = trap_flush_req; + + logic [XLEN-1:0] trap_vector_base; + logic [1:0] trap_vector_mode; + logic [XLEN-1:0] trap_vector_aligned; + + assign trap_vector_base = delegate_to_s ? csr_stvec : csr_mtvec; + assign trap_vector_mode = trap_vector_base[1:0]; + assign trap_vector_aligned = {trap_vector_base[XLEN-1:2], 2'b00}; + + always_comb begin + if (trap_trigger) begin + if ((trap_vector_mode == 2'b01) && trap_is_interrupt) begin + trap_next_pc = trap_vector_aligned + (({{(XLEN-6){1'b0}}, trap_cause_index}) << 2); + end else begin + trap_next_pc = trap_vector_aligned; + end + end else if (mret_en) begin + trap_next_pc = csr_mepc; + end else if (sret_en) begin + trap_next_pc = csr_sepc; + end else begin + trap_next_pc = '0; + end + end + +endmodule diff --git a/rv64cpu/tb/CMakeLists.txt b/rv64cpu/tb/CMakeLists.txt index 48a4185..7b660e8 100644 --- a/rv64cpu/tb/CMakeLists.txt +++ b/rv64cpu/tb/CMakeLists.txt @@ -159,6 +159,16 @@ add_verilator_target( INCLUDE_DIRS ${RTL_COMMON_INCLUDES} TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_hazard_unit.cc ) + +add_verilator_target( + NAME tb_trap_ctrl + TOP_MODULE trap_ctrl + SOURCES ${RTL_CORE_DIR}/include/rv64_pkg.sv + ${RTL_CORE_DIR}/include/csr_pkg.sv + ${RTL_CORE_DIR}/trap_ctrl.sv + INCLUDE_DIRS ${RTL_COMMON_INCLUDES} + TB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/unit/tb_trap_ctrl.cc +) # add_verilator_target( NAME tb_csr diff --git a/rv64cpu/tb/unit/tb_trap_ctrl.cc b/rv64cpu/tb/unit/tb_trap_ctrl.cc new file mode 100644 index 0000000..142d743 --- /dev/null +++ b/rv64cpu/tb/unit/tb_trap_ctrl.cc @@ -0,0 +1,192 @@ +// 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/>. + +// File path: tb/unit/tb_trap_ctrl.cc +// Description: Standalone unit test for trap_ctrl exception/interrupt control logic + +#include <cassert> +#include <cstdint> +#include <iostream> +#include <verilated.h> + +#include "Vtrap_ctrl.h" + +namespace { +constexpr uint64_t EXC_INST_ILLEGAL = 2ULL; +constexpr uint64_t EXC_ECALL_FROM_S = 9ULL; +constexpr uint64_t EXC_LOAD_PAGE_FAULT= 13ULL; + +constexpr uint64_t INT_M_SOFTWARE = 3ULL; +constexpr uint64_t INT_M_TIMER = 7ULL; +constexpr uint64_t INT_M_EXTERNAL = 11ULL; + +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; + +void drive_defaults(Vtrap_ctrl* dut) { + dut->wb_pc = 0; + dut->trap_illegal_instr = 0; + dut->trap_is_ecall = 0; + dut->trap_is_ebreak = 0; + dut->trap_is_mret = 0; + dut->trap_is_sret = 0; + dut->trap_load_fault = 0; + dut->trap_store_fault = 0; + dut->trap_instr_page_fault = 0; + dut->trap_load_page_fault = 0; + dut->trap_store_page_fault = 0; + dut->trap_bad_addr = 0; + dut->trap_bad_instr = 0; + + dut->ext_timer_int = 0; + dut->ext_software_int = 0; + dut->ext_external_int = 0; + + dut->csr_mstatus_mie = 0; + dut->csr_mtvec = 0x800ULL; + dut->csr_stvec = 0x400ULL; + dut->csr_mepc = 0x1000ULL; + dut->csr_sepc = 0x2000ULL; + dut->csr_medeleg = 0ULL; + dut->csr_mideleg = 0ULL; + dut->csr_priv_mode = PRIV_MODE_M; + + dut->eval(); +} + +int pass_count = 0; + +void check(bool cond, const char* name) { + if (cond) { + std::cout << "\033[32m[PASS] " << name << "\033[0m" << std::endl; + pass_count++; + } else { + std::cout << "\033[31m[FAIL] " << name << "\033[0m" << std::endl; + assert(false); + } +} +} // namespace + +int main(int argc, char** argv) { + Verilated::commandArgs(argc, argv); + Vtrap_ctrl* dut = new Vtrap_ctrl; + + std::cout << "----------------------------------\n"; + std::cout << " trap_ctrl Testbench\n"; + std::cout << "----------------------------------\n"; + + // --------------------------------------------------------------------- + // 1) Synchronous exceptions (illegal instruction, ecall, page fault) + // --------------------------------------------------------------------- + drive_defaults(dut); + dut->csr_priv_mode = PRIV_MODE_M; + dut->csr_mtvec = 0x880ULL; + dut->wb_pc = 0x1234ULL; + dut->trap_bad_instr = 0xDEADBEEFu; + dut->trap_illegal_instr = 1; + dut->eval(); + check(dut->trap_trigger, "IllegalInst_TriggersTrap"); + check(dut->trap_mcause == EXC_INST_ILLEGAL, "IllegalInst_Mcause"); + check(dut->trap_mtval == 0xDEADBEEFULL, "IllegalInst_Mtval"); + check(dut->trap_mepc == 0x1234ULL, "IllegalInst_Mepc"); + check(!dut->trap_to_s_mode, "IllegalInst_NoDelegation"); + check(dut->trap_next_pc == 0x880ULL, "IllegalInst_JumpsToMtvec"); + + drive_defaults(dut); + dut->csr_priv_mode = PRIV_MODE_S; + dut->csr_mtvec = 0x440ULL; + dut->wb_pc = 0x5678ULL; + dut->trap_is_ecall = 1; + dut->eval(); + check(dut->trap_trigger, "EcallS_TriggersTrap"); + check(dut->trap_mcause == EXC_ECALL_FROM_S, "EcallS_Mcause"); + check(dut->trap_mtval == 0ULL, "EcallS_MtvalZero"); + check(dut->trap_next_pc == 0x440ULL, "EcallS_JumpsToMtvec"); + + drive_defaults(dut); + dut->trap_load_page_fault = 1; + dut->trap_bad_addr = 0xBADC0FFEE0ULL; + dut->eval(); + check(dut->trap_mcause == EXC_LOAD_PAGE_FAULT, "PageFault_Mcause"); + check(dut->trap_mtval == 0xBADC0FFEE0ULL, "PageFault_Mtval"); + + // --------------------------------------------------------------------- + // 2) Interrupt priority + delegation to supervisor + // --------------------------------------------------------------------- + drive_defaults(dut); + dut->csr_mstatus_mie = 1; + dut->csr_priv_mode = PRIV_MODE_M; + dut->csr_mtvec = 0x300ULL | 0x1ULL; // vectored mode + dut->wb_pc = 0x4000ULL; + dut->ext_timer_int = 1; + dut->ext_software_int = 1; + dut->ext_external_int = 1; + dut->eval(); + const uint64_t expected_vectored_pc = 0x300ULL + (INT_M_EXTERNAL << 2); + check(dut->trap_trigger, "InterruptPriority_Triggers"); + check(dut->trap_mcause == (INTERRUPT_BIT | INT_M_EXTERNAL), "InterruptPriority_Mcause"); + check(dut->trap_mtval == 0ULL, "InterruptPriority_MtvalZero"); + check(dut->trap_next_pc == expected_vectored_pc, "InterruptPriority_VectoredJump"); + + drive_defaults(dut); + dut->csr_mstatus_mie = 0; + dut->ext_external_int = 1; + dut->eval(); + check(!dut->trap_trigger, "InterruptMasked_NoTrigger"); + + 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_mideleg = (1ULL << INT_M_TIMER); + dut->wb_pc = 0xABCDULL; + dut->ext_timer_int = 1; + dut->eval(); + const uint64_t expected_sv_pc = 0x880ULL + (INT_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 | INT_M_TIMER), "InterruptDelegation_Mcause"); + + // --------------------------------------------------------------------- + // 3) MRET/SRET redirect without new traps + // --------------------------------------------------------------------- + drive_defaults(dut); + dut->csr_mepc = 0xCAFEBABEULL; + dut->trap_is_mret = 1; + dut->eval(); + check(dut->mret_en, "Mret_PathEnabled"); + check(dut->trap_next_pc == 0xCAFEBABEULL, "Mret_RestoresMepc"); + check(dut->trap_flush_req && dut->trap_pc_sel, "Mret_ForcesFlush"); + check(!dut->trap_trigger, "Mret_NoTrapOverlap"); + + drive_defaults(dut); + dut->csr_priv_mode = PRIV_MODE_S; + dut->csr_sepc = 0x13579BDFULL; + dut->trap_is_sret = 1; + dut->eval(); + check(dut->sret_en, "Sret_PathEnabled"); + check(dut->trap_next_pc == 0x13579BDFULL, "Sret_RestoresSepc"); + check(dut->trap_flush_req && dut->trap_pc_sel, "Sret_ForcesFlush"); + + std::cout << "----------------------------------\n"; + std::cout << "All " << pass_count << " trap_ctrl tests passed!\n"; + std::cout << "----------------------------------\n"; + + delete dut; + return 0; +} |
