@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
package/lib/fanout-solver.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SimpleRouteJson } from "@tscircuit/capacity-autorouter"
|
|
2
2
|
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
3
|
-
import type
|
|
3
|
+
import { type GraphicsObject, mergeGraphics } from "graphics-debug"
|
|
4
4
|
import { addViaLayerMetadataToSrj } from "./add-via-layer-metadata"
|
|
5
5
|
import { getCornerBandSide, getExitEdgeForDirection } from "./boundary-exit"
|
|
6
6
|
import { buildOutputSimpleRouteJson } from "./build-output"
|
|
@@ -25,11 +25,13 @@ import {
|
|
|
25
25
|
} from "./prepare-buses"
|
|
26
26
|
import {
|
|
27
27
|
fanoutPlansAreClear,
|
|
28
|
+
fanoutPlansAreMutuallyClear,
|
|
28
29
|
type RouteBusStaticClearanceCache,
|
|
29
30
|
routeBus,
|
|
30
31
|
routeBusAlternatives,
|
|
32
|
+
routeBusAlternativesSteps,
|
|
31
33
|
} from "./route-bus"
|
|
32
|
-
import {
|
|
34
|
+
import { routeSingleLayerWithAdaptiveExitsSteps } from "./route-single-layer-adaptive-exits"
|
|
33
35
|
import { routeSingleLayerWithPushAndShove } from "./route-single-layer-push-shove"
|
|
34
36
|
import type {
|
|
35
37
|
AssignmentAttempt,
|
|
@@ -54,6 +56,8 @@ interface ResolvedFanoutConfig {
|
|
|
54
56
|
compactBusTracks: boolean
|
|
55
57
|
allowBlindAndBuriedVias: boolean
|
|
56
58
|
allowSameNetMerges: boolean
|
|
59
|
+
densePlaneReservationBusIds: readonly string[]
|
|
60
|
+
denseUnrestrictedPlaneRoutingBusIds: readonly string[]
|
|
57
61
|
singleLayerPushAndShove: boolean
|
|
58
62
|
singleLayerAdaptiveExits: boolean
|
|
59
63
|
borderDistribution: FanoutBorderDistribution
|
|
@@ -79,6 +83,94 @@ interface MixedTerminationState {
|
|
|
79
83
|
|
|
80
84
|
type RoutingStrategy = "default" | "group-by-layer" | "deep-first"
|
|
81
85
|
|
|
86
|
+
interface FanoutSubsolverRequest {
|
|
87
|
+
type: "subsolver"
|
|
88
|
+
solver: BaseSolver
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type FanoutWorkYield = undefined | FanoutSubsolverRequest
|
|
92
|
+
|
|
93
|
+
class FanoutWorkSolver<T> extends BaseSolver {
|
|
94
|
+
private output: T | undefined
|
|
95
|
+
private hasOutput = false
|
|
96
|
+
private nextInput: unknown
|
|
97
|
+
|
|
98
|
+
constructor(
|
|
99
|
+
private readonly solverName: string,
|
|
100
|
+
private readonly generator: Generator<unknown, T, unknown>,
|
|
101
|
+
private readonly getVisualization: () => GraphicsObject,
|
|
102
|
+
private readonly getStats: () => Record<string, unknown>,
|
|
103
|
+
private readonly getProgress: () => number,
|
|
104
|
+
) {
|
|
105
|
+
super()
|
|
106
|
+
this.MAX_ITERATIONS = 1_000_000
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
override getSolverName(): string {
|
|
110
|
+
return this.solverName
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
override _step(): void {
|
|
114
|
+
if (this.activeSubSolver) {
|
|
115
|
+
this.activeSubSolver.step()
|
|
116
|
+
if (this.activeSubSolver.failed) {
|
|
117
|
+
this.failedSubSolvers = [
|
|
118
|
+
...(this.failedSubSolvers ?? []),
|
|
119
|
+
this.activeSubSolver,
|
|
120
|
+
]
|
|
121
|
+
this.error = this.activeSubSolver.error
|
|
122
|
+
this.failed = true
|
|
123
|
+
this.activeSubSolver = null
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
if (this.activeSubSolver.solved) {
|
|
127
|
+
this.nextInput = this.activeSubSolver.getOutput()
|
|
128
|
+
this.activeSubSolver = null
|
|
129
|
+
}
|
|
130
|
+
this.stats = this.getStats()
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const result = this.generator.next(this.nextInput)
|
|
135
|
+
this.nextInput = undefined
|
|
136
|
+
this.stats = this.getStats()
|
|
137
|
+
if (result.done) {
|
|
138
|
+
this.output = result.value
|
|
139
|
+
this.hasOutput = true
|
|
140
|
+
this.solved = true
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
const yielded = result.value as Partial<FanoutSubsolverRequest> | undefined
|
|
144
|
+
if (yielded?.type === "subsolver" && yielded.solver instanceof BaseSolver) {
|
|
145
|
+
this.activeSubSolver = yielded.solver
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
computeProgress(): number {
|
|
150
|
+
return this.getProgress()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
override getConstructorParams(): [] {
|
|
154
|
+
return []
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
override getOutput(): T {
|
|
158
|
+
if (!this.solved || !this.hasOutput) {
|
|
159
|
+
throw new Error(`${this.solverName}: output requested before completion`)
|
|
160
|
+
}
|
|
161
|
+
return this.output as T
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
override visualize(): GraphicsObject {
|
|
165
|
+
return this.activeSubSolver?.visualize() ?? this.getVisualization()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
interface ActiveFanoutOperation<T> {
|
|
170
|
+
solver: FanoutWorkSolver<T>
|
|
171
|
+
onSolved: (output: T) => void
|
|
172
|
+
}
|
|
173
|
+
|
|
82
174
|
function resolvePositiveNumber(label: string, value: number): number {
|
|
83
175
|
if (!Number.isFinite(value) || value <= 0) {
|
|
84
176
|
throw new Error(
|
|
@@ -151,6 +243,9 @@ function resolveConfig(
|
|
|
151
243
|
compactBusTracks: options.compactBusTracks ?? false,
|
|
152
244
|
allowBlindAndBuriedVias: options.allowBlindAndBuriedVias ?? true,
|
|
153
245
|
allowSameNetMerges: options.allowSameNetMerges ?? false,
|
|
246
|
+
densePlaneReservationBusIds: options.densePlaneReservationBusIds ?? [],
|
|
247
|
+
denseUnrestrictedPlaneRoutingBusIds:
|
|
248
|
+
options.denseUnrestrictedPlaneRoutingBusIds ?? [],
|
|
154
249
|
singleLayerPushAndShove: options.singleLayerPushAndShove ?? false,
|
|
155
250
|
singleLayerAdaptiveExits: options.singleLayerAdaptiveExits ?? false,
|
|
156
251
|
borderDistribution,
|
|
@@ -357,8 +452,14 @@ function createInitialLayerAssignment(params: {
|
|
|
357
452
|
buses: PreparedBus[]
|
|
358
453
|
escapeLayers: string[]
|
|
359
454
|
escapeLayersByBusId: Readonly<Record<string, readonly string[]>>
|
|
455
|
+
preferOrderedCoordinatedWindingLayers: boolean
|
|
360
456
|
}): Readonly<Record<string, string>> {
|
|
361
|
-
const {
|
|
457
|
+
const {
|
|
458
|
+
buses,
|
|
459
|
+
escapeLayers,
|
|
460
|
+
escapeLayersByBusId,
|
|
461
|
+
preferOrderedCoordinatedWindingLayers,
|
|
462
|
+
} = params
|
|
362
463
|
const assignment: Record<string, string> = {}
|
|
363
464
|
const directionsByComponent = new Map<string, Set<PreparedBus["direction"]>>()
|
|
364
465
|
let nextViaLayerIndex = 0
|
|
@@ -394,6 +495,16 @@ function createInitialLayerAssignment(params: {
|
|
|
394
495
|
) {
|
|
395
496
|
assignment[bus.busId] = sourceLayer
|
|
396
497
|
} else if (viaLayers.length > 0) {
|
|
498
|
+
if (
|
|
499
|
+
preferOrderedCoordinatedWindingLayers &&
|
|
500
|
+
busUsesCoordinatedWinding(bus)
|
|
501
|
+
) {
|
|
502
|
+
// Coordinated winding treats allowedLayers as an ordered preference.
|
|
503
|
+
// A global round-robin index can otherwise skip a bus's first choice
|
|
504
|
+
// just because a previous bus had a different set of legal layers.
|
|
505
|
+
assignment[bus.busId] = viaLayers[0]!
|
|
506
|
+
continue
|
|
507
|
+
}
|
|
397
508
|
const componentDirections = directionsByComponent.get(bus.componentId)!
|
|
398
509
|
const hasOpposingDirection =
|
|
399
510
|
(componentDirections.has("left") && componentDirections.has("right")) ||
|
|
@@ -756,12 +867,12 @@ function getCandidateEscapeLayersForBus(params: {
|
|
|
756
867
|
export class FanoutSolver extends BaseSolver {
|
|
757
868
|
readonly preparedBuses: PreparedBus[]
|
|
758
869
|
readonly attempts: FanoutAttemptSummary[] = []
|
|
759
|
-
readonly layerAssignments: Array<Readonly<Record<string, string>>>
|
|
870
|
+
readonly layerAssignments: Array<Readonly<Record<string, string>>> = []
|
|
760
871
|
readonly config: ResolvedFanoutConfig
|
|
761
872
|
private readonly routingSrj: SimpleRouteJson
|
|
762
|
-
private readonly escapeLayersByBusId:
|
|
763
|
-
|
|
764
|
-
|
|
873
|
+
private readonly escapeLayersByBusId: Record<string, readonly string[]> = {}
|
|
874
|
+
private readonly boundaryBuses: PreparedBus[]
|
|
875
|
+
private readonly fixedPlaneAssignments: Readonly<Record<string, string>>
|
|
765
876
|
private readonly evaluatedAssignmentKeys = new Set<string>()
|
|
766
877
|
private readonly queuedAssignmentKeys = new Set<string>()
|
|
767
878
|
private readonly assignmentRepairDepthByKey = new Map<string, number>()
|
|
@@ -779,8 +890,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
779
890
|
}
|
|
780
891
|
>()
|
|
781
892
|
private groupedBeamEvaluated = false
|
|
893
|
+
private routingInitialized = false
|
|
894
|
+
private nextCandidateLayerBusIndex = 0
|
|
782
895
|
private nextAssignmentIndex = 0
|
|
783
896
|
private nextGeneratedAssignmentIndex = 0
|
|
897
|
+
private activeOperation: ActiveFanoutOperation<unknown> | null = null
|
|
898
|
+
private inProgressPlans: FanoutRoutePlan[] = []
|
|
899
|
+
private activeRoutingVisualization: GraphicsObject | null = null
|
|
900
|
+
private activeAdaptiveVisualization: GraphicsObject | null = null
|
|
784
901
|
private bestAttempt: AssignmentAttempt | null = null
|
|
785
902
|
private lengthMatchingFailure: FanoutValidationIssue | null = null
|
|
786
903
|
private endpointCompletion: CompleteOriginalEndpointsResult | null = null
|
|
@@ -857,56 +974,333 @@ export class FanoutSolver extends BaseSolver {
|
|
|
857
974
|
)
|
|
858
975
|
}
|
|
859
976
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
977
|
+
this.boundaryBuses = this.preparedBuses.filter(
|
|
978
|
+
(bus) => bus.termination.type === "boundary",
|
|
979
|
+
)
|
|
980
|
+
this.fixedPlaneAssignments = Object.fromEntries(
|
|
864
981
|
this.preparedBuses.flatMap((bus) =>
|
|
865
982
|
bus.termination.type === "plane"
|
|
866
983
|
? [[bus.busId, bus.termination.layer] as const]
|
|
867
984
|
: [],
|
|
868
985
|
),
|
|
869
986
|
)
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
987
|
+
const workUnitsPerAssignment = this.preparedBuses.length * 3 + 8
|
|
988
|
+
const estimatedWorkUnitCount =
|
|
989
|
+
this.boundaryBuses.length +
|
|
990
|
+
1 +
|
|
991
|
+
this.config.maxLayerCombinations * workUnitsPerAssignment +
|
|
992
|
+
this.preparedBuses.length * 2 +
|
|
993
|
+
20
|
|
994
|
+
this.MAX_ITERATIONS = Math.max(10_000, estimatedWorkUnitCount)
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
override getSolverName(): string {
|
|
998
|
+
return "FanoutSolver"
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
private stepRoutingInitialization(): void {
|
|
1002
|
+
const bus = this.boundaryBuses[this.nextCandidateLayerBusIndex]
|
|
1003
|
+
if (bus) {
|
|
1004
|
+
this.escapeLayersByBusId[bus.busId] = getCandidateEscapeLayersForBus({
|
|
1005
|
+
bus,
|
|
1006
|
+
srj: this.routingSrj,
|
|
1007
|
+
config: this.config,
|
|
1008
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
1009
|
+
})
|
|
1010
|
+
this.nextCandidateLayerBusIndex++
|
|
1011
|
+
this.stats = {
|
|
1012
|
+
phase: "discover-candidate-layers",
|
|
1013
|
+
bus: bus.busId,
|
|
1014
|
+
busIndex: this.nextCandidateLayerBusIndex,
|
|
1015
|
+
busCount: this.boundaryBuses.length,
|
|
1016
|
+
}
|
|
1017
|
+
return
|
|
1018
|
+
}
|
|
1019
|
+
|
|
887
1020
|
const generatedAssignments = generateLayerAssignments({
|
|
888
|
-
busIds:
|
|
1021
|
+
busIds: this.boundaryBuses.map((candidate) => candidate.busId),
|
|
889
1022
|
layers: this.config.escapeLayers,
|
|
890
|
-
layersByBusId: escapeLayersByBusId,
|
|
1023
|
+
layersByBusId: this.escapeLayersByBusId,
|
|
891
1024
|
maxAssignments: this.config.maxLayerCombinations,
|
|
892
1025
|
}).map((assignment) => ({
|
|
893
1026
|
...assignment,
|
|
894
|
-
...fixedPlaneAssignments,
|
|
1027
|
+
...this.fixedPlaneAssignments,
|
|
895
1028
|
}))
|
|
896
|
-
this.layerAssignments
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1029
|
+
this.layerAssignments.push(
|
|
1030
|
+
...prioritizeLayerAssignment({
|
|
1031
|
+
initialAssignment: createInitialLayerAssignment({
|
|
1032
|
+
buses: this.preparedBuses,
|
|
1033
|
+
escapeLayers: this.config.escapeLayers,
|
|
1034
|
+
escapeLayersByBusId: this.escapeLayersByBusId,
|
|
1035
|
+
preferOrderedCoordinatedWindingLayers:
|
|
1036
|
+
this.config.densePlaneReservationBusIds.length > 0 ||
|
|
1037
|
+
this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0,
|
|
1038
|
+
}),
|
|
1039
|
+
generatedAssignments,
|
|
1040
|
+
maxAssignments: this.config.maxLayerCombinations,
|
|
901
1041
|
}),
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1042
|
+
)
|
|
1043
|
+
this.routingInitialized = true
|
|
1044
|
+
this.stats = {
|
|
1045
|
+
phase: "prepare-layer-assignments",
|
|
1046
|
+
assignmentCount: this.layerAssignments.length,
|
|
1047
|
+
}
|
|
906
1048
|
}
|
|
907
1049
|
|
|
908
|
-
|
|
909
|
-
|
|
1050
|
+
private *initializeRoutingSteps(): Generator<FanoutWorkYield, void, unknown> {
|
|
1051
|
+
while (!this.routingInitialized) {
|
|
1052
|
+
this.stepRoutingInitialization()
|
|
1053
|
+
if (!this.routingInitialized) yield
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
private setInProgressPlans(params: {
|
|
1058
|
+
phase: string
|
|
1059
|
+
plans: readonly FanoutRoutePlan[]
|
|
1060
|
+
strategy?: RoutingStrategy | "grouped-beam"
|
|
1061
|
+
unitIndex?: number
|
|
1062
|
+
unitCount?: number
|
|
1063
|
+
busId?: string
|
|
1064
|
+
}): void {
|
|
1065
|
+
this.inProgressPlans = [...params.plans]
|
|
1066
|
+
this.stats = {
|
|
1067
|
+
...this.stats,
|
|
1068
|
+
phase: params.phase,
|
|
1069
|
+
...(params.strategy ? { routingStrategy: params.strategy } : {}),
|
|
1070
|
+
...(params.unitIndex !== undefined ? { workUnit: params.unitIndex } : {}),
|
|
1071
|
+
...(params.unitCount !== undefined
|
|
1072
|
+
? { workUnitCount: params.unitCount }
|
|
1073
|
+
: {}),
|
|
1074
|
+
...(params.busId ? { bus: params.busId } : {}),
|
|
1075
|
+
routedConnections: `${params.plans.length}/${this.inputSrj.connections.length}`,
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
private visualizeCurrentState(): GraphicsObject {
|
|
1080
|
+
const visualizedSrj =
|
|
1081
|
+
this.endpointCompletion?.simpleRouteJson ??
|
|
1082
|
+
(!this.solved && !this.failed && this.inProgressPlans.length > 0
|
|
1083
|
+
? buildOutputSimpleRouteJson({
|
|
1084
|
+
inputSrj: this.inputSrj,
|
|
1085
|
+
plans: this.inProgressPlans,
|
|
1086
|
+
layerNames: this.config.layerNames,
|
|
1087
|
+
})
|
|
1088
|
+
: undefined) ??
|
|
1089
|
+
this.bestAttempt?.outputSrj ??
|
|
1090
|
+
this.inputSrj
|
|
1091
|
+
return visualizeSimpleRouteJson(visualizedSrj)
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
private visualizeWorkState(solverName: string): GraphicsObject {
|
|
1095
|
+
const base = this.visualizeCurrentState()
|
|
1096
|
+
const boundary =
|
|
1097
|
+
this.preparedBuses[0]?.sharedBoundary ?? this.inputSrj.bounds
|
|
1098
|
+
const activeBusId =
|
|
1099
|
+
typeof this.stats.bus === "string" ? this.stats.bus : undefined
|
|
1100
|
+
const activeBus = activeBusId
|
|
1101
|
+
? this.preparedBuses.find((bus) => bus.busId === activeBusId)
|
|
1102
|
+
: undefined
|
|
1103
|
+
const width = boundary.maxX - boundary.minX
|
|
1104
|
+
const height = boundary.maxY - boundary.minY
|
|
1105
|
+
const annotationSize = Math.max(Math.min(width, height) * 0.025, 0.25)
|
|
1106
|
+
const phase =
|
|
1107
|
+
typeof this.stats.phase === "string" ? this.stats.phase : "starting"
|
|
1108
|
+
const detail = [
|
|
1109
|
+
typeof this.stats.routeConnection === "string"
|
|
1110
|
+
? `connection ${this.stats.routeConnection}`
|
|
1111
|
+
: undefined,
|
|
1112
|
+
typeof this.stats.searchBatch === "number"
|
|
1113
|
+
? `batch ${this.stats.searchBatch}`
|
|
1114
|
+
: undefined,
|
|
1115
|
+
typeof this.stats.expandedStates === "number"
|
|
1116
|
+
? `${this.stats.expandedStates.toLocaleString()} states`
|
|
1117
|
+
: undefined,
|
|
1118
|
+
]
|
|
1119
|
+
.filter(Boolean)
|
|
1120
|
+
.join(" · ")
|
|
1121
|
+
const title = `${solverName}: ${phase}`
|
|
1122
|
+
return {
|
|
1123
|
+
...mergeGraphics(base, {
|
|
1124
|
+
rects: [
|
|
1125
|
+
{
|
|
1126
|
+
center: {
|
|
1127
|
+
x: (boundary.minX + boundary.maxX) / 2,
|
|
1128
|
+
y: (boundary.minY + boundary.maxY) / 2,
|
|
1129
|
+
},
|
|
1130
|
+
width,
|
|
1131
|
+
height,
|
|
1132
|
+
fill: "rgba(0, 0, 0, 0)",
|
|
1133
|
+
stroke: "rgba(14, 165, 233, 0.8)",
|
|
1134
|
+
label: `${solverName} working boundary`,
|
|
1135
|
+
},
|
|
1136
|
+
],
|
|
1137
|
+
circles: (activeBus?.connections ?? []).map((connection) => ({
|
|
1138
|
+
center: connection.sourcePoint,
|
|
1139
|
+
radius: Math.max(
|
|
1140
|
+
annotationSize,
|
|
1141
|
+
Math.min(
|
|
1142
|
+
connection.sourceObstacle.width,
|
|
1143
|
+
connection.sourceObstacle.height,
|
|
1144
|
+
) * 0.6,
|
|
1145
|
+
),
|
|
1146
|
+
fill: "rgba(250, 204, 21, 0.25)",
|
|
1147
|
+
stroke: "#f59e0b",
|
|
1148
|
+
label: `active bus ${activeBusId}: ${connection.connection.name}`,
|
|
1149
|
+
})),
|
|
1150
|
+
texts: [
|
|
1151
|
+
{
|
|
1152
|
+
x: boundary.minX,
|
|
1153
|
+
y: boundary.maxY + annotationSize * 2,
|
|
1154
|
+
text: `${solverName} · ${phase}${detail ? ` · ${detail}` : ""}`,
|
|
1155
|
+
color: "#0f172a",
|
|
1156
|
+
fontSize: annotationSize * 1.5,
|
|
1157
|
+
anchorSide: "bottom_left",
|
|
1158
|
+
},
|
|
1159
|
+
],
|
|
1160
|
+
}),
|
|
1161
|
+
title,
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
private visualizeBoundaryRoutingState(): GraphicsObject {
|
|
1166
|
+
if (!this.activeRoutingVisualization) {
|
|
1167
|
+
return this.visualizeWorkState("BoundaryBusRoutingSolver")
|
|
1168
|
+
}
|
|
1169
|
+
return {
|
|
1170
|
+
...mergeGraphics(
|
|
1171
|
+
this.visualizeCurrentState(),
|
|
1172
|
+
this.activeRoutingVisualization,
|
|
1173
|
+
),
|
|
1174
|
+
title: this.activeRoutingVisualization.title,
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
private visualizeAdaptiveRoutingState(): GraphicsObject {
|
|
1179
|
+
if (this.activeAdaptiveVisualization) {
|
|
1180
|
+
return this.activeAdaptiveVisualization
|
|
1181
|
+
}
|
|
1182
|
+
const boundary =
|
|
1183
|
+
this.preparedBuses[0]?.sharedBoundary ?? this.inputSrj.bounds
|
|
1184
|
+
const width = boundary.maxX - boundary.minX
|
|
1185
|
+
const height = boundary.maxY - boundary.minY
|
|
1186
|
+
const annotationSize = Math.max(Math.min(width, height) * 0.02, 0.2)
|
|
1187
|
+
return {
|
|
1188
|
+
title: "SingleLayerAdaptiveExitSolver: preparing flow grid",
|
|
1189
|
+
rects: [
|
|
1190
|
+
{
|
|
1191
|
+
center: {
|
|
1192
|
+
x: (boundary.minX + boundary.maxX) / 2,
|
|
1193
|
+
y: (boundary.minY + boundary.maxY) / 2,
|
|
1194
|
+
},
|
|
1195
|
+
width,
|
|
1196
|
+
height,
|
|
1197
|
+
fill: "rgba(0, 0, 0, 0)",
|
|
1198
|
+
stroke: "rgba(14, 165, 233, 0.9)",
|
|
1199
|
+
label: "adaptive flow grid boundary",
|
|
1200
|
+
},
|
|
1201
|
+
],
|
|
1202
|
+
points: this.preparedBuses.flatMap((bus) =>
|
|
1203
|
+
bus.connections.map((connection) => ({
|
|
1204
|
+
...connection.sourcePoint,
|
|
1205
|
+
color: "#f97316",
|
|
1206
|
+
label: "adaptive route source",
|
|
1207
|
+
})),
|
|
1208
|
+
),
|
|
1209
|
+
texts: [
|
|
1210
|
+
{
|
|
1211
|
+
x: boundary.minX,
|
|
1212
|
+
y: boundary.maxY + annotationSize * 2,
|
|
1213
|
+
text: "preparing adaptive flow grid",
|
|
1214
|
+
color: "#0f172a",
|
|
1215
|
+
fontSize: annotationSize * 1.5,
|
|
1216
|
+
anchorSide: "bottom_left",
|
|
1217
|
+
},
|
|
1218
|
+
],
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
private startOperation<T>(params: {
|
|
1223
|
+
name: string
|
|
1224
|
+
generator: Generator<unknown, T, unknown>
|
|
1225
|
+
onSolved: (output: T) => void
|
|
1226
|
+
getProgress?: () => number
|
|
1227
|
+
}): void {
|
|
1228
|
+
const solver = this.createWorkSolver(
|
|
1229
|
+
params.name,
|
|
1230
|
+
params.generator,
|
|
1231
|
+
params.getProgress,
|
|
1232
|
+
)
|
|
1233
|
+
this.activeOperation = {
|
|
1234
|
+
solver,
|
|
1235
|
+
onSolved: params.onSolved,
|
|
1236
|
+
} as ActiveFanoutOperation<unknown>
|
|
1237
|
+
this.activeSubSolver = solver
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
private createWorkSolver<T>(
|
|
1241
|
+
name: string,
|
|
1242
|
+
generator: Generator<unknown, T, unknown>,
|
|
1243
|
+
getProgress?: () => number,
|
|
1244
|
+
getVisualization?: () => GraphicsObject,
|
|
1245
|
+
): FanoutWorkSolver<T> {
|
|
1246
|
+
return new FanoutWorkSolver(
|
|
1247
|
+
name,
|
|
1248
|
+
generator,
|
|
1249
|
+
getVisualization ?? (() => this.visualizeWorkState(name)),
|
|
1250
|
+
() => ({ ...this.stats }),
|
|
1251
|
+
getProgress ?? (() => 0),
|
|
1252
|
+
)
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
private *routeBusAlternativesWorkSteps(
|
|
1256
|
+
params: Parameters<typeof routeBusAlternativesSteps>[0],
|
|
1257
|
+
maximumAlternatives: number,
|
|
1258
|
+
): Generator<FanoutWorkYield, FanoutRoutePlan[][], unknown> {
|
|
1259
|
+
const steps = routeBusAlternativesSteps(params, maximumAlternatives, true)
|
|
1260
|
+
let result = steps.next()
|
|
1261
|
+
while (!result.done) {
|
|
1262
|
+
const { winding } = result.value
|
|
1263
|
+
if (winding.visualization) {
|
|
1264
|
+
this.activeRoutingVisualization = winding.visualization
|
|
1265
|
+
}
|
|
1266
|
+
this.stats = {
|
|
1267
|
+
...this.stats,
|
|
1268
|
+
phase: "route-boundary-bus-connection",
|
|
1269
|
+
bus: result.value.busId,
|
|
1270
|
+
targetLayer: result.value.targetLayer,
|
|
1271
|
+
routeOrderAttempt: winding.routeOrderAttempt,
|
|
1272
|
+
routeConnection: `${winding.connectionIndex + 1}/${winding.connectionCount}`,
|
|
1273
|
+
connection: winding.connectionName,
|
|
1274
|
+
searchBatch: winding.searchBatch,
|
|
1275
|
+
expandedStates: winding.expandedStateCount,
|
|
1276
|
+
connectionComplete: winding.connectionComplete,
|
|
1277
|
+
}
|
|
1278
|
+
yield
|
|
1279
|
+
result = steps.next()
|
|
1280
|
+
}
|
|
1281
|
+
return result.value
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
private stepActiveOperation(): void {
|
|
1285
|
+
const operation = this.activeOperation
|
|
1286
|
+
if (!operation) return
|
|
1287
|
+
operation.solver.step()
|
|
1288
|
+
if (operation.solver.failed) {
|
|
1289
|
+
this.failedSubSolvers = [
|
|
1290
|
+
...(this.failedSubSolvers ?? []),
|
|
1291
|
+
operation.solver,
|
|
1292
|
+
]
|
|
1293
|
+
this.error = operation.solver.error
|
|
1294
|
+
this.failed = true
|
|
1295
|
+
this.activeOperation = null
|
|
1296
|
+
this.activeSubSolver = null
|
|
1297
|
+
return
|
|
1298
|
+
}
|
|
1299
|
+
if (!operation.solver.solved) return
|
|
1300
|
+
const output = operation.solver.getOutput()
|
|
1301
|
+
this.activeOperation = null
|
|
1302
|
+
this.activeSubSolver = null
|
|
1303
|
+
operation.onSolved(output)
|
|
910
1304
|
}
|
|
911
1305
|
|
|
912
1306
|
private completeBestAttemptEndpoints(): void {
|
|
@@ -983,15 +1377,43 @@ export class FanoutSolver extends BaseSolver {
|
|
|
983
1377
|
* dogbones. This is intentionally bounded independently of the number of
|
|
984
1378
|
* plane drops so dense power fields cannot explode the general beam search.
|
|
985
1379
|
*/
|
|
986
|
-
private
|
|
1380
|
+
private *routeDenseThroughAllMixedTerminationSteps(params: {
|
|
987
1381
|
busLayerAssignments: Readonly<Record<string, string>>
|
|
988
1382
|
busesInRoutingOrder: readonly PreparedBus[]
|
|
989
|
-
}): MixedTerminationState | null {
|
|
1383
|
+
}): Generator<FanoutWorkYield, MixedTerminationState | null, unknown> {
|
|
990
1384
|
if (this.config.allowBlindAndBuriedVias) return null
|
|
1385
|
+
const debugDense = (...values: unknown[]) => {
|
|
1386
|
+
if (process.env.FANOUT_DEBUG_DENSE === "1") {
|
|
1387
|
+
if (
|
|
1388
|
+
process.env.FANOUT_DEBUG_DENSE_SUMMARY === "1" &&
|
|
1389
|
+
![
|
|
1390
|
+
"start",
|
|
1391
|
+
"plane-match:preflight-failed",
|
|
1392
|
+
"plane-match:incremental-complete",
|
|
1393
|
+
"plane-match:incremental-failed",
|
|
1394
|
+
"plane-route:alternate-candidate-counts",
|
|
1395
|
+
"plane-route:alternate-search",
|
|
1396
|
+
"plane-route:promote-failed",
|
|
1397
|
+
"plane-route:alternate-choice",
|
|
1398
|
+
"plane-route:promote-zero-candidates",
|
|
1399
|
+
"length-match:complete",
|
|
1400
|
+
"length-match:start",
|
|
1401
|
+
"plane-route:failed",
|
|
1402
|
+
"dense-validation",
|
|
1403
|
+
].includes(String(values[0]))
|
|
1404
|
+
) {
|
|
1405
|
+
return
|
|
1406
|
+
}
|
|
1407
|
+
console.error("dense:", ...values)
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
991
1410
|
|
|
992
1411
|
const unsortedBoundaryBuses = params.busesInRoutingOrder.filter(
|
|
993
1412
|
(bus) => bus.termination.type === "boundary",
|
|
994
1413
|
)
|
|
1414
|
+
const useConfiguredDensePlaneRouting =
|
|
1415
|
+
this.config.densePlaneReservationBusIds.length > 0 ||
|
|
1416
|
+
this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0
|
|
995
1417
|
const useJointBoundaryViaReservation = shouldUseJointBoundaryViaReservation(
|
|
996
1418
|
unsortedBoundaryBuses.map((bus) => bus.connections.length),
|
|
997
1419
|
)
|
|
@@ -1013,84 +1435,181 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1013
1435
|
]),
|
|
1014
1436
|
)
|
|
1015
1437
|
: null
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
const secondPairRoutingPriority = pairRoutingPriorityKeyByBusId?.get(
|
|
1032
|
-
second.busId,
|
|
1033
|
-
)
|
|
1034
|
-
if (
|
|
1035
|
-
firstPairRoutingPriority !== undefined &&
|
|
1036
|
-
secondPairRoutingPriority !== undefined &&
|
|
1037
|
-
firstPairRoutingPriority !== secondPairRoutingPriority
|
|
1038
|
-
) {
|
|
1039
|
-
// The third pair can be fenced off by two earlier pair windings. Let
|
|
1040
|
-
// the pair with the shortest farthest-lane boundary reach claim its
|
|
1041
|
-
// channel first without relying on caller-specific bus identifiers.
|
|
1042
|
-
return firstPairRoutingPriority - secondPairRoutingPriority
|
|
1438
|
+
const wideBoundaryBuses = unsortedBoundaryBuses.filter(
|
|
1439
|
+
(bus) => bus.connections.length >= 8,
|
|
1440
|
+
)
|
|
1441
|
+
const hasThreeWideBoundaryBuses =
|
|
1442
|
+
useConfiguredDensePlaneRouting && wideBoundaryBuses.length === 3
|
|
1443
|
+
const getBoundaryTargetSpan = (bus: PreparedBus) => {
|
|
1444
|
+
const coordinates = bus.connections.map((connection) => {
|
|
1445
|
+
const target = connection.exitTargetPoint ?? connection.targetPoint
|
|
1446
|
+
return bus.exitEdge === "top" || bus.exitEdge === "bottom"
|
|
1447
|
+
? target.x
|
|
1448
|
+
: target.y
|
|
1449
|
+
})
|
|
1450
|
+
return {
|
|
1451
|
+
minimum: Math.min(...coordinates),
|
|
1452
|
+
maximum: Math.max(...coordinates),
|
|
1043
1453
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1454
|
+
}
|
|
1455
|
+
const narrowBusOverlapsWideTargetSpan = (bus: PreparedBus): boolean => {
|
|
1456
|
+
if (bus.connections.length >= 8) return false
|
|
1457
|
+
const span = getBoundaryTargetSpan(bus)
|
|
1458
|
+
return wideBoundaryBuses.some((wideBus) => {
|
|
1459
|
+
if (wideBus.exitEdge !== bus.exitEdge) return false
|
|
1460
|
+
const wideSpan = getBoundaryTargetSpan(wideBus)
|
|
1461
|
+
return (
|
|
1462
|
+
span.maximum >= wideSpan.minimum - 1e-9 &&
|
|
1463
|
+
span.minimum <= wideSpan.maximum + 1e-9
|
|
1464
|
+
)
|
|
1465
|
+
})
|
|
1466
|
+
}
|
|
1467
|
+
const getContainingWideSourceField = (
|
|
1468
|
+
bus: PreparedBus,
|
|
1469
|
+
): PreparedBus | undefined => {
|
|
1470
|
+
if (bus.connections.length >= 8) return undefined
|
|
1471
|
+
return wideBoundaryBuses.find((wideBus) => {
|
|
1472
|
+
const wideXCoordinates = wideBus.connections.map(
|
|
1473
|
+
(connection) => connection.sourcePoint.x,
|
|
1474
|
+
)
|
|
1475
|
+
const wideYCoordinates = wideBus.connections.map(
|
|
1476
|
+
(connection) => connection.sourcePoint.y,
|
|
1477
|
+
)
|
|
1478
|
+
const minimumX = Math.min(...wideXCoordinates)
|
|
1479
|
+
const maximumX = Math.max(...wideXCoordinates)
|
|
1480
|
+
const minimumY = Math.min(...wideYCoordinates)
|
|
1481
|
+
const maximumY = Math.max(...wideYCoordinates)
|
|
1482
|
+
return bus.connections.every(
|
|
1483
|
+
(connection) =>
|
|
1484
|
+
connection.sourcePoint.x >= minimumX - 1e-9 &&
|
|
1485
|
+
connection.sourcePoint.x <= maximumX + 1e-9 &&
|
|
1486
|
+
connection.sourcePoint.y >= minimumY - 1e-9 &&
|
|
1487
|
+
connection.sourcePoint.y <= maximumY + 1e-9,
|
|
1488
|
+
)
|
|
1489
|
+
})
|
|
1490
|
+
}
|
|
1491
|
+
const narrowBusIsEmbeddedInWideSourceField = (bus: PreparedBus): boolean =>
|
|
1492
|
+
Boolean(getContainingWideSourceField(bus))
|
|
1493
|
+
const getThreeWideRoutingPriority = (bus: PreparedBus): number =>
|
|
1494
|
+
narrowBusOverlapsWideTargetSpan(bus) &&
|
|
1495
|
+
!narrowBusIsEmbeddedInWideSourceField(bus)
|
|
1496
|
+
? 0
|
|
1497
|
+
: bus.connections.length >= 8
|
|
1498
|
+
? 1
|
|
1499
|
+
: bus.connections.length > 1
|
|
1500
|
+
? 2
|
|
1501
|
+
: 3
|
|
1502
|
+
const initiallySortedBoundaryBuses = unsortedBoundaryBuses.toSorted(
|
|
1503
|
+
(first, second) => {
|
|
1504
|
+
// Reserve the dense escape field for the widest buses first. Small
|
|
1505
|
+
// control groups can usually route around their copper, while routing
|
|
1506
|
+
// a two-line corner bus first can consume a critical channel needed by
|
|
1507
|
+
// an eight-line winding bus and force the expensive fallback search.
|
|
1508
|
+
if (useJointBoundaryViaReservation || wideBoundaryBuses.length > 0) {
|
|
1509
|
+
if (hasThreeWideBoundaryBuses) {
|
|
1510
|
+
const threeWidePriorityDifference =
|
|
1511
|
+
getThreeWideRoutingPriority(first) -
|
|
1512
|
+
getThreeWideRoutingPriority(second)
|
|
1513
|
+
if (threeWidePriorityDifference !== 0) {
|
|
1514
|
+
return threeWidePriorityDifference
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
const connectionCountDifference =
|
|
1518
|
+
unsortedBoundaryBuses.length === 2 &&
|
|
1519
|
+
Math.max(
|
|
1520
|
+
...unsortedBoundaryBuses.map((bus) => bus.connections.length),
|
|
1521
|
+
) >= 8
|
|
1522
|
+
? first.connections.length - second.connections.length
|
|
1523
|
+
: second.connections.length - first.connections.length
|
|
1524
|
+
if (connectionCountDifference !== 0) return connectionCountDifference
|
|
1525
|
+
}
|
|
1526
|
+
const firstLayer = params.busLayerAssignments[first.busId]
|
|
1527
|
+
const secondLayer = params.busLayerAssignments[second.busId]
|
|
1528
|
+
const firstPairRoutingPriority = pairRoutingPriorityKeyByBusId?.get(
|
|
1529
|
+
first.busId,
|
|
1530
|
+
)
|
|
1531
|
+
const secondPairRoutingPriority = pairRoutingPriorityKeyByBusId?.get(
|
|
1532
|
+
second.busId,
|
|
1533
|
+
)
|
|
1534
|
+
if (
|
|
1535
|
+
firstPairRoutingPriority !== undefined &&
|
|
1536
|
+
secondPairRoutingPriority !== undefined &&
|
|
1537
|
+
firstPairRoutingPriority !== secondPairRoutingPriority
|
|
1538
|
+
) {
|
|
1539
|
+
// The third pair can be fenced off by two earlier pair windings. Let
|
|
1540
|
+
// the pair with the shortest farthest-lane boundary reach claim its
|
|
1541
|
+
// channel first without relying on caller-specific bus identifiers.
|
|
1542
|
+
return firstPairRoutingPriority - secondPairRoutingPriority
|
|
1543
|
+
}
|
|
1544
|
+
const cornerBandDifference =
|
|
1545
|
+
Number(
|
|
1546
|
+
Boolean(getCornerBandSide(second.exitEdge, second.preferredExit)),
|
|
1547
|
+
) -
|
|
1548
|
+
Number(
|
|
1549
|
+
Boolean(getCornerBandSide(first.exitEdge, first.preferredExit)),
|
|
1066
1550
|
)
|
|
1551
|
+
if (cornerBandDifference !== 0) return cornerBandDifference
|
|
1552
|
+
const firstIsCorner = Boolean(
|
|
1553
|
+
getCornerBandSide(first.exitEdge, first.preferredExit),
|
|
1554
|
+
)
|
|
1555
|
+
if (!firstIsCorner) {
|
|
1556
|
+
const getSourceSpan = (bus: PreparedBus): number => {
|
|
1557
|
+
const xCoordinates = bus.connections.map(
|
|
1558
|
+
(connection) => connection.sourcePoint.x,
|
|
1559
|
+
)
|
|
1560
|
+
const yCoordinates = bus.connections.map(
|
|
1561
|
+
(connection) => connection.sourcePoint.y,
|
|
1562
|
+
)
|
|
1563
|
+
return (
|
|
1564
|
+
Math.max(...xCoordinates) -
|
|
1565
|
+
Math.min(...xCoordinates) +
|
|
1566
|
+
Math.max(...yCoordinates) -
|
|
1567
|
+
Math.min(...yCoordinates)
|
|
1568
|
+
)
|
|
1569
|
+
}
|
|
1570
|
+
const sourceSpanDifference =
|
|
1571
|
+
getSourceSpan(second) - getSourceSpan(first)
|
|
1572
|
+
if (Math.abs(sourceSpanDifference) > 1e-9) {
|
|
1573
|
+
return sourceSpanDifference
|
|
1574
|
+
}
|
|
1067
1575
|
}
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1576
|
+
const layerDifference =
|
|
1577
|
+
this.config.layerNames.indexOf(firstLayer ?? "") -
|
|
1578
|
+
this.config.layerNames.indexOf(secondLayer ?? "")
|
|
1579
|
+
if (layerDifference !== 0) return -layerDifference
|
|
1580
|
+
if (
|
|
1581
|
+
unsortedBoundaryBuses.length !== 6 &&
|
|
1582
|
+
unsortedBoundaryBuses.length !== 7 &&
|
|
1583
|
+
unsortedBoundaryBuses.length !== 8 &&
|
|
1584
|
+
unsortedBoundaryBuses.length !== 9
|
|
1585
|
+
) {
|
|
1586
|
+
return 0
|
|
1072
1587
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
)
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1588
|
+
// The general routing order can differ across the two components as a
|
|
1589
|
+
// function of local pad geometry. Keep otherwise-equivalent corner buses
|
|
1590
|
+
// in one deterministic order for the six- through nine-bus paths so their
|
|
1591
|
+
// boundary lanes do not swap between the two ends of a direct
|
|
1592
|
+
// interconnect. Leave the released four- and five-bus tie behavior
|
|
1593
|
+
// unchanged.
|
|
1594
|
+
return first.busId.localeCompare(second.busId)
|
|
1595
|
+
},
|
|
1596
|
+
)
|
|
1597
|
+
const debugBoundaryOrder =
|
|
1598
|
+
process.env.FANOUT_DEBUG_BOUNDARY_ORDER?.split(",") ??
|
|
1599
|
+
(process.env.FANOUT_DEBUG_FIRST_BOUNDARY_BUS
|
|
1600
|
+
? [process.env.FANOUT_DEBUG_FIRST_BOUNDARY_BUS]
|
|
1601
|
+
: [])
|
|
1602
|
+
const boundaryBuses =
|
|
1603
|
+
debugBoundaryOrder.length > 0
|
|
1604
|
+
? initiallySortedBoundaryBuses.toSorted((first, second) => {
|
|
1605
|
+
const firstIndex = debugBoundaryOrder.indexOf(first.busId)
|
|
1606
|
+
const secondIndex = debugBoundaryOrder.indexOf(second.busId)
|
|
1607
|
+
return (
|
|
1608
|
+
(firstIndex < 0 ? Number.POSITIVE_INFINITY : firstIndex) -
|
|
1609
|
+
(secondIndex < 0 ? Number.POSITIVE_INFINITY : secondIndex)
|
|
1610
|
+
)
|
|
1611
|
+
})
|
|
1612
|
+
: initiallySortedBoundaryBuses
|
|
1094
1613
|
// Preserve the caller/input order for the dense singleton fill. The
|
|
1095
1614
|
// general routing sort is useful for heterogeneous buses, but ordering a
|
|
1096
1615
|
// regular BGA power field by obstacle depth creates artificial local
|
|
@@ -1099,6 +1618,43 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1099
1618
|
const planeBuses = this.preparedBuses.filter(
|
|
1100
1619
|
(bus) => bus.termination.type === "plane",
|
|
1101
1620
|
)
|
|
1621
|
+
const denseAdditionalObstacles = useConfiguredDensePlaneRouting
|
|
1622
|
+
? this.routingSrj.obstacles
|
|
1623
|
+
: undefined
|
|
1624
|
+
const initialPlaneReservationCount = Number.parseInt(
|
|
1625
|
+
process.env.FANOUT_INITIAL_PLANE_RESERVATIONS ?? "8",
|
|
1626
|
+
10,
|
|
1627
|
+
)
|
|
1628
|
+
const debugInitialPlaneIndices =
|
|
1629
|
+
process.env.FANOUT_DEBUG_INITIAL_PLANE_INDICES?.split(",").map(
|
|
1630
|
+
(index) => Number(index) - 1,
|
|
1631
|
+
)
|
|
1632
|
+
const debugInitialPlaneBusIds =
|
|
1633
|
+
process.env.FANOUT_DEBUG_INITIAL_PLANE_BUS_IDS?.split(",")
|
|
1634
|
+
let activeBoundaryReservationPlaneBuses = debugInitialPlaneBusIds
|
|
1635
|
+
? planeBuses.filter((bus) => debugInitialPlaneBusIds.includes(bus.busId))
|
|
1636
|
+
: debugInitialPlaneIndices
|
|
1637
|
+
? debugInitialPlaneIndices.flatMap((index) =>
|
|
1638
|
+
planeBuses[index] ? [planeBuses[index]!] : [],
|
|
1639
|
+
)
|
|
1640
|
+
: this.config.densePlaneReservationBusIds.length > 0
|
|
1641
|
+
? planeBuses.filter((bus) =>
|
|
1642
|
+
this.config.densePlaneReservationBusIds.includes(bus.busId),
|
|
1643
|
+
)
|
|
1644
|
+
: useConfiguredDensePlaneRouting
|
|
1645
|
+
? planeBuses.slice(
|
|
1646
|
+
0,
|
|
1647
|
+
Number.isFinite(initialPlaneReservationCount)
|
|
1648
|
+
? initialPlaneReservationCount
|
|
1649
|
+
: 8,
|
|
1650
|
+
)
|
|
1651
|
+
: planeBuses
|
|
1652
|
+
debugDense(
|
|
1653
|
+
"start",
|
|
1654
|
+
boundaryBuses.map((bus) => `${bus.busId}:${bus.connections.length}`),
|
|
1655
|
+
`planes:${planeBuses.length}`,
|
|
1656
|
+
`joint:${useJointBoundaryViaReservation}`,
|
|
1657
|
+
)
|
|
1102
1658
|
if (
|
|
1103
1659
|
boundaryBuses.length === 0 ||
|
|
1104
1660
|
boundaryBuses.length > 9 ||
|
|
@@ -1129,18 +1685,40 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1129
1685
|
singletonBoundaryBusCount > 1 &&
|
|
1130
1686
|
shouldDeferSingletonBoundaryViaReservation(boundaryBusConnectionCounts)
|
|
1131
1687
|
const preferredBoundaryPerpendicularSideByBusId = new Map(
|
|
1132
|
-
boundaryBuses.map((bus) => [
|
|
1688
|
+
boundaryBuses.map((bus) => [
|
|
1689
|
+
bus.busId,
|
|
1690
|
+
hasThreeWideBoundaryBuses &&
|
|
1691
|
+
bus.connections.length < 8 &&
|
|
1692
|
+
narrowBusIsEmbeddedInWideSourceField(bus)
|
|
1693
|
+
? (-1 as const)
|
|
1694
|
+
: (1 as const),
|
|
1695
|
+
]),
|
|
1133
1696
|
)
|
|
1134
1697
|
const preferBoundaryOutwardByBusId = new Map(
|
|
1135
1698
|
boundaryBuses.map((bus) => [
|
|
1136
1699
|
bus.busId,
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1700
|
+
bus.connections.length === 1
|
|
1701
|
+
? hasThreeWideBoundaryBuses &&
|
|
1702
|
+
narrowBusIsEmbeddedInWideSourceField(bus)
|
|
1703
|
+
? true
|
|
1704
|
+
: useGeometryAwareSingletonOutwardPreference &&
|
|
1705
|
+
getCornerBandSide(bus.exitEdge, bus.preferredExit)
|
|
1706
|
+
? getDenseSingletonBoundaryGeometry(bus).targetProjection > 0
|
|
1707
|
+
: getExitEdgeForDirection(bus.direction) !== bus.exitEdge
|
|
1708
|
+
: bus.exitEdge === "top" && bus.connections.length >= 8
|
|
1709
|
+
? false
|
|
1710
|
+
: getExitEdgeForDirection(bus.direction) !== bus.exitEdge,
|
|
1142
1711
|
]),
|
|
1143
1712
|
)
|
|
1713
|
+
const debugFlippedBoundaryBus = process.env.FANOUT_DEBUG_FLIP_BOUNDARY_BUS
|
|
1714
|
+
if (debugFlippedBoundaryBus) {
|
|
1715
|
+
preferredBoundaryPerpendicularSideByBusId.set(debugFlippedBoundaryBus, -1)
|
|
1716
|
+
}
|
|
1717
|
+
const debugOutwardBoundaryBus =
|
|
1718
|
+
process.env.FANOUT_DEBUG_OUTWARD_BOUNDARY_BUS
|
|
1719
|
+
if (debugOutwardBoundaryBus) {
|
|
1720
|
+
preferBoundaryOutwardByBusId.set(debugOutwardBoundaryBus, true)
|
|
1721
|
+
}
|
|
1144
1722
|
const canShareCopper = (
|
|
1145
1723
|
firstConnectionIndex: number,
|
|
1146
1724
|
secondConnectionIndex: number,
|
|
@@ -1176,6 +1754,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1176
1754
|
(bus) => bus.connections.length === 1,
|
|
1177
1755
|
)
|
|
1178
1756
|
const singletonDeferralCandidates =
|
|
1757
|
+
!hasThreeWideBoundaryBuses &&
|
|
1179
1758
|
shouldDeferSingletonBoundaryViaReservation(boundaryBusConnectionCounts)
|
|
1180
1759
|
? singletonBoundaryBuses
|
|
1181
1760
|
.toSorted(
|
|
@@ -1190,8 +1769,8 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1190
1769
|
),
|
|
1191
1770
|
)
|
|
1192
1771
|
: []
|
|
1193
|
-
const
|
|
1194
|
-
boundaryBuses.length === 9
|
|
1772
|
+
const multiLayerLeadingSingletonBuses =
|
|
1773
|
+
boundaryBuses.length === 8 || boundaryBuses.length === 9
|
|
1195
1774
|
? singletonDeferralCandidates.filter((singletonBus) => {
|
|
1196
1775
|
const singletonTargetLayer =
|
|
1197
1776
|
params.busLayerAssignments[singletonBus.busId]
|
|
@@ -1205,6 +1784,53 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1205
1784
|
)
|
|
1206
1785
|
})
|
|
1207
1786
|
: []
|
|
1787
|
+
const throughAllLeadingSingletonBuses = hasThreeWideBoundaryBuses
|
|
1788
|
+
? singletonBoundaryBuses.filter((singletonBus) => {
|
|
1789
|
+
const containingWideBus = getContainingWideSourceField(singletonBus)
|
|
1790
|
+
const singletonTargetLayer =
|
|
1791
|
+
params.busLayerAssignments[singletonBus.busId]
|
|
1792
|
+
const containingWideLayers =
|
|
1793
|
+
containingWideBus?.routableEscapeLayers ??
|
|
1794
|
+
containingWideBus?.allowedLayers ??
|
|
1795
|
+
[]
|
|
1796
|
+
return Boolean(
|
|
1797
|
+
containingWideBus &&
|
|
1798
|
+
singletonTargetLayer &&
|
|
1799
|
+
!containingWideLayers.includes(singletonTargetLayer),
|
|
1800
|
+
)
|
|
1801
|
+
})
|
|
1802
|
+
: []
|
|
1803
|
+
const throughAllLeadingCompanionBuses =
|
|
1804
|
+
throughAllLeadingSingletonBuses.flatMap((singletonBus) => {
|
|
1805
|
+
const containingWideBus = getContainingWideSourceField(singletonBus)
|
|
1806
|
+
const singletonTargetLayer =
|
|
1807
|
+
params.busLayerAssignments[singletonBus.busId]
|
|
1808
|
+
return boundaryBuses.filter(
|
|
1809
|
+
(candidate) =>
|
|
1810
|
+
candidate.connections.length > 1 &&
|
|
1811
|
+
candidate.connections.length < 8 &&
|
|
1812
|
+
candidate.direction === singletonBus.direction &&
|
|
1813
|
+
params.busLayerAssignments[candidate.busId] ===
|
|
1814
|
+
singletonTargetLayer &&
|
|
1815
|
+
getContainingWideSourceField(candidate) === containingWideBus,
|
|
1816
|
+
)
|
|
1817
|
+
})
|
|
1818
|
+
const throughAllLeadingBuses =
|
|
1819
|
+
process.env.FANOUT_DEBUG_DISABLE_LEADING_NARROW === "1"
|
|
1820
|
+
? []
|
|
1821
|
+
: throughAllLeadingSingletonBuses.flatMap((singletonBus) => [
|
|
1822
|
+
singletonBus,
|
|
1823
|
+
...throughAllLeadingCompanionBuses.filter(
|
|
1824
|
+
(candidate) =>
|
|
1825
|
+
candidate.direction === singletonBus.direction &&
|
|
1826
|
+
params.busLayerAssignments[candidate.busId] ===
|
|
1827
|
+
params.busLayerAssignments[singletonBus.busId],
|
|
1828
|
+
),
|
|
1829
|
+
])
|
|
1830
|
+
const leadingWideSingletonBuses = [
|
|
1831
|
+
...multiLayerLeadingSingletonBuses,
|
|
1832
|
+
...throughAllLeadingBuses,
|
|
1833
|
+
].filter((bus, index, buses) => buses.indexOf(bus) === index)
|
|
1208
1834
|
const leadingLaneCountByWideCornerBand = new Map<string, number>()
|
|
1209
1835
|
if (boundaryBuses.length === 9 && leadingWideSingletonBuses.length > 0) {
|
|
1210
1836
|
for (const bus of leadingWideSingletonBuses) {
|
|
@@ -1229,11 +1855,22 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1229
1855
|
)
|
|
1230
1856
|
}
|
|
1231
1857
|
}
|
|
1232
|
-
const
|
|
1233
|
-
|
|
1858
|
+
const viaProvisionalBoundaryBusSet = new Set([
|
|
1859
|
+
...(process.env.FANOUT_DEBUG_PROVISIONAL_NARROW === "1"
|
|
1860
|
+
? boundaryBuses.filter((bus) => bus.connections.length < 8)
|
|
1861
|
+
: []),
|
|
1862
|
+
...singletonDeferralCandidates.filter(
|
|
1234
1863
|
(bus) => !leadingWideSingletonBuses.includes(bus),
|
|
1235
1864
|
),
|
|
1236
|
-
|
|
1865
|
+
...(hasThreeWideBoundaryBuses
|
|
1866
|
+
? boundaryBuses.filter(
|
|
1867
|
+
(bus) =>
|
|
1868
|
+
bus.connections.length === 2 &&
|
|
1869
|
+
!narrowBusOverlapsWideTargetSpan(bus) &&
|
|
1870
|
+
!leadingWideSingletonBuses.includes(bus),
|
|
1871
|
+
)
|
|
1872
|
+
: []),
|
|
1873
|
+
])
|
|
1237
1874
|
const getCornerBandTargetTrackOffset = (bus: PreparedBus): number => {
|
|
1238
1875
|
const side = getCornerBandSide(bus.exitEdge, bus.preferredExit)
|
|
1239
1876
|
if (!bus.exitEdge || !side) return 0
|
|
@@ -1247,11 +1884,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1247
1884
|
})
|
|
1248
1885
|
}
|
|
1249
1886
|
const initiallyMatchedBoundaryBuses = boundaryBuses.filter(
|
|
1250
|
-
(bus) => !
|
|
1887
|
+
(bus) => !viaProvisionalBoundaryBusSet.has(bus),
|
|
1251
1888
|
)
|
|
1252
1889
|
const jointViaPoints = useJointBoundaryViaReservation
|
|
1253
1890
|
? matchComponentDogboneViaSites(
|
|
1254
|
-
[
|
|
1891
|
+
[
|
|
1892
|
+
...activeBoundaryReservationPlaneBuses,
|
|
1893
|
+
...initiallyMatchedBoundaryBuses,
|
|
1894
|
+
],
|
|
1255
1895
|
{
|
|
1256
1896
|
viaDiameter: this.config.viaDiameter,
|
|
1257
1897
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
@@ -1260,28 +1900,133 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1260
1900
|
maximumSearchStates: 100_000,
|
|
1261
1901
|
preferredBoundaryPerpendicularSideByBusId,
|
|
1262
1902
|
preferBoundaryOutwardByBusId,
|
|
1903
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
1904
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1263
1905
|
canShareCopper,
|
|
1264
1906
|
},
|
|
1265
1907
|
)
|
|
1266
1908
|
: null
|
|
1267
|
-
|
|
1909
|
+
let seedViaPoints =
|
|
1268
1910
|
jointViaPoints ??
|
|
1269
|
-
matchComponentDogboneViaSites(
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1911
|
+
matchComponentDogboneViaSites(
|
|
1912
|
+
[...activeBoundaryReservationPlaneBuses, boundaryBuses[0]!],
|
|
1913
|
+
{
|
|
1914
|
+
viaDiameter: this.config.viaDiameter,
|
|
1915
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1916
|
+
traceWidth: this.config.traceWidth,
|
|
1917
|
+
clearance: this.config.clearance,
|
|
1918
|
+
maximumSearchStates: 20_000,
|
|
1919
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
1920
|
+
preferBoundaryOutwardByBusId,
|
|
1921
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
1922
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1923
|
+
canShareCopper,
|
|
1924
|
+
},
|
|
1925
|
+
)
|
|
1926
|
+
const debugFixedVias = process.env.FANOUT_DEBUG_FIXED_VIAS
|
|
1927
|
+
if (seedViaPoints && debugFixedVias) {
|
|
1928
|
+
seedViaPoints = new Map(seedViaPoints)
|
|
1929
|
+
for (const entry of debugFixedVias.split(",")) {
|
|
1930
|
+
const parts = entry.split(":")
|
|
1931
|
+
const rawY = parts.pop()
|
|
1932
|
+
const rawX = parts.pop()
|
|
1933
|
+
const connectionName = parts.join(":")
|
|
1934
|
+
const connectionIndex = [...connectionNameByIndex].find(
|
|
1935
|
+
([, name]) => name === connectionName,
|
|
1936
|
+
)?.[0]
|
|
1937
|
+
const x = Number(rawX)
|
|
1938
|
+
const y = Number(rawY)
|
|
1939
|
+
if (
|
|
1940
|
+
connectionIndex !== undefined &&
|
|
1941
|
+
Number.isFinite(x) &&
|
|
1942
|
+
Number.isFinite(y)
|
|
1943
|
+
) {
|
|
1944
|
+
seedViaPoints.set(connectionIndex, { x, y })
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
const debugStagedPlaneBuses = process.env.FANOUT_DEBUG_STAGED_PLANE_BUS_IDS
|
|
1949
|
+
? planeBuses.filter((bus) =>
|
|
1950
|
+
process.env
|
|
1951
|
+
.FANOUT_DEBUG_STAGED_PLANE_BUS_IDS!.split(",")
|
|
1952
|
+
.includes(bus.busId),
|
|
1953
|
+
)
|
|
1954
|
+
: (process.env.FANOUT_DEBUG_STAGED_PLANE_INDICES?.split(",").flatMap(
|
|
1955
|
+
(index) =>
|
|
1956
|
+
planeBuses[Number(index) - 1]
|
|
1957
|
+
? [planeBuses[Number(index) - 1]!]
|
|
1958
|
+
: [],
|
|
1959
|
+
) ?? [])
|
|
1960
|
+
if (seedViaPoints && debugStagedPlaneBuses.length > 0) {
|
|
1961
|
+
for (const stagedPlaneBus of debugStagedPlaneBuses) {
|
|
1962
|
+
if (activeBoundaryReservationPlaneBuses.includes(stagedPlaneBus)) {
|
|
1963
|
+
continue
|
|
1964
|
+
}
|
|
1965
|
+
const stagedViaPoints = matchComponentDogboneViaSites(
|
|
1966
|
+
[
|
|
1967
|
+
...activeBoundaryReservationPlaneBuses,
|
|
1968
|
+
stagedPlaneBus,
|
|
1969
|
+
...initiallyMatchedBoundaryBuses,
|
|
1970
|
+
],
|
|
1971
|
+
{
|
|
1972
|
+
viaDiameter: this.config.viaDiameter,
|
|
1973
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1974
|
+
traceWidth: this.config.traceWidth,
|
|
1975
|
+
clearance: this.config.clearance,
|
|
1976
|
+
maximumSearchStates: 100_000,
|
|
1977
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
1978
|
+
preferBoundaryOutwardByBusId,
|
|
1979
|
+
fixedViaPointsByConnectionIndex: seedViaPoints,
|
|
1980
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
1981
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1982
|
+
canShareCopper,
|
|
1983
|
+
},
|
|
1984
|
+
)
|
|
1985
|
+
debugDense(
|
|
1986
|
+
"plane-reservation:staged",
|
|
1987
|
+
stagedPlaneBus.busId,
|
|
1988
|
+
stagedViaPoints?.size ?? "failed",
|
|
1989
|
+
)
|
|
1990
|
+
if (!stagedViaPoints) break
|
|
1991
|
+
seedViaPoints = new Map([...seedViaPoints, ...stagedViaPoints])
|
|
1992
|
+
activeBoundaryReservationPlaneBuses.push(stagedPlaneBus)
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
debugDense("seed", seedViaPoints?.size ?? "failed")
|
|
1996
|
+
if (seedViaPoints && process.env.FANOUT_DEBUG_DENSE_POINTS === "1") {
|
|
1997
|
+
console.error(
|
|
1998
|
+
"dense: seed-points",
|
|
1999
|
+
[...seedViaPoints].map(([connectionIndex, point]) => ({
|
|
2000
|
+
connection: connectionNameByIndex.get(connectionIndex),
|
|
2001
|
+
point,
|
|
2002
|
+
})),
|
|
2003
|
+
)
|
|
2004
|
+
}
|
|
2005
|
+
let denseWorkUnitIndex = 1
|
|
2006
|
+
const denseWorkUnitCount = boundaryBuses.length + planeBuses.length + 3
|
|
2007
|
+
this.setInProgressPlans({
|
|
2008
|
+
phase: "reserve-dense-via-sites",
|
|
2009
|
+
plans: [],
|
|
2010
|
+
strategy: "default",
|
|
2011
|
+
unitIndex: denseWorkUnitIndex,
|
|
2012
|
+
unitCount: denseWorkUnitCount,
|
|
2013
|
+
})
|
|
2014
|
+
yield
|
|
1279
2015
|
if (seedViaPoints) {
|
|
1280
2016
|
const denseBoundaryBusesInRoutingOrder = [
|
|
1281
|
-
...
|
|
1282
|
-
...boundaryBuses
|
|
1283
|
-
|
|
1284
|
-
|
|
2017
|
+
...multiLayerLeadingSingletonBuses,
|
|
2018
|
+
...boundaryBuses
|
|
2019
|
+
.filter(
|
|
2020
|
+
(bus) =>
|
|
2021
|
+
!multiLayerLeadingSingletonBuses.includes(bus) &&
|
|
2022
|
+
!throughAllLeadingBuses.includes(bus),
|
|
2023
|
+
)
|
|
2024
|
+
.flatMap((bus) => [
|
|
2025
|
+
...throughAllLeadingBuses.filter(
|
|
2026
|
+
(candidate) => getContainingWideSourceField(candidate) === bus,
|
|
2027
|
+
),
|
|
2028
|
+
bus,
|
|
2029
|
+
]),
|
|
1285
2030
|
]
|
|
1286
2031
|
let fixedViaPointsByConnectionIndex: ReadonlyMap<
|
|
1287
2032
|
number,
|
|
@@ -1321,7 +2066,24 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1321
2066
|
})
|
|
1322
2067
|
})
|
|
1323
2068
|
}
|
|
1324
|
-
const
|
|
2069
|
+
const routeMatchedBoundaryBusSteps = function* (
|
|
2070
|
+
this: FanoutSolver,
|
|
2071
|
+
bus: PreparedBus,
|
|
2072
|
+
): Generator<FanoutWorkYield, boolean, unknown> {
|
|
2073
|
+
debugDense("route:start", bus.busId, matchedPlans.length)
|
|
2074
|
+
if (process.env.FANOUT_DEBUG_DENSE_POINTS === "1") {
|
|
2075
|
+
console.error(
|
|
2076
|
+
"dense: points",
|
|
2077
|
+
bus.busId,
|
|
2078
|
+
bus.connections.map((connection) => ({
|
|
2079
|
+
source: connection.sourcePoint,
|
|
2080
|
+
via: fixedViaPointsByConnectionIndex.get(
|
|
2081
|
+
connection.connectionIndex,
|
|
2082
|
+
),
|
|
2083
|
+
target: connection.exitTargetPoint ?? connection.targetPoint,
|
|
2084
|
+
})),
|
|
2085
|
+
)
|
|
2086
|
+
}
|
|
1325
2087
|
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
1326
2088
|
if (!targetLayer) {
|
|
1327
2089
|
return false
|
|
@@ -1342,10 +2104,60 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1342
2104
|
staticClearanceCache: this.routeStaticClearanceCache,
|
|
1343
2105
|
fixedViaPointsByConnectionIndex,
|
|
1344
2106
|
reservedVias: getReservedVias(bus),
|
|
1345
|
-
viaMinimalOnly:
|
|
2107
|
+
viaMinimalOnly: process.env.FANOUT_DEBUG_ALLOW_EXTRA_VIAS !== "1",
|
|
2108
|
+
fixedViaFallbackRouteOrderAttempts: useConfiguredDensePlaneRouting
|
|
2109
|
+
? 6
|
|
2110
|
+
: 24,
|
|
1346
2111
|
cornerBandTargetTrackOffset: getCornerBandTargetTrackOffset(bus),
|
|
1347
2112
|
} as const
|
|
1348
|
-
|
|
2113
|
+
const routeAlternatives = function* (
|
|
2114
|
+
this: FanoutSolver,
|
|
2115
|
+
candidateRouteParams: Parameters<typeof routeBusAlternativesSteps>[0],
|
|
2116
|
+
maximumAlternatives: number,
|
|
2117
|
+
): Generator<FanoutWorkYield, FanoutRoutePlan[][], unknown> {
|
|
2118
|
+
this.activeRoutingVisualization = null
|
|
2119
|
+
const solver = this.createWorkSolver(
|
|
2120
|
+
"BoundaryBusRoutingSolver",
|
|
2121
|
+
this.routeBusAlternativesWorkSteps(
|
|
2122
|
+
candidateRouteParams,
|
|
2123
|
+
maximumAlternatives,
|
|
2124
|
+
),
|
|
2125
|
+
undefined,
|
|
2126
|
+
() => this.visualizeBoundaryRoutingState(),
|
|
2127
|
+
)
|
|
2128
|
+
return (yield { type: "subsolver", solver }) as FanoutRoutePlan[][]
|
|
2129
|
+
}.bind(this)
|
|
2130
|
+
const routableEscapeLayers =
|
|
2131
|
+
bus.routableEscapeLayers ?? bus.allowedLayers ?? []
|
|
2132
|
+
const singleLayerBus = routableEscapeLayers.some(
|
|
2133
|
+
(layer) => layer !== targetLayer,
|
|
2134
|
+
)
|
|
2135
|
+
? { ...bus, routableEscapeLayers: [targetLayer] }
|
|
2136
|
+
: bus
|
|
2137
|
+
const embeddedNarrowBusAlreadyRouted = boundaryBuses.some(
|
|
2138
|
+
(candidate) =>
|
|
2139
|
+
candidate.connections.length < 8 &&
|
|
2140
|
+
getContainingWideSourceField(candidate) === bus &&
|
|
2141
|
+
matchedPlans.some((plan) => plan.busId === candidate.busId),
|
|
2142
|
+
)
|
|
2143
|
+
// In the configured dense-plane mode, a second allowed layer is an
|
|
2144
|
+
// optional winding crossover channel rather than a requirement. Try
|
|
2145
|
+
// the simpler single-layer route first unless an already-routed narrow
|
|
2146
|
+
// bus occupies that direct winding channel. Preserve the released
|
|
2147
|
+
// multi-layer search order for callers that did not opt into this mode.
|
|
2148
|
+
const preferSingleLayerWinding =
|
|
2149
|
+
useConfiguredDensePlaneRouting &&
|
|
2150
|
+
singleLayerBus !== bus &&
|
|
2151
|
+
!embeddedNarrowBusAlreadyRouted
|
|
2152
|
+
let busPlans = (yield* routeAlternatives(
|
|
2153
|
+
preferSingleLayerWinding
|
|
2154
|
+
? { ...routeParams, bus: singleLayerBus }
|
|
2155
|
+
: routeParams,
|
|
2156
|
+
1,
|
|
2157
|
+
))[0]
|
|
2158
|
+
if (!busPlans && preferSingleLayerWinding) {
|
|
2159
|
+
busPlans = (yield* routeAlternatives(routeParams, 1))[0]
|
|
2160
|
+
}
|
|
1349
2161
|
if (busPlans && bus.maxLengthSkew !== undefined) {
|
|
1350
2162
|
const lengths = busPlans.map((plan) => plan.length)
|
|
1351
2163
|
const rawSkew = Math.max(...lengths) - Math.min(...lengths)
|
|
@@ -1360,7 +2172,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1360
2172
|
// skewed that compact meanders are unlikely to absorb the deficit.
|
|
1361
2173
|
// This keeps already-near-matched buses on the single-attempt path.
|
|
1362
2174
|
if (needsRouteDiversity) {
|
|
1363
|
-
busPlans =
|
|
2175
|
+
busPlans = (yield* routeAlternatives(routeParams, 3)).toSorted(
|
|
1364
2176
|
(first, second) => {
|
|
1365
2177
|
const firstLengths = first.map((plan) => plan.length)
|
|
1366
2178
|
const secondLengths = second.map((plan) => plan.length)
|
|
@@ -1374,16 +2186,43 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1374
2186
|
}
|
|
1375
2187
|
}
|
|
1376
2188
|
if (!busPlans) {
|
|
2189
|
+
debugDense("route:failed", bus.busId)
|
|
1377
2190
|
return false
|
|
1378
2191
|
}
|
|
1379
2192
|
matchedPlans.push(...busPlans)
|
|
2193
|
+
debugDense("route:complete", bus.busId, busPlans.length)
|
|
1380
2194
|
return true
|
|
1381
|
-
}
|
|
2195
|
+
}.bind(this)
|
|
1382
2196
|
|
|
1383
2197
|
const firstBoundaryBus = denseBoundaryBusesInRoutingOrder[0]!
|
|
1384
2198
|
const routedBoundaryBuses: PreparedBus[] = []
|
|
1385
|
-
|
|
2199
|
+
const reserveAllPlaneDogbonesAfterFirstWideBus = (
|
|
2200
|
+
bus: PreparedBus,
|
|
2201
|
+
): void => {
|
|
2202
|
+
if (
|
|
2203
|
+
bus.connections.length >= 8 &&
|
|
2204
|
+
!useConfiguredDensePlaneRouting &&
|
|
2205
|
+
process.env.FANOUT_DEBUG_NO_PLANE_EXPANSION !== "1" &&
|
|
2206
|
+
activeBoundaryReservationPlaneBuses.length < planeBuses.length
|
|
2207
|
+
) {
|
|
2208
|
+
activeBoundaryReservationPlaneBuses = planeBuses
|
|
2209
|
+
debugDense("plane-reservations:expanded", planeBuses.length)
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
const firstBoundaryBusRouted =
|
|
2213
|
+
yield* routeMatchedBoundaryBusSteps(firstBoundaryBus)
|
|
2214
|
+
this.setInProgressPlans({
|
|
2215
|
+
phase: "route-dense-boundary-buses",
|
|
2216
|
+
plans: matchedPlans,
|
|
2217
|
+
strategy: "default",
|
|
2218
|
+
unitIndex: ++denseWorkUnitIndex,
|
|
2219
|
+
unitCount: denseWorkUnitCount,
|
|
2220
|
+
busId: firstBoundaryBus.busId,
|
|
2221
|
+
})
|
|
2222
|
+
yield
|
|
2223
|
+
if (firstBoundaryBusRouted) {
|
|
1386
2224
|
routedBoundaryBuses.push(firstBoundaryBus)
|
|
2225
|
+
reserveAllPlaneDogbonesAfterFirstWideBus(firstBoundaryBus)
|
|
1387
2226
|
} else {
|
|
1388
2227
|
matchedRoutingSucceeded = false
|
|
1389
2228
|
}
|
|
@@ -1402,18 +2241,51 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1402
2241
|
candidateIndex++
|
|
1403
2242
|
) {
|
|
1404
2243
|
const candidateBus = remainingBoundaryBuses[candidateIndex]!
|
|
2244
|
+
debugDense("candidate:start", candidateBus.busId)
|
|
2245
|
+
const candidateMatchingBase = new Map(fixedViaPointsByConnectionIndex)
|
|
2246
|
+
const debugLateFixedVias = process.env.FANOUT_DEBUG_LATE_FIXED_VIAS
|
|
2247
|
+
if (debugLateFixedVias) {
|
|
2248
|
+
const candidateConnectionIndices = new Set(
|
|
2249
|
+
candidateBus.connections.map(
|
|
2250
|
+
(connection) => connection.connectionIndex,
|
|
2251
|
+
),
|
|
2252
|
+
)
|
|
2253
|
+
for (const entry of debugLateFixedVias.split(",")) {
|
|
2254
|
+
const parts = entry.split(":")
|
|
2255
|
+
const rawY = parts.pop()
|
|
2256
|
+
const rawX = parts.pop()
|
|
2257
|
+
const connectionName = parts.join(":")
|
|
2258
|
+
const connectionIndex = [...connectionNameByIndex].find(
|
|
2259
|
+
([, name]) => name === connectionName,
|
|
2260
|
+
)?.[0]
|
|
2261
|
+
const x = Number(rawX)
|
|
2262
|
+
const y = Number(rawY)
|
|
2263
|
+
if (
|
|
2264
|
+
connectionIndex !== undefined &&
|
|
2265
|
+
candidateConnectionIndices.has(connectionIndex) &&
|
|
2266
|
+
Number.isFinite(x) &&
|
|
2267
|
+
Number.isFinite(y)
|
|
2268
|
+
) {
|
|
2269
|
+
candidateMatchingBase.set(connectionIndex, { x, y })
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
1405
2273
|
const candidateHasFixedViaPoints = candidateBus.connections.every(
|
|
1406
2274
|
(connection) =>
|
|
1407
|
-
|
|
2275
|
+
candidateMatchingBase.has(connection.connectionIndex),
|
|
1408
2276
|
)
|
|
1409
|
-
const
|
|
2277
|
+
const newlyMatchedViaPoints =
|
|
1410
2278
|
jointViaPoints && candidateHasFixedViaPoints
|
|
1411
2279
|
? // The joint map is deliberately kept intact so getReservedVias()
|
|
1412
2280
|
// blocks every already-reserved future through-barrel during A*.
|
|
1413
2281
|
// Provisional singleton and plane dogbones are rematched later.
|
|
1414
|
-
new Map(
|
|
2282
|
+
new Map(candidateMatchingBase)
|
|
1415
2283
|
: matchComponentDogboneViaSites(
|
|
1416
|
-
[
|
|
2284
|
+
[
|
|
2285
|
+
...activeBoundaryReservationPlaneBuses,
|
|
2286
|
+
...routedBoundaryBuses,
|
|
2287
|
+
candidateBus,
|
|
2288
|
+
],
|
|
1417
2289
|
{
|
|
1418
2290
|
viaDiameter: this.config.viaDiameter,
|
|
1419
2291
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
@@ -1422,12 +2294,31 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1422
2294
|
maximumSearchStates: 100_000,
|
|
1423
2295
|
preferredBoundaryPerpendicularSideByBusId,
|
|
1424
2296
|
preferBoundaryOutwardByBusId,
|
|
1425
|
-
fixedViaPointsByConnectionIndex:
|
|
1426
|
-
fixedViaPointsByConnectionIndex,
|
|
2297
|
+
fixedViaPointsByConnectionIndex: candidateMatchingBase,
|
|
1427
2298
|
blockingSegments,
|
|
2299
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2300
|
+
preferPlaneCheckerboardSites:
|
|
2301
|
+
useConfiguredDensePlaneRouting,
|
|
1428
2302
|
canShareCopper,
|
|
1429
2303
|
},
|
|
1430
2304
|
)
|
|
2305
|
+
const extendedViaPoints = newlyMatchedViaPoints
|
|
2306
|
+
? new Map([...candidateMatchingBase, ...newlyMatchedViaPoints])
|
|
2307
|
+
: null
|
|
2308
|
+
debugDense(
|
|
2309
|
+
"candidate:matched",
|
|
2310
|
+
candidateBus.busId,
|
|
2311
|
+
extendedViaPoints?.size ?? "failed",
|
|
2312
|
+
)
|
|
2313
|
+
this.setInProgressPlans({
|
|
2314
|
+
phase: "reserve-next-dense-boundary-bus",
|
|
2315
|
+
plans: matchedPlans,
|
|
2316
|
+
strategy: "default",
|
|
2317
|
+
unitIndex: denseWorkUnitIndex,
|
|
2318
|
+
unitCount: denseWorkUnitCount,
|
|
2319
|
+
busId: candidateBus.busId,
|
|
2320
|
+
})
|
|
2321
|
+
yield
|
|
1431
2322
|
if (!extendedViaPoints) continue
|
|
1432
2323
|
const previousFixedViaPoints = fixedViaPointsByConnectionIndex
|
|
1433
2324
|
const previousPlanCount = matchedPlans.length
|
|
@@ -1441,7 +2332,12 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1441
2332
|
if (laterBuses.length === 1) {
|
|
1442
2333
|
const laterBus = laterBuses[0]!
|
|
1443
2334
|
const futureAssignment = matchComponentDogboneViaSites(
|
|
1444
|
-
[
|
|
2335
|
+
[
|
|
2336
|
+
...activeBoundaryReservationPlaneBuses,
|
|
2337
|
+
...routedBoundaryBuses,
|
|
2338
|
+
candidateBus,
|
|
2339
|
+
laterBus,
|
|
2340
|
+
],
|
|
1445
2341
|
{
|
|
1446
2342
|
viaDiameter: this.config.viaDiameter,
|
|
1447
2343
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
@@ -1452,9 +2348,16 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1452
2348
|
preferBoundaryOutwardByBusId,
|
|
1453
2349
|
fixedViaPointsByConnectionIndex: extendedViaPoints,
|
|
1454
2350
|
blockingSegments,
|
|
2351
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2352
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1455
2353
|
canShareCopper,
|
|
1456
2354
|
},
|
|
1457
2355
|
)
|
|
2356
|
+
debugDense(
|
|
2357
|
+
"future:matched",
|
|
2358
|
+
laterBus.busId,
|
|
2359
|
+
futureAssignment?.size ?? "failed",
|
|
2360
|
+
)
|
|
1458
2361
|
if (futureAssignment) {
|
|
1459
2362
|
const candidateCountByConnectionIndex = new Map<number, number>()
|
|
1460
2363
|
for (const candidate of getComponentDogboneViaSiteCandidates(
|
|
@@ -1465,6 +2368,8 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1465
2368
|
traceWidth: this.config.traceWidth,
|
|
1466
2369
|
clearance: this.config.clearance,
|
|
1467
2370
|
blockingSegments,
|
|
2371
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2372
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1468
2373
|
canShareCopper,
|
|
1469
2374
|
},
|
|
1470
2375
|
)) {
|
|
@@ -1499,7 +2404,8 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1499
2404
|
}
|
|
1500
2405
|
}
|
|
1501
2406
|
fixedViaPointsByConnectionIndex = candidateFixedViaPoints
|
|
1502
|
-
if (
|
|
2407
|
+
if (yield* routeMatchedBoundaryBusSteps(candidateBus)) {
|
|
2408
|
+
debugDense("lookahead:start", candidateBus.busId)
|
|
1503
2409
|
const candidateLeavesAFeasibleExtension =
|
|
1504
2410
|
laterBuses.length === 0 ||
|
|
1505
2411
|
laterBuses.some((laterBus) => {
|
|
@@ -1512,7 +2418,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1512
2418
|
return Boolean(
|
|
1513
2419
|
matchComponentDogboneViaSites(
|
|
1514
2420
|
[
|
|
1515
|
-
...
|
|
2421
|
+
...activeBoundaryReservationPlaneBuses,
|
|
1516
2422
|
...routedBoundaryBuses,
|
|
1517
2423
|
candidateBus,
|
|
1518
2424
|
laterBus,
|
|
@@ -1528,39 +2434,828 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1528
2434
|
fixedViaPointsByConnectionIndex:
|
|
1529
2435
|
fixedViaPointsByConnectionIndex,
|
|
1530
2436
|
blockingSegments: lookaheadBlockingSegments,
|
|
2437
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2438
|
+
preferPlaneCheckerboardSites:
|
|
2439
|
+
useConfiguredDensePlaneRouting,
|
|
1531
2440
|
canShareCopper,
|
|
1532
2441
|
},
|
|
1533
2442
|
),
|
|
1534
2443
|
)
|
|
1535
|
-
})
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
2444
|
+
})
|
|
2445
|
+
debugDense(
|
|
2446
|
+
"lookahead:complete",
|
|
2447
|
+
candidateBus.busId,
|
|
2448
|
+
candidateLeavesAFeasibleExtension,
|
|
2449
|
+
)
|
|
2450
|
+
if (candidateLeavesAFeasibleExtension) {
|
|
2451
|
+
selectedBusIndex = candidateIndex
|
|
2452
|
+
routedBoundaryBuses.push(candidateBus)
|
|
2453
|
+
reserveAllPlaneDogbonesAfterFirstWideBus(candidateBus)
|
|
2454
|
+
this.setInProgressPlans({
|
|
2455
|
+
phase: "route-dense-boundary-buses",
|
|
2456
|
+
plans: matchedPlans,
|
|
2457
|
+
strategy: "default",
|
|
2458
|
+
unitIndex: ++denseWorkUnitIndex,
|
|
2459
|
+
unitCount: denseWorkUnitCount,
|
|
2460
|
+
busId: candidateBus.busId,
|
|
2461
|
+
})
|
|
2462
|
+
yield
|
|
2463
|
+
break
|
|
2464
|
+
}
|
|
2465
|
+
matchedPlans.splice(previousPlanCount)
|
|
2466
|
+
}
|
|
2467
|
+
fixedViaPointsByConnectionIndex = previousFixedViaPoints
|
|
2468
|
+
this.setInProgressPlans({
|
|
2469
|
+
phase: "retry-dense-boundary-bus",
|
|
2470
|
+
plans: matchedPlans,
|
|
2471
|
+
strategy: "default",
|
|
2472
|
+
unitIndex: denseWorkUnitIndex,
|
|
2473
|
+
unitCount: denseWorkUnitCount,
|
|
2474
|
+
busId: candidateBus.busId,
|
|
2475
|
+
})
|
|
2476
|
+
yield
|
|
2477
|
+
}
|
|
2478
|
+
if (selectedBusIndex < 0) {
|
|
2479
|
+
matchedRoutingSucceeded = false
|
|
2480
|
+
break
|
|
2481
|
+
}
|
|
2482
|
+
remainingBoundaryBuses.splice(selectedBusIndex, 1)
|
|
2483
|
+
}
|
|
2484
|
+
let matchedPlaneBusesInRoutingOrder: PreparedBus[] | null = null
|
|
2485
|
+
if (matchedRoutingSucceeded) {
|
|
2486
|
+
let feasibleViaPoints: Map<number, { x: number; y: number }> | null =
|
|
2487
|
+
null
|
|
2488
|
+
let feasibleAlternatePlanePlans: FanoutRoutePlan[] = []
|
|
2489
|
+
const matchViaPointsAroundPlans = (
|
|
2490
|
+
candidatePlans: readonly FanoutRoutePlan[],
|
|
2491
|
+
): Map<number, { x: number; y: number }> | null => {
|
|
2492
|
+
feasibleAlternatePlanePlans = []
|
|
2493
|
+
const fixedBoundaryViaPoints = new Map(
|
|
2494
|
+
candidatePlans.flatMap((plan) =>
|
|
2495
|
+
plan.via
|
|
2496
|
+
? [[plan.connectionIndex, plan.via.center] as const]
|
|
2497
|
+
: [],
|
|
2498
|
+
),
|
|
2499
|
+
)
|
|
2500
|
+
const blockingSegments = candidatePlans.flatMap((plan) =>
|
|
2501
|
+
plan.segments.map((segment) => ({
|
|
2502
|
+
connectionIndex: plan.connectionIndex,
|
|
2503
|
+
segment,
|
|
2504
|
+
})),
|
|
2505
|
+
)
|
|
2506
|
+
if (
|
|
2507
|
+
useConfiguredDensePlaneRouting ||
|
|
2508
|
+
process.env.FANOUT_DEBUG_INCREMENTAL_PLANE_MATCH === "1"
|
|
2509
|
+
) {
|
|
2510
|
+
let incrementalViaPoints = new Map(fixedBoundaryViaPoints)
|
|
2511
|
+
const matchedPlaneBuses = [...activeBoundaryReservationPlaneBuses]
|
|
2512
|
+
for (const planeBus of matchedPlaneBuses) {
|
|
2513
|
+
for (const connection of planeBus.connections) {
|
|
2514
|
+
const reservedPoint = fixedViaPointsByConnectionIndex.get(
|
|
2515
|
+
connection.connectionIndex,
|
|
2516
|
+
)
|
|
2517
|
+
if (reservedPoint) {
|
|
2518
|
+
incrementalViaPoints.set(
|
|
2519
|
+
connection.connectionIndex,
|
|
2520
|
+
reservedPoint,
|
|
2521
|
+
)
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
for (const planeBus of matchedPlaneBuses) {
|
|
2526
|
+
const targetLayer = params.busLayerAssignments[planeBus.busId]
|
|
2527
|
+
if (!targetLayer) return null
|
|
2528
|
+
const reservedPlanePlans = routeBus({
|
|
2529
|
+
srj: this.routingSrj,
|
|
2530
|
+
bus: planeBus,
|
|
2531
|
+
targetLayer,
|
|
2532
|
+
acceptedPlans: [
|
|
2533
|
+
...candidatePlans,
|
|
2534
|
+
...feasibleAlternatePlanePlans,
|
|
2535
|
+
],
|
|
2536
|
+
layerNames: this.config.layerNames,
|
|
2537
|
+
traceWidth: this.config.traceWidth,
|
|
2538
|
+
viaDiameter: this.config.viaDiameter,
|
|
2539
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
2540
|
+
clearance: this.config.clearance,
|
|
2541
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
2542
|
+
allowBlindAndBuriedVias: false,
|
|
2543
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
2544
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
2545
|
+
fixedViaPointsByConnectionIndex: incrementalViaPoints,
|
|
2546
|
+
})
|
|
2547
|
+
if (!reservedPlanePlans) return null
|
|
2548
|
+
feasibleAlternatePlanePlans.push(...reservedPlanePlans)
|
|
2549
|
+
}
|
|
2550
|
+
const independentlyUnmatchablePlaneBuses = planeBuses.filter(
|
|
2551
|
+
(planeBus) =>
|
|
2552
|
+
!matchedPlaneBuses.includes(planeBus) &&
|
|
2553
|
+
!matchComponentDogboneViaSites(
|
|
2554
|
+
[...matchedPlaneBuses, planeBus, ...boundaryBuses],
|
|
2555
|
+
{
|
|
2556
|
+
viaDiameter: this.config.viaDiameter,
|
|
2557
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
2558
|
+
traceWidth: this.config.traceWidth,
|
|
2559
|
+
clearance: this.config.clearance,
|
|
2560
|
+
maximumSearchStates: 100_000,
|
|
2561
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
2562
|
+
preferBoundaryOutwardByBusId,
|
|
2563
|
+
fixedViaPointsByConnectionIndex: incrementalViaPoints,
|
|
2564
|
+
blockingSegments,
|
|
2565
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2566
|
+
preferPlaneCheckerboardSites:
|
|
2567
|
+
useConfiguredDensePlaneRouting,
|
|
2568
|
+
canShareCopper,
|
|
2569
|
+
},
|
|
2570
|
+
),
|
|
2571
|
+
)
|
|
2572
|
+
const shallowestPlaneLayerIndex = Math.min(
|
|
2573
|
+
...planeBuses.map((bus) =>
|
|
2574
|
+
this.config.layerNames.indexOf(
|
|
2575
|
+
params.busLayerAssignments[bus.busId] ?? "",
|
|
2576
|
+
),
|
|
2577
|
+
),
|
|
2578
|
+
)
|
|
2579
|
+
const deeperPlaneBuses =
|
|
2580
|
+
useConfiguredDensePlaneRouting ||
|
|
2581
|
+
process.env.FANOUT_DEBUG_ROUTE_DEEP_PLANES_FIRST === "1"
|
|
2582
|
+
? planeBuses.filter(
|
|
2583
|
+
(bus) =>
|
|
2584
|
+
!matchedPlaneBuses.includes(bus) &&
|
|
2585
|
+
this.config.layerNames.indexOf(
|
|
2586
|
+
params.busLayerAssignments[bus.busId] ?? "",
|
|
2587
|
+
) > shallowestPlaneLayerIndex,
|
|
2588
|
+
)
|
|
2589
|
+
: []
|
|
2590
|
+
const additionalAlternatePlaneBusIds = new Set([
|
|
2591
|
+
...this.config.denseUnrestrictedPlaneRoutingBusIds,
|
|
2592
|
+
...(process.env.FANOUT_DEBUG_ADDITIONAL_ALTERNATE_PLANE_BUS_IDS?.split(
|
|
2593
|
+
",",
|
|
2594
|
+
) ?? []),
|
|
2595
|
+
])
|
|
2596
|
+
const additionalAlternatePlaneBuses = planeBuses.filter(
|
|
2597
|
+
(bus) =>
|
|
2598
|
+
!matchedPlaneBuses.includes(bus) &&
|
|
2599
|
+
additionalAlternatePlaneBusIds.has(bus.busId),
|
|
2600
|
+
)
|
|
2601
|
+
const alternatePlaneBuses = [
|
|
2602
|
+
...deeperPlaneBuses,
|
|
2603
|
+
...independentlyUnmatchablePlaneBuses.filter(
|
|
2604
|
+
(bus) => !deeperPlaneBuses.includes(bus),
|
|
2605
|
+
),
|
|
2606
|
+
...additionalAlternatePlaneBuses.filter(
|
|
2607
|
+
(bus) =>
|
|
2608
|
+
!deeperPlaneBuses.includes(bus) &&
|
|
2609
|
+
!independentlyUnmatchablePlaneBuses.includes(bus),
|
|
2610
|
+
),
|
|
2611
|
+
]
|
|
2612
|
+
const debugAlternatePlaneOrder =
|
|
2613
|
+
process.env.FANOUT_DEBUG_ALTERNATE_PLANE_ORDER?.split(",") ?? []
|
|
2614
|
+
const orderedAlternatePlaneBuses = alternatePlaneBuses.toSorted(
|
|
2615
|
+
(first, second) => {
|
|
2616
|
+
const firstLayerIndex = this.config.layerNames.indexOf(
|
|
2617
|
+
params.busLayerAssignments[first.busId] ?? "",
|
|
2618
|
+
)
|
|
2619
|
+
const secondLayerIndex = this.config.layerNames.indexOf(
|
|
2620
|
+
params.busLayerAssignments[second.busId] ?? "",
|
|
2621
|
+
)
|
|
2622
|
+
if (
|
|
2623
|
+
firstLayerIndex !== secondLayerIndex &&
|
|
2624
|
+
process.env.FANOUT_DEBUG_ALTERNATE_IGNORE_LAYERS !== "1"
|
|
2625
|
+
) {
|
|
2626
|
+
return process.env.FANOUT_DEBUG_ALTERNATE_SHALLOW_FIRST ===
|
|
2627
|
+
"1"
|
|
2628
|
+
? firstLayerIndex - secondLayerIndex
|
|
2629
|
+
: secondLayerIndex - firstLayerIndex
|
|
2630
|
+
}
|
|
2631
|
+
const firstPriority = debugAlternatePlaneOrder.indexOf(
|
|
2632
|
+
first.busId,
|
|
2633
|
+
)
|
|
2634
|
+
const secondPriority = debugAlternatePlaneOrder.indexOf(
|
|
2635
|
+
second.busId,
|
|
2636
|
+
)
|
|
2637
|
+
return (
|
|
2638
|
+
(firstPriority < 0
|
|
2639
|
+
? debugAlternatePlaneOrder.length
|
|
2640
|
+
: firstPriority) -
|
|
2641
|
+
(secondPriority < 0
|
|
2642
|
+
? debugAlternatePlaneOrder.length
|
|
2643
|
+
: secondPriority) ||
|
|
2644
|
+
first.connections[0]!.connectionIndex -
|
|
2645
|
+
second.connections[0]!.connectionIndex
|
|
2646
|
+
)
|
|
2647
|
+
},
|
|
2648
|
+
)
|
|
2649
|
+
if (alternatePlaneBuses.length > 0) {
|
|
2650
|
+
debugDense(
|
|
2651
|
+
"plane-match:preflight-failed",
|
|
2652
|
+
independentlyUnmatchablePlaneBuses.map((bus) => bus.busId),
|
|
2653
|
+
)
|
|
2654
|
+
if (
|
|
2655
|
+
!useConfiguredDensePlaneRouting &&
|
|
2656
|
+
process.env.FANOUT_DEBUG_ROUTE_UNMATCHED_PLANES !== "1"
|
|
2657
|
+
) {
|
|
2658
|
+
return null
|
|
2659
|
+
}
|
|
2660
|
+
let alternatePlaneSearchStates = 0
|
|
2661
|
+
const maximumAlternatePlaneSearchStates = Number(
|
|
2662
|
+
process.env.FANOUT_DEBUG_ALTERNATE_SEARCH_STATES ??
|
|
2663
|
+
(useConfiguredDensePlaneRouting ? 3_000_000 : 1_000),
|
|
2664
|
+
)
|
|
2665
|
+
const maximumAlternatePlaneRoutes = Number(
|
|
2666
|
+
process.env.FANOUT_DEBUG_ALTERNATE_ROUTE_COUNT ??
|
|
2667
|
+
(useConfiguredDensePlaneRouting ? 128 : 8),
|
|
2668
|
+
)
|
|
2669
|
+
let deepestAlternatePlaneSearchIndex = 0
|
|
2670
|
+
const alternatePlaneFailureCountByBusId = new Map<
|
|
2671
|
+
string,
|
|
2672
|
+
number
|
|
2673
|
+
>()
|
|
2674
|
+
const getPlaneRouteAlternatives = (
|
|
2675
|
+
planeBus: PreparedBus,
|
|
2676
|
+
additionalAcceptedPlans: FanoutRoutePlan[],
|
|
2677
|
+
maximumRoutes = maximumAlternatePlaneRoutes,
|
|
2678
|
+
): FanoutRoutePlan[][] => {
|
|
2679
|
+
const targetLayer = params.busLayerAssignments[planeBus.busId]
|
|
2680
|
+
if (!targetLayer) return []
|
|
2681
|
+
return routeBusAlternatives(
|
|
2682
|
+
{
|
|
2683
|
+
srj: this.routingSrj,
|
|
2684
|
+
bus: planeBus,
|
|
2685
|
+
targetLayer,
|
|
2686
|
+
acceptedPlans: [
|
|
2687
|
+
...candidatePlans,
|
|
2688
|
+
...additionalAcceptedPlans,
|
|
2689
|
+
],
|
|
2690
|
+
layerNames: this.config.layerNames,
|
|
2691
|
+
traceWidth: this.config.traceWidth,
|
|
2692
|
+
viaDiameter: this.config.viaDiameter,
|
|
2693
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
2694
|
+
clearance: this.config.clearance,
|
|
2695
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
2696
|
+
allowBlindAndBuriedVias: false,
|
|
2697
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
2698
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
2699
|
+
},
|
|
2700
|
+
maximumRoutes,
|
|
2701
|
+
)
|
|
2702
|
+
}
|
|
2703
|
+
const routeAlternatePlaneBuses = (
|
|
2704
|
+
remainingPlaneBuses: PreparedBus[],
|
|
2705
|
+
acceptedAlternatePlans: FanoutRoutePlan[],
|
|
2706
|
+
): FanoutRoutePlan[] | null => {
|
|
2707
|
+
if (remainingPlaneBuses.length === 0) {
|
|
2708
|
+
return acceptedAlternatePlans
|
|
2709
|
+
}
|
|
2710
|
+
if (
|
|
2711
|
+
alternatePlaneSearchStates >=
|
|
2712
|
+
maximumAlternatePlaneSearchStates
|
|
2713
|
+
) {
|
|
2714
|
+
return null
|
|
2715
|
+
}
|
|
2716
|
+
deepestAlternatePlaneSearchIndex = Math.max(
|
|
2717
|
+
deepestAlternatePlaneSearchIndex,
|
|
2718
|
+
orderedAlternatePlaneBuses.length -
|
|
2719
|
+
remainingPlaneBuses.length,
|
|
2720
|
+
)
|
|
2721
|
+
const alternativesByBus = remainingPlaneBuses.map(
|
|
2722
|
+
(planeBus) => ({
|
|
2723
|
+
planeBus,
|
|
2724
|
+
alternatives: getPlaneRouteAlternatives(
|
|
2725
|
+
planeBus,
|
|
2726
|
+
acceptedAlternatePlans,
|
|
2727
|
+
),
|
|
2728
|
+
}),
|
|
2729
|
+
)
|
|
2730
|
+
const orderedSelections =
|
|
2731
|
+
process.env.FANOUT_DEBUG_DYNAMIC_ALTERNATE_ORDER === "1"
|
|
2732
|
+
? alternativesByBus.toSorted(
|
|
2733
|
+
(first, second) =>
|
|
2734
|
+
first.alternatives.length -
|
|
2735
|
+
second.alternatives.length ||
|
|
2736
|
+
orderedAlternatePlaneBuses.indexOf(first.planeBus) -
|
|
2737
|
+
orderedAlternatePlaneBuses.indexOf(second.planeBus),
|
|
2738
|
+
)
|
|
2739
|
+
: alternativesByBus
|
|
2740
|
+
const selected = orderedSelections[0]!
|
|
2741
|
+
const { planeBus, alternatives } = selected
|
|
2742
|
+
if (
|
|
2743
|
+
process.env.FANOUT_DEBUG_ALTERNATE_CHOICES === "1" &&
|
|
2744
|
+
alternatePlaneSearchStates < 64
|
|
2745
|
+
) {
|
|
2746
|
+
debugDense(
|
|
2747
|
+
"plane-route:alternate-choice",
|
|
2748
|
+
`depth:${orderedAlternatePlaneBuses.length - remainingPlaneBuses.length}`,
|
|
2749
|
+
planeBus.busId,
|
|
2750
|
+
alternativesByBus.map((entry) => [
|
|
2751
|
+
entry.planeBus.busId,
|
|
2752
|
+
entry.alternatives.length,
|
|
2753
|
+
]),
|
|
2754
|
+
)
|
|
2755
|
+
}
|
|
2756
|
+
if (alternatives.length === 0) {
|
|
2757
|
+
alternatePlaneFailureCountByBusId.set(
|
|
2758
|
+
planeBus.busId,
|
|
2759
|
+
(alternatePlaneFailureCountByBusId.get(planeBus.busId) ??
|
|
2760
|
+
0) + 1,
|
|
2761
|
+
)
|
|
2762
|
+
return null
|
|
2763
|
+
}
|
|
2764
|
+
const selectionsToSearch =
|
|
2765
|
+
process.env.FANOUT_DEBUG_BRANCH_ALTERNATE_ORDER === "1"
|
|
2766
|
+
? orderedSelections
|
|
2767
|
+
: [selected]
|
|
2768
|
+
const alternateActions = selectionsToSearch.flatMap(
|
|
2769
|
+
(selection) =>
|
|
2770
|
+
selection.alternatives.map((alternative) => ({
|
|
2771
|
+
selection,
|
|
2772
|
+
alternative,
|
|
2773
|
+
remaining: remainingPlaneBuses.filter(
|
|
2774
|
+
(candidate) => candidate !== selection.planeBus,
|
|
2775
|
+
),
|
|
2776
|
+
})),
|
|
2777
|
+
)
|
|
2778
|
+
const orderedActions =
|
|
2779
|
+
process.env.FANOUT_DEBUG_LEAST_CONSTRAINING_ALTERNATES === "1"
|
|
2780
|
+
? alternateActions
|
|
2781
|
+
.map((action) => {
|
|
2782
|
+
const acceptedPlans = [
|
|
2783
|
+
...acceptedAlternatePlans,
|
|
2784
|
+
...action.alternative,
|
|
2785
|
+
]
|
|
2786
|
+
const remainingOptionCounts = action.remaining.map(
|
|
2787
|
+
(remainingBus) =>
|
|
2788
|
+
getPlaneRouteAlternatives(
|
|
2789
|
+
remainingBus,
|
|
2790
|
+
acceptedPlans,
|
|
2791
|
+
Math.min(4, maximumAlternatePlaneRoutes),
|
|
2792
|
+
).length,
|
|
2793
|
+
)
|
|
2794
|
+
return {
|
|
2795
|
+
...action,
|
|
2796
|
+
remainingOptionCounts,
|
|
2797
|
+
minimumRemainingOptions:
|
|
2798
|
+
remainingOptionCounts.length === 0
|
|
2799
|
+
? Number.POSITIVE_INFINITY
|
|
2800
|
+
: Math.min(...remainingOptionCounts),
|
|
2801
|
+
totalRemainingOptions: remainingOptionCounts.reduce(
|
|
2802
|
+
(total, count) => total + count,
|
|
2803
|
+
0,
|
|
2804
|
+
),
|
|
2805
|
+
}
|
|
2806
|
+
})
|
|
2807
|
+
.filter(
|
|
2808
|
+
(action) => action.minimumRemainingOptions !== 0,
|
|
2809
|
+
)
|
|
2810
|
+
.toSorted(
|
|
2811
|
+
(first, second) =>
|
|
2812
|
+
second.minimumRemainingOptions -
|
|
2813
|
+
first.minimumRemainingOptions ||
|
|
2814
|
+
second.totalRemainingOptions -
|
|
2815
|
+
first.totalRemainingOptions,
|
|
2816
|
+
)
|
|
2817
|
+
: alternateActions
|
|
2818
|
+
for (const action of orderedActions) {
|
|
2819
|
+
alternatePlaneSearchStates++
|
|
2820
|
+
const completedPlans = routeAlternatePlaneBuses(
|
|
2821
|
+
action.remaining,
|
|
2822
|
+
[...acceptedAlternatePlans, ...action.alternative],
|
|
2823
|
+
)
|
|
2824
|
+
if (completedPlans) return completedPlans
|
|
2825
|
+
if (
|
|
2826
|
+
alternatePlaneSearchStates >=
|
|
2827
|
+
maximumAlternatePlaneSearchStates
|
|
2828
|
+
) {
|
|
2829
|
+
break
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
alternatePlaneFailureCountByBusId.set(
|
|
2833
|
+
planeBus.busId,
|
|
2834
|
+
(alternatePlaneFailureCountByBusId.get(planeBus.busId) ?? 0) +
|
|
2835
|
+
1,
|
|
2836
|
+
)
|
|
2837
|
+
return null
|
|
2838
|
+
}
|
|
2839
|
+
let alternatePlanePlans: FanoutRoutePlan[] | null
|
|
2840
|
+
if (
|
|
2841
|
+
useConfiguredDensePlaneRouting ||
|
|
2842
|
+
process.env.FANOUT_DEBUG_EXACT_COVER_ALTERNATES === "1"
|
|
2843
|
+
) {
|
|
2844
|
+
type IndependentPlaneRouteCandidate = {
|
|
2845
|
+
key: string
|
|
2846
|
+
planeBus: PreparedBus
|
|
2847
|
+
plans: FanoutRoutePlan[]
|
|
2848
|
+
}
|
|
2849
|
+
const candidateSets = orderedAlternatePlaneBuses.map(
|
|
2850
|
+
(planeBus) => ({
|
|
2851
|
+
planeBus,
|
|
2852
|
+
candidates: getPlaneRouteAlternatives(
|
|
2853
|
+
planeBus,
|
|
2854
|
+
feasibleAlternatePlanePlans,
|
|
2855
|
+
).map((plans, index) => ({
|
|
2856
|
+
key: `${planeBus.busId}:${index}`,
|
|
2857
|
+
planeBus,
|
|
2858
|
+
plans,
|
|
2859
|
+
})),
|
|
2860
|
+
}),
|
|
2861
|
+
)
|
|
2862
|
+
debugDense(
|
|
2863
|
+
"plane-route:alternate-candidate-counts",
|
|
2864
|
+
candidateSets.map((candidateSet) => [
|
|
2865
|
+
candidateSet.planeBus.busId,
|
|
2866
|
+
candidateSet.candidates.length,
|
|
2867
|
+
]),
|
|
2868
|
+
)
|
|
2869
|
+
const compatibilityByCandidatePair = new Map<string, boolean>()
|
|
2870
|
+
const candidatesAreCompatible = (
|
|
2871
|
+
first: IndependentPlaneRouteCandidate,
|
|
2872
|
+
second: IndependentPlaneRouteCandidate,
|
|
2873
|
+
): boolean => {
|
|
2874
|
+
const cacheKey = [first.key, second.key].toSorted().join("|")
|
|
2875
|
+
const cached = compatibilityByCandidatePair.get(cacheKey)
|
|
2876
|
+
if (cached !== undefined) return cached
|
|
2877
|
+
const compatible = fanoutPlansAreMutuallyClear({
|
|
2878
|
+
plans: [...first.plans, ...second.plans],
|
|
2879
|
+
srj: this.routingSrj,
|
|
2880
|
+
clearance: this.config.clearance,
|
|
2881
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
2882
|
+
})
|
|
2883
|
+
compatibilityByCandidatePair.set(cacheKey, compatible)
|
|
2884
|
+
return compatible
|
|
2885
|
+
}
|
|
2886
|
+
const selectCompatiblePlaneRoutes = (
|
|
2887
|
+
remainingCandidateSets: typeof candidateSets,
|
|
2888
|
+
selectedCandidates: IndependentPlaneRouteCandidate[],
|
|
2889
|
+
): IndependentPlaneRouteCandidate[] | null => {
|
|
2890
|
+
if (remainingCandidateSets.length === 0) {
|
|
2891
|
+
return selectedCandidates
|
|
2892
|
+
}
|
|
2893
|
+
if (
|
|
2894
|
+
alternatePlaneSearchStates >=
|
|
2895
|
+
maximumAlternatePlaneSearchStates
|
|
2896
|
+
) {
|
|
2897
|
+
return null
|
|
2898
|
+
}
|
|
2899
|
+
deepestAlternatePlaneSearchIndex = Math.max(
|
|
2900
|
+
deepestAlternatePlaneSearchIndex,
|
|
2901
|
+
orderedAlternatePlaneBuses.length -
|
|
2902
|
+
remainingCandidateSets.length,
|
|
2903
|
+
)
|
|
2904
|
+
const selectedSet = remainingCandidateSets.toSorted(
|
|
2905
|
+
(first, second) =>
|
|
2906
|
+
first.candidates.length - second.candidates.length,
|
|
2907
|
+
)[0]!
|
|
2908
|
+
if (selectedSet.candidates.length === 0) {
|
|
2909
|
+
alternatePlaneFailureCountByBusId.set(
|
|
2910
|
+
selectedSet.planeBus.busId,
|
|
2911
|
+
(alternatePlaneFailureCountByBusId.get(
|
|
2912
|
+
selectedSet.planeBus.busId,
|
|
2913
|
+
) ?? 0) + 1,
|
|
2914
|
+
)
|
|
2915
|
+
return null
|
|
2916
|
+
}
|
|
2917
|
+
const otherSets = remainingCandidateSets.filter(
|
|
2918
|
+
(candidateSet) => candidateSet !== selectedSet,
|
|
2919
|
+
)
|
|
2920
|
+
const candidateBatchSize = Number(
|
|
2921
|
+
process.env.FANOUT_DEBUG_ALTERNATE_CANDIDATE_BATCH_SIZE ??
|
|
2922
|
+
16,
|
|
2923
|
+
)
|
|
2924
|
+
for (
|
|
2925
|
+
let batchStart = 0;
|
|
2926
|
+
batchStart < selectedSet.candidates.length;
|
|
2927
|
+
batchStart += candidateBatchSize
|
|
2928
|
+
) {
|
|
2929
|
+
const actions = selectedSet.candidates
|
|
2930
|
+
.slice(batchStart, batchStart + candidateBatchSize)
|
|
2931
|
+
.map((candidate) => {
|
|
2932
|
+
const projectedSets = otherSets.map((candidateSet) => ({
|
|
2933
|
+
...candidateSet,
|
|
2934
|
+
candidates: candidateSet.candidates.filter(
|
|
2935
|
+
(otherCandidate) =>
|
|
2936
|
+
candidatesAreCompatible(
|
|
2937
|
+
candidate,
|
|
2938
|
+
otherCandidate,
|
|
2939
|
+
),
|
|
2940
|
+
),
|
|
2941
|
+
}))
|
|
2942
|
+
const projectedCounts = projectedSets.map(
|
|
2943
|
+
(candidateSet) => candidateSet.candidates.length,
|
|
2944
|
+
)
|
|
2945
|
+
return {
|
|
2946
|
+
candidate,
|
|
2947
|
+
projectedSets,
|
|
2948
|
+
minimumProjectedCount:
|
|
2949
|
+
projectedCounts.length === 0
|
|
2950
|
+
? Number.POSITIVE_INFINITY
|
|
2951
|
+
: Math.min(...projectedCounts),
|
|
2952
|
+
totalProjectedCount: projectedCounts.reduce(
|
|
2953
|
+
(total, count) => total + count,
|
|
2954
|
+
0,
|
|
2955
|
+
),
|
|
2956
|
+
}
|
|
2957
|
+
})
|
|
2958
|
+
.filter((action) => action.minimumProjectedCount !== 0)
|
|
2959
|
+
.toSorted(
|
|
2960
|
+
(first, second) =>
|
|
2961
|
+
second.minimumProjectedCount -
|
|
2962
|
+
first.minimumProjectedCount ||
|
|
2963
|
+
second.totalProjectedCount -
|
|
2964
|
+
first.totalProjectedCount,
|
|
2965
|
+
)
|
|
2966
|
+
for (const action of actions) {
|
|
2967
|
+
alternatePlaneSearchStates++
|
|
2968
|
+
const selected = selectCompatiblePlaneRoutes(
|
|
2969
|
+
action.projectedSets,
|
|
2970
|
+
[...selectedCandidates, action.candidate],
|
|
2971
|
+
)
|
|
2972
|
+
if (selected) return selected
|
|
2973
|
+
if (
|
|
2974
|
+
alternatePlaneSearchStates >=
|
|
2975
|
+
maximumAlternatePlaneSearchStates
|
|
2976
|
+
) {
|
|
2977
|
+
break
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
if (
|
|
2981
|
+
alternatePlaneSearchStates >=
|
|
2982
|
+
maximumAlternatePlaneSearchStates
|
|
2983
|
+
) {
|
|
2984
|
+
break
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
alternatePlaneFailureCountByBusId.set(
|
|
2988
|
+
selectedSet.planeBus.busId,
|
|
2989
|
+
(alternatePlaneFailureCountByBusId.get(
|
|
2990
|
+
selectedSet.planeBus.busId,
|
|
2991
|
+
) ?? 0) + 1,
|
|
2992
|
+
)
|
|
2993
|
+
return null
|
|
2994
|
+
}
|
|
2995
|
+
const selectedCandidates = selectCompatiblePlaneRoutes(
|
|
2996
|
+
candidateSets,
|
|
2997
|
+
[],
|
|
2998
|
+
)
|
|
2999
|
+
alternatePlanePlans = selectedCandidates
|
|
3000
|
+
? [
|
|
3001
|
+
...feasibleAlternatePlanePlans,
|
|
3002
|
+
...selectedCandidates.flatMap(
|
|
3003
|
+
(candidate) => candidate.plans,
|
|
3004
|
+
),
|
|
3005
|
+
]
|
|
3006
|
+
: null
|
|
3007
|
+
} else {
|
|
3008
|
+
alternatePlanePlans = routeAlternatePlaneBuses(
|
|
3009
|
+
orderedAlternatePlaneBuses,
|
|
3010
|
+
feasibleAlternatePlanePlans,
|
|
3011
|
+
)
|
|
3012
|
+
}
|
|
3013
|
+
debugDense(
|
|
3014
|
+
"plane-route:alternate-search",
|
|
3015
|
+
alternatePlanePlans ? "complete" : "failed",
|
|
3016
|
+
alternatePlaneSearchStates,
|
|
3017
|
+
`depth:${deepestAlternatePlaneSearchIndex}/${orderedAlternatePlaneBuses.length}`,
|
|
3018
|
+
[...alternatePlaneFailureCountByBusId].toSorted(
|
|
3019
|
+
([, first], [, second]) => second - first,
|
|
3020
|
+
),
|
|
3021
|
+
)
|
|
3022
|
+
if (!alternatePlanePlans) return null
|
|
3023
|
+
feasibleAlternatePlanePlans = alternatePlanePlans
|
|
3024
|
+
}
|
|
3025
|
+
let planeBusIdsRoutedWithoutDogbones = new Set<string>()
|
|
3026
|
+
let allBlockingSegments = [...blockingSegments]
|
|
3027
|
+
let alternateBlockingVias: {
|
|
3028
|
+
connectionIndex: number
|
|
3029
|
+
center: { x: number; y: number }
|
|
3030
|
+
diameter: number
|
|
3031
|
+
spanLayers: readonly string[]
|
|
3032
|
+
}[] = []
|
|
3033
|
+
let planeBusesToMatch = [...planeBuses]
|
|
3034
|
+
let candidateCountByConnectionIndex = new Map<number, number>()
|
|
3035
|
+
let candidatePointsByConnectionIndex = new Map<
|
|
3036
|
+
number,
|
|
3037
|
+
{ x: number; y: number }[]
|
|
3038
|
+
>()
|
|
3039
|
+
const refreshPlaneDogboneCandidates = (): void => {
|
|
3040
|
+
planeBusIdsRoutedWithoutDogbones = new Set(
|
|
3041
|
+
feasibleAlternatePlanePlans.map((plan) => plan.busId),
|
|
3042
|
+
)
|
|
3043
|
+
const alternateBlockingSegments =
|
|
3044
|
+
feasibleAlternatePlanePlans.flatMap((plan) =>
|
|
3045
|
+
[...plan.segments, ...(plan.planeEndpointSegments ?? [])].map(
|
|
3046
|
+
(segment) => ({
|
|
3047
|
+
connectionIndex: plan.connectionIndex,
|
|
3048
|
+
segment,
|
|
3049
|
+
}),
|
|
3050
|
+
),
|
|
3051
|
+
)
|
|
3052
|
+
allBlockingSegments = [
|
|
3053
|
+
...blockingSegments,
|
|
3054
|
+
...alternateBlockingSegments,
|
|
3055
|
+
]
|
|
3056
|
+
alternateBlockingVias = feasibleAlternatePlanePlans.flatMap(
|
|
3057
|
+
(plan) =>
|
|
3058
|
+
[
|
|
3059
|
+
plan.via,
|
|
3060
|
+
...(plan.additionalVias ?? []),
|
|
3061
|
+
plan.planeEndpointVia,
|
|
3062
|
+
].flatMap((via) =>
|
|
3063
|
+
via
|
|
3064
|
+
? [
|
|
3065
|
+
{
|
|
3066
|
+
connectionIndex: plan.connectionIndex,
|
|
3067
|
+
center: via.center,
|
|
3068
|
+
diameter: via.diameter,
|
|
3069
|
+
spanLayers: via.spanLayers,
|
|
3070
|
+
},
|
|
3071
|
+
]
|
|
3072
|
+
: [],
|
|
3073
|
+
),
|
|
3074
|
+
)
|
|
3075
|
+
planeBusesToMatch = planeBuses.filter(
|
|
3076
|
+
(bus) => !planeBusIdsRoutedWithoutDogbones.has(bus.busId),
|
|
3077
|
+
)
|
|
3078
|
+
candidateCountByConnectionIndex = new Map()
|
|
3079
|
+
candidatePointsByConnectionIndex = new Map()
|
|
3080
|
+
for (const candidate of getComponentDogboneViaSiteCandidates(
|
|
3081
|
+
planeBusesToMatch,
|
|
3082
|
+
{
|
|
3083
|
+
viaDiameter: this.config.viaDiameter,
|
|
3084
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
3085
|
+
traceWidth: this.config.traceWidth,
|
|
3086
|
+
clearance: this.config.clearance,
|
|
3087
|
+
blockingSegments: allBlockingSegments,
|
|
3088
|
+
blockingVias: alternateBlockingVias,
|
|
3089
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
3090
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
3091
|
+
canShareCopper,
|
|
3092
|
+
},
|
|
3093
|
+
)) {
|
|
3094
|
+
candidateCountByConnectionIndex.set(
|
|
3095
|
+
candidate.connectionIndex,
|
|
3096
|
+
(candidateCountByConnectionIndex.get(
|
|
3097
|
+
candidate.connectionIndex,
|
|
3098
|
+
) ?? 0) + 1,
|
|
3099
|
+
)
|
|
3100
|
+
const points =
|
|
3101
|
+
candidatePointsByConnectionIndex.get(
|
|
3102
|
+
candidate.connectionIndex,
|
|
3103
|
+
) ?? []
|
|
3104
|
+
points.push(candidate.point)
|
|
3105
|
+
candidatePointsByConnectionIndex.set(
|
|
3106
|
+
candidate.connectionIndex,
|
|
3107
|
+
points,
|
|
3108
|
+
)
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
refreshPlaneDogboneCandidates()
|
|
3112
|
+
for (
|
|
3113
|
+
let promotionPass = 0;
|
|
3114
|
+
promotionPass < planeBuses.length;
|
|
3115
|
+
promotionPass++
|
|
3116
|
+
) {
|
|
3117
|
+
const zeroCandidatePlaneBuses = planeBusesToMatch.filter(
|
|
3118
|
+
(bus) =>
|
|
3119
|
+
!candidateCountByConnectionIndex.has(
|
|
3120
|
+
bus.connections[0]!.connectionIndex,
|
|
3121
|
+
),
|
|
3122
|
+
)
|
|
3123
|
+
if (zeroCandidatePlaneBuses.length === 0) break
|
|
3124
|
+
debugDense(
|
|
3125
|
+
"plane-route:promote-zero-candidates",
|
|
3126
|
+
zeroCandidatePlaneBuses.map((bus) => bus.busId),
|
|
3127
|
+
)
|
|
3128
|
+
if (
|
|
3129
|
+
zeroCandidatePlaneBuses.some((bus) =>
|
|
3130
|
+
matchedPlaneBuses.includes(bus),
|
|
3131
|
+
)
|
|
3132
|
+
) {
|
|
3133
|
+
return null
|
|
3134
|
+
}
|
|
3135
|
+
for (const planeBus of zeroCandidatePlaneBuses) {
|
|
3136
|
+
const targetLayer = params.busLayerAssignments[planeBus.busId]
|
|
3137
|
+
if (!targetLayer) return null
|
|
3138
|
+
const promotedPlans = routeBusAlternatives(
|
|
3139
|
+
{
|
|
3140
|
+
srj: this.routingSrj,
|
|
3141
|
+
bus: planeBus,
|
|
3142
|
+
targetLayer,
|
|
3143
|
+
acceptedPlans: [
|
|
3144
|
+
...candidatePlans,
|
|
3145
|
+
...feasibleAlternatePlanePlans,
|
|
3146
|
+
],
|
|
3147
|
+
layerNames: this.config.layerNames,
|
|
3148
|
+
traceWidth: this.config.traceWidth,
|
|
3149
|
+
viaDiameter: this.config.viaDiameter,
|
|
3150
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
3151
|
+
clearance: this.config.clearance,
|
|
3152
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
3153
|
+
allowBlindAndBuriedVias: false,
|
|
3154
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
3155
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
3156
|
+
},
|
|
3157
|
+
8,
|
|
3158
|
+
)[0]
|
|
3159
|
+
if (!promotedPlans) {
|
|
3160
|
+
debugDense("plane-route:promote-failed", planeBus.busId)
|
|
3161
|
+
return null
|
|
3162
|
+
}
|
|
3163
|
+
feasibleAlternatePlanePlans.push(...promotedPlans)
|
|
3164
|
+
}
|
|
3165
|
+
refreshPlaneDogboneCandidates()
|
|
3166
|
+
}
|
|
3167
|
+
const debugPlaneMatchOrder =
|
|
3168
|
+
process.env.FANOUT_DEBUG_PLANE_MATCH_ORDER?.split(",") ?? []
|
|
3169
|
+
const incrementalPlaneBuses = planeBusesToMatch.toSorted(
|
|
3170
|
+
(first, second) => {
|
|
3171
|
+
const candidateCountDifference =
|
|
3172
|
+
(candidateCountByConnectionIndex.get(
|
|
3173
|
+
first.connections[0]!.connectionIndex,
|
|
3174
|
+
) ?? 0) -
|
|
3175
|
+
(candidateCountByConnectionIndex.get(
|
|
3176
|
+
second.connections[0]!.connectionIndex,
|
|
3177
|
+
) ?? 0)
|
|
3178
|
+
if (candidateCountDifference !== 0) {
|
|
3179
|
+
return candidateCountDifference
|
|
3180
|
+
}
|
|
3181
|
+
const firstPriority = debugPlaneMatchOrder.indexOf(first.busId)
|
|
3182
|
+
const secondPriority = debugPlaneMatchOrder.indexOf(
|
|
3183
|
+
second.busId,
|
|
3184
|
+
)
|
|
3185
|
+
const priorityDifference =
|
|
3186
|
+
(firstPriority < 0
|
|
3187
|
+
? debugPlaneMatchOrder.length
|
|
3188
|
+
: firstPriority) -
|
|
3189
|
+
(secondPriority < 0
|
|
3190
|
+
? debugPlaneMatchOrder.length
|
|
3191
|
+
: secondPriority)
|
|
3192
|
+
if (priorityDifference !== 0) return priorityDifference
|
|
3193
|
+
return (
|
|
3194
|
+
first.connections[0]!.connectionIndex -
|
|
3195
|
+
second.connections[0]!.connectionIndex
|
|
3196
|
+
)
|
|
3197
|
+
},
|
|
3198
|
+
)
|
|
3199
|
+
for (const planeBus of incrementalPlaneBuses) {
|
|
3200
|
+
if (matchedPlaneBuses.includes(planeBus)) continue
|
|
3201
|
+
if (
|
|
3202
|
+
process.env.FANOUT_DEBUG_PLANE_CANDIDATES?.split(",").includes(
|
|
3203
|
+
planeBus.busId,
|
|
3204
|
+
)
|
|
3205
|
+
) {
|
|
3206
|
+
debugDense(
|
|
3207
|
+
"plane-match:candidates",
|
|
3208
|
+
planeBus.busId,
|
|
3209
|
+
candidatePointsByConnectionIndex.get(
|
|
3210
|
+
planeBus.connections[0]!.connectionIndex,
|
|
3211
|
+
),
|
|
3212
|
+
)
|
|
3213
|
+
}
|
|
3214
|
+
const nextViaPoints = matchComponentDogboneViaSites(
|
|
3215
|
+
[...matchedPlaneBuses, planeBus, ...boundaryBuses],
|
|
3216
|
+
{
|
|
3217
|
+
viaDiameter: this.config.viaDiameter,
|
|
3218
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
3219
|
+
traceWidth: this.config.traceWidth,
|
|
3220
|
+
clearance: this.config.clearance,
|
|
3221
|
+
maximumSearchStates: 100_000,
|
|
3222
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
3223
|
+
preferBoundaryOutwardByBusId,
|
|
3224
|
+
fixedViaPointsByConnectionIndex: incrementalViaPoints,
|
|
3225
|
+
blockingSegments: allBlockingSegments,
|
|
3226
|
+
blockingVias: alternateBlockingVias,
|
|
3227
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
3228
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
3229
|
+
canShareCopper,
|
|
3230
|
+
},
|
|
3231
|
+
)
|
|
3232
|
+
debugDense(
|
|
3233
|
+
nextViaPoints
|
|
3234
|
+
? "plane-match:incremental"
|
|
3235
|
+
: "plane-match:incremental-failed",
|
|
3236
|
+
planeBus.busId,
|
|
3237
|
+
candidateCountByConnectionIndex.get(
|
|
3238
|
+
planeBus.connections[0]!.connectionIndex,
|
|
3239
|
+
) ?? 0,
|
|
3240
|
+
nextViaPoints?.get(planeBus.connections[0]!.connectionIndex),
|
|
3241
|
+
nextViaPoints?.size ?? "failed",
|
|
3242
|
+
)
|
|
3243
|
+
if (!nextViaPoints) return null
|
|
3244
|
+
incrementalViaPoints = new Map([
|
|
3245
|
+
...incrementalViaPoints,
|
|
3246
|
+
...nextViaPoints,
|
|
3247
|
+
])
|
|
3248
|
+
matchedPlaneBuses.push(planeBus)
|
|
1540
3249
|
}
|
|
1541
|
-
|
|
3250
|
+
matchedPlaneBusesInRoutingOrder = matchedPlaneBuses.filter(
|
|
3251
|
+
(bus) => !planeBusIdsRoutedWithoutDogbones.has(bus.busId),
|
|
3252
|
+
)
|
|
3253
|
+
debugDense(
|
|
3254
|
+
"plane-match:incremental-complete",
|
|
3255
|
+
incrementalViaPoints.size,
|
|
3256
|
+
)
|
|
3257
|
+
return incrementalViaPoints
|
|
1542
3258
|
}
|
|
1543
|
-
fixedViaPointsByConnectionIndex = previousFixedViaPoints
|
|
1544
|
-
}
|
|
1545
|
-
if (selectedBusIndex < 0) {
|
|
1546
|
-
matchedRoutingSucceeded = false
|
|
1547
|
-
break
|
|
1548
|
-
}
|
|
1549
|
-
remainingBoundaryBuses.splice(selectedBusIndex, 1)
|
|
1550
|
-
}
|
|
1551
|
-
if (matchedRoutingSucceeded) {
|
|
1552
|
-
let feasibleViaPoints: Map<number, { x: number; y: number }> | null =
|
|
1553
|
-
null
|
|
1554
|
-
const matchViaPointsAroundPlans = (
|
|
1555
|
-
candidatePlans: readonly FanoutRoutePlan[],
|
|
1556
|
-
): Map<number, { x: number; y: number }> | null => {
|
|
1557
|
-
const fixedBoundaryViaPoints = new Map(
|
|
1558
|
-
candidatePlans.flatMap((plan) =>
|
|
1559
|
-
plan.via
|
|
1560
|
-
? [[plan.connectionIndex, plan.via.center] as const]
|
|
1561
|
-
: [],
|
|
1562
|
-
),
|
|
1563
|
-
)
|
|
1564
3259
|
return matchComponentDogboneViaSites(
|
|
1565
3260
|
[...planeBuses, ...boundaryBuses],
|
|
1566
3261
|
{
|
|
@@ -1572,16 +3267,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1572
3267
|
preferredBoundaryPerpendicularSideByBusId,
|
|
1573
3268
|
preferBoundaryOutwardByBusId,
|
|
1574
3269
|
fixedViaPointsByConnectionIndex: fixedBoundaryViaPoints,
|
|
1575
|
-
blockingSegments
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
segment,
|
|
1579
|
-
})),
|
|
1580
|
-
),
|
|
3270
|
+
blockingSegments,
|
|
3271
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
3272
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1581
3273
|
canShareCopper,
|
|
1582
3274
|
},
|
|
1583
3275
|
)
|
|
1584
3276
|
}
|
|
3277
|
+
debugDense("length-match:start", matchedPlans.length)
|
|
1585
3278
|
const matchedLengthResult = matchBusPlanLengths({
|
|
1586
3279
|
plans: matchedPlans,
|
|
1587
3280
|
preparedBuses: this.preparedBuses,
|
|
@@ -1598,22 +3291,45 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1598
3291
|
return true
|
|
1599
3292
|
},
|
|
1600
3293
|
})
|
|
3294
|
+
debugDense(
|
|
3295
|
+
"length-match:complete",
|
|
3296
|
+
matchedLengthResult.plans?.length ?? "failed",
|
|
3297
|
+
)
|
|
3298
|
+
this.setInProgressPlans({
|
|
3299
|
+
phase: "match-dense-boundary-lengths",
|
|
3300
|
+
plans: matchedLengthResult.plans ?? matchedPlans,
|
|
3301
|
+
strategy: "default",
|
|
3302
|
+
unitIndex: ++denseWorkUnitIndex,
|
|
3303
|
+
unitCount: denseWorkUnitCount,
|
|
3304
|
+
})
|
|
3305
|
+
yield
|
|
1601
3306
|
if (matchedLengthResult.plans) {
|
|
1602
3307
|
matchedPlans = matchedLengthResult.plans
|
|
1603
3308
|
const rematchedViaPoints =
|
|
1604
3309
|
feasibleViaPoints ?? matchViaPointsAroundPlans(matchedPlans)
|
|
1605
3310
|
if (rematchedViaPoints) {
|
|
1606
3311
|
fixedViaPointsByConnectionIndex = rematchedViaPoints
|
|
3312
|
+
matchedPlans.push(...feasibleAlternatePlanePlans)
|
|
1607
3313
|
} else {
|
|
1608
3314
|
matchedRoutingSucceeded = false
|
|
1609
3315
|
}
|
|
1610
3316
|
} else {
|
|
1611
3317
|
matchedRoutingSucceeded = false
|
|
1612
3318
|
}
|
|
3319
|
+
this.setInProgressPlans({
|
|
3320
|
+
phase: "rematch-dense-via-sites",
|
|
3321
|
+
plans: matchedPlans,
|
|
3322
|
+
strategy: "default",
|
|
3323
|
+
unitIndex: denseWorkUnitIndex,
|
|
3324
|
+
unitCount: denseWorkUnitCount,
|
|
3325
|
+
})
|
|
3326
|
+
yield
|
|
1613
3327
|
}
|
|
1614
3328
|
if (matchedRoutingSucceeded) {
|
|
1615
|
-
for (const bus of planeBuses) {
|
|
3329
|
+
for (const bus of matchedPlaneBusesInRoutingOrder ?? planeBuses) {
|
|
3330
|
+
debugDense("plane-route:start", bus.busId)
|
|
1616
3331
|
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
3332
|
+
const blockingBusCounts = new Map<string, number>()
|
|
1617
3333
|
const busPlans = targetLayer
|
|
1618
3334
|
? routeBus({
|
|
1619
3335
|
srj: this.routingSrj,
|
|
@@ -1629,17 +3345,36 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1629
3345
|
allowBlindAndBuriedVias: false,
|
|
1630
3346
|
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1631
3347
|
staticClearanceCache: this.routeStaticClearanceCache,
|
|
3348
|
+
blockingBusCounts,
|
|
1632
3349
|
fixedViaPointsByConnectionIndex,
|
|
1633
3350
|
})
|
|
1634
3351
|
: null
|
|
1635
3352
|
if (!busPlans) {
|
|
3353
|
+
debugDense(
|
|
3354
|
+
"plane-route:failed",
|
|
3355
|
+
bus.busId,
|
|
3356
|
+
fixedViaPointsByConnectionIndex.get(
|
|
3357
|
+
bus.connections[0]!.connectionIndex,
|
|
3358
|
+
),
|
|
3359
|
+
[...blockingBusCounts],
|
|
3360
|
+
)
|
|
1636
3361
|
matchedRoutingSucceeded = false
|
|
1637
3362
|
break
|
|
1638
3363
|
}
|
|
1639
3364
|
matchedPlans.push(...busPlans)
|
|
3365
|
+
debugDense("plane-route:complete", bus.busId)
|
|
3366
|
+
this.setInProgressPlans({
|
|
3367
|
+
phase: "route-dense-plane-buses",
|
|
3368
|
+
plans: matchedPlans,
|
|
3369
|
+
strategy: "default",
|
|
3370
|
+
unitIndex: ++denseWorkUnitIndex,
|
|
3371
|
+
unitCount: denseWorkUnitCount,
|
|
3372
|
+
busId: bus.busId,
|
|
3373
|
+
})
|
|
3374
|
+
yield
|
|
1640
3375
|
}
|
|
1641
3376
|
}
|
|
1642
|
-
|
|
3377
|
+
const densePlansAreClear =
|
|
1643
3378
|
matchedRoutingSucceeded &&
|
|
1644
3379
|
fanoutPlansAreClear({
|
|
1645
3380
|
plans: matchedPlans,
|
|
@@ -1649,11 +3384,19 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1649
3384
|
allowBlindAndBuriedVias: false,
|
|
1650
3385
|
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1651
3386
|
})
|
|
1652
|
-
|
|
3387
|
+
debugDense(
|
|
3388
|
+
"dense-validation",
|
|
3389
|
+
matchedRoutingSucceeded,
|
|
3390
|
+
matchedPlans.length,
|
|
3391
|
+
densePlansAreClear,
|
|
3392
|
+
)
|
|
3393
|
+
if (densePlansAreClear) {
|
|
1653
3394
|
return { plans: matchedPlans, failedBusIds: [] }
|
|
1654
3395
|
}
|
|
1655
3396
|
}
|
|
1656
3397
|
|
|
3398
|
+
if (process.env.FANOUT_DEBUG_DENSE_ONLY === "1") return null
|
|
3399
|
+
|
|
1657
3400
|
const maximumStates = 8
|
|
1658
3401
|
const getBoundaryStates = (
|
|
1659
3402
|
alternativesPerBoundaryBus: number,
|
|
@@ -2003,11 +3746,11 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2003
3746
|
return bestState
|
|
2004
3747
|
}
|
|
2005
3748
|
|
|
2006
|
-
private
|
|
3749
|
+
private *evaluateAssignmentWithStrategySteps(
|
|
2007
3750
|
assignmentIndex: number,
|
|
2008
3751
|
busLayerAssignments: Readonly<Record<string, string>>,
|
|
2009
3752
|
routingStrategy: RoutingStrategy,
|
|
2010
|
-
): EvaluatedAssignment {
|
|
3753
|
+
): Generator<FanoutWorkYield, EvaluatedAssignment, unknown> {
|
|
2011
3754
|
let plans: AssignmentAttempt["plans"] = []
|
|
2012
3755
|
let failedBusIds: string[] = []
|
|
2013
3756
|
let blockingBusCounts = new Map<string, number>()
|
|
@@ -2026,21 +3769,53 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2026
3769
|
clearance: this.config.clearance,
|
|
2027
3770
|
borderDistribution: this.config.borderDistribution,
|
|
2028
3771
|
}
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
:
|
|
3772
|
+
let singleLayerPlans = routeSingleLayerWithPushAndShove(singleLayerParams)
|
|
3773
|
+
if (!singleLayerPlans && this.config.singleLayerAdaptiveExits) {
|
|
3774
|
+
this.setInProgressPlans({
|
|
3775
|
+
phase: "prepare-single-layer-adaptive-exits",
|
|
3776
|
+
plans,
|
|
3777
|
+
strategy: routingStrategy,
|
|
3778
|
+
})
|
|
3779
|
+
yield
|
|
3780
|
+
this.setInProgressPlans({
|
|
3781
|
+
phase: "route-single-layer-adaptive-exits",
|
|
3782
|
+
plans,
|
|
3783
|
+
strategy: routingStrategy,
|
|
3784
|
+
})
|
|
3785
|
+
this.activeAdaptiveVisualization = null
|
|
3786
|
+
const adaptiveSolver = this.createWorkSolver(
|
|
3787
|
+
"SingleLayerAdaptiveExitSolver",
|
|
3788
|
+
routeSingleLayerWithAdaptiveExitsSteps({
|
|
3789
|
+
...singleLayerParams,
|
|
3790
|
+
availableBoundaryRegions: resolveAvailableBoundaryRegions(
|
|
3791
|
+
this.options.availableCornersAndSides,
|
|
3792
|
+
),
|
|
3793
|
+
onProgress: (visualization, adaptiveStats) => {
|
|
3794
|
+
this.activeAdaptiveVisualization = visualization
|
|
3795
|
+
this.stats = { ...this.stats, ...adaptiveStats }
|
|
3796
|
+
},
|
|
3797
|
+
}),
|
|
3798
|
+
undefined,
|
|
3799
|
+
() => this.visualizeAdaptiveRoutingState(),
|
|
3800
|
+
)
|
|
3801
|
+
singleLayerPlans = (yield {
|
|
3802
|
+
type: "subsolver",
|
|
3803
|
+
solver: adaptiveSolver,
|
|
3804
|
+
}) as FanoutRoutePlan[] | null
|
|
3805
|
+
}
|
|
2039
3806
|
if (singleLayerPlans) {
|
|
2040
3807
|
plans.push(...singleLayerPlans)
|
|
2041
3808
|
} else {
|
|
2042
3809
|
failedBusIds.push(...this.preparedBuses.map((bus) => bus.busId))
|
|
2043
3810
|
}
|
|
3811
|
+
this.setInProgressPlans({
|
|
3812
|
+
phase: "route-single-layer",
|
|
3813
|
+
plans,
|
|
3814
|
+
strategy: routingStrategy,
|
|
3815
|
+
unitIndex: 1,
|
|
3816
|
+
unitCount: 1,
|
|
3817
|
+
})
|
|
3818
|
+
yield
|
|
2044
3819
|
}
|
|
2045
3820
|
const busesInRoutingOrder = [...this.preparedBuses].sort((a, b) => {
|
|
2046
3821
|
const aUsesCoordinatedWinding = busUsesCoordinatedWinding(a)
|
|
@@ -2076,23 +3851,56 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2076
3851
|
)
|
|
2077
3852
|
})
|
|
2078
3853
|
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
3854
|
+
let mixedTerminationState: MixedTerminationState | null = null
|
|
3855
|
+
if (!useSingleLayerPushAndShove && routingStrategy === "default") {
|
|
3856
|
+
const denseSolver = this.createWorkSolver(
|
|
3857
|
+
"DenseMixedTerminationSolver",
|
|
3858
|
+
this.routeDenseThroughAllMixedTerminationSteps({
|
|
3859
|
+
busLayerAssignments,
|
|
3860
|
+
busesInRoutingOrder,
|
|
3861
|
+
}),
|
|
3862
|
+
() => {
|
|
3863
|
+
const workUnit = Number(this.stats.workUnit ?? 0)
|
|
3864
|
+
const workUnitCount = Number(this.stats.workUnitCount ?? 0)
|
|
3865
|
+
return workUnitCount > 0 ? workUnit / workUnitCount : 0
|
|
3866
|
+
},
|
|
3867
|
+
)
|
|
3868
|
+
mixedTerminationState = (yield {
|
|
3869
|
+
type: "subsolver",
|
|
3870
|
+
solver: denseSolver,
|
|
3871
|
+
}) as MixedTerminationState | null
|
|
3872
|
+
}
|
|
3873
|
+
if (
|
|
3874
|
+
!mixedTerminationState &&
|
|
3875
|
+
!useSingleLayerPushAndShove &&
|
|
3876
|
+
routingStrategy === "default" &&
|
|
3877
|
+
process.env.FANOUT_DEBUG_DENSE_ONLY === "1"
|
|
3878
|
+
) {
|
|
3879
|
+
mixedTerminationState = {
|
|
3880
|
+
plans: [],
|
|
3881
|
+
failedBusIds: this.preparedBuses.map((bus) => bus.busId),
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
2086
3884
|
|
|
2087
3885
|
if (mixedTerminationState) {
|
|
2088
3886
|
plans = mixedTerminationState.plans
|
|
2089
3887
|
failedBusIds = mixedTerminationState.failedBusIds
|
|
3888
|
+
this.setInProgressPlans({
|
|
3889
|
+
phase: "route-dense-mixed-terminations",
|
|
3890
|
+
plans,
|
|
3891
|
+
strategy: routingStrategy,
|
|
3892
|
+
unitIndex: this.preparedBuses.length,
|
|
3893
|
+
unitCount: this.preparedBuses.length,
|
|
3894
|
+
})
|
|
3895
|
+
yield
|
|
2090
3896
|
}
|
|
2091
3897
|
|
|
2092
3898
|
let routingPrefixKey = `${routingStrategy}|`
|
|
3899
|
+
let routedBusIndex = 0
|
|
2093
3900
|
for (const bus of useSingleLayerPushAndShove || mixedTerminationState
|
|
2094
3901
|
? []
|
|
2095
3902
|
: busesInRoutingOrder) {
|
|
3903
|
+
routedBusIndex++
|
|
2096
3904
|
const targetLayer = busLayerAssignments[bus.busId]
|
|
2097
3905
|
if (!targetLayer) {
|
|
2098
3906
|
throw new Error(
|
|
@@ -2110,6 +3918,15 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2110
3918
|
plans = [...cachedPrefix.plans]
|
|
2111
3919
|
failedBusIds = [...cachedPrefix.failedBusIds]
|
|
2112
3920
|
blockingBusCounts = new Map(cachedPrefix.blockingBusCounts)
|
|
3921
|
+
this.setInProgressPlans({
|
|
3922
|
+
phase: "route-assignment",
|
|
3923
|
+
plans,
|
|
3924
|
+
strategy: routingStrategy,
|
|
3925
|
+
unitIndex: routedBusIndex,
|
|
3926
|
+
unitCount: busesInRoutingOrder.length,
|
|
3927
|
+
busId: bus.busId,
|
|
3928
|
+
})
|
|
3929
|
+
yield
|
|
2113
3930
|
continue
|
|
2114
3931
|
}
|
|
2115
3932
|
const currentBusBlockingCounts = new Map<string, number>()
|
|
@@ -2145,6 +3962,15 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2145
3962
|
failedBusIds: [...failedBusIds],
|
|
2146
3963
|
blockingBusCounts: new Map(blockingBusCounts),
|
|
2147
3964
|
})
|
|
3965
|
+
this.setInProgressPlans({
|
|
3966
|
+
phase: "route-assignment",
|
|
3967
|
+
plans,
|
|
3968
|
+
strategy: routingStrategy,
|
|
3969
|
+
unitIndex: routedBusIndex,
|
|
3970
|
+
unitCount: busesInRoutingOrder.length,
|
|
3971
|
+
busId: bus.busId,
|
|
3972
|
+
})
|
|
3973
|
+
yield
|
|
2148
3974
|
}
|
|
2149
3975
|
|
|
2150
3976
|
let validationIssues: FanoutAttemptSummary["validationIssues"]
|
|
@@ -2218,6 +4044,11 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2218
4044
|
score,
|
|
2219
4045
|
...(validationIssues ? { validationIssues } : {}),
|
|
2220
4046
|
}
|
|
4047
|
+
this.setInProgressPlans({
|
|
4048
|
+
phase: "finalize-assignment-strategy",
|
|
4049
|
+
plans,
|
|
4050
|
+
strategy: routingStrategy,
|
|
4051
|
+
})
|
|
2221
4052
|
return {
|
|
2222
4053
|
summary,
|
|
2223
4054
|
plans,
|
|
@@ -2228,15 +4059,16 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2228
4059
|
}
|
|
2229
4060
|
}
|
|
2230
4061
|
|
|
2231
|
-
private
|
|
4062
|
+
private *evaluateAssignmentSteps(
|
|
2232
4063
|
assignmentIndex: number,
|
|
2233
4064
|
busLayerAssignments: Readonly<Record<string, string>>,
|
|
2234
|
-
): EvaluatedAssignment {
|
|
2235
|
-
let bestAttempt = this.
|
|
4065
|
+
): Generator<FanoutWorkYield, EvaluatedAssignment, unknown> {
|
|
4066
|
+
let bestAttempt = yield* this.evaluateAssignmentWithStrategySteps(
|
|
2236
4067
|
assignmentIndex,
|
|
2237
4068
|
busLayerAssignments,
|
|
2238
4069
|
"default",
|
|
2239
4070
|
)
|
|
4071
|
+
if (process.env.FANOUT_DEBUG_DENSE_ONLY === "1") return bestAttempt
|
|
2240
4072
|
if (
|
|
2241
4073
|
bestAttempt.summary.routedConnectionCount ===
|
|
2242
4074
|
this.inputSrj.connections.length &&
|
|
@@ -2246,7 +4078,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2246
4078
|
}
|
|
2247
4079
|
|
|
2248
4080
|
for (const routingStrategy of ["group-by-layer", "deep-first"] as const) {
|
|
2249
|
-
const attempt = this.
|
|
4081
|
+
const attempt = yield* this.evaluateAssignmentWithStrategySteps(
|
|
2250
4082
|
assignmentIndex,
|
|
2251
4083
|
busLayerAssignments,
|
|
2252
4084
|
routingStrategy,
|
|
@@ -2274,10 +4106,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2274
4106
|
* power or signal lane cannot starve a later bus before the solver explores
|
|
2275
4107
|
* an alternate layer/track combination.
|
|
2276
4108
|
*/
|
|
2277
|
-
private
|
|
4109
|
+
private *evaluateGroupedBeamSteps(
|
|
2278
4110
|
assignmentIndex: number,
|
|
2279
4111
|
groupByDirection = false,
|
|
2280
|
-
): EvaluatedAssignment | null {
|
|
4112
|
+
): Generator<FanoutWorkYield, EvaluatedAssignment | null, unknown> {
|
|
2281
4113
|
if (this.config.escapeLayers.length < 2) return null
|
|
2282
4114
|
if (this.preparedBuses.length > 56) return null
|
|
2283
4115
|
const totalConnections = this.inputSrj.connections.length
|
|
@@ -2386,7 +4218,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2386
4218
|
)
|
|
2387
4219
|
}
|
|
2388
4220
|
|
|
4221
|
+
let searchedBusIndex = 0
|
|
2389
4222
|
for (const bus of busesInSearchOrder) {
|
|
4223
|
+
searchedBusIndex++
|
|
2390
4224
|
const nextStates: GroupedBeamState[] = []
|
|
2391
4225
|
for (const state of states) {
|
|
2392
4226
|
const candidateLayers =
|
|
@@ -2474,6 +4308,15 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2474
4308
|
states.push(state)
|
|
2475
4309
|
if (states.length >= beamWidth) break
|
|
2476
4310
|
}
|
|
4311
|
+
this.setInProgressPlans({
|
|
4312
|
+
phase: "route-grouped-beam",
|
|
4313
|
+
plans: states[0]?.plans ?? [],
|
|
4314
|
+
strategy: "grouped-beam",
|
|
4315
|
+
unitIndex: searchedBusIndex,
|
|
4316
|
+
unitCount: busesInSearchOrder.length,
|
|
4317
|
+
busId: bus.busId,
|
|
4318
|
+
})
|
|
4319
|
+
yield
|
|
2477
4320
|
}
|
|
2478
4321
|
|
|
2479
4322
|
let bestState: GroupedBeamState | undefined
|
|
@@ -2493,9 +4336,15 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2493
4336
|
(bus) => bus.maxLengthSkew !== undefined,
|
|
2494
4337
|
)
|
|
2495
4338
|
for (const state of states) {
|
|
2496
|
-
if (state.plans.length !== this.inputSrj.connections.length)
|
|
4339
|
+
if (state.plans.length !== this.inputSrj.connections.length) {
|
|
4340
|
+
yield
|
|
4341
|
+
continue
|
|
4342
|
+
}
|
|
2497
4343
|
const lengthMatching = this.matchCompletePlanLengths(state.plans)
|
|
2498
|
-
if (!lengthMatching.plans)
|
|
4344
|
+
if (!lengthMatching.plans) {
|
|
4345
|
+
yield
|
|
4346
|
+
continue
|
|
4347
|
+
}
|
|
2499
4348
|
const lengthMatchedPlans = lengthMatching.plans
|
|
2500
4349
|
const candidateOutput = buildOutputSimpleRouteJson({
|
|
2501
4350
|
inputSrj: this.inputSrj,
|
|
@@ -2505,6 +4354,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2505
4354
|
if (
|
|
2506
4355
|
!this.validateCompletePlans(lengthMatchedPlans, candidateOutput).valid
|
|
2507
4356
|
) {
|
|
4357
|
+
yield
|
|
2508
4358
|
continue
|
|
2509
4359
|
}
|
|
2510
4360
|
const candidateState = { ...state, plans: lengthMatchedPlans }
|
|
@@ -2523,6 +4373,12 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2523
4373
|
bestAdditionalViaCount = candidateAdditionalViaCount
|
|
2524
4374
|
}
|
|
2525
4375
|
if (!hasLengthConstraints) break
|
|
4376
|
+
this.setInProgressPlans({
|
|
4377
|
+
phase: "validate-grouped-beam",
|
|
4378
|
+
plans: lengthMatchedPlans,
|
|
4379
|
+
strategy: "grouped-beam",
|
|
4380
|
+
})
|
|
4381
|
+
yield
|
|
2526
4382
|
}
|
|
2527
4383
|
if (!bestState || !outputSrj) return null
|
|
2528
4384
|
const score = bestMatchedScore
|
|
@@ -2544,6 +4400,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2544
4400
|
}
|
|
2545
4401
|
}
|
|
2546
4402
|
|
|
4403
|
+
private *evaluateGroupedBeamAlternativesSteps(
|
|
4404
|
+
assignmentIndex: number,
|
|
4405
|
+
): Generator<FanoutWorkYield, EvaluatedAssignment | null, unknown> {
|
|
4406
|
+
const primaryAttempt = yield* this.evaluateGroupedBeamSteps(assignmentIndex)
|
|
4407
|
+
if (primaryAttempt) return primaryAttempt
|
|
4408
|
+
return yield* this.evaluateGroupedBeamSteps(assignmentIndex, true)
|
|
4409
|
+
}
|
|
4410
|
+
|
|
2547
4411
|
private prioritizeFailedBusRepairs(
|
|
2548
4412
|
assignment: Readonly<Record<string, string>>,
|
|
2549
4413
|
failedBusIds: readonly string[],
|
|
@@ -2710,52 +4574,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2710
4574
|
return targetedRepairSearchFinished
|
|
2711
4575
|
}
|
|
2712
4576
|
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
) {
|
|
2718
|
-
this.completeBestAttemptEndpoints()
|
|
2719
|
-
this.solved = true
|
|
2720
|
-
return
|
|
2721
|
-
}
|
|
2722
|
-
// Try the deterministic assignment and only its targeted repair queue
|
|
2723
|
-
// before paying for the grouped beam. If the beam cannot solve, continue
|
|
2724
|
-
// with the broader generated-assignment search below.
|
|
2725
|
-
if (this.shouldEvaluateGroupedBeam()) {
|
|
2726
|
-
this.groupedBeamEvaluated = true
|
|
2727
|
-
let beamAttempt = this.evaluateGroupedBeam(-1)
|
|
2728
|
-
if (!beamAttempt) {
|
|
2729
|
-
beamAttempt = this.evaluateGroupedBeam(-1, true)
|
|
2730
|
-
}
|
|
2731
|
-
if (beamAttempt) {
|
|
2732
|
-
this.attempts.push(beamAttempt.summary)
|
|
2733
|
-
if (
|
|
2734
|
-
!this.bestAttempt ||
|
|
2735
|
-
this.isAttemptBetter(beamAttempt, this.bestAttempt)
|
|
2736
|
-
) {
|
|
2737
|
-
this.bestAttempt = beamAttempt
|
|
2738
|
-
}
|
|
2739
|
-
const bestSummary = this.bestAttempt.summary
|
|
2740
|
-
this.stats = {
|
|
2741
|
-
assignment:
|
|
2742
|
-
bestSummary.assignmentIndex < 0
|
|
2743
|
-
? 0
|
|
2744
|
-
: bestSummary.assignmentIndex + 1,
|
|
2745
|
-
assignmentCount: this.config.maxLayerCombinations,
|
|
2746
|
-
routedBuses: `${bestSummary.routedBusCount}/${this.preparedBuses.length}`,
|
|
2747
|
-
routedConnections: `${bestSummary.routedConnectionCount}/${this.inputSrj.connections.length}`,
|
|
2748
|
-
failedBuses: "none",
|
|
2749
|
-
bestScore: bestSummary.score,
|
|
2750
|
-
}
|
|
2751
|
-
if (
|
|
2752
|
-
this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0
|
|
2753
|
-
) {
|
|
2754
|
-
this.completeBestAttemptEndpoints()
|
|
2755
|
-
this.solved = true
|
|
2756
|
-
return
|
|
2757
|
-
}
|
|
2758
|
-
}
|
|
4577
|
+
private commitGroupedBeamAttempt(
|
|
4578
|
+
beamAttempt: EvaluatedAssignment | null,
|
|
4579
|
+
): void {
|
|
4580
|
+
if (!beamAttempt) {
|
|
2759
4581
|
if (
|
|
2760
4582
|
this.hasCompleteBestAttempt() &&
|
|
2761
4583
|
this.bestAttempt &&
|
|
@@ -2763,10 +4585,75 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2763
4585
|
) {
|
|
2764
4586
|
this.completeBestAttemptEndpoints()
|
|
2765
4587
|
this.solved = true
|
|
2766
|
-
return
|
|
2767
4588
|
}
|
|
4589
|
+
return
|
|
4590
|
+
}
|
|
4591
|
+
this.attempts.push(beamAttempt.summary)
|
|
4592
|
+
if (
|
|
4593
|
+
!this.bestAttempt ||
|
|
4594
|
+
this.isAttemptBetter(beamAttempt, this.bestAttempt)
|
|
4595
|
+
) {
|
|
4596
|
+
this.bestAttempt = beamAttempt
|
|
4597
|
+
}
|
|
4598
|
+
const bestSummary = this.bestAttempt.summary
|
|
4599
|
+
this.stats = {
|
|
4600
|
+
phase: "complete-grouped-beam",
|
|
4601
|
+
assignment:
|
|
4602
|
+
bestSummary.assignmentIndex < 0 ? 0 : bestSummary.assignmentIndex + 1,
|
|
4603
|
+
assignmentCount: this.config.maxLayerCombinations,
|
|
4604
|
+
routedBuses: `${bestSummary.routedBusCount}/${this.preparedBuses.length}`,
|
|
4605
|
+
routedConnections: `${bestSummary.routedConnectionCount}/${this.inputSrj.connections.length}`,
|
|
4606
|
+
failedBuses: "none",
|
|
4607
|
+
bestScore: bestSummary.score,
|
|
4608
|
+
}
|
|
4609
|
+
if (this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0) {
|
|
4610
|
+
this.completeBestAttemptEndpoints()
|
|
4611
|
+
this.solved = true
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
|
|
4615
|
+
private commitAssignmentAttempt(
|
|
4616
|
+
assignment: Readonly<Record<string, string>>,
|
|
4617
|
+
attempt: EvaluatedAssignment,
|
|
4618
|
+
): void {
|
|
4619
|
+
this.nextAssignmentIndex++
|
|
4620
|
+
this.evaluatedAssignmentKeys.add(JSON.stringify(assignment))
|
|
4621
|
+
if (
|
|
4622
|
+
!this.bestAttempt ||
|
|
4623
|
+
attempt.summary.routedConnectionCount >=
|
|
4624
|
+
this.bestAttempt.summary.routedConnectionCount
|
|
4625
|
+
) {
|
|
4626
|
+
this.prioritizeFailedBusRepairs(
|
|
4627
|
+
assignment,
|
|
4628
|
+
attempt.summary.failedBusIds,
|
|
4629
|
+
attempt.blockingBusIds,
|
|
4630
|
+
)
|
|
4631
|
+
}
|
|
4632
|
+
this.attempts.push(attempt.summary)
|
|
4633
|
+
if (!this.bestAttempt || this.isAttemptBetter(attempt, this.bestAttempt)) {
|
|
4634
|
+
this.bestAttempt = attempt
|
|
4635
|
+
}
|
|
4636
|
+
this.stats = {
|
|
4637
|
+
phase: "complete-assignment",
|
|
4638
|
+
assignment: attempt.summary.assignmentIndex + 1,
|
|
4639
|
+
assignmentCount: this.config.maxLayerCombinations,
|
|
4640
|
+
routedBuses: `${attempt.summary.routedBusCount}/${this.preparedBuses.length}`,
|
|
4641
|
+
routedConnections: `${attempt.summary.routedConnectionCount}/${this.inputSrj.connections.length}`,
|
|
4642
|
+
failedBuses: attempt.summary.failedBusIds.join(", ") || "none",
|
|
4643
|
+
bestScore: this.bestAttempt.summary.score,
|
|
4644
|
+
}
|
|
4645
|
+
if (
|
|
4646
|
+
this.groupedBeamEvaluated &&
|
|
4647
|
+
attempt.summary.routedConnectionCount ===
|
|
4648
|
+
this.inputSrj.connections.length &&
|
|
4649
|
+
this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0
|
|
4650
|
+
) {
|
|
4651
|
+
this.completeBestAttemptEndpoints()
|
|
4652
|
+
this.solved = true
|
|
2768
4653
|
}
|
|
4654
|
+
}
|
|
2769
4655
|
|
|
4656
|
+
private getNextAssignment(): Readonly<Record<string, string>> | undefined {
|
|
2770
4657
|
let assignment: Readonly<Record<string, string>> | undefined
|
|
2771
4658
|
while (
|
|
2772
4659
|
!assignment &&
|
|
@@ -2802,68 +4689,138 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2802
4689
|
if (this.evaluatedAssignmentKeys.has(candidateKey)) continue
|
|
2803
4690
|
assignment = candidate
|
|
2804
4691
|
}
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
const validationMessage =
|
|
2813
|
-
this.lengthMatchingFailure?.message ??
|
|
2814
|
-
this.bestAttempt?.summary.validationIssues?.[0]?.message
|
|
2815
|
-
this.error = validationMessage
|
|
2816
|
-
? `FanoutSolver: ${validationMessage}`
|
|
2817
|
-
: this.bestAttempt
|
|
2818
|
-
? `FanoutSolver: best layer assignment routed ${this.bestAttempt.summary.routedConnectionCount}/${this.inputSrj.connections.length} connections`
|
|
2819
|
-
: "FanoutSolver: no layer assignment could be evaluated"
|
|
2820
|
-
}
|
|
4692
|
+
return assignment
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
private finishWithoutAnotherAssignment(): void {
|
|
4696
|
+
if (this.hasCompleteBestAttempt()) {
|
|
4697
|
+
this.completeBestAttemptEndpoints()
|
|
4698
|
+
this.solved = true
|
|
2821
4699
|
return
|
|
2822
4700
|
}
|
|
4701
|
+
this.failed = true
|
|
4702
|
+
const validationMessage =
|
|
4703
|
+
this.lengthMatchingFailure?.message ??
|
|
4704
|
+
this.bestAttempt?.summary.validationIssues?.[0]?.message
|
|
4705
|
+
this.error = validationMessage
|
|
4706
|
+
? `FanoutSolver: ${validationMessage}`
|
|
4707
|
+
: this.bestAttempt
|
|
4708
|
+
? `FanoutSolver: best layer assignment routed ${this.bestAttempt.summary.routedConnectionCount}/${this.inputSrj.connections.length} connections`
|
|
4709
|
+
: "FanoutSolver: no layer assignment could be evaluated"
|
|
4710
|
+
}
|
|
2823
4711
|
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
this.nextAssignmentIndex++
|
|
2829
|
-
this.evaluatedAssignmentKeys.add(JSON.stringify(assignment))
|
|
2830
|
-
if (
|
|
2831
|
-
!this.bestAttempt ||
|
|
2832
|
-
attempt.summary.routedConnectionCount >=
|
|
2833
|
-
this.bestAttempt.summary.routedConnectionCount
|
|
2834
|
-
) {
|
|
2835
|
-
this.prioritizeFailedBusRepairs(
|
|
2836
|
-
assignment,
|
|
2837
|
-
attempt.summary.failedBusIds,
|
|
2838
|
-
attempt.blockingBusIds,
|
|
2839
|
-
)
|
|
2840
|
-
}
|
|
2841
|
-
this.attempts.push(attempt.summary)
|
|
2842
|
-
if (!this.bestAttempt || this.isAttemptBetter(attempt, this.bestAttempt)) {
|
|
2843
|
-
this.bestAttempt = attempt
|
|
4712
|
+
override _step(): void {
|
|
4713
|
+
if (this.activeOperation) {
|
|
4714
|
+
this.stepActiveOperation()
|
|
4715
|
+
return
|
|
2844
4716
|
}
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
4717
|
+
|
|
4718
|
+
if (!this.routingInitialized) {
|
|
4719
|
+
this.startOperation({
|
|
4720
|
+
name: "FanoutCandidateLayerSolver",
|
|
4721
|
+
generator: this.initializeRoutingSteps(),
|
|
4722
|
+
onSolved: () => {},
|
|
4723
|
+
getProgress: () =>
|
|
4724
|
+
this.nextCandidateLayerBusIndex /
|
|
4725
|
+
Math.max(1, this.boundaryBuses.length + 1),
|
|
4726
|
+
})
|
|
4727
|
+
return
|
|
2852
4728
|
}
|
|
4729
|
+
|
|
2853
4730
|
if (
|
|
2854
|
-
this.
|
|
2855
|
-
|
|
2856
|
-
this.inputSrj.connections.length &&
|
|
2857
|
-
this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0
|
|
4731
|
+
this.nextAssignmentIndex > 0 &&
|
|
4732
|
+
this.hasGloballyViaMinimalBestAttempt()
|
|
2858
4733
|
) {
|
|
2859
4734
|
this.completeBestAttemptEndpoints()
|
|
2860
4735
|
this.solved = true
|
|
4736
|
+
return
|
|
4737
|
+
}
|
|
4738
|
+
// Try the deterministic assignment and only its targeted repair queue
|
|
4739
|
+
// before paying for the grouped beam. If the beam cannot solve, continue
|
|
4740
|
+
// with the broader generated-assignment search below.
|
|
4741
|
+
if (this.shouldEvaluateGroupedBeam()) {
|
|
4742
|
+
this.groupedBeamEvaluated = true
|
|
4743
|
+
this.startOperation({
|
|
4744
|
+
name: "FanoutGroupedBeamSolver",
|
|
4745
|
+
generator: this.evaluateGroupedBeamAlternativesSteps(-1),
|
|
4746
|
+
onSolved: (attempt) => this.commitGroupedBeamAttempt(attempt),
|
|
4747
|
+
getProgress: () => {
|
|
4748
|
+
const workUnit = Number(this.stats.workUnit ?? 0)
|
|
4749
|
+
const workUnitCount = Number(this.stats.workUnitCount ?? 0)
|
|
4750
|
+
return workUnitCount > 0 ? workUnit / workUnitCount : 0
|
|
4751
|
+
},
|
|
4752
|
+
})
|
|
4753
|
+
this.stats = { ...this.stats, phase: "prepare-grouped-beam" }
|
|
4754
|
+
return
|
|
4755
|
+
}
|
|
4756
|
+
|
|
4757
|
+
const assignment = this.getNextAssignment()
|
|
4758
|
+
if (!assignment && !this.groupedBeamEvaluated) return
|
|
4759
|
+
if (!assignment) {
|
|
4760
|
+
this.finishWithoutAnotherAssignment()
|
|
4761
|
+
return
|
|
4762
|
+
}
|
|
4763
|
+
|
|
4764
|
+
this.startOperation({
|
|
4765
|
+
name: "FanoutAssignmentSolver",
|
|
4766
|
+
generator: this.evaluateAssignmentSteps(
|
|
4767
|
+
this.nextAssignmentIndex,
|
|
4768
|
+
assignment,
|
|
4769
|
+
),
|
|
4770
|
+
onSolved: (attempt) => this.commitAssignmentAttempt(assignment, attempt),
|
|
4771
|
+
getProgress: () => {
|
|
4772
|
+
const strategyIndex =
|
|
4773
|
+
this.stats.routingStrategy === "group-by-layer"
|
|
4774
|
+
? 1
|
|
4775
|
+
: this.stats.routingStrategy === "deep-first"
|
|
4776
|
+
? 2
|
|
4777
|
+
: 0
|
|
4778
|
+
const workUnit = Number(this.stats.workUnit ?? 0)
|
|
4779
|
+
const workUnitCount = Number(this.stats.workUnitCount ?? 0)
|
|
4780
|
+
const strategyFraction =
|
|
4781
|
+
workUnitCount > 0 ? Math.min(1, workUnit / workUnitCount) : 0
|
|
4782
|
+
return (strategyIndex + strategyFraction) / 3
|
|
4783
|
+
},
|
|
4784
|
+
})
|
|
4785
|
+
this.stats = {
|
|
4786
|
+
...this.stats,
|
|
4787
|
+
phase: "prepare-assignment",
|
|
4788
|
+
assignment: this.nextAssignmentIndex + 1,
|
|
4789
|
+
assignmentCount: this.config.maxLayerCombinations,
|
|
4790
|
+
routedConnections: `0/${this.inputSrj.connections.length}`,
|
|
2861
4791
|
}
|
|
2862
4792
|
}
|
|
2863
4793
|
|
|
2864
4794
|
computeProgress(): number {
|
|
2865
4795
|
if (this.solved || this.failed) return 1
|
|
2866
|
-
|
|
4796
|
+
if (!this.routingInitialized) {
|
|
4797
|
+
return (
|
|
4798
|
+
0.05 *
|
|
4799
|
+
(this.nextCandidateLayerBusIndex /
|
|
4800
|
+
Math.max(1, this.boundaryBuses.length + 1))
|
|
4801
|
+
)
|
|
4802
|
+
}
|
|
4803
|
+
let activeAssignmentFraction = 0
|
|
4804
|
+
if (this.activeSubSolver?.getSolverName() === "FanoutAssignmentSolver") {
|
|
4805
|
+
const strategyIndex =
|
|
4806
|
+
this.stats.routingStrategy === "group-by-layer"
|
|
4807
|
+
? 1
|
|
4808
|
+
: this.stats.routingStrategy === "deep-first"
|
|
4809
|
+
? 2
|
|
4810
|
+
: 0
|
|
4811
|
+
const workUnit = Number(this.stats.workUnit ?? 0)
|
|
4812
|
+
const workUnitCount = Number(this.stats.workUnitCount ?? 0)
|
|
4813
|
+
const strategyFraction =
|
|
4814
|
+
workUnitCount > 0 ? Math.min(1, workUnit / workUnitCount) : 0
|
|
4815
|
+
activeAssignmentFraction = (strategyIndex + strategyFraction) / 3
|
|
4816
|
+
}
|
|
4817
|
+
return Math.min(
|
|
4818
|
+
0.99,
|
|
4819
|
+
0.05 +
|
|
4820
|
+
0.95 *
|
|
4821
|
+
((this.nextAssignmentIndex + activeAssignmentFraction) /
|
|
4822
|
+
this.config.maxLayerCombinations),
|
|
4823
|
+
)
|
|
2867
4824
|
}
|
|
2868
4825
|
|
|
2869
4826
|
override getConstructorParams(): [SimpleRouteJson, FanoutSolverOptions] {
|
|
@@ -2937,10 +4894,6 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2937
4894
|
}
|
|
2938
4895
|
|
|
2939
4896
|
override visualize(): GraphicsObject {
|
|
2940
|
-
|
|
2941
|
-
this.endpointCompletion?.simpleRouteJson ??
|
|
2942
|
-
this.bestAttempt?.outputSrj ??
|
|
2943
|
-
this.inputSrj
|
|
2944
|
-
return visualizeSimpleRouteJson(visualizedSrj)
|
|
4897
|
+
return this.activeSubSolver?.visualize() ?? this.visualizeCurrentState()
|
|
2945
4898
|
}
|
|
2946
4899
|
}
|