Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
10cec03
Add congestion ML pipeline from scratch (U-Net + GNN, 3 output heads)
JayRaj21 Aug 7, 2026
a6286ac
Add Swin, RF/XGBoost, Ensemble, and Diffusion congestion models
JayRaj21 Aug 7, 2026
c6ee9e9
Add test suite and fix Swin LayerNorm shape bug
JayRaj21 Aug 7, 2026
3b569d0
Add run_pipeline.sh and extract_existing.sh for easy end-to-end testing
JayRaj21 Aug 7, 2026
1261b7e
Fix extraction scripts: wrong OpenROAD Python API usage
JayRaj21 Aug 7, 2026
fe37faf
Add thermal prediction pipeline: HotSpot U-Net, dataset builder, and …
JayRaj21 Aug 11, 2026
a875c09
gitignore: exclude ML training logs and pipeline log directory
JayRaj21 Aug 12, 2026
c25f113
docs: add thermal report, update dev log, fix visualizer warnings
JayRaj21 Aug 14, 2026
e8561d3
Fix CI: apply black formatting to all Python files
JayRaj21 Aug 22, 2026
4d64fba
Merge remote-tracking branch 'origin/master' into thermal-solver
JayRaj21 Aug 27, 2026
d4cea7c
docs: log thermal-solver sync with fork master in dev log
JayRaj21 Aug 27, 2026
0413b03
Add Laplacian smoothness loss and cell-type weighted power model
JayRaj21 Aug 27, 2026
79ad0d6
Add IR-drop solver track (PDNSim-based) + real-data validation of bot…
JayRaj21 Aug 27, 2026
1b39966
Expand real IR-drop/thermal validation to 6 designs across all 3 PDKs
JayRaj21 Aug 27, 2026
dc3e4f8
Fix extract_irdrop_batch.sh: resolve liberty/voltage via make print-X…
JayRaj21 Aug 27, 2026
b6e9947
no-mistakes(review): Contain make print-X errors and gate it behind i…
JayRaj21 Aug 27, 2026
b548fa3
no-mistakes(document): Document review-round make print-X hardening f…
JayRaj21 Aug 28, 2026
3ea6e3d
Remove flow/thermal_report.html: blocked by security filename scan
JayRaj21 Aug 28, 2026
a132396
gitignore: re-exclude flow/thermal_report.html
JayRaj21 Aug 28, 2026
5e9a74b
no-mistakes: apply CI fixes
JayRaj21 Aug 28, 2026
1d54c96
no-mistakes: apply CI fixes
JayRaj21 Aug 28, 2026
0e0f833
Fix flow/ cd-depth in ml scripts after no-mistakes' flow/ml -> flow/u…
JayRaj21 Aug 28, 2026
2cf36ea
docs: log no-mistakes validation run + path-move fix in DESIGN_RUNS.md
JayRaj21 Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,13 @@ MODULE.bazel.lock
# python venv
venv/
tmp/

# Congestion/thermal ML — generated data, model weights, and logs
flow/util/ml/congestion/data/*.npy
flow/util/ml/congestion/data/*.npz
flow/util/ml/congestion/data/*.png
flow/util/ml/congestion/checkpoints/*.pt
flow/util/ml/data/
flow/util/ml/congestion/*.log
flow/util/ml/congestion/pipeline/logs/
flow/thermal_report.html
Binary file not shown.
49 changes: 49 additions & 0 deletions flow/util/ml/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Custom ORFS image with ML dependencies (HotSpot thermal solver + Python packages).
#
# Build:
# docker build -t openroad/orfs-ml:latest flow/util/ml/
#
# Use (instead of plain docker_shell):
# OR_IMAGE=openroad/orfs-ml:latest util/docker_shell <cmd>
#
# The base image already contains OpenROAD, Yosys, KLayout, and all ORFS tooling.
# This layer adds:
# - HotSpot v7.0 (compact thermal solver, RC circuit model)
# - Python packages needed by the ML pipeline

FROM openroad/orfs:latest

USER root

# ── System dependencies ────────────────────────────────────────────────────
# git/make/gcc: build HotSpot from source (not packaged in any distro)
# libblas-dev: HotSpot links against BLAS for matrix ops in its thermal solver
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
gcc \
make \
libblas-dev \
&& rm -rf /var/lib/apt/lists/*

# ── HotSpot v7.0 ──────────────────────────────────────────────────────────
# Clone, build, and install to /usr/local/bin so it's on PATH everywhere.
# --depth 1 fetches only the latest commit — faster, no history needed.
# We remove the source tree after installing to keep the image small.
RUN git clone --depth 1 https://github.com/uvahotspot/HotSpot.git /tmp/hotspot \
&& cd /tmp/hotspot \
&& make \
&& cp hotspot /usr/local/bin/hotspot \
&& rm -rf /tmp/hotspot

# ── Python ML packages ─────────────────────────────────────────────────────
# These are needed by training/inference scripts inside the container.
# torch-geometric and its deps (torch-scatter etc.) are installed separately
# because they require matching the PyTorch version already in the base image.
RUN pip3 install --no-cache-dir \
numpy \
scipy \
scikit-learn \
xgboost \
torch \
torch-geometric

1,102 changes: 1,102 additions & 0 deletions flow/util/ml/congestion/DESIGN_RUNS.md

Large diffs are not rendered by default.

Empty file.
101 changes: 101 additions & 0 deletions flow/util/ml/congestion/data_collection/batch_run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env bash
# Collect congestion training data for a set of designs.
# Runs each design through placement + GRT, then extracts features and labels.
#
# Usage: bash batch_run.sh [--grid N] [--out-dir DIR]
#
# Must be run from the flow/ directory.
set -euo pipefail

GRID=64
OUT_DIR="util/ml/congestion/data"
FLOW_DIR="$(cd "$(dirname "$0")/../../../.." && pwd)"

while [[ $# -gt 0 ]]; do
case "$1" in
--grid) GRID="$2"; shift 2 ;;
--out-dir) OUT_DIR="$2"; shift 2 ;;
*) echo "Unknown arg: $1" >&2; exit 1 ;;
esac
done

mkdir -p "$OUT_DIR"

# Designs to collect data from — mix of sizes and complexities
DESIGNS=(
"nangate45/aes"
"nangate45/gcd"
"nangate45/jpeg"
"nangate45/swerv"
"nangate45/swerv_wrapper"
"nangate45/ibex"
"nangate45/coyote_tc"
"nangate45/tinyRocket"
"sky130hd/gcd"
"sky130hd/ibex"
"sky130hd/jpeg"
"sky130hd/aes"
)

ok=0
fail=0

for entry in "${DESIGNS[@]}"; do
platform="${entry%/*}"
design="${entry#*/}"
tag="${platform}_${design}"
features_out="/work/util/ml/congestion/data/${tag}_features.npz"
labels_out="/work/util/ml/congestion/data/${tag}_labels.npz"
config="/work/designs/${platform}/${design}/config.mk"

echo "=== $tag ==="

# Check config exists
if [[ ! -f "designs/${platform}/${design}/config.mk" ]]; then
echo " SKIP — no config.mk"
continue
fi

# Run through GRT (make's dependency chain handles place+cts+grt)
echo " Running make grt..."
if ! util/docker_shell make \
DESIGN_CONFIG="$config" \
DESIGN_HOME=/work/designs \
grt 2>&1 | tee "/tmp/${tag}_make.log" | tail -5; then
echo " FAIL — make grt exited non-zero"
(( fail++ )) || true
continue
fi

# Extract features from detailed placement ODB
# Resolve nickname from config (DESIGN_NICKNAME may differ from folder name)
nickname=$(grep -m1 'DESIGN_NICKNAME' "designs/${platform}/${design}/config.mk" \
| sed 's/.*=\s*//' | tr -d '[:space:]') || nickname="$design"
dp_odb="/work/results/${platform}/${nickname}/base/3_5_place_dp.odb"
grt_odb="/work/results/${platform}/${nickname}/base/5_1_grt.odb"

echo " Extracting features..."
if ! util/docker_shell openroad -python \
/work/util/ml/congestion/data_collection/extract_features.py \
--odb "$dp_odb" --out "$features_out" --grid "$GRID"; then
echo " FAIL — feature extraction"
(( fail++ )) || true
continue
fi

echo " Extracting labels..."
if ! util/docker_shell openroad -python \
/work/util/ml/congestion/data_collection/extract_labels.py \
--odb "$grt_odb" --out "$labels_out" --grid "$GRID"; then
echo " FAIL — label extraction"
(( fail++ )) || true
continue
fi

echo " OK"
(( ok++ )) || true
done

echo ""
echo "Done: $ok succeeded, $fail failed"
echo "Data in: $OUT_DIR"
79 changes: 79 additions & 0 deletions flow/util/ml/congestion/data_collection/extract_existing.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env bash
# Extract features and labels from ODB files that already exist in flow/results/.
# Skips designs where either ODB is missing.
# Run from the flow/ directory.
set -euo pipefail

GRID=${1:-64}
OUT_DIR="util/ml/congestion/data"
mkdir -p "$OUT_DIR"

ok=0
skip=0
fail=0

# List of platform/nickname pairs to try
DESIGNS=(
"asap7 aes"
"nangate45 aes"
"nangate45 adder4"
"nangate45 dynamic_node"
"nangate45 gcd"
"nangate45 ibex"
"nangate45 jpeg"
"nangate45 swerv"
"nangate45 tinyRocket"
"sky130hd aes"
"sky130hd ibex"
"sky130hd jpeg"
"sky130hd riscv32i"
)

for entry in "${DESIGNS[@]}"; do
platform=$(echo "$entry" | cut -d' ' -f1)
nickname=$(echo "$entry" | cut -d' ' -f2)
tag="${platform}_${nickname}"

dp_odb="results/${platform}/${nickname}/base/3_5_place_dp.odb"
grt_odb="results/${platform}/${nickname}/base/5_1_grt.odb"

if [[ ! -f "$dp_odb" || ! -f "$grt_odb" ]]; then
echo "SKIP $tag — missing ODB files"
(( skip++ )) || true
continue
fi

feat_out="${OUT_DIR}/${tag}_features.npz"
label_out="${OUT_DIR}/${tag}_labels.npz"

echo "=== $tag ==="

echo " Extracting features..."
if ! util/docker_shell openroad -python \
/work/util/ml/congestion/data_collection/extract_features.py \
--odb "/work/${dp_odb}" \
--out "/work/${feat_out}" \
--grid "$GRID" < /dev/null 2>&1 | sed 's/^/ /'; then
echo " FAIL — feature extraction"
(( fail++ )) || true
continue
fi

echo " Extracting labels..."
if ! util/docker_shell openroad -python \
/work/util/ml/congestion/data_collection/extract_labels.py \
--odb "/work/${grt_odb}" \
--out "/work/${label_out}" \
--grid "$GRID" < /dev/null 2>&1 | sed 's/^/ /'; then
echo " FAIL — label extraction"
(( fail++ )) || true
continue
fi

echo " OK"
(( ok++ )) || true
done

echo ""
echo "Done: $ok extracted, $skip skipped, $fail failed"
echo "Data written to: $OUT_DIR"
109 changes: 109 additions & 0 deletions flow/util/ml/congestion/data_collection/extract_features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""
Extract input features from an ODB file after detailed placement.

Produces a .npz with four 64x64 arrays:
cell_density - normalized cell area per grid cell
macro_density - fraction of cell area occupied by macros
pin_density - number of instance pins per grid cell (normalized)
fanout_density - mean fanout of cells in each grid cell (normalized)

Run inside Docker via util/docker_shell:
openroad -python extract_features.py --odb <path> --out <path.npz> [--grid 64]
"""

import argparse
import sys

import numpy as np
from openroad import Design, Tech


def _parse_args():
ap = argparse.ArgumentParser()
ap.add_argument("--odb", required=True)
ap.add_argument("--out", required=True)
ap.add_argument("--grid", type=int, default=64)
return ap.parse_args()


def extract_features(odb_path: str, grid: int = 64) -> dict[str, np.ndarray]:
tech = Tech()
design = Design(tech)
design.readDb(odb_path)
block = design.getBlock()

die = block.getDieArea()
x0, y0 = die.xMin(), die.yMin()
x1, y1 = die.xMax(), die.yMax()
die_w = x1 - x0
die_h = y1 - y0

cell_density = np.zeros((grid, grid), dtype=np.float32)
macro_density = np.zeros((grid, grid), dtype=np.float32)
pin_density = np.zeros((grid, grid), dtype=np.float32)
fanout_sum = np.zeros((grid, grid), dtype=np.float32)
fanout_count = np.zeros((grid, grid), dtype=np.float32)

for inst in block.getInsts():
bbox = inst.getBBox()
cx = (bbox.xMin() + bbox.xMax()) / 2
cy = (bbox.yMin() + bbox.yMax()) / 2

gx = int((cx - x0) / die_w * grid)
gy = int((cy - y0) / die_h * grid)
gx = min(max(gx, 0), grid - 1)
gy = min(max(gy, 0), grid - 1)

cell_w = bbox.xMax() - bbox.xMin()
cell_h = bbox.yMax() - bbox.yMin()
area = cell_w * cell_h

cell_density[gy, gx] += area
master = inst.getMaster()
if master.isBlock():
macro_density[gy, gx] += area

iterm_count = 0
fanout = 0
for iterm in inst.getITerms():
iterm_count += 1
net = iterm.getNet()
if net is not None:
fanout += net.getITermCount()
pin_density[gy, gx] += iterm_count
if iterm_count > 0:
fanout_sum[gy, gx] += fanout / iterm_count
fanout_count[gy, gx] += 1

cell_area = die_w / grid * die_h / grid
cell_density /= cell_area + 1e-9
macro_density /= cell_area + 1e-9

max_pins = pin_density.max()
if max_pins > 0:
pin_density /= max_pins

fanout_density = np.where(fanout_count > 0, fanout_sum / fanout_count, 0.0)
max_fo = fanout_density.max()
if max_fo > 0:
fanout_density /= max_fo

return {
"cell_density": cell_density,
"macro_density": macro_density,
"pin_density": pin_density,
"fanout_density": fanout_density,
}


def main():
args = _parse_args()
features = extract_features(args.odb, args.grid)
np.savez(args.out, **features)
for k, v in features.items():
print(f" {k}: min={v.min():.4f} mean={v.mean():.4f} max={v.max():.4f}")
print(f"Saved -> {args.out}")


if __name__ == "__main__":
main()
Loading
Loading