@tscircuit/fanout-solver 0.0.37 → 0.0.39

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