Skip to main content

Power Analysis

Integration type: Integrated tool. rb power is built around OpenROAD today; the backend interface (BasePower + _POWER_BACKENDS registry) is designed so commercial flows can be added without changing the schema.

External binary required: openroad25Q1 (same as rb pnr).

See also: Place-and-Route, Synthesis.

rb power runs OpenROAD's report_power on the tech-mapped netlist produced by an upstream rb synth run. It supports three activity sources — built-in static defaults, synthetic global toggle/duty, and per-signal activity from a SAIF or VCD trace — and reports total / internal / switching / leakage power.

Where the numbers come from

rb power runs on either the post-synth netlist (default) or the post-PnR routed netlist with SPEF parasitics. The netlist-source: field in power.yaml selects which.

Aspectnetlist-source: synth (default)netlist-source: pnr
Netlistsynth_netlist.v from rb synth<top>.routed.v from rb pnr
SDCUser-supplied constraints:Post-CTS <top>.routed.sdc (or user constraints: if set)
ParasiticsNone<top>.routed.spef read via read_spef
Clock treeFlat (no CTS buffers)Real CTS-buffered tree
Wire capacitanceNone (zero)Extracted from routing
Internal powerGate-accurate (Liberty)Gate-accurate (Liberty)
LeakageGate-accurate (Liberty)Gate-accurate (Liberty)
SwitchingUnder-estimatedRealistic
Upstream run neededrb synthrb synth + rb pnr

LEF is loaded in both cases because OpenROAD's gate-level read_verilog requires a technology view in its in-memory DB; report_power itself only consults Liberty (per-cell internal/switching coefficients, leakage tables).

For early PPA exploration where you just want a leakage + activity-aware switching estimate, the synth-source path is fast and cheap. For sign-off-grade switching numbers where the clock tree matters, use the pnr-source path.

Supported backend

Today only openroad is wired up. The tool: field in power.yaml selects it; the dispatch is a registry (_POWER_BACKENDS in runner/power_runner.py), so a commercial backend is one line plus a BasePower subclass under tools/power_<name>.py.

Power config: power.yaml

rtl-buddy-filetype: power_config

runs:
- name: "demo_power_static"
desc: "Static power on Nangate45 typ corner"
tool: "openroad"
mode: "static"
synth: "demo_synth_nangate45"
synth-path: "../../synth/demo/synth.yaml"
constraints: "../../synth/demo/constraints.sdc"
platform: "nangate45_typ"
reglvl: 1000

- name: "demo_power_dynamic_synthetic"
desc: "Dynamic power with synthetic global activity"
tool: "openroad"
mode: "dynamic"
synth: "demo_synth_nangate45"
synth-path: "../../synth/demo/synth.yaml"
constraints: "../../synth/demo/constraints.sdc"
platform: "nangate45_typ"
activity:
default-toggle-rate: 0.2
default-static-prob: 0.5
reglvl: 1000

- name: "demo_power_dynamic_saif"
desc: "Dynamic power driven by simulation SAIF"
tool: "openroad"
mode: "dynamic"
synth: "demo_synth_nangate45"
synth-path: "../../synth/demo/synth.yaml"
constraints: "../../synth/demo/constraints.sdc"
platform: "nangate45_typ"
activity:
saif: "../../verif/demo/artefacts/csr_smoke/dump.saif"
scope: "tb_top/u_dut"
reglvl: 1000

- name: "demo_power_postpnr"
desc: "Post-PnR power with SPEF parasitics"
tool: "openroad"
mode: "dynamic"
netlist-source: "pnr"
pnr: "demo_pnr_nangate45"
pnr-path: "../../pnr/demo/pnr.yaml"
platform: "nangate45_typ"
activity:
saif: "../../verif/demo/artefacts/csr_smoke/dump.saif"
scope: "tb_top/u_dut"
reglvl: 1000

Fields

FieldDescription
nameRun identifier; used on the command line and in artefacts/<name>/
descHuman-readable description
toolBackend tool name — only openroad is supported today
mode"static" or "dynamic". Static skips activity entirely; dynamic applies one of the activity sources below
netlist-source"synth" (default) or "pnr". Selects post-synth vs post-PnR netlist
synthName of the upstream rb synth entry — required when netlist-source: synth
synth-pathPath to the synth.yaml, resolved relative to power.yaml — required when netlist-source: synth
pnrName of the upstream rb pnr entry — required when netlist-source: pnr
pnr-pathPath to the pnr.yaml, resolved relative to power.yaml — required when netlist-source: pnr
constraintsSDC path (required for synth source; optional for pnr source — defaults to routed.sdc)
platformcfg-pnr-platforms entry name — reused for Liberty + corner
activity.saifPath to a SAIF v2 file (mutually exclusive with vcd)
activity.vcdPath to a VCD trace (mutually exclusive with saif)
activity.scopeHierarchical scope passed to OpenROAD's -scope flag when reading a trace. Only valid alongside saif/vcd — set without a trace, it raises a config-load error
activity.default-toggle-rateSynthetic global activity rate (used when mode: dynamic and no trace is supplied). Default 0.1
activity.default-static-probSynthetic global duty cycle. Default 0.5
reglvlRegression level for filtering (same semantics as rb synth / rb pnr)

Activity source resolution

Decision happens at config load (PowerConfig.get_activity_source()), not in the backend, so every backend agrees on the strategy:

modeactivity.saifactivity.vcd→ resolved source
static(ignored)(ignored)default — no activity command emitted
dynamicsetsaif — backend emits read_saif
dynamicsetvcd — backend emits read_power_activities -vcd
dynamicsynthetic — backend emits set_power_activity -global

The resolved source is recorded in the results table as the Activity column.

Capturing a SAIF from simulation

rb saif converts an FST or VCD waveform trace into SAIF v2.0 (backward direction):

# 1. Run the sim in debug mode so it produces an FST
rb -M debug test csr_smoke

# 2. Convert the FST to SAIF
rb saif verif/<suite>/artefacts/csr_smoke/dump.fst verif/<suite>/artefacts/csr_smoke/dump.saif

# 3. Reference it from power.yaml (activity.saif: ...) and run rb power
rb power demo_power_dynamic_saif -c power/demo/power.yaml -l 1000

The converter walks the trace hierarchy, computes per-bit T0/T1/TX/TZ time-in-state and TC toggle counters, and emits SAIF in the trace's native timescale so values stay exact integers. Memory-array elements (FST [N] vars) are skipped — they don't correspond to gate-level nets in the synth netlist.

When the SAIF is rooted at the testbench (e.g. tb_top), pass scope: "tb_top/u_dut" so OpenROAD knows where in the SAIF tree the design top lives.

Running power

# All runs in the default ./power.yaml
rb power

# A single run from a specific config
rb power demo_power_dynamic_saif -c power/demo/power.yaml

# Reglvl-gated runs
rb power -c power/demo/power.yaml -l 1000

# List runs without executing
rb power -c power/demo/power.yaml --list

Regression

# Default: ./power_regression.yaml
rb power-regression

# Explicit config
rb power-regression -c power_regression.yaml -l 1000

power_regression.yaml:

rtl-buddy-filetype: power_reg_config
power-configs:
- power/demo_block_a/power.yaml
- power/demo_block_b/power.yaml

Results table

Power Results Summary
┏━━━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━┳━━━━━━━┳━━━━━━━┳━━━━━━━┓
┃ Power ┃Res… ┃Desc ┃Mode ┃Source ┃Activity┃Total ┃Intern…┃Switch.┃Leakag…┃
┡━━━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━╇━━━━━━━╇━━━━━━━╇━━━━━━━┩
│ …static│PASS │… │static│synth │default │422 µW│ 344 µW│22.6 µW│54.9 µW│
│ …synth │PASS │… │dynam.│synth │synth. │515 µW│ 363 µW│93.1 µW│59.3 µW│
│ …saif │PASS │… │dynam.│synth │saif │906 µW│ 646 µW│ 203 µW│57.1 µW│
│ …postp │PASS │… │dynam.│ pnr │saif │1.110 │ 708 µW│ 341 µW│59.8 µW│
│ │ │ │ │ │ │ mW │ │ │ │
└────────┴──────┴──────┴──────┴───────┴────────┴──────┴───────┴───────┴───────┘

Columns:

  • Modestatic or dynamic from power.yaml.
  • Sourcesynth (post-synth netlist) or pnr (post-PnR ODB). Set by netlist-source:.
  • Activity — resolved activity strategy: default (static), synthetic, saif, or vcd.

Power values auto-scale between W / mW / µW / nW for readability. Columns only render when at least one run populates them.

Artefacts

Per-run outputs land under power/<suite>/artefacts/<run>/:

FileContents
power.logFull OpenROAD log
power.tclTemplated Tcl handed to OpenROAD
power.rptRaw report_power output (Total line is what the parser consumes)

Pass/fail detection

A run is PASS when:

  1. openroad exits with code 0.
  2. The log has no [ERROR ...] lines.
  3. The Total line in power.rpt parses cleanly.

Otherwise FAIL is returned with the parser/exit-code message. SKIP is returned when the run's reglvl is above the -l filter or when tool: is not in the backend registry.

Out of scope (today)

  • Per-instance power breakdown. The current parser only takes the Total line; per-module/per-instance numbers are in power.rpt but not surfaced.
  • Multi-corner power signoff. One corner per run; multi-corner needs a richer schema.
  • RTL-level power estimation (Joules-style). Would need a different backend; the activity schema would extend without breaking.