diff --git a/dv/cosim/spike_cosim.cc b/dv/cosim/spike_cosim.cc index 32537875ed..c36cd6f031 100644 --- a/dv/cosim/spike_cosim.cc +++ b/dv/cosim/spike_cosim.cc @@ -217,6 +217,10 @@ bool SpikeCosim::step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc, // (If the current step causes a synchronous trap, it will be // recorded against the current pc) initial_spike_pc = (processor->get_state()->pc & 0xffffffff); + // Debug-mode state the instruction executes in; a trap on a stepped + // instruction enters debug mode within this same step, but the cpuctrlsts + // flag update is decided by the mode at trap take. + bool initial_spike_debug_mode = processor->get_state()->debug_mode; processor->step(1); // ISS @@ -277,7 +281,7 @@ bool SpikeCosim::step(uint32_t write_reg, uint32_t write_reg_data, uint32_t pc, return false; } - handle_cpuctrl_exception_entry(); + handle_cpuctrl_exception_entry(initial_spike_debug_mode); // This is all the checking possible when consider a // synchronously-trapping instruction that never retired. @@ -533,8 +537,8 @@ void SpikeCosim::leave_nmi_mode() { #endif } -void SpikeCosim::handle_cpuctrl_exception_entry() { - if (!processor->get_state()->debug_mode) { +void SpikeCosim::handle_cpuctrl_exception_entry(bool debug_mode_at_trap) { + if (!debug_mode_at_trap) { bool old_sync_exc_seen = change_cpuctrlsts_sync_exc_seen(true); if (old_sync_exc_seen) { set_cpuctrlsts_double_fault_seen(); diff --git a/dv/cosim/spike_cosim.h b/dv/cosim/spike_cosim.h index 2849206e0c..d9f50f3f52 100644 --- a/dv/cosim/spike_cosim.h +++ b/dv/cosim/spike_cosim.h @@ -88,7 +88,7 @@ class SpikeCosim : public simif_t, public Cosim { bool change_cpuctrlsts_sync_exc_seen(bool flag); void set_cpuctrlsts_double_fault_seen(); - void handle_cpuctrl_exception_entry(); + void handle_cpuctrl_exception_entry(bool debug_mode_at_trap); void initial_proc_setup(uint32_t start_pc, uint32_t start_mtvec, uint32_t mhpm_counter_num); diff --git a/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml b/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml index 49eee3562a..cbfaac80f8 100644 --- a/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml +++ b/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml @@ -77,6 +77,35 @@ test_srcs: mcounteren_test/mcounteren_lock_test.S config: riscv-tests +- test: dummy_instr_minstret_test + desc: > + Dummy instructions must not be counted in minstret. The retired-instruction + count over a straight-line block of known length is read with dummy + instruction insertion disabled, enabled at its highest rate, and disabled + again; the cosim compares the read values as well. + iterations: 1 + test_srcs: dummy_instr_test/dummy_instr_minstret_test.S + config: riscv-tests + rtl_params: + PMPEnable: 1 + SecureIbex: 1 + +- test: dummy_instr_zcmp_test + desc: > + Dummy instructions must not disturb an expanded Zcmp sequence. cm.push, + cm.pop, cm.mvsa01, cm.mva01s, cm.popret and cm.popretz are executed + repeatedly with dummy instruction insertion at its highest rate and every + architectural effect is checked. Cosim mismatches are non-fatal because + the cosim does not enable Zcmp for the reference model. + iterations: 1 + test_srcs: dummy_instr_test/dummy_instr_zcmp_test.S + config: riscv-tests + rtl_params: + PMPEnable: 1 + SecureIbex: 1 + RV32ZC: ["ibex_pkg::RV32ZcaZcmp", "ibex_pkg::RV32ZcaZcbZcmp"] + sim_opts: +disable_cosim=1 + - test: pmp_mseccfg_test_rlb1_l0_0_u0 desc: > mseccfg test @@ -475,18 +504,18 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32mi/scall.S config: riscv-tests -- test: shamt +- test: sh-misaligned desc: > - riscv test - shamt + riscv test - sh-misaligned iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32mi/shamt.S + test_srcs: ../../../../vendor/riscv-tests/isa/rv32mi/sh-misaligned.S config: riscv-tests -- test: sh-misaligned +- test: shamt desc: > - riscv test - sh-misaligned + riscv test - shamt iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32mi/sh-misaligned.S + test_srcs: ../../../../vendor/riscv-tests/isa/rv32mi/shamt.S config: riscv-tests - test: sw-misaligned @@ -524,6 +553,13 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32um/divu.S config: riscv-tests +- test: mul + desc: > + riscv test - mul + iterations: 1 + test_srcs: ../../../../vendor/riscv-tests/isa/rv32um/mul.S + config: riscv-tests + - test: mulh desc: > riscv test - mulh @@ -545,13 +581,6 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32um/mulhu.S config: riscv-tests -- test: mul - desc: > - riscv test - mul - iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32um/mul.S - config: riscv-tests - - test: rem desc: > riscv test - rem @@ -566,13 +595,6 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32um/remu.S config: riscv-tests -- test: addi - desc: > - riscv test - addi - iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/addi.S - config: riscv-tests - - test: add desc: > riscv test - add @@ -580,11 +602,11 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/add.S config: riscv-tests -- test: andi +- test: addi desc: > - riscv test - andi + riscv test - addi iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/andi.S + test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/addi.S config: riscv-tests - test: and @@ -594,6 +616,13 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/and.S config: riscv-tests +- test: andi + desc: > + riscv test - andi + iterations: 1 + test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/andi.S + config: riscv-tests + - test: auipc desc: > riscv test - auipc @@ -650,18 +679,18 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/fence_i.S config: riscv-tests -- test: jalr +- test: jal desc: > - riscv test - jalr + riscv test - jal iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/jalr.S + test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/jal.S config: riscv-tests -- test: jal +- test: jalr desc: > - riscv test - jal + riscv test - jalr iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/jal.S + test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/jalr.S config: riscv-tests - test: lb @@ -706,18 +735,18 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/lw.S config: riscv-tests -- test: ori +- test: or desc: > - riscv test - ori + riscv test - or iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/ori.S + test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/or.S config: riscv-tests -- test: or +- test: ori desc: > - riscv test - or + riscv test - ori iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/or.S + test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/ori.S config: riscv-tests - test: sb @@ -741,6 +770,13 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/simple.S config: riscv-tests +- test: sll + desc: > + riscv test - sll + iterations: 1 + test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/sll.S + config: riscv-tests + - test: slli desc: > riscv test - slli @@ -748,11 +784,11 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/slli.S config: riscv-tests -- test: sll +- test: slt desc: > - riscv test - sll + riscv test - slt iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/sll.S + test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/slt.S config: riscv-tests - test: slti @@ -769,18 +805,18 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/sltiu.S config: riscv-tests -- test: slt +- test: sltu desc: > - riscv test - slt + riscv test - sltu iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/slt.S + test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/sltu.S config: riscv-tests -- test: sltu +- test: sra desc: > - riscv test - sltu + riscv test - sra iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/sltu.S + test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/sra.S config: riscv-tests - test: srai @@ -790,11 +826,11 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/srai.S config: riscv-tests -- test: sra +- test: srl desc: > - riscv test - sra + riscv test - srl iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/sra.S + test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/srl.S config: riscv-tests - test: srli @@ -804,13 +840,6 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/srli.S config: riscv-tests -- test: srl - desc: > - riscv test - srl - iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/srl.S - config: riscv-tests - - test: sub desc: > riscv test - sub @@ -825,18 +854,18 @@ test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/sw.S config: riscv-tests -- test: xori +- test: xor desc: > - riscv test - xori + riscv test - xor iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/xori.S + test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/xor.S config: riscv-tests -- test: xor +- test: xori desc: > - riscv test - xor + riscv test - xori iterations: 1 - test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/xor.S + test_srcs: ../../../../vendor/riscv-tests/isa/rv32ui/xori.S config: riscv-tests # Test-suite: riscv-arch-tests diff --git a/dv/uvm/core_ibex/directed_tests/dummy_instr_test/dummy_instr_minstret_test.S b/dv/uvm/core_ibex/directed_tests/dummy_instr_test/dummy_instr_minstret_test.S new file mode 100644 index 0000000000..d002884f87 --- /dev/null +++ b/dv/uvm/core_ibex/directed_tests/dummy_instr_test/dummy_instr_minstret_test.S @@ -0,0 +1,66 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# Dummy instructions must not be counted in minstret. +# +# The retired-instruction count over a straight-line block of known length is +# read before, during and after dummy instruction insertion is enabled at its +# highest rate (cpuctrlsts.dummy_instr_en = 1, dummy_instr_mask = 0, which +# inserts a dummy at least every four instructions). Each window spans the +# first csrr and eight nops, so the two reads must always differ by nine. +# The cosim compares the read values against the reference model as well. + +#include "riscv_test.h" +#include "test_macros.h" + +# cpuctrlsts (0x7c0): bit 2 dummy_instr_en, bits 5:3 dummy_instr_mask. +#define CPUCTRLSTS 0x7c0 +#define CPUCTRLSTS_DUMMY_INSTR_EN 0x4 + +.macro CHECK_MINSTRET_DELTA idx + li gp, \idx + csrr t0, minstret + nop + nop + nop + nop + nop + nop + nop + nop + csrr t1, minstret + sub t2, t1, t0 + li t3, 9 + bne t2, t3, fail +.endm + +RVTEST_RV32M +RVTEST_CODE_BEGIN + + # Baseline with dummy instructions disabled. + CHECK_MINSTRET_DELTA 1 + + # Enable dummy instruction insertion at the highest rate and repeat the + # measurement; the insertion points walk across the window. + csrsi CPUCTRLSTS, CPUCTRLSTS_DUMMY_INSTR_EN + li t4, 32 +1: + CHECK_MINSTRET_DELTA 2 + addi t4, t4, -1 + bnez t4, 1b + + # Disable again and re-check the baseline. + csrci CPUCTRLSTS, CPUCTRLSTS_DUMMY_INSTR_EN + CHECK_MINSTRET_DELTA 3 + + j pass + + TEST_PASSFAIL + +RVTEST_CODE_END + + .data +RVTEST_DATA_BEGIN + TEST_DATA +RVTEST_DATA_END diff --git a/dv/uvm/core_ibex/directed_tests/dummy_instr_test/dummy_instr_zcmp_test.S b/dv/uvm/core_ibex/directed_tests/dummy_instr_test/dummy_instr_zcmp_test.S new file mode 100644 index 0000000000..c254dfcf0d --- /dev/null +++ b/dv/uvm/core_ibex/directed_tests/dummy_instr_test/dummy_instr_zcmp_test.S @@ -0,0 +1,228 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# Dummy instructions must not disturb an expanded Zcmp sequence. +# +# One round executes cm.push/cm.pop with the full register list, cm.mvsa01, +# cm.mva01s, and cm.popret/cm.popretz through a call, and checks every +# architectural effect: the stack frame contents and padding, sp, the popped +# registers, the moved registers and the return. The round runs once with +# dummy instruction insertion disabled and then repeatedly with insertion +# enabled at its highest rate (cpuctrlsts.dummy_instr_en = 1, +# dummy_instr_mask = 0, a dummy at least every four instructions), so the +# insertion points walk across every micro-op of every sequence. A micro-op +# skipped because a dummy displaced it leaves a canary in the frame, a stale +# register value, a wrong sp or a fall-through past the return. +# +# The encodings are emitted as raw halfwords because the pinned toolchain has +# no Zcmp support. Cosim mismatches are non-fatal for this test: the +# core_ibex cosim does not enable Zcmp for the reference model, so the +# model traps on every cm.* instruction. + +#include "riscv_test.h" +#include "test_macros.h" + +# cpuctrlsts (0x7c0): bit 2 dummy_instr_en, bits 5:3 dummy_instr_mask. +#define CPUCTRLSTS 0x7c0 +#define CPUCTRLSTS_DUMMY_INSTR_EN 0x4 + +#define CANARY 0xdeadbeef + +# Register use: +# gp (TESTNUM) index of the round in progress, for the failure trace +# t4 round counter for the loop with dummies enabled +# t0, t1 scratch inside a round +# ra and s0-s11 are the pushed/popped registers, a0/a1 and s0-s3 the moved ones. + +# The frame of cm.push {ra, s0-s11}, -64 is 64 bytes. The registers are stored +# from the top of the frame downwards in the order s11, s10 ... s0, ra (Zc spec +# 1.0, cm.push): s11 at 60(sp), s10 at 56(sp) ... s0 at 16(sp), ra at 12(sp). +# 0(sp)..8(sp) is padding the sequence must not write. +.macro CHECK_SLOT offset, reg + lw t0, \offset(sp) + bne t0, \reg, fail +.endm + +.macro CHECK_CANARY offset + lw t0, \offset(sp) + li t1, CANARY + bne t0, t1, fail +.endm + +.macro CHECK_REG reg, value + li t0, \value + bne t0, \reg, fail +.endm + +.macro ZCMP_ROUND idx + li gp, \idx + + # Fill the frame with a canary and give every pushed register a distinct value. + la sp, stack_top + li t0, CANARY + sw t0, -4(sp) + sw t0, -8(sp) + sw t0, -12(sp) + sw t0, -16(sp) + sw t0, -20(sp) + sw t0, -24(sp) + sw t0, -28(sp) + sw t0, -32(sp) + sw t0, -36(sp) + sw t0, -40(sp) + sw t0, -44(sp) + sw t0, -48(sp) + sw t0, -52(sp) + sw t0, -56(sp) + sw t0, -60(sp) + sw t0, -64(sp) + li ra, 0x1a000000 + \idx + li s0, 0x50000000 + \idx + li s1, 0x51000000 + \idx + li s2, 0x52000000 + \idx + li s3, 0x53000000 + \idx + li s4, 0x54000000 + \idx + li s5, 0x55000000 + \idx + li s6, 0x56000000 + \idx + li s7, 0x57000000 + \idx + li s8, 0x58000000 + \idx + li s9, 0x59000000 + \idx + li s10, 0x5a000000 + \idx + li s11, 0x5b000000 + \idx + + .2byte 0xb8f2 # cm.push {ra, s0-s11}, -64 + + la t0, stack_top + addi t0, t0, -64 + bne t0, sp, fail + CHECK_SLOT 60, s11 + CHECK_SLOT 56, s10 + CHECK_SLOT 52, s9 + CHECK_SLOT 48, s8 + CHECK_SLOT 44, s7 + CHECK_SLOT 40, s6 + CHECK_SLOT 36, s5 + CHECK_SLOT 32, s4 + CHECK_SLOT 28, s3 + CHECK_SLOT 24, s2 + CHECK_SLOT 20, s1 + CHECK_SLOT 16, s0 + CHECK_SLOT 12, ra + CHECK_CANARY 8 + CHECK_CANARY 4 + CHECK_CANARY 0 + + # Clobber everything the pop must restore. + li ra, 0 + li s0, 0 + li s1, 0 + li s2, 0 + li s3, 0 + li s4, 0 + li s5, 0 + li s6, 0 + li s7, 0 + li s8, 0 + li s9, 0 + li s10, 0 + li s11, 0 + + .2byte 0xbaf2 # cm.pop {ra, s0-s11}, 64 + + la t0, stack_top + bne t0, sp, fail + CHECK_REG ra, 0x1a000000 + \idx + CHECK_REG s0, 0x50000000 + \idx + CHECK_REG s1, 0x51000000 + \idx + CHECK_REG s2, 0x52000000 + \idx + CHECK_REG s3, 0x53000000 + \idx + CHECK_REG s4, 0x54000000 + \idx + CHECK_REG s5, 0x55000000 + \idx + CHECK_REG s6, 0x56000000 + \idx + CHECK_REG s7, 0x57000000 + \idx + CHECK_REG s8, 0x58000000 + \idx + CHECK_REG s9, 0x59000000 + \idx + CHECK_REG s10, 0x5a000000 + \idx + CHECK_REG s11, 0x5b000000 + \idx + + # cm.mvsa01 s0, s1: s0 = a0, s1 = a1. + li a0, 0xa0a0a0a0 + li a1, 0xa1a1a1a1 + li s0, 0 + li s1, 0 + .2byte 0xac26 # cm.mvsa01 s0, s1 + bne s0, a0, fail + bne s1, a1, fail + + # cm.mva01s s2, s3: a0 = s2, a1 = s3. + li s2, 0x52525252 + li s3, 0x53535353 + li a0, 0 + li a1, 0 + .2byte 0xad6e # cm.mva01s s2, s3 + bne a0, s2, fail + bne a1, s3, fail + + # cm.popret and cm.popretz through a call: the return must happen, a0 must + # carry the callee's value (or zero), and sp must be back where it started. + li a0, 0 + jal ra, popret_sub + li t0, 0x55 + bne t0, a0, fail + la t0, stack_top + bne t0, sp, fail + + li a0, 0 + jal ra, popretz_sub + bnez a0, fail + la t0, stack_top + bne t0, sp, fail +.endm + +RVTEST_RV32M +RVTEST_CODE_BEGIN + + # Baseline with dummy instructions disabled. + ZCMP_ROUND 1 + + # Enable dummy instruction insertion at the highest rate and repeat. + csrsi CPUCTRLSTS, CPUCTRLSTS_DUMMY_INSTR_EN + li t4, 48 +1: + ZCMP_ROUND 2 + addi t4, t4, -1 + bnez t4, 1b + + csrci CPUCTRLSTS, CPUCTRLSTS_DUMMY_INSTR_EN + j pass + + TEST_PASSFAIL + + # The jump after each cm.popret/cm.popretz is only reached if the return + # micro-op was dropped. + .balign 4 +popret_sub: + .2byte 0xb842 # cm.push {ra}, -16 + li a0, 0x55 + .2byte 0xbe42 # cm.popret {ra}, 16 + j fail + + .balign 4 +popretz_sub: + .2byte 0xb842 # cm.push {ra}, -16 + li a0, 0x55 + .2byte 0xbc42 # cm.popretz {ra}, 16 + j fail + +RVTEST_CODE_END + + .data +RVTEST_DATA_BEGIN + TEST_DATA + .balign 16 +stack_bot: + .fill 32, 4, 0 +stack_top: + .fill 4, 4, 0 +RVTEST_DATA_END diff --git a/dv/uvm/core_ibex/directed_tests/gen_testlist.py b/dv/uvm/core_ibex/directed_tests/gen_testlist.py index 2ae1487121..044368e429 100644 --- a/dv/uvm/core_ibex/directed_tests/gen_testlist.py +++ b/dv/uvm/core_ibex/directed_tests/gen_testlist.py @@ -97,6 +97,35 @@ def add_configs_and_handwritten_directed_tests(): test_srcs: mcounteren_test/mcounteren_lock_test.S config: riscv-tests +- test: dummy_instr_minstret_test + desc: > + Dummy instructions must not be counted in minstret. The retired-instruction + count over a straight-line block of known length is read with dummy + instruction insertion disabled, enabled at its highest rate, and disabled + again; the cosim compares the read values as well. + iterations: 1 + test_srcs: dummy_instr_test/dummy_instr_minstret_test.S + config: riscv-tests + rtl_params: + PMPEnable: 1 + SecureIbex: 1 + +- test: dummy_instr_zcmp_test + desc: > + Dummy instructions must not disturb an expanded Zcmp sequence. cm.push, + cm.pop, cm.mvsa01, cm.mva01s, cm.popret and cm.popretz are executed + repeatedly with dummy instruction insertion at its highest rate and every + architectural effect is checked. Cosim mismatches are non-fatal because + the cosim does not enable Zcmp for the reference model. + iterations: 1 + test_srcs: dummy_instr_test/dummy_instr_zcmp_test.S + config: riscv-tests + rtl_params: + PMPEnable: 1 + SecureIbex: 1 + RV32ZC: ["ibex_pkg::RV32ZcaZcmp", "ibex_pkg::RV32ZcaZcbZcmp"] + sim_opts: +disable_cosim=1 + - test: pmp_mseccfg_test_rlb1_l0_0_u0 desc: > mseccfg test @@ -432,13 +461,11 @@ def append_directed_testlist(tests, test_suite, test_suite_name, is_assembly): # Test-suite: {test_suite_name} '''.format(test_suite_name = test_suite_name) extension = '.S' if is_assembly else '.c' - extension_grep = ' | egrep .S' if is_assembly else ' | egrep .c' for test_group_name in tests: - available_tests = os.popen('ls '+test_suite+test_group_name+extension_grep).read() - available_testlist = [] - for test in available_tests.split('\n')[:-1]: - available_testlist.append(test) + # Sort the directory listing so the output does not depend on the locale. + available_testlist = sorted(f for f in os.listdir(test_suite + test_group_name) + if f.endswith(extension)) for test_name_str in available_testlist: test_name = test_name_str.split(extension)[0] testlist_string = testlist_string + ''' @@ -455,11 +482,8 @@ def append_directed_testlist(tests, test_suite, test_suite_name, is_assembly): f.write(testlist_string) def list_tests(dir): - testlist_str = os.popen('ls '+dir).read() - testlist = [] - for test in testlist_str.split('\n')[:-1]: - testlist.append(test) - print(testlist) + testlist = sorted(os.listdir(dir)) + print(testlist) return testlist def _main() -> int: diff --git a/dv/uvm/core_ibex/riscv_dv_extension/ibex_asm_program_gen.sv b/dv/uvm/core_ibex/riscv_dv_extension/ibex_asm_program_gen.sv index 4f7544b09a..d8fa45f9fb 100644 --- a/dv/uvm/core_ibex/riscv_dv_extension/ibex_asm_program_gen.sv +++ b/dv/uvm/core_ibex/riscv_dv_extension/ibex_asm_program_gen.sv @@ -75,6 +75,9 @@ class ibex_asm_program_gen extends riscv_asm_program_gen; $sformatf("addi x%0d, x%0d, 4", cfg.gpr[0], cfg.gpr[0]), $sformatf("csrw 0x%0x, x%0d", MEPC, cfg.gpr[0]) }; + // The trap dispatcher pushed a kernel-stack frame on entry; a returning + // handler must pop it, or every ECALL leaks one frame. + pop_gpr_from_kernel_stack(MSTATUS, MSCRATCH, cfg.mstatus_mprv, cfg.sp, cfg.tp, instr); instr.push_back("mret"); gen_section(get_label("ecall_handler", hart), instr); endfunction diff --git a/dv/uvm/core_ibex/riscv_dv_extension/ibex_directed_instr_lib.sv b/dv/uvm/core_ibex/riscv_dv_extension/ibex_directed_instr_lib.sv index 8dd96e18a4..c95f9f4d20 100644 --- a/dv/uvm/core_ibex/riscv_dv_extension/ibex_directed_instr_lib.sv +++ b/dv/uvm/core_ibex/riscv_dv_extension/ibex_directed_instr_lib.sv @@ -100,6 +100,7 @@ class ibex_rand_cpuctrlsts_stream extends riscv_directed_instr_stream; riscv_instr instrs[4]; bit toggle_dit; bit toggle_dummy_instr; + bit force_dummy_instr; bit toggle_icache; bit icache_en; bit dit_en; @@ -122,6 +123,17 @@ class ibex_rand_cpuctrlsts_stream extends riscv_directed_instr_stream; toggle_dummy_instr = 1'b0; end + // +toggle_dummy_instr only removes the constraint pinning dummy_instr_en to zero, so a stream + // arms the feature about half the time and reaches the shortest insertion interval (mask 0) + // about one time in sixteen. +force_dummy_instr pins enable=1 and mask=0 so every emitted + // stream arms at the highest rate; it implies toggle_dummy_instr so the two cannot contradict. + if (!$value$plusargs("force_dummy_instr=%d", force_dummy_instr)) begin + force_dummy_instr = 1'b0; + end + if (force_dummy_instr) begin + toggle_dummy_instr = 1'b1; + end + if (!$value$plusargs("toggle_icache=%d", toggle_icache)) begin toggle_icache = 1'b0; end @@ -129,9 +141,11 @@ class ibex_rand_cpuctrlsts_stream extends riscv_directed_instr_stream; `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(icache_en, if (!toggle_icache) icache_en == 0;); `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(dit_en, if (!toggle_dit) dit_en == 0;); `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(dummy_instr_en, - if (!toggle_dummy_instr) dummy_instr_en == 0;); + if (!toggle_dummy_instr) dummy_instr_en == 0; + if (force_dummy_instr) dummy_instr_en == 1;); `DV_CHECK_STD_RANDOMIZE_WITH_FATAL(dummy_instr_mask, - if (!toggle_dummy_instr) dummy_instr_mask == 0;); + if (!toggle_dummy_instr) dummy_instr_mask == 0; + if (force_dummy_instr) dummy_instr_mask == 0;); cpuctrlsts_mask = {3'b111, {4{!dummy_instr_en}}, !dit_en, !icache_en}; cpuctrlsts_val = {dummy_instr_mask, dummy_instr_en, dit_en, icache_en}; diff --git a/dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml b/dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml index 3c2a447480..22f609e234 100644 --- a/dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml +++ b/dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml @@ -653,11 +653,11 @@ +no_ebreak=0 +no_ecall=0 +no_branch_jump=0 - +instr_cnt=10000 + +instr_cnt=2000 +no_csr_instr=0 +randomize_csr=1 +gen_all_csrs_by_default=1 - +add_csr_write=MSTATUS,MEPC,MCAUSE,MTVAL,0x7c0,0x7c1 + +add_csr_write=0x7c0,0x7c1 +no_fence=0 +no_wfi=0 +num_of_sub_program=1 @@ -671,6 +671,49 @@ +enable_debug_seq=1 +enable_bad_intg_on_uninit_access=0 +- test: riscv_debug_single_step_dummy_test + description: > + Single stepping through a random program with dummy instruction insertion armed at the + highest rate. Single-step completion is keyed off instr_valid_id, which is asserted for dummy + instructions too, so a dummy arriving in the window after DRET can satisfy the step while the + instruction at dpc never executes. The stock single-step test only arms dummy insertion by + accident through a random write to cpuctrlsts. This test keeps to that charter: the program + takes no traps of its own (no ecall, ebreak, wfi, dret, illegal instructions or + sub-programs) and the CSR writes are limited to cpuctrlsts/secureseed, so every debug entry + is a debug request or a completed step. Stepping through live trap handlers stays with + riscv_debug_single_step_test. + iterations: 20 + gen_test: riscv_instr_base_test + gen_opts: > + +require_signature_addr=1 + +gen_debug_section=1 + +no_dret=1 + +no_ebreak=1 + +no_ecall=1 + +no_branch_jump=0 + +instr_cnt=2000 + +no_csr_instr=0 + +randomize_csr=1 + +gen_all_csrs_by_default=1 + +add_csr_write=0x7c0,0x7c1 + +no_fence=0 + +no_wfi=1 + +num_of_sub_program=0 + +enable_debug_single_step=1 + +illegal_instr_ratio=0 + +hint_instr_ratio=1 + +directed_instr_0=ibex_rand_cpuctrlsts_stream,8 + +force_dummy_instr=1 + +boot_mode=m + rtl_test: core_ibex_debug_single_step_test + rtl_params: + SecureIbex: 1 + sim_opts: > + +require_signature_addr=1 + +max_interval=1500 + +enable_debug_seq=1 + +enable_bad_intg_on_uninit_access=0 + - test: riscv_reset_test description: > Randomly reset the core once in the middle of program execution diff --git a/dv/uvm/core_ibex/scripts/directed_test_schema.py b/dv/uvm/core_ibex/scripts/directed_test_schema.py index 7cd4faf71f..3730ba7cb8 100755 --- a/dv/uvm/core_ibex/scripts/directed_test_schema.py +++ b/dv/uvm/core_ibex/scripts/directed_test_schema.py @@ -8,7 +8,7 @@ import sys import pydantic import pathlib3x as pathlib -from typing import List, Any +from typing import List, Any, Optional import scripts_lib @@ -52,6 +52,11 @@ class Config: # noqa rtl_test: str rtl_params: dict timeout_s: pydantic.conint(gt=0) + # Optional plusargs appended to the simulator command line. run_rtl.py + # already reads 'sim_opts' from the test options for both test types, but + # for a directed test the options come from this schema, so without this + # field pydantic silently dropped it. + sim_opts: Optional[str] = None # Directed Test Build Options gcc_opts: str # any options that don't specify a path eg. "-O3 -g -static" diff --git a/dv/uvm/core_ibex/scripts/ibex_cmd.py b/dv/uvm/core_ibex/scripts/ibex_cmd.py index 0d8c5346e2..9243bc56a1 100644 --- a/dv/uvm/core_ibex/scripts/ibex_cmd.py +++ b/dv/uvm/core_ibex/scripts/ibex_cmd.py @@ -182,8 +182,8 @@ def filter_tests_by_config(cfg: ibex_config.Config, f"rtl_params of {p_val}, which doesn't contain the " f"expected '{config_val}'.") break - - # The test is accepted if we got this far + else: + # The test is accepted if every parameter matched filtered_test_list.append(test) return filtered_test_list diff --git a/dv/uvm/core_ibex/tests/core_ibex_seq_lib.sv b/dv/uvm/core_ibex/tests/core_ibex_seq_lib.sv index 97be142bb2..7f828e9627 100644 --- a/dv/uvm/core_ibex/tests/core_ibex_seq_lib.sv +++ b/dv/uvm/core_ibex/tests/core_ibex_seq_lib.sv @@ -175,11 +175,17 @@ class debug_seq extends core_base_seq#(irq_seq_item); `uvm_object_new int unsigned drop_delay = 75; + // Longest time the request is held waiting for the core to enter debug mode. + int unsigned hold_timeout_cycles = 5000; + virtual core_ibex_rvfi_if rvfi_vif; 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") end + if (!uvm_config_db#(virtual core_ibex_rvfi_if)::get(null, "", "rvfi_if", rvfi_vif)) begin + `uvm_fatal(get_full_name(), "Cannot get rvfi_if") + end dut_vif.dut_cb.debug_req <= 1'b0; super.body(); endtask @@ -188,6 +194,22 @@ class debug_seq extends core_base_seq#(irq_seq_item); `uvm_info(get_full_name(), "Sending debug request", UVM_HIGH) dut_vif.dut_cb.debug_req <= 1'b1; clk_vif.wait_clks(drop_delay); + // A debug module keeps haltreq asserted until the hart reports halted, which the debug ROM does + // with its first instructions. Keep the request up until the core has retired an instruction + // that reports the request on RVFI (the first debug ROM instruction, or the next retirement if + // the core was already in debug mode), so a request that wakes the core from WFI is still + // asserted when that instruction enters ID, where RVFI samples it for the cosim. + `DV_SPINWAIT_EXIT(begin + wait (dut_vif.dut_cb.debug_mode == 1'b1); + do @(rvfi_vif.monitor_cb); + while (!(rvfi_vif.monitor_cb.valid && rvfi_vif.monitor_cb.ext_debug_req)); + end, + begin + clk_vif.wait_clks(hold_timeout_cycles); + `uvm_error(get_full_name(), + "No retirement reported the debug request before the hold timeout") + end, + "") dut_vif.dut_cb.debug_req <= 1'b0; endtask diff --git a/rtl/ibex_controller.sv b/rtl/ibex_controller.sv index 4f29557c9c..a0d4878662 100644 --- a/rtl/ibex_controller.sv +++ b/rtl/ibex_controller.sv @@ -36,6 +36,7 @@ module ibex_controller import ibex_pkg::*; #( // instr from IF-ID pipeline stage input logic instr_valid_i, // instr is valid + input logic dummy_instr_id_i, // instr in ID is a dummy input logic [31:0] instr_i, // uncompressed instr data for mtval input logic [15:0] instr_compressed_i, // instr compressed data for mtval input logic instr_is_compressed_i, // instr is compressed @@ -459,7 +460,12 @@ module ibex_controller import ibex_pkg::*; #( // single step mode. The first valid instruction on debug mode entry will clear it. Hold its value // when there is no valid instruction so `do_single_step_d` remains asserted until debug mode is // entered. - assign do_single_step_d = instr_valid_i ? ~debug_mode_q & debug_single_step_i : do_single_step_q; + // Dummy instructions are architecturally invisible but assert `instr_valid_i` like any other + // instruction. Without excluding them, a dummy landing in the window after DRET satisfies the + // step: debug mode is re-entered with nothing retired, dpc unchanged and the instruction at dpc + // never executed. A dummy must neither arm nor clear the request, so it takes the hold arm. + assign do_single_step_d = (instr_valid_i & ~dummy_instr_id_i) ? + ~debug_mode_q & debug_single_step_i : do_single_step_q; // Enter debug mode due to: // * external `debug_req_i` // * core in single step mode (dcsr.step == 1). diff --git a/rtl/ibex_core.sv b/rtl/ibex_core.sv index 19ec6f51ff..214072f27e 100644 --- a/rtl/ibex_core.sv +++ b/rtl/ibex_core.sv @@ -682,6 +682,7 @@ module ibex_core import ibex_pkg::*; import ibex_cheriot_pkg::*; #( // from/to IF-ID pipeline register .instr_valid_i (instr_valid_id), + .dummy_instr_id_i (dummy_instr_id), .instr_rdata_i (instr_rdata_id), .instr_rdata_alu_i (instr_rdata_alu_id), .instr_rdata_c_i (instr_rdata_c_id), @@ -2440,6 +2441,54 @@ module ibex_core import ibex_pkg::*; import ibex_cheriot_pkg::*; #( // If the ID stage signals its ready the mult/div FSMs must be idle in the following cycle // `ASSERT(MultDivFSMIdleOnIdReady, id_in_ready |=> ex_block_i.sva_multdiv_fsm_idle) + // A dummy instruction must not be what satisfies a single step (Debug v0.13.2 4.8.1: halt after + // a single instruction). `do_single_step_d` takes the hold arm for a valid dummy, so the request + // is stable across one whether or not it was already armed. + `ASSERT(SingleStepStableAcrossDummy, + instr_valid_id & dummy_instr_id |=> $stable(id_stage_i.controller_i.do_single_step_q)) + `COVER(SingleStepDummyWhileDisarmed, + instr_valid_id & dummy_instr_id & ~id_stage_i.controller_i.do_single_step_q) + `COVER(SingleStepDummyWhileArmed, + instr_valid_id & dummy_instr_id & id_stage_i.controller_i.do_single_step_q) + +`ifdef RVFI + // The architectural half of the same property: a step-caused debug entry is preceded by exactly + // one architectural retirement. Retirements are counted on RVFI, which excludes dummies by + // construction and, unlike perf_instr_ret_wb, reports ebreak, ecall, illegal and fetch-error + // instructions, all of which are legitimate things to step over. A Zcmp sequence presents one + // RVFI item per micro-op, so only its last micro-op (or a trapping one, which ends the sequence + // without a last item) closes an architectural instruction. DRET does not count as the step's + // own progress; its item can be presented after debug_mode falls, so the debug-mode indication + // captured with the item is used. The counter saturates so an overrunning step cannot wrap back + // to a pass. + logic [1:0] step_rvfi_cnt_q; + logic step_arch_retire; + + assign step_arch_retire = rvfi_valid && !rvfi_ext_debug_mode && + (rvfi_trap || !rvfi_ext_expanded_insn_valid || + rvfi_ext_expanded_insn_last); + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + step_rvfi_cnt_q <= '0; + end else if (debug_mode) begin + step_rvfi_cnt_q <= '0; + end else if (step_arch_retire && !(&step_rvfi_cnt_q)) begin + step_rvfi_cnt_q <= step_rvfi_cnt_q + 2'd1; + end + end + + `COVER(StepCountedTrappedNonFinalUop, + rvfi_valid && rvfi_trap && rvfi_ext_expanded_insn_valid && + !rvfi_ext_expanded_insn_last && debug_single_step) + + // Checked on the edge that commits the debug entry and saves dcsr.cause: the earlier + // enter_debug_mode request precedes the stepped instruction's rvfi_valid by several cycles. + `ASSERT(StepDebugEntryHasArchitecturalProgress, + debug_mode_entering && debug_csr_save && (debug_cause == DBG_CAUSE_STEP) + |=> (step_rvfi_cnt_q == 2'd1)) +`endif + ////////// // FCOV // ////////// diff --git a/rtl/ibex_id_stage.sv b/rtl/ibex_id_stage.sv index 09e035bff7..1b96878229 100644 --- a/rtl/ibex_id_stage.sv +++ b/rtl/ibex_id_stage.sv @@ -38,6 +38,7 @@ module ibex_id_stage import ibex_cheriot_pkg::*; #( // Interface to IF stage input logic instr_valid_i, + input logic dummy_instr_id_i, // instr in ID is a dummy input logic [31:0] instr_rdata_i, // from IF-ID pipeline registers input logic [31:0] instr_rdata_alu_i, // from IF-ID pipeline registers input logic [15:0] instr_rdata_c_i, // from IF-ID pipeline registers @@ -636,6 +637,7 @@ module ibex_id_stage import ibex_cheriot_pkg::*; #( // from IF-ID pipeline .instr_valid_i (instr_valid_i), + .dummy_instr_id_i (dummy_instr_id_i), .instr_i (instr_rdata_i), .instr_compressed_i (instr_rdata_c_i), .instr_is_compressed_i (instr_is_compressed_i), diff --git a/rtl/ibex_if_stage.sv b/rtl/ibex_if_stage.sv index ab2dc912d6..f37d4345b7 100644 --- a/rtl/ibex_if_stage.sv +++ b/rtl/ibex_if_stage.sv @@ -490,7 +490,8 @@ module ibex_if_stage import ibex_pkg::*; import ibex_cheriot_pkg::*; #( .clk_i (clk_i), .rst_ni (rst_ni), .valid_i (fetch_valid & ~fetch_err), - .id_in_ready_i (id_in_ready_i & ~pc_set_i), + // Hold the expander when dummy insertion replaces its current micro-op. + .id_in_ready_i (id_in_ready_i & ~pc_set_i & ~stall_dummy_instr), .instr_i (if_instr_rdata), .cheriot_enable_i (cheriot_enable_i), .instr_o (instr_decompressed), @@ -534,6 +535,19 @@ module ibex_if_stage import ibex_pkg::*; import ibex_cheriot_pkg::*; #( // PC of the dummy instruction will match whatever is next from the prefetch buffer. assign stall_dummy_instr = insert_dummy_instr; + // A dummy inserted while the compressed decoder presents an expansion micro-op must not advance + // the Zcmp expander: cm_state_q always holds, and the rlist/sp_offset position holds while a + // sequence is in flight. In CmIdle those two are a combinational pre-load from instr_i that is + // recomputed once the dummy has passed, so they are not checked there. The antecedent requires + // a genuine accept (valid, ready, no redirect) because a flush also reloads the expander. + `ASSERT(NoZcmpExpansionAdvanceOnDummy, + insert_dummy_instr && fetch_valid && !fetch_err && id_in_ready_i && !pc_set_i && + (instr_gets_expanded != INSTR_NOT_EXPANDED) + |=> $stable(compressed_decoder_i.cm_state_q) && + (($past(compressed_decoder_i.cm_state_q) == compressed_decoder_i.CmIdle) || + $stable({compressed_decoder_i.cm_rlist_q, + compressed_decoder_i.cm_sp_offset_q}))) + // Register the dummy instruction indication into the ID stage always_ff @(posedge clk_i or negedge rst_ni) begin if (!rst_ni) begin diff --git a/rtl/ibex_wb_stage.sv b/rtl/ibex_wb_stage.sv index 4379ee0334..0c4e73e926 100644 --- a/rtl/ibex_wb_stage.sv +++ b/rtl/ibex_wb_stage.sv @@ -203,9 +203,13 @@ module ibex_wb_stage import ibex_cheriot_pkg::*; #( // Speculative versions of the signals do not factor in exceptions and whether the instruction // is done yet. These are used to get correct values for instructions reading the relevant // performance counters in the ID stage. - assign perf_instr_ret_wb_spec_o = wb_count_q & wb_valid_q; + // Dummy instructions are architecturally invisible, so they must not be counted as retired. + // instr_perf_count_id_i cannot exclude them (they are indistinguishable in ID); the marker for + // the instruction occupying WB is available here. + assign perf_instr_ret_wb_spec_o = wb_count_q & wb_valid_q & ~dummy_instr_wb_o; assign perf_instr_ret_compressed_wb_spec_o = perf_instr_ret_wb_spec_o & wb_compressed_q; assign perf_instr_ret_wb_o = instr_done_wb_o & wb_count_q & + ~dummy_instr_wb_o & ~(lsu_resp_valid_i & lsu_resp_err_i); assign perf_instr_ret_compressed_wb_o = perf_instr_ret_wb_o & wb_compressed_q; @@ -261,7 +265,9 @@ module ibex_wb_stage import ibex_cheriot_pkg::*; #( // values will be correct. assign perf_instr_ret_wb_spec_o = 1'b0; assign perf_instr_ret_compressed_wb_spec_o = 1'b0; + // Same exclusion of dummy instructions as the writeback-stage branch above. assign perf_instr_ret_wb_o = instr_perf_count_id_i & en_wb_i & + ~dummy_instr_wb_o & ~(lsu_resp_valid_i & lsu_resp_err_i); assign perf_instr_ret_compressed_wb_o = perf_instr_ret_wb_o & instr_is_compressed_id_i; @@ -308,4 +314,19 @@ module ibex_wb_stage import ibex_cheriot_pkg::*; #( `DV_FCOV_SIGNAL_GEN_IF(logic, wb_valid, g_writeback_stage.wb_valid_q, WritebackStage) `ASSERT(RFWriteFromOneSourceOnly, $onehot0(rf_wdata_wb_mux_we)) + + // A dummy instruction is architecturally invisible, so retiring one must not advance the retired + // instruction counter that drives minstret, neither through the final retire indication nor + // through the speculative one consumed by a CSR read in ID. Both WritebackStage branches must + // keep this true. Sibling of WaddrAZeroForDummyInstr in ibex_top.sv. + `ASSERT(NoMinstretForDummyInstr, dummy_instr_wb_o |-> ~perf_instr_ret_wb_o) + `ASSERT(NoSpecMinstretForDummyInstr, dummy_instr_wb_o |-> ~perf_instr_ret_wb_spec_o) + + // The dummy marker can stay asserted after the WB entry stops being valid, so cover the cycles + // where a dummy genuinely occupies the stage (wb_valid_q with a writeback stage, en_wb_i without). + if (WritebackStage) begin : g_dummy_cover_wb + `COVER(DummyInstrLiveInWb, dummy_instr_wb_o & g_writeback_stage.wb_valid_q) + end else begin : g_dummy_cover_bypass + `COVER(DummyInstrLiveInWb, dummy_instr_wb_o & en_wb_i) + end endmodule diff --git a/vendor/google_riscv-dv/src/riscv_debug_rom_gen.sv b/vendor/google_riscv-dv/src/riscv_debug_rom_gen.sv index 767540bd38..2b6f544894 100644 --- a/vendor/google_riscv-dv/src/riscv_debug_rom_gen.sv +++ b/vendor/google_riscv-dv/src/riscv_debug_rom_gen.sv @@ -116,9 +116,16 @@ class riscv_debug_rom_gen extends riscv_asm_program_gen; endfunction // Generate exception handling routine for debug ROM - // TODO(udinator) - remains empty for now, only a DRET + // Route through debug_end so the GPR frame pushed at debug_rom entry is + // unwound before dret; dpc is untouched by in-debug exceptions, so the + // interrupted program resumes correctly. virtual function void gen_debug_exception_handler(); - str = {"dret"}; + if (cfg.gen_debug_section) begin + str = {$sformatf("j %0sdebug_end", hart_prefix(hart))}; + end else begin + // No debug_end section exists in this configuration; nothing was pushed. + str = {"dret"}; + end gen_section($sformatf("%0sdebug_exception", hart_prefix(hart)), str); endfunction diff --git a/vendor/google_riscv-dv/src/riscv_instr_pkg.sv b/vendor/google_riscv-dv/src/riscv_instr_pkg.sv index 12f248b74e..0c356b51c2 100644 --- a/vendor/google_riscv-dv/src/riscv_instr_pkg.sv +++ b/vendor/google_riscv-dv/src/riscv_instr_pkg.sv @@ -1381,7 +1381,18 @@ package riscv_instr_pkg; riscv_reg_t tp, ref string instr[$]); string store_instr = (XLEN == 32) ? "sw" : "sd"; - if (scratch inside {implemented_csr}) begin + // On RV32 with BARE translation, claim the whole frame (USP slot plus 32 GPR slots) in a + // single instruction and publish the claim in gpr.TP before any slot is written, so a debug + // entry taken mid-push allocates below this frame instead of on top of it. Other + // configurations keep the original sequence. + bit atomic_claim = (scratch inside {implemented_csr}) && (XLEN == 32) && (SATP_MODE == BARE); + if (atomic_claim) begin + instr.push_back($sformatf("addi x%0d, x%0d, -132", tp, tp)); + // Push USP from gpr.SP onto the kernel stack (the top slot of the frame) + instr.push_back($sformatf("%0s x%0d, 128(x%0d)", store_instr, sp, tp)); + // Move KSP to gpr.SP + instr.push_back($sformatf("add x%0d, x%0d, zero", sp, tp)); + end else if (scratch inside {implemented_csr}) begin // Push USP from gpr.SP onto the kernel stack instr.push_back($sformatf("addi x%0d, x%0d, -4", tp, tp)); instr.push_back($sformatf("%0s x%0d, (x%0d)", store_instr, sp, tp)); @@ -1408,8 +1419,10 @@ package riscv_instr_pkg; end end // Push all GPRs (except for x0) to kernel stack - // (gpr.SP currently holds the KSP) - instr.push_back($sformatf("addi x%0d, x%0d, -%0d", sp, sp, 32 * (XLEN/8))); + // (gpr.SP currently holds the KSP; with an atomic claim the frame is already reserved) + if (!atomic_claim) begin + instr.push_back($sformatf("addi x%0d, x%0d, -%0d", sp, sp, 32 * (XLEN/8))); + end for(int i = 1; i < 32; i++) begin instr.push_back($sformatf("%0s x%0d, %0d(x%0d)", store_instr, i, i * (XLEN/8), sp)); end @@ -1426,12 +1439,21 @@ package riscv_instr_pkg; riscv_reg_t tp, ref string instr[$]); string load_instr = (XLEN == 32) ? "lw" : "ld"; + // Mirror of the claim made in push_gpr_to_kernel_stack. + bit atomic_claim = (scratch inside {implemented_csr}) && (XLEN == 32) && (SATP_MODE == BARE); // Move KSP to gpr.SP instr.push_back($sformatf("add x%0d, x%0d, zero", sp, tp)); // Pop GPRs from kernel stack for(int i = 1; i < 32; i++) begin instr.push_back($sformatf("%0s x%0d, %0d(x%0d)", load_instr, i, i * (XLEN/8), sp)); end + if (atomic_claim) begin + // Restore USP while the claim is still held, then release the USP slot and the frame in a + // single instruction (mirrors the push-side claim). + instr.push_back($sformatf("%0s x%0d, 128(x%0d)", load_instr, sp, tp)); + instr.push_back($sformatf("addi x%0d, x%0d, 132", tp, tp)); + return; + end instr.push_back($sformatf("addi x%0d, x%0d, %0d", sp, sp, 32 * (XLEN/8))); if (scratch inside {implemented_csr}) begin // Move KSP back to gpr.TP diff --git a/vendor/patches/google_riscv-dv/0006-pop-the-debug-frame-on-the-debug-exception-path.patch b/vendor/patches/google_riscv-dv/0006-pop-the-debug-frame-on-the-debug-exception-path.patch new file mode 100644 index 0000000000..fc9be8794d --- /dev/null +++ b/vendor/patches/google_riscv-dv/0006-pop-the-debug-frame-on-the-debug-exception-path.patch @@ -0,0 +1,17 @@ +--- a/src/riscv_debug_rom_gen.sv ++++ b/src/riscv_debug_rom_gen.sv +@@ -118,5 +118,12 @@ class riscv_debug_rom_gen extends riscv_asm_program_gen; + // Generate exception handling routine for debug ROM +- // TODO(udinator) - remains empty for now, only a DRET ++ // Route through debug_end so the GPR frame pushed at debug_rom entry is ++ // unwound before dret; dpc is untouched by in-debug exceptions, so the ++ // interrupted program resumes correctly. + virtual function void gen_debug_exception_handler(); +- str = {"dret"}; ++ if (cfg.gen_debug_section) begin ++ str = {$sformatf("j %0sdebug_end", hart_prefix(hart))}; ++ end else begin ++ // No debug_end section exists in this configuration; nothing was pushed. ++ str = {"dret"}; ++ end + gen_section($sformatf("%0sdebug_exception", hart_prefix(hart)), str); diff --git a/vendor/patches/google_riscv-dv/0007-make-the-kernel-stack-frame-claim-atomic.patch b/vendor/patches/google_riscv-dv/0007-make-the-kernel-stack-frame-claim-atomic.patch new file mode 100644 index 0000000000..7ce06c0162 --- /dev/null +++ b/vendor/patches/google_riscv-dv/0007-make-the-kernel-stack-frame-claim-atomic.patch @@ -0,0 +1,57 @@ +--- a/src/riscv_instr_pkg.sv ++++ b/src/riscv_instr_pkg.sv +@@ -1381,7 +1381,18 @@ package riscv_instr_pkg; + riscv_reg_t tp, + ref string instr[$]); + string store_instr = (XLEN == 32) ? "sw" : "sd"; +- if (scratch inside {implemented_csr}) begin ++ // On RV32 with BARE translation, claim the whole frame (USP slot plus 32 GPR slots) in a ++ // single instruction and publish the claim in gpr.TP before any slot is written, so a debug ++ // entry taken mid-push allocates below this frame instead of on top of it. Other ++ // configurations keep the original sequence. ++ bit atomic_claim = (scratch inside {implemented_csr}) && (XLEN == 32) && (SATP_MODE == BARE); ++ if (atomic_claim) begin ++ instr.push_back($sformatf("addi x%0d, x%0d, -132", tp, tp)); ++ // Push USP from gpr.SP onto the kernel stack (the top slot of the frame) ++ instr.push_back($sformatf("%0s x%0d, 128(x%0d)", store_instr, sp, tp)); ++ // Move KSP to gpr.SP ++ instr.push_back($sformatf("add x%0d, x%0d, zero", sp, tp)); ++ end else if (scratch inside {implemented_csr}) begin + // Push USP from gpr.SP onto the kernel stack + instr.push_back($sformatf("addi x%0d, x%0d, -4", tp, tp)); + instr.push_back($sformatf("%0s x%0d, (x%0d)", store_instr, sp, tp)); +@@ -1408,8 +1419,10 @@ package riscv_instr_pkg; + end + end + // Push all GPRs (except for x0) to kernel stack +- // (gpr.SP currently holds the KSP) +- instr.push_back($sformatf("addi x%0d, x%0d, -%0d", sp, sp, 32 * (XLEN/8))); ++ // (gpr.SP currently holds the KSP; with an atomic claim the frame is already reserved) ++ if (!atomic_claim) begin ++ instr.push_back($sformatf("addi x%0d, x%0d, -%0d", sp, sp, 32 * (XLEN/8))); ++ end + for(int i = 1; i < 32; i++) begin + instr.push_back($sformatf("%0s x%0d, %0d(x%0d)", store_instr, i, i * (XLEN/8), sp)); + end +@@ -1426,12 +1439,21 @@ package riscv_instr_pkg; + riscv_reg_t tp, + ref string instr[$]); + string load_instr = (XLEN == 32) ? "lw" : "ld"; ++ // Mirror of the claim made in push_gpr_to_kernel_stack. ++ bit atomic_claim = (scratch inside {implemented_csr}) && (XLEN == 32) && (SATP_MODE == BARE); + // Move KSP to gpr.SP + instr.push_back($sformatf("add x%0d, x%0d, zero", sp, tp)); + // Pop GPRs from kernel stack + for(int i = 1; i < 32; i++) begin + instr.push_back($sformatf("%0s x%0d, %0d(x%0d)", load_instr, i, i * (XLEN/8), sp)); + end ++ if (atomic_claim) begin ++ // Restore USP while the claim is still held, then release the USP slot and the frame in a ++ // single instruction (mirrors the push-side claim). ++ instr.push_back($sformatf("%0s x%0d, 128(x%0d)", load_instr, sp, tp)); ++ instr.push_back($sformatf("addi x%0d, x%0d, 132", tp, tp)); ++ return; ++ end + instr.push_back($sformatf("addi x%0d, x%0d, %0d", sp, sp, 32 * (XLEN/8))); + if (scratch inside {implemented_csr}) begin + // Move KSP back to gpr.TP