Skip to content

Commit 287d9bb

Browse files
drandyhaasclaude
andcommitted
check_net_connectivity: size spatial-query rings/cells to the net's real geometry (#363)
Profiling the set11 rp2350_fpga_eensy plane steps showed the plane-step cleanup passes spend most of their time rebuilding the connectivity graph, and each build was dominated by two spatial loops scanning fixed worst-case neighbourhoods over 0.05mm-pitch plane fill: - the point-point loop queried a fixed 1.0mm radius when the pair tolerance it feeds is max(size1,size2)/4 (~0.11mm here); - the T-junction loop queried psize/2 + 1.0 when the credit rule is (psize + seg.width)/2 (~0.2-0.4mm here); - both indexes used fixed 1.0mm cells, which hold hundreds of points/segments each on dense fill. Compute the exact per-net bounds (max point size, max segment width) and size the index cells to the query radius so every scan is a 3x3 ring proportional to real neighbours. GND graph build on the eensy plane board: 518ms -> 84ms (6.2x). route_planes step: 14.5s -> 9.9s. Union results are unchanged (same components); union ORDER can change, which may flip ties among redundant same-net segments in cleanup (observed: one fewer redundant +3V3 segment, DRC/connectivity grades identical). Verified: connectivity unit tests (via-in-pad, pad-overlap, via-barrel, endpoint-cap, orphan-island, strict-collapse) pass; full-board check_connected reports byte-identical on 8 routed boards; A/B replay of both eensy plane steps grades identically (0 DRC violations, same disconnected pads). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UKFKAEkBFgwTKh1E4oJkaQ
1 parent dc771db commit 287d9bb

1 file changed

Lines changed: 23 additions & 10 deletions

File tree

check_connected.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -533,14 +533,19 @@ def layer_sort_key(layer):
533533
for pid in points_in_zone[1:]:
534534
_union(points_in_zone[0], pid)
535535

536-
# Build spatial index for points (use 1mm grid cells)
537-
point_index = SpatialIndex(cell_size=1.0)
536+
# Connect all points that are within tolerance on the same layer
537+
# Use spatial index for O(n) average instead of O(n²).
538+
# The pair tolerance below is max(size1, size2)/4 (floored at `tolerance`),
539+
# so the widest ring any pair can need is max_point_size/4 — using that
540+
# exact bound instead of a fixed 1.0, and sizing the index cells to it so
541+
# the 3x3-cell scan spans ~3x the radius rather than 3 mm, keeps this loop
542+
# proportional to real neighbours on dense plane fill (issue #363: the
543+
# fixed radius/cell made it dominate plane-step runtime).
544+
max_point_size = max((p[4] for p in all_points), default=0.0)
545+
max_tolerance = max(max_point_size / 4, tolerance)
546+
point_index = SpatialIndex(cell_size=max(max_tolerance * 1.01, 0.05))
538547
for x, y, layer, pid, size in all_points:
539548
point_index.add(x, y, layer, pid, size)
540-
541-
# Connect all points that are within tolerance on the same layer
542-
# Use spatial index for O(n) average instead of O(n²)
543-
max_tolerance = 1.0 # Maximum possible tolerance (size/4 capped at reasonable value)
544549
for x1, y1, l1, id1, size1 in all_points:
545550
# Query nearby points on same layer
546551
for x2, y2, id2, size2 in point_index.query_nearby(x1, y1, l1, max_tolerance):
@@ -638,8 +643,13 @@ def layer_sort_key(layer):
638643
if _point_in_pad(ex, ey, pad, margin=_m):
639644
_union(pad_repr_id[pad_idx], eid)
640645

641-
# Build spatial index for segments
642-
seg_index = SegmentIndex(cell_size=1.0)
646+
# Build spatial index for segments. Cell size = the widest credit reach
647+
# any point below can need, so every query below is a single 3x3-cell
648+
# ring — on 0.05mm-pitch plane fill the old fixed 1mm cells held hundreds
649+
# of segments each and this loop dominated plane-step runtime (#363).
650+
max_seg_width = max((seg.width for seg in segments), default=0.0)
651+
_max_reach = max((max_point_size + max_seg_width) / 2, tolerance)
652+
seg_index = SegmentIndex(cell_size=max(_max_reach * 1.01, 0.05))
643653
for seg_idx, seg in enumerate(segments):
644654
seg_start_id = seg_idx * 2
645655
seg_index.add(seg, seg_start_id)
@@ -650,8 +660,11 @@ def layer_sort_key(layer):
650660
ptype = point_info[pid][0]
651661
# Query segments that might contain this point. Endpoint/via points
652662
# credit out to (psize + seg_width)/2; the query ring must cover
653-
# that for LARGE vias (size + width can exceed the 1mm cell).
654-
_reach = psize / 2 + 1.0
663+
# that for LARGE vias (size + width can exceed the 1mm cell). Use the
664+
# net's real max segment width for the bound, not a fixed 1.0 — the
665+
# constant put the ring at 5x5 cells for every point on dense plane
666+
# fill and dominated plane-step runtime (issue #363).
667+
_reach = max((psize + max_seg_width) / 2, tolerance)
655668
for seg, seg_start_id in seg_index.query_near(px, py, player,
656669
radius=_reach):
657670
seg_end_id = seg_start_id + 1

0 commit comments

Comments
 (0)