mirror of
https://github.com/lowRISC/ibex.git
synced 2025-04-19 11:34:45 -04:00
SQUASHED: Add new uvm test to hit hardware breakpoints coverpoints
Generate test_done: and test_fail: sections using handshake mechanism Adding this behaviour to ibex_asm_program_gen allows all test to benefit from the option of jumping directly to these label. Previously, ECALL was used to provide a single path to this code. Redefine ECALL handler to no-longer jump to 'write_tohost:' This prevents the simulation from entering an infinite loop which it can no longer detect and terminate from. Add new uvm test to hit hardware breakpoints coverpoints Overrides some riscv-dv classes to create a custom debug_rom for this test, which is used to setup the breakpoint registers. I have found it difficult to get stimulus of this hardware feature without a more directed test. Improvements or ideas are welcome here. Test-specific timeout of 5min within which I see >90% pass rate. Change defaults for bad_intg on uninit accesses for Dmem/Imem Imem : never create bad_intg on uninit access Dmem : by default, enable bad_intg on uninit access. Plusarg to change behaviour.
This commit is contained in:
parent
b8d8e23380
commit
6234aafe35
9 changed files with 311 additions and 18 deletions
|
@ -21,7 +21,10 @@ class ibex_mem_intf_response_agent extends uvm_agent;
|
|||
|
||||
super.build_phase(phase);
|
||||
monitor = ibex_mem_intf_monitor::type_id::create("monitor", this);
|
||||
cfg = ibex_mem_intf_response_agent_cfg::type_id::create("cfg", this);
|
||||
if (cfg == null)
|
||||
if(!uvm_config_db #(ibex_mem_intf_response_agent_cfg)::get(this, "", "cfg", cfg))
|
||||
`uvm_fatal(`gfn, "Could not locate mem_intf cfg object in uvm_config_db!")
|
||||
|
||||
if(get_is_active() == UVM_ACTIVE) begin
|
||||
driver = ibex_mem_intf_response_driver::type_id::create("driver", this);
|
||||
sequencer = ibex_mem_intf_response_sequencer::type_id::create("sequencer", this);
|
||||
|
|
|
@ -37,7 +37,7 @@ class ibex_mem_intf_response_agent_cfg extends uvm_object;
|
|||
int unsigned zero_delay_pct = 50;
|
||||
|
||||
// CONTROL_KNOB : enable/disable to generation of bad integrity upon uninit accesses
|
||||
bit enable_bad_intg_on_uninit_access = 1;
|
||||
bit enable_bad_intg_on_uninit_access = 0;
|
||||
|
||||
constraint zero_delays_c {
|
||||
zero_delays dist {1 :/ zero_delay_pct,
|
||||
|
@ -56,7 +56,6 @@ class ibex_mem_intf_response_agent_cfg extends uvm_object;
|
|||
|
||||
function new(string name = "");
|
||||
super.new(name);
|
||||
void'($value$plusargs("enable_bad_intg_on_uninit_access=%0d", enable_bad_intg_on_uninit_access));
|
||||
endfunction
|
||||
|
||||
endclass
|
||||
|
|
|
@ -12,6 +12,8 @@ class ibex_asm_program_gen extends riscv_asm_program_gen;
|
|||
`uvm_object_new
|
||||
|
||||
virtual function void gen_program();
|
||||
string instr[$];
|
||||
|
||||
default_include_csr_write = {
|
||||
MSCRATCH,
|
||||
MVENDORID,
|
||||
|
@ -53,6 +55,35 @@ class ibex_asm_program_gen extends riscv_asm_program_gen;
|
|||
riscv_csr_instr::create_csr_filter(cfg);
|
||||
|
||||
super.gen_program();
|
||||
|
||||
// Override the main gen_program() routine to append our own custom test_done/test_fail routines
|
||||
// Generate test_done and test_fail routines at the end of the baseclass program.
|
||||
gen_test_end(.result(TEST_PASS), .instr(instr));
|
||||
instr_stream = {instr_stream,
|
||||
{format_string("test_done:", LABEL_STR_LEN)},
|
||||
instr};
|
||||
instr.delete();
|
||||
gen_test_end(.result(TEST_FAIL), .instr(instr));
|
||||
instr_stream = {instr_stream,
|
||||
{format_string("test_fail:", LABEL_STR_LEN)},
|
||||
instr};
|
||||
endfunction
|
||||
|
||||
// ECALL trap handler
|
||||
// For riscv-dv in Ibex, ECALL is no-longer used to end the test.
|
||||
// Hence, redefine a simple version here that just increments
|
||||
// MEPC+4 then calls 'mret'. (ECALL is always 4-bytes in RV32)
|
||||
virtual function void gen_ecall_handler(int hart);
|
||||
string instr[$];
|
||||
dump_perf_stats(instr);
|
||||
gen_register_dump(instr);
|
||||
instr = {instr,
|
||||
$sformatf("csrr x%0d, 0x%0x", cfg.gpr[0], MEPC),
|
||||
$sformatf("addi x%0d, x%0d, 4", cfg.gpr[0], cfg.gpr[0]),
|
||||
$sformatf("csrw 0x%0x, x%0d", MEPC, cfg.gpr[0])
|
||||
};
|
||||
instr.push_back("mret");
|
||||
gen_section(get_label("ecall_handler", hart), instr);
|
||||
endfunction
|
||||
|
||||
virtual function void gen_program_header();
|
||||
|
@ -87,31 +118,30 @@ class ibex_asm_program_gen extends riscv_asm_program_gen;
|
|||
instr.push_back("csrwi 0x7c0, 1");
|
||||
endfunction
|
||||
|
||||
// Generate "test_done" section.
|
||||
// Re-define gen_test_done() to override the base-class with an empty implementation.
|
||||
// Then, our own overrided gen_program() can append new test_done code.
|
||||
virtual function void gen_test_done();
|
||||
// empty
|
||||
endfunction
|
||||
|
||||
// The test is ended from the UVM testbench, which awaits the following write to
|
||||
// the special test-control signature address (signature_addr - 0x4).
|
||||
// The ECALL trap handler will handle the clean up procedure while awaiting the
|
||||
// simulation to be ended.
|
||||
virtual function void gen_test_done();
|
||||
// #FIXME# The existing ECALL trap handler will handle the clean up procedure
|
||||
// while awaiting the simulation to be ended.
|
||||
virtual function void gen_test_end(input test_result_t result,
|
||||
ref string instr[$]);
|
||||
bit [XLEN-1:0] test_control_addr;
|
||||
string str;
|
||||
string i = indent; // lint-hack
|
||||
test_control_addr = cfg.signature_addr - 4'h4;
|
||||
|
||||
str = {str,
|
||||
{format_string("test_done:", LABEL_STR_LEN), "\n"},
|
||||
{i, "li gp, 1", "\n"}
|
||||
};
|
||||
|
||||
if (cfg.bare_program_mode) begin
|
||||
str = {str,
|
||||
{i, "j write_tohost", "\n"}
|
||||
};
|
||||
str = {i, "j write_tohost", "\n"};
|
||||
end else begin
|
||||
// The testbench will await a write of TEST_PASS, and use that to end the test.
|
||||
str = {str,
|
||||
str = {
|
||||
{i, $sformatf( "li x%0d, 0x%0h", cfg.gpr[1], test_control_addr), "\n"},
|
||||
{i, $sformatf( "li x%0d, 0x%0h", cfg.gpr[0], TEST_PASS), "\n"},
|
||||
{i, $sformatf( "li x%0d, 0x%0h", cfg.gpr[0], result), "\n"},
|
||||
{i, $sformatf("slli x%0d, x%0d, 8", cfg.gpr[0], cfg.gpr[0]), "\n"},
|
||||
{i, $sformatf("addi x%0d, x%0d, 0x%0h", cfg.gpr[0], cfg.gpr[0], TEST_RESULT), "\n"},
|
||||
{i, $sformatf( "sw x%0d, 0(x%0d)", cfg.gpr[0], cfg.gpr[1]), "\n"},
|
||||
|
@ -120,7 +150,7 @@ class ibex_asm_program_gen extends riscv_asm_program_gen;
|
|||
};
|
||||
end
|
||||
|
||||
instr_stream.push_back(str);
|
||||
instr.push_back(str);
|
||||
endfunction
|
||||
|
||||
endclass
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
class ibex_hardware_triggers_asm_program_gen extends ibex_asm_program_gen;
|
||||
|
||||
`uvm_object_utils(ibex_hardware_triggers_asm_program_gen)
|
||||
`uvm_object_new
|
||||
|
||||
// Same implementation as the parent class, except substitute for our custom debug_rom class.
|
||||
virtual function void gen_debug_rom(int hart);
|
||||
`uvm_info(`gfn, "Creating debug ROM", UVM_LOW)
|
||||
debug_rom = ibex_hardware_triggers_debug_rom_gen::
|
||||
type_id::create("debug_rom", , {"uvm_test_top", ".", `gfn});
|
||||
debug_rom.cfg = cfg;
|
||||
debug_rom.hart = hart;
|
||||
debug_rom.gen_program();
|
||||
instr_stream = {instr_stream, debug_rom.instr_stream};
|
||||
endfunction
|
||||
|
||||
endclass
|
||||
|
||||
|
||||
class ibex_hardware_triggers_debug_rom_gen extends riscv_debug_rom_gen;
|
||||
|
||||
`uvm_object_utils(ibex_hardware_triggers_debug_rom_gen)
|
||||
`uvm_object_new
|
||||
|
||||
int unsigned ibex_trigger_idx = 0; // See. [DbgHwBreakNum]
|
||||
|
||||
virtual function void gen_program();
|
||||
string instr[$];
|
||||
|
||||
// Don't save off GPRs (ie. this WILL modify program flow)
|
||||
// (We want to capture a register value (gpr[1]) from the directed_instr_streams in
|
||||
// main() that contains the address for our next trigger.)
|
||||
// This works in tandem with 'ibex_breakpoint_stream' which stores the address of
|
||||
// the instruction to trigger on in a fixed register, then executes an EBREAK to
|
||||
// enter debug mode via the dcsr.ebreakm=1 functionality. The debug rom code
|
||||
// then sets up the breakpoint trigger to this address, and returns, allowing main
|
||||
// to continue executing until we hit the trigger.
|
||||
|
||||
// riscv-debug-1.0.0-STABLE
|
||||
// 5.5 Trigger Registers
|
||||
// <..>
|
||||
// As as result, a debugger can write any supported trigger as follows..
|
||||
//
|
||||
// 1. Write 0 to TDATA1. (This will result in TDATA1 containing a non-zero value,
|
||||
// since the register is WARL).
|
||||
// 2. Write desired values to TDATA2 and TDATA3.
|
||||
// 3. Write desired value to TDATA1.
|
||||
// <..>
|
||||
|
||||
instr = {// Check DCSR.cause (DCSR[8:6]) to branch to the next block of code.
|
||||
$sformatf("csrr x%0d, 0x%0x", cfg.scratch_reg, DCSR),
|
||||
$sformatf("slli x%0d, x%0d, 0x17", cfg.scratch_reg, cfg.scratch_reg),
|
||||
$sformatf("srli x%0d, x%0d, 0x1d", cfg.scratch_reg, cfg.scratch_reg),
|
||||
$sformatf("li x%0d, 0x1", cfg.gpr[0]), // EBREAK = 1
|
||||
$sformatf("beq x%0d, x%0d, 1f", cfg.scratch_reg, cfg.gpr[0]),
|
||||
$sformatf("li x%0d, 0x2", cfg.gpr[0]), // TRIGGER = 2
|
||||
$sformatf("beq x%0d, x%0d, 2f", cfg.scratch_reg, cfg.gpr[0]),
|
||||
$sformatf("li x%0d, 0x3", cfg.gpr[0]), // HALTREQ = 3
|
||||
$sformatf("beq x%0d, x%0d, 3f", cfg.scratch_reg, cfg.gpr[0]),
|
||||
|
||||
// DCSR.cause == EBREAK
|
||||
"1: nop",
|
||||
// 'ibex_breakpoint_stream' inserts EBREAKs such that cfg.gpr[1]
|
||||
// now contain the address of the next trigger.
|
||||
// Enable the trigger and set to this address.
|
||||
$sformatf("csrrwi zero, 0x%0x, %0d", TSELECT, ibex_trigger_idx),
|
||||
$sformatf("csrrw zero, 0x%0x, x0", TDATA1),
|
||||
$sformatf("csrrw zero, 0x%0x, x%0d", TDATA2, cfg.gpr[1]),
|
||||
$sformatf("csrrwi zero, 0x%0x, 5", TDATA1),
|
||||
// Increment the PC + 4 (EBREAK does not do this for you.)
|
||||
$sformatf("csrr x%0d, 0x%0x", cfg.gpr[0], DPC),
|
||||
$sformatf("addi x%0d, x%0d, 4", cfg.gpr[0], cfg.gpr[0]),
|
||||
$sformatf("csrw 0x%0x, x%0d", DPC, cfg.gpr[0]),
|
||||
"j 4f",
|
||||
|
||||
// DCSR.cause == TRIGGER
|
||||
"2: nop",
|
||||
// Disable the trigger until the next breakpoint is known.
|
||||
$sformatf("csrrwi zero, 0x%0x, %0d", TSELECT, ibex_trigger_idx),
|
||||
$sformatf("csrrw zero, 0x%0x, x0", TDATA1),
|
||||
$sformatf("csrrw zero, 0x%0x, x0", TDATA2),
|
||||
"j 4f",
|
||||
|
||||
// DCSR.cause == HALTREQ
|
||||
"3: nop",
|
||||
// Use this once near the start of the test to configure ebreakm/u to enter debug mode
|
||||
// Set DCSR.ebreakm (DCSR[15]) = 1
|
||||
// Set DCSR.ebreaku (DCSR[12]) = 1
|
||||
$sformatf("li x%0d, 0x9000", cfg.scratch_reg),
|
||||
$sformatf("csrs 0x%0x, x%0d", DCSR, cfg.scratch_reg),
|
||||
|
||||
"4: nop"
|
||||
};
|
||||
|
||||
debug_main = {instr,
|
||||
$sformatf("la x%0d, debug_end", cfg.scratch_reg),
|
||||
$sformatf("jalr x0, x%0d, 0", cfg.scratch_reg)
|
||||
};
|
||||
format_section(debug_main);
|
||||
gen_section($sformatf("%0sdebug_rom", hart_prefix(hart)), debug_main);
|
||||
|
||||
debug_end = {dret};
|
||||
format_section(debug_end);
|
||||
gen_section($sformatf("%0sdebug_end", hart_prefix(hart)), debug_end);
|
||||
|
||||
gen_debug_exception_handler();
|
||||
endfunction
|
||||
|
||||
|
||||
// If we get an exception in debug_mode, fail the test immediately.
|
||||
// (something has gone wrong with our stimulus generation)
|
||||
virtual function void gen_debug_exception_handler();
|
||||
string instr[$];
|
||||
instr = {$sformatf("la x%0d, test_fail", cfg.scratch_reg),
|
||||
$sformatf("jalr x1, x%0d, 0", cfg.scratch_reg)};
|
||||
format_section(instr);
|
||||
gen_section($sformatf("%0sdebug_exception", hart_prefix(hart)), instr);
|
||||
endfunction
|
||||
|
||||
endclass
|
||||
|
||||
class ibex_hardware_triggers_illegal_instr extends riscv_illegal_instr;
|
||||
|
||||
`uvm_object_utils(ibex_hardware_triggers_illegal_instr)
|
||||
`uvm_object_new
|
||||
|
||||
// Make it super-obvious where the illegal instructions are in the assembly.
|
||||
function void post_randomize();
|
||||
super.post_randomize();
|
||||
comment = "INVALID";
|
||||
endfunction
|
||||
|
||||
endclass // ibex_illegal_instr
|
|
@ -0,0 +1,62 @@
|
|||
// Copyright lowRISC contributors.
|
||||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
|
||||
// Define a short riscv-dv directed instruction stream to setup the hardware breakpoint (TSELECT/TDATA) functionality, and then trigger on an instruction at the end of this stream.
|
||||
class ibex_breakpoint_stream extends riscv_directed_instr_stream;
|
||||
|
||||
riscv_pseudo_instr la_instr;
|
||||
riscv_instr ebreak_insn;
|
||||
rand int unsigned num_of_instr;
|
||||
|
||||
constraint instr_c {
|
||||
num_of_instr inside {[5:10]};
|
||||
}
|
||||
|
||||
`uvm_object_utils(ibex_breakpoint_stream)
|
||||
|
||||
function new(string name = "");
|
||||
super.new(name);
|
||||
endfunction
|
||||
|
||||
function void post_randomize();
|
||||
riscv_instr instr;
|
||||
string trigger_label, gn;
|
||||
|
||||
// Setup a randomized main body of instructions.
|
||||
initialize_instr_list(num_of_instr);
|
||||
setup_allowed_instr(1, 1); // no_branch=1/no_load_store=1
|
||||
foreach(instr_list[i]) begin
|
||||
instr = riscv_instr::type_id::create("instr");
|
||||
randomize_instr(instr);
|
||||
instr_list[i] = instr;
|
||||
// Copy this from the base-class behaviour
|
||||
instr_list[i].atomic = 1'b1;
|
||||
instr_list[i].has_label = 1'b0;
|
||||
end
|
||||
|
||||
// Give the last insn of the main body a label, then set the breakpoint address to that label.
|
||||
gn = get_name();
|
||||
trigger_label = {gn};
|
||||
instr_list[$].label = trigger_label;
|
||||
instr_list[$].has_label = 1'b1;
|
||||
|
||||
// Load the address of the trigger point as the (last insn of the stream + 4)
|
||||
// Store in gpr[1] ()
|
||||
la_instr = riscv_pseudo_instr::type_id::create("la_instr");
|
||||
la_instr.pseudo_instr_name = LA;
|
||||
la_instr.imm_str = $sformatf("%0s+4", trigger_label);
|
||||
la_instr.rd = cfg.gpr[1];
|
||||
|
||||
// Create the ebreak insn which will cause us to enter debug mode, and run the
|
||||
// special code in the debugrom.
|
||||
ebreak_insn = riscv_instr::get_instr(EBREAK);
|
||||
|
||||
// Add the instructions into the stream.
|
||||
instr_list = {la_instr,
|
||||
ebreak_insn,
|
||||
instr_list};
|
||||
endfunction
|
||||
|
||||
endclass
|
|
@ -147,6 +147,38 @@
|
|||
compare_final_value_only: 1
|
||||
verbose: 1
|
||||
|
||||
- test: riscv_debug_triggers_test
|
||||
description: >
|
||||
Test stimulus includes directed routine which set the breakpoint addr forwards a few instructions.
|
||||
This test has no pass/fail criteria, but instead is used to gather appropriate coverage.
|
||||
// rtl_test:core_ibex_single_debug_pulse_test
|
||||
// In combination with the test-program, this HALTREQ triggers code that then
|
||||
// enables the DCSR.ebreakm/u functionality. From then on, EBREAK instructions trigger the
|
||||
// DUT to run some debug-mode code to setup the next breakpoint address.
|
||||
iterations: 5
|
||||
gen_test: riscv_instr_base_test
|
||||
gen_opts: >
|
||||
+require_signature_addr=1
|
||||
+gen_debug_section=1
|
||||
+no_ebreak=1
|
||||
+no_branch_jump=1
|
||||
+instr_cnt=10000
|
||||
+no_csr_instr=1
|
||||
+illegal_instr_ratio=20
|
||||
+no_fence=1
|
||||
+num_of_sub_program=0
|
||||
+randomize_csr=0
|
||||
+directed_instr_0=ibex_breakpoint_stream,20
|
||||
+uvm_set_type_override=ibex_asm_program_gen,ibex_hardware_triggers_asm_program_gen
|
||||
+uvm_set_type_override=riscv_debug_rom_gen,ibex_hardware_triggers_debug_rom_gen
|
||||
+uvm_set_type_override=riscv_illegal_instr,ibex_hardware_triggers_illegal_instr
|
||||
rtl_test: core_ibex_single_debug_pulse_test
|
||||
sim_opts: >
|
||||
+require_signature_addr=1
|
||||
+enable_debug_seq=1
|
||||
+enable_bad_intg_on_uninit_access=0
|
||||
timeout_s: 300
|
||||
|
||||
- test: riscv_debug_stress_test
|
||||
description: >
|
||||
Randomly assert debug_req_i more often, debug_rom is empty, with only a dret instruction
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
`include "ibex_asm_program_gen.sv"
|
||||
`include "ibex_directed_instr_lib.sv"
|
||||
`include "ibex_debug_triggers_overrides.sv"
|
||||
|
|
|
@ -9,6 +9,8 @@ class core_ibex_base_test extends uvm_test;
|
|||
core_ibex_env env;
|
||||
core_ibex_env_cfg cfg;
|
||||
core_ibex_cosim_cfg cosim_cfg;
|
||||
ibex_mem_intf_response_agent_cfg imem_cfg;
|
||||
ibex_mem_intf_response_agent_cfg dmem_cfg;
|
||||
virtual clk_rst_if clk_vif;
|
||||
virtual core_ibex_dut_probe_if dut_vif;
|
||||
virtual core_ibex_instr_monitor_if instr_vif;
|
||||
|
@ -146,6 +148,17 @@ class core_ibex_base_test extends uvm_test;
|
|||
|
||||
uvm_config_db#(core_ibex_cosim_cfg)::set(null, "*cosim_agent*", "cosim_cfg", cosim_cfg);
|
||||
|
||||
imem_cfg = ibex_mem_intf_response_agent_cfg::type_id::create("imem_cfg", this);
|
||||
dmem_cfg = ibex_mem_intf_response_agent_cfg::type_id::create("dmem_cfg", this);
|
||||
// Never create bad integrity bits in response to accessing uninit memory
|
||||
// on the Iside, as the Ibex can fetch speculatively.
|
||||
imem_cfg.enable_bad_intg_on_uninit_access = 0;
|
||||
// By default, enable bad_intg on the Dside (read plusarg to overwrite this behaviour)
|
||||
dmem_cfg.enable_bad_intg_on_uninit_access = 1;
|
||||
void'($value$plusargs("enable_bad_intg_on_uninit_access=%0d", dmem_cfg.enable_bad_intg_on_uninit_access));
|
||||
uvm_config_db#(ibex_mem_intf_response_agent_cfg)::set(this, "*instr_if_response_agent*", "cfg", imem_cfg);
|
||||
uvm_config_db#(ibex_mem_intf_response_agent_cfg)::set(this, "*data_if_response_agent*", "cfg", dmem_cfg);
|
||||
|
||||
uvm_config_db#(core_ibex_env_cfg)::set(this, "*", "cfg", cfg);
|
||||
mem = mem_model_pkg::mem_model#()::type_id::create("mem");
|
||||
// Create virtual sequence and assign memory handle
|
||||
|
|
|
@ -1162,6 +1162,21 @@ class core_ibex_debug_single_step_test extends core_ibex_directed_test;
|
|||
|
||||
endclass
|
||||
|
||||
|
||||
class core_ibex_single_debug_pulse_test extends core_ibex_directed_test;
|
||||
|
||||
`uvm_component_utils(core_ibex_single_debug_pulse_test)
|
||||
`uvm_component_new
|
||||
|
||||
virtual task check_stimulus();
|
||||
vseq.debug_seq_single_h.max_interval = 0;
|
||||
// Start as soon as device is initialized.
|
||||
vseq.start_debug_single_seq();
|
||||
wait (test_done === 1'b1);
|
||||
endtask
|
||||
|
||||
endclass
|
||||
|
||||
// Memory interface error test class
|
||||
class core_ibex_mem_error_test extends core_ibex_directed_test;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue