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
This commit is contained in:
onur
2026-09-01 13:26:37 +03:00
parent 4fa38e35bd
commit 791ca5c9fa
11 changed files with 61270 additions and 62320 deletions
+6 -2
View File
@@ -105,9 +105,13 @@ kicad-cli pcb drc --severity-all \
-o outputs/drc_final_placement.rpt hardware/board.kicad_pcb
```
Güncel placement kontrolünde ERC 0 ihlaldir. DRC'deki 12 `isolated_copper`
Güncel placement kontrolünde ERC 0 ihlaldir. DRC'deki 21 `isolated_copper`
uyarısı ve 499 açık bağlantı, kartın henüz route edilmemiş olmasından
kaynaklanır; üretim öncesi kabul edilebilir final sonuç değildir.
kaynaklanır; üretim öncesi kabul edilebilir final sonuç değildir. Sayının
yüksek olmasının nedeni, sekiz katmanda iki büyük I/O rayının kendi plane
katmanı olmaması ve L6/B.Cu üzerinde ada olarak taşınmasıdır; route edilmemiş
kartta bu adaları henüz hiçbir şey birleştirmez. Route edilmiş kartta
`isolated_copper` sayısı sıfırdır.
## Kaynakların önceliği
+1 -1
View File
@@ -29,7 +29,7 @@
(condition "A.Reference == 'U1' || B.Reference == 'U1'")
(constraint clearance (min 0.10mm)))
# JLC10161H L1/L2 geometry (F->L2 prepreg unchanged from the 8L build). Net-class widths are routing defaults only;
# 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'")
+58801 -60541
View File
File diff suppressed because it is too large Load Diff
+574 -2
View File
@@ -1,4 +1,576 @@
{
"meta": { "filename": "board.kicad_pro", "version": 1 },
"erc": {}, "net_settings": {}, "text_variables": {}
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"apply_defaults_to_fp_barcodes": false,
"apply_defaults_to_fp_dimensions": false,
"apply_defaults_to_fp_fields": false,
"apply_defaults_to_fp_shapes": false,
"apply_defaults_to_fp_text": false,
"board_outline_line_width": 0.05,
"copper_line_width": 0.2,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.05,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 0,
"units_format": 0
},
"fab_line_width": 0.1,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.1,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.8,
"height": 1.27,
"width": 1.27
},
"silk_line_width": 0.1,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.1,
"silk_text_upright": false,
"zones": {
"border_display_style": 2,
"border_hatch_pitch": 0.5,
"corner_radius": 0.0,
"corner_smoothing": 0,
"fill_mode": 0,
"hatch_gap": 1.5,
"hatch_orientation": 0.0,
"hatch_smoothing_level": 0,
"hatch_smoothing_value": 0.1,
"hatch_thickness": 1.0,
"min_clearance": 0.5,
"min_island_area": 10.0,
"min_thickness": 0.25,
"pad_connection": 1,
"remove_islands": 0,
"thermal_relief_gap": 0.5,
"thermal_relief_spoke_width": 0.5
}
},
"diff_pair_dimensions": [],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"creepage": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_filters_mismatch": "ignore",
"footprint_symbol_field_mismatch": "warning",
"footprint_symbol_mismatch": "warning",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_to_hole": "warning",
"holes_co_located": "warning",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"mirrored_text_on_front_layer": "warning",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"missing_tuning_profile": "warning",
"net_conflict": "warning",
"nonmirrored_text_on_back_layer": "warning",
"npth_inside_courtyard": "error",
"padstack": "warning",
"pth_inside_courtyard": "error",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_on_edge_cuts": "error",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_angle": "error",
"track_dangling": "warning",
"track_not_centered_on_via": "ignore",
"track_on_post_machined_layer": "error",
"track_segment_length": "error",
"track_width": "error",
"tracks_crossing": "error",
"tuning_profile_track_geometries": "ignore",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rules": {
"max_error": 0.005,
"min_clearance": 0.1,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.25,
"min_groove_width": 0.0,
"min_hole_clearance": 0.19,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.2,
"min_microvia_drill": 0.1,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.8,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.225,
"min_track_width": 0.1,
"min_via_annular_width": 0.1,
"min_via_diameter": 0.45,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_onpthpad": true,
"td_onroundshapesonly": false,
"td_onsmdpad": true,
"td_ontrackend": false,
"td_onvia": true
}
],
"teardrop_parameters": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_on_pad_in_zone": false,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [],
"tuning_pattern_settings": {
"diff_pair_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 1.0
},
"diff_pair_skew_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
},
"single_track_defaults": {
"corner_radius_percentage": 80,
"corner_style": 1,
"max_amplitude": 1.0,
"min_amplitude": 0.2,
"single_sided": false,
"spacing": 0.6
}
},
"via_dimensions": [],
"zones_allow_external_fillets": false
},
"ipc2581": {
"bom_rev": "",
"dist": "",
"distpn": "",
"internal_id": "",
"mfg": "",
"mpn": "",
"sch_revision": ""
},
"layer_pairs": [],
"layer_presets": [],
"viewports": []
},
"boards": [],
"component_class_settings": {
"assignments": [],
"meta": {
"version": 0
},
"sheet_component_classes": {
"enabled": false
}
},
"cvpcb": {
"equivalence_files": []
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "board.kicad_pro",
"version": 3
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.1,
"diff_pair_gap": 0.15,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.135,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": 2147483647,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.12,
"tuning_profile": "",
"via_diameter": 0.45,
"via_drill": 0.225,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.1,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "BGA_ESCAPE",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": -1,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.1,
"tuning_profile": "",
"via_diameter": 0.45,
"via_drill": 0.225,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.15,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "CLOCK",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": -1,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.15,
"tuning_profile": "",
"via_diameter": 0.45,
"via_drill": 0.225,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.1,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "POWER",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": -1,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.4,
"tuning_profile": "",
"via_diameter": 0.45,
"via_drill": 0.225,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "RF50_CPWG",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": -1,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.164,
"tuning_profile": "",
"via_diameter": 0.45,
"via_drill": 0.225,
"wire_width": 6
},
{
"bus_width": 12,
"clearance": 0.15,
"diff_pair_gap": 0.15,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.135,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "USB90_DIFF",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"priority": -1,
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.135,
"tuning_profile": "",
"via_diameter": 0.45,
"via_drill": 0.225,
"wire_width": 6
}
],
"meta": {
"version": 5
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": [
{
"netclass": "RF50_CPWG",
"pattern": "RF_SMA"
},
{
"netclass": "RF50_CPWG",
"pattern": "RF_PRE_SAW"
},
{
"netclass": "RF50_CPWG",
"pattern": "RF_POST_SAW"
},
{
"netclass": "RF50_CPWG",
"pattern": "MAX_MIXIN_HI"
},
{
"netclass": "USB90_DIFF",
"pattern": "USB_D_P"
},
{
"netclass": "USB90_DIFF",
"pattern": "USB_D_N"
},
{
"netclass": "USB90_DIFF",
"pattern": "FTDI_D_P"
},
{
"netclass": "USB90_DIFF",
"pattern": "FTDI_D_N"
},
{
"netclass": "CLOCK",
"pattern": "MAX_CLKOUT*"
},
{
"netclass": "CLOCK",
"pattern": "MAX_SCLK*"
},
{
"netclass": "CLOCK",
"pattern": "MAX_ADC_CLKIN"
},
{
"netclass": "CLOCK",
"pattern": "QSPI_SCLK"
},
{
"netclass": "CLOCK",
"pattern": "CLK_40M_FPGA"
},
{
"netclass": "CLOCK",
"pattern": "OSC40_OUT"
},
{
"netclass": "CLOCK",
"pattern": "TCXO16_OUT"
},
{
"netclass": "CLOCK",
"pattern": "FTDI_OSC12"
},
{
"netclass": "CLOCK",
"pattern": "FTDI_OSCI"
},
{
"netclass": "CLOCK",
"pattern": "IMU_SCK*"
},
{
"netclass": "CLOCK",
"pattern": "*TCK*"
},
{
"netclass": "POWER",
"pattern": "VCC*"
},
{
"netclass": "POWER",
"pattern": "SYS5"
},
{
"netclass": "POWER",
"pattern": "VBUS_RAW"
},
{
"netclass": "POWER",
"pattern": "USB5_PROT"
},
{
"netclass": "POWER",
"pattern": "EXT5_*"
},
{
"netclass": "POWER",
"pattern": "GND"
},
{
"netclass": "POWER",
"pattern": "CORE_SW"
},
{
"netclass": "POWER",
"pattern": "BUCK1V8_SW"
},
{
"netclass": "POWER",
"pattern": "BUCK3V3_SW"
},
{
"netclass": "POWER",
"pattern": "ANT_BIAS_SEL"
},
{
"netclass": "POWER",
"pattern": "ANT_BIAS_SW"
},
{
"netclass": "POWER",
"pattern": "FTDI_1V8"
},
{
"netclass": "POWER",
"pattern": "FTDI_VPHY"
},
{
"netclass": "POWER",
"pattern": "FTDI_VPLL"
},
{
"netclass": "POWER",
"pattern": "IMU_3V3"
},
{
"netclass": "POWER",
"pattern": "IMU_VREGA2"
},
{
"netclass": "POWER",
"pattern": "IMU_VREGD"
},
{
"netclass": "POWER",
"pattern": "USB_SHIELD"
},
{
"netclass": "BGA_ESCAPE",
"pattern": "EXP_*"
},
{
"netclass": "BGA_ESCAPE",
"pattern": "PMOD*"
}
]
},
"pcbnew": {
"last_paths": {
"idf": "",
"netlist": "",
"plot": "",
"specctra_dsn": "",
"step": "",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"bus_aliases": {},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"top_level_sheets": []
},
"sheets": [],
"text_variables": {},
"tuning_profiles": {
"meta": {
"version": 0
},
"tuning_profiles_impedance_geometric": []
}
}
+1 -1
View File
@@ -29,7 +29,7 @@
(condition "A.Reference == 'U1' || B.Reference == 'U1'")
(constraint clearance (min 0.10mm)))
# JLC10161H L1/L2 geometry (F->L2 prepreg unchanged from the 8L build). Net-class widths are routing defaults only;
# 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'")
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,5 +1,5 @@
** Drc report for board_routed.kicad_pcb **
** Created on 2026-09-01T12:55:21 **
** Created on 2026-09-01T13:26:20 **
** Report includes: Errors, Warnings, Exclusions **
** Found 0 DRC violations **
+1 -1
View File
@@ -1,4 +1,4 @@
ERC report (2026-09-01T12:27:27, Encoding UTF8)
ERC report (2026-09-01T13:26:17, Encoding UTF8)
Report includes: Errors, Warnings, Exclusions
***** Sheet /
+2 -1
View File
@@ -408,7 +408,8 @@ def add_named_rule_areas(board: pcbnew.BOARD) -> None:
under the RF path, which is exactly what an unrouted plane layer gives.
"""
below = pcbnew.LSET()
for layer in (pcbnew.In2_Cu, pcbnew.In3_Cu, pcbnew.In4_Cu, pcbnew.In5_Cu, pcbnew.In7_Cu, pcbnew.In8_Cu, pcbnew.B_Cu):
for layer in (pcbnew.In2_Cu, pcbnew.In3_Cu, pcbnew.In4_Cu, pcbnew.In5_Cu,
pcbnew.B_Cu):
below.AddLayer(layer)
top = pcbnew.LSET()
top.AddLayer(pcbnew.F_Cu)
+171 -70
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Create the placed ten-layer KiCad PCB from the audited design database.
"""Create the placed eight-layer KiCad PCB from the audited design database.
The script is deterministic so placement, net assignments, stackup, and rules
can be regenerated and independently audited. Routing uses the staged
@@ -338,31 +338,28 @@ def configure_rules(board: pcbnew.BOARD) -> None:
def add_stackup(board: pcbnew.BOARD) -> None:
# Ten-layer 1.6 mm build. The Ti375 north-east escape carries ~54 signals
# (bank 2E PMOD3/4 + the HVIO banks' PMOD1/2, the MAX2771 interface, both
# JTAG chains, QSPI, IMU) out of a ~10 mm span; five routing layers gave
# ~60 escape slots for 54 nets and the last handful never fit. Six routing
# layers (F, L3, L5, L7, L9, B) restore the margin. L6 is the single power
# plane - every rail lives there as an island - so it is the only layer the
# escape cannot use, and it is not needed there. L2/L4/L8 are solid GND;
# the outer F->L2 and L9->B prepreg is unchanged 3313 (0.0994 mm), so the
# F.Cu 50-ohm CPWG and 90-ohm USB geometries carry over untouched.
board.SetCopperLayerCount(10)
# Eight-layer 1.6 mm build, JLC08161H-3313. L2 and L7 are the two solid
# GND reference planes and carry no routing; L4 is the only dedicated power
# plane. That leaves five routable layers - F.Cu, L3, L5, L6 and B.Cu -
# which is what the Ti375 escape, the RF front end and the USB pair are
# built around. The two big FPGA I/O rails have no plane of their own and
# live as islands on L6 (VCC1V8) and B.Cu (VCC3V3). The outer F->L2 and
# L7->B prepreg is 3313 (0.0994 mm), which is what sets the F.Cu 50-ohm
# CPWG and 90-ohm USB geometries.
board.SetCopperLayerCount(8)
names = {
pcbnew.In1_Cu: "L2_GND", pcbnew.In2_Cu: "L3_SIG",
pcbnew.In3_Cu: "L4_PWR", pcbnew.In4_Cu: "L5_SIG",
pcbnew.In5_Cu: "L6_SIG", pcbnew.In6_Cu: "L7_GND",
pcbnew.In7_Cu: "L8_PWR", pcbnew.In8_Cu: "L9_SIG",
}
for layer, name in names.items():
board.SetLayerName(layer, name)
# L2/L7 solid GND; L4 the core + front-end power plane; L8 the two big FPGA
# I/O rails (VCC3V3 background, VCC1V8 carved). Both power planes sit
# between GND and a routing layer, and neither is a layer the BGA escape
# needs, so all six routing layers (F, L3, L5, L6, L9, B) stay clean.
for layer in (pcbnew.In1_Cu, pcbnew.In3_Cu, pcbnew.In6_Cu, pcbnew.In7_Cu):
# L2/L7 solid GND, L4 the core + front-end power plane. The power plane
# sits between GND and a routing layer and is not a layer the BGA escape
# needs, so all five routing layers (F, L3, L5, L6, B) stay usable.
for layer in (pcbnew.In1_Cu, pcbnew.In3_Cu, pcbnew.In6_Cu):
board.SetLayerType(layer, pcbnew.LT_POWER)
for layer in (pcbnew.In2_Cu, pcbnew.In4_Cu, pcbnew.In5_Cu, pcbnew.In8_Cu):
for layer in (pcbnew.In2_Cu, pcbnew.In4_Cu, pcbnew.In5_Cu):
board.SetLayerType(layer, pcbnew.LT_SIGNAL)
@@ -532,18 +529,18 @@ def add_zone(board: pcbnew.BOARD, net_name: str, layer: int,
def add_power_planes(board: pcbnew.BOARD) -> None:
"""Ten-layer power: solid GND on L2/L7 and B.Cu, the core + front-end rails
on the L4 plane, and the two big FPGA I/O rails on the L8 plane.
"""Eight-layer power: solid GND on L2 and L7, the core and front-end rails
on the L4 plane, and the two big FPGA I/O rails as islands on L6 (VCC1V8)
and B.Cu (VCC3V3).
Both power planes sit between a GND plane and a routing layer. Neither is
a layer the Ti375 escape needs, so all six routing layers (F, L3, L5, L6,
L9, B) stay clean and the I/O-rail pads stitch straight into L8 instead of
needing a via to a buried background.
There is no third plane layer to give the I/O rails, so they share the two
routing layers with the most free area. That is why those rails end up
fragmented by signal routing and why the residual-connection stage has to
treat a foreign pour as an obstacle.
"""
full = (X0 + 0.50, Y0 + 0.50, X1 - 0.50, Y1 - 0.50)
for layer in (pcbnew.In1_Cu, pcbnew.In6_Cu):
add_zone(board, "GND", layer, full, clearance=PLANE_CLEARANCE)
add_zone(board, "GND", pcbnew.B_Cu, full, priority=0, clearance=0.20)
# L4 (In3_Cu): core plus the 5-V input, the always-on auxiliary rail and
# the four 3.0-V front-end rails. Islands follow the functional floorplan.
@@ -571,19 +568,29 @@ def add_power_planes(board: pcbnew.BOARD) -> None:
add_zone(board, net_name, pcbnew.In3_Cu, bounds, priority,
clearance=PLANE_CLEARANCE if net_name == "VCC0V95" else 0.20)
# L8 (In7_Cu): the two big FPGA I/O rails. VCC3V3 is the board-wide
# background; VCC1V8 is carved out under the BGA config banks, along the
# 1.8-V PMOD headers, over the configuration flash and at its converter.
l8 = (
("VCC3V3", full, 1),
("VCC1V8", (99.00, 39.50, 122.00, 92.00), 10),
("VCC1V8", (115.00, 20.50, 174.50, 40.50), 11),
("VCC1V8", (94.00, 45.00, 106.00, 56.00), 12),
("VCC1V8", (108.50, 91.50, 127.00, 106.00), 13),
("VCC3V3", (128.00, 91.50, 141.00, 106.00), 20),
# The two big FPGA I/O rails do not get a plane layer of their own on an
# eight-layer board, so they are carried as islands on the two routing
# layers that have the room: VCC1V8 on L6 over its own loads (the BGA
# config banks, the 1.8-V PMOD headers and the configuration flash), and
# VCC3V3 on B.Cu under the three regions that draw it. Both layers still
# route signals around the islands, which is why the residual-connection
# stage refuses to cross a foreign pour.
l6 = (
("VCC1V8", (93.50, 44.00, 123.00, 92.00), 10),
("VCC1V8", (108.50, 91.50, 128.00, 106.00), 11),
("VCC1V8", (124.00, 20.50, 147.00, 28.50), 12),
)
for net_name, bounds, priority in l8:
add_zone(board, net_name, pcbnew.In7_Cu, bounds, priority,
for net_name, bounds, priority in l6:
add_zone(board, net_name, pcbnew.In5_Cu, bounds, priority,
clearance=PLANE_CLEARANCE)
bcu = (
("VCC3V3", (51.00, 45.00, 76.00, 92.00), 1),
("VCC3V3", (88.50, 22.50, 124.00, 86.00), 2),
("VCC3V3", (128.00, 56.50, 153.00, 104.50), 3),
)
for net_name, bounds, priority in bcu:
add_zone(board, net_name, pcbnew.B_Cu, bounds, priority,
clearance=PLANE_CLEARANCE)
@@ -619,9 +626,9 @@ def add_temporary_critical_keepouts(board: pcbnew.BOARD) -> None:
copper, so nothing here reaches the manufactured board.
"""
# Every copper layer under F.Cu except the solid GND planes (L2/L7): the
# routing layers L3/L5/L6/L9/B and the L4/L8 power planes.
# routing layers L3/L5/L6/B and the L4 power plane.
inner = (pcbnew.In2_Cu, pcbnew.In3_Cu, pcbnew.In4_Cu, pcbnew.In5_Cu,
pcbnew.In7_Cu, pcbnew.In8_Cu, pcbnew.B_Cu)
pcbnew.B_Cu)
below = pcbnew.LSET()
for layer in inner:
below.AddLayer(layer)
@@ -651,7 +658,7 @@ def add_temporary_critical_keepouts(board: pcbnew.BOARD) -> None:
def add_documentation_graphics(board: pcbnew.BOARD) -> None:
add_text(board, "Ti375 GNSS / SCH16T", 105, 33.0, 1.1)
add_text(board, "REV A1 JLC10161H 10L", 105, 35.2, 0.8)
add_text(board, "REV A1 JLC08161H-3313 8L", 105, 35.2, 0.8)
add_text(board, "USB-C PWR", 37, 30, 0.8)
add_text(board, "EXT_5V", 28, 108.0, 0.9)
add_text(board, "GPS L1/L1C ACTIVE", 162, 34, 0.9)
@@ -668,35 +675,129 @@ def add_documentation_graphics(board: pcbnew.BOARD) -> None:
add_text(board, "PMOD4 1V8", 132, 30, 0.8)
STACKUP = '''\t\t(stackup
\t\t\t(layer "F.SilkS" (type "Top Silk Screen"))
\t\t\t(layer "F.Paste" (type "Top Solder Paste"))
\t\t\t(layer "F.Mask" (type "Top Solder Mask") (thickness 0.01))
\t\t\t(layer "F.Cu" (type "copper") (thickness 0.035))
\t\t\t(layer "dielectric 1" (type "prepreg") (thickness 0.0994) (material "JLC 3313") (epsilon_r 4.1) (loss_tangent 0.02))
\t\t\t(layer "L2_GND" (type "copper") (thickness 0.0152))
\t\t\t(layer "dielectric 2" (type "core") (thickness 0.19) (material "JLC FR-4 core") (epsilon_r 4.4) (loss_tangent 0.02))
\t\t\t(layer "L3_SIG" (type "copper") (thickness 0.0152))
\t\t\t(layer "dielectric 3" (type "prepreg") (thickness 0.15) (material "JLC 2116") (epsilon_r 4.16) (loss_tangent 0.02))
\t\t\t(layer "L4_PWR" (type "copper") (thickness 0.0152))
\t\t\t(layer "dielectric 4" (type "core") (thickness 0.19) (material "JLC FR-4 core") (epsilon_r 4.4) (loss_tangent 0.02))
\t\t\t(layer "L5_SIG" (type "copper") (thickness 0.0152))
\t\t\t(layer "dielectric 5" (type "prepreg") (thickness 0.15) (material "JLC 2116") (epsilon_r 4.16) (loss_tangent 0.02))
\t\t\t(layer "L6_SIG" (type "copper") (thickness 0.0152))
\t\t\t(layer "dielectric 6" (type "core") (thickness 0.19) (material "JLC FR-4 core") (epsilon_r 4.4) (loss_tangent 0.02))
\t\t\t(layer "L7_GND" (type "copper") (thickness 0.0152))
\t\t\t(layer "dielectric 7" (type "prepreg") (thickness 0.15) (material "JLC 2116") (epsilon_r 4.16) (loss_tangent 0.02))
\t\t\t(layer "L8_PWR" (type "copper") (thickness 0.0152))
\t\t\t(layer "dielectric 8" (type "core") (thickness 0.19) (material "JLC FR-4 core") (epsilon_r 4.4) (loss_tangent 0.02))
\t\t\t(layer "L9_SIG" (type "copper") (thickness 0.0152))
\t\t\t(layer "dielectric 9" (type "prepreg") (thickness 0.0994) (material "JLC 3313") (epsilon_r 4.1) (loss_tangent 0.02))
\t\t\t(layer "B.Cu" (type "copper") (thickness 0.035))
\t\t\t(layer "B.Mask" (type "Bottom Solder Mask") (thickness 0.01))
\t\t\t(layer "B.Paste" (type "Bottom Solder Paste"))
\t\t\t(layer "B.SilkS" (type "Bottom Silk Screen"))
\t\t\t(copper_finish "ENIG")
\t\t\t(dielectric_constraints yes)
\t\t)'''
STACKUP = ''' (stackup
(layer "F.SilkS"
(type "Top Silk Screen")
)
(layer "F.Paste"
(type "Top Solder Paste")
)
(layer "F.Mask"
(type "Top Solder Mask")
(thickness 0.01)
)
(layer "F.Cu"
(type "copper")
(thickness 0.035)
)
(layer "dielectric 1"
(type "prepreg")
(thickness 0.0994)
(material "JLC 3313")
(epsilon_r 4.1)
(loss_tangent 0.02)
)
(layer "dielectric 2"
(type "copper")
(thickness 0.0152)
(material "FR4")
(epsilon_r 4.5)
(loss_tangent 0.02)
)
(layer "dielectric 3"
(type "core")
(thickness 0.1)
(material "JLC FR-4 core")
(epsilon_r 4.36)
(loss_tangent 0.02)
)
(layer "dielectric 4"
(type "copper")
(thickness 0.0152)
(material "FR4")
(epsilon_r 4.5)
(loss_tangent 0.02)
)
(layer "dielectric 5"
(type "prepreg")
(thickness 0.3568)
(material "JLC 3x2116")
(epsilon_r 4.16)
(loss_tangent 0.02)
)
(layer "dielectric 6"
(type "copper")
(thickness 0.0152)
(material "FR4")
(epsilon_r 4.5)
(loss_tangent 0.02)
)
(layer "dielectric 7"
(type "core")
(thickness 0.3)
(material "JLC FR-4 core")
(epsilon_r 4.41)
(loss_tangent 0.02)
)
(layer "dielectric 8"
(type "copper")
(thickness 0.0152)
(material "FR4")
(epsilon_r 4.5)
(loss_tangent 0.02)
)
(layer "dielectric 9"
(type "prepreg")
(thickness 0.3568)
(material "JLC 3x2116")
(epsilon_r 4.16)
(loss_tangent 0.02)
)
(layer "dielectric 10"
(type "copper")
(thickness 0.0152)
(material "FR4")
(epsilon_r 4.5)
(loss_tangent 0.02)
)
(layer "dielectric 11"
(type "core")
(thickness 0.1)
(material "JLC FR-4 core")
(epsilon_r 4.36)
(loss_tangent 0.02)
)
(layer "dielectric 12"
(type "copper")
(thickness 0.0152)
(material "FR4")
(epsilon_r 4.5)
(loss_tangent 0.02)
)
(layer "dielectric 13"
(type "prepreg")
(thickness 0.0994)
(material "JLC 3313")
(epsilon_r 4.1)
(loss_tangent 0.02)
)
(layer "B.Cu"
(type "copper")
(thickness 0.035)
)
(layer "B.Mask"
(type "Bottom Solder Mask")
(thickness 0.01)
)
(layer "B.Paste"
(type "Bottom Solder Paste")
)
(layer "B.SilkS"
(type "Bottom Silk Screen")
)
(copper_finish "ENIG")
(dielectric_constraints yes)
)'''
def inject_stackup(path: Path) -> None:
@@ -723,7 +824,7 @@ def main() -> None:
title.SetRevision("A0")
title.SetCompany("JLCPCB manufacturing design")
title.SetComment(0, "J7 USB-C 5 V or EXT_5V, muxed, never both; J1 debug-only, no back-feed")
title.SetComment(1, "JLC10161H exact ten-layer construction; F->L2 and L9->B keep 3313 for F.Cu impedance")
title.SetComment(1, "JLC08161H-3313 exact eight-layer construction; F->L2 and L7->B keep 3313 for F.Cu impedance")
if not pcbnew.ZONE_FILLER(board).Fill(board.Zones()):
raise RuntimeError("Initial inner-plane fill failed")
pcbnew.SaveBoard(str(BOARD_FILE), board)
+1 -1
View File
@@ -93,7 +93,7 @@ RF_NO_DIGITAL_REGION = (152.0, 36.0, 174.5, 43.7)
# restriction itself; without it the deterministic completion is free to drop a
# charge-pump or control net onto L3 directly beneath the 50-ohm line.
RF_REGION_LAYERS = (pcbnew.In2_Cu, pcbnew.In3_Cu, pcbnew.In4_Cu, pcbnew.In5_Cu,
pcbnew.In7_Cu, pcbnew.In8_Cu, pcbnew.B_Cu)
pcbnew.B_Cu)
def rf_region_forbids(net: str, layer: int | None) -> bool: