@tscircuit/fanout-solver 0.0.54 → 0.0.56
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/README.md +13 -0
- package/lib/fanout-solver.ts +267 -10
- package/lib/get-dogbone-side-variants.ts +26 -0
- package/lib/refine-adaptive-plane-reservations.ts +94 -0
- package/lib/route-bus.ts +375 -134
- package/lib/route-via-minimal-winding.ts +28 -0
- package/lib/runtime-process.ts +10 -0
- package/lib/select-compatible-candidates.ts +253 -0
- package/lib/should-use-adaptive-dense-plane-routing.ts +95 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -306,6 +306,19 @@ To capture the inputs without solving, use `bun run generate:dataset31`.
|
|
|
306
306
|
The optional `--dataset dataset31` flag is accepted for explicit CI invocation;
|
|
307
307
|
other dataset selections are rejected.
|
|
308
308
|
|
|
309
|
+
To isolate a failure without changing pads or clearance rules, reduce a captured
|
|
310
|
+
input by bus or connection count. This is a diagnostic, not a benchmark score:
|
|
311
|
+
|
|
312
|
+
```sh
|
|
313
|
+
bun scripts/debug-dataset31.ts --input benchmark-results/inputs/10-left-bottom-offset.json --buses DDR_ADDR_CTRL --connection-limit 7 --output /tmp/address-seven
|
|
314
|
+
bun scripts/debug-dataset31.ts --input benchmark-results/inputs/10-left-bottom-offset.json --buses DDR_ADDR_CTRL --output /tmp/address-eight
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Each run has a hard process deadline and saves JSON progress and an SVG preview.
|
|
318
|
+
Use `--dump-input <path> --capture-only` to save the exact reduced solver input
|
|
319
|
+
as a regression fixture. Retained buses keep their original targets and timing
|
|
320
|
+
limits; only removed connections and their differential-pair metadata are pruned.
|
|
321
|
+
|
|
309
322
|
Each sample runs in an isolated process, with up to four concurrent processes
|
|
310
323
|
locally and a **120-second hard timeout** by default. A synchronous solver hang,
|
|
311
324
|
exception, or unsolved case does not prevent later samples from running.
|
package/lib/fanout-solver.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { SimpleRouteJson } from "@tscircuit/capacity-autorouter"
|
|
2
2
|
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
3
|
-
import {
|
|
3
|
+
import { selectCompatibleCandidates } from "./select-compatible-candidates"
|
|
4
|
+
import { refineAdaptivePlaneReservationCore } from "./refine-adaptive-plane-reservations"
|
|
5
|
+
import { shouldUseAdaptiveDensePlaneRouting } from "./should-use-adaptive-dense-plane-routing"
|
|
4
6
|
import { type GraphicsObject, mergeGraphics } from "graphics-debug"
|
|
5
7
|
import { addViaLayerMetadataToSrj } from "./add-via-layer-metadata"
|
|
6
8
|
import { getCornerBandSide, getExitEdgeForDirection } from "./boundary-exit"
|
|
@@ -34,6 +36,8 @@ import {
|
|
|
34
36
|
} from "./route-bus"
|
|
35
37
|
import { routeSingleLayerWithAdaptiveExitsSteps } from "./route-single-layer-adaptive-exits"
|
|
36
38
|
import { routeSingleLayerWithPushAndShove } from "./route-single-layer-push-shove"
|
|
39
|
+
import { getRuntimeProcess } from "./runtime-process"
|
|
40
|
+
import { shortenBusPlans } from "./shorten-bus-plans"
|
|
37
41
|
import type {
|
|
38
42
|
AssignmentAttempt,
|
|
39
43
|
Bounds,
|
|
@@ -49,6 +53,11 @@ import type {
|
|
|
49
53
|
import { validateFanoutSolution } from "./validate-fanout-solution"
|
|
50
54
|
import { visualizeSimpleRouteJson } from "./visualize-simple-route-json"
|
|
51
55
|
|
|
56
|
+
// Browser Web Workers do not expose Node's `process` global. Keep a local,
|
|
57
|
+
// browser-safe adapter so the optional debug environment flags still work in
|
|
58
|
+
// Node and Bun without crashing browser-based routing.
|
|
59
|
+
const process = getRuntimeProcess(globalThis)
|
|
60
|
+
|
|
52
61
|
interface ResolvedFanoutConfig {
|
|
53
62
|
traceWidth: number
|
|
54
63
|
viaDiameter: number
|
|
@@ -1035,7 +1044,11 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1035
1044
|
escapeLayersByBusId: this.escapeLayersByBusId,
|
|
1036
1045
|
preferOrderedCoordinatedWindingLayers:
|
|
1037
1046
|
this.config.densePlaneReservationBusIds.length > 0 ||
|
|
1038
|
-
this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0
|
|
1047
|
+
this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0 ||
|
|
1048
|
+
shouldUseAdaptiveDensePlaneRouting(
|
|
1049
|
+
this.preparedBuses,
|
|
1050
|
+
this.config.allowBlindAndBuriedVias,
|
|
1051
|
+
),
|
|
1039
1052
|
}),
|
|
1040
1053
|
generatedAssignments,
|
|
1041
1054
|
maxAssignments: this.config.maxLayerCombinations,
|
|
@@ -1383,6 +1396,8 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1383
1396
|
busesInRoutingOrder: readonly PreparedBus[]
|
|
1384
1397
|
denseRoutingStrategy?: "pad-aligned" | "boundary-aligned"
|
|
1385
1398
|
lengthMatchingStage?: "before-planes" | "after-planes"
|
|
1399
|
+
promotedPlaneReservationBusIds?: readonly string[]
|
|
1400
|
+
planeReservationRetryCount?: number
|
|
1386
1401
|
}): Generator<FanoutWorkYield, MixedTerminationState | null, unknown> {
|
|
1387
1402
|
if (this.config.allowBlindAndBuriedVias) return null
|
|
1388
1403
|
const usePadAlignedDenseRouting =
|
|
@@ -1398,6 +1413,8 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1398
1413
|
"plane-match:incremental-failed",
|
|
1399
1414
|
"plane-route:alternate-candidate-counts",
|
|
1400
1415
|
"plane-route:alternate-search",
|
|
1416
|
+
"plane-route:csp",
|
|
1417
|
+
"plane-reservation-core",
|
|
1401
1418
|
"plane-route:promote-failed",
|
|
1402
1419
|
"plane-route:alternate-choice",
|
|
1403
1420
|
"plane-route:promote-zero-candidates",
|
|
@@ -1416,9 +1433,20 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1416
1433
|
const unsortedBoundaryBuses = params.busesInRoutingOrder.filter(
|
|
1417
1434
|
(bus) => bus.termination.type === "boundary",
|
|
1418
1435
|
)
|
|
1419
|
-
const
|
|
1436
|
+
const configuredDensePlaneRouting =
|
|
1420
1437
|
this.config.densePlaneReservationBusIds.length > 0 ||
|
|
1421
1438
|
this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0
|
|
1439
|
+
const useAdaptiveDensePlaneRouting =
|
|
1440
|
+
!configuredDensePlaneRouting &&
|
|
1441
|
+
shouldUseAdaptiveDensePlaneRouting(
|
|
1442
|
+
this.preparedBuses,
|
|
1443
|
+
this.config.allowBlindAndBuriedVias,
|
|
1444
|
+
)
|
|
1445
|
+
const useConfiguredDensePlaneRouting =
|
|
1446
|
+
configuredDensePlaneRouting || useAdaptiveDensePlaneRouting
|
|
1447
|
+
const useAdaptiveJointPlaneSelection =
|
|
1448
|
+
useAdaptiveDensePlaneRouting &&
|
|
1449
|
+
(params.planeReservationRetryCount ?? 0) === 0
|
|
1422
1450
|
const matchLengthsAfterPlanes =
|
|
1423
1451
|
useConfiguredDensePlaneRouting &&
|
|
1424
1452
|
params.lengthMatchingStage !== "before-planes"
|
|
@@ -1657,6 +1685,17 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1657
1685
|
: 8,
|
|
1658
1686
|
)
|
|
1659
1687
|
: planeBuses
|
|
1688
|
+
if (params.promotedPlaneReservationBusIds) {
|
|
1689
|
+
activeBoundaryReservationPlaneBuses = [
|
|
1690
|
+
...new Set([
|
|
1691
|
+
...activeBoundaryReservationPlaneBuses,
|
|
1692
|
+
...planeBuses.filter((bus) =>
|
|
1693
|
+
params.promotedPlaneReservationBusIds!.includes(bus.busId),
|
|
1694
|
+
),
|
|
1695
|
+
]),
|
|
1696
|
+
]
|
|
1697
|
+
}
|
|
1698
|
+
const unroutablePlaneBusIds = new Set<string>()
|
|
1660
1699
|
debugDense(
|
|
1661
1700
|
"start",
|
|
1662
1701
|
boundaryBuses.map((bus) => `${bus.busId}:${bus.connections.length}`),
|
|
@@ -1713,7 +1752,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1713
1752
|
getCornerBandSide(bus.exitEdge, bus.preferredExit)
|
|
1714
1753
|
? getDenseSingletonBoundaryGeometry(bus).targetProjection > 0
|
|
1715
1754
|
: getExitEdgeForDirection(bus.direction) !== bus.exitEdge
|
|
1716
|
-
:
|
|
1755
|
+
: hasThreeWideBoundaryBuses && bus.connections.length >= 8
|
|
1717
1756
|
? false
|
|
1718
1757
|
: getExitEdgeForDirection(bus.direction) !== bus.exitEdge,
|
|
1719
1758
|
]),
|
|
@@ -2108,6 +2147,16 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2108
2147
|
return [
|
|
2109
2148
|
{
|
|
2110
2149
|
connectionName: connection.connection.name,
|
|
2150
|
+
sourceEscapeSegment: matchedPlans.some(
|
|
2151
|
+
(plan) => plan.connectionIndex === connection.connectionIndex,
|
|
2152
|
+
)
|
|
2153
|
+
? undefined
|
|
2154
|
+
: {
|
|
2155
|
+
start: connection.sourcePoint,
|
|
2156
|
+
end: center,
|
|
2157
|
+
layer: connection.sourceLayer,
|
|
2158
|
+
width: this.config.traceWidth,
|
|
2159
|
+
},
|
|
2111
2160
|
via: {
|
|
2112
2161
|
center,
|
|
2113
2162
|
diameter: this.config.viaDiameter,
|
|
@@ -2169,7 +2218,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2169
2218
|
fixedViaPointsByConnectionIndex,
|
|
2170
2219
|
reservedVias: getReservedVias(bus),
|
|
2171
2220
|
viaMinimalOnly: process.env.FANOUT_DEBUG_ALLOW_EXTRA_VIAS !== "1",
|
|
2172
|
-
allowBoundarySideViaFallback:
|
|
2221
|
+
allowBoundarySideViaFallback: bus.connections.length === 1,
|
|
2173
2222
|
preferCornerBoundaryVia: useConfiguredDensePlaneRouting,
|
|
2174
2223
|
adaptiveWindingRouteOrder,
|
|
2175
2224
|
alignWindingGridToPads:
|
|
@@ -2310,7 +2359,21 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2310
2359
|
))[0]
|
|
2311
2360
|
if (busPlans) break
|
|
2312
2361
|
}
|
|
2313
|
-
if (!busPlans)
|
|
2362
|
+
if (!busPlans) {
|
|
2363
|
+
fixedViaPointsByConnectionIndex = originalPoints
|
|
2364
|
+
if (bus.connections.length === 2) {
|
|
2365
|
+
busPlans = (yield* routeAlternatives(
|
|
2366
|
+
{
|
|
2367
|
+
...routeParams,
|
|
2368
|
+
fixedViaPointsByConnectionIndex: originalPoints,
|
|
2369
|
+
reservedVias: getReservedVias(bus),
|
|
2370
|
+
allowBoundarySideViaFallback: true,
|
|
2371
|
+
fixedViaFallbackRouteOrderAttempts: 1,
|
|
2372
|
+
},
|
|
2373
|
+
1,
|
|
2374
|
+
))[0]
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2314
2377
|
}
|
|
2315
2378
|
if (busPlans && bus.maxLengthSkew !== undefined) {
|
|
2316
2379
|
const lengths = busPlans.map((plan) => plan.length)
|
|
@@ -3068,6 +3131,79 @@ export class FanoutSolver extends BaseSolver {
|
|
|
3068
3131
|
})),
|
|
3069
3132
|
}),
|
|
3070
3133
|
)
|
|
3134
|
+
if (useAdaptiveJointPlaneSelection) {
|
|
3135
|
+
const acceptedPlans = [
|
|
3136
|
+
...candidatePlans,
|
|
3137
|
+
...feasibleAlternatePlanePlans,
|
|
3138
|
+
]
|
|
3139
|
+
for (const planeBus of planeBuses) {
|
|
3140
|
+
if (
|
|
3141
|
+
matchedPlaneBuses.includes(planeBus) ||
|
|
3142
|
+
candidateSets.some((set) => set.planeBus === planeBus)
|
|
3143
|
+
)
|
|
3144
|
+
continue
|
|
3145
|
+
const candidates: IndependentPlaneRouteCandidate[] = []
|
|
3146
|
+
const sites = getComponentDogboneViaSiteCandidates(
|
|
3147
|
+
[planeBus],
|
|
3148
|
+
{
|
|
3149
|
+
viaDiameter: this.config.viaDiameter,
|
|
3150
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
3151
|
+
traceWidth: this.config.traceWidth,
|
|
3152
|
+
clearance: this.config.clearance,
|
|
3153
|
+
blockingSegments: acceptedPlans.flatMap((plan) =>
|
|
3154
|
+
plan.segments.map((segment) => ({
|
|
3155
|
+
connectionIndex: plan.connectionIndex,
|
|
3156
|
+
segment,
|
|
3157
|
+
})),
|
|
3158
|
+
),
|
|
3159
|
+
blockingVias: acceptedPlans.flatMap((plan) =>
|
|
3160
|
+
[
|
|
3161
|
+
plan.via,
|
|
3162
|
+
...(plan.additionalVias ?? []),
|
|
3163
|
+
plan.planeEndpointVia,
|
|
3164
|
+
]
|
|
3165
|
+
.filter((via) => via !== undefined)
|
|
3166
|
+
.map((via) => ({
|
|
3167
|
+
connectionIndex: plan.connectionIndex,
|
|
3168
|
+
center: via!.center,
|
|
3169
|
+
diameter: via!.diameter,
|
|
3170
|
+
spanLayers: via!.spanLayers,
|
|
3171
|
+
})),
|
|
3172
|
+
),
|
|
3173
|
+
additionalObstacles: this.routingSrj.obstacles,
|
|
3174
|
+
preferPlaneCheckerboardSites: true,
|
|
3175
|
+
canShareCopper,
|
|
3176
|
+
},
|
|
3177
|
+
)
|
|
3178
|
+
for (const site of sites) {
|
|
3179
|
+
const plans = routeBus({
|
|
3180
|
+
srj: this.routingSrj,
|
|
3181
|
+
bus: planeBus,
|
|
3182
|
+
targetLayer:
|
|
3183
|
+
params.busLayerAssignments[planeBus.busId]!,
|
|
3184
|
+
acceptedPlans,
|
|
3185
|
+
layerNames: this.config.layerNames,
|
|
3186
|
+
traceWidth: this.config.traceWidth,
|
|
3187
|
+
viaDiameter: this.config.viaDiameter,
|
|
3188
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
3189
|
+
clearance: this.config.clearance,
|
|
3190
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
3191
|
+
allowBlindAndBuriedVias: false,
|
|
3192
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
3193
|
+
fixedViaPointsByConnectionIndex: new Map([
|
|
3194
|
+
[site.connectionIndex, site.point],
|
|
3195
|
+
]),
|
|
3196
|
+
})
|
|
3197
|
+
if (plans)
|
|
3198
|
+
candidates.push({
|
|
3199
|
+
key: `${planeBus.busId}:local:${candidates.length}`,
|
|
3200
|
+
planeBus,
|
|
3201
|
+
plans,
|
|
3202
|
+
})
|
|
3203
|
+
}
|
|
3204
|
+
candidateSets.push({ planeBus, candidates })
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3071
3207
|
debugDense(
|
|
3072
3208
|
"plane-route:alternate-candidate-counts",
|
|
3073
3209
|
candidateSets.map((candidateSet) => [
|
|
@@ -3075,6 +3211,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
3075
3211
|
candidateSet.candidates.length,
|
|
3076
3212
|
]),
|
|
3077
3213
|
)
|
|
3214
|
+
for (const candidateSet of candidateSets) {
|
|
3215
|
+
if (candidateSet.candidates.length === 0)
|
|
3216
|
+
unroutablePlaneBusIds.add(candidateSet.planeBus.busId)
|
|
3217
|
+
}
|
|
3078
3218
|
const compatibilityByCandidatePair = new Map<string, boolean>()
|
|
3079
3219
|
const candidatesAreCompatible = (
|
|
3080
3220
|
first: IndependentPlaneRouteCandidate,
|
|
@@ -3201,10 +3341,82 @@ export class FanoutSolver extends BaseSolver {
|
|
|
3201
3341
|
)
|
|
3202
3342
|
return null
|
|
3203
3343
|
}
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3344
|
+
let selectedCandidates: IndependentPlaneRouteCandidate[] | null
|
|
3345
|
+
if (useAdaptiveDensePlaneRouting) {
|
|
3346
|
+
const getUniqueDomains = (sets: typeof candidateSets) =>
|
|
3347
|
+
sets.map((set) => [
|
|
3348
|
+
...new Map(
|
|
3349
|
+
set.candidates.map((candidate) => [
|
|
3350
|
+
JSON.stringify(
|
|
3351
|
+
candidate.plans.map((plan) => [
|
|
3352
|
+
plan.segments,
|
|
3353
|
+
plan.via,
|
|
3354
|
+
plan.additionalVias,
|
|
3355
|
+
plan.planeEndpointSegments,
|
|
3356
|
+
plan.planeEndpointVia,
|
|
3357
|
+
]),
|
|
3358
|
+
),
|
|
3359
|
+
candidate,
|
|
3360
|
+
]),
|
|
3361
|
+
).values(),
|
|
3362
|
+
])
|
|
3363
|
+
const runCandidateSelection = (
|
|
3364
|
+
sets: typeof candidateSets,
|
|
3365
|
+
maximumSearchStates = maximumAlternatePlaneSearchStates,
|
|
3366
|
+
) =>
|
|
3367
|
+
selectCompatibleCandidates({
|
|
3368
|
+
candidateSets: getUniqueDomains(sets),
|
|
3369
|
+
maximumSearchStates,
|
|
3370
|
+
areCompatible: (first, second) =>
|
|
3371
|
+
fanoutPlansAreMutuallyClear({
|
|
3372
|
+
plans: [...first.plans, ...second.plans],
|
|
3373
|
+
srj: this.routingSrj,
|
|
3374
|
+
clearance: this.config.clearance,
|
|
3375
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
3376
|
+
}),
|
|
3377
|
+
})
|
|
3378
|
+
const result = runCandidateSelection(candidateSets)
|
|
3379
|
+
debugDense(
|
|
3380
|
+
"plane-route:csp",
|
|
3381
|
+
result.selection ? "complete" : "failed",
|
|
3382
|
+
result.searchStates,
|
|
3383
|
+
result.emptyDomainIndices.map(
|
|
3384
|
+
(index) => candidateSets[index]!.planeBus.busId,
|
|
3385
|
+
),
|
|
3386
|
+
)
|
|
3387
|
+
selectedCandidates = result.selection
|
|
3388
|
+
alternatePlaneSearchStates = result.searchStates
|
|
3389
|
+
if (!result.selection && useAdaptiveDensePlaneRouting) {
|
|
3390
|
+
const expanded = result.emptyDomainIndices
|
|
3391
|
+
.map((index) => candidateSets[index]!.planeBus.busId)
|
|
3392
|
+
.filter(
|
|
3393
|
+
(id) =>
|
|
3394
|
+
!orderedAlternatePlaneBuses.some(
|
|
3395
|
+
(bus) => bus.busId === id,
|
|
3396
|
+
),
|
|
3397
|
+
)
|
|
3398
|
+
if (expanded.length > 0) {
|
|
3399
|
+
debugDense("plane-route:expand-domains", expanded)
|
|
3400
|
+
return matchViaPointsAroundPlans(
|
|
3401
|
+
candidatePlans,
|
|
3402
|
+
new Set([...promotedAlternatePlaneBusIds, ...expanded]),
|
|
3403
|
+
)
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
if (!result.selection)
|
|
3407
|
+
for (const index of (params.planeReservationRetryCount ??
|
|
3408
|
+
0) === 0
|
|
3409
|
+
? result.conflictDomainIndices
|
|
3410
|
+
: result.emptyDomainIndices.slice(0, 1))
|
|
3411
|
+
unroutablePlaneBusIds.add(
|
|
3412
|
+
candidateSets[index]!.planeBus.busId,
|
|
3413
|
+
)
|
|
3414
|
+
} else {
|
|
3415
|
+
selectedCandidates = selectCompatiblePlaneRoutes(
|
|
3416
|
+
candidateSets,
|
|
3417
|
+
[],
|
|
3418
|
+
)
|
|
3419
|
+
}
|
|
3208
3420
|
alternatePlanePlans = selectedCandidates
|
|
3209
3421
|
? [
|
|
3210
3422
|
...feasibleAlternatePlanePlans,
|
|
@@ -3230,6 +3442,18 @@ export class FanoutSolver extends BaseSolver {
|
|
|
3230
3442
|
)
|
|
3231
3443
|
if (!alternatePlanePlans) return null
|
|
3232
3444
|
feasibleAlternatePlanePlans = alternatePlanePlans
|
|
3445
|
+
if (useAdaptiveJointPlaneSelection) {
|
|
3446
|
+
matchedPlaneBusesInRoutingOrder = []
|
|
3447
|
+
return new Map([
|
|
3448
|
+
...fixedBoundaryViaPoints,
|
|
3449
|
+
...alternatePlanePlans
|
|
3450
|
+
.filter((plan) => plan.via)
|
|
3451
|
+
.map(
|
|
3452
|
+
(plan) =>
|
|
3453
|
+
[plan.connectionIndex, plan.via!.center] as const,
|
|
3454
|
+
),
|
|
3455
|
+
])
|
|
3456
|
+
}
|
|
3233
3457
|
}
|
|
3234
3458
|
let planeBusIdsRoutedWithoutDogbones = new Set<string>()
|
|
3235
3459
|
let allBlockingSegments = [...blockingSegments]
|
|
@@ -3679,6 +3903,39 @@ export class FanoutSolver extends BaseSolver {
|
|
|
3679
3903
|
}
|
|
3680
3904
|
}
|
|
3681
3905
|
|
|
3906
|
+
if (
|
|
3907
|
+
useAdaptiveDensePlaneRouting &&
|
|
3908
|
+
unroutablePlaneBusIds.size > 0 &&
|
|
3909
|
+
(params.planeReservationRetryCount ?? 0) < 5
|
|
3910
|
+
) {
|
|
3911
|
+
const newlyPromoted = [...unroutablePlaneBusIds].filter(
|
|
3912
|
+
(id) =>
|
|
3913
|
+
!activeBoundaryReservationPlaneBuses.some((bus) => bus.busId === id),
|
|
3914
|
+
)
|
|
3915
|
+
const refinedPromotions =
|
|
3916
|
+
(params.planeReservationRetryCount ?? 0) === 0
|
|
3917
|
+
? refineAdaptivePlaneReservationCore({
|
|
3918
|
+
candidateBusIds: newlyPromoted,
|
|
3919
|
+
activeBusIds: new Set(
|
|
3920
|
+
activeBoundaryReservationPlaneBuses.map((bus) => bus.busId),
|
|
3921
|
+
),
|
|
3922
|
+
planeBuses,
|
|
3923
|
+
})
|
|
3924
|
+
: newlyPromoted
|
|
3925
|
+
if (refinedPromotions.length > 0) {
|
|
3926
|
+
debugDense("plane-reservation-core", refinedPromotions)
|
|
3927
|
+
debugDense("promote-reservations", refinedPromotions)
|
|
3928
|
+
return yield* this.routeDenseThroughAllMixedTerminationSteps({
|
|
3929
|
+
...params,
|
|
3930
|
+
promotedPlaneReservationBusIds: [
|
|
3931
|
+
...(params.promotedPlaneReservationBusIds ?? []),
|
|
3932
|
+
...refinedPromotions,
|
|
3933
|
+
],
|
|
3934
|
+
planeReservationRetryCount:
|
|
3935
|
+
(params.planeReservationRetryCount ?? 0) + 1,
|
|
3936
|
+
})
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3682
3939
|
if (matchLengthsAfterPlanes) {
|
|
3683
3940
|
return yield* this.routeDenseThroughAllMixedTerminationSteps({
|
|
3684
3941
|
...params,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { FanoutDirection, Point2D } from "./types"
|
|
2
|
+
|
|
3
|
+
/** A bounded fallback for channels trapped by uniform outward dogbones. */
|
|
4
|
+
export function getDogboneSideVariants(
|
|
5
|
+
connections: readonly { connectionIndex: number; sourcePoint: Point2D }[],
|
|
6
|
+
direction: FanoutDirection,
|
|
7
|
+
): number[][] {
|
|
8
|
+
const axis = direction === "up" || direction === "down" ? "y" : "x"
|
|
9
|
+
const variants = connections
|
|
10
|
+
.slice(0, 32)
|
|
11
|
+
.map(({ connectionIndex }) => [connectionIndex])
|
|
12
|
+
// Two pads in the same escape row can jointly close a winding corridor.
|
|
13
|
+
// Move both to the other half-pitch row, keeping every other site unchanged.
|
|
14
|
+
// Do not enumerate the exponential Cartesian product of all via sites.
|
|
15
|
+
for (let first = 0; first < connections.length; first++) {
|
|
16
|
+
for (let second = first + 1; second < connections.length; second++) {
|
|
17
|
+
if (variants.length >= 32) return variants
|
|
18
|
+
const a = connections[first]!
|
|
19
|
+
const b = connections[second]!
|
|
20
|
+
if (Math.abs(a.sourcePoint[axis] - b.sourcePoint[axis]) <= 1e-9) {
|
|
21
|
+
variants.push([a.connectionIndex, b.connectionIndex])
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return variants
|
|
26
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { PreparedBus } from "./types"
|
|
2
|
+
|
|
3
|
+
const EPSILON = 1e-9
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A failed arc-consistency chain ends with the domain that removed the last
|
|
7
|
+
* supported candidate. When that plane pad has an adjacent peer farther into
|
|
8
|
+
* the component, reserving the peer keeps the outer pad available for a
|
|
9
|
+
* displaced route while protecting the same local dogbone corridor.
|
|
10
|
+
*/
|
|
11
|
+
export function refineAdaptivePlaneReservationCore(params: {
|
|
12
|
+
candidateBusIds: readonly string[]
|
|
13
|
+
activeBusIds: ReadonlySet<string>
|
|
14
|
+
planeBuses: readonly PreparedBus[]
|
|
15
|
+
}): string[] {
|
|
16
|
+
const core = params.candidateBusIds.filter(
|
|
17
|
+
(busId) => !params.activeBusIds.has(busId),
|
|
18
|
+
)
|
|
19
|
+
if (core.length < 2) return core
|
|
20
|
+
|
|
21
|
+
const terminalIndex = core.length - 1
|
|
22
|
+
const terminal = params.planeBuses.find(
|
|
23
|
+
(bus) => bus.busId === core[terminalIndex],
|
|
24
|
+
)
|
|
25
|
+
const source = terminal?.connections[0]?.sourcePoint
|
|
26
|
+
if (
|
|
27
|
+
!terminal ||
|
|
28
|
+
!source ||
|
|
29
|
+
terminal.connections.length !== 1 ||
|
|
30
|
+
terminal.termination.type !== "plane"
|
|
31
|
+
)
|
|
32
|
+
return core
|
|
33
|
+
const terminalLayer = terminal.termination.layer
|
|
34
|
+
|
|
35
|
+
const perpendicularAxis =
|
|
36
|
+
terminal.direction === "up" || terminal.direction === "down" ? "x" : "y"
|
|
37
|
+
const parallelAxis = perpendicularAxis === "x" ? "y" : "x"
|
|
38
|
+
const componentCoordinates =
|
|
39
|
+
perpendicularAxis === "x" ? terminal.xCoordinates : terminal.yCoordinates
|
|
40
|
+
const pitch = perpendicularAxis === "x" ? terminal.pitchX : terminal.pitchY
|
|
41
|
+
if (
|
|
42
|
+
componentCoordinates.length === 0 ||
|
|
43
|
+
!Number.isFinite(pitch) ||
|
|
44
|
+
pitch <= EPSILON
|
|
45
|
+
) {
|
|
46
|
+
return core
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const componentCenter =
|
|
50
|
+
(Math.min(...componentCoordinates) + Math.max(...componentCoordinates)) / 2
|
|
51
|
+
const inwardSign = Math.sign(componentCenter - source[perpendicularAxis])
|
|
52
|
+
if (inwardSign === 0) return core
|
|
53
|
+
|
|
54
|
+
const coreIds = new Set(core)
|
|
55
|
+
const replacement = params.planeBuses
|
|
56
|
+
.filter((bus) => {
|
|
57
|
+
const candidateSource = bus.connections[0]?.sourcePoint
|
|
58
|
+
if (
|
|
59
|
+
bus === terminal ||
|
|
60
|
+
bus.componentId !== terminal.componentId ||
|
|
61
|
+
bus.termination.type !== "plane" ||
|
|
62
|
+
bus.termination.layer !== terminalLayer ||
|
|
63
|
+
bus.direction !== terminal.direction ||
|
|
64
|
+
bus.connections.length !== 1 ||
|
|
65
|
+
!candidateSource ||
|
|
66
|
+
params.activeBusIds.has(bus.busId) ||
|
|
67
|
+
coreIds.has(bus.busId) ||
|
|
68
|
+
Math.abs(candidateSource[parallelAxis] - source[parallelAxis]) > EPSILON
|
|
69
|
+
) {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
const inwardDistance =
|
|
73
|
+
(candidateSource[perpendicularAxis] - source[perpendicularAxis]) *
|
|
74
|
+
inwardSign
|
|
75
|
+
return inwardDistance > EPSILON && inwardDistance <= pitch + EPSILON
|
|
76
|
+
})
|
|
77
|
+
.toSorted((first, second) => {
|
|
78
|
+
const firstSource = first.connections[0]!.sourcePoint
|
|
79
|
+
const secondSource = second.connections[0]!.sourcePoint
|
|
80
|
+
return (
|
|
81
|
+
Math.abs(firstSource[perpendicularAxis] - source[perpendicularAxis]) -
|
|
82
|
+
Math.abs(
|
|
83
|
+
secondSource[perpendicularAxis] - source[perpendicularAxis],
|
|
84
|
+
) ||
|
|
85
|
+
first.connections[0]!.connectionIndex -
|
|
86
|
+
second.connections[0]!.connectionIndex
|
|
87
|
+
)
|
|
88
|
+
})[0]
|
|
89
|
+
|
|
90
|
+
if (!replacement) return core
|
|
91
|
+
return core.map((busId, index) =>
|
|
92
|
+
index === terminalIndex ? replacement.busId : busId,
|
|
93
|
+
)
|
|
94
|
+
}
|