@tscircuit/fanout-solver 0.0.35 → 0.0.37
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/README.md +14 -0
- package/lib/boundary-exit.ts +52 -0
- package/lib/build-output.ts +32 -10
- package/lib/fanout-exit-position.ts +78 -0
- package/lib/fanout-solver.ts +267 -38
- package/lib/index.ts +16 -13
- package/lib/prepare-buses.ts +261 -36
- package/lib/route-bus.ts +543 -59
- package/lib/route-single-layer-adaptive-exits.ts +2 -2
- package/lib/route-single-layer-push-shove.ts +2 -1
- package/lib/route-via-minimal-winding.ts +898 -0
- package/lib/types.ts +63 -3
- package/lib/validate-fanout-solution.ts +63 -11
- package/package.json +1 -1
package/lib/fanout-solver.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { SimpleRouteJson } from "@tscircuit/capacity-autorouter"
|
|
2
2
|
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
3
3
|
import type { GraphicsObject } from "graphics-debug"
|
|
4
|
+
import { getCornerBandSide } from "./boundary-exit"
|
|
4
5
|
import { buildOutputSimpleRouteJson } from "./build-output"
|
|
5
6
|
import {
|
|
6
|
-
completeOriginalEndpoints,
|
|
7
7
|
type CompleteOriginalEndpointsResult,
|
|
8
|
+
completeOriginalEndpoints,
|
|
8
9
|
} from "./complete-original-endpoints"
|
|
9
10
|
import { generateLayerAssignments, getCopperLayerNames } from "./layer-names"
|
|
10
11
|
import {
|
|
@@ -12,14 +13,12 @@ import {
|
|
|
12
13
|
resolveAvailableBoundaryRegions,
|
|
13
14
|
} from "./prepare-buses"
|
|
14
15
|
import {
|
|
16
|
+
type RouteBusStaticClearanceCache,
|
|
15
17
|
routeBus,
|
|
16
18
|
routeBusAlternatives,
|
|
17
|
-
type RouteBusStaticClearanceCache,
|
|
18
19
|
} from "./route-bus"
|
|
19
20
|
import { routeSingleLayerWithAdaptiveExits } from "./route-single-layer-adaptive-exits"
|
|
20
21
|
import { routeSingleLayerWithPushAndShove } from "./route-single-layer-push-shove"
|
|
21
|
-
import { validateFanoutSolution } from "./validate-fanout-solution"
|
|
22
|
-
import { visualizeSimpleRouteJson } from "./visualize-simple-route-json"
|
|
23
22
|
import type {
|
|
24
23
|
AssignmentAttempt,
|
|
25
24
|
Bounds,
|
|
@@ -30,6 +29,8 @@ import type {
|
|
|
30
29
|
FanoutSolverOutput,
|
|
31
30
|
PreparedBus,
|
|
32
31
|
} from "./types"
|
|
32
|
+
import { validateFanoutSolution } from "./validate-fanout-solution"
|
|
33
|
+
import { visualizeSimpleRouteJson } from "./visualize-simple-route-json"
|
|
33
34
|
|
|
34
35
|
interface ResolvedFanoutConfig {
|
|
35
36
|
traceWidth: number
|
|
@@ -146,6 +147,45 @@ function resolveConfig(
|
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
function validateCornerBandCapacities(
|
|
151
|
+
buses: readonly PreparedBus[],
|
|
152
|
+
config: ResolvedFanoutConfig,
|
|
153
|
+
): void {
|
|
154
|
+
const checkedBands = new Set<string>()
|
|
155
|
+
const exitPitch = Math.max(
|
|
156
|
+
config.traceWidth + config.clearance,
|
|
157
|
+
config.viaDiameter + config.clearance,
|
|
158
|
+
)
|
|
159
|
+
// Keep the block clear of the physical end of the edge and leave one
|
|
160
|
+
// unoccupied via-pitch between the minimum and maximum quarter bands.
|
|
161
|
+
const endInset = Math.max(
|
|
162
|
+
config.viaDiameter / 2 + config.clearance,
|
|
163
|
+
exitPitch,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
for (const bus of buses) {
|
|
167
|
+
const side = getCornerBandSide(bus.exitEdge, bus.preferredExit)
|
|
168
|
+
if (!bus.exitEdge || !side) continue
|
|
169
|
+
const bandKey = `${bus.exitEdge}:${side}`
|
|
170
|
+
if (checkedBands.has(bandKey)) continue
|
|
171
|
+
checkedBands.add(bandKey)
|
|
172
|
+
|
|
173
|
+
const edgeLength =
|
|
174
|
+
bus.exitEdge === "left" || bus.exitEdge === "right"
|
|
175
|
+
? bus.sharedBoundary.maxY - bus.sharedBoundary.minY
|
|
176
|
+
: bus.sharedBoundary.maxX - bus.sharedBoundary.minX
|
|
177
|
+
const connectionCount =
|
|
178
|
+
bus.cornerBandConnectionCount ?? bus.connections.length
|
|
179
|
+
const halfTrackSpan = ((connectionCount - 1) * exitPitch) / 2
|
|
180
|
+
const availableHalfTrackSpan = edgeLength / 4 - endInset
|
|
181
|
+
if (halfTrackSpan > availableHalfTrackSpan + 1e-6) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`FanoutSolver: ${side} band on the ${bus.exitEdge} edge cannot fit ${connectionCount} via-safe exits`,
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
149
189
|
function assignmentLoadPenalty(
|
|
150
190
|
assignment: Readonly<Record<string, string>>,
|
|
151
191
|
buses: readonly PreparedBus[],
|
|
@@ -179,6 +219,7 @@ function getPlanViaCount(plans: readonly FanoutRoutePlan[]): number {
|
|
|
179
219
|
(count, plan) =>
|
|
180
220
|
count +
|
|
181
221
|
Number(Boolean(plan.via)) +
|
|
222
|
+
(plan.additionalVias?.length ?? 0) +
|
|
182
223
|
Number(Boolean(plan.planeEndpointVia)),
|
|
183
224
|
0,
|
|
184
225
|
)
|
|
@@ -217,6 +258,27 @@ function busUsesDestinationGuidedTracks(bus: PreparedBus): boolean {
|
|
|
217
258
|
})
|
|
218
259
|
}
|
|
219
260
|
|
|
261
|
+
function getCommonExplicitExitTargetLayer(
|
|
262
|
+
bus: PreparedBus,
|
|
263
|
+
): string | undefined {
|
|
264
|
+
if (
|
|
265
|
+
bus.connections.length === 0 ||
|
|
266
|
+
bus.connections.some(
|
|
267
|
+
(connection) =>
|
|
268
|
+
!connection.hasExplicitLayeredExitTarget ||
|
|
269
|
+
!connection.exitTargetPoint?.layer,
|
|
270
|
+
)
|
|
271
|
+
) {
|
|
272
|
+
return undefined
|
|
273
|
+
}
|
|
274
|
+
const targetLayers = new Set(
|
|
275
|
+
bus.connections.map((connection) => connection.exitTargetPoint!.layer!),
|
|
276
|
+
)
|
|
277
|
+
if (targetLayers.size !== 1) return undefined
|
|
278
|
+
const [targetLayer] = targetLayers
|
|
279
|
+
return targetLayer
|
|
280
|
+
}
|
|
281
|
+
|
|
220
282
|
function busIsOnOutwardComponentEdge(bus: PreparedBus): boolean {
|
|
221
283
|
const isHorizontal = bus.direction === "left" || bus.direction === "right"
|
|
222
284
|
const directionalCoordinates = isHorizontal
|
|
@@ -287,7 +349,14 @@ function createInitialLayerAssignment(params: {
|
|
|
287
349
|
const viaLayers = routableEscapeLayers.filter(
|
|
288
350
|
(layer) => layer !== sourceLayer,
|
|
289
351
|
)
|
|
352
|
+
const commonExitTargetLayer = getCommonExplicitExitTargetLayer(bus)
|
|
290
353
|
if (
|
|
354
|
+
commonExitTargetLayer &&
|
|
355
|
+
routableEscapeLayers.includes(commonExitTargetLayer)
|
|
356
|
+
) {
|
|
357
|
+
assignment[bus.busId] = commonExitTargetLayer
|
|
358
|
+
} else if (
|
|
359
|
+
!busUsesCoordinatedWinding(bus) &&
|
|
291
360
|
routableEscapeLayers.includes(sourceLayer) &&
|
|
292
361
|
(busUsesDestinationGuidedTracks(bus) || busIsOnOutwardComponentEdge(bus))
|
|
293
362
|
) {
|
|
@@ -328,6 +397,17 @@ function prioritizeLayerAssignment(params: {
|
|
|
328
397
|
].slice(0, maxAssignments)
|
|
329
398
|
}
|
|
330
399
|
|
|
400
|
+
function busUsesCoordinatedWinding(bus: PreparedBus): boolean {
|
|
401
|
+
return Boolean(
|
|
402
|
+
bus.exitEdge &&
|
|
403
|
+
bus.termination.type === "boundary" &&
|
|
404
|
+
bus.connections.length > 0 &&
|
|
405
|
+
bus.connections.every(
|
|
406
|
+
(connection) => connection.hasExplicitLayeredExitTarget === true,
|
|
407
|
+
),
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
|
|
331
411
|
function getCandidateEscapeLayersForBus(params: {
|
|
332
412
|
bus: PreparedBus
|
|
333
413
|
srj: SimpleRouteJson
|
|
@@ -340,6 +420,10 @@ function getCandidateEscapeLayersForBus(params: {
|
|
|
340
420
|
busAllowedLayers === undefined
|
|
341
421
|
? config.escapeLayers
|
|
342
422
|
: config.escapeLayers.filter((layer) => busAllowedLayers.includes(layer))
|
|
423
|
+
// A coordinated winding route is deliberately planned with the other buses'
|
|
424
|
+
// committed escape vias present. Testing it in isolation is both expensive
|
|
425
|
+
// and can reject a layer whose shared via field guides a valid bus ordering.
|
|
426
|
+
if (busUsesCoordinatedWinding(bus)) return allowedEscapeLayers
|
|
343
427
|
const individuallyRoutableLayers = allowedEscapeLayers.filter(
|
|
344
428
|
(targetLayer) =>
|
|
345
429
|
routeBus({
|
|
@@ -411,7 +495,21 @@ export class FanoutSolver extends BaseSolver {
|
|
|
411
495
|
}
|
|
412
496
|
this.config = resolveConfig(inputSrj, options)
|
|
413
497
|
this.preparedBuses = prepareFanoutBuses(this.routingSrj, options)
|
|
498
|
+
validateCornerBandCapacities(this.preparedBuses, this.config)
|
|
414
499
|
for (const bus of this.preparedBuses) {
|
|
500
|
+
for (const connection of bus.connections) {
|
|
501
|
+
if (!connection.hasExplicitLayeredExitTarget) continue
|
|
502
|
+
const targetLayer = connection.exitTargetPoint?.layer
|
|
503
|
+
if (
|
|
504
|
+
typeof targetLayer !== "string" ||
|
|
505
|
+
targetLayer.length === 0 ||
|
|
506
|
+
!this.config.layerNames.includes(targetLayer)
|
|
507
|
+
) {
|
|
508
|
+
throw new Error(
|
|
509
|
+
`FanoutSolver: connection exit target for "${connection.connection.name}" uses unavailable layer "${String(targetLayer)}"`,
|
|
510
|
+
)
|
|
511
|
+
}
|
|
512
|
+
}
|
|
415
513
|
for (const allowedLayer of bus.allowedLayers ?? []) {
|
|
416
514
|
if (!this.config.layerNames.includes(allowedLayer)) {
|
|
417
515
|
throw new Error(
|
|
@@ -430,6 +528,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
430
528
|
`FanoutSolver: bus "${bus.busId}" has no allowed layer in escapeLayers`,
|
|
431
529
|
)
|
|
432
530
|
}
|
|
531
|
+
bus.routableEscapeLayers = this.config.escapeLayers.filter(
|
|
532
|
+
(layer) => bus.allowedLayers?.includes(layer) ?? true,
|
|
533
|
+
)
|
|
433
534
|
if (bus.termination.type !== "plane") continue
|
|
434
535
|
const planeLayer = bus.termination.layer
|
|
435
536
|
if (!this.config.layerNames.includes(planeLayer)) {
|
|
@@ -566,7 +667,13 @@ export class FanoutSolver extends BaseSolver {
|
|
|
566
667
|
let failedBusIds: string[] = []
|
|
567
668
|
let blockingBusCounts = new Map<string, number>()
|
|
568
669
|
const isSingleLayerFanout = this.config.escapeLayers.length === 1
|
|
569
|
-
|
|
670
|
+
const useSingleLayerPushAndShove =
|
|
671
|
+
isSingleLayerFanout &&
|
|
672
|
+
this.config.singleLayerPushAndShove &&
|
|
673
|
+
!this.preparedBuses.some(
|
|
674
|
+
(bus) => bus.exitEdge && bus.preferredExit?.includes("-"),
|
|
675
|
+
)
|
|
676
|
+
if (useSingleLayerPushAndShove) {
|
|
570
677
|
const singleLayerParams = {
|
|
571
678
|
srj: this.routingSrj,
|
|
572
679
|
buses: this.preparedBuses,
|
|
@@ -590,10 +697,22 @@ export class FanoutSolver extends BaseSolver {
|
|
|
590
697
|
failedBusIds.push(...this.preparedBuses.map((bus) => bus.busId))
|
|
591
698
|
}
|
|
592
699
|
}
|
|
593
|
-
const busesInRoutingOrder = [...this.preparedBuses].sort(
|
|
594
|
-
(a
|
|
700
|
+
const busesInRoutingOrder = [...this.preparedBuses].sort((a, b) => {
|
|
701
|
+
const aUsesCoordinatedWinding = busUsesCoordinatedWinding(a)
|
|
702
|
+
const bUsesCoordinatedWinding = busUsesCoordinatedWinding(b)
|
|
703
|
+
const aLayerIndex = this.config.layerNames.indexOf(
|
|
704
|
+
busLayerAssignments[a.busId] ?? "",
|
|
705
|
+
)
|
|
706
|
+
const bLayerIndex = this.config.layerNames.indexOf(
|
|
707
|
+
busLayerAssignments[b.busId] ?? "",
|
|
708
|
+
)
|
|
709
|
+
return (
|
|
595
710
|
Number(b.termination.type === "plane") -
|
|
596
711
|
Number(a.termination.type === "plane") ||
|
|
712
|
+
Number(bUsesCoordinatedWinding) - Number(aUsesCoordinatedWinding) ||
|
|
713
|
+
(aUsesCoordinatedWinding && bUsesCoordinatedWinding
|
|
714
|
+
? bLayerIndex - aLayerIndex
|
|
715
|
+
: 0) ||
|
|
597
716
|
(routingStrategy === "group-by-layer"
|
|
598
717
|
? (busLayerAssignments[a.busId] ?? "").localeCompare(
|
|
599
718
|
busLayerAssignments[b.busId] ?? "",
|
|
@@ -605,13 +724,12 @@ export class FanoutSolver extends BaseSolver {
|
|
|
605
724
|
: b.connections.length - a.connections.length ||
|
|
606
725
|
(routingStrategy === "deep-first"
|
|
607
726
|
? getBusDistanceToBoundary(b) - getBusDistanceToBoundary(a)
|
|
608
|
-
: getBusDistanceToBoundary(a) - getBusDistanceToBoundary(b)))
|
|
609
|
-
|
|
727
|
+
: getBusDistanceToBoundary(a) - getBusDistanceToBoundary(b)))
|
|
728
|
+
)
|
|
729
|
+
})
|
|
610
730
|
|
|
611
731
|
let routingPrefixKey = `${routingStrategy}|`
|
|
612
|
-
for (const bus of
|
|
613
|
-
? []
|
|
614
|
-
: busesInRoutingOrder) {
|
|
732
|
+
for (const bus of useSingleLayerPushAndShove ? [] : busesInRoutingOrder) {
|
|
615
733
|
const targetLayer = busLayerAssignments[bus.busId]
|
|
616
734
|
if (!targetLayer) {
|
|
617
735
|
throw new Error(
|
|
@@ -735,7 +853,8 @@ export class FanoutSolver extends BaseSolver {
|
|
|
735
853
|
)
|
|
736
854
|
if (
|
|
737
855
|
bestAttempt.summary.routedConnectionCount ===
|
|
738
|
-
|
|
856
|
+
this.inputSrj.connections.length &&
|
|
857
|
+
this.getCoordinatedAdditionalViaCount(bestAttempt.plans) === 0
|
|
739
858
|
) {
|
|
740
859
|
return bestAttempt
|
|
741
860
|
}
|
|
@@ -746,12 +865,13 @@ export class FanoutSolver extends BaseSolver {
|
|
|
746
865
|
busLayerAssignments,
|
|
747
866
|
routingStrategy,
|
|
748
867
|
)
|
|
749
|
-
if (attempt
|
|
868
|
+
if (this.isAttemptBetter(attempt, bestAttempt)) {
|
|
750
869
|
bestAttempt = attempt
|
|
751
870
|
}
|
|
752
871
|
if (
|
|
753
872
|
bestAttempt.summary.routedConnectionCount ===
|
|
754
|
-
|
|
873
|
+
this.inputSrj.connections.length &&
|
|
874
|
+
this.getCoordinatedAdditionalViaCount(bestAttempt.plans) === 0
|
|
755
875
|
) {
|
|
756
876
|
return bestAttempt
|
|
757
877
|
}
|
|
@@ -784,7 +904,24 @@ export class FanoutSolver extends BaseSolver {
|
|
|
784
904
|
return null
|
|
785
905
|
}
|
|
786
906
|
|
|
907
|
+
const getMaximumViaSpan = (bus: PreparedBus): number => {
|
|
908
|
+
const sourceLayerIndex = this.config.layerNames.indexOf(
|
|
909
|
+
bus.connections[0]?.sourceLayer ?? "",
|
|
910
|
+
)
|
|
911
|
+
const candidateLayers =
|
|
912
|
+
bus.termination.type === "plane"
|
|
913
|
+
? [bus.termination.layer]
|
|
914
|
+
: (this.escapeLayersByBusId[bus.busId] ?? this.config.escapeLayers)
|
|
915
|
+
return Math.max(
|
|
916
|
+
0,
|
|
917
|
+
...candidateLayers.map((layer) =>
|
|
918
|
+
Math.abs(this.config.layerNames.indexOf(layer) - sourceLayerIndex),
|
|
919
|
+
),
|
|
920
|
+
)
|
|
921
|
+
}
|
|
787
922
|
const busesInSearchOrder = [...this.preparedBuses].sort((a, b) => {
|
|
923
|
+
const aUsesCoordinatedWinding = busUsesCoordinatedWinding(a)
|
|
924
|
+
const bUsesCoordinatedWinding = busUsesCoordinatedWinding(b)
|
|
788
925
|
const aLayerCount =
|
|
789
926
|
a.termination.type === "plane"
|
|
790
927
|
? 1
|
|
@@ -798,6 +935,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
798
935
|
return (
|
|
799
936
|
Number(b.termination.type === "plane") -
|
|
800
937
|
Number(a.termination.type === "plane") ||
|
|
938
|
+
Number(bUsesCoordinatedWinding) - Number(aUsesCoordinatedWinding) ||
|
|
939
|
+
(aUsesCoordinatedWinding && bUsesCoordinatedWinding
|
|
940
|
+
? getMaximumViaSpan(b) - getMaximumViaSpan(a)
|
|
941
|
+
: 0) ||
|
|
801
942
|
(groupByDirection ? a.direction.localeCompare(b.direction) : 0) ||
|
|
802
943
|
aLayerCount - bLayerCount ||
|
|
803
944
|
b.componentObstacles.length - a.componentObstacles.length ||
|
|
@@ -835,7 +976,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
835
976
|
return count
|
|
836
977
|
}
|
|
837
978
|
const sourceLayer = bus.connections[0]?.sourceLayer
|
|
838
|
-
|
|
979
|
+
const preferredLayer =
|
|
980
|
+
getCommonExplicitExitTargetLayer(bus) ?? sourceLayer
|
|
981
|
+
return state.assignment[bus.busId] === preferredLayer
|
|
839
982
|
? count
|
|
840
983
|
: count + bus.connections.length
|
|
841
984
|
},
|
|
@@ -875,10 +1018,13 @@ export class FanoutSolver extends BaseSolver {
|
|
|
875
1018
|
)
|
|
876
1019
|
}
|
|
877
1020
|
const sourceLayer = bus.connections[0]?.sourceLayer
|
|
1021
|
+
const commonExitTargetLayer = getCommonExplicitExitTargetLayer(bus)
|
|
878
1022
|
const preferSourceLayer = busUsesDestinationGuidedTracks(bus)
|
|
879
1023
|
const orderedLayers = candidateLayers.toSorted(
|
|
880
1024
|
(first, second) =>
|
|
881
1025
|
(layerLoads.get(first) ?? 0) - (layerLoads.get(second) ?? 0) ||
|
|
1026
|
+
Number(second === commonExitTargetLayer) -
|
|
1027
|
+
Number(first === commonExitTargetLayer) ||
|
|
882
1028
|
(preferSourceLayer
|
|
883
1029
|
? Number(second === sourceLayer) - Number(first === sourceLayer)
|
|
884
1030
|
: Number(first === sourceLayer) -
|
|
@@ -918,6 +1064,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
918
1064
|
|
|
919
1065
|
if (nextStates.length === 0) return null
|
|
920
1066
|
nextStates.sort((first, second) => {
|
|
1067
|
+
const additionalViaDifference =
|
|
1068
|
+
this.getCoordinatedAdditionalViaCount(first.plans) -
|
|
1069
|
+
this.getCoordinatedAdditionalViaCount(second.plans)
|
|
1070
|
+
if (additionalViaDifference !== 0) return additionalViaDifference
|
|
921
1071
|
const scoreDifference = getStateScore(first) - getStateScore(second)
|
|
922
1072
|
if (Math.abs(scoreDifference) > 1e-9) return scoreDifference
|
|
923
1073
|
return JSON.stringify(first.assignment).localeCompare(
|
|
@@ -936,19 +1086,23 @@ export class FanoutSolver extends BaseSolver {
|
|
|
936
1086
|
}
|
|
937
1087
|
}
|
|
938
1088
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
const
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
!this.validateCompletePlans(
|
|
949
|
-
|
|
950
|
-
|
|
1089
|
+
let bestState: GroupedBeamState | undefined
|
|
1090
|
+
let outputSrj: SimpleRouteJson | undefined
|
|
1091
|
+
for (const state of states) {
|
|
1092
|
+
if (state.plans.length !== this.inputSrj.connections.length) continue
|
|
1093
|
+
const candidateOutput = buildOutputSimpleRouteJson({
|
|
1094
|
+
inputSrj: this.inputSrj,
|
|
1095
|
+
plans: state.plans,
|
|
1096
|
+
layerNames: this.config.layerNames,
|
|
1097
|
+
})
|
|
1098
|
+
if (!this.validateCompletePlans(state.plans, candidateOutput).valid) {
|
|
1099
|
+
continue
|
|
1100
|
+
}
|
|
1101
|
+
bestState = state
|
|
1102
|
+
outputSrj = candidateOutput
|
|
1103
|
+
break
|
|
951
1104
|
}
|
|
1105
|
+
if (!bestState || !outputSrj) return null
|
|
952
1106
|
const score =
|
|
953
1107
|
bestState.plans.length === this.inputSrj.connections.length
|
|
954
1108
|
? bestState.plans.reduce((total, plan) => total + plan.length, 0) +
|
|
@@ -1071,6 +1225,66 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1071
1225
|
)
|
|
1072
1226
|
}
|
|
1073
1227
|
|
|
1228
|
+
private getCoordinatedAdditionalViaCount(
|
|
1229
|
+
plans: readonly FanoutRoutePlan[],
|
|
1230
|
+
): number {
|
|
1231
|
+
const coordinatedBusIds = new Set(
|
|
1232
|
+
this.preparedBuses
|
|
1233
|
+
.filter(busUsesCoordinatedWinding)
|
|
1234
|
+
.map((bus) => bus.busId),
|
|
1235
|
+
)
|
|
1236
|
+
return plans.reduce(
|
|
1237
|
+
(count, plan) =>
|
|
1238
|
+
count +
|
|
1239
|
+
(coordinatedBusIds.has(plan.busId)
|
|
1240
|
+
? (plan.additionalVias?.length ?? 0)
|
|
1241
|
+
: 0),
|
|
1242
|
+
0,
|
|
1243
|
+
)
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
private isAttemptBetter(
|
|
1247
|
+
candidate: AssignmentAttempt,
|
|
1248
|
+
current: AssignmentAttempt,
|
|
1249
|
+
): boolean {
|
|
1250
|
+
if (
|
|
1251
|
+
candidate.summary.routedConnectionCount !==
|
|
1252
|
+
current.summary.routedConnectionCount
|
|
1253
|
+
) {
|
|
1254
|
+
return (
|
|
1255
|
+
candidate.summary.routedConnectionCount >
|
|
1256
|
+
current.summary.routedConnectionCount
|
|
1257
|
+
)
|
|
1258
|
+
}
|
|
1259
|
+
if (candidate.summary.routedBusCount !== current.summary.routedBusCount) {
|
|
1260
|
+
return candidate.summary.routedBusCount > current.summary.routedBusCount
|
|
1261
|
+
}
|
|
1262
|
+
const candidateAdditionalVias = this.getCoordinatedAdditionalViaCount(
|
|
1263
|
+
candidate.plans,
|
|
1264
|
+
)
|
|
1265
|
+
const currentAdditionalVias = this.getCoordinatedAdditionalViaCount(
|
|
1266
|
+
current.plans,
|
|
1267
|
+
)
|
|
1268
|
+
if (candidateAdditionalVias !== currentAdditionalVias) {
|
|
1269
|
+
return candidateAdditionalVias < currentAdditionalVias
|
|
1270
|
+
}
|
|
1271
|
+
return candidate.summary.score < current.summary.score
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
private hasGloballyViaMinimalBestAttempt(): boolean {
|
|
1275
|
+
if (!this.hasCompleteBestAttempt() || !this.bestAttempt) return false
|
|
1276
|
+
if (
|
|
1277
|
+
this.preparedBuses.length === 0 ||
|
|
1278
|
+
!this.preparedBuses.every(busUsesCoordinatedWinding)
|
|
1279
|
+
) {
|
|
1280
|
+
return false
|
|
1281
|
+
}
|
|
1282
|
+
return this.bestAttempt.plans.every(
|
|
1283
|
+
(plan) =>
|
|
1284
|
+
plan.via !== undefined && (plan.additionalVias?.length ?? 0) === 0,
|
|
1285
|
+
)
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1074
1288
|
private shouldEvaluateGroupedBeam(): boolean {
|
|
1075
1289
|
if (this.groupedBeamEvaluated || this.nextAssignmentIndex === 0) {
|
|
1076
1290
|
return false
|
|
@@ -1085,6 +1299,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1085
1299
|
}
|
|
1086
1300
|
|
|
1087
1301
|
override _step(): void {
|
|
1302
|
+
if (
|
|
1303
|
+
this.nextAssignmentIndex > 0 &&
|
|
1304
|
+
this.hasGloballyViaMinimalBestAttempt()
|
|
1305
|
+
) {
|
|
1306
|
+
this.completeBestAttemptEndpoints()
|
|
1307
|
+
this.solved = true
|
|
1308
|
+
return
|
|
1309
|
+
}
|
|
1088
1310
|
// Try the deterministic assignment and only its targeted repair queue
|
|
1089
1311
|
// before paying for the grouped beam. If the beam cannot solve, continue
|
|
1090
1312
|
// with the broader generated-assignment search below.
|
|
@@ -1098,7 +1320,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1098
1320
|
this.attempts.push(beamAttempt.summary)
|
|
1099
1321
|
if (
|
|
1100
1322
|
!this.bestAttempt ||
|
|
1101
|
-
beamAttempt
|
|
1323
|
+
this.isAttemptBetter(beamAttempt, this.bestAttempt)
|
|
1102
1324
|
) {
|
|
1103
1325
|
this.bestAttempt = beamAttempt
|
|
1104
1326
|
}
|
|
@@ -1114,11 +1336,19 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1114
1336
|
failedBuses: "none",
|
|
1115
1337
|
bestScore: bestSummary.score,
|
|
1116
1338
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1339
|
+
if (
|
|
1340
|
+
this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0
|
|
1341
|
+
) {
|
|
1342
|
+
this.completeBestAttemptEndpoints()
|
|
1343
|
+
this.solved = true
|
|
1344
|
+
return
|
|
1345
|
+
}
|
|
1120
1346
|
}
|
|
1121
|
-
if (
|
|
1347
|
+
if (
|
|
1348
|
+
this.hasCompleteBestAttempt() &&
|
|
1349
|
+
this.bestAttempt &&
|
|
1350
|
+
this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0
|
|
1351
|
+
) {
|
|
1122
1352
|
this.completeBestAttemptEndpoints()
|
|
1123
1353
|
this.solved = true
|
|
1124
1354
|
return
|
|
@@ -1192,10 +1422,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1192
1422
|
)
|
|
1193
1423
|
}
|
|
1194
1424
|
this.attempts.push(attempt.summary)
|
|
1195
|
-
if (
|
|
1196
|
-
!this.bestAttempt ||
|
|
1197
|
-
attempt.summary.score < this.bestAttempt.summary.score
|
|
1198
|
-
) {
|
|
1425
|
+
if (!this.bestAttempt || this.isAttemptBetter(attempt, this.bestAttempt)) {
|
|
1199
1426
|
this.bestAttempt = attempt
|
|
1200
1427
|
}
|
|
1201
1428
|
this.stats = {
|
|
@@ -1208,7 +1435,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1208
1435
|
}
|
|
1209
1436
|
if (
|
|
1210
1437
|
this.groupedBeamEvaluated &&
|
|
1211
|
-
attempt.summary.routedConnectionCount ===
|
|
1438
|
+
attempt.summary.routedConnectionCount ===
|
|
1439
|
+
this.inputSrj.connections.length &&
|
|
1440
|
+
this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0
|
|
1212
1441
|
) {
|
|
1213
1442
|
this.completeBestAttemptEndpoints()
|
|
1214
1443
|
this.solved = true
|
package/lib/index.ts
CHANGED
|
@@ -1,19 +1,8 @@
|
|
|
1
|
-
export { FanoutSolver } from "./fanout-solver"
|
|
2
1
|
export { completeOriginalEndpoints } from "./complete-original-endpoints"
|
|
2
|
+
export { getFanoutExitPositionConfig } from "./fanout-exit-position"
|
|
3
|
+
export { FanoutSolver } from "./fanout-solver"
|
|
3
4
|
export { getCopperLayerColor } from "./layer-colors"
|
|
4
5
|
export { getCopperLayerNames } from "./layer-names"
|
|
5
|
-
export { validateOriginalEndpointConnectivity } from "./validate-original-endpoint-connectivity"
|
|
6
|
-
export { validateRoutedCopperDrc } from "./validate-routed-copper-drc"
|
|
7
|
-
export { validateFanoutSolution } from "./validate-fanout-solution"
|
|
8
|
-
export type {
|
|
9
|
-
OriginalEndpointConnectivityIssue,
|
|
10
|
-
OriginalEndpointConnectivityReport,
|
|
11
|
-
} from "./validate-original-endpoint-connectivity"
|
|
12
|
-
export type {
|
|
13
|
-
RoutedCopperDrcIssue,
|
|
14
|
-
RoutedCopperDrcIssueCode,
|
|
15
|
-
RoutedCopperDrcReport,
|
|
16
|
-
} from "./validate-routed-copper-drc"
|
|
17
6
|
export type {
|
|
18
7
|
Bounds,
|
|
19
8
|
FanoutAttemptSummary,
|
|
@@ -30,6 +19,8 @@ export type {
|
|
|
30
19
|
FanoutDownstreamRouterOptions,
|
|
31
20
|
FanoutEdge,
|
|
32
21
|
FanoutEndpointCompletionReport,
|
|
22
|
+
FanoutExitPosition,
|
|
23
|
+
FanoutExitPositionConfig,
|
|
33
24
|
FanoutPlaneConnectivity,
|
|
34
25
|
FanoutPlaneTermination,
|
|
35
26
|
FanoutRoutePlan,
|
|
@@ -41,3 +32,15 @@ export type {
|
|
|
41
32
|
PreparedBus,
|
|
42
33
|
SimpleRouteJsonWithFanoutPlanes,
|
|
43
34
|
} from "./types"
|
|
35
|
+
export { validateFanoutSolution } from "./validate-fanout-solution"
|
|
36
|
+
export type {
|
|
37
|
+
OriginalEndpointConnectivityIssue,
|
|
38
|
+
OriginalEndpointConnectivityReport,
|
|
39
|
+
} from "./validate-original-endpoint-connectivity"
|
|
40
|
+
export { validateOriginalEndpointConnectivity } from "./validate-original-endpoint-connectivity"
|
|
41
|
+
export type {
|
|
42
|
+
RoutedCopperDrcIssue,
|
|
43
|
+
RoutedCopperDrcIssueCode,
|
|
44
|
+
RoutedCopperDrcReport,
|
|
45
|
+
} from "./validate-routed-copper-drc"
|
|
46
|
+
export { validateRoutedCopperDrc } from "./validate-routed-copper-drc"
|