@tscircuit/fanout-solver 0.0.36 → 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/lib/build-output.ts +32 -10
- package/lib/fanout-solver.ts +214 -30
- package/lib/prepare-buses.ts +6 -2
- package/lib/route-bus.ts +378 -81
- package/lib/route-via-minimal-winding.ts +898 -0
- package/lib/types.ts +14 -3
- package/lib/validate-fanout-solution.ts +12 -10
- package/package.json +1 -1
package/lib/route-bus.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
connectionsShareElectricalNet,
|
|
23
23
|
obstacleSharesElectricalNet,
|
|
24
24
|
} from "./net-identity"
|
|
25
|
+
import { routeViaMinimalWinding } from "./route-via-minimal-winding"
|
|
25
26
|
import type {
|
|
26
27
|
Bounds,
|
|
27
28
|
FanoutDirection,
|
|
@@ -90,6 +91,91 @@ function getLocalCornerSide(
|
|
|
90
91
|
)
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
function busUsesCoordinatedWindingChannel(bus: PreparedBus): boolean {
|
|
95
|
+
return Boolean(
|
|
96
|
+
bus.exitEdge &&
|
|
97
|
+
bus.termination.type === "boundary" &&
|
|
98
|
+
bus.connections.length > 0 &&
|
|
99
|
+
bus.connections.every(
|
|
100
|
+
(connection) => connection.hasExplicitLayeredExitTarget === true,
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getStableConnectionIdentity(
|
|
106
|
+
connection: PreparedConnection["connection"],
|
|
107
|
+
): string {
|
|
108
|
+
if (
|
|
109
|
+
"source_trace_id" in connection &&
|
|
110
|
+
typeof connection.source_trace_id === "string"
|
|
111
|
+
) {
|
|
112
|
+
return connection.source_trace_id
|
|
113
|
+
}
|
|
114
|
+
return connection.name
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getWindingTargetRank(params: {
|
|
118
|
+
bus: PreparedBus
|
|
119
|
+
connection: PreparedConnection
|
|
120
|
+
boundaryDirection: FanoutDirection
|
|
121
|
+
layerNames: readonly string[]
|
|
122
|
+
}): { rank: number; connectionCount: number } {
|
|
123
|
+
const { bus, connection, boundaryDirection, layerNames } = params
|
|
124
|
+
const orderedConnections = bus.connections.toSorted((first, second) => {
|
|
125
|
+
const axisDifference =
|
|
126
|
+
getPerpendicularAxis(
|
|
127
|
+
first.exitTargetPoint ?? first.targetPoint,
|
|
128
|
+
boundaryDirection,
|
|
129
|
+
) -
|
|
130
|
+
getPerpendicularAxis(
|
|
131
|
+
second.exitTargetPoint ?? second.targetPoint,
|
|
132
|
+
boundaryDirection,
|
|
133
|
+
)
|
|
134
|
+
if (axisDifference !== 0) return axisDifference
|
|
135
|
+
|
|
136
|
+
// Equal target coordinates came from distinct target layers and do not
|
|
137
|
+
// impose a physical order after this bus is collapsed onto one escape
|
|
138
|
+
// layer. Keep that free choice independent of allowed-layer ordering so
|
|
139
|
+
// the local fanout can choose a deterministic, via-minimal permutation.
|
|
140
|
+
const identityDifference = first.connection.name.localeCompare(
|
|
141
|
+
second.connection.name,
|
|
142
|
+
)
|
|
143
|
+
if (identityDifference !== 0) return identityDifference
|
|
144
|
+
|
|
145
|
+
const firstStableId = getStableConnectionIdentity(first.connection)
|
|
146
|
+
const secondStableId = getStableConnectionIdentity(second.connection)
|
|
147
|
+
const stableIdentityDifference = firstStableId.localeCompare(secondStableId)
|
|
148
|
+
if (stableIdentityDifference !== 0) return stableIdentityDifference
|
|
149
|
+
|
|
150
|
+
const firstLayer = first.exitTargetPoint?.layer
|
|
151
|
+
const secondLayer = second.exitTargetPoint?.layer
|
|
152
|
+
return (
|
|
153
|
+
layerNames.indexOf(firstLayer ?? "") -
|
|
154
|
+
layerNames.indexOf(secondLayer ?? "") ||
|
|
155
|
+
first.connectionIndex - second.connectionIndex
|
|
156
|
+
)
|
|
157
|
+
})
|
|
158
|
+
const rank = orderedConnections.findIndex(
|
|
159
|
+
(candidate) => candidate.connectionIndex === connection.connectionIndex,
|
|
160
|
+
)
|
|
161
|
+
if (rank < 0) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`FanoutSolver: connection "${connection.connection.name}" is missing from winding order`,
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
return { rank, connectionCount: orderedConnections.length }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function getWindingCrossoverLayer(params: {
|
|
170
|
+
bus: PreparedBus
|
|
171
|
+
escapeLayer: string
|
|
172
|
+
}): string | undefined {
|
|
173
|
+
const { bus, escapeLayer } = params
|
|
174
|
+
return (bus.routableEscapeLayers ?? bus.allowedLayers)?.find(
|
|
175
|
+
(layer) => layer !== escapeLayer,
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
93
179
|
function getCornerTargetTrack(params: {
|
|
94
180
|
bus: PreparedBus
|
|
95
181
|
connection: PreparedConnection
|
|
@@ -97,6 +183,7 @@ function getCornerTargetTrack(params: {
|
|
|
97
183
|
traceWidth: number
|
|
98
184
|
viaDiameter: number
|
|
99
185
|
clearance: number
|
|
186
|
+
layerNames: readonly string[]
|
|
100
187
|
}): number {
|
|
101
188
|
const {
|
|
102
189
|
bus,
|
|
@@ -105,6 +192,7 @@ function getCornerTargetTrack(params: {
|
|
|
105
192
|
traceWidth,
|
|
106
193
|
viaDiameter,
|
|
107
194
|
clearance,
|
|
195
|
+
layerNames,
|
|
108
196
|
} = params
|
|
109
197
|
const side = getCornerSide(bus)
|
|
110
198
|
if (!side || !bus.exitEdge) {
|
|
@@ -121,17 +209,27 @@ function getCornerTargetTrack(params: {
|
|
|
121
209
|
? bus.sharedBoundary.maxY
|
|
122
210
|
: bus.sharedBoundary.maxX
|
|
123
211
|
const pitch = Math.max(traceWidth + clearance, viaDiameter + clearance)
|
|
124
|
-
const
|
|
212
|
+
const baseBandCenter =
|
|
125
213
|
boundaryMinimum +
|
|
126
214
|
(boundaryMaximum - boundaryMinimum) * (side === "minimum" ? 0.25 : 0.75)
|
|
215
|
+
const windingTarget = busUsesCoordinatedWindingChannel(bus)
|
|
216
|
+
? getWindingTargetRank({
|
|
217
|
+
bus,
|
|
218
|
+
connection,
|
|
219
|
+
boundaryDirection,
|
|
220
|
+
layerNames,
|
|
221
|
+
})
|
|
222
|
+
: undefined
|
|
127
223
|
const bandConnectionCount = Math.max(
|
|
128
224
|
bus.connections.length,
|
|
129
225
|
bus.cornerBandConnectionCount ?? bus.connections.length,
|
|
226
|
+
cornerExitLaneOffset + (windingTarget?.connectionCount ?? 0),
|
|
130
227
|
)
|
|
131
|
-
const firstTrack =
|
|
132
|
-
const rank = getConnectionRank(bus, connection)
|
|
228
|
+
const firstTrack = baseBandCenter - ((bandConnectionCount - 1) * pitch) / 2
|
|
229
|
+
const rank = windingTarget?.rank ?? getConnectionRank(bus, connection)
|
|
133
230
|
const globalSlot = cornerExitLaneOffset + rank
|
|
134
231
|
const reverseSlotOrder =
|
|
232
|
+
!windingTarget &&
|
|
135
233
|
(side === "maximum") === directionSign(boundaryDirection) > 0
|
|
136
234
|
const orientedSlot = reverseSlotOrder
|
|
137
235
|
? bandConnectionCount - 1 - globalSlot
|
|
@@ -390,12 +488,16 @@ function getPreferredTrack(params: {
|
|
|
390
488
|
connection: PreparedConnection
|
|
391
489
|
traceWidth: number
|
|
392
490
|
}): number {
|
|
393
|
-
const preferredTrack =
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
491
|
+
const preferredTrack =
|
|
492
|
+
getCornerSide(params.bus) || busUsesCoordinatedWindingChannel(params.bus)
|
|
493
|
+
? getPerpendicularAxis(
|
|
494
|
+
params.connection.sourcePoint,
|
|
495
|
+
params.bus.direction,
|
|
496
|
+
)
|
|
497
|
+
: getPerpendicularAxis(
|
|
498
|
+
params.connection.exitTargetPoint ?? params.connection.targetPoint,
|
|
499
|
+
params.bus.direction,
|
|
500
|
+
)
|
|
399
501
|
const boundaryMinimum = isHorizontal(params.bus.direction)
|
|
400
502
|
? params.bus.sharedBoundary.minY
|
|
401
503
|
: params.bus.sharedBoundary.minX
|
|
@@ -577,6 +679,57 @@ function chamferOrthogonalPolyline(
|
|
|
577
679
|
return chamfered
|
|
578
680
|
}
|
|
579
681
|
|
|
682
|
+
function getInitialViaPoint(params: {
|
|
683
|
+
preparedConnection: PreparedConnection
|
|
684
|
+
bus: PreparedBus
|
|
685
|
+
targetLayer: string
|
|
686
|
+
traceWidth: number
|
|
687
|
+
viaDiameter: number
|
|
688
|
+
clearance: number
|
|
689
|
+
viaHandedness: ViaHandedness
|
|
690
|
+
}): Point2D {
|
|
691
|
+
const {
|
|
692
|
+
preparedConnection,
|
|
693
|
+
bus,
|
|
694
|
+
targetLayer,
|
|
695
|
+
traceWidth,
|
|
696
|
+
viaDiameter,
|
|
697
|
+
clearance,
|
|
698
|
+
viaHandedness,
|
|
699
|
+
} = params
|
|
700
|
+
const sourcePoint = {
|
|
701
|
+
x: preparedConnection.sourcePoint.x,
|
|
702
|
+
y: preparedConnection.sourcePoint.y,
|
|
703
|
+
}
|
|
704
|
+
const targetUsesVia = targetLayer !== preparedConnection.sourceLayer
|
|
705
|
+
const directionalPitch = getDirectionalPitch(bus)
|
|
706
|
+
const perpendicularPitch = getPerpendicularPitch(bus)
|
|
707
|
+
const directionalPadSize = isHorizontal(bus.direction)
|
|
708
|
+
? preparedConnection.sourceObstacle.width
|
|
709
|
+
: preparedConnection.sourceObstacle.height
|
|
710
|
+
const initialEscapeDistance =
|
|
711
|
+
targetUsesVia && !busIsOnOutwardComponentEdge(bus)
|
|
712
|
+
? directionalPadSize >= directionalPitch
|
|
713
|
+
? directionalPadSize / 2 + viaDiameter / 2 + clearance + 1e-3
|
|
714
|
+
: directionalPitch * 0.5
|
|
715
|
+
: !targetUsesVia
|
|
716
|
+
? directionalPadSize / 2 + traceWidth / 2 + clearance + 1e-3
|
|
717
|
+
: Math.max(
|
|
718
|
+
directionalPitch * 0.5,
|
|
719
|
+
directionalPadSize / 2 +
|
|
720
|
+
(targetUsesVia ? viaDiameter : traceWidth) / 2 +
|
|
721
|
+
clearance +
|
|
722
|
+
1e-3,
|
|
723
|
+
)
|
|
724
|
+
const viaAxis =
|
|
725
|
+
getAxis(sourcePoint, bus.direction) +
|
|
726
|
+
directionSign(bus.direction) * initialEscapeDistance
|
|
727
|
+
const viaPerpendicularAxis =
|
|
728
|
+
getPerpendicularAxis(sourcePoint, bus.direction) +
|
|
729
|
+
viaHandedness * perpendicularPitch * 0.5
|
|
730
|
+
return makePoint(viaAxis, viaPerpendicularAxis, bus.direction)
|
|
731
|
+
}
|
|
732
|
+
|
|
580
733
|
function buildPlan(params: {
|
|
581
734
|
preparedConnection: PreparedConnection
|
|
582
735
|
bus: PreparedBus
|
|
@@ -619,36 +772,27 @@ function buildPlan(params: {
|
|
|
619
772
|
x: preparedConnection.sourcePoint.x,
|
|
620
773
|
y: preparedConnection.sourcePoint.y,
|
|
621
774
|
}
|
|
775
|
+
const escapeLayer = targetLayer
|
|
776
|
+
const windingCrossoverLayer = busUsesCoordinatedWindingChannel(bus)
|
|
777
|
+
? getWindingCrossoverLayer({
|
|
778
|
+
bus,
|
|
779
|
+
escapeLayer,
|
|
780
|
+
})
|
|
781
|
+
: undefined
|
|
782
|
+
const usesLayeredWindingChannel = Boolean(windingCrossoverLayer)
|
|
622
783
|
const sign = directionSign(bus.direction)
|
|
623
784
|
const directionalPitch = getDirectionalPitch(bus)
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
? directionalPadSize / 2 + traceWidth / 2 + clearance + 1e-3
|
|
636
|
-
: Math.max(
|
|
637
|
-
directionalPitch * 0.5,
|
|
638
|
-
directionalPadSize / 2 +
|
|
639
|
-
(targetUsesVia ? viaDiameter : traceWidth) / 2 +
|
|
640
|
-
clearance +
|
|
641
|
-
1e-3,
|
|
642
|
-
)
|
|
643
|
-
const viaAxis =
|
|
644
|
-
getAxis(sourcePoint, bus.direction) + sign * initialEscapeDistance
|
|
645
|
-
const sourcePerpendicularAxis = getPerpendicularAxis(
|
|
646
|
-
sourcePoint,
|
|
647
|
-
bus.direction,
|
|
648
|
-
)
|
|
649
|
-
const viaPerpendicularAxis =
|
|
650
|
-
sourcePerpendicularAxis + viaHandedness * perpendicularPitch * 0.5
|
|
651
|
-
const viaPoint = makePoint(viaAxis, viaPerpendicularAxis, bus.direction)
|
|
785
|
+
const viaPoint = getInitialViaPoint({
|
|
786
|
+
preparedConnection,
|
|
787
|
+
bus,
|
|
788
|
+
targetLayer,
|
|
789
|
+
traceWidth,
|
|
790
|
+
viaDiameter,
|
|
791
|
+
clearance,
|
|
792
|
+
viaHandedness,
|
|
793
|
+
})
|
|
794
|
+
const viaAxis = getAxis(viaPoint, bus.direction)
|
|
795
|
+
const viaPerpendicularAxis = getPerpendicularAxis(viaPoint, bus.direction)
|
|
652
796
|
const spreadLaneDistance =
|
|
653
797
|
viaDiameter / 2 +
|
|
654
798
|
traceWidth / 2 +
|
|
@@ -675,7 +819,7 @@ function buildPlan(params: {
|
|
|
675
819
|
: bus.direction
|
|
676
820
|
const boundarySign = directionSign(boundaryDirection)
|
|
677
821
|
const boundaryExitAxis = getExitAxis(bus, boundaryDirection)
|
|
678
|
-
const
|
|
822
|
+
const boundaryTargetTrack =
|
|
679
823
|
cornerSide && bus.exitEdge
|
|
680
824
|
? getCornerTargetTrack({
|
|
681
825
|
bus,
|
|
@@ -684,8 +828,15 @@ function buildPlan(params: {
|
|
|
684
828
|
traceWidth,
|
|
685
829
|
viaDiameter,
|
|
686
830
|
clearance,
|
|
831
|
+
layerNames,
|
|
687
832
|
})
|
|
688
|
-
:
|
|
833
|
+
: usesLayeredWindingChannel
|
|
834
|
+
? getPerpendicularAxis(
|
|
835
|
+
preparedConnection.exitTargetPoint ??
|
|
836
|
+
preparedConnection.targetPoint,
|
|
837
|
+
boundaryDirection,
|
|
838
|
+
)
|
|
839
|
+
: track
|
|
689
840
|
const connectionRank = getConnectionRank(bus, preparedConnection)
|
|
690
841
|
const localCornerSide = getLocalCornerSide(bus)
|
|
691
842
|
const localChannelLaneIndex =
|
|
@@ -704,11 +855,11 @@ function buildPlan(params: {
|
|
|
704
855
|
boundarySign > 0
|
|
705
856
|
? globalBoundarySlot
|
|
706
857
|
: boundaryBandConnectionCount - 1 - globalBoundarySlot
|
|
858
|
+
const channelPitch = usesLayeredWindingChannel
|
|
859
|
+
? viaDiameter / 2 + traceWidth / 2 + clearance
|
|
860
|
+
: traceWidth + clearance
|
|
707
861
|
const channelInset = (laneIndex: number) =>
|
|
708
|
-
viaDiameter / 2 +
|
|
709
|
-
traceWidth / 2 +
|
|
710
|
-
clearance +
|
|
711
|
-
laneIndex * (traceWidth + clearance)
|
|
862
|
+
viaDiameter / 2 + traceWidth / 2 + clearance + laneIndex * channelPitch
|
|
712
863
|
const localChannelAxis =
|
|
713
864
|
getExitAxis(bus, bus.direction) - sign * channelInset(localChannelLaneIndex)
|
|
714
865
|
const boundaryChannelAxis =
|
|
@@ -725,13 +876,17 @@ function buildPlan(params: {
|
|
|
725
876
|
)
|
|
726
877
|
const boundaryChannelTargetPoint = makePoint(
|
|
727
878
|
boundaryChannelAxis,
|
|
728
|
-
|
|
879
|
+
boundaryTargetTrack,
|
|
729
880
|
boundaryDirection,
|
|
730
881
|
)
|
|
882
|
+
const windingInputTransitionPoint =
|
|
883
|
+
isHorizontal(bus.direction) !== isHorizontal(boundaryDirection)
|
|
884
|
+
? localChannelTargetPoint
|
|
885
|
+
: makePoint(boundaryChannelAxis, track, boundaryDirection)
|
|
731
886
|
const exitPoint = terminateAtVia
|
|
732
887
|
? viaPoint
|
|
733
|
-
: cornerSide
|
|
734
|
-
? makePoint(boundaryExitAxis,
|
|
888
|
+
: cornerSide || usesLayeredWindingChannel
|
|
889
|
+
? makePoint(boundaryExitAxis, boundaryTargetTrack, boundaryDirection)
|
|
735
890
|
: makePoint(exitAxis, track, bus.direction)
|
|
736
891
|
const segments: RoutedSegment[] = []
|
|
737
892
|
const route: SimplifiedPcbTrace["route"] = []
|
|
@@ -792,40 +947,110 @@ function buildPlan(params: {
|
|
|
792
947
|
})
|
|
793
948
|
}
|
|
794
949
|
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
950
|
+
const appendLayerPath = (points: Point2D[], layer: string): void => {
|
|
951
|
+
for (let index = 1; index < points.length; index++) {
|
|
952
|
+
const previousPoint = points[index - 1]!
|
|
953
|
+
const nextPoint = points[index]!
|
|
954
|
+
appendSegment(segments, previousPoint, nextPoint, traceWidth, layer)
|
|
955
|
+
route.push({
|
|
956
|
+
route_type: "wire",
|
|
957
|
+
x: nextPoint.x,
|
|
958
|
+
y: nextPoint.y,
|
|
959
|
+
width: traceWidth,
|
|
960
|
+
layer,
|
|
961
|
+
})
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
const additionalVias: NonNullable<FanoutRoutePlan["additionalVias"]> = []
|
|
966
|
+
if (usesLayeredWindingChannel && windingCrossoverLayer) {
|
|
967
|
+
const escapePoints = chamferOrthogonalPolyline(
|
|
968
|
+
[
|
|
969
|
+
viaPoint,
|
|
970
|
+
...(useNestedSpread ? [spreadPoint] : []),
|
|
971
|
+
doglegPoint,
|
|
972
|
+
...(isHorizontal(bus.direction) !== isHorizontal(boundaryDirection)
|
|
973
|
+
? [localChannelSourcePoint]
|
|
974
|
+
: []),
|
|
975
|
+
windingInputTransitionPoint,
|
|
976
|
+
],
|
|
977
|
+
Math.max(traceWidth + clearance, traceWidth * 2),
|
|
978
|
+
)
|
|
979
|
+
appendLayerPath(escapePoints, escapeLayer)
|
|
980
|
+
|
|
981
|
+
const appendTransitionVia = (
|
|
982
|
+
center: Point2D,
|
|
983
|
+
fromLayer: string,
|
|
984
|
+
toLayer: string,
|
|
985
|
+
): void => {
|
|
986
|
+
if (fromLayer === toLayer) return
|
|
987
|
+
additionalVias.push({
|
|
988
|
+
center,
|
|
989
|
+
diameter: viaDiameter,
|
|
990
|
+
holeDiameter: viaHoleDiameter,
|
|
991
|
+
fromLayer,
|
|
992
|
+
toLayer,
|
|
993
|
+
spanLayers: getLayerSpan(fromLayer, toLayer, layerNames),
|
|
994
|
+
})
|
|
995
|
+
route.push({
|
|
996
|
+
route_type: "via",
|
|
997
|
+
x: center.x,
|
|
998
|
+
y: center.y,
|
|
999
|
+
from_layer: fromLayer,
|
|
1000
|
+
to_layer: toLayer,
|
|
1001
|
+
via_diameter: viaDiameter,
|
|
1002
|
+
via_hole_diameter: viaHoleDiameter,
|
|
1003
|
+
})
|
|
1004
|
+
route.push({
|
|
1005
|
+
route_type: "wire",
|
|
1006
|
+
x: center.x,
|
|
1007
|
+
y: center.y,
|
|
1008
|
+
width: traceWidth,
|
|
1009
|
+
layer: toLayer,
|
|
1010
|
+
})
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
appendTransitionVia(
|
|
1014
|
+
windingInputTransitionPoint,
|
|
1015
|
+
escapeLayer,
|
|
1016
|
+
windingCrossoverLayer,
|
|
1017
|
+
)
|
|
1018
|
+
appendLayerPath(
|
|
1019
|
+
[windingInputTransitionPoint, boundaryChannelTargetPoint],
|
|
1020
|
+
windingCrossoverLayer,
|
|
1021
|
+
)
|
|
1022
|
+
appendTransitionVia(
|
|
1023
|
+
boundaryChannelTargetPoint,
|
|
1024
|
+
windingCrossoverLayer,
|
|
1025
|
+
escapeLayer,
|
|
1026
|
+
)
|
|
1027
|
+
appendLayerPath([boundaryChannelTargetPoint, exitPoint], escapeLayer)
|
|
1028
|
+
} else {
|
|
1029
|
+
const targetLayerPoints = terminateAtVia
|
|
1030
|
+
? [viaPoint]
|
|
1031
|
+
: cornerSide
|
|
813
1032
|
? chamferOrthogonalPolyline(
|
|
814
|
-
[
|
|
1033
|
+
[
|
|
1034
|
+
viaPoint,
|
|
1035
|
+
...(useNestedSpread ? [spreadPoint] : []),
|
|
1036
|
+
doglegPoint,
|
|
1037
|
+
localChannelSourcePoint,
|
|
1038
|
+
...(isHorizontal(bus.direction) !==
|
|
1039
|
+
isHorizontal(boundaryDirection)
|
|
1040
|
+
? [localChannelTargetPoint]
|
|
1041
|
+
: []),
|
|
1042
|
+
boundaryChannelTargetPoint,
|
|
1043
|
+
exitPoint,
|
|
1044
|
+
],
|
|
815
1045
|
Math.max(traceWidth + clearance, traceWidth * 2),
|
|
816
1046
|
)
|
|
817
|
-
:
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
x: nextPoint.x,
|
|
825
|
-
y: nextPoint.y,
|
|
826
|
-
width: traceWidth,
|
|
827
|
-
layer: targetLayer,
|
|
828
|
-
})
|
|
1047
|
+
: useNestedSpread
|
|
1048
|
+
? chamferOrthogonalPolyline(
|
|
1049
|
+
[viaPoint, spreadPoint, doglegPoint, exitPoint],
|
|
1050
|
+
Math.max(traceWidth + clearance, traceWidth * 2),
|
|
1051
|
+
)
|
|
1052
|
+
: [viaPoint, doglegPoint, exitPoint]
|
|
1053
|
+
appendLayerPath(targetLayerPoints, escapeLayer)
|
|
829
1054
|
}
|
|
830
1055
|
|
|
831
1056
|
const outputIds = createFanoutOutputIds({
|
|
@@ -841,7 +1066,7 @@ function buildPlan(params: {
|
|
|
841
1066
|
sourceObstacle: preparedConnection.sourceObstacle,
|
|
842
1067
|
sourceLayer: preparedConnection.sourceLayer,
|
|
843
1068
|
targetPoint: preparedConnection.targetPoint,
|
|
844
|
-
targetLayer,
|
|
1069
|
+
targetLayer: escapeLayer,
|
|
845
1070
|
termination: bus.termination,
|
|
846
1071
|
direction: bus.direction,
|
|
847
1072
|
...(bus.exitEdge ? { exitEdge: bus.exitEdge } : {}),
|
|
@@ -864,6 +1089,7 @@ function buildPlan(params: {
|
|
|
864
1089
|
},
|
|
865
1090
|
segments,
|
|
866
1091
|
via,
|
|
1092
|
+
...(additionalVias.length > 0 ? { additionalVias } : {}),
|
|
867
1093
|
length: segments.reduce(
|
|
868
1094
|
(total, segment) => total + distance(segment.start, segment.end),
|
|
869
1095
|
0,
|
|
@@ -1121,9 +1347,11 @@ function getPlanSegments(plan: FanoutRoutePlan): RoutedSegment[] {
|
|
|
1121
1347
|
}
|
|
1122
1348
|
|
|
1123
1349
|
function getPlanVias(plan: FanoutRoutePlan) {
|
|
1124
|
-
return [
|
|
1125
|
-
|
|
1126
|
-
|
|
1350
|
+
return [
|
|
1351
|
+
plan.via,
|
|
1352
|
+
...(plan.additionalVias ?? []),
|
|
1353
|
+
plan.planeEndpointVia,
|
|
1354
|
+
].filter((via): via is NonNullable<FanoutRoutePlan["via"]> => Boolean(via))
|
|
1127
1355
|
}
|
|
1128
1356
|
|
|
1129
1357
|
function planIsStaticallyClear(params: {
|
|
@@ -1612,6 +1840,75 @@ export function routeBusAlternatives(
|
|
|
1612
1840
|
alternatives.push(plans)
|
|
1613
1841
|
}
|
|
1614
1842
|
|
|
1843
|
+
if (busUsesCoordinatedWindingChannel(bus) && bus.exitEdge) {
|
|
1844
|
+
const boundaryDirection = getDirectionForExitEdge(bus.exitEdge)
|
|
1845
|
+
const boundaryExitAxis = getExitAxis(bus, boundaryDirection)
|
|
1846
|
+
const cornerSide = getCornerSide(bus)
|
|
1847
|
+
for (const viaHandedness of viaHandednesses) {
|
|
1848
|
+
const terminals = bus.connections.map((preparedConnection) => {
|
|
1849
|
+
const boundaryTrack = cornerSide
|
|
1850
|
+
? getCornerTargetTrack({
|
|
1851
|
+
bus,
|
|
1852
|
+
connection: preparedConnection,
|
|
1853
|
+
cornerExitLaneOffset: cornerLaneOffsets.exit,
|
|
1854
|
+
traceWidth,
|
|
1855
|
+
viaDiameter,
|
|
1856
|
+
clearance,
|
|
1857
|
+
layerNames,
|
|
1858
|
+
})
|
|
1859
|
+
: getPerpendicularAxis(
|
|
1860
|
+
preparedConnection.exitTargetPoint ??
|
|
1861
|
+
preparedConnection.targetPoint,
|
|
1862
|
+
boundaryDirection,
|
|
1863
|
+
)
|
|
1864
|
+
return {
|
|
1865
|
+
connection: preparedConnection,
|
|
1866
|
+
viaPoint: getInitialViaPoint({
|
|
1867
|
+
preparedConnection,
|
|
1868
|
+
bus,
|
|
1869
|
+
targetLayer,
|
|
1870
|
+
traceWidth,
|
|
1871
|
+
viaDiameter,
|
|
1872
|
+
clearance,
|
|
1873
|
+
viaHandedness,
|
|
1874
|
+
}),
|
|
1875
|
+
exitPoint: makePoint(
|
|
1876
|
+
boundaryExitAxis,
|
|
1877
|
+
boundaryTrack,
|
|
1878
|
+
boundaryDirection,
|
|
1879
|
+
),
|
|
1880
|
+
}
|
|
1881
|
+
})
|
|
1882
|
+
const viaMinimalPlans = routeViaMinimalWinding({
|
|
1883
|
+
srj,
|
|
1884
|
+
bus,
|
|
1885
|
+
targetLayer,
|
|
1886
|
+
terminals,
|
|
1887
|
+
acceptedPlans,
|
|
1888
|
+
layerNames,
|
|
1889
|
+
traceWidth,
|
|
1890
|
+
viaDiameter,
|
|
1891
|
+
viaHoleDiameter,
|
|
1892
|
+
clearance,
|
|
1893
|
+
allowSameNetMerges,
|
|
1894
|
+
})
|
|
1895
|
+
if (
|
|
1896
|
+
!viaMinimalPlans ||
|
|
1897
|
+
!fanoutPlansAreClear({
|
|
1898
|
+
plans: [...acceptedPlans, ...viaMinimalPlans],
|
|
1899
|
+
srj,
|
|
1900
|
+
sharedBoundary: bus.sharedBoundary,
|
|
1901
|
+
clearance,
|
|
1902
|
+
allowSameNetMerges,
|
|
1903
|
+
})
|
|
1904
|
+
) {
|
|
1905
|
+
continue
|
|
1906
|
+
}
|
|
1907
|
+
addAlternative(viaMinimalPlans)
|
|
1908
|
+
if (alternatives.length >= maxAlternatives) return alternatives
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1615
1912
|
const searchConnectionOrder = (
|
|
1616
1913
|
connectionOrder: PreparedConnection[],
|
|
1617
1914
|
viaHandedness: ViaHandedness,
|