@tscircuit/fanout-solver 0.0.49 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/fanout-solver.ts +1617 -143
- package/lib/match-component-dogbone-via-sites.ts +101 -14
- package/lib/route-bus.ts +345 -52
- package/lib/route-via-minimal-winding.ts +94 -23
- package/lib/types.ts +11 -0
- package/package.json +1 -1
package/lib/fanout-solver.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SimpleRouteJson } from "@tscircuit/capacity-autorouter"
|
|
2
2
|
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
3
|
-
import {
|
|
3
|
+
import { type GraphicsObject, mergeGraphics } from "graphics-debug"
|
|
4
4
|
import { addViaLayerMetadataToSrj } from "./add-via-layer-metadata"
|
|
5
5
|
import { getCornerBandSide, getExitEdgeForDirection } from "./boundary-exit"
|
|
6
6
|
import { buildOutputSimpleRouteJson } from "./build-output"
|
|
@@ -25,10 +25,11 @@ import {
|
|
|
25
25
|
} from "./prepare-buses"
|
|
26
26
|
import {
|
|
27
27
|
fanoutPlansAreClear,
|
|
28
|
+
fanoutPlansAreMutuallyClear,
|
|
28
29
|
type RouteBusStaticClearanceCache,
|
|
29
30
|
routeBus,
|
|
30
|
-
routeBusAlternativesSteps,
|
|
31
31
|
routeBusAlternatives,
|
|
32
|
+
routeBusAlternativesSteps,
|
|
32
33
|
} from "./route-bus"
|
|
33
34
|
import { routeSingleLayerWithAdaptiveExitsSteps } from "./route-single-layer-adaptive-exits"
|
|
34
35
|
import { routeSingleLayerWithPushAndShove } from "./route-single-layer-push-shove"
|
|
@@ -55,6 +56,8 @@ interface ResolvedFanoutConfig {
|
|
|
55
56
|
compactBusTracks: boolean
|
|
56
57
|
allowBlindAndBuriedVias: boolean
|
|
57
58
|
allowSameNetMerges: boolean
|
|
59
|
+
densePlaneReservationBusIds: readonly string[]
|
|
60
|
+
denseUnrestrictedPlaneRoutingBusIds: readonly string[]
|
|
58
61
|
singleLayerPushAndShove: boolean
|
|
59
62
|
singleLayerAdaptiveExits: boolean
|
|
60
63
|
borderDistribution: FanoutBorderDistribution
|
|
@@ -85,7 +88,7 @@ interface FanoutSubsolverRequest {
|
|
|
85
88
|
solver: BaseSolver
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
type FanoutWorkYield =
|
|
91
|
+
type FanoutWorkYield = undefined | FanoutSubsolverRequest
|
|
89
92
|
|
|
90
93
|
class FanoutWorkSolver<T> extends BaseSolver {
|
|
91
94
|
private output: T | undefined
|
|
@@ -240,6 +243,9 @@ function resolveConfig(
|
|
|
240
243
|
compactBusTracks: options.compactBusTracks ?? false,
|
|
241
244
|
allowBlindAndBuriedVias: options.allowBlindAndBuriedVias ?? true,
|
|
242
245
|
allowSameNetMerges: options.allowSameNetMerges ?? false,
|
|
246
|
+
densePlaneReservationBusIds: options.densePlaneReservationBusIds ?? [],
|
|
247
|
+
denseUnrestrictedPlaneRoutingBusIds:
|
|
248
|
+
options.denseUnrestrictedPlaneRoutingBusIds ?? [],
|
|
243
249
|
singleLayerPushAndShove: options.singleLayerPushAndShove ?? false,
|
|
244
250
|
singleLayerAdaptiveExits: options.singleLayerAdaptiveExits ?? false,
|
|
245
251
|
borderDistribution,
|
|
@@ -446,8 +452,14 @@ function createInitialLayerAssignment(params: {
|
|
|
446
452
|
buses: PreparedBus[]
|
|
447
453
|
escapeLayers: string[]
|
|
448
454
|
escapeLayersByBusId: Readonly<Record<string, readonly string[]>>
|
|
455
|
+
preferOrderedCoordinatedWindingLayers: boolean
|
|
449
456
|
}): Readonly<Record<string, string>> {
|
|
450
|
-
const {
|
|
457
|
+
const {
|
|
458
|
+
buses,
|
|
459
|
+
escapeLayers,
|
|
460
|
+
escapeLayersByBusId,
|
|
461
|
+
preferOrderedCoordinatedWindingLayers,
|
|
462
|
+
} = params
|
|
451
463
|
const assignment: Record<string, string> = {}
|
|
452
464
|
const directionsByComponent = new Map<string, Set<PreparedBus["direction"]>>()
|
|
453
465
|
let nextViaLayerIndex = 0
|
|
@@ -483,6 +495,16 @@ function createInitialLayerAssignment(params: {
|
|
|
483
495
|
) {
|
|
484
496
|
assignment[bus.busId] = sourceLayer
|
|
485
497
|
} else if (viaLayers.length > 0) {
|
|
498
|
+
if (
|
|
499
|
+
preferOrderedCoordinatedWindingLayers &&
|
|
500
|
+
busUsesCoordinatedWinding(bus)
|
|
501
|
+
) {
|
|
502
|
+
// Coordinated winding treats allowedLayers as an ordered preference.
|
|
503
|
+
// A global round-robin index can otherwise skip a bus's first choice
|
|
504
|
+
// just because a previous bus had a different set of legal layers.
|
|
505
|
+
assignment[bus.busId] = viaLayers[0]!
|
|
506
|
+
continue
|
|
507
|
+
}
|
|
486
508
|
const componentDirections = directionsByComponent.get(bus.componentId)!
|
|
487
509
|
const hasOpposingDirection =
|
|
488
510
|
(componentDirections.has("left") && componentDirections.has("right")) ||
|
|
@@ -1010,6 +1032,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1010
1032
|
buses: this.preparedBuses,
|
|
1011
1033
|
escapeLayers: this.config.escapeLayers,
|
|
1012
1034
|
escapeLayersByBusId: this.escapeLayersByBusId,
|
|
1035
|
+
preferOrderedCoordinatedWindingLayers:
|
|
1036
|
+
this.config.densePlaneReservationBusIds.length > 0 ||
|
|
1037
|
+
this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0,
|
|
1013
1038
|
}),
|
|
1014
1039
|
generatedAssignments,
|
|
1015
1040
|
maxAssignments: this.config.maxLayerCombinations,
|
|
@@ -1357,10 +1382,38 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1357
1382
|
busesInRoutingOrder: readonly PreparedBus[]
|
|
1358
1383
|
}): Generator<FanoutWorkYield, MixedTerminationState | null, unknown> {
|
|
1359
1384
|
if (this.config.allowBlindAndBuriedVias) return null
|
|
1385
|
+
const debugDense = (...values: unknown[]) => {
|
|
1386
|
+
if (process.env.FANOUT_DEBUG_DENSE === "1") {
|
|
1387
|
+
if (
|
|
1388
|
+
process.env.FANOUT_DEBUG_DENSE_SUMMARY === "1" &&
|
|
1389
|
+
![
|
|
1390
|
+
"start",
|
|
1391
|
+
"plane-match:preflight-failed",
|
|
1392
|
+
"plane-match:incremental-complete",
|
|
1393
|
+
"plane-match:incremental-failed",
|
|
1394
|
+
"plane-route:alternate-candidate-counts",
|
|
1395
|
+
"plane-route:alternate-search",
|
|
1396
|
+
"plane-route:promote-failed",
|
|
1397
|
+
"plane-route:alternate-choice",
|
|
1398
|
+
"plane-route:promote-zero-candidates",
|
|
1399
|
+
"length-match:complete",
|
|
1400
|
+
"length-match:start",
|
|
1401
|
+
"plane-route:failed",
|
|
1402
|
+
"dense-validation",
|
|
1403
|
+
].includes(String(values[0]))
|
|
1404
|
+
) {
|
|
1405
|
+
return
|
|
1406
|
+
}
|
|
1407
|
+
console.error("dense:", ...values)
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1360
1410
|
|
|
1361
1411
|
const unsortedBoundaryBuses = params.busesInRoutingOrder.filter(
|
|
1362
1412
|
(bus) => bus.termination.type === "boundary",
|
|
1363
1413
|
)
|
|
1414
|
+
const useConfiguredDensePlaneRouting =
|
|
1415
|
+
this.config.densePlaneReservationBusIds.length > 0 ||
|
|
1416
|
+
this.config.denseUnrestrictedPlaneRoutingBusIds.length > 0
|
|
1364
1417
|
const useJointBoundaryViaReservation = shouldUseJointBoundaryViaReservation(
|
|
1365
1418
|
unsortedBoundaryBuses.map((bus) => bus.connections.length),
|
|
1366
1419
|
)
|
|
@@ -1382,84 +1435,181 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1382
1435
|
]),
|
|
1383
1436
|
)
|
|
1384
1437
|
: null
|
|
1385
|
-
const
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
const secondPairRoutingPriority = pairRoutingPriorityKeyByBusId?.get(
|
|
1401
|
-
second.busId,
|
|
1402
|
-
)
|
|
1403
|
-
if (
|
|
1404
|
-
firstPairRoutingPriority !== undefined &&
|
|
1405
|
-
secondPairRoutingPriority !== undefined &&
|
|
1406
|
-
firstPairRoutingPriority !== secondPairRoutingPriority
|
|
1407
|
-
) {
|
|
1408
|
-
// The third pair can be fenced off by two earlier pair windings. Let
|
|
1409
|
-
// the pair with the shortest farthest-lane boundary reach claim its
|
|
1410
|
-
// channel first without relying on caller-specific bus identifiers.
|
|
1411
|
-
return firstPairRoutingPriority - secondPairRoutingPriority
|
|
1438
|
+
const wideBoundaryBuses = unsortedBoundaryBuses.filter(
|
|
1439
|
+
(bus) => bus.connections.length >= 8,
|
|
1440
|
+
)
|
|
1441
|
+
const hasThreeWideBoundaryBuses =
|
|
1442
|
+
useConfiguredDensePlaneRouting && wideBoundaryBuses.length === 3
|
|
1443
|
+
const getBoundaryTargetSpan = (bus: PreparedBus) => {
|
|
1444
|
+
const coordinates = bus.connections.map((connection) => {
|
|
1445
|
+
const target = connection.exitTargetPoint ?? connection.targetPoint
|
|
1446
|
+
return bus.exitEdge === "top" || bus.exitEdge === "bottom"
|
|
1447
|
+
? target.x
|
|
1448
|
+
: target.y
|
|
1449
|
+
})
|
|
1450
|
+
return {
|
|
1451
|
+
minimum: Math.min(...coordinates),
|
|
1452
|
+
maximum: Math.max(...coordinates),
|
|
1412
1453
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1454
|
+
}
|
|
1455
|
+
const narrowBusOverlapsWideTargetSpan = (bus: PreparedBus): boolean => {
|
|
1456
|
+
if (bus.connections.length >= 8) return false
|
|
1457
|
+
const span = getBoundaryTargetSpan(bus)
|
|
1458
|
+
return wideBoundaryBuses.some((wideBus) => {
|
|
1459
|
+
if (wideBus.exitEdge !== bus.exitEdge) return false
|
|
1460
|
+
const wideSpan = getBoundaryTargetSpan(wideBus)
|
|
1461
|
+
return (
|
|
1462
|
+
span.maximum >= wideSpan.minimum - 1e-9 &&
|
|
1463
|
+
span.minimum <= wideSpan.maximum + 1e-9
|
|
1464
|
+
)
|
|
1465
|
+
})
|
|
1466
|
+
}
|
|
1467
|
+
const getContainingWideSourceField = (
|
|
1468
|
+
bus: PreparedBus,
|
|
1469
|
+
): PreparedBus | undefined => {
|
|
1470
|
+
if (bus.connections.length >= 8) return undefined
|
|
1471
|
+
return wideBoundaryBuses.find((wideBus) => {
|
|
1472
|
+
const wideXCoordinates = wideBus.connections.map(
|
|
1473
|
+
(connection) => connection.sourcePoint.x,
|
|
1474
|
+
)
|
|
1475
|
+
const wideYCoordinates = wideBus.connections.map(
|
|
1476
|
+
(connection) => connection.sourcePoint.y,
|
|
1477
|
+
)
|
|
1478
|
+
const minimumX = Math.min(...wideXCoordinates)
|
|
1479
|
+
const maximumX = Math.max(...wideXCoordinates)
|
|
1480
|
+
const minimumY = Math.min(...wideYCoordinates)
|
|
1481
|
+
const maximumY = Math.max(...wideYCoordinates)
|
|
1482
|
+
return bus.connections.every(
|
|
1483
|
+
(connection) =>
|
|
1484
|
+
connection.sourcePoint.x >= minimumX - 1e-9 &&
|
|
1485
|
+
connection.sourcePoint.x <= maximumX + 1e-9 &&
|
|
1486
|
+
connection.sourcePoint.y >= minimumY - 1e-9 &&
|
|
1487
|
+
connection.sourcePoint.y <= maximumY + 1e-9,
|
|
1488
|
+
)
|
|
1489
|
+
})
|
|
1490
|
+
}
|
|
1491
|
+
const narrowBusIsEmbeddedInWideSourceField = (bus: PreparedBus): boolean =>
|
|
1492
|
+
Boolean(getContainingWideSourceField(bus))
|
|
1493
|
+
const getThreeWideRoutingPriority = (bus: PreparedBus): number =>
|
|
1494
|
+
narrowBusOverlapsWideTargetSpan(bus) &&
|
|
1495
|
+
!narrowBusIsEmbeddedInWideSourceField(bus)
|
|
1496
|
+
? 0
|
|
1497
|
+
: bus.connections.length >= 8
|
|
1498
|
+
? 1
|
|
1499
|
+
: bus.connections.length > 1
|
|
1500
|
+
? 2
|
|
1501
|
+
: 3
|
|
1502
|
+
const initiallySortedBoundaryBuses = unsortedBoundaryBuses.toSorted(
|
|
1503
|
+
(first, second) => {
|
|
1504
|
+
// Reserve the dense escape field for the widest buses first. Small
|
|
1505
|
+
// control groups can usually route around their copper, while routing
|
|
1506
|
+
// a two-line corner bus first can consume a critical channel needed by
|
|
1507
|
+
// an eight-line winding bus and force the expensive fallback search.
|
|
1508
|
+
if (useJointBoundaryViaReservation || wideBoundaryBuses.length > 0) {
|
|
1509
|
+
if (hasThreeWideBoundaryBuses) {
|
|
1510
|
+
const threeWidePriorityDifference =
|
|
1511
|
+
getThreeWideRoutingPriority(first) -
|
|
1512
|
+
getThreeWideRoutingPriority(second)
|
|
1513
|
+
if (threeWidePriorityDifference !== 0) {
|
|
1514
|
+
return threeWidePriorityDifference
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
const connectionCountDifference =
|
|
1518
|
+
unsortedBoundaryBuses.length === 2 &&
|
|
1519
|
+
Math.max(
|
|
1520
|
+
...unsortedBoundaryBuses.map((bus) => bus.connections.length),
|
|
1521
|
+
) >= 8
|
|
1522
|
+
? first.connections.length - second.connections.length
|
|
1523
|
+
: second.connections.length - first.connections.length
|
|
1524
|
+
if (connectionCountDifference !== 0) return connectionCountDifference
|
|
1525
|
+
}
|
|
1526
|
+
const firstLayer = params.busLayerAssignments[first.busId]
|
|
1527
|
+
const secondLayer = params.busLayerAssignments[second.busId]
|
|
1528
|
+
const firstPairRoutingPriority = pairRoutingPriorityKeyByBusId?.get(
|
|
1529
|
+
first.busId,
|
|
1530
|
+
)
|
|
1531
|
+
const secondPairRoutingPriority = pairRoutingPriorityKeyByBusId?.get(
|
|
1532
|
+
second.busId,
|
|
1533
|
+
)
|
|
1534
|
+
if (
|
|
1535
|
+
firstPairRoutingPriority !== undefined &&
|
|
1536
|
+
secondPairRoutingPriority !== undefined &&
|
|
1537
|
+
firstPairRoutingPriority !== secondPairRoutingPriority
|
|
1538
|
+
) {
|
|
1539
|
+
// The third pair can be fenced off by two earlier pair windings. Let
|
|
1540
|
+
// the pair with the shortest farthest-lane boundary reach claim its
|
|
1541
|
+
// channel first without relying on caller-specific bus identifiers.
|
|
1542
|
+
return firstPairRoutingPriority - secondPairRoutingPriority
|
|
1543
|
+
}
|
|
1544
|
+
const cornerBandDifference =
|
|
1545
|
+
Number(
|
|
1546
|
+
Boolean(getCornerBandSide(second.exitEdge, second.preferredExit)),
|
|
1547
|
+
) -
|
|
1548
|
+
Number(
|
|
1549
|
+
Boolean(getCornerBandSide(first.exitEdge, first.preferredExit)),
|
|
1435
1550
|
)
|
|
1551
|
+
if (cornerBandDifference !== 0) return cornerBandDifference
|
|
1552
|
+
const firstIsCorner = Boolean(
|
|
1553
|
+
getCornerBandSide(first.exitEdge, first.preferredExit),
|
|
1554
|
+
)
|
|
1555
|
+
if (!firstIsCorner) {
|
|
1556
|
+
const getSourceSpan = (bus: PreparedBus): number => {
|
|
1557
|
+
const xCoordinates = bus.connections.map(
|
|
1558
|
+
(connection) => connection.sourcePoint.x,
|
|
1559
|
+
)
|
|
1560
|
+
const yCoordinates = bus.connections.map(
|
|
1561
|
+
(connection) => connection.sourcePoint.y,
|
|
1562
|
+
)
|
|
1563
|
+
return (
|
|
1564
|
+
Math.max(...xCoordinates) -
|
|
1565
|
+
Math.min(...xCoordinates) +
|
|
1566
|
+
Math.max(...yCoordinates) -
|
|
1567
|
+
Math.min(...yCoordinates)
|
|
1568
|
+
)
|
|
1569
|
+
}
|
|
1570
|
+
const sourceSpanDifference =
|
|
1571
|
+
getSourceSpan(second) - getSourceSpan(first)
|
|
1572
|
+
if (Math.abs(sourceSpanDifference) > 1e-9) {
|
|
1573
|
+
return sourceSpanDifference
|
|
1574
|
+
}
|
|
1436
1575
|
}
|
|
1437
|
-
const
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1576
|
+
const layerDifference =
|
|
1577
|
+
this.config.layerNames.indexOf(firstLayer ?? "") -
|
|
1578
|
+
this.config.layerNames.indexOf(secondLayer ?? "")
|
|
1579
|
+
if (layerDifference !== 0) return -layerDifference
|
|
1580
|
+
if (
|
|
1581
|
+
unsortedBoundaryBuses.length !== 6 &&
|
|
1582
|
+
unsortedBoundaryBuses.length !== 7 &&
|
|
1583
|
+
unsortedBoundaryBuses.length !== 8 &&
|
|
1584
|
+
unsortedBoundaryBuses.length !== 9
|
|
1585
|
+
) {
|
|
1586
|
+
return 0
|
|
1441
1587
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
)
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1588
|
+
// The general routing order can differ across the two components as a
|
|
1589
|
+
// function of local pad geometry. Keep otherwise-equivalent corner buses
|
|
1590
|
+
// in one deterministic order for the six- through nine-bus paths so their
|
|
1591
|
+
// boundary lanes do not swap between the two ends of a direct
|
|
1592
|
+
// interconnect. Leave the released four- and five-bus tie behavior
|
|
1593
|
+
// unchanged.
|
|
1594
|
+
return first.busId.localeCompare(second.busId)
|
|
1595
|
+
},
|
|
1596
|
+
)
|
|
1597
|
+
const debugBoundaryOrder =
|
|
1598
|
+
process.env.FANOUT_DEBUG_BOUNDARY_ORDER?.split(",") ??
|
|
1599
|
+
(process.env.FANOUT_DEBUG_FIRST_BOUNDARY_BUS
|
|
1600
|
+
? [process.env.FANOUT_DEBUG_FIRST_BOUNDARY_BUS]
|
|
1601
|
+
: [])
|
|
1602
|
+
const boundaryBuses =
|
|
1603
|
+
debugBoundaryOrder.length > 0
|
|
1604
|
+
? initiallySortedBoundaryBuses.toSorted((first, second) => {
|
|
1605
|
+
const firstIndex = debugBoundaryOrder.indexOf(first.busId)
|
|
1606
|
+
const secondIndex = debugBoundaryOrder.indexOf(second.busId)
|
|
1607
|
+
return (
|
|
1608
|
+
(firstIndex < 0 ? Number.POSITIVE_INFINITY : firstIndex) -
|
|
1609
|
+
(secondIndex < 0 ? Number.POSITIVE_INFINITY : secondIndex)
|
|
1610
|
+
)
|
|
1611
|
+
})
|
|
1612
|
+
: initiallySortedBoundaryBuses
|
|
1463
1613
|
// Preserve the caller/input order for the dense singleton fill. The
|
|
1464
1614
|
// general routing sort is useful for heterogeneous buses, but ordering a
|
|
1465
1615
|
// regular BGA power field by obstacle depth creates artificial local
|
|
@@ -1468,6 +1618,43 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1468
1618
|
const planeBuses = this.preparedBuses.filter(
|
|
1469
1619
|
(bus) => bus.termination.type === "plane",
|
|
1470
1620
|
)
|
|
1621
|
+
const denseAdditionalObstacles = useConfiguredDensePlaneRouting
|
|
1622
|
+
? this.routingSrj.obstacles
|
|
1623
|
+
: undefined
|
|
1624
|
+
const initialPlaneReservationCount = Number.parseInt(
|
|
1625
|
+
process.env.FANOUT_INITIAL_PLANE_RESERVATIONS ?? "8",
|
|
1626
|
+
10,
|
|
1627
|
+
)
|
|
1628
|
+
const debugInitialPlaneIndices =
|
|
1629
|
+
process.env.FANOUT_DEBUG_INITIAL_PLANE_INDICES?.split(",").map(
|
|
1630
|
+
(index) => Number(index) - 1,
|
|
1631
|
+
)
|
|
1632
|
+
const debugInitialPlaneBusIds =
|
|
1633
|
+
process.env.FANOUT_DEBUG_INITIAL_PLANE_BUS_IDS?.split(",")
|
|
1634
|
+
let activeBoundaryReservationPlaneBuses = debugInitialPlaneBusIds
|
|
1635
|
+
? planeBuses.filter((bus) => debugInitialPlaneBusIds.includes(bus.busId))
|
|
1636
|
+
: debugInitialPlaneIndices
|
|
1637
|
+
? debugInitialPlaneIndices.flatMap((index) =>
|
|
1638
|
+
planeBuses[index] ? [planeBuses[index]!] : [],
|
|
1639
|
+
)
|
|
1640
|
+
: this.config.densePlaneReservationBusIds.length > 0
|
|
1641
|
+
? planeBuses.filter((bus) =>
|
|
1642
|
+
this.config.densePlaneReservationBusIds.includes(bus.busId),
|
|
1643
|
+
)
|
|
1644
|
+
: useConfiguredDensePlaneRouting
|
|
1645
|
+
? planeBuses.slice(
|
|
1646
|
+
0,
|
|
1647
|
+
Number.isFinite(initialPlaneReservationCount)
|
|
1648
|
+
? initialPlaneReservationCount
|
|
1649
|
+
: 8,
|
|
1650
|
+
)
|
|
1651
|
+
: planeBuses
|
|
1652
|
+
debugDense(
|
|
1653
|
+
"start",
|
|
1654
|
+
boundaryBuses.map((bus) => `${bus.busId}:${bus.connections.length}`),
|
|
1655
|
+
`planes:${planeBuses.length}`,
|
|
1656
|
+
`joint:${useJointBoundaryViaReservation}`,
|
|
1657
|
+
)
|
|
1471
1658
|
if (
|
|
1472
1659
|
boundaryBuses.length === 0 ||
|
|
1473
1660
|
boundaryBuses.length > 9 ||
|
|
@@ -1498,18 +1685,80 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1498
1685
|
singletonBoundaryBusCount > 1 &&
|
|
1499
1686
|
shouldDeferSingletonBoundaryViaReservation(boundaryBusConnectionCounts)
|
|
1500
1687
|
const preferredBoundaryPerpendicularSideByBusId = new Map(
|
|
1501
|
-
boundaryBuses.map((bus) => [
|
|
1688
|
+
boundaryBuses.map((bus) => [
|
|
1689
|
+
bus.busId,
|
|
1690
|
+
hasThreeWideBoundaryBuses &&
|
|
1691
|
+
bus.connections.length < 8 &&
|
|
1692
|
+
narrowBusIsEmbeddedInWideSourceField(bus)
|
|
1693
|
+
? (-1 as const)
|
|
1694
|
+
: (1 as const),
|
|
1695
|
+
]),
|
|
1502
1696
|
)
|
|
1503
1697
|
const preferBoundaryOutwardByBusId = new Map(
|
|
1504
1698
|
boundaryBuses.map((bus) => [
|
|
1505
1699
|
bus.busId,
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1700
|
+
bus.connections.length === 1
|
|
1701
|
+
? hasThreeWideBoundaryBuses &&
|
|
1702
|
+
narrowBusIsEmbeddedInWideSourceField(bus)
|
|
1703
|
+
? true
|
|
1704
|
+
: useGeometryAwareSingletonOutwardPreference &&
|
|
1705
|
+
getCornerBandSide(bus.exitEdge, bus.preferredExit)
|
|
1706
|
+
? getDenseSingletonBoundaryGeometry(bus).targetProjection > 0
|
|
1707
|
+
: getExitEdgeForDirection(bus.direction) !== bus.exitEdge
|
|
1708
|
+
: bus.exitEdge === "top" && bus.connections.length >= 8
|
|
1709
|
+
? false
|
|
1710
|
+
: getExitEdgeForDirection(bus.direction) !== bus.exitEdge,
|
|
1511
1711
|
]),
|
|
1512
1712
|
)
|
|
1713
|
+
// An outward half-pitch dogbone can enter a neighboring wide bus's pad
|
|
1714
|
+
// field when a corner bus first escapes toward the package center. Keep
|
|
1715
|
+
// those through-barrels on the opposite side before any earlier bus
|
|
1716
|
+
// commits copper; rematching after that point can be too late.
|
|
1717
|
+
for (const bus of wideBoundaryBuses) {
|
|
1718
|
+
if (useConfiguredDensePlaneRouting) continue
|
|
1719
|
+
if (!getCornerBandSide(bus.exitEdge, bus.preferredExit)) continue
|
|
1720
|
+
const entersNeighboringSourceField = wideBoundaryBuses.some((other) => {
|
|
1721
|
+
if (other === bus || other.componentId !== bus.componentId) return false
|
|
1722
|
+
const xs = other.connections.map(
|
|
1723
|
+
(connection) => connection.sourcePoint.x,
|
|
1724
|
+
)
|
|
1725
|
+
const ys = other.connections.map(
|
|
1726
|
+
(connection) => connection.sourcePoint.y,
|
|
1727
|
+
)
|
|
1728
|
+
const minX = Math.min(...xs)
|
|
1729
|
+
const maxX = Math.max(...xs)
|
|
1730
|
+
const minY = Math.min(...ys)
|
|
1731
|
+
const maxY = Math.max(...ys)
|
|
1732
|
+
return bus.connections.some(({ sourcePoint }) => {
|
|
1733
|
+
const x =
|
|
1734
|
+
sourcePoint.x +
|
|
1735
|
+
(bus.direction === "left"
|
|
1736
|
+
? -bus.pitchX / 2
|
|
1737
|
+
: bus.direction === "right"
|
|
1738
|
+
? bus.pitchX / 2
|
|
1739
|
+
: 0)
|
|
1740
|
+
const y =
|
|
1741
|
+
sourcePoint.y +
|
|
1742
|
+
(bus.direction === "down"
|
|
1743
|
+
? -bus.pitchY / 2
|
|
1744
|
+
: bus.direction === "up"
|
|
1745
|
+
? bus.pitchY / 2
|
|
1746
|
+
: 0)
|
|
1747
|
+
return x >= minX && x <= maxX && y >= minY && y <= maxY
|
|
1748
|
+
})
|
|
1749
|
+
})
|
|
1750
|
+
if (entersNeighboringSourceField)
|
|
1751
|
+
preferBoundaryOutwardByBusId.set(bus.busId, false)
|
|
1752
|
+
}
|
|
1753
|
+
const debugFlippedBoundaryBus = process.env.FANOUT_DEBUG_FLIP_BOUNDARY_BUS
|
|
1754
|
+
if (debugFlippedBoundaryBus) {
|
|
1755
|
+
preferredBoundaryPerpendicularSideByBusId.set(debugFlippedBoundaryBus, -1)
|
|
1756
|
+
}
|
|
1757
|
+
const debugOutwardBoundaryBus =
|
|
1758
|
+
process.env.FANOUT_DEBUG_OUTWARD_BOUNDARY_BUS
|
|
1759
|
+
if (debugOutwardBoundaryBus) {
|
|
1760
|
+
preferBoundaryOutwardByBusId.set(debugOutwardBoundaryBus, true)
|
|
1761
|
+
}
|
|
1513
1762
|
const canShareCopper = (
|
|
1514
1763
|
firstConnectionIndex: number,
|
|
1515
1764
|
secondConnectionIndex: number,
|
|
@@ -1545,6 +1794,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1545
1794
|
(bus) => bus.connections.length === 1,
|
|
1546
1795
|
)
|
|
1547
1796
|
const singletonDeferralCandidates =
|
|
1797
|
+
!hasThreeWideBoundaryBuses &&
|
|
1548
1798
|
shouldDeferSingletonBoundaryViaReservation(boundaryBusConnectionCounts)
|
|
1549
1799
|
? singletonBoundaryBuses
|
|
1550
1800
|
.toSorted(
|
|
@@ -1559,8 +1809,8 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1559
1809
|
),
|
|
1560
1810
|
)
|
|
1561
1811
|
: []
|
|
1562
|
-
const
|
|
1563
|
-
boundaryBuses.length === 9
|
|
1812
|
+
const multiLayerLeadingSingletonBuses =
|
|
1813
|
+
boundaryBuses.length === 8 || boundaryBuses.length === 9
|
|
1564
1814
|
? singletonDeferralCandidates.filter((singletonBus) => {
|
|
1565
1815
|
const singletonTargetLayer =
|
|
1566
1816
|
params.busLayerAssignments[singletonBus.busId]
|
|
@@ -1574,6 +1824,53 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1574
1824
|
)
|
|
1575
1825
|
})
|
|
1576
1826
|
: []
|
|
1827
|
+
const throughAllLeadingSingletonBuses = hasThreeWideBoundaryBuses
|
|
1828
|
+
? singletonBoundaryBuses.filter((singletonBus) => {
|
|
1829
|
+
const containingWideBus = getContainingWideSourceField(singletonBus)
|
|
1830
|
+
const singletonTargetLayer =
|
|
1831
|
+
params.busLayerAssignments[singletonBus.busId]
|
|
1832
|
+
const containingWideLayers =
|
|
1833
|
+
containingWideBus?.routableEscapeLayers ??
|
|
1834
|
+
containingWideBus?.allowedLayers ??
|
|
1835
|
+
[]
|
|
1836
|
+
return Boolean(
|
|
1837
|
+
containingWideBus &&
|
|
1838
|
+
singletonTargetLayer &&
|
|
1839
|
+
!containingWideLayers.includes(singletonTargetLayer),
|
|
1840
|
+
)
|
|
1841
|
+
})
|
|
1842
|
+
: []
|
|
1843
|
+
const throughAllLeadingCompanionBuses =
|
|
1844
|
+
throughAllLeadingSingletonBuses.flatMap((singletonBus) => {
|
|
1845
|
+
const containingWideBus = getContainingWideSourceField(singletonBus)
|
|
1846
|
+
const singletonTargetLayer =
|
|
1847
|
+
params.busLayerAssignments[singletonBus.busId]
|
|
1848
|
+
return boundaryBuses.filter(
|
|
1849
|
+
(candidate) =>
|
|
1850
|
+
candidate.connections.length > 1 &&
|
|
1851
|
+
candidate.connections.length < 8 &&
|
|
1852
|
+
candidate.direction === singletonBus.direction &&
|
|
1853
|
+
params.busLayerAssignments[candidate.busId] ===
|
|
1854
|
+
singletonTargetLayer &&
|
|
1855
|
+
getContainingWideSourceField(candidate) === containingWideBus,
|
|
1856
|
+
)
|
|
1857
|
+
})
|
|
1858
|
+
const throughAllLeadingBuses =
|
|
1859
|
+
process.env.FANOUT_DEBUG_DISABLE_LEADING_NARROW === "1"
|
|
1860
|
+
? []
|
|
1861
|
+
: throughAllLeadingSingletonBuses.flatMap((singletonBus) => [
|
|
1862
|
+
singletonBus,
|
|
1863
|
+
...throughAllLeadingCompanionBuses.filter(
|
|
1864
|
+
(candidate) =>
|
|
1865
|
+
candidate.direction === singletonBus.direction &&
|
|
1866
|
+
params.busLayerAssignments[candidate.busId] ===
|
|
1867
|
+
params.busLayerAssignments[singletonBus.busId],
|
|
1868
|
+
),
|
|
1869
|
+
])
|
|
1870
|
+
const leadingWideSingletonBuses = [
|
|
1871
|
+
...multiLayerLeadingSingletonBuses,
|
|
1872
|
+
...throughAllLeadingBuses,
|
|
1873
|
+
].filter((bus, index, buses) => buses.indexOf(bus) === index)
|
|
1577
1874
|
const leadingLaneCountByWideCornerBand = new Map<string, number>()
|
|
1578
1875
|
if (boundaryBuses.length === 9 && leadingWideSingletonBuses.length > 0) {
|
|
1579
1876
|
for (const bus of leadingWideSingletonBuses) {
|
|
@@ -1598,11 +1895,30 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1598
1895
|
)
|
|
1599
1896
|
}
|
|
1600
1897
|
}
|
|
1601
|
-
const
|
|
1602
|
-
|
|
1603
|
-
(bus) =>
|
|
1604
|
-
|
|
1605
|
-
|
|
1898
|
+
const viaProvisionalBoundaryBusSet = new Set([
|
|
1899
|
+
...(process.env.FANOUT_DEBUG_PROVISIONAL_NARROW === "1"
|
|
1900
|
+
? boundaryBuses.filter((bus) => bus.connections.length < 8)
|
|
1901
|
+
: []),
|
|
1902
|
+
...singletonDeferralCandidates.filter((bus) => {
|
|
1903
|
+
const containingBus = getContainingWideSourceField(bus)
|
|
1904
|
+
const sharesContainingBusLayer =
|
|
1905
|
+
!useConfiguredDensePlaneRouting &&
|
|
1906
|
+
containingBus &&
|
|
1907
|
+
params.busLayerAssignments[containingBus.busId] ===
|
|
1908
|
+
params.busLayerAssignments[bus.busId]
|
|
1909
|
+
return (
|
|
1910
|
+
!leadingWideSingletonBuses.includes(bus) && !sharesContainingBusLayer
|
|
1911
|
+
)
|
|
1912
|
+
}),
|
|
1913
|
+
...(hasThreeWideBoundaryBuses
|
|
1914
|
+
? boundaryBuses.filter(
|
|
1915
|
+
(bus) =>
|
|
1916
|
+
bus.connections.length === 2 &&
|
|
1917
|
+
!narrowBusOverlapsWideTargetSpan(bus) &&
|
|
1918
|
+
!leadingWideSingletonBuses.includes(bus),
|
|
1919
|
+
)
|
|
1920
|
+
: []),
|
|
1921
|
+
])
|
|
1606
1922
|
const getCornerBandTargetTrackOffset = (bus: PreparedBus): number => {
|
|
1607
1923
|
const side = getCornerBandSide(bus.exitEdge, bus.preferredExit)
|
|
1608
1924
|
if (!bus.exitEdge || !side) return 0
|
|
@@ -1616,11 +1932,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1616
1932
|
})
|
|
1617
1933
|
}
|
|
1618
1934
|
const initiallyMatchedBoundaryBuses = boundaryBuses.filter(
|
|
1619
|
-
(bus) => !
|
|
1935
|
+
(bus) => !viaProvisionalBoundaryBusSet.has(bus),
|
|
1620
1936
|
)
|
|
1621
1937
|
const jointViaPoints = useJointBoundaryViaReservation
|
|
1622
1938
|
? matchComponentDogboneViaSites(
|
|
1623
|
-
[
|
|
1939
|
+
[
|
|
1940
|
+
...activeBoundaryReservationPlaneBuses,
|
|
1941
|
+
...initiallyMatchedBoundaryBuses,
|
|
1942
|
+
],
|
|
1624
1943
|
{
|
|
1625
1944
|
viaDiameter: this.config.viaDiameter,
|
|
1626
1945
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
@@ -1629,22 +1948,108 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1629
1948
|
maximumSearchStates: 100_000,
|
|
1630
1949
|
preferredBoundaryPerpendicularSideByBusId,
|
|
1631
1950
|
preferBoundaryOutwardByBusId,
|
|
1951
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
1952
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1632
1953
|
canShareCopper,
|
|
1633
1954
|
},
|
|
1634
1955
|
)
|
|
1635
1956
|
: null
|
|
1636
|
-
|
|
1957
|
+
let seedViaPoints =
|
|
1637
1958
|
jointViaPoints ??
|
|
1638
|
-
matchComponentDogboneViaSites(
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1959
|
+
matchComponentDogboneViaSites(
|
|
1960
|
+
[...activeBoundaryReservationPlaneBuses, boundaryBuses[0]!],
|
|
1961
|
+
{
|
|
1962
|
+
viaDiameter: this.config.viaDiameter,
|
|
1963
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1964
|
+
traceWidth: this.config.traceWidth,
|
|
1965
|
+
clearance: this.config.clearance,
|
|
1966
|
+
maximumSearchStates: 20_000,
|
|
1967
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
1968
|
+
preferBoundaryOutwardByBusId,
|
|
1969
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
1970
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1971
|
+
canShareCopper,
|
|
1972
|
+
},
|
|
1973
|
+
)
|
|
1974
|
+
const debugFixedVias = process.env.FANOUT_DEBUG_FIXED_VIAS
|
|
1975
|
+
if (seedViaPoints && debugFixedVias) {
|
|
1976
|
+
seedViaPoints = new Map(seedViaPoints)
|
|
1977
|
+
for (const entry of debugFixedVias.split(",")) {
|
|
1978
|
+
const parts = entry.split(":")
|
|
1979
|
+
const rawY = parts.pop()
|
|
1980
|
+
const rawX = parts.pop()
|
|
1981
|
+
const connectionName = parts.join(":")
|
|
1982
|
+
const connectionIndex = [...connectionNameByIndex].find(
|
|
1983
|
+
([, name]) => name === connectionName,
|
|
1984
|
+
)?.[0]
|
|
1985
|
+
const x = Number(rawX)
|
|
1986
|
+
const y = Number(rawY)
|
|
1987
|
+
if (
|
|
1988
|
+
connectionIndex !== undefined &&
|
|
1989
|
+
Number.isFinite(x) &&
|
|
1990
|
+
Number.isFinite(y)
|
|
1991
|
+
) {
|
|
1992
|
+
seedViaPoints.set(connectionIndex, { x, y })
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
const debugStagedPlaneBuses = process.env.FANOUT_DEBUG_STAGED_PLANE_BUS_IDS
|
|
1997
|
+
? planeBuses.filter((bus) =>
|
|
1998
|
+
process.env
|
|
1999
|
+
.FANOUT_DEBUG_STAGED_PLANE_BUS_IDS!.split(",")
|
|
2000
|
+
.includes(bus.busId),
|
|
2001
|
+
)
|
|
2002
|
+
: (process.env.FANOUT_DEBUG_STAGED_PLANE_INDICES?.split(",").flatMap(
|
|
2003
|
+
(index) =>
|
|
2004
|
+
planeBuses[Number(index) - 1]
|
|
2005
|
+
? [planeBuses[Number(index) - 1]!]
|
|
2006
|
+
: [],
|
|
2007
|
+
) ?? [])
|
|
2008
|
+
if (seedViaPoints && debugStagedPlaneBuses.length > 0) {
|
|
2009
|
+
for (const stagedPlaneBus of debugStagedPlaneBuses) {
|
|
2010
|
+
if (activeBoundaryReservationPlaneBuses.includes(stagedPlaneBus)) {
|
|
2011
|
+
continue
|
|
2012
|
+
}
|
|
2013
|
+
const stagedViaPoints = matchComponentDogboneViaSites(
|
|
2014
|
+
[
|
|
2015
|
+
...activeBoundaryReservationPlaneBuses,
|
|
2016
|
+
stagedPlaneBus,
|
|
2017
|
+
...initiallyMatchedBoundaryBuses,
|
|
2018
|
+
],
|
|
2019
|
+
{
|
|
2020
|
+
viaDiameter: this.config.viaDiameter,
|
|
2021
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
2022
|
+
traceWidth: this.config.traceWidth,
|
|
2023
|
+
clearance: this.config.clearance,
|
|
2024
|
+
maximumSearchStates: 100_000,
|
|
2025
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
2026
|
+
preferBoundaryOutwardByBusId,
|
|
2027
|
+
fixedViaPointsByConnectionIndex: seedViaPoints,
|
|
2028
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2029
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
2030
|
+
canShareCopper,
|
|
2031
|
+
},
|
|
2032
|
+
)
|
|
2033
|
+
debugDense(
|
|
2034
|
+
"plane-reservation:staged",
|
|
2035
|
+
stagedPlaneBus.busId,
|
|
2036
|
+
stagedViaPoints?.size ?? "failed",
|
|
2037
|
+
)
|
|
2038
|
+
if (!stagedViaPoints) break
|
|
2039
|
+
seedViaPoints = new Map([...seedViaPoints, ...stagedViaPoints])
|
|
2040
|
+
activeBoundaryReservationPlaneBuses.push(stagedPlaneBus)
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
debugDense("seed", seedViaPoints?.size ?? "failed")
|
|
2044
|
+
if (seedViaPoints && process.env.FANOUT_DEBUG_DENSE_POINTS === "1") {
|
|
2045
|
+
console.error(
|
|
2046
|
+
"dense: seed-points",
|
|
2047
|
+
[...seedViaPoints].map(([connectionIndex, point]) => ({
|
|
2048
|
+
connection: connectionNameByIndex.get(connectionIndex),
|
|
2049
|
+
point,
|
|
2050
|
+
})),
|
|
2051
|
+
)
|
|
2052
|
+
}
|
|
1648
2053
|
let denseWorkUnitIndex = 1
|
|
1649
2054
|
const denseWorkUnitCount = boundaryBuses.length + planeBuses.length + 3
|
|
1650
2055
|
this.setInProgressPlans({
|
|
@@ -1657,10 +2062,19 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1657
2062
|
yield
|
|
1658
2063
|
if (seedViaPoints) {
|
|
1659
2064
|
const denseBoundaryBusesInRoutingOrder = [
|
|
1660
|
-
...
|
|
1661
|
-
...boundaryBuses
|
|
1662
|
-
|
|
1663
|
-
|
|
2065
|
+
...multiLayerLeadingSingletonBuses,
|
|
2066
|
+
...boundaryBuses
|
|
2067
|
+
.filter(
|
|
2068
|
+
(bus) =>
|
|
2069
|
+
!multiLayerLeadingSingletonBuses.includes(bus) &&
|
|
2070
|
+
!throughAllLeadingBuses.includes(bus),
|
|
2071
|
+
)
|
|
2072
|
+
.flatMap((bus) => [
|
|
2073
|
+
...throughAllLeadingBuses.filter(
|
|
2074
|
+
(candidate) => getContainingWideSourceField(candidate) === bus,
|
|
2075
|
+
),
|
|
2076
|
+
bus,
|
|
2077
|
+
]),
|
|
1664
2078
|
]
|
|
1665
2079
|
let fixedViaPointsByConnectionIndex: ReadonlyMap<
|
|
1666
2080
|
number,
|
|
@@ -1704,10 +2118,31 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1704
2118
|
this: FanoutSolver,
|
|
1705
2119
|
bus: PreparedBus,
|
|
1706
2120
|
): Generator<FanoutWorkYield, boolean, unknown> {
|
|
2121
|
+
debugDense("route:start", bus.busId, matchedPlans.length)
|
|
2122
|
+
if (process.env.FANOUT_DEBUG_DENSE_POINTS === "1") {
|
|
2123
|
+
console.error(
|
|
2124
|
+
"dense: points",
|
|
2125
|
+
bus.busId,
|
|
2126
|
+
bus.connections.map((connection) => ({
|
|
2127
|
+
source: connection.sourcePoint,
|
|
2128
|
+
via: fixedViaPointsByConnectionIndex.get(
|
|
2129
|
+
connection.connectionIndex,
|
|
2130
|
+
),
|
|
2131
|
+
target: connection.exitTargetPoint ?? connection.targetPoint,
|
|
2132
|
+
})),
|
|
2133
|
+
)
|
|
2134
|
+
}
|
|
1707
2135
|
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
1708
2136
|
if (!targetLayer) {
|
|
1709
2137
|
return false
|
|
1710
2138
|
}
|
|
2139
|
+
const adaptiveWindingRouteOrder =
|
|
2140
|
+
!useConfiguredDensePlaneRouting &&
|
|
2141
|
+
!getCornerBandSide(bus.exitEdge, bus.preferredExit) &&
|
|
2142
|
+
bus.connections.length > 2 &&
|
|
2143
|
+
wideBoundaryBuses.some((candidate) =>
|
|
2144
|
+
getCornerBandSide(candidate.exitEdge, candidate.preferredExit),
|
|
2145
|
+
)
|
|
1711
2146
|
const routeParams = {
|
|
1712
2147
|
srj: this.routingSrj,
|
|
1713
2148
|
bus,
|
|
@@ -1724,18 +2159,27 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1724
2159
|
staticClearanceCache: this.routeStaticClearanceCache,
|
|
1725
2160
|
fixedViaPointsByConnectionIndex,
|
|
1726
2161
|
reservedVias: getReservedVias(bus),
|
|
1727
|
-
viaMinimalOnly:
|
|
2162
|
+
viaMinimalOnly: process.env.FANOUT_DEBUG_ALLOW_EXTRA_VIAS !== "1",
|
|
2163
|
+
allowBoundarySideViaFallback: !useConfiguredDensePlaneRouting,
|
|
2164
|
+
adaptiveWindingRouteOrder,
|
|
2165
|
+
alignWindingGridToPads: !useConfiguredDensePlaneRouting,
|
|
2166
|
+
fixedViaFallbackRouteOrderAttempts: adaptiveWindingRouteOrder
|
|
2167
|
+
? 60
|
|
2168
|
+
: useConfiguredDensePlaneRouting
|
|
2169
|
+
? 6
|
|
2170
|
+
: 24,
|
|
1728
2171
|
cornerBandTargetTrackOffset: getCornerBandTargetTrackOffset(bus),
|
|
1729
2172
|
} as const
|
|
1730
2173
|
const routeAlternatives = function* (
|
|
1731
2174
|
this: FanoutSolver,
|
|
2175
|
+
candidateRouteParams: Parameters<typeof routeBusAlternativesSteps>[0],
|
|
1732
2176
|
maximumAlternatives: number,
|
|
1733
2177
|
): Generator<FanoutWorkYield, FanoutRoutePlan[][], unknown> {
|
|
1734
2178
|
this.activeRoutingVisualization = null
|
|
1735
2179
|
const solver = this.createWorkSolver(
|
|
1736
2180
|
"BoundaryBusRoutingSolver",
|
|
1737
2181
|
this.routeBusAlternativesWorkSteps(
|
|
1738
|
-
|
|
2182
|
+
candidateRouteParams,
|
|
1739
2183
|
maximumAlternatives,
|
|
1740
2184
|
),
|
|
1741
2185
|
undefined,
|
|
@@ -1743,7 +2187,119 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1743
2187
|
)
|
|
1744
2188
|
return (yield { type: "subsolver", solver }) as FanoutRoutePlan[][]
|
|
1745
2189
|
}.bind(this)
|
|
1746
|
-
|
|
2190
|
+
const routableEscapeLayers =
|
|
2191
|
+
bus.routableEscapeLayers ?? bus.allowedLayers ?? []
|
|
2192
|
+
const singleLayerBus = routableEscapeLayers.some(
|
|
2193
|
+
(layer) => layer !== targetLayer,
|
|
2194
|
+
)
|
|
2195
|
+
? { ...bus, routableEscapeLayers: [targetLayer] }
|
|
2196
|
+
: bus
|
|
2197
|
+
const embeddedNarrowBusAlreadyRouted = boundaryBuses.some(
|
|
2198
|
+
(candidate) =>
|
|
2199
|
+
candidate.connections.length < 8 &&
|
|
2200
|
+
getContainingWideSourceField(candidate) === bus &&
|
|
2201
|
+
matchedPlans.some((plan) => plan.busId === candidate.busId),
|
|
2202
|
+
)
|
|
2203
|
+
// In the configured dense-plane mode, a second allowed layer is an
|
|
2204
|
+
// optional winding crossover channel rather than a requirement. Try
|
|
2205
|
+
// the simpler single-layer route first unless an already-routed narrow
|
|
2206
|
+
// bus occupies that direct winding channel. Preserve the released
|
|
2207
|
+
// multi-layer search order for callers that did not opt into this mode.
|
|
2208
|
+
const preferSingleLayerWinding =
|
|
2209
|
+
useConfiguredDensePlaneRouting &&
|
|
2210
|
+
singleLayerBus !== bus &&
|
|
2211
|
+
!embeddedNarrowBusAlreadyRouted
|
|
2212
|
+
let busPlans = (yield* routeAlternatives(
|
|
2213
|
+
preferSingleLayerWinding
|
|
2214
|
+
? { ...routeParams, bus: singleLayerBus }
|
|
2215
|
+
: routeParams,
|
|
2216
|
+
1,
|
|
2217
|
+
))[0]
|
|
2218
|
+
if (!busPlans && preferSingleLayerWinding) {
|
|
2219
|
+
busPlans = (yield* routeAlternatives(routeParams, 1))[0]
|
|
2220
|
+
}
|
|
2221
|
+
if (!busPlans && !useConfiguredDensePlaneRouting) {
|
|
2222
|
+
const originalPoints = fixedViaPointsByConnectionIndex
|
|
2223
|
+
const originalOutward =
|
|
2224
|
+
preferBoundaryOutwardByBusId.get(bus.busId) ?? true
|
|
2225
|
+
const originalSide =
|
|
2226
|
+
preferredBoundaryPerpendicularSideByBusId.get(bus.busId) ?? 1
|
|
2227
|
+
for (const [outward, side] of [
|
|
2228
|
+
[!originalOutward, originalSide],
|
|
2229
|
+
[originalOutward, -originalSide],
|
|
2230
|
+
[!originalOutward, -originalSide],
|
|
2231
|
+
] as const) {
|
|
2232
|
+
const rematchedPoints = matchComponentDogboneViaSites(
|
|
2233
|
+
[
|
|
2234
|
+
...new Set([
|
|
2235
|
+
...activeBoundaryReservationPlaneBuses,
|
|
2236
|
+
...initiallyMatchedBoundaryBuses,
|
|
2237
|
+
...this.preparedBuses.filter((candidate) =>
|
|
2238
|
+
matchedPlans.some((plan) => plan.busId === candidate.busId),
|
|
2239
|
+
),
|
|
2240
|
+
bus,
|
|
2241
|
+
]),
|
|
2242
|
+
],
|
|
2243
|
+
{
|
|
2244
|
+
viaDiameter: this.config.viaDiameter,
|
|
2245
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
2246
|
+
traceWidth: this.config.traceWidth,
|
|
2247
|
+
clearance: this.config.clearance,
|
|
2248
|
+
maximumSearchStates: 100_000,
|
|
2249
|
+
preferredBoundaryPerpendicularSideByBusId: new Map([
|
|
2250
|
+
...preferredBoundaryPerpendicularSideByBusId,
|
|
2251
|
+
[bus.busId, side as -1 | 1],
|
|
2252
|
+
]),
|
|
2253
|
+
preferBoundaryOutwardByBusId: new Map([
|
|
2254
|
+
...preferBoundaryOutwardByBusId,
|
|
2255
|
+
[bus.busId, outward],
|
|
2256
|
+
]),
|
|
2257
|
+
fixedViaPointsByConnectionIndex: new Map(
|
|
2258
|
+
matchedPlans
|
|
2259
|
+
.filter((plan) => plan.via)
|
|
2260
|
+
.map((plan) => [plan.connectionIndex, plan.via!.center]),
|
|
2261
|
+
),
|
|
2262
|
+
preferredViaPointsByConnectionIndex: new Map(
|
|
2263
|
+
[...originalPoints].filter(
|
|
2264
|
+
([index]) =>
|
|
2265
|
+
!bus.connections.some(
|
|
2266
|
+
(connection) => connection.connectionIndex === index,
|
|
2267
|
+
),
|
|
2268
|
+
),
|
|
2269
|
+
),
|
|
2270
|
+
blockingSegments: matchedPlans.flatMap((plan) =>
|
|
2271
|
+
plan.segments.map((segment) => ({
|
|
2272
|
+
connectionIndex: plan.connectionIndex,
|
|
2273
|
+
segment,
|
|
2274
|
+
})),
|
|
2275
|
+
),
|
|
2276
|
+
additionalObstacles: this.routingSrj.obstacles,
|
|
2277
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
2278
|
+
canShareCopper,
|
|
2279
|
+
},
|
|
2280
|
+
)
|
|
2281
|
+
debugDense(
|
|
2282
|
+
"retry-sites",
|
|
2283
|
+
bus.busId,
|
|
2284
|
+
outward,
|
|
2285
|
+
side,
|
|
2286
|
+
rematchedPoints?.size ?? "failed",
|
|
2287
|
+
)
|
|
2288
|
+
if (!rematchedPoints) continue
|
|
2289
|
+
fixedViaPointsByConnectionIndex = rematchedPoints
|
|
2290
|
+
busPlans = (yield* routeAlternatives(
|
|
2291
|
+
{
|
|
2292
|
+
...routeParams,
|
|
2293
|
+
fixedViaPointsByConnectionIndex: rematchedPoints,
|
|
2294
|
+
reservedVias: getReservedVias(bus),
|
|
2295
|
+
fixedViaFallbackRouteOrderAttempts: 3,
|
|
2296
|
+
},
|
|
2297
|
+
1,
|
|
2298
|
+
))[0]
|
|
2299
|
+
if (busPlans) break
|
|
2300
|
+
}
|
|
2301
|
+
if (!busPlans) fixedViaPointsByConnectionIndex = originalPoints
|
|
2302
|
+
}
|
|
1747
2303
|
if (busPlans && bus.maxLengthSkew !== undefined) {
|
|
1748
2304
|
const lengths = busPlans.map((plan) => plan.length)
|
|
1749
2305
|
const rawSkew = Math.max(...lengths) - Math.min(...lengths)
|
|
@@ -1758,7 +2314,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1758
2314
|
// skewed that compact meanders are unlikely to absorb the deficit.
|
|
1759
2315
|
// This keeps already-near-matched buses on the single-attempt path.
|
|
1760
2316
|
if (needsRouteDiversity) {
|
|
1761
|
-
busPlans = (yield* routeAlternatives(3)).toSorted(
|
|
2317
|
+
busPlans = (yield* routeAlternatives(routeParams, 3)).toSorted(
|
|
1762
2318
|
(first, second) => {
|
|
1763
2319
|
const firstLengths = first.map((plan) => plan.length)
|
|
1764
2320
|
const secondLengths = second.map((plan) => plan.length)
|
|
@@ -1772,14 +2328,29 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1772
2328
|
}
|
|
1773
2329
|
}
|
|
1774
2330
|
if (!busPlans) {
|
|
2331
|
+
debugDense("route:failed", bus.busId)
|
|
1775
2332
|
return false
|
|
1776
2333
|
}
|
|
1777
2334
|
matchedPlans.push(...busPlans)
|
|
2335
|
+
debugDense("route:complete", bus.busId, busPlans.length)
|
|
1778
2336
|
return true
|
|
1779
2337
|
}.bind(this)
|
|
1780
2338
|
|
|
1781
2339
|
const firstBoundaryBus = denseBoundaryBusesInRoutingOrder[0]!
|
|
1782
2340
|
const routedBoundaryBuses: PreparedBus[] = []
|
|
2341
|
+
const reserveAllPlaneDogbonesAfterFirstWideBus = (
|
|
2342
|
+
bus: PreparedBus,
|
|
2343
|
+
): void => {
|
|
2344
|
+
if (
|
|
2345
|
+
bus.connections.length >= 8 &&
|
|
2346
|
+
!useConfiguredDensePlaneRouting &&
|
|
2347
|
+
process.env.FANOUT_DEBUG_NO_PLANE_EXPANSION !== "1" &&
|
|
2348
|
+
activeBoundaryReservationPlaneBuses.length < planeBuses.length
|
|
2349
|
+
) {
|
|
2350
|
+
activeBoundaryReservationPlaneBuses = planeBuses
|
|
2351
|
+
debugDense("plane-reservations:expanded", planeBuses.length)
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
1783
2354
|
const firstBoundaryBusRouted =
|
|
1784
2355
|
yield* routeMatchedBoundaryBusSteps(firstBoundaryBus)
|
|
1785
2356
|
this.setInProgressPlans({
|
|
@@ -1793,6 +2364,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1793
2364
|
yield
|
|
1794
2365
|
if (firstBoundaryBusRouted) {
|
|
1795
2366
|
routedBoundaryBuses.push(firstBoundaryBus)
|
|
2367
|
+
reserveAllPlaneDogbonesAfterFirstWideBus(firstBoundaryBus)
|
|
1796
2368
|
} else {
|
|
1797
2369
|
matchedRoutingSucceeded = false
|
|
1798
2370
|
}
|
|
@@ -1811,18 +2383,51 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1811
2383
|
candidateIndex++
|
|
1812
2384
|
) {
|
|
1813
2385
|
const candidateBus = remainingBoundaryBuses[candidateIndex]!
|
|
2386
|
+
debugDense("candidate:start", candidateBus.busId)
|
|
2387
|
+
const candidateMatchingBase = new Map(fixedViaPointsByConnectionIndex)
|
|
2388
|
+
const debugLateFixedVias = process.env.FANOUT_DEBUG_LATE_FIXED_VIAS
|
|
2389
|
+
if (debugLateFixedVias) {
|
|
2390
|
+
const candidateConnectionIndices = new Set(
|
|
2391
|
+
candidateBus.connections.map(
|
|
2392
|
+
(connection) => connection.connectionIndex,
|
|
2393
|
+
),
|
|
2394
|
+
)
|
|
2395
|
+
for (const entry of debugLateFixedVias.split(",")) {
|
|
2396
|
+
const parts = entry.split(":")
|
|
2397
|
+
const rawY = parts.pop()
|
|
2398
|
+
const rawX = parts.pop()
|
|
2399
|
+
const connectionName = parts.join(":")
|
|
2400
|
+
const connectionIndex = [...connectionNameByIndex].find(
|
|
2401
|
+
([, name]) => name === connectionName,
|
|
2402
|
+
)?.[0]
|
|
2403
|
+
const x = Number(rawX)
|
|
2404
|
+
const y = Number(rawY)
|
|
2405
|
+
if (
|
|
2406
|
+
connectionIndex !== undefined &&
|
|
2407
|
+
candidateConnectionIndices.has(connectionIndex) &&
|
|
2408
|
+
Number.isFinite(x) &&
|
|
2409
|
+
Number.isFinite(y)
|
|
2410
|
+
) {
|
|
2411
|
+
candidateMatchingBase.set(connectionIndex, { x, y })
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
1814
2415
|
const candidateHasFixedViaPoints = candidateBus.connections.every(
|
|
1815
2416
|
(connection) =>
|
|
1816
|
-
|
|
2417
|
+
candidateMatchingBase.has(connection.connectionIndex),
|
|
1817
2418
|
)
|
|
1818
|
-
const
|
|
2419
|
+
const newlyMatchedViaPoints =
|
|
1819
2420
|
jointViaPoints && candidateHasFixedViaPoints
|
|
1820
2421
|
? // The joint map is deliberately kept intact so getReservedVias()
|
|
1821
2422
|
// blocks every already-reserved future through-barrel during A*.
|
|
1822
2423
|
// Provisional singleton and plane dogbones are rematched later.
|
|
1823
|
-
new Map(
|
|
2424
|
+
new Map(candidateMatchingBase)
|
|
1824
2425
|
: matchComponentDogboneViaSites(
|
|
1825
|
-
[
|
|
2426
|
+
[
|
|
2427
|
+
...activeBoundaryReservationPlaneBuses,
|
|
2428
|
+
...routedBoundaryBuses,
|
|
2429
|
+
candidateBus,
|
|
2430
|
+
],
|
|
1826
2431
|
{
|
|
1827
2432
|
viaDiameter: this.config.viaDiameter,
|
|
1828
2433
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
@@ -1831,24 +2436,22 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1831
2436
|
maximumSearchStates: 100_000,
|
|
1832
2437
|
preferredBoundaryPerpendicularSideByBusId,
|
|
1833
2438
|
preferBoundaryOutwardByBusId,
|
|
1834
|
-
fixedViaPointsByConnectionIndex:
|
|
1835
|
-
fixedViaPointsByConnectionIndex,
|
|
2439
|
+
fixedViaPointsByConnectionIndex: candidateMatchingBase,
|
|
1836
2440
|
blockingSegments,
|
|
2441
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2442
|
+
preferPlaneCheckerboardSites:
|
|
2443
|
+
useConfiguredDensePlaneRouting,
|
|
1837
2444
|
canShareCopper,
|
|
1838
2445
|
},
|
|
1839
2446
|
)
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
})
|
|
1849
|
-
yield
|
|
1850
|
-
continue
|
|
1851
|
-
}
|
|
2447
|
+
const extendedViaPoints = newlyMatchedViaPoints
|
|
2448
|
+
? new Map([...candidateMatchingBase, ...newlyMatchedViaPoints])
|
|
2449
|
+
: null
|
|
2450
|
+
debugDense(
|
|
2451
|
+
"candidate:matched",
|
|
2452
|
+
candidateBus.busId,
|
|
2453
|
+
extendedViaPoints?.size ?? "failed",
|
|
2454
|
+
)
|
|
1852
2455
|
this.setInProgressPlans({
|
|
1853
2456
|
phase: "reserve-next-dense-boundary-bus",
|
|
1854
2457
|
plans: matchedPlans,
|
|
@@ -1858,6 +2461,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1858
2461
|
busId: candidateBus.busId,
|
|
1859
2462
|
})
|
|
1860
2463
|
yield
|
|
2464
|
+
if (!extendedViaPoints) continue
|
|
1861
2465
|
const previousFixedViaPoints = fixedViaPointsByConnectionIndex
|
|
1862
2466
|
const previousPlanCount = matchedPlans.length
|
|
1863
2467
|
const laterBuses = remainingBoundaryBuses.filter(
|
|
@@ -1870,7 +2474,12 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1870
2474
|
if (laterBuses.length === 1) {
|
|
1871
2475
|
const laterBus = laterBuses[0]!
|
|
1872
2476
|
const futureAssignment = matchComponentDogboneViaSites(
|
|
1873
|
-
[
|
|
2477
|
+
[
|
|
2478
|
+
...activeBoundaryReservationPlaneBuses,
|
|
2479
|
+
...routedBoundaryBuses,
|
|
2480
|
+
candidateBus,
|
|
2481
|
+
laterBus,
|
|
2482
|
+
],
|
|
1874
2483
|
{
|
|
1875
2484
|
viaDiameter: this.config.viaDiameter,
|
|
1876
2485
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
@@ -1881,9 +2490,16 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1881
2490
|
preferBoundaryOutwardByBusId,
|
|
1882
2491
|
fixedViaPointsByConnectionIndex: extendedViaPoints,
|
|
1883
2492
|
blockingSegments,
|
|
2493
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2494
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1884
2495
|
canShareCopper,
|
|
1885
2496
|
},
|
|
1886
2497
|
)
|
|
2498
|
+
debugDense(
|
|
2499
|
+
"future:matched",
|
|
2500
|
+
laterBus.busId,
|
|
2501
|
+
futureAssignment?.size ?? "failed",
|
|
2502
|
+
)
|
|
1887
2503
|
if (futureAssignment) {
|
|
1888
2504
|
const candidateCountByConnectionIndex = new Map<number, number>()
|
|
1889
2505
|
for (const candidate of getComponentDogboneViaSiteCandidates(
|
|
@@ -1894,6 +2510,8 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1894
2510
|
traceWidth: this.config.traceWidth,
|
|
1895
2511
|
clearance: this.config.clearance,
|
|
1896
2512
|
blockingSegments,
|
|
2513
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2514
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1897
2515
|
canShareCopper,
|
|
1898
2516
|
},
|
|
1899
2517
|
)) {
|
|
@@ -1929,6 +2547,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1929
2547
|
}
|
|
1930
2548
|
fixedViaPointsByConnectionIndex = candidateFixedViaPoints
|
|
1931
2549
|
if (yield* routeMatchedBoundaryBusSteps(candidateBus)) {
|
|
2550
|
+
debugDense("lookahead:start", candidateBus.busId)
|
|
1932
2551
|
const candidateLeavesAFeasibleExtension =
|
|
1933
2552
|
laterBuses.length === 0 ||
|
|
1934
2553
|
laterBuses.some((laterBus) => {
|
|
@@ -1941,7 +2560,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1941
2560
|
return Boolean(
|
|
1942
2561
|
matchComponentDogboneViaSites(
|
|
1943
2562
|
[
|
|
1944
|
-
...
|
|
2563
|
+
...activeBoundaryReservationPlaneBuses,
|
|
1945
2564
|
...routedBoundaryBuses,
|
|
1946
2565
|
candidateBus,
|
|
1947
2566
|
laterBus,
|
|
@@ -1957,14 +2576,23 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1957
2576
|
fixedViaPointsByConnectionIndex:
|
|
1958
2577
|
fixedViaPointsByConnectionIndex,
|
|
1959
2578
|
blockingSegments: lookaheadBlockingSegments,
|
|
2579
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2580
|
+
preferPlaneCheckerboardSites:
|
|
2581
|
+
useConfiguredDensePlaneRouting,
|
|
1960
2582
|
canShareCopper,
|
|
1961
2583
|
},
|
|
1962
2584
|
),
|
|
1963
2585
|
)
|
|
1964
2586
|
})
|
|
2587
|
+
debugDense(
|
|
2588
|
+
"lookahead:complete",
|
|
2589
|
+
candidateBus.busId,
|
|
2590
|
+
candidateLeavesAFeasibleExtension,
|
|
2591
|
+
)
|
|
1965
2592
|
if (candidateLeavesAFeasibleExtension) {
|
|
1966
2593
|
selectedBusIndex = candidateIndex
|
|
1967
2594
|
routedBoundaryBuses.push(candidateBus)
|
|
2595
|
+
reserveAllPlaneDogbonesAfterFirstWideBus(candidateBus)
|
|
1968
2596
|
this.setInProgressPlans({
|
|
1969
2597
|
phase: "route-dense-boundary-buses",
|
|
1970
2598
|
plans: matchedPlans,
|
|
@@ -1995,12 +2623,15 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1995
2623
|
}
|
|
1996
2624
|
remainingBoundaryBuses.splice(selectedBusIndex, 1)
|
|
1997
2625
|
}
|
|
2626
|
+
let matchedPlaneBusesInRoutingOrder: PreparedBus[] | null = null
|
|
1998
2627
|
if (matchedRoutingSucceeded) {
|
|
1999
2628
|
let feasibleViaPoints: Map<number, { x: number; y: number }> | null =
|
|
2000
2629
|
null
|
|
2630
|
+
let feasibleAlternatePlanePlans: FanoutRoutePlan[] = []
|
|
2001
2631
|
const matchViaPointsAroundPlans = (
|
|
2002
2632
|
candidatePlans: readonly FanoutRoutePlan[],
|
|
2003
2633
|
): Map<number, { x: number; y: number }> | null => {
|
|
2634
|
+
feasibleAlternatePlanePlans = []
|
|
2004
2635
|
const fixedBoundaryViaPoints = new Map(
|
|
2005
2636
|
candidatePlans.flatMap((plan) =>
|
|
2006
2637
|
plan.via
|
|
@@ -2008,8 +2639,812 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2008
2639
|
: [],
|
|
2009
2640
|
),
|
|
2010
2641
|
)
|
|
2011
|
-
|
|
2012
|
-
|
|
2642
|
+
const blockingSegments = candidatePlans.flatMap((plan) =>
|
|
2643
|
+
plan.segments.map((segment) => ({
|
|
2644
|
+
connectionIndex: plan.connectionIndex,
|
|
2645
|
+
segment,
|
|
2646
|
+
})),
|
|
2647
|
+
)
|
|
2648
|
+
const boundaryBusesToMatch = useConfiguredDensePlaneRouting
|
|
2649
|
+
? boundaryBuses
|
|
2650
|
+
: []
|
|
2651
|
+
const blockingVias = useConfiguredDensePlaneRouting
|
|
2652
|
+
? []
|
|
2653
|
+
: candidatePlans.flatMap((plan) =>
|
|
2654
|
+
[
|
|
2655
|
+
plan.via,
|
|
2656
|
+
...(plan.additionalVias ?? []),
|
|
2657
|
+
plan.planeEndpointVia,
|
|
2658
|
+
]
|
|
2659
|
+
.filter((via) => via !== undefined)
|
|
2660
|
+
.map((via) => ({
|
|
2661
|
+
connectionIndex: plan.connectionIndex,
|
|
2662
|
+
center: via!.center,
|
|
2663
|
+
diameter: via!.diameter,
|
|
2664
|
+
spanLayers: via!.spanLayers,
|
|
2665
|
+
})),
|
|
2666
|
+
)
|
|
2667
|
+
// Completed boundary paths can reach their via with several source-
|
|
2668
|
+
// layer segments. Treat their actual copper as fixed obstacles instead
|
|
2669
|
+
// of reinterpreting each as a straight pad-to-via dogbone.
|
|
2670
|
+
const retainedViaPoints = useConfiguredDensePlaneRouting
|
|
2671
|
+
? null
|
|
2672
|
+
: matchComponentDogboneViaSites(planeBuses, {
|
|
2673
|
+
viaDiameter: this.config.viaDiameter,
|
|
2674
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
2675
|
+
traceWidth: this.config.traceWidth,
|
|
2676
|
+
clearance: this.config.clearance,
|
|
2677
|
+
maximumSearchStates: 1,
|
|
2678
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
2679
|
+
preferBoundaryOutwardByBusId,
|
|
2680
|
+
fixedViaPointsByConnectionIndex: new Map([
|
|
2681
|
+
...fixedViaPointsByConnectionIndex,
|
|
2682
|
+
...fixedBoundaryViaPoints,
|
|
2683
|
+
]),
|
|
2684
|
+
blockingSegments,
|
|
2685
|
+
blockingVias,
|
|
2686
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2687
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
2688
|
+
canShareCopper,
|
|
2689
|
+
})
|
|
2690
|
+
if (retainedViaPoints)
|
|
2691
|
+
return new Map([...fixedBoundaryViaPoints, ...retainedViaPoints])
|
|
2692
|
+
if (
|
|
2693
|
+
useConfiguredDensePlaneRouting ||
|
|
2694
|
+
process.env.FANOUT_DEBUG_INCREMENTAL_PLANE_MATCH === "1"
|
|
2695
|
+
) {
|
|
2696
|
+
let incrementalViaPoints = new Map(fixedBoundaryViaPoints)
|
|
2697
|
+
const matchedPlaneBuses = [...activeBoundaryReservationPlaneBuses]
|
|
2698
|
+
for (const planeBus of matchedPlaneBuses) {
|
|
2699
|
+
for (const connection of planeBus.connections) {
|
|
2700
|
+
const reservedPoint = fixedViaPointsByConnectionIndex.get(
|
|
2701
|
+
connection.connectionIndex,
|
|
2702
|
+
)
|
|
2703
|
+
if (reservedPoint) {
|
|
2704
|
+
incrementalViaPoints.set(
|
|
2705
|
+
connection.connectionIndex,
|
|
2706
|
+
reservedPoint,
|
|
2707
|
+
)
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
for (const planeBus of matchedPlaneBuses) {
|
|
2712
|
+
const targetLayer = params.busLayerAssignments[planeBus.busId]
|
|
2713
|
+
if (!targetLayer) return null
|
|
2714
|
+
const reservedPlanePlans = routeBus({
|
|
2715
|
+
srj: this.routingSrj,
|
|
2716
|
+
bus: planeBus,
|
|
2717
|
+
targetLayer,
|
|
2718
|
+
acceptedPlans: [
|
|
2719
|
+
...candidatePlans,
|
|
2720
|
+
...feasibleAlternatePlanePlans,
|
|
2721
|
+
],
|
|
2722
|
+
layerNames: this.config.layerNames,
|
|
2723
|
+
traceWidth: this.config.traceWidth,
|
|
2724
|
+
viaDiameter: this.config.viaDiameter,
|
|
2725
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
2726
|
+
clearance: this.config.clearance,
|
|
2727
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
2728
|
+
allowBlindAndBuriedVias: false,
|
|
2729
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
2730
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
2731
|
+
fixedViaPointsByConnectionIndex: incrementalViaPoints,
|
|
2732
|
+
})
|
|
2733
|
+
if (!reservedPlanePlans) return null
|
|
2734
|
+
feasibleAlternatePlanePlans.push(...reservedPlanePlans)
|
|
2735
|
+
}
|
|
2736
|
+
const independentlyUnmatchablePlaneBuses = planeBuses.filter(
|
|
2737
|
+
(planeBus) =>
|
|
2738
|
+
!matchedPlaneBuses.includes(planeBus) &&
|
|
2739
|
+
!matchComponentDogboneViaSites(
|
|
2740
|
+
[...matchedPlaneBuses, planeBus, ...boundaryBusesToMatch],
|
|
2741
|
+
{
|
|
2742
|
+
viaDiameter: this.config.viaDiameter,
|
|
2743
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
2744
|
+
traceWidth: this.config.traceWidth,
|
|
2745
|
+
clearance: this.config.clearance,
|
|
2746
|
+
maximumSearchStates: 100_000,
|
|
2747
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
2748
|
+
preferBoundaryOutwardByBusId,
|
|
2749
|
+
fixedViaPointsByConnectionIndex: incrementalViaPoints,
|
|
2750
|
+
blockingSegments,
|
|
2751
|
+
blockingVias,
|
|
2752
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2753
|
+
preferPlaneCheckerboardSites:
|
|
2754
|
+
useConfiguredDensePlaneRouting,
|
|
2755
|
+
canShareCopper,
|
|
2756
|
+
},
|
|
2757
|
+
),
|
|
2758
|
+
)
|
|
2759
|
+
const shallowestPlaneLayerIndex = Math.min(
|
|
2760
|
+
...planeBuses.map((bus) =>
|
|
2761
|
+
this.config.layerNames.indexOf(
|
|
2762
|
+
params.busLayerAssignments[bus.busId] ?? "",
|
|
2763
|
+
),
|
|
2764
|
+
),
|
|
2765
|
+
)
|
|
2766
|
+
const deeperPlaneBuses =
|
|
2767
|
+
useConfiguredDensePlaneRouting ||
|
|
2768
|
+
process.env.FANOUT_DEBUG_ROUTE_DEEP_PLANES_FIRST === "1"
|
|
2769
|
+
? planeBuses.filter(
|
|
2770
|
+
(bus) =>
|
|
2771
|
+
!matchedPlaneBuses.includes(bus) &&
|
|
2772
|
+
this.config.layerNames.indexOf(
|
|
2773
|
+
params.busLayerAssignments[bus.busId] ?? "",
|
|
2774
|
+
) > shallowestPlaneLayerIndex,
|
|
2775
|
+
)
|
|
2776
|
+
: []
|
|
2777
|
+
const additionalAlternatePlaneBusIds = new Set([
|
|
2778
|
+
...this.config.denseUnrestrictedPlaneRoutingBusIds,
|
|
2779
|
+
...(process.env.FANOUT_DEBUG_ADDITIONAL_ALTERNATE_PLANE_BUS_IDS?.split(
|
|
2780
|
+
",",
|
|
2781
|
+
) ?? []),
|
|
2782
|
+
])
|
|
2783
|
+
const additionalAlternatePlaneBuses = planeBuses.filter(
|
|
2784
|
+
(bus) =>
|
|
2785
|
+
!matchedPlaneBuses.includes(bus) &&
|
|
2786
|
+
additionalAlternatePlaneBusIds.has(bus.busId),
|
|
2787
|
+
)
|
|
2788
|
+
const alternatePlaneBuses = [
|
|
2789
|
+
...deeperPlaneBuses,
|
|
2790
|
+
...independentlyUnmatchablePlaneBuses.filter(
|
|
2791
|
+
(bus) => !deeperPlaneBuses.includes(bus),
|
|
2792
|
+
),
|
|
2793
|
+
...additionalAlternatePlaneBuses.filter(
|
|
2794
|
+
(bus) =>
|
|
2795
|
+
!deeperPlaneBuses.includes(bus) &&
|
|
2796
|
+
!independentlyUnmatchablePlaneBuses.includes(bus),
|
|
2797
|
+
),
|
|
2798
|
+
]
|
|
2799
|
+
const debugAlternatePlaneOrder =
|
|
2800
|
+
process.env.FANOUT_DEBUG_ALTERNATE_PLANE_ORDER?.split(",") ?? []
|
|
2801
|
+
const orderedAlternatePlaneBuses = alternatePlaneBuses.toSorted(
|
|
2802
|
+
(first, second) => {
|
|
2803
|
+
const firstLayerIndex = this.config.layerNames.indexOf(
|
|
2804
|
+
params.busLayerAssignments[first.busId] ?? "",
|
|
2805
|
+
)
|
|
2806
|
+
const secondLayerIndex = this.config.layerNames.indexOf(
|
|
2807
|
+
params.busLayerAssignments[second.busId] ?? "",
|
|
2808
|
+
)
|
|
2809
|
+
if (
|
|
2810
|
+
firstLayerIndex !== secondLayerIndex &&
|
|
2811
|
+
process.env.FANOUT_DEBUG_ALTERNATE_IGNORE_LAYERS !== "1"
|
|
2812
|
+
) {
|
|
2813
|
+
return process.env.FANOUT_DEBUG_ALTERNATE_SHALLOW_FIRST ===
|
|
2814
|
+
"1"
|
|
2815
|
+
? firstLayerIndex - secondLayerIndex
|
|
2816
|
+
: secondLayerIndex - firstLayerIndex
|
|
2817
|
+
}
|
|
2818
|
+
const firstPriority = debugAlternatePlaneOrder.indexOf(
|
|
2819
|
+
first.busId,
|
|
2820
|
+
)
|
|
2821
|
+
const secondPriority = debugAlternatePlaneOrder.indexOf(
|
|
2822
|
+
second.busId,
|
|
2823
|
+
)
|
|
2824
|
+
return (
|
|
2825
|
+
(firstPriority < 0
|
|
2826
|
+
? debugAlternatePlaneOrder.length
|
|
2827
|
+
: firstPriority) -
|
|
2828
|
+
(secondPriority < 0
|
|
2829
|
+
? debugAlternatePlaneOrder.length
|
|
2830
|
+
: secondPriority) ||
|
|
2831
|
+
first.connections[0]!.connectionIndex -
|
|
2832
|
+
second.connections[0]!.connectionIndex
|
|
2833
|
+
)
|
|
2834
|
+
},
|
|
2835
|
+
)
|
|
2836
|
+
if (alternatePlaneBuses.length > 0) {
|
|
2837
|
+
debugDense(
|
|
2838
|
+
"plane-match:preflight-failed",
|
|
2839
|
+
independentlyUnmatchablePlaneBuses.map((bus) => bus.busId),
|
|
2840
|
+
)
|
|
2841
|
+
if (
|
|
2842
|
+
!useConfiguredDensePlaneRouting &&
|
|
2843
|
+
process.env.FANOUT_DEBUG_ROUTE_UNMATCHED_PLANES !== "1"
|
|
2844
|
+
) {
|
|
2845
|
+
return null
|
|
2846
|
+
}
|
|
2847
|
+
let alternatePlaneSearchStates = 0
|
|
2848
|
+
const maximumAlternatePlaneSearchStates = Number(
|
|
2849
|
+
process.env.FANOUT_DEBUG_ALTERNATE_SEARCH_STATES ??
|
|
2850
|
+
(useConfiguredDensePlaneRouting ? 3_000_000 : 1_000),
|
|
2851
|
+
)
|
|
2852
|
+
const maximumAlternatePlaneRoutes = Number(
|
|
2853
|
+
process.env.FANOUT_DEBUG_ALTERNATE_ROUTE_COUNT ??
|
|
2854
|
+
(useConfiguredDensePlaneRouting ? 128 : 8),
|
|
2855
|
+
)
|
|
2856
|
+
let deepestAlternatePlaneSearchIndex = 0
|
|
2857
|
+
const alternatePlaneFailureCountByBusId = new Map<
|
|
2858
|
+
string,
|
|
2859
|
+
number
|
|
2860
|
+
>()
|
|
2861
|
+
const getPlaneRouteAlternatives = (
|
|
2862
|
+
planeBus: PreparedBus,
|
|
2863
|
+
additionalAcceptedPlans: FanoutRoutePlan[],
|
|
2864
|
+
maximumRoutes = maximumAlternatePlaneRoutes,
|
|
2865
|
+
): FanoutRoutePlan[][] => {
|
|
2866
|
+
const targetLayer = params.busLayerAssignments[planeBus.busId]
|
|
2867
|
+
if (!targetLayer) return []
|
|
2868
|
+
return routeBusAlternatives(
|
|
2869
|
+
{
|
|
2870
|
+
srj: this.routingSrj,
|
|
2871
|
+
bus: planeBus,
|
|
2872
|
+
targetLayer,
|
|
2873
|
+
acceptedPlans: [
|
|
2874
|
+
...candidatePlans,
|
|
2875
|
+
...additionalAcceptedPlans,
|
|
2876
|
+
],
|
|
2877
|
+
layerNames: this.config.layerNames,
|
|
2878
|
+
traceWidth: this.config.traceWidth,
|
|
2879
|
+
viaDiameter: this.config.viaDiameter,
|
|
2880
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
2881
|
+
clearance: this.config.clearance,
|
|
2882
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
2883
|
+
allowBlindAndBuriedVias: false,
|
|
2884
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
2885
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
2886
|
+
},
|
|
2887
|
+
maximumRoutes,
|
|
2888
|
+
)
|
|
2889
|
+
}
|
|
2890
|
+
const routeAlternatePlaneBuses = (
|
|
2891
|
+
remainingPlaneBuses: PreparedBus[],
|
|
2892
|
+
acceptedAlternatePlans: FanoutRoutePlan[],
|
|
2893
|
+
): FanoutRoutePlan[] | null => {
|
|
2894
|
+
if (remainingPlaneBuses.length === 0) {
|
|
2895
|
+
return acceptedAlternatePlans
|
|
2896
|
+
}
|
|
2897
|
+
if (
|
|
2898
|
+
alternatePlaneSearchStates >=
|
|
2899
|
+
maximumAlternatePlaneSearchStates
|
|
2900
|
+
) {
|
|
2901
|
+
return null
|
|
2902
|
+
}
|
|
2903
|
+
deepestAlternatePlaneSearchIndex = Math.max(
|
|
2904
|
+
deepestAlternatePlaneSearchIndex,
|
|
2905
|
+
orderedAlternatePlaneBuses.length -
|
|
2906
|
+
remainingPlaneBuses.length,
|
|
2907
|
+
)
|
|
2908
|
+
const alternativesByBus = remainingPlaneBuses.map(
|
|
2909
|
+
(planeBus) => ({
|
|
2910
|
+
planeBus,
|
|
2911
|
+
alternatives: getPlaneRouteAlternatives(
|
|
2912
|
+
planeBus,
|
|
2913
|
+
acceptedAlternatePlans,
|
|
2914
|
+
),
|
|
2915
|
+
}),
|
|
2916
|
+
)
|
|
2917
|
+
const orderedSelections =
|
|
2918
|
+
process.env.FANOUT_DEBUG_DYNAMIC_ALTERNATE_ORDER === "1"
|
|
2919
|
+
? alternativesByBus.toSorted(
|
|
2920
|
+
(first, second) =>
|
|
2921
|
+
first.alternatives.length -
|
|
2922
|
+
second.alternatives.length ||
|
|
2923
|
+
orderedAlternatePlaneBuses.indexOf(first.planeBus) -
|
|
2924
|
+
orderedAlternatePlaneBuses.indexOf(second.planeBus),
|
|
2925
|
+
)
|
|
2926
|
+
: alternativesByBus
|
|
2927
|
+
const selected = orderedSelections[0]!
|
|
2928
|
+
const { planeBus, alternatives } = selected
|
|
2929
|
+
if (
|
|
2930
|
+
process.env.FANOUT_DEBUG_ALTERNATE_CHOICES === "1" &&
|
|
2931
|
+
alternatePlaneSearchStates < 64
|
|
2932
|
+
) {
|
|
2933
|
+
debugDense(
|
|
2934
|
+
"plane-route:alternate-choice",
|
|
2935
|
+
`depth:${orderedAlternatePlaneBuses.length - remainingPlaneBuses.length}`,
|
|
2936
|
+
planeBus.busId,
|
|
2937
|
+
alternativesByBus.map((entry) => [
|
|
2938
|
+
entry.planeBus.busId,
|
|
2939
|
+
entry.alternatives.length,
|
|
2940
|
+
]),
|
|
2941
|
+
)
|
|
2942
|
+
}
|
|
2943
|
+
if (alternatives.length === 0) {
|
|
2944
|
+
alternatePlaneFailureCountByBusId.set(
|
|
2945
|
+
planeBus.busId,
|
|
2946
|
+
(alternatePlaneFailureCountByBusId.get(planeBus.busId) ??
|
|
2947
|
+
0) + 1,
|
|
2948
|
+
)
|
|
2949
|
+
return null
|
|
2950
|
+
}
|
|
2951
|
+
const selectionsToSearch =
|
|
2952
|
+
process.env.FANOUT_DEBUG_BRANCH_ALTERNATE_ORDER === "1"
|
|
2953
|
+
? orderedSelections
|
|
2954
|
+
: [selected]
|
|
2955
|
+
const alternateActions = selectionsToSearch.flatMap(
|
|
2956
|
+
(selection) =>
|
|
2957
|
+
selection.alternatives.map((alternative) => ({
|
|
2958
|
+
selection,
|
|
2959
|
+
alternative,
|
|
2960
|
+
remaining: remainingPlaneBuses.filter(
|
|
2961
|
+
(candidate) => candidate !== selection.planeBus,
|
|
2962
|
+
),
|
|
2963
|
+
})),
|
|
2964
|
+
)
|
|
2965
|
+
const orderedActions =
|
|
2966
|
+
process.env.FANOUT_DEBUG_LEAST_CONSTRAINING_ALTERNATES === "1"
|
|
2967
|
+
? alternateActions
|
|
2968
|
+
.map((action) => {
|
|
2969
|
+
const acceptedPlans = [
|
|
2970
|
+
...acceptedAlternatePlans,
|
|
2971
|
+
...action.alternative,
|
|
2972
|
+
]
|
|
2973
|
+
const remainingOptionCounts = action.remaining.map(
|
|
2974
|
+
(remainingBus) =>
|
|
2975
|
+
getPlaneRouteAlternatives(
|
|
2976
|
+
remainingBus,
|
|
2977
|
+
acceptedPlans,
|
|
2978
|
+
Math.min(4, maximumAlternatePlaneRoutes),
|
|
2979
|
+
).length,
|
|
2980
|
+
)
|
|
2981
|
+
return {
|
|
2982
|
+
...action,
|
|
2983
|
+
remainingOptionCounts,
|
|
2984
|
+
minimumRemainingOptions:
|
|
2985
|
+
remainingOptionCounts.length === 0
|
|
2986
|
+
? Number.POSITIVE_INFINITY
|
|
2987
|
+
: Math.min(...remainingOptionCounts),
|
|
2988
|
+
totalRemainingOptions: remainingOptionCounts.reduce(
|
|
2989
|
+
(total, count) => total + count,
|
|
2990
|
+
0,
|
|
2991
|
+
),
|
|
2992
|
+
}
|
|
2993
|
+
})
|
|
2994
|
+
.filter(
|
|
2995
|
+
(action) => action.minimumRemainingOptions !== 0,
|
|
2996
|
+
)
|
|
2997
|
+
.toSorted(
|
|
2998
|
+
(first, second) =>
|
|
2999
|
+
second.minimumRemainingOptions -
|
|
3000
|
+
first.minimumRemainingOptions ||
|
|
3001
|
+
second.totalRemainingOptions -
|
|
3002
|
+
first.totalRemainingOptions,
|
|
3003
|
+
)
|
|
3004
|
+
: alternateActions
|
|
3005
|
+
for (const action of orderedActions) {
|
|
3006
|
+
alternatePlaneSearchStates++
|
|
3007
|
+
const completedPlans = routeAlternatePlaneBuses(
|
|
3008
|
+
action.remaining,
|
|
3009
|
+
[...acceptedAlternatePlans, ...action.alternative],
|
|
3010
|
+
)
|
|
3011
|
+
if (completedPlans) return completedPlans
|
|
3012
|
+
if (
|
|
3013
|
+
alternatePlaneSearchStates >=
|
|
3014
|
+
maximumAlternatePlaneSearchStates
|
|
3015
|
+
) {
|
|
3016
|
+
break
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
alternatePlaneFailureCountByBusId.set(
|
|
3020
|
+
planeBus.busId,
|
|
3021
|
+
(alternatePlaneFailureCountByBusId.get(planeBus.busId) ?? 0) +
|
|
3022
|
+
1,
|
|
3023
|
+
)
|
|
3024
|
+
return null
|
|
3025
|
+
}
|
|
3026
|
+
let alternatePlanePlans: FanoutRoutePlan[] | null
|
|
3027
|
+
if (
|
|
3028
|
+
useConfiguredDensePlaneRouting ||
|
|
3029
|
+
process.env.FANOUT_DEBUG_EXACT_COVER_ALTERNATES === "1"
|
|
3030
|
+
) {
|
|
3031
|
+
type IndependentPlaneRouteCandidate = {
|
|
3032
|
+
key: string
|
|
3033
|
+
planeBus: PreparedBus
|
|
3034
|
+
plans: FanoutRoutePlan[]
|
|
3035
|
+
}
|
|
3036
|
+
const candidateSets = orderedAlternatePlaneBuses.map(
|
|
3037
|
+
(planeBus) => ({
|
|
3038
|
+
planeBus,
|
|
3039
|
+
candidates: getPlaneRouteAlternatives(
|
|
3040
|
+
planeBus,
|
|
3041
|
+
feasibleAlternatePlanePlans,
|
|
3042
|
+
).map((plans, index) => ({
|
|
3043
|
+
key: `${planeBus.busId}:${index}`,
|
|
3044
|
+
planeBus,
|
|
3045
|
+
plans,
|
|
3046
|
+
})),
|
|
3047
|
+
}),
|
|
3048
|
+
)
|
|
3049
|
+
debugDense(
|
|
3050
|
+
"plane-route:alternate-candidate-counts",
|
|
3051
|
+
candidateSets.map((candidateSet) => [
|
|
3052
|
+
candidateSet.planeBus.busId,
|
|
3053
|
+
candidateSet.candidates.length,
|
|
3054
|
+
]),
|
|
3055
|
+
)
|
|
3056
|
+
const compatibilityByCandidatePair = new Map<string, boolean>()
|
|
3057
|
+
const candidatesAreCompatible = (
|
|
3058
|
+
first: IndependentPlaneRouteCandidate,
|
|
3059
|
+
second: IndependentPlaneRouteCandidate,
|
|
3060
|
+
): boolean => {
|
|
3061
|
+
const cacheKey = [first.key, second.key].toSorted().join("|")
|
|
3062
|
+
const cached = compatibilityByCandidatePair.get(cacheKey)
|
|
3063
|
+
if (cached !== undefined) return cached
|
|
3064
|
+
const compatible = fanoutPlansAreMutuallyClear({
|
|
3065
|
+
plans: [...first.plans, ...second.plans],
|
|
3066
|
+
srj: this.routingSrj,
|
|
3067
|
+
clearance: this.config.clearance,
|
|
3068
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
3069
|
+
})
|
|
3070
|
+
compatibilityByCandidatePair.set(cacheKey, compatible)
|
|
3071
|
+
return compatible
|
|
3072
|
+
}
|
|
3073
|
+
const selectCompatiblePlaneRoutes = (
|
|
3074
|
+
remainingCandidateSets: typeof candidateSets,
|
|
3075
|
+
selectedCandidates: IndependentPlaneRouteCandidate[],
|
|
3076
|
+
): IndependentPlaneRouteCandidate[] | null => {
|
|
3077
|
+
if (remainingCandidateSets.length === 0) {
|
|
3078
|
+
return selectedCandidates
|
|
3079
|
+
}
|
|
3080
|
+
if (
|
|
3081
|
+
alternatePlaneSearchStates >=
|
|
3082
|
+
maximumAlternatePlaneSearchStates
|
|
3083
|
+
) {
|
|
3084
|
+
return null
|
|
3085
|
+
}
|
|
3086
|
+
deepestAlternatePlaneSearchIndex = Math.max(
|
|
3087
|
+
deepestAlternatePlaneSearchIndex,
|
|
3088
|
+
orderedAlternatePlaneBuses.length -
|
|
3089
|
+
remainingCandidateSets.length,
|
|
3090
|
+
)
|
|
3091
|
+
const selectedSet = remainingCandidateSets.toSorted(
|
|
3092
|
+
(first, second) =>
|
|
3093
|
+
first.candidates.length - second.candidates.length,
|
|
3094
|
+
)[0]!
|
|
3095
|
+
if (selectedSet.candidates.length === 0) {
|
|
3096
|
+
alternatePlaneFailureCountByBusId.set(
|
|
3097
|
+
selectedSet.planeBus.busId,
|
|
3098
|
+
(alternatePlaneFailureCountByBusId.get(
|
|
3099
|
+
selectedSet.planeBus.busId,
|
|
3100
|
+
) ?? 0) + 1,
|
|
3101
|
+
)
|
|
3102
|
+
return null
|
|
3103
|
+
}
|
|
3104
|
+
const otherSets = remainingCandidateSets.filter(
|
|
3105
|
+
(candidateSet) => candidateSet !== selectedSet,
|
|
3106
|
+
)
|
|
3107
|
+
const candidateBatchSize = Number(
|
|
3108
|
+
process.env.FANOUT_DEBUG_ALTERNATE_CANDIDATE_BATCH_SIZE ??
|
|
3109
|
+
16,
|
|
3110
|
+
)
|
|
3111
|
+
for (
|
|
3112
|
+
let batchStart = 0;
|
|
3113
|
+
batchStart < selectedSet.candidates.length;
|
|
3114
|
+
batchStart += candidateBatchSize
|
|
3115
|
+
) {
|
|
3116
|
+
const actions = selectedSet.candidates
|
|
3117
|
+
.slice(batchStart, batchStart + candidateBatchSize)
|
|
3118
|
+
.map((candidate) => {
|
|
3119
|
+
const projectedSets = otherSets.map((candidateSet) => ({
|
|
3120
|
+
...candidateSet,
|
|
3121
|
+
candidates: candidateSet.candidates.filter(
|
|
3122
|
+
(otherCandidate) =>
|
|
3123
|
+
candidatesAreCompatible(
|
|
3124
|
+
candidate,
|
|
3125
|
+
otherCandidate,
|
|
3126
|
+
),
|
|
3127
|
+
),
|
|
3128
|
+
}))
|
|
3129
|
+
const projectedCounts = projectedSets.map(
|
|
3130
|
+
(candidateSet) => candidateSet.candidates.length,
|
|
3131
|
+
)
|
|
3132
|
+
return {
|
|
3133
|
+
candidate,
|
|
3134
|
+
projectedSets,
|
|
3135
|
+
minimumProjectedCount:
|
|
3136
|
+
projectedCounts.length === 0
|
|
3137
|
+
? Number.POSITIVE_INFINITY
|
|
3138
|
+
: Math.min(...projectedCounts),
|
|
3139
|
+
totalProjectedCount: projectedCounts.reduce(
|
|
3140
|
+
(total, count) => total + count,
|
|
3141
|
+
0,
|
|
3142
|
+
),
|
|
3143
|
+
}
|
|
3144
|
+
})
|
|
3145
|
+
.filter((action) => action.minimumProjectedCount !== 0)
|
|
3146
|
+
.toSorted(
|
|
3147
|
+
(first, second) =>
|
|
3148
|
+
second.minimumProjectedCount -
|
|
3149
|
+
first.minimumProjectedCount ||
|
|
3150
|
+
second.totalProjectedCount -
|
|
3151
|
+
first.totalProjectedCount,
|
|
3152
|
+
)
|
|
3153
|
+
for (const action of actions) {
|
|
3154
|
+
alternatePlaneSearchStates++
|
|
3155
|
+
const selected = selectCompatiblePlaneRoutes(
|
|
3156
|
+
action.projectedSets,
|
|
3157
|
+
[...selectedCandidates, action.candidate],
|
|
3158
|
+
)
|
|
3159
|
+
if (selected) return selected
|
|
3160
|
+
if (
|
|
3161
|
+
alternatePlaneSearchStates >=
|
|
3162
|
+
maximumAlternatePlaneSearchStates
|
|
3163
|
+
) {
|
|
3164
|
+
break
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
if (
|
|
3168
|
+
alternatePlaneSearchStates >=
|
|
3169
|
+
maximumAlternatePlaneSearchStates
|
|
3170
|
+
) {
|
|
3171
|
+
break
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
alternatePlaneFailureCountByBusId.set(
|
|
3175
|
+
selectedSet.planeBus.busId,
|
|
3176
|
+
(alternatePlaneFailureCountByBusId.get(
|
|
3177
|
+
selectedSet.planeBus.busId,
|
|
3178
|
+
) ?? 0) + 1,
|
|
3179
|
+
)
|
|
3180
|
+
return null
|
|
3181
|
+
}
|
|
3182
|
+
const selectedCandidates = selectCompatiblePlaneRoutes(
|
|
3183
|
+
candidateSets,
|
|
3184
|
+
[],
|
|
3185
|
+
)
|
|
3186
|
+
alternatePlanePlans = selectedCandidates
|
|
3187
|
+
? [
|
|
3188
|
+
...feasibleAlternatePlanePlans,
|
|
3189
|
+
...selectedCandidates.flatMap(
|
|
3190
|
+
(candidate) => candidate.plans,
|
|
3191
|
+
),
|
|
3192
|
+
]
|
|
3193
|
+
: null
|
|
3194
|
+
} else {
|
|
3195
|
+
alternatePlanePlans = routeAlternatePlaneBuses(
|
|
3196
|
+
orderedAlternatePlaneBuses,
|
|
3197
|
+
feasibleAlternatePlanePlans,
|
|
3198
|
+
)
|
|
3199
|
+
}
|
|
3200
|
+
debugDense(
|
|
3201
|
+
"plane-route:alternate-search",
|
|
3202
|
+
alternatePlanePlans ? "complete" : "failed",
|
|
3203
|
+
alternatePlaneSearchStates,
|
|
3204
|
+
`depth:${deepestAlternatePlaneSearchIndex}/${orderedAlternatePlaneBuses.length}`,
|
|
3205
|
+
[...alternatePlaneFailureCountByBusId].toSorted(
|
|
3206
|
+
([, first], [, second]) => second - first,
|
|
3207
|
+
),
|
|
3208
|
+
)
|
|
3209
|
+
if (!alternatePlanePlans) return null
|
|
3210
|
+
feasibleAlternatePlanePlans = alternatePlanePlans
|
|
3211
|
+
}
|
|
3212
|
+
let planeBusIdsRoutedWithoutDogbones = new Set<string>()
|
|
3213
|
+
let allBlockingSegments = [...blockingSegments]
|
|
3214
|
+
let alternateBlockingVias: {
|
|
3215
|
+
connectionIndex: number
|
|
3216
|
+
center: { x: number; y: number }
|
|
3217
|
+
diameter: number
|
|
3218
|
+
spanLayers: readonly string[]
|
|
3219
|
+
}[] = []
|
|
3220
|
+
let planeBusesToMatch = [...planeBuses]
|
|
3221
|
+
let candidateCountByConnectionIndex = new Map<number, number>()
|
|
3222
|
+
let candidatePointsByConnectionIndex = new Map<
|
|
3223
|
+
number,
|
|
3224
|
+
{ x: number; y: number }[]
|
|
3225
|
+
>()
|
|
3226
|
+
const refreshPlaneDogboneCandidates = (): void => {
|
|
3227
|
+
planeBusIdsRoutedWithoutDogbones = new Set(
|
|
3228
|
+
feasibleAlternatePlanePlans.map((plan) => plan.busId),
|
|
3229
|
+
)
|
|
3230
|
+
const alternateBlockingSegments =
|
|
3231
|
+
feasibleAlternatePlanePlans.flatMap((plan) =>
|
|
3232
|
+
[...plan.segments, ...(plan.planeEndpointSegments ?? [])].map(
|
|
3233
|
+
(segment) => ({
|
|
3234
|
+
connectionIndex: plan.connectionIndex,
|
|
3235
|
+
segment,
|
|
3236
|
+
}),
|
|
3237
|
+
),
|
|
3238
|
+
)
|
|
3239
|
+
allBlockingSegments = [
|
|
3240
|
+
...blockingSegments,
|
|
3241
|
+
...alternateBlockingSegments,
|
|
3242
|
+
]
|
|
3243
|
+
alternateBlockingVias = feasibleAlternatePlanePlans.flatMap(
|
|
3244
|
+
(plan) =>
|
|
3245
|
+
[
|
|
3246
|
+
plan.via,
|
|
3247
|
+
...(plan.additionalVias ?? []),
|
|
3248
|
+
plan.planeEndpointVia,
|
|
3249
|
+
].flatMap((via) =>
|
|
3250
|
+
via
|
|
3251
|
+
? [
|
|
3252
|
+
{
|
|
3253
|
+
connectionIndex: plan.connectionIndex,
|
|
3254
|
+
center: via.center,
|
|
3255
|
+
diameter: via.diameter,
|
|
3256
|
+
spanLayers: via.spanLayers,
|
|
3257
|
+
},
|
|
3258
|
+
]
|
|
3259
|
+
: [],
|
|
3260
|
+
),
|
|
3261
|
+
)
|
|
3262
|
+
planeBusesToMatch = planeBuses.filter(
|
|
3263
|
+
(bus) => !planeBusIdsRoutedWithoutDogbones.has(bus.busId),
|
|
3264
|
+
)
|
|
3265
|
+
candidateCountByConnectionIndex = new Map()
|
|
3266
|
+
candidatePointsByConnectionIndex = new Map()
|
|
3267
|
+
for (const candidate of getComponentDogboneViaSiteCandidates(
|
|
3268
|
+
planeBusesToMatch,
|
|
3269
|
+
{
|
|
3270
|
+
viaDiameter: this.config.viaDiameter,
|
|
3271
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
3272
|
+
traceWidth: this.config.traceWidth,
|
|
3273
|
+
clearance: this.config.clearance,
|
|
3274
|
+
blockingSegments: allBlockingSegments,
|
|
3275
|
+
blockingVias: alternateBlockingVias,
|
|
3276
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
3277
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
3278
|
+
canShareCopper,
|
|
3279
|
+
},
|
|
3280
|
+
)) {
|
|
3281
|
+
candidateCountByConnectionIndex.set(
|
|
3282
|
+
candidate.connectionIndex,
|
|
3283
|
+
(candidateCountByConnectionIndex.get(
|
|
3284
|
+
candidate.connectionIndex,
|
|
3285
|
+
) ?? 0) + 1,
|
|
3286
|
+
)
|
|
3287
|
+
const points =
|
|
3288
|
+
candidatePointsByConnectionIndex.get(
|
|
3289
|
+
candidate.connectionIndex,
|
|
3290
|
+
) ?? []
|
|
3291
|
+
points.push(candidate.point)
|
|
3292
|
+
candidatePointsByConnectionIndex.set(
|
|
3293
|
+
candidate.connectionIndex,
|
|
3294
|
+
points,
|
|
3295
|
+
)
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
refreshPlaneDogboneCandidates()
|
|
3299
|
+
for (
|
|
3300
|
+
let promotionPass = 0;
|
|
3301
|
+
promotionPass < planeBuses.length;
|
|
3302
|
+
promotionPass++
|
|
3303
|
+
) {
|
|
3304
|
+
const zeroCandidatePlaneBuses = planeBusesToMatch.filter(
|
|
3305
|
+
(bus) =>
|
|
3306
|
+
!candidateCountByConnectionIndex.has(
|
|
3307
|
+
bus.connections[0]!.connectionIndex,
|
|
3308
|
+
),
|
|
3309
|
+
)
|
|
3310
|
+
if (zeroCandidatePlaneBuses.length === 0) break
|
|
3311
|
+
debugDense(
|
|
3312
|
+
"plane-route:promote-zero-candidates",
|
|
3313
|
+
zeroCandidatePlaneBuses.map((bus) => bus.busId),
|
|
3314
|
+
)
|
|
3315
|
+
if (
|
|
3316
|
+
zeroCandidatePlaneBuses.some((bus) =>
|
|
3317
|
+
matchedPlaneBuses.includes(bus),
|
|
3318
|
+
)
|
|
3319
|
+
) {
|
|
3320
|
+
return null
|
|
3321
|
+
}
|
|
3322
|
+
for (const planeBus of zeroCandidatePlaneBuses) {
|
|
3323
|
+
const targetLayer = params.busLayerAssignments[planeBus.busId]
|
|
3324
|
+
if (!targetLayer) return null
|
|
3325
|
+
const promotedPlans = routeBusAlternatives(
|
|
3326
|
+
{
|
|
3327
|
+
srj: this.routingSrj,
|
|
3328
|
+
bus: planeBus,
|
|
3329
|
+
targetLayer,
|
|
3330
|
+
acceptedPlans: [
|
|
3331
|
+
...candidatePlans,
|
|
3332
|
+
...feasibleAlternatePlanePlans,
|
|
3333
|
+
],
|
|
3334
|
+
layerNames: this.config.layerNames,
|
|
3335
|
+
traceWidth: this.config.traceWidth,
|
|
3336
|
+
viaDiameter: this.config.viaDiameter,
|
|
3337
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
3338
|
+
clearance: this.config.clearance,
|
|
3339
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
3340
|
+
allowBlindAndBuriedVias: false,
|
|
3341
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
3342
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
3343
|
+
},
|
|
3344
|
+
8,
|
|
3345
|
+
)[0]
|
|
3346
|
+
if (!promotedPlans) {
|
|
3347
|
+
debugDense("plane-route:promote-failed", planeBus.busId)
|
|
3348
|
+
return null
|
|
3349
|
+
}
|
|
3350
|
+
feasibleAlternatePlanePlans.push(...promotedPlans)
|
|
3351
|
+
}
|
|
3352
|
+
refreshPlaneDogboneCandidates()
|
|
3353
|
+
}
|
|
3354
|
+
const debugPlaneMatchOrder =
|
|
3355
|
+
process.env.FANOUT_DEBUG_PLANE_MATCH_ORDER?.split(",") ?? []
|
|
3356
|
+
const incrementalPlaneBuses = planeBusesToMatch.toSorted(
|
|
3357
|
+
(first, second) => {
|
|
3358
|
+
const candidateCountDifference =
|
|
3359
|
+
(candidateCountByConnectionIndex.get(
|
|
3360
|
+
first.connections[0]!.connectionIndex,
|
|
3361
|
+
) ?? 0) -
|
|
3362
|
+
(candidateCountByConnectionIndex.get(
|
|
3363
|
+
second.connections[0]!.connectionIndex,
|
|
3364
|
+
) ?? 0)
|
|
3365
|
+
if (candidateCountDifference !== 0) {
|
|
3366
|
+
return candidateCountDifference
|
|
3367
|
+
}
|
|
3368
|
+
const firstPriority = debugPlaneMatchOrder.indexOf(first.busId)
|
|
3369
|
+
const secondPriority = debugPlaneMatchOrder.indexOf(
|
|
3370
|
+
second.busId,
|
|
3371
|
+
)
|
|
3372
|
+
const priorityDifference =
|
|
3373
|
+
(firstPriority < 0
|
|
3374
|
+
? debugPlaneMatchOrder.length
|
|
3375
|
+
: firstPriority) -
|
|
3376
|
+
(secondPriority < 0
|
|
3377
|
+
? debugPlaneMatchOrder.length
|
|
3378
|
+
: secondPriority)
|
|
3379
|
+
if (priorityDifference !== 0) return priorityDifference
|
|
3380
|
+
return (
|
|
3381
|
+
first.connections[0]!.connectionIndex -
|
|
3382
|
+
second.connections[0]!.connectionIndex
|
|
3383
|
+
)
|
|
3384
|
+
},
|
|
3385
|
+
)
|
|
3386
|
+
for (const planeBus of incrementalPlaneBuses) {
|
|
3387
|
+
if (matchedPlaneBuses.includes(planeBus)) continue
|
|
3388
|
+
if (
|
|
3389
|
+
process.env.FANOUT_DEBUG_PLANE_CANDIDATES?.split(",").includes(
|
|
3390
|
+
planeBus.busId,
|
|
3391
|
+
)
|
|
3392
|
+
) {
|
|
3393
|
+
debugDense(
|
|
3394
|
+
"plane-match:candidates",
|
|
3395
|
+
planeBus.busId,
|
|
3396
|
+
candidatePointsByConnectionIndex.get(
|
|
3397
|
+
planeBus.connections[0]!.connectionIndex,
|
|
3398
|
+
),
|
|
3399
|
+
)
|
|
3400
|
+
}
|
|
3401
|
+
const nextViaPoints = matchComponentDogboneViaSites(
|
|
3402
|
+
[...matchedPlaneBuses, planeBus, ...boundaryBusesToMatch],
|
|
3403
|
+
{
|
|
3404
|
+
viaDiameter: this.config.viaDiameter,
|
|
3405
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
3406
|
+
traceWidth: this.config.traceWidth,
|
|
3407
|
+
clearance: this.config.clearance,
|
|
3408
|
+
maximumSearchStates: 100_000,
|
|
3409
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
3410
|
+
preferBoundaryOutwardByBusId,
|
|
3411
|
+
fixedViaPointsByConnectionIndex: incrementalViaPoints,
|
|
3412
|
+
blockingSegments: allBlockingSegments,
|
|
3413
|
+
blockingVias: [...blockingVias, ...alternateBlockingVias],
|
|
3414
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
3415
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
3416
|
+
canShareCopper,
|
|
3417
|
+
},
|
|
3418
|
+
)
|
|
3419
|
+
debugDense(
|
|
3420
|
+
nextViaPoints
|
|
3421
|
+
? "plane-match:incremental"
|
|
3422
|
+
: "plane-match:incremental-failed",
|
|
3423
|
+
planeBus.busId,
|
|
3424
|
+
candidateCountByConnectionIndex.get(
|
|
3425
|
+
planeBus.connections[0]!.connectionIndex,
|
|
3426
|
+
) ?? 0,
|
|
3427
|
+
nextViaPoints?.get(planeBus.connections[0]!.connectionIndex),
|
|
3428
|
+
nextViaPoints?.size ?? "failed",
|
|
3429
|
+
)
|
|
3430
|
+
if (!nextViaPoints) return null
|
|
3431
|
+
incrementalViaPoints = new Map([
|
|
3432
|
+
...incrementalViaPoints,
|
|
3433
|
+
...nextViaPoints,
|
|
3434
|
+
])
|
|
3435
|
+
matchedPlaneBuses.push(planeBus)
|
|
3436
|
+
}
|
|
3437
|
+
matchedPlaneBusesInRoutingOrder = matchedPlaneBuses.filter(
|
|
3438
|
+
(bus) => !planeBusIdsRoutedWithoutDogbones.has(bus.busId),
|
|
3439
|
+
)
|
|
3440
|
+
debugDense(
|
|
3441
|
+
"plane-match:incremental-complete",
|
|
3442
|
+
incrementalViaPoints.size,
|
|
3443
|
+
)
|
|
3444
|
+
return incrementalViaPoints
|
|
3445
|
+
}
|
|
3446
|
+
const planeViaPoints = matchComponentDogboneViaSites(
|
|
3447
|
+
[...planeBuses, ...boundaryBusesToMatch],
|
|
2013
3448
|
{
|
|
2014
3449
|
viaDiameter: this.config.viaDiameter,
|
|
2015
3450
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
@@ -2019,16 +3454,18 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2019
3454
|
preferredBoundaryPerpendicularSideByBusId,
|
|
2020
3455
|
preferBoundaryOutwardByBusId,
|
|
2021
3456
|
fixedViaPointsByConnectionIndex: fixedBoundaryViaPoints,
|
|
2022
|
-
blockingSegments
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
})),
|
|
2027
|
-
),
|
|
3457
|
+
blockingSegments,
|
|
3458
|
+
blockingVias,
|
|
3459
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
3460
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
2028
3461
|
canShareCopper,
|
|
2029
3462
|
},
|
|
2030
3463
|
)
|
|
3464
|
+
return planeViaPoints
|
|
3465
|
+
? new Map([...fixedBoundaryViaPoints, ...planeViaPoints])
|
|
3466
|
+
: null
|
|
2031
3467
|
}
|
|
3468
|
+
debugDense("length-match:start", matchedPlans.length)
|
|
2032
3469
|
const matchedLengthResult = matchBusPlanLengths({
|
|
2033
3470
|
plans: matchedPlans,
|
|
2034
3471
|
preparedBuses: this.preparedBuses,
|
|
@@ -2045,6 +3482,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2045
3482
|
return true
|
|
2046
3483
|
},
|
|
2047
3484
|
})
|
|
3485
|
+
debugDense(
|
|
3486
|
+
"length-match:complete",
|
|
3487
|
+
matchedLengthResult.plans?.length ?? "failed",
|
|
3488
|
+
)
|
|
2048
3489
|
this.setInProgressPlans({
|
|
2049
3490
|
phase: "match-dense-boundary-lengths",
|
|
2050
3491
|
plans: matchedLengthResult.plans ?? matchedPlans,
|
|
@@ -2059,6 +3500,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2059
3500
|
feasibleViaPoints ?? matchViaPointsAroundPlans(matchedPlans)
|
|
2060
3501
|
if (rematchedViaPoints) {
|
|
2061
3502
|
fixedViaPointsByConnectionIndex = rematchedViaPoints
|
|
3503
|
+
matchedPlans.push(...feasibleAlternatePlanePlans)
|
|
2062
3504
|
} else {
|
|
2063
3505
|
matchedRoutingSucceeded = false
|
|
2064
3506
|
}
|
|
@@ -2075,8 +3517,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2075
3517
|
yield
|
|
2076
3518
|
}
|
|
2077
3519
|
if (matchedRoutingSucceeded) {
|
|
2078
|
-
for (const bus of planeBuses) {
|
|
3520
|
+
for (const bus of matchedPlaneBusesInRoutingOrder ?? planeBuses) {
|
|
3521
|
+
debugDense("plane-route:start", bus.busId)
|
|
2079
3522
|
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
3523
|
+
const blockingBusCounts = new Map<string, number>()
|
|
2080
3524
|
const busPlans = targetLayer
|
|
2081
3525
|
? routeBus({
|
|
2082
3526
|
srj: this.routingSrj,
|
|
@@ -2092,14 +3536,24 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2092
3536
|
allowBlindAndBuriedVias: false,
|
|
2093
3537
|
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
2094
3538
|
staticClearanceCache: this.routeStaticClearanceCache,
|
|
3539
|
+
blockingBusCounts,
|
|
2095
3540
|
fixedViaPointsByConnectionIndex,
|
|
2096
3541
|
})
|
|
2097
3542
|
: null
|
|
2098
3543
|
if (!busPlans) {
|
|
3544
|
+
debugDense(
|
|
3545
|
+
"plane-route:failed",
|
|
3546
|
+
bus.busId,
|
|
3547
|
+
fixedViaPointsByConnectionIndex.get(
|
|
3548
|
+
bus.connections[0]!.connectionIndex,
|
|
3549
|
+
),
|
|
3550
|
+
[...blockingBusCounts],
|
|
3551
|
+
)
|
|
2099
3552
|
matchedRoutingSucceeded = false
|
|
2100
3553
|
break
|
|
2101
3554
|
}
|
|
2102
3555
|
matchedPlans.push(...busPlans)
|
|
3556
|
+
debugDense("plane-route:complete", bus.busId)
|
|
2103
3557
|
this.setInProgressPlans({
|
|
2104
3558
|
phase: "route-dense-plane-buses",
|
|
2105
3559
|
plans: matchedPlans,
|
|
@@ -2111,7 +3565,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2111
3565
|
yield
|
|
2112
3566
|
}
|
|
2113
3567
|
}
|
|
2114
|
-
|
|
3568
|
+
const densePlansAreClear =
|
|
2115
3569
|
matchedRoutingSucceeded &&
|
|
2116
3570
|
fanoutPlansAreClear({
|
|
2117
3571
|
plans: matchedPlans,
|
|
@@ -2121,11 +3575,19 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2121
3575
|
allowBlindAndBuriedVias: false,
|
|
2122
3576
|
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
2123
3577
|
})
|
|
2124
|
-
|
|
3578
|
+
debugDense(
|
|
3579
|
+
"dense-validation",
|
|
3580
|
+
matchedRoutingSucceeded,
|
|
3581
|
+
matchedPlans.length,
|
|
3582
|
+
densePlansAreClear,
|
|
3583
|
+
)
|
|
3584
|
+
if (densePlansAreClear) {
|
|
2125
3585
|
return { plans: matchedPlans, failedBusIds: [] }
|
|
2126
3586
|
}
|
|
2127
3587
|
}
|
|
2128
3588
|
|
|
3589
|
+
if (process.env.FANOUT_DEBUG_DENSE_ONLY === "1") return null
|
|
3590
|
+
|
|
2129
3591
|
const maximumStates = 8
|
|
2130
3592
|
const getBoundaryStates = (
|
|
2131
3593
|
alternativesPerBoundaryBus: number,
|
|
@@ -2599,6 +4061,17 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2599
4061
|
solver: denseSolver,
|
|
2600
4062
|
}) as MixedTerminationState | null
|
|
2601
4063
|
}
|
|
4064
|
+
if (
|
|
4065
|
+
!mixedTerminationState &&
|
|
4066
|
+
!useSingleLayerPushAndShove &&
|
|
4067
|
+
routingStrategy === "default" &&
|
|
4068
|
+
process.env.FANOUT_DEBUG_DENSE_ONLY === "1"
|
|
4069
|
+
) {
|
|
4070
|
+
mixedTerminationState = {
|
|
4071
|
+
plans: [],
|
|
4072
|
+
failedBusIds: this.preparedBuses.map((bus) => bus.busId),
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
2602
4075
|
|
|
2603
4076
|
if (mixedTerminationState) {
|
|
2604
4077
|
plans = mixedTerminationState.plans
|
|
@@ -2786,6 +4259,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2786
4259
|
busLayerAssignments,
|
|
2787
4260
|
"default",
|
|
2788
4261
|
)
|
|
4262
|
+
if (process.env.FANOUT_DEBUG_DENSE_ONLY === "1") return bestAttempt
|
|
2789
4263
|
if (
|
|
2790
4264
|
bestAttempt.summary.routedConnectionCount ===
|
|
2791
4265
|
this.inputSrj.connections.length &&
|