From 6c610c932fbe0a012d1a9015b30936a63cbdb00c Mon Sep 17 00:00:00 2001 From: "Naveen R. Iyer" Date: Thu, 19 Feb 2026 20:16:20 -0600 Subject: [PATCH] fix: spec-compliant interrupt entry/exit sequence - Check per-source interrupt enable before taking interrupt - Save and restore privilege stack on trap entry and return - Clear trap value register on interrupt entry - Support vectored trap vector mode - Fix trap return to unconditionally restore interrupt enable - Use clear-then-set for privilege mode field updates Signed-off-by: Naveen R. Iyer --- inc/BASE_ISA.h | 11 ++++++++++- src/RV32.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++-------- src/RV64.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 100 insertions(+), 17 deletions(-) diff --git a/inc/BASE_ISA.h b/inc/BASE_ISA.h index 620ad8b..9906f97 100644 --- a/inc/BASE_ISA.h +++ b/inc/BASE_ISA.h @@ -1534,13 +1534,22 @@ namespace riscv_tlm { this->regs->setPC(new_pc); - // update mstatus + /* Restore mstatus privilege stack per spec: + * MIE <- MPIE (unconditional), MPIE <- 1, + * MPP <- least-privileged supported mode */ unsigned_T csr_temp; csr_temp = this->regs->getCSR(CSR_MSTATUS); if (csr_temp & MSTATUS_MPIE) { csr_temp |= MSTATUS_MIE; + } else { + csr_temp &= ~MSTATUS_MIE; } csr_temp |= MSTATUS_MPIE; + /* MPP <- least-privileged supported mode (M for now) + * TODO: when U-mode is implemented, set MPP to 0x0 (U-mode) + * instead of 0x3 (M-mode) */ + csr_temp &= ~(0x3 << 11); /* clear MPP (bits 12:11) */ + csr_temp |= (0x3 << 11); /* 0x3 = M-mode */ this->regs->setCSR(CSR_MSTATUS, csr_temp); return true; diff --git a/src/RV32.cpp b/src/RV32.cpp index 3070188..d61f5bd 100644 --- a/src/RV32.cpp +++ b/src/RV32.cpp @@ -76,14 +76,24 @@ namespace riscv_tlm { return ret_value; } - /* Map cause code to the corresponding mip bit */ + /* Map cause code to the corresponding mip/mie bit */ BaseType cause_code = int_cause & 0x7FFFFFFF; BaseType mip_bit; + BaseType mie_bit; switch (cause_code) { - case 3: mip_bit = MIP_MSIP; break; - case 7: mip_bit = MIP_MTIP; break; - case 11: mip_bit = MIP_MEIP; break; - default: mip_bit = MIP_MEIP; break; + case 3: mip_bit = MIP_MSIP; mie_bit = MIE_MSIE; break; + case 7: mip_bit = MIP_MTIP; mie_bit = MIE_MTIE; break; + case 11: mip_bit = MIP_MEIP; mie_bit = MIE_MEIE; break; + default: mip_bit = MIP_MEIP; mie_bit = MIE_MEIE; break; + } + + /* Check per-source enable in mie register */ + BaseType mie_val = register_bank->getCSR(CSR_MIE); + if ((mie_val & mie_bit) == 0) { + logger->debug("{} ns. PC: 0x{:x}. Interrupt masked by mie", + sc_core::sc_time_stamp().value(), + register_bank->getPC()); + return ret_value; } csr_temp = register_bank->getCSR(CSR_MIP); @@ -95,6 +105,21 @@ namespace riscv_tlm { logger->debug("{} ns. PC: 0x{:x}. Interrupt!", sc_core::sc_time_stamp().value(), register_bank->getPC()); + /* Save mstatus privilege stack: MPIE <- MIE, MIE <- 0, MPP <- M */ + BaseType mstatus = register_bank->getCSR(CSR_MSTATUS); + if (mstatus & MSTATUS_MIE) { + mstatus |= MSTATUS_MPIE; + } else { + mstatus &= ~MSTATUS_MPIE; + } + mstatus &= ~MSTATUS_MIE; + /* MPP <- previous privilege mode (M-mode for now) + * TODO: when U/S-mode is implemented, set MPP to the + * privilege mode that was active before the trap */ + mstatus &= ~(0x3 << 11); /* clear MPP (bits 12:11) */ + mstatus |= (0x3 << 11); /* 0x3 = M-mode */ + register_bank->setCSR(CSR_MSTATUS, mstatus); + /* updated MEPC register */ BaseType old_pc = register_bank->getPC(); register_bank->setCSR(CSR_MEPC, old_pc); @@ -106,9 +131,21 @@ namespace riscv_tlm { /* update MCAUSE register, use the cause from the interrupt source */ register_bank->setCSR(CSR_MCAUSE, int_cause); - /* set new PC address */ - BaseType new_pc = register_bank->getCSR(CSR_MTVEC); - //new_pc = new_pc & 0xFFFFFFFC; // last two bits always to 0 + /* mtval is set to 0 for standard interrupts */ + register_bank->setCSR(CSR_MTVAL, 0); + + /* set new PC address, respecting mtvec MODE */ + BaseType mtvec = register_bank->getCSR(CSR_MTVEC); + BaseType mode = mtvec & 0x3; + BaseType base = mtvec & ~0x3; + BaseType new_pc; + if (mode == 1) { + /* Vectored: interrupts go to BASE + 4 * cause_code */ + new_pc = base + 4 * cause_code; + } else { + /* Direct: all traps go to BASE */ + new_pc = base; + } logger->debug("{} ns. PC: 0x{:x}. NEW PC Value 0x{:x}", sc_core::sc_time_stamp().value(), register_bank->getPC(), new_pc); diff --git a/src/RV64.cpp b/src/RV64.cpp index a2a3f5d..6dc0d6d 100644 --- a/src/RV64.cpp +++ b/src/RV64.cpp @@ -74,14 +74,24 @@ namespace riscv_tlm { return ret_value; } - /* Map cause code to the corresponding mip bit */ + /* Map cause code to the corresponding mip/mie bit */ BaseType cause_code = int_cause & 0x7FFFFFFF; BaseType mip_bit; + BaseType mie_bit; switch (cause_code) { - case 3: mip_bit = MIP_MSIP; break; - case 7: mip_bit = MIP_MTIP; break; - case 11: mip_bit = MIP_MEIP; break; - default: mip_bit = MIP_MEIP; break; + case 3: mip_bit = MIP_MSIP; mie_bit = MIE_MSIE; break; + case 7: mip_bit = MIP_MTIP; mie_bit = MIE_MTIE; break; + case 11: mip_bit = MIP_MEIP; mie_bit = MIE_MEIE; break; + default: mip_bit = MIP_MEIP; mie_bit = MIE_MEIE; break; + } + + /* Check per-source enable in mie register */ + BaseType mie_val = register_bank->getCSR(CSR_MIE); + if ((mie_val & mie_bit) == 0) { + logger->debug("{} ns. PC: 0x{:x}. Interrupt masked by mie", + sc_core::sc_time_stamp().value(), + register_bank->getPC()); + return ret_value; } csr_temp = register_bank->getCSR(CSR_MIP); @@ -93,6 +103,21 @@ namespace riscv_tlm { logger->debug("{} ns. PC: 0x{:x}. Interrupt!", sc_core::sc_time_stamp().value(), register_bank->getPC()); + /* Save mstatus privilege stack: MPIE <- MIE, MIE <- 0, MPP <- M */ + BaseType mstatus = register_bank->getCSR(CSR_MSTATUS); + if (mstatus & MSTATUS_MIE) { + mstatus |= MSTATUS_MPIE; + } else { + mstatus &= ~MSTATUS_MPIE; + } + mstatus &= ~MSTATUS_MIE; + /* MPP <- previous privilege mode (M-mode for now) + * TODO: when U/S-mode is implemented, set MPP to the + * privilege mode that was active before the trap */ + mstatus &= ~(0x3 << 11); /* clear MPP (bits 12:11) */ + mstatus |= (0x3 << 11); /* 0x3 = M-mode */ + register_bank->setCSR(CSR_MSTATUS, mstatus); + /* updated MEPC register */ BaseType old_pc = register_bank->getPC(); register_bank->setCSR(CSR_MEPC, old_pc); @@ -104,9 +129,21 @@ namespace riscv_tlm { /* update MCAUSE register, use the cause from the interrupt source */ register_bank->setCSR(CSR_MCAUSE, int_cause); - /* set new PC address */ - BaseType new_pc = register_bank->getCSR(CSR_MTVEC); - //new_pc = new_pc & 0xFFFFFFFC; // last two bits always to 0 + /* mtval is set to 0 for standard interrupts */ + register_bank->setCSR(CSR_MTVAL, 0); + + /* set new PC address, respecting mtvec MODE */ + BaseType mtvec = register_bank->getCSR(CSR_MTVEC); + BaseType mode = mtvec & 0x3; + BaseType base = mtvec & ~0x3; + BaseType new_pc; + if (mode == 1) { + /* Vectored: interrupts go to BASE + 4 * cause_code */ + new_pc = base + 4 * cause_code; + } else { + /* Direct: all traps go to BASE */ + new_pc = base; + } logger->debug("{} ns. PC: 0x{:x}. NEW PC Value 0x{:x}", sc_core::sc_time_stamp().value(), register_bank->getPC(), new_pc);