Skip to content

Commit db0b0d0

Browse files
committed
bullet-point-generator: reproduce jpeg final tests
1 parent 5b09b29 commit db0b0d0

File tree

7 files changed

+1362
-1
lines changed

7 files changed

+1362
-1
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@ davit-todos/testcases/
101101
davit-todos/testcases/**/*.odb
102102

103103
# Google doc packaging outputs (generated).
104-
bullet-point-*/
104+
bullet-point-[0-9]*/
105+
bullet-point-generator/testcases/
106+
bullet-point-generator/**/tmp_tcl/
105107
amur_table_filled.md
106108

107109
dependencies/

bullet-point-generator/README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Bullet-Point Generator (JPEG-REAL1, bullet-point-3..7)
2+
3+
This folder contains scripts + explicit, one-command-at-a-time invocations to reproduce the Google-doc bullet points **3–7** for the JPEG-REAL1 “Final Tests”.
4+
5+
Outputs are written to top-level folders:
6+
7+
- `bullet-point-3/`
8+
- `bullet-point-4/`
9+
- `bullet-point-5/`
10+
- `bullet-point-6/`
11+
- `bullet-point-7/`
12+
13+
These are gitignored by default.
14+
15+
## Prereqs
16+
17+
- A **pre-placed** JPEG run directory with (at least):
18+
- `3_5_place_dp.odb`
19+
- `3_place.sdc`
20+
- `6_final.def` (for DIEAREA / DBU)
21+
- An OpenROAD binary built from the `tools/OpenROAD` submodule (recommended):
22+
- `./build_openroad.sh -o`
23+
- Expected binary: `tools/OpenROAD/build/bin/openroad`
24+
- Python deps for the OR-Tools baseline + OpenROAD-Python plotting:
25+
- `pip install ortools matplotlib networkx`
26+
27+
## Run (one command at a time)
28+
29+
See `bullet-point-generator/commands.sh`. Every line is intended to be runnable independently.
30+
31+
After running the cases, generate the filled table with:
32+
33+
- `python3 bullet-point-generator/fill_amur_table.py --out amur_table_filled.md`
34+
35+
Notable knobs to tune QoR/runtime:
36+
- `SCANOPT_TIME_LIMIT` (seconds, OpenROAD ScanOpt time budget)
37+
- `SCANOPT_ROUNDS` (ScanOpt ILS rounds)
38+
- `ORTOOLS_TIME_*` (OR-Tools time limit)
39+
40+
Polarity modes:
41+
- `mid` (default): mixed polarity allowed; falling stitched before rising
42+
- `strict`: forbids mixing polarities in a chain (so mixed-polarity `K=1` should fail)

bullet-point-generator/commands.sh

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# -----------------------------------------------------------------------------
5+
# Configure these (or export before running).
6+
# -----------------------------------------------------------------------------
7+
8+
# Pre-placed JPEG flow run dir (must contain 3_5_place_dp.odb, 3_place.sdc, 6_final.def).
9+
BASE_RUN_DIR=${BASE_RUN_DIR:-""}
10+
11+
# OpenROAD binary (must support: set_dft_config -polarity_mode mid|strict).
12+
OPENROAD=${OPENROAD:-"$(pwd)/tools/OpenROAD/build/bin/openroad"}
13+
14+
# Liberty (Sky130HD default).
15+
LIBERTY=${LIBERTY:-"$(pwd)/flow/platforms/sky130hd/lib/sky130_fd_sc_hd__tt_025C_1v80.lib"}
16+
17+
# OpenROAD ScanOpt knobs (increase for heavier runs).
18+
SCANOPT_TIME_LIMIT=${SCANOPT_TIME_LIMIT:-15}
19+
SCANOPT_ROUNDS=${SCANOPT_ROUNDS:-500000}
20+
SCANOPT_SEED=${SCANOPT_SEED:-1}
21+
22+
# OR-Tools knobs.
23+
ORTOOLS_TIME_20M=${ORTOOLS_TIME_20M:-1200}
24+
ORTOOLS_TIME_6H=${ORTOOLS_TIME_6H:-21600}
25+
26+
TESTCASES_DIR=${TESTCASES_DIR:-"bullet-point-generator/testcases"}
27+
28+
if [[ -z "${BASE_RUN_DIR}" ]]; then
29+
echo "[bpgen] ERROR: BASE_RUN_DIR is empty."
30+
echo "[bpgen] Set it to a pre-placed JPEG run dir containing: 3_5_place_dp.odb, 3_place.sdc, 6_final.def"
31+
exit 1
32+
fi
33+
34+
if [[ ! -x "${OPENROAD}" ]]; then
35+
echo "[bpgen] ERROR: OPENROAD binary not found/executable: ${OPENROAD}"
36+
exit 1
37+
fi
38+
39+
# -----------------------------------------------------------------------------
40+
# 0) Create testcases (JPEG-REAL1 + variants A..F).
41+
# -----------------------------------------------------------------------------
42+
python3 bullet-point-generator/make_testcases.py --base-run-dir "$BASE_RUN_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --out-dir "$TESTCASES_DIR"
43+
44+
# -----------------------------------------------------------------------------
45+
# 3) Optimizers (JPEG-REAL1, K=1, begin/end LL)
46+
# -----------------------------------------------------------------------------
47+
python3 bullet-point-generator/run_openroad_case.py --case-id 3a --testcase JPEG-REAL1 --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 1 --begin LL --end LL --solver HEURISTIC --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
48+
python3 bullet-point-generator/run_openroad_case.py --case-id 3b --testcase JPEG-REAL1 --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 1 --begin LL --end LL --solver SCANOPT --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
49+
python3 bullet-point-generator/run_ortools_case.py --case-id 3c --testcase JPEG-REAL1 --testcases-dir "$TESTCASES_DIR" --time-limit-s "$ORTOOLS_TIME_20M" --begin LL --end LL
50+
python3 bullet-point-generator/run_ortools_case.py --case-id 3d --testcase JPEG-REAL1 --testcases-dir "$TESTCASES_DIR" --time-limit-s "$ORTOOLS_TIME_6H" --begin LL --end LL
51+
52+
# -----------------------------------------------------------------------------
53+
# 4) Ports/chains (JPEG-REAL1)
54+
# -----------------------------------------------------------------------------
55+
python3 bullet-point-generator/run_openroad_case.py --case-id 4a --testcase JPEG-REAL1 --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 1 --begin LL --end UR --solver SCANOPT --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
56+
python3 bullet-point-generator/run_openroad_case.py --case-id 4b --testcase JPEG-REAL1 --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 2 --begin LL --end LL --solver SCANOPT --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
57+
python3 bullet-point-generator/run_openroad_case.py --case-id 4c --testcase JPEG-REAL1 --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 2 --begin LL --end UR --solver SCANOPT --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
58+
python3 bullet-point-generator/run_openroad_case.py --case-id 4d --testcase JPEG-REAL1 --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 2 --begin UL --end UR --solver SCANOPT --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
59+
60+
# -----------------------------------------------------------------------------
61+
# 5) Polarity (JPEG-REAL1A / JPEG-REAL1B)
62+
# -----------------------------------------------------------------------------
63+
# 5c: show BOTH modes.
64+
python3 bullet-point-generator/run_openroad_case.py --case-id 5c_mid --testcase JPEG-REAL1A --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 1 --begin LL --end LL --solver SCANOPT --polarity-mode mid --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
65+
python3 bullet-point-generator/run_openroad_case.py --case-id 5c_strict --testcase JPEG-REAL1A --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 1 --begin LL --end LL --solver SCANOPT --polarity-mode strict --expect ERROR --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
66+
67+
# Google-doc entries (you can pick mid or strict; strict matches the "should error?" comment for K=1):
68+
python3 bullet-point-generator/run_openroad_case.py --case-id 5d --testcase JPEG-REAL1A --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 2 --begin LL --end LL --solver SCANOPT --polarity-mode strict --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
69+
python3 bullet-point-generator/run_openroad_case.py --case-id 5e --testcase JPEG-REAL1B --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 2 --begin LL --end LL --solver SCANOPT --polarity-mode strict --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
70+
71+
# -----------------------------------------------------------------------------
72+
# 6) Clocks (JPEG-REAL1C / JPEG-REAL1D)
73+
# -----------------------------------------------------------------------------
74+
python3 bullet-point-generator/run_openroad_case.py --case-id 6c --testcase JPEG-REAL1C --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 1 --begin LL --end LL --solver SCANOPT --expect ERROR --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
75+
python3 bullet-point-generator/run_openroad_case.py --case-id 6d --testcase JPEG-REAL1C --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 2 --begin LL --end LL --solver SCANOPT --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
76+
python3 bullet-point-generator/run_openroad_case.py --case-id 6e --testcase JPEG-REAL1C --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 3 --begin LL --end LL --solver SCANOPT --expect ERROR --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
77+
python3 bullet-point-generator/run_openroad_case.py --case-id 6f --testcase JPEG-REAL1C --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 4 --begin LL --end LL --solver SCANOPT --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
78+
python3 bullet-point-generator/run_openroad_case.py --case-id 6g --testcase JPEG-REAL1D --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 2 --begin LL --end LL --solver SCANOPT --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
79+
python3 bullet-point-generator/run_openroad_case.py --case-id 6h --testcase JPEG-REAL1D --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 4 --begin LL --end LL --solver SCANOPT --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
80+
81+
# -----------------------------------------------------------------------------
82+
# 7) Grouping (JPEG-REAL1E / JPEG-REAL1F)
83+
# -----------------------------------------------------------------------------
84+
python3 bullet-point-generator/run_openroad_case.py --case-id 7c --testcase JPEG-REAL1E --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 1 --begin LL --end LL --solver SCANOPT --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
85+
python3 bullet-point-generator/run_openroad_case.py --case-id 7d --testcase JPEG-REAL1F --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 1 --begin LL --end LL --solver SCANOPT --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
86+
python3 bullet-point-generator/run_openroad_case.py --case-id 7e --testcase JPEG-REAL1E --testcases-dir "$TESTCASES_DIR" --openroad "$OPENROAD" --liberty "$LIBERTY" --k 2 --begin LL --end LL --solver SCANOPT --expect ERROR --scanopt-time-limit "$SCANOPT_TIME_LIMIT" --scanopt-rounds "$SCANOPT_ROUNDS" --scanopt-seed "$SCANOPT_SEED"
87+
88+
# -----------------------------------------------------------------------------
89+
# Summary table (optional).
90+
# -----------------------------------------------------------------------------
91+
python3 bullet-point-generator/fill_amur_table.py --out amur_table_filled.md
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
#!/usr/bin/env python3
2+
3+
from __future__ import annotations
4+
5+
import argparse
6+
import json
7+
from dataclasses import dataclass
8+
from pathlib import Path
9+
from typing import Any, Dict, List, Optional, Tuple
10+
11+
12+
def _repo_root() -> Path:
13+
return Path(__file__).resolve().parents[1]
14+
15+
16+
def _read_json(path: Path) -> Any:
17+
return json.loads(path.read_text(encoding="utf-8", errors="ignore"))
18+
19+
20+
def _find_first_png(case_dir: Path) -> Optional[Path]:
21+
pngs = sorted(case_dir.glob("*.png"))
22+
return pngs[0] if pngs else None
23+
24+
25+
def _fmt_num(x: Optional[float]) -> str:
26+
if x is None:
27+
return ""
28+
return f"{x:.3f}"
29+
30+
31+
def _fmt_runtime(x: Optional[float]) -> str:
32+
if x is None:
33+
return ""
34+
# Keep short while still readable.
35+
if x >= 1000.0:
36+
return f"{x:.0f}"
37+
return f"{x:.3f}"
38+
39+
40+
@dataclass(frozen=True)
41+
class Row:
42+
test_id: str
43+
purpose: str
44+
case_id: str
45+
bullet: int
46+
47+
48+
def _case_dir(root: Path, bullet: int, case_id: str) -> Path:
49+
return root / f"bullet-point-{bullet}" / case_id
50+
51+
52+
def _load_openroad_metrics(case_dir: Path) -> Tuple[Optional[float], Optional[float]]:
53+
metrics_path = case_dir / "metrics.json"
54+
if not metrics_path.exists():
55+
return None, None
56+
data = _read_json(metrics_path)
57+
if isinstance(data, list) and data:
58+
m = data[0]
59+
if isinstance(m, dict):
60+
return (
61+
float(m["total_um"]) if m.get("total_um") is not None else None,
62+
float(m["openroad_runtime_s"])
63+
if m.get("openroad_runtime_s") is not None
64+
else None,
65+
)
66+
return None, None
67+
68+
69+
def _load_ortools_metrics(case_dir: Path) -> Tuple[Optional[float], Optional[float]]:
70+
metrics_path = case_dir / "metrics.json"
71+
if not metrics_path.exists():
72+
return None, None
73+
data = _read_json(metrics_path)
74+
if isinstance(data, dict) and "total_cost_um" in data:
75+
return (
76+
float(data["total_cost_um"]) if data.get("total_cost_um") is not None else None,
77+
float(data["wall_s"]) if data.get("wall_s") is not None else None,
78+
)
79+
return None, None
80+
81+
82+
def _load_status_wall(case_dir: Path) -> Optional[float]:
83+
status_path = case_dir / "status.json"
84+
if not status_path.exists():
85+
return None
86+
data = _read_json(status_path)
87+
if isinstance(data, dict) and data.get("wall_s") is not None:
88+
return float(data["wall_s"])
89+
return None
90+
91+
92+
def _format_link(root: Path, path: Path, label: str) -> str:
93+
rel = path.resolve().relative_to(root.resolve())
94+
return f"[{label}]({rel.as_posix()})"
95+
96+
97+
def main(argv: Optional[List[str]] = None) -> int:
98+
ap = argparse.ArgumentParser(
99+
description="Fill the AMUR TABLE for bullet-point-3..7 runs (writes markdown)."
100+
)
101+
ap.add_argument(
102+
"--out",
103+
type=Path,
104+
default=Path("amur_table_filled.md"),
105+
help="Markdown output path (repo-relative by default).",
106+
)
107+
args = ap.parse_args(argv)
108+
109+
root = _repo_root()
110+
out_path = args.out
111+
if not out_path.is_absolute():
112+
out_path = (root / out_path).resolve()
113+
114+
# Google-doc table rows, plus an explicit 5c_mid/5c_strict appendix.
115+
rows: List[Row] = [
116+
Row("3a", "Optimizers", "3a", 3),
117+
Row("3b", "Optimizers", "3b", 3),
118+
Row("3c", "Optimizers", "3c", 3),
119+
Row("3d", "Optimizers", "3d", 3),
120+
Row("4a", "Ports/chains", "4a", 4),
121+
Row("4b", "Ports/chains", "4b", 4),
122+
Row("4c", "Ports/chains", "4c", 4),
123+
Row("4d", "Ports/chains", "4d", 4),
124+
# 5c in the doc is the "should error?" case; by default map to strict.
125+
Row("5c", "Polarity", "5c_strict", 5),
126+
Row("5d", "Polarity", "5d", 5),
127+
Row("5e", "Polarity", "5e", 5),
128+
Row("6c", "Clocks", "6c", 6),
129+
Row("6d", "Clocks", "6d", 6),
130+
Row("6e", "Clocks", "6e", 6),
131+
Row("6f", "Clocks", "6f", 6),
132+
Row("6g", "Clocks", "6g", 6),
133+
Row("6h", "Clocks", "6h", 6),
134+
Row("7c", "Grouping", "7c", 7),
135+
Row("7d", "Grouping", "7d", 7),
136+
Row("7e", "Grouping", "7e", 7),
137+
]
138+
139+
lines: List[str] = []
140+
lines.append("| Test ID | Purpose | Total chain length (um) | Runtime (sec) | Link to visualization |")
141+
lines.append("| ------: | ------------ | ----------------------- | ------------- | --------------------- |")
142+
143+
for row in rows:
144+
case_dir = _case_dir(root, row.bullet, row.case_id)
145+
total_um: Optional[float] = None
146+
runtime_s: Optional[float] = None
147+
link: str = ""
148+
149+
if row.case_id in {"3c", "3d"}:
150+
total_um, runtime_s = _load_ortools_metrics(case_dir)
151+
png = _find_first_png(case_dir)
152+
if png is not None:
153+
link = _format_link(root, png, "plot")
154+
else:
155+
total_um, runtime_s = _load_openroad_metrics(case_dir)
156+
if runtime_s is None:
157+
runtime_s = _load_status_wall(case_dir)
158+
png = _find_first_png(case_dir)
159+
if png is not None:
160+
link = _format_link(root, png, "plot")
161+
else:
162+
stdout_log = case_dir / "stdout.log"
163+
if stdout_log.exists():
164+
link = _format_link(root, stdout_log, "log")
165+
166+
lines.append(
167+
f"| {row.test_id} | {row.purpose} | {_fmt_num(total_um)} | {_fmt_runtime(runtime_s)} | {link} |"
168+
)
169+
170+
# Appendix: explicitly show the polarity-mode delta for 5c.
171+
lines.append("")
172+
lines.append("## Polarity mode delta (5c)")
173+
lines.append(
174+
"This section is not part of the original AMUR TABLE, but shows how `mid` vs `strict` affects the `5c` expectation."
175+
)
176+
lines.append("")
177+
lines.append("| Case | Polarity mode | Result | Total chain length (um) | Runtime (sec) | Link |")
178+
lines.append("| ---- | ------------- | ------ | ----------------------- | ------------- | ---- |")
179+
180+
for case_id, mode in [("5c_mid", "mid"), ("5c_strict", "strict")]:
181+
case_dir = _case_dir(root, 5, case_id)
182+
total_um, runtime_s = _load_openroad_metrics(case_dir)
183+
if runtime_s is None:
184+
runtime_s = _load_status_wall(case_dir)
185+
png = _find_first_png(case_dir)
186+
link = ""
187+
if png is not None:
188+
link = _format_link(root, png, "plot")
189+
else:
190+
stdout_log = case_dir / "stdout.log"
191+
if stdout_log.exists():
192+
link = _format_link(root, stdout_log, "log")
193+
result = "OK" if (case_dir / "metrics.json").exists() else "ERROR"
194+
lines.append(
195+
f"| {case_id} | {mode} | {result} | {_fmt_num(total_um)} | {_fmt_runtime(runtime_s)} | {link} |"
196+
)
197+
198+
out_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
199+
print(f"WROTE: {out_path}")
200+
return 0
201+
202+
203+
if __name__ == "__main__":
204+
raise SystemExit(main())
205+

0 commit comments

Comments
 (0)