Mainboard: routed, DRC 0, not the print candidate

/ DINO / updated 2026-09-14 / from dino-homebrew @ 29bf1ad pcb kicad power peripheral-bus dma interrupts

The last entry ended with a decision. The breadboard is done, next is a 2-layer mainboard, power first. This one is the four days after that. There is a routed layout in KiCad with 0 DRC errors. It is not the one I am going to print, and the second half of this entry is why.

What I tried

Making the schematic one netlist. The twelve sheets had never actually been connected. Every cross-sheet label was sheet-local, so KiCad saw 122 names split per sheet, ERC reported 90 undriven inputs whose driver sat one sheet over, and a PCB ratsnest stopped at every sheet boundary. Converting them to global labels was the first job, and the proof was mechanical: the netlist after equals a name-join of the netlist before plus the fifteen CW alias pairs, pin for pin. ERC went from 477 to 2, and the real fixes underneath the label churn were small: ROM data pins typed as inputs instead of tri-state, a ‘382 ground pin typed as power out, four orphan symbols rehomed to a project library. Every symbol got a stock through-hole footprint. The draft PCB that fell out was 198 footprints dropped in sheet rows on a 374 x 524 mm outline with nothing routed.

The power sheet. I am not designing a supply. A picoPSU (24-pin ATX plug, 12 V brick behind it) gives +5 V at 6-8 A regulated to 1.5%, plus +3.3 V, +12 V, -12 V, PS_ON and PWR_OK for free. No -5 V exists on any ATX unit made since 2003, so none is drawn. The sheet is J9 (the 24-pin Mini-Fit Jr the unit snaps into), the rails to their power nets, PS_ON# to a 2-pin header. That closed the last power_pin_not_driven warning. Until this sheet the schematic had no supply on +5 V or GND at all.

The card bus. Eight 2x25 0.1-inch card-edge sockets on the core, one 50-pin table that generates the symbol (socket and edge flavours), the finger footprint and a placeholder socket footprint. Grounds bracket both buses, W0-7 on the front row, M0-15 on the back, CLK through a ‘04 buffer so no card ever sees the ‘74 output raw, and five pins named and reserved with no copper behind them: ~IRQ, ~NMI, ~DMARQ, ~DMAAK, RDY. The DIP switch, which has been “card zero” since phase E, physically left the core. Its sheet became its own project, dino_io, with an edge connector. The proof for that split is that the core and card netlists joined through the slot-0 pin numbers partition every pre-existing pin exactly as the core did before the split. ERC 0/0 on both projects.

Placement. The sheet-row draft was a bad prior. Sheets are drawn by function; nets don’t care. The first tell was PC0-15 spanning 250 mm because the two ‘245s that read the PC back onto the bus (U72, U73, phase C) sat on the MDR sheet, two rows away from the counters. So: a DIP cell grid, 14 columns at 15.24 mm and six rows sized by the tallest package in each, and simulated annealing over the cell assignment with the cost function half-perimeter wire length, CLK weighted 6x, ~CLK 4x, CLK_B 6x, the oscillator-to-divider net 6x, the reset RC node 5x. Unweighted HPWL went from 41,704 mm (sheet rows) to 32,395 mm. Every 0.1 uF sits 4 mm pad-to-pad above its chip’s VCC pin by construction. Then a hand pass: the eight OB LEDs and their 330 R resistors came off the board entirely and became a 1x8 header, J11 (“here’s an OUT, do with it what you will”), which frees the LED design and about 40 mm of board; the W park resistors moved to the top of the slot column, which is the bus terminus; reset RC and the PSU connector to the bottom right.

The slot pitch is 20.32 mm, 0.8 inch, ISA spacing, after starting at 0.7. And I had the card overhang rule wrong in the first cut of the keepout test. I forbade any part in the strip a seated card sweeps. Apple II and 5150 cards happily pass over DIPs. Only part HEIGHT matters under a card, so the test now checks that nothing taller than a DIP (the ATX connector, the electrolytic, the pushbutton, pin headers) sits in a card’s envelope. That test, test_pcb_envelope.py, is the keepout; it reads the saved board file and it is the only thing enforcing the mechanics.

Routing. Netclasses typed into KiCad’s Board Setup (Default 0.25/0.25 mm, CLK 0.3 mm on CLK/~CLK/CLK_B, PWR 1.0 mm on the four rails), typed there because KiCad owns that file and discards anything written behind its back on the next save. DSN exported with KiCad’s own pcbnew Python with the project loaded so the classes travel. Freerouting 2.4.1, headless, six runs:

run  placement    GND       result (KiCad's count, pours on)     time
1    sheet rows   routed    13 open, 0 short, 10,945 tracks     58 min
2    same         routed    reached 1 open, SAVED 17            58 min
4    annealed     stripped  0 signal open, 67 GND open, 3 short  6 min
5    run 4 seeded routed    20 GND open                         15 min
6    annealed     routed    0 open, 2 short, 7,708 tracks        9 min  <- used

Run 6 is the board. GND pours on both layers after the SES import, 0.25 mm clearance / 0.2 mm minimum width.

What I measured

What broke or surprised me

Freerouting saves whatever pass it was on when you stop it, and that is rarely the best one. Run 2 reached 1 unrouted at pass 28-38 and saved 17, because stopping it mid-rip-up strands whatever it had torn out. Its unrouted count is not KiCad’s either (run 1: 13 vs 2). Every short it made had the same shape: a 0.7 mm hairpin at a DIP pad exit looping through the neighbour’s exit. Fix is delete the hairpin, lay one straight segment. Five by hand.

The starved thermals were a per-layer artefact and I stepped them one at a time on purpose, as practice for the re-roll. A pad with four spokes on F.Cu and one on B.Cu still flags B.Cu. The zones had been at 0.3/0.25: the pour needed 0.85 mm of channel where the router had spaced traces for a 0.75 mm track, so it was locked out of exactly the channels it needed. Dropping to 0.25/0.2 cleared most. The rest, three moves in the order to try them: a GND jump (short track from the pad to a via on the nearest bridge on the other layer; decouplers want the short loop anyway, and a via on a through-hole board is the same plated hole as the 1,900 DIP pins); a one-layer rule area (keep-out copper fill) over a sliver the pour reaches but nothing else can, which kills the island on that layer and leaves the other layer’s spokes alone; and NOT pad connection “None”, which drops the zone on both layers and leaves a socket ground pin with no track at all unconnected. Why “Remove islands: Always” never helped: a pocket hanging off a pad’s spokes counts as connected to the pad, so the filler keeps it and DRC then reports the spokes as leading to an island, so the check is circular. 43 jump vias and one rule area later: 0 errors.

The breadboard’s power section is undocumented. When I went looking for what bulk capacitance to draw, the answer is that there are electrolytics all over the breadboards, placed sporadically and optimistically, none in the schematic. So the breadboard only shows that a power section WITH bulks works, and the schematic has none. Same rule as the 1 uF/10 uF reset capacitor in phase G: a component value is copper too.

Two additions the board has to carry that have never been built. The proposals for DMA (an 8257 on the core, fly-by, HOLD granted only at instruction boundaries, no microcode burn) and a maskable interrupt (8080-style opcode injection at the fetch, POPF/PUSHF as prerequisites, one three-ROM burn) were written against the netlist this week. Both need gates on core copper (the M driver enables, the RAM strobes, the bridge controls, the T-counter clear) that no card can reach. So Rev A either carries the hooks socketed and inert, or Rev B is a re-layout. Decision: carry them. The daughterboard option was considered and rejected for the same reason.

Re-reading that plan found a hole. “Socket empty = today’s machine” is true for the 8257 and its buffers (every output pulled to its idle level) and FALSE for the override multiplexers. An empty ‘157 socket in the ~RAM_OE_G path leaves the path open. Three in-path ‘157s and two lifted pins on U61 are where all the design risk is; the other ten packages are decoder-inert or strap-inert.

Where this lands

The rule for printing is that every unknown on the board gets a no-respin escape. A strap, a jumper, a shunt plug, a socket swap, a value change, a slower can. A respin is cheap at the fab and costs about a month.

So the DRC-0 board is a proof that the machine routes on two layers at this size. The board that prints is the re-roll of it with:

The first milestone on Rev A is the board SHUNTED with every hook socket empty, rails at the test points under 50 mV, PROG_isa 0xB4, PROG_isasoak 0x00: the breadboard machine on a ground plane. Everything above that has a plug, a jumper or a socket as its escape, and none of it can force a respin.

Next

Putting the machine down for a bit. Pick-up point is written, the DRC-0 board and the placer scripts are committed at 29bf1ad. When it comes back up, the first thing is one walk of the boards with the bulk-cap table and the part-markings sheet in hand, then the two bench tasks, then the picoPSU. The schematic work (power section, U80-U99, the plugs and jumpers, J12, CLKIN, holes) waits on nothing but the cap values.