@tscircuit/fanout-solver 0.0.41 → 0.0.42
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 +97 -44
- package/package.json +1 -1
package/lib/fanout-solver.ts
CHANGED
|
@@ -440,6 +440,36 @@ 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 === 4 &&
|
|
449
|
+
new Set(boundaryBusConnectionCounts).size > 1)
|
|
450
|
+
)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function shouldDeferSingletonBoundaryViaReservation(
|
|
454
|
+
boundaryBusConnectionCounts: readonly number[],
|
|
455
|
+
): boolean {
|
|
456
|
+
return (
|
|
457
|
+
boundaryBusConnectionCounts.length === 5 &&
|
|
458
|
+
boundaryBusConnectionCounts.filter((count) => count === 1).length === 1
|
|
459
|
+
)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function shouldSearchAdditionalBoundaryRouteTopologies(params: {
|
|
463
|
+
boundaryBusCount: number
|
|
464
|
+
rawSkew: number
|
|
465
|
+
maximumSkew: number
|
|
466
|
+
}): boolean {
|
|
467
|
+
if (params.boundaryBusCount >= 5) return false
|
|
468
|
+
return (
|
|
469
|
+
params.rawSkew - params.maximumSkew > Math.max(1, params.maximumSkew * 0.25)
|
|
470
|
+
)
|
|
471
|
+
}
|
|
472
|
+
|
|
443
473
|
function getCandidateEscapeLayersForBus(params: {
|
|
444
474
|
bus: PreparedBus
|
|
445
475
|
srj: SimpleRouteJson
|
|
@@ -725,16 +755,15 @@ export class FanoutSolver extends BaseSolver {
|
|
|
725
755
|
const unsortedBoundaryBuses = params.busesInRoutingOrder.filter(
|
|
726
756
|
(bus) => bus.termination.type === "boundary",
|
|
727
757
|
)
|
|
728
|
-
const
|
|
729
|
-
unsortedBoundaryBuses.
|
|
730
|
-
|
|
731
|
-
1
|
|
758
|
+
const useJointBoundaryViaReservation = shouldUseJointBoundaryViaReservation(
|
|
759
|
+
unsortedBoundaryBuses.map((bus) => bus.connections.length),
|
|
760
|
+
)
|
|
732
761
|
const boundaryBuses = unsortedBoundaryBuses.toSorted((first, second) => {
|
|
733
762
|
// Reserve the dense escape field for the widest buses first. Small
|
|
734
763
|
// control groups can usually route around their copper, while routing
|
|
735
764
|
// a two-line corner bus first can consume a critical channel needed by
|
|
736
765
|
// an eight-line winding bus and force the expensive fallback search.
|
|
737
|
-
if (
|
|
766
|
+
if (useJointBoundaryViaReservation) {
|
|
738
767
|
const connectionCountDifference =
|
|
739
768
|
second.connections.length - first.connections.length
|
|
740
769
|
if (connectionCountDifference !== 0) return connectionCountDifference
|
|
@@ -786,7 +815,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
786
815
|
)
|
|
787
816
|
if (
|
|
788
817
|
boundaryBuses.length === 0 ||
|
|
789
|
-
boundaryBuses.length >
|
|
818
|
+
boundaryBuses.length > 5 ||
|
|
790
819
|
planeBuses.length < 8 ||
|
|
791
820
|
boundaryBuses.some((bus) => !busUsesCoordinatedWinding(bus)) ||
|
|
792
821
|
planeBuses.some((bus) => bus.connections.length !== 1) ||
|
|
@@ -833,22 +862,37 @@ export class FanoutSolver extends BaseSolver {
|
|
|
833
862
|
),
|
|
834
863
|
)
|
|
835
864
|
}
|
|
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
|
-
|
|
865
|
+
// Five boundary buses, and heterogeneous four-bus groups, leave too little
|
|
866
|
+
// slack for incremental site allocation: a valid early trace can consume
|
|
867
|
+
// the last dogbone site of a later narrow bus. Reserve the multi-line bus
|
|
868
|
+
// barrels before routing copper. A single one-line bus stays provisional
|
|
869
|
+
// because its flexible site can be rematched around the wide-bus copper.
|
|
870
|
+
// Plane sites are likewise rematched around completed boundary plans.
|
|
871
|
+
const boundaryBusConnectionCounts = boundaryBuses.map(
|
|
872
|
+
(bus) => bus.connections.length,
|
|
873
|
+
)
|
|
874
|
+
const provisionalSingletonBus = shouldDeferSingletonBoundaryViaReservation(
|
|
875
|
+
boundaryBusConnectionCounts,
|
|
876
|
+
)
|
|
877
|
+
? boundaryBuses.find((bus) => bus.connections.length === 1)
|
|
878
|
+
: undefined
|
|
879
|
+
const initiallyMatchedBoundaryBuses = provisionalSingletonBus
|
|
880
|
+
? boundaryBuses.filter((bus) => bus !== provisionalSingletonBus)
|
|
881
|
+
: boundaryBuses
|
|
882
|
+
const jointViaPoints = useJointBoundaryViaReservation
|
|
883
|
+
? matchComponentDogboneViaSites(
|
|
884
|
+
[...planeBuses, ...initiallyMatchedBoundaryBuses],
|
|
885
|
+
{
|
|
886
|
+
viaDiameter: this.config.viaDiameter,
|
|
887
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
888
|
+
traceWidth: this.config.traceWidth,
|
|
889
|
+
clearance: this.config.clearance,
|
|
890
|
+
maximumSearchStates: 100_000,
|
|
891
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
892
|
+
preferBoundaryOutwardByBusId,
|
|
893
|
+
canShareCopper,
|
|
894
|
+
},
|
|
895
|
+
)
|
|
852
896
|
: null
|
|
853
897
|
const seedViaPoints =
|
|
854
898
|
jointViaPoints ??
|
|
@@ -929,7 +973,11 @@ export class FanoutSolver extends BaseSolver {
|
|
|
929
973
|
const lengths = busPlans.map((plan) => plan.length)
|
|
930
974
|
const rawSkew = Math.max(...lengths) - Math.min(...lengths)
|
|
931
975
|
const needsRouteDiversity =
|
|
932
|
-
|
|
976
|
+
shouldSearchAdditionalBoundaryRouteTopologies({
|
|
977
|
+
boundaryBusCount: boundaryBuses.length,
|
|
978
|
+
rawSkew,
|
|
979
|
+
maximumSkew: bus.maxLengthSkew,
|
|
980
|
+
})
|
|
933
981
|
// Only pay for additional A* variants when the first topology is so
|
|
934
982
|
// skewed that compact meanders are unlikely to absorb the deficit.
|
|
935
983
|
// This keeps already-near-matched buses on the single-attempt path.
|
|
@@ -976,27 +1024,32 @@ export class FanoutSolver extends BaseSolver {
|
|
|
976
1024
|
candidateIndex++
|
|
977
1025
|
) {
|
|
978
1026
|
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
|
-
|
|
1027
|
+
const candidateHasFixedViaPoints = candidateBus.connections.every(
|
|
1028
|
+
(connection) =>
|
|
1029
|
+
fixedViaPointsByConnectionIndex.has(connection.connectionIndex),
|
|
1030
|
+
)
|
|
1031
|
+
const extendedViaPoints =
|
|
1032
|
+
jointViaPoints && candidateHasFixedViaPoints
|
|
1033
|
+
? // The joint map is deliberately kept intact so getReservedVias()
|
|
1034
|
+
// blocks every already-reserved future through-barrel during A*.
|
|
1035
|
+
// Provisional singleton and plane dogbones are rematched later.
|
|
1036
|
+
new Map(fixedViaPointsByConnectionIndex)
|
|
1037
|
+
: matchComponentDogboneViaSites(
|
|
1038
|
+
[...planeBuses, ...routedBoundaryBuses, candidateBus],
|
|
1039
|
+
{
|
|
1040
|
+
viaDiameter: this.config.viaDiameter,
|
|
1041
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1042
|
+
traceWidth: this.config.traceWidth,
|
|
1043
|
+
clearance: this.config.clearance,
|
|
1044
|
+
maximumSearchStates: 100_000,
|
|
1045
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
1046
|
+
preferBoundaryOutwardByBusId,
|
|
1047
|
+
fixedViaPointsByConnectionIndex:
|
|
1048
|
+
fixedViaPointsByConnectionIndex,
|
|
1049
|
+
blockingSegments,
|
|
1050
|
+
canShareCopper,
|
|
1051
|
+
},
|
|
1052
|
+
)
|
|
1000
1053
|
if (!extendedViaPoints) continue
|
|
1001
1054
|
const previousFixedViaPoints = fixedViaPointsByConnectionIndex
|
|
1002
1055
|
const previousPlanCount = matchedPlans.length
|