Skip to content

Instantly share code, notes, and snippets.

@0x2000580D
Last active February 17, 2026 00:45
Show Gist options
  • Select an option

  • Save 0x2000580D/0a528aade40da54a69d9ff2131d1917a to your computer and use it in GitHub Desktop.

Select an option

Save 0x2000580D/0a528aade40da54a69d9ff2131d1917a to your computer and use it in GitHub Desktop.
voltage-states and voltage-states-sram on Apple Silicon

voltage-states and voltage-states-sram on Apple Silicon

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).

Example

ECPU states from M3 Pro...

Screenshot 2026-02-16 at 12 28 42 PM
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.

$$ Period = {Raw \over 65536 } $$

$$ Frequency = {1 \over Period \times 10^{-9} } $$

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).

$$ Period = {88086 \over 65536} \approx 1.34408 {\ \text{nanoseconds}} $$

$$ Frequency = {1 \over 1.34408 \times 10^{-9} {\ \text{seconds}} } \approx 744,003,333 {\ \text{(744mhz)}} $$

We can further prove this works getting the highest frequency 2748mhz from 28 5d 00 00 (23848).

$$ Period = {23848 \over 65536} \approx 0.36389 {\ \text{nanoseconds}} $$

$$ Frequency = {1 \over 0.36389 \times 10^{-9} {\ \text{seconds}} } \approx 2,748,083,211 {\ \text{(2748mhz)}} $$

...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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment