diff --git a/bhv/cv32e40x_wrapper.sv b/bhv/cv32e40x_wrapper.sv index 7708818e..451d4e20 100644 --- a/bhv/cv32e40x_wrapper.sv +++ b/bhv/cv32e40x_wrapper.sv @@ -233,6 +233,7 @@ module cv32e40x_wrapper .ptr_in_if_i (core_i.if_stage_i.ptr_in_if_o), .instr_req_o (core_i.instr_req_o), .instr_dbg_o (core_i.instr_dbg_o), + .mstatus_i (core_i.cs_registers_i.mstatus_rdata), .*); bind cv32e40x_cs_registers: core_i.cs_registers_i diff --git a/rtl/cv32e40x_clic_int_controller.sv b/rtl/cv32e40x_clic_int_controller.sv index bf6bd73f..778aa752 100644 --- a/rtl/cv32e40x_clic_int_controller.sv +++ b/rtl/cv32e40x_clic_int_controller.sv @@ -151,7 +151,7 @@ module cv32e40x_clic_int_controller import cv32e40x_pkg::*; /////////////////////////// // The outputs for mnxti will only be used within cs_registers when a CSR instruction is accessing mnxti - + // The mxnti path to interrupts does not take mstatus.mie or dcsr.stepie into account. assign mnxti_irq_pending_o = clic_irq_q && (clic_irq_level_q > mcause_i.mpil) && (clic_irq_level_q > mintthresh_i) && diff --git a/rtl/cv32e40x_controller_fsm.sv b/rtl/cv32e40x_controller_fsm.sv index a83723da..9cc6d56d 100644 --- a/rtl/cv32e40x_controller_fsm.sv +++ b/rtl/cv32e40x_controller_fsm.sv @@ -211,6 +211,10 @@ module cv32e40x_controller_fsm import cv32e40x_pkg::*; logic [2:0] debug_cause_n; logic [2:0] debug_cause_q; + // Flop for remembering causes of wakeup + logic woke_to_debug_q; + logic woke_to_interrupt_q; + logic [10:0] exc_cause; // id of taken interrupt. Max width, unused bits are tied off. logic fence_req_set; @@ -474,7 +478,9 @@ module cv32e40x_controller_fsm import cv32e40x_pkg::*; // timing is considered haltreq should be considered only asserted on the following // instruction (i.e. the asynchronous haltreq signal is considered asserted too late to // impact the current instruction in the pipeline). - assign async_debug_allowed = lsu_interruptible_i && !fencei_ongoing && !xif_in_wb && !clic_ptr_in_pipeline && sequence_interruptible; + // If the core woke up from sleep due to interrupts, the wakeup reason will be honored + // by not allowing async debug the cycle after wakeup. + assign async_debug_allowed = lsu_interruptible_i && !fencei_ongoing && !xif_in_wb && !clic_ptr_in_pipeline && sequence_interruptible && !woke_to_interrupt_q; // synchronous debug entry have far fewer restrictions than asynchronous entries. In principle synchronous debug entry should have the same // 'allowed' signal as exceptions - that is it should always be possible. @@ -491,7 +497,9 @@ module cv32e40x_controller_fsm import cv32e40x_pkg::*; // that can currently cause a deadlock if debug_req_i gets asserted while in debug mode, as // a pending but not allowed async debug will cause the ID stage to halt forever while trying // to get to an interruptible state. - assign pending_async_debug = debug_req_i && !debug_mode_q; + // When the core wakes up from sleep due to debug_req_i, woke_to_debug_q will be set for exactly one cycle + // to allow the core to enter debug one cycle after waking up, even though debug_req_i may have been deasserted. + assign pending_async_debug = (debug_req_i || woke_to_debug_q) && !debug_mode_q; // Determine cause of debug. Set for all causes of debug entry. // In case of ebreak during debug mode, the entry code in DEBUG_TAKEN will @@ -532,7 +540,9 @@ module cv32e40x_controller_fsm import cv32e40x_pkg::*; assign interrupt_allowed = lsu_interruptible_i && debug_interruptible && !fencei_ongoing && !xif_in_wb && !clic_ptr_in_pipeline && sequence_interruptible && !interrupt_blanking_q; // Allowing NMI's follow the same rule as regular interrupts, except we don't need to regard blanking of NMIs after a load/store. - assign nmi_allowed = lsu_interruptible_i && debug_interruptible && !fencei_ongoing && !xif_in_wb && !clic_ptr_in_pipeline && sequence_interruptible; + // If the core woke up from sleep due to either debug or regular interrupts, the wakeup reason is honored by not allowing NMIs in the cycle after + // waking up to such an event. + assign nmi_allowed = lsu_interruptible_i && debug_interruptible && !fencei_ongoing && !xif_in_wb && !clic_ptr_in_pipeline && sequence_interruptible && !(woke_to_debug_q || woke_to_interrupt_q); // Do not allow interrupts if in debug mode, or single stepping without dcsr.stepie set. assign debug_interruptible = !(debug_mode_q || (dcsr_i.step && !dcsr_i.stepie)); @@ -1332,6 +1342,16 @@ module cv32e40x_controller_fsm import cv32e40x_pkg::*; end end + // Flags for remembering if the core woke up due to a debug request or interrupt. + always_ff @(posedge clk, negedge rst_n) begin + if (rst_n == 1'b0) begin + woke_to_debug_q <= 1'b0; + woke_to_interrupt_q <= 1'b0; + end else begin + woke_to_debug_q <= (ctrl_fsm_cs == SLEEP) && debug_req_i; + woke_to_interrupt_q <= (ctrl_fsm_cs == SLEEP) && irq_wu_ctrl_i; + end + end ///////////////////// // Debug state FSM // diff --git a/sva/cv32e40x_controller_fsm_sva.sv b/sva/cv32e40x_controller_fsm_sva.sv index 0e4ad2bc..5b3067b5 100644 --- a/sva/cv32e40x_controller_fsm_sva.sv +++ b/sva/cv32e40x_controller_fsm_sva.sv @@ -112,7 +112,11 @@ module cv32e40x_controller_fsm_sva input logic debug_req_i, input logic fetch_enable_i, input logic instr_req_o, - input logic instr_dbg_o + input logic instr_dbg_o, + input logic wfe_in_wb, + input mstatus_t mstatus_i, + input logic woke_to_interrupt_q, + input logic woke_to_debug_q ); @@ -288,9 +292,11 @@ module cv32e40x_controller_fsm_sva else `uvm_error("controller", "Fencei handshake active while lsu_busy_o = 1") // assert that NMI's are not reported on irq_ack + // Exception for the case where the core wakes from SLEEP due to an interrupt + // - in that case the interrupt is honored while there may be a pending nmi. a_irq_ack_no_nmi : assert property (@(posedge clk) disable iff (!rst_n) - ctrl_fsm_o.irq_ack |-> !pending_nmi) + ctrl_fsm_o.irq_ack |-> !(pending_nmi && !woke_to_interrupt_q)) else `uvm_error("controller", "irq_ack set while there's a pending NMI") // Assert that intr_taken is always single cycle. I.e. no double counting @@ -875,8 +881,55 @@ end (abort_op_wb_i && (ctrl_fsm_ns == DEBUG_TAKEN))) else `uvm_error("controller", "Debug not entered on a WPT match") - - + // Ensure debug mode is entered if woken up by a debug request + a_sleep_to_debug: + assert property (@(posedge clk) disable iff (!rst_n) + (ctrl_fsm_cs == SLEEP) && + (ctrl_fsm_ns == FUNCTIONAL) && + debug_req_i && + !(pending_nmi || irq_wu_ctrl_i || (wfe_in_wb && wu_wfe_i)) + |=> + (ctrl_fsm_ns == DEBUG_TAKEN)) + else `uvm_error("controller", "Woke from sleep due to debug_req but debug mode not entered") + + // Ensure interrupt is taken if woken up by an interrupt + a_sleep_to_irq: + assert property (@(posedge clk) disable iff (!rst_n) + (ctrl_fsm_cs == SLEEP) && + (ctrl_fsm_ns == FUNCTIONAL) && + irq_wu_ctrl_i && + !(pending_nmi || debug_req_i || (wfe_in_wb && wu_wfe_i)) && + mstatus_i.mie + |=> + (ctrl_fsm_o.pc_mux == PC_TRAP_IRQ) || (ctrl_fsm_o.pc_mux == PC_TRAP_CLICV)) + else `uvm_error("controller", "Woke from sleep due to irq but irq not taken") + + // Ensure NMI is taken if woken up by an NMI + a_sleep_to_nmi: + assert property (@(posedge clk) disable iff (!rst_n) + (ctrl_fsm_cs == SLEEP) && + (ctrl_fsm_ns == FUNCTIONAL) && + pending_nmi && + !(debug_req_i || irq_wu_ctrl_i || (wfe_in_wb && wu_wfe_i)) + |=> + (ctrl_fsm_o.pc_mux == PC_TRAP_NMI)) + else `uvm_error("controller", "Woke from sleep due to NMI but NMI not taken") + + // woke_to_debug_q shall only be high for a single cycle + a_woke_to_debug_single_cycle: + assert property (@(posedge clk) disable iff (!rst_n) + woke_to_debug_q + |=> + !woke_to_debug_q) + else `uvm_error("controller", "woke_to_debug_q asserted for more than one cycle") + + // woke_to_interrupt_q shall only be high for a single cycle + a_woke_to_interrupt_single_cycle: + assert property (@(posedge clk) disable iff (!rst_n) + woke_to_interrupt_q + |=> + !woke_to_interrupt_q) + else `uvm_error("controller", "woke_to_interrupt_q asserted for more than one cycle") endmodule