@tscircuit/fanout-solver 0.0.49 → 0.0.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/fanout-solver.ts +1422 -139
- package/lib/match-component-dogbone-via-sites.ts +92 -14
- package/lib/route-bus.ts +101 -44
- 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,40 @@ 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
|
+
const debugFlippedBoundaryBus = process.env.FANOUT_DEBUG_FLIP_BOUNDARY_BUS
|
|
1714
|
+
if (debugFlippedBoundaryBus) {
|
|
1715
|
+
preferredBoundaryPerpendicularSideByBusId.set(debugFlippedBoundaryBus, -1)
|
|
1716
|
+
}
|
|
1717
|
+
const debugOutwardBoundaryBus =
|
|
1718
|
+
process.env.FANOUT_DEBUG_OUTWARD_BOUNDARY_BUS
|
|
1719
|
+
if (debugOutwardBoundaryBus) {
|
|
1720
|
+
preferBoundaryOutwardByBusId.set(debugOutwardBoundaryBus, true)
|
|
1721
|
+
}
|
|
1513
1722
|
const canShareCopper = (
|
|
1514
1723
|
firstConnectionIndex: number,
|
|
1515
1724
|
secondConnectionIndex: number,
|
|
@@ -1545,6 +1754,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1545
1754
|
(bus) => bus.connections.length === 1,
|
|
1546
1755
|
)
|
|
1547
1756
|
const singletonDeferralCandidates =
|
|
1757
|
+
!hasThreeWideBoundaryBuses &&
|
|
1548
1758
|
shouldDeferSingletonBoundaryViaReservation(boundaryBusConnectionCounts)
|
|
1549
1759
|
? singletonBoundaryBuses
|
|
1550
1760
|
.toSorted(
|
|
@@ -1559,8 +1769,8 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1559
1769
|
),
|
|
1560
1770
|
)
|
|
1561
1771
|
: []
|
|
1562
|
-
const
|
|
1563
|
-
boundaryBuses.length === 9
|
|
1772
|
+
const multiLayerLeadingSingletonBuses =
|
|
1773
|
+
boundaryBuses.length === 8 || boundaryBuses.length === 9
|
|
1564
1774
|
? singletonDeferralCandidates.filter((singletonBus) => {
|
|
1565
1775
|
const singletonTargetLayer =
|
|
1566
1776
|
params.busLayerAssignments[singletonBus.busId]
|
|
@@ -1574,6 +1784,53 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1574
1784
|
)
|
|
1575
1785
|
})
|
|
1576
1786
|
: []
|
|
1787
|
+
const throughAllLeadingSingletonBuses = hasThreeWideBoundaryBuses
|
|
1788
|
+
? singletonBoundaryBuses.filter((singletonBus) => {
|
|
1789
|
+
const containingWideBus = getContainingWideSourceField(singletonBus)
|
|
1790
|
+
const singletonTargetLayer =
|
|
1791
|
+
params.busLayerAssignments[singletonBus.busId]
|
|
1792
|
+
const containingWideLayers =
|
|
1793
|
+
containingWideBus?.routableEscapeLayers ??
|
|
1794
|
+
containingWideBus?.allowedLayers ??
|
|
1795
|
+
[]
|
|
1796
|
+
return Boolean(
|
|
1797
|
+
containingWideBus &&
|
|
1798
|
+
singletonTargetLayer &&
|
|
1799
|
+
!containingWideLayers.includes(singletonTargetLayer),
|
|
1800
|
+
)
|
|
1801
|
+
})
|
|
1802
|
+
: []
|
|
1803
|
+
const throughAllLeadingCompanionBuses =
|
|
1804
|
+
throughAllLeadingSingletonBuses.flatMap((singletonBus) => {
|
|
1805
|
+
const containingWideBus = getContainingWideSourceField(singletonBus)
|
|
1806
|
+
const singletonTargetLayer =
|
|
1807
|
+
params.busLayerAssignments[singletonBus.busId]
|
|
1808
|
+
return boundaryBuses.filter(
|
|
1809
|
+
(candidate) =>
|
|
1810
|
+
candidate.connections.length > 1 &&
|
|
1811
|
+
candidate.connections.length < 8 &&
|
|
1812
|
+
candidate.direction === singletonBus.direction &&
|
|
1813
|
+
params.busLayerAssignments[candidate.busId] ===
|
|
1814
|
+
singletonTargetLayer &&
|
|
1815
|
+
getContainingWideSourceField(candidate) === containingWideBus,
|
|
1816
|
+
)
|
|
1817
|
+
})
|
|
1818
|
+
const throughAllLeadingBuses =
|
|
1819
|
+
process.env.FANOUT_DEBUG_DISABLE_LEADING_NARROW === "1"
|
|
1820
|
+
? []
|
|
1821
|
+
: throughAllLeadingSingletonBuses.flatMap((singletonBus) => [
|
|
1822
|
+
singletonBus,
|
|
1823
|
+
...throughAllLeadingCompanionBuses.filter(
|
|
1824
|
+
(candidate) =>
|
|
1825
|
+
candidate.direction === singletonBus.direction &&
|
|
1826
|
+
params.busLayerAssignments[candidate.busId] ===
|
|
1827
|
+
params.busLayerAssignments[singletonBus.busId],
|
|
1828
|
+
),
|
|
1829
|
+
])
|
|
1830
|
+
const leadingWideSingletonBuses = [
|
|
1831
|
+
...multiLayerLeadingSingletonBuses,
|
|
1832
|
+
...throughAllLeadingBuses,
|
|
1833
|
+
].filter((bus, index, buses) => buses.indexOf(bus) === index)
|
|
1577
1834
|
const leadingLaneCountByWideCornerBand = new Map<string, number>()
|
|
1578
1835
|
if (boundaryBuses.length === 9 && leadingWideSingletonBuses.length > 0) {
|
|
1579
1836
|
for (const bus of leadingWideSingletonBuses) {
|
|
@@ -1598,11 +1855,22 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1598
1855
|
)
|
|
1599
1856
|
}
|
|
1600
1857
|
}
|
|
1601
|
-
const
|
|
1602
|
-
|
|
1858
|
+
const viaProvisionalBoundaryBusSet = new Set([
|
|
1859
|
+
...(process.env.FANOUT_DEBUG_PROVISIONAL_NARROW === "1"
|
|
1860
|
+
? boundaryBuses.filter((bus) => bus.connections.length < 8)
|
|
1861
|
+
: []),
|
|
1862
|
+
...singletonDeferralCandidates.filter(
|
|
1603
1863
|
(bus) => !leadingWideSingletonBuses.includes(bus),
|
|
1604
1864
|
),
|
|
1605
|
-
|
|
1865
|
+
...(hasThreeWideBoundaryBuses
|
|
1866
|
+
? boundaryBuses.filter(
|
|
1867
|
+
(bus) =>
|
|
1868
|
+
bus.connections.length === 2 &&
|
|
1869
|
+
!narrowBusOverlapsWideTargetSpan(bus) &&
|
|
1870
|
+
!leadingWideSingletonBuses.includes(bus),
|
|
1871
|
+
)
|
|
1872
|
+
: []),
|
|
1873
|
+
])
|
|
1606
1874
|
const getCornerBandTargetTrackOffset = (bus: PreparedBus): number => {
|
|
1607
1875
|
const side = getCornerBandSide(bus.exitEdge, bus.preferredExit)
|
|
1608
1876
|
if (!bus.exitEdge || !side) return 0
|
|
@@ -1616,11 +1884,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1616
1884
|
})
|
|
1617
1885
|
}
|
|
1618
1886
|
const initiallyMatchedBoundaryBuses = boundaryBuses.filter(
|
|
1619
|
-
(bus) => !
|
|
1887
|
+
(bus) => !viaProvisionalBoundaryBusSet.has(bus),
|
|
1620
1888
|
)
|
|
1621
1889
|
const jointViaPoints = useJointBoundaryViaReservation
|
|
1622
1890
|
? matchComponentDogboneViaSites(
|
|
1623
|
-
[
|
|
1891
|
+
[
|
|
1892
|
+
...activeBoundaryReservationPlaneBuses,
|
|
1893
|
+
...initiallyMatchedBoundaryBuses,
|
|
1894
|
+
],
|
|
1624
1895
|
{
|
|
1625
1896
|
viaDiameter: this.config.viaDiameter,
|
|
1626
1897
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
@@ -1629,22 +1900,108 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1629
1900
|
maximumSearchStates: 100_000,
|
|
1630
1901
|
preferredBoundaryPerpendicularSideByBusId,
|
|
1631
1902
|
preferBoundaryOutwardByBusId,
|
|
1903
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
1904
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1632
1905
|
canShareCopper,
|
|
1633
1906
|
},
|
|
1634
1907
|
)
|
|
1635
1908
|
: null
|
|
1636
|
-
|
|
1909
|
+
let seedViaPoints =
|
|
1637
1910
|
jointViaPoints ??
|
|
1638
|
-
matchComponentDogboneViaSites(
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1911
|
+
matchComponentDogboneViaSites(
|
|
1912
|
+
[...activeBoundaryReservationPlaneBuses, boundaryBuses[0]!],
|
|
1913
|
+
{
|
|
1914
|
+
viaDiameter: this.config.viaDiameter,
|
|
1915
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1916
|
+
traceWidth: this.config.traceWidth,
|
|
1917
|
+
clearance: this.config.clearance,
|
|
1918
|
+
maximumSearchStates: 20_000,
|
|
1919
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
1920
|
+
preferBoundaryOutwardByBusId,
|
|
1921
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
1922
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1923
|
+
canShareCopper,
|
|
1924
|
+
},
|
|
1925
|
+
)
|
|
1926
|
+
const debugFixedVias = process.env.FANOUT_DEBUG_FIXED_VIAS
|
|
1927
|
+
if (seedViaPoints && debugFixedVias) {
|
|
1928
|
+
seedViaPoints = new Map(seedViaPoints)
|
|
1929
|
+
for (const entry of debugFixedVias.split(",")) {
|
|
1930
|
+
const parts = entry.split(":")
|
|
1931
|
+
const rawY = parts.pop()
|
|
1932
|
+
const rawX = parts.pop()
|
|
1933
|
+
const connectionName = parts.join(":")
|
|
1934
|
+
const connectionIndex = [...connectionNameByIndex].find(
|
|
1935
|
+
([, name]) => name === connectionName,
|
|
1936
|
+
)?.[0]
|
|
1937
|
+
const x = Number(rawX)
|
|
1938
|
+
const y = Number(rawY)
|
|
1939
|
+
if (
|
|
1940
|
+
connectionIndex !== undefined &&
|
|
1941
|
+
Number.isFinite(x) &&
|
|
1942
|
+
Number.isFinite(y)
|
|
1943
|
+
) {
|
|
1944
|
+
seedViaPoints.set(connectionIndex, { x, y })
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
const debugStagedPlaneBuses = process.env.FANOUT_DEBUG_STAGED_PLANE_BUS_IDS
|
|
1949
|
+
? planeBuses.filter((bus) =>
|
|
1950
|
+
process.env
|
|
1951
|
+
.FANOUT_DEBUG_STAGED_PLANE_BUS_IDS!.split(",")
|
|
1952
|
+
.includes(bus.busId),
|
|
1953
|
+
)
|
|
1954
|
+
: (process.env.FANOUT_DEBUG_STAGED_PLANE_INDICES?.split(",").flatMap(
|
|
1955
|
+
(index) =>
|
|
1956
|
+
planeBuses[Number(index) - 1]
|
|
1957
|
+
? [planeBuses[Number(index) - 1]!]
|
|
1958
|
+
: [],
|
|
1959
|
+
) ?? [])
|
|
1960
|
+
if (seedViaPoints && debugStagedPlaneBuses.length > 0) {
|
|
1961
|
+
for (const stagedPlaneBus of debugStagedPlaneBuses) {
|
|
1962
|
+
if (activeBoundaryReservationPlaneBuses.includes(stagedPlaneBus)) {
|
|
1963
|
+
continue
|
|
1964
|
+
}
|
|
1965
|
+
const stagedViaPoints = matchComponentDogboneViaSites(
|
|
1966
|
+
[
|
|
1967
|
+
...activeBoundaryReservationPlaneBuses,
|
|
1968
|
+
stagedPlaneBus,
|
|
1969
|
+
...initiallyMatchedBoundaryBuses,
|
|
1970
|
+
],
|
|
1971
|
+
{
|
|
1972
|
+
viaDiameter: this.config.viaDiameter,
|
|
1973
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1974
|
+
traceWidth: this.config.traceWidth,
|
|
1975
|
+
clearance: this.config.clearance,
|
|
1976
|
+
maximumSearchStates: 100_000,
|
|
1977
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
1978
|
+
preferBoundaryOutwardByBusId,
|
|
1979
|
+
fixedViaPointsByConnectionIndex: seedViaPoints,
|
|
1980
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
1981
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1982
|
+
canShareCopper,
|
|
1983
|
+
},
|
|
1984
|
+
)
|
|
1985
|
+
debugDense(
|
|
1986
|
+
"plane-reservation:staged",
|
|
1987
|
+
stagedPlaneBus.busId,
|
|
1988
|
+
stagedViaPoints?.size ?? "failed",
|
|
1989
|
+
)
|
|
1990
|
+
if (!stagedViaPoints) break
|
|
1991
|
+
seedViaPoints = new Map([...seedViaPoints, ...stagedViaPoints])
|
|
1992
|
+
activeBoundaryReservationPlaneBuses.push(stagedPlaneBus)
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
debugDense("seed", seedViaPoints?.size ?? "failed")
|
|
1996
|
+
if (seedViaPoints && process.env.FANOUT_DEBUG_DENSE_POINTS === "1") {
|
|
1997
|
+
console.error(
|
|
1998
|
+
"dense: seed-points",
|
|
1999
|
+
[...seedViaPoints].map(([connectionIndex, point]) => ({
|
|
2000
|
+
connection: connectionNameByIndex.get(connectionIndex),
|
|
2001
|
+
point,
|
|
2002
|
+
})),
|
|
2003
|
+
)
|
|
2004
|
+
}
|
|
1648
2005
|
let denseWorkUnitIndex = 1
|
|
1649
2006
|
const denseWorkUnitCount = boundaryBuses.length + planeBuses.length + 3
|
|
1650
2007
|
this.setInProgressPlans({
|
|
@@ -1657,10 +2014,19 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1657
2014
|
yield
|
|
1658
2015
|
if (seedViaPoints) {
|
|
1659
2016
|
const denseBoundaryBusesInRoutingOrder = [
|
|
1660
|
-
...
|
|
1661
|
-
...boundaryBuses
|
|
1662
|
-
|
|
1663
|
-
|
|
2017
|
+
...multiLayerLeadingSingletonBuses,
|
|
2018
|
+
...boundaryBuses
|
|
2019
|
+
.filter(
|
|
2020
|
+
(bus) =>
|
|
2021
|
+
!multiLayerLeadingSingletonBuses.includes(bus) &&
|
|
2022
|
+
!throughAllLeadingBuses.includes(bus),
|
|
2023
|
+
)
|
|
2024
|
+
.flatMap((bus) => [
|
|
2025
|
+
...throughAllLeadingBuses.filter(
|
|
2026
|
+
(candidate) => getContainingWideSourceField(candidate) === bus,
|
|
2027
|
+
),
|
|
2028
|
+
bus,
|
|
2029
|
+
]),
|
|
1664
2030
|
]
|
|
1665
2031
|
let fixedViaPointsByConnectionIndex: ReadonlyMap<
|
|
1666
2032
|
number,
|
|
@@ -1704,6 +2070,20 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1704
2070
|
this: FanoutSolver,
|
|
1705
2071
|
bus: PreparedBus,
|
|
1706
2072
|
): Generator<FanoutWorkYield, boolean, unknown> {
|
|
2073
|
+
debugDense("route:start", bus.busId, matchedPlans.length)
|
|
2074
|
+
if (process.env.FANOUT_DEBUG_DENSE_POINTS === "1") {
|
|
2075
|
+
console.error(
|
|
2076
|
+
"dense: points",
|
|
2077
|
+
bus.busId,
|
|
2078
|
+
bus.connections.map((connection) => ({
|
|
2079
|
+
source: connection.sourcePoint,
|
|
2080
|
+
via: fixedViaPointsByConnectionIndex.get(
|
|
2081
|
+
connection.connectionIndex,
|
|
2082
|
+
),
|
|
2083
|
+
target: connection.exitTargetPoint ?? connection.targetPoint,
|
|
2084
|
+
})),
|
|
2085
|
+
)
|
|
2086
|
+
}
|
|
1707
2087
|
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
1708
2088
|
if (!targetLayer) {
|
|
1709
2089
|
return false
|
|
@@ -1724,18 +2104,22 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1724
2104
|
staticClearanceCache: this.routeStaticClearanceCache,
|
|
1725
2105
|
fixedViaPointsByConnectionIndex,
|
|
1726
2106
|
reservedVias: getReservedVias(bus),
|
|
1727
|
-
viaMinimalOnly:
|
|
2107
|
+
viaMinimalOnly: process.env.FANOUT_DEBUG_ALLOW_EXTRA_VIAS !== "1",
|
|
2108
|
+
fixedViaFallbackRouteOrderAttempts: useConfiguredDensePlaneRouting
|
|
2109
|
+
? 6
|
|
2110
|
+
: 24,
|
|
1728
2111
|
cornerBandTargetTrackOffset: getCornerBandTargetTrackOffset(bus),
|
|
1729
2112
|
} as const
|
|
1730
2113
|
const routeAlternatives = function* (
|
|
1731
2114
|
this: FanoutSolver,
|
|
2115
|
+
candidateRouteParams: Parameters<typeof routeBusAlternativesSteps>[0],
|
|
1732
2116
|
maximumAlternatives: number,
|
|
1733
2117
|
): Generator<FanoutWorkYield, FanoutRoutePlan[][], unknown> {
|
|
1734
2118
|
this.activeRoutingVisualization = null
|
|
1735
2119
|
const solver = this.createWorkSolver(
|
|
1736
2120
|
"BoundaryBusRoutingSolver",
|
|
1737
2121
|
this.routeBusAlternativesWorkSteps(
|
|
1738
|
-
|
|
2122
|
+
candidateRouteParams,
|
|
1739
2123
|
maximumAlternatives,
|
|
1740
2124
|
),
|
|
1741
2125
|
undefined,
|
|
@@ -1743,7 +2127,37 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1743
2127
|
)
|
|
1744
2128
|
return (yield { type: "subsolver", solver }) as FanoutRoutePlan[][]
|
|
1745
2129
|
}.bind(this)
|
|
1746
|
-
|
|
2130
|
+
const routableEscapeLayers =
|
|
2131
|
+
bus.routableEscapeLayers ?? bus.allowedLayers ?? []
|
|
2132
|
+
const singleLayerBus = routableEscapeLayers.some(
|
|
2133
|
+
(layer) => layer !== targetLayer,
|
|
2134
|
+
)
|
|
2135
|
+
? { ...bus, routableEscapeLayers: [targetLayer] }
|
|
2136
|
+
: bus
|
|
2137
|
+
const embeddedNarrowBusAlreadyRouted = boundaryBuses.some(
|
|
2138
|
+
(candidate) =>
|
|
2139
|
+
candidate.connections.length < 8 &&
|
|
2140
|
+
getContainingWideSourceField(candidate) === bus &&
|
|
2141
|
+
matchedPlans.some((plan) => plan.busId === candidate.busId),
|
|
2142
|
+
)
|
|
2143
|
+
// In the configured dense-plane mode, a second allowed layer is an
|
|
2144
|
+
// optional winding crossover channel rather than a requirement. Try
|
|
2145
|
+
// the simpler single-layer route first unless an already-routed narrow
|
|
2146
|
+
// bus occupies that direct winding channel. Preserve the released
|
|
2147
|
+
// multi-layer search order for callers that did not opt into this mode.
|
|
2148
|
+
const preferSingleLayerWinding =
|
|
2149
|
+
useConfiguredDensePlaneRouting &&
|
|
2150
|
+
singleLayerBus !== bus &&
|
|
2151
|
+
!embeddedNarrowBusAlreadyRouted
|
|
2152
|
+
let busPlans = (yield* routeAlternatives(
|
|
2153
|
+
preferSingleLayerWinding
|
|
2154
|
+
? { ...routeParams, bus: singleLayerBus }
|
|
2155
|
+
: routeParams,
|
|
2156
|
+
1,
|
|
2157
|
+
))[0]
|
|
2158
|
+
if (!busPlans && preferSingleLayerWinding) {
|
|
2159
|
+
busPlans = (yield* routeAlternatives(routeParams, 1))[0]
|
|
2160
|
+
}
|
|
1747
2161
|
if (busPlans && bus.maxLengthSkew !== undefined) {
|
|
1748
2162
|
const lengths = busPlans.map((plan) => plan.length)
|
|
1749
2163
|
const rawSkew = Math.max(...lengths) - Math.min(...lengths)
|
|
@@ -1758,7 +2172,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1758
2172
|
// skewed that compact meanders are unlikely to absorb the deficit.
|
|
1759
2173
|
// This keeps already-near-matched buses on the single-attempt path.
|
|
1760
2174
|
if (needsRouteDiversity) {
|
|
1761
|
-
busPlans = (yield* routeAlternatives(3)).toSorted(
|
|
2175
|
+
busPlans = (yield* routeAlternatives(routeParams, 3)).toSorted(
|
|
1762
2176
|
(first, second) => {
|
|
1763
2177
|
const firstLengths = first.map((plan) => plan.length)
|
|
1764
2178
|
const secondLengths = second.map((plan) => plan.length)
|
|
@@ -1772,14 +2186,29 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1772
2186
|
}
|
|
1773
2187
|
}
|
|
1774
2188
|
if (!busPlans) {
|
|
2189
|
+
debugDense("route:failed", bus.busId)
|
|
1775
2190
|
return false
|
|
1776
2191
|
}
|
|
1777
2192
|
matchedPlans.push(...busPlans)
|
|
2193
|
+
debugDense("route:complete", bus.busId, busPlans.length)
|
|
1778
2194
|
return true
|
|
1779
2195
|
}.bind(this)
|
|
1780
2196
|
|
|
1781
2197
|
const firstBoundaryBus = denseBoundaryBusesInRoutingOrder[0]!
|
|
1782
2198
|
const routedBoundaryBuses: PreparedBus[] = []
|
|
2199
|
+
const reserveAllPlaneDogbonesAfterFirstWideBus = (
|
|
2200
|
+
bus: PreparedBus,
|
|
2201
|
+
): void => {
|
|
2202
|
+
if (
|
|
2203
|
+
bus.connections.length >= 8 &&
|
|
2204
|
+
!useConfiguredDensePlaneRouting &&
|
|
2205
|
+
process.env.FANOUT_DEBUG_NO_PLANE_EXPANSION !== "1" &&
|
|
2206
|
+
activeBoundaryReservationPlaneBuses.length < planeBuses.length
|
|
2207
|
+
) {
|
|
2208
|
+
activeBoundaryReservationPlaneBuses = planeBuses
|
|
2209
|
+
debugDense("plane-reservations:expanded", planeBuses.length)
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
1783
2212
|
const firstBoundaryBusRouted =
|
|
1784
2213
|
yield* routeMatchedBoundaryBusSteps(firstBoundaryBus)
|
|
1785
2214
|
this.setInProgressPlans({
|
|
@@ -1793,6 +2222,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1793
2222
|
yield
|
|
1794
2223
|
if (firstBoundaryBusRouted) {
|
|
1795
2224
|
routedBoundaryBuses.push(firstBoundaryBus)
|
|
2225
|
+
reserveAllPlaneDogbonesAfterFirstWideBus(firstBoundaryBus)
|
|
1796
2226
|
} else {
|
|
1797
2227
|
matchedRoutingSucceeded = false
|
|
1798
2228
|
}
|
|
@@ -1811,18 +2241,51 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1811
2241
|
candidateIndex++
|
|
1812
2242
|
) {
|
|
1813
2243
|
const candidateBus = remainingBoundaryBuses[candidateIndex]!
|
|
2244
|
+
debugDense("candidate:start", candidateBus.busId)
|
|
2245
|
+
const candidateMatchingBase = new Map(fixedViaPointsByConnectionIndex)
|
|
2246
|
+
const debugLateFixedVias = process.env.FANOUT_DEBUG_LATE_FIXED_VIAS
|
|
2247
|
+
if (debugLateFixedVias) {
|
|
2248
|
+
const candidateConnectionIndices = new Set(
|
|
2249
|
+
candidateBus.connections.map(
|
|
2250
|
+
(connection) => connection.connectionIndex,
|
|
2251
|
+
),
|
|
2252
|
+
)
|
|
2253
|
+
for (const entry of debugLateFixedVias.split(",")) {
|
|
2254
|
+
const parts = entry.split(":")
|
|
2255
|
+
const rawY = parts.pop()
|
|
2256
|
+
const rawX = parts.pop()
|
|
2257
|
+
const connectionName = parts.join(":")
|
|
2258
|
+
const connectionIndex = [...connectionNameByIndex].find(
|
|
2259
|
+
([, name]) => name === connectionName,
|
|
2260
|
+
)?.[0]
|
|
2261
|
+
const x = Number(rawX)
|
|
2262
|
+
const y = Number(rawY)
|
|
2263
|
+
if (
|
|
2264
|
+
connectionIndex !== undefined &&
|
|
2265
|
+
candidateConnectionIndices.has(connectionIndex) &&
|
|
2266
|
+
Number.isFinite(x) &&
|
|
2267
|
+
Number.isFinite(y)
|
|
2268
|
+
) {
|
|
2269
|
+
candidateMatchingBase.set(connectionIndex, { x, y })
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
1814
2273
|
const candidateHasFixedViaPoints = candidateBus.connections.every(
|
|
1815
2274
|
(connection) =>
|
|
1816
|
-
|
|
2275
|
+
candidateMatchingBase.has(connection.connectionIndex),
|
|
1817
2276
|
)
|
|
1818
|
-
const
|
|
2277
|
+
const newlyMatchedViaPoints =
|
|
1819
2278
|
jointViaPoints && candidateHasFixedViaPoints
|
|
1820
2279
|
? // The joint map is deliberately kept intact so getReservedVias()
|
|
1821
2280
|
// blocks every already-reserved future through-barrel during A*.
|
|
1822
2281
|
// Provisional singleton and plane dogbones are rematched later.
|
|
1823
|
-
new Map(
|
|
2282
|
+
new Map(candidateMatchingBase)
|
|
1824
2283
|
: matchComponentDogboneViaSites(
|
|
1825
|
-
[
|
|
2284
|
+
[
|
|
2285
|
+
...activeBoundaryReservationPlaneBuses,
|
|
2286
|
+
...routedBoundaryBuses,
|
|
2287
|
+
candidateBus,
|
|
2288
|
+
],
|
|
1826
2289
|
{
|
|
1827
2290
|
viaDiameter: this.config.viaDiameter,
|
|
1828
2291
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
@@ -1831,24 +2294,22 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1831
2294
|
maximumSearchStates: 100_000,
|
|
1832
2295
|
preferredBoundaryPerpendicularSideByBusId,
|
|
1833
2296
|
preferBoundaryOutwardByBusId,
|
|
1834
|
-
fixedViaPointsByConnectionIndex:
|
|
1835
|
-
fixedViaPointsByConnectionIndex,
|
|
2297
|
+
fixedViaPointsByConnectionIndex: candidateMatchingBase,
|
|
1836
2298
|
blockingSegments,
|
|
2299
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2300
|
+
preferPlaneCheckerboardSites:
|
|
2301
|
+
useConfiguredDensePlaneRouting,
|
|
1837
2302
|
canShareCopper,
|
|
1838
2303
|
},
|
|
1839
2304
|
)
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
})
|
|
1849
|
-
yield
|
|
1850
|
-
continue
|
|
1851
|
-
}
|
|
2305
|
+
const extendedViaPoints = newlyMatchedViaPoints
|
|
2306
|
+
? new Map([...candidateMatchingBase, ...newlyMatchedViaPoints])
|
|
2307
|
+
: null
|
|
2308
|
+
debugDense(
|
|
2309
|
+
"candidate:matched",
|
|
2310
|
+
candidateBus.busId,
|
|
2311
|
+
extendedViaPoints?.size ?? "failed",
|
|
2312
|
+
)
|
|
1852
2313
|
this.setInProgressPlans({
|
|
1853
2314
|
phase: "reserve-next-dense-boundary-bus",
|
|
1854
2315
|
plans: matchedPlans,
|
|
@@ -1858,6 +2319,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1858
2319
|
busId: candidateBus.busId,
|
|
1859
2320
|
})
|
|
1860
2321
|
yield
|
|
2322
|
+
if (!extendedViaPoints) continue
|
|
1861
2323
|
const previousFixedViaPoints = fixedViaPointsByConnectionIndex
|
|
1862
2324
|
const previousPlanCount = matchedPlans.length
|
|
1863
2325
|
const laterBuses = remainingBoundaryBuses.filter(
|
|
@@ -1870,7 +2332,12 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1870
2332
|
if (laterBuses.length === 1) {
|
|
1871
2333
|
const laterBus = laterBuses[0]!
|
|
1872
2334
|
const futureAssignment = matchComponentDogboneViaSites(
|
|
1873
|
-
[
|
|
2335
|
+
[
|
|
2336
|
+
...activeBoundaryReservationPlaneBuses,
|
|
2337
|
+
...routedBoundaryBuses,
|
|
2338
|
+
candidateBus,
|
|
2339
|
+
laterBus,
|
|
2340
|
+
],
|
|
1874
2341
|
{
|
|
1875
2342
|
viaDiameter: this.config.viaDiameter,
|
|
1876
2343
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
@@ -1881,9 +2348,16 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1881
2348
|
preferBoundaryOutwardByBusId,
|
|
1882
2349
|
fixedViaPointsByConnectionIndex: extendedViaPoints,
|
|
1883
2350
|
blockingSegments,
|
|
2351
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2352
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1884
2353
|
canShareCopper,
|
|
1885
2354
|
},
|
|
1886
2355
|
)
|
|
2356
|
+
debugDense(
|
|
2357
|
+
"future:matched",
|
|
2358
|
+
laterBus.busId,
|
|
2359
|
+
futureAssignment?.size ?? "failed",
|
|
2360
|
+
)
|
|
1887
2361
|
if (futureAssignment) {
|
|
1888
2362
|
const candidateCountByConnectionIndex = new Map<number, number>()
|
|
1889
2363
|
for (const candidate of getComponentDogboneViaSiteCandidates(
|
|
@@ -1894,6 +2368,8 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1894
2368
|
traceWidth: this.config.traceWidth,
|
|
1895
2369
|
clearance: this.config.clearance,
|
|
1896
2370
|
blockingSegments,
|
|
2371
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2372
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
1897
2373
|
canShareCopper,
|
|
1898
2374
|
},
|
|
1899
2375
|
)) {
|
|
@@ -1929,6 +2405,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1929
2405
|
}
|
|
1930
2406
|
fixedViaPointsByConnectionIndex = candidateFixedViaPoints
|
|
1931
2407
|
if (yield* routeMatchedBoundaryBusSteps(candidateBus)) {
|
|
2408
|
+
debugDense("lookahead:start", candidateBus.busId)
|
|
1932
2409
|
const candidateLeavesAFeasibleExtension =
|
|
1933
2410
|
laterBuses.length === 0 ||
|
|
1934
2411
|
laterBuses.some((laterBus) => {
|
|
@@ -1941,7 +2418,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1941
2418
|
return Boolean(
|
|
1942
2419
|
matchComponentDogboneViaSites(
|
|
1943
2420
|
[
|
|
1944
|
-
...
|
|
2421
|
+
...activeBoundaryReservationPlaneBuses,
|
|
1945
2422
|
...routedBoundaryBuses,
|
|
1946
2423
|
candidateBus,
|
|
1947
2424
|
laterBus,
|
|
@@ -1957,14 +2434,23 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1957
2434
|
fixedViaPointsByConnectionIndex:
|
|
1958
2435
|
fixedViaPointsByConnectionIndex,
|
|
1959
2436
|
blockingSegments: lookaheadBlockingSegments,
|
|
2437
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2438
|
+
preferPlaneCheckerboardSites:
|
|
2439
|
+
useConfiguredDensePlaneRouting,
|
|
1960
2440
|
canShareCopper,
|
|
1961
2441
|
},
|
|
1962
2442
|
),
|
|
1963
2443
|
)
|
|
1964
2444
|
})
|
|
2445
|
+
debugDense(
|
|
2446
|
+
"lookahead:complete",
|
|
2447
|
+
candidateBus.busId,
|
|
2448
|
+
candidateLeavesAFeasibleExtension,
|
|
2449
|
+
)
|
|
1965
2450
|
if (candidateLeavesAFeasibleExtension) {
|
|
1966
2451
|
selectedBusIndex = candidateIndex
|
|
1967
2452
|
routedBoundaryBuses.push(candidateBus)
|
|
2453
|
+
reserveAllPlaneDogbonesAfterFirstWideBus(candidateBus)
|
|
1968
2454
|
this.setInProgressPlans({
|
|
1969
2455
|
phase: "route-dense-boundary-buses",
|
|
1970
2456
|
plans: matchedPlans,
|
|
@@ -1995,12 +2481,15 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1995
2481
|
}
|
|
1996
2482
|
remainingBoundaryBuses.splice(selectedBusIndex, 1)
|
|
1997
2483
|
}
|
|
2484
|
+
let matchedPlaneBusesInRoutingOrder: PreparedBus[] | null = null
|
|
1998
2485
|
if (matchedRoutingSucceeded) {
|
|
1999
2486
|
let feasibleViaPoints: Map<number, { x: number; y: number }> | null =
|
|
2000
2487
|
null
|
|
2488
|
+
let feasibleAlternatePlanePlans: FanoutRoutePlan[] = []
|
|
2001
2489
|
const matchViaPointsAroundPlans = (
|
|
2002
2490
|
candidatePlans: readonly FanoutRoutePlan[],
|
|
2003
2491
|
): Map<number, { x: number; y: number }> | null => {
|
|
2492
|
+
feasibleAlternatePlanePlans = []
|
|
2004
2493
|
const fixedBoundaryViaPoints = new Map(
|
|
2005
2494
|
candidatePlans.flatMap((plan) =>
|
|
2006
2495
|
plan.via
|
|
@@ -2008,6 +2497,765 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2008
2497
|
: [],
|
|
2009
2498
|
),
|
|
2010
2499
|
)
|
|
2500
|
+
const blockingSegments = candidatePlans.flatMap((plan) =>
|
|
2501
|
+
plan.segments.map((segment) => ({
|
|
2502
|
+
connectionIndex: plan.connectionIndex,
|
|
2503
|
+
segment,
|
|
2504
|
+
})),
|
|
2505
|
+
)
|
|
2506
|
+
if (
|
|
2507
|
+
useConfiguredDensePlaneRouting ||
|
|
2508
|
+
process.env.FANOUT_DEBUG_INCREMENTAL_PLANE_MATCH === "1"
|
|
2509
|
+
) {
|
|
2510
|
+
let incrementalViaPoints = new Map(fixedBoundaryViaPoints)
|
|
2511
|
+
const matchedPlaneBuses = [...activeBoundaryReservationPlaneBuses]
|
|
2512
|
+
for (const planeBus of matchedPlaneBuses) {
|
|
2513
|
+
for (const connection of planeBus.connections) {
|
|
2514
|
+
const reservedPoint = fixedViaPointsByConnectionIndex.get(
|
|
2515
|
+
connection.connectionIndex,
|
|
2516
|
+
)
|
|
2517
|
+
if (reservedPoint) {
|
|
2518
|
+
incrementalViaPoints.set(
|
|
2519
|
+
connection.connectionIndex,
|
|
2520
|
+
reservedPoint,
|
|
2521
|
+
)
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
for (const planeBus of matchedPlaneBuses) {
|
|
2526
|
+
const targetLayer = params.busLayerAssignments[planeBus.busId]
|
|
2527
|
+
if (!targetLayer) return null
|
|
2528
|
+
const reservedPlanePlans = routeBus({
|
|
2529
|
+
srj: this.routingSrj,
|
|
2530
|
+
bus: planeBus,
|
|
2531
|
+
targetLayer,
|
|
2532
|
+
acceptedPlans: [
|
|
2533
|
+
...candidatePlans,
|
|
2534
|
+
...feasibleAlternatePlanePlans,
|
|
2535
|
+
],
|
|
2536
|
+
layerNames: this.config.layerNames,
|
|
2537
|
+
traceWidth: this.config.traceWidth,
|
|
2538
|
+
viaDiameter: this.config.viaDiameter,
|
|
2539
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
2540
|
+
clearance: this.config.clearance,
|
|
2541
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
2542
|
+
allowBlindAndBuriedVias: false,
|
|
2543
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
2544
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
2545
|
+
fixedViaPointsByConnectionIndex: incrementalViaPoints,
|
|
2546
|
+
})
|
|
2547
|
+
if (!reservedPlanePlans) return null
|
|
2548
|
+
feasibleAlternatePlanePlans.push(...reservedPlanePlans)
|
|
2549
|
+
}
|
|
2550
|
+
const independentlyUnmatchablePlaneBuses = planeBuses.filter(
|
|
2551
|
+
(planeBus) =>
|
|
2552
|
+
!matchedPlaneBuses.includes(planeBus) &&
|
|
2553
|
+
!matchComponentDogboneViaSites(
|
|
2554
|
+
[...matchedPlaneBuses, planeBus, ...boundaryBuses],
|
|
2555
|
+
{
|
|
2556
|
+
viaDiameter: this.config.viaDiameter,
|
|
2557
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
2558
|
+
traceWidth: this.config.traceWidth,
|
|
2559
|
+
clearance: this.config.clearance,
|
|
2560
|
+
maximumSearchStates: 100_000,
|
|
2561
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
2562
|
+
preferBoundaryOutwardByBusId,
|
|
2563
|
+
fixedViaPointsByConnectionIndex: incrementalViaPoints,
|
|
2564
|
+
blockingSegments,
|
|
2565
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
2566
|
+
preferPlaneCheckerboardSites:
|
|
2567
|
+
useConfiguredDensePlaneRouting,
|
|
2568
|
+
canShareCopper,
|
|
2569
|
+
},
|
|
2570
|
+
),
|
|
2571
|
+
)
|
|
2572
|
+
const shallowestPlaneLayerIndex = Math.min(
|
|
2573
|
+
...planeBuses.map((bus) =>
|
|
2574
|
+
this.config.layerNames.indexOf(
|
|
2575
|
+
params.busLayerAssignments[bus.busId] ?? "",
|
|
2576
|
+
),
|
|
2577
|
+
),
|
|
2578
|
+
)
|
|
2579
|
+
const deeperPlaneBuses =
|
|
2580
|
+
useConfiguredDensePlaneRouting ||
|
|
2581
|
+
process.env.FANOUT_DEBUG_ROUTE_DEEP_PLANES_FIRST === "1"
|
|
2582
|
+
? planeBuses.filter(
|
|
2583
|
+
(bus) =>
|
|
2584
|
+
!matchedPlaneBuses.includes(bus) &&
|
|
2585
|
+
this.config.layerNames.indexOf(
|
|
2586
|
+
params.busLayerAssignments[bus.busId] ?? "",
|
|
2587
|
+
) > shallowestPlaneLayerIndex,
|
|
2588
|
+
)
|
|
2589
|
+
: []
|
|
2590
|
+
const additionalAlternatePlaneBusIds = new Set([
|
|
2591
|
+
...this.config.denseUnrestrictedPlaneRoutingBusIds,
|
|
2592
|
+
...(process.env.FANOUT_DEBUG_ADDITIONAL_ALTERNATE_PLANE_BUS_IDS?.split(
|
|
2593
|
+
",",
|
|
2594
|
+
) ?? []),
|
|
2595
|
+
])
|
|
2596
|
+
const additionalAlternatePlaneBuses = planeBuses.filter(
|
|
2597
|
+
(bus) =>
|
|
2598
|
+
!matchedPlaneBuses.includes(bus) &&
|
|
2599
|
+
additionalAlternatePlaneBusIds.has(bus.busId),
|
|
2600
|
+
)
|
|
2601
|
+
const alternatePlaneBuses = [
|
|
2602
|
+
...deeperPlaneBuses,
|
|
2603
|
+
...independentlyUnmatchablePlaneBuses.filter(
|
|
2604
|
+
(bus) => !deeperPlaneBuses.includes(bus),
|
|
2605
|
+
),
|
|
2606
|
+
...additionalAlternatePlaneBuses.filter(
|
|
2607
|
+
(bus) =>
|
|
2608
|
+
!deeperPlaneBuses.includes(bus) &&
|
|
2609
|
+
!independentlyUnmatchablePlaneBuses.includes(bus),
|
|
2610
|
+
),
|
|
2611
|
+
]
|
|
2612
|
+
const debugAlternatePlaneOrder =
|
|
2613
|
+
process.env.FANOUT_DEBUG_ALTERNATE_PLANE_ORDER?.split(",") ?? []
|
|
2614
|
+
const orderedAlternatePlaneBuses = alternatePlaneBuses.toSorted(
|
|
2615
|
+
(first, second) => {
|
|
2616
|
+
const firstLayerIndex = this.config.layerNames.indexOf(
|
|
2617
|
+
params.busLayerAssignments[first.busId] ?? "",
|
|
2618
|
+
)
|
|
2619
|
+
const secondLayerIndex = this.config.layerNames.indexOf(
|
|
2620
|
+
params.busLayerAssignments[second.busId] ?? "",
|
|
2621
|
+
)
|
|
2622
|
+
if (
|
|
2623
|
+
firstLayerIndex !== secondLayerIndex &&
|
|
2624
|
+
process.env.FANOUT_DEBUG_ALTERNATE_IGNORE_LAYERS !== "1"
|
|
2625
|
+
) {
|
|
2626
|
+
return process.env.FANOUT_DEBUG_ALTERNATE_SHALLOW_FIRST ===
|
|
2627
|
+
"1"
|
|
2628
|
+
? firstLayerIndex - secondLayerIndex
|
|
2629
|
+
: secondLayerIndex - firstLayerIndex
|
|
2630
|
+
}
|
|
2631
|
+
const firstPriority = debugAlternatePlaneOrder.indexOf(
|
|
2632
|
+
first.busId,
|
|
2633
|
+
)
|
|
2634
|
+
const secondPriority = debugAlternatePlaneOrder.indexOf(
|
|
2635
|
+
second.busId,
|
|
2636
|
+
)
|
|
2637
|
+
return (
|
|
2638
|
+
(firstPriority < 0
|
|
2639
|
+
? debugAlternatePlaneOrder.length
|
|
2640
|
+
: firstPriority) -
|
|
2641
|
+
(secondPriority < 0
|
|
2642
|
+
? debugAlternatePlaneOrder.length
|
|
2643
|
+
: secondPriority) ||
|
|
2644
|
+
first.connections[0]!.connectionIndex -
|
|
2645
|
+
second.connections[0]!.connectionIndex
|
|
2646
|
+
)
|
|
2647
|
+
},
|
|
2648
|
+
)
|
|
2649
|
+
if (alternatePlaneBuses.length > 0) {
|
|
2650
|
+
debugDense(
|
|
2651
|
+
"plane-match:preflight-failed",
|
|
2652
|
+
independentlyUnmatchablePlaneBuses.map((bus) => bus.busId),
|
|
2653
|
+
)
|
|
2654
|
+
if (
|
|
2655
|
+
!useConfiguredDensePlaneRouting &&
|
|
2656
|
+
process.env.FANOUT_DEBUG_ROUTE_UNMATCHED_PLANES !== "1"
|
|
2657
|
+
) {
|
|
2658
|
+
return null
|
|
2659
|
+
}
|
|
2660
|
+
let alternatePlaneSearchStates = 0
|
|
2661
|
+
const maximumAlternatePlaneSearchStates = Number(
|
|
2662
|
+
process.env.FANOUT_DEBUG_ALTERNATE_SEARCH_STATES ??
|
|
2663
|
+
(useConfiguredDensePlaneRouting ? 3_000_000 : 1_000),
|
|
2664
|
+
)
|
|
2665
|
+
const maximumAlternatePlaneRoutes = Number(
|
|
2666
|
+
process.env.FANOUT_DEBUG_ALTERNATE_ROUTE_COUNT ??
|
|
2667
|
+
(useConfiguredDensePlaneRouting ? 128 : 8),
|
|
2668
|
+
)
|
|
2669
|
+
let deepestAlternatePlaneSearchIndex = 0
|
|
2670
|
+
const alternatePlaneFailureCountByBusId = new Map<
|
|
2671
|
+
string,
|
|
2672
|
+
number
|
|
2673
|
+
>()
|
|
2674
|
+
const getPlaneRouteAlternatives = (
|
|
2675
|
+
planeBus: PreparedBus,
|
|
2676
|
+
additionalAcceptedPlans: FanoutRoutePlan[],
|
|
2677
|
+
maximumRoutes = maximumAlternatePlaneRoutes,
|
|
2678
|
+
): FanoutRoutePlan[][] => {
|
|
2679
|
+
const targetLayer = params.busLayerAssignments[planeBus.busId]
|
|
2680
|
+
if (!targetLayer) return []
|
|
2681
|
+
return routeBusAlternatives(
|
|
2682
|
+
{
|
|
2683
|
+
srj: this.routingSrj,
|
|
2684
|
+
bus: planeBus,
|
|
2685
|
+
targetLayer,
|
|
2686
|
+
acceptedPlans: [
|
|
2687
|
+
...candidatePlans,
|
|
2688
|
+
...additionalAcceptedPlans,
|
|
2689
|
+
],
|
|
2690
|
+
layerNames: this.config.layerNames,
|
|
2691
|
+
traceWidth: this.config.traceWidth,
|
|
2692
|
+
viaDiameter: this.config.viaDiameter,
|
|
2693
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
2694
|
+
clearance: this.config.clearance,
|
|
2695
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
2696
|
+
allowBlindAndBuriedVias: false,
|
|
2697
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
2698
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
2699
|
+
},
|
|
2700
|
+
maximumRoutes,
|
|
2701
|
+
)
|
|
2702
|
+
}
|
|
2703
|
+
const routeAlternatePlaneBuses = (
|
|
2704
|
+
remainingPlaneBuses: PreparedBus[],
|
|
2705
|
+
acceptedAlternatePlans: FanoutRoutePlan[],
|
|
2706
|
+
): FanoutRoutePlan[] | null => {
|
|
2707
|
+
if (remainingPlaneBuses.length === 0) {
|
|
2708
|
+
return acceptedAlternatePlans
|
|
2709
|
+
}
|
|
2710
|
+
if (
|
|
2711
|
+
alternatePlaneSearchStates >=
|
|
2712
|
+
maximumAlternatePlaneSearchStates
|
|
2713
|
+
) {
|
|
2714
|
+
return null
|
|
2715
|
+
}
|
|
2716
|
+
deepestAlternatePlaneSearchIndex = Math.max(
|
|
2717
|
+
deepestAlternatePlaneSearchIndex,
|
|
2718
|
+
orderedAlternatePlaneBuses.length -
|
|
2719
|
+
remainingPlaneBuses.length,
|
|
2720
|
+
)
|
|
2721
|
+
const alternativesByBus = remainingPlaneBuses.map(
|
|
2722
|
+
(planeBus) => ({
|
|
2723
|
+
planeBus,
|
|
2724
|
+
alternatives: getPlaneRouteAlternatives(
|
|
2725
|
+
planeBus,
|
|
2726
|
+
acceptedAlternatePlans,
|
|
2727
|
+
),
|
|
2728
|
+
}),
|
|
2729
|
+
)
|
|
2730
|
+
const orderedSelections =
|
|
2731
|
+
process.env.FANOUT_DEBUG_DYNAMIC_ALTERNATE_ORDER === "1"
|
|
2732
|
+
? alternativesByBus.toSorted(
|
|
2733
|
+
(first, second) =>
|
|
2734
|
+
first.alternatives.length -
|
|
2735
|
+
second.alternatives.length ||
|
|
2736
|
+
orderedAlternatePlaneBuses.indexOf(first.planeBus) -
|
|
2737
|
+
orderedAlternatePlaneBuses.indexOf(second.planeBus),
|
|
2738
|
+
)
|
|
2739
|
+
: alternativesByBus
|
|
2740
|
+
const selected = orderedSelections[0]!
|
|
2741
|
+
const { planeBus, alternatives } = selected
|
|
2742
|
+
if (
|
|
2743
|
+
process.env.FANOUT_DEBUG_ALTERNATE_CHOICES === "1" &&
|
|
2744
|
+
alternatePlaneSearchStates < 64
|
|
2745
|
+
) {
|
|
2746
|
+
debugDense(
|
|
2747
|
+
"plane-route:alternate-choice",
|
|
2748
|
+
`depth:${orderedAlternatePlaneBuses.length - remainingPlaneBuses.length}`,
|
|
2749
|
+
planeBus.busId,
|
|
2750
|
+
alternativesByBus.map((entry) => [
|
|
2751
|
+
entry.planeBus.busId,
|
|
2752
|
+
entry.alternatives.length,
|
|
2753
|
+
]),
|
|
2754
|
+
)
|
|
2755
|
+
}
|
|
2756
|
+
if (alternatives.length === 0) {
|
|
2757
|
+
alternatePlaneFailureCountByBusId.set(
|
|
2758
|
+
planeBus.busId,
|
|
2759
|
+
(alternatePlaneFailureCountByBusId.get(planeBus.busId) ??
|
|
2760
|
+
0) + 1,
|
|
2761
|
+
)
|
|
2762
|
+
return null
|
|
2763
|
+
}
|
|
2764
|
+
const selectionsToSearch =
|
|
2765
|
+
process.env.FANOUT_DEBUG_BRANCH_ALTERNATE_ORDER === "1"
|
|
2766
|
+
? orderedSelections
|
|
2767
|
+
: [selected]
|
|
2768
|
+
const alternateActions = selectionsToSearch.flatMap(
|
|
2769
|
+
(selection) =>
|
|
2770
|
+
selection.alternatives.map((alternative) => ({
|
|
2771
|
+
selection,
|
|
2772
|
+
alternative,
|
|
2773
|
+
remaining: remainingPlaneBuses.filter(
|
|
2774
|
+
(candidate) => candidate !== selection.planeBus,
|
|
2775
|
+
),
|
|
2776
|
+
})),
|
|
2777
|
+
)
|
|
2778
|
+
const orderedActions =
|
|
2779
|
+
process.env.FANOUT_DEBUG_LEAST_CONSTRAINING_ALTERNATES === "1"
|
|
2780
|
+
? alternateActions
|
|
2781
|
+
.map((action) => {
|
|
2782
|
+
const acceptedPlans = [
|
|
2783
|
+
...acceptedAlternatePlans,
|
|
2784
|
+
...action.alternative,
|
|
2785
|
+
]
|
|
2786
|
+
const remainingOptionCounts = action.remaining.map(
|
|
2787
|
+
(remainingBus) =>
|
|
2788
|
+
getPlaneRouteAlternatives(
|
|
2789
|
+
remainingBus,
|
|
2790
|
+
acceptedPlans,
|
|
2791
|
+
Math.min(4, maximumAlternatePlaneRoutes),
|
|
2792
|
+
).length,
|
|
2793
|
+
)
|
|
2794
|
+
return {
|
|
2795
|
+
...action,
|
|
2796
|
+
remainingOptionCounts,
|
|
2797
|
+
minimumRemainingOptions:
|
|
2798
|
+
remainingOptionCounts.length === 0
|
|
2799
|
+
? Number.POSITIVE_INFINITY
|
|
2800
|
+
: Math.min(...remainingOptionCounts),
|
|
2801
|
+
totalRemainingOptions: remainingOptionCounts.reduce(
|
|
2802
|
+
(total, count) => total + count,
|
|
2803
|
+
0,
|
|
2804
|
+
),
|
|
2805
|
+
}
|
|
2806
|
+
})
|
|
2807
|
+
.filter(
|
|
2808
|
+
(action) => action.minimumRemainingOptions !== 0,
|
|
2809
|
+
)
|
|
2810
|
+
.toSorted(
|
|
2811
|
+
(first, second) =>
|
|
2812
|
+
second.minimumRemainingOptions -
|
|
2813
|
+
first.minimumRemainingOptions ||
|
|
2814
|
+
second.totalRemainingOptions -
|
|
2815
|
+
first.totalRemainingOptions,
|
|
2816
|
+
)
|
|
2817
|
+
: alternateActions
|
|
2818
|
+
for (const action of orderedActions) {
|
|
2819
|
+
alternatePlaneSearchStates++
|
|
2820
|
+
const completedPlans = routeAlternatePlaneBuses(
|
|
2821
|
+
action.remaining,
|
|
2822
|
+
[...acceptedAlternatePlans, ...action.alternative],
|
|
2823
|
+
)
|
|
2824
|
+
if (completedPlans) return completedPlans
|
|
2825
|
+
if (
|
|
2826
|
+
alternatePlaneSearchStates >=
|
|
2827
|
+
maximumAlternatePlaneSearchStates
|
|
2828
|
+
) {
|
|
2829
|
+
break
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
alternatePlaneFailureCountByBusId.set(
|
|
2833
|
+
planeBus.busId,
|
|
2834
|
+
(alternatePlaneFailureCountByBusId.get(planeBus.busId) ?? 0) +
|
|
2835
|
+
1,
|
|
2836
|
+
)
|
|
2837
|
+
return null
|
|
2838
|
+
}
|
|
2839
|
+
let alternatePlanePlans: FanoutRoutePlan[] | null
|
|
2840
|
+
if (
|
|
2841
|
+
useConfiguredDensePlaneRouting ||
|
|
2842
|
+
process.env.FANOUT_DEBUG_EXACT_COVER_ALTERNATES === "1"
|
|
2843
|
+
) {
|
|
2844
|
+
type IndependentPlaneRouteCandidate = {
|
|
2845
|
+
key: string
|
|
2846
|
+
planeBus: PreparedBus
|
|
2847
|
+
plans: FanoutRoutePlan[]
|
|
2848
|
+
}
|
|
2849
|
+
const candidateSets = orderedAlternatePlaneBuses.map(
|
|
2850
|
+
(planeBus) => ({
|
|
2851
|
+
planeBus,
|
|
2852
|
+
candidates: getPlaneRouteAlternatives(
|
|
2853
|
+
planeBus,
|
|
2854
|
+
feasibleAlternatePlanePlans,
|
|
2855
|
+
).map((plans, index) => ({
|
|
2856
|
+
key: `${planeBus.busId}:${index}`,
|
|
2857
|
+
planeBus,
|
|
2858
|
+
plans,
|
|
2859
|
+
})),
|
|
2860
|
+
}),
|
|
2861
|
+
)
|
|
2862
|
+
debugDense(
|
|
2863
|
+
"plane-route:alternate-candidate-counts",
|
|
2864
|
+
candidateSets.map((candidateSet) => [
|
|
2865
|
+
candidateSet.planeBus.busId,
|
|
2866
|
+
candidateSet.candidates.length,
|
|
2867
|
+
]),
|
|
2868
|
+
)
|
|
2869
|
+
const compatibilityByCandidatePair = new Map<string, boolean>()
|
|
2870
|
+
const candidatesAreCompatible = (
|
|
2871
|
+
first: IndependentPlaneRouteCandidate,
|
|
2872
|
+
second: IndependentPlaneRouteCandidate,
|
|
2873
|
+
): boolean => {
|
|
2874
|
+
const cacheKey = [first.key, second.key].toSorted().join("|")
|
|
2875
|
+
const cached = compatibilityByCandidatePair.get(cacheKey)
|
|
2876
|
+
if (cached !== undefined) return cached
|
|
2877
|
+
const compatible = fanoutPlansAreMutuallyClear({
|
|
2878
|
+
plans: [...first.plans, ...second.plans],
|
|
2879
|
+
srj: this.routingSrj,
|
|
2880
|
+
clearance: this.config.clearance,
|
|
2881
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
2882
|
+
})
|
|
2883
|
+
compatibilityByCandidatePair.set(cacheKey, compatible)
|
|
2884
|
+
return compatible
|
|
2885
|
+
}
|
|
2886
|
+
const selectCompatiblePlaneRoutes = (
|
|
2887
|
+
remainingCandidateSets: typeof candidateSets,
|
|
2888
|
+
selectedCandidates: IndependentPlaneRouteCandidate[],
|
|
2889
|
+
): IndependentPlaneRouteCandidate[] | null => {
|
|
2890
|
+
if (remainingCandidateSets.length === 0) {
|
|
2891
|
+
return selectedCandidates
|
|
2892
|
+
}
|
|
2893
|
+
if (
|
|
2894
|
+
alternatePlaneSearchStates >=
|
|
2895
|
+
maximumAlternatePlaneSearchStates
|
|
2896
|
+
) {
|
|
2897
|
+
return null
|
|
2898
|
+
}
|
|
2899
|
+
deepestAlternatePlaneSearchIndex = Math.max(
|
|
2900
|
+
deepestAlternatePlaneSearchIndex,
|
|
2901
|
+
orderedAlternatePlaneBuses.length -
|
|
2902
|
+
remainingCandidateSets.length,
|
|
2903
|
+
)
|
|
2904
|
+
const selectedSet = remainingCandidateSets.toSorted(
|
|
2905
|
+
(first, second) =>
|
|
2906
|
+
first.candidates.length - second.candidates.length,
|
|
2907
|
+
)[0]!
|
|
2908
|
+
if (selectedSet.candidates.length === 0) {
|
|
2909
|
+
alternatePlaneFailureCountByBusId.set(
|
|
2910
|
+
selectedSet.planeBus.busId,
|
|
2911
|
+
(alternatePlaneFailureCountByBusId.get(
|
|
2912
|
+
selectedSet.planeBus.busId,
|
|
2913
|
+
) ?? 0) + 1,
|
|
2914
|
+
)
|
|
2915
|
+
return null
|
|
2916
|
+
}
|
|
2917
|
+
const otherSets = remainingCandidateSets.filter(
|
|
2918
|
+
(candidateSet) => candidateSet !== selectedSet,
|
|
2919
|
+
)
|
|
2920
|
+
const candidateBatchSize = Number(
|
|
2921
|
+
process.env.FANOUT_DEBUG_ALTERNATE_CANDIDATE_BATCH_SIZE ??
|
|
2922
|
+
16,
|
|
2923
|
+
)
|
|
2924
|
+
for (
|
|
2925
|
+
let batchStart = 0;
|
|
2926
|
+
batchStart < selectedSet.candidates.length;
|
|
2927
|
+
batchStart += candidateBatchSize
|
|
2928
|
+
) {
|
|
2929
|
+
const actions = selectedSet.candidates
|
|
2930
|
+
.slice(batchStart, batchStart + candidateBatchSize)
|
|
2931
|
+
.map((candidate) => {
|
|
2932
|
+
const projectedSets = otherSets.map((candidateSet) => ({
|
|
2933
|
+
...candidateSet,
|
|
2934
|
+
candidates: candidateSet.candidates.filter(
|
|
2935
|
+
(otherCandidate) =>
|
|
2936
|
+
candidatesAreCompatible(
|
|
2937
|
+
candidate,
|
|
2938
|
+
otherCandidate,
|
|
2939
|
+
),
|
|
2940
|
+
),
|
|
2941
|
+
}))
|
|
2942
|
+
const projectedCounts = projectedSets.map(
|
|
2943
|
+
(candidateSet) => candidateSet.candidates.length,
|
|
2944
|
+
)
|
|
2945
|
+
return {
|
|
2946
|
+
candidate,
|
|
2947
|
+
projectedSets,
|
|
2948
|
+
minimumProjectedCount:
|
|
2949
|
+
projectedCounts.length === 0
|
|
2950
|
+
? Number.POSITIVE_INFINITY
|
|
2951
|
+
: Math.min(...projectedCounts),
|
|
2952
|
+
totalProjectedCount: projectedCounts.reduce(
|
|
2953
|
+
(total, count) => total + count,
|
|
2954
|
+
0,
|
|
2955
|
+
),
|
|
2956
|
+
}
|
|
2957
|
+
})
|
|
2958
|
+
.filter((action) => action.minimumProjectedCount !== 0)
|
|
2959
|
+
.toSorted(
|
|
2960
|
+
(first, second) =>
|
|
2961
|
+
second.minimumProjectedCount -
|
|
2962
|
+
first.minimumProjectedCount ||
|
|
2963
|
+
second.totalProjectedCount -
|
|
2964
|
+
first.totalProjectedCount,
|
|
2965
|
+
)
|
|
2966
|
+
for (const action of actions) {
|
|
2967
|
+
alternatePlaneSearchStates++
|
|
2968
|
+
const selected = selectCompatiblePlaneRoutes(
|
|
2969
|
+
action.projectedSets,
|
|
2970
|
+
[...selectedCandidates, action.candidate],
|
|
2971
|
+
)
|
|
2972
|
+
if (selected) return selected
|
|
2973
|
+
if (
|
|
2974
|
+
alternatePlaneSearchStates >=
|
|
2975
|
+
maximumAlternatePlaneSearchStates
|
|
2976
|
+
) {
|
|
2977
|
+
break
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
if (
|
|
2981
|
+
alternatePlaneSearchStates >=
|
|
2982
|
+
maximumAlternatePlaneSearchStates
|
|
2983
|
+
) {
|
|
2984
|
+
break
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
alternatePlaneFailureCountByBusId.set(
|
|
2988
|
+
selectedSet.planeBus.busId,
|
|
2989
|
+
(alternatePlaneFailureCountByBusId.get(
|
|
2990
|
+
selectedSet.planeBus.busId,
|
|
2991
|
+
) ?? 0) + 1,
|
|
2992
|
+
)
|
|
2993
|
+
return null
|
|
2994
|
+
}
|
|
2995
|
+
const selectedCandidates = selectCompatiblePlaneRoutes(
|
|
2996
|
+
candidateSets,
|
|
2997
|
+
[],
|
|
2998
|
+
)
|
|
2999
|
+
alternatePlanePlans = selectedCandidates
|
|
3000
|
+
? [
|
|
3001
|
+
...feasibleAlternatePlanePlans,
|
|
3002
|
+
...selectedCandidates.flatMap(
|
|
3003
|
+
(candidate) => candidate.plans,
|
|
3004
|
+
),
|
|
3005
|
+
]
|
|
3006
|
+
: null
|
|
3007
|
+
} else {
|
|
3008
|
+
alternatePlanePlans = routeAlternatePlaneBuses(
|
|
3009
|
+
orderedAlternatePlaneBuses,
|
|
3010
|
+
feasibleAlternatePlanePlans,
|
|
3011
|
+
)
|
|
3012
|
+
}
|
|
3013
|
+
debugDense(
|
|
3014
|
+
"plane-route:alternate-search",
|
|
3015
|
+
alternatePlanePlans ? "complete" : "failed",
|
|
3016
|
+
alternatePlaneSearchStates,
|
|
3017
|
+
`depth:${deepestAlternatePlaneSearchIndex}/${orderedAlternatePlaneBuses.length}`,
|
|
3018
|
+
[...alternatePlaneFailureCountByBusId].toSorted(
|
|
3019
|
+
([, first], [, second]) => second - first,
|
|
3020
|
+
),
|
|
3021
|
+
)
|
|
3022
|
+
if (!alternatePlanePlans) return null
|
|
3023
|
+
feasibleAlternatePlanePlans = alternatePlanePlans
|
|
3024
|
+
}
|
|
3025
|
+
let planeBusIdsRoutedWithoutDogbones = new Set<string>()
|
|
3026
|
+
let allBlockingSegments = [...blockingSegments]
|
|
3027
|
+
let alternateBlockingVias: {
|
|
3028
|
+
connectionIndex: number
|
|
3029
|
+
center: { x: number; y: number }
|
|
3030
|
+
diameter: number
|
|
3031
|
+
spanLayers: readonly string[]
|
|
3032
|
+
}[] = []
|
|
3033
|
+
let planeBusesToMatch = [...planeBuses]
|
|
3034
|
+
let candidateCountByConnectionIndex = new Map<number, number>()
|
|
3035
|
+
let candidatePointsByConnectionIndex = new Map<
|
|
3036
|
+
number,
|
|
3037
|
+
{ x: number; y: number }[]
|
|
3038
|
+
>()
|
|
3039
|
+
const refreshPlaneDogboneCandidates = (): void => {
|
|
3040
|
+
planeBusIdsRoutedWithoutDogbones = new Set(
|
|
3041
|
+
feasibleAlternatePlanePlans.map((plan) => plan.busId),
|
|
3042
|
+
)
|
|
3043
|
+
const alternateBlockingSegments =
|
|
3044
|
+
feasibleAlternatePlanePlans.flatMap((plan) =>
|
|
3045
|
+
[...plan.segments, ...(plan.planeEndpointSegments ?? [])].map(
|
|
3046
|
+
(segment) => ({
|
|
3047
|
+
connectionIndex: plan.connectionIndex,
|
|
3048
|
+
segment,
|
|
3049
|
+
}),
|
|
3050
|
+
),
|
|
3051
|
+
)
|
|
3052
|
+
allBlockingSegments = [
|
|
3053
|
+
...blockingSegments,
|
|
3054
|
+
...alternateBlockingSegments,
|
|
3055
|
+
]
|
|
3056
|
+
alternateBlockingVias = feasibleAlternatePlanePlans.flatMap(
|
|
3057
|
+
(plan) =>
|
|
3058
|
+
[
|
|
3059
|
+
plan.via,
|
|
3060
|
+
...(plan.additionalVias ?? []),
|
|
3061
|
+
plan.planeEndpointVia,
|
|
3062
|
+
].flatMap((via) =>
|
|
3063
|
+
via
|
|
3064
|
+
? [
|
|
3065
|
+
{
|
|
3066
|
+
connectionIndex: plan.connectionIndex,
|
|
3067
|
+
center: via.center,
|
|
3068
|
+
diameter: via.diameter,
|
|
3069
|
+
spanLayers: via.spanLayers,
|
|
3070
|
+
},
|
|
3071
|
+
]
|
|
3072
|
+
: [],
|
|
3073
|
+
),
|
|
3074
|
+
)
|
|
3075
|
+
planeBusesToMatch = planeBuses.filter(
|
|
3076
|
+
(bus) => !planeBusIdsRoutedWithoutDogbones.has(bus.busId),
|
|
3077
|
+
)
|
|
3078
|
+
candidateCountByConnectionIndex = new Map()
|
|
3079
|
+
candidatePointsByConnectionIndex = new Map()
|
|
3080
|
+
for (const candidate of getComponentDogboneViaSiteCandidates(
|
|
3081
|
+
planeBusesToMatch,
|
|
3082
|
+
{
|
|
3083
|
+
viaDiameter: this.config.viaDiameter,
|
|
3084
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
3085
|
+
traceWidth: this.config.traceWidth,
|
|
3086
|
+
clearance: this.config.clearance,
|
|
3087
|
+
blockingSegments: allBlockingSegments,
|
|
3088
|
+
blockingVias: alternateBlockingVias,
|
|
3089
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
3090
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
3091
|
+
canShareCopper,
|
|
3092
|
+
},
|
|
3093
|
+
)) {
|
|
3094
|
+
candidateCountByConnectionIndex.set(
|
|
3095
|
+
candidate.connectionIndex,
|
|
3096
|
+
(candidateCountByConnectionIndex.get(
|
|
3097
|
+
candidate.connectionIndex,
|
|
3098
|
+
) ?? 0) + 1,
|
|
3099
|
+
)
|
|
3100
|
+
const points =
|
|
3101
|
+
candidatePointsByConnectionIndex.get(
|
|
3102
|
+
candidate.connectionIndex,
|
|
3103
|
+
) ?? []
|
|
3104
|
+
points.push(candidate.point)
|
|
3105
|
+
candidatePointsByConnectionIndex.set(
|
|
3106
|
+
candidate.connectionIndex,
|
|
3107
|
+
points,
|
|
3108
|
+
)
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
refreshPlaneDogboneCandidates()
|
|
3112
|
+
for (
|
|
3113
|
+
let promotionPass = 0;
|
|
3114
|
+
promotionPass < planeBuses.length;
|
|
3115
|
+
promotionPass++
|
|
3116
|
+
) {
|
|
3117
|
+
const zeroCandidatePlaneBuses = planeBusesToMatch.filter(
|
|
3118
|
+
(bus) =>
|
|
3119
|
+
!candidateCountByConnectionIndex.has(
|
|
3120
|
+
bus.connections[0]!.connectionIndex,
|
|
3121
|
+
),
|
|
3122
|
+
)
|
|
3123
|
+
if (zeroCandidatePlaneBuses.length === 0) break
|
|
3124
|
+
debugDense(
|
|
3125
|
+
"plane-route:promote-zero-candidates",
|
|
3126
|
+
zeroCandidatePlaneBuses.map((bus) => bus.busId),
|
|
3127
|
+
)
|
|
3128
|
+
if (
|
|
3129
|
+
zeroCandidatePlaneBuses.some((bus) =>
|
|
3130
|
+
matchedPlaneBuses.includes(bus),
|
|
3131
|
+
)
|
|
3132
|
+
) {
|
|
3133
|
+
return null
|
|
3134
|
+
}
|
|
3135
|
+
for (const planeBus of zeroCandidatePlaneBuses) {
|
|
3136
|
+
const targetLayer = params.busLayerAssignments[planeBus.busId]
|
|
3137
|
+
if (!targetLayer) return null
|
|
3138
|
+
const promotedPlans = routeBusAlternatives(
|
|
3139
|
+
{
|
|
3140
|
+
srj: this.routingSrj,
|
|
3141
|
+
bus: planeBus,
|
|
3142
|
+
targetLayer,
|
|
3143
|
+
acceptedPlans: [
|
|
3144
|
+
...candidatePlans,
|
|
3145
|
+
...feasibleAlternatePlanePlans,
|
|
3146
|
+
],
|
|
3147
|
+
layerNames: this.config.layerNames,
|
|
3148
|
+
traceWidth: this.config.traceWidth,
|
|
3149
|
+
viaDiameter: this.config.viaDiameter,
|
|
3150
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
3151
|
+
clearance: this.config.clearance,
|
|
3152
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
3153
|
+
allowBlindAndBuriedVias: false,
|
|
3154
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
3155
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
3156
|
+
},
|
|
3157
|
+
8,
|
|
3158
|
+
)[0]
|
|
3159
|
+
if (!promotedPlans) {
|
|
3160
|
+
debugDense("plane-route:promote-failed", planeBus.busId)
|
|
3161
|
+
return null
|
|
3162
|
+
}
|
|
3163
|
+
feasibleAlternatePlanePlans.push(...promotedPlans)
|
|
3164
|
+
}
|
|
3165
|
+
refreshPlaneDogboneCandidates()
|
|
3166
|
+
}
|
|
3167
|
+
const debugPlaneMatchOrder =
|
|
3168
|
+
process.env.FANOUT_DEBUG_PLANE_MATCH_ORDER?.split(",") ?? []
|
|
3169
|
+
const incrementalPlaneBuses = planeBusesToMatch.toSorted(
|
|
3170
|
+
(first, second) => {
|
|
3171
|
+
const candidateCountDifference =
|
|
3172
|
+
(candidateCountByConnectionIndex.get(
|
|
3173
|
+
first.connections[0]!.connectionIndex,
|
|
3174
|
+
) ?? 0) -
|
|
3175
|
+
(candidateCountByConnectionIndex.get(
|
|
3176
|
+
second.connections[0]!.connectionIndex,
|
|
3177
|
+
) ?? 0)
|
|
3178
|
+
if (candidateCountDifference !== 0) {
|
|
3179
|
+
return candidateCountDifference
|
|
3180
|
+
}
|
|
3181
|
+
const firstPriority = debugPlaneMatchOrder.indexOf(first.busId)
|
|
3182
|
+
const secondPriority = debugPlaneMatchOrder.indexOf(
|
|
3183
|
+
second.busId,
|
|
3184
|
+
)
|
|
3185
|
+
const priorityDifference =
|
|
3186
|
+
(firstPriority < 0
|
|
3187
|
+
? debugPlaneMatchOrder.length
|
|
3188
|
+
: firstPriority) -
|
|
3189
|
+
(secondPriority < 0
|
|
3190
|
+
? debugPlaneMatchOrder.length
|
|
3191
|
+
: secondPriority)
|
|
3192
|
+
if (priorityDifference !== 0) return priorityDifference
|
|
3193
|
+
return (
|
|
3194
|
+
first.connections[0]!.connectionIndex -
|
|
3195
|
+
second.connections[0]!.connectionIndex
|
|
3196
|
+
)
|
|
3197
|
+
},
|
|
3198
|
+
)
|
|
3199
|
+
for (const planeBus of incrementalPlaneBuses) {
|
|
3200
|
+
if (matchedPlaneBuses.includes(planeBus)) continue
|
|
3201
|
+
if (
|
|
3202
|
+
process.env.FANOUT_DEBUG_PLANE_CANDIDATES?.split(",").includes(
|
|
3203
|
+
planeBus.busId,
|
|
3204
|
+
)
|
|
3205
|
+
) {
|
|
3206
|
+
debugDense(
|
|
3207
|
+
"plane-match:candidates",
|
|
3208
|
+
planeBus.busId,
|
|
3209
|
+
candidatePointsByConnectionIndex.get(
|
|
3210
|
+
planeBus.connections[0]!.connectionIndex,
|
|
3211
|
+
),
|
|
3212
|
+
)
|
|
3213
|
+
}
|
|
3214
|
+
const nextViaPoints = matchComponentDogboneViaSites(
|
|
3215
|
+
[...matchedPlaneBuses, planeBus, ...boundaryBuses],
|
|
3216
|
+
{
|
|
3217
|
+
viaDiameter: this.config.viaDiameter,
|
|
3218
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
3219
|
+
traceWidth: this.config.traceWidth,
|
|
3220
|
+
clearance: this.config.clearance,
|
|
3221
|
+
maximumSearchStates: 100_000,
|
|
3222
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
3223
|
+
preferBoundaryOutwardByBusId,
|
|
3224
|
+
fixedViaPointsByConnectionIndex: incrementalViaPoints,
|
|
3225
|
+
blockingSegments: allBlockingSegments,
|
|
3226
|
+
blockingVias: alternateBlockingVias,
|
|
3227
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
3228
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
3229
|
+
canShareCopper,
|
|
3230
|
+
},
|
|
3231
|
+
)
|
|
3232
|
+
debugDense(
|
|
3233
|
+
nextViaPoints
|
|
3234
|
+
? "plane-match:incremental"
|
|
3235
|
+
: "plane-match:incremental-failed",
|
|
3236
|
+
planeBus.busId,
|
|
3237
|
+
candidateCountByConnectionIndex.get(
|
|
3238
|
+
planeBus.connections[0]!.connectionIndex,
|
|
3239
|
+
) ?? 0,
|
|
3240
|
+
nextViaPoints?.get(planeBus.connections[0]!.connectionIndex),
|
|
3241
|
+
nextViaPoints?.size ?? "failed",
|
|
3242
|
+
)
|
|
3243
|
+
if (!nextViaPoints) return null
|
|
3244
|
+
incrementalViaPoints = new Map([
|
|
3245
|
+
...incrementalViaPoints,
|
|
3246
|
+
...nextViaPoints,
|
|
3247
|
+
])
|
|
3248
|
+
matchedPlaneBuses.push(planeBus)
|
|
3249
|
+
}
|
|
3250
|
+
matchedPlaneBusesInRoutingOrder = matchedPlaneBuses.filter(
|
|
3251
|
+
(bus) => !planeBusIdsRoutedWithoutDogbones.has(bus.busId),
|
|
3252
|
+
)
|
|
3253
|
+
debugDense(
|
|
3254
|
+
"plane-match:incremental-complete",
|
|
3255
|
+
incrementalViaPoints.size,
|
|
3256
|
+
)
|
|
3257
|
+
return incrementalViaPoints
|
|
3258
|
+
}
|
|
2011
3259
|
return matchComponentDogboneViaSites(
|
|
2012
3260
|
[...planeBuses, ...boundaryBuses],
|
|
2013
3261
|
{
|
|
@@ -2019,16 +3267,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2019
3267
|
preferredBoundaryPerpendicularSideByBusId,
|
|
2020
3268
|
preferBoundaryOutwardByBusId,
|
|
2021
3269
|
fixedViaPointsByConnectionIndex: fixedBoundaryViaPoints,
|
|
2022
|
-
blockingSegments
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
segment,
|
|
2026
|
-
})),
|
|
2027
|
-
),
|
|
3270
|
+
blockingSegments,
|
|
3271
|
+
additionalObstacles: denseAdditionalObstacles,
|
|
3272
|
+
preferPlaneCheckerboardSites: useConfiguredDensePlaneRouting,
|
|
2028
3273
|
canShareCopper,
|
|
2029
3274
|
},
|
|
2030
3275
|
)
|
|
2031
3276
|
}
|
|
3277
|
+
debugDense("length-match:start", matchedPlans.length)
|
|
2032
3278
|
const matchedLengthResult = matchBusPlanLengths({
|
|
2033
3279
|
plans: matchedPlans,
|
|
2034
3280
|
preparedBuses: this.preparedBuses,
|
|
@@ -2045,6 +3291,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2045
3291
|
return true
|
|
2046
3292
|
},
|
|
2047
3293
|
})
|
|
3294
|
+
debugDense(
|
|
3295
|
+
"length-match:complete",
|
|
3296
|
+
matchedLengthResult.plans?.length ?? "failed",
|
|
3297
|
+
)
|
|
2048
3298
|
this.setInProgressPlans({
|
|
2049
3299
|
phase: "match-dense-boundary-lengths",
|
|
2050
3300
|
plans: matchedLengthResult.plans ?? matchedPlans,
|
|
@@ -2059,6 +3309,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2059
3309
|
feasibleViaPoints ?? matchViaPointsAroundPlans(matchedPlans)
|
|
2060
3310
|
if (rematchedViaPoints) {
|
|
2061
3311
|
fixedViaPointsByConnectionIndex = rematchedViaPoints
|
|
3312
|
+
matchedPlans.push(...feasibleAlternatePlanePlans)
|
|
2062
3313
|
} else {
|
|
2063
3314
|
matchedRoutingSucceeded = false
|
|
2064
3315
|
}
|
|
@@ -2075,8 +3326,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2075
3326
|
yield
|
|
2076
3327
|
}
|
|
2077
3328
|
if (matchedRoutingSucceeded) {
|
|
2078
|
-
for (const bus of planeBuses) {
|
|
3329
|
+
for (const bus of matchedPlaneBusesInRoutingOrder ?? planeBuses) {
|
|
3330
|
+
debugDense("plane-route:start", bus.busId)
|
|
2079
3331
|
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
3332
|
+
const blockingBusCounts = new Map<string, number>()
|
|
2080
3333
|
const busPlans = targetLayer
|
|
2081
3334
|
? routeBus({
|
|
2082
3335
|
srj: this.routingSrj,
|
|
@@ -2092,14 +3345,24 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2092
3345
|
allowBlindAndBuriedVias: false,
|
|
2093
3346
|
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
2094
3347
|
staticClearanceCache: this.routeStaticClearanceCache,
|
|
3348
|
+
blockingBusCounts,
|
|
2095
3349
|
fixedViaPointsByConnectionIndex,
|
|
2096
3350
|
})
|
|
2097
3351
|
: null
|
|
2098
3352
|
if (!busPlans) {
|
|
3353
|
+
debugDense(
|
|
3354
|
+
"plane-route:failed",
|
|
3355
|
+
bus.busId,
|
|
3356
|
+
fixedViaPointsByConnectionIndex.get(
|
|
3357
|
+
bus.connections[0]!.connectionIndex,
|
|
3358
|
+
),
|
|
3359
|
+
[...blockingBusCounts],
|
|
3360
|
+
)
|
|
2099
3361
|
matchedRoutingSucceeded = false
|
|
2100
3362
|
break
|
|
2101
3363
|
}
|
|
2102
3364
|
matchedPlans.push(...busPlans)
|
|
3365
|
+
debugDense("plane-route:complete", bus.busId)
|
|
2103
3366
|
this.setInProgressPlans({
|
|
2104
3367
|
phase: "route-dense-plane-buses",
|
|
2105
3368
|
plans: matchedPlans,
|
|
@@ -2111,7 +3374,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2111
3374
|
yield
|
|
2112
3375
|
}
|
|
2113
3376
|
}
|
|
2114
|
-
|
|
3377
|
+
const densePlansAreClear =
|
|
2115
3378
|
matchedRoutingSucceeded &&
|
|
2116
3379
|
fanoutPlansAreClear({
|
|
2117
3380
|
plans: matchedPlans,
|
|
@@ -2121,11 +3384,19 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2121
3384
|
allowBlindAndBuriedVias: false,
|
|
2122
3385
|
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
2123
3386
|
})
|
|
2124
|
-
|
|
3387
|
+
debugDense(
|
|
3388
|
+
"dense-validation",
|
|
3389
|
+
matchedRoutingSucceeded,
|
|
3390
|
+
matchedPlans.length,
|
|
3391
|
+
densePlansAreClear,
|
|
3392
|
+
)
|
|
3393
|
+
if (densePlansAreClear) {
|
|
2125
3394
|
return { plans: matchedPlans, failedBusIds: [] }
|
|
2126
3395
|
}
|
|
2127
3396
|
}
|
|
2128
3397
|
|
|
3398
|
+
if (process.env.FANOUT_DEBUG_DENSE_ONLY === "1") return null
|
|
3399
|
+
|
|
2129
3400
|
const maximumStates = 8
|
|
2130
3401
|
const getBoundaryStates = (
|
|
2131
3402
|
alternativesPerBoundaryBus: number,
|
|
@@ -2599,6 +3870,17 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2599
3870
|
solver: denseSolver,
|
|
2600
3871
|
}) as MixedTerminationState | null
|
|
2601
3872
|
}
|
|
3873
|
+
if (
|
|
3874
|
+
!mixedTerminationState &&
|
|
3875
|
+
!useSingleLayerPushAndShove &&
|
|
3876
|
+
routingStrategy === "default" &&
|
|
3877
|
+
process.env.FANOUT_DEBUG_DENSE_ONLY === "1"
|
|
3878
|
+
) {
|
|
3879
|
+
mixedTerminationState = {
|
|
3880
|
+
plans: [],
|
|
3881
|
+
failedBusIds: this.preparedBuses.map((bus) => bus.busId),
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
2602
3884
|
|
|
2603
3885
|
if (mixedTerminationState) {
|
|
2604
3886
|
plans = mixedTerminationState.plans
|
|
@@ -2786,6 +4068,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2786
4068
|
busLayerAssignments,
|
|
2787
4069
|
"default",
|
|
2788
4070
|
)
|
|
4071
|
+
if (process.env.FANOUT_DEBUG_DENSE_ONLY === "1") return bestAttempt
|
|
2789
4072
|
if (
|
|
2790
4073
|
bestAttempt.summary.routedConnectionCount ===
|
|
2791
4074
|
this.inputSrj.connections.length &&
|