Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/vars.env
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
marnovandermaas marked this conversation as resolved.
RISCV_TOOLCHAIN_TAR_VERSION=20220210-1
RISCV_TOOLCHAIN_TAR_VARIANT=lowrisc-toolchain-gcc-rv32imcb
RISCV_COMPLIANCE_GIT_VERSION=844c6660ef3f0d9b96957991109dfd80cc4938e2
Expand Down
4 changes: 4 additions & 0 deletions doc/01_overview/compliance.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 18 additions & 2 deletions doc/03_reference/performance_counters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
---------------------------------

Expand Down
18 changes: 17 additions & 1 deletion dv/cosim/spike_cosim.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<log_file_t>(trace_log_path.c_str());
Expand Down Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions dv/cosim/spike_cosim.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 11 additions & 1 deletion dv/formal/check/top.sv
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of making this an input as opposed to a parameter? Is there a use-case to be able to switch this dynamically during runtime?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This allows locking the mcounteren in a certain configuration. For example, the boot code could enable only the instret counter to be accessible to the user mode and then lock it to prevent the software from switching it later and use other counters to profile the core. This option, gives us the most flexibility and security.

output logic core_sleep_o,
output logic alert_minor_o,
output logic alert_major_internal_o,
Expand Down Expand Up @@ -162,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 |
Expand Down Expand Up @@ -441,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.

Expand Down
1 change: 1 addition & 0 deletions dv/riscv_compliance/rtl/ibex_riscv_compliance.sv
Original file line number Diff line number Diff line change
Expand Up @@ -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 ( ),
Expand Down
17 changes: 17 additions & 0 deletions dv/uvm/core_ibex/directed_tests/directed_testlist.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,23 @@
test_srcs: empty/empty.S
Comment thread
gautschimi marked this conversation as resolved.
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: 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
Expand Down
21 changes: 19 additions & 2 deletions dv/uvm/core_ibex/directed_tests/gen_testlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,23 @@ 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: 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
Expand Down Expand Up @@ -469,11 +486,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':
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Comment thread
marnovandermaas marked this conversation as resolved.
Comment thread
marnovandermaas marked this conversation as resolved.

# 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
Loading
Loading