Skip to content

flow/designs: per-PDK WNS plots and README from rules-base.json#4307

Closed
oharboe wants to merge 2 commits into
The-OpenROAD-Project:masterfrom
oharboe:asap7-wns-plots
Closed

flow/designs: per-PDK WNS plots and README from rules-base.json#4307
oharboe wants to merge 2 commits into
The-OpenROAD-Project:masterfrom
oharboe:asap7-wns-plots

Conversation

@oharboe

@oharboe oharboe commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

What

Adds flow/util/plot_wns.py, a small local script (just python3, no bazel) that reads the committed rules-base.json baselines and regenerates two things, all committed so they render on GitHub with nothing to run:

1. Per-PDK WNS by designflow/designs/<pdk>/wns.png + a ## WNS section in flow/designs/<pdk>/README.md (bar = finish WNS, markers = cts/globalroute). Covers all 9 PDKs with timing baselines (76 designs).

2. Cross-PDK WNS estimate accuracyflow/designs/wns_accuracy.png + a ## WNS estimate accuracy across PDKs section in flow/designs/README.md. For each design, the per-stage estimate error (stage − finish) / clock_period (clock period parsed from the .sdc) so the PDKs are comparable; + = optimistic, = pessimistic. 67 designs / 8 PDKs (designs with no cts/globalroute slack or no parsable period are omitted).

Run with:

python3 flow/util/plot_wns.py            # all PDKs + cross-PDK accuracy
python3 flow/util/plot_wns.py --pdk asap7

Regeneration is idempotent: generated content lives between markers, so hand-written findings prose is preserved.

Why

No OpenROAD/ORFS run is needed — the data already lives in the tree, so the plots are deterministic and the committed PNGs + tables render directly on GitHub. The goal is to share the code and the findings in a form that can be viewed and discussed online without running anything.

Findings

WNS estimate accuracy (cross-PDK):

WNS estimate accuracy

  • Global route usually tightens the estimate (MAE drops ctsglobalroute): sky130hs 10.5%→1.9%, gt2n 3.3%→0.0%, gf12 2.2%→1.1%. ihp-sg13g2/gf180 are accurate already at cts.
  • cts is biased optimistic everywhere (bias ≥ 0); globalroute over-corrects into pessimism for sky130hd (−3.5%), sky130hs, gf12, nangate45.
  • sky130hd is the exception where routing makes the estimate worse (grt MAE 3.5% > cts 2.9%).
  • Design-specific outliers dominate: sky130hs/gcd cts +45.9% (corrected by grt), asap7/swerv_wrapper +14.9% at both stages.

This is the design-level companion to the per-net GRT-vs-RCX divergence in flow/docs/rcx (#4302).

asap7 WNS by design:

asap7 WNS

All 18 asap7 designs close with negative setup slack; swerv_wrapper/mock-alu are ~10× the −15…−50 cluster.

Notes

  • Only matplotlib (already pinned in flow/util/requirements_lock.txt) + stdlib.
  • Designs without rules-base.json (e.g. asap7 minimal, rcx-fanout-*) are skipped; finish-only designs (e.g. gf55/aes) appear in the per-PDK view but not the accuracy view.
  • Per-PDK WNS values are each PDK's native unit (ps/ns); the accuracy view is unitless (% of clock period), comparable across PDKs.

🤖 Generated with Claude Code

Add flow/util/plot_wns.py, a local (no-bazel) script that reads the
committed rules-base.json baselines and regenerates, per PDK, a
worst-setup-slack bar chart (wns.png) and a "## WNS" README section
between generated markers. It covers all 9 PDKs that ship timing
baselines (76 designs); the bar is finish-stage WNS, with cts and
globalroute drawn as markers so stage-to-stage movement is visible.

No OpenROAD/ORFS run is required -- the data already lives in the tree,
so the plots are deterministic and the committed PNGs + tables render
on GitHub with nothing to run. asap7/README.md additionally carries a
hand-written findings section discussing the results.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a Python script, plot_wns.py, to automatically parse worst setup slack (WNS) data from rules-base.json baselines and generate markdown tables and bar charts in the README files of various PDKs. The review feedback highlights three key areas for improvement: adding explicit encoding="utf-8" parameters to file operations to prevent encoding crashes on non-UTF-8 platforms (such as Windows), utilizing the defined FINISH_KEY constant within the STAGES list to eliminate unused code, and adding a type check to ensure the parsed JSON is a dictionary to avoid potential AttributeError crashes.

Comment thread flow/util/plot_wns.py Outdated
Comment on lines +158 to +170
if os.path.isfile(readme_path):
with open(readme_path) as f:
text = f.read()
if BEGIN in text and END in text:
pre = text[: text.index(BEGIN)]
post = text[text.index(END) + len(END):]
new = pre + section + post.lstrip("\n")
else:
new = text.rstrip("\n") + "\n\n" + section
else:
new = f"# {pdk} designs\n\n" + section
with open(readme_path, "w") as f:
f.write(new)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The generated README files contain non-ASCII characters such as em-dashes () and almost-equal-to signs (). When running this script on platforms where the default system encoding is not UTF-8 (such as Windows), opening these files without explicitly specifying encoding="utf-8" will cause UnicodeDecodeError or UnicodeEncodeError crashes. Specifying encoding="utf-8" on both read and write operations prevents this.

Suggested change
if os.path.isfile(readme_path):
with open(readme_path) as f:
text = f.read()
if BEGIN in text and END in text:
pre = text[: text.index(BEGIN)]
post = text[text.index(END) + len(END):]
new = pre + section + post.lstrip("\n")
else:
new = text.rstrip("\n") + "\n\n" + section
else:
new = f"# {pdk} designs\n\n" + section
with open(readme_path, "w") as f:
f.write(new)
if os.path.isfile(readme_path):
with open(readme_path, encoding="utf-8") as f:
text = f.read()
if BEGIN in text and END in text:
pre = text[: text.index(BEGIN)]
post = text[text.index(END) + len(END):]
new = pre + section + post.lstrip("\n")
else:
new = text.rstrip("\n") + "\n\n" + section
else:
new = f"# {pdk} designs\n\n" + section
with open(readme_path, "w", encoding="utf-8") as f:
f.write(new)

Comment thread flow/util/plot_wns.py
Comment on lines +37 to +43
# Stage key -> (short label, plot marker). Order is flow order.
STAGES = [
("cts__timing__setup__ws", "cts", "v"),
("globalroute__timing__setup__ws", "globalroute", "^"),
("finish__timing__setup__ws", "finish", None), # finish is drawn as the bar
]
FINISH_KEY = "finish__timing__setup__ws"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The constant FINISH_KEY is defined but never used in the script. It is cleaner and more maintainable to define it first and use it directly in the STAGES list to avoid hardcoding the string twice.

Suggested change
# Stage key -> (short label, plot marker). Order is flow order.
STAGES = [
("cts__timing__setup__ws", "cts", "v"),
("globalroute__timing__setup__ws", "globalroute", "^"),
("finish__timing__setup__ws", "finish", None), # finish is drawn as the bar
]
FINISH_KEY = "finish__timing__setup__ws"
FINISH_KEY = "finish__timing__setup__ws"
# Stage key -> (short label, plot marker). Order is flow order.
STAGES = [
("cts__timing__setup__ws", "cts", "v"),
("globalroute__timing__setup__ws", "globalroute", "^"),
(FINISH_KEY, "finish", None), # finish is drawn as the bar
]

Comment thread flow/util/plot_wns.py
Comment on lines +57 to +65
try:
with open(path) as f:
data = json.load(f)
except (OSError, ValueError):
return None
entry = data.get(key)
if isinstance(entry, dict) and isinstance(entry.get("value"), (int, float)):
return float(entry["value"])
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If rules-base.json is malformed or empty, json.load(f) could return a non-dictionary object (like a list or string). Calling data.get(key) would then raise an AttributeError, which is not caught by the except (OSError, ValueError) block and would crash the script. Additionally, specifying encoding="utf-8" when opening files is a best practice to ensure cross-platform compatibility (especially on Windows).

Suggested change
try:
with open(path) as f:
data = json.load(f)
except (OSError, ValueError):
return None
entry = data.get(key)
if isinstance(entry, dict) and isinstance(entry.get("value"), (int, float)):
return float(entry["value"])
return None
try:
with open(path, encoding="utf-8") as f:
data = json.load(f)
except (OSError, ValueError):
return None
if not isinstance(data, dict):
return None
entry = data.get(key)
if isinstance(entry, dict) and isinstance(entry.get("value"), (int, float)):
return float(entry["value"])
return None

Extend plot_wns.py to quantify how well the cts and globalroute
worst-slack estimates predict the final WNS. Each design's per-stage
error (stage - finish) is normalized by its clock period, parsed from
the .sdc, so PDKs with different timing units are comparable.

Adds flow/designs/wns_accuracy.png (per-PDK strip plot of normalized
estimate error, + optimistic / - pessimistic) and a new
flow/designs/README.md with a "## WNS estimate accuracy across PDKs"
section: a per-PDK MAE/bias table plus hand-written findings. Covers the
67 designs across 8 PDKs that expose cts/globalroute slack and a parsable
clock period; the rest are noted as omitted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
@oharboe

oharboe commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator Author

@maliberty grt is pretty accurate for the designs in ORFS... so the pathology I saw is not present there...

@oharboe oharboe closed this Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant