mirror of
https://github.com/lowRISC/ibex.git
synced 2025-04-22 12:57:13 -04:00
[dv/ibex] Add two new interrupt/debug tests
As a result of lowRISC/opentitan#2405 and lowRISC/ibex#928 (reporting that interrupts that came in while a load instruction was in the ID stage caused some incorrect behavior in Ibex), this PR adds some new directed interrupt and debug tests to check that the core behaves properly during execution of each supported instruction when some external irq/debug stimulus comes in. To do this, we use the two new functions `decode_instr(...)` and `decode_compressed_instr(...)` in `core_ibex_test_list.sv` to "decode" every instruction that the `core_ibex_instr_monitor_if` sees in the ID stage of the pipeline. Once the testbench decodes an instruction that we have not seen before, it can then drive interrupt or debug stimulus into the core. Once any given instruction has been detected by the testbench (and stimulus driven), it will no longer drive stimulus if this instruction is seen in the decode pipeline (e.g. if we have previously detected a `c.addi` instruction in the ID stage and have driven irq/debug stimulus, we will no longer drive stimulus if we see another `c.addi` instruction, no matter the operands). This is to avoid driving irq/debug stimulus after every single instruction as this will add a huge unwanted amount of simulation latency. A few notes: - We drive irq/debug stimulus into the core every time we see a `wfi` instruction, as otherwise we will timeout as the core waits infinitely for some stimulus from the outside world. - We ignore some system-level instructions (ebreak/mret/dret) and illegal instructionsfor now, as driving stimulus during these instructions will result in a nested trap, which requires special handling. - The interrupt agent was modified slightly to drive stimulus by default on the falling edge of the clock, so this way we can "catch" instructions that are in the ID pipeline for only a single cycle. - The duration for which the testbench raises `debug_req_i` for the core is also increased to avoid edge cases where we lower the debug line too early (e.g. while long multicycle instructions like `div` are executing in the ID stage).
This commit is contained in:
parent
414ff7eeb0
commit
75dadb5aef
11 changed files with 449 additions and 73 deletions
|
@ -11,6 +11,7 @@ interface irq_if(input clk);
|
|||
logic irq_nm; // non-maskeable interrupt
|
||||
|
||||
clocking driver_cb @(posedge clk);
|
||||
default output negedge;
|
||||
input reset;
|
||||
output irq_software;
|
||||
output irq_timer;
|
||||
|
@ -32,4 +33,8 @@ interface irq_if(input clk);
|
|||
repeat (num) @(posedge clk);
|
||||
endtask
|
||||
|
||||
task automatic wait_neg_clks(input int num);
|
||||
repeat (num) @(negedge clk);
|
||||
endtask
|
||||
|
||||
endinterface
|
||||
|
|
|
@ -52,7 +52,7 @@ class irq_master_driver extends uvm_driver #(irq_seq_item);
|
|||
drive_seq_item(rsp);
|
||||
seq_item_port.item_done(rsp);
|
||||
end else begin
|
||||
vif.wait_clks(1);
|
||||
vif.wait_neg_clks(1);
|
||||
end
|
||||
end
|
||||
endtask : get_and_drive
|
||||
|
|
18
dv/uvm/core_ibex/env/core_ibex_instr_monitor_if.sv
vendored
Normal file
18
dv/uvm/core_ibex/env/core_ibex_instr_monitor_if.sv
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Interface to probe the instruction moving through the pipeline
|
||||
//
|
||||
// TODO: does not support dummy instruction insertion right now,
|
||||
// might have to revisit and update.
|
||||
interface core_ibex_instr_monitor_if #(parameter DATA_WIDTH = 32);
|
||||
|
||||
// ID stage
|
||||
logic valid_id;
|
||||
logic err_id;
|
||||
logic is_compressed_id;
|
||||
logic [15:0] instr_compressed_id;
|
||||
logic [DATA_WIDTH-1:0] instr_id;
|
||||
|
||||
endinterface
|
|
@ -68,6 +68,7 @@ ${PRJ_DIR}/ibex/dv/uvm/core_ibex/common/ibex_mem_intf_agent/ibex_mem_intf.sv
|
|||
${PRJ_DIR}/ibex/dv/uvm/core_ibex/common/ibex_mem_intf_agent/ibex_mem_intf_agent_pkg.sv
|
||||
${PRJ_DIR}/ibex/dv/uvm/core_ibex/common/irq_agent/irq_if.sv
|
||||
${PRJ_DIR}/ibex/dv/uvm/core_ibex/common/irq_agent/irq_agent_pkg.sv
|
||||
${PRJ_DIR}/ibex/dv/uvm/core_ibex/env/core_ibex_instr_monitor_if.sv
|
||||
${PRJ_DIR}/ibex/dv/uvm/core_ibex/env/core_ibex_dut_probe_if.sv
|
||||
${PRJ_DIR}/ibex/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv
|
||||
${PRJ_DIR}/ibex/dv/uvm/core_ibex/env/core_ibex_csr_if.sv
|
||||
|
|
|
@ -186,6 +186,33 @@
|
|||
compare_final_value_only: 1
|
||||
verbose: 1
|
||||
|
||||
- test: riscv_debug_instr_test
|
||||
description: >
|
||||
At a high level, this test checks that Ibex can correctly respond after receiving debug
|
||||
stimulus while every supported instruction is in its decoding stage.
|
||||
e.g. If the test detects a LUI instruction in the decoding stage, a debug request is
|
||||
generated and sent into Ibex. We never send a debug request if we see a LUI instruction again,
|
||||
as we don't want to send in debug requests for every instruction that is executed.
|
||||
iterations: 25
|
||||
gen_test: riscv_rand_instr_test
|
||||
gen_opts: >
|
||||
+require_signature_addr=1
|
||||
+gen_debug_section=1
|
||||
+randomize_csr=1
|
||||
+no_csr_instr=0
|
||||
+no_ebreak=1
|
||||
+no_fence=0
|
||||
+no_wfi=0
|
||||
+directed_instr_0=riscv_load_store_rand_instr_stream,40
|
||||
+directed_instr_1=riscv_load_store_hazard_instr_stream,40
|
||||
rtl_test: core_ibex_debug_instr_test
|
||||
sim_opts: >
|
||||
+require_signature_addr=1
|
||||
+enable_debug_seq=1
|
||||
compare_opts:
|
||||
compare_final_value_only: 1
|
||||
verbose: 1
|
||||
|
||||
- test: riscv_debug_wfi_test
|
||||
description: >
|
||||
Assert debug_req while core is in WFI sleep state, should jump to debug mode
|
||||
|
@ -384,6 +411,34 @@
|
|||
compare_opts:
|
||||
compare_final_value_only: 1
|
||||
|
||||
- test: riscv_interrupt_instr_test
|
||||
description: >
|
||||
At a high level, this test checks that Ibex can correctly respond after receiving interrupt
|
||||
stimulus while every supported instruction is in its decoding stage.
|
||||
e.g. If the test detects a LUI instruction in the decoding stage, an interrupt is
|
||||
generated and sent into Ibex. We never send an interrupt if we see a LUI instruction again,
|
||||
as we don't want to send interrupts for every instruction that is executed.
|
||||
iterations: 25
|
||||
gen_test: riscv_rand_instr_test
|
||||
gen_opts: >
|
||||
+instr_cnt=6000
|
||||
+require_signature_addr=1
|
||||
+enable_interrupt=1
|
||||
+enable_timer_irq=1
|
||||
+randomize_csr=1
|
||||
+no_csr_instr=0
|
||||
+no_ebreak=1
|
||||
+no_fence=0
|
||||
+no_wfi=0
|
||||
+directed_instr_0=riscv_load_store_rand_instr_stream,40
|
||||
+directed_instr_1=riscv_load_store_hazard_instr_stream,40
|
||||
rtl_test: core_ibex_interrupt_instr_test
|
||||
sim_opts: >
|
||||
+require_signature_addr=1
|
||||
+enable_irq_single_seq=1
|
||||
compare_opts:
|
||||
compare_final_value_only: 1
|
||||
|
||||
- test: riscv_interrupt_wfi_test
|
||||
description: >
|
||||
Inject interrupts after a encountering wfi instructions.
|
||||
|
|
|
@ -20,6 +20,9 @@ module core_ibex_tb_top;
|
|||
// DUT probe interface
|
||||
core_ibex_dut_probe_if dut_if(.clk(clk));
|
||||
|
||||
// Instruction monitor interface
|
||||
core_ibex_instr_monitor_if instr_monitor_if();
|
||||
|
||||
// RVFI interface
|
||||
core_ibex_rvfi_if rvfi_if(.clk(clk));
|
||||
|
||||
|
@ -93,53 +96,63 @@ module core_ibex_tb_top;
|
|||
);
|
||||
|
||||
// Data load/store vif connection
|
||||
assign data_mem_vif.reset = ~rst_n;
|
||||
assign data_mem_vif.reset = ~rst_n;
|
||||
// Instruction fetch vif connnection
|
||||
assign instr_mem_vif.reset = ~rst_n;
|
||||
assign instr_mem_vif.we = 0;
|
||||
assign instr_mem_vif.be = 0;
|
||||
assign instr_mem_vif.wdata = 0;
|
||||
assign instr_mem_vif.reset = ~rst_n;
|
||||
assign instr_mem_vif.we = 0;
|
||||
assign instr_mem_vif.be = 0;
|
||||
assign instr_mem_vif.wdata = 0;
|
||||
// RVFI interface connections
|
||||
assign rvfi_if.valid = dut.rvfi_valid;
|
||||
assign rvfi_if.order = dut.rvfi_order;
|
||||
assign rvfi_if.insn = dut.rvfi_insn;
|
||||
assign rvfi_if.trap = dut.rvfi_trap;
|
||||
assign rvfi_if.intr = dut.rvfi_intr;
|
||||
assign rvfi_if.mode = dut.rvfi_mode;
|
||||
assign rvfi_if.ixl = dut.rvfi_ixl;
|
||||
assign rvfi_if.rs1_addr = dut.rvfi_rs1_addr;
|
||||
assign rvfi_if.rs2_addr = dut.rvfi_rs2_addr;
|
||||
assign rvfi_if.rs1_rdata = dut.rvfi_rs1_rdata;
|
||||
assign rvfi_if.rs2_rdata = dut.rvfi_rs2_rdata;
|
||||
assign rvfi_if.rd_addr = dut.rvfi_rd_addr;
|
||||
assign rvfi_if.rd_wdata = dut.rvfi_rd_wdata;
|
||||
assign rvfi_if.pc_rdata = dut.rvfi_pc_rdata;
|
||||
assign rvfi_if_pc_wdata = dut.rvfi_pc_wdata;
|
||||
assign rvfi_if.mem_addr = dut.rvfi_mem_addr;
|
||||
assign rvfi_if.mem_rmask = dut.rvfi_mem_rmask;
|
||||
assign rvfi_if.mem_rdata = dut.rvfi_mem_rdata;
|
||||
assign rvfi_if.mem_wdata = dut.rvfi_mem_wdata;
|
||||
assign rvfi_if.valid = dut.rvfi_valid;
|
||||
assign rvfi_if.order = dut.rvfi_order;
|
||||
assign rvfi_if.insn = dut.rvfi_insn;
|
||||
assign rvfi_if.trap = dut.rvfi_trap;
|
||||
assign rvfi_if.intr = dut.rvfi_intr;
|
||||
assign rvfi_if.mode = dut.rvfi_mode;
|
||||
assign rvfi_if.ixl = dut.rvfi_ixl;
|
||||
assign rvfi_if.rs1_addr = dut.rvfi_rs1_addr;
|
||||
assign rvfi_if.rs2_addr = dut.rvfi_rs2_addr;
|
||||
assign rvfi_if.rs1_rdata = dut.rvfi_rs1_rdata;
|
||||
assign rvfi_if.rs2_rdata = dut.rvfi_rs2_rdata;
|
||||
assign rvfi_if.rd_addr = dut.rvfi_rd_addr;
|
||||
assign rvfi_if.rd_wdata = dut.rvfi_rd_wdata;
|
||||
assign rvfi_if.pc_rdata = dut.rvfi_pc_rdata;
|
||||
assign rvfi_if_pc_wdata = dut.rvfi_pc_wdata;
|
||||
assign rvfi_if.mem_addr = dut.rvfi_mem_addr;
|
||||
assign rvfi_if.mem_rmask = dut.rvfi_mem_rmask;
|
||||
assign rvfi_if.mem_rdata = dut.rvfi_mem_rdata;
|
||||
assign rvfi_if.mem_wdata = dut.rvfi_mem_wdata;
|
||||
// Irq interface connections
|
||||
assign irq_vif.reset = ~rst_n;
|
||||
assign irq_vif.reset = ~rst_n;
|
||||
// Dut_if interface connections
|
||||
assign dut_if.ecall = dut.u_ibex_core.id_stage_i.controller_i.ecall_insn;
|
||||
assign dut_if.wfi = dut.u_ibex_core.id_stage_i.controller_i.wfi_insn;
|
||||
assign dut_if.ebreak = dut.u_ibex_core.id_stage_i.controller_i.ebrk_insn;
|
||||
assign dut_if.illegal_instr = dut.u_ibex_core.id_stage_i.controller_i.illegal_insn_d;
|
||||
assign dut_if.dret = dut.u_ibex_core.id_stage_i.controller_i.dret_insn;
|
||||
assign dut_if.mret = dut.u_ibex_core.id_stage_i.controller_i.mret_insn;
|
||||
assign dut_if.reset = ~rst_n;
|
||||
assign dut_if.priv_mode = dut.u_ibex_core.priv_mode_id;
|
||||
assign dut_if.ecall = dut.u_ibex_core.id_stage_i.controller_i.ecall_insn;
|
||||
assign dut_if.wfi = dut.u_ibex_core.id_stage_i.controller_i.wfi_insn;
|
||||
assign dut_if.ebreak = dut.u_ibex_core.id_stage_i.controller_i.ebrk_insn;
|
||||
assign dut_if.illegal_instr = dut.u_ibex_core.id_stage_i.controller_i.illegal_insn_d;
|
||||
assign dut_if.dret = dut.u_ibex_core.id_stage_i.controller_i.dret_insn;
|
||||
assign dut_if.mret = dut.u_ibex_core.id_stage_i.controller_i.mret_insn;
|
||||
assign dut_if.reset = ~rst_n;
|
||||
assign dut_if.priv_mode = dut.u_ibex_core.priv_mode_id;
|
||||
// Instruction monitor connections
|
||||
assign instr_monitor_if.valid_id = dut.u_ibex_core.id_stage_i.instr_valid_i;
|
||||
assign instr_monitor_if.err_id = dut.u_ibex_core.id_stage_i.controller_i.instr_fetch_err;
|
||||
assign instr_monitor_if.is_compressed_id = dut.u_ibex_core.id_stage_i.instr_is_compressed_i;
|
||||
assign instr_monitor_if.instr_compressed_id = dut.u_ibex_core.id_stage_i.instr_rdata_c_i;
|
||||
assign instr_monitor_if.instr_id = dut.u_ibex_core.id_stage_i.instr_rdata_i;
|
||||
// CSR interface connections
|
||||
assign csr_if.csr_access = dut.u_ibex_core.csr_access;
|
||||
assign csr_if.csr_addr = dut.u_ibex_core.csr_addr;
|
||||
assign csr_if.csr_wdata = dut.u_ibex_core.csr_wdata;
|
||||
assign csr_if.csr_rdata = dut.u_ibex_core.csr_rdata;
|
||||
assign csr_if.csr_op = dut.u_ibex_core.csr_op;
|
||||
assign csr_if.csr_access = dut.u_ibex_core.csr_access;
|
||||
assign csr_if.csr_addr = dut.u_ibex_core.csr_addr;
|
||||
assign csr_if.csr_wdata = dut.u_ibex_core.csr_wdata;
|
||||
assign csr_if.csr_rdata = dut.u_ibex_core.csr_rdata;
|
||||
assign csr_if.csr_op = dut.u_ibex_core.csr_op;
|
||||
|
||||
initial begin
|
||||
uvm_config_db#(virtual clk_if)::set(null, "*", "clk_if", ibex_clk_if);
|
||||
uvm_config_db#(virtual core_ibex_dut_probe_if)::set(null, "*", "dut_if", dut_if);
|
||||
uvm_config_db#(virtual core_ibex_instr_monitor_if)::set(null,
|
||||
"*",
|
||||
"instr_monitor_if",
|
||||
instr_monitor_if);
|
||||
uvm_config_db#(virtual core_ibex_csr_if)::set(null, "*", "csr_if", csr_if);
|
||||
uvm_config_db#(virtual core_ibex_rvfi_if)::set(null, "*", "rvfi_if", rvfi_if);
|
||||
uvm_config_db#(virtual ibex_mem_intf)::set(null, "*data_if_slave*", "vif", data_mem_vif);
|
||||
|
|
|
@ -8,6 +8,7 @@ class core_ibex_base_test extends uvm_test;
|
|||
core_ibex_env_cfg cfg;
|
||||
virtual clk_if clk_vif;
|
||||
virtual core_ibex_dut_probe_if dut_vif;
|
||||
virtual core_ibex_instr_monitor_if instr_vif;
|
||||
virtual core_ibex_csr_if csr_vif;
|
||||
mem_model_pkg::mem_model mem;
|
||||
core_ibex_vseq vseq;
|
||||
|
@ -39,13 +40,18 @@ class core_ibex_base_test extends uvm_test;
|
|||
super.build_phase(phase);
|
||||
$value$plusargs("timeout_in_cycles=%0d", timeout_in_cycles);
|
||||
if (!uvm_config_db#(virtual clk_if)::get(null, "", "clk_if", clk_vif)) begin
|
||||
`uvm_fatal(get_full_name(), "Cannot get clk_if")
|
||||
`uvm_fatal(`gfn, "Cannot get clk_if")
|
||||
end
|
||||
if (!uvm_config_db#(virtual core_ibex_dut_probe_if)::get(null, "", "dut_if", dut_vif)) begin
|
||||
`uvm_fatal(get_full_name(), "Cannot get dut_if")
|
||||
`uvm_fatal(`gfn, "Cannot get dut_if")
|
||||
end
|
||||
if (!uvm_config_db#(virtual core_ibex_instr_monitor_if)::get(null, "",
|
||||
"instr_monitor_if",
|
||||
instr_vif)) begin
|
||||
`uvm_fatal(`gfn, "Cannot get instr_monitor_if")
|
||||
end
|
||||
if (!uvm_config_db#(virtual core_ibex_csr_if)::get(null, "", "csr_if", csr_vif)) begin
|
||||
`uvm_fatal(get_full_name(), "Cannot get csr_if")
|
||||
`uvm_fatal(`gfn, "Cannot get csr_if")
|
||||
end
|
||||
env = core_ibex_env::type_id::create("env", this);
|
||||
cfg = core_ibex_env_cfg::type_id::create("cfg", this);
|
||||
|
|
|
@ -66,56 +66,76 @@ endclass
|
|||
|
||||
// Interrupt sequences
|
||||
|
||||
class irq_raise_seq extends core_base_seq#(irq_seq_item);
|
||||
class irq_base_seq extends core_base_seq #(irq_seq_item);
|
||||
|
||||
`uvm_object_utils(irq_raise_seq)
|
||||
`uvm_object_utils(irq_base_seq)
|
||||
`uvm_object_new
|
||||
|
||||
bit no_nmi;
|
||||
bit no_fast;
|
||||
|
||||
virtual task send_req();
|
||||
irq_seq_item irq;
|
||||
irq = irq_seq_item::type_id::create($sformatf("irq_raise_single[%0d]", iteration_cnt));
|
||||
irq = irq_seq_item::type_id::create($sformatf("irq_seq_item[%0d]", iteration_cnt));
|
||||
start_item(irq);
|
||||
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq, num_of_interrupt > 1; irq_nm == ~no_nmi;)
|
||||
randomize_item(irq);
|
||||
finish_item(irq);
|
||||
get_response(irq);
|
||||
endtask
|
||||
|
||||
virtual function void randomize_item(irq_seq_item irq);
|
||||
`uvm_fatal(`gfn, "This task must be implemented in extended irq sequences")
|
||||
endfunction
|
||||
|
||||
endclass
|
||||
|
||||
class irq_raise_single_seq extends core_base_seq#(irq_seq_item);
|
||||
class irq_raise_seq extends irq_base_seq;
|
||||
|
||||
`uvm_object_utils(irq_raise_seq)
|
||||
`uvm_object_new
|
||||
|
||||
bit no_nmi;
|
||||
bit no_fast;
|
||||
|
||||
virtual function void randomize_item(irq_seq_item irq);
|
||||
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq, num_of_interrupt > 1;
|
||||
irq_nm == ~no_nmi;
|
||||
if (no_fast) {
|
||||
irq_fast == '0;
|
||||
})
|
||||
endfunction
|
||||
|
||||
endclass
|
||||
|
||||
class irq_raise_single_seq extends irq_base_seq;
|
||||
|
||||
`uvm_object_utils(irq_raise_single_seq)
|
||||
`uvm_object_new
|
||||
bit no_nmi;
|
||||
|
||||
virtual task send_req();
|
||||
irq_seq_item irq;
|
||||
irq = irq_seq_item::type_id::create($sformatf("irq_raise_single[%0d]", iteration_cnt));
|
||||
start_item(irq);
|
||||
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq, num_of_interrupt == 1; irq_nm == ~no_nmi;)
|
||||
finish_item(irq);
|
||||
get_response(irq);
|
||||
endtask
|
||||
bit no_nmi;
|
||||
bit no_fast;
|
||||
|
||||
virtual function void randomize_item(irq_seq_item irq);
|
||||
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq, num_of_interrupt == 1;
|
||||
irq_nm == ~no_nmi;
|
||||
if (no_fast) {
|
||||
irq_fast == '0;
|
||||
})
|
||||
endfunction
|
||||
|
||||
endclass
|
||||
|
||||
// Irq sequence to deassert all interrupt lines, since Ibex interrupts are level sensitive
|
||||
class irq_drop_seq extends core_base_seq#(irq_seq_item);
|
||||
class irq_drop_seq extends irq_base_seq;
|
||||
|
||||
`uvm_object_utils(irq_drop_seq)
|
||||
`uvm_object_new
|
||||
|
||||
// TODO(udinator) - for nested interrupt tests, test scenarios where a random number of interrupts
|
||||
// are dropped
|
||||
virtual task send_req();
|
||||
irq_seq_item irq;
|
||||
irq = irq_seq_item::type_id::create($sformatf("irq_drop[%0d]", iteration_cnt));
|
||||
start_item(irq);
|
||||
virtual function void randomize_item(irq_seq_item irq);
|
||||
`DV_CHECK_RANDOMIZE_WITH_FATAL(irq, num_of_interrupt == 0;)
|
||||
finish_item(irq);
|
||||
get_response(irq);
|
||||
endtask
|
||||
endfunction
|
||||
|
||||
endclass
|
||||
|
||||
|
@ -127,6 +147,8 @@ class debug_seq extends core_base_seq#(irq_seq_item);
|
|||
`uvm_object_utils(debug_seq)
|
||||
`uvm_object_new
|
||||
|
||||
int unsigned drop_delay = 75;
|
||||
|
||||
virtual task body();
|
||||
if (!uvm_config_db#(virtual core_ibex_dut_probe_if)::get(null, "", "dut_if", dut_vif)) begin
|
||||
`uvm_fatal(get_full_name(), "Cannot get dut_if")
|
||||
|
@ -138,7 +160,7 @@ class debug_seq extends core_base_seq#(irq_seq_item);
|
|||
virtual task send_req();
|
||||
`uvm_info(get_full_name(), "Sending debug request", UVM_HIGH)
|
||||
dut_vif.dut_cb.debug_req <= 1'b1;
|
||||
clk_vif.wait_clks(50);
|
||||
clk_vif.wait_clks(drop_delay);
|
||||
dut_vif.dut_cb.debug_req <= 1'b0;
|
||||
endtask
|
||||
|
||||
|
|
|
@ -196,11 +196,12 @@ class core_ibex_debug_intr_basic_test extends core_ibex_base_test;
|
|||
endtask
|
||||
|
||||
virtual task send_irq_stimulus_start(input bit no_nmi,
|
||||
input bit no_fast,
|
||||
output bit ret_val);
|
||||
bit irq_valid;
|
||||
// send the interrupt
|
||||
if (cfg.enable_irq_single_seq) vseq.start_irq_raise_single_seq(no_nmi);
|
||||
else if (cfg.enable_irq_multiple_seq) vseq.start_irq_raise_seq(no_nmi);
|
||||
if (cfg.enable_irq_single_seq) vseq.start_irq_raise_single_seq(no_nmi, no_fast);
|
||||
else if (cfg.enable_irq_multiple_seq) vseq.start_irq_raise_seq(no_nmi, no_fast);
|
||||
irq_collected_port.get(irq_txn);
|
||||
irq = {irq_txn.irq_nm, irq_txn.irq_fast, 4'b0, irq_txn.irq_external, 3'b0,
|
||||
irq_txn.irq_timer, 3'b0, irq_txn.irq_software, 3'b0};
|
||||
|
@ -269,9 +270,9 @@ class core_ibex_debug_intr_basic_test extends core_ibex_base_test;
|
|||
wait_ret("mret", 1000);
|
||||
endtask
|
||||
|
||||
virtual task send_irq_stimulus(bit no_nmi = 1'b0);
|
||||
virtual task send_irq_stimulus(bit no_nmi = 1'b0, bit no_fast = 1'b0);
|
||||
bit ret_val;
|
||||
send_irq_stimulus_start(no_nmi, ret_val);
|
||||
send_irq_stimulus_start(no_nmi, no_fast, ret_val);
|
||||
if (ret_val) send_irq_stimulus_end();
|
||||
endtask
|
||||
|
||||
|
@ -364,6 +365,9 @@ class core_ibex_directed_test extends core_ibex_debug_intr_basic_test;
|
|||
`uvm_component_utils(core_ibex_directed_test)
|
||||
`uvm_component_new
|
||||
|
||||
instr_t seen_instr[$];
|
||||
bit [15:0] seen_compressed_instr[$];
|
||||
|
||||
virtual task send_stimulus();
|
||||
fork
|
||||
begin
|
||||
|
@ -390,6 +394,9 @@ class core_ibex_directed_test extends core_ibex_debug_intr_basic_test;
|
|||
// Wait for core initialization before starting the stimulus check loop - first write
|
||||
// to signature address is guaranteed to be core initialization info
|
||||
wait_for_core_setup();
|
||||
// Wait for a little bit to guarantee that the core has started executing <main>
|
||||
// before starting to generate stimulus for the core.
|
||||
clk_vif.wait_clks(50);
|
||||
// Should be extended by derived classes.
|
||||
// DO NOT use this test class directly.
|
||||
fork
|
||||
|
@ -465,6 +472,209 @@ class core_ibex_directed_test extends core_ibex_debug_intr_basic_test;
|
|||
"Incorrect dcsr.prv value!")
|
||||
endfunction
|
||||
|
||||
// Check if we have seen the same type of instruction before by comparing the instruction
|
||||
// currently in the ID stage against the global seen_instr[$] queue.
|
||||
// If we've seen the same type of instruction before, return 0, otherwise add it to the
|
||||
// seen_instr[$] queue and return 1.
|
||||
virtual function bit decode_instr(bit [ibex_mem_intf_agent_pkg::DATA_WIDTH-1:0] instr);
|
||||
ibex_pkg::opcode_e opcode;
|
||||
bit [2:0] funct3;
|
||||
bit [6:0] funct7;
|
||||
bit [12:0] system_imm;
|
||||
instr_t instr_fields;
|
||||
|
||||
opcode = instr[6:0];
|
||||
funct3 = instr[14:12];
|
||||
funct7 = instr[31:25];
|
||||
system_imm = instr[31:20];
|
||||
|
||||
// Now we search seen_instr[$] to check if a same instruction has been seen before.
|
||||
case (opcode)
|
||||
OPCODE_LUI, OPCODE_AUIPC, OPCODE_JAL: begin
|
||||
// these instructions only depend on opcode.
|
||||
foreach (seen_instr[i]) begin
|
||||
if (opcode == seen_instr[i].opcode) begin
|
||||
return 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
OPCODE_JALR, OPCODE_BRANCH, OPCODE_LOAD,
|
||||
OPCODE_STORE, OPCODE_MISC_MEM: begin
|
||||
// these instructions only depend on opcode and funct3
|
||||
// to be identified.
|
||||
foreach (seen_instr[i]) begin
|
||||
if (opcode == seen_instr[i].opcode &&
|
||||
funct3 == seen_instr[i].funct3) begin
|
||||
return 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
OPCODE_OP_IMM: begin
|
||||
// register-immediate arithmetic instructions are handled separately
|
||||
// as slli/srli/srai rely on funct7 in addition to opcode/funct3.
|
||||
foreach (seen_instr[i]) begin
|
||||
if (opcode == seen_instr[i].opcode &&
|
||||
funct3 == seen_instr[i].funct3) begin
|
||||
// handle slli/srli/srai instructions.
|
||||
if (funct3 inside {3'b001, 3'b101}) begin
|
||||
if (funct7 == seen_instr[i].funct7) begin
|
||||
return 0;
|
||||
end
|
||||
end else begin
|
||||
return 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
OPCODE_OP: begin
|
||||
// all register-register arithmetic instructions rely on
|
||||
// opcode/funct3/funct7 for identification.
|
||||
foreach (seen_instr[i]) begin
|
||||
if (opcode == seen_instr[i].opcode &&
|
||||
funct3 == seen_instr[i].funct3 &&
|
||||
funct7 == seen_instr[i].funct7) begin
|
||||
return 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
OPCODE_SYSTEM: begin
|
||||
// explicitly set is_seen to 0 and return on WFI instructions,
|
||||
// as if we don't interrupt them, every test will timeout.
|
||||
if (funct3 == 3'b000 && system_imm == 12'h105) begin
|
||||
return 0;
|
||||
end else if (funct3 == 3'b000 && system_imm != 12'h001) begin
|
||||
// raise is_seen if ecall/mret/dret is detected,
|
||||
// we exclude them for now (this leads to nested traps).
|
||||
return 0;
|
||||
end else begin
|
||||
foreach (seen_instr[i]) begin
|
||||
if (opcode == seen_instr[i].opcode &&
|
||||
funct3 == seen_instr[i].funct3 &&
|
||||
system_imm == seen_instr[i].system_imm) begin
|
||||
return 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
default: begin
|
||||
`uvm_fatal(`gfn, "Illegal instruction detected")
|
||||
end
|
||||
endcase
|
||||
|
||||
// We haven't seen this type of instruction before, so add it to seen_instr[$]
|
||||
// to flag it as 'seen' the next time we decode an instruction.
|
||||
instr_fields = '{opcode, funct3, funct7, system_imm};
|
||||
seen_instr.push_back(instr_fields);
|
||||
return 1;
|
||||
|
||||
endfunction
|
||||
|
||||
// Similarly to decode_instr(...), this function checks whether we have seen the
|
||||
// compressed instruction currently in the ID stage before by comparing it to the
|
||||
// global seen_compressed_instr[$] queue.
|
||||
// If we have seen it before, it returns 0, otherwise the instruction is added to the
|
||||
// and it returns 1.
|
||||
virtual function bit decode_compressed_instr(bit [15:0] instr);
|
||||
|
||||
foreach (seen_compressed_instr[i]) begin
|
||||
if (instr[1:0] == seen_compressed_instr[i][1:0]) begin
|
||||
case (instr[1:0])
|
||||
2'b00: begin
|
||||
if (instr[15:13] == seen_compressed_instr[i][15:13]) begin
|
||||
return 0;
|
||||
end
|
||||
end
|
||||
2'b01: begin
|
||||
if (instr[15:13] == seen_compressed_instr[i][15:13]) begin
|
||||
case (instr[15:13])
|
||||
3'b000, 3'b001, 3'b010,
|
||||
3'b011, 3'b101, 3'b110, 3'b111: begin
|
||||
return 0;
|
||||
end
|
||||
3'b100: begin
|
||||
if (instr[11:10] == seen_compressed_instr[i][11:10]) begin
|
||||
case (instr[11:10])
|
||||
2'b00, 2'b01, 2'b10: begin
|
||||
return 0;
|
||||
end
|
||||
2'b11: begin
|
||||
if (instr[12] == seen_compressed_instr[i][12] &&
|
||||
instr[6:5] == seen_compressed_instr[i][6:5]) begin
|
||||
return 0;
|
||||
end
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
default: begin
|
||||
`uvm_fatal(`gfn, "Invalid C1 compressed instruction")
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
2'b10: begin
|
||||
if (instr[15:13] == seen_compressed_instr[i][15:13]) begin
|
||||
case (instr[15:13])
|
||||
3'b000, 3'b010, 3'b110: begin
|
||||
return 0;
|
||||
end
|
||||
3'b100: begin
|
||||
if (instr[12] == seen_compressed_instr[i][12]) begin
|
||||
return 0;
|
||||
end
|
||||
end
|
||||
default: begin
|
||||
`uvm_fatal(`gfn, "Illegal C2 compressed instruction")
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
default: begin
|
||||
`uvm_fatal(`gfn, "Instruction is not compressed")
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// If we get here we have not seen the current instruction before,
|
||||
// so add it to seen_compressed_instr[$].
|
||||
seen_compressed_instr.push_back(instr);
|
||||
return 1'b1;
|
||||
|
||||
endfunction
|
||||
|
||||
endclass
|
||||
|
||||
// A directed interrupt test that sends interrupt stimulus into the core
|
||||
// after seeing every unique (and supported) RISC-V instruction in the core's
|
||||
// Instruction Decode stage.
|
||||
class core_ibex_interrupt_instr_test extends core_ibex_directed_test;
|
||||
|
||||
`uvm_component_utils(core_ibex_interrupt_instr_test)
|
||||
`uvm_component_new
|
||||
|
||||
virtual task check_stimulus();
|
||||
vseq.irq_raise_single_seq_h.max_delay = 0;
|
||||
vseq.irq_raise_single_seq_h.max_interval = 0;
|
||||
forever begin
|
||||
// hold until we see a valid instruction in the ID stage of the pipeline.
|
||||
wait (instr_vif.valid_id && !(instr_vif.err_id || dut_vif.illegal_instr));
|
||||
|
||||
// We don't want to send fast interrupts, as due to the random setup of MIE,
|
||||
// there's no guarantee that the interrupt will actually be taken.
|
||||
if (instr_vif.is_compressed_id) begin
|
||||
if (decode_compressed_instr(instr_vif.instr_compressed_id)) begin
|
||||
send_irq_stimulus(.no_fast(1'b1));
|
||||
end
|
||||
end else begin
|
||||
if (decode_instr(instr_vif.instr_id)) begin
|
||||
send_irq_stimulus(.no_fast(1'b1));
|
||||
end
|
||||
end
|
||||
clk_vif.wait_clks(1);
|
||||
end
|
||||
endtask
|
||||
|
||||
endclass
|
||||
|
||||
// Interrupt WFI test class
|
||||
|
@ -557,7 +767,7 @@ class core_ibex_debug_in_irq_test extends core_ibex_directed_test;
|
|||
// then finish interrupt handling routine
|
||||
bit valid_irq;
|
||||
forever begin
|
||||
send_irq_stimulus_start(1'b0, valid_irq);
|
||||
send_irq_stimulus_start(1'b0, 1'b0, valid_irq);
|
||||
if (valid_irq) begin
|
||||
fork
|
||||
begin
|
||||
|
@ -586,7 +796,7 @@ class core_ibex_nested_irq_test extends core_ibex_directed_test;
|
|||
bit valid_nested_irq;
|
||||
int unsigned initial_irq_delay;
|
||||
forever begin
|
||||
send_irq_stimulus_start(1'b1, valid_irq);
|
||||
send_irq_stimulus_start(1'b1, 1'b0, valid_irq);
|
||||
if (valid_irq) begin
|
||||
initial_irq_delay = vseq.irq_raise_seq_h.max_delay;
|
||||
vseq.irq_raise_seq_h.max_delay = 0;
|
||||
|
@ -606,6 +816,42 @@ class core_ibex_nested_irq_test extends core_ibex_directed_test;
|
|||
|
||||
endclass
|
||||
|
||||
// A directed debug test that sends debug stimulus into the core
|
||||
// after seeing every unique (and supported) RISC-V instruction in the core's
|
||||
// Instruction Decode stage.
|
||||
class core_ibex_debug_instr_test extends core_ibex_directed_test;
|
||||
|
||||
`uvm_component_utils(core_ibex_debug_instr_test)
|
||||
`uvm_component_new
|
||||
|
||||
virtual task check_stimulus();
|
||||
vseq.debug_seq_single_h.max_delay = 0;
|
||||
vseq.debug_seq_single_h.max_interval = 0;
|
||||
forever begin
|
||||
// hold until we see a valid instruction in the ID stage of the pipeline.
|
||||
wait (instr_vif.valid_id && !(instr_vif.err_id || dut_vif.illegal_instr));
|
||||
|
||||
// We don't want to send fast interrupts, as due to the random setup of MIE,
|
||||
// there's no guarantee that the interrupt will actually be taken.
|
||||
if (instr_vif.is_compressed_id) begin
|
||||
if (decode_compressed_instr(instr_vif.instr_compressed_id)) begin
|
||||
send_debug_stimulus(init_operating_mode,
|
||||
$sformatf("Did not jump into debug mode after instruction[0x%0x]",
|
||||
instr_vif.instr_compressed_id));
|
||||
end
|
||||
end else begin
|
||||
if (decode_instr(instr_vif.instr_id)) begin
|
||||
send_debug_stimulus(init_operating_mode,
|
||||
$sformatf("Did not jump into debug mode after instruction[0x%0x]",
|
||||
instr_vif.instr_id));
|
||||
end
|
||||
end
|
||||
clk_vif.wait_clks(1);
|
||||
end
|
||||
endtask
|
||||
|
||||
endclass
|
||||
|
||||
// Debug WFI test class
|
||||
class core_ibex_debug_wfi_test extends core_ibex_directed_test;
|
||||
|
||||
|
|
|
@ -14,6 +14,14 @@ package core_ibex_test_pkg;
|
|||
import riscv_signature_pkg::*;
|
||||
import ibex_pkg::*;
|
||||
|
||||
typedef struct {
|
||||
ibex_pkg::opcode_e opcode;
|
||||
bit [2:0] funct3;
|
||||
bit [6:0] funct7;
|
||||
// 12-bit immediate, used only for SYSTEM instructions
|
||||
bit [11:0] system_imm;
|
||||
} instr_t;
|
||||
|
||||
`include "core_ibex_report_server.sv"
|
||||
`include "core_ibex_seq_lib.sv"
|
||||
`include "core_ibex_vseq.sv"
|
||||
|
|
|
@ -93,13 +93,15 @@ class core_ibex_vseq extends uvm_sequence;
|
|||
debug_seq_single_h.start(null);
|
||||
endtask
|
||||
|
||||
virtual task start_irq_raise_single_seq(bit no_nmi = 1'b0);
|
||||
virtual task start_irq_raise_single_seq(bit no_nmi = 1'b0, bit no_fast = 1'b0);
|
||||
irq_raise_single_seq_h.no_nmi = no_nmi;
|
||||
irq_raise_single_seq_h.no_fast = no_fast;
|
||||
irq_raise_single_seq_h.start(p_sequencer.irq_seqr);
|
||||
endtask
|
||||
|
||||
virtual task start_irq_raise_seq(bit no_nmi = 1'b0);
|
||||
virtual task start_irq_raise_seq(bit no_nmi = 1'b0, bit no_fast = 1'b0);
|
||||
irq_raise_seq_h.no_nmi = no_nmi;
|
||||
irq_raise_seq_h.no_fast = no_fast;
|
||||
irq_raise_seq_h.start(p_sequencer.irq_seqr);
|
||||
endtask
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue