From 6355f79294283177affac9e4f3c5a0c892561fcd Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Fri, 24 Apr 2026 14:37:48 +0200 Subject: [PATCH 01/12] [rtl] Add register for `mcounteren` --- rtl/ibex_cs_registers.sv | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/rtl/ibex_cs_registers.sv b/rtl/ibex_cs_registers.sv index 11c181b8c..564c801eb 100644 --- a/rtl/ibex_cs_registers.sv +++ b/rtl/ibex_cs_registers.sv @@ -258,6 +258,11 @@ module ibex_cs_registers import ibex_pkg::*; #( logic [MHPMCounterNum+3-1:0] mcountinhibit_d, mcountinhibit_q; logic mcountinhibit_we; + // mcounteren: machine counter enable (controls U-mode counter access) + logic [31:0] mcounteren; + logic [MHPMCounterNum+3-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 // where they exist and is otherwise 0. @@ -374,9 +379,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 +601,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 +679,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, @@ -1277,6 +1282,15 @@ module ibex_cs_registers import ibex_pkg::*; #( 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+2:2], 1'b0, csr_wdata_int[0]}; + end else begin + mcounteren_d = mcounteren_q; + end + end + // event selection (hardwired) & control always_comb begin : gen_mhpmcounter_incr @@ -1434,6 +1448,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+3), + .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 72eaebf931a48c878707d9224b10f3e50cd2b27b Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Fri, 24 Apr 2026 16:49:16 +0200 Subject: [PATCH 02/12] [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 564c801eb..b279b607e 100644 --- a/rtl/ibex_cs_registers.sv +++ b/rtl/ibex_cs_registers.sv @@ -507,6 +507,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 fbe63b91c..45b406444 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 216c77cb1d7db0d4a4f78c8ebd8a25a7e4bc550f Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Mon, 27 Apr 2026 16:21:32 +0200 Subject: [PATCH 03/12] [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 adea4b633..dc5e6c8b6 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 ea53592d2..48ab742dc 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 a2658d123..cb1ec5c57 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 1f5e08602..58ef1296e 100644 --- a/examples/simple_system/rtl/ibex_simple_system.sv +++ b/examples/simple_system/rtl/ibex_simple_system.sv @@ -273,6 +273,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 79eef6976..85c623abc 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 b279b607e..961909360 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, @@ -708,7 +709,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 f83f3061f..b0fb5ed7b 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 1a0712c4f..0f6419a89 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 a5f37d1d6..3823dd219 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 bacacea2d65fed1a918590e3aade987097a007ff Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Wed, 3 Jun 2026 12:08:38 +0200 Subject: [PATCH 04/12] [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 2b5b7b251..2ad18d0a7 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 497d1ec9ebc40e33f64b4256425a019a43d7fd27 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Mon, 27 Apr 2026 16:22:20 +0200 Subject: [PATCH 05/12] [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 daa400709..32537875e 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 68fd2204f..2849206e0 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 ed45547dea606f857e1837d75c69892ce33636bc Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Thu, 28 May 2026 16:24:56 +0200 Subject: [PATCH 06/12] [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 c92260814..3b24c06f0 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 791beb67953d3be60e1c5bb362faca435e16c4ce Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Thu, 28 May 2026 16:32:30 +0200 Subject: [PATCH 07/12] [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 2517f54a7..9c581994f 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 3b24c06f0..25b9fcf0c 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 000000000..388576f83 --- /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 6c1539ec28456b23f0df3892bbccf7d9c3b3f9e8 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Mon, 11 May 2026 09:58:16 +0200 Subject: [PATCH 08/12] [dv] Correctly set `minstret` in cosim --- dv/cosim/cosim.h | 11 ++++++++++ dv/cosim/cosim_dpi.cc | 7 +++++++ dv/cosim/cosim_dpi.h | 1 + dv/cosim/cosim_dpi.svh | 1 + dv/cosim/spike_cosim.cc | 21 +++++++++++++++++++ dv/cosim/spike_cosim.h | 1 + .../ibex_cosim_agent/ibex_cosim_scoreboard.sv | 1 + .../ibex_cosim_agent/ibex_rvfi_monitor.sv | 1 + .../ibex_cosim_agent/ibex_rvfi_seq_item.sv | 2 ++ dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv | 2 ++ dv/uvm/core_ibex/tb/core_ibex_tb_top.sv | 1 + rtl/ibex_core.sv | 6 ++++++ rtl/ibex_lockstep.sv | 1 + rtl/ibex_top.sv | 2 ++ rtl/ibex_top_tracing.sv | 4 ++++ 15 files changed, 62 insertions(+) diff --git a/dv/cosim/cosim.h b/dv/cosim/cosim.h index 4a5c63c75..305af005d 100644 --- a/dv/cosim/cosim.h +++ b/dv/cosim/cosim.h @@ -128,6 +128,17 @@ class Cosim { // A full 64-bit value is provided setting both the mcycle and mcycleh CSRs. virtual void set_mcycle(uint64_t mcycle) = 0; + // Set the value of minstret. + // + // The co-simulation model doesn't alter the value of minstret itself (other + // than instructions that do a direct CSR write). minstret should be set to + // the correct value before any `step` call that may execute an instruction + // that observes the value of minstret. + // + // A full 64-bit value is provided setting both the minstret and minstreth + // CSRs. + virtual void set_minstret(uint64_t minstret) = 0; + // Set the value of a CSR. This is used when it is needed to have direct // communication between DUT and Spike (e.g. Performance counters). virtual void set_csr(const int csr_num, const uint32_t new_val) = 0; diff --git a/dv/cosim/cosim_dpi.cc b/dv/cosim/cosim_dpi.cc index 30a3da74d..fac12f79b 100644 --- a/dv/cosim/cosim_dpi.cc +++ b/dv/cosim/cosim_dpi.cc @@ -52,6 +52,13 @@ void riscv_cosim_set_mcycle(Cosim *cosim, svBitVecVal *mcycle) { cosim->set_mcycle(mcycle_full); } +void riscv_cosim_set_minstret(Cosim *cosim, svBitVecVal *minstret) { + assert(cosim); + + uint64_t minstret_full = minstret[0] | (uint64_t)minstret[1] << 32; + cosim->set_minstret(minstret_full); +} + void riscv_cosim_set_csr(Cosim *cosim, const int csr_id, const svBitVecVal *csr_val) { assert(cosim); diff --git a/dv/cosim/cosim_dpi.h b/dv/cosim/cosim_dpi.h index bbadbc5e3..f6df9ce27 100644 --- a/dv/cosim/cosim_dpi.h +++ b/dv/cosim/cosim_dpi.h @@ -23,6 +23,7 @@ void riscv_cosim_set_nmi(Cosim *cosim, svBit nmi); void riscv_cosim_set_nmi_int(Cosim *cosim, svBit nmi_int); void riscv_cosim_set_debug_req(Cosim *cosim, svBit debug_req); void riscv_cosim_set_mcycle(Cosim *cosim, svBitVecVal *mcycle); +void riscv_cosim_set_minstret(Cosim *cosim, svBitVecVal *minstret); void riscv_cosim_set_csr(Cosim *cosim, const int csr_id, const svBitVecVal *csr_val); void riscv_cosim_set_ic_scr_key_valid(Cosim *cosim, svBit valid); diff --git a/dv/cosim/cosim_dpi.svh b/dv/cosim/cosim_dpi.svh index 35ecd3b8c..4e8cea072 100644 --- a/dv/cosim/cosim_dpi.svh +++ b/dv/cosim/cosim_dpi.svh @@ -18,6 +18,7 @@ import "DPI-C" function void riscv_cosim_set_nmi(chandle cosim_handle, bit nmi); import "DPI-C" function void riscv_cosim_set_nmi_int(chandle cosim_handle, bit nmi_int); import "DPI-C" function void riscv_cosim_set_debug_req(chandle cosim_handle, bit debug_req); import "DPI-C" function void riscv_cosim_set_mcycle(chandle cosim_handle, bit [63:0] mcycle); +import "DPI-C" function void riscv_cosim_set_minstret(chandle cosim_handle, bit [63:0] minstret); import "DPI-C" function void riscv_cosim_set_csr(chandle cosim_handle, int csr_id, bit [31:0] csr_val); import "DPI-C" function void riscv_cosim_set_ic_scr_key_valid(chandle cosim_handle, bit valid); diff --git a/dv/cosim/spike_cosim.cc b/dv/cosim/spike_cosim.cc index 32537875e..a5d5caa24 100644 --- a/dv/cosim/spike_cosim.cc +++ b/dv/cosim/spike_cosim.cc @@ -750,6 +750,27 @@ void SpikeCosim::set_mcycle(uint64_t mcycle) { // to write all 64 bits at once at least. } +void SpikeCosim::set_minstret(uint64_t minstret) { + uint32_t upper_minstret = minstret >> 32; + uint32_t lower_minstret = minstret & 0xffffffff; + + // Spike decrements the MINSTRET CSR when you write to it. This is the same + // issue as with MCYCLE. See `set_mcycle` for more details. + + // Write the lower half first, incremented twice due to the double decrement + processor->get_state()->csrmap[CSR_MINSTRET]->write(lower_minstret + 2); + + if ((processor->get_state()->csrmap[CSR_MINSTRET]->read() & 0xffffffff) == + 0) { + // If the lower half is 0 at this point then the upper half will get + // decremented, so increment it first. + upper_minstret++; + } + + // Set the upper half + processor->get_state()->csrmap[CSR_MINSTRETH]->write(upper_minstret); +} + void SpikeCosim::set_csr(const int csr_num, const uint32_t new_val) { // Note that this is tested with ibex-cosim-v0.3 version of Spike. 'set_csr' // method might have a hardwired zero for mhpmcounterX registers. diff --git a/dv/cosim/spike_cosim.h b/dv/cosim/spike_cosim.h index 2849206e0..9fd603ee5 100644 --- a/dv/cosim/spike_cosim.h +++ b/dv/cosim/spike_cosim.h @@ -132,6 +132,7 @@ class SpikeCosim : public simif_t, public Cosim { void set_nmi_int(bool nmi_int) override; void set_debug_req(bool debug_req) override; void set_mcycle(uint64_t mcycle) override; + void set_minstret(uint64_t minstret) override; void set_csr(const int csr_num, const uint32_t new_val) override; void set_ic_scr_key_valid(bool valid) override; void notify_dside_access(const DSideAccessInfo &access_info) override; diff --git a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_cosim_scoreboard.sv b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_cosim_scoreboard.sv index f55e1936f..04f056150 100644 --- a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_cosim_scoreboard.sv +++ b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_cosim_scoreboard.sv @@ -151,6 +151,7 @@ class ibex_cosim_scoreboard extends uvm_scoreboard; riscv_cosim_set_nmi_int(cosim_handle, rvfi_instr.nmi_int); riscv_cosim_set_mip(cosim_handle, rvfi_instr.pre_mip, rvfi_instr.post_mip); riscv_cosim_set_mcycle(cosim_handle, rvfi_instr.mcycle); + riscv_cosim_set_minstret(cosim_handle, rvfi_instr.minstret); // Set performance counters through a pseudo-backdoor write for (int i=0; i < 10; i++) begin diff --git a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_monitor.sv b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_monitor.sv index 0bf50116e..d68b5222c 100644 --- a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_monitor.sv +++ b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_monitor.sv @@ -44,6 +44,7 @@ class ibex_rvfi_monitor extends uvm_monitor; trans_collected.debug_req = vif.monitor_cb.ext_debug_req; trans_collected.rf_wr_suppress = vif.monitor_cb.ext_rf_wr_suppress; trans_collected.mcycle = vif.monitor_cb.ext_mcycle; + trans_collected.minstret = vif.monitor_cb.ext_minstret; trans_collected.ic_scr_key_valid = vif.monitor_cb.ext_ic_scr_key_valid; for (int i=0; i < 10; i++) begin diff --git a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_seq_item.sv b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_seq_item.sv index dceba31c4..c8a6ee5fd 100644 --- a/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_seq_item.sv +++ b/dv/uvm/core_ibex/common/ibex_cosim_agent/ibex_rvfi_seq_item.sv @@ -16,6 +16,7 @@ class ibex_rvfi_seq_item extends uvm_sequence_item; bit debug_req; bit rf_wr_suppress; bit [63:0] mcycle; + bit [63:0] minstret; bit [31:0] mhpmcounters [10]; bit [31:0] mhpmcountersh [10]; @@ -34,6 +35,7 @@ class ibex_rvfi_seq_item extends uvm_sequence_item; `uvm_field_int (debug_req, UVM_DEFAULT) `uvm_field_int (rf_wr_suppress, UVM_DEFAULT) `uvm_field_int (mcycle, UVM_DEFAULT) + `uvm_field_int (minstret, UVM_DEFAULT) `uvm_field_sarray_int (mhpmcounters, UVM_DEFAULT) `uvm_field_sarray_int (mhpmcountersh, UVM_DEFAULT) `uvm_field_int (ic_scr_key_valid, UVM_DEFAULT) diff --git a/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv b/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv index 0199b87f7..a3034dbf8 100644 --- a/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv +++ b/dv/uvm/core_ibex/env/core_ibex_rvfi_if.sv @@ -33,6 +33,7 @@ interface core_ibex_rvfi_if(input logic clk); logic [31:0] ext_debug_req; logic [31:0] ext_rf_wr_suppress; logic [63:0] ext_mcycle; + logic [63:0] ext_minstret; logic ext_irq_valid; logic [31:0] ext_mhpmcounters [10]; @@ -70,6 +71,7 @@ interface core_ibex_rvfi_if(input logic clk); input ext_debug_req; input ext_rf_wr_suppress; input ext_mcycle; + input ext_minstret; input ext_mhpmcounters; input ext_mhpmcountersh; input ext_ic_scr_key_valid; 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 cb1ec5c57..b2f2c675d 100644 --- a/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv +++ b/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv @@ -249,6 +249,7 @@ module core_ibex_tb_top; assign rvfi_if.ext_debug_req = dut.rvfi_ext_debug_req; assign rvfi_if.ext_rf_wr_suppress = dut.rvfi_ext_rf_wr_suppress; assign rvfi_if.ext_mcycle = dut.rvfi_ext_mcycle; + assign rvfi_if.ext_minstret = dut.rvfi_ext_minstret; assign rvfi_if.ext_mhpmcounters = dut.rvfi_ext_mhpmcounters; assign rvfi_if.ext_mhpmcountersh = dut.rvfi_ext_mhpmcountersh; assign rvfi_if.ext_ic_scr_key_valid = dut.rvfi_ext_ic_scr_key_valid; diff --git a/rtl/ibex_core.sv b/rtl/ibex_core.sv index 85c623abc..0f0556c86 100644 --- a/rtl/ibex_core.sv +++ b/rtl/ibex_core.sv @@ -156,6 +156,7 @@ module ibex_core import ibex_pkg::*; #( output logic rvfi_ext_debug_mode, output logic rvfi_ext_rf_wr_suppress, output logic [63:0] rvfi_ext_mcycle, + output logic [63:0] rvfi_ext_minstret, output logic [31:0] rvfi_ext_mhpmcounters [10], output logic [31:0] rvfi_ext_mhpmcountersh [10], output logic rvfi_ext_ic_scr_key_valid, @@ -1316,6 +1317,7 @@ module ibex_core import ibex_pkg::*; #( logic rvfi_ext_stage_debug_req [RVFI_STAGES+1]; logic rvfi_ext_stage_debug_mode [RVFI_STAGES]; logic [63:0] rvfi_ext_stage_mcycle [RVFI_STAGES]; + logic [63:0] rvfi_ext_stage_minstret [RVFI_STAGES]; logic [31:0] rvfi_ext_stage_mhpmcounters [RVFI_STAGES][10]; logic [31:0] rvfi_ext_stage_mhpmcountersh [RVFI_STAGES][10]; logic rvfi_ext_stage_ic_scr_key_valid [RVFI_STAGES]; @@ -1384,6 +1386,7 @@ module ibex_core import ibex_pkg::*; #( assign rvfi_ext_debug_req = rvfi_ext_stage_debug_req [RVFI_STAGES]; assign rvfi_ext_debug_mode = rvfi_ext_stage_debug_mode [RVFI_STAGES-1]; assign rvfi_ext_mcycle = rvfi_ext_stage_mcycle [RVFI_STAGES-1]; + assign rvfi_ext_minstret = rvfi_ext_stage_minstret [RVFI_STAGES-1]; assign rvfi_ext_mhpmcounters = rvfi_ext_stage_mhpmcounters [RVFI_STAGES-1]; assign rvfi_ext_mhpmcountersh = rvfi_ext_stage_mhpmcountersh [RVFI_STAGES-1]; assign rvfi_ext_ic_scr_key_valid = rvfi_ext_stage_ic_scr_key_valid [RVFI_STAGES-1]; @@ -1607,6 +1610,7 @@ module ibex_core import ibex_pkg::*; #( rvfi_ext_stage_debug_req[i+1] <= '0; rvfi_ext_stage_debug_mode[i] <= '0; rvfi_ext_stage_mcycle[i] <= '0; + rvfi_ext_stage_minstret[i] <= '0; rvfi_ext_stage_ic_scr_key_valid[i] <= '0; rvfi_ext_stage_expanded_insn_valid[i] <= '0; rvfi_ext_stage_expanded_insn[i] <= '0; @@ -1661,6 +1665,7 @@ module ibex_core import ibex_pkg::*; #( rvfi_stage_mem_addr[i] <= rvfi_mem_addr_d; rvfi_ext_stage_debug_mode[i] <= debug_mode; rvfi_ext_stage_mcycle[i] <= cs_registers_i.mcycle_counter_i.counter_val_o; + rvfi_ext_stage_minstret[i] <= cs_registers_i.mhpmcounter[2]; rvfi_ext_stage_ic_scr_key_valid[i] <= cs_registers_i.cpuctrlsts_ic_scr_key_valid_q; rvfi_ext_stage_expanded_insn_valid[i] <= rvfi_expanded_insn_valid; rvfi_ext_stage_expanded_insn[i] <= rvfi_expanded_insn; @@ -1731,6 +1736,7 @@ module ibex_core import ibex_pkg::*; #( rvfi_ext_stage_debug_mode[i] <= rvfi_ext_stage_debug_mode[i-1]; rvfi_ext_stage_mcycle[i] <= rvfi_ext_stage_mcycle[i-1]; + rvfi_ext_stage_minstret[i] <= rvfi_ext_stage_minstret[i-1]; rvfi_ext_stage_ic_scr_key_valid[i] <= rvfi_ext_stage_ic_scr_key_valid[i-1]; rvfi_ext_stage_mhpmcounters[i] <= rvfi_ext_stage_mhpmcounters[i-1]; rvfi_ext_stage_mhpmcountersh[i] <= rvfi_ext_stage_mhpmcountersh[i-1]; diff --git a/rtl/ibex_lockstep.sv b/rtl/ibex_lockstep.sv index b0fb5ed7b..8f1b69c08 100644 --- a/rtl/ibex_lockstep.sv +++ b/rtl/ibex_lockstep.sv @@ -547,6 +547,7 @@ module ibex_lockstep import ibex_pkg::*; #( .rvfi_ext_debug_mode (), .rvfi_ext_rf_wr_suppress (), .rvfi_ext_mcycle (), + .rvfi_ext_minstret (), .rvfi_ext_mhpmcounters (), .rvfi_ext_mhpmcountersh (), .rvfi_ext_ic_scr_key_valid (), diff --git a/rtl/ibex_top.sv b/rtl/ibex_top.sv index 0f6419a89..99c72eb83 100644 --- a/rtl/ibex_top.sv +++ b/rtl/ibex_top.sv @@ -149,6 +149,7 @@ module ibex_top import ibex_pkg::*; #( output logic rvfi_ext_debug_mode, output logic rvfi_ext_rf_wr_suppress, output logic [63:0] rvfi_ext_mcycle, + output logic [63:0] rvfi_ext_minstret, output logic [31:0] rvfi_ext_mhpmcounters [10], output logic [31:0] rvfi_ext_mhpmcountersh [10], output logic rvfi_ext_ic_scr_key_valid, @@ -446,6 +447,7 @@ module ibex_top import ibex_pkg::*; #( .rvfi_ext_debug_mode, .rvfi_ext_rf_wr_suppress, .rvfi_ext_mcycle, + .rvfi_ext_minstret, .rvfi_ext_mhpmcounters, .rvfi_ext_mhpmcountersh, .rvfi_ext_ic_scr_key_valid, diff --git a/rtl/ibex_top_tracing.sv b/rtl/ibex_top_tracing.sv index 3823dd219..5e37cf80c 100644 --- a/rtl/ibex_top_tracing.sv +++ b/rtl/ibex_top_tracing.sv @@ -154,6 +154,7 @@ module ibex_top_tracing import ibex_pkg::*; #( logic rvfi_ext_debug_mode; logic rvfi_ext_rf_wr_suppress; logic [63:0] rvfi_ext_mcycle; + logic [63:0] rvfi_ext_minstret; logic [31:0] rvfi_ext_mhpmcounters [10]; logic [31:0] rvfi_ext_mhpmcountersh [10]; @@ -175,6 +176,7 @@ module ibex_top_tracing import ibex_pkg::*; #( logic unused_rvfi_ext_debug_mode; logic unused_rvfi_ext_rf_wr_suppress; logic [63:0] unused_rvfi_ext_mcycle; + logic [63:0] unused_rvfi_ext_minstret; logic unused_rvfi_ext_ic_scr_key_valid; logic unused_rvfi_ext_irq_valid; logic unused_rvfi_ext_expanded_insn_last; @@ -189,6 +191,7 @@ module ibex_top_tracing import ibex_pkg::*; #( assign unused_rvfi_ext_debug_mode = rvfi_ext_debug_mode; assign unused_rvfi_ext_rf_wr_suppress = rvfi_ext_rf_wr_suppress; assign unused_rvfi_ext_mcycle = rvfi_ext_mcycle; + assign unused_rvfi_ext_minstret = rvfi_ext_minstret; assign unused_perf_regs = rvfi_ext_mhpmcounters; assign unused_perf_regsh = rvfi_ext_mhpmcountersh; assign unused_rvfi_ext_ic_scr_key_valid = rvfi_ext_ic_scr_key_valid; @@ -305,6 +308,7 @@ module ibex_top_tracing import ibex_pkg::*; #( .rvfi_ext_debug_mode, .rvfi_ext_rf_wr_suppress, .rvfi_ext_mcycle, + .rvfi_ext_minstret, .rvfi_ext_mhpmcounters, .rvfi_ext_mhpmcountersh, .rvfi_ext_ic_scr_key_valid, From c27c0137f46ae2a73a3bd8c7b6a32629ba5d1f28 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Tue, 12 May 2026 17:41:35 +0200 Subject: [PATCH 09/12] [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 9c581994f..49eee3562 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 25b9fcf0c..91516761c 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 000000000..30f7fc251 --- /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 2de5d8fff..a1e0bc8b1 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 b2f2c675d..9bc8754a1 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 5794dde7c..0186201c6 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 e0d993566c130d665a74bed33524873c264ed311 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Wed, 13 May 2026 11:41:39 +0200 Subject: [PATCH 10/12] [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 f9436be88..4a34db187 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 35c347cd6..9225c8a64 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 37ddcd4bda120358b541a044f80e81d955e27a64 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Wed, 13 May 2026 11:48:46 +0200 Subject: [PATCH 11/12] [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 dc5e6c8b6..8f41c17f9 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 49bffacbaa1695fcfcb478fef25c6b843ddb96f7 Mon Sep 17 00:00:00 2001 From: Samuel Riedel Date: Tue, 26 May 2026 14:04:46 +0200 Subject: [PATCH 12/12] [dv] Check u-mode counter alias the correct m-mode counter --- .../mcounteren_test/mcounteren_test.S | 111 ++++++++++++++++++ 1 file changed, 111 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 388576f83..816217e4a 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,79 @@ post_all_tests: mv ra, s3 ret +# ----------------------------------------------------------------------- +# Counter Alias Verification Subroutine +# ----------------------------------------------------------------------- +# Verifies that the user-mode counter CSRs alias their M-mode counterparts. +# cycle and instret are checked by the cosim. +check_counter_aliases: + mv s3, ra + + # 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 # -----------------------------------------------------------------------