@tscircuit/fanout-solver 0.0.41 → 0.0.43

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 +122 -45
  2. package/package.json +1 -1
@@ -440,6 +440,52 @@ function busUsesCoordinatedWinding(bus: PreparedBus): boolean {
440
440
  )
441
441
  }
442
442
 
443
+ export function shouldUseJointBoundaryViaReservation(
444
+ boundaryBusConnectionCounts: readonly number[],
445
+ ): boolean {
446
+ return (
447
+ boundaryBusConnectionCounts.length === 5 ||
448
+ boundaryBusConnectionCounts.length === 6 ||
449
+ (boundaryBusConnectionCounts.length === 4 &&
450
+ new Set(boundaryBusConnectionCounts).size > 1)
451
+ )
452
+ }
453
+
454
+ export function shouldDeferSingletonBoundaryViaReservation(
455
+ boundaryBusConnectionCounts: readonly number[],
456
+ ): boolean {
457
+ return (
458
+ (boundaryBusConnectionCounts.length === 5 ||
459
+ boundaryBusConnectionCounts.length === 6) &&
460
+ boundaryBusConnectionCounts.filter((count) => count === 1).length === 1
461
+ )
462
+ }
463
+
464
+ export function shouldSearchAdditionalBoundaryRouteTopologies(params: {
465
+ boundaryBusCount: number
466
+ connectionCount: number
467
+ rawSkew: number
468
+ maximumSkew: number
469
+ }): boolean {
470
+ if (params.boundaryBusCount === 5 || params.boundaryBusCount > 6) {
471
+ return false
472
+ }
473
+ if (params.boundaryBusCount === 6) {
474
+ // A sixth bus removes enough meander space that a severely skewed wide
475
+ // topology can be impossible to tune. Keep the retry away from narrow
476
+ // differential/control groups and from modest deficits that the atomic
477
+ // length matcher can absorb directly.
478
+ return (
479
+ params.connectionCount > 2 &&
480
+ params.rawSkew - params.maximumSkew >
481
+ Math.max(1, params.maximumSkew * 0.5)
482
+ )
483
+ }
484
+ return (
485
+ params.rawSkew - params.maximumSkew > Math.max(1, params.maximumSkew * 0.25)
486
+ )
487
+ }
488
+
443
489
  function getCandidateEscapeLayersForBus(params: {
444
490
  bus: PreparedBus
445
491
  srj: SimpleRouteJson
@@ -725,16 +771,15 @@ export class FanoutSolver extends BaseSolver {
725
771
  const unsortedBoundaryBuses = params.busesInRoutingOrder.filter(
726
772
  (bus) => bus.termination.type === "boundary",
727
773
  )
728
- const useJointFourBusReservation =
729
- unsortedBoundaryBuses.length === 4 &&
730
- new Set(unsortedBoundaryBuses.map((bus) => bus.connections.length)).size >
731
- 1
774
+ const useJointBoundaryViaReservation = shouldUseJointBoundaryViaReservation(
775
+ unsortedBoundaryBuses.map((bus) => bus.connections.length),
776
+ )
732
777
  const boundaryBuses = unsortedBoundaryBuses.toSorted((first, second) => {
733
778
  // Reserve the dense escape field for the widest buses first. Small
734
779
  // control groups can usually route around their copper, while routing
735
780
  // a two-line corner bus first can consume a critical channel needed by
736
781
  // an eight-line winding bus and force the expensive fallback search.
737
- if (useJointFourBusReservation) {
782
+ if (useJointBoundaryViaReservation) {
738
783
  const connectionCountDifference =
739
784
  second.connections.length - first.connections.length
740
785
  if (connectionCountDifference !== 0) return connectionCountDifference
@@ -774,7 +819,14 @@ export class FanoutSolver extends BaseSolver {
774
819
  const layerDifference =
775
820
  this.config.layerNames.indexOf(firstLayer ?? "") -
776
821
  this.config.layerNames.indexOf(secondLayer ?? "")
777
- return -layerDifference
822
+ if (layerDifference !== 0) return -layerDifference
823
+ if (unsortedBoundaryBuses.length !== 6) return 0
824
+ // The general routing order can differ across the two components as a
825
+ // 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.
829
+ return first.busId.localeCompare(second.busId)
778
830
  })
779
831
  // Preserve the caller/input order for the dense singleton fill. The
780
832
  // general routing sort is useful for heterogeneous buses, but ordering a
@@ -786,7 +838,7 @@ export class FanoutSolver extends BaseSolver {
786
838
  )
787
839
  if (
788
840
  boundaryBuses.length === 0 ||
789
- boundaryBuses.length > 4 ||
841
+ boundaryBuses.length > 6 ||
790
842
  planeBuses.length < 8 ||
791
843
  boundaryBuses.some((bus) => !busUsesCoordinatedWinding(bus)) ||
792
844
  planeBuses.some((bus) => bus.connections.length !== 1) ||
@@ -833,22 +885,37 @@ export class FanoutSolver extends BaseSolver {
833
885
  ),
834
886
  )
835
887
  }
836
- // Four boundary buses leave too little slack for incremental site
837
- // allocation: a valid early trace can consume the last dogbone site of a
838
- // later narrow bus. Reserve every physical barrel before routing copper.
839
- // Plane sites remain provisional and are rematched around the completed
840
- // boundary plans below.
841
- const jointViaPoints = useJointFourBusReservation
842
- ? matchComponentDogboneViaSites([...planeBuses, ...boundaryBuses], {
843
- viaDiameter: this.config.viaDiameter,
844
- viaHoleDiameter: this.config.viaHoleDiameter,
845
- traceWidth: this.config.traceWidth,
846
- clearance: this.config.clearance,
847
- maximumSearchStates: 100_000,
848
- preferredBoundaryPerpendicularSideByBusId,
849
- preferBoundaryOutwardByBusId,
850
- canShareCopper,
851
- })
888
+ // Five or six boundary buses, and heterogeneous four-bus groups, leave too little
889
+ // slack for incremental site allocation: a valid early trace can consume
890
+ // the last dogbone site of a later narrow bus. Reserve the multi-line bus
891
+ // barrels before routing copper. A single one-line bus stays provisional
892
+ // because its flexible site can be rematched around the wide-bus copper.
893
+ // Plane sites are likewise rematched around completed boundary plans.
894
+ const boundaryBusConnectionCounts = boundaryBuses.map(
895
+ (bus) => bus.connections.length,
896
+ )
897
+ const provisionalSingletonBus = shouldDeferSingletonBoundaryViaReservation(
898
+ boundaryBusConnectionCounts,
899
+ )
900
+ ? boundaryBuses.find((bus) => bus.connections.length === 1)
901
+ : undefined
902
+ const initiallyMatchedBoundaryBuses = provisionalSingletonBus
903
+ ? boundaryBuses.filter((bus) => bus !== provisionalSingletonBus)
904
+ : boundaryBuses
905
+ const jointViaPoints = useJointBoundaryViaReservation
906
+ ? matchComponentDogboneViaSites(
907
+ [...planeBuses, ...initiallyMatchedBoundaryBuses],
908
+ {
909
+ viaDiameter: this.config.viaDiameter,
910
+ viaHoleDiameter: this.config.viaHoleDiameter,
911
+ traceWidth: this.config.traceWidth,
912
+ clearance: this.config.clearance,
913
+ maximumSearchStates: 100_000,
914
+ preferredBoundaryPerpendicularSideByBusId,
915
+ preferBoundaryOutwardByBusId,
916
+ canShareCopper,
917
+ },
918
+ )
852
919
  : null
853
920
  const seedViaPoints =
854
921
  jointViaPoints ??
@@ -929,7 +996,12 @@ export class FanoutSolver extends BaseSolver {
929
996
  const lengths = busPlans.map((plan) => plan.length)
930
997
  const rawSkew = Math.max(...lengths) - Math.min(...lengths)
931
998
  const needsRouteDiversity =
932
- rawSkew - bus.maxLengthSkew > Math.max(1, bus.maxLengthSkew * 0.25)
999
+ shouldSearchAdditionalBoundaryRouteTopologies({
1000
+ boundaryBusCount: boundaryBuses.length,
1001
+ connectionCount: bus.connections.length,
1002
+ rawSkew,
1003
+ maximumSkew: bus.maxLengthSkew,
1004
+ })
933
1005
  // Only pay for additional A* variants when the first topology is so
934
1006
  // skewed that compact meanders are unlikely to absorb the deficit.
935
1007
  // This keeps already-near-matched buses on the single-attempt path.
@@ -976,27 +1048,32 @@ export class FanoutSolver extends BaseSolver {
976
1048
  candidateIndex++
977
1049
  ) {
978
1050
  const candidateBus = remainingBoundaryBuses[candidateIndex]!
979
- const extendedViaPoints = jointViaPoints
980
- ? // The joint map is deliberately kept intact so getReservedVias()
981
- // blocks every future through-barrel during A*. Plane dogbones are
982
- // validated/rematched once the boundary copper is complete.
983
- new Map(fixedViaPointsByConnectionIndex)
984
- : matchComponentDogboneViaSites(
985
- [...planeBuses, ...routedBoundaryBuses, candidateBus],
986
- {
987
- viaDiameter: this.config.viaDiameter,
988
- viaHoleDiameter: this.config.viaHoleDiameter,
989
- traceWidth: this.config.traceWidth,
990
- clearance: this.config.clearance,
991
- maximumSearchStates: 100_000,
992
- preferredBoundaryPerpendicularSideByBusId,
993
- preferBoundaryOutwardByBusId,
994
- fixedViaPointsByConnectionIndex:
995
- fixedViaPointsByConnectionIndex,
996
- blockingSegments,
997
- canShareCopper,
998
- },
999
- )
1051
+ const candidateHasFixedViaPoints = candidateBus.connections.every(
1052
+ (connection) =>
1053
+ fixedViaPointsByConnectionIndex.has(connection.connectionIndex),
1054
+ )
1055
+ const extendedViaPoints =
1056
+ jointViaPoints && candidateHasFixedViaPoints
1057
+ ? // The joint map is deliberately kept intact so getReservedVias()
1058
+ // blocks every already-reserved future through-barrel during A*.
1059
+ // Provisional singleton and plane dogbones are rematched later.
1060
+ new Map(fixedViaPointsByConnectionIndex)
1061
+ : matchComponentDogboneViaSites(
1062
+ [...planeBuses, ...routedBoundaryBuses, candidateBus],
1063
+ {
1064
+ viaDiameter: this.config.viaDiameter,
1065
+ viaHoleDiameter: this.config.viaHoleDiameter,
1066
+ traceWidth: this.config.traceWidth,
1067
+ clearance: this.config.clearance,
1068
+ maximumSearchStates: 100_000,
1069
+ preferredBoundaryPerpendicularSideByBusId,
1070
+ preferBoundaryOutwardByBusId,
1071
+ fixedViaPointsByConnectionIndex:
1072
+ fixedViaPointsByConnectionIndex,
1073
+ blockingSegments,
1074
+ canShareCopper,
1075
+ },
1076
+ )
1000
1077
  if (!extendedViaPoints) continue
1001
1078
  const previousFixedViaPoints = fixedViaPointsByConnectionIndex
1002
1079
  const previousPlanCount = matchedPlans.length
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fanout-solver",
3
- "version": "0.0.41",
3
+ "version": "0.0.43",
4
4
  "description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
5
5
  "module": "lib/index.ts",
6
6
  "type": "module",