@tscircuit/fanout-solver 0.0.63 → 0.0.64

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.
@@ -1551,6 +1551,11 @@ export class FanoutSolver extends BaseSolver {
1551
1551
  promotedPlaneReservationBusIds?: readonly string[]
1552
1552
  preferredBoundaryViaPoints?: ReadonlyMap<number, { x: number; y: number }>
1553
1553
  planeReservationRetryCount?: number
1554
+ boundaryRecovery?: {
1555
+ busId: string
1556
+ preferOutward: boolean
1557
+ perpendicularSide: -1 | 1
1558
+ }
1554
1559
  }): Generator<FanoutWorkYield, MixedTerminationState | null, unknown> {
1555
1560
  if (this.config.allowBlindAndBuriedVias) return null
1556
1561
  // An outside-package singleton escape can depend on the completed signal
@@ -1601,12 +1606,16 @@ export class FanoutSolver extends BaseSolver {
1601
1606
  )
1602
1607
  const useConfiguredDensePlaneRouting =
1603
1608
  configuredDensePlaneRouting || useAdaptiveDensePlaneRouting
1609
+ const useBoundaryRecovery = params.boundaryRecovery !== undefined
1610
+ const useJointPlaneRepair =
1611
+ useConfiguredDensePlaneRouting || useBoundaryRecovery
1604
1612
  // A completed boundary assignment remains worth repairing jointly after
1605
1613
  // plane reservations change; a greedy refill can discard that assignment.
1606
- const useAdaptiveJointPlaneSelection =
1607
- useAdaptiveDensePlaneRouting &&
1608
- ((params.planeReservationRetryCount ?? 0) === 0 ||
1609
- Boolean(params.preferredBoundaryViaPoints))
1614
+ const useJointPlaneSelection =
1615
+ useBoundaryRecovery ||
1616
+ (useAdaptiveDensePlaneRouting &&
1617
+ ((params.planeReservationRetryCount ?? 0) === 0 ||
1618
+ Boolean(params.preferredBoundaryViaPoints)))
1610
1619
  const matchLengthsAfterPlanes =
1611
1620
  useConfiguredDensePlaneRouting &&
1612
1621
  params.lengthMatchingStage !== "before-planes"
@@ -1852,7 +1861,9 @@ export class FanoutSolver extends BaseSolver {
1852
1861
  process.env.FANOUT_DEBUG_BOUNDARY_ORDER?.split(",") ??
1853
1862
  (process.env.FANOUT_DEBUG_FIRST_BOUNDARY_BUS
1854
1863
  ? [process.env.FANOUT_DEBUG_FIRST_BOUNDARY_BUS]
1855
- : [])
1864
+ : params.boundaryRecovery
1865
+ ? [params.boundaryRecovery.busId]
1866
+ : [])
1856
1867
  const boundaryBuses =
1857
1868
  debugBoundaryOrder.length > 0
1858
1869
  ? initiallySortedBoundaryBuses.toSorted((first, second) => {
@@ -1914,6 +1925,7 @@ export class FanoutSolver extends BaseSolver {
1914
1925
  ]
1915
1926
  }
1916
1927
  const unroutablePlaneBusIds = new Set<string>()
1928
+ let failedWideBoundaryBus: PreparedBus | undefined
1917
1929
  debugDense(
1918
1930
  "start",
1919
1931
  boundaryBuses.map((bus) => `${bus.busId}:${bus.connections.length}`),
@@ -2015,6 +2027,16 @@ export class FanoutSolver extends BaseSolver {
2015
2027
  if (entersNeighboringSourceField)
2016
2028
  preferBoundaryOutwardByBusId.set(bus.busId, false)
2017
2029
  }
2030
+ if (params.boundaryRecovery) {
2031
+ preferredBoundaryPerpendicularSideByBusId.set(
2032
+ params.boundaryRecovery.busId,
2033
+ params.boundaryRecovery.perpendicularSide,
2034
+ )
2035
+ preferBoundaryOutwardByBusId.set(
2036
+ params.boundaryRecovery.busId,
2037
+ params.boundaryRecovery.preferOutward,
2038
+ )
2039
+ }
2018
2040
  const debugFlippedBoundaryBus = process.env.FANOUT_DEBUG_FLIP_BOUNDARY_BUS
2019
2041
  if (debugFlippedBoundaryBus) {
2020
2042
  preferredBoundaryPerpendicularSideByBusId.set(debugFlippedBoundaryBus, -1)
@@ -2653,6 +2675,7 @@ export class FanoutSolver extends BaseSolver {
2653
2675
  allowBoundarySideViaFallback: bus.connections.length === 1,
2654
2676
  preferCornerBoundaryVia: useConfiguredDensePlaneRouting,
2655
2677
  adaptiveWindingRouteOrder,
2678
+ allowFixedViaReservedExitFallback: useBoundaryRecovery,
2656
2679
  // Retain pad-aligned channels even when plane sites are reserved
2657
2680
  // adaptively; the boundary grid can fence off a turning wide bus.
2658
2681
  alignWindingGridToPads:
@@ -3035,6 +3058,7 @@ export class FanoutSolver extends BaseSolver {
3035
3058
  }
3036
3059
  }
3037
3060
  if (!busPlans) {
3061
+ if (bus.connections.length >= 8) failedWideBoundaryBus ??= bus
3038
3062
  debugDense("route:failed", bus.busId)
3039
3063
  return false
3040
3064
  }
@@ -3365,6 +3389,7 @@ export class FanoutSolver extends BaseSolver {
3365
3389
  promotedAlternatePlaneBusIds: ReadonlySet<string> = new Set(),
3366
3390
  ): Map<number, { x: number; y: number }> | null => {
3367
3391
  feasibleAlternatePlanePlans = []
3392
+ matchedPlaneBusesInRoutingOrder = null
3368
3393
  const fixedBoundaryViaPoints = new Map(
3369
3394
  candidatePlans.flatMap((plan) =>
3370
3395
  plan.via
@@ -3431,11 +3456,16 @@ export class FanoutSolver extends BaseSolver {
3431
3456
  if (retainedViaPoints)
3432
3457
  return new Map([...fixedBoundaryViaPoints, ...retainedViaPoints])
3433
3458
  if (
3434
- useConfiguredDensePlaneRouting ||
3459
+ useJointPlaneRepair ||
3435
3460
  process.env.FANOUT_DEBUG_INCREMENTAL_PLANE_MATCH === "1"
3436
3461
  ) {
3437
3462
  let incrementalViaPoints = new Map(fixedBoundaryViaPoints)
3438
- const matchedPlaneBuses = [...activeBoundaryReservationPlaneBuses]
3463
+ // Reservations guided the boundary search, but their copper is
3464
+ // still uncommitted. Recovery must choose local and longer plane
3465
+ // escapes together instead of locking every provisional site.
3466
+ const matchedPlaneBuses = useBoundaryRecovery
3467
+ ? []
3468
+ : [...activeBoundaryReservationPlaneBuses]
3439
3469
  for (const planeBus of matchedPlaneBuses) {
3440
3470
  for (const connection of planeBus.connections) {
3441
3471
  const reservedPoint = fixedViaPointsByConnectionIndex.get(
@@ -3581,7 +3611,7 @@ export class FanoutSolver extends BaseSolver {
3581
3611
  independentlyUnmatchablePlaneBuses.map((bus) => bus.busId),
3582
3612
  )
3583
3613
  if (
3584
- !useConfiguredDensePlaneRouting &&
3614
+ !useJointPlaneRepair &&
3585
3615
  process.env.FANOUT_DEBUG_ROUTE_UNMATCHED_PLANES !== "1"
3586
3616
  ) {
3587
3617
  return null
@@ -3596,7 +3626,9 @@ export class FanoutSolver extends BaseSolver {
3596
3626
  ? 10_000
3597
3627
  : useConfiguredDensePlaneRouting
3598
3628
  ? 3_000_000
3599
- : 1_000),
3629
+ : useBoundaryRecovery
3630
+ ? 10_000
3631
+ : 1_000),
3600
3632
  )
3601
3633
  const maximumAlternatePlaneRoutes = Number(
3602
3634
  process.env.FANOUT_DEBUG_ALTERNATE_ROUTE_COUNT ??
@@ -3774,7 +3806,7 @@ export class FanoutSolver extends BaseSolver {
3774
3806
  }
3775
3807
  let alternatePlanePlans: FanoutRoutePlan[] | null
3776
3808
  if (
3777
- useConfiguredDensePlaneRouting ||
3809
+ useJointPlaneRepair ||
3778
3810
  process.env.FANOUT_DEBUG_EXACT_COVER_ALTERNATES === "1"
3779
3811
  ) {
3780
3812
  type IndependentPlaneRouteCandidate = {
@@ -3795,7 +3827,7 @@ export class FanoutSolver extends BaseSolver {
3795
3827
  })),
3796
3828
  }),
3797
3829
  )
3798
- if (useAdaptiveJointPlaneSelection) {
3830
+ if (useJointPlaneSelection) {
3799
3831
  const acceptedPlans = [
3800
3832
  ...candidatePlans,
3801
3833
  ...feasibleAlternatePlanePlans,
@@ -4106,7 +4138,7 @@ export class FanoutSolver extends BaseSolver {
4106
4138
  )
4107
4139
  if (!alternatePlanePlans) return null
4108
4140
  feasibleAlternatePlanePlans = alternatePlanePlans
4109
- if (useAdaptiveJointPlaneSelection) {
4141
+ if (useJointPlaneSelection) {
4110
4142
  matchedPlaneBusesInRoutingOrder = []
4111
4143
  return new Map([
4112
4144
  ...fixedBoundaryViaPoints,
@@ -4620,6 +4652,33 @@ export class FanoutSolver extends BaseSolver {
4620
4652
  denseRoutingStrategy: "boundary-aligned",
4621
4653
  })
4622
4654
  if (boundaryAlignedState) return boundaryAlignedState
4655
+ // A later wide bus can be fenced by provisional through-vias even
4656
+ // though its requested exit order is routable. After both existing
4657
+ // grids fail, give that bus first choice of the field and retry the
4658
+ // four dogbone orientations. The marker bounds recursion and enables
4659
+ // joint selection of the remaining uncommitted plane escapes.
4660
+ if (!useBoundaryRecovery && failedWideBoundaryBus) {
4661
+ const busId = failedWideBoundaryBus.busId
4662
+ const outward = preferBoundaryOutwardByBusId.get(busId) ?? true
4663
+ const side = preferredBoundaryPerpendicularSideByBusId.get(busId) ?? 1
4664
+ for (const [preferOutward, perpendicularSide] of [
4665
+ [!outward, -side],
4666
+ [!outward, side],
4667
+ [outward, -side],
4668
+ [outward, side],
4669
+ ] as const) {
4670
+ const recoveredState =
4671
+ yield* this.routeDenseThroughAllMixedTerminationSteps({
4672
+ ...params,
4673
+ boundaryRecovery: {
4674
+ busId,
4675
+ preferOutward,
4676
+ perpendicularSide: perpendicularSide as -1 | 1,
4677
+ },
4678
+ })
4679
+ if (recoveredState) return recoveredState
4680
+ }
4681
+ }
4623
4682
  }
4624
4683
  if (
4625
4684
  !usePadAlignedDenseRouting ||
package/lib/route-bus.ts CHANGED
@@ -75,6 +75,8 @@ export interface RouteBusParams {
75
75
  alignWindingGridToPads?: boolean
76
76
  /** Bounds the final fixed-via winding fallback after ordered attempts. */
77
77
  fixedViaFallbackRouteOrderAttempts?: number
78
+ /** Retry caller-fixed sites while preserving future exit gaps during recovery. */
79
+ allowFixedViaReservedExitFallback?: boolean
78
80
  /** Skip this many otherwise-clear plane escapes when enumerating alternatives. */
79
81
  planeCandidateSkipCount?: number
80
82
  /** Dense corner-band phase that preserves existing lane centers when leading lanes are prepended. */
@@ -2606,6 +2608,7 @@ export function* routeBusAlternativesSteps(
2606
2608
  adaptiveWindingRouteOrder = false,
2607
2609
  alignWindingGridToPads = false,
2608
2610
  fixedViaFallbackRouteOrderAttempts = 24,
2611
+ allowFixedViaReservedExitFallback = false,
2609
2612
  cornerBandTargetTrackOffset,
2610
2613
  } = params
2611
2614
  if (!Number.isInteger(maxAlternatives) || maxAlternatives < 1) {
@@ -2994,6 +2997,29 @@ export function* routeBusAlternativesSteps(
2994
2997
  })),
2995
2998
  ).flat()
2996
2999
  : []),
3000
+ // A single-layer target permutation can also let an early lane
3001
+ // close a future terminal's exit gap. Keep the original attempts
3002
+ // first, then retry the same fixed sites with every exit reserved.
3003
+ ...(allowFixedViaReservedExitFallback &&
3004
+ fixedViaPointsByConnectionIndex &&
3005
+ !getCornerSide(bus) &&
3006
+ windingTargetOrderCount === 1 &&
3007
+ bus.connections.length > 2
3008
+ ? [true, false].map((preferTargetDirectedLaneBias) => ({
3009
+ label: `fixed-vias-reserved-exits-${preferTargetDirectedLaneBias}`,
3010
+ useViaInPad: false,
3011
+ getViaHandedness: () => 0 as const,
3012
+ getViaPoint: (connection: PreparedConnection) =>
3013
+ coordinatedViaPoints.get(connection.connectionIndex)!,
3014
+ maximumRouteOrderAttempts: Math.min(
3015
+ maximumThroughAllRouteOrderAttempts,
3016
+ fixedViaFallbackRouteOrderAttempts,
3017
+ ),
3018
+ windingOrderIndex: 0,
3019
+ preferTargetDirectedLaneBias,
3020
+ reserveTerminalExitPoints: true,
3021
+ }))
3022
+ : []),
2997
3023
  ]
2998
3024
  : []
2999
3025
  const planeTerminationsAlreadyOccupyTheFanout = acceptedPlans.some(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fanout-solver",
3
- "version": "0.0.63",
3
+ "version": "0.0.64",
4
4
  "description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
5
5
  "module": "lib/index.ts",
6
6
  "type": "module",