DVFS concentrates every power and clock failure mode into one transition: the wrong order crashes timing, the wrong ratio breaks a crossing, a slow regulator voids the settle wait, and a test that only visits OPP0 covers none of it. This lesson collects the modes in one table with the guard for each, and writes down the regulator promises the whole scheme stands on.
Four modes cover the space. Order inversion crashes paths at the new frequency on the old voltage; the guard is the controller of the first lesson. A crossing-ratio change breaks assumptions baked into synchroniser depths and FIFO sizes; the guard is re-verification at every ratio, because a crossing's latency, FIFO depth and synchroniser MTBF change with the two frequencies. The single shared rail adds a constraint to every mode: lowering the voltage lowers it for the always-on logic and the memories too, so their minimum-voltage limits bound the lowest operating point. A regulator slower than its illustrative model voids every settle wait; the guard is qualifying waits on voltage-good, with the slew rate owned by the regulator data, flagged illustrative here. And a skipped operating point in test leaves a gear that was never entered; the guard is a transition-coverage matrix over the OPP table demanding every pair in both directions under traffic. The script below computes, per operating point, how full the CPU-to-fabric asynchronous FIFO gets during a 16-beat burst and checks that it still fits.
# Re-verifying a crossing per OPP: async FIFO fill for a 16-beat burst.
# Source writes one beat per source clock; the destination starts reading after a
# 2-destination-clock pointer synchroniser delay and reads one beat per clock.
# OPP0 frequencies NanoSoC values; OPP1 and the depth illustrative.
from fractions import Fraction as F
BURST, DEPTH = 16, 12 # beats; illustrative FIFO depth
def fill(f_src_mhz, f_dst_mhz):
t_write_us = F(BURST) / f_src_mhz # time to push the burst
latency_us = F(2) / f_dst_mhz # pointer synchroniser delay
reads = max(0, int((t_write_us - latency_us) * f_dst_mhz)) # beats drained meanwhile
return BURST - min(BURST, reads)
OPPS = {"OPP0": (192, 96), "OPP1": (96, 64)} # (cpu MHz, sys MHz)
fills = {k: fill(*v) for k, v in OPPS.items()}
assert fills == {"OPP0": 10, "OPP1": 8}, fills
assert all(f <= DEPTH for f in fills.values()) # every shipped gear fits
# A gear that is NOT re-verified: cpu 192 MHz into a 48 MHz fabric overflows.
assert fill(192, 48) == 14 and fill(192, 48) > DEPTH
print("PASS: burst fill 10 at OPP0, 8 at OPP1 fits depth 12; a 192:48 gear (fill 14) is caught")Failure | Symptom | Caught by | Fix |
|---|---|---|---|
Order inversion | Crash only when going faster | Transition test both directions | Raise V then f; lower f then V; gate on voltage-good |
Ratio-sensitive crossing | FIFO overflow or stall at one gear; rare metastability only if the destination clock got faster | Ratio-sweep CDC sims at every OPP | Re-verify depths, latencies and synchroniser MTBF per ratio |
Slow regulator | Settle wait expires early, crash follows | Rail capture during transition | Wait on voltage-good; own the slew number |
Untested gear | Field failure at a rarely used point | Transition-coverage matrix | Every OPP pair both ways under traffic |
The regulator promises are written as inputs with owners: slew rate, tolerance band and voltage-good timing, all illustrative here and all flagged.
The transition-coverage matrix is filled, not planned: every OPP pair both directions with traffic crossing every bridge.
The STA signoff covers every OPP corner, not just the top frequency; the low-voltage corners close timing explicitly.
Run the transition matrix with randomised traffic and randomised regulator delay around the illustrative slew: every transition must complete with no corrupted transaction and no lost interrupt. If one gear fails while others pass, compare its ratio against the verified set before touching voltages; an unverified ratio is a CDC problem wearing a DVFS symptom. If all gears fail going up, capture the rail: the regulator is slower than its number, and the number, not the wait, is wrong.
The crossings at the new ratios: FIFO depths and synchroniser allowances verified at OPP0 do not automatically hold at OPP1 ratios. Re-run the ratio-sweep CDC and traffic tests at the failing pair; the transition exposed a ratio hole, not a voltage hole.