@tscircuit/fanout-solver 0.0.64 → 0.0.65
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 +198 -6
- package/lib/get-free-boundary-tracks.ts +102 -0
- package/lib/match-angularly-ordered-local-vias.ts +239 -0
- package/lib/reflect-fanout-x.ts +67 -0
- package/lib/repair-peripheral-bus-lengths.ts +167 -0
- package/lib/route-adaptive-left-crossbar-bus.ts +425 -0
- package/lib/route-bottom-crossbar-bus.ts +559 -0
- package/lib/route-bus.ts +18 -2
- package/lib/route-left-crossbar-bus.ts +399 -0
- package/lib/route-opposite-bottom-crossbar-bus.ts +70 -0
- package/lib/route-peripheral-source-escapes.ts +488 -0
- package/lib/route-reserved-narrow-buses.ts +430 -0
- package/lib/route-reserved-source-buses.ts +243 -0
- package/lib/route-shallow-split-perimeter-bus.ts +268 -0
- package/lib/route-split-perimeter-bus.ts +397 -0
- package/lib/route-split-perimeter-source-escapes.ts +579 -0
- package/lib/route-staged-perimeter-bus.ts +257 -0
- package/lib/route-via-minimal-winding.ts +78 -30
- package/package.json +1 -1
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { getCornerBandSide } from "./boundary-exit"
|
|
2
|
+
import {
|
|
3
|
+
distance,
|
|
4
|
+
distancePointToSegment,
|
|
5
|
+
distanceSegmentToSegment,
|
|
6
|
+
} from "./geometry"
|
|
7
|
+
import { getViaSpanLayers } from "./layer-names"
|
|
8
|
+
import {
|
|
9
|
+
fanoutPlansAreClear,
|
|
10
|
+
getCornerTargetTrack,
|
|
11
|
+
type RouteBusParams,
|
|
12
|
+
} from "./route-bus"
|
|
13
|
+
import type { PeripheralSourceEscape } from "./route-peripheral-source-escapes"
|
|
14
|
+
import {
|
|
15
|
+
routeViaMinimalWindingAlternativesSteps,
|
|
16
|
+
type RouteViaMinimalWindingProgress,
|
|
17
|
+
} from "./route-via-minimal-winding"
|
|
18
|
+
import type {
|
|
19
|
+
Bounds,
|
|
20
|
+
FanoutRoutePlan,
|
|
21
|
+
Point2D,
|
|
22
|
+
RoutedSegment,
|
|
23
|
+
RoutedVia,
|
|
24
|
+
} from "./types"
|
|
25
|
+
|
|
26
|
+
/** Route a right-edge bus through a peripheral crossbar on its two allowed layers. */
|
|
27
|
+
export function* routeLeftCrossbarBusSteps(
|
|
28
|
+
params: RouteBusParams & {
|
|
29
|
+
sourceEscapes: readonly PeripheralSourceEscape[]
|
|
30
|
+
sourceBoundary: Bounds
|
|
31
|
+
},
|
|
32
|
+
): Generator<RouteViaMinimalWindingProgress, FanoutRoutePlan[] | null, void> {
|
|
33
|
+
const {
|
|
34
|
+
bus,
|
|
35
|
+
sourceBoundary,
|
|
36
|
+
sourceEscapes,
|
|
37
|
+
traceWidth: w,
|
|
38
|
+
clearance: c,
|
|
39
|
+
targetLayer,
|
|
40
|
+
} = params
|
|
41
|
+
const cornerSide = getCornerBandSide(bus.exitEdge, bus.preferredExit)
|
|
42
|
+
if (
|
|
43
|
+
params.allowBlindAndBuriedVias ||
|
|
44
|
+
bus.exitEdge !== "right" ||
|
|
45
|
+
(cornerSide !== undefined && cornerSide !== "minimum") ||
|
|
46
|
+
bus.connections.length < 3
|
|
47
|
+
)
|
|
48
|
+
return null
|
|
49
|
+
const allowedLayers = bus.routableEscapeLayers ?? bus.allowedLayers ?? []
|
|
50
|
+
const crossoverLayer = allowedLayers.find((layer) => layer !== targetLayer)
|
|
51
|
+
if (
|
|
52
|
+
!crossoverLayer ||
|
|
53
|
+
!allowedLayers.includes(targetLayer) ||
|
|
54
|
+
bus.connections.some((connection) => connection.sourceLayer === targetLayer)
|
|
55
|
+
)
|
|
56
|
+
return null
|
|
57
|
+
const byIndex = new Map(
|
|
58
|
+
sourceEscapes.map((source) => [source.connectionIndex, source]),
|
|
59
|
+
)
|
|
60
|
+
const pitch = w + c,
|
|
61
|
+
padPitch = Math.min(bus.pitchX, bus.pitchY),
|
|
62
|
+
viaPitch = params.viaDiameter + c
|
|
63
|
+
const center = {
|
|
64
|
+
x: (bus.componentBounds.minX + bus.componentBounds.maxX) / 2,
|
|
65
|
+
y: (bus.componentBounds.minY + bus.componentBounds.maxY) / 2,
|
|
66
|
+
}
|
|
67
|
+
const firstColumn =
|
|
68
|
+
bus.sharedBoundary.minX + Math.max(padPitch / 2, params.viaDiameter / 2 + c)
|
|
69
|
+
if (
|
|
70
|
+
firstColumn + (bus.connections.length - 1) * viaPitch >=
|
|
71
|
+
sourceBoundary.minX
|
|
72
|
+
)
|
|
73
|
+
return null
|
|
74
|
+
const sameCornerCount = params.acceptedPlans.filter(
|
|
75
|
+
(plan) =>
|
|
76
|
+
plan.exitEdge === bus.exitEdge && plan.cornerBandSide === "minimum",
|
|
77
|
+
).length
|
|
78
|
+
const terminals = bus.connections.map((connection) => {
|
|
79
|
+
const source = byIndex.get(connection.connectionIndex)
|
|
80
|
+
if (!source)
|
|
81
|
+
throw new Error(
|
|
82
|
+
`FanoutSolver: missing crossbar source escape for ${connection.connection.name}`,
|
|
83
|
+
)
|
|
84
|
+
return {
|
|
85
|
+
connection,
|
|
86
|
+
viaPoint: source.via.center,
|
|
87
|
+
exitPoint: {
|
|
88
|
+
x: bus.sharedBoundary.maxX,
|
|
89
|
+
y: cornerSide
|
|
90
|
+
? getCornerTargetTrack({
|
|
91
|
+
...params,
|
|
92
|
+
connection,
|
|
93
|
+
cornerExitLaneOffset: sameCornerCount,
|
|
94
|
+
})
|
|
95
|
+
: getCornerTargetTrack({
|
|
96
|
+
...params,
|
|
97
|
+
bus: { ...bus, preferredExit: "bottom-right" },
|
|
98
|
+
connection,
|
|
99
|
+
cornerExitLaneOffset: sameCornerCount,
|
|
100
|
+
windingOrderIndex: 0,
|
|
101
|
+
}),
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
const targetOrdered = terminals.toSorted(
|
|
106
|
+
(a, b) => a.exitPoint.y - b.exitPoint.y,
|
|
107
|
+
)
|
|
108
|
+
if (!cornerSide)
|
|
109
|
+
for (const [rank, terminal] of targetOrdered.entries())
|
|
110
|
+
terminal.exitPoint.y = sourceBoundary.minY + padPitch + rank * viaPitch
|
|
111
|
+
const crossoverTrackByIndex = new Map(
|
|
112
|
+
targetOrdered.map((terminal) => [
|
|
113
|
+
terminal.connection.connectionIndex,
|
|
114
|
+
terminal.exitPoint.y,
|
|
115
|
+
]),
|
|
116
|
+
)
|
|
117
|
+
const maxPort = Math.min(
|
|
118
|
+
sourceBoundary.maxY - pitch,
|
|
119
|
+
Math.max(...terminals.map((t) => t.viaPoint.y)) + 2 * padPitch,
|
|
120
|
+
)
|
|
121
|
+
const minPort = Math.max(
|
|
122
|
+
sourceBoundary.minY + pitch,
|
|
123
|
+
Math.min(...terminals.map((t) => t.viaPoint.y)) - 2 * padPitch,
|
|
124
|
+
...(cornerSide
|
|
125
|
+
? []
|
|
126
|
+
: [
|
|
127
|
+
Math.max(...crossoverTrackByIndex.values()) +
|
|
128
|
+
params.viaDiameter / 2 +
|
|
129
|
+
w / 2 +
|
|
130
|
+
c +
|
|
131
|
+
pitch,
|
|
132
|
+
]),
|
|
133
|
+
)
|
|
134
|
+
if (
|
|
135
|
+
maxPort - minPort < (terminals.length - 1) * viaPitch ||
|
|
136
|
+
minPort <=
|
|
137
|
+
Math.max(...crossoverTrackByIndex.values()) +
|
|
138
|
+
params.viaDiameter / 2 +
|
|
139
|
+
w / 2 +
|
|
140
|
+
c
|
|
141
|
+
)
|
|
142
|
+
return null
|
|
143
|
+
const angle = (i: number) => {
|
|
144
|
+
const p = terminals[i]!.viaPoint
|
|
145
|
+
const a = Math.atan2(p.y - center.y, p.x - center.x)
|
|
146
|
+
return a < 0 ? a + 2 * Math.PI : a
|
|
147
|
+
}
|
|
148
|
+
const order = terminals.map((_, i) => i).sort((a, b) => angle(a) - angle(b))
|
|
149
|
+
const portByIndex = new Map(
|
|
150
|
+
order.map((index, rank) => [
|
|
151
|
+
index,
|
|
152
|
+
{
|
|
153
|
+
x: sourceBoundary.minX,
|
|
154
|
+
y: maxPort - ((maxPort - minPort) * rank) / (terminals.length - 1),
|
|
155
|
+
},
|
|
156
|
+
]),
|
|
157
|
+
)
|
|
158
|
+
const stageBoundary = {
|
|
159
|
+
minX: sourceBoundary.minX,
|
|
160
|
+
maxX: center.x,
|
|
161
|
+
minY: Math.min(
|
|
162
|
+
minPort - padPitch,
|
|
163
|
+
Math.min(...terminals.map((terminal) => terminal.viaPoint.y)) -
|
|
164
|
+
2 * padPitch,
|
|
165
|
+
),
|
|
166
|
+
maxY: sourceBoundary.maxY,
|
|
167
|
+
}
|
|
168
|
+
const sourcePaths = new Map(
|
|
169
|
+
sourceEscapes.map((source) => [
|
|
170
|
+
source.connectionIndex,
|
|
171
|
+
[source.segments[0]!.start, ...source.segments.map((s) => s.end)],
|
|
172
|
+
]),
|
|
173
|
+
)
|
|
174
|
+
const ownIndices = new Set(terminals.map((t) => t.connection.connectionIndex))
|
|
175
|
+
const reservedVias = sourceEscapes
|
|
176
|
+
.filter((source) => !ownIndices.has(source.connectionIndex))
|
|
177
|
+
.map((source) => ({
|
|
178
|
+
connectionName: source.connectionName,
|
|
179
|
+
via: source.via,
|
|
180
|
+
}))
|
|
181
|
+
let stage: FanoutRoutePlan[] | undefined
|
|
182
|
+
for (const laneBias of [0, -1, 1] as const) {
|
|
183
|
+
const alternatives = yield* routeViaMinimalWindingAlternativesSteps(
|
|
184
|
+
{
|
|
185
|
+
...params,
|
|
186
|
+
bus: {
|
|
187
|
+
...bus,
|
|
188
|
+
exitEdge: "left",
|
|
189
|
+
direction: "left",
|
|
190
|
+
preferredExit: undefined,
|
|
191
|
+
sharedBoundary: stageBoundary,
|
|
192
|
+
},
|
|
193
|
+
terminals: terminals.map((t, i) => ({
|
|
194
|
+
...t,
|
|
195
|
+
exitPoint: portByIndex.get(i)!,
|
|
196
|
+
})),
|
|
197
|
+
sourceEscapePaths: sourcePaths,
|
|
198
|
+
reservedVias,
|
|
199
|
+
gridStep: pitch / 4,
|
|
200
|
+
gridStepDivisor: 2,
|
|
201
|
+
alignGridToPads: true,
|
|
202
|
+
maximumRouteOrderAttempts: 1,
|
|
203
|
+
routeOrder: order,
|
|
204
|
+
laneBias,
|
|
205
|
+
},
|
|
206
|
+
1,
|
|
207
|
+
false,
|
|
208
|
+
)
|
|
209
|
+
if (alternatives.length) {
|
|
210
|
+
stage = alternatives[0]
|
|
211
|
+
break
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (!stage) return null
|
|
215
|
+
const sorted = stage.toSorted((a, b) =>
|
|
216
|
+
cornerSide
|
|
217
|
+
? a.exitPoint.y - b.exitPoint.y
|
|
218
|
+
: terminals.find(
|
|
219
|
+
(t) => t.connection.connectionIndex === a.connectionIndex,
|
|
220
|
+
)!.exitPoint.y -
|
|
221
|
+
terminals.find(
|
|
222
|
+
(t) => t.connection.connectionIndex === b.connectionIndex,
|
|
223
|
+
)!.exitPoint.y,
|
|
224
|
+
)
|
|
225
|
+
const via = (
|
|
226
|
+
point: Point2D,
|
|
227
|
+
fromLayer: string,
|
|
228
|
+
toLayer: string,
|
|
229
|
+
): RoutedVia => ({
|
|
230
|
+
center: point,
|
|
231
|
+
diameter: params.viaDiameter,
|
|
232
|
+
holeDiameter: params.viaHoleDiameter,
|
|
233
|
+
fromLayer,
|
|
234
|
+
toLayer,
|
|
235
|
+
spanLayers: getViaSpanLayers({
|
|
236
|
+
fromLayer,
|
|
237
|
+
toLayer,
|
|
238
|
+
layerNames: params.layerNames,
|
|
239
|
+
allowBlindAndBuriedVias: false,
|
|
240
|
+
}),
|
|
241
|
+
})
|
|
242
|
+
const prefixByIndex = new Map<number, FanoutRoutePlan>()
|
|
243
|
+
for (const [rank, plan] of sorted.entries()) {
|
|
244
|
+
const last = plan.segments.at(-1)!,
|
|
245
|
+
dx = last.end.x - last.start.x,
|
|
246
|
+
dy = last.end.y - last.start.y
|
|
247
|
+
if (dx >= 0 || Math.abs(dy) > -dx + 1e-7) return null
|
|
248
|
+
const original = terminals.find(
|
|
249
|
+
(t) => t.connection.connectionIndex === plan.connectionIndex,
|
|
250
|
+
)!
|
|
251
|
+
const column = firstColumn + rank * viaPitch,
|
|
252
|
+
first = { x: column, y: plan.exitPoint.y },
|
|
253
|
+
second = {
|
|
254
|
+
x: column,
|
|
255
|
+
y: crossoverTrackByIndex.get(plan.connectionIndex)!,
|
|
256
|
+
}
|
|
257
|
+
const additions: RoutedSegment[] = [
|
|
258
|
+
{ start: plan.exitPoint, end: first, width: w, layer: targetLayer },
|
|
259
|
+
{ start: first, end: second, width: w, layer: crossoverLayer },
|
|
260
|
+
]
|
|
261
|
+
prefixByIndex.set(plan.connectionIndex, {
|
|
262
|
+
...plan,
|
|
263
|
+
segments: [...plan.segments, ...additions],
|
|
264
|
+
additionalVias: [
|
|
265
|
+
via(first, targetLayer, crossoverLayer),
|
|
266
|
+
via(second, crossoverLayer, targetLayer),
|
|
267
|
+
],
|
|
268
|
+
direction: bus.direction,
|
|
269
|
+
exitEdge: bus.exitEdge,
|
|
270
|
+
cornerBandSide: getCornerBandSide(bus.exitEdge, bus.preferredExit),
|
|
271
|
+
exitPoint: original.exitPoint,
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
const prefixes = [...prefixByIndex.values()]
|
|
275
|
+
const continuationConnections = terminals.map((t) => ({
|
|
276
|
+
...t.connection,
|
|
277
|
+
sourcePoint: {
|
|
278
|
+
...prefixByIndex.get(t.connection.connectionIndex)!.additionalVias![1]!
|
|
279
|
+
.center,
|
|
280
|
+
layer: targetLayer,
|
|
281
|
+
},
|
|
282
|
+
sourceLayer: targetLayer,
|
|
283
|
+
}))
|
|
284
|
+
const continuations = yield* routeViaMinimalWindingAlternativesSteps(
|
|
285
|
+
{
|
|
286
|
+
...params,
|
|
287
|
+
bus: { ...bus, connections: continuationConnections },
|
|
288
|
+
terminals: continuationConnections.map((connection, i) => ({
|
|
289
|
+
connection,
|
|
290
|
+
viaPoint: connection.sourcePoint,
|
|
291
|
+
exitPoint: terminals[i]!.exitPoint,
|
|
292
|
+
})),
|
|
293
|
+
acceptedPlans: [...params.acceptedPlans, ...prefixes],
|
|
294
|
+
reservedVias: [
|
|
295
|
+
...sourceEscapes.map((source) => ({
|
|
296
|
+
connectionName: source.connectionName,
|
|
297
|
+
via: source.via,
|
|
298
|
+
})),
|
|
299
|
+
...prefixes.flatMap((plan) =>
|
|
300
|
+
plan.additionalVias!.map((via) => ({
|
|
301
|
+
connectionName: plan.connectionName,
|
|
302
|
+
via,
|
|
303
|
+
})),
|
|
304
|
+
),
|
|
305
|
+
],
|
|
306
|
+
allowSourceLayerRouting: true,
|
|
307
|
+
sourceEscapePaths: undefined,
|
|
308
|
+
gridStep: pitch / 2,
|
|
309
|
+
gridStepDivisor: 2,
|
|
310
|
+
alignGridToPads: true,
|
|
311
|
+
maximumRouteOrderAttempts: 12,
|
|
312
|
+
},
|
|
313
|
+
1,
|
|
314
|
+
false,
|
|
315
|
+
)
|
|
316
|
+
if (!continuations.length) return null
|
|
317
|
+
const plans = stage.map((original) => {
|
|
318
|
+
const prefix = prefixByIndex.get(original.connectionIndex)!,
|
|
319
|
+
tail = continuations[0]!.find(
|
|
320
|
+
(plan) => plan.connectionIndex === original.connectionIndex,
|
|
321
|
+
)!,
|
|
322
|
+
[first, second] = prefix.additionalVias!
|
|
323
|
+
const wire = (point: Point2D, layer: string) => ({
|
|
324
|
+
route_type: "wire" as const,
|
|
325
|
+
...point,
|
|
326
|
+
width: w,
|
|
327
|
+
layer,
|
|
328
|
+
})
|
|
329
|
+
const viaPoint = (via: RoutedVia) => ({
|
|
330
|
+
route_type: "via" as const,
|
|
331
|
+
...via.center,
|
|
332
|
+
from_layer: via.fromLayer,
|
|
333
|
+
to_layer: via.toLayer,
|
|
334
|
+
via_diameter: via.diameter,
|
|
335
|
+
via_hole_diameter: via.holeDiameter,
|
|
336
|
+
})
|
|
337
|
+
const segments = [...prefix.segments, ...tail.segments]
|
|
338
|
+
return {
|
|
339
|
+
...prefix,
|
|
340
|
+
segments,
|
|
341
|
+
trace: {
|
|
342
|
+
...original.trace,
|
|
343
|
+
route: [
|
|
344
|
+
...original.trace.route,
|
|
345
|
+
wire(first!.center, targetLayer),
|
|
346
|
+
viaPoint(first!),
|
|
347
|
+
wire(first!.center, crossoverLayer),
|
|
348
|
+
wire(second!.center, crossoverLayer),
|
|
349
|
+
viaPoint(second!),
|
|
350
|
+
...tail.trace.route,
|
|
351
|
+
],
|
|
352
|
+
},
|
|
353
|
+
length: segments.reduce((sum, s) => sum + distance(s.start, s.end), 0),
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
if (
|
|
357
|
+
!fanoutPlansAreClear({
|
|
358
|
+
...params,
|
|
359
|
+
plans: [...params.acceptedPlans, ...plans],
|
|
360
|
+
sharedBoundary: bus.sharedBoundary,
|
|
361
|
+
})
|
|
362
|
+
)
|
|
363
|
+
return null
|
|
364
|
+
for (const plan of plans)
|
|
365
|
+
for (const source of sourceEscapes) {
|
|
366
|
+
if (plan.connectionIndex === source.connectionIndex) continue
|
|
367
|
+
for (const s of plan.segments) {
|
|
368
|
+
if (
|
|
369
|
+
source.via.spanLayers.includes(s.layer) &&
|
|
370
|
+
distancePointToSegment(source.via.center, s.start, s.end) <
|
|
371
|
+
source.via.diameter / 2 + w / 2 + c - 1e-7
|
|
372
|
+
)
|
|
373
|
+
return null
|
|
374
|
+
for (const other of source.segments)
|
|
375
|
+
if (
|
|
376
|
+
other.layer === s.layer &&
|
|
377
|
+
distanceSegmentToSegment(s.start, s.end, other.start, other.end) <
|
|
378
|
+
w + c - 1e-7
|
|
379
|
+
)
|
|
380
|
+
return null
|
|
381
|
+
}
|
|
382
|
+
for (const added of plan.additionalVias!) {
|
|
383
|
+
if (
|
|
384
|
+
distance(added.center, source.via.center) <
|
|
385
|
+
params.viaDiameter + c - 1e-7
|
|
386
|
+
)
|
|
387
|
+
return null
|
|
388
|
+
for (const s of source.segments)
|
|
389
|
+
if (
|
|
390
|
+
added.spanLayers.includes(s.layer) &&
|
|
391
|
+
distancePointToSegment(added.center, s.start, s.end) <
|
|
392
|
+
params.viaDiameter / 2 + w / 2 + c - 1e-7
|
|
393
|
+
)
|
|
394
|
+
return null
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// The caller length-matches all complete buses before final validation.
|
|
398
|
+
return plans
|
|
399
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { reflectFanoutX } from "./reflect-fanout-x"
|
|
2
|
+
import { routeBottomCrossbarBusSteps } from "./route-bottom-crossbar-bus"
|
|
3
|
+
import type { RouteBusParams } from "./route-bus"
|
|
4
|
+
import type { PeripheralSourceEscape } from "./route-peripheral-source-escapes"
|
|
5
|
+
import type { RouteViaMinimalWindingProgress } from "./route-via-minimal-winding"
|
|
6
|
+
import type { Bounds, FanoutRoutePlan } from "./types"
|
|
7
|
+
|
|
8
|
+
/** Use the annulus opposite the requested edge when its nearer source exits are blocked. */
|
|
9
|
+
export function* routeOppositeBottomCrossbarBusSteps(
|
|
10
|
+
params: RouteBusParams & {
|
|
11
|
+
sourceEscapes: readonly PeripheralSourceEscape[]
|
|
12
|
+
sourceBoundary: Bounds
|
|
13
|
+
},
|
|
14
|
+
): Generator<RouteViaMinimalWindingProgress, FanoutRoutePlan[] | null, void> {
|
|
15
|
+
if (params.bus.exitEdge !== "right") return null
|
|
16
|
+
const mirrored = reflectFanoutX(params)
|
|
17
|
+
const padPitch = Math.min(params.bus.pitchX, params.bus.pitchY)
|
|
18
|
+
const pitch = params.traceWidth + params.clearance
|
|
19
|
+
const portPitch =
|
|
20
|
+
Math.ceil((params.viaDiameter + params.clearance) / pitch) * pitch
|
|
21
|
+
const maximumPortShift = Math.max(
|
|
22
|
+
0,
|
|
23
|
+
Math.min(
|
|
24
|
+
8,
|
|
25
|
+
Math.floor(
|
|
26
|
+
((mirrored.sourceBoundary.maxX - mirrored.sourceBoundary.minX) / 2 -
|
|
27
|
+
params.bus.connections.length * portPitch -
|
|
28
|
+
pitch) /
|
|
29
|
+
padPitch,
|
|
30
|
+
),
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
const layouts = [
|
|
34
|
+
undefined,
|
|
35
|
+
{ sourcePortOffset: -padPitch, rowOffset: 0, compactRows: false },
|
|
36
|
+
...[0, 1, 2, 3].map((row) => ({
|
|
37
|
+
sourcePortOffset: -padPitch,
|
|
38
|
+
rowOffset: row * pitch,
|
|
39
|
+
compactRows: true,
|
|
40
|
+
})),
|
|
41
|
+
// Earlier crossbars can occupy the central source ports. Try bounded
|
|
42
|
+
// outward windows that still fit every source port inside the same border.
|
|
43
|
+
...Array.from(
|
|
44
|
+
{ length: maximumPortShift },
|
|
45
|
+
(_, index) => maximumPortShift - index,
|
|
46
|
+
).flatMap((shift) =>
|
|
47
|
+
[0, 1, 2, 3].map((row) => ({
|
|
48
|
+
sourcePortOffset: shift * padPitch,
|
|
49
|
+
rowOffset: row * pitch,
|
|
50
|
+
compactRows: true,
|
|
51
|
+
})),
|
|
52
|
+
),
|
|
53
|
+
]
|
|
54
|
+
let plans: FanoutRoutePlan[] | null = null
|
|
55
|
+
for (const oppositeLayout of layouts) {
|
|
56
|
+
plans = yield* routeBottomCrossbarBusSteps({ ...mirrored, oppositeLayout })
|
|
57
|
+
if (plans) break
|
|
58
|
+
}
|
|
59
|
+
if (!plans) return null
|
|
60
|
+
const byIndex = new Map(
|
|
61
|
+
params.bus.connections.map((connection) => [
|
|
62
|
+
connection.connectionIndex,
|
|
63
|
+
connection,
|
|
64
|
+
]),
|
|
65
|
+
)
|
|
66
|
+
return reflectFanoutX(plans).map((plan) => ({
|
|
67
|
+
...plan,
|
|
68
|
+
sourceObstacle: byIndex.get(plan.connectionIndex)!.sourceObstacle,
|
|
69
|
+
}))
|
|
70
|
+
}
|