Files
pcb/hardware/board.kicad_dru
T
onur 791ca5c9fa Restack the generator and source board to eight layers, matching the deliverable
The routed deliverable has always been eight layers (JLC08161H-3313), but
generate_pcb.py still emitted ten and hardware/board.kicad_pcb was itself still
a ten-layer board. Source and generator agreed with each other and disagreed
with what is actually being fabricated, so running the documented regeneration
flow would have silently replaced the eight-layer design with a conflicting
ten-layer one and destroyed the routing.

generate_pcb.py now builds the real stack: SetCopperLayerCount(8), no L8_PWR /
L9_SIG, and the STACKUP string taken verbatim from board_routed.kicad_pcb - the
two are now byte-identical. Silkscreen reads JLC08161H-3313 8L instead of
JLC10161H 10L, and the title block follows.

The zone layout follows the same correction. With only one dedicated power
plane there is no third plane for the two big FPGA I/O rails, so they are
carried as islands on the routing layers that have room: VCC1V8 on L6 and
VCC3V3 on B.Cu, replacing the ten-layer arrangement of a VCC3V3 background on
L8 and a board-wide GND pour on B.Cu. The regenerated board now reproduces the
routed board's 26 copper zones exactly (the 27th, the F.Cu RF ground pour, is
added later by finalize_routing).

Also drops the In7_Cu/In8_Cu remnants from finalize_routing's RF_NO_DIGITAL
layer set and route_local_power's RF_REGION_LAYERS - those layers do not exist
on this board, so they were dead bits in a layer mask - and corrects the
JLC10161H reference in board.kicad_dru.

The placement check now reports 21 isolated_copper warnings rather than 17.
That is expected and explained in README.md: on an unrouted board the I/O-rail
islands on L6 and B.Cu have nothing joining them yet. The routed board still
reports zero.

Verified: regenerated board is 8 layers with a stackup identical to the routed
board, 312 footprints (311 + R108), TEMP keepouts at their corrected extents.
design_data assertions pass, ERC 0/0, routed board still 0 DRC violations,
0 unconnected, every net one copper group, parity unchanged at 402.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012KaTmCCxCEW4awsmXRNT1d
2026-09-01 13:26:37 +03:00

85 lines
4.5 KiB
Plaintext

(version 1)
# The edge-launch SMA copper must reach the routed board edge by construction.
(rule "J2 edge-launch lands"
(condition "A.Reference == 'J2' || B.Reference == 'J2'")
(constraint edge_clearance (min -0.01mm)))
# The manufacturer USB-C locating holes are 0.194 mm from grounded shell
# lands. This exception is internal to the qualified connector footprint.
(rule "USB-C locating-hole clearance"
(condition "A.Reference == 'J1' && B.Reference == 'J1'")
(constraint hole_clearance (min 0.19mm)))
# RZV0024A is an ultra-dense high-current package. Its adjusted corner lands
# and thermal zones meet the selected JLCPCB 0.10-mm copper rule.
(rule "TPS6287B RZV internal clearance"
(condition "A.Reference == 'U13' && B.Reference == 'U13'")
(constraint clearance (min 0.10mm)))
# Inside the Ti375 escape field the 0.8-mm ball pitch fixes the geometry: a
# 0.45-mm dogbone via between four lands leaves 0.1407 mm, and a 0.12-mm trace
# threading between two lands leaves 0.14 mm only if it stays exactly centred.
# This field therefore runs at the fabricator's 0.10-mm copper minimum, which
# is what the Default class uses across the rest of the board anyway. The rule
# only fires when one of the two objects belongs to U1, so it relaxes copper
# against the balls and nothing else: two tracks crossing the field still owe
# each other their own net classes' clearance, CLOCK's 0.15 mm included.
(rule "Ti375 0.8-mm BGA escape clearance"
(condition "A.Reference == 'U1' || B.Reference == 'U1'")
(constraint clearance (min 0.10mm)))
# JLC08161H-3313 L1/L2 geometry (3313 prepreg, 0.0994 mm, er 4.1). Net-class widths are routing defaults only;
# these rules make the controlled-impedance geometries actual DRC limits.
(rule "GNSS 50-ohm CPWG geometry"
(condition "A.NetClass == 'RF50_CPWG'")
(constraint track_width (min 0.164mm) (opt 0.164mm) (max 0.164mm)))
(rule "GNSS RF stays on top layer"
(condition "A.NetClass == 'RF50_CPWG' && A.Type == 'Track' && A.Layer != 'F.Cu'")
(constraint disallow track))
(rule "USB2 90-ohm differential geometry"
(condition "A.NetClass == 'USB90_DIFF'")
(constraint track_width (min 0.135mm) (opt 0.135mm) (max 0.135mm)))
# The TPD2EUSB30A clamp is a SOT-23 whose ground pad sits between its two
# channel pads, so the pair has to open up for about 1.5 mm to pass it. The
# 0.90-mm coupled limit still applies over the whole rest of the run.
(rule "USB2 coupled gap"
(condition "A.NetClass == 'USB90_DIFF' && AB.isCoupledDiffPair() && !A.intersectsArea('USB_ESD_BREAKOUT')")
(constraint diff_pair_gap (min 0.15mm) (opt 0.15mm) (max 0.90mm)))
# The 50-ohm line owns the band it runs in on every layer under F.Cu, so no
# digital route can pass beneath or immediately beside it. Ground stitching
# and the MAX2771's own supply drops still have to reach it, so the disallow is
# scoped to the signal classes rather than made a plain keepout. The RF line
# itself is on F.Cu, which the area does not cover, and the L2/L7 GND planes
# stay solid because nothing routes on them at all -- the actual RF return path
# is guaranteed by those planes, not by this area.
#
# The area covers y <= 43.7, which is the same corridor generate_pcb.py
# reserves as TEMP_RF_LINE and clears the RF copper (y <= 42.6) by 1.1 mm. It
# deliberately stops short of the MAX2771's digital pin rows at y = 44.5 ..
# 47.4: those pins have to reach the FPGA, and with the area extended over them
# every one of them was forced onto F.Cu alone, which does not fit.
(rule "No digital copper under the GNSS front end"
(condition "A.intersectsArea('RF_NO_DIGITAL') && A.NetClass != 'POWER'")
(constraint disallow track via))
(rule "Clock trunk width outside Ti375 escape"
(condition "A.NetClass == 'CLOCK' && A.Type == 'Track' && !A.insideCourtyard('U1')")
(constraint track_width (min 0.15mm)))
# A 0.8-mm ball pitch leaves a 0.35-mm channel between two adjacent 0.45-mm
# dogbone vias. A 0.10-mm trace fits that channel at the escape field's
# 0.10-mm copper minimum but not at CLOCK's 0.15 mm, so a clock ball could not
# be escaped from the package at all. Inside the field -- and only where BOTH
# objects are inside it -- copper therefore runs at the same 0.10 mm the rest
# of this field already uses. The clock trunk keeps its 0.15 mm the moment it
# leaves U1's courtyard, which is where coupling to neighbours actually costs
# something.
(rule "Ti375 escape field copper clearance"
(condition "A.insideCourtyard('U1') && B.insideCourtyard('U1')")
(constraint clearance (min 0.10mm)))