@tscircuit/fanout-solver 0.0.49 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/fanout-solver.ts +1617 -143
- package/lib/match-component-dogbone-via-sites.ts +101 -14
- package/lib/route-bus.ts +345 -52
- package/lib/route-via-minimal-winding.ts +94 -23
- package/lib/types.ts +11 -0
- package/package.json +1 -1
|
@@ -64,6 +64,12 @@ export interface RouteViaMinimalWindingParams {
|
|
|
64
64
|
gridStepDivisor?: 1 | 2
|
|
65
65
|
/** Bias bounded fixed-site searches toward the remote target band. */
|
|
66
66
|
preferTargetDirectedLaneBias?: boolean
|
|
67
|
+
/** Internal path-only mode used before a boundary-side via is appended. */
|
|
68
|
+
allowSourceLayerRouting?: boolean
|
|
69
|
+
/** Promote a blocked terminal while staying within maximumRouteOrderAttempts. */
|
|
70
|
+
adaptiveRouteOrder?: boolean
|
|
71
|
+
/** Align a fine grid with pad/interstice centers instead of the boundary. */
|
|
72
|
+
alignGridToPads?: boolean
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
export interface RouteViaMinimalWindingProgress {
|
|
@@ -493,6 +499,7 @@ function buildPlan(params: {
|
|
|
493
499
|
layer: connection.sourceLayer,
|
|
494
500
|
}
|
|
495
501
|
const hasSourceDogbone = distance(sourcePoint, terminal.viaPoint) > EPSILON
|
|
502
|
+
const changesLayer = connection.sourceLayer !== targetLayer
|
|
496
503
|
const targetSegments = getSegments(targetLayerPoints, traceWidth, targetLayer)
|
|
497
504
|
const spanLayers = getViaSpanLayers({
|
|
498
505
|
fromLayer: connection.sourceLayer,
|
|
@@ -528,20 +535,24 @@ function buildPlan(params: {
|
|
|
528
535
|
},
|
|
529
536
|
]
|
|
530
537
|
: []),
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
538
|
+
...(changesLayer
|
|
539
|
+
? [
|
|
540
|
+
{
|
|
541
|
+
route_type: "via" as const,
|
|
542
|
+
...terminal.viaPoint,
|
|
543
|
+
from_layer: connection.sourceLayer,
|
|
544
|
+
to_layer: targetLayer,
|
|
545
|
+
via_diameter: viaDiameter,
|
|
546
|
+
via_hole_diameter: viaHoleDiameter,
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
route_type: "wire" as const,
|
|
550
|
+
...terminal.viaPoint,
|
|
551
|
+
width: traceWidth,
|
|
552
|
+
layer: targetLayer,
|
|
553
|
+
},
|
|
554
|
+
]
|
|
555
|
+
: []),
|
|
545
556
|
...targetLayerPoints.slice(1).map((point) => ({
|
|
546
557
|
route_type: "wire" as const,
|
|
547
558
|
...point,
|
|
@@ -589,7 +600,7 @@ function buildPlan(params: {
|
|
|
589
600
|
route,
|
|
590
601
|
},
|
|
591
602
|
segments,
|
|
592
|
-
via,
|
|
603
|
+
via: changesLayer ? via : undefined,
|
|
593
604
|
length: segments.reduce(
|
|
594
605
|
(total, segment) => total + distance(segment.start, segment.end),
|
|
595
606
|
0,
|
|
@@ -624,6 +635,9 @@ export function* routeViaMinimalWindingAlternativesSteps(
|
|
|
624
635
|
reservedVias = [],
|
|
625
636
|
gridStepDivisor = 1,
|
|
626
637
|
preferTargetDirectedLaneBias = false,
|
|
638
|
+
allowSourceLayerRouting = false,
|
|
639
|
+
adaptiveRouteOrder = false,
|
|
640
|
+
alignGridToPads = false,
|
|
627
641
|
} = params
|
|
628
642
|
if (
|
|
629
643
|
maximumRouteOrderAttempts !== undefined &&
|
|
@@ -643,18 +657,33 @@ export function* routeViaMinimalWindingAlternativesSteps(
|
|
|
643
657
|
if (
|
|
644
658
|
terminals.length === 0 ||
|
|
645
659
|
!bus.exitEdge ||
|
|
646
|
-
|
|
647
|
-
(
|
|
648
|
-
|
|
660
|
+
(!allowSourceLayerRouting &&
|
|
661
|
+
terminals.some(
|
|
662
|
+
(terminal) => terminal.connection.sourceLayer === targetLayer,
|
|
663
|
+
))
|
|
649
664
|
) {
|
|
650
665
|
return []
|
|
651
666
|
}
|
|
652
667
|
|
|
653
|
-
const
|
|
668
|
+
const baseGridStep = (traceWidth + clearance) / gridStepDivisor
|
|
669
|
+
const pitch = Math.min(bus.pitchX, bus.pitchY)
|
|
670
|
+
const alignGridToPitch =
|
|
671
|
+
alignGridToPads && gridStepDivisor === 2 && Number.isFinite(pitch)
|
|
672
|
+
const gridStep = alignGridToPitch
|
|
673
|
+
? pitch / (2 * Math.ceil(pitch / (2 * baseGridStep)))
|
|
674
|
+
: baseGridStep
|
|
654
675
|
if (!Number.isFinite(gridStep) || gridStep <= 0) return []
|
|
655
676
|
const { minX, maxX, minY, maxY } = bus.sharedBoundary
|
|
656
|
-
const
|
|
657
|
-
const
|
|
677
|
+
const originX = bus.xCoordinates[0] ?? minX
|
|
678
|
+
const originY = bus.yCoordinates[0] ?? minY
|
|
679
|
+
const gridMinX = alignGridToPitch
|
|
680
|
+
? originX + Math.ceil((minX - originX) / gridStep) * gridStep
|
|
681
|
+
: minX
|
|
682
|
+
const gridMinY = alignGridToPitch
|
|
683
|
+
? originY + Math.ceil((minY - originY) / gridStep) * gridStep
|
|
684
|
+
: minY
|
|
685
|
+
const columnCount = Math.floor((maxX - gridMinX) / gridStep) + 1
|
|
686
|
+
const rowCount = Math.floor((maxY - gridMinY) / gridStep) + 1
|
|
658
687
|
const nodeCount = columnCount * rowCount
|
|
659
688
|
if (columnCount < 2 || rowCount < 2 || nodeCount > MAX_GRID_NODE_COUNT) {
|
|
660
689
|
return []
|
|
@@ -665,7 +694,7 @@ export function* routeViaMinimalWindingAlternativesSteps(
|
|
|
665
694
|
return {
|
|
666
695
|
column,
|
|
667
696
|
row,
|
|
668
|
-
point: { x:
|
|
697
|
+
point: { x: gridMinX + column * gridStep, y: gridMinY + row * gridStep },
|
|
669
698
|
}
|
|
670
699
|
})
|
|
671
700
|
const sampledGridPoints = includeVisualization
|
|
@@ -797,6 +826,18 @@ export function* routeViaMinimalWindingAlternativesSteps(
|
|
|
797
826
|
}
|
|
798
827
|
for (const blocker of acceptedAttemptSegments) {
|
|
799
828
|
if (sharesNet(connectionName, blocker.connectionName)) continue
|
|
829
|
+
const margin = (segment.width + blocker.segment.width) / 2 + clearance
|
|
830
|
+
if (
|
|
831
|
+
Math.max(segment.start.x, segment.end.x) + margin <
|
|
832
|
+
Math.min(blocker.segment.start.x, blocker.segment.end.x) ||
|
|
833
|
+
Math.min(segment.start.x, segment.end.x) - margin >
|
|
834
|
+
Math.max(blocker.segment.start.x, blocker.segment.end.x) ||
|
|
835
|
+
Math.max(segment.start.y, segment.end.y) + margin <
|
|
836
|
+
Math.min(blocker.segment.start.y, blocker.segment.end.y) ||
|
|
837
|
+
Math.min(segment.start.y, segment.end.y) - margin >
|
|
838
|
+
Math.max(blocker.segment.start.y, blocker.segment.end.y)
|
|
839
|
+
)
|
|
840
|
+
continue
|
|
800
841
|
if (
|
|
801
842
|
distanceSegmentToSegment(
|
|
802
843
|
segment.start,
|
|
@@ -1283,6 +1324,9 @@ export function* routeViaMinimalWindingAlternativesSteps(
|
|
|
1283
1324
|
const initialRouteOrderFactories: Array<
|
|
1284
1325
|
() => readonly ViaMinimalWindingTerminal[]
|
|
1285
1326
|
> = []
|
|
1327
|
+
if (alignGridToPads && preferTargetDirectedLaneBias && viasAreBeforeTargets) {
|
|
1328
|
+
initialRouteOrderFactories.push(() => targetOrderedTerminals)
|
|
1329
|
+
}
|
|
1286
1330
|
if (viasAreBeforeTargets) {
|
|
1287
1331
|
initialRouteOrderFactories.push(() => [
|
|
1288
1332
|
...targetOrderedTerminals.slice(1),
|
|
@@ -1340,11 +1384,28 @@ export function* routeViaMinimalWindingAlternativesSteps(
|
|
|
1340
1384
|
getItemKey: (terminal) => terminal.connection.connection.name,
|
|
1341
1385
|
maximumOrderCount: maximumRouteOrderCount,
|
|
1342
1386
|
})
|
|
1387
|
+
const pendingRouteOrders: ViaMinimalWindingTerminal[][] = []
|
|
1388
|
+
const seenAdaptiveOrders = new Set<string>()
|
|
1389
|
+
const adaptiveRouteOrders = function* () {
|
|
1390
|
+
while (true) {
|
|
1391
|
+
const pending = pendingRouteOrders.shift()
|
|
1392
|
+
const next = pending
|
|
1393
|
+
? { done: false, value: pending }
|
|
1394
|
+
: routeOrders.next()
|
|
1395
|
+
if (next.done) return
|
|
1396
|
+
const key = next.value
|
|
1397
|
+
.map((terminal) => terminal.connection.connection.name)
|
|
1398
|
+
.join("|")
|
|
1399
|
+
if (seenAdaptiveOrders.has(key)) continue
|
|
1400
|
+
seenAdaptiveOrders.add(key)
|
|
1401
|
+
yield next.value
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1343
1404
|
|
|
1344
1405
|
const alternatives: FanoutRoutePlan[][] = []
|
|
1345
1406
|
const seenAlternativeKeys = new Set<string>()
|
|
1346
1407
|
let routeOrderAttemptCount = 0
|
|
1347
|
-
for (const routeOrder of
|
|
1408
|
+
for (const routeOrder of adaptiveRouteOrders()) {
|
|
1348
1409
|
for (const laneBias of laneBiases) {
|
|
1349
1410
|
if (
|
|
1350
1411
|
maximumRouteOrderAttempts !== undefined &&
|
|
@@ -1415,6 +1476,16 @@ export function* routeViaMinimalWindingAlternativesSteps(
|
|
|
1415
1476
|
: {}),
|
|
1416
1477
|
}
|
|
1417
1478
|
if (!points) {
|
|
1479
|
+
if (
|
|
1480
|
+
adaptiveRouteOrder &&
|
|
1481
|
+
maximumRouteOrderAttempts !== undefined &&
|
|
1482
|
+
terminalIndex > 0
|
|
1483
|
+
) {
|
|
1484
|
+
const others = routeOrder.filter(
|
|
1485
|
+
(candidate) => candidate !== terminal,
|
|
1486
|
+
)
|
|
1487
|
+
pendingRouteOrders.push([terminal, ...others])
|
|
1488
|
+
}
|
|
1418
1489
|
failed = true
|
|
1419
1490
|
break
|
|
1420
1491
|
}
|
package/lib/types.ts
CHANGED
|
@@ -197,6 +197,17 @@ export interface FanoutSolverOptions {
|
|
|
197
197
|
allowBlindAndBuriedVias?: boolean
|
|
198
198
|
/** Allow branches belonging to the same electrical net to share copper. */
|
|
199
199
|
allowSameNetMerges?: boolean
|
|
200
|
+
/**
|
|
201
|
+
* Plane buses whose adjacent dogbone sites must be reserved before routing
|
|
202
|
+
* dense through-via boundary buses. This is an advanced deterministic hint
|
|
203
|
+
* for fields where later signal copper would otherwise consume those sites.
|
|
204
|
+
*/
|
|
205
|
+
densePlaneReservationBusIds?: readonly string[]
|
|
206
|
+
/**
|
|
207
|
+
* Plane buses that should participate in the bounded alternate-route CSP
|
|
208
|
+
* instead of using an adjacent component dogbone.
|
|
209
|
+
*/
|
|
210
|
+
denseUnrestrictedPlaneRoutingBusIds?: readonly string[]
|
|
200
211
|
singleLayerPushAndShove?: boolean
|
|
201
212
|
/**
|
|
202
213
|
* When preferred single-layer exits cannot coexist, allow a global
|