If you're calculating average frequency or voltage for a compute unit on Apple Silicon, probably dealing with state residency data from the IOReport, you'll need to have a frequency/voltage table that corresponds with the unit you're analyzing. Y'know, pstates, vstates, that good shit.
State tables are stored for various thingies in the IORegistry, you'll find them in the PMGR. There are two types of properties that store this information:
voltage-statesN, most likely corresponds with Core Rails (for the actual processing parts of a compute unit). The frequency values are given as raw values from the system registers.voltage-statesN-sram, most likely corresponds with SRAM Rails (for the literal static ram, L1, L2 caches of a compute unit). Frequency is precalculated, readable, and SRAM volatges are higher (stability stuff).
Here N is the number/identifier making it unique to whatever the vstates correspond to. For example:
- 1 = ECPU
- 5 = PCPU
- 8 = ANE (I think?)
- 9 = GPU
- other ones I dont know
There is an odd exception that may sometimes occur, but I only know of with voltage-states9, where the frequency values are precomputed for some reason (like the SRAM states) instead of being the raw clock periods...? I don't know why.
As far as I've seen, each state is an 8-byte group, two unsigned 32-bit integers (freq/raw, mvolts).
ECPU states from M3 Pro...
voltage-states1 |
voltage-states1-sram |
|---|---|
16 58 01 00 62 02 00 00 = 88086, 610 |
00 8a 58 2c 16 03 00 00 = 744000000, 790 |
35 f5 00 00 85 02 00 00 = 62773, 645 |
00 2d 3a 3e 1b 03 00 00 = 1044000000, 795 |
71 ad 00 00 b7 02 00 00 = 44401, 695 |
00 f9 f9 57 34 03 00 00 = 1476000000, 820 |
be 7f 00 00 20 03 00 00 = 32702, 800 |
00 9d 72 77 75 03 00 00 = 2004000000, 885 |
df 70 00 00 52 03 00 00 = 28895, 850 |
00 ef 2e 87 a7 03 00 00 = 2268000000, 935 |
93 68 00 00 89 03 00 00 = 26771, 905 |
00 84 e9 91 ca 03 00 00 = 2448000000, 970 |
f8 60 00 00 cf 03 00 00 = 24824, 975 |
00 34 5b 9d 10 04 00 00 = 2640000000, 1040 |
28 5d 00 00 cf 03 00 00 = 23848, 975 |
00 27 cb a3 10 04 00 00 = 2748000000, 1040 |
Here you can clearly see the difference between the two state properties. The raw values for the frequency seem to actually be a clock periods, the length of a single cycle typically in nanoseconds*, but it's stored fixed-point (Q16.16 format). Specifically, it would've been multiplied by 65536 (216) when stored, so we can divide it by that to get the original value in nanoseconds*. Once we have the clock period we can convert to frequency.
*Nanoseconds should be the standard from M1 up until the M4, which might not use nanoseconds anymore.
You would apply this for all the vstates that have the raw clock periods instead of readable frequency. As an example In the case of the voltage-states1 (ECPU vstates), getting the lowest frequency 744mhz from 16 58 01 00 (88086).
We can further prove this works getting the highest frequency 2748mhz from 28 5d 00 00 (23848).
...So calculating each state we're left with:
| Raw clock period | Result (hz) |
|---|---|
| 88086 | 744003333 |
| 62773 | 1044026602 |
| 44401 | 1476014760 |
| 32702 | 2004048177 |
| 28895 | 2268088002 |
| 26771 | 2448040344 |
| 24824 | 2640054913 |
| 23848 | 2748083211 |
Yay!
These calculations are the same that good ol' powermetrics would need to do internally as it doesn't use the SRAM vstates (which you can see if you peak at it's strings). Me, though, I would exclusively always use the frequencies from the SRAM vstates in my code because they did the job. Figuring out the raw data conversion wasn't really a concern initially, since the code worked with the other states, but the discrepancies bugged me.