@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.
@@ -1,6 +1,6 @@
1
1
  import type { SimpleRouteJson } from "@tscircuit/capacity-autorouter"
2
2
  import { BaseSolver } from "@tscircuit/solver-utils"
3
- import { mergeGraphics, type GraphicsObject } from "graphics-debug"
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 = void | FanoutSubsolverRequest
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 { buses, escapeLayers, escapeLayersByBusId } = params
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 boundaryBuses = unsortedBoundaryBuses.toSorted((first, second) => {
1386
- // Reserve the dense escape field for the widest buses first. Small
1387
- // control groups can usually route around their copper, while routing
1388
- // a two-line corner bus first can consume a critical channel needed by
1389
- // an eight-line winding bus and force the expensive fallback search.
1390
- if (useJointBoundaryViaReservation) {
1391
- const connectionCountDifference =
1392
- second.connections.length - first.connections.length
1393
- if (connectionCountDifference !== 0) return connectionCountDifference
1394
- }
1395
- const firstLayer = params.busLayerAssignments[first.busId]
1396
- const secondLayer = params.busLayerAssignments[second.busId]
1397
- const firstPairRoutingPriority = pairRoutingPriorityKeyByBusId?.get(
1398
- first.busId,
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
- const cornerBandDifference =
1414
- Number(
1415
- Boolean(getCornerBandSide(second.exitEdge, second.preferredExit)),
1416
- ) -
1417
- Number(Boolean(getCornerBandSide(first.exitEdge, first.preferredExit)))
1418
- if (cornerBandDifference !== 0) return cornerBandDifference
1419
- const firstIsCorner = Boolean(
1420
- getCornerBandSide(first.exitEdge, first.preferredExit),
1421
- )
1422
- if (!firstIsCorner) {
1423
- const getSourceSpan = (bus: PreparedBus): number => {
1424
- const xCoordinates = bus.connections.map(
1425
- (connection) => connection.sourcePoint.x,
1426
- )
1427
- const yCoordinates = bus.connections.map(
1428
- (connection) => connection.sourcePoint.y,
1429
- )
1430
- return (
1431
- Math.max(...xCoordinates) -
1432
- Math.min(...xCoordinates) +
1433
- Math.max(...yCoordinates) -
1434
- Math.min(...yCoordinates)
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 sourceSpanDifference =
1438
- getSourceSpan(second) - getSourceSpan(first)
1439
- if (Math.abs(sourceSpanDifference) > 1e-9) {
1440
- return sourceSpanDifference
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
- const layerDifference =
1444
- this.config.layerNames.indexOf(firstLayer ?? "") -
1445
- this.config.layerNames.indexOf(secondLayer ?? "")
1446
- if (layerDifference !== 0) return -layerDifference
1447
- if (
1448
- unsortedBoundaryBuses.length !== 6 &&
1449
- unsortedBoundaryBuses.length !== 7 &&
1450
- unsortedBoundaryBuses.length !== 8 &&
1451
- unsortedBoundaryBuses.length !== 9
1452
- ) {
1453
- return 0
1454
- }
1455
- // The general routing order can differ across the two components as a
1456
- // function of local pad geometry. Keep otherwise-equivalent corner buses
1457
- // in one deterministic order for the six- through nine-bus paths so their
1458
- // boundary lanes do not swap between the two ends of a direct
1459
- // interconnect. Leave the released four- and five-bus tie behavior
1460
- // unchanged.
1461
- return first.busId.localeCompare(second.busId)
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) => [bus.busId, 1 as const]),
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
- useGeometryAwareSingletonOutwardPreference &&
1507
- bus.connections.length === 1 &&
1508
- getCornerBandSide(bus.exitEdge, bus.preferredExit)
1509
- ? getDenseSingletonBoundaryGeometry(bus).targetProjection > 0
1510
- : getExitEdgeForDirection(bus.direction) !== bus.exitEdge,
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 leadingWideSingletonBuses =
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 viaProvisionalSingletonBusSet = new Set(
1602
- singletonDeferralCandidates.filter(
1603
- (bus) => !leadingWideSingletonBuses.includes(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) => !viaProvisionalSingletonBusSet.has(bus),
1935
+ (bus) => !viaProvisionalBoundaryBusSet.has(bus),
1620
1936
  )
1621
1937
  const jointViaPoints = useJointBoundaryViaReservation
1622
1938
  ? matchComponentDogboneViaSites(
1623
- [...planeBuses, ...initiallyMatchedBoundaryBuses],
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
- const seedViaPoints =
1957
+ let seedViaPoints =
1637
1958
  jointViaPoints ??
1638
- matchComponentDogboneViaSites([...planeBuses, boundaryBuses[0]!], {
1639
- viaDiameter: this.config.viaDiameter,
1640
- viaHoleDiameter: this.config.viaHoleDiameter,
1641
- traceWidth: this.config.traceWidth,
1642
- clearance: this.config.clearance,
1643
- maximumSearchStates: 20_000,
1644
- preferredBoundaryPerpendicularSideByBusId,
1645
- preferBoundaryOutwardByBusId,
1646
- canShareCopper,
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
- ...leadingWideSingletonBuses,
1661
- ...boundaryBuses.filter(
1662
- (bus) => !leadingWideSingletonBuses.includes(bus),
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: true,
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
- routeParams,
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
- let busPlans = (yield* routeAlternatives(1))[0]
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
- fixedViaPointsByConnectionIndex.has(connection.connectionIndex),
2417
+ candidateMatchingBase.has(connection.connectionIndex),
1817
2418
  )
1818
- const extendedViaPoints =
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(fixedViaPointsByConnectionIndex)
2424
+ new Map(candidateMatchingBase)
1824
2425
  : matchComponentDogboneViaSites(
1825
- [...planeBuses, ...routedBoundaryBuses, candidateBus],
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
- if (!extendedViaPoints) {
1841
- this.setInProgressPlans({
1842
- phase: "reserve-next-dense-boundary-bus",
1843
- plans: matchedPlans,
1844
- strategy: "default",
1845
- unitIndex: denseWorkUnitIndex,
1846
- unitCount: denseWorkUnitCount,
1847
- busId: candidateBus.busId,
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
- [...planeBuses, ...routedBoundaryBuses, candidateBus, laterBus],
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
- ...planeBuses,
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
- return matchComponentDogboneViaSites(
2012
- [...planeBuses, ...boundaryBuses],
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: candidatePlans.flatMap((plan) =>
2023
- plan.segments.map((segment) => ({
2024
- connectionIndex: plan.connectionIndex,
2025
- segment,
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
- if (
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 &&