@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.
- package/lib/fanout-solver.ts +122 -45
- package/package.json +1 -1
package/lib/fanout-solver.ts
CHANGED
|
@@ -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
|
|
729
|
-
unsortedBoundaryBuses.
|
|
730
|
-
|
|
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 (
|
|
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 >
|
|
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
|
-
//
|
|
837
|
-
// allocation: a valid early trace can consume
|
|
838
|
-
// later narrow bus. Reserve
|
|
839
|
-
//
|
|
840
|
-
//
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
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
|
-
|
|
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
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
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
|