From 092f860d7f2c577196283f503f444347a582caae Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 5 Sep 2026 14:41:50 -0700 Subject: [PATCH 1/3] [rtl] Reject the reserved cm.mvsa01 encoding Zcmp reserves cm.mvsa01 with r1s' == r2s', since both fields name destinations. The decoder only checked funct2 and expanded it into two writes to the same register. Raise an illegal-instruction exception instead, without starting the expansion, as the decoder already does for the reserved rlists of cm.push and cm.pop. Signed-off-by: Kulan Palanichamy --- rtl/ibex_compressed_decoder.sv | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/rtl/ibex_compressed_decoder.sv b/rtl/ibex_compressed_decoder.sv index eedd228b9c..f2948878b6 100644 --- a/rtl/ibex_compressed_decoder.sv +++ b/rtl/ibex_compressed_decoder.sv @@ -786,10 +786,16 @@ module ibex_compressed_decoder import ibex_pkg::*; #( // No cm.mvsa01 instruction is active yet; start a new one. // Move a0 to register indicated by r1s'. instr_o = cm_mvsa01(.a01(1'b0), .rs(instr_i[9:7])); - // Ensure the second move happens atomically with this one. - gets_expanded = INSTR_EXPANDED_COMMIT; - if (valid_i && id_in_ready_i) begin - cm_state_d = CmMvSecondReg; + if (instr_i[9:7] == instr_i[4:2]) begin + // r1s' == r2s' is reserved --> illegal instruction, not expanded. + illegal_instr_o = 1'b1; + gets_expanded = INSTR_NOT_EXPANDED; + end else begin + // Ensure the second move happens atomically with this one. + gets_expanded = INSTR_EXPANDED_COMMIT; + if (valid_i && id_in_ready_i) begin + cm_state_d = CmMvSecondReg; + end end end CmMvSecondReg: begin From 8d9a9a29476cbd218733006aec0eafe3ed9e27cc Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 5 Sep 2026 14:41:50 -0700 Subject: [PATCH 2/3] [rtl] Keep a reserved Zcmp rlist trap unexpanded cm.push, cm.pop, cm.popret and cm.popretz with rlist 0-3 raise illegal_instr_o but stay marked as expanded. So the RVFI trap row shows the 32-bit uop instead of the fetched 16-bit encoding, and the controller treats the trap as part of an atomic Zcmp sequence, holding off debug requests, single step and triggers around it. mcause and mtval were already right. Mark these paths INSTR_NOT_EXPANDED. IbexIllegalInstrNotExpanded checks that no illegal instruction from the compressed decoder is marked as expanded. Signed-off-by: Kulan Palanichamy --- rtl/ibex_compressed_decoder.sv | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rtl/ibex_compressed_decoder.sv b/rtl/ibex_compressed_decoder.sv index f2948878b6..a06b4229b1 100644 --- a/rtl/ibex_compressed_decoder.sv +++ b/rtl/ibex_compressed_decoder.sv @@ -633,8 +633,9 @@ module ibex_compressed_decoder import ibex_pkg::*; #( // the list. Then work our way down by decrementing `rlist` each cycle. instr_o = cm_push_store_reg(.rlist(cm_rlist_d), .sp_offset(5'd1)); if (cm_rlist_d <= 5'd3) begin - // Reserved --> illegal instruction. + // Reserved --> illegal instruction, not expanded. illegal_instr_o = 1'b1; + gets_expanded = INSTR_NOT_EXPANDED; end else if (cm_rlist_d == 5'd4) begin // Only `ra` has to be stored, which is done in this cycle. Proceed by // decrementing SP. @@ -701,8 +702,9 @@ module ibex_compressed_decoder import ibex_pkg::*; #( // the list. Then work our way down by decrementing `rlist` each cycle. instr_o = cm_pop_load_reg(.rlist(cm_rlist_d), .sp_offset(cm_sp_offset_d)); if (cm_rlist_d <= 5'd3) begin - // Reserved --> illegal instruction. + // Reserved --> illegal instruction, not expanded. illegal_instr_o = 1'b1; + gets_expanded = INSTR_NOT_EXPANDED; end else if (cm_rlist_d == 5'd4) begin // Only `ra` has to be loaded, which is done in this cycle. Proceed by // incrementing SP. @@ -941,5 +943,8 @@ module ibex_compressed_decoder import ibex_pkg::*; #( `ASSERT(IbexC2Known1, (valid_i && (instr_i[1:0] == 2'b10)) |-> !$isunknown(instr_i[15:13])) `ASSERT(IbexPushPopFSMStable, !valid_i |-> cm_state_d == cm_state_q) + // An illegal instruction is never expanded. + `ASSERT(IbexIllegalInstrNotExpanded, (valid_i && illegal_instr_o) |-> + (gets_expanded == INSTR_NOT_EXPANDED)) endmodule From 8982fc763f6ee015d80813072d64a8883b95ea45 Mon Sep 17 00:00:00 2001 From: Kulan Palanichamy Date: Sat, 5 Sep 2026 13:00:47 -0700 Subject: [PATCH 3/3] [dv] Add zcmp_reserved_test Nothing in core_ibex runs a reserved Zcmp encoding: riscv-dv cannot generate Zcmp and the vendored suites predate it. zcmp_reserved_test runs the eight cm.mvsa01 encodings with equal registers, the two reserved funct2 values of cm.mv*, and cm.push, cm.pop, cm.popretz and cm.popret with rlist 0-3. Each must trap once with mcause 2 and mtval equal to the encoding, and must not touch the registers or stack words a real expansion would. Legal neighbours run as controls. The test runs with +disable_cosim=1 (mismatches are not fatal) because the cosim does not enable Zcmp in Spike, which traps on every cm.* instruction. The self-checks decide the result. Signed-off-by: Kulan Palanichamy --- .../directed_tests/directed_testlist.yaml | 13 ++ .../core_ibex/directed_tests/gen_testlist.py | 13 ++ .../zcmp_reserved_test/zcmp_reserved_test.S | 217 ++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 dv/uvm/core_ibex/directed_tests/zcmp_reserved_test/zcmp_reserved_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..597d48b64d 100644 --- a/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml +++ b/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml @@ -77,6 +77,19 @@ test_srcs: mcounteren_test/mcounteren_lock_test.S config: riscv-tests +- test: zcmp_reserved_test + desc: > + Reserved Zcmp encodings trap as illegal instructions and are not + expanded. Cosim mismatches are not fatal because the cosim does not + enable Zcmp. + iterations: 1 + test_srcs: zcmp_reserved_test/zcmp_reserved_test.S + config: riscv-tests + rtl_params: + PMPEnable: 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/gen_testlist.py b/dv/uvm/core_ibex/directed_tests/gen_testlist.py index f13665606a..a328b1fcd8 100644 --- a/dv/uvm/core_ibex/directed_tests/gen_testlist.py +++ b/dv/uvm/core_ibex/directed_tests/gen_testlist.py @@ -97,6 +97,19 @@ def add_configs_and_handwritten_directed_tests(): test_srcs: mcounteren_test/mcounteren_lock_test.S config: riscv-tests +- test: zcmp_reserved_test + desc: > + Reserved Zcmp encodings trap as illegal instructions and are not + expanded. Cosim mismatches are not fatal because the cosim does not + enable Zcmp. + iterations: 1 + test_srcs: zcmp_reserved_test/zcmp_reserved_test.S + config: riscv-tests + rtl_params: + PMPEnable: 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/zcmp_reserved_test/zcmp_reserved_test.S b/dv/uvm/core_ibex/directed_tests/zcmp_reserved_test/zcmp_reserved_test.S new file mode 100644 index 0000000000..aacd961d19 --- /dev/null +++ b/dv/uvm/core_ibex/directed_tests/zcmp_reserved_test/zcmp_reserved_test.S @@ -0,0 +1,217 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# Reserved Zcmp encodings must raise an illegal-instruction exception: +# - cm.mvsa01 with r1s' == r2s'; +# - the cm.mv* group with funct2 = 00 or 10; +# - cm.push/cm.pop/cm.popretz/cm.popret with rlist 0..3. +# Each must trap once with mcause = 2 and mtval = the encoding, and must not +# change the registers or stack words a real expansion would touch. Legal +# neighbours run as controls. +# +# The encodings are raw halfwords because the pinned toolchain has no Zcmp. +# Cosim mismatches are not fatal: the cosim does not enable Zcmp. + +#include "riscv_test.h" +#include "test_macros.h" + +# Register use: +# gp (TESTNUM) index of the check in progress, for the failure trace +# s10 number of illegal-instruction traps taken +# s11 mtval the trap handler expects on the next trap +# t3, t4 scratch for the checks +# t5, t6 scratch inside the trap handler + +# cm.mvsa01 with r1s' == r2s' == \sreg. The encoding must trap once and must not +# write \sreg (the unfixed decoder expanded it into two writes to \sreg). +.macro CHECK_RESERVED_MVSA01 idx, encoding, sreg + li gp, \idx + li s11, \encoding + li a0, 0xa0a0a0a0 + li a1, 0xa1a1a1a1 + li \sreg, 0xc0ffee00 + \idx + mv t3, s10 + .2byte \encoding + addi t3, t3, 1 + bne t3, s10, fail + li t4, 0xc0ffee00 + \idx + bne t4, \sreg, fail +.endm + +# cm.mv* with a reserved funct2. The encoding must trap once and must not +# write any of the registers the two defined moves use. +.macro CHECK_RESERVED_MV idx, encoding + li gp, \idx + li s11, \encoding + li a0, 0xa0a0a0a0 + li a1, 0xa1a1a1a1 + li s0, 0x50000000 + \idx + li s1, 0x51000000 + \idx + mv t3, s10 + .2byte \encoding + addi t3, t3, 1 + bne t3, s10, fail + li t4, 0xa0a0a0a0 + bne t4, a0, fail + li t4, 0xa1a1a1a1 + bne t4, a1, fail + li t4, 0x50000000 + \idx + bne t4, s0, fail + li t4, 0x51000000 + \idx + bne t4, s1, fail +.endm + +# cm.push/cm.pop/cm.popretz/cm.popret with a reserved rlist. The encoding must +# trap once and must not adjust sp, store below sp, or write ra or a0 (the +# registers a real pop/popret/popretz of rlist 4 would load or clear). +.macro CHECK_RESERVED_RLIST idx, encoding + li gp, \idx + li s11, \encoding + la sp, stack_top + li ra, 0x1a000000 + \idx + li a0, 0xa0000000 + \idx + li t4, 0xdeadbeef + sw t4, -4(sp) + sw t4, -8(sp) + sw t4, -12(sp) + sw t4, -16(sp) + mv t3, s10 + .2byte \encoding + addi t3, t3, 1 + bne t3, s10, fail + la t4, stack_top + bne t4, sp, fail + li t4, 0x1a000000 + \idx + bne t4, ra, fail + li t4, 0xa0000000 + \idx + bne t4, a0, fail + li t3, 0xdeadbeef + lw t4, -4(sp) + bne t4, t3, fail + lw t4, -8(sp) + bne t4, t3, fail + lw t4, -12(sp) + bne t4, t3, fail + lw t4, -16(sp) + bne t4, t3, fail +.endm + +RVTEST_RV32M +RVTEST_CODE_BEGIN + + li s10, 0 + li s11, 0 + + # Reserved cm.mvsa01: 101 011 r1s' 01 r2s' 10 with r1s' == r2s' (s0..s7). + CHECK_RESERVED_MVSA01 1, 0xac22, s0 + CHECK_RESERVED_MVSA01 2, 0xaca6, s1 + CHECK_RESERVED_MVSA01 3, 0xad2a, s2 + CHECK_RESERVED_MVSA01 4, 0xadae, s3 + CHECK_RESERVED_MVSA01 5, 0xae32, s4 + CHECK_RESERVED_MVSA01 6, 0xaeb6, s5 + CHECK_RESERVED_MVSA01 7, 0xaf3a, s6 + CHECK_RESERVED_MVSA01 8, 0xafbe, s7 + + # Reserved funct2 of the cm.mv* group: 101 011 000 ff 001 10 with ff = 00, 10. + CHECK_RESERVED_MV 9, 0xac06 + CHECK_RESERVED_MV 10, 0xac46 + + # Reserved register lists: 101 11 fam 0 rlist spimm 10 with rlist 0..3 + # (fam = 00 push, 01 pop, 10 popretz, 11 popret), spimm = 0. + CHECK_RESERVED_RLIST 11, 0xb802 + CHECK_RESERVED_RLIST 12, 0xb812 + CHECK_RESERVED_RLIST 13, 0xb822 + CHECK_RESERVED_RLIST 14, 0xb832 + CHECK_RESERVED_RLIST 15, 0xba02 + CHECK_RESERVED_RLIST 16, 0xba12 + CHECK_RESERVED_RLIST 17, 0xba22 + CHECK_RESERVED_RLIST 18, 0xba32 + CHECK_RESERVED_RLIST 19, 0xbc02 + CHECK_RESERVED_RLIST 20, 0xbc12 + CHECK_RESERVED_RLIST 21, 0xbc22 + CHECK_RESERVED_RLIST 22, 0xbc32 + CHECK_RESERVED_RLIST 23, 0xbe02 + CHECK_RESERVED_RLIST 24, 0xbe12 + CHECK_RESERVED_RLIST 25, 0xbe22 + CHECK_RESERVED_RLIST 26, 0xbe32 + + # Controls: the legal neighbours must execute without trapping. + # cm.mvsa01 s0, s1: s0 = a0, s1 = a1. + li gp, 27 + li s11, 0 + li a0, 0xa0a0a0a0 + li a1, 0xa1a1a1a1 + li s0, 0 + li s1, 0 + mv t3, s10 + .2byte 0xac26 # cm.mvsa01 s0, s1 + bne t3, s10, fail + bne s0, a0, fail + bne s1, a1, fail + + # cm.mva01s s2, s3: a0 = s2, a1 = s3. + li gp, 28 + li s2, 0x52525252 + li s3, 0x53535353 + li a0, 0 + li a1, 0 + mv t3, s10 + .2byte 0xad6e # cm.mva01s s2, s3 + bne t3, s10, fail + bne a0, s2, fail + bne a1, s3, fail + + # cm.push {ra}, -16 followed by cm.pop {ra}, 16: ra round-trips through the + # stack and sp returns to where it started. + li gp, 29 + la sp, stack_top + li ra, 0x5a5a5a5a + mv t3, s10 + .2byte 0xb842 + la t4, stack_top + addi t4, t4, -16 + bne t4, sp, fail + li ra, 0 + .2byte 0xba42 + bne t3, s10, fail + la t4, stack_top + bne t4, sp, fail + li t4, 0x5a5a5a5a + bne t4, ra, fail + + # 26 reserved encodings, 26 traps. + li gp, 30 + li t3, 26 + bne t3, s10, fail + + j pass + + TEST_PASSFAIL + + # Every trap must be an illegal-instruction exception carrying the fetched + # 16-bit encoding in mtval. Skip the halfword and resume. + .balign 4 + .global mtvec_handler +mtvec_handler: + csrr t5, mcause + li t6, CAUSE_ILLEGAL_INSTRUCTION + bne t5, t6, fail + csrr t5, mtval + bne t5, s11, fail + addi s10, s10, 1 + csrr t5, mepc + addi t5, t5, 2 + csrw mepc, t5 + mret + +RVTEST_CODE_END + + .data +RVTEST_DATA_BEGIN + TEST_DATA + .balign 16 +stack_bot: + .fill 4, 4, 0 +stack_top: +RVTEST_DATA_END