Skip to content

Commit b43e728

Browse files
author
aghose
committed
benchmarks: add sky130hd/jpeg horror story + wirelength
1 parent ee7f663 commit b43e728

File tree

6 files changed

+268
-38
lines changed

6 files changed

+268
-38
lines changed

benchmarks/doomed_clips/benchmark.py

Lines changed: 128 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
STANDARD_DESIGNS = ("aes", "ibex", "jpeg")
1717

1818
STRESS_SUITE = "stress"
19+
HORROR_SUITE = "horror"
1920

2021
# Chosen to increase routing difficulty without making the suite too brittle.
2122
STRESS_CORE_UTILIZATION = {
@@ -31,6 +32,12 @@
3132
("nangate45", "aes"): ["FLOORPLAN_DEF="],
3233
}
3334

35+
HORROR_DESIGN_CONFIG = "./designs/sky130hd/jpeg/config_horror.mk"
36+
HORROR_PLACE_DENSITY_LB_ADDON = "0.25"
37+
HORROR_GLOBAL_ROUTE_ARGS = (
38+
"-congestion_iterations 30 -congestion_report_iter_step 5 -verbose -allow_congestion"
39+
)
40+
3441

3542
@dataclass(frozen=True)
3643
class IterTime:
@@ -52,7 +59,9 @@ class RunResult:
5259
variant: str
5360
drt_seconds: int | None
5461
final_drvs: int | None
62+
final_wirelength: int | None
5563
iter_drvs: dict[int, int]
64+
iter_wirelength: dict[int, int]
5665
iter_times: dict[int, IterTime]
5766

5867
@property
@@ -169,18 +178,29 @@ def parse_iter_times(log_text: str) -> dict[int, IterTime]:
169178
return result
170179

171180

172-
def parse_route_metrics(metrics_path: Path) -> tuple[int | None, dict[int, int]]:
181+
def parse_route_metrics(metrics_path: Path) -> tuple[int | None, dict[int, int], int | None, dict[int, int]]:
173182
if not metrics_path.exists():
174-
return None, {}
183+
return None, {}, None, {}
175184
data = json.loads(metrics_path.read_text())
176185
final_drvs = data.get("detailedroute__route__drc_errors")
186+
final_wirelength = data.get("detailedroute__route__wirelength")
177187
iter_drvs: dict[int, int] = {}
188+
iter_wirelength: dict[int, int] = {}
178189
for key, value in data.items():
179190
m = re.match(r"detailedroute__route__drc_errors__iter:(\d+)$", key)
180-
if not m:
191+
if m:
192+
iter_drvs[int(m.group(1))] = int(value)
181193
continue
182-
iter_drvs[int(m.group(1))] = int(value)
183-
return (int(final_drvs) if final_drvs is not None else None), iter_drvs
194+
m = re.match(r"detailedroute__route__wirelength__iter:(\d+)$", key)
195+
if m:
196+
iter_wirelength[int(m.group(1))] = int(value)
197+
continue
198+
return (
199+
int(final_drvs) if final_drvs is not None else None,
200+
iter_drvs,
201+
int(final_wirelength) if final_wirelength is not None else None,
202+
iter_wirelength,
203+
)
184204

185205

186206
def parse_run(work_home: Path, platform: str, design: str, variant: str) -> RunResult:
@@ -189,14 +209,16 @@ def parse_run(work_home: Path, platform: str, design: str, variant: str) -> RunR
189209
log_text = log_path.read_text() if log_path.exists() else ""
190210
drt_seconds = parse_drt_seconds(log_text)
191211
iter_times = parse_iter_times(log_text)
192-
final_drvs, iter_drvs = parse_route_metrics(metrics_path)
212+
final_drvs, iter_drvs, final_wirelength, iter_wirelength = parse_route_metrics(metrics_path)
193213
return RunResult(
194214
platform=platform,
195215
design=design,
196216
variant=variant,
197217
drt_seconds=drt_seconds,
198218
final_drvs=final_drvs,
219+
final_wirelength=final_wirelength,
199220
iter_drvs=iter_drvs,
221+
iter_wirelength=iter_wirelength,
200222
iter_times=iter_times,
201223
)
202224

@@ -275,6 +297,8 @@ def run_drt(
275297
env: dict[str, str],
276298
extra_args: str | None,
277299
make_overrides: list[str],
300+
extra_make_overrides: list[str] | None = None,
301+
extra_env: dict[str, str] | None = None,
278302
) -> None:
279303
platform = Path(design_config).parts[-3]
280304
design = Path(design_config).parts[-2]
@@ -286,11 +310,18 @@ def run_drt(
286310
dst_variant=variant,
287311
)
288312
env = dict(env)
313+
if extra_env:
314+
env.update(extra_env)
289315
if extra_args:
290316
env["DETAILED_ROUTE_EXTRA_ARGS"] = extra_args
291317
else:
292318
env.pop("DETAILED_ROUTE_EXTRA_ARGS", None)
293319

320+
combined_overrides = list(make_overrides)
321+
if extra_make_overrides:
322+
combined_overrides.extend(extra_make_overrides)
323+
combined_overrides = _normalize_make_overrides(combined_overrides)
324+
294325
_make(
295326
flow_dir,
296327
[
@@ -299,7 +330,7 @@ def run_drt(
299330
f"WORK_HOME={work_home}",
300331
f"NUM_CORES={threads}",
301332
f"OPENROAD_EXE={openroad_exe}",
302-
*make_overrides,
333+
*combined_overrides,
303334
"do-5_2_route",
304335
],
305336
env=env,
@@ -379,10 +410,16 @@ def main() -> int:
379410
)
380411
ap.add_argument(
381412
"--suite",
382-
choices=("standard", STRESS_SUITE),
413+
choices=("standard", STRESS_SUITE, HORROR_SUITE),
383414
default="standard",
384415
help="Benchmark suite selection.",
385416
)
417+
ap.add_argument(
418+
"--mode",
419+
choices=("both", "control", "doomed"),
420+
default="both",
421+
help="Which variants to run (default: both).",
422+
)
386423
ap.add_argument(
387424
"--platform",
388425
action="append",
@@ -452,6 +489,24 @@ def main() -> int:
452489
default=None,
453490
help="Directory for summary artifacts (default: <work_home>/doomed_clips_benchmark).",
454491
)
492+
ap.add_argument(
493+
"--horror-control-end-iter",
494+
type=int,
495+
default=40,
496+
help=f"For --suite {HORROR_SUITE}: DETAILED_ROUTE_END_ITERATION for the control run.",
497+
)
498+
ap.add_argument(
499+
"--horror-slay-max-iter",
500+
type=int,
501+
default=3,
502+
help=f"For --suite {HORROR_SUITE}: DETAILED_ROUTE_MULTI_START_MAX_ITER for the doomed run.",
503+
)
504+
ap.add_argument(
505+
"--horror-slay-max-runs",
506+
type=int,
507+
default=1,
508+
help=f"For --suite {HORROR_SUITE}: DETAILED_ROUTE_MULTI_START_MAX_RUNS for the doomed run.",
509+
)
455510
args = ap.parse_args()
456511

457512
flow_dir: Path = args.flow_dir
@@ -479,11 +534,14 @@ def main() -> int:
479534
design = parts[-2]
480535
suite.append((platform, design, design_config))
481536
else:
482-
platforms = args.platform or list(STANDARD_PLATFORMS)
483-
designs = args.design or list(STANDARD_DESIGNS)
484-
for platform in platforms:
485-
for design in designs:
486-
suite.append((platform, design, f"./designs/{platform}/{design}/config.mk"))
537+
if args.suite == HORROR_SUITE:
538+
suite.append(("sky130hd", "jpeg", HORROR_DESIGN_CONFIG))
539+
else:
540+
platforms = args.platform or list(STANDARD_PLATFORMS)
541+
designs = args.design or list(STANDARD_DESIGNS)
542+
for platform in platforms:
543+
for design in designs:
544+
suite.append((platform, design, f"./designs/{platform}/{design}/config.mk"))
487545
env = dict(os.environ)
488546
env["OR_SEED"] = str(args.or_seed)
489547
env["OR_K"] = str(args.or_k)
@@ -520,6 +578,15 @@ def main() -> int:
520578
)
521579
make_overrides.extend(STRESS_PER_CASE_OVERRIDES.get((platform, design), []))
522580

581+
if args.suite == HORROR_SUITE:
582+
make_overrides.extend(
583+
[
584+
f"PLACE_DENSITY_LB_ADDON={HORROR_PLACE_DENSITY_LB_ADDON}",
585+
f"GLOBAL_ROUTE_ARGS={HORROR_GLOBAL_ROUTE_ARGS}",
586+
"SKIP_ANTENNA_REPAIR_POST_DRT=1",
587+
]
588+
)
589+
523590
make_overrides.extend(user_overrides)
524591
try:
525592
make_overrides = _normalize_make_overrides(make_overrides)
@@ -543,28 +610,46 @@ def main() -> int:
543610
env=env,
544611
make_overrides=make_overrides,
545612
)
546-
run_drt(
547-
flow_dir=flow_dir,
548-
work_home=work_home,
549-
design_config=design_config,
550-
variant=control_variant,
551-
openroad_exe=openroad_control,
552-
threads=args.threads,
553-
env=env,
554-
extra_args=None,
555-
make_overrides=make_overrides,
556-
)
557-
run_drt(
558-
flow_dir=flow_dir,
559-
work_home=work_home,
560-
design_config=design_config,
561-
variant=doomed_variant,
562-
openroad_exe=openroad_doomed,
563-
threads=args.threads,
564-
env=env,
565-
extra_args=args.doomed_args,
566-
make_overrides=make_overrides,
567-
)
613+
if args.mode in ("both", "control"):
614+
run_drt(
615+
flow_dir=flow_dir,
616+
work_home=work_home,
617+
design_config=design_config,
618+
variant=control_variant,
619+
openroad_exe=openroad_control,
620+
threads=args.threads,
621+
env=env,
622+
extra_args=None,
623+
make_overrides=make_overrides,
624+
extra_make_overrides=[
625+
f"DETAILED_ROUTE_END_ITERATION={args.horror_control_end_iter}"
626+
]
627+
if args.suite == HORROR_SUITE
628+
else None,
629+
)
630+
if args.mode in ("both", "doomed"):
631+
doomed_env: dict[str, str] | None = None
632+
if args.suite == HORROR_SUITE:
633+
doomed_env = {
634+
"DETAILED_ROUTE_MULTI_START": "1",
635+
"DETAILED_ROUTE_MULTI_START_MAX_ITER": str(args.horror_slay_max_iter),
636+
"DETAILED_ROUTE_MULTI_START_MAX_RUNS": str(args.horror_slay_max_runs),
637+
"DETAILED_ROUTE_MULTI_START_ACCEPT_BEST": "1",
638+
"DETAILED_ROUTE_MULTI_START_FALLBACK_TO_SINGLE": "0",
639+
"DETAILED_ROUTE_MULTI_START_OR_K": "0.0",
640+
}
641+
run_drt(
642+
flow_dir=flow_dir,
643+
work_home=work_home,
644+
design_config=design_config,
645+
variant=doomed_variant,
646+
openroad_exe=openroad_doomed,
647+
threads=args.threads,
648+
env=env,
649+
extra_args=args.doomed_args,
650+
make_overrides=make_overrides,
651+
extra_env=doomed_env,
652+
)
568653

569654
control = parse_run(work_home, platform, design, control_variant)
570655
doomed = parse_run(work_home, platform, design, doomed_variant)
@@ -588,12 +673,20 @@ def main() -> int:
588673
"speedup": speedup,
589674
"control_final_drvs": "" if control.final_drvs is None else str(control.final_drvs),
590675
"doomed_final_drvs": "" if doomed.final_drvs is None else str(doomed.final_drvs),
676+
"control_final_wirelength": ""
677+
if control.final_wirelength is None
678+
else str(control.final_wirelength),
679+
"doomed_final_wirelength": ""
680+
if doomed.final_wirelength is None
681+
else str(doomed.final_wirelength),
591682
"control_avg_eff_cores": f"{control.avg_effective_cores:.2f}"
592683
if control.avg_effective_cores is not None
593684
else "",
594685
"doomed_avg_eff_cores": f"{doomed.avg_effective_cores:.2f}"
595686
if doomed.avg_effective_cores is not None
596687
else "",
688+
"control_iter_count": str(len(control.iter_times)),
689+
"doomed_iter_count": str(len(doomed.iter_times)),
597690
}
598691
)
599692
keys = list(rows[0].keys()) if rows else []

doc-doomed.md

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
This repo contains a prototype “doomed clip” handling mode for OpenROAD’s detailed router (`drt` / TritonRoute) plus OpenROAD-flow-scripts (ORFS) hooks and a benchmark harness.
44

5-
Goal: reduce **long-tail detailed-route runtime** caused by a small number of stubborn tiles by (1) prioritizing expensive tiles earlier and (2) optionally exploring multiple cost settings on the worst tiles.
5+
Goal: reduce **long-tail detailed-route runtime** caused by a small number of stubborn tiles by:
6+
7+
1. **Inside OpenROAD DRT**: prioritize expensive tiles earlier (“doomed clips”).
8+
2. **At the ORFS level**: optionally run **multi-start DRT** (multiple short attempts with early-kill) to bound wall time.
69

710
## Repo Layout (what’s where)
811

@@ -11,8 +14,9 @@ Goal: reduce **long-tail detailed-route runtime** caused by a small number of st
1114
- Feature work is implemented in a separate OpenROAD worktree (see below).
1215
- ORFS changes (this repo):
1316
- `flow/scripts/detail_route.tcl`: adds `DETAILED_ROUTE_EXTRA_ARGS` (append-only) hook.
17+
- `flow/scripts/flow.sh`, `flow/scripts/multi_start_drt.py`: optional multi-start wrapper for `5_2_route` when `DETAILED_ROUTE_MULTI_START=1`.
1418
- `flow/scripts/global_route.tcl`: propagates `-allow_congestion` into incremental `global_route` calls (needed for “stress” experiments).
15-
- `docs/user/FlowVariables.md`, `docs/toc.yml`: documents the new flow variable.
19+
- `docs/user/FlowVariables.md`, `docs/toc.yml`: documents `DETAILED_ROUTE_EXTRA_ARGS` and `DETAILED_ROUTE_MULTI_START*`.
1620
- Benchmark harness (this repo):
1721
- `benchmarks/doomed_clips/benchmark.py`: runs control vs doomed from a shared GRT checkpoint and summarizes results.
1822
- `docs/user/DoomedClipsBenchmark.md`: benchmark usage notes.
@@ -43,6 +47,27 @@ High-level algorithm:
4347
2. At iteration *k+1*, tiles are scored (normalized by max runtime/max DRVs) and **sorted so worst tiles run first** within each batch.
4448
3. Optional: for the worst tiles, multiple worker cost variants run; the best result is selected.
4549

50+
## ORFS Feature: Multi-Start DRT (+ Early Kill)
51+
52+
This is **outside OpenROAD** (a flow wrapper). It runs multiple short
53+
`detailed_route` attempts from the same `5_1_grt` checkpoint, varying seeds (and
54+
optionally `OR_K`) and killing attempts that are clearly not converging within
55+
the iteration budget.
56+
57+
Enable it with:
58+
59+
- `DETAILED_ROUTE_MULTI_START=1`
60+
61+
Core knobs:
62+
63+
- `DETAILED_ROUTE_MULTI_START_MAX_ITER` (default 10)
64+
- `DETAILED_ROUTE_MULTI_START_MAX_RUNS` (default 8)
65+
- `DETAILED_ROUTE_MULTI_START_ACCEPT_BEST` (default 0)
66+
- `DETAILED_ROUTE_MULTI_START_FALLBACK_TO_SINGLE` (default 1)
67+
68+
When multi-start is enabled, per-attempt artifacts are written under
69+
`_multistart/tryXX/` in `results/`, `logs/`, `reports/`, `objects/`.
70+
4671
## How To Set Up Baseline vs Feature OpenROAD (recommended)
4772

4873
This doc assumes you want **two OpenROAD builds**:
@@ -120,6 +145,14 @@ Outputs:
120145
- `<work_home>/doomed_clips_benchmark/suite_summary.md`
121146
- `<work_home>/doomed_clips_benchmark/suite_summary.csv`
122147

148+
## Horror Story (sky130hd/jpeg)
149+
150+
For a reproducible long-tail `sky130hd/jpeg` case (multi-hour baseline) plus a
151+
clean “slay” recipe that caps DRT to ~3–10 iterations, see:
152+
153+
- `docs/user/DoomedClipsHorrorStory.md`
154+
- `<work_home>/doomed_clips_benchmark/suite_summary.csv`
155+
123156
## “Stress” Benchmarking (making the baseline explode on purpose)
124157

125158
To make the feature’s value obvious, you typically need cases where:
@@ -203,4 +236,3 @@ Example:
203236

204237
- Implemented: OpenROAD `-doomed_clips` feature + ORFS hook to pass args + benchmark harness.
205238
- Not finished: a stress configuration that shows a **large** and **reliable** speedup (e.g. >5–10%) across multiple runs; further tuning/measurement improvements are expected.
206-

docs/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ entries:
1919
title: Flow Variables
2020
- file: user/DoomedClipsBenchmark.md
2121
title: Doomed DRT Clips Benchmark
22+
- file: user/DoomedClipsHorrorStory.md
23+
title: Doomed DRT Clips Horror Story
2224
- file: contrib/Metrics.md
2325
title: Metrics
2426
- file: user/InstructionsForAutoTuner.md

docs/user/DoomedClipsBenchmark.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ For each run, collect:
1212
- **Per-iteration CPU vs elapsed time**: parsed from `[INFO DRT-0267] cpu time = ... elapsed time = ...` in `5_2_route.log`.
1313
- Compute **effective cores** per iteration as `cpu_seconds / elapsed_seconds` (higher is better; long-tail imbalance lowers this).
1414
- **Per-iteration DRVs**: from `5_2_route.json` keys `detailedroute__route__drc_errors__iter:<iter>`.
15+
- **Wirelength**: from `5_2_route.json` keys `detailedroute__route__wirelength` and `detailedroute__route__wirelength__iter:<iter>`.
1516

1617
The “doomed clips” feature is considered successful when **elapsed time drops** and **effective cores rises**, with **no regression in DRVs**.
1718

@@ -121,6 +122,11 @@ python3 benchmarks/doomed_clips/benchmark.py \
121122
--openroad tools/OpenROAD-drt-doomed-clips/build/bin/openroad
122123
```
123124

125+
## Horror Story (sky130hd/jpeg)
126+
127+
For a reproducible long-tail case and a “slay” recipe (multi-start, 3–10 iters),
128+
see `docs/user/DoomedClipsHorrorStory.md`.
129+
124130
## Example Results (standard suite)
125131

126132
Example output from `suite_summary.md` (32 threads, checkpoint reuse, baseline vs `-doomed_clips`), showing **no DRV regression** and mostly **sub-2% runtime deltas**:

0 commit comments

Comments
 (0)