ppa-measure · diff
git:20261105.13b7a3f to git:20261106.a0a79f8
24 added, 236 removed. Audit A to A.
---
name: ppa-measure
- description: Read PPA artefacts that the tools already wrote — STA reports, power reports, area reports, DEF/GDS summaries — and turn them into an evidence-linked measurement report of canonical `vibeic.ppa.metric.v1` records, each carrying its scope and its source hash. Use when the user says "measure PPA", "what is the real area/power/timing", "collect the PPA numbers", "post-route PPA", or when a report must state what was measured AND what was not.
+ description: Read PPA artefacts that the tools already wrote — STA reports, power reports, area reports, DEF/GDS summaries — and turn them into an evidence-linked measurement report of canonical `vibeic.ppa.metric.v1` records, each carrying its scope and its native source path. Use when the user says "measure PPA", "what is the real area/power/timing", "collect the PPA numbers", "post-route PPA", or when a report must state what was measured AND what was not.
---
- # PPA Measure
-
- ## The boundary this skill lives inside
-
- This skill produces an **evidence-linked report**. It never produces a
- gate verdict, and no part of it asks the model to settle whether the design is
- good enough. The pass/fail call belongs to a deterministic program —
- `_ppa/feasibility.py` per `docs/PPA_INTERFACES.md` §4 — and this report only
- supplies the records that program consumes.
-
- Concretely, this skill may say *"setup WNS is -0.124 ns at the `ss` corner,
- parsed from `<file>` whose sha256 is `<hash>`"*. It may not say what that number
- means for the release. If a caller wants the release answer, the handoff is the
- gate program, and the report says so on its own `Verdict authority:` line.
-
- The reason for the split is not tidiness. A number that a model produced and a
- number a parser produced look identical once they are in a table, and only one
- of them can be re-derived from an artefact. Keeping the verdict in a program
- keeps the re-derivable half load-bearing.
-
- ## When to use
-
- Trigger when the user:
- - Has finished a synthesis / PnR / STA / power run and wants the numbers collected
- - Asks what the design's real post-route PPA is, as opposed to an early estimate
- - Needs the inputs a feasibility gate or a Pareto comparison will read
- - Needs an honest statement of PPA coverage — which metrics exist and which do not
-
- **Not** this skill: an early, pre-synthesis guess. That is `ppa-predict`, and the
- two must never be mixed in one table. A `ppa-predict` number carries status
- `ESTIMATED`, which `docs/PPA_INTERFACES.md` §2 forbids from ever entering final
- PPA; a `ppa-measure` number carries status `MEASURED` and a source hash.
-
- ## Inputs to gather
-
- 1. The design / run directory, and which stage it reached (synthesis, post-place,
- post-route, post-route-extracted)
- 2. The artefacts themselves: STA report(s), power report(s), area report, DEF or
- GDS summary — by path, one per view
- 3. The analysis views actually run: process corner, voltage, temperature, RC
- corner, clock, check type
- 4. The activity basis for any power number: vectorless, or a named VCD/SAIF
- 5. Whether an extracted-parasitics run exists, or only the pre-extraction estimate
-
- If an input is absent, that absence is a result. Record it as `NOT_MEASURED`
- with a `reason`; do not substitute a value from a different stage and do not
- leave the row out.
-
- ## Workflow
-
- 1. **Enumerate the views before reading any number.** Write down the list of
- (stage, mode, process, voltage, temperature, rc_corner, clock, check) tuples
- the run was supposed to cover. This list is the denominator; without it a
- report of three green rows cannot be told apart from a run that only produced
- three rows.
- 2. **Parse, never recompute.** Each number is lifted from an artefact by a parser
- and hashed as parsed. A number you arrived at by arithmetic is `DERIVED` and
- must carry its formula alongside; a number you arrived at by judgement is not
- a measurement at all and does not belong in the table.
- 3. **Bind every record to its source.** `source.path`, `source.sha256`,
- `source.tool`, `source.parser`. A record without a resolvable source is
- `INVALID`, not `MEASURED`.
- 4. **Keep the taxonomy split.** Synthesis area and post-route area are different
- metrics, not two samples of one. Vectorless power and VCD power are different
- metrics. Pre-extraction and post-extraction timing are different metrics.
- Collapsing them is the single most common way a PPA table becomes a fiction.
- 5. **Emit the coverage line.** Count `MEASURED`, `NOT_MEASURED`,
- `NOT_APPLICABLE`. An unstated denominator is how "we measured everything"
- and "we measured what happened to be lying around" print the same page.
- 6. **Generate the report from the records — do not typeset it.** The markdown
- below is emitted by `programs/ppa_report_gen.py`, which reads the records and
- writes both the human page and the `claims.json` that binds every sentence in
- it to the artefact behind it. See the next section.
- 7. **Hand off.** Name the program that will read these records and state that the
- verdict is its output, not this document's.
-
- ## Where the records come from
-
- Everything below consumes `vibeic.ppa.metric.v1` records, and until this section
- existed nothing here said who WRITES them. Two producers do, and both need
- `--json` — each writes its bundle only when handed one, so an invocation without
- it runs and emits nothing:
-
- ```bash
- # sign-off evidence: the physical, reliability and equivalence axes, read from
- # a completed run's own artefacts (drc_signoff.json, lvs_verdict.json, antenna,
- # IR, EM, equivalence) and emitted as canonical metric names
- python3 plugins/vibe-ic/programs/ppa_signoff_records.py <project> \
- --json reports/ppa/records/signoff.json
-
- # the design-for-ECO spare population, which the eco_readiness axis refuses from.
- # It takes the two spare artefacts BY PATH, not a project root, and --preservation
- # is optional precisely because step 34 writes it conditionally.
- python3 plugins/vibe-ic/programs/ppa_eco_spare_records.py \
- --spare-plan phase3/stage3/pnr/spare_cells.json \
- --preservation reports/spare_preservation.json \
- --stage stage4 --json reports/ppa/records/eco_spares.json
- ```
-
- `ppa_signoff_records` is the missing half of `ppa_feasibility_check`: that gate
- proves its axes from canonical metric names, and before this producer existed
- seven of those names were written by nothing in this tree — so a run that
- measured DRC, LVS, antenna, IR, EM and equivalence still had no record saying
- so. `ppa_eco_spare_records` exists because a place-and-route search once deleted
- a design's entire spare-cell population and scored BETTER for it — smaller area,
- lower power, and no axis anywhere saying the layout could no longer be repaired
- by a metal-only ECO. The axis is the refusal; this program is the evidence it
- refuses from.
-
- THEY ARE RUN HERE AND NOT BY A FLOW CLAUSE, and the reason is recorded in the
- flow yaml at step 37.5ic rather than guessed at. 37.5ic is the first step where
- every axis they read coexists, which is why wiring them there was tried on
- 2026-08-25 — but one of the files they read, `reports/spare_preservation.json`,
- is written by a CONDITIONAL clause at step 34 and is declared in no step's
- `required_outputs`. A gate reading it therefore makes that path
- produced-consumed-undeclared (d7 `W2`), and the repair reached for was an
- UNCONDITIONAL `required_outputs` row over a conditionally-produced artefact —
- which reds every spare-less design over a file nobody owes. What would make them
- wireable is stated there too: a step that produces
- `reports/spare_preservation.json` unconditionally and declares it. Until then
- the honest runner is this line, and an operator who has a completed run in hand
- has exactly the inputs the clause could not guarantee.
+ # Measure PPA
- ## The report is generated, and then it is checked
+ Read the selected rows in `<project>/reports/flow_engine.json` and open their
+ native `reports`. `flow/steps.json` names each current tool and
+ `flow/native_limits.json` retains its original limits. Review `tool_ran`,
+ `native_steps`, `native_metrics`, and each metric's value, operator and limit.
+ A missing native measurement is `NOT_MEASURED`. AI reviews the engineering
+ meaning of every result and fixes problems in the design, tool or plugin.
```bash
- # records (file or directory of vibeic.ppa.metric.v1) -> page + claims
- python3 plugins/vibe-ic/programs/ppa_report_gen.py <records.json|records_dir> \
- --out reports/ppa/report.md --claims reports/ppa/claims.json \
- --json reports/ppa/report_run.json
-
- # the page may not say more than the claims support
- python3 plugins/vibe-ic/programs/ppa_page_claim_check.py reports/ppa/report.md \
- --claims reports/ppa/claims.json --cite-numbers
+ python3 programs/flow_engine.py report --project <project>
```
- **Why a generator and not a template.** A report is the last place a number is
- touched before a human believes it, and a sentence carries implications an
- artefact does not. `ppa_report_gen` is a gate, not a formatter:
-
- | rc | meaning |
- |---|---|
- | 0 | the page and `claims.json` were written |
- | 1 | `[REFUSE]` — a record cannot support the sentence it would become: a `NOT_MEASURED` carrying a `value`, a numeric sentinel (`0` / `-1` / `""`) standing in for "not measured", a collapsed single PPA score, or two records producing one claim id from different facts |
- | 2 | `[CANNOT CHECK] NO_INPUT` (the path is not readable) or `EMPTY_CORPUS` (the path is there and holds no record — the zero is STATED, with the path it counted) |
-
- An rc=1 is not a formatting problem to route around by writing the page by
- hand. It is the generator naming a record you must fix upstream, in step 2 or
- step 3.
-
- **`claims.json` is the runnable half of the prose.** Every sentence a reader
- will believe carries `[claim:<id>]`, and each claim names the artefact path and
- hash that supports it. `ppa_page_claim_check --cite-numbers` then re-runs the
- page every landing: a claim whose status outruns its weakest cited evidence, a
- citation that resolves to nothing, and a sentence stating a number with no
- citation are each rc=1. Prose cannot be re-read every landing; a citation can.
-
- Pass the records path the way it should appear in the published page — the
- generator quotes the path it was given, so a `/tmp/...` argument puts an
- absolute machine-local path into the artefact and trips this skill's
- `X_no_volatile_paths` cross-check.
-
- The `NOT_MEASURED` rows are printed by the generator, with their `reason`,
- never dropped. That is the same rule as step 5 and the "Do not" list below,
- enforced once in a program instead of remembered three times.
-
- ## Do not
-
- - Do not write `0`, `-1` or an empty string to mean "not measured". There are no
- numeric sentinels; there is a status field and it is `NOT_MEASURED` with a reason.
- - Do not omit a row because it has no number. A missing row and a measured zero
- are different facts and a reader cannot tell them apart after the fact.
- - Do not compare two numbers whose `scope` differs. That comparison is
- `UNDETERMINED`; it does not have a winner.
- - Do not carry a `ppa-predict` estimate into this report, in any column, under
- any heading.
- - Do not restate a program's exit code as your own conclusion. Quote it with its
- program name and its rc.
- - Do not hand-write the report page when `ppa_report_gen` refuses your records.
- The refusal is about a record, and typing the page yourself publishes exactly
- the sentence the refusal was protecting a reader from.
- - Do not report a number whose artefact you could not open. "I could not read it"
- and "I read it and it was empty" are different results and must print differently.
-
- ## Output format
-
- The deliverable is one markdown report. The template below is the whole shape;
- `<...>` are the parts you fill in.
-
- # PPA Measurement — <design> @ <stage>
-
- Verdict authority: _ppa/feasibility.py — this report states no pass/fail of its own.
-
- ## Summary
- <two or three sentences: which stage, which views were run, what is absent>
-
- Coverage: MEASURED=7 NOT_MEASURED=2 NOT_APPLICABLE=1
-
- ## Measurements
-
- | metric | status | value | unit | stage | corner | source sha256 |
- |---|---|---|---|---|---|---|
- | timing.setup.wns_ns | MEASURED | -0.124 | ns | post_route_extracted | ss/1.62V/125C | sha256:3f9a1c7d |
- | power.total_mw | NOT_MEASURED | - | - | post_route_extracted | - | - |
-
- ## Not measured, and why
-
- | metric | reason |
- |---|---|
- | power.total_mw | no activity basis: neither a vectorless run nor a VCD exists |
-
- ## Evidence
-
- | artefact | sha256 | tool |
- |---|---|---|
- | phase3/stage3/sta/sta_mcorner_ocv.rpt | sha256:b204e8a1 | opensta |
-
- Next: run /ppa-diagnose
-
- Every row of `## Measurements` is the human view of one canonical record. The
- records themselves are the machine deliverable and go in the report's JSON
- sidecar, one per row, in the frozen shape:
+ Collect the numbers from the tool's actual outputs. Name the design, top,
+ stage, corner, mode, Liberty files, activity basis and parasitics used.
- ```json
- {
- "schema": "vibeic.ppa.metric.v1",
- "metric": "timing.setup.wns_ns",
- "status": "MEASURED",
- "value": -0.124,
- "unit": "ns",
- "scope": {"stage": "post_route_extracted", "mode": "functional",
- "process": "ss", "voltage_v": 1.62, "temperature_c": 125,
- "rc_corner": "max", "clock": "clk", "check": "setup"},
- "source": {"path": "phase3/stage3/sta/sta_mcorner_ocv.rpt",
- "sha256": "sha256:77c1de40", "tool": "opensta",
- "parser": "ppa_metric_extract.py"}
- }
- ```
+ - Read cell area and cell counts from the synthesis or physical database.
+ Distinguish cell area, macro area, core area and die area, and keep their units.
+ - Read setup and hold WNS, TNS, unconstrained paths and DRV from native timing
+ reports. Identify whether timing used estimated or extracted parasitics.
+ - Read power with its voltage, process/temperature corner and switching-activity
+ basis. Missing activity is a limitation, not measured design power.
+ - Read wirelength, via counts, congestion and physical violations from the
+ native implementation reports when those measures were requested.
- Serialize with `programs/_ppa/canonical_json.py` and nothing else — sorted keys,
- no spaces, UTF-8, no NaN. A hash taken over a hand-rolled `json.dumps` is a hash
- of a different document than the one the next reader will re-serialize.
+ Write a table with each measured value, units, original requirement, result
+ and source path. Report an absent value as `NOT_MEASURED`. Do not replace it
+ with zero, an estimate, a stale report or a hash-bound claim. Explain any
+ reused measurement's actual scope. Review physical plausibility and state
+ what the run did not measure. Early estimates belong in `ppa-predict`.