@tscircuit/fanout-solver 0.0.35 → 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/README.md +14 -0
- package/lib/boundary-exit.ts +52 -0
- package/lib/build-output.ts +32 -10
- package/lib/fanout-exit-position.ts +78 -0
- package/lib/fanout-solver.ts +267 -38
- package/lib/index.ts +16 -13
- package/lib/prepare-buses.ts +261 -36
- package/lib/route-bus.ts +543 -59
- package/lib/route-single-layer-adaptive-exits.ts +2 -2
- package/lib/route-single-layer-push-shove.ts +2 -1
- package/lib/route-via-minimal-winding.ts +898 -0
- package/lib/types.ts +63 -3
- package/lib/validate-fanout-solution.ts +63 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,6 +29,10 @@ and treats each bus-layer decision atomically.
|
|
|
29
29
|
(`left`, `right`, `top`, or `bottom`) or corner (`top-left`, `top-right`,
|
|
30
30
|
`bottom-left`, or `bottom-right`). A corner chooses a compatible adjacent
|
|
31
31
|
edge and reserves the bus at that end of the border.
|
|
32
|
+
- Accepts an unambiguous `exitPosition` bus field when the local pad escape and
|
|
33
|
+
final boundary edge differ. For example, `rightside_top` escapes locally
|
|
34
|
+
upward into the upper band and terminates on the right boundary, while
|
|
35
|
+
`topside_right` escapes locally rightward and terminates on the top boundary.
|
|
32
36
|
- `availableCornersAndSides` can restrict every boundary-terminated bus to
|
|
33
37
|
named regions of the shared boundary. For example,
|
|
34
38
|
`['top_left', 'top_middle', 'top_right']` allows only top-edge exits;
|
|
@@ -161,6 +165,16 @@ const autorouter = new CapacityMeshSolver(
|
|
|
161
165
|
autorouter.solve()
|
|
162
166
|
```
|
|
163
167
|
|
|
168
|
+
Canonical exit positions are edge-first: `topside_left`, `topside_center`,
|
|
169
|
+
`topside_right`, `rightside_top`, `rightside_center`, `rightside_bottom`,
|
|
170
|
+
`bottomside_right`, `bottomside_center`, `bottomside_left`, `leftside_bottom`,
|
|
171
|
+
`leftside_center`, `leftside_top`, and `center`. They normalize atomically into
|
|
172
|
+
the local `direction`, boundary-band `preferredExit`, and physical `exitEdge`;
|
|
173
|
+
conflicting bus-level legacy fields are rejected. Existing buses that omit
|
|
174
|
+
`exitPosition` retain their previous behavior. Hosts can import
|
|
175
|
+
`getFanoutExitPositionConfig` to inspect the same normalized tuple without
|
|
176
|
+
duplicating this mapping.
|
|
177
|
+
|
|
164
178
|
The downstream callback is optional. It lets the application choose its
|
|
165
179
|
board-level router while keeping `@tscircuit/fanout-solver` free of a runtime
|
|
166
180
|
autorouter import. Returned traces are still accepted only after the fanout
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { FanoutBorderTarget, FanoutDirection, FanoutEdge } from "./types"
|
|
2
|
+
|
|
3
|
+
export function getExitEdgeForDirection(
|
|
4
|
+
direction: FanoutDirection,
|
|
5
|
+
): FanoutEdge {
|
|
6
|
+
switch (direction) {
|
|
7
|
+
case "left":
|
|
8
|
+
return "left"
|
|
9
|
+
case "right":
|
|
10
|
+
return "right"
|
|
11
|
+
case "up":
|
|
12
|
+
return "top"
|
|
13
|
+
case "down":
|
|
14
|
+
return "bottom"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getDirectionForExitEdge(exitEdge: FanoutEdge): FanoutDirection {
|
|
19
|
+
switch (exitEdge) {
|
|
20
|
+
case "left":
|
|
21
|
+
return "left"
|
|
22
|
+
case "right":
|
|
23
|
+
return "right"
|
|
24
|
+
case "top":
|
|
25
|
+
return "up"
|
|
26
|
+
case "bottom":
|
|
27
|
+
return "down"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function borderTargetIncludesEdge(
|
|
32
|
+
preferredExit: FanoutBorderTarget,
|
|
33
|
+
exitEdge: FanoutEdge,
|
|
34
|
+
): boolean {
|
|
35
|
+
return preferredExit === exitEdge || preferredExit.includes(exitEdge)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Returns the lower/left or upper/right band selected along an explicit edge.
|
|
40
|
+
* Edge-center targets do not select a corner band.
|
|
41
|
+
*/
|
|
42
|
+
export function getCornerBandSide(
|
|
43
|
+
exitEdge: FanoutEdge | undefined,
|
|
44
|
+
preferredExit: FanoutBorderTarget | undefined,
|
|
45
|
+
): "minimum" | "maximum" | undefined {
|
|
46
|
+
if (!exitEdge || !preferredExit?.includes("-")) return undefined
|
|
47
|
+
if (!borderTargetIncludesEdge(preferredExit, exitEdge)) return undefined
|
|
48
|
+
if (exitEdge === "left" || exitEdge === "right") {
|
|
49
|
+
return preferredExit.startsWith("top-") ? "maximum" : "minimum"
|
|
50
|
+
}
|
|
51
|
+
return preferredExit.endsWith("-right") ? "maximum" : "minimum"
|
|
52
|
+
}
|
package/lib/build-output.ts
CHANGED
|
@@ -5,10 +5,9 @@ import type { FanoutRoutePlan, SimpleRouteJsonWithFanoutPlanes } from "./types"
|
|
|
5
5
|
function createViaObstacle(
|
|
6
6
|
plan: FanoutRoutePlan,
|
|
7
7
|
layerNames: string[],
|
|
8
|
-
|
|
8
|
+
via: NonNullable<FanoutRoutePlan["via"]>,
|
|
9
|
+
viaIndex: number | "endpoint",
|
|
9
10
|
): Obstacle | null {
|
|
10
|
-
const via = endpoint ? plan.planeEndpointVia : plan.via
|
|
11
|
-
if (!via) return null
|
|
12
11
|
const zLayers = via.spanLayers.map((layer) => {
|
|
13
12
|
const layerIndex = layerNames.indexOf(layer)
|
|
14
13
|
if (layerIndex < 0) {
|
|
@@ -20,9 +19,12 @@ function createViaObstacle(
|
|
|
20
19
|
})
|
|
21
20
|
const outputIds = createFanoutOutputIds(plan)
|
|
22
21
|
return {
|
|
23
|
-
obstacleId:
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
obstacleId:
|
|
23
|
+
viaIndex === "endpoint"
|
|
24
|
+
? outputIds.planeEndpointViaObstacleId
|
|
25
|
+
: viaIndex === 0
|
|
26
|
+
? outputIds.viaObstacleId
|
|
27
|
+
: `${outputIds.viaObstacleId}:${viaIndex}`,
|
|
26
28
|
type: "rect",
|
|
27
29
|
center: via.center,
|
|
28
30
|
width: via.diameter,
|
|
@@ -67,10 +69,30 @@ export function buildOutputSimpleRouteJson(params: {
|
|
|
67
69
|
if (plan.termination.type === "plane") {
|
|
68
70
|
planeTerminatedConnectionNames.add(plan.connectionName)
|
|
69
71
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
if (plan.via) {
|
|
73
|
+
const viaObstacle = createViaObstacle(plan, layerNames, plan.via, 0)
|
|
74
|
+
if (viaObstacle) viaObstacles.push(viaObstacle)
|
|
75
|
+
}
|
|
76
|
+
for (const [additionalViaIndex, via] of (
|
|
77
|
+
plan.additionalVias ?? []
|
|
78
|
+
).entries()) {
|
|
79
|
+
const viaObstacle = createViaObstacle(
|
|
80
|
+
plan,
|
|
81
|
+
layerNames,
|
|
82
|
+
via,
|
|
83
|
+
additionalViaIndex + 1,
|
|
84
|
+
)
|
|
85
|
+
if (viaObstacle) viaObstacles.push(viaObstacle)
|
|
86
|
+
}
|
|
87
|
+
if (plan.planeEndpointVia) {
|
|
88
|
+
const endpointViaObstacle = createViaObstacle(
|
|
89
|
+
plan,
|
|
90
|
+
layerNames,
|
|
91
|
+
plan.planeEndpointVia,
|
|
92
|
+
"endpoint",
|
|
93
|
+
)
|
|
94
|
+
if (endpointViaObstacle) viaObstacles.push(endpointViaObstacle)
|
|
95
|
+
}
|
|
74
96
|
}
|
|
75
97
|
const planTraces = plans.flatMap((plan) => [
|
|
76
98
|
plan.trace,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { FanoutExitPosition, FanoutExitPositionConfig } from "./types"
|
|
2
|
+
|
|
3
|
+
const FANOUT_EXIT_POSITION_CONFIGS: Readonly<
|
|
4
|
+
Record<FanoutExitPosition, Readonly<FanoutExitPositionConfig>>
|
|
5
|
+
> = {
|
|
6
|
+
topside_left: {
|
|
7
|
+
direction: "left",
|
|
8
|
+
preferredExit: "top-left",
|
|
9
|
+
exitEdge: "top",
|
|
10
|
+
},
|
|
11
|
+
topside_center: {
|
|
12
|
+
direction: "up",
|
|
13
|
+
preferredExit: "top",
|
|
14
|
+
exitEdge: "top",
|
|
15
|
+
},
|
|
16
|
+
topside_right: {
|
|
17
|
+
direction: "right",
|
|
18
|
+
preferredExit: "top-right",
|
|
19
|
+
exitEdge: "top",
|
|
20
|
+
},
|
|
21
|
+
rightside_top: {
|
|
22
|
+
direction: "up",
|
|
23
|
+
preferredExit: "top-right",
|
|
24
|
+
exitEdge: "right",
|
|
25
|
+
},
|
|
26
|
+
rightside_center: {
|
|
27
|
+
direction: "right",
|
|
28
|
+
preferredExit: "right",
|
|
29
|
+
exitEdge: "right",
|
|
30
|
+
},
|
|
31
|
+
rightside_bottom: {
|
|
32
|
+
direction: "down",
|
|
33
|
+
preferredExit: "bottom-right",
|
|
34
|
+
exitEdge: "right",
|
|
35
|
+
},
|
|
36
|
+
bottomside_right: {
|
|
37
|
+
direction: "right",
|
|
38
|
+
preferredExit: "bottom-right",
|
|
39
|
+
exitEdge: "bottom",
|
|
40
|
+
},
|
|
41
|
+
bottomside_center: {
|
|
42
|
+
direction: "down",
|
|
43
|
+
preferredExit: "bottom",
|
|
44
|
+
exitEdge: "bottom",
|
|
45
|
+
},
|
|
46
|
+
bottomside_left: {
|
|
47
|
+
direction: "left",
|
|
48
|
+
preferredExit: "bottom-left",
|
|
49
|
+
exitEdge: "bottom",
|
|
50
|
+
},
|
|
51
|
+
leftside_bottom: {
|
|
52
|
+
direction: "down",
|
|
53
|
+
preferredExit: "bottom-left",
|
|
54
|
+
exitEdge: "left",
|
|
55
|
+
},
|
|
56
|
+
leftside_center: {
|
|
57
|
+
direction: "left",
|
|
58
|
+
preferredExit: "left",
|
|
59
|
+
exitEdge: "left",
|
|
60
|
+
},
|
|
61
|
+
leftside_top: {
|
|
62
|
+
direction: "up",
|
|
63
|
+
preferredExit: "top-left",
|
|
64
|
+
exitEdge: "left",
|
|
65
|
+
},
|
|
66
|
+
center: {},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Resolves a canonical exit position into the solver's orthogonal fields. */
|
|
70
|
+
export function getFanoutExitPositionConfig(
|
|
71
|
+
exitPosition: FanoutExitPosition,
|
|
72
|
+
): Readonly<FanoutExitPositionConfig> {
|
|
73
|
+
const config = FANOUT_EXIT_POSITION_CONFIGS[exitPosition]
|
|
74
|
+
if (!config) {
|
|
75
|
+
throw new Error(`Invalid fanout exit position "${exitPosition}"`)
|
|
76
|
+
}
|
|
77
|
+
return config
|
|
78
|
+
}
|