Chip_Router / chip_routingv2.py
TechAvenger's picture
Upload 9 files
44b3df5 verified
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Author : Mihir Mithani
@Date : 08-05-2026 , 10:44
@File : chip_routingv2.py
@Desc :
"""
"""
chip_routing_cuopt.py
─────────────────────────────────────────────────────────────────────────────
Interactive chip routing optimizer using NVIDIA cuOpt.
Features
────────
β€’ User enters grid dimensions (m Γ— n) via GUI
β€’ Click cells to select them, then name the component placed there
β€’ Create connection pairs by clicking source β†’ sink cells
β€’ Runs cuOpt VRP solver to find optimal routing
β€’ Visualises the routed result on the grid
Usage
─────
pip install requests
python chip_routing_cuopt.py
Set your API key in the NVIDIA_API_KEY variable below (or via env var).
"""
import time
import tkinter as tk
from tkinter import messagebox, simpledialog
import requests
import API
# ─── API Configuration ────────────────────────────────────────────────────────
NVIDIA_API_KEY = API.API()
INVOKE_URL = "https://optimize.api.nvidia.com/v1/nvidia/cuopt"
FETCH_URL_FMT = "https://optimize.api.nvidia.com/v1/status/{}"
POLL_INTERVAL_S = 1.2
MAX_WAIT_S = 120
HEADERS = {
"Authorization": f"Bearer {NVIDIA_API_KEY}",
"Accept": "application/json",
"Content-Type": "application/json",
}
# ─── Colors ───────────────────────────────────────────────────────────────────
CLR_BG = "#0f1117"
CLR_PANEL = "#1a1d27"
CLR_BORDER = "#2e3248"
CLR_ACCENT = "#5b6af0"
CLR_ACCENT2 = "#a78bfa"
CLR_TEXT = "#e8eaf6"
CLR_MUTED = "#6b7280"
CLR_CELL_EMPTY = "#1e2133"
CLR_CELL_COMP = "#1e2d50"
CLR_CELL_DEPOT = "#2d1e50"
CLR_CELL_SELA = "#1e3b2a" # source (green tint)
CLR_CELL_SELB = "#3b2a1e" # sink (amber tint)
CLR_CELL_ROUTED = "#1a3320"
CLR_CELL_HOVER = "#252840"
CLR_SUCCESS = "#34d399"
CLR_WARNING = "#fbbf24"
CLR_DANGER = "#f87171"
CLR_NET_COLORS = [
"#5b6af0", "#a78bfa", "#34d399", "#fbbf24", "#f87171",
"#38bdf8", "#fb7185", "#4ade80", "#facc15", "#818cf8",
]
CELL_W = 90
CELL_H = 60
PAD = 12
# ─── Matrix Builders ──────────────────────────────────────────────────────────
def node_to_rc(n, cols):
return divmod(n, cols)
def manhattan(a, b, cols):
r1, c1 = node_to_rc(a, cols)
r2, c2 = node_to_rc(b, cols)
return abs(r1 - r2) + abs(c1 - c2)
def build_cost_matrix(rows, cols, layer_id):
n = rows * cols
mat = []
for a in range(n):
row = []
ra, ca = node_to_rc(a, cols)
for b in range(n):
if a == b:
row.append(0)
continue
rb, cb = node_to_rc(b, cols)
hd = abs(ca - cb)
vd = abs(ra - rb)
penalty = vd if layer_id == 1 else hd
row.append(max(1, hd + vd + penalty))
mat.append(row)
return mat
def build_delay_matrix(rows, cols):
n = rows * cols
mat = []
for a in range(n):
row = []
for b in range(n):
if a == b:
row.append(0)
else:
row.append(max(1, manhattan(a, b, cols)))
mat.append(row)
return mat
def build_payload(rows, cols, pairs):
n_nets = len(pairs)
max_t = rows * cols + 4
cap = n_nets + 4
cost_m1 = build_cost_matrix(rows, cols, 1)
cost_m2 = build_cost_matrix(rows, cols, 2)
delay = build_delay_matrix(rows, cols)
return {
"action": "cuOpt_OptimizedRouting",
"data": {
"cost_matrix_data": {"data": {"1": cost_m1, "2": cost_m2}},
"travel_time_matrix_data": {"data": {"1": delay, "2": delay}},
"fleet_data": {
"vehicle_locations": [[0, 0], [0, 0]],
"vehicle_ids": ["M1_router", "M2_router"],
"capacities": [[cap, cap], [cap, cap]],
"vehicle_time_windows": [[0, max_t], [0, max_t]],
"vehicle_types": [1, 2],
"vehicle_max_costs": [rows * cols * 6, rows * cols * 6],
"vehicle_max_times": [max_t, max_t],
"skip_first_trips": [False, False],
"drop_return_trips": [True, True],
"min_vehicles": 1,
},
"task_data": {
"task_locations": [p["sink"] for p in pairs],
"task_ids": [p["name"] for p in pairs],
"demand": [[1] * n_nets, [1] * n_nets],
"task_time_windows": [[0, max_t]] * n_nets,
"service_times": [0] * n_nets,
},
"solver_config": {
"time_limit": 5,
"objectives": {
"cost": 2,
"travel_time": 1,
"variance_route_size": 1,
"variance_route_service_time": 0,
"prize": 0,
},
"verbose_mode": False,
"error_logging": True,
},
},
"client_version": "chip_router_interactive_v2",
}
def call_cuopt(payload):
session = requests.Session()
response = session.post(INVOKE_URL, headers=HEADERS, json=payload, timeout=30)
elapsed = 0
while response.status_code == 202:
req_id = response.headers.get("NVCF-REQID", "")
fetch_url = FETCH_URL_FMT.format(req_id)
time.sleep(POLL_INTERVAL_S)
elapsed += POLL_INTERVAL_S
if elapsed > MAX_WAIT_S:
raise TimeoutError("cuOpt request timed out")
response = session.get(fetch_url, headers=HEADERS, timeout=30)
response.raise_for_status()
return response.json()
# ─── Main Application ─────────────────────────────────────────────────────────
class ChipRoutingApp(tk.Tk):
def __init__(self):
super().__init__()
self.title("Chip Routing Optimizer Β· cuOpt")
self.configure(bg=CLR_BG)
self.resizable(True, True)
self.geometry("1100x750")
# State
self.rows = 0
self.cols = 0
self.components = {} # node_idx β†’ component name
self.pairs = [] # list of dicts {name, src, sink, src_name, sink_name}
self.sel_cell = None # currently selected cell (editing mode)
self.pair_src = None # first cell picked in pair mode
self.mode = "edit" # "edit" | "pair" | "result"
self.cell_btns = {} # node_idx β†’ canvas item ids
self.net_colors = {} # pair name β†’ color
self._build_ui()
# ── UI Construction ───────────────────────────────────────────────────────
def _build_ui(self):
# Left panel
self.left = tk.Frame(self, bg=CLR_PANEL, width=260)
self.left.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 0))
self.left.pack_propagate(False)
self._build_left_panel()
# Right canvas area
self.right = tk.Frame(self, bg=CLR_BG)
self.right.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Canvas with scrollbars
self.canvas_frame = tk.Frame(self.right, bg=CLR_BG)
self.canvas_frame.pack(fill=tk.BOTH, expand=True, padx=PAD, pady=PAD)
self.hscroll = tk.Scrollbar(self.canvas_frame, orient=tk.HORIZONTAL)
self.vscroll = tk.Scrollbar(self.canvas_frame, orient=tk.VERTICAL)
self.canvas = tk.Canvas(
self.canvas_frame,
bg=CLR_BG,
highlightthickness=0,
xscrollcommand=self.hscroll.set,
yscrollcommand=self.vscroll.set,
)
self.hscroll.config(command=self.canvas.xview)
self.vscroll.config(command=self.canvas.yview)
self.hscroll.pack(side=tk.BOTTOM, fill=tk.X)
self.vscroll.pack(side=tk.RIGHT, fill=tk.Y)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Status bar
self.status_var = tk.StringVar(value="Enter grid dimensions and click Build.")
self.status_bar = tk.Label(
self.right, textvariable=self.status_var,
bg=CLR_PANEL, fg=CLR_MUTED, font=("Consolas", 10),
anchor=tk.W, padx=12, pady=6,
)
self.status_bar.pack(fill=tk.X, side=tk.BOTTOM)
def _build_left_panel(self):
lp = self.left
tk.Label(lp, text="CHIP ROUTER", bg=CLR_PANEL, fg=CLR_ACCENT,
font=("Consolas", 13, "bold")).pack(pady=(18, 2))
tk.Label(lp, text="cuOpt VRP Engine", bg=CLR_PANEL, fg=CLR_MUTED,
font=("Consolas", 9)).pack(pady=(0, 16))
self._sep(lp)
# ── Step 1: Grid dims ─────────────────────────────────────────────
tk.Label(lp, text="β‘  GRID DIMENSIONS", bg=CLR_PANEL, fg=CLR_TEXT,
font=("Consolas", 10, "bold")).pack(anchor=tk.W, padx=14, pady=(10, 4))
df = tk.Frame(lp, bg=CLR_PANEL)
df.pack(fill=tk.X, padx=14, pady=4)
tk.Label(df, text="Rows (m)", bg=CLR_PANEL, fg=CLR_MUTED,
font=("Consolas", 9)).grid(row=0, column=0, sticky=tk.W)
self.rows_var = tk.IntVar(value=4)
tk.Spinbox(df, from_=2, to=12, textvariable=self.rows_var, width=5,
bg=CLR_CELL_EMPTY, fg=CLR_TEXT, insertbackground=CLR_TEXT,
buttonbackground=CLR_BORDER, relief=tk.FLAT).grid(row=0, column=1, padx=6)
tk.Label(df, text="Cols (n)", bg=CLR_PANEL, fg=CLR_MUTED,
font=("Consolas", 9)).grid(row=1, column=0, sticky=tk.W, pady=(4, 0))
self.cols_var = tk.IntVar(value=5)
tk.Spinbox(df, from_=2, to=12, textvariable=self.cols_var, width=5,
bg=CLR_CELL_EMPTY, fg=CLR_TEXT, insertbackground=CLR_TEXT,
buttonbackground=CLR_BORDER, relief=tk.FLAT).grid(row=1, column=1, padx=6, pady=(4, 0))
self._btn(lp, "β–Ά BUILD GRID", self._on_build, CLR_ACCENT)
self._sep(lp)
# ── Step 2: Component placement ───────────────────────────────────
tk.Label(lp, text="β‘‘ PLACE COMPONENTS", bg=CLR_PANEL, fg=CLR_TEXT,
font=("Consolas", 10, "bold")).pack(anchor=tk.W, padx=14, pady=(10, 4))
tk.Label(lp, text="Click any cell to select it,\nthen name the component below.",
bg=CLR_PANEL, fg=CLR_MUTED, font=("Consolas", 8),
justify=tk.LEFT).pack(anchor=tk.W, padx=14)
cf = tk.Frame(lp, bg=CLR_PANEL)
cf.pack(fill=tk.X, padx=14, pady=6)
tk.Label(cf, text="Name", bg=CLR_PANEL, fg=CLR_MUTED,
font=("Consolas", 9)).pack(side=tk.LEFT)
self.comp_var = tk.StringVar()
self.comp_entry = tk.Entry(cf, textvariable=self.comp_var, width=14,
bg=CLR_CELL_EMPTY, fg=CLR_TEXT, relief=tk.FLAT,
insertbackground=CLR_TEXT, font=("Consolas", 10))
self.comp_entry.pack(side=tk.LEFT, padx=(6, 0))
self.comp_entry.bind("<Return>", lambda _: self._on_place_comp())
bf = tk.Frame(lp, bg=CLR_PANEL)
bf.pack(fill=tk.X, padx=14)
self._btn_small(bf, "Place", self._on_place_comp, CLR_SUCCESS, side=tk.LEFT)
self._btn_small(bf, "Clear", self._on_clear_comp, CLR_DANGER, side=tk.LEFT)
self.sel_label = tk.Label(lp, text="No cell selected", bg=CLR_PANEL,
fg=CLR_MUTED, font=("Consolas", 8))
self.sel_label.pack(anchor=tk.W, padx=14, pady=(4, 0))
self._sep(lp)
# ── Step 3: Wire pairs ────────────────────────────────────────────
tk.Label(lp, text="β‘’ WIRE PAIRS", bg=CLR_PANEL, fg=CLR_TEXT,
font=("Consolas", 10, "bold")).pack(anchor=tk.W, padx=14, pady=(10, 4))
tk.Label(lp, text="Click to toggle pair mode.",
bg=CLR_PANEL, fg=CLR_MUTED, font=("Consolas", 8),
justify=tk.LEFT).pack(anchor=tk.W, padx=14)
self.pair_mode_btn = tk.Button(
lp, text="β›“ ENTER PAIR MODE", font=("Consolas", 9, "bold"),
bg=CLR_CELL_EMPTY, fg=CLR_ACCENT2, relief=tk.FLAT,
activebackground=CLR_BORDER, activeforeground=CLR_ACCENT2,
command=self._toggle_pair_mode, cursor="hand2",
)
self.pair_mode_btn.pack(fill=tk.X, padx=14, pady=(6, 4))
self.pair_status_lbl = tk.Label(lp, text="", bg=CLR_PANEL,
fg=CLR_WARNING, font=("Consolas", 8))
self.pair_status_lbl.pack(anchor=tk.W, padx=14)
# Pair list
self.pair_list_frame = tk.Frame(lp, bg=CLR_PANEL)
self.pair_list_frame.pack(fill=tk.X, padx=14, pady=4)
self._sep(lp)
# ── Step 4: Route ─────────────────────────────────────────────────
tk.Label(lp, text="β‘£ ROUTE", bg=CLR_PANEL, fg=CLR_TEXT,
font=("Consolas", 10, "bold")).pack(anchor=tk.W, padx=14, pady=(10, 4))
self._btn(lp, "⚑ RUN CUOPT", self._on_run, CLR_ACCENT2)
self._btn(lp, "β†Ί RESET ALL", self._on_reset, CLR_MUTED)
def _sep(self, parent):
tk.Frame(parent, bg=CLR_BORDER, height=1).pack(fill=tk.X, padx=0, pady=4)
def _btn(self, parent, text, cmd, color):
tk.Button(
parent, text=text, font=("Consolas", 9, "bold"),
bg=color, fg=CLR_BG, relief=tk.FLAT,
activebackground=color, activeforeground=CLR_BG,
command=cmd, cursor="hand2", padx=6, pady=6,
).pack(fill=tk.X, padx=14, pady=4)
def _btn_small(self, parent, text, cmd, color, side=tk.LEFT):
tk.Button(
parent, text=text, font=("Consolas", 8),
bg=color, fg=CLR_BG, relief=tk.FLAT,
activebackground=color, activeforeground=CLR_BG,
command=cmd, cursor="hand2", padx=6, pady=3,
).pack(side=side, padx=(0, 4))
# ── Grid Drawing ──────────────────────────────────────────────────────────
def _on_build(self):
self.rows = self.rows_var.get()
self.cols = self.cols_var.get()
self.components = {}
self.pairs = []
self.sel_cell = None
self.pair_src = None
self.mode = "edit"
self.net_colors = {}
self.pair_mode_btn.config(text="β›“ ENTER PAIR MODE", bg=CLR_CELL_EMPTY, fg=CLR_ACCENT2)
self._render_grid()
self._refresh_pair_list()
self._set_status(f"Grid {self.rows}Γ—{self.cols} built. Click cells to name components.")
def _render_grid(self, routed_nodes=None, route_assignments=None):
self.canvas.delete("all")
self.cell_items = {} # node β†’ (rect_id, text_id, sub_id)
routed_nodes = routed_nodes or {}
route_assignments = route_assignments or {}
total_w = self.cols * CELL_W + PAD * 2
total_h = self.rows * CELL_H + PAD * 2
self.canvas.config(scrollregion=(0, 0, total_w, total_h))
for r in range(self.rows):
for c in range(self.cols):
n = r * self.cols + c
x0 = PAD + c * CELL_W
y0 = PAD + r * CELL_H
x1 = x0 + CELL_W - 2
y1 = y0 + CELL_H - 2
cx = (x0 + x1) / 2
cy = (y0 + y1) / 2
color = self._cell_color(n, routed_nodes)
rid = self.canvas.create_rectangle(
x0, y0, x1, y1, fill=color, outline=CLR_BORDER,
width=1, tags=(f"cell_{n}", "cell"),
)
# node index label (bottom-right)
self.canvas.create_text(
x1 - 3, y1 - 2, text=f"{r},{c}",
fill=CLR_MUTED, font=("Consolas", 7), anchor=tk.SE,
tags=(f"cell_{n}",),
)
# component name
label = "DEPOT" if n == 0 else self.components.get(n, "")
lcolor = CLR_ACCENT2 if n == 0 else CLR_TEXT
tid = self.canvas.create_text(
cx, cy - 4, text=label,
fill=lcolor, font=("Consolas", 9, "bold"),
width=CELL_W - 8, anchor=tk.CENTER,
tags=(f"cell_{n}",),
)
# net assignment (result mode)
sub = ""
if n in route_assignments:
sub = route_assignments[n]
sid = self.canvas.create_text(
cx, y1 - 10, text=sub,
fill=CLR_SUCCESS, font=("Consolas", 7),
width=CELL_W - 6, anchor=tk.CENTER,
tags=(f"cell_{n}",),
)
self.cell_items[n] = (rid, tid, sid)
# bind clicks
for tag_id in (rid, tid, sid):
self.canvas.tag_bind(tag_id, "<Button-1>",
lambda e, nd=n: self._on_cell_click(nd))
self.canvas.tag_bind(tag_id, "<Enter>",
lambda e, nd=n: self._on_cell_hover(nd, True))
self.canvas.tag_bind(tag_id, "<Leave>",
lambda e, nd=n: self._on_cell_hover(nd, False))
# Draw pair source lines (in pair mode, show pairs as arrows)
if self.mode in ("pair", "edit"):
self._draw_pair_arrows()
def _draw_pair_arrows(self):
self.canvas.delete("arrow")
for i, p in enumerate(self.pairs):
color = self.net_colors.get(p["name"], CLR_NET_COLORS[i % len(CLR_NET_COLORS)])
sr, sc = node_to_rc(p["src"], self.cols)
dr, dc = node_to_rc(p["sink"], self.cols)
sx = PAD + sc * CELL_W + CELL_W // 2
sy = PAD + sr * CELL_H + CELL_H // 2
dx = PAD + dc * CELL_W + CELL_W // 2
dy = PAD + dr * CELL_H + CELL_H // 2
self.canvas.create_line(
sx, sy, dx, dy, fill=color, width=2,
arrow=tk.LAST, arrowshape=(8, 10, 4),
dash=(4, 3), tags="arrow",
)
mx, my = (sx + dx) / 2, (sy + dy) / 2
self.canvas.create_text(
mx, my - 8, text=p["name"],
fill=color, font=("Consolas", 7, "bold"),
tags="arrow",
)
def _cell_color(self, n, routed_nodes):
if n == 0:
return CLR_CELL_DEPOT
if n in routed_nodes:
return CLR_CELL_ROUTED
if n == self.pair_src:
return CLR_CELL_SELB
if n == self.sel_cell:
return CLR_CELL_SELB
if n in self.components:
return CLR_CELL_COMP
return CLR_CELL_EMPTY
def _recolor_cell(self, n, color=None):
if n not in self.cell_items:
return
rid = self.cell_items[n][0]
c = color or self._cell_color(n, {})
self.canvas.itemconfig(rid, fill=c)
def _on_cell_hover(self, n, entering):
if n not in self.cell_items:
return
if entering:
cur = self.canvas.itemcget(self.cell_items[n][0], "fill")
if cur == CLR_CELL_EMPTY:
self.canvas.itemconfig(self.cell_items[n][0], fill=CLR_CELL_HOVER)
else:
self._recolor_cell(n)
# ── Cell Click Logic ──────────────────────────────────────────────────────
def _on_cell_click(self, n):
if self.mode == "edit":
# deselect previous
if self.sel_cell is not None:
self._recolor_cell(self.sel_cell)
self.sel_cell = n
self._recolor_cell(n, CLR_CELL_SELB)
r, c = node_to_rc(n, self.cols)
name = self.components.get(n, "")
self.comp_var.set(name)
self.comp_entry.focus_set()
if n == 0:
self.sel_label.config(text=f"Cell ({r},{c}) β€” depot/origin")
else:
self.sel_label.config(
text=f"Cell ({r},{c}){' Β· ' + name if name else ' β€” unnamed'}"
)
self._set_status(f"Selected ({r},{c}). Type a name and press Enter or Place.")
elif self.mode == "pair":
if self.pair_src is None:
# pick source
self.pair_src = n
self._recolor_cell(n, CLR_CELL_SELB)
r, c = node_to_rc(n, self.cols)
name = self.components.get(n, "DEPOT" if n == 0 else f"node{n}")
self.pair_status_lbl.config(
text=f"Source: {name} ({r},{c}). Now pick sink β†’"
)
else:
if n == self.pair_src:
self._recolor_cell(n)
self.pair_src = None
self.pair_status_lbl.config(text="Source cleared. Pick again.")
return
# ask for net name
default_name = f"NET{len(self.pairs)}"
net_name = simpledialog.askstring(
"Net name",
f"Name for this connection\n(src→sink):",
initialvalue=default_name,
parent=self,
)
if not net_name:
net_name = default_name
net_name = net_name.strip().upper().replace(" ", "_")
src_name = self.components.get(self.pair_src, "DEPOT" if self.pair_src == 0 else f"node{self.pair_src}")
sink_name = self.components.get(n, "DEPOT" if n == 0 else f"node{n}")
ci = len(self.pairs) % len(CLR_NET_COLORS)
self.net_colors[net_name] = CLR_NET_COLORS[ci]
self.pairs.append({
"name": net_name,
"src": self.pair_src,
"sink": n,
"src_name": src_name,
"sink_name": sink_name,
})
self._recolor_cell(self.pair_src)
self.pair_src = None
self.pair_status_lbl.config(text=f"Pair '{net_name}' added. Pick next source β†’")
self._refresh_pair_list()
self._draw_pair_arrows()
self._set_status(f"Pair '{net_name}' added. {len(self.pairs)} pair(s) total.")
# ── Component Editing ─────────────────────────────────────────────────────
def _on_place_comp(self):
if self.sel_cell is None or self.sel_cell == 0:
self._set_status("Select a non-depot cell first.")
return
name = self.comp_var.get().strip()
if not name:
self._set_status("Enter a component name first.")
return
self.components[self.sel_cell] = name
# update canvas text
if self.sel_cell in self.cell_items:
self.canvas.itemconfig(self.cell_items[self.sel_cell][1], text=name)
self._recolor_cell(self.sel_cell, CLR_CELL_COMP)
r, c = node_to_rc(self.sel_cell, self.cols)
self._set_status(f"Placed '{name}' at ({r},{c}).")
# also refresh any pairs that reference this node
for p in self.pairs:
if p["src"] == self.sel_cell:
p["src_name"] = name
if p["sink"] == self.sel_cell:
p["sink_name"] = name
self._refresh_pair_list()
def _on_clear_comp(self):
if self.sel_cell is None or self.sel_cell == 0:
return
self.components.pop(self.sel_cell, None)
if self.sel_cell in self.cell_items:
self.canvas.itemconfig(self.cell_items[self.sel_cell][1], text="")
self._recolor_cell(self.sel_cell, CLR_CELL_SELB)
self.comp_var.set("")
self._set_status("Component cleared.")
# ── Pair Mode ─────────────────────────────────────────────────────────────
def _toggle_pair_mode(self):
if self.rows == 0:
self._set_status("Build a grid first.")
return
if self.mode == "edit":
self.mode = "pair"
self.pair_mode_btn.config(
text="βœ• EXIT PAIR MODE", bg=CLR_ACCENT2, fg=CLR_BG
)
self.pair_status_lbl.config(text="Click a source cell β†’")
self._set_status("Pair mode: click source, then sink to create a connection.")
else:
self.mode = "edit"
self.pair_src = None
self.pair_mode_btn.config(
text="β›“ ENTER PAIR MODE", bg=CLR_CELL_EMPTY, fg=CLR_ACCENT2
)
self.pair_status_lbl.config(text="")
self._render_grid()
self._set_status("Back to edit mode.")
def _refresh_pair_list(self):
for w in self.pair_list_frame.winfo_children():
w.destroy()
for i, p in enumerate(self.pairs):
color = self.net_colors.get(p["name"], CLR_ACCENT)
row = tk.Frame(self.pair_list_frame, bg=CLR_CELL_EMPTY)
row.pack(fill=tk.X, pady=2)
tk.Label(row, text="●", bg=CLR_CELL_EMPTY, fg=color,
font=("Consolas", 9)).pack(side=tk.LEFT, padx=(4, 2))
tk.Label(row, text=f"{p['name']}: {p['src_name']}β†’{p['sink_name']}",
bg=CLR_CELL_EMPTY, fg=CLR_TEXT,
font=("Consolas", 8), anchor=tk.W).pack(side=tk.LEFT, fill=tk.X, expand=True)
tk.Button(row, text="βœ•", bg=CLR_CELL_EMPTY, fg=CLR_DANGER,
font=("Consolas", 8), relief=tk.FLAT, cursor="hand2",
command=lambda idx=i: self._remove_pair(idx)).pack(side=tk.RIGHT, padx=2)
def _remove_pair(self, idx):
if 0 <= idx < len(self.pairs):
self.pairs.pop(idx)
self._refresh_pair_list()
self._draw_pair_arrows()
# ── Run cuOpt ─────────────────────────────────────────────────────────────
def _on_run(self):
if self.rows == 0:
messagebox.showerror("No grid", "Build a grid first.")
return
if not self.pairs:
messagebox.showerror("No pairs", "Create at least one wire pair first.")
return
self._set_status("⏳ Building matrices and submitting to cuOpt…")
self.update()
try:
payload = build_payload(self.rows, self.cols, self.pairs)
body = call_cuopt(payload)
self._show_results(body)
except Exception as e:
self._set_status(f"ERROR: {e}")
messagebox.showerror("cuOpt error", str(e))
def _show_results(self, body):
routes = body.get("response", {}).get("solver_response", {})
vehicle_data = routes.get("vehicle_data", {})
obj = routes.get("solution_cost", routes.get("total_objective", "β€”"))
routed_nodes = {} # node β†’ list of net names
route_assignment = {} # node β†’ short label string
result_lines = [f"Solver objective: {obj}\n"]
for vid, data in vehicle_data.items():
task_seq = data.get("task_id", [])
route_nodes = data.get("route", [])
arrivals = data.get("arrival_stamp", [])
stops = []
for i, t in enumerate(task_seq):
if str(t) == "Depot":
continue
node = route_nodes[i] if i < len(route_nodes) else None
arr = arrivals[i] if i < len(arrivals) else "?"
stops.append((str(t), int(node) if node is not None else None, arr))
if node is not None:
n = int(node)
routed_nodes.setdefault(n, []).append(str(t))
layer = "M1" if "M1" in str(vid) else "M2"
result_lines.append(f"── {vid} [{layer}] ({len(stops)} nets)")
for net_name, node, arr in stops:
pair = next((p for p in self.pairs if p["name"] == net_name), None)
src = pair["src"] if pair else 0
r1, c1 = node_to_rc(src, self.cols)
r2, c2 = node_to_rc(node, self.cols) if node is not None else ("?", "?")
slack = "?"
if pair:
lat = pair.get("late", self.rows * self.cols + 4)
slack = f"{lat - float(arr):+.1f}" if arr != "?" else "?"
result_lines.append(
f" {net_name:<12} ({r1},{c1})β†’({r2},{c2}) "
f"arr={arr if arr != '?' else '?':<5} slack={slack}"
)
result_lines.append("")
# Build route_assignment label per node
for node, nets in routed_nodes.items():
route_assignment[node] = ",".join(nets)
self.mode = "result"
self._render_grid(routed_nodes=set(routed_nodes.keys()),
route_assignments=route_assignment)
# Show result window
self._show_result_window("\n".join(result_lines))
self._set_status(f"Routing complete. Objective={obj}. Routed {len(self.pairs)} net(s).")
def _show_result_window(self, text):
win = tk.Toplevel(self)
win.title("Routing Results")
win.configure(bg=CLR_BG)
win.geometry("540x420")
tk.Label(win, text="ROUTING RESULTS", bg=CLR_BG, fg=CLR_ACCENT,
font=("Consolas", 12, "bold")).pack(pady=(14, 4))
frame = tk.Frame(win, bg=CLR_BG)
frame.pack(fill=tk.BOTH, expand=True, padx=14, pady=(0, 14))
sb = tk.Scrollbar(frame)
sb.pack(side=tk.RIGHT, fill=tk.Y)
txt = tk.Text(frame, bg=CLR_PANEL, fg=CLR_TEXT, font=("Consolas", 9),
relief=tk.FLAT, yscrollcommand=sb.set, wrap=tk.NONE)
txt.pack(fill=tk.BOTH, expand=True)
sb.config(command=txt.yview)
txt.insert(tk.END, text)
txt.config(state=tk.DISABLED)
tk.Button(win, text="Close", bg=CLR_ACCENT, fg=CLR_BG,
font=("Consolas", 9, "bold"), relief=tk.FLAT,
command=win.destroy, cursor="hand2").pack(pady=(0, 12))
# ── Reset ─────────────────────────────────────────────────────────────────
def _on_reset(self):
self.components = {}
self.pairs = []
self.sel_cell = None
self.pair_src = None
self.mode = "edit"
self.net_colors = {}
self.pair_mode_btn.config(text="β›“ ENTER PAIR MODE", bg=CLR_CELL_EMPTY, fg=CLR_ACCENT2)
self.pair_status_lbl.config(text="")
self.sel_label.config(text="No cell selected")
self.comp_var.set("")
if self.rows:
self._render_grid()
self._refresh_pair_list()
self._set_status("Reset. Place components and re-wire pairs.")
# ── Helpers ───────────────────────────────────────────────────────────────
def _set_status(self, msg):
self.status_var.set(f" {msg}")
# ─── Entry Point ──────────────────────────────────────────────────────────────
if __name__ == "__main__":
app = ChipRoutingApp()
app.mainloop()