Skip to main content

study-pr

Run a measurement study on the flow and publish it as a closed, reference-only bazel-orfs pull request. Covers the two kinds of patch a study carries into OpenROAD, ORFS or yosys (instrumentation that stays on the study branch, candidate fixes carried in patches/ one concern each), the edit/measure loop, the noise discipline that makes a runtime or QoR number trustworthy (byte-identical inputs, fixed and witnessed knobs, repeats with 2σ and a stated resolution, an idle machine asserted not hoped), the harness shape (manual flow targets, unit-tested parsers, a report that discovers results and says "not yet measured"), and the PR shape (study/<slug> branch, the finding as the title, graphs and tables in the body with images committed to the branch, raw data as a CSV comment, a closing comment). Use when asked to "study", "measure", "find out where the time goes", "A/B a knob or patch", or to document design decisions that go into an upstream PR.

Jump to install

Source facts

Repository
The-OpenROAD-Project/bazel-orfs
Last source activity
September 10, 2026 at 18:48
Detected SKILL.md language
English
Stars
26
Forks
23

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
study-pr
description
Run a measurement study on the flow and publish it as a closed, reference-only bazel-orfs pull request. Covers the two kinds of patch a study carries into OpenROAD, ORFS or yosys (instrumentation that stays on the study branch, candidate fixes carried in patches/ one concern each), the edit/measure loop, the noise discipline that makes a runtime or QoR number trustworthy (byte-identical inputs, fixed and witnessed knobs, repeats with 2σ and a stated resolution, an idle machine asserted not hoped), the harness shape (manual flow targets, unit-tested parsers, a report that discovers results and says "not yet measured"), and the PR shape (study/<slug> branch, the finding as the title, graphs and tables in the body with images committed to the branch, raw data as a CSV comment, a closing comment). Use when asked to "study", "measure", "find out where the time goes", "A/B a knob or patch", or to document design decisions that go into an upstream PR.
## What a study PR is A study answers one question with data and is published as a pull request that is **opened to be read, then closed**. The PR body is the report. The branch carries everything needed to re-take the numbers: harness, patches, raw results, figures. Nothing merges from it; anything durable is split into its own PR first (one concern per PR). There is **no write-up in the tree**, no `.md` under `docs/` or `ideas/`: a table typed by hand rots the first time the campaign is re-run, and the report generator is what keeps a partial study from reading as a complete one. Precedents to copy the shape from: #965 (gpl runtime), #968 (threads policy), #958/#963 (pre-route pessimism, and its merged harness under `test/pre_route_pessimism/`), #966 (two probes, no BUILD at all). ## Two kinds of patch, two homes A study patches OpenROAD, ORFS or yosys for two different reasons, and they must not be mixed in one file: | kind | purpose | where it lives | fate | | --- | --- | --- | --- | | instrumentation | timers, counters, extra reports; changes no result | the study branch only, as a `patches/00NN-*-instrument-*.patch` or a BYO checkout | dies with the branch; never an upstream candidate | | candidate fix | makes something faster or better | `patches/00NN-<tool>-<concern>.patch`, one concern per file, wired into `MODULE.bazel` (`archive_override(patches=…)`) or `ORFS_PATCHES` in `orfs_source.bzl` | measured alone; if it wins it is a listed upstream candidate, if it loses it is written up and **not carried** | Every arm that claims to measure a fix runs a **real binary** built with the patch through the module graph, not a hypothesis. Each candidate patch gets a header saying what it fixes, that it is not upstreamed, and how it retires, plus a Chesterton's Fence paragraph in the PR body: why the existing code looked reasonable, so the next reader does not draw the same wrong conclusion. Instrumentation should extend a pattern the tool already has (rsz's `utl::DebugScopedTimer`, ORFS's `report_metrics`) rather than invent one. Whether it gets a `patches/` file or stays in a checkout, its output is what the harvester parses, so pin its format before running arms. Upstream repositories stay read-only: candidates are **listed** in the body with their measured effect, never opened. The human decides when. ## The edit/measure loop - Iterate with a bring-your-own binary (`byo-openroad` skill) or a deployed `_deps` tree; record arms with the carried patch through the module graph. - Time from the log, not from bazel: `--@bazel-orfs//:log_timestamps` stamps every line with elapsed seconds, and a cached stage rebuilds for free, so a bazel wall clock measures the cache. Use the tool's own runtime lines (`RSZ-0505` style) and progress tables as the primary signal, `perf record` on the main thread as the cross-check that the timers add up (a BYO build wants `--copt=-g1 --copt=-fno-omit-frame-pointer`). - Preflight the code path before an hours-long run (`orfs-deps-preflight`): a `SKIP_*` default that routes around the thing under test produces a clean, fast, meaningless number. For repair questions, the traps in `repair-timing-grt` apply. - Big reproducers that cannot be committed go in a bazel-orfs GitHub release and are linked from the body. ## Noise discipline A number without these is an anecdote: 1. **Byte-identical inputs.** Build the stage input once and share it via `previous_stage` (see `study_arm` in `test/pre_route_pessimism/arms.bzl`); only the arm's variable changes. 2. **Fixed, witnessed knobs.** Pin `-threads` for every arm and pin the process with `taskset`; read the knob back from the log (`ORD-0030 Using N thread(s)`) and **discard** a sample whose witness disagrees with its arm rather than averaging it in. 3. **An idle machine, asserted.** Refuse to record when the 1-minute load average exceeds a threshold at arm start, and store `loadavg_at_start` in every result. A concurrent compile turned 1.8 s of placement into 16.8 s. 4. **Repeats, 2σ, resolution.** Report individual repeat values, not a mean. Spread is 2σ; the resolvable difference at `k` runs per arm is `2σ·sqrt(2/k)`. Inside it the verdict is **"did not resolve"**, never "no effect". 2σ of a single run is zero, which is honest. 5. **QoR as a hash where none is claimed, as picoseconds where some is.** A byte-identical ODB (or ORFS's `result_sha1`) is the strongest statement a change can make. Where QoR moves, quote `clk_period - WNS` in ps plus TNS and area, never a percentage of WNS. A design that closes before the step under test measures nothing about it. 6. **Tens of seconds per case.** A 5 s baseline drowns in noise; pick vehicles where the step is long, or build a synthetic sweep. 7. **One result file per sample** `(design, stage, arm, repeat)` so a campaign is resumable and a new arm appears in the tables by being run. ## Harness shape Under `test/<study_slug>/`: - `BUILD.bazel` with every flow-running target `tags = ["manual"]` (`bazelisk query` proves it), and non-manual `py_test`s over every parser. - `campaign.py` (run arms, idle gate, pinning, resumable results), `harvest.py` or `elapsed.py` (parse logs into JSON), `report.py` (generate the PR body's tables from whatever `results/` holds), `plots.py`. - The report **discovers** results; no result is a declared build input. With `results/` deleted, `report.py` fails with "run the campaign first", and a section with no data renders as **"Not yet measured"**. - Results and figures under `docs/studies/<slug>/` (or `test/<slug>/results/`), committed on the study branch. If the raw data is a few hundred rows, a fenced ```csv comment on the PR is enough and nothing is committed under `results/` (#968). - Probes that load an ODB source `stage_src.tcl`: `RESULTS_DIR` derives from the package **declaring** the run, so a probe declared elsewhere fails with `ORD-0007`. ## The PR **Branch** `study/<question-slug>`, with the upstream PR number appended when the study is about one (`study/size-down-fanout-11320`). Commits use a `study(<area>):` scope. Never delete the branch: the body's image URLs point into it. **Title**: the finding as a sentence, `study: where a single global placement spends its time, with the patches it points at`. Not the question. **Body**, in this order: 1. Disposition paragraph, first line, bold: *This is a documentation PR: it is here to be read, then closed.* Name what it carries (patches, harness, figures) and where anything durable went (`#NNN merged`). 2. TL;DR / the finding, in the form an upstream PR can quote. 3. Method: unit of measurement, machine (cores, SMT, memory), thread pin, repeats, what was held byte-identical. 4. Results: **every quantitative claim in a table**; a figure's alt text states the claim. Recurring shapes: per-arm `wall | delta | 2σ | verdict | same result`; per-serial-item attribution `item | wall | what it is`; the quadrant table `hypothesis | QoR | runtime | status` where status is one of *carried, patches/00NN* / *measured, refuted, not carried* / *not done, upper bound X*; cost-to-re-run `axis | target | leaves | wall`. 5. Negative results kept: "What did not move the needle", "Three things this rules out". A refuted patch gets its numbers and the reason the evidence misled. 6. Limits and caveats, written before anyone asks. 7. Upstream candidates: `PR-to-be | patches | measured effect, alone`, with the Chesterton's Fence paragraph per patch. Listed, not acted on. 8. Reproducing: fenced `sh` with the exact bazel lines, the `_deps` deploy, the plot regeneration, and the traps hit on the way. **Images**: commit PNG/SVG to the branch and reference them by **pinned-SHA raw URL** so later commits do not move them: `https://raw.githubusercontent.com/The-OpenROAD-Project/bazel-orfs/<sha>/docs/studies/<slug>/fig.png`. Relative paths break on a closed PR. For small charts, a ```mermaid `xychart-beta` block or an ASCII bar chart renders in the body with no asset. **Comments**: raw samples as one fenced ```csv comment with the host line and column semantics above it, so every table can be recomputed without re-running. The **closing comment** states intent and what stays reachable: > Closing as intended, reference only. Self-contained: the patches, harness, > figures and raw data are all on this branch, and the write-up is in the PR > body above. Infrastructure proposed for merge in #NNN. ## Before pushing Run the Confidentiality purge from `CLAUDE.md` over the body, the comments, the figures' text and the committed scripts: no local paths, hostnames or user names (a machine is "48 threads on 24 cores, 256 GB", not its name), no private URLs, no ally names. `bazelisk run //:fix_lint` on the branch; the harness's unit tests pass; `bazelisk query` shows every flow target manual.
View on GitHub