Day 24 / 25
← Day 23 Day 25 →
HomeVerificationDay 24 — Low Power Verification
Track 6 — Advanced Verification Topics

Low Power Verification

By EcrioniX · Updated June 2026

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.

⏱ 38 min read📖 Day 24 of 25🎯 UPF · Isolation · Retention · PST · Questa PA
Always-ON Domain VDD_AO = 1.0V (constant) PMU / PCU Boot ROM / Retention Ctrl ISO ISO Power-Down Domain VDD_PD = 0.9V / OFF DSP Core / AI Accelerator Retention Registers (RET) VDD_RET (always on, shadow latch) Shadow UPF Power Domain Architecture — AO + PD with Isolation & Retention
Contents
  1. UPF Power Intent Basics
  2. Isolation Cell Insertion & Verification
  3. Retention Register Save / Restore
  4. Power State Table (PST) Coverage
  5. X-Propagation from Ungated Domains
  6. Questa Power Aware Simulation Flow
  7. Common Low Power Bugs
  8. FAQ

1. UPF Power Intent Basics

IEEE 1801 create_power_domain add_port_state supply network

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}
What add_port_state does: it binds a named logical state (FULL_ON, OFF, PARTIAL) to a concrete voltage. The PST then references these logical names so verification tools know exactly when a domain is live versus powered down.

UPF Structural Hierarchy

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 ConstructPurposeKey Argument
create_power_domainGroup RTL elements into a domain-elements {}
create_supply_netDeclare a logical wire for a supply rail-domain
create_pstPower State Table declaration-supply_nets {}
add_pst_stateEnumerate legal supply state combos-state {net state}
create_isolation_ruleRequire isolation at crossing-isolation_sense
create_retention_ruleRequire retention on flip-flops-target_domain
map_retention_cellBind rule to a library cell-lib_cell

2. Isolation Cell Insertion & Verification

create_isolation_rule isolation_sense SVA check

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 Cell Requirements:
## 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)

SVA Assertion: Output Clamped to Safe Value

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 it in: use 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.

3. Retention Register Save / Restore

save / restore shadow latch sequence check

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.

  • SAVE sequence: Assert retention enable (ret_en = 1) while VDD_PD is still at full voltage. The shadow latch captures the primary FF state on the next clock edge.
  • Hold ret_en = 1 for at least 1 full clock period to guarantee setup/hold on the shadow latch.
  • De-assert the main clock (or gate it) and then drop VDD_PD. The primary FF is now X but shadow latch holds the value.
  • RESTORE sequence: Bring VDD_PD back up. Wait for the power-good strobe (PG asserted).
  • Assert restore enable (ret_restore = 1). The shadow value loads back into the primary FF on the next clock edge.
  • De-assert 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
    Wrong polarity is the #1 retention bug: if UPF says -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.

    4. Power State Table (PST) Coverage

    PST rows covergroup illegal transitions

    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 NameVDD_AOVDD_PD (DSP)VDD_MEMDescription
    NORMALFULL_ONFULL_ONFULL_ONAll domains active
    DSP_SLEEPFULL_ONOFFFULL_ONDSP powered down, memory live
    MEM_RETAINFULL_ONOFFMEM_ON (retention)DSP off, memory in self-refresh
    DEEP_SLEEPFULL_ONOFFOFFOnly 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: any transition that hits an 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.

    5. X-Propagation from Ungated Domains

    X-pessimism PA simulation X-optimism risk

    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.

    Containment Strategy

    // 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);

    6. Questa Power Aware Simulation Flow

    vlog vopt vsim -pa upf_mode

    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 Checks Performed During Simulation

    PA Check CategoryWhat It DetectsSeverity
    Domain crossing without isolationSignal crosses power boundary with no isolation ruleError
    Isolation enable from wrong domainiso_en driven by PD_domain (powered off = X)Error
    Retention enable polarity mismatchRTL activates on opposite edge vs. UPF intentError
    Level shifter missingSignal crosses between different voltage domains without LSWarning
    PST illegal state enteredSupply combo not listed in any add_pst_state rowError
    Retention restore before PGrestore_en asserted before power-good settlesError
    Clamp value mismatchIsolation cell drives value different from UPF -clamp_valueError
    Coverage closure with PA: use 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.

    7. Common Low Power Bugs

    missing isolation wrong polarity restore timing

    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.

    Bug 1: Missing Isolation Cell

    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.

    How it hides: in pure RTL simulation a missing isolation cell only surfaces if the testbench actually switches the domain off AND a consuming always-on register captures the X. Many teams never trigger this because they never drop the domain supply in simulation. PA elaboration catches it structurally — no simulation required.

    Bug 2: Wrong Retention Enable Polarity

    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}

    Bug 3: Incorrect Restore Timing

    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.

    Verification technique: model PG assertion as a configurable number of cycles after VDD ramp. Run directed tests with PG delay = 1, 2, 4, and 10 cycles. Assert that ret_restore never precedes PG regardless of the delay value. This catches the hardcoded timing assumption before tapeout.

    Summary Bug Table

    BugSymptom in SimCaught By
    Missing isolation cellX on AO domain input after PD offPA elaboration / ISO_ENABLE SVA
    Wrong retention polarityRestored value is stale or Xcheck_retention_cycle task / PA runtime
    Restore before PGMetastability — passes sim, fails siliconRET_RESTORE_AFTER_PG SVA with PG delay sweep
    iso_en from wrong domainiso_en becomes X when PD off → X propagatesPA check UPF-017
    PST illegal transitionRuntime PA error, potential data lossPST covergroup illegal_bins
    Level shifter missingLogic errors at domain boundaryPA elaboration UPF-019

    Day 24 Key Takeaways

    FAQ

    What is the purpose of an isolation cell in low power design?
    An isolation cell clamps the output of a powered-off domain to a safe, known logic value (0 or 1) so that logic in an always-on domain that receives those outputs does not see spurious transitions or X values. Without isolation, a powered-off driver floats and the receiving gate can oscillate, causing incorrect state changes or short-circuit current. In UPF you specify the safe value with create_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.
    What bugs are caught by power state table (PST) coverage?
    A power state table enumerates all legal combinations of supply states across every power domain. PST coverage checks that every row in that table is actually exercised during simulation. Bugs caught include: (1) missing isolation — a domain is powered off but the isolation enable was never asserted; (2) unreachable states — a supply rail has no test sequence that drives it to off; (3) illegal transitions — the design enters a power combination not listed in the PST, which UPF tools flag as a violation; (4) restore-before-power — the retention restore command fires before VDD has stabilised, corrupting register contents.
    Why do X values appear during low power simulation and how are they contained?
    When a power domain is turned off, all flip-flop outputs in that domain become X because the supply is no longer driving them. These Xs propagate through combinational logic into always-on domains if no isolation cell is present or if the isolation enable is asserted too late. They are contained by: (1) correct isolation cell insertion — the cell output is forced to the safe value before power drops; (2) retention registers — state is saved before power-off so the restore value is valid, not X; (3) power-aware (PA) simulation with X-pessimism reduction — Questa PA uses UPF supply semantics to drive 0/1 instead of X on domain crossings that have valid isolation.
    What is the correct sequence for retention register save and restore in UPF?
    The IEEE 1801 mandated sequence for save is: (1) assert retention enable (SAVE command in UPF) while the supply is still fully on; (2) hold enable asserted for at least one clock edge so the shadow latch captures data; (3) power down the primary supply. For restore: (1) power up the primary supply and wait for it to stabilise (PG rail reaches full voltage); (2) assert the restore enable (RESTORE command); (3) hold for at least one clock edge so the master latch loads the shadow value; (4) de-assert restore enable before normal clocking resumes. Verifying wrong polarity of the retention enable or swapped save/restore ordering are the two most common bugs caught by UPF-aware simulation.
    ← Previous
    Day 23 — CDC Verification
    Next →
    Day 25 — DV Interview Questions