Add multi-layer routing with vias
Extends the grid router with a layer axis: A* nodes are (col, row, layer), in-plane moves stay on a layer, and a via move transitions between layers at a cell for a configurable via_cost (so the router prefers one layer but changes layers to get through). Occupancy is tracked per (cell, layer); a through via must be clear on every signal layer and then blocks all of them for other nets. RouteResult now carries per-layer wire segments and per-net via locations. The pipeline emits each segment on its layer and each via as (via <padstack> x y), using the DSN's via padstack; wire and via coordinates are converted from board units back to DSN units. route() and route_dsn_board() take an optional layers= to restrict routing (e.g. a single layer) for comparison.
This commit is contained in:
@@ -5,15 +5,16 @@ writer — the Java-free replacement for the ``freerouting.jar`` step:
|
||||
|
||||
dsn_text -> parse_dsn -> build_board -> route_board -> write_ses -> ses_text
|
||||
|
||||
Router output is in board units; it is converted back to DSN units (dividing by
|
||||
the resolution) for the SES ``(wire (path ...))`` scopes.
|
||||
Router output is in board units; wire paths and via locations are converted back
|
||||
to DSN units (dividing by the resolution) for the SES ``(wire (path ...))`` and
|
||||
``(via <padstack> x y)`` scopes.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from freeroute.board import build_board
|
||||
from freeroute.dsn import DsnBoard, parse_dsn
|
||||
from freeroute.ses import RoutedWire, RoutingResult, write_ses
|
||||
from freeroute.ses import RoutedVia, RoutedWire, RoutingResult, write_ses
|
||||
|
||||
from .grid_router import RouteResult, route_board
|
||||
|
||||
@@ -22,6 +23,8 @@ __all__ = ["route", "route_dsn_board", "build_routing_result"]
|
||||
#: fallback trace width / clearance in DSN units when the DSN has no rules
|
||||
_DEFAULT_WIDTH_DSN = 2000
|
||||
_DEFAULT_CLEARANCE_DSN = 2000
|
||||
#: fallback via padstack name when the DSN declares none
|
||||
_DEFAULT_VIA = "Via"
|
||||
|
||||
|
||||
def _rule_width_dsn(dsn: DsnBoard) -> float:
|
||||
@@ -36,41 +39,53 @@ def _rule_clearance_dsn(dsn: DsnBoard) -> float:
|
||||
return _DEFAULT_CLEARANCE_DSN
|
||||
|
||||
|
||||
def route_dsn_board(dsn: DsnBoard) -> tuple[RouteResult, int, list[str]]:
|
||||
def _via_padstack(dsn: DsnBoard) -> str:
|
||||
return dsn.via_padstack_names[0] if dsn.via_padstack_names else _DEFAULT_VIA
|
||||
|
||||
|
||||
def route_dsn_board(
|
||||
dsn: DsnBoard, *, layers: list[int] | None = None
|
||||
) -> tuple[RouteResult, int, list[str]]:
|
||||
"""Route a parsed :class:`DsnBoard`; return the board-unit result, the scale,
|
||||
and the layer names."""
|
||||
and the layer names. ``layers`` restricts routing to those signal-layer
|
||||
indices (default: all signal layers, i.e. multi-layer with vias)."""
|
||||
board = build_board(dsn)
|
||||
scale = max(dsn.resolution.value, 1)
|
||||
width_board = round(_rule_width_dsn(dsn) * scale)
|
||||
clearance_board = round(_rule_clearance_dsn(dsn) * scale)
|
||||
result = route_board(board, trace_width=width_board, clearance=clearance_board, layer=0)
|
||||
result = route_board(board, trace_width=width_board, clearance=clearance_board, layers=layers)
|
||||
return result, scale, [layer.name for layer in dsn.layers]
|
||||
|
||||
|
||||
def build_routing_result(dsn: DsnBoard) -> RoutingResult:
|
||||
"""Route ``dsn`` and convert the board-unit paths to a DSN-unit
|
||||
def build_routing_result(dsn: DsnBoard, *, layers: list[int] | None = None) -> RoutingResult:
|
||||
"""Route ``dsn`` and convert the board-unit paths + vias to a DSN-unit
|
||||
:class:`~freeroute.ses.RoutingResult` for SES emission."""
|
||||
route, scale, layer_names = route_dsn_board(dsn)
|
||||
layer_name = layer_names[route.layer] if layer_names else "F.Cu"
|
||||
route, scale, layer_names = route_dsn_board(dsn, layers=layers)
|
||||
width_dsn = _rule_width_dsn(dsn)
|
||||
via_name = _via_padstack(dsn)
|
||||
# net_number is assigned in DSN order by build_board, so index i -> number i+1
|
||||
numbers_to_names = {i + 1: n.name for i, n in enumerate(dsn.nets)}
|
||||
|
||||
result = RoutingResult()
|
||||
for net_no, paths in route.wires.items():
|
||||
for net_no, segments in route.wires.items():
|
||||
name = numbers_to_names.get(net_no, str(net_no))
|
||||
for path in paths:
|
||||
for layer_index, path in segments:
|
||||
layer_name = layer_names[layer_index] if layer_index < len(layer_names) else "F.Cu"
|
||||
coords: list[float] = []
|
||||
for point in path:
|
||||
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))
|
||||
for net_no, locations in route.vias.items():
|
||||
name = numbers_to_names.get(net_no, str(net_no))
|
||||
for loc in locations:
|
||||
result.add_via(name, RoutedVia(padstack=via_name, x=loc.x / scale, y=loc.y / scale))
|
||||
return result
|
||||
|
||||
|
||||
def route(dsn_text: str) -> str:
|
||||
def route(dsn_text: str, *, layers: list[int] | None = None) -> str:
|
||||
"""Route a Specctra DSN string and return the routed SES string."""
|
||||
dsn = parse_dsn(dsn_text)
|
||||
result = build_routing_result(dsn)
|
||||
result = build_routing_result(dsn, layers=layers)
|
||||
return write_ses(dsn, result)
|
||||
|
||||
Reference in New Issue
Block a user