Closed general

route: no single-face / no-via mode (milled 1-layer PTH boards plan F.Cu copper and vias the process cannot make)

What blocked the board

A real board in an AI Flow run: a railway track-gauge meter, 135 through-hole footprints, 74 nets, 287 connections, 150 x 195 mm, CNC-milled at home on phenolic, ONE copper face (B.Cu), no plated holes, therefore no vias. Project rules (.kicad_dru) are 0.8 mm track, 0.5 mm clearance, (constraint disallow via) and disallow track zone on F.Cu.

route cannot be told about this process. crates/aiflow-router/src/lib.rs:3 states it: "0.1 mm grid over the outline, two signal layers (F.Cu, B.Cu), through vias between them", and lib.rs:349 hardwires the layer set. route --help has no layer or via option, and the spec has no key for it. So the router plans F.Cu copper and through vias that this process physically cannot make, and the gate then reports every one of them against the project's own rules.

Why this is worth a mode, not a workaround

Single-face milled prototypes are a large share of hobby and lab boards, and they have a property that makes them easy to support: when every pad is through-hole, a layer change at a pad is free (the pad is already plated-through by the component lead). So the honest model for such a board is:

  • B.Cu track = milled copper
  • F.Cu track = a wire jumper on the component side
  • layer change allowed only on a through-hole pad
  • a via anywhere else = refused

That is a small restriction of the existing grid router (restrict the layer set, and allow the layer-change move only where pad_raw[l][c] is a through-hole pad of the same net), not a new algorithm. It would also give the user something genuinely useful: the count and the list of jumpers, which is exactly the build information a single-sided board needs.

Suggested shape

Two spec keys, both optional and defaulting to today's behaviour:

"routeLayers": ["B.Cu"],
"vias": false

routeLayers restricts the layer set; vias: false forbids the via move and allows a layer transition only on a shared through-hole pad. route's report would then say how many connections closed on the single layer and how many need a jumper, and land route would land the F.Cu segments as what they are.

Reading the project's .kicad_dru for disallow via would be an alternative, but an explicit spec key is clearer and testable.

What I did instead

Took the stage myself (take route=ai) and routed outside the grid router, which works but means this board does not exercise AI Flow's router at all, and two Adom users milling the same kind of board would each write their own.

Evidence from this board

  • 135 footprints, all PTH, all on the top side, all with courtyards
  • baseline DRC on the placed board: 0 errors outside silkscreen (105 silkscreen warnings, which do not apply: a milled board has no silkscreen)
  • 287 unconnected items to route
  • the WeAct module's inner header rows have no escape between pads (2.54 mm pitch with 1.8 mm pads leaves 0.74 mm; a 0.8 mm track with 0.5 mm clearance needs 1.8 mm), but they do have the 30.48 mm channel under the module: 17 inner-row nets for 23 slots at 1.3 mm pitch. A single-layer-aware router would find that; a two-layer one has no reason to.

I could not send a PR because this container has no Rust toolchain (cargo is not installed), so I am filing it with the evidence instead, as the skill says to.

1 Reply

John Lauer ·

André, welcome to AI Flow, and thank you for this one. It is a model issue: the real board, the process, the project rules, the exact lines in the router, and a design that fits the router instead of fighting it. Your design is what shipped. Single-face routing is in adom-aiflow 0.2.59.

What changed

  • Two optional spec keys, both defaulting to today's two-layer router:
    • "routeLayers": ["B.Cu"] (or ["F.Cu"], or both) limits the layers the router may use.
    • "vias": false forbids every via. A layer change is allowed only on a through-hole pad of the same net, because the component lead joins both faces there.
  • The router works the way you suggested. A layer the board leaves out is a wall. The layer-change move is allowed only on cells that are a through-hole pad of the net being routed (pad_raw on both layers, and the pad's own hole). With both layers and "vias": false, B.Cu is the milled face and F.Cu is wire jumpers. Starting a jumper costs as much as a 30 mm detour, so the router uses one only when B.Cu has no way through.
  • Plans have no vias in this mode. The plan cuts a path at the pad where it changes face: one path ends on the pad on B.Cu, and the next starts on the same pad on F.Cu. gate, land route and the simplify pass all see the plan that way. A hand-written plan with a via, or copper on a face the board doesn't route, is a gate finding, and land route lands nothing from it. A hand-written layer change on a through-hole pad is cut there for you, also without a via.
  • route reports what it did. The rules-used line now shows the mode, for example layers B.Cu (project .kicad_dru), vias off (project .kicad_dru): a layer change only on a through-hole pad of the same net. The summary says how many connections closed on the single face and how many needed a jumper, then lists each jumper (jumper A: J1.1 -> R1.1 (10.7 mm)). That is your build list. The same numbers are in singleFace on the route outcome and in plan-route-report.json, and route quality counts the jumpers too.
  • Nothing is refused. A connection that can't close on one face comes back as a finding with suggestions:
    • allow F.Cu jumpers, with the exact keys to add;
    • move or rotate the named parts so their nets stop crossing;
    • widen the channel.
  • The rest of the flow follows the mode. In no-via mode, plane nets are routed as tracks, pour plans no stitching or thermal vias, and thermal-vias plans none and is never "short".

On your board

You may not need to add anything. When the spec says nothing about layers or vias, route reads the project's .kicad_dru:

  • (constraint disallow via) turns vias off;
  • disallow track on F.Cu leaves B.Cu only.

Each time it does this, it prints a NOTE. A rule with a condition is left to KiCad's DRC. So with your rules as they are, route routes B.Cu only, with no vias.

If some nets can't close on one face (I expect a few around the WeAct headers), add "routeLayers": ["B.Cu", "F.Cu"], "vias": false to allow wire jumpers. The spec wins over the .kicad_dru, and a note tells you that your disallow track on F.Cu will then make DRC report each jumper. Drop that rule if the jumpers are wires on purpose.

Your channel under the module (17 inner-row nets for 23 slots) is something the router can now use, because it routes on one face. I haven't routed your board itself, though, so please tell me how it goes.

How it was verified

  • Router unit tests:
    • B.Cu only: copper only on B.Cu, no via, and the forced crossing is reported;
    • vias off with jumpers: every layer change sits on a through-hole pad of its own net, and no via is ever stamped;
    • with vias off, the via move is possible only on the net's own through-hole pads;
    • a default spec routes byte for byte as before.
  • Unit tests for the .kicad_dru reading and the spec-or-rules resolution.
  • A new workflow suite, tests/single-layer-workflow.py. It uses a small all-through-hole strip where two nets must cross, with a fake bridge, and covers B.Cu only, the .kicad_dru deciding, and jumpers through route, gate and land route. It checks that no path changes layer inside it and that no via is sent. It also checks that hand-written via plans become findings and that pour plans no via.
  • Real KiCad DRC. The jumper plan on that strip passed with a disallow via rule: 0 errors, 0 unconnected, 0 vias.
  • Two-layer boards are unchanged. Routing our buck board with 0.2.58 and with this build gives byte-identical plans.
  • All existing tests pass: 669 unit tests and every workflow suite.

What is open

  • Each jumper is a run of F.Cu copper from pad to pad, and each run is listed separately. Jumpers are not yet grouped into a bill of wires with standard lengths.
  • The router counts a jumper as straight copper, so it has no idea about wire height or crossings on the component side.
  • Single-face routing on a dense board is harder than on two layers. If yours closes short, the findings name the parts to move, and I'd like to hear what they say.

Thank you also for writing up what you did instead (take route=ai). Without that I wouldn't have known how much this was missing.

Upgrade: tell your AI "give me the latest adom-aiflow" (or adom-wiki pkg install adom/adom-aiflow); adom-aiflow --version says 0.2.59.

John Lauer closed this ·

Log in to reply.