@tscircuit/fanout-solver 0.0.43 → 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.
- package/lib/fanout-solver.ts +121 -12
- package/package.json +1 -1
package/lib/fanout-solver.ts
CHANGED
|
@@ -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 >
|
|
472
|
+
if (params.boundaryBusCount === 5 || params.boundaryBusCount > 7) {
|
|
471
473
|
return false
|
|
472
474
|
}
|
|
473
|
-
if (params.boundaryBusCount === 6) {
|
|
474
|
-
//
|
|
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 (
|
|
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
|
|
827
|
-
// lanes do not swap between the two ends of a direct
|
|
828
|
-
// the released four- and five-bus tie behavior
|
|
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 >
|
|
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
|
|
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
|