@tscircuit/fanout-solver 0.0.23 → 0.0.24
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 +6 -7
- package/lib/fanout-solver.ts +24 -20
- package/lib/route-bus.ts +38 -18
- package/lib/types.ts +0 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,12 +61,12 @@ and treats each bus-layer decision atomically.
|
|
|
61
61
|
- Uses a straight pad-pair escape when the via fits. Otherwise it uses a 45°
|
|
62
62
|
four-pad interstitial escape and nested side bands that spread deeper
|
|
63
63
|
two-layer buses around already-routed outer buses.
|
|
64
|
-
- `compactBusTracks`
|
|
65
|
-
|
|
66
|
-
breakout corridor.
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
- `compactBusTracks` provides a trace/clearance-pitch fallback envelope when
|
|
65
|
+
the original endpoint track cannot route, so a wide pad row does not consume
|
|
66
|
+
a disproportionately wide breakout corridor.
|
|
67
|
+
- Boundary tracks project onto the original downstream pad coordinates and
|
|
68
|
+
prefer their layer when legal. This lets ordered edge-pad buses make direct,
|
|
69
|
+
visibly continuous pad connections.
|
|
70
70
|
- Chamfers orthogonal routing corners into 45° segments before validating and
|
|
71
71
|
emitting the fanout.
|
|
72
72
|
- Verifies oriented-pad, via, trace, and already-routed fanout clearance on
|
|
@@ -125,7 +125,6 @@ const fanoutSolver = new FanoutSolver(simpleRouteJson, {
|
|
|
125
125
|
availableCornersAndSides: ["top_left", "top", "top_right"],
|
|
126
126
|
borderDistribution: "even",
|
|
127
127
|
compactBusTracks: true,
|
|
128
|
-
preferOriginalEndpointTracks: true,
|
|
129
128
|
buses: [
|
|
130
129
|
{
|
|
131
130
|
busId: "ground",
|
package/lib/fanout-solver.ts
CHANGED
|
@@ -40,7 +40,6 @@ interface ResolvedFanoutConfig {
|
|
|
40
40
|
viaHoleDiameter: number
|
|
41
41
|
clearance: number
|
|
42
42
|
compactBusTracks: boolean
|
|
43
|
-
preferOriginalEndpointTracks: boolean
|
|
44
43
|
allowSameNetMerges: boolean
|
|
45
44
|
singleLayerPushAndShove: boolean
|
|
46
45
|
singleLayerAdaptiveExits: boolean
|
|
@@ -132,7 +131,6 @@ function resolveConfig(
|
|
|
132
131
|
viaHoleDiameter,
|
|
133
132
|
clearance,
|
|
134
133
|
compactBusTracks: options.compactBusTracks ?? false,
|
|
135
|
-
preferOriginalEndpointTracks: options.preferOriginalEndpointTracks ?? false,
|
|
136
134
|
allowSameNetMerges: options.allowSameNetMerges ?? false,
|
|
137
135
|
singleLayerPushAndShove: options.singleLayerPushAndShove ?? false,
|
|
138
136
|
singleLayerAdaptiveExits: options.singleLayerAdaptiveExits ?? false,
|
|
@@ -210,6 +208,19 @@ function getBusDistanceToBoundary(bus: PreparedBus): number {
|
|
|
210
208
|
}
|
|
211
209
|
}
|
|
212
210
|
|
|
211
|
+
function busUsesOriginalEndpointTracks(bus: PreparedBus): boolean {
|
|
212
|
+
const isHorizontal = bus.direction === "left" || bus.direction === "right"
|
|
213
|
+
return bus.connections.some((connection) => {
|
|
214
|
+
const sourceTrack = isHorizontal
|
|
215
|
+
? connection.sourcePoint.y
|
|
216
|
+
: connection.sourcePoint.x
|
|
217
|
+
const targetTrack = isHorizontal
|
|
218
|
+
? connection.targetPoint.y
|
|
219
|
+
: connection.targetPoint.x
|
|
220
|
+
return Math.abs(sourceTrack - targetTrack) > 1e-6
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
213
224
|
function busIsOnOutwardComponentEdge(bus: PreparedBus): boolean {
|
|
214
225
|
const isHorizontal = bus.direction === "left" || bus.direction === "right"
|
|
215
226
|
const directionalCoordinates = isHorizontal
|
|
@@ -256,14 +267,8 @@ function createPreferredLayerAssignment(params: {
|
|
|
256
267
|
buses: PreparedBus[]
|
|
257
268
|
escapeLayers: string[]
|
|
258
269
|
escapeLayersByBusId: Readonly<Record<string, readonly string[]>>
|
|
259
|
-
preferOriginalEndpointTracks: boolean
|
|
260
270
|
}): Readonly<Record<string, string>> {
|
|
261
|
-
const {
|
|
262
|
-
buses,
|
|
263
|
-
escapeLayers,
|
|
264
|
-
escapeLayersByBusId,
|
|
265
|
-
preferOriginalEndpointTracks,
|
|
266
|
-
} = params
|
|
271
|
+
const { buses, escapeLayers, escapeLayersByBusId } = params
|
|
267
272
|
const assignment: Record<string, string> = {}
|
|
268
273
|
const directionsByComponent = new Map<string, Set<PreparedBus["direction"]>>()
|
|
269
274
|
let nextViaLayerIndex = 0
|
|
@@ -288,7 +293,7 @@ function createPreferredLayerAssignment(params: {
|
|
|
288
293
|
)
|
|
289
294
|
if (
|
|
290
295
|
routableEscapeLayers.includes(sourceLayer) &&
|
|
291
|
-
(
|
|
296
|
+
(busUsesOriginalEndpointTracks(bus) || busIsOnOutwardComponentEdge(bus))
|
|
292
297
|
) {
|
|
293
298
|
assignment[bus.busId] = sourceLayer
|
|
294
299
|
} else if (viaLayers.length > 0) {
|
|
@@ -347,7 +352,6 @@ function getCandidateEscapeLayersForBus(params: {
|
|
|
347
352
|
viaHoleDiameter: config.viaHoleDiameter,
|
|
348
353
|
clearance: config.clearance,
|
|
349
354
|
compactBusTracks: config.compactBusTracks,
|
|
350
|
-
preferOriginalEndpointTracks: config.preferOriginalEndpointTracks,
|
|
351
355
|
allowSameNetMerges: config.allowSameNetMerges,
|
|
352
356
|
staticClearanceCache,
|
|
353
357
|
}) !== null,
|
|
@@ -458,7 +462,6 @@ export class FanoutSolver extends BaseSolver {
|
|
|
458
462
|
buses: this.preparedBuses,
|
|
459
463
|
escapeLayers: this.config.escapeLayers,
|
|
460
464
|
escapeLayersByBusId,
|
|
461
|
-
preferOriginalEndpointTracks: this.config.preferOriginalEndpointTracks,
|
|
462
465
|
}),
|
|
463
466
|
generatedAssignments,
|
|
464
467
|
maxAssignments: this.config.maxLayerCombinations,
|
|
@@ -605,7 +608,6 @@ export class FanoutSolver extends BaseSolver {
|
|
|
605
608
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
606
609
|
clearance: this.config.clearance,
|
|
607
610
|
compactBusTracks: this.config.compactBusTracks,
|
|
608
|
-
preferOriginalEndpointTracks: this.config.preferOriginalEndpointTracks,
|
|
609
611
|
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
610
612
|
staticClearanceCache: this.routeStaticClearanceCache,
|
|
611
613
|
blockingBusCounts: currentBusBlockingCounts,
|
|
@@ -791,7 +793,12 @@ export class FanoutSolver extends BaseSolver {
|
|
|
791
793
|
const viaCount = getPlanViaCount(state.plans)
|
|
792
794
|
const offEndpointLayerConnectionCount = this.preparedBuses.reduce(
|
|
793
795
|
(count, bus) => {
|
|
794
|
-
if (
|
|
796
|
+
if (
|
|
797
|
+
bus.termination.type !== "boundary" ||
|
|
798
|
+
!busUsesOriginalEndpointTracks(bus)
|
|
799
|
+
) {
|
|
800
|
+
return count
|
|
801
|
+
}
|
|
795
802
|
const sourceLayer = bus.connections[0]?.sourceLayer
|
|
796
803
|
return state.assignment[bus.busId] === sourceLayer
|
|
797
804
|
? count
|
|
@@ -802,9 +809,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
802
809
|
return (
|
|
803
810
|
routeLength +
|
|
804
811
|
viaCount * 0.1 +
|
|
805
|
-
|
|
806
|
-
? offEndpointLayerConnectionCount * 10_000
|
|
807
|
-
: 0) +
|
|
812
|
+
offEndpointLayerConnectionCount * 10_000 +
|
|
808
813
|
assignmentLoadPenalty(
|
|
809
814
|
state.assignment,
|
|
810
815
|
this.preparedBuses,
|
|
@@ -835,10 +840,11 @@ export class FanoutSolver extends BaseSolver {
|
|
|
835
840
|
)
|
|
836
841
|
}
|
|
837
842
|
const sourceLayer = bus.connections[0]?.sourceLayer
|
|
843
|
+
const preferSourceLayer = busUsesOriginalEndpointTracks(bus)
|
|
838
844
|
const orderedLayers = candidateLayers.toSorted(
|
|
839
845
|
(first, second) =>
|
|
840
846
|
(layerLoads.get(first) ?? 0) - (layerLoads.get(second) ?? 0) ||
|
|
841
|
-
(
|
|
847
|
+
(preferSourceLayer
|
|
842
848
|
? Number(second === sourceLayer) - Number(first === sourceLayer)
|
|
843
849
|
: Number(first === sourceLayer) -
|
|
844
850
|
Number(second === sourceLayer)) ||
|
|
@@ -858,8 +864,6 @@ export class FanoutSolver extends BaseSolver {
|
|
|
858
864
|
viaHoleDiameter: this.config.viaHoleDiameter,
|
|
859
865
|
clearance: this.config.clearance,
|
|
860
866
|
compactBusTracks: this.config.compactBusTracks,
|
|
861
|
-
preferOriginalEndpointTracks:
|
|
862
|
-
this.config.preferOriginalEndpointTracks,
|
|
863
867
|
allowSameNetMerges: this.config.allowSameNetMerges,
|
|
864
868
|
staticClearanceCache: this.routeStaticClearanceCache,
|
|
865
869
|
},
|
package/lib/route-bus.ts
CHANGED
|
@@ -39,7 +39,6 @@ export interface RouteBusParams {
|
|
|
39
39
|
viaHoleDiameter: number
|
|
40
40
|
clearance: number
|
|
41
41
|
compactBusTracks: boolean
|
|
42
|
-
preferOriginalEndpointTracks?: boolean
|
|
43
42
|
allowSameNetMerges?: boolean
|
|
44
43
|
staticClearanceCache?: RouteBusStaticClearanceCache
|
|
45
44
|
blockingBusCounts?: Map<string, number>
|
|
@@ -293,10 +292,19 @@ function getTrackCandidates(params: {
|
|
|
293
292
|
function getPreferredTrack(params: {
|
|
294
293
|
bus: PreparedBus
|
|
295
294
|
connection: PreparedConnection
|
|
295
|
+
}): number {
|
|
296
|
+
return getPerpendicularAxis(
|
|
297
|
+
params.connection.targetPoint,
|
|
298
|
+
params.bus.direction,
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function getLegacyPreferredTrack(params: {
|
|
303
|
+
bus: PreparedBus
|
|
304
|
+
connection: PreparedConnection
|
|
296
305
|
targetUsesVia: boolean
|
|
297
306
|
interstitialEscape: boolean
|
|
298
307
|
compactBusTracks: boolean
|
|
299
|
-
preferOriginalEndpointTracks: boolean
|
|
300
308
|
traceWidth: number
|
|
301
309
|
viaDiameter: number
|
|
302
310
|
clearance: number
|
|
@@ -307,7 +315,6 @@ function getPreferredTrack(params: {
|
|
|
307
315
|
targetUsesVia,
|
|
308
316
|
interstitialEscape,
|
|
309
317
|
compactBusTracks,
|
|
310
|
-
preferOriginalEndpointTracks,
|
|
311
318
|
traceWidth,
|
|
312
319
|
viaDiameter,
|
|
313
320
|
clearance,
|
|
@@ -319,9 +326,6 @@ function getPreferredTrack(params: {
|
|
|
319
326
|
connection.sourcePoint,
|
|
320
327
|
bus.direction,
|
|
321
328
|
)
|
|
322
|
-
if (preferOriginalEndpointTracks) {
|
|
323
|
-
return getPerpendicularAxis(connection.targetPoint, bus.direction)
|
|
324
|
-
}
|
|
325
329
|
if (compactBusTracks) {
|
|
326
330
|
const connectionRank = getConnectionRank(bus, connection)
|
|
327
331
|
const componentCenter =
|
|
@@ -332,11 +336,9 @@ function getPreferredTrack(params: {
|
|
|
332
336
|
(traceWidth + clearance)
|
|
333
337
|
)
|
|
334
338
|
}
|
|
335
|
-
if (!targetUsesVia) return sourceTrack
|
|
336
|
-
if (!interstitialEscape) return sourceTrack
|
|
339
|
+
if (!targetUsesVia || !interstitialEscape) return sourceTrack
|
|
337
340
|
|
|
338
341
|
const depthInRows = getDepthInRows(bus)
|
|
339
|
-
|
|
340
342
|
const trackPitch = traceWidth + clearance
|
|
341
343
|
const halfConnectionCount = Math.ceil(bus.connections.length / 2)
|
|
342
344
|
const sideBandWidth = (halfConnectionCount - 1) * trackPitch
|
|
@@ -1296,7 +1298,6 @@ export function routeBusAlternatives(
|
|
|
1296
1298
|
viaHoleDiameter,
|
|
1297
1299
|
clearance,
|
|
1298
1300
|
compactBusTracks,
|
|
1299
|
-
preferOriginalEndpointTracks = false,
|
|
1300
1301
|
staticClearanceCache,
|
|
1301
1302
|
blockingBusCounts,
|
|
1302
1303
|
allowSameNetMerges = false,
|
|
@@ -1359,23 +1360,42 @@ export function routeBusAlternatives(
|
|
|
1359
1360
|
|
|
1360
1361
|
const preparedConnection = connectionOrder[connectionIndex]!
|
|
1361
1362
|
const connectionRank = getConnectionRank(bus, preparedConnection)
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1363
|
+
const preferredTracks = [
|
|
1364
|
+
getPreferredTrack({
|
|
1365
|
+
bus,
|
|
1366
|
+
connection: preparedConnection,
|
|
1367
|
+
}),
|
|
1368
|
+
getLegacyPreferredTrack({
|
|
1366
1369
|
bus,
|
|
1367
1370
|
connection: preparedConnection,
|
|
1368
1371
|
targetUsesVia,
|
|
1369
1372
|
interstitialEscape,
|
|
1370
1373
|
compactBusTracks,
|
|
1371
|
-
preferOriginalEndpointTracks,
|
|
1372
1374
|
traceWidth,
|
|
1373
1375
|
viaDiameter,
|
|
1374
1376
|
clearance,
|
|
1375
1377
|
}),
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1378
|
+
].filter(
|
|
1379
|
+
(track, index, tracks) =>
|
|
1380
|
+
tracks.findIndex((candidate) => Math.abs(candidate - track) < 1e-9) ===
|
|
1381
|
+
index,
|
|
1382
|
+
)
|
|
1383
|
+
const trackCandidates = preferredTracks
|
|
1384
|
+
.flatMap((preferredTrack) =>
|
|
1385
|
+
getTrackCandidates({
|
|
1386
|
+
bus,
|
|
1387
|
+
connection: preparedConnection,
|
|
1388
|
+
preferredTrack,
|
|
1389
|
+
traceWidth,
|
|
1390
|
+
clearance,
|
|
1391
|
+
}),
|
|
1392
|
+
)
|
|
1393
|
+
.filter(
|
|
1394
|
+
(track, index, tracks) =>
|
|
1395
|
+
tracks.findIndex(
|
|
1396
|
+
(candidate) => Math.abs(candidate.value - track.value) < 1e-9,
|
|
1397
|
+
) === index,
|
|
1398
|
+
)
|
|
1379
1399
|
for (
|
|
1380
1400
|
let trackIndex = 0;
|
|
1381
1401
|
trackIndex < trackCandidates.length;
|
package/lib/types.ts
CHANGED
|
@@ -102,13 +102,6 @@ export interface FanoutSolverOptions {
|
|
|
102
102
|
viaHoleDiameter?: number
|
|
103
103
|
clearance?: number
|
|
104
104
|
compactBusTracks?: boolean
|
|
105
|
-
/**
|
|
106
|
-
* Prefer the perpendicular coordinate of each original downstream endpoint
|
|
107
|
-
* when assigning boundary tracks. Ordered edge-pad buses can then enter
|
|
108
|
-
* same-layer pads directly instead of requiring a second global router to
|
|
109
|
-
* undo compacted fanout tracks.
|
|
110
|
-
*/
|
|
111
|
-
preferOriginalEndpointTracks?: boolean
|
|
112
105
|
/** Allow branches belonging to the same electrical net to share copper. */
|
|
113
106
|
allowSameNetMerges?: boolean
|
|
114
107
|
singleLayerPushAndShove?: boolean
|