diff --git a/doc/03_reference/cosim.rst b/doc/03_reference/cosim.rst index de8b0401..0f667539 100644 --- a/doc/03_reference/cosim.rst +++ b/doc/03_reference/cosim.rst @@ -133,7 +133,15 @@ The DV environment is responsible for determining when to call ``set_mip``, ``se The state of the incoming interrupts and debug request is sampled when an instruction moves from IF to ID/EX. The sampled state is tracked with the rest of the RVFI pipeline and used to call ``set_mip``, ``set_debug_req`` and ``set_nmi`` when the instruction is output by the RVFI. -See the comments in :file:`rtl/ibex_core.sv`, around the ``new_debug_req``, ``new_nmi`` and ``new_irq`` signals for further details. + +A complication occurs when more than one interrupt or debug requests occur between individual instruction fetches. +One interrupt or debug request may take priority over another when they all occur together but when they occur in time is important as well. +If interrupt and debug request notification is associated exclusively with retired instructions the co-simulation system cannot correctly prioritise multiple interrupts and debug requests. +To deal with this the RVFI can also signal an interrupt event not associated with an instruction by setting ``rvfi_ext_irq_valid`` without setting ``rvfi_valid``. +When this is set the interrupt related RVFI signals are valid and provide the interrupt state. +The RVFI is used in this way, as opposed to a separate notification interface, so the interrupt notifications are ordered relative to the retired instructions. + +See the comments in :file:`rtl/ibex_core.sv`, around the ``new_debug_req``, ``new_nmi``, ``new_irq`` and ``rvfi_irq_valid`` signals for further details. Memory Access Checking and Bus Errors ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/dv/cosim/spike_cosim.cc b/dv/cosim/spike_cosim.cc index c265401d..9a581e4f 100644 --- a/dv/cosim/spike_cosim.cc +++ b/dv/cosim/spike_cosim.cc @@ -571,11 +571,50 @@ void SpikeCosim::initial_proc_setup(uint32_t start_pc, uint32_t start_mtvec, } void SpikeCosim::set_mip(uint32_t mip) { + uint32_t new_mip = mip; + uint32_t old_mip = processor->get_state()->mip->read(); + processor->get_state()->mip->write_with_mask(0xffffffff, mip); + + if (processor->get_state()->debug_mode || + (processor->halt_request == processor_t::HR_REGULAR) || + (!get_field(processor->get_csr(CSR_MSTATUS), MSTATUS_MIE) && + processor->get_state()->prv == PRV_M)) { + // Return now if new MIP won't trigger an interrupt handler either because + // we're in or heading to debug mode or interrupts are disabled. + return; + } + + uint32_t old_enabled_irq = old_mip & processor->get_state()->mie->read(); + uint32_t new_enabled_irq = new_mip & processor->get_state()->mie->read(); + + // Check to see if new MIP will trigger an interrupt (which occurs when new + // MIP produces an enabled interrupt for the first time). + if ((old_enabled_irq == 0) && (new_enabled_irq != 0)) { + // Early interrupt handle if the interrupt is triggered. + early_interrupt_handle(); + } +} + +void SpikeCosim::early_interrupt_handle() { + // Execute a spike step on the assumption an interrupt will occur so no new + // instruction is executed just the state altered to reflect the interrupt. + uint32_t initial_spike_pc = (processor->get_state()->pc & 0xffffffff); + processor->step(1); + + if (processor->get_state()->last_inst_pc != PC_INVALID) { + std::stringstream err_str; + err_str << "Attempted step for interrupt, expecting no instruction would " + << "be executed but saw one. PC before: " << std::hex + << initial_spike_pc + << " PC after: " << (processor->get_state()->pc & 0xffffffff); + errors.emplace_back(err_str.str()); + } } void SpikeCosim::set_nmi(bool nmi) { - if (nmi && !nmi_mode && !processor->get_state()->debug_mode) { + if (nmi && !nmi_mode && !processor->get_state()->debug_mode && + processor->halt_request != processor_t::HR_REGULAR) { processor->get_state()->nmi = true; nmi_mode = true; @@ -585,11 +624,14 @@ void SpikeCosim::set_nmi(bool nmi) { mstack.mpie = get_field(processor->get_csr(CSR_MSTATUS), MSTATUS_MPIE); mstack.epc = processor->get_csr(CSR_MEPC); mstack.cause = processor->get_csr(CSR_MCAUSE); + + early_interrupt_handle(); } } void SpikeCosim::set_nmi_int(bool nmi_int) { - if (nmi_int && !nmi_mode && !processor->get_state()->debug_mode) { + if (nmi_int && !nmi_mode && !processor->get_state()->debug_mode && + processor->halt_request != processor_t::HR_REGULAR) { processor->get_state()->nmi_int = true; nmi_mode = true; @@ -599,6 +641,8 @@ void SpikeCosim::set_nmi_int(bool nmi_int) { mstack.mpie = get_field(processor->get_csr(CSR_MSTATUS), MSTATUS_MPIE); mstack.epc = processor->get_csr(CSR_MEPC); mstack.cause = processor->get_csr(CSR_MCAUSE); + + early_interrupt_handle(); } } diff --git a/dv/cosim/spike_cosim.h b/dv/cosim/spike_cosim.h index 3c07e8c4..369afa7f 100644 --- a/dv/cosim/spike_cosim.h +++ b/dv/cosim/spike_cosim.h @@ -83,6 +83,8 @@ class SpikeCosim : public simif_t, public Cosim { void initial_proc_setup(uint32_t start_pc, uint32_t start_mtvec, uint32_t mhpm_counter_num); + void early_interrupt_handle(); + unsigned int insn_cnt; public: diff --git a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_cosim_scoreboard.sv b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_cosim_scoreboard.sv index a8d0cbc9..8123d17d 100644 --- a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_cosim_scoreboard.sv +++ b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_cosim_scoreboard.sv @@ -116,6 +116,16 @@ class ibex_cosim_scoreboard extends uvm_scoreboard; forever begin rvfi_port.get(rvfi_instr); + if (rvfi_instr.irq_only) begin + // RVFI item is only notifying about new interrupts, not a retired instruction, so provide + // cosim with interrupt information and loop back to await the next item. + riscv_cosim_set_nmi(cosim_handle, rvfi_instr.nmi); + riscv_cosim_set_nmi_int(cosim_handle, rvfi_instr.nmi_int); + riscv_cosim_set_mip(cosim_handle, rvfi_instr.mip); + + continue; + end + if (iside_error_queue.size() > 0) begin // Remove entries from iside_error_queue where the instruction never reaches the RVFI // interface because it was flushed. @@ -131,10 +141,12 @@ class ibex_cosim_scoreboard extends uvm_scoreboard; end end + // Note these must be called in this order to ensure debug vs nmi vs normal interrupt are + // handled with the correct priority when they occur together. + riscv_cosim_set_debug_req(cosim_handle, rvfi_instr.debug_req); riscv_cosim_set_nmi(cosim_handle, rvfi_instr.nmi); riscv_cosim_set_nmi_int(cosim_handle, rvfi_instr.nmi_int); riscv_cosim_set_mip(cosim_handle, rvfi_instr.mip); - riscv_cosim_set_debug_req(cosim_handle, rvfi_instr.debug_req); riscv_cosim_set_mcycle(cosim_handle, rvfi_instr.mcycle); // Set performance counters through a pseudo-backdoor write diff --git a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_monitor.sv b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_monitor.sv index df5a79d9..c3fb29a2 100644 --- a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_monitor.sv +++ b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_monitor.sv @@ -27,10 +27,11 @@ class ibex_rvfi_monitor extends uvm_monitor; forever begin // Wait for a retired instruction - while(!vif.monitor_cb.valid) vif.wait_clks(1); + while(!(vif.monitor_cb.valid || vif.monitor_cb.ext_irq_valid)) vif.wait_clks(1); // Read instruction details from RVFI interface trans_collected = ibex_rvfi_seq_item::type_id::create("trans_collected"); + trans_collected.irq_only = !vif.monitor_cb.valid && vif.monitor_cb.ext_irq_valid; trans_collected.trap = vif.monitor_cb.trap; trans_collected.pc = vif.monitor_cb.pc_rdata; trans_collected.rd_addr = vif.monitor_cb.rd_addr; diff --git a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_seq_item.sv b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_seq_item.sv index 97a5cf6d..55e11f9d 100644 --- a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_seq_item.sv +++ b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_seq_item.sv @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 class ibex_rvfi_seq_item extends uvm_sequence_item; + bit irq_only; bit trap; bit [31:0] pc; bit [4:0] rd_addr; diff --git a/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv b/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv index 83e997fc..641e8152 100644 --- a/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv +++ b/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv @@ -32,6 +32,7 @@ interface core_ibex_rvfi_if(input logic clk); logic [31:0] ext_debug_req; logic [31:0] ext_rf_wr_suppress; logic [63:0] ext_mcycle; + logic ext_irq_valid; logic [31:0] ext_mhpmcounters [10]; logic [31:0] ext_mhpmcountersh [10]; @@ -70,6 +71,7 @@ interface core_ibex_rvfi_if(input logic clk); input ext_mhpmcounters; input ext_mhpmcountersh; input ext_ic_scr_key_valid; + input ext_irq_valid; endclocking task automatic wait_clks(input int num); diff --git a/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv b/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv index c62e762b..cf1f13b1 100644 --- a/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv +++ b/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv @@ -205,6 +205,7 @@ module core_ibex_tb_top; assign rvfi_if.ext_mhpmcounters = dut.rvfi_ext_mhpmcounters; assign rvfi_if.ext_mhpmcountersh = dut.rvfi_ext_mhpmcountersh; assign rvfi_if.ext_ic_scr_key_valid = dut.rvfi_ext_ic_scr_key_valid; + assign rvfi_if.ext_irq_valid = dut.rvfi_ext_irq_valid; // Irq interface connections assign irq_vif.reset = ~rst_n; // Dut_if interface connections diff --git a/rtl/ibex_core.sv b/rtl/ibex_core.sv index b73a56b6..67548fdb 100644 --- a/rtl/ibex_core.sv +++ b/rtl/ibex_core.sv @@ -147,6 +147,7 @@ module ibex_core import ibex_pkg::*; #( output logic [31:0] rvfi_ext_mhpmcounters [10], output logic [31:0] rvfi_ext_mhpmcountersh [10], output logic rvfi_ext_ic_scr_key_valid, + output logic rvfi_ext_irq_valid, `endif // CPU Control Signals @@ -1232,6 +1233,7 @@ module ibex_core import ibex_pkg::*; #( logic [31:0] rvfi_mem_addr_q; logic rvfi_trap_id; logic rvfi_trap_wb; + logic rvfi_irq_valid; logic [63:0] rvfi_stage_order_d; logic rvfi_id_done; logic rvfi_wb_done; @@ -1257,6 +1259,7 @@ module ibex_core import ibex_pkg::*; #( logic [31:0] rvfi_ext_stage_mhpmcounters [RVFI_STAGES][10]; logic [31:0] rvfi_ext_stage_mhpmcountersh [RVFI_STAGES][10]; logic rvfi_ext_stage_ic_scr_key_valid [RVFI_STAGES]; + logic rvfi_ext_stage_irq_valid [RVFI_STAGES+1]; logic rvfi_stage_valid_d [RVFI_STAGES]; @@ -1307,6 +1310,7 @@ module ibex_core import ibex_pkg::*; #( assign rvfi_ext_mhpmcounters = rvfi_ext_stage_mhpmcounters [RVFI_STAGES-1]; assign rvfi_ext_mhpmcountersh = rvfi_ext_stage_mhpmcountersh [RVFI_STAGES-1]; assign rvfi_ext_ic_scr_key_valid = rvfi_ext_stage_ic_scr_key_valid [RVFI_STAGES-1]; + assign rvfi_ext_irq_valid = rvfi_ext_stage_irq_valid [RVFI_STAGES]; // When an instruction takes a trap the `rvfi_trap` signal will be set. Instructions that take // traps flush the pipeline so ordinarily wouldn't be seen to be retire. The RVFI tracking @@ -1394,7 +1398,8 @@ module ibex_core import ibex_pkg::*; #( assign new_debug_req = (debug_req_i & ~debug_mode); assign new_nmi = irq_nm_i & ~nmi_mode & ~debug_mode; assign new_nmi_int = id_stage_i.controller_i.irq_nm_int & ~nmi_mode & ~debug_mode; - assign new_irq = irq_pending_o & csr_mstatus_mie & ~nmi_mode & ~debug_mode; + assign new_irq = irq_pending_o & (csr_mstatus_mie || (priv_mode_id == PRIV_LVL_U)) & ~nmi_mode & + ~debug_mode; always_ff @(posedge clk_i or negedge rst_ni) begin if (!rst_ni) begin @@ -1403,6 +1408,7 @@ module ibex_core import ibex_pkg::*; #( captured_nmi <= 1'b0; captured_nmi_int <= 1'b0; captured_debug_req <= 1'b0; + rvfi_irq_valid <= 1'b0; end else begin // Capture when ID stage has emptied out and something occurs that will cause a trap and we // haven't yet captured @@ -1421,6 +1427,18 @@ module ibex_core import ibex_pkg::*; #( captured_debug_req <= debug_req_i; end + // When the pipeline has emptied in preparation for handling a new interrupt send + // a notification up the RVFI pipeline. This is used by the cosim to deal with cases where an + // interrupt occurs before another interrupt or debug request but both occur before the first + // instruction of the handler is executed and retired (where the cosim will see all the + // interrupts and debug requests at once with no way to determine which occurred first). + if (~instr_valid_id & ~new_debug_req & (new_irq | new_nmi | new_nmi_int) & ready_wb & + ~captured_valid) begin + rvfi_irq_valid <= 1'b1; + end else begin + rvfi_irq_valid <= 1'b0; + end + // Capture cleared out as soon as a new instruction appears in ID if (if_stage_i.instr_valid_id_d) begin captured_valid <= 1'b0; @@ -1440,7 +1458,7 @@ module ibex_core import ibex_pkg::*; #( rvfi_ext_stage_nmi[0] <= '0; rvfi_ext_stage_nmi_int[0] <= '0; rvfi_ext_stage_debug_req[0] <= '0; - end else if (if_stage_i.instr_valid_id_d & if_stage_i.instr_new_id_d) begin + end else if ((if_stage_i.instr_valid_id_d & if_stage_i.instr_new_id_d) | rvfi_irq_valid) begin rvfi_ext_stage_mip[0] <= instr_valid_id | ~captured_valid ? cs_registers_i.mip : captured_mip; rvfi_ext_stage_nmi[0] <= instr_valid_id | ~captured_valid ? irq_nm_i : @@ -1453,6 +1471,29 @@ module ibex_core import ibex_pkg::*; #( end end + + // rvfi_irq_valid signals an interrupt event to the cosim. These should only occur when the RVFI + // pipe is empty so just send it straigh through. + for (genvar i = 0; i < RVFI_STAGES + 1; i = i + 1) begin : g_rvfi_irq_valid + if (i == 0) begin : g_rvfi_irq_valid_first_stage + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + rvfi_ext_stage_irq_valid[i] <= 1'b0; + end else begin + rvfi_ext_stage_irq_valid[i] <= rvfi_irq_valid; + end + end + end else begin : g_rvfi_irq_valid_other_stages + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + rvfi_ext_stage_irq_valid[i] <= 1'b0; + end else begin + rvfi_ext_stage_irq_valid[i] <= rvfi_ext_stage_irq_valid[i-1]; + end + end + end + end + for (genvar i = 0; i < RVFI_STAGES; i = i + 1) begin : g_rvfi_stages always_ff @(posedge clk_i or negedge rst_ni) begin if (!rst_ni) begin @@ -1515,10 +1556,6 @@ module ibex_core import ibex_pkg::*; #( rvfi_stage_mem_rdata[i] <= rvfi_mem_rdata_d; rvfi_stage_mem_wdata[i] <= rvfi_mem_wdata_d; rvfi_stage_mem_addr[i] <= rvfi_mem_addr_d; - rvfi_ext_stage_mip[i+1] <= rvfi_ext_stage_mip[i]; - rvfi_ext_stage_nmi[i+1] <= rvfi_ext_stage_nmi[i]; - rvfi_ext_stage_nmi_int[i+1] <= rvfi_ext_stage_nmi_int[i]; - rvfi_ext_stage_debug_req[i+1] <= rvfi_ext_stage_debug_req[i]; rvfi_ext_stage_debug_mode[i] <= debug_mode; rvfi_ext_stage_mcycle[i] <= cs_registers_i.mcycle_counter_i.counter_val_o; rvfi_ext_stage_ic_scr_key_valid[i] <= cs_registers_i.cpuctrlsts_ic_scr_key_valid_q; @@ -1529,6 +1566,17 @@ module ibex_core import ibex_pkg::*; #( rvfi_ext_stage_mhpmcountersh[i][k] <= cs_registers_i.mhpmcounter[k+3][63:32]; end end + + // Some of the rvfi_ext_* signals are used to provide an interrupt notification (signalled + // via rvfi_ext_irq_valid) when there isn't a valid retired instruction as well as + // providing information along with a retired instruction. Move these up the rvfi pipeline + // for both cases. + if (rvfi_id_done | rvfi_ext_stage_irq_valid[i]) begin + rvfi_ext_stage_mip[i+1] <= rvfi_ext_stage_mip[i]; + rvfi_ext_stage_nmi[i+1] <= rvfi_ext_stage_nmi[i]; + rvfi_ext_stage_nmi_int[i+1] <= rvfi_ext_stage_nmi_int[i]; + rvfi_ext_stage_debug_req[i+1] <= rvfi_ext_stage_debug_req[i]; + end end else begin if (rvfi_wb_done) begin rvfi_stage_halt[i] <= rvfi_stage_halt[i-1]; @@ -1559,16 +1607,23 @@ module ibex_core import ibex_pkg::*; #( rvfi_stage_rd_wdata[i] <= rvfi_rd_wdata_d; rvfi_stage_mem_rdata[i] <= rvfi_mem_rdata_d; - rvfi_ext_stage_mip[i+1] <= rvfi_ext_stage_mip[i]; - rvfi_ext_stage_nmi[i+1] <= rvfi_ext_stage_nmi[i]; - rvfi_ext_stage_nmi_int[i+1] <= rvfi_ext_stage_nmi_int[i]; - rvfi_ext_stage_debug_req[i+1] <= rvfi_ext_stage_debug_req[i]; rvfi_ext_stage_debug_mode[i] <= rvfi_ext_stage_debug_mode[i-1]; rvfi_ext_stage_mcycle[i] <= rvfi_ext_stage_mcycle[i-1]; rvfi_ext_stage_ic_scr_key_valid[i] <= rvfi_ext_stage_ic_scr_key_valid[i-1]; rvfi_ext_stage_mhpmcounters[i] <= rvfi_ext_stage_mhpmcounters[i-1]; rvfi_ext_stage_mhpmcountersh[i] <= rvfi_ext_stage_mhpmcountersh[i-1]; end + + // Some of the rvfi_ext_* signals are used to provide an interrupt notification (signalled + // via rvfi_ext_irq_valid) when there isn't a valid retired instruction as well as + // providing information along with a retired instruction. Move these up the rvfi pipeline + // for both cases. + if (rvfi_wb_done | rvfi_ext_stage_irq_valid[i]) begin + rvfi_ext_stage_mip[i+1] <= rvfi_ext_stage_mip[i]; + rvfi_ext_stage_nmi[i+1] <= rvfi_ext_stage_nmi[i]; + rvfi_ext_stage_nmi_int[i+1] <= rvfi_ext_stage_nmi_int[i]; + rvfi_ext_stage_debug_req[i+1] <= rvfi_ext_stage_debug_req[i]; + end end end end diff --git a/rtl/ibex_lockstep.sv b/rtl/ibex_lockstep.sv index 9f2cf22f..7778ff74 100644 --- a/rtl/ibex_lockstep.sv +++ b/rtl/ibex_lockstep.sv @@ -437,6 +437,7 @@ module ibex_lockstep import ibex_pkg::*; #( .rvfi_ext_mhpmcounters (), .rvfi_ext_mhpmcountersh (), .rvfi_ext_ic_scr_key_valid (), + .rvfi_ext_irq_valid (), `endif .fetch_enable_i (shadow_inputs_q[0].fetch_enable), diff --git a/rtl/ibex_top.sv b/rtl/ibex_top.sv index 7621f21d..d1760630 100644 --- a/rtl/ibex_top.sv +++ b/rtl/ibex_top.sv @@ -126,6 +126,7 @@ module ibex_top import ibex_pkg::*; #( output logic [31:0] rvfi_ext_mhpmcounters [10], output logic [31:0] rvfi_ext_mhpmcountersh [10], output logic rvfi_ext_ic_scr_key_valid, + output logic rvfi_ext_irq_valid, `endif // CPU Control Signals @@ -398,6 +399,7 @@ module ibex_top import ibex_pkg::*; #( .rvfi_ext_mhpmcounters, .rvfi_ext_mhpmcountersh, .rvfi_ext_ic_scr_key_valid, + .rvfi_ext_irq_valid, `endif .fetch_enable_i (fetch_enable_buf), diff --git a/rtl/ibex_top_tracing.sv b/rtl/ibex_top_tracing.sv index 61964560..7e480de5 100644 --- a/rtl/ibex_top_tracing.sv +++ b/rtl/ibex_top_tracing.sv @@ -130,6 +130,7 @@ module ibex_top_tracing import ibex_pkg::*; #( logic [31:0] rvfi_ext_mhpmcounters [10]; logic [31:0] rvfi_ext_mhpmcountersh [10]; logic rvfi_ext_ic_scr_key_valid; + logic rvfi_ext_irq_valid; logic [31:0] unused_perf_regs [10]; logic [31:0] unused_perf_regsh [10]; @@ -143,6 +144,7 @@ module ibex_top_tracing import ibex_pkg::*; #( logic unused_rvfi_ext_rf_wr_suppress; logic [63:0] unused_rvfi_ext_mcycle; logic unused_rvfi_ext_ic_scr_key_valid; + logic unused_rvfi_ext_irq_valid; // Tracer doesn't use these signals, though other modules may probe down into tracer to observe // them. @@ -156,6 +158,7 @@ module ibex_top_tracing import ibex_pkg::*; #( assign unused_perf_regs = rvfi_ext_mhpmcounters; assign unused_perf_regsh = rvfi_ext_mhpmcountersh; assign unused_rvfi_ext_ic_scr_key_valid = rvfi_ext_ic_scr_key_valid; + assign unused_rvfi_ext_irq_valid = rvfi_ext_irq_valid; ibex_top #( .PMPEnable ( PMPEnable ), @@ -259,6 +262,7 @@ module ibex_top_tracing import ibex_pkg::*; #( .rvfi_ext_mhpmcounters, .rvfi_ext_mhpmcountersh, .rvfi_ext_ic_scr_key_valid, + .rvfi_ext_irq_valid, .fetch_enable_i, .alert_minor_o,