@tscircuit/fanout-solver 0.0.49 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -64,6 +64,12 @@ export interface RouteViaMinimalWindingParams {
64
64
  gridStepDivisor?: 1 | 2
65
65
  /** Bias bounded fixed-site searches toward the remote target band. */
66
66
  preferTargetDirectedLaneBias?: boolean
67
+ /** Internal path-only mode used before a boundary-side via is appended. */
68
+ allowSourceLayerRouting?: boolean
69
+ /** Promote a blocked terminal while staying within maximumRouteOrderAttempts. */
70
+ adaptiveRouteOrder?: boolean
71
+ /** Align a fine grid with pad/interstice centers instead of the boundary. */
72
+ alignGridToPads?: boolean
67
73
  }
68
74
 
69
75
  export interface RouteViaMinimalWindingProgress {
@@ -493,6 +499,7 @@ function buildPlan(params: {
493
499
  layer: connection.sourceLayer,
494
500
  }
495
501
  const hasSourceDogbone = distance(sourcePoint, terminal.viaPoint) > EPSILON
502
+ const changesLayer = connection.sourceLayer !== targetLayer
496
503
  const targetSegments = getSegments(targetLayerPoints, traceWidth, targetLayer)
497
504
  const spanLayers = getViaSpanLayers({
498
505
  fromLayer: connection.sourceLayer,
@@ -528,20 +535,24 @@ function buildPlan(params: {
528
535
  },
529
536
  ]
530
537
  : []),
531
- {
532
- route_type: "via",
533
- ...terminal.viaPoint,
534
- from_layer: connection.sourceLayer,
535
- to_layer: targetLayer,
536
- via_diameter: viaDiameter,
537
- via_hole_diameter: viaHoleDiameter,
538
- },
539
- {
540
- route_type: "wire",
541
- ...terminal.viaPoint,
542
- width: traceWidth,
543
- layer: targetLayer,
544
- },
538
+ ...(changesLayer
539
+ ? [
540
+ {
541
+ route_type: "via" as const,
542
+ ...terminal.viaPoint,
543
+ from_layer: connection.sourceLayer,
544
+ to_layer: targetLayer,
545
+ via_diameter: viaDiameter,
546
+ via_hole_diameter: viaHoleDiameter,
547
+ },
548
+ {
549
+ route_type: "wire" as const,
550
+ ...terminal.viaPoint,
551
+ width: traceWidth,
552
+ layer: targetLayer,
553
+ },
554
+ ]
555
+ : []),
545
556
  ...targetLayerPoints.slice(1).map((point) => ({
546
557
  route_type: "wire" as const,
547
558
  ...point,
@@ -589,7 +600,7 @@ function buildPlan(params: {
589
600
  route,
590
601
  },
591
602
  segments,
592
- via,
603
+ via: changesLayer ? via : undefined,
593
604
  length: segments.reduce(
594
605
  (total, segment) => total + distance(segment.start, segment.end),
595
606
  0,
@@ -624,6 +635,9 @@ export function* routeViaMinimalWindingAlternativesSteps(
624
635
  reservedVias = [],
625
636
  gridStepDivisor = 1,
626
637
  preferTargetDirectedLaneBias = false,
638
+ allowSourceLayerRouting = false,
639
+ adaptiveRouteOrder = false,
640
+ alignGridToPads = false,
627
641
  } = params
628
642
  if (
629
643
  maximumRouteOrderAttempts !== undefined &&
@@ -643,18 +657,33 @@ export function* routeViaMinimalWindingAlternativesSteps(
643
657
  if (
644
658
  terminals.length === 0 ||
645
659
  !bus.exitEdge ||
646
- terminals.some(
647
- (terminal) => terminal.connection.sourceLayer === targetLayer,
648
- )
660
+ (!allowSourceLayerRouting &&
661
+ terminals.some(
662
+ (terminal) => terminal.connection.sourceLayer === targetLayer,
663
+ ))
649
664
  ) {
650
665
  return []
651
666
  }
652
667
 
653
- const gridStep = (traceWidth + clearance) / gridStepDivisor
668
+ const baseGridStep = (traceWidth + clearance) / gridStepDivisor
669
+ const pitch = Math.min(bus.pitchX, bus.pitchY)
670
+ const alignGridToPitch =
671
+ alignGridToPads && gridStepDivisor === 2 && Number.isFinite(pitch)
672
+ const gridStep = alignGridToPitch
673
+ ? pitch / (2 * Math.ceil(pitch / (2 * baseGridStep)))
674
+ : baseGridStep
654
675
  if (!Number.isFinite(gridStep) || gridStep <= 0) return []
655
676
  const { minX, maxX, minY, maxY } = bus.sharedBoundary
656
- const columnCount = Math.floor((maxX - minX) / gridStep) + 1
657
- const rowCount = Math.floor((maxY - minY) / gridStep) + 1
677
+ const originX = bus.xCoordinates[0] ?? minX
678
+ const originY = bus.yCoordinates[0] ?? minY
679
+ const gridMinX = alignGridToPitch
680
+ ? originX + Math.ceil((minX - originX) / gridStep) * gridStep
681
+ : minX
682
+ const gridMinY = alignGridToPitch
683
+ ? originY + Math.ceil((minY - originY) / gridStep) * gridStep
684
+ : minY
685
+ const columnCount = Math.floor((maxX - gridMinX) / gridStep) + 1
686
+ const rowCount = Math.floor((maxY - gridMinY) / gridStep) + 1
658
687
  const nodeCount = columnCount * rowCount
659
688
  if (columnCount < 2 || rowCount < 2 || nodeCount > MAX_GRID_NODE_COUNT) {
660
689
  return []
@@ -665,7 +694,7 @@ export function* routeViaMinimalWindingAlternativesSteps(
665
694
  return {
666
695
  column,
667
696
  row,
668
- point: { x: minX + column * gridStep, y: minY + row * gridStep },
697
+ point: { x: gridMinX + column * gridStep, y: gridMinY + row * gridStep },
669
698
  }
670
699
  })
671
700
  const sampledGridPoints = includeVisualization
@@ -797,6 +826,18 @@ export function* routeViaMinimalWindingAlternativesSteps(
797
826
  }
798
827
  for (const blocker of acceptedAttemptSegments) {
799
828
  if (sharesNet(connectionName, blocker.connectionName)) continue
829
+ const margin = (segment.width + blocker.segment.width) / 2 + clearance
830
+ if (
831
+ Math.max(segment.start.x, segment.end.x) + margin <
832
+ Math.min(blocker.segment.start.x, blocker.segment.end.x) ||
833
+ Math.min(segment.start.x, segment.end.x) - margin >
834
+ Math.max(blocker.segment.start.x, blocker.segment.end.x) ||
835
+ Math.max(segment.start.y, segment.end.y) + margin <
836
+ Math.min(blocker.segment.start.y, blocker.segment.end.y) ||
837
+ Math.min(segment.start.y, segment.end.y) - margin >
838
+ Math.max(blocker.segment.start.y, blocker.segment.end.y)
839
+ )
840
+ continue
800
841
  if (
801
842
  distanceSegmentToSegment(
802
843
  segment.start,
@@ -1283,6 +1324,9 @@ export function* routeViaMinimalWindingAlternativesSteps(
1283
1324
  const initialRouteOrderFactories: Array<
1284
1325
  () => readonly ViaMinimalWindingTerminal[]
1285
1326
  > = []
1327
+ if (alignGridToPads && preferTargetDirectedLaneBias && viasAreBeforeTargets) {
1328
+ initialRouteOrderFactories.push(() => targetOrderedTerminals)
1329
+ }
1286
1330
  if (viasAreBeforeTargets) {
1287
1331
  initialRouteOrderFactories.push(() => [
1288
1332
  ...targetOrderedTerminals.slice(1),
@@ -1340,11 +1384,28 @@ export function* routeViaMinimalWindingAlternativesSteps(
1340
1384
  getItemKey: (terminal) => terminal.connection.connection.name,
1341
1385
  maximumOrderCount: maximumRouteOrderCount,
1342
1386
  })
1387
+ const pendingRouteOrders: ViaMinimalWindingTerminal[][] = []
1388
+ const seenAdaptiveOrders = new Set<string>()
1389
+ const adaptiveRouteOrders = function* () {
1390
+ while (true) {
1391
+ const pending = pendingRouteOrders.shift()
1392
+ const next = pending
1393
+ ? { done: false, value: pending }
1394
+ : routeOrders.next()
1395
+ if (next.done) return
1396
+ const key = next.value
1397
+ .map((terminal) => terminal.connection.connection.name)
1398
+ .join("|")
1399
+ if (seenAdaptiveOrders.has(key)) continue
1400
+ seenAdaptiveOrders.add(key)
1401
+ yield next.value
1402
+ }
1403
+ }
1343
1404
 
1344
1405
  const alternatives: FanoutRoutePlan[][] = []
1345
1406
  const seenAlternativeKeys = new Set<string>()
1346
1407
  let routeOrderAttemptCount = 0
1347
- for (const routeOrder of routeOrders) {
1408
+ for (const routeOrder of adaptiveRouteOrders()) {
1348
1409
  for (const laneBias of laneBiases) {
1349
1410
  if (
1350
1411
  maximumRouteOrderAttempts !== undefined &&
@@ -1415,6 +1476,16 @@ export function* routeViaMinimalWindingAlternativesSteps(
1415
1476
  : {}),
1416
1477
  }
1417
1478
  if (!points) {
1479
+ if (
1480
+ adaptiveRouteOrder &&
1481
+ maximumRouteOrderAttempts !== undefined &&
1482
+ terminalIndex > 0
1483
+ ) {
1484
+ const others = routeOrder.filter(
1485
+ (candidate) => candidate !== terminal,
1486
+ )
1487
+ pendingRouteOrders.push([terminal, ...others])
1488
+ }
1418
1489
  failed = true
1419
1490
  break
1420
1491
  }
package/lib/types.ts CHANGED
@@ -197,6 +197,17 @@ export interface FanoutSolverOptions {
197
197
  allowBlindAndBuriedVias?: boolean
198
198
  /** Allow branches belonging to the same electrical net to share copper. */
199
199
  allowSameNetMerges?: boolean
200
+ /**
201
+ * Plane buses whose adjacent dogbone sites must be reserved before routing
202
+ * dense through-via boundary buses. This is an advanced deterministic hint
203
+ * for fields where later signal copper would otherwise consume those sites.
204
+ */
205
+ densePlaneReservationBusIds?: readonly string[]
206
+ /**
207
+ * Plane buses that should participate in the bounded alternate-route CSP
208
+ * instead of using an adjacent component dogbone.
209
+ */
210
+ denseUnrestrictedPlaneRoutingBusIds?: readonly string[]
200
211
  singleLayerPushAndShove?: boolean
201
212
  /**
202
213
  * When preferred single-layer exits cannot coexist, allow a global
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fanout-solver",
3
- "version": "0.0.49",
3
+ "version": "0.0.51",
4
4
  "description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
5
5
  "module": "lib/index.ts",
6
6
  "type": "module",