@tscircuit/fanout-solver 0.0.38 → 0.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/lib/add-via-layer-metadata.ts +48 -0
- package/lib/complete-original-endpoints.ts +30 -2
- package/lib/fanout-solver.ts +905 -14
- package/lib/geometry.ts +21 -0
- package/lib/get-routed-trace-copper.ts +15 -6
- package/lib/index.ts +3 -0
- package/lib/layer-names.ts +40 -0
- package/lib/match-bus-lengths.ts +237 -34
- package/lib/match-component-dogbone-via-sites.ts +655 -0
- package/lib/route-bus.ts +1026 -130
- package/lib/route-via-minimal-winding.ts +423 -76
- package/lib/types.ts +35 -7
- package/lib/validate-fanout-solution.ts +34 -6
- package/lib/validate-routed-copper-drc.ts +47 -8
- package/package.json +1 -1
package/lib/route-bus.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "./boundary-exit"
|
|
11
11
|
import { createFanoutOutputIds } from "./fanout-output-ids"
|
|
12
12
|
import {
|
|
13
|
+
circleFitsInsideObstacle,
|
|
13
14
|
distance,
|
|
14
15
|
distancePointToObstacle,
|
|
15
16
|
distancePointToSegment,
|
|
@@ -17,12 +18,15 @@ import {
|
|
|
17
18
|
segmentsAreClear,
|
|
18
19
|
} from "./geometry"
|
|
19
20
|
import { getAllRoutedTraceCopper } from "./get-routed-trace-copper"
|
|
20
|
-
import {
|
|
21
|
+
import { getViaSpanLayers } from "./layer-names"
|
|
21
22
|
import {
|
|
22
23
|
connectionsShareElectricalNet,
|
|
23
24
|
obstacleSharesElectricalNet,
|
|
24
25
|
} from "./net-identity"
|
|
25
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
routeViaMinimalWindingAlternatives,
|
|
28
|
+
type ViaMinimalWindingReservedVia,
|
|
29
|
+
} from "./route-via-minimal-winding"
|
|
26
30
|
import type {
|
|
27
31
|
Bounds,
|
|
28
32
|
FanoutDirection,
|
|
@@ -47,9 +51,15 @@ export interface RouteBusParams {
|
|
|
47
51
|
viaHoleDiameter: number
|
|
48
52
|
clearance: number
|
|
49
53
|
compactBusTracks: boolean
|
|
54
|
+
allowBlindAndBuriedVias?: boolean
|
|
50
55
|
allowSameNetMerges?: boolean
|
|
51
56
|
staticClearanceCache?: RouteBusStaticClearanceCache
|
|
52
57
|
blockingBusCounts?: Map<string, number>
|
|
58
|
+
rejectedViaMinimalCandidates?: FanoutRoutePlan[][]
|
|
59
|
+
stopAfterFirstRejectedViaMinimalCandidate?: boolean
|
|
60
|
+
fixedViaPointsByConnectionIndex?: ReadonlyMap<number, Point2D>
|
|
61
|
+
reservedVias?: readonly ViaMinimalWindingReservedVia[]
|
|
62
|
+
viaMinimalOnly?: boolean
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
interface TrackCandidate {
|
|
@@ -59,6 +69,13 @@ interface TrackCandidate {
|
|
|
59
69
|
|
|
60
70
|
type ViaHandedness = -1 | 0 | 1
|
|
61
71
|
|
|
72
|
+
function allowsViaInPad(srj: SimpleRouteJson): boolean {
|
|
73
|
+
return (
|
|
74
|
+
(srj as SimpleRouteJson & { allowViaInPad?: boolean }).allowViaInPad ===
|
|
75
|
+
true
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
function isHorizontal(direction: FanoutDirection): boolean {
|
|
63
80
|
return direction === "left" || direction === "right"
|
|
64
81
|
}
|
|
@@ -114,14 +131,22 @@ function getStableConnectionIdentity(
|
|
|
114
131
|
return connection.name
|
|
115
132
|
}
|
|
116
133
|
|
|
117
|
-
function
|
|
134
|
+
function getWindingTargetOrders(params: {
|
|
118
135
|
bus: PreparedBus
|
|
119
|
-
connection: PreparedConnection
|
|
120
136
|
boundaryDirection: FanoutDirection
|
|
121
137
|
layerNames: readonly string[]
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
138
|
+
targetLayer: string
|
|
139
|
+
}): {
|
|
140
|
+
orders: PreparedConnection[][]
|
|
141
|
+
legacyOrder: PreparedConnection[]
|
|
142
|
+
} {
|
|
143
|
+
const { bus, boundaryDirection, layerNames, targetLayer } = params
|
|
144
|
+
const getTargetLayer = (candidate: PreparedConnection): string =>
|
|
145
|
+
candidate.exitTargetPoint?.layer ?? getPointLayer(candidate.targetPoint)
|
|
146
|
+
const compareWithinLayer = (
|
|
147
|
+
first: PreparedConnection,
|
|
148
|
+
second: PreparedConnection,
|
|
149
|
+
): number => {
|
|
125
150
|
const axisDifference =
|
|
126
151
|
getPerpendicularAxis(
|
|
127
152
|
first.exitTargetPoint ?? first.targetPoint,
|
|
@@ -133,28 +158,114 @@ function getWindingTargetRank(params: {
|
|
|
133
158
|
)
|
|
134
159
|
if (axisDifference !== 0) return axisDifference
|
|
135
160
|
|
|
136
|
-
// Equal target coordinates came from distinct target layers and do not
|
|
137
|
-
// impose a physical order after this bus is collapsed onto one escape
|
|
138
|
-
// layer. Keep that free choice independent of allowed-layer ordering so
|
|
139
|
-
// the local fanout can choose a deterministic, via-minimal permutation.
|
|
140
|
-
const identityDifference = first.connection.name.localeCompare(
|
|
141
|
-
second.connection.name,
|
|
142
|
-
)
|
|
143
|
-
if (identityDifference !== 0) return identityDifference
|
|
144
|
-
|
|
145
161
|
const firstStableId = getStableConnectionIdentity(first.connection)
|
|
146
162
|
const secondStableId = getStableConnectionIdentity(second.connection)
|
|
147
163
|
const stableIdentityDifference = firstStableId.localeCompare(secondStableId)
|
|
148
164
|
if (stableIdentityDifference !== 0) return stableIdentityDifference
|
|
149
165
|
|
|
150
|
-
const firstLayer = first.exitTargetPoint?.layer
|
|
151
|
-
const secondLayer = second.exitTargetPoint?.layer
|
|
152
166
|
return (
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
first.connection.name.localeCompare(second.connection.name) ||
|
|
168
|
+
first.connectionIndex - second.connectionIndex
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
const legacyOrderedConnections = bus.connections.toSorted((first, second) => {
|
|
172
|
+
const axisDifference =
|
|
173
|
+
getPerpendicularAxis(
|
|
174
|
+
first.exitTargetPoint ?? first.targetPoint,
|
|
175
|
+
boundaryDirection,
|
|
176
|
+
) -
|
|
177
|
+
getPerpendicularAxis(
|
|
178
|
+
second.exitTargetPoint ?? second.targetPoint,
|
|
179
|
+
boundaryDirection,
|
|
180
|
+
)
|
|
181
|
+
if (axisDifference !== 0) return axisDifference
|
|
182
|
+
return (
|
|
183
|
+
first.connection.name.localeCompare(second.connection.name) ||
|
|
184
|
+
getStableConnectionIdentity(first.connection).localeCompare(
|
|
185
|
+
getStableConnectionIdentity(second.connection),
|
|
186
|
+
) ||
|
|
187
|
+
layerNames.indexOf(getTargetLayer(first)) -
|
|
188
|
+
layerNames.indexOf(getTargetLayer(second)) ||
|
|
155
189
|
first.connectionIndex - second.connectionIndex
|
|
156
190
|
)
|
|
157
191
|
})
|
|
192
|
+
const connectionsByLayer = new Map<string, PreparedConnection[]>()
|
|
193
|
+
for (const candidate of bus.connections) {
|
|
194
|
+
const layer = getTargetLayer(candidate)
|
|
195
|
+
const layerConnections = connectionsByLayer.get(layer) ?? []
|
|
196
|
+
layerConnections.push(candidate)
|
|
197
|
+
connectionsByLayer.set(layer, layerConnections)
|
|
198
|
+
}
|
|
199
|
+
const orderedLayers = [...connectionsByLayer.keys()].toSorted(
|
|
200
|
+
(first, second) =>
|
|
201
|
+
Number(second === targetLayer) - Number(first === targetLayer) ||
|
|
202
|
+
layerNames.indexOf(first) - layerNames.indexOf(second) ||
|
|
203
|
+
first.localeCompare(second),
|
|
204
|
+
)
|
|
205
|
+
const layerOrderByName = new Map(
|
|
206
|
+
orderedLayers.map((layer, layerOrder) => [layer, layerOrder]),
|
|
207
|
+
)
|
|
208
|
+
const rankWithinLayerByConnectionIndex = new Map<number, number>()
|
|
209
|
+
for (const layer of orderedLayers) {
|
|
210
|
+
for (const [rank, candidate] of connectionsByLayer
|
|
211
|
+
.get(layer)!
|
|
212
|
+
.toSorted(compareWithinLayer)
|
|
213
|
+
.entries()) {
|
|
214
|
+
rankWithinLayerByConnectionIndex.set(candidate.connectionIndex, rank)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Winding targets define an order within each copper layer. Their absolute
|
|
218
|
+
// offsets across different layers are not an ordering constraint: unrelated
|
|
219
|
+
// buses can move those layer bands without changing this bus's topology.
|
|
220
|
+
// Build a canonical interleave plus its adjacent linear extensions so a
|
|
221
|
+
// via-minimal search can choose a locally routable merge without violating
|
|
222
|
+
// any same-layer winding order.
|
|
223
|
+
const canonicalOrderedConnections = bus.connections.toSorted(
|
|
224
|
+
(first, second) =>
|
|
225
|
+
(rankWithinLayerByConnectionIndex.get(first.connectionIndex) ?? 0) -
|
|
226
|
+
(rankWithinLayerByConnectionIndex.get(second.connectionIndex) ?? 0) ||
|
|
227
|
+
(layerOrderByName.get(getTargetLayer(first)) ?? 0) -
|
|
228
|
+
(layerOrderByName.get(getTargetLayer(second)) ?? 0) ||
|
|
229
|
+
compareWithinLayer(first, second),
|
|
230
|
+
)
|
|
231
|
+
const candidateOrders: PreparedConnection[][] = [canonicalOrderedConnections]
|
|
232
|
+
for (let index = 0; index + 1 < canonicalOrderedConnections.length; index++) {
|
|
233
|
+
const first = canonicalOrderedConnections[index]!
|
|
234
|
+
const second = canonicalOrderedConnections[index + 1]!
|
|
235
|
+
if (getTargetLayer(first) === getTargetLayer(second)) continue
|
|
236
|
+
const adjacentExtension = [...canonicalOrderedConnections]
|
|
237
|
+
adjacentExtension[index] = second
|
|
238
|
+
adjacentExtension[index + 1] = first
|
|
239
|
+
candidateOrders.push(adjacentExtension)
|
|
240
|
+
}
|
|
241
|
+
// Retain the coordinate-total-order behavior as a compatibility fallback,
|
|
242
|
+
// but do not let sub-nanometer noise between unrelated layer bands choose
|
|
243
|
+
// the primary topology.
|
|
244
|
+
candidateOrders.push(legacyOrderedConnections)
|
|
245
|
+
const seenOrders = new Set<string>()
|
|
246
|
+
const orders = candidateOrders.filter((order) => {
|
|
247
|
+
const key = order.map((candidate) => candidate.connectionIndex).join(",")
|
|
248
|
+
if (seenOrders.has(key)) return false
|
|
249
|
+
seenOrders.add(key)
|
|
250
|
+
return true
|
|
251
|
+
})
|
|
252
|
+
return { orders, legacyOrder: legacyOrderedConnections }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getWindingTargetRank(params: {
|
|
256
|
+
bus: PreparedBus
|
|
257
|
+
connection: PreparedConnection
|
|
258
|
+
boundaryDirection: FanoutDirection
|
|
259
|
+
layerNames: readonly string[]
|
|
260
|
+
targetLayer: string
|
|
261
|
+
windingOrderIndex?: number
|
|
262
|
+
}): { rank: number; connectionCount: number } {
|
|
263
|
+
const { connection, windingOrderIndex = 0 } = params
|
|
264
|
+
const { orders, legacyOrder } = getWindingTargetOrders(params)
|
|
265
|
+
const orderedConnections =
|
|
266
|
+
params.windingOrderIndex === undefined
|
|
267
|
+
? legacyOrder
|
|
268
|
+
: (orders[windingOrderIndex] ?? orders[0] ?? legacyOrder)
|
|
158
269
|
const rank = orderedConnections.findIndex(
|
|
159
270
|
(candidate) => candidate.connectionIndex === connection.connectionIndex,
|
|
160
271
|
)
|
|
@@ -184,6 +295,8 @@ function getCornerTargetTrack(params: {
|
|
|
184
295
|
viaDiameter: number
|
|
185
296
|
clearance: number
|
|
186
297
|
layerNames: readonly string[]
|
|
298
|
+
targetLayer: string
|
|
299
|
+
windingOrderIndex?: number
|
|
187
300
|
}): number {
|
|
188
301
|
const {
|
|
189
302
|
bus,
|
|
@@ -193,6 +306,8 @@ function getCornerTargetTrack(params: {
|
|
|
193
306
|
viaDiameter,
|
|
194
307
|
clearance,
|
|
195
308
|
layerNames,
|
|
309
|
+
targetLayer,
|
|
310
|
+
windingOrderIndex,
|
|
196
311
|
} = params
|
|
197
312
|
const side = getCornerSide(bus)
|
|
198
313
|
if (!side || !bus.exitEdge) {
|
|
@@ -218,6 +333,8 @@ function getCornerTargetTrack(params: {
|
|
|
218
333
|
connection,
|
|
219
334
|
boundaryDirection,
|
|
220
335
|
layerNames,
|
|
336
|
+
targetLayer,
|
|
337
|
+
windingOrderIndex,
|
|
221
338
|
})
|
|
222
339
|
: undefined
|
|
223
340
|
const bandConnectionCount = Math.max(
|
|
@@ -295,6 +412,83 @@ function getPerpendicularPitch(bus: PreparedBus): number {
|
|
|
295
412
|
return isHorizontal(bus.direction) ? bus.pitchY : bus.pitchX
|
|
296
413
|
}
|
|
297
414
|
|
|
415
|
+
function chamferOrthogonalCorners(
|
|
416
|
+
points: readonly Point2D[],
|
|
417
|
+
requestedChamfer: number,
|
|
418
|
+
): Point2D[] {
|
|
419
|
+
if (points.length < 3) return [...points]
|
|
420
|
+
const output: Point2D[] = [points[0]!]
|
|
421
|
+
for (let index = 1; index < points.length - 1; index++) {
|
|
422
|
+
const previous = points[index - 1]!
|
|
423
|
+
const current = points[index]!
|
|
424
|
+
const next = points[index + 1]!
|
|
425
|
+
const incoming = { x: current.x - previous.x, y: current.y - previous.y }
|
|
426
|
+
const outgoing = { x: next.x - current.x, y: next.y - current.y }
|
|
427
|
+
const incomingLength = Math.hypot(incoming.x, incoming.y)
|
|
428
|
+
const outgoingLength = Math.hypot(outgoing.x, outgoing.y)
|
|
429
|
+
const incomingIsAxisAligned =
|
|
430
|
+
Math.abs(incoming.x) <= 1e-9 || Math.abs(incoming.y) <= 1e-9
|
|
431
|
+
const outgoingIsAxisAligned =
|
|
432
|
+
Math.abs(outgoing.x) <= 1e-9 || Math.abs(outgoing.y) <= 1e-9
|
|
433
|
+
const isOrthogonal =
|
|
434
|
+
Math.abs(incoming.x * outgoing.x + incoming.y * outgoing.y) <= 1e-9
|
|
435
|
+
if (
|
|
436
|
+
incomingLength <= 1e-9 ||
|
|
437
|
+
outgoingLength <= 1e-9 ||
|
|
438
|
+
!incomingIsAxisAligned ||
|
|
439
|
+
!outgoingIsAxisAligned ||
|
|
440
|
+
!isOrthogonal
|
|
441
|
+
) {
|
|
442
|
+
output.push(current)
|
|
443
|
+
continue
|
|
444
|
+
}
|
|
445
|
+
const chamfer = Math.min(
|
|
446
|
+
requestedChamfer,
|
|
447
|
+
incomingLength / 3,
|
|
448
|
+
outgoingLength / 3,
|
|
449
|
+
)
|
|
450
|
+
output.push({
|
|
451
|
+
x: current.x - (incoming.x / incomingLength) * chamfer,
|
|
452
|
+
y: current.y - (incoming.y / incomingLength) * chamfer,
|
|
453
|
+
})
|
|
454
|
+
output.push({
|
|
455
|
+
x: current.x + (outgoing.x / outgoingLength) * chamfer,
|
|
456
|
+
y: current.y + (outgoing.y / outgoingLength) * chamfer,
|
|
457
|
+
})
|
|
458
|
+
}
|
|
459
|
+
output.push(points.at(-1)!)
|
|
460
|
+
return output.filter(
|
|
461
|
+
(point, index) => index === 0 || distance(point, output[index - 1]!) > 1e-9,
|
|
462
|
+
)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function getStraightOr45ConnectorVariants(
|
|
466
|
+
start: Point2D,
|
|
467
|
+
end: Point2D,
|
|
468
|
+
): Point2D[][] {
|
|
469
|
+
const deltaX = end.x - start.x
|
|
470
|
+
const deltaY = end.y - start.y
|
|
471
|
+
const absoluteX = Math.abs(deltaX)
|
|
472
|
+
const absoluteY = Math.abs(deltaY)
|
|
473
|
+
if (
|
|
474
|
+
absoluteX <= 1e-9 ||
|
|
475
|
+
absoluteY <= 1e-9 ||
|
|
476
|
+
Math.abs(absoluteX - absoluteY) <= 1e-9
|
|
477
|
+
) {
|
|
478
|
+
return [[start, end]]
|
|
479
|
+
}
|
|
480
|
+
if (absoluteX > absoluteY) {
|
|
481
|
+
return [
|
|
482
|
+
[start, { x: start.x + Math.sign(deltaX) * absoluteY, y: end.y }, end],
|
|
483
|
+
[start, { x: end.x - Math.sign(deltaX) * absoluteY, y: start.y }, end],
|
|
484
|
+
]
|
|
485
|
+
}
|
|
486
|
+
return [
|
|
487
|
+
[start, { x: end.x, y: start.y + Math.sign(deltaY) * absoluteX }, end],
|
|
488
|
+
[start, { x: start.x, y: end.y - Math.sign(deltaY) * absoluteX }, end],
|
|
489
|
+
]
|
|
490
|
+
}
|
|
491
|
+
|
|
298
492
|
function getDepthInRows(bus: PreparedBus): number {
|
|
299
493
|
const directionalCoordinates = (
|
|
300
494
|
isHorizontal(bus.direction) ? bus.xCoordinates : bus.yCoordinates
|
|
@@ -748,6 +942,9 @@ function buildPlan(params: {
|
|
|
748
942
|
cornerBoundaryChannelLaneOffset: number
|
|
749
943
|
clearance: number
|
|
750
944
|
terminateAtVia: boolean
|
|
945
|
+
allowBlindAndBuriedVias: boolean
|
|
946
|
+
initialViaPoint?: Point2D
|
|
947
|
+
sourceEscapePath?: readonly Point2D[]
|
|
751
948
|
}): FanoutRoutePlan {
|
|
752
949
|
const {
|
|
753
950
|
preparedConnection,
|
|
@@ -767,6 +964,9 @@ function buildPlan(params: {
|
|
|
767
964
|
cornerBoundaryChannelLaneOffset,
|
|
768
965
|
clearance,
|
|
769
966
|
terminateAtVia,
|
|
967
|
+
allowBlindAndBuriedVias,
|
|
968
|
+
initialViaPoint,
|
|
969
|
+
sourceEscapePath,
|
|
770
970
|
} = params
|
|
771
971
|
const sourcePoint = {
|
|
772
972
|
x: preparedConnection.sourcePoint.x,
|
|
@@ -782,15 +982,33 @@ function buildPlan(params: {
|
|
|
782
982
|
const usesLayeredWindingChannel = Boolean(windingCrossoverLayer)
|
|
783
983
|
const sign = directionSign(bus.direction)
|
|
784
984
|
const directionalPitch = getDirectionalPitch(bus)
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
985
|
+
const requestedViaPoint = sourceEscapePath?.at(-1)
|
|
986
|
+
const viaPoint =
|
|
987
|
+
requestedViaPoint !== undefined
|
|
988
|
+
? { x: requestedViaPoint.x, y: requestedViaPoint.y }
|
|
989
|
+
: initialViaPoint === undefined
|
|
990
|
+
? getInitialViaPoint({
|
|
991
|
+
preparedConnection,
|
|
992
|
+
bus,
|
|
993
|
+
targetLayer,
|
|
994
|
+
traceWidth,
|
|
995
|
+
viaDiameter,
|
|
996
|
+
clearance,
|
|
997
|
+
viaHandedness,
|
|
998
|
+
})
|
|
999
|
+
: { x: initialViaPoint.x, y: initialViaPoint.y }
|
|
1000
|
+
const resolvedSourceEscapePath = sourceEscapePath
|
|
1001
|
+
? sourceEscapePath.map((point) => ({ x: point.x, y: point.y }))
|
|
1002
|
+
: [sourcePoint, viaPoint]
|
|
1003
|
+
if (
|
|
1004
|
+
resolvedSourceEscapePath.length < 2 ||
|
|
1005
|
+
distance(resolvedSourceEscapePath[0]!, sourcePoint) > 1e-9 ||
|
|
1006
|
+
distance(resolvedSourceEscapePath.at(-1)!, viaPoint) > 1e-9
|
|
1007
|
+
) {
|
|
1008
|
+
throw new Error(
|
|
1009
|
+
`FanoutSolver: source escape path for "${preparedConnection.connection.name}" must run from its source point to its via`,
|
|
1010
|
+
)
|
|
1011
|
+
}
|
|
794
1012
|
const viaAxis = getAxis(viaPoint, bus.direction)
|
|
795
1013
|
const viaPerpendicularAxis = getPerpendicularAxis(viaPoint, bus.direction)
|
|
796
1014
|
const spreadLaneDistance =
|
|
@@ -829,6 +1047,7 @@ function buildPlan(params: {
|
|
|
829
1047
|
viaDiameter,
|
|
830
1048
|
clearance,
|
|
831
1049
|
layerNames,
|
|
1050
|
+
targetLayer,
|
|
832
1051
|
})
|
|
833
1052
|
: usesLayeredWindingChannel
|
|
834
1053
|
? getPerpendicularAxis(
|
|
@@ -899,28 +1118,38 @@ function buildPlan(params: {
|
|
|
899
1118
|
layer: preparedConnection.sourceLayer,
|
|
900
1119
|
start_pcb_port_id: preparedConnection.sourcePoint.pcb_port_id,
|
|
901
1120
|
})
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1121
|
+
for (
|
|
1122
|
+
let pointIndex = 1;
|
|
1123
|
+
pointIndex < resolvedSourceEscapePath.length;
|
|
1124
|
+
pointIndex++
|
|
1125
|
+
) {
|
|
1126
|
+
const previousPoint = resolvedSourceEscapePath[pointIndex - 1]!
|
|
1127
|
+
const point = resolvedSourceEscapePath[pointIndex]!
|
|
1128
|
+
appendSegment(
|
|
1129
|
+
segments,
|
|
1130
|
+
previousPoint,
|
|
1131
|
+
point,
|
|
1132
|
+
traceWidth,
|
|
1133
|
+
preparedConnection.sourceLayer,
|
|
1134
|
+
)
|
|
1135
|
+
if (distance(previousPoint, point) <= 1e-9) continue
|
|
1136
|
+
route.push({
|
|
1137
|
+
route_type: "wire",
|
|
1138
|
+
x: point.x,
|
|
1139
|
+
y: point.y,
|
|
1140
|
+
width: traceWidth,
|
|
1141
|
+
layer: preparedConnection.sourceLayer,
|
|
1142
|
+
})
|
|
1143
|
+
}
|
|
916
1144
|
|
|
917
1145
|
let via: FanoutRoutePlan["via"]
|
|
918
1146
|
if (targetLayer !== preparedConnection.sourceLayer) {
|
|
919
|
-
const spanLayers =
|
|
920
|
-
preparedConnection.sourceLayer,
|
|
921
|
-
targetLayer,
|
|
1147
|
+
const spanLayers = getViaSpanLayers({
|
|
1148
|
+
fromLayer: preparedConnection.sourceLayer,
|
|
1149
|
+
toLayer: targetLayer,
|
|
922
1150
|
layerNames,
|
|
923
|
-
|
|
1151
|
+
allowBlindAndBuriedVias,
|
|
1152
|
+
})
|
|
924
1153
|
via = {
|
|
925
1154
|
center: viaPoint,
|
|
926
1155
|
diameter: viaDiameter,
|
|
@@ -990,7 +1219,12 @@ function buildPlan(params: {
|
|
|
990
1219
|
holeDiameter: viaHoleDiameter,
|
|
991
1220
|
fromLayer,
|
|
992
1221
|
toLayer,
|
|
993
|
-
spanLayers:
|
|
1222
|
+
spanLayers: getViaSpanLayers({
|
|
1223
|
+
fromLayer,
|
|
1224
|
+
toLayer,
|
|
1225
|
+
layerNames,
|
|
1226
|
+
allowBlindAndBuriedVias,
|
|
1227
|
+
}),
|
|
994
1228
|
})
|
|
995
1229
|
route.push({
|
|
996
1230
|
route_type: "via",
|
|
@@ -1066,7 +1300,7 @@ function buildPlan(params: {
|
|
|
1066
1300
|
sourceObstacle: preparedConnection.sourceObstacle,
|
|
1067
1301
|
sourceLayer: preparedConnection.sourceLayer,
|
|
1068
1302
|
targetPoint: preparedConnection.targetPoint,
|
|
1069
|
-
targetLayer
|
|
1303
|
+
targetLayer,
|
|
1070
1304
|
termination: bus.termination,
|
|
1071
1305
|
direction: bus.direction,
|
|
1072
1306
|
...(bus.exitEdge ? { exitEdge: bus.exitEdge } : {}),
|
|
@@ -1190,6 +1424,7 @@ function addPlaneEndpointTerminal(params: {
|
|
|
1190
1424
|
traceWidth: number
|
|
1191
1425
|
viaDiameter: number
|
|
1192
1426
|
viaHoleDiameter: number
|
|
1427
|
+
allowBlindAndBuriedVias: boolean
|
|
1193
1428
|
}): FanoutRoutePlan {
|
|
1194
1429
|
const {
|
|
1195
1430
|
plan,
|
|
@@ -1200,13 +1435,19 @@ function addPlaneEndpointTerminal(params: {
|
|
|
1200
1435
|
traceWidth,
|
|
1201
1436
|
viaDiameter,
|
|
1202
1437
|
viaHoleDiameter,
|
|
1438
|
+
allowBlindAndBuriedVias,
|
|
1203
1439
|
} = params
|
|
1204
1440
|
const targetPoint = {
|
|
1205
1441
|
x: preparedConnection.targetPoint.x,
|
|
1206
1442
|
y: preparedConnection.targetPoint.y,
|
|
1207
1443
|
}
|
|
1208
1444
|
const targetEndpointLayer = getPointLayer(preparedConnection.targetPoint)
|
|
1209
|
-
const spanLayers =
|
|
1445
|
+
const spanLayers = getViaSpanLayers({
|
|
1446
|
+
fromLayer: planeLayer,
|
|
1447
|
+
toLayer: targetEndpointLayer,
|
|
1448
|
+
layerNames,
|
|
1449
|
+
allowBlindAndBuriedVias,
|
|
1450
|
+
})
|
|
1210
1451
|
if (!spanLayers.includes(planeLayer)) {
|
|
1211
1452
|
throw new Error(
|
|
1212
1453
|
`FanoutSolver: via for "${preparedConnection.connection.name}" does not cross plane ${planeLayer}`,
|
|
@@ -1354,14 +1595,36 @@ function getPlanVias(plan: FanoutRoutePlan) {
|
|
|
1354
1595
|
].filter((via): via is NonNullable<FanoutRoutePlan["via"]> => Boolean(via))
|
|
1355
1596
|
}
|
|
1356
1597
|
|
|
1598
|
+
function viaFitsInsidePlanSourcePad(
|
|
1599
|
+
plan: FanoutRoutePlan,
|
|
1600
|
+
via: NonNullable<FanoutRoutePlan["via"]>,
|
|
1601
|
+
): boolean {
|
|
1602
|
+
return (
|
|
1603
|
+
distance(via.center, plan.sourcePoint) <= 1e-9 &&
|
|
1604
|
+
circleFitsInsideObstacle({
|
|
1605
|
+
center: via.center,
|
|
1606
|
+
diameter: via.diameter,
|
|
1607
|
+
obstacle: plan.sourceObstacle,
|
|
1608
|
+
})
|
|
1609
|
+
)
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1357
1612
|
function planIsStaticallyClear(params: {
|
|
1358
1613
|
plan: FanoutRoutePlan
|
|
1359
1614
|
srj: SimpleRouteJson
|
|
1360
1615
|
sharedBoundary: Bounds
|
|
1361
1616
|
clearance: number
|
|
1617
|
+
allowBlindAndBuriedVias: boolean
|
|
1362
1618
|
allowSameNetMerges: boolean
|
|
1363
1619
|
}): boolean {
|
|
1364
|
-
const {
|
|
1620
|
+
const {
|
|
1621
|
+
plan,
|
|
1622
|
+
srj,
|
|
1623
|
+
sharedBoundary,
|
|
1624
|
+
clearance,
|
|
1625
|
+
allowBlindAndBuriedVias,
|
|
1626
|
+
allowSameNetMerges,
|
|
1627
|
+
} = params
|
|
1365
1628
|
const routableBounds = getRoutableBounds(srj.bounds, sharedBoundary)
|
|
1366
1629
|
if (
|
|
1367
1630
|
!pointIsInsideBounds(plan.exitPoint, routableBounds) ||
|
|
@@ -1391,6 +1654,13 @@ function planIsStaticallyClear(params: {
|
|
|
1391
1654
|
}
|
|
1392
1655
|
for (const via of getPlanVias(plan)) {
|
|
1393
1656
|
for (const obstacle of srj.obstacles) {
|
|
1657
|
+
if (
|
|
1658
|
+
allowsViaInPad(srj) &&
|
|
1659
|
+
obstacle === plan.sourceObstacle &&
|
|
1660
|
+
viaFitsInsidePlanSourcePad(plan, via)
|
|
1661
|
+
) {
|
|
1662
|
+
continue
|
|
1663
|
+
}
|
|
1394
1664
|
if (!obstacle.layers.some((layer) => via.spanLayers.includes(layer))) {
|
|
1395
1665
|
continue
|
|
1396
1666
|
}
|
|
@@ -1409,7 +1679,10 @@ function planIsStaticallyClear(params: {
|
|
|
1409
1679
|
}
|
|
1410
1680
|
}
|
|
1411
1681
|
|
|
1412
|
-
for (const traceCopper of getAllRoutedTraceCopper(
|
|
1682
|
+
for (const traceCopper of getAllRoutedTraceCopper(
|
|
1683
|
+
srj,
|
|
1684
|
+
allowBlindAndBuriedVias,
|
|
1685
|
+
)) {
|
|
1413
1686
|
if (
|
|
1414
1687
|
plan.connectionName === traceCopper.connectionName ||
|
|
1415
1688
|
(allowSameNetMerges &&
|
|
@@ -1575,6 +1848,7 @@ function planIsClear(params: {
|
|
|
1575
1848
|
srj: SimpleRouteJson
|
|
1576
1849
|
sharedBoundary: Bounds
|
|
1577
1850
|
clearance: number
|
|
1851
|
+
allowBlindAndBuriedVias: boolean
|
|
1578
1852
|
allowSameNetMerges: boolean
|
|
1579
1853
|
}): boolean {
|
|
1580
1854
|
const {
|
|
@@ -1586,6 +1860,7 @@ function planIsClear(params: {
|
|
|
1586
1860
|
srj,
|
|
1587
1861
|
sharedBoundary,
|
|
1588
1862
|
clearance,
|
|
1863
|
+
allowBlindAndBuriedVias,
|
|
1589
1864
|
allowSameNetMerges,
|
|
1590
1865
|
} = params
|
|
1591
1866
|
let staticallyClear = staticClearanceCache?.get(cacheKey)
|
|
@@ -1595,6 +1870,7 @@ function planIsClear(params: {
|
|
|
1595
1870
|
srj,
|
|
1596
1871
|
sharedBoundary,
|
|
1597
1872
|
clearance,
|
|
1873
|
+
allowBlindAndBuriedVias,
|
|
1598
1874
|
allowSameNetMerges,
|
|
1599
1875
|
})
|
|
1600
1876
|
staticClearanceCache?.set(cacheKey, staticallyClear)
|
|
@@ -1622,6 +1898,7 @@ export function fanoutPlansAreClear(params: {
|
|
|
1622
1898
|
srj: SimpleRouteJson
|
|
1623
1899
|
sharedBoundary: Bounds
|
|
1624
1900
|
clearance: number
|
|
1901
|
+
allowBlindAndBuriedVias?: boolean
|
|
1625
1902
|
allowSameNetMerges?: boolean
|
|
1626
1903
|
}): boolean {
|
|
1627
1904
|
const {
|
|
@@ -1629,6 +1906,7 @@ export function fanoutPlansAreClear(params: {
|
|
|
1629
1906
|
srj,
|
|
1630
1907
|
sharedBoundary,
|
|
1631
1908
|
clearance,
|
|
1909
|
+
allowBlindAndBuriedVias = true,
|
|
1632
1910
|
allowSameNetMerges = false,
|
|
1633
1911
|
} = params
|
|
1634
1912
|
for (let index = 0; index < plans.length; index++) {
|
|
@@ -1639,15 +1917,17 @@ export function fanoutPlansAreClear(params: {
|
|
|
1639
1917
|
srj,
|
|
1640
1918
|
sharedBoundary,
|
|
1641
1919
|
clearance,
|
|
1920
|
+
allowBlindAndBuriedVias,
|
|
1642
1921
|
allowSameNetMerges,
|
|
1643
1922
|
})
|
|
1644
1923
|
) {
|
|
1645
1924
|
return false
|
|
1646
1925
|
}
|
|
1926
|
+
const otherPlans = plans.filter((_, otherIndex) => otherIndex !== index)
|
|
1647
1927
|
if (
|
|
1648
1928
|
!planIsClearOfPlans({
|
|
1649
1929
|
plan,
|
|
1650
|
-
otherPlans
|
|
1930
|
+
otherPlans,
|
|
1651
1931
|
srj,
|
|
1652
1932
|
allowSameNetMerges,
|
|
1653
1933
|
clearance,
|
|
@@ -1674,13 +1954,163 @@ function routePlaneTerminatedBus(
|
|
|
1674
1954
|
clearance,
|
|
1675
1955
|
staticClearanceCache,
|
|
1676
1956
|
blockingBusCounts,
|
|
1957
|
+
allowBlindAndBuriedVias = true,
|
|
1677
1958
|
allowSameNetMerges = false,
|
|
1959
|
+
fixedViaPointsByConnectionIndex,
|
|
1678
1960
|
} = params
|
|
1679
1961
|
const sourceObstacle = bus.connections[0]?.sourceObstacle
|
|
1680
1962
|
if (!sourceObstacle || bus.termination.type !== "plane") return null
|
|
1681
1963
|
const sourceLayer = bus.connections[0]!.sourceLayer
|
|
1682
1964
|
if (targetLayer === sourceLayer) return null
|
|
1683
1965
|
|
|
1966
|
+
if (fixedViaPointsByConnectionIndex) {
|
|
1967
|
+
const fixedPlans: FanoutRoutePlan[] = []
|
|
1968
|
+
for (const preparedConnection of bus.connections) {
|
|
1969
|
+
const fixedViaPoint = fixedViaPointsByConnectionIndex.get(
|
|
1970
|
+
preparedConnection.connectionIndex,
|
|
1971
|
+
)
|
|
1972
|
+
if (!fixedViaPoint) return null
|
|
1973
|
+
const sourcePoint = {
|
|
1974
|
+
x: preparedConnection.sourcePoint.x,
|
|
1975
|
+
y: preparedConnection.sourcePoint.y,
|
|
1976
|
+
}
|
|
1977
|
+
const basePlan = buildPlan({
|
|
1978
|
+
preparedConnection,
|
|
1979
|
+
bus,
|
|
1980
|
+
targetLayer,
|
|
1981
|
+
track: getPerpendicularAxis(sourcePoint, bus.direction),
|
|
1982
|
+
exitAxis: getExitAxis(bus),
|
|
1983
|
+
layerNames,
|
|
1984
|
+
traceWidth,
|
|
1985
|
+
viaDiameter,
|
|
1986
|
+
viaHoleDiameter,
|
|
1987
|
+
viaHandedness: 0,
|
|
1988
|
+
interstitialEscape: false,
|
|
1989
|
+
spreadLaneIndex: 0,
|
|
1990
|
+
cornerExitLaneOffset: 0,
|
|
1991
|
+
cornerLocalChannelLaneOffset: 0,
|
|
1992
|
+
cornerBoundaryChannelLaneOffset: 0,
|
|
1993
|
+
clearance,
|
|
1994
|
+
terminateAtVia: true,
|
|
1995
|
+
allowBlindAndBuriedVias,
|
|
1996
|
+
initialViaPoint: fixedViaPoint,
|
|
1997
|
+
sourceEscapePath: [sourcePoint, fixedViaPoint],
|
|
1998
|
+
})
|
|
1999
|
+
const endpointViaCandidates = getPlaneEndpointViaCandidates({
|
|
2000
|
+
preparedConnection,
|
|
2001
|
+
bus,
|
|
2002
|
+
viaDiameter,
|
|
2003
|
+
clearance,
|
|
2004
|
+
})
|
|
2005
|
+
const plansToTry = [
|
|
2006
|
+
...endpointViaCandidates.map((viaPoint) =>
|
|
2007
|
+
addPlaneEndpointTerminal({
|
|
2008
|
+
plan: basePlan,
|
|
2009
|
+
preparedConnection,
|
|
2010
|
+
planeLayer: targetLayer,
|
|
2011
|
+
viaPoint,
|
|
2012
|
+
layerNames,
|
|
2013
|
+
traceWidth,
|
|
2014
|
+
viaDiameter,
|
|
2015
|
+
viaHoleDiameter,
|
|
2016
|
+
allowBlindAndBuriedVias,
|
|
2017
|
+
}),
|
|
2018
|
+
),
|
|
2019
|
+
basePlan,
|
|
2020
|
+
]
|
|
2021
|
+
const clearPlan = plansToTry.find((candidatePlan, candidateIndex) =>
|
|
2022
|
+
planIsClear({
|
|
2023
|
+
plan: candidatePlan,
|
|
2024
|
+
otherPlans: [...acceptedPlans, ...fixedPlans],
|
|
2025
|
+
staticClearanceCache,
|
|
2026
|
+
blockingBusCounts,
|
|
2027
|
+
cacheKey: `plane-fixed:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${candidateIndex}`,
|
|
2028
|
+
srj,
|
|
2029
|
+
sharedBoundary: bus.sharedBoundary,
|
|
2030
|
+
clearance,
|
|
2031
|
+
allowBlindAndBuriedVias,
|
|
2032
|
+
allowSameNetMerges,
|
|
2033
|
+
}),
|
|
2034
|
+
)
|
|
2035
|
+
if (!clearPlan) return null
|
|
2036
|
+
fixedPlans.push(clearPlan)
|
|
2037
|
+
}
|
|
2038
|
+
return fixedPlans
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
if (
|
|
2042
|
+
allowsViaInPad(srj) &&
|
|
2043
|
+
bus.connections.length === 1 &&
|
|
2044
|
+
circleFitsInsideObstacle({
|
|
2045
|
+
center: bus.connections[0]!.sourcePoint,
|
|
2046
|
+
diameter: viaDiameter,
|
|
2047
|
+
obstacle: sourceObstacle,
|
|
2048
|
+
})
|
|
2049
|
+
) {
|
|
2050
|
+
const preparedConnection = bus.connections[0]!
|
|
2051
|
+
const viaInPadPlan = buildPlan({
|
|
2052
|
+
preparedConnection,
|
|
2053
|
+
bus,
|
|
2054
|
+
targetLayer,
|
|
2055
|
+
track: getPerpendicularAxis(
|
|
2056
|
+
preparedConnection.sourcePoint,
|
|
2057
|
+
bus.direction,
|
|
2058
|
+
),
|
|
2059
|
+
exitAxis: getExitAxis(bus),
|
|
2060
|
+
layerNames,
|
|
2061
|
+
traceWidth,
|
|
2062
|
+
viaDiameter,
|
|
2063
|
+
viaHoleDiameter,
|
|
2064
|
+
viaHandedness: 0,
|
|
2065
|
+
interstitialEscape: false,
|
|
2066
|
+
spreadLaneIndex: 0,
|
|
2067
|
+
cornerExitLaneOffset: 0,
|
|
2068
|
+
cornerLocalChannelLaneOffset: 0,
|
|
2069
|
+
cornerBoundaryChannelLaneOffset: 0,
|
|
2070
|
+
clearance,
|
|
2071
|
+
terminateAtVia: true,
|
|
2072
|
+
allowBlindAndBuriedVias,
|
|
2073
|
+
initialViaPoint: preparedConnection.sourcePoint,
|
|
2074
|
+
})
|
|
2075
|
+
const endpointViaCandidates = getPlaneEndpointViaCandidates({
|
|
2076
|
+
preparedConnection,
|
|
2077
|
+
bus,
|
|
2078
|
+
viaDiameter,
|
|
2079
|
+
clearance,
|
|
2080
|
+
})
|
|
2081
|
+
const plansToTry = [
|
|
2082
|
+
...endpointViaCandidates.map((viaPoint) =>
|
|
2083
|
+
addPlaneEndpointTerminal({
|
|
2084
|
+
plan: viaInPadPlan,
|
|
2085
|
+
preparedConnection,
|
|
2086
|
+
planeLayer: targetLayer,
|
|
2087
|
+
viaPoint,
|
|
2088
|
+
layerNames,
|
|
2089
|
+
traceWidth,
|
|
2090
|
+
viaDiameter,
|
|
2091
|
+
viaHoleDiameter,
|
|
2092
|
+
allowBlindAndBuriedVias,
|
|
2093
|
+
}),
|
|
2094
|
+
),
|
|
2095
|
+
viaInPadPlan,
|
|
2096
|
+
]
|
|
2097
|
+
const clearPlan = plansToTry.find((candidatePlan, candidateIndex) =>
|
|
2098
|
+
planIsClear({
|
|
2099
|
+
plan: candidatePlan,
|
|
2100
|
+
otherPlans: acceptedPlans,
|
|
2101
|
+
staticClearanceCache,
|
|
2102
|
+
blockingBusCounts,
|
|
2103
|
+
cacheKey: `plane-via-in-pad:${bus.busId}:${targetLayer}:${candidateIndex}`,
|
|
2104
|
+
srj,
|
|
2105
|
+
sharedBoundary: bus.sharedBoundary,
|
|
2106
|
+
clearance,
|
|
2107
|
+
allowBlindAndBuriedVias,
|
|
2108
|
+
allowSameNetMerges,
|
|
2109
|
+
}),
|
|
2110
|
+
)
|
|
2111
|
+
if (clearPlan) return [clearPlan]
|
|
2112
|
+
}
|
|
2113
|
+
|
|
1684
2114
|
const candidateDirections: FanoutDirection[] = [
|
|
1685
2115
|
bus.direction,
|
|
1686
2116
|
...(["left", "right", "up", "down"] as const).filter(
|
|
@@ -1709,59 +2139,194 @@ function routePlaneTerminatedBus(
|
|
|
1709
2139
|
preparedConnection.sourcePoint,
|
|
1710
2140
|
direction,
|
|
1711
2141
|
)
|
|
1712
|
-
const
|
|
2142
|
+
const adjacentViaPoint = getInitialViaPoint({
|
|
1713
2143
|
preparedConnection,
|
|
1714
2144
|
bus: directionalBus,
|
|
1715
2145
|
targetLayer,
|
|
1716
|
-
track: sourceTrack,
|
|
1717
|
-
exitAxis: getExitAxis(directionalBus),
|
|
1718
|
-
layerNames,
|
|
1719
2146
|
traceWidth,
|
|
1720
2147
|
viaDiameter,
|
|
1721
|
-
viaHoleDiameter,
|
|
1722
|
-
viaHandedness,
|
|
1723
|
-
interstitialEscape: !pairChannelFitsVia,
|
|
1724
|
-
spreadLaneIndex: 0,
|
|
1725
|
-
cornerExitLaneOffset: 0,
|
|
1726
|
-
cornerLocalChannelLaneOffset: 0,
|
|
1727
|
-
cornerBoundaryChannelLaneOffset: 0,
|
|
1728
|
-
clearance,
|
|
1729
|
-
terminateAtVia: true,
|
|
1730
|
-
})
|
|
1731
|
-
const endpointViaCandidates = getPlaneEndpointViaCandidates({
|
|
1732
|
-
preparedConnection,
|
|
1733
|
-
bus: directionalBus,
|
|
1734
|
-
viaDiameter,
|
|
1735
2148
|
clearance,
|
|
2149
|
+
viaHandedness,
|
|
1736
2150
|
})
|
|
1737
|
-
const
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
viaDiameter,
|
|
1747
|
-
viaHoleDiameter,
|
|
1748
|
-
}),
|
|
1749
|
-
),
|
|
1750
|
-
basePlan,
|
|
1751
|
-
]
|
|
1752
|
-
const plan = plansToTry.find((candidatePlan, candidateIndex) =>
|
|
1753
|
-
planIsClear({
|
|
1754
|
-
plan: candidatePlan,
|
|
1755
|
-
otherPlans: [...acceptedPlans, ...candidatePlans],
|
|
1756
|
-
staticClearanceCache,
|
|
1757
|
-
blockingBusCounts,
|
|
1758
|
-
cacheKey: `plane:${bus.busId}:${targetLayer}:${direction}:${preparedConnection.connectionIndex}:${viaHandedness}:${candidateIndex}`,
|
|
1759
|
-
srj,
|
|
1760
|
-
sharedBoundary: bus.sharedBoundary,
|
|
1761
|
-
clearance,
|
|
1762
|
-
allowSameNetMerges,
|
|
1763
|
-
}),
|
|
2151
|
+
const directionPitch = getDirectionalPitch(directionalBus)
|
|
2152
|
+
const sign = directionSign(direction)
|
|
2153
|
+
const boundaryAxis = getExitAxis(directionalBus, direction)
|
|
2154
|
+
const availableTravel =
|
|
2155
|
+
sign * (boundaryAxis - getAxis(adjacentViaPoint, direction)) -
|
|
2156
|
+
(viaDiameter / 2 + clearance)
|
|
2157
|
+
const maximumEscapeSteps = Math.max(
|
|
2158
|
+
0,
|
|
2159
|
+
Math.floor(availableTravel / directionPitch),
|
|
1764
2160
|
)
|
|
2161
|
+
const sourcePoint = {
|
|
2162
|
+
x: preparedConnection.sourcePoint.x,
|
|
2163
|
+
y: preparedConnection.sourcePoint.y,
|
|
2164
|
+
}
|
|
2165
|
+
const adjacentAxis = getAxis(adjacentViaPoint, direction)
|
|
2166
|
+
const adjacentPerpendicularAxis = getPerpendicularAxis(
|
|
2167
|
+
adjacentViaPoint,
|
|
2168
|
+
direction,
|
|
2169
|
+
)
|
|
2170
|
+
const perpendicularPitch = getPerpendicularPitch(directionalBus)
|
|
2171
|
+
const sourceEscapePaths: Point2D[][] = []
|
|
2172
|
+
const maximumCandidatePaths = 32
|
|
2173
|
+
for (
|
|
2174
|
+
let totalSteps = 0;
|
|
2175
|
+
totalSteps <= maximumEscapeSteps &&
|
|
2176
|
+
sourceEscapePaths.length < maximumCandidatePaths;
|
|
2177
|
+
totalSteps++
|
|
2178
|
+
) {
|
|
2179
|
+
const straightViaPoint = makePoint(
|
|
2180
|
+
adjacentAxis + sign * totalSteps * directionPitch,
|
|
2181
|
+
adjacentPerpendicularAxis,
|
|
2182
|
+
direction,
|
|
2183
|
+
)
|
|
2184
|
+
if (totalSteps === 0) {
|
|
2185
|
+
sourceEscapePaths.push([sourcePoint, adjacentViaPoint])
|
|
2186
|
+
} else {
|
|
2187
|
+
for (const connector of getStraightOr45ConnectorVariants(
|
|
2188
|
+
sourcePoint,
|
|
2189
|
+
straightViaPoint,
|
|
2190
|
+
)) {
|
|
2191
|
+
sourceEscapePaths.push(connector)
|
|
2192
|
+
if (sourceEscapePaths.length >= maximumCandidatePaths) break
|
|
2193
|
+
}
|
|
2194
|
+
if (sourceEscapePaths.length < maximumCandidatePaths) {
|
|
2195
|
+
sourceEscapePaths.push([
|
|
2196
|
+
sourcePoint,
|
|
2197
|
+
adjacentViaPoint,
|
|
2198
|
+
straightViaPoint,
|
|
2199
|
+
])
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
for (
|
|
2204
|
+
let lateralSteps = 1;
|
|
2205
|
+
lateralSteps <= Math.min(3, totalSteps - 1) &&
|
|
2206
|
+
sourceEscapePaths.length < maximumCandidatePaths;
|
|
2207
|
+
lateralSteps++
|
|
2208
|
+
) {
|
|
2209
|
+
const outwardSteps = totalSteps - lateralSteps
|
|
2210
|
+
if (outwardSteps < 1 || outwardSteps > maximumEscapeSteps) {
|
|
2211
|
+
continue
|
|
2212
|
+
}
|
|
2213
|
+
for (const lateralSign of [-1, 1] as const) {
|
|
2214
|
+
const lateralAxis =
|
|
2215
|
+
adjacentPerpendicularAxis +
|
|
2216
|
+
lateralSign * lateralSteps * perpendicularPitch
|
|
2217
|
+
const lateralPoint = makePoint(
|
|
2218
|
+
adjacentAxis,
|
|
2219
|
+
lateralAxis,
|
|
2220
|
+
direction,
|
|
2221
|
+
)
|
|
2222
|
+
const outwardPoint = makePoint(
|
|
2223
|
+
adjacentAxis + sign * outwardSteps * directionPitch,
|
|
2224
|
+
adjacentPerpendicularAxis,
|
|
2225
|
+
direction,
|
|
2226
|
+
)
|
|
2227
|
+
const detourViaPoint = makePoint(
|
|
2228
|
+
adjacentAxis + sign * outwardSteps * directionPitch,
|
|
2229
|
+
lateralAxis,
|
|
2230
|
+
direction,
|
|
2231
|
+
)
|
|
2232
|
+
const chamfer =
|
|
2233
|
+
Math.min(directionPitch, perpendicularPitch) * 0.2
|
|
2234
|
+
for (const connector of getStraightOr45ConnectorVariants(
|
|
2235
|
+
sourcePoint,
|
|
2236
|
+
detourViaPoint,
|
|
2237
|
+
)) {
|
|
2238
|
+
sourceEscapePaths.push(connector)
|
|
2239
|
+
if (sourceEscapePaths.length >= maximumCandidatePaths) break
|
|
2240
|
+
}
|
|
2241
|
+
if (sourceEscapePaths.length >= maximumCandidatePaths) break
|
|
2242
|
+
sourceEscapePaths.push(
|
|
2243
|
+
chamferOrthogonalCorners(
|
|
2244
|
+
[
|
|
2245
|
+
sourcePoint,
|
|
2246
|
+
adjacentViaPoint,
|
|
2247
|
+
lateralPoint,
|
|
2248
|
+
detourViaPoint,
|
|
2249
|
+
],
|
|
2250
|
+
chamfer,
|
|
2251
|
+
),
|
|
2252
|
+
chamferOrthogonalCorners(
|
|
2253
|
+
[
|
|
2254
|
+
sourcePoint,
|
|
2255
|
+
adjacentViaPoint,
|
|
2256
|
+
outwardPoint,
|
|
2257
|
+
detourViaPoint,
|
|
2258
|
+
],
|
|
2259
|
+
chamfer,
|
|
2260
|
+
),
|
|
2261
|
+
)
|
|
2262
|
+
if (sourceEscapePaths.length >= maximumCandidatePaths) break
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
let plan: FanoutRoutePlan | undefined
|
|
2267
|
+
for (const [
|
|
2268
|
+
pathIndex,
|
|
2269
|
+
sourceEscapePath,
|
|
2270
|
+
] of sourceEscapePaths.entries()) {
|
|
2271
|
+
const basePlan = buildPlan({
|
|
2272
|
+
preparedConnection,
|
|
2273
|
+
bus: directionalBus,
|
|
2274
|
+
targetLayer,
|
|
2275
|
+
track: sourceTrack,
|
|
2276
|
+
exitAxis: getExitAxis(directionalBus),
|
|
2277
|
+
layerNames,
|
|
2278
|
+
traceWidth,
|
|
2279
|
+
viaDiameter,
|
|
2280
|
+
viaHoleDiameter,
|
|
2281
|
+
viaHandedness,
|
|
2282
|
+
interstitialEscape: !pairChannelFitsVia,
|
|
2283
|
+
spreadLaneIndex: 0,
|
|
2284
|
+
cornerExitLaneOffset: 0,
|
|
2285
|
+
cornerLocalChannelLaneOffset: 0,
|
|
2286
|
+
cornerBoundaryChannelLaneOffset: 0,
|
|
2287
|
+
clearance,
|
|
2288
|
+
terminateAtVia: true,
|
|
2289
|
+
allowBlindAndBuriedVias,
|
|
2290
|
+
sourceEscapePath,
|
|
2291
|
+
})
|
|
2292
|
+
const endpointViaCandidates = getPlaneEndpointViaCandidates({
|
|
2293
|
+
preparedConnection,
|
|
2294
|
+
bus: directionalBus,
|
|
2295
|
+
viaDiameter,
|
|
2296
|
+
clearance,
|
|
2297
|
+
})
|
|
2298
|
+
const plansToTry = [
|
|
2299
|
+
...endpointViaCandidates.map((viaPoint) =>
|
|
2300
|
+
addPlaneEndpointTerminal({
|
|
2301
|
+
plan: basePlan,
|
|
2302
|
+
preparedConnection,
|
|
2303
|
+
planeLayer: targetLayer,
|
|
2304
|
+
viaPoint,
|
|
2305
|
+
layerNames,
|
|
2306
|
+
traceWidth,
|
|
2307
|
+
viaDiameter,
|
|
2308
|
+
viaHoleDiameter,
|
|
2309
|
+
allowBlindAndBuriedVias,
|
|
2310
|
+
}),
|
|
2311
|
+
),
|
|
2312
|
+
basePlan,
|
|
2313
|
+
]
|
|
2314
|
+
plan = plansToTry.find((candidatePlan, candidateIndex) =>
|
|
2315
|
+
planIsClear({
|
|
2316
|
+
plan: candidatePlan,
|
|
2317
|
+
otherPlans: [...acceptedPlans, ...candidatePlans],
|
|
2318
|
+
staticClearanceCache,
|
|
2319
|
+
blockingBusCounts,
|
|
2320
|
+
cacheKey: `plane:${bus.busId}:${targetLayer}:${direction}:${preparedConnection.connectionIndex}:${viaHandedness}:${pathIndex}:${candidateIndex}`,
|
|
2321
|
+
srj,
|
|
2322
|
+
sharedBoundary: bus.sharedBoundary,
|
|
2323
|
+
clearance,
|
|
2324
|
+
allowBlindAndBuriedVias,
|
|
2325
|
+
allowSameNetMerges,
|
|
2326
|
+
}),
|
|
2327
|
+
)
|
|
2328
|
+
if (plan) break
|
|
2329
|
+
}
|
|
1765
2330
|
if (!plan) {
|
|
1766
2331
|
orderIsClear = false
|
|
1767
2332
|
break
|
|
@@ -1793,13 +2358,28 @@ export function routeBusAlternatives(
|
|
|
1793
2358
|
compactBusTracks,
|
|
1794
2359
|
staticClearanceCache,
|
|
1795
2360
|
blockingBusCounts,
|
|
2361
|
+
allowBlindAndBuriedVias = true,
|
|
1796
2362
|
allowSameNetMerges = false,
|
|
2363
|
+
rejectedViaMinimalCandidates,
|
|
2364
|
+
stopAfterFirstRejectedViaMinimalCandidate = false,
|
|
2365
|
+
fixedViaPointsByConnectionIndex,
|
|
2366
|
+
reservedVias = [],
|
|
2367
|
+
viaMinimalOnly = false,
|
|
1797
2368
|
} = params
|
|
1798
2369
|
if (!Number.isInteger(maxAlternatives) || maxAlternatives < 1) {
|
|
1799
2370
|
throw new Error(
|
|
1800
2371
|
`FanoutSolver: maxAlternatives must be a positive integer, received ${maxAlternatives}`,
|
|
1801
2372
|
)
|
|
1802
2373
|
}
|
|
2374
|
+
if (
|
|
2375
|
+
fixedViaPointsByConnectionIndex &&
|
|
2376
|
+
bus.connections.some(
|
|
2377
|
+
(connection) =>
|
|
2378
|
+
!fixedViaPointsByConnectionIndex.has(connection.connectionIndex),
|
|
2379
|
+
)
|
|
2380
|
+
) {
|
|
2381
|
+
return []
|
|
2382
|
+
}
|
|
1803
2383
|
if (bus.termination.type === "plane") {
|
|
1804
2384
|
const plan = routePlaneTerminatedBus(params)
|
|
1805
2385
|
return plan ? [plan] : []
|
|
@@ -1818,11 +2398,47 @@ export function routeBusAlternatives(
|
|
|
1818
2398
|
viaDiameter / 2 + clearance - 1e-9
|
|
1819
2399
|
const interstitialEscape =
|
|
1820
2400
|
targetUsesVia && !outwardEdgeBus && !pairChannelFitsVia
|
|
1821
|
-
const
|
|
2401
|
+
const availableViaHandednesses: readonly ViaHandedness[] = targetUsesVia
|
|
1822
2402
|
? pairChannelFitsVia || outwardEdgeBus
|
|
1823
2403
|
? [0]
|
|
1824
|
-
:
|
|
2404
|
+
: allowBlindAndBuriedVias
|
|
2405
|
+
? [1, -1]
|
|
2406
|
+
: [-1, 1]
|
|
1825
2407
|
: [0]
|
|
2408
|
+
const viaHandednesses: readonly ViaHandedness[] = (() => {
|
|
2409
|
+
if (allowBlindAndBuriedVias) return availableViaHandednesses
|
|
2410
|
+
if (
|
|
2411
|
+
availableViaHandednesses.length !== 2 ||
|
|
2412
|
+
!availableViaHandednesses.includes(-1) ||
|
|
2413
|
+
!availableViaHandednesses.includes(1)
|
|
2414
|
+
) {
|
|
2415
|
+
return availableViaHandednesses
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
// Prefer placing the dogbone via away from the boundary targets. This
|
|
2419
|
+
// leaves the open routing chamber between the source field and the final
|
|
2420
|
+
// exit band, which is especially important when physical barrels span
|
|
2421
|
+
// every copper layer. The opposite hand remains an immediate fallback.
|
|
2422
|
+
const meanSourceTrack =
|
|
2423
|
+
bus.connections.reduce(
|
|
2424
|
+
(sum, connection) =>
|
|
2425
|
+
sum + getPerpendicularAxis(connection.sourcePoint, bus.direction),
|
|
2426
|
+
0,
|
|
2427
|
+
) / bus.connections.length
|
|
2428
|
+
const meanTargetTrack =
|
|
2429
|
+
bus.connections.reduce(
|
|
2430
|
+
(sum, connection) =>
|
|
2431
|
+
sum +
|
|
2432
|
+
getPerpendicularAxis(
|
|
2433
|
+
connection.exitTargetPoint ?? connection.targetPoint,
|
|
2434
|
+
bus.direction,
|
|
2435
|
+
),
|
|
2436
|
+
0,
|
|
2437
|
+
) / bus.connections.length
|
|
2438
|
+
if (meanTargetTrack > meanSourceTrack + 1e-9) return [-1, 1]
|
|
2439
|
+
if (meanTargetTrack < meanSourceTrack - 1e-9) return [1, -1]
|
|
2440
|
+
return availableViaHandednesses
|
|
2441
|
+
})()
|
|
1826
2442
|
|
|
1827
2443
|
const alternatives: FanoutRoutePlan[][] = []
|
|
1828
2444
|
const seenAlternativeKeys = new Set<string>()
|
|
@@ -1844,8 +2460,233 @@ export function routeBusAlternatives(
|
|
|
1844
2460
|
const boundaryDirection = getDirectionForExitEdge(bus.exitEdge)
|
|
1845
2461
|
const boundaryExitAxis = getExitAxis(bus, boundaryDirection)
|
|
1846
2462
|
const cornerSide = getCornerSide(bus)
|
|
1847
|
-
|
|
2463
|
+
const canUseViaInPadTerminals =
|
|
2464
|
+
allowsViaInPad(srj) &&
|
|
2465
|
+
bus.connections.every((preparedConnection) =>
|
|
2466
|
+
circleFitsInsideObstacle({
|
|
2467
|
+
center: preparedConnection.sourcePoint,
|
|
2468
|
+
diameter: viaDiameter,
|
|
2469
|
+
obstacle: preparedConnection.sourceObstacle,
|
|
2470
|
+
}),
|
|
2471
|
+
)
|
|
2472
|
+
type CoordinatedTerminalPattern = {
|
|
2473
|
+
label: string
|
|
2474
|
+
useViaInPad: boolean
|
|
2475
|
+
getViaHandedness: (
|
|
2476
|
+
preparedConnection: PreparedConnection,
|
|
2477
|
+
) => ViaHandedness
|
|
2478
|
+
getViaPoint?: (preparedConnection: PreparedConnection) => Point2D
|
|
2479
|
+
maximumRouteOrderAttempts?: number
|
|
2480
|
+
windingOrderIndex?: number
|
|
2481
|
+
preferTargetDirectedLaneBias?: boolean
|
|
2482
|
+
}
|
|
2483
|
+
const maximumThroughAllRouteOrderAttempts = 24
|
|
2484
|
+
const windingTargetOrderCount = cornerSide
|
|
2485
|
+
? getWindingTargetOrders({
|
|
2486
|
+
bus,
|
|
2487
|
+
boundaryDirection,
|
|
2488
|
+
layerNames,
|
|
2489
|
+
targetLayer,
|
|
2490
|
+
}).orders.length
|
|
2491
|
+
: 1
|
|
2492
|
+
const uniformDogboneTerminalPatterns: CoordinatedTerminalPattern[] =
|
|
2493
|
+
viaHandednesses.map((viaHandedness) => ({
|
|
2494
|
+
label: `uniform-${viaHandedness}`,
|
|
2495
|
+
useViaInPad: false,
|
|
2496
|
+
getViaHandedness: () => viaHandedness,
|
|
2497
|
+
maximumRouteOrderAttempts: allowBlindAndBuriedVias
|
|
2498
|
+
? undefined
|
|
2499
|
+
: maximumThroughAllRouteOrderAttempts,
|
|
2500
|
+
}))
|
|
2501
|
+
const connectionsBySourceTrack = bus.connections.toSorted(
|
|
2502
|
+
(first, second) =>
|
|
2503
|
+
getPerpendicularAxis(first.sourcePoint, bus.direction) -
|
|
2504
|
+
getPerpendicularAxis(second.sourcePoint, bus.direction) ||
|
|
2505
|
+
getAxis(first.sourcePoint, bus.direction) -
|
|
2506
|
+
getAxis(second.sourcePoint, bus.direction) ||
|
|
2507
|
+
first.connectionIndex - second.connectionIndex,
|
|
2508
|
+
)
|
|
2509
|
+
const sourceTrackRankByConnectionIndex = new Map(
|
|
2510
|
+
connectionsBySourceTrack.map((connection, rank) => [
|
|
2511
|
+
connection.connectionIndex,
|
|
2512
|
+
rank,
|
|
2513
|
+
]),
|
|
2514
|
+
)
|
|
2515
|
+
const getTowardMedianHandedness = (rank: number): ViaHandedness =>
|
|
2516
|
+
rank < connectionsBySourceTrack.length / 2 ? 1 : -1
|
|
2517
|
+
const middleRank = Math.floor(connectionsBySourceTrack.length / 2)
|
|
2518
|
+
const singleFlipRanks = [
|
|
2519
|
+
0,
|
|
2520
|
+
1,
|
|
2521
|
+
middleRank,
|
|
2522
|
+
middleRank + 1,
|
|
2523
|
+
...connectionsBySourceTrack.map((_, rank) => rank),
|
|
2524
|
+
].filter(
|
|
2525
|
+
(rank, index, ranks) =>
|
|
2526
|
+
rank >= 0 &&
|
|
2527
|
+
rank < connectionsBySourceTrack.length &&
|
|
2528
|
+
ranks.indexOf(rank) === index,
|
|
2529
|
+
)
|
|
2530
|
+
const towardMedianFlipRankSets = [
|
|
2531
|
+
[middleRank],
|
|
2532
|
+
[middleRank + 1],
|
|
2533
|
+
[0],
|
|
2534
|
+
[1],
|
|
2535
|
+
[0, middleRank + 1],
|
|
2536
|
+
[1, middleRank],
|
|
2537
|
+
[0, middleRank],
|
|
2538
|
+
[1, middleRank + 1],
|
|
2539
|
+
[0, 1],
|
|
2540
|
+
[middleRank, middleRank + 1],
|
|
2541
|
+
...singleFlipRanks.map((rank) => [rank]),
|
|
2542
|
+
]
|
|
2543
|
+
.map((ranks) =>
|
|
2544
|
+
ranks
|
|
2545
|
+
.filter((rank) => rank >= 0 && rank < connectionsBySourceTrack.length)
|
|
2546
|
+
.toSorted((first, second) => first - second),
|
|
2547
|
+
)
|
|
2548
|
+
.filter(
|
|
2549
|
+
(ranks, index, rankSets) =>
|
|
2550
|
+
ranks.length > 0 &&
|
|
2551
|
+
rankSets.findIndex(
|
|
2552
|
+
(candidate) => candidate.join(",") === ranks.join(","),
|
|
2553
|
+
) === index,
|
|
2554
|
+
)
|
|
2555
|
+
const mixedDogboneTerminalPatterns: CoordinatedTerminalPattern[] =
|
|
2556
|
+
!allowBlindAndBuriedVias &&
|
|
2557
|
+
(reservedVias.length > 0 ||
|
|
2558
|
+
acceptedPlans.some((plan) => plan.termination.type === "plane")) &&
|
|
2559
|
+
viaHandednesses.includes(-1) &&
|
|
2560
|
+
viaHandednesses.includes(1)
|
|
2561
|
+
? [
|
|
2562
|
+
{
|
|
2563
|
+
label: "toward-source-median",
|
|
2564
|
+
useViaInPad: false,
|
|
2565
|
+
maximumRouteOrderAttempts: maximumThroughAllRouteOrderAttempts,
|
|
2566
|
+
getViaHandedness: (connection) =>
|
|
2567
|
+
getTowardMedianHandedness(
|
|
2568
|
+
sourceTrackRankByConnectionIndex.get(
|
|
2569
|
+
connection.connectionIndex,
|
|
2570
|
+
) ?? 0,
|
|
2571
|
+
),
|
|
2572
|
+
},
|
|
2573
|
+
...towardMedianFlipRankSets.slice(0, 12).map((flippedRanks) => ({
|
|
2574
|
+
label: `toward-source-median-with-ranks-${flippedRanks.join("-")}-flipped`,
|
|
2575
|
+
useViaInPad: false,
|
|
2576
|
+
maximumRouteOrderAttempts: 6,
|
|
2577
|
+
getViaHandedness: (connection: PreparedConnection) => {
|
|
2578
|
+
const rank =
|
|
2579
|
+
sourceTrackRankByConnectionIndex.get(
|
|
2580
|
+
connection.connectionIndex,
|
|
2581
|
+
) ?? 0
|
|
2582
|
+
const towardMedian = getTowardMedianHandedness(rank)
|
|
2583
|
+
return flippedRanks.includes(rank)
|
|
2584
|
+
? (-towardMedian as ViaHandedness)
|
|
2585
|
+
: towardMedian
|
|
2586
|
+
},
|
|
2587
|
+
})),
|
|
2588
|
+
{
|
|
2589
|
+
label: "alternating-source-grid-a",
|
|
2590
|
+
useViaInPad: false,
|
|
2591
|
+
maximumRouteOrderAttempts: 3,
|
|
2592
|
+
getViaHandedness: (connection) =>
|
|
2593
|
+
(sourceTrackRankByConnectionIndex.get(
|
|
2594
|
+
connection.connectionIndex,
|
|
2595
|
+
) ?? 0) %
|
|
2596
|
+
2 ===
|
|
2597
|
+
0
|
|
2598
|
+
? -1
|
|
2599
|
+
: 1,
|
|
2600
|
+
},
|
|
2601
|
+
{
|
|
2602
|
+
label: "alternating-source-grid-b",
|
|
2603
|
+
useViaInPad: false,
|
|
2604
|
+
maximumRouteOrderAttempts: 3,
|
|
2605
|
+
getViaHandedness: (connection) =>
|
|
2606
|
+
(sourceTrackRankByConnectionIndex.get(
|
|
2607
|
+
connection.connectionIndex,
|
|
2608
|
+
) ?? 0) %
|
|
2609
|
+
2 ===
|
|
2610
|
+
0
|
|
2611
|
+
? 1
|
|
2612
|
+
: -1,
|
|
2613
|
+
},
|
|
2614
|
+
{
|
|
2615
|
+
label: "away-from-source-median",
|
|
2616
|
+
useViaInPad: false,
|
|
2617
|
+
maximumRouteOrderAttempts: maximumThroughAllRouteOrderAttempts,
|
|
2618
|
+
getViaHandedness: (connection) =>
|
|
2619
|
+
(sourceTrackRankByConnectionIndex.get(
|
|
2620
|
+
connection.connectionIndex,
|
|
2621
|
+
) ?? 0) <
|
|
2622
|
+
connectionsBySourceTrack.length / 2
|
|
2623
|
+
? -1
|
|
2624
|
+
: 1,
|
|
2625
|
+
},
|
|
2626
|
+
]
|
|
2627
|
+
: []
|
|
2628
|
+
const viaInPadTerminalPattern = {
|
|
2629
|
+
label: "via-in-pad",
|
|
2630
|
+
useViaInPad: true,
|
|
2631
|
+
getViaHandedness: () => 0 as const,
|
|
2632
|
+
}
|
|
2633
|
+
const fixedViaTerminalPatterns: CoordinatedTerminalPattern[] =
|
|
2634
|
+
fixedViaPointsByConnectionIndex
|
|
2635
|
+
? [
|
|
2636
|
+
...Array.from(
|
|
2637
|
+
{ length: windingTargetOrderCount },
|
|
2638
|
+
(_, windingOrderIndex) => ({
|
|
2639
|
+
label: `component-matched-vias-winding-${windingOrderIndex}`,
|
|
2640
|
+
useViaInPad: false,
|
|
2641
|
+
getViaHandedness: () => 0 as const,
|
|
2642
|
+
getViaPoint: (connection: PreparedConnection) =>
|
|
2643
|
+
fixedViaPointsByConnectionIndex.get(
|
|
2644
|
+
connection.connectionIndex,
|
|
2645
|
+
)!,
|
|
2646
|
+
maximumRouteOrderAttempts: 1,
|
|
2647
|
+
windingOrderIndex,
|
|
2648
|
+
preferTargetDirectedLaneBias: true,
|
|
2649
|
+
}),
|
|
2650
|
+
),
|
|
2651
|
+
{
|
|
2652
|
+
label: "component-matched-vias-fallback",
|
|
2653
|
+
useViaInPad: false,
|
|
2654
|
+
getViaHandedness: () => 0,
|
|
2655
|
+
getViaPoint: (connection) =>
|
|
2656
|
+
fixedViaPointsByConnectionIndex.get(
|
|
2657
|
+
connection.connectionIndex,
|
|
2658
|
+
)!,
|
|
2659
|
+
// Preserve the existing bounded search after every inexpensive
|
|
2660
|
+
// layer-interleave candidate has had one deterministic attempt.
|
|
2661
|
+
maximumRouteOrderAttempts: maximumThroughAllRouteOrderAttempts,
|
|
2662
|
+
windingOrderIndex: 0,
|
|
2663
|
+
preferTargetDirectedLaneBias: true,
|
|
2664
|
+
},
|
|
2665
|
+
]
|
|
2666
|
+
: []
|
|
2667
|
+
const planeTerminationsAlreadyOccupyTheFanout = acceptedPlans.some(
|
|
2668
|
+
(plan) => plan.termination.type === "plane",
|
|
2669
|
+
)
|
|
2670
|
+
const acceptedBoundaryPlansExist = acceptedPlans.some(
|
|
2671
|
+
(plan) => plan.termination.type === "boundary",
|
|
2672
|
+
)
|
|
2673
|
+
const dogboneTerminalPatterns =
|
|
2674
|
+
acceptedBoundaryPlansExist && !allowBlindAndBuriedVias
|
|
2675
|
+
? [...mixedDogboneTerminalPatterns, ...uniformDogboneTerminalPatterns]
|
|
2676
|
+
: [...uniformDogboneTerminalPatterns, ...mixedDogboneTerminalPatterns]
|
|
2677
|
+
const terminalPatterns: CoordinatedTerminalPattern[] =
|
|
2678
|
+
fixedViaTerminalPatterns.length > 0
|
|
2679
|
+
? fixedViaTerminalPatterns
|
|
2680
|
+
: canUseViaInPadTerminals
|
|
2681
|
+
? planeTerminationsAlreadyOccupyTheFanout
|
|
2682
|
+
? [viaInPadTerminalPattern, ...dogboneTerminalPatterns]
|
|
2683
|
+
: [...dogboneTerminalPatterns, viaInPadTerminalPattern]
|
|
2684
|
+
: dogboneTerminalPatterns
|
|
2685
|
+
const seenTerminalSignatures = new Set<string>()
|
|
2686
|
+
for (const terminalPattern of terminalPatterns) {
|
|
1848
2687
|
const terminals = bus.connections.map((preparedConnection) => {
|
|
2688
|
+
const viaHandedness =
|
|
2689
|
+
terminalPattern.getViaHandedness(preparedConnection)
|
|
1849
2690
|
const boundaryTrack = cornerSide
|
|
1850
2691
|
? getCornerTargetTrack({
|
|
1851
2692
|
bus,
|
|
@@ -1855,6 +2696,8 @@ export function routeBusAlternatives(
|
|
|
1855
2696
|
viaDiameter,
|
|
1856
2697
|
clearance,
|
|
1857
2698
|
layerNames,
|
|
2699
|
+
targetLayer,
|
|
2700
|
+
windingOrderIndex: terminalPattern.windingOrderIndex,
|
|
1858
2701
|
})
|
|
1859
2702
|
: getPerpendicularAxis(
|
|
1860
2703
|
preparedConnection.exitTargetPoint ??
|
|
@@ -1863,15 +2706,22 @@ export function routeBusAlternatives(
|
|
|
1863
2706
|
)
|
|
1864
2707
|
return {
|
|
1865
2708
|
connection: preparedConnection,
|
|
1866
|
-
viaPoint:
|
|
1867
|
-
preparedConnection
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
2709
|
+
viaPoint: terminalPattern.getViaPoint
|
|
2710
|
+
? terminalPattern.getViaPoint(preparedConnection)
|
|
2711
|
+
: terminalPattern.useViaInPad
|
|
2712
|
+
? {
|
|
2713
|
+
x: preparedConnection.sourcePoint.x,
|
|
2714
|
+
y: preparedConnection.sourcePoint.y,
|
|
2715
|
+
}
|
|
2716
|
+
: getInitialViaPoint({
|
|
2717
|
+
preparedConnection,
|
|
2718
|
+
bus,
|
|
2719
|
+
targetLayer,
|
|
2720
|
+
traceWidth,
|
|
2721
|
+
viaDiameter,
|
|
2722
|
+
clearance,
|
|
2723
|
+
viaHandedness,
|
|
2724
|
+
}),
|
|
1875
2725
|
exitPoint: makePoint(
|
|
1876
2726
|
boundaryExitAxis,
|
|
1877
2727
|
boundaryTrack,
|
|
@@ -1879,36 +2729,80 @@ export function routeBusAlternatives(
|
|
|
1879
2729
|
),
|
|
1880
2730
|
}
|
|
1881
2731
|
})
|
|
1882
|
-
const
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
2732
|
+
const terminalSignature = `${terminals
|
|
2733
|
+
.map(
|
|
2734
|
+
(terminal) =>
|
|
2735
|
+
`${terminal.connection.connectionIndex}:${terminal.viaPoint.x}:${terminal.viaPoint.y}:${terminal.exitPoint.x}:${terminal.exitPoint.y}`,
|
|
2736
|
+
)
|
|
2737
|
+
.join("|")}:${terminalPattern.maximumRouteOrderAttempts ?? "all"}`
|
|
2738
|
+
if (seenTerminalSignatures.has(terminalSignature)) continue
|
|
2739
|
+
seenTerminalSignatures.add(terminalSignature)
|
|
2740
|
+
const viaMinimalAlternatives = routeViaMinimalWindingAlternatives(
|
|
2741
|
+
{
|
|
2742
|
+
srj,
|
|
2743
|
+
bus,
|
|
2744
|
+
targetLayer,
|
|
2745
|
+
terminals,
|
|
2746
|
+
acceptedPlans,
|
|
2747
|
+
layerNames,
|
|
2748
|
+
traceWidth,
|
|
2749
|
+
viaDiameter,
|
|
2750
|
+
viaHoleDiameter,
|
|
2751
|
+
clearance,
|
|
2752
|
+
allowBlindAndBuriedVias,
|
|
2753
|
+
allowSameNetMerges,
|
|
2754
|
+
maximumRouteOrderAttempts: terminalPattern.maximumRouteOrderAttempts,
|
|
2755
|
+
reservedVias,
|
|
2756
|
+
gridStepDivisor:
|
|
2757
|
+
fixedViaPointsByConnectionIndex &&
|
|
2758
|
+
Math.min(bus.pitchX, bus.pitchY) -
|
|
2759
|
+
2 * (viaDiameter / 2 + traceWidth / 2 + clearance) <
|
|
2760
|
+
traceWidth + clearance
|
|
2761
|
+
? 2
|
|
2762
|
+
: 1,
|
|
2763
|
+
preferTargetDirectedLaneBias:
|
|
2764
|
+
terminalPattern.preferTargetDirectedLaneBias,
|
|
2765
|
+
},
|
|
2766
|
+
fixedViaPointsByConnectionIndex && viaMinimalOnly
|
|
2767
|
+
? Math.min(2, Math.max(1, maxAlternatives - alternatives.length))
|
|
2768
|
+
: terminalPattern.maximumRouteOrderAttempts === undefined
|
|
2769
|
+
? Math.min(2, maxAlternatives - alternatives.length)
|
|
2770
|
+
: 2,
|
|
2771
|
+
)
|
|
2772
|
+
for (const viaMinimalPlans of viaMinimalAlternatives) {
|
|
2773
|
+
const combinedPlansAreClear = fanoutPlansAreClear({
|
|
1898
2774
|
plans: [...acceptedPlans, ...viaMinimalPlans],
|
|
1899
2775
|
srj,
|
|
1900
2776
|
sharedBoundary: bus.sharedBoundary,
|
|
1901
2777
|
clearance,
|
|
2778
|
+
allowBlindAndBuriedVias,
|
|
1902
2779
|
allowSameNetMerges,
|
|
1903
2780
|
})
|
|
1904
|
-
|
|
1905
|
-
|
|
2781
|
+
if (!combinedPlansAreClear) {
|
|
2782
|
+
const candidateIsInternallyClear = fanoutPlansAreClear({
|
|
2783
|
+
plans: viaMinimalPlans,
|
|
2784
|
+
srj,
|
|
2785
|
+
sharedBoundary: bus.sharedBoundary,
|
|
2786
|
+
clearance,
|
|
2787
|
+
allowBlindAndBuriedVias,
|
|
2788
|
+
allowSameNetMerges,
|
|
2789
|
+
})
|
|
2790
|
+
if (candidateIsInternallyClear && rejectedViaMinimalCandidates) {
|
|
2791
|
+
rejectedViaMinimalCandidates.push(viaMinimalPlans)
|
|
2792
|
+
if (stopAfterFirstRejectedViaMinimalCandidate) {
|
|
2793
|
+
return alternatives
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
continue
|
|
2797
|
+
}
|
|
2798
|
+
addAlternative(viaMinimalPlans)
|
|
2799
|
+
if (alternatives.length >= maxAlternatives) return alternatives
|
|
1906
2800
|
}
|
|
1907
|
-
addAlternative(viaMinimalPlans)
|
|
1908
|
-
if (alternatives.length >= maxAlternatives) return alternatives
|
|
1909
2801
|
}
|
|
1910
2802
|
}
|
|
1911
2803
|
|
|
2804
|
+
if (viaMinimalOnly) return alternatives
|
|
2805
|
+
|
|
1912
2806
|
const searchConnectionOrder = (
|
|
1913
2807
|
connectionOrder: PreparedConnection[],
|
|
1914
2808
|
viaHandedness: ViaHandedness,
|
|
@@ -1987,6 +2881,7 @@ export function routeBusAlternatives(
|
|
|
1987
2881
|
cornerBoundaryChannelLaneOffset: cornerLaneOffsets.boundaryChannel,
|
|
1988
2882
|
clearance,
|
|
1989
2883
|
terminateAtVia: false,
|
|
2884
|
+
allowBlindAndBuriedVias,
|
|
1990
2885
|
})
|
|
1991
2886
|
if (
|
|
1992
2887
|
!planIsClear({
|
|
@@ -1998,6 +2893,7 @@ export function routeBusAlternatives(
|
|
|
1998
2893
|
srj,
|
|
1999
2894
|
sharedBoundary: bus.sharedBoundary,
|
|
2000
2895
|
clearance,
|
|
2896
|
+
allowBlindAndBuriedVias,
|
|
2001
2897
|
allowSameNetMerges,
|
|
2002
2898
|
})
|
|
2003
2899
|
) {
|