@tscircuit/fanout-solver 0.0.40 → 0.0.41

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.
@@ -722,48 +722,60 @@ export class FanoutSolver extends BaseSolver {
722
722
  }): MixedTerminationState | null {
723
723
  if (this.config.allowBlindAndBuriedVias) return null
724
724
 
725
- const boundaryBuses = params.busesInRoutingOrder
726
- .filter((bus) => bus.termination.type === "boundary")
727
- .toSorted((first, second) => {
728
- const cornerBandDifference =
729
- Number(
730
- Boolean(getCornerBandSide(second.exitEdge, second.preferredExit)),
731
- ) -
732
- Number(
733
- Boolean(getCornerBandSide(first.exitEdge, first.preferredExit)),
725
+ const unsortedBoundaryBuses = params.busesInRoutingOrder.filter(
726
+ (bus) => bus.termination.type === "boundary",
727
+ )
728
+ const useJointFourBusReservation =
729
+ unsortedBoundaryBuses.length === 4 &&
730
+ new Set(unsortedBoundaryBuses.map((bus) => bus.connections.length)).size >
731
+ 1
732
+ const boundaryBuses = unsortedBoundaryBuses.toSorted((first, second) => {
733
+ // Reserve the dense escape field for the widest buses first. Small
734
+ // control groups can usually route around their copper, while routing
735
+ // a two-line corner bus first can consume a critical channel needed by
736
+ // an eight-line winding bus and force the expensive fallback search.
737
+ if (useJointFourBusReservation) {
738
+ const connectionCountDifference =
739
+ second.connections.length - first.connections.length
740
+ if (connectionCountDifference !== 0) return connectionCountDifference
741
+ }
742
+ const cornerBandDifference =
743
+ Number(
744
+ Boolean(getCornerBandSide(second.exitEdge, second.preferredExit)),
745
+ ) -
746
+ Number(Boolean(getCornerBandSide(first.exitEdge, first.preferredExit)))
747
+ if (cornerBandDifference !== 0) return cornerBandDifference
748
+ const firstIsCorner = Boolean(
749
+ getCornerBandSide(first.exitEdge, first.preferredExit),
750
+ )
751
+ if (!firstIsCorner) {
752
+ const getSourceSpan = (bus: PreparedBus): number => {
753
+ const xCoordinates = bus.connections.map(
754
+ (connection) => connection.sourcePoint.x,
755
+ )
756
+ const yCoordinates = bus.connections.map(
757
+ (connection) => connection.sourcePoint.y,
758
+ )
759
+ return (
760
+ Math.max(...xCoordinates) -
761
+ Math.min(...xCoordinates) +
762
+ Math.max(...yCoordinates) -
763
+ Math.min(...yCoordinates)
734
764
  )
735
- if (cornerBandDifference !== 0) return cornerBandDifference
736
- const firstIsCorner = Boolean(
737
- getCornerBandSide(first.exitEdge, first.preferredExit),
738
- )
739
- if (!firstIsCorner) {
740
- const getSourceSpan = (bus: PreparedBus): number => {
741
- const xCoordinates = bus.connections.map(
742
- (connection) => connection.sourcePoint.x,
743
- )
744
- const yCoordinates = bus.connections.map(
745
- (connection) => connection.sourcePoint.y,
746
- )
747
- return (
748
- Math.max(...xCoordinates) -
749
- Math.min(...xCoordinates) +
750
- Math.max(...yCoordinates) -
751
- Math.min(...yCoordinates)
752
- )
753
- }
754
- const sourceSpanDifference =
755
- getSourceSpan(second) - getSourceSpan(first)
756
- if (Math.abs(sourceSpanDifference) > 1e-9) {
757
- return sourceSpanDifference
758
- }
759
765
  }
760
- const firstLayer = params.busLayerAssignments[first.busId]
761
- const secondLayer = params.busLayerAssignments[second.busId]
762
- const layerDifference =
763
- this.config.layerNames.indexOf(firstLayer ?? "") -
764
- this.config.layerNames.indexOf(secondLayer ?? "")
765
- return -layerDifference
766
- })
766
+ const sourceSpanDifference =
767
+ getSourceSpan(second) - getSourceSpan(first)
768
+ if (Math.abs(sourceSpanDifference) > 1e-9) {
769
+ return sourceSpanDifference
770
+ }
771
+ }
772
+ const firstLayer = params.busLayerAssignments[first.busId]
773
+ const secondLayer = params.busLayerAssignments[second.busId]
774
+ const layerDifference =
775
+ this.config.layerNames.indexOf(firstLayer ?? "") -
776
+ this.config.layerNames.indexOf(secondLayer ?? "")
777
+ return -layerDifference
778
+ })
767
779
  // Preserve the caller/input order for the dense singleton fill. The
768
780
  // general routing sort is useful for heterogeneous buses, but ordering a
769
781
  // regular BGA power field by obstacle depth creates artificial local
@@ -821,9 +833,26 @@ export class FanoutSolver extends BaseSolver {
821
833
  ),
822
834
  )
823
835
  }
824
- const seedViaPoints = matchComponentDogboneViaSites(
825
- [...planeBuses, boundaryBuses[0]!],
826
- {
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
+ })
852
+ : null
853
+ const seedViaPoints =
854
+ jointViaPoints ??
855
+ matchComponentDogboneViaSites([...planeBuses, boundaryBuses[0]!], {
827
856
  viaDiameter: this.config.viaDiameter,
828
857
  viaHoleDiameter: this.config.viaHoleDiameter,
829
858
  traceWidth: this.config.traceWidth,
@@ -832,8 +861,7 @@ export class FanoutSolver extends BaseSolver {
832
861
  preferredBoundaryPerpendicularSideByBusId,
833
862
  preferBoundaryOutwardByBusId,
834
863
  canShareCopper,
835
- },
836
- )
864
+ })
837
865
  if (seedViaPoints) {
838
866
  let fixedViaPointsByConnectionIndex: ReadonlyMap<
839
867
  number,
@@ -948,21 +976,27 @@ export class FanoutSolver extends BaseSolver {
948
976
  candidateIndex++
949
977
  ) {
950
978
  const candidateBus = remainingBoundaryBuses[candidateIndex]!
951
- const extendedViaPoints = matchComponentDogboneViaSites(
952
- [...planeBuses, ...routedBoundaryBuses, candidateBus],
953
- {
954
- viaDiameter: this.config.viaDiameter,
955
- viaHoleDiameter: this.config.viaHoleDiameter,
956
- traceWidth: this.config.traceWidth,
957
- clearance: this.config.clearance,
958
- maximumSearchStates: 100_000,
959
- preferredBoundaryPerpendicularSideByBusId,
960
- preferBoundaryOutwardByBusId,
961
- fixedViaPointsByConnectionIndex: fixedViaPointsByConnectionIndex,
962
- blockingSegments,
963
- canShareCopper,
964
- },
965
- )
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
+ )
966
1000
  if (!extendedViaPoints) continue
967
1001
  const previousFixedViaPoints = fixedViaPointsByConnectionIndex
968
1002
  const previousPlanCount = matchedPlans.length
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fanout-solver",
3
- "version": "0.0.40",
3
+ "version": "0.0.41",
4
4
  "description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
5
5
  "module": "lib/index.ts",
6
6
  "type": "module",