[dv] Improve interrupt signalling to cosim

Previously any changes in interrupt state or debug requests were
strictly associated with retired instructions. This causes cosim
mismatches where a lower priority interrupt occurs in time before a
higher priority interrupt or debug request but between instruction
fetches/retirements so both the low and high priority interrupts are
signalled with the instruction retirement.

This introduces a way for the RVFI to signal an interrupt has occurred
that isn't associated with an instruction retirement to allow the cosim
to see the seperation in time between different interrupts and debug
requests and hence model behaviour correctly.
This commit is contained in:
Greg Chadwick 2023-04-13 17:56:20 +01:00
parent e587f20d44
commit 1120e8ddbf
12 changed files with 148 additions and 15 deletions

View file

@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -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();
}
}

View file

@ -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:

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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),

View file

@ -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),

View file

@ -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,