GitHub - Koppar-AI/autorouter-bench: Measuring Koppar PCB autorouter against other solutions - benchmark on real open-hardware boards, scored by KiCad

GitHub

9 min read Original article ↗

A benchmark for PCB autorouters on real, professionally routed open-hardware boards, with KiCad as the referee.

Each router gets the same eight boards with their routing removed, the same placement, the same design rules and the same wall-clock cap. What comes back is scored by KiCad, never by the router:

  • Completion: 100 × (1 − unconnected / connections), both counted by KiCad's ratsnest on the board the router returned.
  • DRC: kicad-cli pcb drc --severity-error with the board's benchmark rules, with any zones the router drew refilled. Reported as the errors a router added over the human board's own findings (two upstream boards carry an unclosed outline or a courtyard overlap).
  • Vias, track length and wall-clock seconds, as measured.

A router that reports 100% and leaves a pad stranded scores the stranded pad.

The boards

Board Layers Source Licence
pic-programmer 2 KiCad demos, tag 10.0.4 GPL-3.0-or-later
rp2040-minimal 2 Raspberry Pi RP2040 minimal design, revision 2 (archived download) Raspberry Pi design files
pico-vga 4 (ground planes inside) Raspberry Pi Pico VGA, SD and audio base (archived download) Raspberry Pi design files
cm5io 4 Raspberry Pi CM5 IO board, revision 2 Raspberry Pi design files
rk3328-som-evb 4 OLIMEX RK3328-SOM-EVB GPL-3.0 / CERN-OHL-1.2
scalenode-cm4 4 Antmicro Scalenode CM4 baseboard Apache-2.0
stmp157-som-evb 4 OLIMEX STMP157-BASE-SOM-EVB GPL-3.0
imx8mp-som-evb 4 OLIMEX iMX8MP-SOM-EVB GPL-3.0

No board is stored in this repository. scripts/fetch_boards.py downloads each one from its pinned upstream source and checks it against boards/checksums.json. The two older Raspberry Pi archives are fetched from the Internet Archive, because the live URLs now serve a later revision.

The fetched board is loaded and saved once in KiCad 10, which upgrades the file format and leaves the geometry alone. That is the human reference. The input every router gets is its placement only: the tracks, vias and copper zones are deleted, and the outline, footprints and keepout areas stay. Every net is the router's to connect, ground included; a router may pour planes of its own.

The 25 public boards (set zap25)

The 25 boards Zap! (Mutual Solutions) published its comparison on (zap.mutual.solutions/compare, measured 12-13 October 2026), pinned to the same commits and listed in boards/manifest-zap25.json. They have no benchmark rules file: each board runs under its own project's rules, as in that comparison. They are selected with BOARD_SET=zap25; without it every script sees the eight boards above. Two of them ship custom design rules (.kicad_dru: seven, stm32h7-hdmi-sodimm); they are fetched beside the board as circuit.kicad_dru, so every router and the referee judge by them. Koppar's run on this set, beside the numbers Zap! published for every router, is results/2026-10-zap25/summary.md.

Board Layers Source Licence
cm4-lvds-adapter 6 Antmicro CM4 baseboard LVDS adapter Apache-2.0
ecp5-dc-scm 8 Antmicro ECP5 DC-SCM Apache-2.0
ftdi-jtag-programmer 4 FTDI JTAG programmer CERN-OHL-P-2.0
trellis-core 4 Trellis Core CERN-OHL-P-2.0
bitaxe-ultra 4 Bitaxe Ultra (fork) CERN-OHL-S-2.0
ender3-loadcell 2 Ender-3 load-cell bed-levelling sensor MIT
rp2040-devboard 2 RP2040 development board MIT
discretesim-hwtest 2 discretesim test board MIT
water-level-monitor 2 Arduino water-level monitor MIT
ecp5-bga381-ft2232 4 ECP5 BGA-381 FT2232HQ FIFO board MIT
emberone 6 EmberOne CERN-OHL-S-2.0
argos-demo 4 Argos demo board CERN-OHL-S-2.0
rp2350-pico2-bb48 4 RP2350-PICO2-BB48 MIT
cvbs-mipi-bridge 4 Antmicro CVBS-to-MIPI bridge Apache-2.0
dual-ipex-csi-interposer 4 Antmicro dual IPEX CSI interposer Apache-2.0
gmsl-serializer 4 Antmicro GMSL serializer Apache-2.0
pmod-i3c-sensor 4 Antmicro PMOD I3C sensor board Apache-2.0
si-test-board 4 Antmicro signal-integrity test board Apache-2.0
stm32h7-hdmi-sodimm 6 Antmicro STM32H7 HDMI SO-DIMM board Apache-2.0
tiliqua-motherboard 6 Tiliqua (apfaudio) CERN-OHL-S-2.0
buildbotics-controller 4 Buildbotics controller CERN-OHL-S-2.0
hackrf-pro 6 HackRF Pro CERN-OHL-P-2.0
seven 4 Seven CERN-OHL-P-2.0
kintex-pcie 8 Kintex PCIe board Apache-2.0
xerxes 6 Xerxes Apache-2.0

The benchmark rules

boards/rules/<board>.json lists every change from the upstream project's rules; the fetch script applies exactly that, and every router and the DRC referee get the result. The changes set one floor (0.1 mm clearance and tracks on the 4-layer carriers) and drop the impedance classes and cm5io's PoE spacing, so the table measures routing under that floor, not each design's own constraints. The 25-board set runs under each board's own rules.

PCBWorld-Bench

The real-board test sets of PCBWorld (LG AI Research): D3-A (99 boards) and D3-B (10), built from the public PCBench collection by PCBWorld's own preparation chain. pcbworld/ holds the script that builds them and the clean-pass scorer; pcbworld/README.md says how.

Running it

Needs Linux, KiCad 10 (kicad-cli and a python that imports pcbnew), Python 3.10+ and Node 22+.

export KICAD_PYTHON=/usr/bin/python3        # a python that imports pcbnew
python3 scripts/fetch_boards.py             # the eight boards, checked and prepared
BOARD_SET=zap25 python3 scripts/fetch_boards.py   # the 25 public boards of Zap!'s comparison

tools/install.sh freerouting                # Freerouting 2.5.0 and Java 25
tools/install.sh node                       # tscircuit 0.0.979, KiCad Routing Tools 0.1.9
tools/install.sh tracemaker                 # TraceMaker 0.9.0, built from source (CUDA)

python3 harness/run.py freerouting          # or tscircuit, krt, tracemaker, koppar, koppar_filled, zap; optional board names
python3 scripts/collect.py                  # results/<campaign>/summary.md

CAP_S sets the wall-clock cap per board (default 1200 s). Results merge by board into results/<campaign>/<tool>.json.

How each router is run

  • Freerouting 2.5.0: the board exported to Specctra DSN by KiCad, routed with -mp 100 -mt 8, and the session imported back by KiCad. Freerouting writes its result only when it finishes, so a run past the cap has no board to score.

  • tscircuit capacity autorouter 0.0.979: the board as a SimpleRouteJson (KiCad's bounding box for every pad, hole, copper graphic and keepout; one connection per net, ground included), routed with AutoroutingPipelineSolver, and the returned traces written back onto the board as returned.

  • KiCad Routing Tools 0.1.9 (@tscircuit/krt-wasm): the same input, on a 0.05 mm grid with the board's default clearance. The package routes a whole board or nothing: when it gives up inside the cap, the harness routes one net at a time with the copper routed so far as obstacles and keeps what closes. The package does not include the project's rip-up and reroute, which is Python; results here are for the WASM router core only.

  • TraceMaker 0.9.0: reads and writes KiCad boards directly, given the cap as its time limit.

  • Koppar: a hosted service. Its results are produced by the service on the same machine, with the same inputs and cap, and scored by the same collector. harness/run.py koppar sends a board and its project to the public endpoint at tools.koppar.ai; a board past 300 connections needs an account API key, read from KOPPAR_API_KEY. koppar keeps every via out of pin pads (the default); koppar_filled lets vias sit in pin pads, for a board ordered with filled and capped vias. The service holds a route to the 1200 s cap itself: a step that would end past it is skipped or undone, so the board returned is the last one inside the cap. The harness polls the job for up to the cap + 600 s, which is waiting time, not routing time.

  • Zap! (Mutual Solutions): a hosted service. harness/run.py zap sends the board and its project to zap.mutual.solutions with fab_floor=board (the board's rules, as every other tool gets) and the service defaults otherwise (its own pours, no vias in pads), and minutes = the cap (20). Time is the job's run time on the service, without its queue, upload or download, so the whole cap is routing time, as it is for Koppar. The job is polled for up to the cap + 600 s, like Koppar's; a board that comes back after the cap is scored and marked late, and a job still running then is cancelled. Boards run one at a time. The API key is read from ZAP_KEY_FILE (default ~/.zap-key).

Every router runs with its defaults and the board's rules. A router's author may know settings that do better; the harness is here so anyone can show it.

Results

results/2026-10-placement/summary.md is the October 2026 campaign: one workstation (24 cores, one RTX 5090), 1200 s per board, KiCad 10.0.4 as referee. How each column was produced, and what it does not score, is in results/2026-10-placement/NOTES.md.

Boards fully routed (100% completion) with no DRC error added, out of eight:

Router Boards
Koppar 8
Koppar (filled vias) 8
TraceMaker 0.9.0 5
Zap! 2026.10.14.78 3
Freerouting 2.5.0 0
tscircuit 0.0.979 0
KiCad Routing Tools 0.1.9 0

The 25 public boards

results/2026-10-zap25/summary.md: Koppar, one run per board (measured 2026-10-21), beside the numbers Zap! published for itself, TraceMaker, Freerouting and tscircuit (which we did not re-run). Each board under its own rules.

Router Fully connected Clean (0 open, 0 errors) Connected, no more errors than the human board
Koppar 14 11 14
Zap! 14 6 9
TraceMaker 7 3 4
Freerouting 11 2 2
tscircuit 10 0 0

PCBWorld-Bench

results/pcbworld-2026-10/summary.md: clean-pass rate, one attempt per board. Koppar 1.000 on D3-A and 1.000 on D3-B; DeepPCB reports 0.929 and 0.800, and the PCBWorld paper's baselines are lower (the full table, as DeepPCB published it, is in the summary).

Electrical quality

Completion is not quality. scripts/quality.py measures, on every routed board and the human reference alike: the share of high-speed track over a ground plane (and over any plane), ground vias beside high-speed layer changes, differential pair length mismatch and the share routed side by side, the narrowest track on each power net, and the routed path from each decoupling capacitor to its part (length and vias). scripts/quality_campaign.py runs it over a campaign: results/2026-10-placement/quality.md.

Nets are classified by name (pairs by _P/_N-style suffixes, clocks by CLK, rails by voltage-style names); the patterns are in the script.

What this does not measure

Current is not stated on these boards, so rail widths are reported, not judged against a current. Impedance needs each board's stackup and is not scored. No router in the table matches the human layouts on every measure.

Licence

The code in this repository is MIT licensed. The boards are not part of it; each keeps its upstream licence.