@tscircuit/fanout-solver 0.0.46 → 0.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/fanout-solver.ts +191 -26
- package/lib/route-bus.ts +39 -10
- package/package.json +15 -2
package/lib/fanout-solver.ts
CHANGED
|
@@ -448,6 +448,7 @@ export function shouldUseJointBoundaryViaReservation(
|
|
|
448
448
|
boundaryBusConnectionCounts.length === 6 ||
|
|
449
449
|
boundaryBusConnectionCounts.length === 7 ||
|
|
450
450
|
boundaryBusConnectionCounts.length === 8 ||
|
|
451
|
+
boundaryBusConnectionCounts.length === 9 ||
|
|
451
452
|
(boundaryBusConnectionCounts.length === 4 &&
|
|
452
453
|
new Set(boundaryBusConnectionCounts).size > 1)
|
|
453
454
|
)
|
|
@@ -466,25 +467,96 @@ export function shouldDeferSingletonBoundaryViaReservation(
|
|
|
466
467
|
boundaryBusCount === 7) &&
|
|
467
468
|
singletonBusCount === 1) ||
|
|
468
469
|
(boundaryBusCount === 8 &&
|
|
469
|
-
(singletonBusCount === 1 || singletonBusCount === 2))
|
|
470
|
+
(singletonBusCount === 1 || singletonBusCount === 2)) ||
|
|
471
|
+
(boundaryBusCount === 9 && singletonBusCount >= 1 && singletonBusCount <= 3)
|
|
470
472
|
)
|
|
471
473
|
}
|
|
472
474
|
|
|
475
|
+
export function getDenseSingletonDeferralCandidateCount(
|
|
476
|
+
boundaryBusConnectionCounts: readonly number[],
|
|
477
|
+
): number {
|
|
478
|
+
if (
|
|
479
|
+
!shouldDeferSingletonBoundaryViaReservation(boundaryBusConnectionCounts)
|
|
480
|
+
) {
|
|
481
|
+
return 0
|
|
482
|
+
}
|
|
483
|
+
const singletonBusCount = boundaryBusConnectionCounts.filter(
|
|
484
|
+
(count) => count === 1,
|
|
485
|
+
).length
|
|
486
|
+
return Math.max(1, singletonBusCount - 1)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
interface DenseSourceFieldBus {
|
|
490
|
+
componentId: string
|
|
491
|
+
exitEdge?: PreparedBus["exitEdge"]
|
|
492
|
+
allowedLayers?: readonly string[]
|
|
493
|
+
routableEscapeLayers?: readonly string[]
|
|
494
|
+
connections: readonly { sourcePoint: { x: number; y: number } }[]
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export function isDenseSingletonEmbeddedInMultiLayerWideBus(params: {
|
|
498
|
+
singletonBus: DenseSourceFieldBus
|
|
499
|
+
singletonTargetLayer: string
|
|
500
|
+
wideBuses: readonly DenseSourceFieldBus[]
|
|
501
|
+
}): boolean {
|
|
502
|
+
const sourcePoint = params.singletonBus.connections[0]?.sourcePoint
|
|
503
|
+
if (params.singletonBus.connections.length !== 1 || !sourcePoint) return false
|
|
504
|
+
return params.wideBuses.some((wideBus) => {
|
|
505
|
+
const wideLayers =
|
|
506
|
+
wideBus.routableEscapeLayers ?? wideBus.allowedLayers ?? []
|
|
507
|
+
if (
|
|
508
|
+
wideBus.connections.length < 8 ||
|
|
509
|
+
wideBus.componentId !== params.singletonBus.componentId ||
|
|
510
|
+
wideBus.exitEdge !== params.singletonBus.exitEdge ||
|
|
511
|
+
wideLayers.length < 2 ||
|
|
512
|
+
!wideLayers.includes(params.singletonTargetLayer)
|
|
513
|
+
) {
|
|
514
|
+
return false
|
|
515
|
+
}
|
|
516
|
+
const sourceXs = wideBus.connections.map(
|
|
517
|
+
(connection) => connection.sourcePoint.x,
|
|
518
|
+
)
|
|
519
|
+
const sourceYs = wideBus.connections.map(
|
|
520
|
+
(connection) => connection.sourcePoint.y,
|
|
521
|
+
)
|
|
522
|
+
return (
|
|
523
|
+
sourcePoint.x >= Math.min(...sourceXs) - 1e-9 &&
|
|
524
|
+
sourcePoint.x <= Math.max(...sourceXs) + 1e-9 &&
|
|
525
|
+
sourcePoint.y >= Math.min(...sourceYs) - 1e-9 &&
|
|
526
|
+
sourcePoint.y <= Math.max(...sourceYs) + 1e-9
|
|
527
|
+
)
|
|
528
|
+
})
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function getDenseLeadingCornerBandTargetTrackOffset(params: {
|
|
532
|
+
leadingLaneCount: number
|
|
533
|
+
traceWidth: number
|
|
534
|
+
viaDiameter: number
|
|
535
|
+
clearance: number
|
|
536
|
+
}): number {
|
|
537
|
+
const cornerPitch = Math.max(
|
|
538
|
+
params.traceWidth + params.clearance,
|
|
539
|
+
params.viaDiameter + params.clearance,
|
|
540
|
+
)
|
|
541
|
+
return (-params.leadingLaneCount * cornerPitch) / 2
|
|
542
|
+
}
|
|
543
|
+
|
|
473
544
|
export function shouldSearchAdditionalBoundaryRouteTopologies(params: {
|
|
474
545
|
boundaryBusCount: number
|
|
475
546
|
connectionCount: number
|
|
476
547
|
rawSkew: number
|
|
477
548
|
maximumSkew: number
|
|
478
549
|
}): boolean {
|
|
479
|
-
if (params.boundaryBusCount === 5 || params.boundaryBusCount >
|
|
550
|
+
if (params.boundaryBusCount === 5 || params.boundaryBusCount > 9) {
|
|
480
551
|
return false
|
|
481
552
|
}
|
|
482
553
|
if (
|
|
483
554
|
params.boundaryBusCount === 6 ||
|
|
484
555
|
params.boundaryBusCount === 7 ||
|
|
485
|
-
params.boundaryBusCount === 8
|
|
556
|
+
params.boundaryBusCount === 8 ||
|
|
557
|
+
params.boundaryBusCount === 9
|
|
486
558
|
) {
|
|
487
|
-
// Six through
|
|
559
|
+
// Six through nine buses remove enough meander space that a severely skewed wide
|
|
488
560
|
// topology can be impossible to tune. Keep the retry away from narrow
|
|
489
561
|
// differential/control groups and from modest deficits that the atomic
|
|
490
562
|
// length matcher can absorb directly.
|
|
@@ -537,6 +609,21 @@ export function getDenseSingletonBoundaryGeometry(
|
|
|
537
609
|
export function compareDenseSingletonBoundaryDeferralPriority(
|
|
538
610
|
first: DenseSingletonBoundaryGeometryBus,
|
|
539
611
|
second: DenseSingletonBoundaryGeometryBus,
|
|
612
|
+
): number {
|
|
613
|
+
const firstGeometry = getDenseSingletonBoundaryGeometry(first)
|
|
614
|
+
const secondGeometry = getDenseSingletonBoundaryGeometry(second)
|
|
615
|
+
return (
|
|
616
|
+
Number(firstGeometry.isCorner) - Number(secondGeometry.isCorner) ||
|
|
617
|
+
(firstGeometry.isCorner && secondGeometry.isCorner
|
|
618
|
+
? firstGeometry.targetProjection - secondGeometry.targetProjection
|
|
619
|
+
: 0) ||
|
|
620
|
+
first.busId.localeCompare(second.busId)
|
|
621
|
+
)
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function compareReleasedDenseSingletonBoundaryDeferralPriority(
|
|
625
|
+
first: DenseSingletonBoundaryGeometryBus,
|
|
626
|
+
second: DenseSingletonBoundaryGeometryBus,
|
|
540
627
|
): number {
|
|
541
628
|
const firstGeometry = getDenseSingletonBoundaryGeometry(first)
|
|
542
629
|
const secondGeometry = getDenseSingletonBoundaryGeometry(second)
|
|
@@ -569,7 +656,9 @@ export function getDenseBoundaryPairRoutingPriorityKeys(params: {
|
|
|
569
656
|
const firstBus = pairBuses[0]
|
|
570
657
|
const firstSourceLayer = firstBus?.connections[0]?.sourceLayer
|
|
571
658
|
if (
|
|
572
|
-
(params.boundaryBusCount !== 7 &&
|
|
659
|
+
(params.boundaryBusCount !== 7 &&
|
|
660
|
+
params.boundaryBusCount !== 8 &&
|
|
661
|
+
params.boundaryBusCount !== 9) ||
|
|
573
662
|
pairBuses.length !== 3 ||
|
|
574
663
|
firstBus === undefined ||
|
|
575
664
|
firstBus.exitEdge === undefined ||
|
|
@@ -989,13 +1078,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
989
1078
|
if (
|
|
990
1079
|
unsortedBoundaryBuses.length !== 6 &&
|
|
991
1080
|
unsortedBoundaryBuses.length !== 7 &&
|
|
992
|
-
unsortedBoundaryBuses.length !== 8
|
|
1081
|
+
unsortedBoundaryBuses.length !== 8 &&
|
|
1082
|
+
unsortedBoundaryBuses.length !== 9
|
|
993
1083
|
) {
|
|
994
1084
|
return 0
|
|
995
1085
|
}
|
|
996
1086
|
// The general routing order can differ across the two components as a
|
|
997
1087
|
// function of local pad geometry. Keep otherwise-equivalent corner buses
|
|
998
|
-
// in one deterministic order for the six- through
|
|
1088
|
+
// in one deterministic order for the six- through nine-bus paths so their
|
|
999
1089
|
// boundary lanes do not swap between the two ends of a direct
|
|
1000
1090
|
// interconnect. Leave the released four- and five-bus tie behavior
|
|
1001
1091
|
// unchanged.
|
|
@@ -1011,7 +1101,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1011
1101
|
)
|
|
1012
1102
|
if (
|
|
1013
1103
|
boundaryBuses.length === 0 ||
|
|
1014
|
-
boundaryBuses.length >
|
|
1104
|
+
boundaryBuses.length > 9 ||
|
|
1015
1105
|
planeBuses.length < 8 ||
|
|
1016
1106
|
boundaryBuses.some((bus) => !busUsesCoordinatedWinding(bus)) ||
|
|
1017
1107
|
planeBuses.some((bus) => bus.connections.length !== 1) ||
|
|
@@ -1029,11 +1119,15 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1029
1119
|
),
|
|
1030
1120
|
),
|
|
1031
1121
|
)
|
|
1032
|
-
const
|
|
1033
|
-
(bus) => bus.connections.length
|
|
1122
|
+
const boundaryBusConnectionCounts = boundaryBuses.map(
|
|
1123
|
+
(bus) => bus.connections.length,
|
|
1124
|
+
)
|
|
1125
|
+
const singletonBoundaryBusCount = boundaryBusConnectionCounts.filter(
|
|
1126
|
+
(connectionCount) => connectionCount === 1,
|
|
1034
1127
|
).length
|
|
1035
1128
|
const useGeometryAwareSingletonOutwardPreference =
|
|
1036
|
-
|
|
1129
|
+
singletonBoundaryBusCount > 1 &&
|
|
1130
|
+
shouldDeferSingletonBoundaryViaReservation(boundaryBusConnectionCounts)
|
|
1037
1131
|
const preferredBoundaryPerpendicularSideByBusId = new Map(
|
|
1038
1132
|
boundaryBuses.map((bus) => [bus.busId, 1 as const]),
|
|
1039
1133
|
)
|
|
@@ -1067,29 +1161,93 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1067
1161
|
),
|
|
1068
1162
|
)
|
|
1069
1163
|
}
|
|
1070
|
-
// Five through
|
|
1164
|
+
// Five through nine boundary buses, and heterogeneous four-bus groups, leave too little
|
|
1071
1165
|
// slack for incremental site allocation: a valid early trace can consume
|
|
1072
1166
|
// the last dogbone site of a later narrow bus. Reserve the multi-line bus
|
|
1073
|
-
// barrels before routing copper.
|
|
1074
|
-
//
|
|
1075
|
-
//
|
|
1076
|
-
//
|
|
1167
|
+
// barrels before routing copper. Classify the least-constrained eligible
|
|
1168
|
+
// one-line buses as deferral candidates, preferring a centered singleton,
|
|
1169
|
+
// then a corner singleton whose explicit target lies inward along its local
|
|
1170
|
+
// escape direction. A candidate embedded in a multi-layer wide-bus source
|
|
1171
|
+
// field must instead reserve an outward dogbone and route before that wide
|
|
1172
|
+
// bus; the remaining candidates stay provisional and can be rematched
|
|
1173
|
+
// around completed copper.
|
|
1077
1174
|
// Plane sites are likewise rematched around completed boundary plans.
|
|
1078
|
-
const boundaryBusConnectionCounts = boundaryBuses.map(
|
|
1079
|
-
(bus) => bus.connections.length,
|
|
1080
|
-
)
|
|
1081
1175
|
const singletonBoundaryBuses = boundaryBuses.filter(
|
|
1082
1176
|
(bus) => bus.connections.length === 1,
|
|
1083
1177
|
)
|
|
1084
|
-
const
|
|
1178
|
+
const singletonDeferralCandidates =
|
|
1085
1179
|
shouldDeferSingletonBoundaryViaReservation(boundaryBusConnectionCounts)
|
|
1086
1180
|
? singletonBoundaryBuses
|
|
1087
|
-
.toSorted(
|
|
1088
|
-
|
|
1181
|
+
.toSorted(
|
|
1182
|
+
boundaryBuses.length === 9
|
|
1183
|
+
? compareDenseSingletonBoundaryDeferralPriority
|
|
1184
|
+
: compareReleasedDenseSingletonBoundaryDeferralPriority,
|
|
1185
|
+
)
|
|
1186
|
+
.slice(
|
|
1187
|
+
0,
|
|
1188
|
+
getDenseSingletonDeferralCandidateCount(
|
|
1189
|
+
boundaryBusConnectionCounts,
|
|
1190
|
+
),
|
|
1191
|
+
)
|
|
1089
1192
|
: []
|
|
1090
|
-
const
|
|
1193
|
+
const leadingWideSingletonBuses =
|
|
1194
|
+
boundaryBuses.length === 9
|
|
1195
|
+
? singletonDeferralCandidates.filter((singletonBus) => {
|
|
1196
|
+
const singletonTargetLayer =
|
|
1197
|
+
params.busLayerAssignments[singletonBus.busId]
|
|
1198
|
+
return Boolean(
|
|
1199
|
+
singletonTargetLayer &&
|
|
1200
|
+
isDenseSingletonEmbeddedInMultiLayerWideBus({
|
|
1201
|
+
singletonBus,
|
|
1202
|
+
singletonTargetLayer,
|
|
1203
|
+
wideBuses: boundaryBuses,
|
|
1204
|
+
}),
|
|
1205
|
+
)
|
|
1206
|
+
})
|
|
1207
|
+
: []
|
|
1208
|
+
const leadingLaneCountByWideCornerBand = new Map<string, number>()
|
|
1209
|
+
if (boundaryBuses.length === 9 && leadingWideSingletonBuses.length > 0) {
|
|
1210
|
+
for (const bus of leadingWideSingletonBuses) {
|
|
1211
|
+
preferBoundaryOutwardByBusId.set(bus.busId, true)
|
|
1212
|
+
const side = getCornerBandSide(bus.exitEdge, bus.preferredExit)
|
|
1213
|
+
if (!bus.exitEdge || !side) continue
|
|
1214
|
+
const bandKey = `${bus.exitEdge}:${side}`
|
|
1215
|
+
const sharesBandWithWideBus = boundaryBuses.some((candidate) => {
|
|
1216
|
+
if (candidate === bus || candidate.connections.length < 8)
|
|
1217
|
+
return false
|
|
1218
|
+
return (
|
|
1219
|
+
candidate.exitEdge === bus.exitEdge &&
|
|
1220
|
+
getCornerBandSide(candidate.exitEdge, candidate.preferredExit) ===
|
|
1221
|
+
side
|
|
1222
|
+
)
|
|
1223
|
+
})
|
|
1224
|
+
if (!sharesBandWithWideBus) continue
|
|
1225
|
+
leadingLaneCountByWideCornerBand.set(
|
|
1226
|
+
bandKey,
|
|
1227
|
+
(leadingLaneCountByWideCornerBand.get(bandKey) ?? 0) +
|
|
1228
|
+
bus.connections.length,
|
|
1229
|
+
)
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
const viaProvisionalSingletonBusSet = new Set(
|
|
1233
|
+
singletonDeferralCandidates.filter(
|
|
1234
|
+
(bus) => !leadingWideSingletonBuses.includes(bus),
|
|
1235
|
+
),
|
|
1236
|
+
)
|
|
1237
|
+
const getCornerBandTargetTrackOffset = (bus: PreparedBus): number => {
|
|
1238
|
+
const side = getCornerBandSide(bus.exitEdge, bus.preferredExit)
|
|
1239
|
+
if (!bus.exitEdge || !side) return 0
|
|
1240
|
+
const leadingLaneCount =
|
|
1241
|
+
leadingLaneCountByWideCornerBand.get(`${bus.exitEdge}:${side}`) ?? 0
|
|
1242
|
+
return getDenseLeadingCornerBandTargetTrackOffset({
|
|
1243
|
+
leadingLaneCount,
|
|
1244
|
+
traceWidth: this.config.traceWidth,
|
|
1245
|
+
viaDiameter: this.config.viaDiameter,
|
|
1246
|
+
clearance: this.config.clearance,
|
|
1247
|
+
})
|
|
1248
|
+
}
|
|
1091
1249
|
const initiallyMatchedBoundaryBuses = boundaryBuses.filter(
|
|
1092
|
-
(bus) => !
|
|
1250
|
+
(bus) => !viaProvisionalSingletonBusSet.has(bus),
|
|
1093
1251
|
)
|
|
1094
1252
|
const jointViaPoints = useJointBoundaryViaReservation
|
|
1095
1253
|
? matchComponentDogboneViaSites(
|
|
@@ -1119,6 +1277,12 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1119
1277
|
canShareCopper,
|
|
1120
1278
|
})
|
|
1121
1279
|
if (seedViaPoints) {
|
|
1280
|
+
const denseBoundaryBusesInRoutingOrder = [
|
|
1281
|
+
...leadingWideSingletonBuses,
|
|
1282
|
+
...boundaryBuses.filter(
|
|
1283
|
+
(bus) => !leadingWideSingletonBuses.includes(bus),
|
|
1284
|
+
),
|
|
1285
|
+
]
|
|
1122
1286
|
let fixedViaPointsByConnectionIndex: ReadonlyMap<
|
|
1123
1287
|
number,
|
|
1124
1288
|
{ x: number; y: number }
|
|
@@ -1179,6 +1343,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1179
1343
|
fixedViaPointsByConnectionIndex,
|
|
1180
1344
|
reservedVias: getReservedVias(bus),
|
|
1181
1345
|
viaMinimalOnly: true,
|
|
1346
|
+
cornerBandTargetTrackOffset: getCornerBandTargetTrackOffset(bus),
|
|
1182
1347
|
} as const
|
|
1183
1348
|
let busPlans = routeBusAlternatives(routeParams, 1)[0]
|
|
1184
1349
|
if (busPlans && bus.maxLengthSkew !== undefined) {
|
|
@@ -1215,14 +1380,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1215
1380
|
return true
|
|
1216
1381
|
}
|
|
1217
1382
|
|
|
1218
|
-
const firstBoundaryBus =
|
|
1383
|
+
const firstBoundaryBus = denseBoundaryBusesInRoutingOrder[0]!
|
|
1219
1384
|
const routedBoundaryBuses: PreparedBus[] = []
|
|
1220
1385
|
if (routeMatchedBoundaryBus(firstBoundaryBus)) {
|
|
1221
1386
|
routedBoundaryBuses.push(firstBoundaryBus)
|
|
1222
1387
|
} else {
|
|
1223
1388
|
matchedRoutingSucceeded = false
|
|
1224
1389
|
}
|
|
1225
|
-
const remainingBoundaryBuses =
|
|
1390
|
+
const remainingBoundaryBuses = denseBoundaryBusesInRoutingOrder.slice(1)
|
|
1226
1391
|
while (matchedRoutingSucceeded && remainingBoundaryBuses.length > 0) {
|
|
1227
1392
|
const blockingSegments = matchedPlans.flatMap((plan) =>
|
|
1228
1393
|
plan.segments.map((segment) => ({
|
package/lib/route-bus.ts
CHANGED
|
@@ -60,6 +60,8 @@ export interface RouteBusParams {
|
|
|
60
60
|
fixedViaPointsByConnectionIndex?: ReadonlyMap<number, Point2D>
|
|
61
61
|
reservedVias?: readonly ViaMinimalWindingReservedVia[]
|
|
62
62
|
viaMinimalOnly?: boolean
|
|
63
|
+
/** Dense corner-band phase that preserves existing lane centers when leading lanes are prepended. */
|
|
64
|
+
cornerBandTargetTrackOffset?: number
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
interface TrackCandidate {
|
|
@@ -287,6 +289,24 @@ function getWindingCrossoverLayer(params: {
|
|
|
287
289
|
)
|
|
288
290
|
}
|
|
289
291
|
|
|
292
|
+
export function getBoundaryTargetTrack(params: {
|
|
293
|
+
bus: PreparedBus
|
|
294
|
+
connection: PreparedConnection
|
|
295
|
+
boundaryDirection: FanoutDirection
|
|
296
|
+
}): number {
|
|
297
|
+
const requestedTrack = getPerpendicularAxis(
|
|
298
|
+
params.connection.exitTargetPoint ?? params.connection.targetPoint,
|
|
299
|
+
params.boundaryDirection,
|
|
300
|
+
)
|
|
301
|
+
const boundaryMinimum = isHorizontal(params.boundaryDirection)
|
|
302
|
+
? params.bus.sharedBoundary.minY
|
|
303
|
+
: params.bus.sharedBoundary.minX
|
|
304
|
+
const boundaryMaximum = isHorizontal(params.boundaryDirection)
|
|
305
|
+
? params.bus.sharedBoundary.maxY
|
|
306
|
+
: params.bus.sharedBoundary.maxX
|
|
307
|
+
return Math.max(boundaryMinimum, Math.min(boundaryMaximum, requestedTrack))
|
|
308
|
+
}
|
|
309
|
+
|
|
290
310
|
function getCornerTargetTrack(params: {
|
|
291
311
|
bus: PreparedBus
|
|
292
312
|
connection: PreparedConnection
|
|
@@ -297,6 +317,7 @@ function getCornerTargetTrack(params: {
|
|
|
297
317
|
layerNames: readonly string[]
|
|
298
318
|
targetLayer: string
|
|
299
319
|
windingOrderIndex?: number
|
|
320
|
+
cornerBandTargetTrackOffset?: number
|
|
300
321
|
}): number {
|
|
301
322
|
const {
|
|
302
323
|
bus,
|
|
@@ -308,6 +329,7 @@ function getCornerTargetTrack(params: {
|
|
|
308
329
|
layerNames,
|
|
309
330
|
targetLayer,
|
|
310
331
|
windingOrderIndex,
|
|
332
|
+
cornerBandTargetTrackOffset = 0,
|
|
311
333
|
} = params
|
|
312
334
|
const side = getCornerSide(bus)
|
|
313
335
|
if (!side || !bus.exitEdge) {
|
|
@@ -326,7 +348,8 @@ function getCornerTargetTrack(params: {
|
|
|
326
348
|
const pitch = Math.max(traceWidth + clearance, viaDiameter + clearance)
|
|
327
349
|
const baseBandCenter =
|
|
328
350
|
boundaryMinimum +
|
|
329
|
-
(boundaryMaximum - boundaryMinimum) * (side === "minimum" ? 0.25 : 0.75)
|
|
351
|
+
(boundaryMaximum - boundaryMinimum) * (side === "minimum" ? 0.25 : 0.75) +
|
|
352
|
+
cornerBandTargetTrackOffset
|
|
330
353
|
const windingTarget = busUsesCoordinatedWindingChannel(bus)
|
|
331
354
|
? getWindingTargetRank({
|
|
332
355
|
bus,
|
|
@@ -945,6 +968,7 @@ function buildPlan(params: {
|
|
|
945
968
|
allowBlindAndBuriedVias: boolean
|
|
946
969
|
initialViaPoint?: Point2D
|
|
947
970
|
sourceEscapePath?: readonly Point2D[]
|
|
971
|
+
cornerBandTargetTrackOffset?: number
|
|
948
972
|
}): FanoutRoutePlan {
|
|
949
973
|
const {
|
|
950
974
|
preparedConnection,
|
|
@@ -967,6 +991,7 @@ function buildPlan(params: {
|
|
|
967
991
|
allowBlindAndBuriedVias,
|
|
968
992
|
initialViaPoint,
|
|
969
993
|
sourceEscapePath,
|
|
994
|
+
cornerBandTargetTrackOffset,
|
|
970
995
|
} = params
|
|
971
996
|
const sourcePoint = {
|
|
972
997
|
x: preparedConnection.sourcePoint.x,
|
|
@@ -1048,13 +1073,14 @@ function buildPlan(params: {
|
|
|
1048
1073
|
clearance,
|
|
1049
1074
|
layerNames,
|
|
1050
1075
|
targetLayer,
|
|
1076
|
+
cornerBandTargetTrackOffset,
|
|
1051
1077
|
})
|
|
1052
1078
|
: usesLayeredWindingChannel
|
|
1053
|
-
?
|
|
1054
|
-
|
|
1055
|
-
|
|
1079
|
+
? getBoundaryTargetTrack({
|
|
1080
|
+
bus,
|
|
1081
|
+
connection: preparedConnection,
|
|
1056
1082
|
boundaryDirection,
|
|
1057
|
-
)
|
|
1083
|
+
})
|
|
1058
1084
|
: track
|
|
1059
1085
|
const connectionRank = getConnectionRank(bus, preparedConnection)
|
|
1060
1086
|
const localCornerSide = getLocalCornerSide(bus)
|
|
@@ -2365,6 +2391,7 @@ export function routeBusAlternatives(
|
|
|
2365
2391
|
fixedViaPointsByConnectionIndex,
|
|
2366
2392
|
reservedVias = [],
|
|
2367
2393
|
viaMinimalOnly = false,
|
|
2394
|
+
cornerBandTargetTrackOffset,
|
|
2368
2395
|
} = params
|
|
2369
2396
|
if (!Number.isInteger(maxAlternatives) || maxAlternatives < 1) {
|
|
2370
2397
|
throw new Error(
|
|
@@ -2698,12 +2725,13 @@ export function routeBusAlternatives(
|
|
|
2698
2725
|
layerNames,
|
|
2699
2726
|
targetLayer,
|
|
2700
2727
|
windingOrderIndex: terminalPattern.windingOrderIndex,
|
|
2728
|
+
cornerBandTargetTrackOffset,
|
|
2701
2729
|
})
|
|
2702
|
-
:
|
|
2703
|
-
|
|
2704
|
-
|
|
2730
|
+
: getBoundaryTargetTrack({
|
|
2731
|
+
bus,
|
|
2732
|
+
connection: preparedConnection,
|
|
2705
2733
|
boundaryDirection,
|
|
2706
|
-
)
|
|
2734
|
+
})
|
|
2707
2735
|
return {
|
|
2708
2736
|
connection: preparedConnection,
|
|
2709
2737
|
viaPoint: terminalPattern.getViaPoint
|
|
@@ -2882,6 +2910,7 @@ export function routeBusAlternatives(
|
|
|
2882
2910
|
clearance,
|
|
2883
2911
|
terminateAtVia: false,
|
|
2884
2912
|
allowBlindAndBuriedVias,
|
|
2913
|
+
cornerBandTargetTrackOffset,
|
|
2885
2914
|
})
|
|
2886
2915
|
if (
|
|
2887
2916
|
!planIsClear({
|
|
@@ -2889,7 +2918,7 @@ export function routeBusAlternatives(
|
|
|
2889
2918
|
otherPlans: [...acceptedPlans, ...candidatePlans],
|
|
2890
2919
|
staticClearanceCache,
|
|
2891
2920
|
blockingBusCounts,
|
|
2892
|
-
cacheKey: `boundary:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${viaHandedness}:${trackIndex}:${bus.exitEdge ?? "legacy"}:${cornerLaneOffsets.exit}:${cornerLaneOffsets.localChannel}:${cornerLaneOffsets.boundaryChannel}`,
|
|
2921
|
+
cacheKey: `boundary:${bus.busId}:${targetLayer}:${preparedConnection.connectionIndex}:${viaHandedness}:${trackIndex}:${bus.exitEdge ?? "legacy"}:${cornerLaneOffsets.exit}:${cornerLaneOffsets.localChannel}:${cornerLaneOffsets.boundaryChannel}:${cornerBandTargetTrackOffset ?? 0}`,
|
|
2893
2922
|
srj,
|
|
2894
2923
|
sharedBoundary: bus.sharedBoundary,
|
|
2895
2924
|
clearance,
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/fanout-solver",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.48",
|
|
4
4
|
"description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
|
|
5
5
|
"module": "lib/index.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
8
8
|
"lib"
|
|
9
9
|
],
|
|
10
|
+
"workspaces": [
|
|
11
|
+
"scripts/generate-repro"
|
|
12
|
+
],
|
|
10
13
|
"license": "MIT",
|
|
11
14
|
"repository": {
|
|
12
15
|
"type": "git",
|
|
@@ -28,6 +31,7 @@
|
|
|
28
31
|
"typecheck": "bunx tsc --noEmit",
|
|
29
32
|
"test": "bun test",
|
|
30
33
|
"benchmark": "bun benchmarks/run-benchmark.ts",
|
|
34
|
+
"generate:repro04": "bun scripts/generate-repro/generate-repro04.tsx",
|
|
31
35
|
"benchmark:srj29": "./benchmark.sh",
|
|
32
36
|
"render:dataset": "bun scripts/render-dataset-pngs.ts",
|
|
33
37
|
"format": "biome format --write .",
|
|
@@ -44,7 +48,12 @@
|
|
|
44
48
|
"@tsci/tscircuit.dataset-srj29-bga-decoupling": "github:tscircuit/dataset-srj29-bga-decoupling#bfdf7bc",
|
|
45
49
|
"@tscircuit/alphabet": "0.0.25",
|
|
46
50
|
"@tscircuit/circuit-json-util": "0.0.97",
|
|
51
|
+
"@tscircuit/copper-pour-solver": "0.0.51",
|
|
47
52
|
"@tscircuit/footprinter": "0.0.394",
|
|
53
|
+
"@tscircuit/infer-cable-insertion-point": "0.0.4",
|
|
54
|
+
"@tscircuit/miniflex": "0.0.4",
|
|
55
|
+
"@tscircuit/schematic-trace-solver": "0.0.175",
|
|
56
|
+
"@tscircuit/soup-util": "0.0.41",
|
|
48
57
|
"@types/bun": "1.3.14",
|
|
49
58
|
"@types/react": "19.2.14",
|
|
50
59
|
"@types/react-dom": "19.2.3",
|
|
@@ -52,12 +61,16 @@
|
|
|
52
61
|
"circuit-json": "0.0.455",
|
|
53
62
|
"circuit-json-to-connectivity-map": "0.0.23",
|
|
54
63
|
"circuit-to-svg": "0.0.364",
|
|
55
|
-
"
|
|
64
|
+
"connectivity-map": "1.0.0",
|
|
65
|
+
"flatbush": "4.5.0",
|
|
66
|
+
"format-si-unit": "0.0.12",
|
|
67
|
+
"minicssgrid": "0.0.9",
|
|
56
68
|
"react": "19.2.8",
|
|
57
69
|
"react-cosmos": "7.3.0",
|
|
58
70
|
"react-cosmos-plugin-vite": "7.3.0",
|
|
59
71
|
"react-dom": "19.2.8",
|
|
60
72
|
"schematic-symbols": "0.0.231",
|
|
73
|
+
"spicey": "0.0.14",
|
|
61
74
|
"vite": "8.1.5"
|
|
62
75
|
},
|
|
63
76
|
"peerDependencies": {
|