Battery life and thermal budgets make low power verification one of the highest-stakes disciplines in modern SoC sign-off. UPF (IEEE 1801) encodes the power intent — domains, rails, isolation rules, retention strategies — and power-aware simulation checks that the RTL plus the power network behaves correctly across every supply state. A single missing isolation cell or wrong retention polarity can corrupt data when power is toggled, producing a silicon bug that only shows up at runtime.
Unified Power Format (UPF), standardised as IEEE 1801, is a Tcl-based language that annotates RTL with the power intent the design team intends to implement. The simulator reads the UPF alongside the RTL so it knows which domains can be switched off, what supply voltage each domain nominally receives, and what structural elements (isolation cells, level shifters, retention registers) must be present at the domain crossings.
The four foundational UPF commands engineers encounter first are create_power_domain, create_supply_port / create_supply_net, connect_supply_net, and add_port_state. Understanding exactly what each one declares removes most of the confusion beginners face when reading UPF files.
## upf/top.upf — Minimal UPF for a two-domain SoC # ---- Supply network for Always-On (AO) domain ---- create_supply_port VDD_AO -direction in create_supply_port VSS -direction in create_supply_net VDD_AO_net -domain AO_domain connect_supply_net VDD_AO_net -ports VDD_AO # ---- Always-On domain covers the PMU and boot logic ---- create_power_domain AO_domain -elements {u_pmu u_boot_rom} create_pst top_pst -supply_nets {VDD_AO_net VDD_PD_net} # ---- Switchable domain for DSP core ---- create_supply_port VDD_PD -direction in create_supply_net VDD_PD_net -domain PD_domain connect_supply_net VDD_PD_net -ports VDD_PD create_power_domain PD_domain -elements {u_dsp} # ---- Power State Table rows ---- # Each row names the state of every supply net in order add_pst_state NORMAL -pst top_pst \ -state {VDD_AO_net FULL_ON VDD_PD_net FULL_ON} add_pst_state DSP_OFF -pst top_pst \ -state {VDD_AO_net FULL_ON VDD_PD_net OFF} # ---- Port states control what value each supply takes ---- add_port_state VDD_PD \ -state {FULL_ON 0.9} \ -state {OFF 0.0}
UPF supports hierarchical power intent: a top-level UPF file declares global supply ports and PSTs, then load_upf references sub-block UPF files. During simulation the tool merges all layers. The rule is that child domain UPF rules cannot widen the parent domain — they can only narrow or specialise.
| UPF Construct | Purpose | Key Argument |
|---|---|---|
| create_power_domain | Group RTL elements into a domain | -elements {} |
| create_supply_net | Declare a logical wire for a supply rail | -domain |
| create_pst | Power State Table declaration | -supply_nets {} |
| add_pst_state | Enumerate legal supply state combos | -state {net state} |
| create_isolation_rule | Require isolation at crossing | -isolation_sense |
| create_retention_rule | Require retention on flip-flops | -target_domain |
| map_retention_cell | Bind rule to a library cell | -lib_cell |
Every signal that crosses from a switchable domain into an always-on domain is a domain crossing. When the switchable domain is powered off, that signal floats. An isolation cell sits at the boundary and clamps the output to a safe constant (0 or 1) whenever the isolation enable is asserted. The enable must arrive before the supply rail drops.
-isolation_sense high|low-clamp_value 0|1|Z## UPF isolation rule declaration create_isolation_rule iso_dsp_to_ao \ -from PD_domain \ -to AO_domain \ -isolation_sense high \ # enable=1 → isolate -clamp_value 0 \ # outputs held LOW when isolated -isolation_signal u_pmu.iso_en \ -location parent # cell placed in AO_domain ## Map to a real library isolation cell map_isolation_cell iso_dsp_to_ao \ -domain PD_domain \ -lib_cell ISO_AND_X2 # output = IN & ~ISO_EN (sense=high, clamp=0)
The cleanest way to verify isolation in simulation is an SVA concurrent assertion that fires on every clock cycle when the domain is off and checks every output from PD_domain that feeds AO_domain.
// File: tb/isolation_checks.sv // Bind into the DUT top so $root hierarchy is accessible module isolation_checks #(parameter int NPORTS = 8) ( input logic clk, input logic vdd_pd_on, // 1 = domain powered, 0 = off input logic iso_en, // 1 = isolation active input logic [NPORTS-1:0] dsp_out, // signals crossing from PD→AO input logic [NPORTS-1:0] iso_cell_out // post-isolation values ); // When domain is off, iso_en must be asserted ISO_ENABLE_WHEN_OFF: assert property ( @(posedge clk) !vdd_pd_on |-> iso_en ) else $error("[ISOLATION] Domain off but iso_en=0 — X propagation risk"); // When isolated, each output must be clamped to 0 (clamp_value=0) ISO_CLAMP_VALUE: assert property ( @(posedge clk) iso_en |-> (iso_cell_out === {NPORTS{1'b0}}) ) else $error("[ISOLATION] Clamp value mismatch — expected all-0 when iso_en=1"); // iso_en must be asserted BEFORE power drops (1 cycle lead) ISO_LEAD_TIME: assert property ( @(posedge clk) $fell(vdd_pd_on) |-> $past(iso_en, 1) ) else $error("[ISOLATION] iso_en not asserted before vdd_pd_on fell"); // Cover: domain powers off at least once (completeness) ISO_COV_POWEROFF: cover property ( @(posedge clk) $fell(vdd_pd_on) ); endmodule
bind u_top isolation_checks #(.NPORTS(8)) iso_chk_i (...) in the testbench. This keeps the assertion module out of the RTL while still observing internal signals.Retention registers contain a primary flip-flop (powered by VDD_PD) plus a shadow latch powered by a separate always-on retention supply (VDD_RET). The shadow latch is tiny and leaks almost nothing, so it can remain live while the primary supply is cut. The save/restore sequence must be strictly ordered — any deviation produces corrupted data on restoration.
ret_en = 1) while VDD_PD is still at full voltage. The shadow latch captures the primary FF state on the next clock edge.ret_en = 1 for at least 1 full clock period to guarantee setup/hold on the shadow latch.ret_restore = 1). The shadow value loads back into the primary FF on the next clock edge.ret_restore and resume normal clocked operation. Primary FF now holds the saved value.## UPF retention rule create_retention_rule ret_dsp \ -domain PD_domain \ -retention_power_net VDD_RET_net \ -save_signal {u_pmu.ret_save high} \ -restore_signal {u_pmu.ret_restore high} map_retention_cell ret_dsp \ -domain PD_domain \ -lib_cell DFFR_X2 # retention FF from std-cell library
// Retention sequence verification — SVA // Check that restore fires only AFTER power-good is stable ISO_RET_RESTORE_AFTER_PG: assert property ( @(posedge clk) $rose(ret_restore) |-> $past(pg_vdd_pd, 2) ) else $error("[RETENTION] restore asserted before power-good stabilised"); // Check that save fires while domain supply is still ON RET_SAVE_WHILE_ON: assert property ( @(posedge clk) $rose(ret_save) |-> vdd_pd_on ) else $error("[RETENTION] save asserted after domain power dropped"); // Functional check: data survives a save-poweroff-restore cycle // (done in the UVM scoreboard) task automatic check_retention_cycle( input logic [31:0] expected_val ); // 1. sample DUT register value before save logic [31:0] pre_save = dut.u_dsp.cfg_reg; assert(pre_save === expected_val) else $error("pre-save mismatch: got %0h exp %0h", pre_save, expected_val); // 2. drive save sequence @(posedge clk); ret_save = 1; @(posedge clk); ret_save = 0; // 3. power down vdd_pd_force = 0.0; #100ns; // 4. power up, wait PG, then restore vdd_pd_force = 0.9; @(posedge pg_vdd_pd); repeat(2) @(posedge clk); ret_restore = 1; @(posedge clk); ret_restore = 0; // 5. verify restored value @(posedge clk); logic [31:0] post_restore = dut.u_dsp.cfg_reg; assert(post_restore === expected_val) else $error("[RETENTION] data corrupted: got %0h exp %0h", post_restore, expected_val); endtask
-save_signal {ret_save high} but the RTL latches on the falling edge, the shadow never captures. The net effect is that every restore produces an X or stale value. Always cross-check RTL polarity against the UPF -save_signal attribute.The Power State Table is the exhaustive truth table of every legal combination of supply states across all domains. If the design has 3 power domains, each with 2 supply states (ON / OFF), there are up to 8 combinations — but typically only 3–4 are legal. The UPF PST declares exactly which rows are legal. Verification must prove that every legal row is reached and that no illegal row is ever entered.
| PST State Name | VDD_AO | VDD_PD (DSP) | VDD_MEM | Description |
|---|---|---|---|---|
| NORMAL | FULL_ON | FULL_ON | FULL_ON | All domains active |
| DSP_SLEEP | FULL_ON | OFF | FULL_ON | DSP powered down, memory live |
| MEM_RETAIN | FULL_ON | OFF | MEM_ON (retention) | DSP off, memory in self-refresh |
| DEEP_SLEEP | FULL_ON | OFF | OFF | Only PMU active |
A SystemVerilog functional covergroup for PST coverage should track both the current power state and the transitions between states. Transition coverage catches bugs where the design jumps from NORMAL directly to DEEP_SLEEP, bypassing retention save — a sequencing error that destroys memory contents.
// Power State Table functional coverage typedef enum logic [1:0] { NORMAL = 2'b00, DSP_SLEEP = 2'b01, MEM_RETAIN = 2'b10, DEEP_SLEEP = 2'b11 } pst_state_t; covergroup pst_coverage @(posedge clk); // Every legal PST state must be hit cp_state: coverpoint current_pst { bins normal_op = {NORMAL}; bins dsp_off = {DSP_SLEEP}; bins mem_retain = {MEM_RETAIN}; bins deep_sleep = {DEEP_SLEEP}; } // All legal transitions must be exercised cp_transition: coverpoint current_pst { bins to_dsp_sleep[] = (NORMAL => DSP_SLEEP); bins to_mem_retain[] = (DSP_SLEEP => MEM_RETAIN); bins to_deep_sleep[] = (MEM_RETAIN => DEEP_SLEEP); bins wakeup_fast[] = (DSP_SLEEP => NORMAL); bins full_wakeup[] = (DEEP_SLEEP => MEM_RETAIN => DSP_SLEEP => NORMAL); // Illegal: skip retention save — must NEVER be covered illegal_bins skip_retention = (NORMAL => DEEP_SLEEP); illegal_bins mem_skip = (DSP_SLEEP => DEEP_SLEEP); } // Cross: ensure both DSP and memory domains reach OFF state cx_domain_off: cross vdd_pd_on, vdd_mem_on; endgroup // Instantiate and sample pst_coverage pst_cov_i = new(); always @(posedge clk) pst_cov_i.sample();
illegal_bins entry causes the simulator to flag a violation at runtime, not just a coverage hole. This is the correct way to make illegal power sequences self-detecting.When a power domain is switched off, every flip-flop in that domain whose primary supply has dropped will present an X on its Q output — the simulator models the unknown voltage as an indeterminate logic value. Without isolation, this X floods through the combinational logic in the receiving always-on domain. A single X on a critical select line can corrupt all downstream registers in one clock cycle.
There are two failure modes: X-pessimism (simulation shows X but silicon would resolve to 0 or 1 because of physical contention) and X-optimism (simulation resolves X to 0 because of Verilog semantics but silicon would actually corrupt). X-optimism is the dangerous one. Classic example: if (sel == 1'b0) out = data; — when sel is X, Verilog evaluates the comparison as false and takes the else branch rather than X-propagating. Silicon has no such immunity.
iso_en at least 1 clock before the supply falls. The isolation cell output becomes 0 (or 1) before the domain actually de-energises, so Xs never escape.+xprop / -xprop simulator flags to enable extended X-propagation models that reduce X-optimism. Questa uses -xprop {xprop.cfg}.// X-propagation containment check — assert no X escapes the isolation boundary property p_no_x_on_ao_input; @(posedge clk) !$isunknown(ao_input_bus); // ao_input_bus = post-isolation signals endproperty NO_X_CROSSING: assert property(p_no_x_on_ao_input) else $error("[XPROP] X value detected on AO domain input — isolation cell failure"); // Complementary: when domain IS powered and NOT isolated, // signal must NOT be stuck at the clamp value property p_not_stuck_clamped; @(posedge clk) (vdd_pd_on && !iso_en) |-> !$stable(ao_input_bus); // data should toggle endproperty // (Use cover, not assert, for this — it is a coverage goal) COVER_DATA_TOGGLING: cover property(p_not_stuck_clamped);
Questa Power Aware (PA) is the industry-standard flow for UPF-driven simulation. The key difference from a plain RTL simulation is that the elaboration step reads both the RTL and the UPF together to build a supply-aware netlist model. PA checks run concurrently with functional simulation — you get both functional correctness and structural power intent checking in one run.
## Questa PA simulation Makefile recipe # Step 1 — Compile RTL (SystemVerilog + UPF simultaneously) vlog -sv \ -work work \ rtl/top.sv rtl/pmu.sv rtl/dsp_core.sv \ tb/top_tb.sv tb/isolation_checks.sv # Step 2 — Elaborate with UPF loaded vopt work.top_tb \ -upf upf/top.upf \ -upf_mode abstract \ # or 'full' for gate-level -pa_upf_analyze \ # structural check during elab -access rw+/acc=rn \ -o top_tb_opt # Step 3 — Simulate with PA checks enabled vsim top_tb_opt \ -upf upf/top.upf \ -pa \ # enable Power Aware mode -pa_upf_check on \ # runtime UPF rule checking -sv_seed random \ -xprop xprop.cfg \ # extended X propagation +UVM_TESTNAME=low_power_test \ -do "run -all; quit -f" # xprop.cfg — tell Questa which X-propagation mode to use # File: xprop.cfg # xprop_mode = fuzzy # latch_mode = X # enable_x_check = 1
| PA Check Category | What It Detects | Severity |
|---|---|---|
| Domain crossing without isolation | Signal crosses power boundary with no isolation rule | Error |
| Isolation enable from wrong domain | iso_en driven by PD_domain (powered off = X) | Error |
| Retention enable polarity mismatch | RTL activates on opposite edge vs. UPF intent | Error |
| Level shifter missing | Signal crosses between different voltage domains without LS | Warning |
| PST illegal state entered | Supply combo not listed in any add_pst_state row | Error |
| Retention restore before PG | restore_en asserted before power-good settles | Error |
| Clamp value mismatch | Isolation cell drives value different from UPF -clamp_value | Error |
vcover merge on multiple PA seed runs and then check that both the PST covergroup and the $coverage_off/$coverage_on regions inside power-down sequences reach 100 %. Many teams target 95 % functional + 100 % PST state coverage at signoff.Low power bugs are notoriously difficult to find in RTL simulation because the power switching sequences are often long, asynchronous, and not covered by the standard directed test plan. The three bugs below account for the vast majority of silicon re-spins traced to low power issues.
The UPF declares a crossing from PD_domain to AO_domain but the RTL structural netlist (post-synthesis) either has no isolation cell or the cell is in the wrong power domain. During PA elaboration Questa emits UPF-016: no isolation cell found for crossing. In simulation without PA the X escapes silently.
The RTL designer implements the retention FF with active-low save enable (ret_save_n) but the UPF rule says -save_signal {ret_save high}. The shadow latch therefore saves continuously when it should be idle, and the intended save pulse has no effect. The restored value after power-up is whatever was captured during the last idle period, not the value at the explicit save command.
// Bug example: polarity inversion in RTL vs UPF // RTL (DFF_RET behavioural model — active LOW save) always @(posedge clk or negedge rst_n) begin if (!rst_n) Q <= 0; else if (!save_n) shadow <= D; // saves when save_n=0 (ACTIVE LOW) else Q <= D; end // UPF (incorrectly written as active HIGH) // create_retention_rule ret_r -save_signal {pmu.ret_save HIGH} // ^^^^ // UPF thinks save occurs when ret_save=1 // RTL saves when ret_save=0 → save never happens at right time // FIX: match UPF to RTL polarity // create_retention_rule ret_r -save_signal {pmu.ret_save LOW}
The restore sequence fires the ret_restore pulse one clock cycle before the power-good (PG) signal has asserted. VDD_PD is still ramping — the supply has not reached the minimum operating voltage yet. The shadow value may load correctly into a flip-flop that is running at a marginal voltage, but the subsequent clock edge captures a metastable value. This is the most insidious of the three bugs: it passes simulation (because simulators model ideal supply rails) but fails in silicon at certain temperatures.
ret_restore never precedes PG regardless of the delay value. This catches the hardcoded timing assumption before tapeout.| Bug | Symptom in Sim | Caught By |
|---|---|---|
| Missing isolation cell | X on AO domain input after PD off | PA elaboration / ISO_ENABLE SVA |
| Wrong retention polarity | Restored value is stale or X | check_retention_cycle task / PA runtime |
| Restore before PG | Metastability — passes sim, fails silicon | RET_RESTORE_AFTER_PG SVA with PG delay sweep |
| iso_en from wrong domain | iso_en becomes X when PD off → X propagates | PA check UPF-017 |
| PST illegal transition | Runtime PA error, potential data loss | PST covergroup illegal_bins |
| Level shifter missing | Logic errors at domain boundary | PA elaboration UPF-019 |
illegal_bins flags forbidden transitions at runtime+xprop reduces dangerous X-optimismcreate_isolation_rule -isolation_sense low|high, and verification checks that the isolation enable arrives before power is removed and that the clamped value matches the UPF intent.