Route wider net classes at their width, falling back where they don't fit

The grid is laid out for the default width; a wider trace also needs the
cells its extra half width reaches to be free, and claims a wider halo.
A connection that does not fit at its class width (typically at a
fine-pitch pin) is routed at the default instead and reported. A net is
written at its class width when all of its connections went in at it,
and the exact check verifies it at that width; nets that shove or the
diagonal pass reshaped go out at the default.
This commit is contained in:
2026-09-28 21:16:40 -06:00
parent 783dd8f0d1
commit 4a844e8cd7
6 changed files with 235 additions and 13 deletions
+28 -1
View File
@@ -34,6 +34,25 @@ def _rule_width_dsn(dsn: DsnBoard) -> float:
return rules[0].value if rules else _DEFAULT_WIDTH_DSN
def _net_widths_board(dsn: DsnBoard, scale: int) -> dict[int, int]:
"""Track width of every net whose class asks for more than the default,
in board units, by net number (build_board numbers nets in DSN order)."""
default = _rule_width_dsn(dsn)
by_name: dict[str, float] = {}
for net_class in dsn.net_classes:
if not net_class.width_rules:
continue
width = net_class.width_rules[0].value
if width > default:
for name in net_class.net_names:
by_name[name] = max(by_name.get(name, 0.0), width)
return {
i + 1: round(by_name[net.name] * scale)
for i, net in enumerate(dsn.nets)
if net.name in by_name
}
def _rule_clearance_dsn(dsn: DsnBoard) -> float:
for rule in dsn.structure_rules.clearance_rules:
if not rule.class_pairs: # the layer-wide default clearance
@@ -108,6 +127,7 @@ def route_dsn_board(
route_first=route_first,
access_cost=access_cost,
rollback=rollback,
net_widths=_net_widths_board(dsn, scale),
)
return result, scale, [layer.name for layer in dsn.layers]
@@ -122,6 +142,9 @@ def _to_routing_result(route, dsn: DsnBoard, scale: int, layer_names: list[str])
result = RoutingResult()
for net_no, segments in route.wires.items():
name = numbers_to_names.get(net_no, str(net_no))
# its class's width when every connection of the net went in at it
half = getattr(route, "net_half", {}).get(net_no)
net_width = 2 * half / scale if half else width_dsn
for layer_index, path in segments:
layer_name = layer_names[layer_index] if layer_index < len(layer_names) else "F.Cu"
coords: list[float] = []
@@ -129,7 +152,7 @@ def _to_routing_result(route, dsn: DsnBoard, scale: int, layer_names: list[str])
coords.append(point.x / scale)
coords.append(point.y / scale)
if len(coords) >= 4:
result.add_wire(name, RoutedWire(layer=layer_name, width=width_dsn, coords=coords))
result.add_wire(name, RoutedWire(layer=layer_name, width=net_width, coords=coords))
for net_no, locations in route.vias.items():
name = numbers_to_names.get(net_no, str(net_no))
for loc in locations:
@@ -137,6 +160,9 @@ def _to_routing_result(route, dsn: DsnBoard, scale: int, layer_names: list[str])
result.unrouted = [
(numbers_to_names.get(n, str(n)), a, b, zone) for n, a, b, zone in getattr(route, "failed", [])
]
result.narrowed = [
(numbers_to_names.get(n, str(n)), a, b) for n, a, b in getattr(route, "narrowed", [])
]
return result
@@ -207,6 +233,7 @@ def route_dsn_board_exact(
route_first=route_first,
access_cost=access_cost,
rollback=rollback,
net_widths=_net_widths_board(dsn, scale),
)
return exact, scale, [layer.name for layer in dsn.layers]