@tscircuit/fanout-solver 0.0.63 → 0.0.64
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 +71 -12
- package/lib/route-bus.ts +26 -0
- package/package.json +1 -1
package/lib/fanout-solver.ts
CHANGED
|
@@ -1551,6 +1551,11 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1551
1551
|
promotedPlaneReservationBusIds?: readonly string[]
|
|
1552
1552
|
preferredBoundaryViaPoints?: ReadonlyMap<number, { x: number; y: number }>
|
|
1553
1553
|
planeReservationRetryCount?: number
|
|
1554
|
+
boundaryRecovery?: {
|
|
1555
|
+
busId: string
|
|
1556
|
+
preferOutward: boolean
|
|
1557
|
+
perpendicularSide: -1 | 1
|
|
1558
|
+
}
|
|
1554
1559
|
}): Generator<FanoutWorkYield, MixedTerminationState | null, unknown> {
|
|
1555
1560
|
if (this.config.allowBlindAndBuriedVias) return null
|
|
1556
1561
|
// An outside-package singleton escape can depend on the completed signal
|
|
@@ -1601,12 +1606,16 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1601
1606
|
)
|
|
1602
1607
|
const useConfiguredDensePlaneRouting =
|
|
1603
1608
|
configuredDensePlaneRouting || useAdaptiveDensePlaneRouting
|
|
1609
|
+
const useBoundaryRecovery = params.boundaryRecovery !== undefined
|
|
1610
|
+
const useJointPlaneRepair =
|
|
1611
|
+
useConfiguredDensePlaneRouting || useBoundaryRecovery
|
|
1604
1612
|
// A completed boundary assignment remains worth repairing jointly after
|
|
1605
1613
|
// plane reservations change; a greedy refill can discard that assignment.
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1608
|
-
(
|
|
1609
|
-
|
|
1614
|
+
const useJointPlaneSelection =
|
|
1615
|
+
useBoundaryRecovery ||
|
|
1616
|
+
(useAdaptiveDensePlaneRouting &&
|
|
1617
|
+
((params.planeReservationRetryCount ?? 0) === 0 ||
|
|
1618
|
+
Boolean(params.preferredBoundaryViaPoints)))
|
|
1610
1619
|
const matchLengthsAfterPlanes =
|
|
1611
1620
|
useConfiguredDensePlaneRouting &&
|
|
1612
1621
|
params.lengthMatchingStage !== "before-planes"
|
|
@@ -1852,7 +1861,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1852
1861
|
process.env.FANOUT_DEBUG_BOUNDARY_ORDER?.split(",") ??
|
|
1853
1862
|
(process.env.FANOUT_DEBUG_FIRST_BOUNDARY_BUS
|
|
1854
1863
|
? [process.env.FANOUT_DEBUG_FIRST_BOUNDARY_BUS]
|
|
1855
|
-
:
|
|
1864
|
+
: params.boundaryRecovery
|
|
1865
|
+
? [params.boundaryRecovery.busId]
|
|
1866
|
+
: [])
|
|
1856
1867
|
const boundaryBuses =
|
|
1857
1868
|
debugBoundaryOrder.length > 0
|
|
1858
1869
|
? initiallySortedBoundaryBuses.toSorted((first, second) => {
|
|
@@ -1914,6 +1925,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1914
1925
|
]
|
|
1915
1926
|
}
|
|
1916
1927
|
const unroutablePlaneBusIds = new Set<string>()
|
|
1928
|
+
let failedWideBoundaryBus: PreparedBus | undefined
|
|
1917
1929
|
debugDense(
|
|
1918
1930
|
"start",
|
|
1919
1931
|
boundaryBuses.map((bus) => `${bus.busId}:${bus.connections.length}`),
|
|
@@ -2015,6 +2027,16 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2015
2027
|
if (entersNeighboringSourceField)
|
|
2016
2028
|
preferBoundaryOutwardByBusId.set(bus.busId, false)
|
|
2017
2029
|
}
|
|
2030
|
+
if (params.boundaryRecovery) {
|
|
2031
|
+
preferredBoundaryPerpendicularSideByBusId.set(
|
|
2032
|
+
params.boundaryRecovery.busId,
|
|
2033
|
+
params.boundaryRecovery.perpendicularSide,
|
|
2034
|
+
)
|
|
2035
|
+
preferBoundaryOutwardByBusId.set(
|
|
2036
|
+
params.boundaryRecovery.busId,
|
|
2037
|
+
params.boundaryRecovery.preferOutward,
|
|
2038
|
+
)
|
|
2039
|
+
}
|
|
2018
2040
|
const debugFlippedBoundaryBus = process.env.FANOUT_DEBUG_FLIP_BOUNDARY_BUS
|
|
2019
2041
|
if (debugFlippedBoundaryBus) {
|
|
2020
2042
|
preferredBoundaryPerpendicularSideByBusId.set(debugFlippedBoundaryBus, -1)
|
|
@@ -2653,6 +2675,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2653
2675
|
allowBoundarySideViaFallback: bus.connections.length === 1,
|
|
2654
2676
|
preferCornerBoundaryVia: useConfiguredDensePlaneRouting,
|
|
2655
2677
|
adaptiveWindingRouteOrder,
|
|
2678
|
+
allowFixedViaReservedExitFallback: useBoundaryRecovery,
|
|
2656
2679
|
// Retain pad-aligned channels even when plane sites are reserved
|
|
2657
2680
|
// adaptively; the boundary grid can fence off a turning wide bus.
|
|
2658
2681
|
alignWindingGridToPads:
|
|
@@ -3035,6 +3058,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
3035
3058
|
}
|
|
3036
3059
|
}
|
|
3037
3060
|
if (!busPlans) {
|
|
3061
|
+
if (bus.connections.length >= 8) failedWideBoundaryBus ??= bus
|
|
3038
3062
|
debugDense("route:failed", bus.busId)
|
|
3039
3063
|
return false
|
|
3040
3064
|
}
|
|
@@ -3365,6 +3389,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
3365
3389
|
promotedAlternatePlaneBusIds: ReadonlySet<string> = new Set(),
|
|
3366
3390
|
): Map<number, { x: number; y: number }> | null => {
|
|
3367
3391
|
feasibleAlternatePlanePlans = []
|
|
3392
|
+
matchedPlaneBusesInRoutingOrder = null
|
|
3368
3393
|
const fixedBoundaryViaPoints = new Map(
|
|
3369
3394
|
candidatePlans.flatMap((plan) =>
|
|
3370
3395
|
plan.via
|
|
@@ -3431,11 +3456,16 @@ export class FanoutSolver extends BaseSolver {
|
|
|
3431
3456
|
if (retainedViaPoints)
|
|
3432
3457
|
return new Map([...fixedBoundaryViaPoints, ...retainedViaPoints])
|
|
3433
3458
|
if (
|
|
3434
|
-
|
|
3459
|
+
useJointPlaneRepair ||
|
|
3435
3460
|
process.env.FANOUT_DEBUG_INCREMENTAL_PLANE_MATCH === "1"
|
|
3436
3461
|
) {
|
|
3437
3462
|
let incrementalViaPoints = new Map(fixedBoundaryViaPoints)
|
|
3438
|
-
|
|
3463
|
+
// Reservations guided the boundary search, but their copper is
|
|
3464
|
+
// still uncommitted. Recovery must choose local and longer plane
|
|
3465
|
+
// escapes together instead of locking every provisional site.
|
|
3466
|
+
const matchedPlaneBuses = useBoundaryRecovery
|
|
3467
|
+
? []
|
|
3468
|
+
: [...activeBoundaryReservationPlaneBuses]
|
|
3439
3469
|
for (const planeBus of matchedPlaneBuses) {
|
|
3440
3470
|
for (const connection of planeBus.connections) {
|
|
3441
3471
|
const reservedPoint = fixedViaPointsByConnectionIndex.get(
|
|
@@ -3581,7 +3611,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
3581
3611
|
independentlyUnmatchablePlaneBuses.map((bus) => bus.busId),
|
|
3582
3612
|
)
|
|
3583
3613
|
if (
|
|
3584
|
-
!
|
|
3614
|
+
!useJointPlaneRepair &&
|
|
3585
3615
|
process.env.FANOUT_DEBUG_ROUTE_UNMATCHED_PLANES !== "1"
|
|
3586
3616
|
) {
|
|
3587
3617
|
return null
|
|
@@ -3596,7 +3626,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
3596
3626
|
? 10_000
|
|
3597
3627
|
: useConfiguredDensePlaneRouting
|
|
3598
3628
|
? 3_000_000
|
|
3599
|
-
:
|
|
3629
|
+
: useBoundaryRecovery
|
|
3630
|
+
? 10_000
|
|
3631
|
+
: 1_000),
|
|
3600
3632
|
)
|
|
3601
3633
|
const maximumAlternatePlaneRoutes = Number(
|
|
3602
3634
|
process.env.FANOUT_DEBUG_ALTERNATE_ROUTE_COUNT ??
|
|
@@ -3774,7 +3806,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
3774
3806
|
}
|
|
3775
3807
|
let alternatePlanePlans: FanoutRoutePlan[] | null
|
|
3776
3808
|
if (
|
|
3777
|
-
|
|
3809
|
+
useJointPlaneRepair ||
|
|
3778
3810
|
process.env.FANOUT_DEBUG_EXACT_COVER_ALTERNATES === "1"
|
|
3779
3811
|
) {
|
|
3780
3812
|
type IndependentPlaneRouteCandidate = {
|
|
@@ -3795,7 +3827,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
3795
3827
|
})),
|
|
3796
3828
|
}),
|
|
3797
3829
|
)
|
|
3798
|
-
if (
|
|
3830
|
+
if (useJointPlaneSelection) {
|
|
3799
3831
|
const acceptedPlans = [
|
|
3800
3832
|
...candidatePlans,
|
|
3801
3833
|
...feasibleAlternatePlanePlans,
|
|
@@ -4106,7 +4138,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
4106
4138
|
)
|
|
4107
4139
|
if (!alternatePlanePlans) return null
|
|
4108
4140
|
feasibleAlternatePlanePlans = alternatePlanePlans
|
|
4109
|
-
if (
|
|
4141
|
+
if (useJointPlaneSelection) {
|
|
4110
4142
|
matchedPlaneBusesInRoutingOrder = []
|
|
4111
4143
|
return new Map([
|
|
4112
4144
|
...fixedBoundaryViaPoints,
|
|
@@ -4620,6 +4652,33 @@ export class FanoutSolver extends BaseSolver {
|
|
|
4620
4652
|
denseRoutingStrategy: "boundary-aligned",
|
|
4621
4653
|
})
|
|
4622
4654
|
if (boundaryAlignedState) return boundaryAlignedState
|
|
4655
|
+
// A later wide bus can be fenced by provisional through-vias even
|
|
4656
|
+
// though its requested exit order is routable. After both existing
|
|
4657
|
+
// grids fail, give that bus first choice of the field and retry the
|
|
4658
|
+
// four dogbone orientations. The marker bounds recursion and enables
|
|
4659
|
+
// joint selection of the remaining uncommitted plane escapes.
|
|
4660
|
+
if (!useBoundaryRecovery && failedWideBoundaryBus) {
|
|
4661
|
+
const busId = failedWideBoundaryBus.busId
|
|
4662
|
+
const outward = preferBoundaryOutwardByBusId.get(busId) ?? true
|
|
4663
|
+
const side = preferredBoundaryPerpendicularSideByBusId.get(busId) ?? 1
|
|
4664
|
+
for (const [preferOutward, perpendicularSide] of [
|
|
4665
|
+
[!outward, -side],
|
|
4666
|
+
[!outward, side],
|
|
4667
|
+
[outward, -side],
|
|
4668
|
+
[outward, side],
|
|
4669
|
+
] as const) {
|
|
4670
|
+
const recoveredState =
|
|
4671
|
+
yield* this.routeDenseThroughAllMixedTerminationSteps({
|
|
4672
|
+
...params,
|
|
4673
|
+
boundaryRecovery: {
|
|
4674
|
+
busId,
|
|
4675
|
+
preferOutward,
|
|
4676
|
+
perpendicularSide: perpendicularSide as -1 | 1,
|
|
4677
|
+
},
|
|
4678
|
+
})
|
|
4679
|
+
if (recoveredState) return recoveredState
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4623
4682
|
}
|
|
4624
4683
|
if (
|
|
4625
4684
|
!usePadAlignedDenseRouting ||
|
package/lib/route-bus.ts
CHANGED
|
@@ -75,6 +75,8 @@ export interface RouteBusParams {
|
|
|
75
75
|
alignWindingGridToPads?: boolean
|
|
76
76
|
/** Bounds the final fixed-via winding fallback after ordered attempts. */
|
|
77
77
|
fixedViaFallbackRouteOrderAttempts?: number
|
|
78
|
+
/** Retry caller-fixed sites while preserving future exit gaps during recovery. */
|
|
79
|
+
allowFixedViaReservedExitFallback?: boolean
|
|
78
80
|
/** Skip this many otherwise-clear plane escapes when enumerating alternatives. */
|
|
79
81
|
planeCandidateSkipCount?: number
|
|
80
82
|
/** Dense corner-band phase that preserves existing lane centers when leading lanes are prepended. */
|
|
@@ -2606,6 +2608,7 @@ export function* routeBusAlternativesSteps(
|
|
|
2606
2608
|
adaptiveWindingRouteOrder = false,
|
|
2607
2609
|
alignWindingGridToPads = false,
|
|
2608
2610
|
fixedViaFallbackRouteOrderAttempts = 24,
|
|
2611
|
+
allowFixedViaReservedExitFallback = false,
|
|
2609
2612
|
cornerBandTargetTrackOffset,
|
|
2610
2613
|
} = params
|
|
2611
2614
|
if (!Number.isInteger(maxAlternatives) || maxAlternatives < 1) {
|
|
@@ -2994,6 +2997,29 @@ export function* routeBusAlternativesSteps(
|
|
|
2994
2997
|
})),
|
|
2995
2998
|
).flat()
|
|
2996
2999
|
: []),
|
|
3000
|
+
// A single-layer target permutation can also let an early lane
|
|
3001
|
+
// close a future terminal's exit gap. Keep the original attempts
|
|
3002
|
+
// first, then retry the same fixed sites with every exit reserved.
|
|
3003
|
+
...(allowFixedViaReservedExitFallback &&
|
|
3004
|
+
fixedViaPointsByConnectionIndex &&
|
|
3005
|
+
!getCornerSide(bus) &&
|
|
3006
|
+
windingTargetOrderCount === 1 &&
|
|
3007
|
+
bus.connections.length > 2
|
|
3008
|
+
? [true, false].map((preferTargetDirectedLaneBias) => ({
|
|
3009
|
+
label: `fixed-vias-reserved-exits-${preferTargetDirectedLaneBias}`,
|
|
3010
|
+
useViaInPad: false,
|
|
3011
|
+
getViaHandedness: () => 0 as const,
|
|
3012
|
+
getViaPoint: (connection: PreparedConnection) =>
|
|
3013
|
+
coordinatedViaPoints.get(connection.connectionIndex)!,
|
|
3014
|
+
maximumRouteOrderAttempts: Math.min(
|
|
3015
|
+
maximumThroughAllRouteOrderAttempts,
|
|
3016
|
+
fixedViaFallbackRouteOrderAttempts,
|
|
3017
|
+
),
|
|
3018
|
+
windingOrderIndex: 0,
|
|
3019
|
+
preferTargetDirectedLaneBias,
|
|
3020
|
+
reserveTerminalExitPoints: true,
|
|
3021
|
+
}))
|
|
3022
|
+
: []),
|
|
2997
3023
|
]
|
|
2998
3024
|
: []
|
|
2999
3025
|
const planeTerminationsAlreadyOccupyTheFanout = acceptedPlans.some(
|