mirror of
https://github.com/lowRISC/ibex.git
synced 2025-04-22 12:57:13 -04:00
[dv/ibex] Enhance riscv_debug_single_step test
As pointed out by @tomroberts-lowrisc in #983, the current implementation of riscv_debug_single_step_test cannot handle single-stepping over instructions that change the PC. This PR aims to introduce this functionality, utilizing the new instr_monitor_if. Now, if the core single-steps onto a branch/jump instruction, the testbench will log the new target PC and compare it against the actual target address stored in `dpc`. "Normal" instructions are checked as usual by incrementing the instruction's PC by either 2 or 4 (depending whether it is compressed) and comparing that against `dpc`.
This commit is contained in:
parent
7f9e704f36
commit
1619ea2bc7
4 changed files with 108 additions and 26 deletions
|
@ -6,7 +6,11 @@
|
|||
//
|
||||
// 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);
|
||||
interface core_ibex_instr_monitor_if #(
|
||||
parameter DATA_WIDTH = 32
|
||||
) (
|
||||
input clk
|
||||
);
|
||||
|
||||
// ID stage
|
||||
logic valid_id;
|
||||
|
@ -14,5 +18,19 @@ interface core_ibex_instr_monitor_if #(parameter DATA_WIDTH = 32);
|
|||
logic is_compressed_id;
|
||||
logic [15:0] instr_compressed_id;
|
||||
logic [DATA_WIDTH-1:0] instr_id;
|
||||
logic [DATA_WIDTH-1:0] pc_id;
|
||||
logic branch_taken_id;
|
||||
logic [DATA_WIDTH-1:0] branch_target_id;
|
||||
|
||||
clocking instr_cb @(posedge clk);
|
||||
input valid_id;
|
||||
input err_id;
|
||||
input is_compressed_id;
|
||||
input instr_compressed_id;
|
||||
input instr_id;
|
||||
input pc_id;
|
||||
input branch_taken_id;
|
||||
input branch_target_id;
|
||||
endclocking
|
||||
|
||||
endinterface
|
||||
|
|
|
@ -535,11 +535,11 @@
|
|||
+require_signature_addr=1
|
||||
+gen_debug_section=1
|
||||
+no_ebreak=1
|
||||
+no_branch_jump=1
|
||||
+no_branch_jump=0
|
||||
+instr_cnt=10000
|
||||
+no_csr_instr=1
|
||||
+no_fence=1
|
||||
+num_of_sub_program=0
|
||||
+num_of_sub_program=2
|
||||
+randomize_csr=1
|
||||
+enable_debug_single_step=1
|
||||
rtl_test: core_ibex_debug_single_step_test
|
||||
|
|
|
@ -21,7 +21,7 @@ module core_ibex_tb_top;
|
|||
core_ibex_dut_probe_if dut_if(.clk(clk));
|
||||
|
||||
// Instruction monitor interface
|
||||
core_ibex_instr_monitor_if instr_monitor_if();
|
||||
core_ibex_instr_monitor_if instr_monitor_if(.clk(clk));
|
||||
|
||||
// RVFI interface
|
||||
core_ibex_rvfi_if rvfi_if(.clk(clk));
|
||||
|
@ -139,6 +139,9 @@ module core_ibex_tb_top;
|
|||
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;
|
||||
assign instr_monitor_if.pc_id = dut.u_ibex_core.pc_id;
|
||||
assign instr_monitor_if.branch_taken_id = dut.u_ibex_core.id_stage_i.controller_i.branch_set_i;
|
||||
assign instr_monitor_if.branch_target_id = dut.u_ibex_core.branch_target_ex;
|
||||
// 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;
|
||||
|
|
|
@ -483,7 +483,7 @@ class core_ibex_directed_test extends core_ibex_debug_intr_basic_test;
|
|||
bit [12:0] system_imm;
|
||||
instr_t instr_fields;
|
||||
|
||||
opcode = ibex_pkg::opcode_e'(instr[6:0]);
|
||||
opcode = ibex_pkg::opcode_e`(instr[6:0]);
|
||||
funct3 = instr[14:12];
|
||||
funct7 = instr[31:25];
|
||||
system_imm = instr[31:20];
|
||||
|
@ -658,16 +658,16 @@ class core_ibex_interrupt_instr_test extends core_ibex_directed_test;
|
|||
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));
|
||||
wait (instr_vif.instr_cb.valid_id && !(instr_vif.instr_cb.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
|
||||
if (instr_vif.instr_cb.is_compressed_id) begin
|
||||
if (decode_compressed_instr(instr_vif.instr_cb.instr_compressed_id)) begin
|
||||
send_irq_stimulus(.no_fast(1'b1));
|
||||
end
|
||||
end else begin
|
||||
if (decode_instr(instr_vif.instr_id)) begin
|
||||
if (decode_instr(instr_vif.instr_cb.instr_id)) begin
|
||||
send_irq_stimulus(.no_fast(1'b1));
|
||||
end
|
||||
end
|
||||
|
@ -829,21 +829,21 @@ class core_ibex_debug_instr_test extends core_ibex_directed_test;
|
|||
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));
|
||||
wait (instr_vif.instr_cb.valid_id && !(instr_vif.instr_cb.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
|
||||
if (instr_vif.instr_cb.is_compressed_id) begin
|
||||
if (decode_compressed_instr(instr_vif.instr_cb.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));
|
||||
instr_vif.instr_cb.instr_compressed_id));
|
||||
end
|
||||
end else begin
|
||||
if (decode_instr(instr_vif.instr_id)) begin
|
||||
if (decode_instr(instr_vif.instr_cb.instr_id)) begin
|
||||
send_debug_stimulus(init_operating_mode,
|
||||
$sformatf("Did not jump into debug mode after instruction[0x%0x]",
|
||||
instr_vif.instr_id));
|
||||
instr_vif.instr_cb.instr_id));
|
||||
end
|
||||
end
|
||||
clk_vif.wait_clks(1);
|
||||
|
@ -986,38 +986,85 @@ class core_ibex_debug_single_step_test extends core_ibex_directed_test;
|
|||
`uvm_component_new
|
||||
|
||||
virtual task check_stimulus();
|
||||
bit[ibex_mem_intf_agent_pkg::DATA_WIDTH-1:0] ret_pc;
|
||||
bit [ibex_mem_intf_agent_pkg::DATA_WIDTH-1:0] counter = 0;
|
||||
bit [ibex_mem_intf_agent_pkg::DATA_WIDTH-1:0] next_counter = 0;
|
||||
bit [ibex_mem_intf_agent_pkg::DATA_WIDTH-1:0] ret_pc;
|
||||
bit [ibex_mem_intf_agent_pkg::DATA_WIDTH-1:0] curr_pc;
|
||||
bit [ibex_mem_intf_agent_pkg::DATA_WIDTH-1:0] next_pc;
|
||||
bit [ibex_mem_intf_agent_pkg::DATA_WIDTH-1:0] step_instr;
|
||||
bit [ibex_mem_intf_agent_pkg::DATA_WIDTH-1:0] branch_target;
|
||||
bit branch_taken;
|
||||
bit is_compressed;
|
||||
|
||||
forever begin
|
||||
clk_vif.wait_clks(2000);
|
||||
vseq.start_debug_single_seq();
|
||||
// perform the standard set of debug mode checks
|
||||
check_next_core_status(IN_DEBUG_MODE,
|
||||
"Core did not enter debug mode after debug stimulus", 1000);
|
||||
check_priv_mode(PRIV_LVL_M);
|
||||
wait_for_csr_write(CSR_DPC, 500);
|
||||
ret_pc = signature_data;
|
||||
wait_for_csr_write(CSR_DSCRATCH0, 500);
|
||||
next_counter = signature_data;
|
||||
counter = signature_data;
|
||||
wait_for_csr_write(CSR_DCSR, 1000);
|
||||
check_dcsr_prv(init_operating_mode);
|
||||
check_dcsr_cause(DBG_CAUSE_HALTREQ);
|
||||
`DV_CHECK_EQ_FATAL(signature_data[2], 1'b1, "dcsr.step is not set")
|
||||
// wait for the DRET instruction indicating return out of debug mode.
|
||||
wait_ret("dret", 5000);
|
||||
// now we loop on the counter until we are done single stepping
|
||||
ret_pc = instr_vif.instr_cb.pc_id;
|
||||
// wait for the instruction after dret to log its PC and other information.
|
||||
wait (instr_vif.instr_cb.pc_id != ret_pc &&
|
||||
instr_vif.instr_cb.valid_id &&
|
||||
!dut_vif.dut_cb.dret);
|
||||
curr_pc = instr_vif.instr_cb.pc_id;
|
||||
step_instr = instr_vif.instr_cb.instr_id;
|
||||
is_compressed = instr_vif.instr_cb.is_compressed_id;
|
||||
branch_taken = instr_vif.instr_cb.branch_taken_id;
|
||||
// need to zero the bottom bit of branch_target to prevent unaligned addresses.
|
||||
branch_target = instr_vif.instr_cb.branch_target_id;
|
||||
branch_target[0] = 1'b0;
|
||||
// After the first DRET, the next <counter> instructions will cause a transition
|
||||
// into debug mode, as the core will single-step over all of them.
|
||||
//
|
||||
// Now we loop on the counter until we are done single stepping
|
||||
while (counter >= 0) begin
|
||||
counter = next_counter;
|
||||
check_next_core_status(IN_DEBUG_MODE,
|
||||
"Core did not enter debug mode after debug stimulus", 1000);
|
||||
check_priv_mode(PRIV_LVL_M);
|
||||
wait_for_csr_write(CSR_DPC, 500);
|
||||
if (signature_data - ret_pc !== 'h2 &&
|
||||
signature_data - ret_pc !== 'h4) begin
|
||||
`uvm_fatal(`gfn,
|
||||
$sformatf("DPC value [0x%0x] is not the next instruction after ret_pc [0x%0x]",
|
||||
signature_data, ret_pc))
|
||||
end
|
||||
ret_pc = signature_data;
|
||||
// checks depend whether the interrupt instruction is a branch/jump.
|
||||
// we can also assume the instruction is valid as this test disables illegal instructions.
|
||||
case (ibex_pkg::opcode_e`(step_instr[6:0]))
|
||||
OPCODE_JAL, OPCODE_JALR: begin
|
||||
`DV_CHECK_EQ_FATAL(branch_target, ret_pc,
|
||||
$sformatf("DPC value[0x%0x] does not match jump target[0x%0x] at PC[0x%0x]",
|
||||
ret_pc, branch_target, curr_pc))
|
||||
end
|
||||
OPCODE_BRANCH: begin
|
||||
// first check whether branch is actually taken
|
||||
if (branch_taken) begin
|
||||
`DV_CHECK_EQ_FATAL(branch_target, ret_pc,
|
||||
$sformatf("DPC value[0x%0x] does not match branch target[0x%0x] at PC[0x%0x]",
|
||||
ret_pc, branch_target, curr_pc))
|
||||
end else begin
|
||||
// branch is not taken
|
||||
next_pc = (is_compressed) ? curr_pc + 2 : curr_pc + 4;
|
||||
`DV_CHECK_EQ_FATAL(next_pc, ret_pc,
|
||||
$sformatf("DPC value[0x%0x] does not match expected next PC[0x%0x] at PC[0x%0x]",
|
||||
ret_pc, next_pc, curr_pc))
|
||||
end
|
||||
end
|
||||
// all other instructions
|
||||
default: begin
|
||||
next_pc = (is_compressed) ? curr_pc + 2 : curr_pc + 4;
|
||||
`DV_CHECK_EQ_FATAL(next_pc, ret_pc,
|
||||
$sformatf("DPC value[0x%0x] does not match expected next PC[0x%0x] at PC[0x%0x]",
|
||||
ret_pc, next_pc, curr_pc))
|
||||
end
|
||||
endcase
|
||||
wait_for_csr_write(CSR_DSCRATCH0, 500);
|
||||
next_counter = signature_data;
|
||||
wait_for_csr_write(CSR_DCSR, 500);
|
||||
|
@ -1029,7 +1076,21 @@ class core_ibex_debug_single_step_test extends core_ibex_directed_test;
|
|||
`DV_CHECK_EQ_FATAL(signature_data[2], 1'b1, "dcsr.step is not set")
|
||||
end
|
||||
wait_ret("dret", 5000);
|
||||
if (counter === 0) break;
|
||||
ret_pc = instr_vif.instr_cb.pc_id;
|
||||
if (counter == 0) break;
|
||||
// wait until Ibex steps to the next instruction.
|
||||
wait (instr_vif.instr_cb.pc_id != ret_pc &&
|
||||
instr_vif.instr_cb.valid_id &&
|
||||
!dut_vif.dut_cb.dret);
|
||||
// log information about this instruction
|
||||
curr_pc = instr_vif.instr_cb.pc_id;
|
||||
step_instr = instr_vif.instr_cb.instr_id;
|
||||
is_compressed = instr_vif.instr_cb.is_compressed_id;
|
||||
branch_taken = instr_vif.instr_cb.branch_taken_id;
|
||||
// need to zero the bottom bit of branch_target to prevent unaligned addresses.
|
||||
branch_target = instr_vif.instr_cb.branch_target_id;
|
||||
branch_target[0] = 1'b0;
|
||||
counter = next_counter;
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue