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, with the
59// RTLMIN-6496 workaround: four back-to-back reads of the same CSR in a
60// 16-byte-aligned block. The first three reads are discarded; the fourth
61// is the architecturally correct value. `.align 4` aligns the block to
62// 2^4 = 16 bytes. `nomem` is omitted so the compiler treats the block as
63// a potential memory barrier, preventing it from reordering other
64// loads/stores across the four reads.
65macro_rules! csr_read {
66 ($csr:literal) => {{
67 let v: u64;
68 // SAFETY: csrrs with rs1 = x0 reads without side effect.
69 // The four reads must be consecutive in the instruction stream;
70 // placing them in one asm block prevents the compiler inserting
71 // any intervening instructions.
72 unsafe {
73 asm!(
74 ".align 4",
75 concat!("csrrs {v0}, ", stringify!($csr), ", x0"),
76 concat!("csrrs {v1}, ", stringify!($csr), ", x0"),
77 concat!("csrrs {v2}, ", stringify!($csr), ", x0"),
78 concat!("csrrs {v}, ", stringify!($csr), ", x0"),
79 v0 = out(reg) _,
80 v1 = out(reg) _,
81 v2 = out(reg) _,
82 v = out(reg) v,
83 options(nostack, preserves_flags),
84 );
85 }
86 v
87 }};
88}
89
90// ---------------------------------------------------------------------------
91// Public API
92// ---------------------------------------------------------------------------
93
94/// Read the `cycle` counter (CSR `0xC00`).
95///
96/// Returns the number of cycles elapsed since firmware initialised the
97/// counter. Rolls over at 2^64 cycles.
98#[inline(always)]
99pub fn pmu_read_cycle() -> u64 {
100 csr_read!(0xC00)
101}
102
103/// Read the `instret` counter (CSR `0xC02`).
104///
105/// Returns the number of instructions retired since firmware initialised
106/// the counter.
107#[inline(always)]
108pub fn pmu_read_instret() -> u64 {
109 csr_read!(0xC02)
110}
111
112/// Read hardware performance counter `N` (`hpmcounterN`, CSR `0xC03 + (N-3)`).
113///
114/// `counter` must be in the range `3..=31`; values outside this range return 0.
115/// The semantics of the returned value depend on the event assigned to
116/// counter N by firmware via `mhpmeventN`.
117///
118/// Counter 3 (CSR `0xC03`) is also used by [`crate::timestamp`].
119#[inline(always)]
120pub fn pmu_read(counter: u8) -> u64 {
121 match counter {
122 3 => csr_read!(0xC03),
123 4 => csr_read!(0xC04),
124 5 => csr_read!(0xC05),
125 6 => csr_read!(0xC06),
126 7 => csr_read!(0xC07),
127 8 => csr_read!(0xC08),
128 9 => csr_read!(0xC09),
129 10 => csr_read!(0xC0A),
130 11 => csr_read!(0xC0B),
131 12 => csr_read!(0xC0C),
132 13 => csr_read!(0xC0D),
133 14 => csr_read!(0xC0E),
134 15 => csr_read!(0xC0F),
135 16 => csr_read!(0xC10),
136 17 => csr_read!(0xC11),
137 18 => csr_read!(0xC12),
138 19 => csr_read!(0xC13),
139 20 => csr_read!(0xC14),
140 21 => csr_read!(0xC15),
141 22 => csr_read!(0xC16),
142 23 => csr_read!(0xC17),
143 24 => csr_read!(0xC18),
144 25 => csr_read!(0xC19),
145 26 => csr_read!(0xC1A),
146 27 => csr_read!(0xC1B),
147 28 => csr_read!(0xC1C),
148 29 => csr_read!(0xC1D),
149 30 => csr_read!(0xC1E),
150 31 => csr_read!(0xC1F),
151 _ => 0,
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 /// Verify that PmuEvent discriminants match PRM Chapter 8 event codes.
160 #[test]
161 fn pmu_event_discriminants() {
162 assert_eq!(PmuEvent::TfmaWaitTenb as u64, 18);
163 }
164}