@tscircuit/fanout-solver 0.0.37 → 0.0.39
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 +23 -2
- package/lib/add-via-layer-metadata.ts +48 -0
- package/lib/complete-original-endpoints.ts +30 -2
- package/lib/fanout-solver.ts +721 -33
- 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 +681 -0
- package/lib/match-component-dogbone-via-sites.ts +584 -0
- package/lib/prepare-buses.ts +26 -0
- package/lib/route-bus.ts +872 -112
- package/lib/route-via-minimal-winding.ts +390 -74
- package/lib/types.ts +39 -7
- package/lib/validate-fanout-solution.ts +62 -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
|
}
|
|
@@ -295,6 +312,83 @@ function getPerpendicularPitch(bus: PreparedBus): number {
|
|
|
295
312
|
return isHorizontal(bus.direction) ? bus.pitchY : bus.pitchX
|
|
296
313
|
}
|
|
297
314
|
|
|
315
|
+
function chamferOrthogonalCorners(
|
|
316
|
+
points: readonly Point2D[],
|
|
317
|
+
requestedChamfer: number,
|
|
318
|
+
): Point2D[] {
|
|
319
|
+
if (points.length < 3) return [...points]
|
|
320
|
+
const output: Point2D[] = [points[0]!]
|
|
321
|
+
for (let index = 1; index < points.length - 1; index++) {
|
|
322
|
+
const previous = points[index - 1]!
|
|
323
|
+
const current = points[index]!
|
|
324
|
+
const next = points[index + 1]!
|
|
325
|
+
const incoming = { x: current.x - previous.x, y: current.y - previous.y }
|
|
326
|
+
const outgoing = { x: next.x - current.x, y: next.y - current.y }
|
|
327
|
+
const incomingLength = Math.hypot(incoming.x, incoming.y)
|
|
328
|
+
const outgoingLength = Math.hypot(outgoing.x, outgoing.y)
|
|
329
|
+
const incomingIsAxisAligned =
|
|
330
|
+
Math.abs(incoming.x) <= 1e-9 || Math.abs(incoming.y) <= 1e-9
|
|
331
|
+
const outgoingIsAxisAligned =
|
|
332
|
+
Math.abs(outgoing.x) <= 1e-9 || Math.abs(outgoing.y) <= 1e-9
|
|
333
|
+
const isOrthogonal =
|
|
334
|
+
Math.abs(incoming.x * outgoing.x + incoming.y * outgoing.y) <= 1e-9
|
|
335
|
+
if (
|
|
336
|
+
incomingLength <= 1e-9 ||
|
|
337
|
+
outgoingLength <= 1e-9 ||
|
|
338
|
+
!incomingIsAxisAligned ||
|
|
339
|
+
!outgoingIsAxisAligned ||
|
|
340
|
+
!isOrthogonal
|
|
341
|
+
) {
|
|
342
|
+
output.push(current)
|
|
343
|
+
continue
|
|
344
|
+
}
|
|
345
|
+
const chamfer = Math.min(
|
|
346
|
+
requestedChamfer,
|
|
347
|
+
incomingLength / 3,
|
|
348
|
+
outgoingLength / 3,
|
|
349
|
+
)
|
|
350
|
+
output.push({
|
|
351
|
+
x: current.x - (incoming.x / incomingLength) * chamfer,
|
|
352
|
+
y: current.y - (incoming.y / incomingLength) * chamfer,
|
|
353
|
+
})
|
|
354
|
+
output.push({
|
|
355
|
+
x: current.x + (outgoing.x / outgoingLength) * chamfer,
|
|
356
|
+
y: current.y + (outgoing.y / outgoingLength) * chamfer,
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
output.push(points.at(-1)!)
|
|
360
|
+
return output.filter(
|
|
361
|
+
(point, index) => index === 0 || distance(point, output[index - 1]!) > 1e-9,
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function getStraightOr45ConnectorVariants(
|
|
366
|
+
start: Point2D,
|
|
367
|
+
end: Point2D,
|
|
368
|
+
): Point2D[][] {
|
|
369
|
+
const deltaX = end.x - start.x
|
|
370
|
+
const deltaY = end.y - start.y
|
|
371
|
+
const absoluteX = Math.abs(deltaX)
|
|
372
|
+
const absoluteY = Math.abs(deltaY)
|
|
373
|
+
if (
|
|
374
|
+
absoluteX <= 1e-9 ||
|
|
375
|
+
absoluteY <= 1e-9 ||
|
|
376
|
+
Math.abs(absoluteX - absoluteY) <= 1e-9
|
|
377
|
+
) {
|
|
378
|
+
return [[start, end]]
|
|
379
|
+
}
|
|
380
|
+
if (absoluteX > absoluteY) {
|
|
381
|
+
return [
|
|
382
|
+
[start, { x: start.x + Math.sign(deltaX) * absoluteY, y: end.y }, end],
|
|
383
|
+
[start, { x: end.x - Math.sign(deltaX) * absoluteY, y: start.y }, end],
|
|
384
|
+
]
|
|
385
|
+
}
|
|
386
|
+
return [
|
|
387
|
+
[start, { x: end.x, y: start.y + Math.sign(deltaY) * absoluteX }, end],
|
|
388
|
+
[start, { x: start.x, y: end.y - Math.sign(deltaY) * absoluteX }, end],
|
|
389
|
+
]
|
|
390
|
+
}
|
|
391
|
+
|
|
298
392
|
function getDepthInRows(bus: PreparedBus): number {
|
|
299
393
|
const directionalCoordinates = (
|
|
300
394
|
isHorizontal(bus.direction) ? bus.xCoordinates : bus.yCoordinates
|
|
@@ -748,6 +842,9 @@ function buildPlan(params: {
|
|
|
748
842
|
cornerBoundaryChannelLaneOffset: number
|
|
749
843
|
clearance: number
|
|
750
844
|
terminateAtVia: boolean
|
|
845
|
+
allowBlindAndBuriedVias: boolean
|
|
846
|
+
initialViaPoint?: Point2D
|
|
847
|
+
sourceEscapePath?: readonly Point2D[]
|
|
751
848
|
}): FanoutRoutePlan {
|
|
752
849
|
const {
|
|
753
850
|
preparedConnection,
|
|
@@ -767,6 +864,9 @@ function buildPlan(params: {
|
|
|
767
864
|
cornerBoundaryChannelLaneOffset,
|
|
768
865
|
clearance,
|
|
769
866
|
terminateAtVia,
|
|
867
|
+
allowBlindAndBuriedVias,
|
|
868
|
+
initialViaPoint,
|
|
869
|
+
sourceEscapePath,
|
|
770
870
|
} = params
|
|
771
871
|
const sourcePoint = {
|
|
772
872
|
x: preparedConnection.sourcePoint.x,
|
|
@@ -782,15 +882,33 @@ function buildPlan(params: {
|
|
|
782
882
|
const usesLayeredWindingChannel = Boolean(windingCrossoverLayer)
|
|
783
883
|
const sign = directionSign(bus.direction)
|
|
784
884
|
const directionalPitch = getDirectionalPitch(bus)
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
885
|
+
const requestedViaPoint = sourceEscapePath?.at(-1)
|
|
886
|
+
const viaPoint =
|
|
887
|
+
requestedViaPoint !== undefined
|
|
888
|
+
? { x: requestedViaPoint.x, y: requestedViaPoint.y }
|
|
889
|
+
: initialViaPoint === undefined
|
|
890
|
+
? getInitialViaPoint({
|
|
891
|
+
preparedConnection,
|
|
892
|
+
bus,
|
|
893
|
+
targetLayer,
|
|
894
|
+
traceWidth,
|
|
895
|
+
viaDiameter,
|
|
896
|
+
clearance,
|
|
897
|
+
viaHandedness,
|
|
898
|
+
})
|
|
899
|
+
: { x: initialViaPoint.x, y: initialViaPoint.y }
|
|
900
|
+
const resolvedSourceEscapePath = sourceEscapePath
|
|
901
|
+
? sourceEscapePath.map((point) => ({ x: point.x, y: point.y }))
|
|
902
|
+
: [sourcePoint, viaPoint]
|
|
903
|
+
if (
|
|
904
|
+
resolvedSourceEscapePath.length < 2 ||
|
|
905
|
+
distance(resolvedSourceEscapePath[0]!, sourcePoint) > 1e-9 ||
|
|
906
|
+
distance(resolvedSourceEscapePath.at(-1)!, viaPoint) > 1e-9
|
|
907
|
+
) {
|
|
908
|
+
throw new Error(
|
|
909
|
+
`FanoutSolver: source escape path for "${preparedConnection.connection.name}" must run from its source point to its via`,
|
|
910
|
+
)
|
|
911
|
+
}
|
|
794
912
|
const viaAxis = getAxis(viaPoint, bus.direction)
|
|
795
913
|
const viaPerpendicularAxis = getPerpendicularAxis(viaPoint, bus.direction)
|
|
796
914
|
const spreadLaneDistance =
|
|
@@ -899,28 +1017,38 @@ function buildPlan(params: {
|
|
|
899
1017
|
layer: preparedConnection.sourceLayer,
|
|
900
1018
|
start_pcb_port_id: preparedConnection.sourcePoint.pcb_port_id,
|
|
901
1019
|
})
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1020
|
+
for (
|
|
1021
|
+
let pointIndex = 1;
|
|
1022
|
+
pointIndex < resolvedSourceEscapePath.length;
|
|
1023
|
+
pointIndex++
|
|
1024
|
+
) {
|
|
1025
|
+
const previousPoint = resolvedSourceEscapePath[pointIndex - 1]!
|
|
1026
|
+
const point = resolvedSourceEscapePath[pointIndex]!
|
|
1027
|
+
appendSegment(
|
|
1028
|
+
segments,
|
|
1029
|
+
previousPoint,
|
|
1030
|
+
point,
|
|
1031
|
+
traceWidth,
|
|
1032
|
+
preparedConnection.sourceLayer,
|
|
1033
|
+
)
|
|
1034
|
+
if (distance(previousPoint, point) <= 1e-9) continue
|
|
1035
|
+
route.push({
|
|
1036
|
+
route_type: "wire",
|
|
1037
|
+
x: point.x,
|
|
1038
|
+
y: point.y,
|
|
1039
|
+
width: traceWidth,
|
|
1040
|
+
layer: preparedConnection.sourceLayer,
|
|
1041
|
+
})
|
|
1042
|
+
}
|
|
916
1043
|
|
|
917
1044
|
let via: FanoutRoutePlan["via"]
|
|
918
1045
|
if (targetLayer !== preparedConnection.sourceLayer) {
|
|
919
|
-
const spanLayers =
|
|
920
|
-
preparedConnection.sourceLayer,
|
|
921
|
-
targetLayer,
|
|
1046
|
+
const spanLayers = getViaSpanLayers({
|
|
1047
|
+
fromLayer: preparedConnection.sourceLayer,
|
|
1048
|
+
toLayer: targetLayer,
|
|
922
1049
|
layerNames,
|
|
923
|
-
|
|
1050
|
+
allowBlindAndBuriedVias,
|
|
1051
|
+
})
|
|
924
1052
|
via = {
|
|
925
1053
|
center: viaPoint,
|
|
926
1054
|
diameter: viaDiameter,
|
|
@@ -990,7 +1118,12 @@ function buildPlan(params: {
|
|
|
990
1118
|
holeDiameter: viaHoleDiameter,
|
|
991
1119
|
fromLayer,
|
|
992
1120
|
toLayer,
|
|
993
|
-
spanLayers:
|
|
1121
|
+
spanLayers: getViaSpanLayers({
|
|
1122
|
+
fromLayer,
|
|
1123
|
+
toLayer,
|
|
1124
|
+
layerNames,
|
|
1125
|
+
allowBlindAndBuriedVias,
|
|
1126
|
+
}),
|
|
994
1127
|
})
|
|
995
1128
|
route.push({
|
|
996
1129
|
route_type: "via",
|
|
@@ -1066,7 +1199,7 @@ function buildPlan(params: {
|
|
|
1066
1199
|
sourceObstacle: preparedConnection.sourceObstacle,
|
|
1067
1200
|
sourceLayer: preparedConnection.sourceLayer,
|
|
1068
1201
|
targetPoint: preparedConnection.targetPoint,
|
|
1069
|
-
targetLayer
|
|
1202
|
+
targetLayer,
|
|
1070
1203
|
termination: bus.termination,
|
|
1071
1204
|
direction: bus.direction,
|
|
1072
1205
|
...(bus.exitEdge ? { exitEdge: bus.exitEdge } : {}),
|
|
@@ -1190,6 +1323,7 @@ function addPlaneEndpointTerminal(params: {
|
|
|
1190
1323
|
traceWidth: number
|
|
1191
1324
|
viaDiameter: number
|
|
1192
1325
|
viaHoleDiameter: number
|
|
1326
|
+
allowBlindAndBuriedVias: boolean
|
|
1193
1327
|
}): FanoutRoutePlan {
|
|
1194
1328
|
const {
|
|
1195
1329
|
plan,
|
|
@@ -1200,13 +1334,19 @@ function addPlaneEndpointTerminal(params: {
|
|
|
1200
1334
|
traceWidth,
|
|
1201
1335
|
viaDiameter,
|
|
1202
1336
|
viaHoleDiameter,
|
|
1337
|
+
allowBlindAndBuriedVias,
|
|
1203
1338
|
} = params
|
|
1204
1339
|
const targetPoint = {
|
|
1205
1340
|
x: preparedConnection.targetPoint.x,
|
|
1206
1341
|
y: preparedConnection.targetPoint.y,
|
|
1207
1342
|
}
|
|
1208
1343
|
const targetEndpointLayer = getPointLayer(preparedConnection.targetPoint)
|
|
1209
|
-
const spanLayers =
|
|
1344
|
+
const spanLayers = getViaSpanLayers({
|
|
1345
|
+
fromLayer: planeLayer,
|
|
1346
|
+
toLayer: targetEndpointLayer,
|
|
1347
|
+
layerNames,
|
|
1348
|
+
allowBlindAndBuriedVias,
|
|
1349
|
+
})
|
|
1210
1350
|
if (!spanLayers.includes(planeLayer)) {
|
|
1211
1351
|
throw new Error(
|
|
1212
1352
|
`FanoutSolver: via for "${preparedConnection.connection.name}" does not cross plane ${planeLayer}`,
|
|
@@ -1354,14 +1494,36 @@ function getPlanVias(plan: FanoutRoutePlan) {
|
|
|
1354
1494
|
].filter((via): via is NonNullable<FanoutRoutePlan["via"]> => Boolean(via))
|
|
1355
1495
|
}
|
|
1356
1496
|
|
|
1497
|
+
function viaFitsInsidePlanSourcePad(
|
|
1498
|
+
plan: FanoutRoutePlan,
|
|
1499
|
+
via: NonNullable<FanoutRoutePlan["via"]>,
|
|
1500
|
+
): boolean {
|
|
1501
|
+
return (
|
|
1502
|
+
distance(via.center, plan.sourcePoint) <= 1e-9 &&
|
|
1503
|
+
circleFitsInsideObstacle({
|
|
1504
|
+
center: via.center,
|
|
1505
|
+
diameter: via.diameter,
|
|
1506
|
+
obstacle: plan.sourceObstacle,
|
|
1507
|
+
})
|
|
1508
|
+
)
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1357
1511
|
function planIsStaticallyClear(params: {
|
|
1358
1512
|
plan: FanoutRoutePlan
|
|
1359
1513
|
srj: SimpleRouteJson
|
|
1360
1514
|
sharedBoundary: Bounds
|
|
1361
1515
|
clearance: number
|
|
1516
|
+
allowBlindAndBuriedVias: boolean
|
|
1362
1517
|
allowSameNetMerges: boolean
|
|
1363
1518
|
}): boolean {
|
|
1364
|
-
const {
|
|
1519
|
+
const {
|
|
1520
|
+
plan,
|
|
1521
|
+
srj,
|
|
1522
|
+
sharedBoundary,
|
|
1523
|
+
clearance,
|
|
1524
|
+
allowBlindAndBuriedVias,
|
|
1525
|
+
allowSameNetMerges,
|
|
1526
|
+
} = params
|
|
1365
1527
|
const routableBounds = getRoutableBounds(srj.bounds, sharedBoundary)
|
|
1366
1528
|
if (
|
|
1367
1529
|
!pointIsInsideBounds(plan.exitPoint, routableBounds) ||
|
|
@@ -1391,6 +1553,13 @@ function planIsStaticallyClear(params: {
|
|
|
1391
1553
|
}
|
|
1392
1554
|
for (const via of getPlanVias(plan)) {
|
|
1393
1555
|
for (const obstacle of srj.obstacles) {
|
|
1556
|
+
if (
|
|
1557
|
+
allowsViaInPad(srj) &&
|
|
1558
|
+
obstacle === plan.sourceObstacle &&
|
|
1559
|
+
viaFitsInsidePlanSourcePad(plan, via)
|
|
1560
|
+
) {
|
|
1561
|
+
continue
|
|
1562
|
+
}
|
|
1394
1563
|
if (!obstacle.layers.some((layer) => via.spanLayers.includes(layer))) {
|
|
1395
1564
|
continue
|
|
1396
1565
|
}
|
|
@@ -1409,7 +1578,10 @@ function planIsStaticallyClear(params: {
|
|
|
1409
1578
|
}
|
|
1410
1579
|
}
|
|
1411
1580
|
|
|
1412
|
-
for (const traceCopper of getAllRoutedTraceCopper(
|
|
1581
|
+
for (const traceCopper of getAllRoutedTraceCopper(
|
|
1582
|
+
srj,
|
|
1583
|
+
allowBlindAndBuriedVias,
|
|
1584
|
+
)) {
|
|
1413
1585
|
if (
|
|
1414
1586
|
plan.connectionName === traceCopper.connectionName ||
|
|
1415
1587
|
(allowSameNetMerges &&
|
|
@@ -1575,6 +1747,7 @@ function planIsClear(params: {
|
|
|
1575
1747
|
srj: SimpleRouteJson
|
|
1576
1748
|
sharedBoundary: Bounds
|
|
1577
1749
|
clearance: number
|
|
1750
|
+
allowBlindAndBuriedVias: boolean
|
|
1578
1751
|
allowSameNetMerges: boolean
|
|
1579
1752
|
}): boolean {
|
|
1580
1753
|
const {
|
|
@@ -1586,6 +1759,7 @@ function planIsClear(params: {
|
|
|
1586
1759
|
srj,
|
|
1587
1760
|
sharedBoundary,
|
|
1588
1761
|
clearance,
|
|
1762
|
+
allowBlindAndBuriedVias,
|
|
1589
1763
|
allowSameNetMerges,
|
|
1590
1764
|
} = params
|
|
1591
1765
|
let staticallyClear = staticClearanceCache?.get(cacheKey)
|
|
@@ -1595,6 +1769,7 @@ function planIsClear(params: {
|
|
|
1595
1769
|
srj,
|
|
1596
1770
|
sharedBoundary,
|
|
1597
1771
|
clearance,
|
|
1772
|
+
allowBlindAndBuriedVias,
|
|
1598
1773
|
allowSameNetMerges,
|
|
1599
1774
|
})
|
|
1600
1775
|
staticClearanceCache?.set(cacheKey, staticallyClear)
|
|
@@ -1622,6 +1797,7 @@ export function fanoutPlansAreClear(params: {
|
|
|
1622
1797
|
srj: SimpleRouteJson
|
|
1623
1798
|
sharedBoundary: Bounds
|
|
1624
1799
|
clearance: number
|
|
1800
|
+
allowBlindAndBuriedVias?: boolean
|
|
1625
1801
|
allowSameNetMerges?: boolean
|
|
1626
1802
|
}): boolean {
|
|
1627
1803
|
const {
|
|
@@ -1629,6 +1805,7 @@ export function fanoutPlansAreClear(params: {
|
|
|
1629
1805
|
srj,
|
|
1630
1806
|
sharedBoundary,
|
|
1631
1807
|
clearance,
|
|
1808
|
+
allowBlindAndBuriedVias = true,
|
|
1632
1809
|
allowSameNetMerges = false,
|
|
1633
1810
|
} = params
|
|
1634
1811
|
for (let index = 0; index < plans.length; index++) {
|
|
@@ -1639,15 +1816,17 @@ export function fanoutPlansAreClear(params: {
|
|
|
1639
1816
|
srj,
|
|
1640
1817
|
sharedBoundary,
|
|
1641
1818
|
clearance,
|
|
1819
|
+
allowBlindAndBuriedVias,
|
|
1642
1820
|
allowSameNetMerges,
|
|
1643
1821
|
})
|
|
1644
1822
|
) {
|
|
1645
1823
|
return false
|
|
1646
1824
|
}
|
|
1825
|
+
const otherPlans = plans.filter((_, otherIndex) => otherIndex !== index)
|
|
1647
1826
|
if (
|
|
1648
1827
|
!planIsClearOfPlans({
|
|
1649
1828
|
plan,
|
|
1650
|
-
otherPlans
|
|
1829
|
+
otherPlans,
|
|
1651
1830
|
srj,
|
|
1652
1831
|
allowSameNetMerges,
|
|
1653
1832
|
clearance,
|
|
@@ -1674,13 +1853,163 @@ function routePlaneTerminatedBus(
|
|
|
1674
1853
|
clearance,
|
|
1675
1854
|
staticClearanceCache,
|
|
1676
1855
|
blockingBusCounts,
|
|
1856
|
+
allowBlindAndBuriedVias = true,
|
|
1677
1857
|
allowSameNetMerges = false,
|
|
1858
|
+
fixedViaPointsByConnectionIndex,
|
|
1678
1859
|
} = params
|
|
1679
1860
|
const sourceObstacle = bus.connections[0]?.sourceObstacle
|
|
1680
1861
|
if (!sourceObstacle || bus.termination.type !== "plane") return null
|
|
1681
1862
|
const sourceLayer = bus.connections[0]!.sourceLayer
|
|
1682
1863
|
if (targetLayer === sourceLayer) return null
|
|
1683
1864
|
|
|
1865
|
+
if (fixedViaPointsByConnectionIndex) {
|
|
1866
|
+
const fixedPlans: FanoutRoutePlan[] = []
|
|
1867
|
+
for (const preparedConnection of bus.connections) {
|
|
1868
|
+
const fixedViaPoint = fixedViaPointsByConnectionIndex.get(
|
|
1869
|
+
preparedConnection.connectionIndex,
|
|
1870
|
+
)
|
|
1871
|
+
if (!fixedViaPoint) return null
|
|
1872
|
+
const sourcePoint = {
|
|
1873
|
+
x: preparedConnection.sourcePoint.x,
|
|
1874
|
+
y: preparedConnection.sourcePoint.y,
|
|
1875
|
+
}
|
|
1876
|
+
const basePlan = buildPlan({
|
|
1877
|
+
preparedConnection,
|
|
1878
|
+
bus,
|
|
1879
|
+
targetLayer,
|
|
1880
|
+
track: getPerpendicularAxis(sourcePoint, bus.direction),
|
|
1881
|
+
exitAxis: getExitAxis(bus),
|
|
1882
|
+
layerNames,
|
|
1883
|
+
traceWidth,
|
|
1884
|
+
viaDiameter,
|
|
1885
|
+
viaHoleDiameter,
|
|
1886
|
+
viaHandedness: 0,
|
|
1887
|
+
interstitialEscape: false,
|
|
1888
|
+
spreadLaneIndex: 0,
|
|
1889
|
+
cornerExitLaneOffset: 0,
|
|
1890
|
+
cornerLocalChannelLaneOffset: 0,
|
|
1891
|
+
cornerBoundaryChannelLaneOffset: 0,
|
|
1892
|
+
clearance,
|
|
1893
|
+
terminateAtVia: true,
|
|
1894
|
+
allowBlindAndBuriedVias,
|
|
1895
|
+
initialViaPoint: fixedViaPoint,
|
|
1896
|
+
sourceEscapePath: [sourcePoint, fixedViaPoint],
|
|
1897
|
+
})
|
|
1898
|
+
const endpointViaCandidates = getPlaneEndpointViaCandidates({
|
|
1899
|
+
preparedConnection,
|
|
1900
|
+
bus,
|
|
1901
|
+
viaDiameter,
|
|
1902
|
+
clearance,
|
|
1903
|
+
})
|
|
1904
|
+
const plansToTry = [
|
|
1905
|
+
...endpointViaCandidates.map((viaPoint) =>
|
|
1906
|
+
addPlaneEndpointTerminal({
|
|
1907
|
+
plan: basePlan,
|
|
1908
|
+
preparedConnection,
|
|
1909
|
+
planeLayer: targetLayer,
|
|
1910
|
+
viaPoint,
|
|
1911
|
+
layerNames,
|
|
1912
|
+
traceWidth,
|
|
1913
|
+
viaDiameter,
|
|
1914
|
+
viaHoleDiameter,
|
|
1915
|
+
allowBlindAndBuriedVias,
|
|
1916
|
+
}),
|
|
1917
|
+
),
|
|
1918
|
+
basePlan,
|
|
1919
|
+
]
|
|
1920
|
+
const clearPlan = plansToTry.find((candidatePlan, candidateIndex) =>
|
|
1921
|
+
planIsClear({
|
|
1922
|
+
plan: candidatePlan,
|
|
1923
|
+
otherPlans: [...acceptedPlans, ...fixedPlans],
|
|
1924
|
+
staticClearanceCache,
|
|
1925
|
+
blockingBusCounts,
|
|
1926
|
+
cacheKey: `plane-fixed:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${candidateIndex}`,
|
|
1927
|
+
srj,
|
|
1928
|
+
sharedBoundary: bus.sharedBoundary,
|
|
1929
|
+
clearance,
|
|
1930
|
+
allowBlindAndBuriedVias,
|
|
1931
|
+
allowSameNetMerges,
|
|
1932
|
+
}),
|
|
1933
|
+
)
|
|
1934
|
+
if (!clearPlan) return null
|
|
1935
|
+
fixedPlans.push(clearPlan)
|
|
1936
|
+
}
|
|
1937
|
+
return fixedPlans
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
if (
|
|
1941
|
+
allowsViaInPad(srj) &&
|
|
1942
|
+
bus.connections.length === 1 &&
|
|
1943
|
+
circleFitsInsideObstacle({
|
|
1944
|
+
center: bus.connections[0]!.sourcePoint,
|
|
1945
|
+
diameter: viaDiameter,
|
|
1946
|
+
obstacle: sourceObstacle,
|
|
1947
|
+
})
|
|
1948
|
+
) {
|
|
1949
|
+
const preparedConnection = bus.connections[0]!
|
|
1950
|
+
const viaInPadPlan = buildPlan({
|
|
1951
|
+
preparedConnection,
|
|
1952
|
+
bus,
|
|
1953
|
+
targetLayer,
|
|
1954
|
+
track: getPerpendicularAxis(
|
|
1955
|
+
preparedConnection.sourcePoint,
|
|
1956
|
+
bus.direction,
|
|
1957
|
+
),
|
|
1958
|
+
exitAxis: getExitAxis(bus),
|
|
1959
|
+
layerNames,
|
|
1960
|
+
traceWidth,
|
|
1961
|
+
viaDiameter,
|
|
1962
|
+
viaHoleDiameter,
|
|
1963
|
+
viaHandedness: 0,
|
|
1964
|
+
interstitialEscape: false,
|
|
1965
|
+
spreadLaneIndex: 0,
|
|
1966
|
+
cornerExitLaneOffset: 0,
|
|
1967
|
+
cornerLocalChannelLaneOffset: 0,
|
|
1968
|
+
cornerBoundaryChannelLaneOffset: 0,
|
|
1969
|
+
clearance,
|
|
1970
|
+
terminateAtVia: true,
|
|
1971
|
+
allowBlindAndBuriedVias,
|
|
1972
|
+
initialViaPoint: preparedConnection.sourcePoint,
|
|
1973
|
+
})
|
|
1974
|
+
const endpointViaCandidates = getPlaneEndpointViaCandidates({
|
|
1975
|
+
preparedConnection,
|
|
1976
|
+
bus,
|
|
1977
|
+
viaDiameter,
|
|
1978
|
+
clearance,
|
|
1979
|
+
})
|
|
1980
|
+
const plansToTry = [
|
|
1981
|
+
...endpointViaCandidates.map((viaPoint) =>
|
|
1982
|
+
addPlaneEndpointTerminal({
|
|
1983
|
+
plan: viaInPadPlan,
|
|
1984
|
+
preparedConnection,
|
|
1985
|
+
planeLayer: targetLayer,
|
|
1986
|
+
viaPoint,
|
|
1987
|
+
layerNames,
|
|
1988
|
+
traceWidth,
|
|
1989
|
+
viaDiameter,
|
|
1990
|
+
viaHoleDiameter,
|
|
1991
|
+
allowBlindAndBuriedVias,
|
|
1992
|
+
}),
|
|
1993
|
+
),
|
|
1994
|
+
viaInPadPlan,
|
|
1995
|
+
]
|
|
1996
|
+
const clearPlan = plansToTry.find((candidatePlan, candidateIndex) =>
|
|
1997
|
+
planIsClear({
|
|
1998
|
+
plan: candidatePlan,
|
|
1999
|
+
otherPlans: acceptedPlans,
|
|
2000
|
+
staticClearanceCache,
|
|
2001
|
+
blockingBusCounts,
|
|
2002
|
+
cacheKey: `plane-via-in-pad:${bus.busId}:${targetLayer}:${candidateIndex}`,
|
|
2003
|
+
srj,
|
|
2004
|
+
sharedBoundary: bus.sharedBoundary,
|
|
2005
|
+
clearance,
|
|
2006
|
+
allowBlindAndBuriedVias,
|
|
2007
|
+
allowSameNetMerges,
|
|
2008
|
+
}),
|
|
2009
|
+
)
|
|
2010
|
+
if (clearPlan) return [clearPlan]
|
|
2011
|
+
}
|
|
2012
|
+
|
|
1684
2013
|
const candidateDirections: FanoutDirection[] = [
|
|
1685
2014
|
bus.direction,
|
|
1686
2015
|
...(["left", "right", "up", "down"] as const).filter(
|
|
@@ -1709,59 +2038,194 @@ function routePlaneTerminatedBus(
|
|
|
1709
2038
|
preparedConnection.sourcePoint,
|
|
1710
2039
|
direction,
|
|
1711
2040
|
)
|
|
1712
|
-
const
|
|
2041
|
+
const adjacentViaPoint = getInitialViaPoint({
|
|
1713
2042
|
preparedConnection,
|
|
1714
2043
|
bus: directionalBus,
|
|
1715
2044
|
targetLayer,
|
|
1716
|
-
track: sourceTrack,
|
|
1717
|
-
exitAxis: getExitAxis(directionalBus),
|
|
1718
|
-
layerNames,
|
|
1719
2045
|
traceWidth,
|
|
1720
2046
|
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
2047
|
clearance,
|
|
2048
|
+
viaHandedness,
|
|
1736
2049
|
})
|
|
1737
|
-
const
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
const
|
|
1753
|
-
|
|
1754
|
-
|
|
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
|
-
}),
|
|
2050
|
+
const directionPitch = getDirectionalPitch(directionalBus)
|
|
2051
|
+
const sign = directionSign(direction)
|
|
2052
|
+
const boundaryAxis = getExitAxis(directionalBus, direction)
|
|
2053
|
+
const availableTravel =
|
|
2054
|
+
sign * (boundaryAxis - getAxis(adjacentViaPoint, direction)) -
|
|
2055
|
+
(viaDiameter / 2 + clearance)
|
|
2056
|
+
const maximumEscapeSteps = Math.max(
|
|
2057
|
+
0,
|
|
2058
|
+
Math.floor(availableTravel / directionPitch),
|
|
2059
|
+
)
|
|
2060
|
+
const sourcePoint = {
|
|
2061
|
+
x: preparedConnection.sourcePoint.x,
|
|
2062
|
+
y: preparedConnection.sourcePoint.y,
|
|
2063
|
+
}
|
|
2064
|
+
const adjacentAxis = getAxis(adjacentViaPoint, direction)
|
|
2065
|
+
const adjacentPerpendicularAxis = getPerpendicularAxis(
|
|
2066
|
+
adjacentViaPoint,
|
|
2067
|
+
direction,
|
|
1764
2068
|
)
|
|
2069
|
+
const perpendicularPitch = getPerpendicularPitch(directionalBus)
|
|
2070
|
+
const sourceEscapePaths: Point2D[][] = []
|
|
2071
|
+
const maximumCandidatePaths = 32
|
|
2072
|
+
for (
|
|
2073
|
+
let totalSteps = 0;
|
|
2074
|
+
totalSteps <= maximumEscapeSteps &&
|
|
2075
|
+
sourceEscapePaths.length < maximumCandidatePaths;
|
|
2076
|
+
totalSteps++
|
|
2077
|
+
) {
|
|
2078
|
+
const straightViaPoint = makePoint(
|
|
2079
|
+
adjacentAxis + sign * totalSteps * directionPitch,
|
|
2080
|
+
adjacentPerpendicularAxis,
|
|
2081
|
+
direction,
|
|
2082
|
+
)
|
|
2083
|
+
if (totalSteps === 0) {
|
|
2084
|
+
sourceEscapePaths.push([sourcePoint, adjacentViaPoint])
|
|
2085
|
+
} else {
|
|
2086
|
+
for (const connector of getStraightOr45ConnectorVariants(
|
|
2087
|
+
sourcePoint,
|
|
2088
|
+
straightViaPoint,
|
|
2089
|
+
)) {
|
|
2090
|
+
sourceEscapePaths.push(connector)
|
|
2091
|
+
if (sourceEscapePaths.length >= maximumCandidatePaths) break
|
|
2092
|
+
}
|
|
2093
|
+
if (sourceEscapePaths.length < maximumCandidatePaths) {
|
|
2094
|
+
sourceEscapePaths.push([
|
|
2095
|
+
sourcePoint,
|
|
2096
|
+
adjacentViaPoint,
|
|
2097
|
+
straightViaPoint,
|
|
2098
|
+
])
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
for (
|
|
2103
|
+
let lateralSteps = 1;
|
|
2104
|
+
lateralSteps <= Math.min(3, totalSteps - 1) &&
|
|
2105
|
+
sourceEscapePaths.length < maximumCandidatePaths;
|
|
2106
|
+
lateralSteps++
|
|
2107
|
+
) {
|
|
2108
|
+
const outwardSteps = totalSteps - lateralSteps
|
|
2109
|
+
if (outwardSteps < 1 || outwardSteps > maximumEscapeSteps) {
|
|
2110
|
+
continue
|
|
2111
|
+
}
|
|
2112
|
+
for (const lateralSign of [-1, 1] as const) {
|
|
2113
|
+
const lateralAxis =
|
|
2114
|
+
adjacentPerpendicularAxis +
|
|
2115
|
+
lateralSign * lateralSteps * perpendicularPitch
|
|
2116
|
+
const lateralPoint = makePoint(
|
|
2117
|
+
adjacentAxis,
|
|
2118
|
+
lateralAxis,
|
|
2119
|
+
direction,
|
|
2120
|
+
)
|
|
2121
|
+
const outwardPoint = makePoint(
|
|
2122
|
+
adjacentAxis + sign * outwardSteps * directionPitch,
|
|
2123
|
+
adjacentPerpendicularAxis,
|
|
2124
|
+
direction,
|
|
2125
|
+
)
|
|
2126
|
+
const detourViaPoint = makePoint(
|
|
2127
|
+
adjacentAxis + sign * outwardSteps * directionPitch,
|
|
2128
|
+
lateralAxis,
|
|
2129
|
+
direction,
|
|
2130
|
+
)
|
|
2131
|
+
const chamfer =
|
|
2132
|
+
Math.min(directionPitch, perpendicularPitch) * 0.2
|
|
2133
|
+
for (const connector of getStraightOr45ConnectorVariants(
|
|
2134
|
+
sourcePoint,
|
|
2135
|
+
detourViaPoint,
|
|
2136
|
+
)) {
|
|
2137
|
+
sourceEscapePaths.push(connector)
|
|
2138
|
+
if (sourceEscapePaths.length >= maximumCandidatePaths) break
|
|
2139
|
+
}
|
|
2140
|
+
if (sourceEscapePaths.length >= maximumCandidatePaths) break
|
|
2141
|
+
sourceEscapePaths.push(
|
|
2142
|
+
chamferOrthogonalCorners(
|
|
2143
|
+
[
|
|
2144
|
+
sourcePoint,
|
|
2145
|
+
adjacentViaPoint,
|
|
2146
|
+
lateralPoint,
|
|
2147
|
+
detourViaPoint,
|
|
2148
|
+
],
|
|
2149
|
+
chamfer,
|
|
2150
|
+
),
|
|
2151
|
+
chamferOrthogonalCorners(
|
|
2152
|
+
[
|
|
2153
|
+
sourcePoint,
|
|
2154
|
+
adjacentViaPoint,
|
|
2155
|
+
outwardPoint,
|
|
2156
|
+
detourViaPoint,
|
|
2157
|
+
],
|
|
2158
|
+
chamfer,
|
|
2159
|
+
),
|
|
2160
|
+
)
|
|
2161
|
+
if (sourceEscapePaths.length >= maximumCandidatePaths) break
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
let plan: FanoutRoutePlan | undefined
|
|
2166
|
+
for (const [
|
|
2167
|
+
pathIndex,
|
|
2168
|
+
sourceEscapePath,
|
|
2169
|
+
] of sourceEscapePaths.entries()) {
|
|
2170
|
+
const basePlan = buildPlan({
|
|
2171
|
+
preparedConnection,
|
|
2172
|
+
bus: directionalBus,
|
|
2173
|
+
targetLayer,
|
|
2174
|
+
track: sourceTrack,
|
|
2175
|
+
exitAxis: getExitAxis(directionalBus),
|
|
2176
|
+
layerNames,
|
|
2177
|
+
traceWidth,
|
|
2178
|
+
viaDiameter,
|
|
2179
|
+
viaHoleDiameter,
|
|
2180
|
+
viaHandedness,
|
|
2181
|
+
interstitialEscape: !pairChannelFitsVia,
|
|
2182
|
+
spreadLaneIndex: 0,
|
|
2183
|
+
cornerExitLaneOffset: 0,
|
|
2184
|
+
cornerLocalChannelLaneOffset: 0,
|
|
2185
|
+
cornerBoundaryChannelLaneOffset: 0,
|
|
2186
|
+
clearance,
|
|
2187
|
+
terminateAtVia: true,
|
|
2188
|
+
allowBlindAndBuriedVias,
|
|
2189
|
+
sourceEscapePath,
|
|
2190
|
+
})
|
|
2191
|
+
const endpointViaCandidates = getPlaneEndpointViaCandidates({
|
|
2192
|
+
preparedConnection,
|
|
2193
|
+
bus: directionalBus,
|
|
2194
|
+
viaDiameter,
|
|
2195
|
+
clearance,
|
|
2196
|
+
})
|
|
2197
|
+
const plansToTry = [
|
|
2198
|
+
...endpointViaCandidates.map((viaPoint) =>
|
|
2199
|
+
addPlaneEndpointTerminal({
|
|
2200
|
+
plan: basePlan,
|
|
2201
|
+
preparedConnection,
|
|
2202
|
+
planeLayer: targetLayer,
|
|
2203
|
+
viaPoint,
|
|
2204
|
+
layerNames,
|
|
2205
|
+
traceWidth,
|
|
2206
|
+
viaDiameter,
|
|
2207
|
+
viaHoleDiameter,
|
|
2208
|
+
allowBlindAndBuriedVias,
|
|
2209
|
+
}),
|
|
2210
|
+
),
|
|
2211
|
+
basePlan,
|
|
2212
|
+
]
|
|
2213
|
+
plan = plansToTry.find((candidatePlan, candidateIndex) =>
|
|
2214
|
+
planIsClear({
|
|
2215
|
+
plan: candidatePlan,
|
|
2216
|
+
otherPlans: [...acceptedPlans, ...candidatePlans],
|
|
2217
|
+
staticClearanceCache,
|
|
2218
|
+
blockingBusCounts,
|
|
2219
|
+
cacheKey: `plane:${bus.busId}:${targetLayer}:${direction}:${preparedConnection.connectionIndex}:${viaHandedness}:${pathIndex}:${candidateIndex}`,
|
|
2220
|
+
srj,
|
|
2221
|
+
sharedBoundary: bus.sharedBoundary,
|
|
2222
|
+
clearance,
|
|
2223
|
+
allowBlindAndBuriedVias,
|
|
2224
|
+
allowSameNetMerges,
|
|
2225
|
+
}),
|
|
2226
|
+
)
|
|
2227
|
+
if (plan) break
|
|
2228
|
+
}
|
|
1765
2229
|
if (!plan) {
|
|
1766
2230
|
orderIsClear = false
|
|
1767
2231
|
break
|
|
@@ -1793,13 +2257,28 @@ export function routeBusAlternatives(
|
|
|
1793
2257
|
compactBusTracks,
|
|
1794
2258
|
staticClearanceCache,
|
|
1795
2259
|
blockingBusCounts,
|
|
2260
|
+
allowBlindAndBuriedVias = true,
|
|
1796
2261
|
allowSameNetMerges = false,
|
|
2262
|
+
rejectedViaMinimalCandidates,
|
|
2263
|
+
stopAfterFirstRejectedViaMinimalCandidate = false,
|
|
2264
|
+
fixedViaPointsByConnectionIndex,
|
|
2265
|
+
reservedVias = [],
|
|
2266
|
+
viaMinimalOnly = false,
|
|
1797
2267
|
} = params
|
|
1798
2268
|
if (!Number.isInteger(maxAlternatives) || maxAlternatives < 1) {
|
|
1799
2269
|
throw new Error(
|
|
1800
2270
|
`FanoutSolver: maxAlternatives must be a positive integer, received ${maxAlternatives}`,
|
|
1801
2271
|
)
|
|
1802
2272
|
}
|
|
2273
|
+
if (
|
|
2274
|
+
fixedViaPointsByConnectionIndex &&
|
|
2275
|
+
bus.connections.some(
|
|
2276
|
+
(connection) =>
|
|
2277
|
+
!fixedViaPointsByConnectionIndex.has(connection.connectionIndex),
|
|
2278
|
+
)
|
|
2279
|
+
) {
|
|
2280
|
+
return []
|
|
2281
|
+
}
|
|
1803
2282
|
if (bus.termination.type === "plane") {
|
|
1804
2283
|
const plan = routePlaneTerminatedBus(params)
|
|
1805
2284
|
return plan ? [plan] : []
|
|
@@ -1818,11 +2297,47 @@ export function routeBusAlternatives(
|
|
|
1818
2297
|
viaDiameter / 2 + clearance - 1e-9
|
|
1819
2298
|
const interstitialEscape =
|
|
1820
2299
|
targetUsesVia && !outwardEdgeBus && !pairChannelFitsVia
|
|
1821
|
-
const
|
|
2300
|
+
const availableViaHandednesses: readonly ViaHandedness[] = targetUsesVia
|
|
1822
2301
|
? pairChannelFitsVia || outwardEdgeBus
|
|
1823
2302
|
? [0]
|
|
1824
|
-
:
|
|
2303
|
+
: allowBlindAndBuriedVias
|
|
2304
|
+
? [1, -1]
|
|
2305
|
+
: [-1, 1]
|
|
1825
2306
|
: [0]
|
|
2307
|
+
const viaHandednesses: readonly ViaHandedness[] = (() => {
|
|
2308
|
+
if (allowBlindAndBuriedVias) return availableViaHandednesses
|
|
2309
|
+
if (
|
|
2310
|
+
availableViaHandednesses.length !== 2 ||
|
|
2311
|
+
!availableViaHandednesses.includes(-1) ||
|
|
2312
|
+
!availableViaHandednesses.includes(1)
|
|
2313
|
+
) {
|
|
2314
|
+
return availableViaHandednesses
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
// Prefer placing the dogbone via away from the boundary targets. This
|
|
2318
|
+
// leaves the open routing chamber between the source field and the final
|
|
2319
|
+
// exit band, which is especially important when physical barrels span
|
|
2320
|
+
// every copper layer. The opposite hand remains an immediate fallback.
|
|
2321
|
+
const meanSourceTrack =
|
|
2322
|
+
bus.connections.reduce(
|
|
2323
|
+
(sum, connection) =>
|
|
2324
|
+
sum + getPerpendicularAxis(connection.sourcePoint, bus.direction),
|
|
2325
|
+
0,
|
|
2326
|
+
) / bus.connections.length
|
|
2327
|
+
const meanTargetTrack =
|
|
2328
|
+
bus.connections.reduce(
|
|
2329
|
+
(sum, connection) =>
|
|
2330
|
+
sum +
|
|
2331
|
+
getPerpendicularAxis(
|
|
2332
|
+
connection.exitTargetPoint ?? connection.targetPoint,
|
|
2333
|
+
bus.direction,
|
|
2334
|
+
),
|
|
2335
|
+
0,
|
|
2336
|
+
) / bus.connections.length
|
|
2337
|
+
if (meanTargetTrack > meanSourceTrack + 1e-9) return [-1, 1]
|
|
2338
|
+
if (meanTargetTrack < meanSourceTrack - 1e-9) return [1, -1]
|
|
2339
|
+
return availableViaHandednesses
|
|
2340
|
+
})()
|
|
1826
2341
|
|
|
1827
2342
|
const alternatives: FanoutRoutePlan[][] = []
|
|
1828
2343
|
const seenAlternativeKeys = new Set<string>()
|
|
@@ -1844,8 +2359,204 @@ export function routeBusAlternatives(
|
|
|
1844
2359
|
const boundaryDirection = getDirectionForExitEdge(bus.exitEdge)
|
|
1845
2360
|
const boundaryExitAxis = getExitAxis(bus, boundaryDirection)
|
|
1846
2361
|
const cornerSide = getCornerSide(bus)
|
|
1847
|
-
|
|
2362
|
+
const canUseViaInPadTerminals =
|
|
2363
|
+
allowsViaInPad(srj) &&
|
|
2364
|
+
bus.connections.every((preparedConnection) =>
|
|
2365
|
+
circleFitsInsideObstacle({
|
|
2366
|
+
center: preparedConnection.sourcePoint,
|
|
2367
|
+
diameter: viaDiameter,
|
|
2368
|
+
obstacle: preparedConnection.sourceObstacle,
|
|
2369
|
+
}),
|
|
2370
|
+
)
|
|
2371
|
+
type CoordinatedTerminalPattern = {
|
|
2372
|
+
label: string
|
|
2373
|
+
useViaInPad: boolean
|
|
2374
|
+
getViaHandedness: (
|
|
2375
|
+
preparedConnection: PreparedConnection,
|
|
2376
|
+
) => ViaHandedness
|
|
2377
|
+
getViaPoint?: (preparedConnection: PreparedConnection) => Point2D
|
|
2378
|
+
maximumRouteOrderAttempts?: number
|
|
2379
|
+
}
|
|
2380
|
+
const maximumThroughAllRouteOrderAttempts = 24
|
|
2381
|
+
const uniformDogboneTerminalPatterns: CoordinatedTerminalPattern[] =
|
|
2382
|
+
viaHandednesses.map((viaHandedness) => ({
|
|
2383
|
+
label: `uniform-${viaHandedness}`,
|
|
2384
|
+
useViaInPad: false,
|
|
2385
|
+
getViaHandedness: () => viaHandedness,
|
|
2386
|
+
maximumRouteOrderAttempts: allowBlindAndBuriedVias
|
|
2387
|
+
? undefined
|
|
2388
|
+
: maximumThroughAllRouteOrderAttempts,
|
|
2389
|
+
}))
|
|
2390
|
+
const connectionsBySourceTrack = bus.connections.toSorted(
|
|
2391
|
+
(first, second) =>
|
|
2392
|
+
getPerpendicularAxis(first.sourcePoint, bus.direction) -
|
|
2393
|
+
getPerpendicularAxis(second.sourcePoint, bus.direction) ||
|
|
2394
|
+
getAxis(first.sourcePoint, bus.direction) -
|
|
2395
|
+
getAxis(second.sourcePoint, bus.direction) ||
|
|
2396
|
+
first.connectionIndex - second.connectionIndex,
|
|
2397
|
+
)
|
|
2398
|
+
const sourceTrackRankByConnectionIndex = new Map(
|
|
2399
|
+
connectionsBySourceTrack.map((connection, rank) => [
|
|
2400
|
+
connection.connectionIndex,
|
|
2401
|
+
rank,
|
|
2402
|
+
]),
|
|
2403
|
+
)
|
|
2404
|
+
const getTowardMedianHandedness = (rank: number): ViaHandedness =>
|
|
2405
|
+
rank < connectionsBySourceTrack.length / 2 ? 1 : -1
|
|
2406
|
+
const middleRank = Math.floor(connectionsBySourceTrack.length / 2)
|
|
2407
|
+
const singleFlipRanks = [
|
|
2408
|
+
0,
|
|
2409
|
+
1,
|
|
2410
|
+
middleRank,
|
|
2411
|
+
middleRank + 1,
|
|
2412
|
+
...connectionsBySourceTrack.map((_, rank) => rank),
|
|
2413
|
+
].filter(
|
|
2414
|
+
(rank, index, ranks) =>
|
|
2415
|
+
rank >= 0 &&
|
|
2416
|
+
rank < connectionsBySourceTrack.length &&
|
|
2417
|
+
ranks.indexOf(rank) === index,
|
|
2418
|
+
)
|
|
2419
|
+
const towardMedianFlipRankSets = [
|
|
2420
|
+
[middleRank],
|
|
2421
|
+
[middleRank + 1],
|
|
2422
|
+
[0],
|
|
2423
|
+
[1],
|
|
2424
|
+
[0, middleRank + 1],
|
|
2425
|
+
[1, middleRank],
|
|
2426
|
+
[0, middleRank],
|
|
2427
|
+
[1, middleRank + 1],
|
|
2428
|
+
[0, 1],
|
|
2429
|
+
[middleRank, middleRank + 1],
|
|
2430
|
+
...singleFlipRanks.map((rank) => [rank]),
|
|
2431
|
+
]
|
|
2432
|
+
.map((ranks) =>
|
|
2433
|
+
ranks
|
|
2434
|
+
.filter((rank) => rank >= 0 && rank < connectionsBySourceTrack.length)
|
|
2435
|
+
.toSorted((first, second) => first - second),
|
|
2436
|
+
)
|
|
2437
|
+
.filter(
|
|
2438
|
+
(ranks, index, rankSets) =>
|
|
2439
|
+
ranks.length > 0 &&
|
|
2440
|
+
rankSets.findIndex(
|
|
2441
|
+
(candidate) => candidate.join(",") === ranks.join(","),
|
|
2442
|
+
) === index,
|
|
2443
|
+
)
|
|
2444
|
+
const mixedDogboneTerminalPatterns: CoordinatedTerminalPattern[] =
|
|
2445
|
+
!allowBlindAndBuriedVias &&
|
|
2446
|
+
(reservedVias.length > 0 ||
|
|
2447
|
+
acceptedPlans.some((plan) => plan.termination.type === "plane")) &&
|
|
2448
|
+
viaHandednesses.includes(-1) &&
|
|
2449
|
+
viaHandednesses.includes(1)
|
|
2450
|
+
? [
|
|
2451
|
+
{
|
|
2452
|
+
label: "toward-source-median",
|
|
2453
|
+
useViaInPad: false,
|
|
2454
|
+
maximumRouteOrderAttempts: maximumThroughAllRouteOrderAttempts,
|
|
2455
|
+
getViaHandedness: (connection) =>
|
|
2456
|
+
getTowardMedianHandedness(
|
|
2457
|
+
sourceTrackRankByConnectionIndex.get(
|
|
2458
|
+
connection.connectionIndex,
|
|
2459
|
+
) ?? 0,
|
|
2460
|
+
),
|
|
2461
|
+
},
|
|
2462
|
+
...towardMedianFlipRankSets.slice(0, 12).map((flippedRanks) => ({
|
|
2463
|
+
label: `toward-source-median-with-ranks-${flippedRanks.join("-")}-flipped`,
|
|
2464
|
+
useViaInPad: false,
|
|
2465
|
+
maximumRouteOrderAttempts: 6,
|
|
2466
|
+
getViaHandedness: (connection: PreparedConnection) => {
|
|
2467
|
+
const rank =
|
|
2468
|
+
sourceTrackRankByConnectionIndex.get(
|
|
2469
|
+
connection.connectionIndex,
|
|
2470
|
+
) ?? 0
|
|
2471
|
+
const towardMedian = getTowardMedianHandedness(rank)
|
|
2472
|
+
return flippedRanks.includes(rank)
|
|
2473
|
+
? (-towardMedian as ViaHandedness)
|
|
2474
|
+
: towardMedian
|
|
2475
|
+
},
|
|
2476
|
+
})),
|
|
2477
|
+
{
|
|
2478
|
+
label: "alternating-source-grid-a",
|
|
2479
|
+
useViaInPad: false,
|
|
2480
|
+
maximumRouteOrderAttempts: 3,
|
|
2481
|
+
getViaHandedness: (connection) =>
|
|
2482
|
+
(sourceTrackRankByConnectionIndex.get(
|
|
2483
|
+
connection.connectionIndex,
|
|
2484
|
+
) ?? 0) %
|
|
2485
|
+
2 ===
|
|
2486
|
+
0
|
|
2487
|
+
? -1
|
|
2488
|
+
: 1,
|
|
2489
|
+
},
|
|
2490
|
+
{
|
|
2491
|
+
label: "alternating-source-grid-b",
|
|
2492
|
+
useViaInPad: false,
|
|
2493
|
+
maximumRouteOrderAttempts: 3,
|
|
2494
|
+
getViaHandedness: (connection) =>
|
|
2495
|
+
(sourceTrackRankByConnectionIndex.get(
|
|
2496
|
+
connection.connectionIndex,
|
|
2497
|
+
) ?? 0) %
|
|
2498
|
+
2 ===
|
|
2499
|
+
0
|
|
2500
|
+
? 1
|
|
2501
|
+
: -1,
|
|
2502
|
+
},
|
|
2503
|
+
{
|
|
2504
|
+
label: "away-from-source-median",
|
|
2505
|
+
useViaInPad: false,
|
|
2506
|
+
maximumRouteOrderAttempts: maximumThroughAllRouteOrderAttempts,
|
|
2507
|
+
getViaHandedness: (connection) =>
|
|
2508
|
+
(sourceTrackRankByConnectionIndex.get(
|
|
2509
|
+
connection.connectionIndex,
|
|
2510
|
+
) ?? 0) <
|
|
2511
|
+
connectionsBySourceTrack.length / 2
|
|
2512
|
+
? -1
|
|
2513
|
+
: 1,
|
|
2514
|
+
},
|
|
2515
|
+
]
|
|
2516
|
+
: []
|
|
2517
|
+
const viaInPadTerminalPattern = {
|
|
2518
|
+
label: "via-in-pad",
|
|
2519
|
+
useViaInPad: true,
|
|
2520
|
+
getViaHandedness: () => 0 as const,
|
|
2521
|
+
}
|
|
2522
|
+
const fixedViaTerminalPattern: CoordinatedTerminalPattern | undefined =
|
|
2523
|
+
fixedViaPointsByConnectionIndex
|
|
2524
|
+
? {
|
|
2525
|
+
label: "component-matched-vias",
|
|
2526
|
+
useViaInPad: false,
|
|
2527
|
+
getViaHandedness: () => 0,
|
|
2528
|
+
getViaPoint: (connection) =>
|
|
2529
|
+
fixedViaPointsByConnectionIndex.get(connection.connectionIndex)!,
|
|
2530
|
+
// A fixed component-wide dogbone assignment is a bounded fast
|
|
2531
|
+
// path. Keep enough order/bias attempts for the eight-lane DDR
|
|
2532
|
+
// cases without allowing route-order rotations to grow with an
|
|
2533
|
+
// arbitrarily wide bus.
|
|
2534
|
+
maximumRouteOrderAttempts: maximumThroughAllRouteOrderAttempts,
|
|
2535
|
+
}
|
|
2536
|
+
: undefined
|
|
2537
|
+
const planeTerminationsAlreadyOccupyTheFanout = acceptedPlans.some(
|
|
2538
|
+
(plan) => plan.termination.type === "plane",
|
|
2539
|
+
)
|
|
2540
|
+
const acceptedBoundaryPlansExist = acceptedPlans.some(
|
|
2541
|
+
(plan) => plan.termination.type === "boundary",
|
|
2542
|
+
)
|
|
2543
|
+
const dogboneTerminalPatterns =
|
|
2544
|
+
acceptedBoundaryPlansExist && !allowBlindAndBuriedVias
|
|
2545
|
+
? [...mixedDogboneTerminalPatterns, ...uniformDogboneTerminalPatterns]
|
|
2546
|
+
: [...uniformDogboneTerminalPatterns, ...mixedDogboneTerminalPatterns]
|
|
2547
|
+
const terminalPatterns: CoordinatedTerminalPattern[] =
|
|
2548
|
+
fixedViaTerminalPattern
|
|
2549
|
+
? [fixedViaTerminalPattern]
|
|
2550
|
+
: canUseViaInPadTerminals
|
|
2551
|
+
? planeTerminationsAlreadyOccupyTheFanout
|
|
2552
|
+
? [viaInPadTerminalPattern, ...dogboneTerminalPatterns]
|
|
2553
|
+
: [...dogboneTerminalPatterns, viaInPadTerminalPattern]
|
|
2554
|
+
: dogboneTerminalPatterns
|
|
2555
|
+
const seenTerminalSignatures = new Set<string>()
|
|
2556
|
+
for (const terminalPattern of terminalPatterns) {
|
|
1848
2557
|
const terminals = bus.connections.map((preparedConnection) => {
|
|
2558
|
+
const viaHandedness =
|
|
2559
|
+
terminalPattern.getViaHandedness(preparedConnection)
|
|
1849
2560
|
const boundaryTrack = cornerSide
|
|
1850
2561
|
? getCornerTargetTrack({
|
|
1851
2562
|
bus,
|
|
@@ -1863,15 +2574,22 @@ export function routeBusAlternatives(
|
|
|
1863
2574
|
)
|
|
1864
2575
|
return {
|
|
1865
2576
|
connection: preparedConnection,
|
|
1866
|
-
viaPoint:
|
|
1867
|
-
preparedConnection
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
2577
|
+
viaPoint: terminalPattern.getViaPoint
|
|
2578
|
+
? terminalPattern.getViaPoint(preparedConnection)
|
|
2579
|
+
: terminalPattern.useViaInPad
|
|
2580
|
+
? {
|
|
2581
|
+
x: preparedConnection.sourcePoint.x,
|
|
2582
|
+
y: preparedConnection.sourcePoint.y,
|
|
2583
|
+
}
|
|
2584
|
+
: getInitialViaPoint({
|
|
2585
|
+
preparedConnection,
|
|
2586
|
+
bus,
|
|
2587
|
+
targetLayer,
|
|
2588
|
+
traceWidth,
|
|
2589
|
+
viaDiameter,
|
|
2590
|
+
clearance,
|
|
2591
|
+
viaHandedness,
|
|
2592
|
+
}),
|
|
1875
2593
|
exitPoint: makePoint(
|
|
1876
2594
|
boundaryExitAxis,
|
|
1877
2595
|
boundaryTrack,
|
|
@@ -1879,36 +2597,76 @@ export function routeBusAlternatives(
|
|
|
1879
2597
|
),
|
|
1880
2598
|
}
|
|
1881
2599
|
})
|
|
1882
|
-
const
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
2600
|
+
const terminalSignature = terminals
|
|
2601
|
+
.map(
|
|
2602
|
+
(terminal) =>
|
|
2603
|
+
`${terminal.connection.connectionIndex}:${terminal.viaPoint.x}:${terminal.viaPoint.y}`,
|
|
2604
|
+
)
|
|
2605
|
+
.join("|")
|
|
2606
|
+
if (seenTerminalSignatures.has(terminalSignature)) continue
|
|
2607
|
+
seenTerminalSignatures.add(terminalSignature)
|
|
2608
|
+
const viaMinimalAlternatives = routeViaMinimalWindingAlternatives(
|
|
2609
|
+
{
|
|
2610
|
+
srj,
|
|
2611
|
+
bus,
|
|
2612
|
+
targetLayer,
|
|
2613
|
+
terminals,
|
|
2614
|
+
acceptedPlans,
|
|
2615
|
+
layerNames,
|
|
2616
|
+
traceWidth,
|
|
2617
|
+
viaDiameter,
|
|
2618
|
+
viaHoleDiameter,
|
|
2619
|
+
clearance,
|
|
2620
|
+
allowBlindAndBuriedVias,
|
|
2621
|
+
allowSameNetMerges,
|
|
2622
|
+
maximumRouteOrderAttempts: terminalPattern.maximumRouteOrderAttempts,
|
|
2623
|
+
reservedVias,
|
|
2624
|
+
gridStepDivisor:
|
|
2625
|
+
fixedViaPointsByConnectionIndex &&
|
|
2626
|
+
Math.min(bus.pitchX, bus.pitchY) -
|
|
2627
|
+
2 * (viaDiameter / 2 + traceWidth / 2 + clearance) <
|
|
2628
|
+
traceWidth + clearance
|
|
2629
|
+
? 2
|
|
2630
|
+
: 1,
|
|
2631
|
+
},
|
|
2632
|
+
terminalPattern.maximumRouteOrderAttempts === undefined
|
|
2633
|
+
? Math.min(2, maxAlternatives - alternatives.length)
|
|
2634
|
+
: 2,
|
|
2635
|
+
)
|
|
2636
|
+
for (const viaMinimalPlans of viaMinimalAlternatives) {
|
|
2637
|
+
const combinedPlansAreClear = fanoutPlansAreClear({
|
|
1898
2638
|
plans: [...acceptedPlans, ...viaMinimalPlans],
|
|
1899
2639
|
srj,
|
|
1900
2640
|
sharedBoundary: bus.sharedBoundary,
|
|
1901
2641
|
clearance,
|
|
2642
|
+
allowBlindAndBuriedVias,
|
|
1902
2643
|
allowSameNetMerges,
|
|
1903
2644
|
})
|
|
1904
|
-
|
|
1905
|
-
|
|
2645
|
+
if (!combinedPlansAreClear) {
|
|
2646
|
+
const candidateIsInternallyClear = fanoutPlansAreClear({
|
|
2647
|
+
plans: viaMinimalPlans,
|
|
2648
|
+
srj,
|
|
2649
|
+
sharedBoundary: bus.sharedBoundary,
|
|
2650
|
+
clearance,
|
|
2651
|
+
allowBlindAndBuriedVias,
|
|
2652
|
+
allowSameNetMerges,
|
|
2653
|
+
})
|
|
2654
|
+
if (candidateIsInternallyClear && rejectedViaMinimalCandidates) {
|
|
2655
|
+
rejectedViaMinimalCandidates.push(viaMinimalPlans)
|
|
2656
|
+
if (stopAfterFirstRejectedViaMinimalCandidate) {
|
|
2657
|
+
return alternatives
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
continue
|
|
2661
|
+
}
|
|
2662
|
+
addAlternative(viaMinimalPlans)
|
|
2663
|
+
if (alternatives.length >= maxAlternatives) return alternatives
|
|
1906
2664
|
}
|
|
1907
|
-
addAlternative(viaMinimalPlans)
|
|
1908
|
-
if (alternatives.length >= maxAlternatives) return alternatives
|
|
1909
2665
|
}
|
|
1910
2666
|
}
|
|
1911
2667
|
|
|
2668
|
+
if (viaMinimalOnly) return alternatives
|
|
2669
|
+
|
|
1912
2670
|
const searchConnectionOrder = (
|
|
1913
2671
|
connectionOrder: PreparedConnection[],
|
|
1914
2672
|
viaHandedness: ViaHandedness,
|
|
@@ -1987,6 +2745,7 @@ export function routeBusAlternatives(
|
|
|
1987
2745
|
cornerBoundaryChannelLaneOffset: cornerLaneOffsets.boundaryChannel,
|
|
1988
2746
|
clearance,
|
|
1989
2747
|
terminateAtVia: false,
|
|
2748
|
+
allowBlindAndBuriedVias,
|
|
1990
2749
|
})
|
|
1991
2750
|
if (
|
|
1992
2751
|
!planIsClear({
|
|
@@ -1998,6 +2757,7 @@ export function routeBusAlternatives(
|
|
|
1998
2757
|
srj,
|
|
1999
2758
|
sharedBoundary: bus.sharedBoundary,
|
|
2000
2759
|
clearance,
|
|
2760
|
+
allowBlindAndBuriedVias,
|
|
2001
2761
|
allowSameNetMerges,
|
|
2002
2762
|
})
|
|
2003
2763
|
) {
|