The problem left hanging at the end of Part 4: our fixes come back undone.
We unify the trace widths against the net rules. Next round, they are off again. We get right angles down to 10. Measure again, and there are more. It came out the same no matter how many times we went around.
We ran DRC on the same board four times
The DRC results changed far too often, which is what made us suspicious. So we fixed nothing at all and simply ran DRC on the same board several times in a row.
| Run | Violations (errors) |
|---|---|
| 1 | 0 |
| 2 | 16 |
| 3 | 3 |
| 4 | 50 |
The board was never touched, and the results look like that. Hashing the file gave the answer.
kicad-cli pcb drc --refill-zones board.kicad_pcb
This command overwrites the board file. It saves the refilled zones — the copper pours — back into it. In other words, the thing being checked changes every time you check it.
What a moving baseline broke in the correction loop
The automatic correction loop was not merely “the numbers are noisy.” Every correction tool we had was built like this.
1. Measure the current state with DRC ← baseline
2. Apply a fix
3. Measure with DRC again
4. Roll back if it got worse
If the baseline moves on every call, all of those judgments are meaningless. An improvement gets ruled a regression and rolled back; a regression gets accepted as an improvement. That was the identity of “the fixes keep coming undone.”
It also means every number we had logged as “DRC clean” may have had nothing to do with the actual state of the board.
The fix — measure a copy, never the original
A throwaway copy is what we measure now: the board and the project file go into a temporary folder and DRC runs on the copy. The original is never touched.
| Method | Run 1 | Run 2 | Run 3 |
|---|---|---|---|
| Directly on the original | 0 | 16 | 3 |
| On a copy (project included) | 0 | 0 | 0 |
All three identical, and the hash of the original unchanged.
There is one more trap here. Copying the board file alone is not enough. The project
file next to it (.kicad_pro) holds the net classes and the custom rules, and without
them the same board inflates to 0 → 40 → 118. The question is not “we measured a
copy, so we are safe” but “did we copy the rules along with it?”
If the measurement changes the subject, it is not data.
The first thing to check when building a new verification loop: does this command write to its input file? A single hash comparison is enough.
The second culprit — our scripts were shaving trace width
Trace width was the next thing we looked at once the measurement was fixed. The board still carried 0.225mm and 0.375mm. The rules say 0.3 for signal and 0.5 for power — exactly 0.75× those values.
The culprits were our own correction scripts.
| File | The code that was actually in it |
|---|---|
| Clearance fixer | SetWidth(GetWidth() - 0.05mm) |
| Finishing fixer | SetWidth(w - 0.08mm) |
| Gap connector | for w in (0.5, 0.4, 0.3) — laid traces at whatever width fit |
| Finishing tool | 0.2 hard-coded, net names hard-coded |
Whenever a clearance violation appeared, the scripts shaved the trace until it passed. DRC turns green while the design rules are already broken. This is the worst failure mode there is: the checker says pass and the object is wrong.
We fixed it in two directions.
- One source for width rules. A single function reads the net classes from the project file, and every script that needs a width imports it. All the code where a script decided a width for itself is gone.
- Clearance problems are fixed by changing the path. Not by shaving width or spacing to hide them. Force the rule width, and if a violation appears, that is not a width problem — it is a signal that the path cannot carry that width.
Forcing the rule widths produced 16 clearance violations, all in the 0.176~0.19mm range, 0.02mm short of the 0.2mm required. Neighboring traces that had been spaced for 0.225mm ended up touching once they widened to 0.3mm. We did not roll it back. What needs fixing is the routing through those spots.
The answer key was in our own production boards
Measured values from production boards were the answer key. Up to this point we had been picking values by feel: 0.3mm sounds about right, 0.8/0.4 is probably fine for a via. Then we pulled out the design files and manufacturing data of boards we already build and sell. There was nothing left to guess.
| Item | Measured |
|---|---|
| Default signal width | 0.25mm — 79~92% of total trace length |
| Set of widths in use | 0.15 / 0.20 / 0.25 / 0.30 / 0.50 / 1.00 |
| Trace angles | 0° / 45° / 90° at 100.0% (arbitrary angles: 0mm) |
| Per-layer direction | Top 60% horizontal / bottom 59% vertical |
| Vias | Drill 0.305mm / pad 0.610mm |
| Thermal spokes | 4, width 0.51 (THT) / 0.25 (SMD) |
Three things stood out.
First, the 45° rule is proven by measurement. Arbitrary angles come to exactly 0mm. A human-drawn board uses nothing but 0/45/90, at 100.0%.
Second, the per-layer direction is genuinely being followed. Part 4 argued that the staircases came from missing layer directions — and our own production boards had been doing exactly that all along.
Third, 15% of nets mix widths. But there is a pattern to it: one main width, with a short necking only at the pad entry. On a fine-pitch part such as an RP2040 you cannot escape a pin at 0.25mm, so the trace necks down to 0.2mm to get out and widens again afterwards. So we changed the rule from “one width per net” to “one main width per net plus short necking within an allowed set.” The judgment is made from the actual pin pitch, not from the component name.
Maybe we were short on copper layers — production uses four
The copper layer count was the last thing to come out of this. Opening the Gerbers of the production counterpart of the 16-channel IO board we were building — the 4-channel IO board — showed four copper layers. And the two inner layers were not ground planes but real routing layers: 344mm and 262mm of 0.25mm trace on them.
We were routing a board with four times the channels on two layers.
Recurring staircases, detours, and clearance violations may not be an algorithm problem but a shortage of layers. Before spending days on routing quality, we should have measured how many layers the production board in the same family uses.
If the measurement changes its subject it is not data, and the values you are about to guess are already sitting, measured, in your own production boards.
We folded all the rules in and ran it again, shrank the vias to 0.6/0.3, and kicked off a full reroute. We recorded the traces being laid down in order.
Part 6 — Two layers were never going to work: why the routing was still bad after every rule was met, and rebuilding it with four layers
Earlier in this series: Part 1, Part 2, Part 3, and Part 4 on the router refusing to cooperate.
Contact
- Email: [email protected]
- Instagram: https://www.instagram.com/going.sen/
- Website: https://intosen.com/kr/consult/
Comments
Enter a nickname to leave a comment, or sign in with Google or GitHub.