The 114 clamps and the switch sequence exist twice: once as gates in the netlist and once as intent in the power format, and when the two disagree the tools build the wrong chip politely. On NanoSoC the intent is small enough to hold in your head, two domains, one switch, one isolation rule, one retention rule, which makes it the ideal example of where the integrator's job ends and the tool's begins.
The intent names two power domains with their supplies, puts every block of the first topic's table into one of them, attaches the header switch to PD_SW, applies the clamp-to-0 rule with iso_sw_en to the four signal groups of the first lesson, marks the SRAM0 arrays with the retention rule at the 0.6 factor, and lists the legal states RUN, IDLE and SLEEP with their transitions. The tools then insert the cells, check every crossing and run power-aware simulation; the integrator owns everything the tools cannot invent: the domain partition itself, the clamp value per group, the retention list, and the legal-transition table. A missing isolation cell is usually a tool or library problem; a missing isolation rule is an integrator problem, and only the second kind survives to silicon.
Because NanoSoC has a single voltage, the intent has one deliberate absence: no level shifters. A reviewer coming from a multi-voltage design will ask where they are, and the answer is that with one 0.9 V supply there is nothing to shift; the static check that demands a shifter on every crossing must be told the supplies match, not waived one crossing at a time. Go deeper on the mechanism in Power/PD Handoff and UPF RTL Handoff; verify transitions against Power State Verification.
# Intent completeness audit: every fact the integrator must supply, checked present.
# NanoSoC teaching values; the script models the review, not the UPF syntax.
REQUIRED = ["domains:PD_AON,PD_SW", "switch:PD_SW", "iso:4-groups-114",
"iso_value:clamp-0", "iso_enable:iso_sw_en", "retention:SRAM0-0.6",
"states:RUN-IDLE-SLEEP", "legal:RUN-IDLE,RUN-SLEEP,SLEEP-RUN"]
def audit(intent):
return [r for r in REQUIRED if r not in intent]
INTENT = set(REQUIRED)
assert audit(INTENT) == []
# Injected bugs: an intent without the isolation enable, or without the retention
# rule, must be reported by name.
assert audit(INTENT - {"iso_enable:iso_sw_en"}) == ["iso_enable:iso_sw_en"]
assert audit(INTENT - {"retention:SRAM0-0.6"}) == ["retention:SRAM0-0.6"]
# RUN to SLEEP is legal on NanoSoC, but only via the sequenced path
# (SW_OFF_REQ with the quiesce gate); there is no other way to remove power.
LEGAL = {("RUN", "IDLE"), ("IDLE", "RUN"), ("RUN", "SLEEP"), ("SLEEP", "RUN")}
def transition_ok(a, b, via_sequencer):
return (a, b) in LEGAL and (b != "SLEEP" or via_sequencer)
assert transition_ok("RUN", "SLEEP", True)
assert not transition_ok("RUN", "SLEEP", False) # no other way to remove power
assert not transition_ok("IDLE", "SLEEP", True) # there is no IDLE-to-SLEEP arc
assert not transition_ok("SLEEP", "IDLE", True) # wake always returns through RUN
print("PASS: 8 intent items; missing items named; RUN-SLEEP legal only via sequencer, IDLE-SLEEP illegal")The intent and the block table agree cell for cell: same two domains, same members, same 114 clamps.
The single-supply-no-shifters decision is written down once, so nobody files it as a missing-shifter violation later.
Every SLEEP entry goes through the sequenced path (SW_OFF_REQ with the quiesce gate); there is no other way to remove power.
Run the consistency audit between the RTL hierarchy and the intent before any power-aware simulation: each hierarchical block must resolve to exactly one domain, and each cross-domain net must resolve to exactly one isolation or retention rule. Mismatches here mean the partition table changed without the intent changing, and fixing the table is cheaper than debugging the simulation it would corrupt.
Not waive 114 crossings one by one. Record once that both domains share one 0.9 V supply so no shifting is needed, and configure the check for equal supplies; the flags should all clear together.