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}