aboutsummaryrefslogtreecommitdiff
path: root/rv64cpu
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-03-19 03:16:03 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-03-19 03:16:03 +0800
commitab6a21ae1099bff120111b8a9dcfa50c9fcad5a1 (patch)
treeb197603a4a17cb34a6fc49612b0dca96e797af49 /rv64cpu
parenta03760a54a0f0f0de1c75f449427554674aabaaa (diff)
downloadrcc-project-ab6a21ae1099bff120111b8a9dcfa50c9fcad5a1.tar.gz
rcc-project-ab6a21ae1099bff120111b8a9dcfa50c9fcad5a1.tar.xz
Trap Control unit and tests
Diffstat (limited to 'rv64cpu')
-rw-r--r--rv64cpu/rtl/core/trap_ctrl.sv204
-rw-r--r--rv64cpu/tb/CMakeLists.txt10
-rw-r--r--rv64cpu/tb/unit/tb_trap_ctrl.cc192
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;
+}