@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/README.md +23 -2
- package/lib/add-via-layer-metadata.ts +48 -0
- package/lib/complete-original-endpoints.ts +30 -2
- package/lib/fanout-solver.ts +721 -33
- package/lib/geometry.ts +21 -0
- package/lib/get-routed-trace-copper.ts +15 -6
- package/lib/index.ts +3 -0
- package/lib/layer-names.ts +40 -0
- package/lib/match-bus-lengths.ts +681 -0
- package/lib/match-component-dogbone-via-sites.ts +584 -0
- package/lib/prepare-buses.ts +26 -0
- package/lib/route-bus.ts +872 -112
- package/lib/route-via-minimal-winding.ts +390 -74
- package/lib/types.ts +39 -7
- package/lib/validate-fanout-solution.ts +62 -6
- package/lib/validate-routed-copper-drc.ts +47 -8
- package/package.json +1 -1
package/lib/fanout-solver.ts
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
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 {
|
|
4
|
+
import { addViaLayerMetadataToSrj } from "./add-via-layer-metadata"
|
|
5
|
+
import { getCornerBandSide, getExitEdgeForDirection } from "./boundary-exit"
|
|
5
6
|
import { buildOutputSimpleRouteJson } from "./build-output"
|
|
6
7
|
import {
|
|
7
8
|
type CompleteOriginalEndpointsResult,
|
|
8
9
|
completeOriginalEndpoints,
|
|
9
10
|
} from "./complete-original-endpoints"
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
generateLayerAssignments,
|
|
13
|
+
getCopperLayerNames,
|
|
14
|
+
getViaSpanLayers,
|
|
15
|
+
} from "./layer-names"
|
|
16
|
+
import { matchBusPlanLengths } from "./match-bus-lengths"
|
|
17
|
+
import { matchComponentDogboneViaSites } from "./match-component-dogbone-via-sites"
|
|
18
|
+
import { connectionsShareElectricalNet } from "./net-identity"
|
|
11
19
|
import {
|
|
12
20
|
prepareFanoutBuses,
|
|
13
21
|
resolveAvailableBoundaryRegions,
|
|
14
22
|
} from "./prepare-buses"
|
|
15
23
|
import {
|
|
24
|
+
fanoutPlansAreClear,
|
|
16
25
|
type RouteBusStaticClearanceCache,
|
|
17
26
|
routeBus,
|
|
18
27
|
routeBusAlternatives,
|
|
@@ -27,7 +36,9 @@ import type {
|
|
|
27
36
|
FanoutRoutePlan,
|
|
28
37
|
FanoutSolverOptions,
|
|
29
38
|
FanoutSolverOutput,
|
|
39
|
+
FanoutValidationIssue,
|
|
30
40
|
PreparedBus,
|
|
41
|
+
SimpleRouteJsonWithFanoutPlanes,
|
|
31
42
|
} from "./types"
|
|
32
43
|
import { validateFanoutSolution } from "./validate-fanout-solution"
|
|
33
44
|
import { visualizeSimpleRouteJson } from "./visualize-simple-route-json"
|
|
@@ -38,6 +49,7 @@ interface ResolvedFanoutConfig {
|
|
|
38
49
|
viaHoleDiameter: number
|
|
39
50
|
clearance: number
|
|
40
51
|
compactBusTracks: boolean
|
|
52
|
+
allowBlindAndBuriedVias: boolean
|
|
41
53
|
allowSameNetMerges: boolean
|
|
42
54
|
singleLayerPushAndShove: boolean
|
|
43
55
|
singleLayerAdaptiveExits: boolean
|
|
@@ -57,6 +69,11 @@ interface GroupedBeamState {
|
|
|
57
69
|
plans: FanoutRoutePlan[]
|
|
58
70
|
}
|
|
59
71
|
|
|
72
|
+
interface MixedTerminationState {
|
|
73
|
+
plans: FanoutRoutePlan[]
|
|
74
|
+
failedBusIds: string[]
|
|
75
|
+
}
|
|
76
|
+
|
|
60
77
|
type RoutingStrategy = "default" | "group-by-layer" | "deep-first"
|
|
61
78
|
|
|
62
79
|
function resolvePositiveNumber(label: string, value: number): number {
|
|
@@ -129,6 +146,7 @@ function resolveConfig(
|
|
|
129
146
|
viaHoleDiameter,
|
|
130
147
|
clearance,
|
|
131
148
|
compactBusTracks: options.compactBusTracks ?? false,
|
|
149
|
+
allowBlindAndBuriedVias: options.allowBlindAndBuriedVias ?? true,
|
|
132
150
|
allowSameNetMerges: options.allowSameNetMerges ?? false,
|
|
133
151
|
singleLayerPushAndShove: options.singleLayerPushAndShove ?? false,
|
|
134
152
|
singleLayerAdaptiveExits: options.singleLayerAdaptiveExits ?? false,
|
|
@@ -214,6 +232,17 @@ function getLayerLoadPenaltyWeight(config: ResolvedFanoutConfig): number {
|
|
|
214
232
|
return config.balanceLayerLoadByConnectionCount ? 0.25 : 0.01
|
|
215
233
|
}
|
|
216
234
|
|
|
235
|
+
function comparePlaneRoutingPriority(
|
|
236
|
+
first: PreparedBus,
|
|
237
|
+
second: PreparedBus,
|
|
238
|
+
allowBlindAndBuriedVias: boolean,
|
|
239
|
+
): number {
|
|
240
|
+
const planeDifference =
|
|
241
|
+
Number(first.termination.type === "plane") -
|
|
242
|
+
Number(second.termination.type === "plane")
|
|
243
|
+
return allowBlindAndBuriedVias ? -planeDifference : planeDifference
|
|
244
|
+
}
|
|
245
|
+
|
|
217
246
|
function getPlanViaCount(plans: readonly FanoutRoutePlan[]): number {
|
|
218
247
|
return plans.reduce(
|
|
219
248
|
(count, plan) =>
|
|
@@ -437,6 +466,7 @@ function getCandidateEscapeLayersForBus(params: {
|
|
|
437
466
|
viaHoleDiameter: config.viaHoleDiameter,
|
|
438
467
|
clearance: config.clearance,
|
|
439
468
|
compactBusTracks: config.compactBusTracks,
|
|
469
|
+
allowBlindAndBuriedVias: config.allowBlindAndBuriedVias,
|
|
440
470
|
allowSameNetMerges: config.allowSameNetMerges,
|
|
441
471
|
staticClearanceCache,
|
|
442
472
|
}) !== null,
|
|
@@ -482,7 +512,12 @@ export class FanoutSolver extends BaseSolver {
|
|
|
482
512
|
private nextAssignmentIndex = 0
|
|
483
513
|
private nextGeneratedAssignmentIndex = 0
|
|
484
514
|
private bestAttempt: AssignmentAttempt | null = null
|
|
515
|
+
private lengthMatchingFailure: FanoutValidationIssue | null = null
|
|
485
516
|
private endpointCompletion: CompleteOriginalEndpointsResult | null = null
|
|
517
|
+
private denseDogboneViaPoints:
|
|
518
|
+
| Map<number, { x: number; y: number }>
|
|
519
|
+
| null
|
|
520
|
+
| undefined
|
|
486
521
|
|
|
487
522
|
constructor(
|
|
488
523
|
public readonly inputSrj: SimpleRouteJson,
|
|
@@ -624,6 +659,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
624
659
|
viaDiameter: this.config.viaDiameter,
|
|
625
660
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
626
661
|
clearance: this.config.clearance,
|
|
662
|
+
allowBlindAndBuriedVias: this.config.allowBlindAndBuriedVias,
|
|
627
663
|
effort: this.options.endpointCompletionEffort,
|
|
628
664
|
routeDownstreamConnections: this.options.routeDownstreamConnections,
|
|
629
665
|
})
|
|
@@ -655,9 +691,584 @@ export class FanoutSolver extends BaseSolver {
|
|
|
655
691
|
preparedBuses: this.preparedBuses,
|
|
656
692
|
sharedBoundary: this.getValidationBoundary(),
|
|
657
693
|
clearance: this.config.clearance,
|
|
694
|
+
allowBlindAndBuriedVias: this.config.allowBlindAndBuriedVias,
|
|
658
695
|
})
|
|
659
696
|
}
|
|
660
697
|
|
|
698
|
+
private matchCompletePlanLengths(
|
|
699
|
+
plans: readonly FanoutRoutePlan[],
|
|
700
|
+
): ReturnType<typeof matchBusPlanLengths> {
|
|
701
|
+
return matchBusPlanLengths({
|
|
702
|
+
plans,
|
|
703
|
+
preparedBuses: this.preparedBuses,
|
|
704
|
+
inputSrj: this.inputSrj,
|
|
705
|
+
sharedBoundary: this.getValidationBoundary(),
|
|
706
|
+
clearance: this.config.clearance,
|
|
707
|
+
allowBlindAndBuriedVias: this.config.allowBlindAndBuriedVias,
|
|
708
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
709
|
+
})
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Through-all source vias from a wide boundary bus can consume the only
|
|
714
|
+
* legal dogbone channel for nearby plane pads. Conversely, routing hundreds
|
|
715
|
+
* of singleton plane drops first can strand the boundary bus. Search a tiny
|
|
716
|
+
* number of whole-bus boundary alternatives, then fill the remaining plane
|
|
717
|
+
* dogbones. This is intentionally bounded independently of the number of
|
|
718
|
+
* plane drops so dense power fields cannot explode the general beam search.
|
|
719
|
+
*/
|
|
720
|
+
private routeDenseThroughAllMixedTerminations(params: {
|
|
721
|
+
busLayerAssignments: Readonly<Record<string, string>>
|
|
722
|
+
busesInRoutingOrder: readonly PreparedBus[]
|
|
723
|
+
}): MixedTerminationState | null {
|
|
724
|
+
if (this.config.allowBlindAndBuriedVias) return null
|
|
725
|
+
|
|
726
|
+
const boundaryBuses = params.busesInRoutingOrder.filter(
|
|
727
|
+
(bus) => bus.termination.type === "boundary",
|
|
728
|
+
)
|
|
729
|
+
// Preserve the caller/input order for the dense singleton fill. The
|
|
730
|
+
// general routing sort is useful for heterogeneous buses, but ordering a
|
|
731
|
+
// regular BGA power field by obstacle depth creates artificial local
|
|
732
|
+
// dead-ends and needlessly triggers the widened boundary beam. The local
|
|
733
|
+
// rip-up/retry below handles the genuinely constrained drops.
|
|
734
|
+
const planeBuses = this.preparedBuses.filter(
|
|
735
|
+
(bus) => bus.termination.type === "plane",
|
|
736
|
+
)
|
|
737
|
+
if (
|
|
738
|
+
boundaryBuses.length === 0 ||
|
|
739
|
+
boundaryBuses.length > 4 ||
|
|
740
|
+
planeBuses.length < 8 ||
|
|
741
|
+
boundaryBuses.some((bus) => !busUsesCoordinatedWinding(bus)) ||
|
|
742
|
+
planeBuses.some((bus) => bus.connections.length !== 1) ||
|
|
743
|
+
boundaryBuses.length + planeBuses.length !==
|
|
744
|
+
params.busesInRoutingOrder.length
|
|
745
|
+
) {
|
|
746
|
+
return null
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const connectionNameByIndex = new Map(
|
|
750
|
+
this.preparedBuses.flatMap((bus) =>
|
|
751
|
+
bus.connections.map(
|
|
752
|
+
(connection) =>
|
|
753
|
+
[connection.connectionIndex, connection.connection.name] as const,
|
|
754
|
+
),
|
|
755
|
+
),
|
|
756
|
+
)
|
|
757
|
+
const preferredBoundaryPerpendicularSideByBusId = new Map(
|
|
758
|
+
boundaryBuses.map((bus) => [bus.busId, 1 as const]),
|
|
759
|
+
)
|
|
760
|
+
const preferBoundaryOutwardByBusId = new Map(
|
|
761
|
+
boundaryBuses.map((bus) => [
|
|
762
|
+
bus.busId,
|
|
763
|
+
getExitEdgeForDirection(bus.direction) !== bus.exitEdge,
|
|
764
|
+
]),
|
|
765
|
+
)
|
|
766
|
+
if (this.denseDogboneViaPoints === undefined) {
|
|
767
|
+
this.denseDogboneViaPoints = matchComponentDogboneViaSites(
|
|
768
|
+
this.preparedBuses,
|
|
769
|
+
{
|
|
770
|
+
viaDiameter: this.config.viaDiameter,
|
|
771
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
772
|
+
traceWidth: this.config.traceWidth,
|
|
773
|
+
clearance: this.config.clearance,
|
|
774
|
+
maximumSearchStates: 20_000,
|
|
775
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
776
|
+
preferBoundaryOutwardByBusId,
|
|
777
|
+
canShareCopper: (firstConnectionIndex, secondConnectionIndex) => {
|
|
778
|
+
if (!this.config.allowSameNetMerges) return false
|
|
779
|
+
const firstConnectionName =
|
|
780
|
+
connectionNameByIndex.get(firstConnectionIndex)
|
|
781
|
+
const secondConnectionName = connectionNameByIndex.get(
|
|
782
|
+
secondConnectionIndex,
|
|
783
|
+
)
|
|
784
|
+
return Boolean(
|
|
785
|
+
firstConnectionName &&
|
|
786
|
+
secondConnectionName &&
|
|
787
|
+
connectionsShareElectricalNet(
|
|
788
|
+
this.routingSrj,
|
|
789
|
+
firstConnectionName,
|
|
790
|
+
secondConnectionName,
|
|
791
|
+
),
|
|
792
|
+
)
|
|
793
|
+
},
|
|
794
|
+
},
|
|
795
|
+
)
|
|
796
|
+
}
|
|
797
|
+
const fixedViaPointsByConnectionIndex = this.denseDogboneViaPoints
|
|
798
|
+
if (fixedViaPointsByConnectionIndex) {
|
|
799
|
+
const reservedViasByComponentId = new Map<
|
|
800
|
+
string,
|
|
801
|
+
Array<{
|
|
802
|
+
connectionName: string
|
|
803
|
+
via: {
|
|
804
|
+
center: { x: number; y: number }
|
|
805
|
+
diameter: number
|
|
806
|
+
spanLayers: string[]
|
|
807
|
+
}
|
|
808
|
+
}>
|
|
809
|
+
>()
|
|
810
|
+
for (const bus of this.preparedBuses) {
|
|
811
|
+
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
812
|
+
if (!targetLayer) continue
|
|
813
|
+
const componentReservedVias =
|
|
814
|
+
reservedViasByComponentId.get(bus.componentId) ?? []
|
|
815
|
+
for (const connection of bus.connections) {
|
|
816
|
+
const center = fixedViaPointsByConnectionIndex.get(
|
|
817
|
+
connection.connectionIndex,
|
|
818
|
+
)
|
|
819
|
+
if (!center) continue
|
|
820
|
+
componentReservedVias.push({
|
|
821
|
+
connectionName: connection.connection.name,
|
|
822
|
+
via: {
|
|
823
|
+
center,
|
|
824
|
+
diameter: this.config.viaDiameter,
|
|
825
|
+
spanLayers: getViaSpanLayers({
|
|
826
|
+
fromLayer: connection.sourceLayer,
|
|
827
|
+
toLayer: targetLayer,
|
|
828
|
+
layerNames: this.config.layerNames,
|
|
829
|
+
allowBlindAndBuriedVias: false,
|
|
830
|
+
}),
|
|
831
|
+
},
|
|
832
|
+
})
|
|
833
|
+
}
|
|
834
|
+
reservedViasByComponentId.set(bus.componentId, componentReservedVias)
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const matchedPlans: FanoutRoutePlan[] = []
|
|
838
|
+
let matchedRoutingSucceeded = true
|
|
839
|
+
for (const bus of boundaryBuses) {
|
|
840
|
+
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
841
|
+
if (!targetLayer) {
|
|
842
|
+
matchedRoutingSucceeded = false
|
|
843
|
+
break
|
|
844
|
+
}
|
|
845
|
+
const currentConnectionNames = new Set(
|
|
846
|
+
bus.connections.map((connection) => connection.connection.name),
|
|
847
|
+
)
|
|
848
|
+
const reservedVias = (
|
|
849
|
+
reservedViasByComponentId.get(bus.componentId) ?? []
|
|
850
|
+
).filter(
|
|
851
|
+
({ connectionName }) => !currentConnectionNames.has(connectionName),
|
|
852
|
+
)
|
|
853
|
+
const busPlans = routeBusAlternatives(
|
|
854
|
+
{
|
|
855
|
+
srj: this.routingSrj,
|
|
856
|
+
bus,
|
|
857
|
+
targetLayer,
|
|
858
|
+
acceptedPlans: matchedPlans,
|
|
859
|
+
layerNames: this.config.layerNames,
|
|
860
|
+
traceWidth: this.config.traceWidth,
|
|
861
|
+
viaDiameter: this.config.viaDiameter,
|
|
862
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
863
|
+
clearance: this.config.clearance,
|
|
864
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
865
|
+
allowBlindAndBuriedVias: false,
|
|
866
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
867
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
868
|
+
fixedViaPointsByConnectionIndex,
|
|
869
|
+
reservedVias,
|
|
870
|
+
viaMinimalOnly: true,
|
|
871
|
+
},
|
|
872
|
+
1,
|
|
873
|
+
)[0]
|
|
874
|
+
if (!busPlans) {
|
|
875
|
+
matchedRoutingSucceeded = false
|
|
876
|
+
break
|
|
877
|
+
}
|
|
878
|
+
matchedPlans.push(...busPlans)
|
|
879
|
+
}
|
|
880
|
+
if (matchedRoutingSucceeded) {
|
|
881
|
+
for (const bus of planeBuses) {
|
|
882
|
+
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
883
|
+
const busPlans = targetLayer
|
|
884
|
+
? routeBus({
|
|
885
|
+
srj: this.routingSrj,
|
|
886
|
+
bus,
|
|
887
|
+
targetLayer,
|
|
888
|
+
acceptedPlans: matchedPlans,
|
|
889
|
+
layerNames: this.config.layerNames,
|
|
890
|
+
traceWidth: this.config.traceWidth,
|
|
891
|
+
viaDiameter: this.config.viaDiameter,
|
|
892
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
893
|
+
clearance: this.config.clearance,
|
|
894
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
895
|
+
allowBlindAndBuriedVias: false,
|
|
896
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
897
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
898
|
+
fixedViaPointsByConnectionIndex,
|
|
899
|
+
})
|
|
900
|
+
: null
|
|
901
|
+
if (!busPlans) {
|
|
902
|
+
matchedRoutingSucceeded = false
|
|
903
|
+
break
|
|
904
|
+
}
|
|
905
|
+
matchedPlans.push(...busPlans)
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
if (
|
|
909
|
+
matchedRoutingSucceeded &&
|
|
910
|
+
fanoutPlansAreClear({
|
|
911
|
+
plans: matchedPlans,
|
|
912
|
+
srj: this.routingSrj,
|
|
913
|
+
sharedBoundary: boundaryBuses[0]!.sharedBoundary,
|
|
914
|
+
clearance: this.config.clearance,
|
|
915
|
+
allowBlindAndBuriedVias: false,
|
|
916
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
917
|
+
})
|
|
918
|
+
) {
|
|
919
|
+
return { plans: matchedPlans, failedBusIds: [] }
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const maximumStates = 8
|
|
924
|
+
const getBoundaryStates = (
|
|
925
|
+
alternativesPerBoundaryBus: number,
|
|
926
|
+
initialPlans: readonly FanoutRoutePlan[] = [],
|
|
927
|
+
): MixedTerminationState[] | null => {
|
|
928
|
+
let states: MixedTerminationState[] = [
|
|
929
|
+
{ plans: [...initialPlans], failedBusIds: [] },
|
|
930
|
+
]
|
|
931
|
+
for (const bus of boundaryBuses) {
|
|
932
|
+
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
933
|
+
if (!targetLayer) return null
|
|
934
|
+
const nextStates: MixedTerminationState[] = []
|
|
935
|
+
const alternativesByState = states.map((state) => ({
|
|
936
|
+
state,
|
|
937
|
+
alternatives: routeBusAlternatives(
|
|
938
|
+
{
|
|
939
|
+
srj: this.routingSrj,
|
|
940
|
+
bus,
|
|
941
|
+
targetLayer,
|
|
942
|
+
acceptedPlans: state.plans,
|
|
943
|
+
layerNames: this.config.layerNames,
|
|
944
|
+
traceWidth: this.config.traceWidth,
|
|
945
|
+
viaDiameter: this.config.viaDiameter,
|
|
946
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
947
|
+
clearance: this.config.clearance,
|
|
948
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
949
|
+
allowBlindAndBuriedVias: false,
|
|
950
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
951
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
952
|
+
},
|
|
953
|
+
alternativesPerBoundaryBus,
|
|
954
|
+
),
|
|
955
|
+
}))
|
|
956
|
+
for (
|
|
957
|
+
let alternativeIndex = 0;
|
|
958
|
+
alternativeIndex < alternativesPerBoundaryBus;
|
|
959
|
+
alternativeIndex++
|
|
960
|
+
) {
|
|
961
|
+
for (const { state, alternatives } of alternativesByState) {
|
|
962
|
+
const alternative = alternatives[alternativeIndex]
|
|
963
|
+
if (!alternative) continue
|
|
964
|
+
nextStates.push({
|
|
965
|
+
plans: [...state.plans, ...alternative],
|
|
966
|
+
failedBusIds: [],
|
|
967
|
+
})
|
|
968
|
+
if (nextStates.length >= maximumStates) break
|
|
969
|
+
}
|
|
970
|
+
if (nextStates.length >= maximumStates) break
|
|
971
|
+
}
|
|
972
|
+
if (nextStates.length === 0) return null
|
|
973
|
+
states = nextStates
|
|
974
|
+
}
|
|
975
|
+
return states
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const getJointReservedBoundaryState = (
|
|
979
|
+
initialPlans: readonly FanoutRoutePlan[],
|
|
980
|
+
): MixedTerminationState | null => {
|
|
981
|
+
if (boundaryBuses.length !== 2) return null
|
|
982
|
+
const [firstBus, secondBus] = boundaryBuses
|
|
983
|
+
if (!firstBus || !secondBus) return null
|
|
984
|
+
const routeBoundaryBus = (
|
|
985
|
+
bus: PreparedBus,
|
|
986
|
+
acceptedPlans: FanoutRoutePlan[],
|
|
987
|
+
rejectedViaMinimalCandidates?: FanoutRoutePlan[][],
|
|
988
|
+
): FanoutRoutePlan[] | null => {
|
|
989
|
+
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
990
|
+
if (!targetLayer) return null
|
|
991
|
+
return (
|
|
992
|
+
routeBusAlternatives(
|
|
993
|
+
{
|
|
994
|
+
srj: this.routingSrj,
|
|
995
|
+
bus,
|
|
996
|
+
targetLayer,
|
|
997
|
+
acceptedPlans,
|
|
998
|
+
layerNames: this.config.layerNames,
|
|
999
|
+
traceWidth: this.config.traceWidth,
|
|
1000
|
+
viaDiameter: this.config.viaDiameter,
|
|
1001
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1002
|
+
clearance: this.config.clearance,
|
|
1003
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
1004
|
+
allowBlindAndBuriedVias: false,
|
|
1005
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1006
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
1007
|
+
rejectedViaMinimalCandidates,
|
|
1008
|
+
stopAfterFirstRejectedViaMinimalCandidate:
|
|
1009
|
+
rejectedViaMinimalCandidates !== undefined,
|
|
1010
|
+
},
|
|
1011
|
+
1,
|
|
1012
|
+
)[0] ?? null
|
|
1013
|
+
)
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const firstPlans = routeBoundaryBus(firstBus, [...initialPlans])
|
|
1017
|
+
if (!firstPlans) return null
|
|
1018
|
+
const rejectedSecondCandidates: FanoutRoutePlan[][] = []
|
|
1019
|
+
const secondPlans = routeBoundaryBus(
|
|
1020
|
+
secondBus,
|
|
1021
|
+
[...initialPlans, ...firstPlans],
|
|
1022
|
+
rejectedSecondCandidates,
|
|
1023
|
+
)
|
|
1024
|
+
if (secondPlans) {
|
|
1025
|
+
return {
|
|
1026
|
+
plans: [...initialPlans, ...firstPlans, ...secondPlans],
|
|
1027
|
+
failedBusIds: [],
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
const rejectedSecondPlans = rejectedSecondCandidates[0]
|
|
1032
|
+
if (!rejectedSecondPlans) return null
|
|
1033
|
+
// Keep the candidate's exact copper/vias as immutable blockers while
|
|
1034
|
+
// rerouting the earlier bus, but exclude it from corner-slot allocation.
|
|
1035
|
+
const reservedSecondPlans = rejectedSecondPlans.map((plan) => ({
|
|
1036
|
+
...plan,
|
|
1037
|
+
termination: {
|
|
1038
|
+
type: "plane" as const,
|
|
1039
|
+
layer: plan.targetLayer,
|
|
1040
|
+
},
|
|
1041
|
+
}))
|
|
1042
|
+
const reroutedFirstPlans = routeBoundaryBus(firstBus, [
|
|
1043
|
+
...initialPlans,
|
|
1044
|
+
...reservedSecondPlans,
|
|
1045
|
+
])
|
|
1046
|
+
if (!reroutedFirstPlans) return null
|
|
1047
|
+
const combinedPlans = [
|
|
1048
|
+
...initialPlans,
|
|
1049
|
+
...reroutedFirstPlans,
|
|
1050
|
+
...rejectedSecondPlans,
|
|
1051
|
+
]
|
|
1052
|
+
if (
|
|
1053
|
+
!fanoutPlansAreClear({
|
|
1054
|
+
plans: combinedPlans,
|
|
1055
|
+
srj: this.routingSrj,
|
|
1056
|
+
sharedBoundary: firstBus.sharedBoundary,
|
|
1057
|
+
clearance: this.config.clearance,
|
|
1058
|
+
allowBlindAndBuriedVias: false,
|
|
1059
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1060
|
+
})
|
|
1061
|
+
) {
|
|
1062
|
+
return null
|
|
1063
|
+
}
|
|
1064
|
+
return { plans: combinedPlans, failedBusIds: [] }
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
const planeBusById = new Map(planeBuses.map((bus) => [bus.busId, bus]))
|
|
1068
|
+
const routePlaneBus = (
|
|
1069
|
+
bus: PreparedBus,
|
|
1070
|
+
acceptedPlans: FanoutRoutePlan[],
|
|
1071
|
+
blockingBusCounts?: Map<string, number>,
|
|
1072
|
+
): FanoutRoutePlan[] | null => {
|
|
1073
|
+
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
1074
|
+
if (!targetLayer) return null
|
|
1075
|
+
return routeBus({
|
|
1076
|
+
srj: this.routingSrj,
|
|
1077
|
+
bus,
|
|
1078
|
+
targetLayer,
|
|
1079
|
+
acceptedPlans,
|
|
1080
|
+
layerNames: this.config.layerNames,
|
|
1081
|
+
traceWidth: this.config.traceWidth,
|
|
1082
|
+
viaDiameter: this.config.viaDiameter,
|
|
1083
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1084
|
+
clearance: this.config.clearance,
|
|
1085
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
1086
|
+
allowBlindAndBuriedVias: false,
|
|
1087
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1088
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
1089
|
+
blockingBusCounts,
|
|
1090
|
+
})
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const routePlaneOrder = (
|
|
1094
|
+
boundaryPlans: readonly FanoutRoutePlan[],
|
|
1095
|
+
planeOrder: readonly PreparedBus[],
|
|
1096
|
+
): MixedTerminationState => {
|
|
1097
|
+
const state: MixedTerminationState = {
|
|
1098
|
+
plans: [...boundaryPlans],
|
|
1099
|
+
failedBusIds: [],
|
|
1100
|
+
}
|
|
1101
|
+
for (const bus of planeOrder) {
|
|
1102
|
+
const blockingBusCounts = new Map<string, number>()
|
|
1103
|
+
let busPlans = routePlaneBus(bus, state.plans, blockingBusCounts)
|
|
1104
|
+
|
|
1105
|
+
// A plane dogbone can lose its only local channel to one or two
|
|
1106
|
+
// earlier singleton drops. Rip up only the strongest local blockers,
|
|
1107
|
+
// route the constrained drop first, then put the displaced drops back.
|
|
1108
|
+
// Boundary buses are immutable here and the search is capped at 36
|
|
1109
|
+
// blocker pairs, so dense fields remain predictable.
|
|
1110
|
+
if (!busPlans) {
|
|
1111
|
+
const blockerIds = [...blockingBusCounts.entries()]
|
|
1112
|
+
.filter(([busId]) =>
|
|
1113
|
+
state.plans.some((plan) => plan.busId === busId),
|
|
1114
|
+
)
|
|
1115
|
+
.filter(([busId]) => planeBusById.has(busId))
|
|
1116
|
+
.toSorted(
|
|
1117
|
+
([, firstCount], [, secondCount]) => secondCount - firstCount,
|
|
1118
|
+
)
|
|
1119
|
+
.slice(0, 8)
|
|
1120
|
+
.map(([busId]) => busId)
|
|
1121
|
+
const ripupSets = [
|
|
1122
|
+
...blockerIds.map((busId) => [busId]),
|
|
1123
|
+
...blockerIds.flatMap((first, firstIndex) =>
|
|
1124
|
+
blockerIds.slice(firstIndex + 1).map((second) => [first, second]),
|
|
1125
|
+
),
|
|
1126
|
+
]
|
|
1127
|
+
for (const ripupIds of ripupSets) {
|
|
1128
|
+
const ripupIdSet = new Set(ripupIds)
|
|
1129
|
+
const candidatePlans = state.plans.filter(
|
|
1130
|
+
(plan) => !ripupIdSet.has(plan.busId),
|
|
1131
|
+
)
|
|
1132
|
+
const constrainedPlans = routePlaneBus(bus, candidatePlans)
|
|
1133
|
+
if (!constrainedPlans) continue
|
|
1134
|
+
candidatePlans.push(...constrainedPlans)
|
|
1135
|
+
let repairSucceeded = true
|
|
1136
|
+
for (const blockerId of ripupIds) {
|
|
1137
|
+
const blockerBus = planeBusById.get(blockerId)
|
|
1138
|
+
const replacementPlans = blockerBus
|
|
1139
|
+
? routePlaneBus(blockerBus, candidatePlans)
|
|
1140
|
+
: null
|
|
1141
|
+
if (!replacementPlans) {
|
|
1142
|
+
repairSucceeded = false
|
|
1143
|
+
break
|
|
1144
|
+
}
|
|
1145
|
+
candidatePlans.push(...replacementPlans)
|
|
1146
|
+
}
|
|
1147
|
+
if (!repairSucceeded) continue
|
|
1148
|
+
state.plans = candidatePlans
|
|
1149
|
+
busPlans = []
|
|
1150
|
+
break
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
if (busPlans) state.plans.push(...busPlans)
|
|
1155
|
+
else state.failedBusIds.push(bus.busId)
|
|
1156
|
+
}
|
|
1157
|
+
return state
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
let bestState: MixedTerminationState | null = null
|
|
1161
|
+
let mostRecentBoundaryBestState: MixedTerminationState | null = null
|
|
1162
|
+
const evaluateBoundaryStates = (
|
|
1163
|
+
states: readonly MixedTerminationState[],
|
|
1164
|
+
initialPlaneOrder: readonly PreparedBus[],
|
|
1165
|
+
): MixedTerminationState | null => {
|
|
1166
|
+
let localBestState: MixedTerminationState | null = null
|
|
1167
|
+
for (const boundaryState of states) {
|
|
1168
|
+
let planeOrder = [...initialPlaneOrder]
|
|
1169
|
+
const seenPlaneOrders = new Set<string>()
|
|
1170
|
+
for (let retryIndex = 0; retryIndex < 3; retryIndex++) {
|
|
1171
|
+
const orderKey = planeOrder.map((bus) => bus.busId).join("\u0000")
|
|
1172
|
+
if (seenPlaneOrders.has(orderKey)) break
|
|
1173
|
+
seenPlaneOrders.add(orderKey)
|
|
1174
|
+
const state = routePlaneOrder(boundaryState.plans, planeOrder)
|
|
1175
|
+
if (
|
|
1176
|
+
!bestState ||
|
|
1177
|
+
state.plans.length > bestState.plans.length ||
|
|
1178
|
+
(state.plans.length === bestState.plans.length &&
|
|
1179
|
+
state.failedBusIds.length < bestState.failedBusIds.length)
|
|
1180
|
+
) {
|
|
1181
|
+
bestState = state
|
|
1182
|
+
}
|
|
1183
|
+
if (
|
|
1184
|
+
!localBestState ||
|
|
1185
|
+
state.plans.length > localBestState.plans.length ||
|
|
1186
|
+
(state.plans.length === localBestState.plans.length &&
|
|
1187
|
+
state.failedBusIds.length < localBestState.failedBusIds.length)
|
|
1188
|
+
) {
|
|
1189
|
+
localBestState = state
|
|
1190
|
+
}
|
|
1191
|
+
if (state.failedBusIds.length === 0) return state
|
|
1192
|
+
const failedBusIds = new Set(state.failedBusIds)
|
|
1193
|
+
planeOrder = [
|
|
1194
|
+
...state.failedBusIds.flatMap((busId) => {
|
|
1195
|
+
const bus = planeBusById.get(busId)
|
|
1196
|
+
return bus ? [bus] : []
|
|
1197
|
+
}),
|
|
1198
|
+
...planeOrder.filter((bus) => !failedBusIds.has(bus.busId)),
|
|
1199
|
+
]
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
mostRecentBoundaryBestState = localBestState
|
|
1203
|
+
return null
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// Most dense packages route with the first deterministic boundary choice.
|
|
1207
|
+
// Try that cheap path before widening the beam; eagerly generating four
|
|
1208
|
+
// complete A* variants per state can otherwise dominate runtime and memory.
|
|
1209
|
+
for (const alternativesPerBoundaryBus of [1, 4]) {
|
|
1210
|
+
const states = getBoundaryStates(alternativesPerBoundaryBus)
|
|
1211
|
+
if (!states) continue
|
|
1212
|
+
const completeState = evaluateBoundaryStates(states, planeBuses)
|
|
1213
|
+
if (completeState) return completeState
|
|
1214
|
+
|
|
1215
|
+
// If a completed signal escape encloses one especially constrained
|
|
1216
|
+
// power pad, seed just that failed singleton first and recompute the
|
|
1217
|
+
// small boundary-bus set around its physical through barrel. This keeps
|
|
1218
|
+
// the search local without routing hundreds of plane drops before the
|
|
1219
|
+
// signal buses.
|
|
1220
|
+
const seedFailureIds = (
|
|
1221
|
+
bestState as MixedTerminationState | null
|
|
1222
|
+
)?.failedBusIds.slice(0, 3)
|
|
1223
|
+
if (alternativesPerBoundaryBus === 1 && seedFailureIds) {
|
|
1224
|
+
for (const failedBusId of seedFailureIds) {
|
|
1225
|
+
const seededPlanePlans: FanoutRoutePlan[] = []
|
|
1226
|
+
const seededPlaneBusIds = new Set<string>()
|
|
1227
|
+
let nextFailedBusId: string | undefined = failedBusId
|
|
1228
|
+
for (
|
|
1229
|
+
let seedDepth = 0;
|
|
1230
|
+
seedDepth < 3 && nextFailedBusId;
|
|
1231
|
+
seedDepth++
|
|
1232
|
+
) {
|
|
1233
|
+
const failedPlaneBus = planeBusById.get(nextFailedBusId)
|
|
1234
|
+
if (!failedPlaneBus) break
|
|
1235
|
+
const nextSeedPlans = routePlaneBus(
|
|
1236
|
+
failedPlaneBus,
|
|
1237
|
+
seededPlanePlans,
|
|
1238
|
+
)
|
|
1239
|
+
if (!nextSeedPlans) break
|
|
1240
|
+
seededPlanePlans.push(...nextSeedPlans)
|
|
1241
|
+
seededPlaneBusIds.add(nextFailedBusId)
|
|
1242
|
+
const jointReservedBoundaryState =
|
|
1243
|
+
getJointReservedBoundaryState(seededPlanePlans)
|
|
1244
|
+
if (!jointReservedBoundaryState) break
|
|
1245
|
+
const jointCompleteState = evaluateBoundaryStates(
|
|
1246
|
+
[jointReservedBoundaryState],
|
|
1247
|
+
planeBuses.filter((bus) => !seededPlaneBusIds.has(bus.busId)),
|
|
1248
|
+
)
|
|
1249
|
+
if (jointCompleteState) return jointCompleteState
|
|
1250
|
+
const recentFailedBusIds = (
|
|
1251
|
+
mostRecentBoundaryBestState as MixedTerminationState | null
|
|
1252
|
+
)?.failedBusIds
|
|
1253
|
+
nextFailedBusId = recentFailedBusIds?.find(
|
|
1254
|
+
(busId) => !seededPlaneBusIds.has(busId),
|
|
1255
|
+
)
|
|
1256
|
+
}
|
|
1257
|
+
if (seededPlanePlans.length === 0) continue
|
|
1258
|
+
const seededBoundaryStates = getBoundaryStates(1, seededPlanePlans)
|
|
1259
|
+
if (!seededBoundaryStates) continue
|
|
1260
|
+
const seededCompleteState = evaluateBoundaryStates(
|
|
1261
|
+
seededBoundaryStates,
|
|
1262
|
+
planeBuses.filter((bus) => !seededPlaneBusIds.has(bus.busId)),
|
|
1263
|
+
)
|
|
1264
|
+
if (seededCompleteState) return seededCompleteState
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
return bestState
|
|
1270
|
+
}
|
|
1271
|
+
|
|
661
1272
|
private evaluateAssignmentWithStrategy(
|
|
662
1273
|
assignmentIndex: number,
|
|
663
1274
|
busLayerAssignments: Readonly<Record<string, string>>,
|
|
@@ -707,8 +1318,11 @@ export class FanoutSolver extends BaseSolver {
|
|
|
707
1318
|
busLayerAssignments[b.busId] ?? "",
|
|
708
1319
|
)
|
|
709
1320
|
return (
|
|
710
|
-
|
|
711
|
-
|
|
1321
|
+
comparePlaneRoutingPriority(
|
|
1322
|
+
a,
|
|
1323
|
+
b,
|
|
1324
|
+
this.config.allowBlindAndBuriedVias,
|
|
1325
|
+
) ||
|
|
712
1326
|
Number(bUsesCoordinatedWinding) - Number(aUsesCoordinatedWinding) ||
|
|
713
1327
|
(aUsesCoordinatedWinding && bUsesCoordinatedWinding
|
|
714
1328
|
? bLayerIndex - aLayerIndex
|
|
@@ -728,8 +1342,23 @@ export class FanoutSolver extends BaseSolver {
|
|
|
728
1342
|
)
|
|
729
1343
|
})
|
|
730
1344
|
|
|
1345
|
+
const mixedTerminationState =
|
|
1346
|
+
!useSingleLayerPushAndShove && routingStrategy === "default"
|
|
1347
|
+
? this.routeDenseThroughAllMixedTerminations({
|
|
1348
|
+
busLayerAssignments,
|
|
1349
|
+
busesInRoutingOrder,
|
|
1350
|
+
})
|
|
1351
|
+
: null
|
|
1352
|
+
|
|
1353
|
+
if (mixedTerminationState) {
|
|
1354
|
+
plans = mixedTerminationState.plans
|
|
1355
|
+
failedBusIds = mixedTerminationState.failedBusIds
|
|
1356
|
+
}
|
|
1357
|
+
|
|
731
1358
|
let routingPrefixKey = `${routingStrategy}|`
|
|
732
|
-
for (const bus of useSingleLayerPushAndShove
|
|
1359
|
+
for (const bus of useSingleLayerPushAndShove || mixedTerminationState
|
|
1360
|
+
? []
|
|
1361
|
+
: busesInRoutingOrder) {
|
|
733
1362
|
const targetLayer = busLayerAssignments[bus.busId]
|
|
734
1363
|
if (!targetLayer) {
|
|
735
1364
|
throw new Error(
|
|
@@ -761,6 +1390,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
761
1390
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
762
1391
|
clearance: this.config.clearance,
|
|
763
1392
|
compactBusTracks: this.config.compactBusTracks,
|
|
1393
|
+
allowBlindAndBuriedVias: this.config.allowBlindAndBuriedVias,
|
|
764
1394
|
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
765
1395
|
staticClearanceCache: this.routeStaticClearanceCache,
|
|
766
1396
|
blockingBusCounts: currentBusBlockingCounts,
|
|
@@ -784,6 +1414,29 @@ export class FanoutSolver extends BaseSolver {
|
|
|
784
1414
|
}
|
|
785
1415
|
|
|
786
1416
|
let validationIssues: FanoutAttemptSummary["validationIssues"]
|
|
1417
|
+
if (plans.length === this.inputSrj.connections.length) {
|
|
1418
|
+
const lengthMatching = this.matchCompletePlanLengths(plans)
|
|
1419
|
+
if (lengthMatching.plans) {
|
|
1420
|
+
plans = lengthMatching.plans
|
|
1421
|
+
} else {
|
|
1422
|
+
const constrainedBus = lengthMatching.failedBus
|
|
1423
|
+
const lengthMatchingIssue: FanoutValidationIssue = {
|
|
1424
|
+
code: "bus-length-skew",
|
|
1425
|
+
message: `Bus ${constrainedBus.busId} could not satisfy its ${constrainedBus.maxLengthSkew!.toFixed(6)}mm routed-length skew within the fanout boundary`,
|
|
1426
|
+
busId: constrainedBus.busId,
|
|
1427
|
+
}
|
|
1428
|
+
validationIssues = [lengthMatchingIssue]
|
|
1429
|
+
this.lengthMatchingFailure ??= lengthMatchingIssue
|
|
1430
|
+
plans = []
|
|
1431
|
+
failedBusIds = [
|
|
1432
|
+
constrainedBus.busId,
|
|
1433
|
+
...this.preparedBuses
|
|
1434
|
+
.map((bus) => bus.busId)
|
|
1435
|
+
.filter((busId) => busId !== constrainedBus.busId),
|
|
1436
|
+
]
|
|
1437
|
+
blockingBusCounts.clear()
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
787
1440
|
let outputSrj = buildOutputSimpleRouteJson({
|
|
788
1441
|
inputSrj: this.inputSrj,
|
|
789
1442
|
plans,
|
|
@@ -831,7 +1484,6 @@ export class FanoutSolver extends BaseSolver {
|
|
|
831
1484
|
score,
|
|
832
1485
|
...(validationIssues ? { validationIssues } : {}),
|
|
833
1486
|
}
|
|
834
|
-
|
|
835
1487
|
return {
|
|
836
1488
|
summary,
|
|
837
1489
|
plans,
|
|
@@ -933,8 +1585,11 @@ export class FanoutSolver extends BaseSolver {
|
|
|
933
1585
|
: (this.escapeLayersByBusId[b.busId]?.length ??
|
|
934
1586
|
this.config.escapeLayers.length)
|
|
935
1587
|
return (
|
|
936
|
-
|
|
937
|
-
|
|
1588
|
+
comparePlaneRoutingPriority(
|
|
1589
|
+
a,
|
|
1590
|
+
b,
|
|
1591
|
+
this.config.allowBlindAndBuriedVias,
|
|
1592
|
+
) ||
|
|
938
1593
|
Number(bUsesCoordinatedWinding) - Number(aUsesCoordinatedWinding) ||
|
|
939
1594
|
(aUsesCoordinatedWinding && bUsesCoordinatedWinding
|
|
940
1595
|
? getMaximumViaSpan(b) - getMaximumViaSpan(a)
|
|
@@ -1045,6 +1700,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1045
1700
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1046
1701
|
clearance: this.config.clearance,
|
|
1047
1702
|
compactBusTracks: this.config.compactBusTracks,
|
|
1703
|
+
allowBlindAndBuriedVias: this.config.allowBlindAndBuriedVias,
|
|
1048
1704
|
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1049
1705
|
staticClearanceCache: this.routeStaticClearanceCache,
|
|
1050
1706
|
},
|
|
@@ -1088,32 +1744,54 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1088
1744
|
|
|
1089
1745
|
let bestState: GroupedBeamState | undefined
|
|
1090
1746
|
let outputSrj: SimpleRouteJson | undefined
|
|
1747
|
+
let bestMatchedScore = Number.POSITIVE_INFINITY
|
|
1748
|
+
let bestAdditionalViaCount = Number.POSITIVE_INFINITY
|
|
1749
|
+
const getCompleteStateScore = (state: GroupedBeamState): number =>
|
|
1750
|
+
state.plans.reduce((total, plan) => total + plan.length, 0) +
|
|
1751
|
+
getPlanViaCount(state.plans) * 0.1 +
|
|
1752
|
+
assignmentLoadPenalty(
|
|
1753
|
+
state.assignment,
|
|
1754
|
+
this.preparedBuses,
|
|
1755
|
+
this.config.balanceLayerLoadByConnectionCount,
|
|
1756
|
+
) *
|
|
1757
|
+
getLayerLoadPenaltyWeight(this.config)
|
|
1758
|
+
const hasLengthConstraints = this.preparedBuses.some(
|
|
1759
|
+
(bus) => bus.maxLengthSkew !== undefined,
|
|
1760
|
+
)
|
|
1091
1761
|
for (const state of states) {
|
|
1092
1762
|
if (state.plans.length !== this.inputSrj.connections.length) continue
|
|
1763
|
+
const lengthMatching = this.matchCompletePlanLengths(state.plans)
|
|
1764
|
+
if (!lengthMatching.plans) continue
|
|
1765
|
+
const lengthMatchedPlans = lengthMatching.plans
|
|
1093
1766
|
const candidateOutput = buildOutputSimpleRouteJson({
|
|
1094
1767
|
inputSrj: this.inputSrj,
|
|
1095
|
-
plans:
|
|
1768
|
+
plans: lengthMatchedPlans,
|
|
1096
1769
|
layerNames: this.config.layerNames,
|
|
1097
1770
|
})
|
|
1098
|
-
if (
|
|
1771
|
+
if (
|
|
1772
|
+
!this.validateCompletePlans(lengthMatchedPlans, candidateOutput).valid
|
|
1773
|
+
) {
|
|
1099
1774
|
continue
|
|
1100
1775
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1776
|
+
const candidateState = { ...state, plans: lengthMatchedPlans }
|
|
1777
|
+
const candidateAdditionalViaCount =
|
|
1778
|
+
this.getCoordinatedAdditionalViaCount(lengthMatchedPlans)
|
|
1779
|
+
const candidateScore = getCompleteStateScore(candidateState)
|
|
1780
|
+
if (
|
|
1781
|
+
!bestState ||
|
|
1782
|
+
candidateAdditionalViaCount < bestAdditionalViaCount ||
|
|
1783
|
+
(candidateAdditionalViaCount === bestAdditionalViaCount &&
|
|
1784
|
+
candidateScore < bestMatchedScore)
|
|
1785
|
+
) {
|
|
1786
|
+
bestState = candidateState
|
|
1787
|
+
outputSrj = candidateOutput
|
|
1788
|
+
bestMatchedScore = candidateScore
|
|
1789
|
+
bestAdditionalViaCount = candidateAdditionalViaCount
|
|
1790
|
+
}
|
|
1791
|
+
if (!hasLengthConstraints) break
|
|
1104
1792
|
}
|
|
1105
1793
|
if (!bestState || !outputSrj) return null
|
|
1106
|
-
const score =
|
|
1107
|
-
bestState.plans.length === this.inputSrj.connections.length
|
|
1108
|
-
? bestState.plans.reduce((total, plan) => total + plan.length, 0) +
|
|
1109
|
-
getPlanViaCount(bestState.plans) * 0.1 +
|
|
1110
|
-
assignmentLoadPenalty(
|
|
1111
|
-
bestState.assignment,
|
|
1112
|
-
this.preparedBuses,
|
|
1113
|
-
this.config.balanceLayerLoadByConnectionCount,
|
|
1114
|
-
) *
|
|
1115
|
-
getLayerLoadPenaltyWeight(this.config)
|
|
1116
|
-
: Number.POSITIVE_INFINITY
|
|
1794
|
+
const score = bestMatchedScore
|
|
1117
1795
|
if (!Number.isFinite(score)) return null
|
|
1118
1796
|
|
|
1119
1797
|
const summary: FanoutAttemptSummary = {
|
|
@@ -1397,9 +2075,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1397
2075
|
this.solved = true
|
|
1398
2076
|
} else {
|
|
1399
2077
|
this.failed = true
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
2078
|
+
const validationMessage =
|
|
2079
|
+
this.lengthMatchingFailure?.message ??
|
|
2080
|
+
this.bestAttempt?.summary.validationIssues?.[0]?.message
|
|
2081
|
+
this.error = validationMessage
|
|
2082
|
+
? `FanoutSolver: ${validationMessage}`
|
|
2083
|
+
: this.bestAttempt
|
|
2084
|
+
? `FanoutSolver: best layer assignment routed ${this.bestAttempt.summary.routedConnectionCount}/${this.inputSrj.connections.length} connections`
|
|
2085
|
+
: "FanoutSolver: no layer assignment could be evaluated"
|
|
1403
2086
|
}
|
|
1404
2087
|
return
|
|
1405
2088
|
}
|
|
@@ -1468,14 +2151,17 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1468
2151
|
`FanoutSolver: completed output failed validation: ${validation.issues[0]?.message ?? "unknown validation error"}`,
|
|
1469
2152
|
)
|
|
1470
2153
|
}
|
|
1471
|
-
const finalSrj =
|
|
1472
|
-
|
|
2154
|
+
const finalSrj = addViaLayerMetadataToSrj({
|
|
2155
|
+
srj:
|
|
2156
|
+
this.endpointCompletion?.simpleRouteJson ?? this.bestAttempt.outputSrj,
|
|
2157
|
+
layerNames: this.config.layerNames,
|
|
2158
|
+
allowBlindAndBuriedVias: this.config.allowBlindAndBuriedVias,
|
|
2159
|
+
})
|
|
1473
2160
|
const finalTraceById = new Map(
|
|
1474
2161
|
(finalSrj.traces ?? []).map((trace) => [trace.pcb_trace_id, trace]),
|
|
1475
2162
|
)
|
|
1476
2163
|
return {
|
|
1477
|
-
simpleRouteJson:
|
|
1478
|
-
this.endpointCompletion?.simpleRouteJson ?? this.bestAttempt.outputSrj,
|
|
2164
|
+
simpleRouteJson: finalSrj,
|
|
1479
2165
|
fanoutTraces: this.bestAttempt.plans.flatMap((plan) => [
|
|
1480
2166
|
finalTraceById.get(plan.trace.pcb_trace_id) ?? plan.trace,
|
|
1481
2167
|
...(plan.planeEndpointTrace
|
|
@@ -1485,7 +2171,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1485
2171
|
]
|
|
1486
2172
|
: []),
|
|
1487
2173
|
]),
|
|
1488
|
-
completionTraces: this.endpointCompletion?.traces ?? []
|
|
2174
|
+
completionTraces: (this.endpointCompletion?.traces ?? []).map(
|
|
2175
|
+
(trace) => finalTraceById.get(trace.pcb_trace_id) ?? trace,
|
|
2176
|
+
),
|
|
1489
2177
|
...(this.endpointCompletion
|
|
1490
2178
|
? { endpointCompletion: this.endpointCompletion.report }
|
|
1491
2179
|
: {}),
|
|
@@ -1510,7 +2198,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1510
2198
|
}
|
|
1511
2199
|
}
|
|
1512
2200
|
|
|
1513
|
-
getOutputSimpleRouteJson():
|
|
2201
|
+
getOutputSimpleRouteJson(): SimpleRouteJsonWithFanoutPlanes {
|
|
1514
2202
|
return this.getOutput().simpleRouteJson
|
|
1515
2203
|
}
|
|
1516
2204
|
|