@tscircuit/fanout-solver 0.0.48 → 0.0.50
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 +2277 -324
- package/lib/match-component-dogbone-via-sites.ts +92 -14
- package/lib/route-bus.ts +135 -47
- package/lib/route-single-layer-adaptive-exits.ts +323 -16
- package/lib/route-via-minimal-winding.ts +253 -8
- package/lib/types.ts +11 -0
- package/package.json +1 -1
|
@@ -40,6 +40,17 @@ export interface DogboneViaSiteGeometryRules {
|
|
|
40
40
|
connectionIndex: number
|
|
41
41
|
segment: RoutedSegment
|
|
42
42
|
}[]
|
|
43
|
+
/** Routed vias that every newly assigned through-via/dogbone must clear. */
|
|
44
|
+
blockingVias?: readonly {
|
|
45
|
+
connectionIndex: number
|
|
46
|
+
center: Point2D
|
|
47
|
+
diameter: number
|
|
48
|
+
spanLayers: readonly string[]
|
|
49
|
+
}[]
|
|
50
|
+
/** Board obstacles outside the source component that dogbones must clear. */
|
|
51
|
+
additionalObstacles?: readonly Obstacle[]
|
|
52
|
+
/** Alternate horizontal plane dogbones across BGA rows. */
|
|
53
|
+
preferPlaneCheckerboardSites?: boolean
|
|
43
54
|
/** True only when the two connections are allowed to merge copper. */
|
|
44
55
|
canShareCopper?: (
|
|
45
56
|
firstConnectionIndex: number,
|
|
@@ -148,6 +159,7 @@ function getComponentMatchingInputs(
|
|
|
148
159
|
): ComponentMatchingInput[] {
|
|
149
160
|
const byComponent = new Map<string, ComponentMatchingInput>()
|
|
150
161
|
const componentByConnectionIndex = new Map<number, string>()
|
|
162
|
+
const obstacleSetByComponentId = new Map<string, Set<Obstacle>>()
|
|
151
163
|
|
|
152
164
|
for (const bus of preparedBuses) {
|
|
153
165
|
let component = byComponent.get(bus.componentId)
|
|
@@ -162,6 +174,7 @@ function getComponentMatchingInputs(
|
|
|
162
174
|
pitchY: Number.POSITIVE_INFINITY,
|
|
163
175
|
}
|
|
164
176
|
byComponent.set(bus.componentId, component)
|
|
177
|
+
obstacleSetByComponentId.set(bus.componentId, new Set())
|
|
165
178
|
}
|
|
166
179
|
|
|
167
180
|
component.xCoordinates.push(...bus.xCoordinates)
|
|
@@ -172,8 +185,10 @@ function getComponentMatchingInputs(
|
|
|
172
185
|
if (Number.isFinite(bus.pitchY) && bus.pitchY > EPSILON) {
|
|
173
186
|
component.pitchY = Math.min(component.pitchY, bus.pitchY)
|
|
174
187
|
}
|
|
188
|
+
const obstacleSet = obstacleSetByComponentId.get(bus.componentId)!
|
|
175
189
|
for (const obstacle of bus.componentObstacles) {
|
|
176
|
-
if (!
|
|
190
|
+
if (!obstacleSet.has(obstacle)) {
|
|
191
|
+
obstacleSet.add(obstacle)
|
|
177
192
|
component.obstacles.push(obstacle)
|
|
178
193
|
}
|
|
179
194
|
}
|
|
@@ -325,6 +340,9 @@ function getConnectionCandidates(params: {
|
|
|
325
340
|
}): ViaSiteCandidate[] {
|
|
326
341
|
const { connection, component, rules } = params
|
|
327
342
|
const { preparedConnection, direction } = connection
|
|
343
|
+
const obstacles = rules.additionalObstacles
|
|
344
|
+
? [...new Set([...component.obstacles, ...rules.additionalObstacles])]
|
|
345
|
+
: component.obstacles
|
|
328
346
|
const source = {
|
|
329
347
|
x: preparedConnection.sourcePoint.x,
|
|
330
348
|
y: preparedConnection.sourcePoint.y,
|
|
@@ -368,7 +386,7 @@ function getConnectionCandidates(params: {
|
|
|
368
386
|
if (
|
|
369
387
|
!viaSiteClearsObstacles({
|
|
370
388
|
point,
|
|
371
|
-
obstacles
|
|
389
|
+
obstacles,
|
|
372
390
|
viaDiameter: rules.viaDiameter,
|
|
373
391
|
clearance: rules.clearance,
|
|
374
392
|
})
|
|
@@ -385,7 +403,7 @@ function getConnectionCandidates(params: {
|
|
|
385
403
|
!sourceSegmentClearsOtherObstacles({
|
|
386
404
|
segment: sourceSegment,
|
|
387
405
|
sourceObstacle: preparedConnection.sourceObstacle,
|
|
388
|
-
obstacles
|
|
406
|
+
obstacles,
|
|
389
407
|
clearance: rules.clearance,
|
|
390
408
|
})
|
|
391
409
|
) {
|
|
@@ -423,6 +441,35 @@ function getConnectionCandidates(params: {
|
|
|
423
441
|
},
|
|
424
442
|
)
|
|
425
443
|
if (!candidateClearsRoutedCopper) continue
|
|
444
|
+
const candidateClearsRoutedVias = (rules.blockingVias ?? []).every(
|
|
445
|
+
(blocker) => {
|
|
446
|
+
if (blocker.connectionIndex === preparedConnection.connectionIndex) {
|
|
447
|
+
return true
|
|
448
|
+
}
|
|
449
|
+
if (
|
|
450
|
+
distance(point, blocker.center) <
|
|
451
|
+
(rules.viaDiameter + blocker.diameter) / 2 + rules.clearance - EPSILON
|
|
452
|
+
) {
|
|
453
|
+
return false
|
|
454
|
+
}
|
|
455
|
+
if (
|
|
456
|
+
blocker.spanLayers.includes(sourceSegment.layer) &&
|
|
457
|
+
distancePointToSegment(
|
|
458
|
+
blocker.center,
|
|
459
|
+
sourceSegment.start,
|
|
460
|
+
sourceSegment.end,
|
|
461
|
+
) <
|
|
462
|
+
blocker.diameter / 2 +
|
|
463
|
+
sourceSegment.width / 2 +
|
|
464
|
+
rules.clearance -
|
|
465
|
+
EPSILON
|
|
466
|
+
) {
|
|
467
|
+
return false
|
|
468
|
+
}
|
|
469
|
+
return true
|
|
470
|
+
},
|
|
471
|
+
)
|
|
472
|
+
if (!candidateClearsRoutedVias) continue
|
|
426
473
|
candidates.push({
|
|
427
474
|
connectionIndex: preparedConnection.connectionIndex,
|
|
428
475
|
point,
|
|
@@ -431,10 +478,30 @@ function getConnectionCandidates(params: {
|
|
|
431
478
|
})
|
|
432
479
|
}
|
|
433
480
|
|
|
481
|
+
const perpendicularCoordinates =
|
|
482
|
+
direction === "left" || direction === "right"
|
|
483
|
+
? component.yCoordinates
|
|
484
|
+
: component.xCoordinates
|
|
485
|
+
const sourcePerpendicularCoordinate =
|
|
486
|
+
direction === "left" || direction === "right" ? source.y : source.x
|
|
487
|
+
const perpendicularGridIndex = perpendicularCoordinates.findIndex(
|
|
488
|
+
(coordinate) =>
|
|
489
|
+
Math.abs(coordinate - sourcePerpendicularCoordinate) <= EPSILON,
|
|
490
|
+
)
|
|
491
|
+
const planeCheckerboardSide =
|
|
492
|
+
perpendicularGridIndex >= 0
|
|
493
|
+
? perpendicularGridIndex % 2 === 0
|
|
494
|
+
? 1
|
|
495
|
+
: -1
|
|
496
|
+
: undefined
|
|
434
497
|
const preferredPerpendicularSide =
|
|
435
498
|
connection.terminationType === "boundary"
|
|
436
499
|
? rules.preferredBoundaryPerpendicularSideByBusId?.get(connection.busId)
|
|
437
|
-
:
|
|
500
|
+
: rules.preferPlaneCheckerboardSites &&
|
|
501
|
+
connection.terminationType === "plane" &&
|
|
502
|
+
(direction === "left" || direction === "right")
|
|
503
|
+
? planeCheckerboardSide
|
|
504
|
+
: undefined
|
|
438
505
|
const preferOutward =
|
|
439
506
|
connection.terminationType === "boundary"
|
|
440
507
|
? (rules.preferBoundaryOutwardByBusId?.get(connection.busId) ?? true)
|
|
@@ -525,6 +592,25 @@ function matchComponent(params: {
|
|
|
525
592
|
}),
|
|
526
593
|
)
|
|
527
594
|
if (entries.some((entry) => entry.candidates.length === 0)) return null
|
|
595
|
+
const compatibilityCache = new Map<
|
|
596
|
+
ViaSiteCandidate,
|
|
597
|
+
Map<ViaSiteCandidate, boolean>
|
|
598
|
+
>()
|
|
599
|
+
const candidatesAreCompatible = (
|
|
600
|
+
first: ViaSiteCandidate,
|
|
601
|
+
second: ViaSiteCandidate,
|
|
602
|
+
): boolean => {
|
|
603
|
+
const cached = compatibilityCache.get(first)?.get(second)
|
|
604
|
+
if (cached !== undefined) return cached
|
|
605
|
+
const compatible = candidatesAreMutuallyClear({ first, second, rules })
|
|
606
|
+
const firstCache = compatibilityCache.get(first) ?? new Map()
|
|
607
|
+
firstCache.set(second, compatible)
|
|
608
|
+
compatibilityCache.set(first, firstCache)
|
|
609
|
+
const secondCache = compatibilityCache.get(second) ?? new Map()
|
|
610
|
+
secondCache.set(first, compatible)
|
|
611
|
+
compatibilityCache.set(second, secondCache)
|
|
612
|
+
return compatible
|
|
613
|
+
}
|
|
528
614
|
// Every solution must include each sole candidate. Seed and validate those
|
|
529
615
|
// forced choices once so recursive matching only explores genuine choices.
|
|
530
616
|
const forcedCandidates = entries.flatMap((entry) =>
|
|
@@ -542,11 +628,7 @@ function matchComponent(params: {
|
|
|
542
628
|
previousIndex++
|
|
543
629
|
) {
|
|
544
630
|
if (
|
|
545
|
-
!
|
|
546
|
-
first: candidate,
|
|
547
|
-
second: forcedCandidates[previousIndex]!,
|
|
548
|
-
rules,
|
|
549
|
-
})
|
|
631
|
+
!candidatesAreCompatible(candidate, forcedCandidates[previousIndex]!)
|
|
550
632
|
) {
|
|
551
633
|
return null
|
|
552
634
|
}
|
|
@@ -584,11 +666,7 @@ function matchComponent(params: {
|
|
|
584
666
|
): ViaSiteCandidate[] =>
|
|
585
667
|
entry.candidates.filter((candidate) =>
|
|
586
668
|
[...assignedCandidates.values()].every((assignedCandidate) =>
|
|
587
|
-
|
|
588
|
-
first: candidate,
|
|
589
|
-
second: assignedCandidate,
|
|
590
|
-
rules,
|
|
591
|
-
}),
|
|
669
|
+
candidatesAreCompatible(candidate, assignedCandidate),
|
|
592
670
|
),
|
|
593
671
|
)
|
|
594
672
|
|
package/lib/route-bus.ts
CHANGED
|
@@ -24,7 +24,8 @@ import {
|
|
|
24
24
|
obstacleSharesElectricalNet,
|
|
25
25
|
} from "./net-identity"
|
|
26
26
|
import {
|
|
27
|
-
|
|
27
|
+
type RouteViaMinimalWindingProgress,
|
|
28
|
+
routeViaMinimalWindingAlternativesSteps,
|
|
28
29
|
type ViaMinimalWindingReservedVia,
|
|
29
30
|
} from "./route-via-minimal-winding"
|
|
30
31
|
import type {
|
|
@@ -60,10 +61,21 @@ export interface RouteBusParams {
|
|
|
60
61
|
fixedViaPointsByConnectionIndex?: ReadonlyMap<number, Point2D>
|
|
61
62
|
reservedVias?: readonly ViaMinimalWindingReservedVia[]
|
|
62
63
|
viaMinimalOnly?: boolean
|
|
64
|
+
/** Bounds the final fixed-via winding fallback after ordered attempts. */
|
|
65
|
+
fixedViaFallbackRouteOrderAttempts?: number
|
|
66
|
+
/** Skip this many otherwise-clear plane escapes when enumerating alternatives. */
|
|
67
|
+
planeCandidateSkipCount?: number
|
|
63
68
|
/** Dense corner-band phase that preserves existing lane centers when leading lanes are prepended. */
|
|
64
69
|
cornerBandTargetTrackOffset?: number
|
|
65
70
|
}
|
|
66
71
|
|
|
72
|
+
export interface RouteBusAlternativesProgress {
|
|
73
|
+
phase: "via-minimal-winding"
|
|
74
|
+
busId: string
|
|
75
|
+
targetLayer: string
|
|
76
|
+
winding: RouteViaMinimalWindingProgress
|
|
77
|
+
}
|
|
78
|
+
|
|
67
79
|
interface TrackCandidate {
|
|
68
80
|
value: number
|
|
69
81
|
kind: "corridor" | "gap" | "margin" | "preferred"
|
|
@@ -1865,6 +1877,24 @@ function planIsClearOfPlans(params: {
|
|
|
1865
1877
|
return true
|
|
1866
1878
|
}
|
|
1867
1879
|
|
|
1880
|
+
export function fanoutPlansAreMutuallyClear(params: {
|
|
1881
|
+
plans: readonly FanoutRoutePlan[]
|
|
1882
|
+
srj: SimpleRouteJson
|
|
1883
|
+
clearance: number
|
|
1884
|
+
allowSameNetMerges?: boolean
|
|
1885
|
+
}): boolean {
|
|
1886
|
+
const { plans, srj, clearance, allowSameNetMerges = false } = params
|
|
1887
|
+
return plans.every((plan, index) =>
|
|
1888
|
+
planIsClearOfPlans({
|
|
1889
|
+
plan,
|
|
1890
|
+
otherPlans: plans.filter((_, otherIndex) => otherIndex !== index),
|
|
1891
|
+
srj,
|
|
1892
|
+
allowSameNetMerges,
|
|
1893
|
+
clearance,
|
|
1894
|
+
}),
|
|
1895
|
+
)
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1868
1898
|
function planIsClear(params: {
|
|
1869
1899
|
plan: FanoutRoutePlan
|
|
1870
1900
|
otherPlans: FanoutRoutePlan[]
|
|
@@ -1983,11 +2013,27 @@ function routePlaneTerminatedBus(
|
|
|
1983
2013
|
allowBlindAndBuriedVias = true,
|
|
1984
2014
|
allowSameNetMerges = false,
|
|
1985
2015
|
fixedViaPointsByConnectionIndex,
|
|
2016
|
+
planeCandidateSkipCount = 0,
|
|
1986
2017
|
} = params
|
|
1987
2018
|
const sourceObstacle = bus.connections[0]?.sourceObstacle
|
|
1988
2019
|
if (!sourceObstacle || bus.termination.type !== "plane") return null
|
|
1989
2020
|
const sourceLayer = bus.connections[0]!.sourceLayer
|
|
1990
2021
|
if (targetLayer === sourceLayer) return null
|
|
2022
|
+
let remainingPlaneCandidatesToSkip = planeCandidateSkipCount
|
|
2023
|
+
const selectClearPlanePlan = (
|
|
2024
|
+
plans: FanoutRoutePlan[],
|
|
2025
|
+
isClear: (plan: FanoutRoutePlan, index: number) => boolean,
|
|
2026
|
+
): FanoutRoutePlan | undefined => {
|
|
2027
|
+
for (const [index, plan] of plans.entries()) {
|
|
2028
|
+
if (!isClear(plan, index)) continue
|
|
2029
|
+
if (remainingPlaneCandidatesToSkip > 0) {
|
|
2030
|
+
remainingPlaneCandidatesToSkip--
|
|
2031
|
+
continue
|
|
2032
|
+
}
|
|
2033
|
+
return plan
|
|
2034
|
+
}
|
|
2035
|
+
return undefined
|
|
2036
|
+
}
|
|
1991
2037
|
|
|
1992
2038
|
if (fixedViaPointsByConnectionIndex) {
|
|
1993
2039
|
const fixedPlans: FanoutRoutePlan[] = []
|
|
@@ -2044,19 +2090,21 @@ function routePlaneTerminatedBus(
|
|
|
2044
2090
|
),
|
|
2045
2091
|
basePlan,
|
|
2046
2092
|
]
|
|
2047
|
-
const clearPlan =
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2093
|
+
const clearPlan = selectClearPlanePlan(
|
|
2094
|
+
plansToTry,
|
|
2095
|
+
(candidatePlan, candidateIndex) =>
|
|
2096
|
+
planIsClear({
|
|
2097
|
+
plan: candidatePlan,
|
|
2098
|
+
otherPlans: [...acceptedPlans, ...fixedPlans],
|
|
2099
|
+
staticClearanceCache,
|
|
2100
|
+
blockingBusCounts,
|
|
2101
|
+
cacheKey: `plane-fixed:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${candidateIndex}`,
|
|
2102
|
+
srj,
|
|
2103
|
+
sharedBoundary: bus.sharedBoundary,
|
|
2104
|
+
clearance,
|
|
2105
|
+
allowBlindAndBuriedVias,
|
|
2106
|
+
allowSameNetMerges,
|
|
2107
|
+
}),
|
|
2060
2108
|
)
|
|
2061
2109
|
if (!clearPlan) return null
|
|
2062
2110
|
fixedPlans.push(clearPlan)
|
|
@@ -2120,19 +2168,21 @@ function routePlaneTerminatedBus(
|
|
|
2120
2168
|
),
|
|
2121
2169
|
viaInPadPlan,
|
|
2122
2170
|
]
|
|
2123
|
-
const clearPlan =
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2171
|
+
const clearPlan = selectClearPlanePlan(
|
|
2172
|
+
plansToTry,
|
|
2173
|
+
(candidatePlan, candidateIndex) =>
|
|
2174
|
+
planIsClear({
|
|
2175
|
+
plan: candidatePlan,
|
|
2176
|
+
otherPlans: acceptedPlans,
|
|
2177
|
+
staticClearanceCache,
|
|
2178
|
+
blockingBusCounts,
|
|
2179
|
+
cacheKey: `plane-via-in-pad:${bus.busId}:${targetLayer}:${candidateIndex}`,
|
|
2180
|
+
srj,
|
|
2181
|
+
sharedBoundary: bus.sharedBoundary,
|
|
2182
|
+
clearance,
|
|
2183
|
+
allowBlindAndBuriedVias,
|
|
2184
|
+
allowSameNetMerges,
|
|
2185
|
+
}),
|
|
2136
2186
|
)
|
|
2137
2187
|
if (clearPlan) return [clearPlan]
|
|
2138
2188
|
}
|
|
@@ -2195,7 +2245,7 @@ function routePlaneTerminatedBus(
|
|
|
2195
2245
|
)
|
|
2196
2246
|
const perpendicularPitch = getPerpendicularPitch(directionalBus)
|
|
2197
2247
|
const sourceEscapePaths: Point2D[][] = []
|
|
2198
|
-
const maximumCandidatePaths =
|
|
2248
|
+
const maximumCandidatePaths = 128
|
|
2199
2249
|
for (
|
|
2200
2250
|
let totalSteps = 0;
|
|
2201
2251
|
totalSteps <= maximumEscapeSteps &&
|
|
@@ -2337,19 +2387,21 @@ function routePlaneTerminatedBus(
|
|
|
2337
2387
|
),
|
|
2338
2388
|
basePlan,
|
|
2339
2389
|
]
|
|
2340
|
-
plan =
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2390
|
+
plan = selectClearPlanePlan(
|
|
2391
|
+
plansToTry,
|
|
2392
|
+
(candidatePlan, candidateIndex) =>
|
|
2393
|
+
planIsClear({
|
|
2394
|
+
plan: candidatePlan,
|
|
2395
|
+
otherPlans: [...acceptedPlans, ...candidatePlans],
|
|
2396
|
+
staticClearanceCache,
|
|
2397
|
+
blockingBusCounts,
|
|
2398
|
+
cacheKey: `plane:${bus.busId}:${targetLayer}:${direction}:${preparedConnection.connectionIndex}:${viaHandedness}:${pathIndex}:${candidateIndex}`,
|
|
2399
|
+
srj,
|
|
2400
|
+
sharedBoundary: bus.sharedBoundary,
|
|
2401
|
+
clearance,
|
|
2402
|
+
allowBlindAndBuriedVias,
|
|
2403
|
+
allowSameNetMerges,
|
|
2404
|
+
}),
|
|
2353
2405
|
)
|
|
2354
2406
|
if (plan) break
|
|
2355
2407
|
}
|
|
@@ -2367,10 +2419,11 @@ function routePlaneTerminatedBus(
|
|
|
2367
2419
|
return null
|
|
2368
2420
|
}
|
|
2369
2421
|
|
|
2370
|
-
export function
|
|
2422
|
+
export function* routeBusAlternativesSteps(
|
|
2371
2423
|
params: RouteBusParams,
|
|
2372
2424
|
maxAlternatives = 1,
|
|
2373
|
-
|
|
2425
|
+
includeVisualization = false,
|
|
2426
|
+
): Generator<RouteBusAlternativesProgress, FanoutRoutePlan[][], void> {
|
|
2374
2427
|
const {
|
|
2375
2428
|
srj,
|
|
2376
2429
|
bus,
|
|
@@ -2391,6 +2444,7 @@ export function routeBusAlternatives(
|
|
|
2391
2444
|
fixedViaPointsByConnectionIndex,
|
|
2392
2445
|
reservedVias = [],
|
|
2393
2446
|
viaMinimalOnly = false,
|
|
2447
|
+
fixedViaFallbackRouteOrderAttempts = 24,
|
|
2394
2448
|
cornerBandTargetTrackOffset,
|
|
2395
2449
|
} = params
|
|
2396
2450
|
if (!Number.isInteger(maxAlternatives) || maxAlternatives < 1) {
|
|
@@ -2408,8 +2462,20 @@ export function routeBusAlternatives(
|
|
|
2408
2462
|
return []
|
|
2409
2463
|
}
|
|
2410
2464
|
if (bus.termination.type === "plane") {
|
|
2411
|
-
const
|
|
2412
|
-
|
|
2465
|
+
const alternatives: FanoutRoutePlan[][] = []
|
|
2466
|
+
for (
|
|
2467
|
+
let planeCandidateSkipCount = 0;
|
|
2468
|
+
planeCandidateSkipCount < maxAlternatives;
|
|
2469
|
+
planeCandidateSkipCount++
|
|
2470
|
+
) {
|
|
2471
|
+
const plan = routePlaneTerminatedBus({
|
|
2472
|
+
...params,
|
|
2473
|
+
planeCandidateSkipCount,
|
|
2474
|
+
})
|
|
2475
|
+
if (!plan) break
|
|
2476
|
+
alternatives.push(plan)
|
|
2477
|
+
}
|
|
2478
|
+
return alternatives
|
|
2413
2479
|
}
|
|
2414
2480
|
const exitAxis = getExitAxis(bus)
|
|
2415
2481
|
const sourceObstacle = bus.connections[0]?.sourceObstacle
|
|
@@ -2685,7 +2751,7 @@ export function routeBusAlternatives(
|
|
|
2685
2751
|
)!,
|
|
2686
2752
|
// Preserve the existing bounded search after every inexpensive
|
|
2687
2753
|
// layer-interleave candidate has had one deterministic attempt.
|
|
2688
|
-
maximumRouteOrderAttempts:
|
|
2754
|
+
maximumRouteOrderAttempts: fixedViaFallbackRouteOrderAttempts,
|
|
2689
2755
|
windingOrderIndex: 0,
|
|
2690
2756
|
preferTargetDirectedLaneBias: true,
|
|
2691
2757
|
},
|
|
@@ -2765,7 +2831,7 @@ export function routeBusAlternatives(
|
|
|
2765
2831
|
.join("|")}:${terminalPattern.maximumRouteOrderAttempts ?? "all"}`
|
|
2766
2832
|
if (seenTerminalSignatures.has(terminalSignature)) continue
|
|
2767
2833
|
seenTerminalSignatures.add(terminalSignature)
|
|
2768
|
-
const
|
|
2834
|
+
const windingSteps = routeViaMinimalWindingAlternativesSteps(
|
|
2769
2835
|
{
|
|
2770
2836
|
srj,
|
|
2771
2837
|
bus,
|
|
@@ -2796,7 +2862,19 @@ export function routeBusAlternatives(
|
|
|
2796
2862
|
: terminalPattern.maximumRouteOrderAttempts === undefined
|
|
2797
2863
|
? Math.min(2, maxAlternatives - alternatives.length)
|
|
2798
2864
|
: 2,
|
|
2865
|
+
includeVisualization,
|
|
2799
2866
|
)
|
|
2867
|
+
let windingResult = windingSteps.next()
|
|
2868
|
+
while (!windingResult.done) {
|
|
2869
|
+
yield {
|
|
2870
|
+
phase: "via-minimal-winding",
|
|
2871
|
+
busId: bus.busId,
|
|
2872
|
+
targetLayer,
|
|
2873
|
+
winding: windingResult.value,
|
|
2874
|
+
}
|
|
2875
|
+
windingResult = windingSteps.next()
|
|
2876
|
+
}
|
|
2877
|
+
const viaMinimalAlternatives = windingResult.value
|
|
2800
2878
|
for (const viaMinimalPlans of viaMinimalAlternatives) {
|
|
2801
2879
|
const combinedPlansAreClear = fanoutPlansAreClear({
|
|
2802
2880
|
plans: [...acceptedPlans, ...viaMinimalPlans],
|
|
@@ -2949,6 +3027,16 @@ export function routeBusAlternatives(
|
|
|
2949
3027
|
return alternatives
|
|
2950
3028
|
}
|
|
2951
3029
|
|
|
3030
|
+
export function routeBusAlternatives(
|
|
3031
|
+
params: RouteBusParams,
|
|
3032
|
+
maxAlternatives = 1,
|
|
3033
|
+
): FanoutRoutePlan[][] {
|
|
3034
|
+
const steps = routeBusAlternativesSteps(params, maxAlternatives)
|
|
3035
|
+
let result = steps.next()
|
|
3036
|
+
while (!result.done) result = steps.next()
|
|
3037
|
+
return result.value
|
|
3038
|
+
}
|
|
3039
|
+
|
|
2952
3040
|
export function routeBus(params: RouteBusParams): FanoutRoutePlan[] | null {
|
|
2953
3041
|
return routeBusAlternatives(params, 1)[0] ?? null
|
|
2954
3042
|
}
|