@tscircuit/fanout-solver 0.0.38 → 0.0.40
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 +7 -0
- package/lib/add-via-layer-metadata.ts +48 -0
- package/lib/complete-original-endpoints.ts +30 -2
- package/lib/fanout-solver.ts +905 -14
- 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 +237 -34
- package/lib/match-component-dogbone-via-sites.ts +655 -0
- package/lib/route-bus.ts +1026 -130
- package/lib/route-via-minimal-winding.ts +423 -76
- package/lib/types.ts +35 -7
- package/lib/validate-fanout-solution.ts +34 -6
- package/lib/validate-routed-copper-drc.ts +47 -8
- package/package.json +1 -1
package/lib/fanout-solver.ts
CHANGED
|
@@ -1,19 +1,30 @@
|
|
|
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"
|
|
11
16
|
import { matchBusPlanLengths } from "./match-bus-lengths"
|
|
17
|
+
import {
|
|
18
|
+
getComponentDogboneViaSiteCandidates,
|
|
19
|
+
matchComponentDogboneViaSites,
|
|
20
|
+
} from "./match-component-dogbone-via-sites"
|
|
21
|
+
import { connectionsShareElectricalNet } from "./net-identity"
|
|
12
22
|
import {
|
|
13
23
|
prepareFanoutBuses,
|
|
14
24
|
resolveAvailableBoundaryRegions,
|
|
15
25
|
} from "./prepare-buses"
|
|
16
26
|
import {
|
|
27
|
+
fanoutPlansAreClear,
|
|
17
28
|
type RouteBusStaticClearanceCache,
|
|
18
29
|
routeBus,
|
|
19
30
|
routeBusAlternatives,
|
|
@@ -30,6 +41,7 @@ import type {
|
|
|
30
41
|
FanoutSolverOutput,
|
|
31
42
|
FanoutValidationIssue,
|
|
32
43
|
PreparedBus,
|
|
44
|
+
SimpleRouteJsonWithFanoutPlanes,
|
|
33
45
|
} from "./types"
|
|
34
46
|
import { validateFanoutSolution } from "./validate-fanout-solution"
|
|
35
47
|
import { visualizeSimpleRouteJson } from "./visualize-simple-route-json"
|
|
@@ -40,6 +52,7 @@ interface ResolvedFanoutConfig {
|
|
|
40
52
|
viaHoleDiameter: number
|
|
41
53
|
clearance: number
|
|
42
54
|
compactBusTracks: boolean
|
|
55
|
+
allowBlindAndBuriedVias: boolean
|
|
43
56
|
allowSameNetMerges: boolean
|
|
44
57
|
singleLayerPushAndShove: boolean
|
|
45
58
|
singleLayerAdaptiveExits: boolean
|
|
@@ -59,6 +72,11 @@ interface GroupedBeamState {
|
|
|
59
72
|
plans: FanoutRoutePlan[]
|
|
60
73
|
}
|
|
61
74
|
|
|
75
|
+
interface MixedTerminationState {
|
|
76
|
+
plans: FanoutRoutePlan[]
|
|
77
|
+
failedBusIds: string[]
|
|
78
|
+
}
|
|
79
|
+
|
|
62
80
|
type RoutingStrategy = "default" | "group-by-layer" | "deep-first"
|
|
63
81
|
|
|
64
82
|
function resolvePositiveNumber(label: string, value: number): number {
|
|
@@ -131,6 +149,7 @@ function resolveConfig(
|
|
|
131
149
|
viaHoleDiameter,
|
|
132
150
|
clearance,
|
|
133
151
|
compactBusTracks: options.compactBusTracks ?? false,
|
|
152
|
+
allowBlindAndBuriedVias: options.allowBlindAndBuriedVias ?? true,
|
|
134
153
|
allowSameNetMerges: options.allowSameNetMerges ?? false,
|
|
135
154
|
singleLayerPushAndShove: options.singleLayerPushAndShove ?? false,
|
|
136
155
|
singleLayerAdaptiveExits: options.singleLayerAdaptiveExits ?? false,
|
|
@@ -216,6 +235,17 @@ function getLayerLoadPenaltyWeight(config: ResolvedFanoutConfig): number {
|
|
|
216
235
|
return config.balanceLayerLoadByConnectionCount ? 0.25 : 0.01
|
|
217
236
|
}
|
|
218
237
|
|
|
238
|
+
function comparePlaneRoutingPriority(
|
|
239
|
+
first: PreparedBus,
|
|
240
|
+
second: PreparedBus,
|
|
241
|
+
allowBlindAndBuriedVias: boolean,
|
|
242
|
+
): number {
|
|
243
|
+
const planeDifference =
|
|
244
|
+
Number(first.termination.type === "plane") -
|
|
245
|
+
Number(second.termination.type === "plane")
|
|
246
|
+
return allowBlindAndBuriedVias ? -planeDifference : planeDifference
|
|
247
|
+
}
|
|
248
|
+
|
|
219
249
|
function getPlanViaCount(plans: readonly FanoutRoutePlan[]): number {
|
|
220
250
|
return plans.reduce(
|
|
221
251
|
(count, plan) =>
|
|
@@ -439,6 +469,7 @@ function getCandidateEscapeLayersForBus(params: {
|
|
|
439
469
|
viaHoleDiameter: config.viaHoleDiameter,
|
|
440
470
|
clearance: config.clearance,
|
|
441
471
|
compactBusTracks: config.compactBusTracks,
|
|
472
|
+
allowBlindAndBuriedVias: config.allowBlindAndBuriedVias,
|
|
442
473
|
allowSameNetMerges: config.allowSameNetMerges,
|
|
443
474
|
staticClearanceCache,
|
|
444
475
|
}) !== null,
|
|
@@ -627,6 +658,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
627
658
|
viaDiameter: this.config.viaDiameter,
|
|
628
659
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
629
660
|
clearance: this.config.clearance,
|
|
661
|
+
allowBlindAndBuriedVias: this.config.allowBlindAndBuriedVias,
|
|
630
662
|
effort: this.options.endpointCompletionEffort,
|
|
631
663
|
routeDownstreamConnections: this.options.routeDownstreamConnections,
|
|
632
664
|
})
|
|
@@ -658,6 +690,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
658
690
|
preparedBuses: this.preparedBuses,
|
|
659
691
|
sharedBoundary: this.getValidationBoundary(),
|
|
660
692
|
clearance: this.config.clearance,
|
|
693
|
+
allowBlindAndBuriedVias: this.config.allowBlindAndBuriedVias,
|
|
661
694
|
})
|
|
662
695
|
}
|
|
663
696
|
|
|
@@ -670,10 +703,841 @@ export class FanoutSolver extends BaseSolver {
|
|
|
670
703
|
inputSrj: this.inputSrj,
|
|
671
704
|
sharedBoundary: this.getValidationBoundary(),
|
|
672
705
|
clearance: this.config.clearance,
|
|
706
|
+
allowBlindAndBuriedVias: this.config.allowBlindAndBuriedVias,
|
|
673
707
|
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
674
708
|
})
|
|
675
709
|
}
|
|
676
710
|
|
|
711
|
+
/**
|
|
712
|
+
* Through-all source vias from a wide boundary bus can consume the only
|
|
713
|
+
* legal dogbone channel for nearby plane pads. Conversely, routing hundreds
|
|
714
|
+
* of singleton plane drops first can strand the boundary bus. Search a tiny
|
|
715
|
+
* number of whole-bus boundary alternatives, then fill the remaining plane
|
|
716
|
+
* dogbones. This is intentionally bounded independently of the number of
|
|
717
|
+
* plane drops so dense power fields cannot explode the general beam search.
|
|
718
|
+
*/
|
|
719
|
+
private routeDenseThroughAllMixedTerminations(params: {
|
|
720
|
+
busLayerAssignments: Readonly<Record<string, string>>
|
|
721
|
+
busesInRoutingOrder: readonly PreparedBus[]
|
|
722
|
+
}): MixedTerminationState | null {
|
|
723
|
+
if (this.config.allowBlindAndBuriedVias) return null
|
|
724
|
+
|
|
725
|
+
const boundaryBuses = params.busesInRoutingOrder
|
|
726
|
+
.filter((bus) => bus.termination.type === "boundary")
|
|
727
|
+
.toSorted((first, second) => {
|
|
728
|
+
const cornerBandDifference =
|
|
729
|
+
Number(
|
|
730
|
+
Boolean(getCornerBandSide(second.exitEdge, second.preferredExit)),
|
|
731
|
+
) -
|
|
732
|
+
Number(
|
|
733
|
+
Boolean(getCornerBandSide(first.exitEdge, first.preferredExit)),
|
|
734
|
+
)
|
|
735
|
+
if (cornerBandDifference !== 0) return cornerBandDifference
|
|
736
|
+
const firstIsCorner = Boolean(
|
|
737
|
+
getCornerBandSide(first.exitEdge, first.preferredExit),
|
|
738
|
+
)
|
|
739
|
+
if (!firstIsCorner) {
|
|
740
|
+
const getSourceSpan = (bus: PreparedBus): number => {
|
|
741
|
+
const xCoordinates = bus.connections.map(
|
|
742
|
+
(connection) => connection.sourcePoint.x,
|
|
743
|
+
)
|
|
744
|
+
const yCoordinates = bus.connections.map(
|
|
745
|
+
(connection) => connection.sourcePoint.y,
|
|
746
|
+
)
|
|
747
|
+
return (
|
|
748
|
+
Math.max(...xCoordinates) -
|
|
749
|
+
Math.min(...xCoordinates) +
|
|
750
|
+
Math.max(...yCoordinates) -
|
|
751
|
+
Math.min(...yCoordinates)
|
|
752
|
+
)
|
|
753
|
+
}
|
|
754
|
+
const sourceSpanDifference =
|
|
755
|
+
getSourceSpan(second) - getSourceSpan(first)
|
|
756
|
+
if (Math.abs(sourceSpanDifference) > 1e-9) {
|
|
757
|
+
return sourceSpanDifference
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
const firstLayer = params.busLayerAssignments[first.busId]
|
|
761
|
+
const secondLayer = params.busLayerAssignments[second.busId]
|
|
762
|
+
const layerDifference =
|
|
763
|
+
this.config.layerNames.indexOf(firstLayer ?? "") -
|
|
764
|
+
this.config.layerNames.indexOf(secondLayer ?? "")
|
|
765
|
+
return -layerDifference
|
|
766
|
+
})
|
|
767
|
+
// Preserve the caller/input order for the dense singleton fill. The
|
|
768
|
+
// general routing sort is useful for heterogeneous buses, but ordering a
|
|
769
|
+
// regular BGA power field by obstacle depth creates artificial local
|
|
770
|
+
// dead-ends and needlessly triggers the widened boundary beam. The local
|
|
771
|
+
// rip-up/retry below handles the genuinely constrained drops.
|
|
772
|
+
const planeBuses = this.preparedBuses.filter(
|
|
773
|
+
(bus) => bus.termination.type === "plane",
|
|
774
|
+
)
|
|
775
|
+
if (
|
|
776
|
+
boundaryBuses.length === 0 ||
|
|
777
|
+
boundaryBuses.length > 4 ||
|
|
778
|
+
planeBuses.length < 8 ||
|
|
779
|
+
boundaryBuses.some((bus) => !busUsesCoordinatedWinding(bus)) ||
|
|
780
|
+
planeBuses.some((bus) => bus.connections.length !== 1) ||
|
|
781
|
+
boundaryBuses.length + planeBuses.length !==
|
|
782
|
+
params.busesInRoutingOrder.length
|
|
783
|
+
) {
|
|
784
|
+
return null
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const connectionNameByIndex = new Map(
|
|
788
|
+
this.preparedBuses.flatMap((bus) =>
|
|
789
|
+
bus.connections.map(
|
|
790
|
+
(connection) =>
|
|
791
|
+
[connection.connectionIndex, connection.connection.name] as const,
|
|
792
|
+
),
|
|
793
|
+
),
|
|
794
|
+
)
|
|
795
|
+
const preferredBoundaryPerpendicularSideByBusId = new Map(
|
|
796
|
+
boundaryBuses.map((bus) => [bus.busId, 1 as const]),
|
|
797
|
+
)
|
|
798
|
+
const preferBoundaryOutwardByBusId = new Map(
|
|
799
|
+
boundaryBuses.map((bus) => [
|
|
800
|
+
bus.busId,
|
|
801
|
+
getExitEdgeForDirection(bus.direction) !== bus.exitEdge,
|
|
802
|
+
]),
|
|
803
|
+
)
|
|
804
|
+
const canShareCopper = (
|
|
805
|
+
firstConnectionIndex: number,
|
|
806
|
+
secondConnectionIndex: number,
|
|
807
|
+
): boolean => {
|
|
808
|
+
if (!this.config.allowSameNetMerges) return false
|
|
809
|
+
const firstConnectionName =
|
|
810
|
+
connectionNameByIndex.get(firstConnectionIndex)
|
|
811
|
+
const secondConnectionName = connectionNameByIndex.get(
|
|
812
|
+
secondConnectionIndex,
|
|
813
|
+
)
|
|
814
|
+
return Boolean(
|
|
815
|
+
firstConnectionName &&
|
|
816
|
+
secondConnectionName &&
|
|
817
|
+
connectionsShareElectricalNet(
|
|
818
|
+
this.routingSrj,
|
|
819
|
+
firstConnectionName,
|
|
820
|
+
secondConnectionName,
|
|
821
|
+
),
|
|
822
|
+
)
|
|
823
|
+
}
|
|
824
|
+
const seedViaPoints = matchComponentDogboneViaSites(
|
|
825
|
+
[...planeBuses, boundaryBuses[0]!],
|
|
826
|
+
{
|
|
827
|
+
viaDiameter: this.config.viaDiameter,
|
|
828
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
829
|
+
traceWidth: this.config.traceWidth,
|
|
830
|
+
clearance: this.config.clearance,
|
|
831
|
+
maximumSearchStates: 20_000,
|
|
832
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
833
|
+
preferBoundaryOutwardByBusId,
|
|
834
|
+
canShareCopper,
|
|
835
|
+
},
|
|
836
|
+
)
|
|
837
|
+
if (seedViaPoints) {
|
|
838
|
+
let fixedViaPointsByConnectionIndex: ReadonlyMap<
|
|
839
|
+
number,
|
|
840
|
+
{ x: number; y: number }
|
|
841
|
+
> = seedViaPoints
|
|
842
|
+
let matchedPlans: FanoutRoutePlan[] = []
|
|
843
|
+
let matchedRoutingSucceeded = true
|
|
844
|
+
const getReservedVias = (bus: PreparedBus) => {
|
|
845
|
+
const currentConnectionNames = new Set(
|
|
846
|
+
bus.connections.map((connection) => connection.connection.name),
|
|
847
|
+
)
|
|
848
|
+
return this.preparedBuses.flatMap((preparedBus) => {
|
|
849
|
+
const targetLayer = params.busLayerAssignments[preparedBus.busId]
|
|
850
|
+
if (!targetLayer) return []
|
|
851
|
+
return preparedBus.connections.flatMap((connection) => {
|
|
852
|
+
if (currentConnectionNames.has(connection.connection.name))
|
|
853
|
+
return []
|
|
854
|
+
const center = fixedViaPointsByConnectionIndex.get(
|
|
855
|
+
connection.connectionIndex,
|
|
856
|
+
)
|
|
857
|
+
if (!center) return []
|
|
858
|
+
return [
|
|
859
|
+
{
|
|
860
|
+
connectionName: connection.connection.name,
|
|
861
|
+
via: {
|
|
862
|
+
center,
|
|
863
|
+
diameter: this.config.viaDiameter,
|
|
864
|
+
spanLayers: getViaSpanLayers({
|
|
865
|
+
fromLayer: connection.sourceLayer,
|
|
866
|
+
toLayer: targetLayer,
|
|
867
|
+
layerNames: this.config.layerNames,
|
|
868
|
+
allowBlindAndBuriedVias: false,
|
|
869
|
+
}),
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
]
|
|
873
|
+
})
|
|
874
|
+
})
|
|
875
|
+
}
|
|
876
|
+
const routeMatchedBoundaryBus = (bus: PreparedBus): boolean => {
|
|
877
|
+
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
878
|
+
if (!targetLayer) {
|
|
879
|
+
return false
|
|
880
|
+
}
|
|
881
|
+
const routeParams = {
|
|
882
|
+
srj: this.routingSrj,
|
|
883
|
+
bus,
|
|
884
|
+
targetLayer,
|
|
885
|
+
acceptedPlans: matchedPlans,
|
|
886
|
+
layerNames: this.config.layerNames,
|
|
887
|
+
traceWidth: this.config.traceWidth,
|
|
888
|
+
viaDiameter: this.config.viaDiameter,
|
|
889
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
890
|
+
clearance: this.config.clearance,
|
|
891
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
892
|
+
allowBlindAndBuriedVias: false,
|
|
893
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
894
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
895
|
+
fixedViaPointsByConnectionIndex,
|
|
896
|
+
reservedVias: getReservedVias(bus),
|
|
897
|
+
viaMinimalOnly: true,
|
|
898
|
+
} as const
|
|
899
|
+
let busPlans = routeBusAlternatives(routeParams, 1)[0]
|
|
900
|
+
if (busPlans && bus.maxLengthSkew !== undefined) {
|
|
901
|
+
const lengths = busPlans.map((plan) => plan.length)
|
|
902
|
+
const rawSkew = Math.max(...lengths) - Math.min(...lengths)
|
|
903
|
+
const needsRouteDiversity =
|
|
904
|
+
rawSkew - bus.maxLengthSkew > Math.max(1, bus.maxLengthSkew * 0.25)
|
|
905
|
+
// Only pay for additional A* variants when the first topology is so
|
|
906
|
+
// skewed that compact meanders are unlikely to absorb the deficit.
|
|
907
|
+
// This keeps already-near-matched buses on the single-attempt path.
|
|
908
|
+
if (needsRouteDiversity) {
|
|
909
|
+
busPlans = routeBusAlternatives(routeParams, 3).toSorted(
|
|
910
|
+
(first, second) => {
|
|
911
|
+
const firstLengths = first.map((plan) => plan.length)
|
|
912
|
+
const secondLengths = second.map((plan) => plan.length)
|
|
913
|
+
return (
|
|
914
|
+
Math.max(...firstLengths) -
|
|
915
|
+
Math.min(...firstLengths) -
|
|
916
|
+
(Math.max(...secondLengths) - Math.min(...secondLengths))
|
|
917
|
+
)
|
|
918
|
+
},
|
|
919
|
+
)[0]
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
if (!busPlans) {
|
|
923
|
+
return false
|
|
924
|
+
}
|
|
925
|
+
matchedPlans.push(...busPlans)
|
|
926
|
+
return true
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const firstBoundaryBus = boundaryBuses[0]!
|
|
930
|
+
const routedBoundaryBuses: PreparedBus[] = []
|
|
931
|
+
if (routeMatchedBoundaryBus(firstBoundaryBus)) {
|
|
932
|
+
routedBoundaryBuses.push(firstBoundaryBus)
|
|
933
|
+
} else {
|
|
934
|
+
matchedRoutingSucceeded = false
|
|
935
|
+
}
|
|
936
|
+
const remainingBoundaryBuses = boundaryBuses.slice(1)
|
|
937
|
+
while (matchedRoutingSucceeded && remainingBoundaryBuses.length > 0) {
|
|
938
|
+
const blockingSegments = matchedPlans.flatMap((plan) =>
|
|
939
|
+
plan.segments.map((segment) => ({
|
|
940
|
+
connectionIndex: plan.connectionIndex,
|
|
941
|
+
segment,
|
|
942
|
+
})),
|
|
943
|
+
)
|
|
944
|
+
let selectedBusIndex = -1
|
|
945
|
+
for (
|
|
946
|
+
let candidateIndex = 0;
|
|
947
|
+
candidateIndex < remainingBoundaryBuses.length;
|
|
948
|
+
candidateIndex++
|
|
949
|
+
) {
|
|
950
|
+
const candidateBus = remainingBoundaryBuses[candidateIndex]!
|
|
951
|
+
const extendedViaPoints = matchComponentDogboneViaSites(
|
|
952
|
+
[...planeBuses, ...routedBoundaryBuses, candidateBus],
|
|
953
|
+
{
|
|
954
|
+
viaDiameter: this.config.viaDiameter,
|
|
955
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
956
|
+
traceWidth: this.config.traceWidth,
|
|
957
|
+
clearance: this.config.clearance,
|
|
958
|
+
maximumSearchStates: 100_000,
|
|
959
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
960
|
+
preferBoundaryOutwardByBusId,
|
|
961
|
+
fixedViaPointsByConnectionIndex: fixedViaPointsByConnectionIndex,
|
|
962
|
+
blockingSegments,
|
|
963
|
+
canShareCopper,
|
|
964
|
+
},
|
|
965
|
+
)
|
|
966
|
+
if (!extendedViaPoints) continue
|
|
967
|
+
const previousFixedViaPoints = fixedViaPointsByConnectionIndex
|
|
968
|
+
const previousPlanCount = matchedPlans.length
|
|
969
|
+
const laterBuses = remainingBoundaryBuses.filter(
|
|
970
|
+
(_, laterIndex) => laterIndex !== candidateIndex,
|
|
971
|
+
)
|
|
972
|
+
let candidateFixedViaPoints: ReadonlyMap<
|
|
973
|
+
number,
|
|
974
|
+
{ x: number; y: number }
|
|
975
|
+
> = extendedViaPoints
|
|
976
|
+
if (laterBuses.length === 1) {
|
|
977
|
+
const laterBus = laterBuses[0]!
|
|
978
|
+
const futureAssignment = matchComponentDogboneViaSites(
|
|
979
|
+
[...planeBuses, ...routedBoundaryBuses, candidateBus, laterBus],
|
|
980
|
+
{
|
|
981
|
+
viaDiameter: this.config.viaDiameter,
|
|
982
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
983
|
+
traceWidth: this.config.traceWidth,
|
|
984
|
+
clearance: this.config.clearance,
|
|
985
|
+
maximumSearchStates: 100_000,
|
|
986
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
987
|
+
preferBoundaryOutwardByBusId,
|
|
988
|
+
fixedViaPointsByConnectionIndex: extendedViaPoints,
|
|
989
|
+
blockingSegments,
|
|
990
|
+
canShareCopper,
|
|
991
|
+
},
|
|
992
|
+
)
|
|
993
|
+
if (futureAssignment) {
|
|
994
|
+
const candidateCountByConnectionIndex = new Map<number, number>()
|
|
995
|
+
for (const candidate of getComponentDogboneViaSiteCandidates(
|
|
996
|
+
[laterBus],
|
|
997
|
+
{
|
|
998
|
+
viaDiameter: this.config.viaDiameter,
|
|
999
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1000
|
+
traceWidth: this.config.traceWidth,
|
|
1001
|
+
clearance: this.config.clearance,
|
|
1002
|
+
blockingSegments,
|
|
1003
|
+
canShareCopper,
|
|
1004
|
+
},
|
|
1005
|
+
)) {
|
|
1006
|
+
candidateCountByConnectionIndex.set(
|
|
1007
|
+
candidate.connectionIndex,
|
|
1008
|
+
(candidateCountByConnectionIndex.get(
|
|
1009
|
+
candidate.connectionIndex,
|
|
1010
|
+
) ?? 0) + 1,
|
|
1011
|
+
)
|
|
1012
|
+
}
|
|
1013
|
+
const constrainedConnections = laterBus.connections.toSorted(
|
|
1014
|
+
(first, second) =>
|
|
1015
|
+
(candidateCountByConnectionIndex.get(first.connectionIndex) ??
|
|
1016
|
+
0) -
|
|
1017
|
+
(candidateCountByConnectionIndex.get(
|
|
1018
|
+
second.connectionIndex,
|
|
1019
|
+
) ?? 0) || first.connectionIndex - second.connectionIndex,
|
|
1020
|
+
)
|
|
1021
|
+
const repairedViaPoints = new Map(extendedViaPoints)
|
|
1022
|
+
for (const connection of constrainedConnections) {
|
|
1023
|
+
const criticalPoint = futureAssignment.get(
|
|
1024
|
+
connection.connectionIndex,
|
|
1025
|
+
)
|
|
1026
|
+
if (criticalPoint) {
|
|
1027
|
+
repairedViaPoints.set(
|
|
1028
|
+
connection.connectionIndex,
|
|
1029
|
+
criticalPoint,
|
|
1030
|
+
)
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
candidateFixedViaPoints = repairedViaPoints
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
fixedViaPointsByConnectionIndex = candidateFixedViaPoints
|
|
1037
|
+
if (routeMatchedBoundaryBus(candidateBus)) {
|
|
1038
|
+
const candidateLeavesAFeasibleExtension =
|
|
1039
|
+
laterBuses.length === 0 ||
|
|
1040
|
+
laterBuses.some((laterBus) => {
|
|
1041
|
+
const lookaheadBlockingSegments = matchedPlans.flatMap((plan) =>
|
|
1042
|
+
plan.segments.map((segment) => ({
|
|
1043
|
+
connectionIndex: plan.connectionIndex,
|
|
1044
|
+
segment,
|
|
1045
|
+
})),
|
|
1046
|
+
)
|
|
1047
|
+
return Boolean(
|
|
1048
|
+
matchComponentDogboneViaSites(
|
|
1049
|
+
[
|
|
1050
|
+
...planeBuses,
|
|
1051
|
+
...routedBoundaryBuses,
|
|
1052
|
+
candidateBus,
|
|
1053
|
+
laterBus,
|
|
1054
|
+
],
|
|
1055
|
+
{
|
|
1056
|
+
viaDiameter: this.config.viaDiameter,
|
|
1057
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1058
|
+
traceWidth: this.config.traceWidth,
|
|
1059
|
+
clearance: this.config.clearance,
|
|
1060
|
+
maximumSearchStates: 100_000,
|
|
1061
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
1062
|
+
preferBoundaryOutwardByBusId,
|
|
1063
|
+
fixedViaPointsByConnectionIndex:
|
|
1064
|
+
fixedViaPointsByConnectionIndex,
|
|
1065
|
+
blockingSegments: lookaheadBlockingSegments,
|
|
1066
|
+
canShareCopper,
|
|
1067
|
+
},
|
|
1068
|
+
),
|
|
1069
|
+
)
|
|
1070
|
+
})
|
|
1071
|
+
if (candidateLeavesAFeasibleExtension) {
|
|
1072
|
+
selectedBusIndex = candidateIndex
|
|
1073
|
+
routedBoundaryBuses.push(candidateBus)
|
|
1074
|
+
break
|
|
1075
|
+
}
|
|
1076
|
+
matchedPlans.splice(previousPlanCount)
|
|
1077
|
+
}
|
|
1078
|
+
fixedViaPointsByConnectionIndex = previousFixedViaPoints
|
|
1079
|
+
}
|
|
1080
|
+
if (selectedBusIndex < 0) {
|
|
1081
|
+
matchedRoutingSucceeded = false
|
|
1082
|
+
break
|
|
1083
|
+
}
|
|
1084
|
+
remainingBoundaryBuses.splice(selectedBusIndex, 1)
|
|
1085
|
+
}
|
|
1086
|
+
if (matchedRoutingSucceeded) {
|
|
1087
|
+
let feasibleViaPoints: Map<number, { x: number; y: number }> | null =
|
|
1088
|
+
null
|
|
1089
|
+
const matchViaPointsAroundPlans = (
|
|
1090
|
+
candidatePlans: readonly FanoutRoutePlan[],
|
|
1091
|
+
): Map<number, { x: number; y: number }> | null => {
|
|
1092
|
+
const fixedBoundaryViaPoints = new Map(
|
|
1093
|
+
candidatePlans.flatMap((plan) =>
|
|
1094
|
+
plan.via
|
|
1095
|
+
? [[plan.connectionIndex, plan.via.center] as const]
|
|
1096
|
+
: [],
|
|
1097
|
+
),
|
|
1098
|
+
)
|
|
1099
|
+
return matchComponentDogboneViaSites(
|
|
1100
|
+
[...planeBuses, ...boundaryBuses],
|
|
1101
|
+
{
|
|
1102
|
+
viaDiameter: this.config.viaDiameter,
|
|
1103
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1104
|
+
traceWidth: this.config.traceWidth,
|
|
1105
|
+
clearance: this.config.clearance,
|
|
1106
|
+
maximumSearchStates: 100_000,
|
|
1107
|
+
preferredBoundaryPerpendicularSideByBusId,
|
|
1108
|
+
preferBoundaryOutwardByBusId,
|
|
1109
|
+
fixedViaPointsByConnectionIndex: fixedBoundaryViaPoints,
|
|
1110
|
+
blockingSegments: candidatePlans.flatMap((plan) =>
|
|
1111
|
+
plan.segments.map((segment) => ({
|
|
1112
|
+
connectionIndex: plan.connectionIndex,
|
|
1113
|
+
segment,
|
|
1114
|
+
})),
|
|
1115
|
+
),
|
|
1116
|
+
canShareCopper,
|
|
1117
|
+
},
|
|
1118
|
+
)
|
|
1119
|
+
}
|
|
1120
|
+
const matchedLengthResult = matchBusPlanLengths({
|
|
1121
|
+
plans: matchedPlans,
|
|
1122
|
+
preparedBuses: this.preparedBuses,
|
|
1123
|
+
inputSrj: this.inputSrj,
|
|
1124
|
+
sharedBoundary: this.getValidationBoundary(),
|
|
1125
|
+
clearance: this.config.clearance,
|
|
1126
|
+
allowBlindAndBuriedVias: false,
|
|
1127
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1128
|
+
allowMatchingInsideDenseBounds: true,
|
|
1129
|
+
candidatePlansAreFeasible: (candidatePlans) => {
|
|
1130
|
+
const candidateViaPoints = matchViaPointsAroundPlans(candidatePlans)
|
|
1131
|
+
if (!candidateViaPoints) return false
|
|
1132
|
+
feasibleViaPoints = candidateViaPoints
|
|
1133
|
+
return true
|
|
1134
|
+
},
|
|
1135
|
+
})
|
|
1136
|
+
if (matchedLengthResult.plans) {
|
|
1137
|
+
matchedPlans = matchedLengthResult.plans
|
|
1138
|
+
const rematchedViaPoints =
|
|
1139
|
+
feasibleViaPoints ?? matchViaPointsAroundPlans(matchedPlans)
|
|
1140
|
+
if (rematchedViaPoints) {
|
|
1141
|
+
fixedViaPointsByConnectionIndex = rematchedViaPoints
|
|
1142
|
+
} else {
|
|
1143
|
+
matchedRoutingSucceeded = false
|
|
1144
|
+
}
|
|
1145
|
+
} else {
|
|
1146
|
+
matchedRoutingSucceeded = false
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
if (matchedRoutingSucceeded) {
|
|
1150
|
+
for (const bus of planeBuses) {
|
|
1151
|
+
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
1152
|
+
const busPlans = targetLayer
|
|
1153
|
+
? routeBus({
|
|
1154
|
+
srj: this.routingSrj,
|
|
1155
|
+
bus,
|
|
1156
|
+
targetLayer,
|
|
1157
|
+
acceptedPlans: matchedPlans,
|
|
1158
|
+
layerNames: this.config.layerNames,
|
|
1159
|
+
traceWidth: this.config.traceWidth,
|
|
1160
|
+
viaDiameter: this.config.viaDiameter,
|
|
1161
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1162
|
+
clearance: this.config.clearance,
|
|
1163
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
1164
|
+
allowBlindAndBuriedVias: false,
|
|
1165
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1166
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
1167
|
+
fixedViaPointsByConnectionIndex,
|
|
1168
|
+
})
|
|
1169
|
+
: null
|
|
1170
|
+
if (!busPlans) {
|
|
1171
|
+
matchedRoutingSucceeded = false
|
|
1172
|
+
break
|
|
1173
|
+
}
|
|
1174
|
+
matchedPlans.push(...busPlans)
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
if (
|
|
1178
|
+
matchedRoutingSucceeded &&
|
|
1179
|
+
fanoutPlansAreClear({
|
|
1180
|
+
plans: matchedPlans,
|
|
1181
|
+
srj: this.routingSrj,
|
|
1182
|
+
sharedBoundary: boundaryBuses[0]!.sharedBoundary,
|
|
1183
|
+
clearance: this.config.clearance,
|
|
1184
|
+
allowBlindAndBuriedVias: false,
|
|
1185
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1186
|
+
})
|
|
1187
|
+
) {
|
|
1188
|
+
return { plans: matchedPlans, failedBusIds: [] }
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
const maximumStates = 8
|
|
1193
|
+
const getBoundaryStates = (
|
|
1194
|
+
alternativesPerBoundaryBus: number,
|
|
1195
|
+
initialPlans: readonly FanoutRoutePlan[] = [],
|
|
1196
|
+
): MixedTerminationState[] | null => {
|
|
1197
|
+
let states: MixedTerminationState[] = [
|
|
1198
|
+
{ plans: [...initialPlans], failedBusIds: [] },
|
|
1199
|
+
]
|
|
1200
|
+
for (const bus of boundaryBuses) {
|
|
1201
|
+
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
1202
|
+
if (!targetLayer) return null
|
|
1203
|
+
const nextStates: MixedTerminationState[] = []
|
|
1204
|
+
const alternativesByState = states.map((state) => ({
|
|
1205
|
+
state,
|
|
1206
|
+
alternatives: routeBusAlternatives(
|
|
1207
|
+
{
|
|
1208
|
+
srj: this.routingSrj,
|
|
1209
|
+
bus,
|
|
1210
|
+
targetLayer,
|
|
1211
|
+
acceptedPlans: state.plans,
|
|
1212
|
+
layerNames: this.config.layerNames,
|
|
1213
|
+
traceWidth: this.config.traceWidth,
|
|
1214
|
+
viaDiameter: this.config.viaDiameter,
|
|
1215
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1216
|
+
clearance: this.config.clearance,
|
|
1217
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
1218
|
+
allowBlindAndBuriedVias: false,
|
|
1219
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1220
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
1221
|
+
},
|
|
1222
|
+
alternativesPerBoundaryBus,
|
|
1223
|
+
),
|
|
1224
|
+
}))
|
|
1225
|
+
for (
|
|
1226
|
+
let alternativeIndex = 0;
|
|
1227
|
+
alternativeIndex < alternativesPerBoundaryBus;
|
|
1228
|
+
alternativeIndex++
|
|
1229
|
+
) {
|
|
1230
|
+
for (const { state, alternatives } of alternativesByState) {
|
|
1231
|
+
const alternative = alternatives[alternativeIndex]
|
|
1232
|
+
if (!alternative) continue
|
|
1233
|
+
nextStates.push({
|
|
1234
|
+
plans: [...state.plans, ...alternative],
|
|
1235
|
+
failedBusIds: [],
|
|
1236
|
+
})
|
|
1237
|
+
if (nextStates.length >= maximumStates) break
|
|
1238
|
+
}
|
|
1239
|
+
if (nextStates.length >= maximumStates) break
|
|
1240
|
+
}
|
|
1241
|
+
if (nextStates.length === 0) return null
|
|
1242
|
+
states = nextStates
|
|
1243
|
+
}
|
|
1244
|
+
return states
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
const getJointReservedBoundaryState = (
|
|
1248
|
+
initialPlans: readonly FanoutRoutePlan[],
|
|
1249
|
+
): MixedTerminationState | null => {
|
|
1250
|
+
if (boundaryBuses.length !== 2) return null
|
|
1251
|
+
const [firstBus, secondBus] = boundaryBuses
|
|
1252
|
+
if (!firstBus || !secondBus) return null
|
|
1253
|
+
const routeBoundaryBus = (
|
|
1254
|
+
bus: PreparedBus,
|
|
1255
|
+
acceptedPlans: FanoutRoutePlan[],
|
|
1256
|
+
rejectedViaMinimalCandidates?: FanoutRoutePlan[][],
|
|
1257
|
+
): FanoutRoutePlan[] | null => {
|
|
1258
|
+
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
1259
|
+
if (!targetLayer) return null
|
|
1260
|
+
return (
|
|
1261
|
+
routeBusAlternatives(
|
|
1262
|
+
{
|
|
1263
|
+
srj: this.routingSrj,
|
|
1264
|
+
bus,
|
|
1265
|
+
targetLayer,
|
|
1266
|
+
acceptedPlans,
|
|
1267
|
+
layerNames: this.config.layerNames,
|
|
1268
|
+
traceWidth: this.config.traceWidth,
|
|
1269
|
+
viaDiameter: this.config.viaDiameter,
|
|
1270
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1271
|
+
clearance: this.config.clearance,
|
|
1272
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
1273
|
+
allowBlindAndBuriedVias: false,
|
|
1274
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1275
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
1276
|
+
rejectedViaMinimalCandidates,
|
|
1277
|
+
stopAfterFirstRejectedViaMinimalCandidate:
|
|
1278
|
+
rejectedViaMinimalCandidates !== undefined,
|
|
1279
|
+
},
|
|
1280
|
+
1,
|
|
1281
|
+
)[0] ?? null
|
|
1282
|
+
)
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
const firstPlans = routeBoundaryBus(firstBus, [...initialPlans])
|
|
1286
|
+
if (!firstPlans) return null
|
|
1287
|
+
const rejectedSecondCandidates: FanoutRoutePlan[][] = []
|
|
1288
|
+
const secondPlans = routeBoundaryBus(
|
|
1289
|
+
secondBus,
|
|
1290
|
+
[...initialPlans, ...firstPlans],
|
|
1291
|
+
rejectedSecondCandidates,
|
|
1292
|
+
)
|
|
1293
|
+
if (secondPlans) {
|
|
1294
|
+
return {
|
|
1295
|
+
plans: [...initialPlans, ...firstPlans, ...secondPlans],
|
|
1296
|
+
failedBusIds: [],
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
const rejectedSecondPlans = rejectedSecondCandidates[0]
|
|
1301
|
+
if (!rejectedSecondPlans) return null
|
|
1302
|
+
// Keep the candidate's exact copper/vias as immutable blockers while
|
|
1303
|
+
// rerouting the earlier bus, but exclude it from corner-slot allocation.
|
|
1304
|
+
const reservedSecondPlans = rejectedSecondPlans.map((plan) => ({
|
|
1305
|
+
...plan,
|
|
1306
|
+
termination: {
|
|
1307
|
+
type: "plane" as const,
|
|
1308
|
+
layer: plan.targetLayer,
|
|
1309
|
+
},
|
|
1310
|
+
}))
|
|
1311
|
+
const reroutedFirstPlans = routeBoundaryBus(firstBus, [
|
|
1312
|
+
...initialPlans,
|
|
1313
|
+
...reservedSecondPlans,
|
|
1314
|
+
])
|
|
1315
|
+
if (!reroutedFirstPlans) return null
|
|
1316
|
+
const combinedPlans = [
|
|
1317
|
+
...initialPlans,
|
|
1318
|
+
...reroutedFirstPlans,
|
|
1319
|
+
...rejectedSecondPlans,
|
|
1320
|
+
]
|
|
1321
|
+
if (
|
|
1322
|
+
!fanoutPlansAreClear({
|
|
1323
|
+
plans: combinedPlans,
|
|
1324
|
+
srj: this.routingSrj,
|
|
1325
|
+
sharedBoundary: firstBus.sharedBoundary,
|
|
1326
|
+
clearance: this.config.clearance,
|
|
1327
|
+
allowBlindAndBuriedVias: false,
|
|
1328
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1329
|
+
})
|
|
1330
|
+
) {
|
|
1331
|
+
return null
|
|
1332
|
+
}
|
|
1333
|
+
return { plans: combinedPlans, failedBusIds: [] }
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
const planeBusById = new Map(planeBuses.map((bus) => [bus.busId, bus]))
|
|
1337
|
+
const routePlaneBus = (
|
|
1338
|
+
bus: PreparedBus,
|
|
1339
|
+
acceptedPlans: FanoutRoutePlan[],
|
|
1340
|
+
blockingBusCounts?: Map<string, number>,
|
|
1341
|
+
): FanoutRoutePlan[] | null => {
|
|
1342
|
+
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
1343
|
+
if (!targetLayer) return null
|
|
1344
|
+
return routeBus({
|
|
1345
|
+
srj: this.routingSrj,
|
|
1346
|
+
bus,
|
|
1347
|
+
targetLayer,
|
|
1348
|
+
acceptedPlans,
|
|
1349
|
+
layerNames: this.config.layerNames,
|
|
1350
|
+
traceWidth: this.config.traceWidth,
|
|
1351
|
+
viaDiameter: this.config.viaDiameter,
|
|
1352
|
+
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1353
|
+
clearance: this.config.clearance,
|
|
1354
|
+
compactBusTracks: this.config.compactBusTracks,
|
|
1355
|
+
allowBlindAndBuriedVias: false,
|
|
1356
|
+
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1357
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
1358
|
+
blockingBusCounts,
|
|
1359
|
+
})
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
const routePlaneOrder = (
|
|
1363
|
+
boundaryPlans: readonly FanoutRoutePlan[],
|
|
1364
|
+
planeOrder: readonly PreparedBus[],
|
|
1365
|
+
): MixedTerminationState => {
|
|
1366
|
+
const state: MixedTerminationState = {
|
|
1367
|
+
plans: [...boundaryPlans],
|
|
1368
|
+
failedBusIds: [],
|
|
1369
|
+
}
|
|
1370
|
+
for (const bus of planeOrder) {
|
|
1371
|
+
const blockingBusCounts = new Map<string, number>()
|
|
1372
|
+
let busPlans = routePlaneBus(bus, state.plans, blockingBusCounts)
|
|
1373
|
+
|
|
1374
|
+
// A plane dogbone can lose its only local channel to one or two
|
|
1375
|
+
// earlier singleton drops. Rip up only the strongest local blockers,
|
|
1376
|
+
// route the constrained drop first, then put the displaced drops back.
|
|
1377
|
+
// Boundary buses are immutable here and the search is capped at 36
|
|
1378
|
+
// blocker pairs, so dense fields remain predictable.
|
|
1379
|
+
if (!busPlans) {
|
|
1380
|
+
const blockerIds = [...blockingBusCounts.entries()]
|
|
1381
|
+
.filter(([busId]) =>
|
|
1382
|
+
state.plans.some((plan) => plan.busId === busId),
|
|
1383
|
+
)
|
|
1384
|
+
.filter(([busId]) => planeBusById.has(busId))
|
|
1385
|
+
.toSorted(
|
|
1386
|
+
([, firstCount], [, secondCount]) => secondCount - firstCount,
|
|
1387
|
+
)
|
|
1388
|
+
.slice(0, 8)
|
|
1389
|
+
.map(([busId]) => busId)
|
|
1390
|
+
const ripupSets = [
|
|
1391
|
+
...blockerIds.map((busId) => [busId]),
|
|
1392
|
+
...blockerIds.flatMap((first, firstIndex) =>
|
|
1393
|
+
blockerIds.slice(firstIndex + 1).map((second) => [first, second]),
|
|
1394
|
+
),
|
|
1395
|
+
]
|
|
1396
|
+
for (const ripupIds of ripupSets) {
|
|
1397
|
+
const ripupIdSet = new Set(ripupIds)
|
|
1398
|
+
const candidatePlans = state.plans.filter(
|
|
1399
|
+
(plan) => !ripupIdSet.has(plan.busId),
|
|
1400
|
+
)
|
|
1401
|
+
const constrainedPlans = routePlaneBus(bus, candidatePlans)
|
|
1402
|
+
if (!constrainedPlans) continue
|
|
1403
|
+
candidatePlans.push(...constrainedPlans)
|
|
1404
|
+
let repairSucceeded = true
|
|
1405
|
+
for (const blockerId of ripupIds) {
|
|
1406
|
+
const blockerBus = planeBusById.get(blockerId)
|
|
1407
|
+
const replacementPlans = blockerBus
|
|
1408
|
+
? routePlaneBus(blockerBus, candidatePlans)
|
|
1409
|
+
: null
|
|
1410
|
+
if (!replacementPlans) {
|
|
1411
|
+
repairSucceeded = false
|
|
1412
|
+
break
|
|
1413
|
+
}
|
|
1414
|
+
candidatePlans.push(...replacementPlans)
|
|
1415
|
+
}
|
|
1416
|
+
if (!repairSucceeded) continue
|
|
1417
|
+
state.plans = candidatePlans
|
|
1418
|
+
busPlans = []
|
|
1419
|
+
break
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
if (busPlans) state.plans.push(...busPlans)
|
|
1424
|
+
else state.failedBusIds.push(bus.busId)
|
|
1425
|
+
}
|
|
1426
|
+
return state
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
let bestState: MixedTerminationState | null = null
|
|
1430
|
+
let mostRecentBoundaryBestState: MixedTerminationState | null = null
|
|
1431
|
+
const evaluateBoundaryStates = (
|
|
1432
|
+
states: readonly MixedTerminationState[],
|
|
1433
|
+
initialPlaneOrder: readonly PreparedBus[],
|
|
1434
|
+
): MixedTerminationState | null => {
|
|
1435
|
+
let localBestState: MixedTerminationState | null = null
|
|
1436
|
+
for (const boundaryState of states) {
|
|
1437
|
+
let planeOrder = [...initialPlaneOrder]
|
|
1438
|
+
const seenPlaneOrders = new Set<string>()
|
|
1439
|
+
for (let retryIndex = 0; retryIndex < 3; retryIndex++) {
|
|
1440
|
+
const orderKey = planeOrder.map((bus) => bus.busId).join("\u0000")
|
|
1441
|
+
if (seenPlaneOrders.has(orderKey)) break
|
|
1442
|
+
seenPlaneOrders.add(orderKey)
|
|
1443
|
+
const state = routePlaneOrder(boundaryState.plans, planeOrder)
|
|
1444
|
+
if (
|
|
1445
|
+
!bestState ||
|
|
1446
|
+
state.plans.length > bestState.plans.length ||
|
|
1447
|
+
(state.plans.length === bestState.plans.length &&
|
|
1448
|
+
state.failedBusIds.length < bestState.failedBusIds.length)
|
|
1449
|
+
) {
|
|
1450
|
+
bestState = state
|
|
1451
|
+
}
|
|
1452
|
+
if (
|
|
1453
|
+
!localBestState ||
|
|
1454
|
+
state.plans.length > localBestState.plans.length ||
|
|
1455
|
+
(state.plans.length === localBestState.plans.length &&
|
|
1456
|
+
state.failedBusIds.length < localBestState.failedBusIds.length)
|
|
1457
|
+
) {
|
|
1458
|
+
localBestState = state
|
|
1459
|
+
}
|
|
1460
|
+
if (state.failedBusIds.length === 0) return state
|
|
1461
|
+
const failedBusIds = new Set(state.failedBusIds)
|
|
1462
|
+
planeOrder = [
|
|
1463
|
+
...state.failedBusIds.flatMap((busId) => {
|
|
1464
|
+
const bus = planeBusById.get(busId)
|
|
1465
|
+
return bus ? [bus] : []
|
|
1466
|
+
}),
|
|
1467
|
+
...planeOrder.filter((bus) => !failedBusIds.has(bus.busId)),
|
|
1468
|
+
]
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
mostRecentBoundaryBestState = localBestState
|
|
1472
|
+
return null
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// Most dense packages route with the first deterministic boundary choice.
|
|
1476
|
+
// Try that cheap path before widening the beam; eagerly generating four
|
|
1477
|
+
// complete A* variants per state can otherwise dominate runtime and memory.
|
|
1478
|
+
for (const alternativesPerBoundaryBus of [1, 4]) {
|
|
1479
|
+
const states = getBoundaryStates(alternativesPerBoundaryBus)
|
|
1480
|
+
if (!states) continue
|
|
1481
|
+
const completeState = evaluateBoundaryStates(states, planeBuses)
|
|
1482
|
+
if (completeState) return completeState
|
|
1483
|
+
|
|
1484
|
+
// If a completed signal escape encloses one especially constrained
|
|
1485
|
+
// power pad, seed just that failed singleton first and recompute the
|
|
1486
|
+
// small boundary-bus set around its physical through barrel. This keeps
|
|
1487
|
+
// the search local without routing hundreds of plane drops before the
|
|
1488
|
+
// signal buses.
|
|
1489
|
+
const seedFailureIds = (
|
|
1490
|
+
bestState as MixedTerminationState | null
|
|
1491
|
+
)?.failedBusIds.slice(0, 3)
|
|
1492
|
+
if (alternativesPerBoundaryBus === 1 && seedFailureIds) {
|
|
1493
|
+
for (const failedBusId of seedFailureIds) {
|
|
1494
|
+
const seededPlanePlans: FanoutRoutePlan[] = []
|
|
1495
|
+
const seededPlaneBusIds = new Set<string>()
|
|
1496
|
+
let nextFailedBusId: string | undefined = failedBusId
|
|
1497
|
+
for (
|
|
1498
|
+
let seedDepth = 0;
|
|
1499
|
+
seedDepth < 3 && nextFailedBusId;
|
|
1500
|
+
seedDepth++
|
|
1501
|
+
) {
|
|
1502
|
+
const failedPlaneBus = planeBusById.get(nextFailedBusId)
|
|
1503
|
+
if (!failedPlaneBus) break
|
|
1504
|
+
const nextSeedPlans = routePlaneBus(
|
|
1505
|
+
failedPlaneBus,
|
|
1506
|
+
seededPlanePlans,
|
|
1507
|
+
)
|
|
1508
|
+
if (!nextSeedPlans) break
|
|
1509
|
+
seededPlanePlans.push(...nextSeedPlans)
|
|
1510
|
+
seededPlaneBusIds.add(nextFailedBusId)
|
|
1511
|
+
const jointReservedBoundaryState =
|
|
1512
|
+
getJointReservedBoundaryState(seededPlanePlans)
|
|
1513
|
+
if (!jointReservedBoundaryState) break
|
|
1514
|
+
const jointCompleteState = evaluateBoundaryStates(
|
|
1515
|
+
[jointReservedBoundaryState],
|
|
1516
|
+
planeBuses.filter((bus) => !seededPlaneBusIds.has(bus.busId)),
|
|
1517
|
+
)
|
|
1518
|
+
if (jointCompleteState) return jointCompleteState
|
|
1519
|
+
const recentFailedBusIds = (
|
|
1520
|
+
mostRecentBoundaryBestState as MixedTerminationState | null
|
|
1521
|
+
)?.failedBusIds
|
|
1522
|
+
nextFailedBusId = recentFailedBusIds?.find(
|
|
1523
|
+
(busId) => !seededPlaneBusIds.has(busId),
|
|
1524
|
+
)
|
|
1525
|
+
}
|
|
1526
|
+
if (seededPlanePlans.length === 0) continue
|
|
1527
|
+
const seededBoundaryStates = getBoundaryStates(1, seededPlanePlans)
|
|
1528
|
+
if (!seededBoundaryStates) continue
|
|
1529
|
+
const seededCompleteState = evaluateBoundaryStates(
|
|
1530
|
+
seededBoundaryStates,
|
|
1531
|
+
planeBuses.filter((bus) => !seededPlaneBusIds.has(bus.busId)),
|
|
1532
|
+
)
|
|
1533
|
+
if (seededCompleteState) return seededCompleteState
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
return bestState
|
|
1539
|
+
}
|
|
1540
|
+
|
|
677
1541
|
private evaluateAssignmentWithStrategy(
|
|
678
1542
|
assignmentIndex: number,
|
|
679
1543
|
busLayerAssignments: Readonly<Record<string, string>>,
|
|
@@ -723,8 +1587,11 @@ export class FanoutSolver extends BaseSolver {
|
|
|
723
1587
|
busLayerAssignments[b.busId] ?? "",
|
|
724
1588
|
)
|
|
725
1589
|
return (
|
|
726
|
-
|
|
727
|
-
|
|
1590
|
+
comparePlaneRoutingPriority(
|
|
1591
|
+
a,
|
|
1592
|
+
b,
|
|
1593
|
+
this.config.allowBlindAndBuriedVias,
|
|
1594
|
+
) ||
|
|
728
1595
|
Number(bUsesCoordinatedWinding) - Number(aUsesCoordinatedWinding) ||
|
|
729
1596
|
(aUsesCoordinatedWinding && bUsesCoordinatedWinding
|
|
730
1597
|
? bLayerIndex - aLayerIndex
|
|
@@ -744,8 +1611,23 @@ export class FanoutSolver extends BaseSolver {
|
|
|
744
1611
|
)
|
|
745
1612
|
})
|
|
746
1613
|
|
|
1614
|
+
const mixedTerminationState =
|
|
1615
|
+
!useSingleLayerPushAndShove && routingStrategy === "default"
|
|
1616
|
+
? this.routeDenseThroughAllMixedTerminations({
|
|
1617
|
+
busLayerAssignments,
|
|
1618
|
+
busesInRoutingOrder,
|
|
1619
|
+
})
|
|
1620
|
+
: null
|
|
1621
|
+
|
|
1622
|
+
if (mixedTerminationState) {
|
|
1623
|
+
plans = mixedTerminationState.plans
|
|
1624
|
+
failedBusIds = mixedTerminationState.failedBusIds
|
|
1625
|
+
}
|
|
1626
|
+
|
|
747
1627
|
let routingPrefixKey = `${routingStrategy}|`
|
|
748
|
-
for (const bus of useSingleLayerPushAndShove
|
|
1628
|
+
for (const bus of useSingleLayerPushAndShove || mixedTerminationState
|
|
1629
|
+
? []
|
|
1630
|
+
: busesInRoutingOrder) {
|
|
749
1631
|
const targetLayer = busLayerAssignments[bus.busId]
|
|
750
1632
|
if (!targetLayer) {
|
|
751
1633
|
throw new Error(
|
|
@@ -777,6 +1659,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
777
1659
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
778
1660
|
clearance: this.config.clearance,
|
|
779
1661
|
compactBusTracks: this.config.compactBusTracks,
|
|
1662
|
+
allowBlindAndBuriedVias: this.config.allowBlindAndBuriedVias,
|
|
780
1663
|
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
781
1664
|
staticClearanceCache: this.routeStaticClearanceCache,
|
|
782
1665
|
blockingBusCounts: currentBusBlockingCounts,
|
|
@@ -870,7 +1753,6 @@ export class FanoutSolver extends BaseSolver {
|
|
|
870
1753
|
score,
|
|
871
1754
|
...(validationIssues ? { validationIssues } : {}),
|
|
872
1755
|
}
|
|
873
|
-
|
|
874
1756
|
return {
|
|
875
1757
|
summary,
|
|
876
1758
|
plans,
|
|
@@ -972,8 +1854,11 @@ export class FanoutSolver extends BaseSolver {
|
|
|
972
1854
|
: (this.escapeLayersByBusId[b.busId]?.length ??
|
|
973
1855
|
this.config.escapeLayers.length)
|
|
974
1856
|
return (
|
|
975
|
-
|
|
976
|
-
|
|
1857
|
+
comparePlaneRoutingPriority(
|
|
1858
|
+
a,
|
|
1859
|
+
b,
|
|
1860
|
+
this.config.allowBlindAndBuriedVias,
|
|
1861
|
+
) ||
|
|
977
1862
|
Number(bUsesCoordinatedWinding) - Number(aUsesCoordinatedWinding) ||
|
|
978
1863
|
(aUsesCoordinatedWinding && bUsesCoordinatedWinding
|
|
979
1864
|
? getMaximumViaSpan(b) - getMaximumViaSpan(a)
|
|
@@ -1084,6 +1969,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1084
1969
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
1085
1970
|
clearance: this.config.clearance,
|
|
1086
1971
|
compactBusTracks: this.config.compactBusTracks,
|
|
1972
|
+
allowBlindAndBuriedVias: this.config.allowBlindAndBuriedVias,
|
|
1087
1973
|
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
1088
1974
|
staticClearanceCache: this.routeStaticClearanceCache,
|
|
1089
1975
|
},
|
|
@@ -1534,14 +2420,17 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1534
2420
|
`FanoutSolver: completed output failed validation: ${validation.issues[0]?.message ?? "unknown validation error"}`,
|
|
1535
2421
|
)
|
|
1536
2422
|
}
|
|
1537
|
-
const finalSrj =
|
|
1538
|
-
|
|
2423
|
+
const finalSrj = addViaLayerMetadataToSrj({
|
|
2424
|
+
srj:
|
|
2425
|
+
this.endpointCompletion?.simpleRouteJson ?? this.bestAttempt.outputSrj,
|
|
2426
|
+
layerNames: this.config.layerNames,
|
|
2427
|
+
allowBlindAndBuriedVias: this.config.allowBlindAndBuriedVias,
|
|
2428
|
+
})
|
|
1539
2429
|
const finalTraceById = new Map(
|
|
1540
2430
|
(finalSrj.traces ?? []).map((trace) => [trace.pcb_trace_id, trace]),
|
|
1541
2431
|
)
|
|
1542
2432
|
return {
|
|
1543
|
-
simpleRouteJson:
|
|
1544
|
-
this.endpointCompletion?.simpleRouteJson ?? this.bestAttempt.outputSrj,
|
|
2433
|
+
simpleRouteJson: finalSrj,
|
|
1545
2434
|
fanoutTraces: this.bestAttempt.plans.flatMap((plan) => [
|
|
1546
2435
|
finalTraceById.get(plan.trace.pcb_trace_id) ?? plan.trace,
|
|
1547
2436
|
...(plan.planeEndpointTrace
|
|
@@ -1551,7 +2440,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1551
2440
|
]
|
|
1552
2441
|
: []),
|
|
1553
2442
|
]),
|
|
1554
|
-
completionTraces: this.endpointCompletion?.traces ?? []
|
|
2443
|
+
completionTraces: (this.endpointCompletion?.traces ?? []).map(
|
|
2444
|
+
(trace) => finalTraceById.get(trace.pcb_trace_id) ?? trace,
|
|
2445
|
+
),
|
|
1555
2446
|
...(this.endpointCompletion
|
|
1556
2447
|
? { endpointCompletion: this.endpointCompletion.report }
|
|
1557
2448
|
: {}),
|
|
@@ -1576,7 +2467,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1576
2467
|
}
|
|
1577
2468
|
}
|
|
1578
2469
|
|
|
1579
|
-
getOutputSimpleRouteJson():
|
|
2470
|
+
getOutputSimpleRouteJson(): SimpleRouteJsonWithFanoutPlanes {
|
|
1580
2471
|
return this.getOutput().simpleRouteJson
|
|
1581
2472
|
}
|
|
1582
2473
|
|