@tscircuit/fanout-solver 0.0.64 → 0.0.65
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 +198 -6
- package/lib/get-free-boundary-tracks.ts +102 -0
- package/lib/match-angularly-ordered-local-vias.ts +239 -0
- package/lib/reflect-fanout-x.ts +67 -0
- package/lib/repair-peripheral-bus-lengths.ts +167 -0
- package/lib/route-adaptive-left-crossbar-bus.ts +425 -0
- package/lib/route-bottom-crossbar-bus.ts +559 -0
- package/lib/route-bus.ts +18 -2
- package/lib/route-left-crossbar-bus.ts +399 -0
- package/lib/route-opposite-bottom-crossbar-bus.ts +70 -0
- package/lib/route-peripheral-source-escapes.ts +488 -0
- package/lib/route-reserved-narrow-buses.ts +430 -0
- package/lib/route-reserved-source-buses.ts +243 -0
- package/lib/route-shallow-split-perimeter-bus.ts +268 -0
- package/lib/route-split-perimeter-bus.ts +397 -0
- package/lib/route-split-perimeter-source-escapes.ts +579 -0
- package/lib/route-staged-perimeter-bus.ts +257 -0
- package/lib/route-via-minimal-winding.ts +78 -30
- package/package.json +1 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import type { SimpleRouteJson } from "@tscircuit/capacity-autorouter"
|
|
2
|
+
import {
|
|
3
|
+
distance,
|
|
4
|
+
distancePointToSegment,
|
|
5
|
+
distanceSegmentToSegment,
|
|
6
|
+
} from "./geometry"
|
|
7
|
+
import { fanoutPlansAreClear } from "./route-bus"
|
|
8
|
+
import {
|
|
9
|
+
buildViaMinimalWindingPlan,
|
|
10
|
+
routeViaMinimalWindingAlternativesSteps,
|
|
11
|
+
type RouteViaMinimalWindingProgress,
|
|
12
|
+
} from "./route-via-minimal-winding"
|
|
13
|
+
import type { Bounds, FanoutRoutePlan, Point2D, PreparedBus } from "./types"
|
|
14
|
+
|
|
15
|
+
import type { PeripheralSourceEscape } from "./route-peripheral-source-escapes"
|
|
16
|
+
export type { PeripheralSourceEscape } from "./route-peripheral-source-escapes"
|
|
17
|
+
|
|
18
|
+
/** Join ordered inner-layer source escapes to nested perimeter lanes. */
|
|
19
|
+
export function* routeStagedPerimeterBusSteps(params: {
|
|
20
|
+
srj: SimpleRouteJson
|
|
21
|
+
bus: PreparedBus
|
|
22
|
+
targetLayer: string
|
|
23
|
+
layerNames: string[]
|
|
24
|
+
traceWidth: number
|
|
25
|
+
clearance: number
|
|
26
|
+
viaDiameter: number
|
|
27
|
+
viaHoleDiameter: number
|
|
28
|
+
sourceBoundary: Bounds
|
|
29
|
+
sourceEscapes: readonly PeripheralSourceEscape[]
|
|
30
|
+
remoteConnectionIndices: ReadonlySet<number>
|
|
31
|
+
}): Generator<RouteViaMinimalWindingProgress, FanoutRoutePlan[] | null, void> {
|
|
32
|
+
const {
|
|
33
|
+
srj,
|
|
34
|
+
bus,
|
|
35
|
+
targetLayer,
|
|
36
|
+
sourceBoundary,
|
|
37
|
+
sourceEscapes,
|
|
38
|
+
remoteConnectionIndices,
|
|
39
|
+
traceWidth: width,
|
|
40
|
+
clearance,
|
|
41
|
+
} = params
|
|
42
|
+
if (bus.exitEdge !== "right" || bus.termination.type !== "boundary")
|
|
43
|
+
return null
|
|
44
|
+
const pitch = width + clearance,
|
|
45
|
+
padPitch = Math.min(bus.pitchX, bus.pitchY)
|
|
46
|
+
const byIndex = new Map(sourceEscapes.map((s) => [s.connectionIndex, s]))
|
|
47
|
+
const terminals = bus.connections
|
|
48
|
+
.map((connection) => {
|
|
49
|
+
const source = byIndex.get(connection.connectionIndex)
|
|
50
|
+
if (!source)
|
|
51
|
+
throw new Error(
|
|
52
|
+
`FanoutSolver: missing source escape for ${connection.connection.name}`,
|
|
53
|
+
)
|
|
54
|
+
const target = connection.exitTargetPoint ?? connection.targetPoint
|
|
55
|
+
return {
|
|
56
|
+
connection,
|
|
57
|
+
viaPoint: source.via.center,
|
|
58
|
+
exitPoint: { x: bus.sharedBoundary.maxX, y: target.y },
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.sort((a, b) => a.exitPoint.y - b.exitPoint.y)
|
|
62
|
+
const local = terminals.filter(
|
|
63
|
+
(t) => !remoteConnectionIndices.has(t.connection.connectionIndex),
|
|
64
|
+
)
|
|
65
|
+
if (local.length < 2 || local.length === terminals.length) return null
|
|
66
|
+
const minPadX = Math.min(
|
|
67
|
+
...bus.componentObstacles.map((o) => o.center.x - o.width / 2),
|
|
68
|
+
)
|
|
69
|
+
const nearPort = Math.max(...local.map((t) => t.viaPoint.x)) - width / 2
|
|
70
|
+
const farPort = minPadX + params.viaDiameter / 2
|
|
71
|
+
if (nearPort - farPort < (local.length - 1) * pitch) return null
|
|
72
|
+
const rowY = sourceBoundary.maxY
|
|
73
|
+
const stageBoundary = {
|
|
74
|
+
minX: minPadX - padPitch,
|
|
75
|
+
maxX: Math.max(...local.map((t) => t.viaPoint.x)) + 2 * padPitch,
|
|
76
|
+
minY: Math.min(...local.map((t) => t.viaPoint.y)) - 3 * padPitch,
|
|
77
|
+
maxY: rowY,
|
|
78
|
+
}
|
|
79
|
+
const localIndices = new Set(local.map((t) => t.connection.connectionIndex))
|
|
80
|
+
const stageTerminals = local.map((t, i) => ({
|
|
81
|
+
...t,
|
|
82
|
+
exitPoint: {
|
|
83
|
+
x: nearPort + ((farPort - nearPort) * i) / (local.length - 1),
|
|
84
|
+
y: rowY,
|
|
85
|
+
},
|
|
86
|
+
}))
|
|
87
|
+
const stageBus: PreparedBus = {
|
|
88
|
+
...bus,
|
|
89
|
+
exitEdge: "top",
|
|
90
|
+
direction: "up",
|
|
91
|
+
preferredExit: undefined,
|
|
92
|
+
sharedBoundary: stageBoundary,
|
|
93
|
+
connections: local.map((t) => t.connection),
|
|
94
|
+
}
|
|
95
|
+
const sourcePaths = new Map(
|
|
96
|
+
sourceEscapes.map((s) => [
|
|
97
|
+
s.connectionIndex,
|
|
98
|
+
[s.segments[0]!.start, ...s.segments.map((s) => s.end)],
|
|
99
|
+
]),
|
|
100
|
+
)
|
|
101
|
+
let prefixes: FanoutRoutePlan[] | undefined
|
|
102
|
+
for (const laneBias of [-1, 0, 1] as const) {
|
|
103
|
+
const alternatives = yield* routeViaMinimalWindingAlternativesSteps(
|
|
104
|
+
{
|
|
105
|
+
...params,
|
|
106
|
+
bus: stageBus,
|
|
107
|
+
srj: { ...srj, bounds: stageBoundary },
|
|
108
|
+
acceptedPlans: [],
|
|
109
|
+
terminals: stageTerminals,
|
|
110
|
+
sourceEscapePaths: sourcePaths,
|
|
111
|
+
reservedVias: sourceEscapes
|
|
112
|
+
.filter((s) => !localIndices.has(s.connectionIndex))
|
|
113
|
+
.map((s) => ({ connectionName: s.connectionName, via: s.via })),
|
|
114
|
+
allowBlindAndBuriedVias: false,
|
|
115
|
+
allowSameNetMerges: false,
|
|
116
|
+
gridStepDivisor: 2,
|
|
117
|
+
gridStep: pitch / 4,
|
|
118
|
+
alignGridToPads: true,
|
|
119
|
+
maximumRouteOrderAttempts: 1,
|
|
120
|
+
routeOrder: local.map((_, i) => local.length - i - 1),
|
|
121
|
+
laneBias,
|
|
122
|
+
},
|
|
123
|
+
1,
|
|
124
|
+
false,
|
|
125
|
+
)
|
|
126
|
+
if (alternatives.length) {
|
|
127
|
+
prefixes = alternatives[0]
|
|
128
|
+
break
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!prefixes) return null
|
|
132
|
+
const prefixByIndex = new Map(prefixes.map((p) => [p.connectionIndex, p]))
|
|
133
|
+
const roofStart = rowY + padPitch,
|
|
134
|
+
columnStart =
|
|
135
|
+
bus.sharedBoundary.maxX -
|
|
136
|
+
(local.length + 1) * pitch -
|
|
137
|
+
2 * params.viaDiameter
|
|
138
|
+
if (
|
|
139
|
+
roofStart + (local.length - 1) * pitch + width / 2 >
|
|
140
|
+
bus.sharedBoundary.maxY
|
|
141
|
+
)
|
|
142
|
+
return null
|
|
143
|
+
let lane = 0
|
|
144
|
+
const plans: FanoutRoutePlan[] = []
|
|
145
|
+
for (const terminal of terminals) {
|
|
146
|
+
const source = byIndex.get(terminal.connection.connectionIndex)!,
|
|
147
|
+
prefix = prefixByIndex.get(terminal.connection.connectionIndex)
|
|
148
|
+
let points: Point2D[]
|
|
149
|
+
if (prefix) {
|
|
150
|
+
const ss = prefix.segments.filter((s) => s.layer === targetLayer)
|
|
151
|
+
const last = ss.at(-1)!,
|
|
152
|
+
dx = last.end.x - last.start.x,
|
|
153
|
+
dy = last.end.y - last.start.y
|
|
154
|
+
if (dy <= 0 || Math.abs(dx) > dy + 1e-7) return null
|
|
155
|
+
const port = last.end,
|
|
156
|
+
roof = roofStart + lane * pitch,
|
|
157
|
+
column = columnStart + lane * pitch
|
|
158
|
+
lane++
|
|
159
|
+
const extension = chamferRightAngles(
|
|
160
|
+
[
|
|
161
|
+
port,
|
|
162
|
+
{ x: port.x, y: roof },
|
|
163
|
+
{ x: column, y: roof },
|
|
164
|
+
{ x: column, y: terminal.exitPoint.y },
|
|
165
|
+
terminal.exitPoint,
|
|
166
|
+
],
|
|
167
|
+
width,
|
|
168
|
+
)
|
|
169
|
+
points = [ss[0]!.start, ...ss.map((s) => s.end), ...extension.slice(1)]
|
|
170
|
+
} else points = [source.via.center, terminal.exitPoint]
|
|
171
|
+
plans.push(
|
|
172
|
+
buildViaMinimalWindingPlan({
|
|
173
|
+
...params,
|
|
174
|
+
bus,
|
|
175
|
+
terminal,
|
|
176
|
+
targetLayerPoints: points,
|
|
177
|
+
sourceEscapePoints: sourcePaths.get(
|
|
178
|
+
terminal.connection.connectionIndex,
|
|
179
|
+
),
|
|
180
|
+
allowBlindAndBuriedVias: false,
|
|
181
|
+
}),
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
const lengths = plans.map((p) => p.length)
|
|
185
|
+
if (
|
|
186
|
+
bus.maxLengthSkew !== undefined &&
|
|
187
|
+
Math.max(...lengths) - Math.min(...lengths) > bus.maxLengthSkew + 1e-6
|
|
188
|
+
)
|
|
189
|
+
return null
|
|
190
|
+
if (
|
|
191
|
+
!fanoutPlansAreClear({
|
|
192
|
+
plans,
|
|
193
|
+
srj,
|
|
194
|
+
sharedBoundary: bus.sharedBoundary,
|
|
195
|
+
clearance,
|
|
196
|
+
allowBlindAndBuriedVias: false,
|
|
197
|
+
allowSameNetMerges: false,
|
|
198
|
+
})
|
|
199
|
+
)
|
|
200
|
+
return null
|
|
201
|
+
// A later bus must retain the source vias and source-layer copper reserved above.
|
|
202
|
+
for (const plan of plans)
|
|
203
|
+
for (const source of sourceEscapes) {
|
|
204
|
+
if (plan.connectionIndex === source.connectionIndex) continue
|
|
205
|
+
for (const segment of plan.segments) {
|
|
206
|
+
if (
|
|
207
|
+
source.via.spanLayers.includes(segment.layer) &&
|
|
208
|
+
distancePointToSegment(
|
|
209
|
+
source.via.center,
|
|
210
|
+
segment.start,
|
|
211
|
+
segment.end,
|
|
212
|
+
) <
|
|
213
|
+
source.via.diameter / 2 + segment.width / 2 + clearance - 1e-7
|
|
214
|
+
)
|
|
215
|
+
return null
|
|
216
|
+
for (const other of source.segments)
|
|
217
|
+
if (
|
|
218
|
+
segment.layer === other.layer &&
|
|
219
|
+
distanceSegmentToSegment(
|
|
220
|
+
segment.start,
|
|
221
|
+
segment.end,
|
|
222
|
+
other.start,
|
|
223
|
+
other.end,
|
|
224
|
+
) <
|
|
225
|
+
(segment.width + other.width) / 2 + clearance - 1e-7
|
|
226
|
+
)
|
|
227
|
+
return null
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return plans
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function chamferRightAngles(points: Point2D[], trim: number): Point2D[] {
|
|
234
|
+
// A boundary track can coincide with its roof, making the vertical descent
|
|
235
|
+
// disappear. Remove that repeated corner before normalizing either leg.
|
|
236
|
+
const distinct = points.filter(
|
|
237
|
+
(point, index) => index === 0 || distance(point, points[index - 1]!) > 1e-9,
|
|
238
|
+
)
|
|
239
|
+
return distinct.flatMap((p, i) => {
|
|
240
|
+
if (i === 0 || i === distinct.length - 1) return [p]
|
|
241
|
+
const a = distinct[i - 1]!,
|
|
242
|
+
b = distinct[i + 1]!,
|
|
243
|
+
d = Math.min(trim, distance(a, p) / 3, distance(p, b) / 3)
|
|
244
|
+
if (Math.abs((p.x - a.x) * (b.x - p.x) + (p.y - a.y) * (b.y - p.y)) > 1e-9)
|
|
245
|
+
return [p]
|
|
246
|
+
return [
|
|
247
|
+
{
|
|
248
|
+
x: p.x + ((a.x - p.x) * d) / distance(a, p),
|
|
249
|
+
y: p.y + ((a.y - p.y) * d) / distance(a, p),
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
x: p.x + ((b.x - p.x) * d) / distance(p, b),
|
|
253
|
+
y: p.y + ((b.y - p.y) * d) / distance(p, b),
|
|
254
|
+
},
|
|
255
|
+
]
|
|
256
|
+
})
|
|
257
|
+
}
|
|
@@ -66,6 +66,14 @@ export interface RouteViaMinimalWindingParams {
|
|
|
66
66
|
softReservedVias?: readonly ViaMinimalWindingReservedVia[]
|
|
67
67
|
/** Use a finer uniform grid for narrow channels between reserved vias. */
|
|
68
68
|
gridStepDivisor?: 1 | 2
|
|
69
|
+
/** Exact grid spacing for staged routing through narrow via channels. */
|
|
70
|
+
gridStep?: number
|
|
71
|
+
/** Deterministic terminal order for a caller that has ordered escape ports. */
|
|
72
|
+
routeOrder?: readonly number[]
|
|
73
|
+
/** Side preference for a caller-supplied terminal order. */
|
|
74
|
+
laneBias?: -1 | 0 | 1
|
|
75
|
+
/** Actual copper before the first via when source escape has multiple bends. */
|
|
76
|
+
sourceEscapePaths?: ReadonlyMap<number, readonly Point2D[]>
|
|
69
77
|
/** Bias bounded fixed-site searches toward the remote target band. */
|
|
70
78
|
preferTargetDirectedLaneBias?: boolean
|
|
71
79
|
/** Internal path-only mode used before a boundary-side via is appended. */
|
|
@@ -473,11 +481,12 @@ function getBlockingCopper(params: {
|
|
|
473
481
|
}
|
|
474
482
|
}
|
|
475
483
|
|
|
476
|
-
function
|
|
484
|
+
export function buildViaMinimalWindingPlan(params: {
|
|
477
485
|
bus: PreparedBus
|
|
478
486
|
terminal: ViaMinimalWindingTerminal
|
|
479
487
|
targetLayer: string
|
|
480
488
|
targetLayerPoints: Point2D[]
|
|
489
|
+
sourceEscapePoints?: readonly Point2D[]
|
|
481
490
|
layerNames: string[]
|
|
482
491
|
traceWidth: number
|
|
483
492
|
viaDiameter: number
|
|
@@ -500,12 +509,24 @@ function buildPlan(params: {
|
|
|
500
509
|
x: connection.sourcePoint.x,
|
|
501
510
|
y: connection.sourcePoint.y,
|
|
502
511
|
}
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
512
|
+
const sourcePoints = params.sourceEscapePoints ?? [
|
|
513
|
+
sourcePoint,
|
|
514
|
+
terminal.viaPoint,
|
|
515
|
+
]
|
|
516
|
+
if (
|
|
517
|
+
sourcePoints.length < 2 ||
|
|
518
|
+
distance(sourcePoints[0]!, sourcePoint) > EPSILON ||
|
|
519
|
+
distance(sourcePoints.at(-1)!, terminal.viaPoint) > EPSILON
|
|
520
|
+
) {
|
|
521
|
+
throw new Error(
|
|
522
|
+
"FanoutSolver: source escape must connect the source pad to its first via",
|
|
523
|
+
)
|
|
508
524
|
}
|
|
525
|
+
const sourceSegments = getSegments(
|
|
526
|
+
[...sourcePoints],
|
|
527
|
+
traceWidth,
|
|
528
|
+
connection.sourceLayer,
|
|
529
|
+
)
|
|
509
530
|
const hasSourceDogbone = distance(sourcePoint, terminal.viaPoint) > EPSILON
|
|
510
531
|
const changesLayer = connection.sourceLayer !== targetLayer
|
|
511
532
|
const targetSegments = getSegments(targetLayerPoints, traceWidth, targetLayer)
|
|
@@ -535,12 +556,12 @@ function buildPlan(params: {
|
|
|
535
556
|
},
|
|
536
557
|
...(hasSourceDogbone
|
|
537
558
|
? [
|
|
538
|
-
{
|
|
559
|
+
...sourcePoints.slice(1).map((point) => ({
|
|
539
560
|
route_type: "wire" as const,
|
|
540
|
-
...
|
|
561
|
+
...point,
|
|
541
562
|
width: traceWidth,
|
|
542
563
|
layer: connection.sourceLayer,
|
|
543
|
-
},
|
|
564
|
+
})),
|
|
544
565
|
]
|
|
545
566
|
: []),
|
|
546
567
|
...(changesLayer
|
|
@@ -573,7 +594,7 @@ function buildPlan(params: {
|
|
|
573
594
|
sourcePointIndex: connection.sourcePointIndex,
|
|
574
595
|
})
|
|
575
596
|
const segments = [
|
|
576
|
-
...(hasSourceDogbone ?
|
|
597
|
+
...(hasSourceDogbone ? sourceSegments : []),
|
|
577
598
|
...targetSegments,
|
|
578
599
|
]
|
|
579
600
|
const cornerBandSide = getCornerBandSide(bus.exitEdge, bus.preferredExit)
|
|
@@ -608,6 +629,9 @@ function buildPlan(params: {
|
|
|
608
629
|
route,
|
|
609
630
|
},
|
|
610
631
|
segments,
|
|
632
|
+
...(sourceSegments.length > 1
|
|
633
|
+
? { sourceEscapeSegmentCount: sourceSegments.length }
|
|
634
|
+
: {}),
|
|
611
635
|
via: changesLayer ? via : undefined,
|
|
612
636
|
length: segments.reduce(
|
|
613
637
|
(total, segment) => total + distance(segment.start, segment.end),
|
|
@@ -680,9 +704,11 @@ export function* routeViaMinimalWindingAlternativesSteps(
|
|
|
680
704
|
const pitch = Math.min(bus.pitchX, bus.pitchY)
|
|
681
705
|
const alignGridToPitch =
|
|
682
706
|
alignGridToPads && gridStepDivisor === 2 && Number.isFinite(pitch)
|
|
683
|
-
const gridStep =
|
|
684
|
-
|
|
685
|
-
|
|
707
|
+
const gridStep =
|
|
708
|
+
params.gridStep ??
|
|
709
|
+
(alignGridToPitch
|
|
710
|
+
? pitch / (2 * Math.ceil(pitch / (2 * baseGridStep)))
|
|
711
|
+
: baseGridStep)
|
|
686
712
|
if (!Number.isFinite(gridStep) || gridStep <= 0) return []
|
|
687
713
|
const { minX, maxX, minY, maxY } = bus.sharedBoundary
|
|
688
714
|
const originX = bus.xCoordinates[0] ?? minX
|
|
@@ -1404,26 +1430,45 @@ export function* routeViaMinimalWindingAlternativesSteps(
|
|
|
1404
1430
|
Math.max(...viaTracks) < Math.min(...targetTracks) - EPSILON
|
|
1405
1431
|
const viasAreAfterTargets =
|
|
1406
1432
|
Math.min(...viaTracks) > Math.max(...targetTracks) + EPSILON
|
|
1407
|
-
const laneBiases =
|
|
1408
|
-
?
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
? ([0,
|
|
1412
|
-
:
|
|
1413
|
-
|
|
1414
|
-
? ([1, 0, -1] as const)
|
|
1433
|
+
const laneBiases = params.routeOrder
|
|
1434
|
+
? [params.laneBias ?? 0]
|
|
1435
|
+
: preferTargetDirectedLaneBias
|
|
1436
|
+
? viasAreBeforeTargets
|
|
1437
|
+
? ([0, 1, -1] as const)
|
|
1438
|
+
: viasAreAfterTargets
|
|
1439
|
+
? ([0, -1, 1] as const)
|
|
1415
1440
|
: bus.direction === boundaryDirection &&
|
|
1416
|
-
meanTargetTrack
|
|
1417
|
-
? ([
|
|
1418
|
-
:
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1441
|
+
meanTargetTrack > meanViaTrack + EPSILON
|
|
1442
|
+
? ([1, 0, -1] as const)
|
|
1443
|
+
: bus.direction === boundaryDirection &&
|
|
1444
|
+
meanTargetTrack < meanViaTrack - EPSILON
|
|
1445
|
+
? ([-1, 0, 1] as const)
|
|
1446
|
+
: ([0, 1, -1] as const)
|
|
1447
|
+
: viasAreBeforeTargets
|
|
1448
|
+
? ([1, 0, -1] as const)
|
|
1449
|
+
: viasAreAfterTargets
|
|
1450
|
+
? ([-1, 0, 1] as const)
|
|
1451
|
+
: ([0, 1, -1] as const)
|
|
1424
1452
|
const initialRouteOrderFactories: Array<
|
|
1425
1453
|
() => readonly ViaMinimalWindingTerminal[]
|
|
1426
1454
|
> = []
|
|
1455
|
+
if (params.routeOrder) {
|
|
1456
|
+
if (
|
|
1457
|
+
params.routeOrder.length !== terminals.length ||
|
|
1458
|
+
new Set(params.routeOrder).size !== terminals.length ||
|
|
1459
|
+
params.routeOrder.some(
|
|
1460
|
+
(index) =>
|
|
1461
|
+
!Number.isInteger(index) || index < 0 || index >= terminals.length,
|
|
1462
|
+
)
|
|
1463
|
+
) {
|
|
1464
|
+
throw new Error(
|
|
1465
|
+
"FanoutSolver: routeOrder must contain every terminal index exactly once",
|
|
1466
|
+
)
|
|
1467
|
+
}
|
|
1468
|
+
initialRouteOrderFactories.push(() =>
|
|
1469
|
+
params.routeOrder!.map((index) => terminals[index]!),
|
|
1470
|
+
)
|
|
1471
|
+
}
|
|
1427
1472
|
if (alignGridToPads && preferTargetDirectedLaneBias && viasAreBeforeTargets) {
|
|
1428
1473
|
initialRouteOrderFactories.push(() => targetOrderedTerminals)
|
|
1429
1474
|
}
|
|
@@ -1652,11 +1697,14 @@ export function* routeViaMinimalWindingAlternativesSteps(
|
|
|
1652
1697
|
`FanoutSolver: via-minimal winding route omitted "${terminal.connection.connection.name}"`,
|
|
1653
1698
|
)
|
|
1654
1699
|
}
|
|
1655
|
-
return
|
|
1700
|
+
return buildViaMinimalWindingPlan({
|
|
1656
1701
|
bus,
|
|
1657
1702
|
terminal,
|
|
1658
1703
|
targetLayer,
|
|
1659
1704
|
targetLayerPoints,
|
|
1705
|
+
sourceEscapePoints: params.sourceEscapePaths?.get(
|
|
1706
|
+
terminal.connection.connectionIndex,
|
|
1707
|
+
),
|
|
1660
1708
|
layerNames,
|
|
1661
1709
|
traceWidth,
|
|
1662
1710
|
viaDiameter,
|