From caf5d182152e83703145bb0147779aa8d6489986 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Fri, 5 Jun 2026 11:09:12 +0200 Subject: [PATCH 01/13] [rtl] Fix speculative increment of `minstret` The `wb_count_q` signal can become stale. It tracks whether the current or previous retired instruction increments the `instret` count. If there is a bubble before an `minstret` read, the counter will be updated, but `wb_count_q` and therefore `perf_instr_ret_wb_spec_o` will incorrectly suggest speculatively incrementing the counter further. This led to the `instret` counter being one instruction ahead every time we read it after a stall cycle. We fix this by validating `wb_count_q` and only incrementing speculatively if we actually have to. That is, when we are executing instructions back-to-back and the counter hasn't updated yet. --- rtl/ibex_wb_stage.sv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtl/ibex_wb_stage.sv b/rtl/ibex_wb_stage.sv index 38ce421ca0..5c8075de4e 100644 --- a/rtl/ibex_wb_stage.sv +++ b/rtl/ibex_wb_stage.sv @@ -155,7 +155,7 @@ module ibex_wb_stage #( // 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; + assign perf_instr_ret_wb_spec_o = wb_count_q & wb_valid_q; 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 & ~(lsu_resp_valid_i & lsu_resp_err_i); From a3156a1abd2d5bea183d27f460e9061aca4f9b9e Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Fri, 5 Jun 2026 16:04:56 +0200 Subject: [PATCH 02/13] [rtl] Fix incrementing `minstret` after setting it According to the Specification Section 6.1 CSR Instructions: Some CSRs, such as the instructions-retired counter, instret, may be modified as side effects of instruction execution. In these cases, if a CSR access instruction reads a CSR, it reads the value prior to the execution of the instruction. If a CSR access instruction writes such a CSR, the explicit write is done instead of the update from the side effect. In particular, a value written to instret by one instruction will be the value read by the following instruction. --- rtl/ibex_id_stage.sv | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/rtl/ibex_id_stage.sv b/rtl/ibex_id_stage.sv index 81fe19253f..a7c27f7683 100644 --- a/rtl/ibex_id_stage.sv +++ b/rtl/ibex_id_stage.sv @@ -1076,9 +1076,15 @@ module ibex_id_stage #( end // Signal which instructions to count as retired in minstret, all traps along with ebrk and - // ecall instructions are not counted. + // ecall instructions are not counted. Writes to minstret/minstreth themselves are also excluded + // to avoid incrementing right after a write. + logic minstret_write; + assign minstret_write = csr_access_o & + (csr_op_o inside {CSR_OP_WRITE, CSR_OP_SET, CSR_OP_CLEAR}) & + (csr_addr_o inside {CSR_MINSTRET, CSR_MINSTRETH}); + assign instr_perf_count_id_o = ~ebrk_insn & ~ecall_insn_dec & ~illegal_insn_dec & - ~illegal_csr_insn_i & ~instr_fetch_err_i; + ~illegal_csr_insn_i & ~instr_fetch_err_i & ~minstret_write; // An instruction is ready to move to the writeback stage (or retire if there is no writeback // stage) From 3be372e87c2b9f5feec7a78265d921794709604c Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Fri, 24 Apr 2026 14:37:48 +0200 Subject: [PATCH 03/13] [rtl] Add register for `mcounteren` --- rtl/ibex_cs_registers.sv | 68 +++++++++++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/rtl/ibex_cs_registers.sv b/rtl/ibex_cs_registers.sv index 11c181b8ce..babbc0e182 100644 --- a/rtl/ibex_cs_registers.sv +++ b/rtl/ibex_cs_registers.sv @@ -148,6 +148,8 @@ module ibex_cs_registers import ibex_pkg::*; #( localparam int unsigned RV32BExtra = (RV32B != RV32BNone) ? 1 : 0; localparam int unsigned RV32MEnabled = (RV32M == RV32MNone) ? 0 : 1; localparam int unsigned PMPAddrWidth = (PMPGranularity > 0) ? PMP_ADDR_MSB - PMPGranularity : 32; + // Base index of the first HPM counter (0=cycle, 1=time, 2=instret) + localparam int unsigned MHPMCOUNTER_BASE = 3; // misa localparam logic [31:0] MISA_VALUE = @@ -253,10 +255,15 @@ module ibex_cs_registers import ibex_pkg::*; #( pmp_mseccfg_t pmp_mseccfg; // Hardware performance monitor signals - logic [31:0] mcountinhibit; + logic [31:0] mcountinhibit; // Only have mcountinhibit flops for counters that actually exist - logic [MHPMCounterNum+3-1:0] mcountinhibit_d, mcountinhibit_q; - logic mcountinhibit_we; + logic [MHPMCounterNum+MHPMCOUNTER_BASE-1:0] mcountinhibit_d, mcountinhibit_q; + logic mcountinhibit_we; + + // mcounteren: machine counter enable (controls U-mode counter access) + logic [31:0] mcounteren; + logic [MHPMCounterNum+MHPMCOUNTER_BASE-1:0] mcounteren_d, mcounteren_q; + logic mcounteren_we; // mhpmcounter flops are elaborated below providing only the precise number that is required based // on MHPMCounterNum/MHPMCounterWidth. This signal connects to the Q output of these flops @@ -374,9 +381,7 @@ module ibex_cs_registers import ibex_pkg::*; #( end // mcounteren: machine counter enable - CSR_MCOUNTEREN: begin - csr_rdata_int = '0; - end + CSR_MCOUNTEREN: csr_rdata_int = mcounteren; CSR_MSCRATCH: csr_rdata_int = mscratch_q; @@ -598,6 +603,7 @@ module ibex_cs_registers import ibex_pkg::*; #( mstack_cause_d = mcause_q; mcountinhibit_we = 1'b0; + mcounteren_we = 1'b0; mhpmcounter_we = '0; mhpmcounterh_we = '0; @@ -675,6 +681,7 @@ module ibex_cs_registers import ibex_pkg::*; #( CSR_DSCRATCH1: dscratch1_en = 1'b1; // machine counter/timers + CSR_MCOUNTEREN: mcounteren_we = 1'b1; CSR_MCOUNTINHIBIT: mcountinhibit_we = 1'b1; CSR_MCYCLE, @@ -1271,12 +1278,23 @@ module ibex_cs_registers import ibex_pkg::*; #( always_comb begin : mcountinhibit_update if (mcountinhibit_we == 1'b1) begin // bit 1 must always be 0 - mcountinhibit_d = {csr_wdata_int[MHPMCounterNum+2:2], 1'b0, csr_wdata_int[0]}; + mcountinhibit_d = csr_wdata_int[MHPMCounterNum+MHPMCOUNTER_BASE-1:0]; + mcountinhibit_d[1] = 1'b0; end else begin mcountinhibit_d = mcountinhibit_q; end end + always_comb begin : mcounteren_update + if (mcounteren_we == 1'b1) begin + // bit 1 must always be 0 (no time CSR implemented) + mcounteren_d = csr_wdata_int[MHPMCounterNum+MHPMCOUNTER_BASE-1:0]; + mcounteren_d[1] = 1'b0; + end else begin + mcounteren_d = mcounteren_q; + end + end + // event selection (hardwired) & control always_comb begin : gen_mhpmcounter_incr @@ -1312,14 +1330,15 @@ module ibex_cs_registers import ibex_pkg::*; #( for (int i = 0; i < 32; i++) begin : gen_mhpmevent_active mhpmevent[i] = '0; - if (i >= 3) begin - mhpmevent[i][i - 3] = 1'b1; + if (i >= MHPMCOUNTER_BASE) begin + mhpmevent[i][i - MHPMCOUNTER_BASE] = 1'b1; end end // deactivate mhpmevent[1] = '0; // not existing, reserved - for (int unsigned i = 3 + MHPMCounterNum; i < 32; i++) begin : gen_mhpmevent_inactive + for (int unsigned i = MHPMCOUNTER_BASE + MHPMCounterNum; i < 32; i++) + begin : gen_mhpmevent_inactive mhpmevent[i] = '0; end end @@ -1370,8 +1389,8 @@ module ibex_cs_registers import ibex_pkg::*; #( assign unused_mhpmcounter_incr_1 = mhpmcounter_incr[1]; // Iterate through optionally included counters (MHPMCounterNum controls how many are included) - for (genvar i = 0; i < 29; i++) begin : gen_cntrs - localparam int Cnt = i + 3; + for (genvar i = 0; i < 32 - MHPMCOUNTER_BASE; i++) begin : gen_cntrs + localparam int Cnt = i + MHPMCOUNTER_BASE; if (i < MHPMCounterNum) begin : gen_imp logic [63:0] mhpmcounter_raw, mhpmcounter_next; @@ -1419,9 +1438,9 @@ module ibex_cs_registers import ibex_pkg::*; #( assign mcountinhibit = {{29 - MHPMCounterNum{1'b0}}, mcountinhibit_q}; // Lint tieoffs for unused bits - assign unused_mhphcounter_we = mhpmcounter_we[31:MHPMCounterNum+3]; - assign unused_mhphcounterh_we = mhpmcounterh_we[31:MHPMCounterNum+3]; - assign unused_mhphcounter_incr = mhpmcounter_incr[31:MHPMCounterNum+3]; + assign unused_mhphcounter_we = mhpmcounter_we[31:MHPMCounterNum+MHPMCOUNTER_BASE]; + assign unused_mhphcounterh_we = mhpmcounterh_we[31:MHPMCounterNum+MHPMCOUNTER_BASE]; + assign unused_mhphcounter_incr = mhpmcounter_incr[31:MHPMCounterNum+MHPMCOUNTER_BASE]; end else begin : g_mcountinhibit_full assign mcountinhibit = mcountinhibit_q; end @@ -1434,6 +1453,25 @@ module ibex_cs_registers import ibex_pkg::*; #( end end + if (MHPMCounterNum < 29) begin : g_mcounteren_reduced + assign mcounteren = {{29 - MHPMCounterNum{1'b0}}, mcounteren_q}; + end else begin : g_mcounteren_full + assign mcounteren = mcounteren_q; + end + + ibex_csr #( + .Width (MHPMCounterNum+MHPMCOUNTER_BASE), + .ShadowCopy(1'b0), + .ResetValue('0) + ) u_mcounteren_csr ( + .clk_i (clk_i), + .rst_ni (rst_ni), + .wr_data_i (mcounteren_d), + .wr_en_i (mcounteren_we), + .rd_data_o (mcounteren_q), + .rd_error_o() + ); + ///////////////////////////// // Debug trigger registers // ///////////////////////////// From e761c275cc5721e7a27d57ff09fd2f38279426ec Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Fri, 24 Apr 2026 16:49:16 +0200 Subject: [PATCH 04/13] [rtl] Add unpriviledged counter/timers --- rtl/ibex_cs_registers.sv | 29 ++++++++++++++++++ rtl/ibex_pkg.sv | 63 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/rtl/ibex_cs_registers.sv b/rtl/ibex_cs_registers.sv index babbc0e182..dad8629656 100644 --- a/rtl/ibex_cs_registers.sv +++ b/rtl/ibex_cs_registers.sv @@ -509,6 +509,35 @@ module ibex_cs_registers import ibex_pkg::*; #( csr_rdata_int = mhpmcounter[mhpmcounter_idx][63:32]; end + // Unprivileged Counter/Timers (readable from U-mode subject to mcounteren) + CSR_CYCLE, + CSR_INSTRET, + CSR_HPMCOUNTER3, + CSR_HPMCOUNTER4, CSR_HPMCOUNTER5, CSR_HPMCOUNTER6, CSR_HPMCOUNTER7, + CSR_HPMCOUNTER8, CSR_HPMCOUNTER9, CSR_HPMCOUNTER10, CSR_HPMCOUNTER11, + CSR_HPMCOUNTER12, CSR_HPMCOUNTER13, CSR_HPMCOUNTER14, CSR_HPMCOUNTER15, + CSR_HPMCOUNTER16, CSR_HPMCOUNTER17, CSR_HPMCOUNTER18, CSR_HPMCOUNTER19, + CSR_HPMCOUNTER20, CSR_HPMCOUNTER21, CSR_HPMCOUNTER22, CSR_HPMCOUNTER23, + CSR_HPMCOUNTER24, CSR_HPMCOUNTER25, CSR_HPMCOUNTER26, CSR_HPMCOUNTER27, + CSR_HPMCOUNTER28, CSR_HPMCOUNTER29, CSR_HPMCOUNTER30, CSR_HPMCOUNTER31: begin + csr_rdata_int = mhpmcounter[mhpmcounter_idx][31:0]; + illegal_csr = (priv_lvl_q == PRIV_LVL_U) && !mcounteren[mhpmcounter_idx]; + end + + CSR_CYCLEH, + CSR_INSTRETH, + CSR_HPMCOUNTER3H, + CSR_HPMCOUNTER4H, CSR_HPMCOUNTER5H, CSR_HPMCOUNTER6H, CSR_HPMCOUNTER7H, + CSR_HPMCOUNTER8H, CSR_HPMCOUNTER9H, CSR_HPMCOUNTER10H, CSR_HPMCOUNTER11H, + CSR_HPMCOUNTER12H, CSR_HPMCOUNTER13H, CSR_HPMCOUNTER14H, CSR_HPMCOUNTER15H, + CSR_HPMCOUNTER16H, CSR_HPMCOUNTER17H, CSR_HPMCOUNTER18H, CSR_HPMCOUNTER19H, + CSR_HPMCOUNTER20H, CSR_HPMCOUNTER21H, CSR_HPMCOUNTER22H, CSR_HPMCOUNTER23H, + CSR_HPMCOUNTER24H, CSR_HPMCOUNTER25H, CSR_HPMCOUNTER26H, CSR_HPMCOUNTER27H, + CSR_HPMCOUNTER28H, CSR_HPMCOUNTER29H, CSR_HPMCOUNTER30H, CSR_HPMCOUNTER31H: begin + csr_rdata_int = mhpmcounter[mhpmcounter_idx][63:32]; + illegal_csr = (priv_lvl_q == PRIV_LVL_U) && !mcounteren[mhpmcounter_idx]; + end + // Debug triggers CSR_TSELECT: begin csr_rdata_int = tselect_rdata; diff --git a/rtl/ibex_pkg.sv b/rtl/ibex_pkg.sv index fbe63b91cf..45b406444c 100644 --- a/rtl/ibex_pkg.sv +++ b/rtl/ibex_pkg.sv @@ -608,6 +608,69 @@ package ibex_pkg; CSR_MHPMCOUNTER29H = 12'hB9D, CSR_MHPMCOUNTER30H = 12'hB9E, CSR_MHPMCOUNTER31H = 12'hB9F, + // Unprivileged Counter/Timers (readable from U-mode subject to mcounteren) + CSR_CYCLE = 12'hC00, + CSR_INSTRET = 12'hC02, + CSR_HPMCOUNTER3 = 12'hC03, + CSR_HPMCOUNTER4 = 12'hC04, + CSR_HPMCOUNTER5 = 12'hC05, + CSR_HPMCOUNTER6 = 12'hC06, + CSR_HPMCOUNTER7 = 12'hC07, + CSR_HPMCOUNTER8 = 12'hC08, + CSR_HPMCOUNTER9 = 12'hC09, + CSR_HPMCOUNTER10 = 12'hC0A, + CSR_HPMCOUNTER11 = 12'hC0B, + CSR_HPMCOUNTER12 = 12'hC0C, + CSR_HPMCOUNTER13 = 12'hC0D, + CSR_HPMCOUNTER14 = 12'hC0E, + CSR_HPMCOUNTER15 = 12'hC0F, + CSR_HPMCOUNTER16 = 12'hC10, + CSR_HPMCOUNTER17 = 12'hC11, + CSR_HPMCOUNTER18 = 12'hC12, + CSR_HPMCOUNTER19 = 12'hC13, + CSR_HPMCOUNTER20 = 12'hC14, + CSR_HPMCOUNTER21 = 12'hC15, + CSR_HPMCOUNTER22 = 12'hC16, + CSR_HPMCOUNTER23 = 12'hC17, + CSR_HPMCOUNTER24 = 12'hC18, + CSR_HPMCOUNTER25 = 12'hC19, + CSR_HPMCOUNTER26 = 12'hC1A, + CSR_HPMCOUNTER27 = 12'hC1B, + CSR_HPMCOUNTER28 = 12'hC1C, + CSR_HPMCOUNTER29 = 12'hC1D, + CSR_HPMCOUNTER30 = 12'hC1E, + CSR_HPMCOUNTER31 = 12'hC1F, + CSR_CYCLEH = 12'hC80, + CSR_INSTRETH = 12'hC82, + CSR_HPMCOUNTER3H = 12'hC83, + CSR_HPMCOUNTER4H = 12'hC84, + CSR_HPMCOUNTER5H = 12'hC85, + CSR_HPMCOUNTER6H = 12'hC86, + CSR_HPMCOUNTER7H = 12'hC87, + CSR_HPMCOUNTER8H = 12'hC88, + CSR_HPMCOUNTER9H = 12'hC89, + CSR_HPMCOUNTER10H = 12'hC8A, + CSR_HPMCOUNTER11H = 12'hC8B, + CSR_HPMCOUNTER12H = 12'hC8C, + CSR_HPMCOUNTER13H = 12'hC8D, + CSR_HPMCOUNTER14H = 12'hC8E, + CSR_HPMCOUNTER15H = 12'hC8F, + CSR_HPMCOUNTER16H = 12'hC90, + CSR_HPMCOUNTER17H = 12'hC91, + CSR_HPMCOUNTER18H = 12'hC92, + CSR_HPMCOUNTER19H = 12'hC93, + CSR_HPMCOUNTER20H = 12'hC94, + CSR_HPMCOUNTER21H = 12'hC95, + CSR_HPMCOUNTER22H = 12'hC96, + CSR_HPMCOUNTER23H = 12'hC97, + CSR_HPMCOUNTER24H = 12'hC98, + CSR_HPMCOUNTER25H = 12'hC99, + CSR_HPMCOUNTER26H = 12'hC9A, + CSR_HPMCOUNTER27H = 12'hC9B, + CSR_HPMCOUNTER28H = 12'hC9C, + CSR_HPMCOUNTER29H = 12'hC9D, + CSR_HPMCOUNTER30H = 12'hC9E, + CSR_HPMCOUNTER31H = 12'hC9F, CSR_CPUCTRLSTS = 12'h7C0, CSR_SECURESEED = 12'h7C1 } csr_num_e; From a8412c6d184b0c7ade3026cc804d4be26a7bdf03 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Mon, 27 Apr 2026 16:21:32 +0200 Subject: [PATCH 05/13] [rtl] Add input to lock mcounteren --- dv/formal/check/top.sv | 1 + .../rtl/ibex_riscv_compliance.sv | 1 + dv/uvm/core_ibex/tb/core_ibex_tb_top.sv | 1 + .../simple_system/rtl/ibex_simple_system.sv | 1 + rtl/ibex_core.sv | 2 + rtl/ibex_cs_registers.sv | 3 +- rtl/ibex_lockstep.sv | 40 ++++++++++--------- rtl/ibex_top.sv | 13 ++++++ rtl/ibex_top_tracing.sv | 2 + 9 files changed, 45 insertions(+), 19 deletions(-) diff --git a/dv/formal/check/top.sv b/dv/formal/check/top.sv index adea4b6332..dc5e6c8b6d 100644 --- a/dv/formal/check/top.sv +++ b/dv/formal/check/top.sv @@ -103,6 +103,7 @@ module top import ibex_pkg::*; #( // CPU Control Signals input ibex_mubi_t fetch_enable_i, + input ibex_mubi_t mcounteren_writable_i, output logic core_sleep_o, output logic alert_minor_o, output logic alert_major_internal_o, diff --git a/dv/riscv_compliance/rtl/ibex_riscv_compliance.sv b/dv/riscv_compliance/rtl/ibex_riscv_compliance.sv index ea53592d21..48ab742dc6 100644 --- a/dv/riscv_compliance/rtl/ibex_riscv_compliance.sv +++ b/dv/riscv_compliance/rtl/ibex_riscv_compliance.sv @@ -216,6 +216,7 @@ module ibex_riscv_compliance ( .double_fault_seen_o ( ), .fetch_enable_i (ibex_pkg::IbexMuBiOn ), + .mcounteren_writable_i (ibex_pkg::IbexMuBiOn ), .alert_minor_o ( ), .alert_major_internal_o ( ), .alert_major_bus_o ( ), diff --git a/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv b/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv index a2658d1239..cb1ec5c572 100644 --- a/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv +++ b/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv @@ -169,6 +169,7 @@ module core_ibex_tb_top; .double_fault_seen_o (dut_if.double_fault_seen ), .fetch_enable_i (dut_if.fetch_enable ), + .mcounteren_writable_i (ibex_pkg::IbexMuBiOn ), .alert_minor_o (dut_if.alert_minor ), .alert_major_internal_o (dut_if.alert_major_internal), .alert_major_bus_o (dut_if.alert_major_bus ), diff --git a/examples/simple_system/rtl/ibex_simple_system.sv b/examples/simple_system/rtl/ibex_simple_system.sv index fa2f6016ff..980c7b0317 100644 --- a/examples/simple_system/rtl/ibex_simple_system.sv +++ b/examples/simple_system/rtl/ibex_simple_system.sv @@ -276,6 +276,7 @@ module ibex_simple_system ( .double_fault_seen_o (), .fetch_enable_i (ibex_pkg::IbexMuBiOn), + .mcounteren_writable_i (ibex_pkg::IbexMuBiOn), .alert_minor_o (), .alert_major_internal_o (), .alert_major_bus_o (), diff --git a/rtl/ibex_core.sv b/rtl/ibex_core.sv index 79eef69769..85c623abc4 100644 --- a/rtl/ibex_core.sv +++ b/rtl/ibex_core.sv @@ -168,6 +168,7 @@ module ibex_core import ibex_pkg::*; #( // CPU Control Signals // SEC_CM: FETCH.CTRL.LC_GATED input ibex_mubi_t fetch_enable_i, + input ibex_mubi_t mcounteren_writable_i, output logic alert_minor_o, output logic alert_major_internal_o, output logic alert_major_bus_o, @@ -1135,6 +1136,7 @@ module ibex_core import ibex_pkg::*; #( .icache_enable_o (icache_enable), .csr_shadow_err_o (csr_shadow_err), .ic_scr_key_valid_i (ic_scr_key_valid_i), + .mcounteren_writable_i(mcounteren_writable_i), .csr_save_if_i (csr_save_if), .csr_save_id_i (csr_save_id), diff --git a/rtl/ibex_cs_registers.sv b/rtl/ibex_cs_registers.sv index dad8629656..641db2208b 100644 --- a/rtl/ibex_cs_registers.sv +++ b/rtl/ibex_cs_registers.sv @@ -98,6 +98,7 @@ module ibex_cs_registers import ibex_pkg::*; #( output logic icache_enable_o, output logic csr_shadow_err_o, input logic ic_scr_key_valid_i, + input ibex_mubi_t mcounteren_writable_i, // Exception save/restore input logic csr_save_if_i, @@ -710,7 +711,7 @@ module ibex_cs_registers import ibex_pkg::*; #( CSR_DSCRATCH1: dscratch1_en = 1'b1; // machine counter/timers - CSR_MCOUNTEREN: mcounteren_we = 1'b1; + CSR_MCOUNTEREN: mcounteren_we = mcounteren_writable_i == IbexMuBiOn; CSR_MCOUNTINHIBIT: mcountinhibit_we = 1'b1; CSR_MCYCLE, diff --git a/rtl/ibex_lockstep.sv b/rtl/ibex_lockstep.sv index f83f3061f2..b0fb5ed7b7 100644 --- a/rtl/ibex_lockstep.sv +++ b/rtl/ibex_lockstep.sv @@ -104,6 +104,7 @@ module ibex_lockstep import ibex_pkg::*; #( input logic double_fault_seen_i, input ibex_mubi_t fetch_enable_i, + input ibex_mubi_t mcounteren_writable_i, output logic alert_minor_o, output logic alert_major_internal_o, output logic alert_major_bus_o, @@ -247,6 +248,7 @@ module ibex_lockstep import ibex_pkg::*; #( logic irq_nm; logic debug_req; ibex_mubi_t fetch_enable; + ibex_mubi_t mcounteren_writable; logic ic_scr_key_valid; } delayed_inputs_t; @@ -310,24 +312,25 @@ module ibex_lockstep import ibex_pkg::*; #( end // Assign the inputs to the delay structure - assign shadow_inputs_in.instr_gnt = instr_gnt_i; - assign shadow_inputs_in.instr_rvalid = instr_rvalid_i; - assign shadow_inputs_in.instr_rdata = instr_rdata_i; - assign shadow_inputs_in.instr_err = instr_err_i; - assign shadow_inputs_in.data_gnt = data_gnt_i; - assign shadow_inputs_in.data_rvalid = data_rvalid_i; - assign shadow_inputs_in.data_rdata = data_rdata_i; - assign shadow_inputs_in.data_err = data_err_i; - assign shadow_inputs_in.rf_rdata_a = rf_rdata_a_i; - assign shadow_inputs_in.rf_rdata_b = rf_rdata_b_i; - assign shadow_inputs_in.irq_software = irq_software_i; - assign shadow_inputs_in.irq_timer = irq_timer_i; - assign shadow_inputs_in.irq_external = irq_external_i; - assign shadow_inputs_in.irq_fast = irq_fast_i; - assign shadow_inputs_in.irq_nm = irq_nm_i; - assign shadow_inputs_in.debug_req = debug_req_i; - assign shadow_inputs_in.fetch_enable = fetch_enable_i; - assign shadow_inputs_in.ic_scr_key_valid = ic_scr_key_valid_i; + assign shadow_inputs_in.instr_gnt = instr_gnt_i; + assign shadow_inputs_in.instr_rvalid = instr_rvalid_i; + assign shadow_inputs_in.instr_rdata = instr_rdata_i; + assign shadow_inputs_in.instr_err = instr_err_i; + assign shadow_inputs_in.data_gnt = data_gnt_i; + assign shadow_inputs_in.data_rvalid = data_rvalid_i; + assign shadow_inputs_in.data_rdata = data_rdata_i; + assign shadow_inputs_in.data_err = data_err_i; + assign shadow_inputs_in.rf_rdata_a = rf_rdata_a_i; + assign shadow_inputs_in.rf_rdata_b = rf_rdata_b_i; + assign shadow_inputs_in.irq_software = irq_software_i; + assign shadow_inputs_in.irq_timer = irq_timer_i; + assign shadow_inputs_in.irq_external = irq_external_i; + assign shadow_inputs_in.irq_fast = irq_fast_i; + assign shadow_inputs_in.irq_nm = irq_nm_i; + assign shadow_inputs_in.debug_req = debug_req_i; + assign shadow_inputs_in.fetch_enable = fetch_enable_i; + assign shadow_inputs_in.mcounteren_writable = mcounteren_writable_i; + assign shadow_inputs_in.ic_scr_key_valid = ic_scr_key_valid_i; /////////////////// // Output delays // @@ -554,6 +557,7 @@ module ibex_lockstep import ibex_pkg::*; #( `endif .fetch_enable_i (shadow_inputs_q[0].fetch_enable), + .mcounteren_writable_i (shadow_inputs_q[0].mcounteren_writable), .alert_minor_o (shadow_alert_minor), .alert_major_internal_o (shadow_alert_major_internal), .alert_major_bus_o (shadow_alert_major_bus), diff --git a/rtl/ibex_top.sv b/rtl/ibex_top.sv index 1a0712c4fb..0f6419a89d 100644 --- a/rtl/ibex_top.sv +++ b/rtl/ibex_top.sv @@ -160,6 +160,7 @@ module ibex_top import ibex_pkg::*; #( // CPU Control Signals input ibex_mubi_t fetch_enable_i, + input ibex_mubi_t mcounteren_writable_i, output logic alert_minor_o, output logic alert_major_internal_o, output logic alert_major_bus_o, @@ -244,6 +245,7 @@ module ibex_top import ibex_pkg::*; #( logic scramble_req_d, scramble_req_q; ibex_mubi_t fetch_enable_buf; + ibex_mubi_t mcounteren_writable_buf; ///////////////////// // Main clock gate // @@ -296,6 +298,11 @@ module ibex_top import ibex_pkg::*; #( .out_o(fetch_enable_buf) ); + prim_buf #(.Width($bits(ibex_mubi_t))) u_mcounteren_writable_buf ( + .in_i (mcounteren_writable_i), + .out_o(mcounteren_writable_buf) + ); + // ibex_core takes integrity and data bits together. Combine the separate integrity and data // inputs here. assign data_rdata_core[31:0] = data_rdata_i; @@ -449,6 +456,7 @@ module ibex_top import ibex_pkg::*; #( `endif .fetch_enable_i (fetch_enable_buf), + .mcounteren_writable_i (mcounteren_writable_buf), .alert_minor_o (core_alert_minor), .alert_major_internal_o(core_alert_major_internal), .alert_major_bus_o (core_alert_major_bus), @@ -829,6 +837,7 @@ module ibex_top import ibex_pkg::*; #( crash_dump_o, double_fault_seen_o, fetch_enable_i, + mcounteren_writable_i, core_busy_d }); @@ -879,6 +888,7 @@ module ibex_top import ibex_pkg::*; #( crash_dump_t crash_dump_local; logic double_fault_seen_local; ibex_mubi_t fetch_enable_local; + ibex_mubi_t mcounteren_writable_local; ibex_mubi_t core_busy_local; @@ -922,6 +932,7 @@ module ibex_top import ibex_pkg::*; #( crash_dump_o, double_fault_seen_o, fetch_enable_i, + mcounteren_writable_i, core_busy_d }; @@ -965,6 +976,7 @@ module ibex_top import ibex_pkg::*; #( crash_dump_local, double_fault_seen_local, fetch_enable_local, + mcounteren_writable_local, core_busy_local } = buf_out; @@ -1083,6 +1095,7 @@ module ibex_top import ibex_pkg::*; #( .double_fault_seen_i (double_fault_seen_local), .fetch_enable_i (fetch_enable_local), + .mcounteren_writable_i (mcounteren_writable_local), .alert_minor_o (lockstep_alert_minor_local), .alert_major_internal_o (lockstep_alert_major_internal_local), .alert_major_bus_o (lockstep_alert_major_bus_local), diff --git a/rtl/ibex_top_tracing.sv b/rtl/ibex_top_tracing.sv index a5f37d1d61..3823dd219e 100644 --- a/rtl/ibex_top_tracing.sv +++ b/rtl/ibex_top_tracing.sv @@ -96,6 +96,7 @@ module ibex_top_tracing import ibex_pkg::*; #( // CPU Control Signals input ibex_mubi_t fetch_enable_i, + input ibex_mubi_t mcounteren_writable_i, output logic alert_minor_o, output logic alert_major_internal_o, output logic alert_major_bus_o, @@ -313,6 +314,7 @@ module ibex_top_tracing import ibex_pkg::*; #( .rvfi_ext_expanded_insn_last, .fetch_enable_i, + .mcounteren_writable_i, .alert_minor_o, .alert_major_internal_o, .alert_major_bus_o, From d8ceb5ec68c93b4098d219484222691c66ae864f Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Wed, 3 Jun 2026 12:08:38 +0200 Subject: [PATCH 06/13] [ci] Update `riscv-isa-sim` version --- ci/vars.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/vars.env b/ci/vars.env index 2b5b7b251d..2ad18d0a71 100644 --- a/ci/vars.env +++ b/ci/vars.env @@ -6,7 +6,7 @@ # Quote values to ensure they are parsed as string (version numbers might # end up as float otherwise). VERILATOR_VERSION=v4.210 -IBEX_COSIM_VERSION=6d5b660 +IBEX_COSIM_VERSION=aadf648 RISCV_TOOLCHAIN_TAR_VERSION=20220210-1 RISCV_TOOLCHAIN_TAR_VARIANT=lowrisc-toolchain-gcc-rv32imcb RISCV_COMPLIANCE_GIT_VERSION=844c6660ef3f0d9b96957991109dfd80cc4938e2 From 635efe21e86519da09c1796ef14cfd3411801711 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Mon, 27 Apr 2026 16:22:20 +0200 Subject: [PATCH 07/13] [dv] Implement Ibex-specific mcounteren behavior in cosim --- dv/cosim/spike_cosim.cc | 18 +++++++++++++++++- dv/cosim/spike_cosim.h | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/dv/cosim/spike_cosim.cc b/dv/cosim/spike_cosim.cc index daa400709b..32537875ed 100644 --- a/dv/cosim/spike_cosim.cc +++ b/dv/cosim/spike_cosim.cc @@ -39,7 +39,10 @@ SpikeCosim::SpikeCosim(const std::string &isa_string, uint32_t start_pc, uint32_t pmp_num_regions, uint32_t pmp_granularity, uint32_t mhpm_counter_num, uint32_t dm_start_addr, uint32_t dm_end_addr) - : nmi_mode(false), pending_iside_error(false), insn_cnt(0) { + : nmi_mode(false), + pending_iside_error(false), + insn_cnt(0), + mhpm_counter_num(mhpm_counter_num) { FILE *log_file = nullptr; if (trace_log_path.length() != 0) { log = std::make_unique(trace_log_path.c_str()); @@ -832,6 +835,19 @@ void SpikeCosim::fixup_csr(int csr_num, uint32_t csr_val) { processor->set_csr(csr_num, new_val); #else processor->put_csr(csr_num, new_val); +#endif + break; + } + case CSR_MCOUNTEREN: { + // Bits 3..3+mhpm_counter_num-1 correspond to implemented HPM counters + reg_t hpm_mask = ((1 << mhpm_counter_num) - 1) << 3; + // Bit 0 and 2 are for mcycle and minstret which are always implemented + // Bit 1 is for time which is not implemented, hence the mask 0x5 + reg_t new_val = csr_val & (0x5 | hpm_mask); +#ifdef OLD_SPIKE + processor->set_csr(csr_num, new_val); +#else + processor->put_csr(csr_num, new_val); #endif break; } diff --git a/dv/cosim/spike_cosim.h b/dv/cosim/spike_cosim.h index 68fd2204fb..2849206e0c 100644 --- a/dv/cosim/spike_cosim.h +++ b/dv/cosim/spike_cosim.h @@ -98,6 +98,7 @@ class SpikeCosim : public simif_t, public Cosim { void misaligned_pmp_fixup(); unsigned int insn_cnt; + uint32_t mhpm_counter_num; public: SpikeCosim(const std::string &isa_string, uint32_t start_pc, From abd0c51c10497af0c49102ab3d26449c16288472 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Thu, 28 May 2026 16:24:56 +0200 Subject: [PATCH 08/13] [dv] Fix ordering in `directed_testlist.yml` The set created a random order of the tests. So small modifications or additions to the testlist polluted the diff. Fixing the order in a list makes the output deterministic. I swapped the order of the tests to match the current output order of the testlist to avoid a diff. --- dv/uvm/core_ibex/directed_tests/gen_testlist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dv/uvm/core_ibex/directed_tests/gen_testlist.py b/dv/uvm/core_ibex/directed_tests/gen_testlist.py index c922608140..b094e7c40c 100644 --- a/dv/uvm/core_ibex/directed_tests/gen_testlist.py +++ b/dv/uvm/core_ibex/directed_tests/gen_testlist.py @@ -469,11 +469,11 @@ def _main() -> int: add_configs_and_handwritten_directed_tests() if 'riscv-tests' in test_suite_list or test_suite == 'all': - isa_tests = {'rv32mi', 'rv32uc', 'rv32ui', 'rv32um'} + isa_tests = ['rv32mi', 'rv32uc', 'rv32um', 'rv32ui'] append_directed_testlist(isa_tests, '../../../../vendor/riscv-tests/isa/', 'riscv-tests', 1) if 'riscv-arch-tests' in test_suite_list or test_suite == 'all': - arch_tests = {'rv32i_m/B/src', 'rv32i_m/C/src', 'rv32i_m/I/src', 'rv32i_m/M/src', 'rv32i_m/Zifencei/src'} + arch_tests = ['rv32i_m/M/src', 'rv32i_m/C/src', 'rv32i_m/Zifencei/src', 'rv32i_m/I/src', 'rv32i_m/B/src'] append_directed_testlist(arch_tests, '../../../../vendor/riscv-arch-tests/riscv-test-suite/', 'riscv-arch-tests', 1) if 'epmp-tests' in test_suite_list or test_suite == 'all': From f8180f8a8c86b7979a112b75b4a90d78e9be0ab0 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Thu, 28 May 2026 16:32:30 +0200 Subject: [PATCH 09/13] [dv] Add `mcounteren` directed test --- .../directed_tests/directed_testlist.yaml | 8 + .../core_ibex/directed_tests/gen_testlist.py | 8 + .../mcounteren_test/mcounteren_test.S | 224 ++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 dv/uvm/core_ibex/directed_tests/mcounteren_test/mcounteren_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 2517f54a7e..9c581994f3 100644 --- a/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml +++ b/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml @@ -60,6 +60,14 @@ test_srcs: empty/empty.S config: riscv-tests +- test: mcounteren_test + desc: > + Tests the mcounteren CSR: reset value, hardwired-zero bit 1 (time), + and U-mode counter access gating. + iterations: 1 + test_srcs: mcounteren_test/mcounteren_test.S + config: riscv-tests + - 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 b094e7c40c..d01c9da2ac 100644 --- a/dv/uvm/core_ibex/directed_tests/gen_testlist.py +++ b/dv/uvm/core_ibex/directed_tests/gen_testlist.py @@ -80,6 +80,14 @@ def add_configs_and_handwritten_directed_tests(): test_srcs: empty/empty.S config: riscv-tests +- test: mcounteren_test + desc: > + Tests the mcounteren CSR: reset value, hardwired-zero bit 1 (time), + and U-mode counter access gating. + iterations: 1 + test_srcs: mcounteren_test/mcounteren_test.S + config: riscv-tests + - test: pmp_mseccfg_test_rlb1_l0_0_u0 desc: > mseccfg test diff --git a/dv/uvm/core_ibex/directed_tests/mcounteren_test/mcounteren_test.S b/dv/uvm/core_ibex/directed_tests/mcounteren_test/mcounteren_test.S new file mode 100644 index 0000000000..388576f83a --- /dev/null +++ b/dv/uvm/core_ibex/directed_tests/mcounteren_test/mcounteren_test.S @@ -0,0 +1,224 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# This test verifies mcounteren CSR functionality by sweeping multiple bit +# patterns to ensure User-mode access to all 64 performance counters is +# correctly gated. It confirms that allowed registers read successfully, +# while disabled registers securely trigger illegal instruction exceptions. + +#include "custom_macros.h" +#include "riscv_test.h" +#include "test_macros.h" + +# Register Allocation: +# s1: Expected instruction execution state(1 = should succeed, 0 = should trap) +# s2: Readback snapshot of valid mcounteren bits +# s3: Subroutine return address backup tracking register(ra) +# s4: Label pointing to the next macro boundary +# s5: Subroutine continuation target pointer used upon full block completion + +.macro RUN_CSR_TEST idx, csr_addr + # Check if this counter is configured to be accessible + # Get position at which to check mcounteren bit for this counter + li t0, \csr_addr + andi t0, t0, 0x1F + # Create mask from position + li t1, 1 + sll t1, t1, t0 + and t1, s2, t1 + # s1 = 1 if counter is enabled and should succeed cleanly + snez s1, t1 + + # Set next test label for the handler + la s4, next_test_\idx + + # Read counter.This should trap if s1 == 0 and succeed if s1 == 1 + csrr t0, \csr_addr + # If the counter was supposed to trap but didn't, fail immediately + beqz s1, fail + # Continue to next test +next_test_\idx: +.endm + +.macro RUN_ALL_CSR_TESTS + # Lower 32 Bits of Performance Counters + RUN_CSR_TEST 0, 0xC00 # cycle + RUN_CSR_TEST 1, 0xC01 # time + RUN_CSR_TEST 2, 0xC02 # instret + RUN_CSR_TEST 3, 0xC03 # hpmcounter3 + RUN_CSR_TEST 4, 0xC04 + RUN_CSR_TEST 5, 0xC05 + RUN_CSR_TEST 6, 0xC06 + RUN_CSR_TEST 7, 0xC07 + RUN_CSR_TEST 8, 0xC08 + RUN_CSR_TEST 9, 0xC09 + RUN_CSR_TEST 10, 0xC0A + RUN_CSR_TEST 11, 0xC0B + RUN_CSR_TEST 12, 0xC0C + RUN_CSR_TEST 13, 0xC0D + RUN_CSR_TEST 14, 0xC0E + RUN_CSR_TEST 15, 0xC0F + RUN_CSR_TEST 16, 0xC10 + RUN_CSR_TEST 17, 0xC11 + RUN_CSR_TEST 18, 0xC12 + RUN_CSR_TEST 19, 0xC13 + RUN_CSR_TEST 20, 0xC14 + RUN_CSR_TEST 21, 0xC15 + RUN_CSR_TEST 22, 0xC16 + RUN_CSR_TEST 23, 0xC17 + RUN_CSR_TEST 24, 0xC18 + RUN_CSR_TEST 25, 0xC19 + RUN_CSR_TEST 26, 0xC1A + RUN_CSR_TEST 27, 0xC1B + RUN_CSR_TEST 28, 0xC1C + RUN_CSR_TEST 29, 0xC1D + RUN_CSR_TEST 30, 0xC1E + RUN_CSR_TEST 31, 0xC1F # hpmcounter31 + # Upper 32 Bits of Performance Counters + RUN_CSR_TEST 32, 0xC80 # cycleh + RUN_CSR_TEST 33, 0xC81 # timeh + RUN_CSR_TEST 34, 0xC82 # instreth + RUN_CSR_TEST 35, 0xC83 # hpmcounter3h + RUN_CSR_TEST 36, 0xC84 + RUN_CSR_TEST 37, 0xC85 + RUN_CSR_TEST 38, 0xC86 + RUN_CSR_TEST 39, 0xC87 + RUN_CSR_TEST 40, 0xC88 + RUN_CSR_TEST 41, 0xC89 + RUN_CSR_TEST 42, 0xC8A + RUN_CSR_TEST 43, 0xC8B + RUN_CSR_TEST 44, 0xC8C + RUN_CSR_TEST 45, 0xC8D + RUN_CSR_TEST 46, 0xC8E + RUN_CSR_TEST 47, 0xC8F + RUN_CSR_TEST 48, 0xC90 + RUN_CSR_TEST 49, 0xC91 + RUN_CSR_TEST 50, 0xC92 + RUN_CSR_TEST 51, 0xC93 + RUN_CSR_TEST 52, 0xC94 + RUN_CSR_TEST 53, 0xC95 + RUN_CSR_TEST 54, 0xC96 + RUN_CSR_TEST 55, 0xC97 + RUN_CSR_TEST 56, 0xC98 + RUN_CSR_TEST 57, 0xC99 + RUN_CSR_TEST 58, 0xC9A + RUN_CSR_TEST 59, 0xC9B + RUN_CSR_TEST 60, 0xC9C + RUN_CSR_TEST 61, 0xC9D + RUN_CSR_TEST 62, 0xC9E + RUN_CSR_TEST 63, 0xC9F # hpmcounter31h +.endm + +# ----------------------------------------------------------------------- +# Main Verification Blocks +# ----------------------------------------------------------------------- + +RVTEST_RV32M +RVTEST_CODE_BEGIN + + la t0, mtvec_handler + csrw mtvec, t0 + + # Verify default power - on reset state + csrr t0, mcounteren + bnez t0, fail + + # Verify that we can write to mcounteren and read back the expected value + li t0, 0x1 + csrw mcounteren, t0 + csrr t1, mcounteren + bne t1, t0, fail + + # Verify time enable bit is hardwired to zero while other bits are sticky + li t0, 0x7 + csrw mcounteren, t0 + csrr t1, mcounteren + xori t1, t1, 0x5 + bnez t1, fail + + # Sweep with all implemented counters enabled + li t0, 0xFFFFFFFF + csrw mcounteren, t0 + csrr s2, mcounteren + jal ra, run_all_csr_tests + + # Sweep with all counters disabled + csrwi mcounteren, 0 + csrr s2, mcounteren + jal ra, run_all_csr_tests + + # Sweep using alternating bits + li t0, 0x55555555 + csrw mcounteren, t0 + csrr s2, mcounteren + jal ra, run_all_csr_tests + + # Sweep using arbitrary custom mask + li t0, 0xCD609E2D + csrw mcounteren, t0 + csrr s2, mcounteren + jal ra, run_all_csr_tests + + # If execution hits this line, all pattern sweeps successfully completed + csrwi mcounteren, 0 + j pass + + TEST_PASSFAIL + +# ----------------------------------------------------------------------- +# Evaluation Subroutine +# ----------------------------------------------------------------------- +run_all_csr_tests: + mv s3, ra + la s5, post_all_tests + # Drop to U-mode once per pattern to execute the continuous instruction stream + SWITCH_TO_U_MODE_LABEL(u_mode_sweep_entry) + +.balign 4 +u_mode_sweep_entry: + RUN_ALL_CSR_TESTS + + # Reaching this line implies all 32 checks passed cleanly + ecall + +post_all_tests: + mv ra, s3 + ret + +# ----------------------------------------------------------------------- +# Exception Trap Handler +# ----------------------------------------------------------------------- +.balign 256 +mtvec_handler: + csrr t0, mcause + + # mcause 8 = Environment Call from U-mode (stream finished successfully) + li t1, 8 + beq t0, t1, handle_ecall + + # mcause 2 = Illegal Instruction + li t1, 2 + # Drop out to failure trap on all unexpected exceptions + bne t0, t1, fail + + # If the register was supposed to be accessible (s1 == 1) but faulted, fail + bnez s1, fail + + # Valid hardware restriction exception. Safely continue to next macro block + csrw mepc, s4 + mret + +handle_ecall: + # All unrolled checks passed. Safely return back to M-mode context runner + csrw mepc, s5 + li t0, 0x1800 + csrs mstatus, t0 + mret + +RVTEST_CODE_END + + .data +RVTEST_DATA_BEGIN + TEST_DATA +RVTEST_DATA_END From 42811c3286c0a5ee5fc62c2c087c265d3846a00e Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Tue, 12 May 2026 17:41:35 +0200 Subject: [PATCH 10/13] [dv] Add directed test for mcounteren lock signal --- .../directed_tests/directed_testlist.yaml | 9 +++ .../core_ibex/directed_tests/gen_testlist.py | 9 +++ .../mcounteren_test/mcounteren_lock_test.S | 62 +++++++++++++++++++ .../core_ibex/env/core_ibex_dut_probe_if.sv | 5 +- dv/uvm/core_ibex/tb/core_ibex_tb_top.sv | 2 +- dv/uvm/core_ibex/tests/core_ibex_test_lib.sv | 41 ++++++++++++ 6 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 dv/uvm/core_ibex/directed_tests/mcounteren_test/mcounteren_lock_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 9c581994f3..49eee3562a 100644 --- a/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml +++ b/dv/uvm/core_ibex/directed_tests/directed_testlist.yaml @@ -68,6 +68,15 @@ test_srcs: mcounteren_test/mcounteren_test.S config: riscv-tests +- test: mcounteren_lock_test + desc: > + Tests that mcounteren retains its value after mcounteren_writable_i is + de-asserted mid-simulation (write-lock). + iterations: 1 + rtl_test: core_ibex_mcounteren_lock_test + test_srcs: mcounteren_test/mcounteren_lock_test.S + config: riscv-tests + - 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 d01c9da2ac..2ae1487121 100644 --- a/dv/uvm/core_ibex/directed_tests/gen_testlist.py +++ b/dv/uvm/core_ibex/directed_tests/gen_testlist.py @@ -88,6 +88,15 @@ def add_configs_and_handwritten_directed_tests(): test_srcs: mcounteren_test/mcounteren_test.S config: riscv-tests +- test: mcounteren_lock_test + desc: > + Tests that mcounteren retains its value after mcounteren_writable_i is + de-asserted mid-simulation (write-lock). + iterations: 1 + rtl_test: core_ibex_mcounteren_lock_test + test_srcs: mcounteren_test/mcounteren_lock_test.S + config: riscv-tests + - test: pmp_mseccfg_test_rlb1_l0_0_u0 desc: > mseccfg test diff --git a/dv/uvm/core_ibex/directed_tests/mcounteren_test/mcounteren_lock_test.S b/dv/uvm/core_ibex/directed_tests/mcounteren_test/mcounteren_lock_test.S new file mode 100644 index 0000000000..30f7fc2511 --- /dev/null +++ b/dv/uvm/core_ibex/directed_tests/mcounteren_test/mcounteren_lock_test.S @@ -0,0 +1,62 @@ +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# This test verifies the mcounteren write-lock mechanism by dynamically setting +# the mcounteren_writable_i hardware input. It validates that register updates +# are silently ignored while locked and succeed when unlocked, using immediate +# software readbacks to confirm the state. Inter-process signaling with the UVM +# testbench is achieved by monitoring writes to mcycle (lock command) and +# mcycleh (unlock command). + +#include "riscv_test.h" +#include "test_macros.h" + +RVTEST_RV32M +RVTEST_CODE_BEGIN + + # Initial Write (Unlocked) + li s0, 0x5 + csrw mcounteren, s0 + csrr t1, mcounteren + li t2, 0x5 + # If readback != 0x5, fail immediately + bne t1, t2, fail + + # Tell UVM to lock by writing to mcycle, which is monitored by the UVM + # testbench to set `mcounteren_writable` + csrw mcycle, x0 + + # Small delay loop to let the hardware pin force propagate + .rept 5 + nop + .endr + + # Try to overwrite mcounteren with 0x0 while locked + li s1, 0x0 + csrw mcounteren, s1 + csrr t1, mcounteren + bne t1, s0, fail + + # Tell UVM to unlock by writing to mcycleh + csrw mcycleh, x0 + + .rept 5 + nop + .endr + + # Try to overwrite it with 0x0 again. This time while unlocked + li s3, 0x0 + csrw mcounteren, s3 + csrr t1, mcounteren + bne t1, s3, fail + + # Success Exit + j pass + TEST_PASSFAIL + +RVTEST_CODE_END + +RVTEST_DATA_BEGIN + TEST_DATA +RVTEST_DATA_END diff --git a/dv/uvm/core_ibex/env/core_ibex_dut_probe_if.sv b/dv/uvm/core_ibex/env/core_ibex_dut_probe_if.sv index 2de5d8fffe..a1e0bc8b17 100644 --- a/dv/uvm/core_ibex/env/core_ibex_dut_probe_if.sv +++ b/dv/uvm/core_ibex/env/core_ibex_dut_probe_if.sv @@ -18,6 +18,7 @@ interface core_ibex_dut_probe_if(input logic clk); logic dret; logic mret; ibex_pkg::ibex_mubi_t fetch_enable; + ibex_pkg::ibex_mubi_t mcounteren_writable; logic core_sleep; logic alert_minor; logic alert_major_internal; @@ -59,6 +60,7 @@ interface core_ibex_dut_probe_if(input logic clk); clocking dut_cb @(posedge clk); output fetch_enable; output debug_req; + output mcounteren_writable; input reset; input illegal_instr; input ecall; @@ -91,7 +93,8 @@ interface core_ibex_dut_probe_if(input logic clk); endclocking initial begin - debug_req = 1'b0; + debug_req = 1'b0; + mcounteren_writable = ibex_pkg::IbexMuBiOn; end `DV_CREATE_SIGNAL_PROBE_FUNCTION(signal_probe_rf_ren_a, rf_ren_a) diff --git a/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv b/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv index cb1ec5c572..270ddac024 100644 --- a/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv +++ b/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv @@ -169,7 +169,7 @@ module core_ibex_tb_top; .double_fault_seen_o (dut_if.double_fault_seen ), .fetch_enable_i (dut_if.fetch_enable ), - .mcounteren_writable_i (ibex_pkg::IbexMuBiOn ), + .mcounteren_writable_i (dut_if.mcounteren_writable ), .alert_minor_o (dut_if.alert_minor ), .alert_major_internal_o (dut_if.alert_major_internal), .alert_major_bus_o (dut_if.alert_major_bus ), diff --git a/dv/uvm/core_ibex/tests/core_ibex_test_lib.sv b/dv/uvm/core_ibex/tests/core_ibex_test_lib.sv index 5794dde7ce..0186201c60 100644 --- a/dv/uvm/core_ibex/tests/core_ibex_test_lib.sv +++ b/dv/uvm/core_ibex/tests/core_ibex_test_lib.sv @@ -2025,3 +2025,44 @@ class core_ibex_assorted_traps_interrupts_debug_test extends core_ibex_directed_ endtask endclass + +class core_ibex_mcounteren_lock_test extends core_ibex_base_test; + `uvm_component_utils(core_ibex_mcounteren_lock_test) + `uvm_component_new + + virtual function void build_phase(uvm_phase phase); + super.build_phase(phase); + // Relaxes co-simulation tracking so mismatches during lock don't abort + cosim_cfg.relax_cosim_check = 1'b1; + endfunction + + virtual task send_stimulus(); + // Fork the binary execution in the background + fork + vseq.start(env.vseqr); + join_none + + // Wait for a write to MCYCLE to indicate locking the mcounteren CSRs + wait_for_live_csr_write(CSR_MCYCLE); + dut_vif.dut_cb.mcounteren_writable <= ibex_pkg::IbexMuBiOff; + `uvm_info(`gfn, "Write to MCYCLE: locking mcounteren!", UVM_LOW) + + // Wait for a write to MCYCLEH to indicate unlocking the mcounteren CSRs + wait_for_live_csr_write(CSR_MCYCLEH); + dut_vif.dut_cb.mcounteren_writable <= ibex_pkg::IbexMuBiOn; + `uvm_info(`gfn, "Write to MCYCLEH: unlocking mcounteren!", UVM_LOW) + endtask + + // Snoop the CSR interface to catch writes to the specified CSR addresses + task wait_for_live_csr_write(bit [11:0] addr); + forever begin + @(csr_vif.csr_cb); + if (csr_vif.csr_cb.csr_access === 1'b1 && + csr_vif.csr_cb.csr_addr === addr && + csr_vif.csr_cb.csr_op != CSR_OP_READ) begin + break; + end + end + endtask + +endclass From 230cefc73b76268a00d46a5694ad829a96e51606 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Wed, 13 May 2026 11:41:39 +0200 Subject: [PATCH 11/13] [doc] Document the newly added performance counters --- doc/01_overview/compliance.rst | 4 ++++ doc/03_reference/performance_counters.rst | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/doc/01_overview/compliance.rst b/doc/01_overview/compliance.rst index f9436be88d..4a34db1879 100644 --- a/doc/01_overview/compliance.rst +++ b/doc/01_overview/compliance.rst @@ -43,6 +43,10 @@ In addition, the following instruction set extensions are available. - 2.0 - always enabled + * - **"Zicntr" and "Zihpm"**: Extensions for Counters and Hardware Performance Counters in User mode + - 2.0 + - always enabled + * - **Zifencei**: Instruction-Fetch Fence - 2.0 - always enabled diff --git a/doc/03_reference/performance_counters.rst b/doc/03_reference/performance_counters.rst index 35c347cd62..9225c8a64b 100644 --- a/doc/03_reference/performance_counters.rst +++ b/doc/03_reference/performance_counters.rst @@ -3,10 +3,13 @@ Performance Counters ==================== -Ibex implements performance counters according to the RISC-V Privileged Specification, version 1.11 (see Hardware Performance Monitor, Section 3.1.11). +Ibex implements performance counters according to the RISC-V Privileged Specification, version 1.11 (see Hardware Performance Monitor, Section 3.1.11) and supports the **Zihpm** (Hardware Performance Counters) extension. The performance counters are placed inside the Control and Status Registers (CSRs) and can be accessed with the ``CSRRW(I)`` and ``CSRRS/C(I)`` instructions. -Ibex implements the clock cycle counter ``mcycle(h)``, the retired instruction counter ``minstret(h)``, as well as the 29 event counters ``mhpmcounter3(h)`` - ``mhpmcounter31(h)`` and the corresponding event selector CSRs ``mhpmevent3`` - ``mhpmevent31``, and the ``mcountinhibit`` CSR to individually enable/disable the counters. +Ibex implements the machine-mode clock cycle counter ``mcycle(h)``, the retired instruction counter ``minstret(h)``, as well as the 29 event counters ``mhpmcounter3(h)`` - ``mhpmcounter31(h)`` and the corresponding event selector CSRs ``mhpmevent3`` - ``mhpmevent31``, and the ``mcountinhibit`` CSR to individually enable/disable the counters. + +Additionally, Ibex implements the Zicntr and Zihpm extensions which provide User-mode (U-mode) aliases for these performance counters: ``cycle(h)``, ``instret(h)``, and ``hpmcounter3(h)`` - ``hpmcounter31(h)``. These aliases provide read-only access to the exact same underlying hardware counters configured in M-mode. + ``mcycle(h)`` and ``minstret(h)`` are always available and 64 bit wide. The ``mhpmcounter`` performance counters are optional (unavailable by default) and parametrizable in width. @@ -60,6 +63,19 @@ In particular, to enable/disable ``mcycle(h)``, bit 0 must be written. For ``min The lower 32 bits of all counters can be accessed through the base register, whereas the upper 32 bits are accessed through the ``h``-register. Reads to all these registers are non-destructive. +User-Mode Counter Access (mcounteren) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Access to the U-mode counter aliases (``cycle(h)``, ``instret(h)``, and ``hpmcounterX(h)``) is controlled via the Machine Counter-Enable CSR (``mcounteren``). This register can gate access to the counters from less privileged modes to prevent benchmarking the core if desired. + +* **Bit 0** controls access to ``cycle(h)``. +* **Bit 2** controls access to ``instret(h)``. +* **Bit X** controls access to ``hpmcounterX(h)``. + +When a bit in ``mcounteren`` is clear (0), any attempt to read the corresponding counter alias from U-mode will trigger an illegal instruction exception. + +To secure this mechanism, the ``mcounteren`` register can be locked against software modifications using a MUBI input signal called ``mcounteren_writeable``. When this signal disables writes, any attempt by software to modify the contents of ``mcounteren`` is ignored. + Parametrization at synthesis time --------------------------------- From fb642e3cf7d181c9625956ef0ae309d9515d0ea2 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Wed, 13 May 2026 11:48:46 +0200 Subject: [PATCH 12/13] [formal] Exclude u-mode counters from formal verification --- dv/formal/check/top.sv | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dv/formal/check/top.sv b/dv/formal/check/top.sv index dc5e6c8b6d..8f41c17f9e 100644 --- a/dv/formal/check/top.sv +++ b/dv/formal/check/top.sv @@ -163,7 +163,9 @@ NotDebug: assume property (!ibex_top_i.u_ibex_core.debug_mode & !debug_req_i); ConstantBoot: assume property (boot_addr_i == $past(boot_addr_i)); // 3. Always fetch enable FetchEnable: assume property (fetch_enable_i == IbexMuBiOn); -// 4. Never try to sleep if we couldn't ever wake up +// 4. Always have mcounteren writable +McounterenWritable: assume property (mcounteren_writable_i == IbexMuBiOn); +// 5. Never try to sleep if we couldn't ever wake up WFIStart: assume property (`IDC.ctrl_fsm_cs == SLEEP |-> ( `CSR.mie_q.irq_software | `CSR.mie_q.irq_timer | @@ -442,18 +444,25 @@ logic ex_is_checkable_csr; assign ex_is_checkable_csr = ~( ((CSR_MHPMCOUNTER3H <= `CSR_ADDR) && (`CSR_ADDR <= CSR_MHPMCOUNTER31H)) | ((CSR_MHPMCOUNTER3 <= `CSR_ADDR) && (`CSR_ADDR <= CSR_MHPMCOUNTER31)) | + ((CSR_HPMCOUNTER3H <= `CSR_ADDR) && (`CSR_ADDR <= CSR_HPMCOUNTER31H)) | + ((CSR_HPMCOUNTER3 <= `CSR_ADDR) && (`CSR_ADDR <= CSR_HPMCOUNTER31)) | ((CSR_MHPMEVENT3 <= `CSR_ADDR) && (`CSR_ADDR <= CSR_MHPMEVENT31)) | (`CSR_ADDR == CSR_CPUCTRLSTS) | (`CSR_ADDR == CSR_SECURESEED) | (`CSR_ADDR == CSR_MIE) | (`CSR_ADDR == CSR_MCYCLE) | (`CSR_ADDR == CSR_MCYCLEH) | + (`CSR_ADDR == CSR_CYCLE) | (`CSR_ADDR == CSR_CYCLEH) | // TODO: (`CSR_ADDR == CSR_MINSTRET) | (`CSR_ADDR == CSR_MINSTRETH) | + (`CSR_ADDR == CSR_INSTRET) | (`CSR_ADDR == CSR_INSTRETH) | (`CSR_ADDR == CSR_MCOUNTINHIBIT) ); `undef INSTR +// Force mcounteren to always be zero to match the current Sail model. +McounterenStubbedZero: assume property (`CSR.mcounteren_q == 32'h0); + ////////////////////// Decompression Invariant Defs ////////////////////// // These will be used to show that the decompressed instruction stored is in fact the decompressed version of the compressed instruction. From 3e4615ed362684754612608f6cff398082f44ca8 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Tue, 26 May 2026 14:04:46 +0200 Subject: [PATCH 13/13] [dv] Check u-mode counter alias the correct m-mode counter --- .../mcounteren_test/mcounteren_test.S | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/dv/uvm/core_ibex/directed_tests/mcounteren_test/mcounteren_test.S b/dv/uvm/core_ibex/directed_tests/mcounteren_test/mcounteren_test.S index 388576f83a..373349fa0b 100644 --- a/dv/uvm/core_ibex/directed_tests/mcounteren_test/mcounteren_test.S +++ b/dv/uvm/core_ibex/directed_tests/mcounteren_test/mcounteren_test.S @@ -12,6 +12,7 @@ #include "test_macros.h" # Register Allocation: +# s0: mcounteren WARL readback used by check_counter_aliases # s1: Expected instruction execution state(1 = should succeed, 0 = should trap) # s2: Readback snapshot of valid mcounteren bits # s3: Subroutine return address backup tracking register(ra) @@ -110,6 +111,37 @@ next_test_\idx: RUN_CSR_TEST 63, 0xC9F # hpmcounter31h .endm +# Freeze one HPM counter, write it, drop to U-mode to read the user-mode alias, +# then verify the values match. +# s0 is the mcounteren readback on entry +# s1 carries the M-mode read. +# s2 carries the U-mode read. +.macro CHECK_HPM_ALIAS_ONE idx, mhpmcsr, hpmcsr + li t0, (1 << \idx) + and t1, t0, s0 + beqz t1, skip_hpm_alias_\hpmcsr + + csrs mcountinhibit, t0 + li t0, (0x12345678 ^ \idx) + csrw \mhpmcsr, t0 + csrr s1, \mhpmcsr + + la s5, after_hpm_alias_\hpmcsr + SWITCH_TO_U_MODE_LABEL(hpm_alias_u_\hpmcsr) + +.balign 4 +hpm_alias_u_\hpmcsr: + csrr s2, \hpmcsr + ecall + +after_hpm_alias_\hpmcsr: + bne s2, s1, fail + li t0, (1 << \idx) + csrc mcountinhibit, t0 + +skip_hpm_alias_\hpmcsr: +.endm + # ----------------------------------------------------------------------- # Main Verification Blocks # ----------------------------------------------------------------------- @@ -160,6 +192,12 @@ RVTEST_CODE_BEGIN csrr s2, mcounteren jal ra, run_all_csr_tests + # Re-enable all counters and verify U-mode CSRs alias their M-mode counterparts. + li t0, 0xFFFFFFFF + csrw mcounteren, t0 + csrr s0, mcounteren + jal ra, check_counter_aliases + # If execution hits this line, all pattern sweeps successfully completed csrwi mcounteren, 0 j pass @@ -186,6 +224,97 @@ post_all_tests: mv ra, s3 ret +# ----------------------------------------------------------------------- +# Counter Alias Verification Subroutine +# ----------------------------------------------------------------------- +# Verifies that the user-mode counter CSRs alias their M-mode counterparts. +# cycle is checked by the cosim. +check_counter_aliases: + mv s3, ra + + # Custom minstret test + # instret should be exactly 2 more than minstret. + csrr t0, minstret + nop + csrr t1, instret + addi t2, t0, 2 + bne t1, t2, fail + + # Reset minstret to zero. The write should suppress the automatic retirement + # increment, so the immediate read-back will be 0. Reading instret next counts + # the one csr read. + csrwi minstret, 0 + csrr t0, minstret + csrr t1, instret + bnez t0, fail + li t2, 1 + bne t1, t2, fail + + # HPM alias: iterate over each HPM counter (bits 3-31) that mcounteren + # reports as implemented. Each counter is frozen with mcountinhibit, set to a + # value through the m-mode counter, and read back via the U-mode alias CSR. + CHECK_HPM_ALIAS_ONE 3, mhpmcounter3, hpmcounter3 + CHECK_HPM_ALIAS_ONE 4, mhpmcounter4, hpmcounter4 + CHECK_HPM_ALIAS_ONE 5, mhpmcounter5, hpmcounter5 + CHECK_HPM_ALIAS_ONE 6, mhpmcounter6, hpmcounter6 + CHECK_HPM_ALIAS_ONE 7, mhpmcounter7, hpmcounter7 + CHECK_HPM_ALIAS_ONE 8, mhpmcounter8, hpmcounter8 + CHECK_HPM_ALIAS_ONE 9, mhpmcounter9, hpmcounter9 + CHECK_HPM_ALIAS_ONE 10, mhpmcounter10, hpmcounter10 + CHECK_HPM_ALIAS_ONE 11, mhpmcounter11, hpmcounter11 + CHECK_HPM_ALIAS_ONE 12, mhpmcounter12, hpmcounter12 + CHECK_HPM_ALIAS_ONE 13, mhpmcounter13, hpmcounter13 + CHECK_HPM_ALIAS_ONE 14, mhpmcounter14, hpmcounter14 + CHECK_HPM_ALIAS_ONE 15, mhpmcounter15, hpmcounter15 + CHECK_HPM_ALIAS_ONE 16, mhpmcounter16, hpmcounter16 + CHECK_HPM_ALIAS_ONE 17, mhpmcounter17, hpmcounter17 + CHECK_HPM_ALIAS_ONE 18, mhpmcounter18, hpmcounter18 + CHECK_HPM_ALIAS_ONE 19, mhpmcounter19, hpmcounter19 + CHECK_HPM_ALIAS_ONE 20, mhpmcounter20, hpmcounter20 + CHECK_HPM_ALIAS_ONE 21, mhpmcounter21, hpmcounter21 + CHECK_HPM_ALIAS_ONE 22, mhpmcounter22, hpmcounter22 + CHECK_HPM_ALIAS_ONE 23, mhpmcounter23, hpmcounter23 + CHECK_HPM_ALIAS_ONE 24, mhpmcounter24, hpmcounter24 + CHECK_HPM_ALIAS_ONE 25, mhpmcounter25, hpmcounter25 + CHECK_HPM_ALIAS_ONE 26, mhpmcounter26, hpmcounter26 + CHECK_HPM_ALIAS_ONE 27, mhpmcounter27, hpmcounter27 + CHECK_HPM_ALIAS_ONE 28, mhpmcounter28, hpmcounter28 + CHECK_HPM_ALIAS_ONE 29, mhpmcounter29, hpmcounter29 + CHECK_HPM_ALIAS_ONE 30, mhpmcounter30, hpmcounter30 + CHECK_HPM_ALIAS_ONE 31, mhpmcounter31, hpmcounter31 + CHECK_HPM_ALIAS_ONE 3, mhpmcounter3h, hpmcounter3h + CHECK_HPM_ALIAS_ONE 4, mhpmcounter4h, hpmcounter4h + CHECK_HPM_ALIAS_ONE 5, mhpmcounter5h, hpmcounter5h + CHECK_HPM_ALIAS_ONE 6, mhpmcounter6h, hpmcounter6h + CHECK_HPM_ALIAS_ONE 7, mhpmcounter7h, hpmcounter7h + CHECK_HPM_ALIAS_ONE 8, mhpmcounter8h, hpmcounter8h + CHECK_HPM_ALIAS_ONE 9, mhpmcounter9h, hpmcounter9h + CHECK_HPM_ALIAS_ONE 10, mhpmcounter10h, hpmcounter10h + CHECK_HPM_ALIAS_ONE 11, mhpmcounter11h, hpmcounter11h + CHECK_HPM_ALIAS_ONE 12, mhpmcounter12h, hpmcounter12h + CHECK_HPM_ALIAS_ONE 13, mhpmcounter13h, hpmcounter13h + CHECK_HPM_ALIAS_ONE 14, mhpmcounter14h, hpmcounter14h + CHECK_HPM_ALIAS_ONE 15, mhpmcounter15h, hpmcounter15h + CHECK_HPM_ALIAS_ONE 16, mhpmcounter16h, hpmcounter16h + CHECK_HPM_ALIAS_ONE 17, mhpmcounter17h, hpmcounter17h + CHECK_HPM_ALIAS_ONE 18, mhpmcounter18h, hpmcounter18h + CHECK_HPM_ALIAS_ONE 19, mhpmcounter19h, hpmcounter19h + CHECK_HPM_ALIAS_ONE 20, mhpmcounter20h, hpmcounter20h + CHECK_HPM_ALIAS_ONE 21, mhpmcounter21h, hpmcounter21h + CHECK_HPM_ALIAS_ONE 22, mhpmcounter22h, hpmcounter22h + CHECK_HPM_ALIAS_ONE 23, mhpmcounter23h, hpmcounter23h + CHECK_HPM_ALIAS_ONE 24, mhpmcounter24h, hpmcounter24h + CHECK_HPM_ALIAS_ONE 25, mhpmcounter25h, hpmcounter25h + CHECK_HPM_ALIAS_ONE 26, mhpmcounter26h, hpmcounter26h + CHECK_HPM_ALIAS_ONE 27, mhpmcounter27h, hpmcounter27h + CHECK_HPM_ALIAS_ONE 28, mhpmcounter28h, hpmcounter28h + CHECK_HPM_ALIAS_ONE 29, mhpmcounter29h, hpmcounter29h + CHECK_HPM_ALIAS_ONE 30, mhpmcounter30h, hpmcounter30h + CHECK_HPM_ALIAS_ONE 31, mhpmcounter31h, hpmcounter31h + + mv ra, s3 + ret + # ----------------------------------------------------------------------- # Exception Trap Handler # -----------------------------------------------------------------------