[ibex,dv] Add new test for memory integrity errors

This test picks between inserting an integrity error or a bus error
to the response in the case of a memory request from Ibex. Introduces
a control knob `enable_mem_intg_err` which can control the rate of
having integrity errors per request.

This commit also disables checking for double fault alerts in the
scoreboard because they're expected to be seen while simulating and
they don't cause infinite loop problems because every time a memory
response is requested the error causing part is just randomized.
That means Ibex trying to execute same instruction again would have
a chance to succeed this time.

Signed-off-by: Canberk Topal <ctopal@lowrisc.org>
This commit is contained in:
Canberk Topal 2022-09-20 12:37:14 +01:00 committed by Greg Chadwick
parent bb959c7181
commit 12ae6b2478
5 changed files with 117 additions and 22 deletions

View file

@ -11,17 +11,26 @@ class ibex_mem_intf_response_seq extends uvm_sequence #(ibex_mem_intf_seq_item);
ibex_mem_intf_seq_item item;
mem_model m_mem;
ibex_cosim_agent cosim_agent;
bit enable_intg_error = 1'b0;
bit enable_error = 1'b0;
// Used to ensure that whenever inject_error() is called, the very next transaction will inject an
// error, and that enable_error will not be flipped back to 0 immediately
bit error_synch = 1'b1;
bit is_dmem_seq = 1'b0;
bit suppress_error_on_exc = 1'b0;
`uvm_object_utils(ibex_mem_intf_response_seq)
`uvm_declare_p_sequencer(ibex_mem_intf_response_sequencer)
`uvm_object_new
virtual task body();
virtual core_ibex_dut_probe_if ibex_dut_vif;
if (!uvm_config_db#(virtual core_ibex_dut_probe_if)::get(null, "", "dut_if",
ibex_dut_vif)) begin
`uvm_fatal(`gfn, "failed to get ibex dut_if from uvm_config_db")
end
if (m_mem == null) `uvm_fatal(get_full_name(), "Cannot get memory model")
`uvm_info(`gfn, $sformatf("is_dmem_seq: 0x%0x", is_dmem_seq), UVM_LOW)
forever
@ -37,6 +46,13 @@ class ibex_mem_intf_response_seq extends uvm_sequence #(ibex_mem_intf_seq_item);
req = ibex_mem_intf_seq_item::type_id::create("req");
error_synch = 1'b0;
if (suppress_error_on_exc &&
(ibex_dut_vif.dut_cb.sync_exc_seen || ibex_dut_vif.dut_cb.irq_exc_seen)) begin
enable_error = 1'b0;
enable_intg_error = 1'b0;
end
if (!req.randomize() with {
addr == item.addr;
read_write == item.read_write;
@ -66,6 +82,7 @@ class ibex_mem_intf_response_seq extends uvm_sequence #(ibex_mem_intf_seq_item);
// TODO: Parametrize this. Until then, this needs to be changed manually.
if (aligned_addr inside {32'h8ffffff8, 32'h8ffffffc}) begin
req.error = 1'b0;
enable_intg_error = 1'b0;
end
if (req.error) begin
`DV_CHECK_STD_RANDOMIZE_FATAL(rand_data)
@ -87,9 +104,9 @@ class ibex_mem_intf_response_seq extends uvm_sequence #(ibex_mem_intf_seq_item);
// If data_was_uninitialized is true then we want to force bad integrity bits: invert the
// correct ones, which we know will break things for the codes we use.
if (p_sequencer.cfg.enable_bad_intg_on_uninit_access &&
data_was_uninitialized) begin
if ((p_sequencer.cfg.enable_bad_intg_on_uninit_access && data_was_uninitialized) || enable_intg_error) begin
req.intg = ~req.intg;
enable_intg_error = 1'b0;
end
`uvm_info(get_full_name(), $sformatf("Response transfer:\n%0s", req.sprint()), UVM_HIGH)
@ -103,6 +120,10 @@ class ibex_mem_intf_response_seq extends uvm_sequence #(ibex_mem_intf_seq_item);
this.enable_error = 1'b1;
endfunction
virtual function void inject_intg_error();
this.enable_intg_error = 1'b1;
endfunction
virtual function bit get_error_synch();
return this.error_synch;
endfunction

View file

@ -7,6 +7,7 @@ class core_ibex_env_cfg extends uvm_object;
virtual clk_rst_if ibex_clk_vif;
virtual core_ibex_dut_probe_if ibex_dut_vif;
bit enable_mem_intg_err;
bit enable_irq_single_seq;
bit enable_irq_multiple_seq;
bit enable_irq_nmi_seq;
@ -31,6 +32,7 @@ class core_ibex_env_cfg extends uvm_object;
`uvm_object_utils_begin(core_ibex_env_cfg)
`uvm_field_int(enable_double_fault_detector, UVM_DEFAULT)
`uvm_field_int(is_double_fault_detected_fatal, UVM_DEFAULT)
`uvm_field_int(enable_mem_intg_err, UVM_DEFAULT)
`uvm_field_int(enable_irq_single_seq, UVM_DEFAULT)
`uvm_field_int(enable_irq_multiple_seq, UVM_DEFAULT)
`uvm_field_int(enable_irq_nmi_seq, UVM_DEFAULT)
@ -48,6 +50,7 @@ class core_ibex_env_cfg extends uvm_object;
super.new(name);
void'($value$plusargs("enable_double_fault_detector=%0d", enable_double_fault_detector));
void'($value$plusargs("is_double_fault_detected_fatal=%0d", is_double_fault_detected_fatal));
void'($value$plusargs("enable_mem_intg_err=%0d", enable_mem_intg_err));
void'($value$plusargs("enable_irq_single_seq=%0d", enable_irq_single_seq));
void'($value$plusargs("enable_irq_multiple_seq=%0d", enable_irq_multiple_seq));
void'($value$plusargs("enable_irq_nmi_seq=%0d", enable_irq_nmi_seq));

View file

@ -588,6 +588,27 @@
+disable_pmp_exception_handler=1
rtl_test: core_ibex_mem_error_test
sim_opts: >
+enable_mem_intg_err=0
+enable_double_fault_detector=0
+require_signature_addr=1
compare_opts:
compare_final_value_only: 1
- test: riscv_mem_intg_error_test
description: >
Normal random instruction test, but randomly insert memory load/store integrity errors
iterations: 15
gen_test: riscv_rand_instr_test
gen_opts: >
+require_signature_addr=1
+instr_cnt=10000
+randomize_csr=1
+enable_unaligned_load_store=1
+suppress_pmp_setup=1
rtl_test: core_ibex_mem_error_test
sim_opts: >
+enable_mem_intg_err=1
+enable_double_fault_detector=0
+require_signature_addr=1
compare_opts:
compare_final_value_only: 1

View file

@ -204,9 +204,17 @@ endclass
class memory_error_seq extends core_base_new_seq#(ibex_mem_intf_seq_item);
core_ibex_vseq vseq;
rand bit choose_side;
bit start_seq = 0; // Use this bit to start any unique sequence once
// When set skip error injection if Ibex is currently handling an exception (incluing IRQs)
bit skip_on_exc = 1'b0;
rand error_type_e err_type = PickErr;
error_type_e err_type = PickErr;
rand bit inject_intg_err;
// CONTROL_KNOB: Configure the rate between seeing an integrity error versus seeing a bus error.
int unsigned intg_err_pct = 50;
constraint inject_intg_err_c {
inject_intg_err dist {1 :/ intg_err_pct,
0 :/ 100 - intg_err_pct};
}
`uvm_object_utils(memory_error_seq)
`uvm_declare_p_sequencer(core_ibex_vseqr)
@ -216,14 +224,21 @@ class memory_error_seq extends core_base_new_seq#(ibex_mem_intf_seq_item);
endfunction
virtual task send_req();
case (err_type)
IsideErr: begin
vseq.instr_intf_seq.suppress_error_on_exc = skip_on_exc;
vseq.data_intf_seq.suppress_error_on_exc = skip_on_exc;
`DV_CHECK_MEMBER_RANDOMIZE_FATAL(inject_intg_err)
// If we expect to see only bus errors, we can enable this assertion. Otherwise
// integrity errors would cause alerts to trigger.
`DV_ASSERT_CTRL_REQ("tb_no_alerts_triggered", intg_err_pct == 0)
case ({err_type, inject_intg_err})
{IsideErr, 1'b0}: begin
vseq.instr_intf_seq.inject_error();
end
DsideErr: begin
{DsideErr, 1'b0}: begin
vseq.data_intf_seq.inject_error();
end
PickErr: begin
{PickErr, 1'b0}: begin
`DV_CHECK_STD_RANDOMIZE_FATAL(choose_side)
if (choose_side) begin
vseq.instr_intf_seq.inject_error();
@ -231,6 +246,20 @@ class memory_error_seq extends core_base_new_seq#(ibex_mem_intf_seq_item);
vseq.data_intf_seq.inject_error();
end
end
{IsideErr, 1'b1}: begin
vseq.instr_intf_seq.inject_intg_error();
end
{DsideErr, 1'b1}: begin
vseq.data_intf_seq.inject_intg_error();
end
{PickErr, 1'b1}: begin
`DV_CHECK_STD_RANDOMIZE_FATAL(choose_side)
if (choose_side) begin
vseq.instr_intf_seq.inject_intg_error();
end else begin
vseq.data_intf_seq.inject_intg_error();
end
end
default: begin
// DO nothing
end

View file

@ -1630,6 +1630,9 @@ class core_ibex_mem_error_test extends core_ibex_directed_test;
`uvm_component_utils(core_ibex_mem_error_test)
`uvm_component_new
int illegal_instruction_threshold = 20;
int illegal_instruction_exceptions_seen = 0;
virtual task check_stimulus();
memory_error_seq memory_error_seq_h;
memory_error_seq_h = memory_error_seq::type_id::create("memory_error_seq_h", this);
@ -1639,24 +1642,42 @@ class core_ibex_mem_error_test extends core_ibex_directed_test;
memory_error_seq_h.iteration_modes = InfiniteRuns;
memory_error_seq_h.stimulus_delay_cycles_min = 800; // Interval between injected errors
memory_error_seq_h.stimulus_delay_cycles_max = 5000;
memory_error_seq_h.intg_err_pct = cfg.enable_mem_intg_err ? 75 : 0;
memory_error_seq_h.skip_on_exc = 1'b1;
fork
begin
forever begin
memory_error_seq_h.start(env.vseqr);
// Wait until we are out of IRQ handler to the inject errors
wait_ret("mret", 20000);
end
end
begin
forever begin
wait_for_core_status(HANDLING_IRQ);
// Do not allow error injection while we are handling IRQ
memory_error_seq_h.stop();
end
end
run_illegal_instr_watcher();
memory_error_seq_h.start(env.vseqr);
join_none
endtask
task run_illegal_instr_watcher();
// When integrity errors are present loads that see them won't write to the register file.
// Generated code from RISC-DV may be using the loads to produce known constants in register
// that are then used elsewhere, in particular for jump targets. As the register write doesn't
// occur this results in jumping to places that weren't intended which in turn can result in
// illegal instruction exceptions.
//
// As a simple fix for this we observe illegal instruction exceptions and terminate the test
// with a pass after hitting a certain threshold when the test is generating integrity errors.
//
// We don't terminate immediately as sometimes the test hits an illegal instruction exception
// but finds its way back to generated code and terminates as usual. Sometimes it doesn't. The
// treshold allows for normal test termination in cases where that's possible.
if (!cfg.enable_mem_intg_err) begin
return;
end
forever begin
wait_for_core_exception(ibex_pkg::ExcCauseIllegalInsn);
++illegal_instruction_exceptions_seen;
end
endtask
virtual task wait_for_custom_test_done();
wait(illegal_instruction_exceptions_seen == illegal_instruction_threshold);
`uvm_info(`gfn, "Terminating test early due to illegal instruction threshold reached", UVM_LOW)
endtask
endclass
// U-mode mstatus.tw test class