@tscircuit/schematic-trace-solver 0.0.190 → 0.0.192
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/dist/index.d.ts +2 -0
- package/dist/index.js +437 -211
- package/lib/solvers/InlineNetLabelSolver/getLocalTraceLabelShifts.ts +29 -0
- package/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +7 -0
- package/lib/solvers/LongDistancePairSolver/getParallelNetLabelPolicy.ts +98 -0
- package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +24 -2
- package/lib/solvers/MspConnectionPairSolver/getParallelRailPairs.ts +178 -0
- package/lib/solvers/NetLabelToTraceSolver/NetLabelToTraceSolver.ts +13 -0
- package/lib/solvers/SameNetJunctionAlignmentSolver/collapseRedundantSharedEndpointStubs.ts +12 -6
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +837 -799
- package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +904 -866
- package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +533 -524
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260724T175257Z/__snapshots__/bug-report-20260724T175257Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260804T171919Z/__snapshots__/bug-report-20260804T171919Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260806T093501Z/__snapshots__/bug-report-20260806T093501Z.snap.svg +94 -94
- package/tests/bug-reports/bug-report-20260815T073240Z/__snapshots__/bug-report-20260815T073240Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260901T064117Z/bug-report-20260901T064117Z.test.ts +13 -2
- package/tests/bug-reports/bug-report-20260902T092425Z/__snapshots__/bug-report-20260902T092425Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260902T092425Z/bug-report-20260902T092425Z.test.ts +16 -2
- package/tests/bug-reports/bug-report-20260905T041712Z/__snapshots__/bug-report-20260905T041712Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260907T083336Z/__snapshots__/bug-report-20260907T083336Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260907T110144Z/__snapshots__/bug-report-20260907T110144Z.snap.svg +19 -58
- package/tests/bug-reports/bug-report-20260907T110144Z/bug-report-20260907T110144Z.test.ts +2 -2
- package/tests/bug-reports/bug-report-20260907T145640Z/__snapshots__/bug-report-20260907T145640Z.snap.svg +917 -933
- package/tests/examples/__snapshots__/example13.snap.svg +2 -2
- package/tests/examples/__snapshots__/example15.snap.svg +2 -2
- package/tests/examples/__snapshots__/example51.snap.svg +2 -2
- package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +2 -2
- package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-bq25895-cross-net-trace-overlap.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-esp12f-section.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-example35-minimize-trace-crossing.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-hub-usb-sheet.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-isolated-rs485-isow7841.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-powerbank-3v-system-power.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-repeated-power-rail-junctions.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-smartwatch-power-sheet.snap.svg +111 -75
- package/tests/repros/__snapshots__/repro-tida00553-j4-cell-divider.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-type-c-vbus-led-ground-label.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-usb-power-vbus-label-detour.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro161-power-section-autolayout.snap.svg +2 -2
- package/tests/repros/repro-trellis-core-decoupling-snake-traces.test.ts +66 -0
- package/tests/solvers/InlineNetLabelSolver/local-trace-label-shifts.test.ts +62 -0
- package/tests/solvers/LongDistancePairSolver/__snapshots__/decoupling-mixed-supplies.snap.svg +112 -0
- package/tests/solvers/LongDistancePairSolver/__snapshots__/horizontal-rails.snap.svg +79 -0
- package/tests/solvers/LongDistancePairSolver/__snapshots__/vertical-rails.snap.svg +86 -0
- package/tests/solvers/LongDistancePairSolver/parallel-decoupling-labels.test.ts +505 -0
- package/tests/solvers/SameNetJunctionAlignmentSolver/collapse-redundant-shared-endpoint-stubs.test.ts +45 -0
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
|
|
3
|
+
import { getTraceConnectedPinComponents } from "lib/solvers/SchematicTraceLinesSolver/getTraceConnectedPinComponents"
|
|
4
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
5
|
+
import "tests/fixtures/matcher"
|
|
6
|
+
|
|
7
|
+
const createBank = (): InputProblem => ({
|
|
8
|
+
chips: [0, 2, 4].map((x, index) => ({
|
|
9
|
+
chipId: `C${index + 1}`,
|
|
10
|
+
center: { x, y: 0 },
|
|
11
|
+
width: 0.6,
|
|
12
|
+
height: 0.8,
|
|
13
|
+
pins: [
|
|
14
|
+
{ pinId: `C${index + 1}.1`, x, y: 0.4 },
|
|
15
|
+
{ pinId: `C${index + 1}.2`, x, y: -0.4 },
|
|
16
|
+
],
|
|
17
|
+
})),
|
|
18
|
+
directConnections: [],
|
|
19
|
+
netConnections: [
|
|
20
|
+
{ netId: "VDD", pinIds: ["C1.1", "C2.1", "C3.1"] },
|
|
21
|
+
{ netId: "GND", isGround: true, pinIds: ["C1.2", "C2.2", "C3.2"] },
|
|
22
|
+
],
|
|
23
|
+
availableNetLabelOrientations: { VDD: ["y+"], GND: ["y-"] },
|
|
24
|
+
maxMspPairDistance: 1,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const solve = (input: InputProblem) => {
|
|
28
|
+
const solver = new SchematicTracePipelineSolver(input)
|
|
29
|
+
solver.solve()
|
|
30
|
+
expect(solver.solved).toBe(true)
|
|
31
|
+
expect(solver.failed).toBe(false)
|
|
32
|
+
return solver
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const rotated of [false, true]) {
|
|
36
|
+
test(`aligned parallel capacitors share rails (rotated=${rotated})`, async () => {
|
|
37
|
+
const input = createBank()
|
|
38
|
+
if (rotated) {
|
|
39
|
+
for (const chip of input.chips) {
|
|
40
|
+
chip.center = { x: chip.center.y, y: chip.center.x }
|
|
41
|
+
;[chip.width, chip.height] = [chip.height, chip.width]
|
|
42
|
+
for (const pin of chip.pins) [pin.x, pin.y] = [pin.y, pin.x]
|
|
43
|
+
}
|
|
44
|
+
input.availableNetLabelOrientations = { VDD: ["x+"], GND: ["x-"] }
|
|
45
|
+
}
|
|
46
|
+
// Rail detection should not depend on pin order or component metadata.
|
|
47
|
+
input.chips[1]!.pins.reverse()
|
|
48
|
+
const solver = solve(input)
|
|
49
|
+
expect(solver.longDistancePairSolver!.getOutput().newTraces).toEqual([])
|
|
50
|
+
const { traces, netLabelPlacements } =
|
|
51
|
+
solver.netLabelToTraceSolver!.getOutput()
|
|
52
|
+
expect(netLabelPlacements).toHaveLength(2)
|
|
53
|
+
expect(traces).toHaveLength(4)
|
|
54
|
+
for (const net of input.netConnections) {
|
|
55
|
+
const components = getTraceConnectedPinComponents({
|
|
56
|
+
pinIds: net.pinIds,
|
|
57
|
+
traces,
|
|
58
|
+
})
|
|
59
|
+
expect(components).toHaveLength(1)
|
|
60
|
+
// Every rail stays outside the component bodies on its own side.
|
|
61
|
+
const sign = net.netId === "GND" ? -1 : 1
|
|
62
|
+
for (const trace of components[0]!.traces) {
|
|
63
|
+
for (const point of trace.tracePath) {
|
|
64
|
+
expect(sign * point[rotated ? "x" : "y"]).toBeGreaterThanOrEqual(
|
|
65
|
+
0.4 - 1e-6,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
await expect(solver).toMatchSolverSnapshot(
|
|
71
|
+
import.meta.path,
|
|
72
|
+
rotated ? "vertical-rails" : "horizontal-rails",
|
|
73
|
+
)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
test("keeps shared rails within the configured routing distance", () => {
|
|
78
|
+
const input = createBank()
|
|
79
|
+
input.maxMspPairDistance = 2
|
|
80
|
+
const solver = solve(input)
|
|
81
|
+
expect(solver.mspConnectionPairSolver!.mspConnectionPairs).toHaveLength(4)
|
|
82
|
+
expect(
|
|
83
|
+
solver.netLabelToTraceSolver!.getOutput().netLabelPlacements,
|
|
84
|
+
).toHaveLength(2)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test("uniformly spaced capacitor rows retain shared rails at a wider pitch", () => {
|
|
88
|
+
const input = createBank()
|
|
89
|
+
for (const chip of input.chips) {
|
|
90
|
+
chip.center.x *= 3
|
|
91
|
+
for (const pin of chip.pins) pin.x *= 3
|
|
92
|
+
}
|
|
93
|
+
const solver = solve(input)
|
|
94
|
+
expect(solver.mspConnectionPairSolver!.mspConnectionPairs).toHaveLength(4)
|
|
95
|
+
expect(solver.longDistancePairSolver!.getOutput().newTraces).toEqual([])
|
|
96
|
+
const { traces, netLabelPlacements } =
|
|
97
|
+
solver.netLabelToTraceSolver!.getOutput()
|
|
98
|
+
expect(traces).toHaveLength(4)
|
|
99
|
+
expect(netLabelPlacements).toHaveLength(2)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test("preserves explicitly wired rails while labeling the independent ground pins", () => {
|
|
103
|
+
const input = createBank()
|
|
104
|
+
input.directConnections = [
|
|
105
|
+
{ pinIds: ["C1.1", "C2.1"] },
|
|
106
|
+
{ pinIds: ["C2.1", "C3.1"] },
|
|
107
|
+
]
|
|
108
|
+
const solver = solve(input)
|
|
109
|
+
const recovered = solver.longDistancePairSolver!.getOutput().newTraces
|
|
110
|
+
expect(recovered.length).toBeGreaterThan(0)
|
|
111
|
+
expect(
|
|
112
|
+
recovered.every((trace) => trace.pinIds.every((pin) => pin.endsWith(".1"))),
|
|
113
|
+
).toBe(true)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test("fallback labels allow shared rails without long-distance recovery", () => {
|
|
117
|
+
const input = createBank()
|
|
118
|
+
input.directConnections = [
|
|
119
|
+
{ pinIds: ["C1.1", "C2.1"], netLabelWidth: 0.5 },
|
|
120
|
+
{ pinIds: ["C2.1", "C3.1"], netLabelWidth: 0.5 },
|
|
121
|
+
]
|
|
122
|
+
const solver = solve(input)
|
|
123
|
+
expect(solver.longDistancePairSolver!.getOutput().newTraces).toEqual([])
|
|
124
|
+
expect(
|
|
125
|
+
solver.netLabelToTraceSolver!.getOutput().netLabelPlacements,
|
|
126
|
+
).toHaveLength(2)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test("recognizes a ground alias by its metadata and global connectivity", () => {
|
|
130
|
+
const input = createBank()
|
|
131
|
+
input.netConnections[1]!.netId = "return-net"
|
|
132
|
+
input.netConnections[1]!.isGround = true
|
|
133
|
+
input.netConnections.push({ netId: "alias", pinIds: ["C1.2"] })
|
|
134
|
+
input.availableNetLabelOrientations = { VDD: ["y+"], "return-net": ["y-"] }
|
|
135
|
+
const solver = solve(input)
|
|
136
|
+
expect(solver.longDistancePairSolver!.getOutput().newTraces).toEqual([])
|
|
137
|
+
expect(solver.mspConnectionPairSolver!.mspConnectionPairs).toHaveLength(4)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test("preserves long-distance recovery for unrelated signal connections", () => {
|
|
141
|
+
const input = createBank()
|
|
142
|
+
input.netConnections[1]!.netId = "SIGNAL"
|
|
143
|
+
input.netConnections[1]!.isGround = false
|
|
144
|
+
expect(
|
|
145
|
+
solve(input).longDistancePairSolver!.getOutput().newTraces.length,
|
|
146
|
+
).toBeGreaterThan(0)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test.each([undefined, false])(
|
|
150
|
+
"does not infer ground from the net name when isGround=%s",
|
|
151
|
+
(isGround) => {
|
|
152
|
+
const input = createBank()
|
|
153
|
+
input.netConnections[1]!.isGround = isGround
|
|
154
|
+
const solver = solve(input)
|
|
155
|
+
expect(solver.mspConnectionPairSolver!.mspConnectionPairs).toHaveLength(0)
|
|
156
|
+
expect(
|
|
157
|
+
solver.longDistancePairSolver!.getOutput().newTraces.length,
|
|
158
|
+
).toBeGreaterThan(0)
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
for (const { rotated, pitch } of [
|
|
163
|
+
{ rotated: false, pitch: 2 },
|
|
164
|
+
{ rotated: true, pitch: 2 },
|
|
165
|
+
{ rotated: false, pitch: 6 },
|
|
166
|
+
{ rotated: true, pitch: 6 },
|
|
167
|
+
]) {
|
|
168
|
+
test(`large gaps separate aligned capacitor banks (rotated=${rotated}, pitch=${pitch})`, () => {
|
|
169
|
+
const input = createBank()
|
|
170
|
+
input.chips = [0, 1, 4.5, 5.5].map((offset, index) => ({
|
|
171
|
+
...structuredClone(input.chips[0]!),
|
|
172
|
+
chipId: `C${index + 1}`,
|
|
173
|
+
center: rotated
|
|
174
|
+
? { x: 0, y: offset * pitch }
|
|
175
|
+
: { x: offset * pitch, y: 0 },
|
|
176
|
+
width: rotated ? 0.8 : 0.6,
|
|
177
|
+
height: rotated ? 0.6 : 0.8,
|
|
178
|
+
pins: [0.4, -0.4].map((pinOffset, pinIndex) => ({
|
|
179
|
+
pinId: `C${index + 1}.${pinIndex + 1}`,
|
|
180
|
+
x: rotated ? pinOffset : offset * pitch,
|
|
181
|
+
y: rotated ? offset * pitch : pinOffset,
|
|
182
|
+
})),
|
|
183
|
+
}))
|
|
184
|
+
input.netConnections[0]!.isGround = false
|
|
185
|
+
for (const [index, net] of input.netConnections.entries()) {
|
|
186
|
+
net.pinIds = input.chips.map((chip) => chip.pins[index]!.pinId)
|
|
187
|
+
}
|
|
188
|
+
if (rotated) {
|
|
189
|
+
input.availableNetLabelOrientations = { VDD: ["x+"], GND: ["x-"] }
|
|
190
|
+
}
|
|
191
|
+
const solver = solve(input)
|
|
192
|
+
expect(solver.mspConnectionPairSolver!.mspConnectionPairs).toHaveLength(4)
|
|
193
|
+
expect(solver.longDistancePairSolver!.getOutput().newTraces).toEqual([])
|
|
194
|
+
const { traces, netLabelPlacements } =
|
|
195
|
+
solver.netLabelToTraceSolver!.getOutput()
|
|
196
|
+
expect(traces).toHaveLength(4)
|
|
197
|
+
expect(netLabelPlacements).toHaveLength(4)
|
|
198
|
+
for (const [index, net] of input.netConnections.entries()) {
|
|
199
|
+
const components = getTraceConnectedPinComponents({
|
|
200
|
+
pinIds: net.pinIds,
|
|
201
|
+
traces,
|
|
202
|
+
})
|
|
203
|
+
expect(components.map((component) => component.pinIds.sort())).toEqual([
|
|
204
|
+
[`C1.${index + 1}`, `C2.${index + 1}`],
|
|
205
|
+
[`C3.${index + 1}`, `C4.${index + 1}`],
|
|
206
|
+
])
|
|
207
|
+
for (const component of components) {
|
|
208
|
+
expect(
|
|
209
|
+
netLabelPlacements.filter((label) =>
|
|
210
|
+
label.pinIds.some((pinId) => component.pinIds.includes(pinId)),
|
|
211
|
+
),
|
|
212
|
+
).toHaveLength(1)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
test("two isolated capacitors do not form a rail across a large gap", () => {
|
|
219
|
+
const input = createBank()
|
|
220
|
+
input.chips.splice(1, 1)
|
|
221
|
+
for (const net of input.netConnections) net.pinIds.splice(1, 1)
|
|
222
|
+
const solver = solve(input)
|
|
223
|
+
expect(solver.mspConnectionPairSolver!.mspConnectionPairs).toHaveLength(0)
|
|
224
|
+
expect(solver.longDistancePairSolver!.getOutput().newTraces).toEqual([])
|
|
225
|
+
const { traces, netLabelPlacements } =
|
|
226
|
+
solver.netLabelToTraceSolver!.getOutput()
|
|
227
|
+
expect(traces).toHaveLength(0)
|
|
228
|
+
expect(netLabelPlacements).toHaveLength(4)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
test.each([4, 7])(
|
|
232
|
+
"rail separation survives ground recovery across a gap of %d",
|
|
233
|
+
(gap) => {
|
|
234
|
+
const input = createBank()
|
|
235
|
+
input.chips.splice(1, 1)
|
|
236
|
+
input.chips[1]!.center.x = gap
|
|
237
|
+
for (const pin of input.chips[1]!.pins) pin.x = gap
|
|
238
|
+
input.chips.push({
|
|
239
|
+
chipId: "U1",
|
|
240
|
+
center: { x: gap / 2, y: -4 },
|
|
241
|
+
width: 1,
|
|
242
|
+
height: 1,
|
|
243
|
+
pins: [{ pinId: "U1.GND", x: gap / 2, y: -4.5 }],
|
|
244
|
+
})
|
|
245
|
+
input.netConnections = [
|
|
246
|
+
{ netId: "VDD1", isGround: false, pinIds: ["C1.1"] },
|
|
247
|
+
{ netId: "VDD2", isGround: false, pinIds: ["C3.1"] },
|
|
248
|
+
{ netId: "GND", isGround: true, pinIds: ["C1.2", "C3.2", "U1.GND"] },
|
|
249
|
+
]
|
|
250
|
+
input.availableNetLabelOrientations = {
|
|
251
|
+
VDD1: ["y+"],
|
|
252
|
+
VDD2: ["y+"],
|
|
253
|
+
GND: ["y-"],
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const solver = solve(input)
|
|
257
|
+
expect(solver.mspConnectionPairSolver!.mspConnectionPairs).toHaveLength(0)
|
|
258
|
+
expect(solver.longDistancePairSolver!.getOutput().newTraces).toEqual([])
|
|
259
|
+
const { traces, netLabelPlacements } =
|
|
260
|
+
solver.netLabelToTraceSolver!.getOutput()
|
|
261
|
+
expect(traces).toHaveLength(0)
|
|
262
|
+
expect(netLabelPlacements).toHaveLength(5)
|
|
263
|
+
},
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
test("keeps distant net-only loads local even when they are staggered", () => {
|
|
267
|
+
const input = createBank()
|
|
268
|
+
for (const [index, chip] of input.chips.entries()) {
|
|
269
|
+
chip.center.y += index * 2
|
|
270
|
+
for (const pin of chip.pins) pin.y += index * 2
|
|
271
|
+
}
|
|
272
|
+
expect(solve(input).longDistancePairSolver!.getOutput().newTraces).toEqual([])
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
test("different-size components do not bypass the net connection distance", () => {
|
|
276
|
+
const input = createBank()
|
|
277
|
+
for (const [index, chip] of input.chips.entries()) {
|
|
278
|
+
chip.height += index * 0.2
|
|
279
|
+
chip.pins[0]!.y += index * 0.1
|
|
280
|
+
chip.pins[1]!.y -= index * 0.1
|
|
281
|
+
}
|
|
282
|
+
const solver = solve(input)
|
|
283
|
+
expect(solver.longDistancePairSolver!.getOutput().newTraces).toEqual([])
|
|
284
|
+
expect(
|
|
285
|
+
solver.netLabelToTraceSolver!.getOutput().netLabelPlacements,
|
|
286
|
+
).toHaveLength(6)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
test("parallel branch labeling does not depend on optional symbol metadata", () => {
|
|
290
|
+
const input = createBank()
|
|
291
|
+
for (const chip of input.chips) chip.symbolName = "resistor"
|
|
292
|
+
const solver = solve(input)
|
|
293
|
+
expect(solver.longDistancePairSolver!.getOutput().newTraces).toEqual([])
|
|
294
|
+
expect(
|
|
295
|
+
solver.netLabelToTraceSolver!.getOutput().netLabelPlacements,
|
|
296
|
+
).toHaveLength(6)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
test("recognizes parallel banks across named-net aliases", () => {
|
|
300
|
+
const input = createBank()
|
|
301
|
+
input.netConnections = [
|
|
302
|
+
{ netId: "VDD", pinIds: ["C1.1", "C2.1"] },
|
|
303
|
+
{ netId: "VDD_ALIAS", pinIds: ["C2.1", "C3.1"] },
|
|
304
|
+
input.netConnections[1]!,
|
|
305
|
+
]
|
|
306
|
+
expect(solve(input).longDistancePairSolver!.getOutput().newTraces).toEqual([])
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
test("final label-to-trace recovery preserves shared bank rails", () => {
|
|
310
|
+
const input = createBank()
|
|
311
|
+
input.netConnections[0]!.isGround = false
|
|
312
|
+
input.netConnections[1]!.isGround = true
|
|
313
|
+
const output = solve(input).netLabelToTraceSolver!.getOutput()
|
|
314
|
+
expect(output.traces.filter((trace) => trace.pinIds.length > 1)).toHaveLength(
|
|
315
|
+
4,
|
|
316
|
+
)
|
|
317
|
+
expect(output.netLabelPlacements).toHaveLength(2)
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
test("an explicit bank wire does not extend to other branches", () => {
|
|
321
|
+
const input = createBank()
|
|
322
|
+
input.directConnections = [{ pinIds: ["C1.1", "C2.1"] }]
|
|
323
|
+
const traces = solve(input).longDistancePairSolver!.getOutput().newTraces
|
|
324
|
+
expect(traces.map((trace) => [...trace.pinIds].sort())).toEqual([
|
|
325
|
+
["C1.1", "C2.1"],
|
|
326
|
+
])
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
test("different supplies share only the ground rail", () => {
|
|
330
|
+
const input = createBank()
|
|
331
|
+
input.netConnections = [
|
|
332
|
+
{ netId: "VDD1", pinIds: ["C1.1"] },
|
|
333
|
+
{ netId: "VDD2", pinIds: ["C2.1"] },
|
|
334
|
+
{ netId: "VDD3", pinIds: ["C3.1"] },
|
|
335
|
+
input.netConnections[1]!,
|
|
336
|
+
]
|
|
337
|
+
const output = solve(input).netLabelToTraceSolver!.getOutput()
|
|
338
|
+
expect(output.traces).toHaveLength(2)
|
|
339
|
+
expect(
|
|
340
|
+
output.traces.every((trace) =>
|
|
341
|
+
trace.pinIds.every((pinId) => pinId.endsWith(".2")),
|
|
342
|
+
),
|
|
343
|
+
).toBe(true)
|
|
344
|
+
expect(output.netLabelPlacements).toHaveLength(4)
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
test("loads connected through source wires retain their existing recovery", () => {
|
|
348
|
+
const input = createBank()
|
|
349
|
+
input.netConnections[0]!.pinIds = ["C1.1"]
|
|
350
|
+
input.directConnections = [
|
|
351
|
+
{ pinIds: ["C1.1", "C2.1"] },
|
|
352
|
+
{ pinIds: ["C2.1", "C3.1"] },
|
|
353
|
+
]
|
|
354
|
+
const traces = solve(input).longDistancePairSolver!.getOutput().newTraces
|
|
355
|
+
expect(
|
|
356
|
+
traces.some((trace) => trace.pinIds.every((pinId) => pinId.endsWith(".2"))),
|
|
357
|
+
).toBe(true)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
test("two aligned capacitors can share both rails beyond the local distance", () => {
|
|
361
|
+
const input = createBank()
|
|
362
|
+
input.chips.pop()
|
|
363
|
+
for (const net of input.netConnections) net.pinIds.pop()
|
|
364
|
+
const solver = solve(input)
|
|
365
|
+
expect(solver.mspConnectionPairSolver!.mspConnectionPairs).toHaveLength(2)
|
|
366
|
+
expect(
|
|
367
|
+
solver.netLabelToTraceSolver!.getOutput().netLabelPlacements,
|
|
368
|
+
).toHaveLength(2)
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
test("an intervening component breaks the shared rail", () => {
|
|
372
|
+
const input = createBank()
|
|
373
|
+
input.chips.push({
|
|
374
|
+
chipId: "barrier",
|
|
375
|
+
center: { x: 1, y: 0.6 },
|
|
376
|
+
width: 0.2,
|
|
377
|
+
height: 0.8,
|
|
378
|
+
pins: [],
|
|
379
|
+
})
|
|
380
|
+
const output = solve(input).netLabelToTraceSolver!.getOutput()
|
|
381
|
+
expect(output.traces).toHaveLength(2)
|
|
382
|
+
expect(
|
|
383
|
+
output.traces.every(
|
|
384
|
+
(trace) => !trace.pinIds.some((pinId) => pinId.startsWith("C1.")),
|
|
385
|
+
),
|
|
386
|
+
).toBe(true)
|
|
387
|
+
expect(
|
|
388
|
+
output.netLabelPlacements.filter((label) =>
|
|
389
|
+
label.pinIds.some((pinId) => pinId.startsWith("C1.")),
|
|
390
|
+
),
|
|
391
|
+
).toHaveLength(2)
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
test("shared rails stay within their schematic section", () => {
|
|
395
|
+
const input = createBank()
|
|
396
|
+
input.chips[2]!.sectionId = "separate"
|
|
397
|
+
const output = solve(input).netLabelToTraceSolver!.getOutput()
|
|
398
|
+
expect(output.traces).toHaveLength(2)
|
|
399
|
+
expect(
|
|
400
|
+
output.traces.every(
|
|
401
|
+
(trace) => !trace.pinIds.some((pinId) => pinId.startsWith("C3.")),
|
|
402
|
+
),
|
|
403
|
+
).toBe(true)
|
|
404
|
+
expect(output.netLabelPlacements).toHaveLength(4)
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
test("a common ground rail does not recover wires across interleaved supplies", () => {
|
|
408
|
+
const input = createBank()
|
|
409
|
+
input.chips = [0, 2, 4, 6, 8, 10].map((x, index) => ({
|
|
410
|
+
chipId: `C${index + 1}`,
|
|
411
|
+
center: { x, y: 0 },
|
|
412
|
+
width: 0.6,
|
|
413
|
+
height: 0.8,
|
|
414
|
+
pins: [
|
|
415
|
+
{ pinId: `C${index + 1}.1`, x, y: 0.4 },
|
|
416
|
+
{ pinId: `C${index + 1}.2`, x, y: -0.4 },
|
|
417
|
+
],
|
|
418
|
+
}))
|
|
419
|
+
input.netConnections = [
|
|
420
|
+
{ netId: "VDD", isGround: false, pinIds: ["C1.1", "C3.1", "C5.1"] },
|
|
421
|
+
{ netId: "VEE", isGround: false, pinIds: ["C2.1", "C4.1", "C6.1"] },
|
|
422
|
+
{
|
|
423
|
+
netId: "GND",
|
|
424
|
+
isGround: true,
|
|
425
|
+
pinIds: input.chips.map((chip) => chip.pins[1]!.pinId),
|
|
426
|
+
},
|
|
427
|
+
]
|
|
428
|
+
const solver = solve(input)
|
|
429
|
+
const output = solver.netLabelToTraceSolver!.getOutput()
|
|
430
|
+
expect(solver.longDistancePairSolver!.getOutput().newTraces).toHaveLength(0)
|
|
431
|
+
expect(output.traces).toHaveLength(5)
|
|
432
|
+
expect(
|
|
433
|
+
output.traces.every((trace) =>
|
|
434
|
+
trace.pinIds.every((pinId) => pinId.endsWith(".2")),
|
|
435
|
+
),
|
|
436
|
+
).toBe(true)
|
|
437
|
+
expect(output.netLabelPlacements).toHaveLength(7)
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
test("decoupling row shares GND while keeping DEC supplies separate", async () => {
|
|
441
|
+
const input = createBank()
|
|
442
|
+
input.chips = Array.from({ length: 6 }, (_, index) => ({
|
|
443
|
+
chipId: [
|
|
444
|
+
"C_MCU_VDD1",
|
|
445
|
+
"C_MCU_VDD3",
|
|
446
|
+
"C_MCU_VDD2",
|
|
447
|
+
"C_MCU_DEC1",
|
|
448
|
+
"C_MCU_DEC3",
|
|
449
|
+
"C_MCU_DEC4",
|
|
450
|
+
][index]!,
|
|
451
|
+
symbolName: "capacitor_down",
|
|
452
|
+
center: { x: index * 3, y: 0 },
|
|
453
|
+
width: 0.6,
|
|
454
|
+
height: 0.76,
|
|
455
|
+
pins: [
|
|
456
|
+
{ pinId: `C${index + 1}.1`, x: index * 3, y: 0.38 },
|
|
457
|
+
{ pinId: `C${index + 1}.2`, x: index * 3, y: -0.38 },
|
|
458
|
+
],
|
|
459
|
+
}))
|
|
460
|
+
input.netConnections = [
|
|
461
|
+
{ netId: "V3V3", netLabelText: "V3V3", pinIds: ["C1.1", "C2.1", "C3.1"] },
|
|
462
|
+
...["DEC1", "DEC3", "DEC4"].map((netId, index) => ({
|
|
463
|
+
netId,
|
|
464
|
+
netLabelText: netId,
|
|
465
|
+
pinIds: [`C${index + 4}.1`],
|
|
466
|
+
})),
|
|
467
|
+
{
|
|
468
|
+
netId: "GND",
|
|
469
|
+
isGround: true,
|
|
470
|
+
netLabelText: "GND",
|
|
471
|
+
pinIds: input.chips.map((chip) => chip.pins[1]!.pinId),
|
|
472
|
+
},
|
|
473
|
+
]
|
|
474
|
+
input.availableNetLabelOrientations = {
|
|
475
|
+
V3V3: ["y+"],
|
|
476
|
+
DEC1: ["y+"],
|
|
477
|
+
DEC3: ["y+"],
|
|
478
|
+
DEC4: ["y+"],
|
|
479
|
+
GND: ["y-"],
|
|
480
|
+
}
|
|
481
|
+
const solver = solve(input)
|
|
482
|
+
const { traces, netLabelPlacements } =
|
|
483
|
+
solver.netLabelToTraceSolver!.getOutput()
|
|
484
|
+
expect(traces).toHaveLength(7)
|
|
485
|
+
expect(netLabelPlacements).toHaveLength(5)
|
|
486
|
+
for (const net of input.netConnections) {
|
|
487
|
+
expect(
|
|
488
|
+
getTraceConnectedPinComponents({ pinIds: net.pinIds, traces }),
|
|
489
|
+
).toHaveLength(1)
|
|
490
|
+
expect(
|
|
491
|
+
netLabelPlacements.filter((label) => label.netId === net.netId),
|
|
492
|
+
).toHaveLength(1)
|
|
493
|
+
}
|
|
494
|
+
for (const trace of traces) {
|
|
495
|
+
const pinNets = trace.pinIds.map(
|
|
496
|
+
(pinId) =>
|
|
497
|
+
input.netConnections.find((net) => net.pinIds.includes(pinId))!.netId,
|
|
498
|
+
)
|
|
499
|
+
expect(new Set(pinNets).size).toBe(1)
|
|
500
|
+
}
|
|
501
|
+
await expect(solver).toMatchSolverSnapshot(
|
|
502
|
+
import.meta.path,
|
|
503
|
+
"decoupling-mixed-supplies",
|
|
504
|
+
)
|
|
505
|
+
})
|
|
@@ -99,3 +99,48 @@ test("collapses a shared stub and partial rail overlap", () => {
|
|
|
99
99
|
{ x: -1, y: -1 },
|
|
100
100
|
])
|
|
101
101
|
})
|
|
102
|
+
|
|
103
|
+
test("collapses every duplicate stub at a shared same-net endpoint", () => {
|
|
104
|
+
const traces = [
|
|
105
|
+
createTrace({
|
|
106
|
+
mspPairId: "outer-shared",
|
|
107
|
+
pinIds: ["outer", "shared"],
|
|
108
|
+
tracePath: [
|
|
109
|
+
{ x: 2, y: 3 },
|
|
110
|
+
{ x: 0, y: 3 },
|
|
111
|
+
{ x: 0, y: 0 },
|
|
112
|
+
],
|
|
113
|
+
}),
|
|
114
|
+
createTrace({
|
|
115
|
+
mspPairId: "inner-shared",
|
|
116
|
+
pinIds: ["inner", "shared"],
|
|
117
|
+
tracePath: [
|
|
118
|
+
{ x: 2, y: 1 },
|
|
119
|
+
{ x: 0, y: 1 },
|
|
120
|
+
{ x: 0, y: 0 },
|
|
121
|
+
],
|
|
122
|
+
}),
|
|
123
|
+
createTrace({
|
|
124
|
+
mspPairId: "middle-shared",
|
|
125
|
+
pinIds: ["middle", "shared"],
|
|
126
|
+
tracePath: [
|
|
127
|
+
{ x: 2, y: 2 },
|
|
128
|
+
{ x: 0, y: 2 },
|
|
129
|
+
{ x: 0, y: 0 },
|
|
130
|
+
],
|
|
131
|
+
}),
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
const result = collapseRedundantSharedEndpointStubs({
|
|
135
|
+
traces,
|
|
136
|
+
netLabelPlacements: [],
|
|
137
|
+
netLabelConnectorTraceIds: new Set<MspConnectionPairId>(),
|
|
138
|
+
multiPinNetPinIds: new Set(["shared"]),
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
expect(
|
|
142
|
+
result.filter((trace) =>
|
|
143
|
+
trace.tracePath.some((point) => point.x === 0 && point.y === 0),
|
|
144
|
+
),
|
|
145
|
+
).toHaveLength(1)
|
|
146
|
+
})
|