A QFN placed at 45 degrees had no exit on the grid: every horizontal or
vertical step from a pin passes its neighbours inside clearance. A pin whose
pad is turned more than ten degrees off the grid axes now leaves along the
pad's own axis, away from the part, until an L from there to a grid cell
clears every other net's pad, then searches as usual. The slanted leg is
checked and stored as an exact slab (segment_slab), not its bounding box,
in the grid stub checks and in the exact engine.
Where pins leave side by side, a cell names only its first owner, so stubs
are also indexed per cell for every connection near them; the exact
stub-vs-stub check, trace entry and rip-up all consult that index. A pin
with an axis exit and no clear cell reports no way out instead of falling
back to a cell on another net's route. diagnose.py replays with the exits.
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.
Two connections of a net can share a via. The grid kept one owner per via
cell next to the claim list, and the search and rip-up read only that owner:
a route of another net through the via ripped the first user and left the
second's via under the new trace. The exact check then threw one of them
out. The search and rip-up now read the claim list, and the single owner is
gone.
The exact check also tested a via against the outline as its square box,
whose corner reaches past a 45-degree chamfer where the round via does not.
The grid keeps the via's centre its radius plus the edge clearance from the
outline, as KiCad measures it; the exact check now does the same.
kitspace_minisumo_v3 seed 4: 4 rejected connections to 0, 11 failed to 7.
A circle pad was its bounding box in both the grid and the exact check, so a
4 mm mounting hole's square corners refused legal routes 0.8 mm clear of the
real copper. It is now a 16-sided polygon whose sides touch the circle, so
the router is never closer than KiCad measures. diagnose.py takes --seed.
Rotated oval pads were built from padded corners that are not in outline
order, so they fell back to their bounding box; neighbouring boxes of a
rotated socket overlap, and the grid's own-pad exemption opened the
overlap to the pin's net, which the exact check then refused. Pads are now
the convex hull of their corners, the exact tree checks a rotated pad
against that outline, and the grid blocks cells against the same outline.
A via reused by a second connection of its net left that connection with
no claim on the via's footprint, so ripping the first owner freed cells
around a via still on the board. Footprint cells now keep every claimant.
A net class wider than the default was searched as a default trace: its
spread was empty below half a cell, halos were circular while the exact
check compares boxes, and a rip-up route only counted what its centre
crossed. The spread now covers the extra width in whole cells, halos are
square, and rip-up victims include what the spread crosses.
bench/diagnose.py now routes with net-class widths and hole spacing, as
the CLI does.
Copies the board (stripping its tracks unless --keep), exports the DSN
through mckicad, routes, applies the session and runs DRC with zones
refilled, counting hole problems whatever their severity. FREEROUTE and
MCK choose the builds under test.
A 1.0 mm trace from a 1.27 mm pitch pin (the RN2483 row on the TTN
Enschede mote) has to stay within 0.06 mm of the pin's centreline until
it is past the neighbouring pads. No grid column is that close, and the
router could not get there:
- A stub ran as one slanted segment to its cell centre. The exact check
sees that as its bounding box, which lands on the next pad. Orthogonal
stubs now leave straight along the pin's axis and jog to the cell
only at the end.
- End nodes were chosen as in free space even in the rip-up search, so
once another net crossed a pin's only exit, the pin had no end left
and rip-up could never take the exit back. In rip-up mode those cells
and stubs are now enterable at the rip penalty, and routes on a stub
are ripped along with the ones the path crosses.
- Two neighbouring pins' stubs each claim the cells a centred trace
would crowd, so their claims overlapped well before their copper
did. Stub against stub is now checked on the copper.
- Escapes also look one column either side, so two pins leaving the
same way can jog apart.
TTN Enschede mote, exact engine with --shove: 12 unrouted before, 0
after; KiCad DRC on the result has no clearance violations and no
unconnected items. The 0.5 mm pitch row test at resolution 1 now
routes all 5 pins (it pinned 2).
Committed vias were checked for hole spacing, but a route's own vias are
not committed until it is done, so a route hopping layers (more of them
on 6 layers) could put two of its vias 0.41 mm apart, as th-eda's
6-layer flight controller showed. A via is now also checked against
layer changes earlier on its own route.
A fixed via sits anywhere, not on a cell centre, and may have a bigger
drill than the router's via; checking hole spacing between cell centres
with one drill let the flight controller's routed 0.2 mm via come 0.19
mm from a hand-placed 0.25 mm one. The board now carries each fixed
via's drill (from KiCad's padstack name), and cells are checked against
fixed vias exactly.
A route could drop a via next to (or onto) a through-hole pad of its own
net, where it changes layer for free, and a via reusing a fixed via's
cell came out a little off the fixed via's centre, beside it. No via now
goes within hole spacing of a through-hole pad of any net; a via in a pad
is checked exactly against every via and hole; and a routed via on a
fixed via of its net is not written. charge-sense: 4 hole_to_hole to 0;
flight controller: 2 to 0, unconnected unchanged.
Fixed tracks and vias only kept other nets away, so a pin fanned out to
its plane was routed to the plane again, beside its own fan-out. Pins,
zones and planes that fixed copper of their net touches now form one
connected group: a fanned-out supply pin needs nothing more, a fanned-out
signal routes from its via, and copper touching no pin or zone adds
nothing to route.
The exact engine rebuilds its output from each connection's own vias,
so connections sharing a plane via each wrote a copy: th-eda's flight
controller had 71 spots carrying 2 to 4 vias of one net. Vias are now
written once per net and spot, and never on a via already on the board.
Every pin of a supply drops its own via to the plane, and nothing kept
two of them apart: the flight controller came back with 199 co-located
holes and 109 hole-to-hole errors, nearly all VDD_3V3 and GND. A via now
keeps its drill plus --hole-to-hole (default 0.25 mm, KiCad's) from
every via centre, fixed ones included, except an own-net via at the same
cell, which it reuses; such a via is written once, and none is written
on top of a via already on the board.
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.
A start that was already a plane end joined the plane at once, with no
check that its via fits: under a sensor's via keepout the via went in
the pad and the exact check refused it. A via leaving or reaching a
walled-in pin goes in that pin's pad, but was only checked at the grid
cell's centre; it is now checked exactly at the pin against via
keepouts and other nets' pads and fixed copper.
Tracks and vias already on the board were ignored, so other nets could
be routed straight through them (the flight controller's 82 GND
stitching vias and its guard tracks). They are now copper of their net:
other nets keep a pad's distance from them in the grid, the exact check
and the room engine, and they add no connections to route.
A plane end is marked with its zone, not a pin, so a route from a pad
outside the plane's outline crashed looking for the pin's location
(the flight-controller board's first placement).
Over 8 net orders it left 25 to 30 fewer connections unrouted on the
dense sense boards and 16 to 18 fewer on two PCBench boards, and was
never clearly worse. KiCad DRC on the sense harness: 41 opens to 28.
The connections a rip-up route crosses are rerouted at once in free
space. If that leaves fewer connections routed than before, the new
route comes out and every displaced connection goes back on its old
path, with its rip count as it was. Even trades are kept.
A route may not leave a cell inside a wire keepout as a wire, nor reach
a pin there by one: it changes layer instead, and the via goes in the
pad. A pin whose every stub enters a keepout is reached by a via only.
A structure keepout named rule_area... (mckicad names KiCad rule areas
so) is checked for overlap only, as KiCad's DRC does, with the free ends
of each wire tested for their round caps.
--access-cost C makes a route pay C to pass where it would close another
pin's way out (its end cells, the via it may need there, and the
clearance around both) while that pin still has connections to route.
A pin inside a wire keepout can only leave by a via; its stub to the
cell centre was track inside the keepout, which the exact check refused.
The via now goes on the pin itself. Pin stubs are also checked exactly
against wire keepouts, as the exact engine will check them.
bench/diagnose.py reports what each refused connection collided with.
rejected by the exact check, unroutable even alone, walled in (a pin's
free region is closed off by other copper, with the nets that close it),
or congestion. The board state is the best pass's, rebuilt by replaying
its paths.
Each round routes every connection that has failed so far ahead of the
rest, which keep their order. The promoted set only grows, so two groups
cannot take turns failing; the best round is kept.
Runs every board with every variant under the same seeds and compares
them seed by seed, so a change is judged against the routing-order
noise rather than one lucky or unlucky run.
The router is deterministic, but its result depends heavily on which
connection routes first. A seed shuffles that order reproducibly, so a
change can be judged over several orders instead of the one it happened
to get.
On a four-layer board with a GND plane on an inner power layer, freeroute
ignored the plane (it only used zones on routing layers) and chained every
GND pad to the next with tracks across the board. Now a through-hole pad
inside a plane is joined to it already, and for a net with a plane every
other group connects straight to the plane: a route may end on any cell
inside it where a via fits (checked lazily, like other vias), and ending
there places that via. Signal nets are unaffected.
Tested on a four-layer DSN: three SMD GND pads each get a short stub and
their own via, the THT GND pad needs nothing, and no track crosses the
board. Ahead of the TerraHawk ESC, a four-layer board.
Recommend the exact engine for KiCad boards; document --edge-clearance,
--grid-resolution, --heuristic-weight and the unrouted report; list what is
read from a KiCad DSN (pads, vias, keepout kinds, outline, zones, fine
pitch), each checked against KiCad; replace the scope section with the
TerraHawk measurements and what still trails the JAR.
On the rc5 sense board at its real 0.2 mm clearance, ten pins of the
0.5 mm pitch STM32 could only reach a pocket of seven or eight cells
inside their own pads: the grid columns miss the narrow legal band past
the pad tip, so twelve connections could not route even on an empty
board. For a crowded pin (a neighbouring pad of another net closer than a
trace can pass) whose nearby cells are such a pocket against pads and
keepouts, offer straight stubs along each axis to the first open cell,
accepted only if the stub clears every other net's pad by the full
margin, measured exactly. All twelve then route alone; on the full board
unrouted connections drop from 61 to 59, the rest being congestion.