From 1a3c5e9720639523268f662d62f9bfe4933906a1 Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 5 Sep 2026 12:58:58 -0700 Subject: [PATCH 01/14] [dv] Add sim_opts to the directed-test schema run_rtl.py reads 'sim_opts' out of the test options for both riscv-dv and directed tests, but the pydantic schema that validates a directed test's configuration has no such field, so pydantic's default extra="ignore" dropped it. A directed test therefore could not pass a plusarg through its own testlist entry. Add the field to DConfig, optional so every existing entry stays valid, and on DConfig rather than DTest so a config can supply a default that an individual test still overrides after the merge. Signed-off-by: Kulan Palanichamy --- dv/uvm/core_ibex/scripts/directed_test_schema.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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" From 5f4b0a79e40324102d99888a5ad53d9870828d75 Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 5 Sep 2026 13:17:05 -0700 Subject: [PATCH 02/14] [dv] Accept a test once in filter_tests_by_config The append that accepts a test sat inside the loop over its rtl_params, so a test listing N parameters was scheduled N times: make warned that its targets were given more than once and regr.log counted every seed N times. Every existing entry lists a single parameter, which hid it. Append in the loop's else clause instead, once all parameters matched. Signed-off-by: Kulan Palanichamy --- dv/uvm/core_ibex/scripts/ibex_cmd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From ea69428de4c9ab0b268544130f280d0c0be6d0ef Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 5 Sep 2026 14:39:00 -0700 Subject: [PATCH 03/14] [dv] Sort directory listings in gen_testlist.py gen_testlist.py enumerated the vendored test directories with ls, whose order follows the collation of the current locale: under C/POSIX sh-misaligned sorts before shamt, under en_US after it. Regenerating directed_testlist.yaml therefore produced a different file depending on the environment, and the committed file could not be checked against its generator. List the directories with os.listdir() and sort the names in code point order, which does not depend on the locale. Regenerate directed_testlist.yaml: no entry changes, only the order of the vendored tests. Signed-off-by: Kulan Palanichamy --- .../directed_tests/directed_testlist.yaml | 120 +++++++++--------- .../core_ibex/directed_tests/gen_testlist.py | 15 +-- 2 files changed, 65 insertions(+), 70 deletions(-) diff --git a/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml b/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml index 49eee3562a..9f5b120731 100644 --- a/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml +++ b/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml @@ -475,18 +475,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 +524,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 +552,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 +566,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 +573,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 +587,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 +650,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 +706,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 +741,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 +755,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 +776,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 +797,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 +811,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 +825,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/gen_testlist.py b/dv/uvm/core_ibex/directed_tests/gen_testlist.py index 2ae1487121..c3bde9c97d 100644 --- a/dv/uvm/core_ibex/directed_tests/gen_testlist.py +++ b/dv/uvm/core_ibex/directed_tests/gen_testlist.py @@ -432,13 +432,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 +453,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: From 9df4b2d004db671642a281c04d1931dc3e54770e Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Wed, 19 Aug 2026 11:23:53 -0700 Subject: [PATCH 04/14] [dv/cosim] Sample debug mode before the step handle_cpuctrl_exception_entry() tested Spike's debug-mode state after processor->step(), but a trap taken on a stepped instruction enters debug mode inside that same step (the catch block in execute.cc), so the post-step check saw debug mode and skipped the sync_exc_seen and double_fault_seen updates. The RTL sets those flags at trap take, which is outside debug mode, so the models diverged: riscv_debug_single_step seeds read cpuctrlsts as 0x141 on the DUT against 0x101 in the model. Capture debug mode before the step and pass it in. The RTL behaviour is architecturally correct; only the model's sampling point was wrong. Signed-off-by: Kulan Palanichamy --- dv/cosim/spike_cosim.cc | 10 +++++++--- dv/cosim/spike_cosim.h | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) 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); From f447c1ca8a535cd904f85242c6172fee710cce2a Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 5 Sep 2026 14:39:47 -0700 Subject: [PATCH 05/14] [dv] Pop the trap frame in the ECALL handler The Ibex ECALL handler override advanced MEPC and executed mret without popping the kernel-stack frame the trap dispatcher had pushed on entry, so every ECALL leaked one 132-byte frame. Once enough frames had leaked, a generated subprogram epilogue reloaded its return address from a slot that now held unrelated saved state and jumped into uninitialized memory, after which the program walked mepc+4 through zeroed memory until the wall-clock timeout. The cosimulator stayed silent because Spike executed the same architecturally consistent corruption. Pop the frame before the mret, restoring every interrupted GPR including the randomized SP and TP roles. MEPC is rewritten before the pop, so restoring the scratch register cannot lose the new return address, and the register-dump stores keep their memory side effects. The leak only becomes visible once single stepping runs through the whole program (see the debug ROM fix in the vendored riscv-dv), which is why the single-step tests were the first to hit it. Signed-off-by: Kulan Palanichamy --- dv/uvm/core_ibex/riscv_dv_extension/ibex_asm_program_gen.sv | 3 +++ 1 file changed, 3 insertions(+) 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 From 84b2eaf3c319b9fe6307b521d012a8b93ed180e1 Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Wed, 19 Aug 2026 11:23:37 -0700 Subject: [PATCH 06/14] [vendor] Pop the debug frame on debug exceptions The debug exception handler ended in a bare dret, so every exception taken inside debug mode returned without popping the frame the debug ROM had pushed. Each in-debug exception leaked one 132-byte frame; one seed leaked 825 frames and walked 107.9 KB down from kernel_stack_end into the program image. Route the handler to debug_end (pop then dret) whenever a debug section was generated; dpc is untouched by an in-debug exception, so the canonical epilogue returns to the right place. Keep the bare dret when there is no debug section and therefore no frame to release. This restores whole-program single stepping: before it, stepping silently stopped at the first in-debug exception, so the single-step tests passed without stepping through the program. Carried as vendor/patches/google_riscv-dv/0006 so that the next vendor update re-applies it. Signed-off-by: Kulan Palanichamy --- .../google_riscv-dv/src/riscv_debug_rom_gen.sv | 11 +++++++++-- ...ebug-frame-on-the-debug-exception-path.patch | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 vendor/patches/google_riscv-dv/0006-pop-the-debug-frame-on-the-debug-exception-path.patch 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/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); From 7d327df405eeb92e90ef9b74a915b0626d72e46c Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 5 Sep 2026 22:51:20 -0700 Subject: [PATCH 07/14] [vendor] Make the kernel-stack claim atomic push_gpr_to_kernel_stack published its claim in TP only at the end of the push. With dcsr.step armed, stepping a trap handler lets each debug entry allocate a frame from the stale TP, four bytes into the handler's half-built frame: the debug frame's slot 8 lands on the handler's saved SP and slot 28 on its frame pointer, so the handler's pop reloads its base mid-sequence and the register file unwinds to garbage. On the configuration Ibex runs (RV32, BARE translation, scratch CSR implemented) claim the frame in a single instruction (addi tp, tp, -132) so TP publishes before any store, keep the USP slot at the top of the frame (offset 128, so the frame layout and every address are unchanged), restore USP while the claim is still held, and release in one instruction. A debug entry interrupting any point of the sequence now allocates below the active frame. Every other configuration (RV64, address translation, no scratch CSR) keeps the original sequence untouched. Carried as vendor/patches/google_riscv-dv/0007 so that the next vendor update re-applies it. Signed-off-by: Kulan Palanichamy --- vendor/google_riscv-dv/src/riscv_instr_pkg.sv | 28 ++++++++- ...-the-kernel-stack-frame-claim-atomic.patch | 57 +++++++++++++++++++ 2 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 vendor/patches/google_riscv-dv/0007-make-the-kernel-stack-frame-claim-atomic.patch 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/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 From 200dbeba3c8eb2f9b90cfdda14e8aeb3ec2bbc09 Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 5 Sep 2026 17:46:38 -0700 Subject: [PATCH 08/14] [dv] Restrict single-step test CSR writes The test randomized writes to MSTATUS, MEPC, MCAUSE and MTVAL. Under real stepping the random debug body executes interleaved between every instruction of a live mtvec handler, so a random 'csrw mepc, ' from the body clobbers the return address the handler underneath still needs, and the program mrets into zeroed memory. Trace-proven: csrrw x1, mepc, x15 with x15 = 0, then mret, then a walk from address 0. The defect was masked because the bare dret in the debug ROM kept the debug body degenerate; with the debug frame fix stepping runs through the whole program. Trim add_csr_write to cpuctrlsts (0x7c0) and secureseed (0x7c1), the CSRs the test is actually about, and reduce instr_cnt from 10000 to 2000 to keep simulator runtime manageable now that stepping runs through the whole program; the test still arms hundreds of steps per seed. Signed-off-by: Kulan Palanichamy --- dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml b/dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml index 3c2a447480..450bf739a5 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 From fb6f11bcb201697fa2d8971147cff18e63473742 Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 5 Sep 2026 22:51:20 -0700 Subject: [PATCH 09/14] [dv] Hold debug_req until the hart has halted debug_seq pulsed debug_req for a fixed 75 cycles. A request that wakes the core from WFI needs longer than that to reach the debug ROM: the clock restarts, the instruction after the WFI is refetched into ID, debug entry flushes it and the halt address is fetched. RVFI attaches a captured request to the first instruction entering ID, which is the flushed one, and the debug ROM's first instruction then reports the live debug_req, already low. The cosim never learns of the request and steps Spike past the WFI: riscv_debug_single_step_test seed 12 with real stepping failed with "DUT retired 80000000 but the ISS retired 800035f4". A debug module keeps haltreq asserted until the hart reports halted, which the debug ROM does with its first instructions. Do the same: after the pulse, keep debug_req high until RVFI has reported a retirement that carries the request (debug_mode alone rises at debug entry, before the halt address is fetched, so it is too early). Not seeing one within 5000 cycles is an error. debug_new_seq is left alone: its only user, riscv_assorted_traps_interrupts_debug_test, runs with +no_wfi=1 and pulses for 3000-5000 cycles. Signed-off-by: Kulan Palanichamy --- dv/uvm/core_ibex/tests/core_ibex_seq_lib.sv | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) 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 From 5fc107210e6dda250ace66ed0ebdcaa676dc0f6d Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 5 Sep 2026 13:03:00 -0700 Subject: [PATCH 10/14] [rtl] Do not count a dummy instruction as a step do_single_step_d keys off instr_valid_i alone, and dummy instructions assert it like any other -- they are deliberately indistinguishable in the pipeline. A dummy landing in the window after DRET then satisfies the step: debug is re-entered having retired nothing, dpc is unchanged, and the instruction being stepped never runs. Debug v0.13.2 4.8.1 asks for a halt after a single instruction. Plumb dummy_instr_id down to the controller and exclude dummies from both arming and clearing the request. SingleStepStableAcrossDummy guards the request across a dummy; StepDebugEntryHasArchitecturalProgress requires exactly one architectural retirement before a step-caused debug entry. Signed-off-by: Kulan Palanichamy --- rtl/ibex_controller.sv | 8 ++++++- rtl/ibex_core.sv | 49 ++++++++++++++++++++++++++++++++++++++++++ rtl/ibex_id_stage.sv | 2 ++ 3 files changed, 58 insertions(+), 1 deletion(-) 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), From da5f016193e2e857de0588f2fe34d00b0a88edcf Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 15 Aug 2026 11:30:27 -0700 Subject: [PATCH 11/14] [rtl] Exclude dummy instructions from minstret Dummy instructions have no functional impact on processor state (doc/03_reference/security.rst), so retiring one must not advance the retired-instruction counter. perf_instr_ret_wb_o is qualified by instr_perf_count_id_i, which already excludes ebreak, ecall, illegal, illegal-CSR, fetch error, minstret writes and expanded Zcmp uops -- but has no notion of dummies, because ID cannot tell them apart. Mask them in both WritebackStage branches and on the speculative retire indication that a CSR read of minstret consumes, where dummy_instr_wb_o is available. NoMinstretForDummyInstr and NoSpecMinstretForDummyInstr check both branches. Verified with dummy_instr_minstret_test (5 seeds each) on the opentitan configuration and on a local copy of it with WritebackStage: 0, so both generate branches are covered; no shipped configuration combines SecureIbex with WritebackStage: 0. Signed-off-by: Kulan Palanichamy --- rtl/ibex_wb_stage.sv | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) 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 From 276cf7370b2f0e6b18549d4a8a878eb148bda9c1 Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 5 Sep 2026 13:03:46 -0700 Subject: [PATCH 12/14] [rtl] Hold the Zcmp expander on dummy insertion The dummy mux replaces instr_decompressed only after the compressed decoder has already treated the micro-op as consumed. A dummy inserted mid-expansion therefore advances the Zcmp state machine while the pipeline receives the dummy instead of that uop, dropping it entirely: a cm.push store omitted while sp is still adjusted, a cm.pop load or return omitted, or half of a cm.mvsa01. That is architectural corruption, not a timing perturbation. Qualify id_in_ready_i the way every other consumer of the held instruction in this module already is -- fetch_ready, the skid buffer and the PC increment checker. NoZcmpExpansionAdvanceOnDummy guards it. Signed-off-by: Kulan Palanichamy --- rtl/ibex_if_stage.sv | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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 From 29a6b7d5fac30e1f2d0ccb5746c4cb937a1b42ae Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 5 Sep 2026 13:05:16 -0700 Subject: [PATCH 13/14] [dv] Add a single-step test with dummies armed +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 maximum insertion rate about one time in sixteen. That is fine for spreading coverage and useless for proving a specific interaction: a seed that never arms is not evidence of anything. +force_dummy_instr pins enable=1 and mask=000 so every emitted stream arms at the highest rate, and implies toggle_dummy_instr so the two cannot contradict. riscv_debug_single_step_dummy_test drives dummy instructions against single step and keeps to that: the generated program takes no traps of its own (no ecall, ebreak, wfi, dret, illegal instructions or sub-programs) and only writes cpuctrlsts/secureseed, so every debug entry is a debug request or a completed step. Stepping through live trap handlers is riscv_debug_single_step_test's business. Signed-off-by: Kulan Palanichamy --- .../ibex_directed_instr_lib.sv | 18 +++++++- .../riscv_dv_extension/testlist.yaml | 43 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) 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 450bf739a5..22f609e234 100644 --- a/dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml +++ b/dv/uvm/core_ibex/riscv_dv_extension/testlist.yaml @@ -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 From 339190521fc9bc3efa453aa9ade2a0f94d966e3e Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sun, 6 Sep 2026 09:11:27 -0700 Subject: [PATCH 14/14] [dv] Add dummy-instruction directed tests Nothing in core_ibex checks that dummy instructions are architecturally invisible beyond the register file: the tests that toggle dummy insertion execute no Zcmp and read no counters, and the Zcmp expansion has no random stimulus at all. dummy_instr_minstret_test reads minstret across a straight-line block of known length with dummy insertion disabled, enabled at the highest rate and disabled again, and requires the same delta every time. The cosim compares the read values against the reference model as a second oracle. dummy_instr_zcmp_test runs cm.push/cm.pop with the full register list, cm.mvsa01, cm.mva01s and cm.popret/cm.popretz through a call, once without dummy insertion and then 48 times with it at the highest rate, and checks the frame contents and padding, sp, the popped and moved registers and the returns. A micro-op displaced by a dummy leaves a canary, a stale register, a wrong sp or a fall-through past the return. Cosim mismatches are demoted to non-fatal for this test (+disable_cosim=1) because the core_ibex cosim does not enable Zcmp for the reference model, which therefore traps on every cm.* instruction; the self-checks carry the verdict. Signed-off-by: Kulan Palanichamy --- .../directed_tests/directed_testlist.yaml | 29 +++ .../dummy_instr_minstret_test.S | 66 +++++ .../dummy_instr_test/dummy_instr_zcmp_test.S | 228 ++++++++++++++++++ .../core_ibex/directed_tests/gen_testlist.py | 29 +++ 4 files changed, 352 insertions(+) create mode 100644 dv/uvm/core_ibex/directed_tests/dummy_instr_test/dummy_instr_minstret_test.S create mode 100644 dv/uvm/core_ibex/directed_tests/dummy_instr_test/dummy_instr_zcmp_test.S diff --git a/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml b/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml index 9f5b120731..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 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 c3bde9c97d..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