@tscircuit/fanout-solver 0.0.44 → 0.0.45

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.
Files changed (2) hide show
  1. package/lib/fanout-solver.ts +121 -12
  2. package/package.json +1 -1
@@ -446,6 +446,7 @@ export function shouldUseJointBoundaryViaReservation(
446
446
  return (
447
447
  boundaryBusConnectionCounts.length === 5 ||
448
448
  boundaryBusConnectionCounts.length === 6 ||
449
+ boundaryBusConnectionCounts.length === 7 ||
449
450
  (boundaryBusConnectionCounts.length === 4 &&
450
451
  new Set(boundaryBusConnectionCounts).size > 1)
451
452
  )
@@ -456,7 +457,8 @@ export function shouldDeferSingletonBoundaryViaReservation(
456
457
  ): boolean {
457
458
  return (
458
459
  (boundaryBusConnectionCounts.length === 5 ||
459
- boundaryBusConnectionCounts.length === 6) &&
460
+ boundaryBusConnectionCounts.length === 6 ||
461
+ boundaryBusConnectionCounts.length === 7) &&
460
462
  boundaryBusConnectionCounts.filter((count) => count === 1).length === 1
461
463
  )
462
464
  }
@@ -467,11 +469,11 @@ export function shouldSearchAdditionalBoundaryRouteTopologies(params: {
467
469
  rawSkew: number
468
470
  maximumSkew: number
469
471
  }): boolean {
470
- if (params.boundaryBusCount === 5 || params.boundaryBusCount > 6) {
472
+ if (params.boundaryBusCount === 5 || params.boundaryBusCount > 7) {
471
473
  return false
472
474
  }
473
- if (params.boundaryBusCount === 6) {
474
- // A sixth bus removes enough meander space that a severely skewed wide
475
+ if (params.boundaryBusCount === 6 || params.boundaryBusCount === 7) {
476
+ // Six or seven buses remove enough meander space that a severely skewed wide
475
477
  // topology can be impossible to tune. Keep the retry away from narrow
476
478
  // differential/control groups and from modest deficits that the atomic
477
479
  // length matcher can absorb directly.
@@ -486,6 +488,73 @@ export function shouldSearchAdditionalBoundaryRouteTopologies(params: {
486
488
  )
487
489
  }
488
490
 
491
+ interface DensePairRoutingPriorityBus {
492
+ componentId: string
493
+ exitEdge?: PreparedBus["exitEdge"]
494
+ assignedLayer?: string
495
+ connections: readonly {
496
+ sourceLayer: string
497
+ sourcePoint: { x: number; y: number }
498
+ exitTargetPoint?: { x: number; y: number; layer?: string }
499
+ }[]
500
+ }
501
+
502
+ export function getDenseSevenBusPairRoutingPriorityKeys(params: {
503
+ boundaryBusCount: number
504
+ pairBuses: readonly DensePairRoutingPriorityBus[]
505
+ }): number[] | null {
506
+ const { pairBuses } = params
507
+ const firstBus = pairBuses[0]
508
+ const firstSourceLayer = firstBus?.connections[0]?.sourceLayer
509
+ if (
510
+ params.boundaryBusCount !== 7 ||
511
+ pairBuses.length !== 3 ||
512
+ firstBus === undefined ||
513
+ firstBus.exitEdge === undefined ||
514
+ firstBus.assignedLayer === undefined ||
515
+ firstSourceLayer === undefined ||
516
+ pairBuses.some(
517
+ (bus) =>
518
+ bus.connections.length !== 2 ||
519
+ bus.componentId !== firstBus.componentId ||
520
+ bus.exitEdge !== firstBus.exitEdge ||
521
+ bus.assignedLayer !== firstBus.assignedLayer ||
522
+ bus.connections.some((connection) => {
523
+ const exitTarget = connection.exitTargetPoint
524
+ return (
525
+ connection.sourceLayer !== firstSourceLayer ||
526
+ exitTarget?.layer !== firstBus.assignedLayer ||
527
+ !Number.isFinite(connection.sourcePoint.x) ||
528
+ !Number.isFinite(connection.sourcePoint.y) ||
529
+ !Number.isFinite(exitTarget?.x) ||
530
+ !Number.isFinite(exitTarget?.y)
531
+ )
532
+ }),
533
+ )
534
+ ) {
535
+ return null
536
+ }
537
+
538
+ const getMeanSourceToExitTargetDistance = (
539
+ bus: DensePairRoutingPriorityBus,
540
+ ): number =>
541
+ bus.connections.reduce((total, connection) => {
542
+ const exitTarget = connection.exitTargetPoint!
543
+ return (
544
+ total +
545
+ Math.hypot(
546
+ exitTarget.x - connection.sourcePoint.x,
547
+ exitTarget.y - connection.sourcePoint.y,
548
+ )
549
+ )
550
+ }, 0) / bus.connections.length
551
+ // Quantize once so equality is transitive. An epsilon-based pairwise
552
+ // comparator can otherwise produce A = B, B = C, but A != C.
553
+ return pairBuses.map((bus) =>
554
+ Number(getMeanSourceToExitTargetDistance(bus).toFixed(9)),
555
+ )
556
+ }
557
+
489
558
  function getCandidateEscapeLayersForBus(params: {
490
559
  bus: PreparedBus
491
560
  srj: SimpleRouteJson
@@ -774,6 +843,24 @@ export class FanoutSolver extends BaseSolver {
774
843
  const useJointBoundaryViaReservation = shouldUseJointBoundaryViaReservation(
775
844
  unsortedBoundaryBuses.map((bus) => bus.connections.length),
776
845
  )
846
+ const twoConnectionBoundaryBuses = unsortedBoundaryBuses.filter(
847
+ (bus) => bus.connections.length === 2,
848
+ )
849
+ const pairRoutingPriorityKeys = getDenseSevenBusPairRoutingPriorityKeys({
850
+ boundaryBusCount: unsortedBoundaryBuses.length,
851
+ pairBuses: twoConnectionBoundaryBuses.map((bus) => ({
852
+ ...bus,
853
+ assignedLayer: params.busLayerAssignments[bus.busId],
854
+ })),
855
+ })
856
+ const pairRoutingPriorityKeyByBusId = pairRoutingPriorityKeys
857
+ ? new Map(
858
+ twoConnectionBoundaryBuses.map((bus, index) => [
859
+ bus.busId,
860
+ pairRoutingPriorityKeys[index]!,
861
+ ]),
862
+ )
863
+ : null
777
864
  const boundaryBuses = unsortedBoundaryBuses.toSorted((first, second) => {
778
865
  // Reserve the dense escape field for the widest buses first. Small
779
866
  // control groups can usually route around their copper, while routing
@@ -784,6 +871,24 @@ export class FanoutSolver extends BaseSolver {
784
871
  second.connections.length - first.connections.length
785
872
  if (connectionCountDifference !== 0) return connectionCountDifference
786
873
  }
874
+ const firstLayer = params.busLayerAssignments[first.busId]
875
+ const secondLayer = params.busLayerAssignments[second.busId]
876
+ const firstPairRoutingPriority = pairRoutingPriorityKeyByBusId?.get(
877
+ first.busId,
878
+ )
879
+ const secondPairRoutingPriority = pairRoutingPriorityKeyByBusId?.get(
880
+ second.busId,
881
+ )
882
+ if (
883
+ firstPairRoutingPriority !== undefined &&
884
+ secondPairRoutingPriority !== undefined &&
885
+ firstPairRoutingPriority !== secondPairRoutingPriority
886
+ ) {
887
+ // The third pair can be fenced off by two earlier pair windings. Let
888
+ // the pair with the shortest explicit boundary reach claim its channel
889
+ // first without relying on caller-specific bus identifiers.
890
+ return firstPairRoutingPriority - secondPairRoutingPriority
891
+ }
787
892
  const cornerBandDifference =
788
893
  Number(
789
894
  Boolean(getCornerBandSide(second.exitEdge, second.preferredExit)),
@@ -814,18 +919,22 @@ export class FanoutSolver extends BaseSolver {
814
919
  return sourceSpanDifference
815
920
  }
816
921
  }
817
- const firstLayer = params.busLayerAssignments[first.busId]
818
- const secondLayer = params.busLayerAssignments[second.busId]
819
922
  const layerDifference =
820
923
  this.config.layerNames.indexOf(firstLayer ?? "") -
821
924
  this.config.layerNames.indexOf(secondLayer ?? "")
822
925
  if (layerDifference !== 0) return -layerDifference
823
- if (unsortedBoundaryBuses.length !== 6) return 0
926
+ if (
927
+ unsortedBoundaryBuses.length !== 6 &&
928
+ unsortedBoundaryBuses.length !== 7
929
+ ) {
930
+ return 0
931
+ }
824
932
  // The general routing order can differ across the two components as a
825
933
  // function of local pad geometry. Keep otherwise-equivalent corner buses
826
- // in one deterministic order for the six-bus path so their boundary
827
- // lanes do not swap between the two ends of a direct interconnect. Leave
828
- // the released four- and five-bus tie behavior unchanged.
934
+ // in one deterministic order for the six- and seven-bus paths so their
935
+ // boundary lanes do not swap between the two ends of a direct
936
+ // interconnect. Leave the released four- and five-bus tie behavior
937
+ // unchanged.
829
938
  return first.busId.localeCompare(second.busId)
830
939
  })
831
940
  // Preserve the caller/input order for the dense singleton fill. The
@@ -838,7 +947,7 @@ export class FanoutSolver extends BaseSolver {
838
947
  )
839
948
  if (
840
949
  boundaryBuses.length === 0 ||
841
- boundaryBuses.length > 6 ||
950
+ boundaryBuses.length > 7 ||
842
951
  planeBuses.length < 8 ||
843
952
  boundaryBuses.some((bus) => !busUsesCoordinatedWinding(bus)) ||
844
953
  planeBuses.some((bus) => bus.connections.length !== 1) ||
@@ -885,7 +994,7 @@ export class FanoutSolver extends BaseSolver {
885
994
  ),
886
995
  )
887
996
  }
888
- // Five or six boundary buses, and heterogeneous four-bus groups, leave too little
997
+ // Five through seven boundary buses, and heterogeneous four-bus groups, leave too little
889
998
  // slack for incremental site allocation: a valid early trace can consume
890
999
  // the last dogbone site of a later narrow bus. Reserve the multi-line bus
891
1000
  // barrels before routing copper. A single one-line bus stays provisional
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fanout-solver",
3
- "version": "0.0.44",
3
+ "version": "0.0.45",
4
4
  "description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
5
5
  "module": "lib/index.ts",
6
6
  "type": "module",