Skip to main content

et_kernel/
pmu.rs

1//! Performance Monitoring Unit (PMU) counter API for the ET-SoC-1 Minion core.
2//!
3//! The ET-SoC-1 implements the RISC-V Zihpm extension: hardware performance
4//! counters accessible from U-mode via the `hpmcounterN` CSRs (PRM Chapter 8).
5//! Each counter is a 64-bit read-only accumulator that increments on each
6//! occurrence of the event assigned to it by firmware (or `pmu_configure` if
7//! U-mode write access to `mhpmeventN` is confirmed).
8//!
9//! # Available counters
10//!
11//! - `hpmcounter3` (CSR `0xC03`): also read by [`crate::timestamp`] as a
12//!   cycle counter. Whether the assigned event is `cycle` or a custom PMU event
13//!   depends on the firmware's `mhpmeventN` configuration.
14//! - `hpmcounter4` .. `hpmcounter31` (CSR `0xC04` .. `0xC1F`): available for
15//!   application use subject to firmware assignment.
16//!
17//! # Usage pattern
18//!
19//! ```no_run
20//! use et_kernel::pmu::{PmuEvent, pmu_read};
21//!
22//! // Read counter 4 before and after a tensor operation; the delta is the
23//! // number of TFMA_WAIT_TENB events that occurred (assuming firmware assigned
24//! // PmuEvent::TfmaWaitTenb to counter 4 via mhpmevent4).
25//! let before = pmu_read(4);
26//! // ... tensor operations ...
27//! let after  = pmu_read(4);
28//! let delta  = after.wrapping_sub(before);
29//! ```
30
31use core::arch::asm;
32
33// ---------------------------------------------------------------------------
34// PMU event codes (PRM Chapter 8)
35// ---------------------------------------------------------------------------
36
37/// PMU event codes for the ET-SoC-1. The value is written to `mhpmeventN`
38/// (CSR `0x320 + N`) to select what counter N accumulates.
39///
40/// Firmware or a privileged shim configures the mapping; U-mode can read
41/// the resulting counts via [`pmu_read`] but typically cannot write
42/// `mhpmeventN` without M-mode delegation.
43#[repr(u64)]
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum PmuEvent {
46    /// Cycles spent waiting for TenB load to complete before TensorFMA32.
47    /// Measures the B-load serialisation cost; high values indicate that
48    /// the crossbar or DRAM is the bottleneck for B tiles.
49    ///
50    /// (PRM Chapter 8, event code 18.)
51    TfmaWaitTenb = 18,
52}
53
54// ---------------------------------------------------------------------------
55// CSR read helper macro
56// ---------------------------------------------------------------------------
57
58// Reads an hpmcounterN CSR where N is a compile-time literal.
59// RISC-V requires the CSR address to be an immediate in the instruction.
60macro_rules! csr_read {
61    ($csr:literal) => {{
62        let v: u64;
63        // SAFETY: csrrs with rs1 = x0 reads without side effect.
64        unsafe {
65            asm!(
66                concat!("csrrs {v}, ", stringify!($csr), ", x0"),
67                v = out(reg) v,
68                options(nomem, nostack, preserves_flags),
69            );
70        }
71        v
72    }};
73}
74
75// ---------------------------------------------------------------------------
76// Public API
77// ---------------------------------------------------------------------------
78
79/// Read the `cycle` counter (CSR `0xC00`).
80///
81/// Returns the number of cycles elapsed since firmware initialised the
82/// counter. Rolls over at 2^64 cycles.
83#[inline(always)]
84pub fn pmu_read_cycle() -> u64 {
85    csr_read!(0xC00)
86}
87
88/// Read the `instret` counter (CSR `0xC02`).
89///
90/// Returns the number of instructions retired since firmware initialised
91/// the counter.
92#[inline(always)]
93pub fn pmu_read_instret() -> u64 {
94    csr_read!(0xC02)
95}
96
97/// Read hardware performance counter `N` (`hpmcounterN`, CSR `0xC03 + (N-3)`).
98///
99/// `counter` must be in the range `3..=31`; values outside this range return 0.
100/// The semantics of the returned value depend on the event assigned to
101/// counter N by firmware via `mhpmeventN`.
102///
103/// Counter 3 (CSR `0xC03`) is also used by [`crate::timestamp`].
104#[inline(always)]
105pub fn pmu_read(counter: u8) -> u64 {
106    match counter {
107        3  => csr_read!(0xC03),
108        4  => csr_read!(0xC04),
109        5  => csr_read!(0xC05),
110        6  => csr_read!(0xC06),
111        7  => csr_read!(0xC07),
112        8  => csr_read!(0xC08),
113        9  => csr_read!(0xC09),
114        10 => csr_read!(0xC0A),
115        11 => csr_read!(0xC0B),
116        12 => csr_read!(0xC0C),
117        13 => csr_read!(0xC0D),
118        14 => csr_read!(0xC0E),
119        15 => csr_read!(0xC0F),
120        16 => csr_read!(0xC10),
121        17 => csr_read!(0xC11),
122        18 => csr_read!(0xC12),
123        19 => csr_read!(0xC13),
124        20 => csr_read!(0xC14),
125        21 => csr_read!(0xC15),
126        22 => csr_read!(0xC16),
127        23 => csr_read!(0xC17),
128        24 => csr_read!(0xC18),
129        25 => csr_read!(0xC19),
130        26 => csr_read!(0xC1A),
131        27 => csr_read!(0xC1B),
132        28 => csr_read!(0xC1C),
133        29 => csr_read!(0xC1D),
134        30 => csr_read!(0xC1E),
135        31 => csr_read!(0xC1F),
136        _  => 0,
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    /// Verify that PmuEvent discriminants match PRM Chapter 8 event codes.
145    #[test]
146    fn pmu_event_discriminants() {
147        assert_eq!(PmuEvent::TfmaWaitTenb as u64, 18);
148    }
149}