What Vajra-LP checks that simulation cannot see
A block that is powered off does not misbehave. It goes dark, and its outputs drift to an undefined level that the neighbours downstream will happily latch. Simulation with everything powered rarely shows it. The structure that prevents it lives in the power intent, and that is a document you check statically, before a single cycle runs.
The static half of low-power verification, read from UPF.
Low-power design splits a chip into regions that can be switched off independently to save energy. That single idea creates a family of failure modes that ordinary functional simulation is poorly placed to catch, because the bugs live at the boundary between a region that is off and a region that is on, and a default simulation keeps everything on. The intent for all of this is written in a UPF file, and Vajra-LP reads that intent and checks it as a structure.
Five things the power intent must get right
A UPF description names the parts of the low-power contract, and each one is a place a design can be wrong:
- Power domains are the regions that share a supply and switch together.
- Isolation clamps a domain's outputs to a known value while it is off, so downstream logic never samples a floating signal.
- Retention preserves selected state through a power-down so the domain wakes where it left off.
- Level shifters translate signals between domains at different voltages.
- Power states enumerate which combinations of on and off are legal at once.
Vajra-LP is a static check of these five. It does not run the design, it reads the intent and asks whether the structure is complete and consistent. That is the half of low-power verification that happens before dynamic power-aware simulation, and it catches the class of bug that simulation with everything powered will simply never present.
A production SoC intent, read in one pass
Point it at the UPF for a production SoC top and it inventories the intent before it judges anything:
$ vajra-lp check wzax1_top.upf power domains : 36 declared strategies: iso=29 ls=16 ret=19 switch=23 power states : 104 switchable : 27 / 36 domains supply nets : 31 declared, 12 switched rails, 17 roots ⚠ 130 input construct(s) NOT modelled: their intent is not checked (a finding-free report does NOT cover the above)
Thirty-six domains, twenty-seven of them able to power off, a hundred and four legal power states. That is the surface a human cannot hold in their head, which is the reason a static tool exists: to hold the whole contract at once and find the corner of it that is inconsistent. The line that matters most is the warning: 130 input constructs fell outside what this build models, so their intent is not checked, and the tool says so up front rather than letting a short finding list read as a clean bill.
The finding: a domain that goes dark with nothing to clamp it
Here is the report the tool renders for this design.

Vajra-LP report for wzax1_top. Five PL-ISO-01 errors, each a switchable domain whose outputs cross into an on domain with no isolation strategy.
Five domains failed the same check:
findings: 5 (5 error, 0 warning, 0 info) | memory: 18 prior, 5 recurring PL-ISO-01 x5 Off-to-on crossing without isolation [ERROR/recurring] PL-ISO-01: domain 'PD_HSM' can power OFF but has no output isolation strategy (off-to-on crossing unprotected) [ERROR/recurring] PL-ISO-01: domain 'PD_MEM_BANK0' ... [ERROR/recurring] PL-ISO-01: domain 'PD_MEM_BANK1' ... [ERROR/recurring] PL-ISO-01: domain 'PD_MEM_BANK2' ... [ERROR/recurring] PL-ISO-01: domain 'PD_MEM_BANK3' ...
The rule PL-ISO-01 is the isolation gap in one line: a domain that can power off, feeding a domain that stays on, with no isolation clamp between them. When the source domain goes dark its outputs are undefined, and the live domain downstream latches that undefined level as if it were data. It is precisely the bug a fully-powered simulation cannot show you, because in that simulation the source domain is never off.
PL-ISO-01: the missing clamp on the off-to-on crossing. With isolation, the live domain sees a defined value while the source is dark.
The verdict is a diagnosis, not just a count
What lifts this above a linter is that the tool does not stop at five errors. A second command resolves them to a single dominant cause and proposes what to do:
$ vajra-lp close wzax1_top.upf declared strategies: iso=29 ls=16 ret=19 switch=23 run : 5 errors, 0 warnings (score 500) closure state: UNVERIFIED verdict : KEEP input coverage: UNVERIFIED (130 constructs not modelled) dominant RC : LC1_MISSING_ISO_CTRL (5) action plan (ranked by learned effectiveness): - WAIVE_IF_REDUNDANT waive if crossing is provably safe - ADD_ISO_CONTROL add set_isolation_control signal for the domain
Read the closure block carefully, because the state is the whole point. It is UNVERIFIED, not FAIL and not PASS and not a clean close. The tool found 5 isolation errors, but it also could not model 130 of the design's UPF constructs, so it refuses to claim the design is verified either way: the errors it did find stand, and the part it could not check stays explicitly unverified. The five findings collapse to one root cause, LC1_MISSING_ISO_CTRL, so you fix a category rather than five lines, and the verdict is KEEP: these are worth keeping rather than waiving.
The machine-readable report from check -out-dir adds one more number the terminal does not print: coverage 129 / 144 (90%). That is not simulation coverage and not a toggle percentage. It is static structural completeness, four checklist items across the 36 domains, measuring how much of the intent the tool could evaluate. Paired with the 130 unmodelled constructs, it is the tool stating the boundary of its own check rather than letting a short finding list read as a clean bill.
The model it hands to formal
The static check is also the front of a longer pipeline. The power intent Vajra-LP validates becomes a model that a formal engine can reason about, so a question like is this isolation ever needed given the legal power states can be answered by proof rather than inspection. The static tool establishes that the intent is well-formed; the formal low-power app then reasons about whether the intent is sufficient. The first has to hold before the second means anything, which is why the structural check comes first and refuses to sign off while errors are open.
The most expensive low-power bugs are invisible to a simulation that keeps everything on. They live in the intent, in a domain that can go dark with nothing to clamp its outputs. Reading that intent as a structure, before a cycle runs, is how you find them while they are still cheap to fix.