--negotiate: PathFinder rounds on the empty board before routing
Every connection is routed N times on the empty board, where routes may share cells at a price that doubles each round for every other net on a cell, and a cell that stays shared gains history. The real routing then pays that history on free cells, so the first routes stop taking the space later routes (a fine-pitch part's escapes) have no way round. Off by default. The old history term never steered: it only accrued at rip-up fights, after the first routes had claimed the contested space, and routed connections are never revisited in later passes.
This commit is contained in:
@@ -180,6 +180,7 @@ class GridRouter:
|
||||
seed: int | None = None,
|
||||
route_first: frozenset | None = None,
|
||||
access_cost: float = 0.0,
|
||||
negotiate: int = 0,
|
||||
rollback: bool = True,
|
||||
net_widths: dict[int, int] | None = None,
|
||||
via_drill: int | None = None,
|
||||
@@ -191,6 +192,15 @@ class GridRouter:
|
||||
# and the clearance around both) cost other nets this much extra, so
|
||||
# they route round a fine-pitch pin's way out instead of sealing it.
|
||||
self.access_cost = max(float(access_cost), 0.0)
|
||||
# Negotiated congestion (PathFinder): before the real routing, every
|
||||
# connection is routed this many times on the empty board, where routes
|
||||
# may overlap at a price that grows where they compete. The cells that
|
||||
# stay contested keep a history cost the real routing pays, so the first
|
||||
# routes leave room around the places later routes have no way round.
|
||||
self.negotiate = max(int(negotiate), 0)
|
||||
self._history: dict[Node, float] = {}
|
||||
self._present: dict[Node, dict[int, int]] | None = None
|
||||
self._present_cost = 0.0
|
||||
self._doorways: dict[Node, list[tuple[int, int]]] = {}
|
||||
self._pending: dict[int, int] = {}
|
||||
# Rip-up as a transaction: the connections a rip-up route crosses are
|
||||
@@ -767,6 +777,8 @@ class GridRouter:
|
||||
continue
|
||||
if self._doorways:
|
||||
enter += self._access(nxt, net_no)
|
||||
if self._history or self._present is not None:
|
||||
enter += self._congestion(nxt, net_no)
|
||||
step = 1.0 if dx == 0 or dy == 0 else math.sqrt(2)
|
||||
self._relax(
|
||||
current, nxt, base + step + enter, goal_cell, g_score, came_from, open_heap
|
||||
@@ -779,9 +791,12 @@ class GridRouter:
|
||||
if layer == cl:
|
||||
continue
|
||||
nxt = (cx, cy, layer)
|
||||
if g_via < g_score.get(nxt, math.inf):
|
||||
g_layer = g_via
|
||||
if self._history or self._present is not None:
|
||||
g_layer += self._congestion(nxt, net_no)
|
||||
if g_layer < g_score.get(nxt, math.inf):
|
||||
heapq.heappush(
|
||||
open_heap, (g_via + self._h(nxt, goal_cell), nxt, g_via, current)
|
||||
open_heap, (g_layer + self._h(nxt, goal_cell), nxt, g_layer, current)
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -1359,6 +1374,74 @@ class GridRouter:
|
||||
return self.access_cost
|
||||
return 0.0
|
||||
|
||||
# --- negotiated congestion --------------------------------------------
|
||||
|
||||
def _congestion(self, node: Node, net_no: int) -> float:
|
||||
"""History of a contested cell, plus while negotiating the price of
|
||||
sharing it with the other nets on it now."""
|
||||
cost = self._history.get(node, 0.0)
|
||||
if self._present is not None:
|
||||
users = self._present.get(node)
|
||||
if users:
|
||||
others = len(users) - (1 if net_no in users else 0)
|
||||
cost += self._present_cost * others
|
||||
return cost
|
||||
|
||||
def _footprint(self, path: list[Node]) -> set[Node]:
|
||||
"""The nodes a route claims: its cells and their clearance halo on
|
||||
its layer, and every via's footprint on all layers."""
|
||||
wire_segs, via_cells = _split_path(path)
|
||||
halo = [(0, 0), *self._halo_offsets]
|
||||
nodes: set[Node] = set()
|
||||
for layer, cells in wire_segs:
|
||||
for cx, cy in cells:
|
||||
for dx, dy in halo:
|
||||
nodes.add((cx + dx, cy + dy, layer))
|
||||
for cell in via_cells:
|
||||
for c in self._via_cells(cell):
|
||||
for layer in self.layers:
|
||||
nodes.add((*c, layer))
|
||||
return nodes
|
||||
|
||||
def _negotiate(self, order: list[_Connection], conns: list[_Connection]) -> None:
|
||||
"""Route every connection on the empty board ``self.negotiate`` times,
|
||||
letting routes share cells at a price: the number of other nets on a
|
||||
cell times a factor that doubles every round, plus the cell's history,
|
||||
which grows by one for every extra net each round it stays shared.
|
||||
Only the history is kept; the board is left empty."""
|
||||
present: dict[Node, dict[int, int]] = {}
|
||||
claimed: dict[int, set[Node]] = {}
|
||||
ends = [(c, c.a_end, c.b_end) for c in conns]
|
||||
self._present, self._present_cost = present, 0.5
|
||||
try:
|
||||
for _ in range(self.negotiate):
|
||||
for conn in order:
|
||||
for node in claimed.pop(conn.index, ()):
|
||||
users = present[node]
|
||||
users[conn.net_no] -= 1
|
||||
if not users[conn.net_no]:
|
||||
del users[conn.net_no]
|
||||
path = self._search(conn, allow_ripup=False, conns=conns)
|
||||
if path is None:
|
||||
continue
|
||||
nodes = self._footprint(path)
|
||||
claimed[conn.index] = nodes
|
||||
for node in nodes:
|
||||
users = present.setdefault(node, {})
|
||||
users[conn.net_no] = users.get(conn.net_no, 0) + 1
|
||||
shared = 0
|
||||
for node, users in present.items():
|
||||
if len(users) > 1:
|
||||
shared += 1
|
||||
self._history[node] = self._history.get(node, 0.0) + len(users) - 1
|
||||
if not shared:
|
||||
break
|
||||
self._present_cost *= 2
|
||||
finally:
|
||||
self._present = None
|
||||
for conn, a_end, b_end in ends: # the search picks ends; undo that
|
||||
conn.a_end, conn.b_end = a_end, b_end
|
||||
|
||||
# --- passes -------------------------------------------------------------
|
||||
|
||||
def route(self) -> RouteResult:
|
||||
@@ -1382,6 +1465,8 @@ class GridRouter:
|
||||
if self.route_first:
|
||||
# stable: the promoted keep their order among themselves, as do the rest
|
||||
order.sort(key=lambda c: (c.net_no, *_ends_of(c)) not in self.route_first)
|
||||
if self.negotiate:
|
||||
self._negotiate(order, conns)
|
||||
for _ in range(passes):
|
||||
changed = False
|
||||
for conn in order:
|
||||
@@ -1778,6 +1863,7 @@ def route_board(
|
||||
seed: int | None = None,
|
||||
route_first: frozenset | None = None,
|
||||
access_cost: float = 0.0,
|
||||
negotiate: int = 0,
|
||||
rollback: bool = True,
|
||||
net_widths: dict[int, int] | None = None,
|
||||
via_drill: int | None = None,
|
||||
@@ -1800,6 +1886,7 @@ def route_board(
|
||||
seed=seed,
|
||||
route_first=route_first,
|
||||
access_cost=access_cost,
|
||||
negotiate=negotiate,
|
||||
rollback=rollback,
|
||||
net_widths=net_widths,
|
||||
via_drill=via_drill,
|
||||
|
||||
Reference in New Issue
Block a user