What it is
KiCad-Autoplace automates the tedious middle of PCB design: component placement and routing. You prepare a board in KiCad — draw the outline, lock any parts you care about, add your ground/power pours — and the app places everything else, routes it, and hands back a finished .kicad_pcb. It’s built for the students at the DTU Ballerup electronics workshop, and it’s the placement-and-routing half of the same fabrication workflow behind my SRM-CAM mill tool: SRM-CAM turns a finished board into machine code, this one produces the finished board in the first place.
It’s effectively a solo build (mine, with a CI bot filling in the rest).
How it works, in practice
Rather than gamble on one greedy layout, the engine generates several placement candidates in parallel and scores each — wirelength (HPWL), reduction vs. the raw import, net-crossing count, and overlaps — and presents them as a gallery to pick from.
Pick a candidate and the results speak in hard numbers — on this 131-part board, 33.6% shorter total wirelength, net crossings cut from 427 to 164, and zero overlaps after legalisation.
Then a route-driven refinement pass routes the board with FreeRouting and re-anneals the congested spots, tracking the live routed estimate as it goes.
The payoff: a real board
The point of all this is a board you can actually make. Here’s the same design in the KiCad PCB editor — the raw import the tool starts from, and the placed-and-routed result it produces:
Under the hood
The engine core is pure Python with no KiCad dependency — placement, routing orchestration, and refinement — with a thin kicad_io / routing / refine layer bridging to KiCad’s bundled pcbnew and to FreeRouting. On top sits an Electron desktop app; a cli.py headless runner (place, place-multi, refine, finalize, preflight, metrics) drives the same engine, and because the core is dependency-free the tests run under any Python. It also ships as a KiCad PCM plugin.
The pipeline itself is classic: block detection → a floorplan or force-directed seed → simulated-annealing refinement → legalisation → an aesthetic alignment pass. But the part I care about most is the gate around it: a change only lands if the unit tests stay green, and it doesn’t regress FreeRouting’s routed-% on the canonical gate boards, and its own proxy metric improves. FreeRouting is noisy (±3 nets run-to-run), so anything smaller than that is treated as noise, never signal. That gate is what lets the engine be tuned aggressively — including through AI-orchestrated optimisation sessions — without silently making things worse; it’s the same verification-first way I work with AI everywhere else.
What went wrong and how it was diagnosed
The two worst bugs both hid in the measurement, not the engine.
The boards weren’t placing badly — the scoreboard was lying. For a while the small test boards appeared to route only 59–81%, under both the tool’s placement and the humans’ original layouts. That looked like a routing ceiling — until we looked at what was left unrouted. It was the ground net, every time. The cause was in the gate harness: ground pours declared with no net were filled but never electrically tied to a pad, so FreeRouting saw a filled plane and counted every ground connection as permanently unrouted. Once the ground pours were connected with thermal spokes, the same boards routed 100%. Every routed-% logged before that fix was deflated — a sharp reminder that a number you don’t trust is worse than no number at all.
A “deterministic” engine that wasn’t. Placement with a fixed seed was supposed to be perfectly reproducible, but under concurrent CPU load the same board would occasionally settle into a slightly different layout. The root cause was a single unordered iteration: the cost function summed per-net wirelength by looping over a set of net-name strings, so the floating-point accumulation order followed Python’s hash randomisation. A last-bit difference in one sum was enough to flip a single simulated-annealing accept — which then cascaded into a different final layout. Sorting the nets before summing made it bit-for-bit reproducible across processes, verified by replaying a board under a range of hash seeds.
What it achieves
- Places at expert-human parity across DTU’s board corpus — re-routing the humans’ own footprint positions yields identical routed counts to the engine’s placement.
- On a fresh external board it beat a raw KiCad import outright — routed coverage 56.9% → 70.0%.
- Backed by 112 passing unit tests and the FreeRouting non-regression gate above, so improvements are measured, not asserted.
Tools & skills demonstrated
A real desktop application (Electron over a pure-Python engine), PCB-domain automation (connectivity-aware placement via simulated annealing, plane-aware routing, FreeRouting orchestration, fabrication-rule handling), and — the part I’m proudest of — a rigorous measurement and gating methodology that makes engine changes provable rather than plausible. A substantial, self-directed tool that other people at DTU actually use to make real boards.