

Prerouted
A fast autorouter for KiCad boards that, above all, reroutes incrementally: given the previous route, it keeps what an edit left standing and routes again only what the edit disturbed.
- Size
- 127 × 95 mm
- Copper layers
- 2
- Thickness
- 1.6 mm
- Parts
- 160
- Nets
- 126
- Pads
- 631
- Tracks, vias
- 175, 277
- Pours
- 16
- Routed
- Yes
- KiCad
- KiCad 10.0 (file 20260206)
- Created
- 13 Oct 2026
- Updated
- 13 Oct 2026
Overview
Prerouted is an open-source printed circuit board by bruk-io, from the bruk-io/pcb-route repository on GitHub, published under the MIT licence. It is a 2-layer board of 127 × 95 mm with 160 parts on 126 nets, routed with 175 tracks and 277 vias.
It is built around the 74AHCT1G125SE, ESP32-S3-WROOM-1-N8R2 and ESP32-CAM, with the TPS62933DRL, PCA9685PW and TPS2116DRL among its other parts. Its parts include 62 resistors, 39 capacitors, 30 connectors, 11 transistors and 10 ICs. It belongs with the ESP32 and RISC-V boards of this library.
Main components
1074AHCT1G125SE
SOT-353-40℃~+125℃ 1 1 4.5V~5.5V 4.7ns@5V,50pF 40uA 74AHCT 8mA 8mA Schmitt trigger Tri-State Unidirectional SOT-353 Buffers, Drivers, Receivers, Transceivers ROHSDiodes IncorporatedBuffers, Drivers, Receivers, TransceiversESP32-S3-WROOM-1-N8R2
SMD,25.5x18mm-40℃~+85℃ -98.2dBm 2.4GHz 20.5dBm 355mA 3V~3.6V 97mA Bluetooth5.0 ESP32-S3 Chip On-board PCB Antenna UART、SPI、I2C、I2S、USB SMD,25.5x18mm WiFi Modules ROHSEspressif SystemsWiFi ModulesESP32-CAM
插件插件 WiFi Modules ROHSAi-ThinkerWiFi ModulesTPS62933DRL
SOT-583-40℃~+150℃@(TJ) 1 12uA 2.2MHz 3.8V~30V 3A 800mV~22V Adjustable Buck Buck Built-in Yes SOT-583 DC-DC Converters ROHSTexas InstrumentsDC-DC ConvertersPCA9685PW
TSSOP-28-40℃~+85℃ 100kHz~1MHz 16 2.3V~5.5V 25mA 5.5V Built-in PWM TSSOP-28 LED Drivers ROHSNXP SemiconductorsLED DriversTPS2116DRL
SOT-583Operating status indication SOT-583 Power Management - Specialized ROHSTexas InstrumentsPower Distribution SwitchesUSBLC6-2SC6
SOT-23-60.35pF、0.8pF 100nA 150W@8/20us 15V 2 5V 6A@8/20us 6V ESD SOT-23-6 ESD and Surge Protection (TVS/ESD) ROHSUMWESD And Surge Protection (TVS/ESD)BSC010N04LS
TDSON-8FL-55℃~+150℃ 1 N-channel 139W 1mΩ@10V 247A 2V 320pF 40V 9.52nF 95nC TDSON-8FL MOSFETs ROHSInfineon TechnologiesMOSFETsSMBJ8.5A
DO-214AA(SMB)-55℃~+150℃ 10uA 14.4V 41.7A 600W@10/1000us 8.5V 9.44V TVS Unidirectional DO-214AA(SMB) ESD and Surge Protection (TVS/ESD) ROHSFTRESD And Surge Protection (TVS/ESD)SMCJ7.0A
SMC(DO-214AB)-55℃~+150℃ 1.5kW 125A 12V 200uA 7.78V 7V IEC 61000-4-2 TVS Unidirectional SMC(DO-214AB) ESD and Surge Protection (TVS/ESD) ROHSLiownESD And Surge Protection (TVS/ESD)
README
README.mdpcb-route
A fast autorouter for KiCad boards that, above all, reroutes incrementally: given the previous
route, it keeps what an edit left standing and routes again only what the edit disturbed. Nothing
is locked except hand-laid copper. It reads a Specctra design and writes a Specctra session, on
Freerouting's command line (-de board.dsn -do board.ses -mp 40), so it can run where Freerouting
is called today.
On the one real board it has been measured on (a two-layer robot carrier, below), a full route takes 55 s against Freerouting's 337 s, and rerouting after a small edit takes 3.5 s, both clean under KiCad's DRC. It uses more vias than Freerouting (98 against 58). Freerouting can also keep existing copper, routing only what is missing; that mode has not been timed against this one yet.
Use
python route.py -de kicad/board.dsn -do kicad/board.ses -mp 40 --board kicad/prerouted.kicad_pcb
python route.py ... --previous golden/board.ses # incremental; golden/prerouted.kicad_pcb is read too| Flag | Meaning |
|---|---|
-de, -do, -mp | As Freerouting: design in, session out, most negotiation rounds |
--board | The KiCad board the DSN was written from (geometry and rules come from it) |
--previous | A previous session of this board, to reuse |
--previous-board | The board that session was routed on (default: prerouted.kicad_pcb beside it) |
--max-seconds | No new round after this; the result so far is written (default 600) |
--min-width | No track narrower than this, in mm |
--seed | A different net order and starting prices, for a retry |
--via-cost | Millimetres of track one via is worth in the finished route (default 16); raise it for fewer vias, at more time. Negotiation settles with cheap vias first (2.4 mm, or --via-cost if lower); via passes then reroute the nets that have vias at this cost |
--cache | Where board exports are kept (default ~/.cache/pcb-route, or PCB_ROUTE_CACHE) |
Log lines match Freerouting's: Starting auto-routing..., Auto-routing was completed in N seconds.,
Saving '<file>'..., plus Router: <rounds> rounds, <vias> vias, <cells> cells searched. SIGTERM
finishes the net being routed and writes the result (a via pass under way is dropped). Exit code 0
when every net is routed without conflict, 2 when something is left, 1 when nothing was written.
Pads wholly inside a track keep-out (under a pour) are left for the pour, as Freerouting leaves them; the log lists them.
Install
Needs KiCad 10's Python (pcbnew; set KICAD_PYTHON if it is not /usr/bin/python3), numpy,
matplotlib, and the router built with Mojo 1.1 (pip install mojo):
mojo build -D ASSERT=none -o router/negotiate router/negotiate.mojo-D ASSERT=none turns off Mojo's bounds check on every collection index, which costs the router a
fair share of its speed. Mojo runs on Linux and macOS (on Windows, through WSL).
Tests
python -m pytest tests (about 6 minutes). The quick tests route pcbkit's blinky example, from a
pcbkit checkout beside this repository (or set PCBKIT); the rest use the robot carrier in
boards/spot. tests/test_snapshots.py pins the router's output; a change that alters it fails
until accepted with UPDATE_SNAPSHOTS=1 (the test's docstring says when that is right).
How it works
board.pyturns the KiCad board into a grid (0.05 mm cells per layer): for each net class, which net may stand in each cell, where it may drill, and each net's pads. Exports are cached; after an edit, the edited board is read as a patch of the previous export, redrawing only the grid around what changed, with the same result as a full read (tests/test_patch.py).- With
--previous,incremental.pyworks out which pieces of the previous route still stand on the edited grid; they are offered to the router as kept copper. - The router (
router/) routes every net with A* on the grid, then negotiates: cells two nets share get dearer every round, and the nets in conflict are routed again, until none share a cell. Kept copper is laid first and is only routed afresh if other nets keep running over it. - Via passes: once the board has settled with cheap vias, vias get dearer, the nets that have them are routed again, and negotiation settles the board again. If it does not, or vias do not fall, the earlier result stands.
- Tidying: a grid path alternates straight and diagonal steps. Each connection is redrawn between its fixed points (ends, vias, junctions) as runs of at most two pieces, one straight and one at 45 degrees, wherever every cell of the run is clear of other copper.
ses.pywrites the session; reused pieces are written as they were.
Results on the robot carrier (12 Oct 2026, one 2-core cloud machine)
The board: a 127 x 95 mm two-layer carrier with about 160 parts. The edit: C2, C41, C50 and C51
moved (boards/spot/golden to boards/spot/kicad). Each session was imported into pcbnew 10 and
checked with KiCad DRC at every severity, before pours.
| Run | Wall time | Copper problems | Vias | Track pieces | Track |
|---|---|---|---|---|---|
Freerouting 1.9.0, -mp 40, full route | 337 s | 4 unconnected (3 connections at U8 left unrouted) | 58 | 1,453 | 4,490 mm |
| pcb-route, full route, cold cache | 55 s (43 s routing) | 0 | 98 | 1,588 | 3,961 mm |
pcb-route, full route, --via-cost 2.4 (no via passes), board export cached | 17 s | 0 | 169 | 1,520 | 3,612 mm |
pcb-route, full route, --via-cost 40, board export cached | 91 s | 0 | 71 | 1,720 | 4,366 mm |
pcb-route, --previous golden/..., golden export cached | 3.5 s | 0 | 75 | 1,559 | 4,791 mm |
The incremental run keeps 96% of the previous route's track; the edited board is read as a patch in 1.6 s. Of the full route's 98 vias, about 17 are on the three U8 connections Freerouting left unrouted. No router via lands in a surface-mount pad or within the board's hole-to-hole distance of an existing hole; hand-laid tracks and vias are untouched (they are not written to the session).
Known limits
- Measured on one real board and one small example. Dense 4- to 6-layer boards and fine-pitch BGAs are untried.
- More vias than Freerouting. Most of the rest are crossings: a track meets another net's track on its layer, and no detour cheaper than the via cost is clear.
- No length matching, differential pairs or impedance control.
--min-widthabove the net classes' own widths widens every track; on the carrier that left two fine-pitch pads unreachable and 3 clearance faults. At or below the class widths it is only a floor.- Reused copper is trusted where the edit did not change what may stand in its cells; a net whose class width or clearance changed is routed afresh.
- The time limit and SIGTERM stop after the net being routed; a SIGTERM before routing starts (while the board is read) leaves no session.
- The grid takes memory: about 190 MB per export of the carrier, and about 0.7 GB while routing.
Use from pcbkit
This began as the router for pcbkit, a board-as-code tool (INTENT.md has the
problem it set out to solve). There:
build_command: the command line above,--board= the boardprewrote the DSN from.pcbkit route --eco: pass--previous golden/<stem>.sesinstead of copying and locking copper.- The retry loop: pass
--seed <try number>; results are otherwise deterministic.
Files
route.py- the command lineboard.py- KiCad 10 board to the router's grid (pre-routes, keep-outs, per-pad clearances)incremental.py- which of the previous session's pieces still standrouter/- the router:negotiate.mojothe command line, negotiation and via passes,board.mojothe grid and nets,congestion.mojoprices and footprints,search.mojothe A* search,tidy.mojothe straightening,cost.mojostep costs,buffer.mojolazily zeroed per-cell storage,stop.mojoSIGTERM,mt19937.mojothe seeded jitterses.py- the session writercheck.py- import a session with pcbnew and count KiCad DRC's copper problemstests/test_route.py- the command line's contracttests/test_patch.py- a patched board read equals a full read, for each kind of edittests/test_snapshots.py- the router's output, pinnedtests/test_quality.py- tidying and via passes keep the route conflict-free, and full and incremental routes pass KiCad DRC
How the router got fast (12 Oct 2026, robot carrier)
The router began as a C++ program. Its first Mojo version was a straight port that matched it byte for byte at the same speed (a full route in 47 s against 48 s); the changes below, each measured on its own with the output compared every time, then halved that, before via passes and tidying were added:
- Pricing a via site (the dearest cell under the via, on every layer) was over half a full route.
Each row of the via's disc is scanned in SIMD vectors, and where no copper covers a vector, a
cell's price is exactly
1 + history, so only the largest history is needed. - A via is priced only when its turn comes in the search: it is queued at the cheapest a via can be, and most queued vias never come up. The paths found are as cheap; the search takes 30% less time, and about 40% less in a via pass.
- The A* estimate divided twice per neighbour to find its row and column; neighbours take them from the cell being expanded, and the one division left per expansion is 32-bit unsigned.
- A cell's best cost, "seen" and "closed" marks sit in one 16-byte record, so a visit reads one cache line, not three.
- Counting a net's keep-out footprint was 40% of an incremental run. Rows are tested 16 cells at a time and only unmarked cells are visited; when a path cell is next to the one before it, only the edge of the disc that the step uncovers can hold unmarked cells.
- Page faults were a third of an incremental run's time. Grid-sized buffers are mapped with huge pages (Linux), and the board's grids are read straight into them: 200k faults down to 15k.
Where the time goes as --via-cost rises: the search, not the arithmetic. Once a track is blocked
on its layer, A* has to try every detour cheaper than a via before it takes one, so the cells
searched grow with about the square of the via cost (79 M cells at 2.4 mm, 221 M at 16 mm, 520 M
at 40 mm), at about 190 ns each. On the default full route: negotiation 17 s, the first via pass
20 s, the second 4 s, tidying under 1 s. Charging the higher via cost through every negotiation
round instead of in via passes costs 2-5 times the time for the same vias.
Tried and dropped: packing occupancy, kept copper and history into one record per class (no faster); skipping a via's price when every other layer is already reached more cheaply (under 0.1% of sites); counting the via a cell's layer forces into the A* estimate (target pads are rarely on one layer only); restarting the sharing price for a via pass (never settled); taking vias out while tidying through clear cells only (8 vias for 4 s; a via pass does better).
Notes on Mojo 1.1: List.read_bytes() took 0.45 s on the 200 MB board file against 0.17 s for C's
fread, so grids are read into buffers directly. The Mojo runtime runs threads of its own, so
blocking SIGTERM on the main thread is not enough: a C-ABI handler passes a stop signal on to the
main thread, which polls for it between nets. An environment variable set to an empty value counts
as unset, and numbers must parse cleanly.
Parts
160| Qty | Value | Footprint | References |
|---|---|---|---|
| 16 | 220 | Resistor_SMD:R_0603_1608Metric | R60, R61, R62, R63, R64, R65, R66, R67, R68, R69, R70, R71, R72, R73, R74, R75 |
| 15 | 100n | Capacitor_SMD:C_0603_1608Metric | C2, C3, C4, C6, C12, C13, C19, C20, C31, C41, C46, C47, C48, C50, C80 |
| 13 | 10k | Resistor_SMD:R_0603_1608Metric | R2, R6, R13, R15, R19, R30, R31, R42, R44, R50, R82, R84, R91 |
| 8 | BSC010N04LS | Package_TO_SOT_SMD:TDSON-8-1 | Q1, Q2, Q3, Q4, Q8, Q9, Q10, Q11 |
| 7 | 100k | Resistor_SMD:R_0603_1608Metric | R10, R16, R17, R40, R88, R89, R90 |
| 5 | 470u 16V | Capacitor_SMD:CP_Elec_8x10 | C5, C42, C43, C44, C45 |
| 4 | 10u | Capacitor_SMD:C_0805_2012Metric | C18, C24, C51, C81 |
| 4 | 22u | Capacitor_SMD:C_1206_3216Metric | C15, C16, C22, C23 |
| 3 | 100 | Resistor_SMD:R_0603_1608Metric | R7, R8, R45 |
| 3 | 2N7002 | Package_TO_SOT_SMD:SOT-23 | Q5, Q6, Q7 |
| 2 | 10 | Resistor_SMD:R_0603_1608Metric | R4, R5 |
| 2 | 10u | Capacitor_SMD:C_1206_3216Metric | C10, C11 |
| 2 | 15k | Resistor_SMD:R_0603_1608Metric | R83, R85 |
| 2 | 1k | Resistor_SMD:R_0603_1608Metric | R20, R87 |
| 2 | 2.2k | Resistor_SMD:R_0603_1608Metric | R51, R52 |
| 2 | 22 | Resistor_SMD:R_0603_1608Metric | R23, R24 |
| 2 | 220p | Capacitor_SMD:C_0603_1608Metric | C25, C26 |
| 2 | 22u | Capacitor_SMD:C_0805_2012Metric | C17, C30 |
| 2 | 33n | Capacitor_SMD:C_0603_1608Metric | C14, C21 |
| 2 | 4.7k | Resistor_SMD:R_0603_1608Metric | R80, R81 |
| 2 | 470n | Capacitor_SMD:C_0603_1608Metric | C1, C40 |
| 2 | 47k | Resistor_SMD:R_0603_1608Metric | R11, R43 |
| 2 | 5.1k | Resistor_SMD:R_0603_1608Metric | R21, R22 |
| 2 | LM74502-Q1 | Package_TO_SOT_SMD:SOT-23-8 | U1, U3 |
| 2 | QWIIC | Connector_JST:JST_SH_BM04B-SRSS-TB_1x04-1MP_P1.00mm_Vertical | J30, J31 |
| 2 | TPS62933 | Package_TO_SOT_SMD:SOT-583-8 | U5, U7 |
| 1 | 1m | Resistor_SMD:R_Shunt_Isabellenhuette_BVR4026 | R3 |
| 1 | 1u | Capacitor_SMD:C_0603_1608Metric | C32 |
| 1 | 31.6k | Resistor_SMD:R_0603_1608Metric | R18 |
| 1 | 330 | Resistor_SMD:R_0603_1608Metric | R86 |
| 1 | 35.7k | Resistor_SMD:R_0603_1608Metric | R14 |
| 1 | 38.3k | Resistor_SMD:R_0603_1608Metric | R1 |
| 1 | 4.7u | Inductor_SMD:L_Bourns_SRP6060FA | L2 |
| 1 | 49.9k | Resistor_SMD:R_0603_1608Metric | R41 |
| 1 | 52.3k | Resistor_SMD:R_0603_1608Metric | R12 |
| 1 | 6.8u | Inductor_SMD:L_Bourns_SRP6060FA | L1 |
| 1 | 74AHCT1G125 | Package_TO_SOT_SMD:SOT-23-5 | U9 |
| 1 | ALWAYS ON | Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm | JP1 |
| 1 | BATTERY 2S | Connector_AMASS:AMASS_XT60PW-M_1x02_P7.20mm_Horizontal | J1 |
| 1 | BOOT | Button_Switch_SMD:SW_SPST_TL3342 | SW2 |
| 1 | CH0 FLS | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J10 |
| 1 | CH1 FLU | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J11 |
| 1 | CH10 RRU | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J20 |
| 1 | CH11 RRL | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J21 |
| 1 | CH12 AUX12 | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J22 |
| 1 | CH13 AUX13 | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J23 |
| 1 | CH14 AUX14 | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J24 |
| 1 | CH15 AUX15 | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J25 |
| 1 | CH2 FLL | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J12 |
| 1 | CH3 FRS | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J13 |
| 1 | CH4 FRU | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J14 |
| 1 | CH5 FRL | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J15 |
| 1 | CH6 RLS | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J16 |
| 1 | CH7 RLU | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J17 |
| 1 | CH8 RLL | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J18 |
| 1 | CH9 RRS | spotmicro:PinHeader_1x03_P2.54mm_Vertical_Servo | J19 |
| 1 | ESP32-CAM | Connector_JST:JST_XH_B7B-XH-A_1x07_P2.50mm_Vertical | J37 |
| 1 | ESP32-S3-WROOM-1-N8R2 | RF_Module:ESP32-S3-WROOM-1 | U8 |
| 1 | EXPANSION | Connector_PinHeader_2.54mm:PinHeader_1x10_P2.54mm_Vertical | J39 |
| 1 | FROM SERVO BUCK | Connector_AMASS:AMASS_XT60PW-F_1x02_P7.20mm_Horizontal | J3 |
| 1 | Green | LED_SMD:LED_0603_1608Metric | D2 |
| 1 | IMU | Connector_JST:JST_XH_B5B-XH-A_1x05_P2.50mm_Vertical | J33 |
| 1 | INA226 | Package_SO:MSOP-10_3x3mm_P0.5mm | U2 |
| 1 | LED RING | Connector_JST:JST_XH_B3B-XH-A_1x03_P2.50mm_Vertical | J36 |
| 1 | OLED | Connector_JST:JST_XH_B4B-XH-A_1x04_P2.50mm_Vertical | J32 |
| 1 | PCA9685PW | Package_SO:TSSOP-28_4.4x9.7mm_P0.65mm | U4 |
| 1 | POWER BTN | Connector_JST:JST_XH_B4B-XH-A_1x04_P2.50mm_Vertical | J38 |
| 1 | RESET | Button_Switch_SMD:SW_SPST_TL3342 | SW1 |
| 1 | SMBJ8.5A | Diode_SMD:D_SMB | D1 |
| 1 | SMCJ7.0A | Diode_SMD:D_SMC | D3 |
| 1 | SONAR L | Connector_JST:JST_XH_B4B-XH-A_1x04_P2.50mm_Vertical | J34 |
| 1 | SONAR R | Connector_JST:JST_XH_B4B-XH-A_1x04_P2.50mm_Vertical | J35 |
| 1 | TO SERVO BUCK | Connector_AMASS:AMASS_XT60PW-F_1x02_P7.20mm_Horizontal | J2 |
| 1 | TPS2116 | Package_TO_SOT_SMD:SOT-583-8 | U6 |
| 1 | USB-C | Connector_USB:USB_C_Receptacle_HRO_TYPE-C-31-M-12 | J4 |
| 1 | USBLC6-2SC6 | Package_TO_SOT_SMD:SOT-23-6 | U10 |
Similar boards
- 8 layers140 × 314.2 mm

- 2 layers79 × 59.5 mm

- 4 layers112 × 75 mm

- 4 layers126 × 90 mm

Incubator controller
tanaes/OvoEasy_incubator_controller
KiCad design for the OvoEasy incubator controller board
169 parts - 4 layers144 × 127 mm

Frothfet
hoeken/frothfet
Yarrboard load controller board with current sense, volt sense, fuses, and sender support.
394 parts14GPL-3.0 - 4 layers190 × 110 mm

- 6 layers131.5 × 132.4 mm

BedCooler
NicholasKorth/BedCooler
A dual-zone heating and cooling controller for a bed: a 6-layer KiCad board with an STM32G474 real-time controller and a Raspberry Pi Compute Module 5 host.
258 parts11 - 4 layers100 × 80 mm

RBPS
riteshsalikeripatil/Battery_Management_System
PCB and schematic design files for the RBPS hardware project.
98 parts1
Questions
How big is the Prerouted PCB?
It measures 127 × 95 mm and has 2 copper layers, 1.6 mm thick.
What parts does the Prerouted use?
160 parts, built around the 74AHCT1G125SE, ESP32-S3-WROOM-1-N8R2, ESP32-CAM and TPS62933DRL. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.
Where can I download the Prerouted KiCad files?
From the bruk-io/pcb-route repository on GitHub, at the commit this page was made from: the board is boards/spot/golden/prerouted.kicad_pcb. PCBKit opens the board in the browser, with nothing to install.
Can I use the Prerouted design in my own project?
Its repository publishes it under the MIT licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).