board module#

20-layer HDI substrate, generated from a fabrication-house stackup report.

Ground truth#

Every number in this file is transcribed from one CSV row of

ACME Circuit Technology quote ACME-Q26-0417, Rev B (2026-07-14) js1-stackup-components/part1-stackup/JS1_Part1_Fab-Stackup.csv

Nothing here is estimated or carried over from another design. If you re-issue the stackup, change the CSV first and re-derive — never edit these constants directly.

Unit conversions the CSV forces#

JITX is millimetres throughout, and has no field for several things a fab quotes, so three conversions happen on the way in:

  • mils -> mm. The report’s primary units are mils; it gives mm alongside and declares mm controlling where the two disagree. We use the mm column.

  • oz -> mm. Conductor has no copper-weight concept. Foil weight is expressed only as thickness: 1 oz = 0.0350 mm, 1/2 oz = 0.0175 mm. The outer layers add 18 um of panel plating on top of the 1/2 oz base foil, so they are 0.0355 mm, not 0.0175 mm — use the report’s finished thickness column, never the nominal weight.

  • Rz um -> mm. The report states roughness as Rz in micrometres, matte and drum side separately. Conductor.roughness is a single scalar in mm, so we take the matte (bonding) side — the surface that faces the dielectric and dominates conductor loss — and divide by 1000.

What JITX cannot hold#

Things the report states that have no field in the API, and therefore survive only in these docstrings. This list is the result of walking the report column by column; if a re-issue adds a column, walk it again rather than assuming the list is closed.

  • Dk/Df frequency. Dielectric stores bare scalars. The report quotes Dk and Df at 10 GHz.

  • Roughness model. Conductor.roughness is one scalar — there is no Huray or Hammerstad selection. The drum-side Rz and any Cannonball-Huray nodule parameters have to live in the EM tool’s own stackup override.

  • Fill material. Via.filled is a boolean. The report distinguishes copper-filled microvias from resin-filled mechanical drills; both become filled = True.

  • Via capping. The report caps all twelve structures. There is no capping attribute, so it is recorded in the via docstrings only.

  • Reference-plane widths. RoutingStructure.Layer.reference requires a desired plane width per layer. The report names which planes reference each line, in the IMPEDANCE Ref_layers column, and never how wide they are, so the widths below are the substrate-modeler skill’s labeled engineering default — 3x the dielectric height between line and plane — the one class of value in this file that no CSV row backs, labeled as such at every use. See REF_PLANE_WIDTH.

  • Impedance and thickness tolerances. Both are ±10% in the report. RoutingStructure.impedance and Material.thickness are scalars.

  • Copper weight. Conductor has no oz concept; see the conversions above. The nominal weight survives only in the foil-class docstrings.

Layer structure#

20 copper layers, mirror-symmetric about a 0.800 mm core, built 5 + 10 + 5: a 10-layer sub-composite (L6..L15) with five sequential build-up laminations per side. Signal layers are L1/L20 (surface microstrip) and L3/L5/L7/L9 plus the mirrors L18/L16/L14/L12 (symmetric stripline); everything else is a GND plane.

Declared as an explicit Stackup, not Symmetric. Symmetric would halve the source but leaves the bottom half as anonymous proxies with no named Python attributes, and the report describes both halves explicitly — including per-layer foil assignments that a reader needs to be able to point at.

Conductor-index map (what vias and routing structures reference)#

Indices count copper layers only — soldermask and dielectrics are not indexed. 0 is L1 and -1 is L20, so with 20 copper layers:

L1 = 0    L6  = 5     L11 = 10    L16 = 15 = -5
L2 = 1    L7  = 6     L12 = 11    L17 = 16 = -4
L3 = 2    L8  = 7     L13 = 12    L18 = 17 = -3
L4 = 3    L9  = 8     L14 = 13    L19 = 18 = -2
L5 = 4    L10 = 9     L15 = 14    L20 = 19 = -1

Negative indices are used for the bottom half so the mirror symmetry is readable at the call site.

No signal-integrity via models#

The vias below carry no models= entry, deliberately. A fab report supplies geometry, not electrical models, and there is no honest way to derive via inductance and capacitance from a drill table. JITX will insert placeholder models, which means any timing or insertion-loss constraint routed through a via will be reported as unsatisfied — that is the correct signal. Obtain EM-simulated or measured models per layer pair before relying on SI constraints, and add them here then.

Spare routing layers#

L7, L9, L12 and L14 are signal-capable stripline layers that Rev B specifies no controlled-impedance structure for, so no routing structure covers them. They are reachable only through BuriedVia_L6_L15 and THVia_L1_L20.

Three API ambiguities this file had to resolve#

All three are open questions against the JITX API, tracked under “API findings” in internal/kits/js1-stackup-components/TODO.md along with further findings that don’t bear on this file’s own choices.

  1. ``Conductor.roughness`` has no documented unit. jitxlib.materials uses mm-scale values (0.0030.005, i.e. 3–5 µm), and other JITX lengths are millimetres throughout, so this file treats roughness as mm and converts the report’s µm accordingly. The attribute’s docstring should state the unit.

  2. ``DifferentialRoutingStructure.Layer.pair_spacing`` is ambiguous. Its docstring says only “internal spacing within the differential pair”, without saying whether that is edge-to-edge or centre-to-centre. This file uses edge-to-edge, matching both the substrate-modeler skill and the report’s own explicit statement.

  3. A reference plane of unstated width still needs a width. RoutingStructure.Layer.reference takes either a scalar layer plus a desired_width, or a mapping of layers to widths. The scalar form without a width raises TypeError: Must specify desired_width if layer is not a mapping. The mapping form accepts None values at construction time, but they are translation-fatal: jitx._translate.routing assigns desired_width into a protobuf float with no None guard (verified on 4.2.2 and 4.4.0rc2). The failure is latent in this kit because routing structures only translate when a net, topology or rule applies them, and main.py’s smoke design has an empty circuit — it would surface in the first consumer design that uses one. So this file carries the planes with the substrate-modeler skill’s labeled engineering default instead (REF_PLANE_WIDTH, 3x the dielectric height between line and plane), which is rule-backed rather than invented, and satisfies the Mapping[int, float] annotation — no pyright suppression remains.

FAB_REPORT = 'ACME-Q26-0417 Rev B'#

Quote and revision every constant in this module is transcribed from.

class Soldermask(*, name=None, thickness=None, representing=None)[source]#

Bases: Dielectric

D-SM — LPI soldermask. Dk 3.80 / Df 0.0200. 0.50 mil.

Parameters:
material_name: ClassVar[str | None] = 'LPI soldermask'#

Name of the material. If not specified, the name of the class is used.

dielectric_coefficient: ClassVar[float | None] = 3.8#
loss_tangent: ClassVar[float | None] = 0.02#
thickness: float | None = 0.0127#

Thickness of the layer in mm. Is ideally specified as a class attribute. If no thickness is declared for this material, a thickness must be specified in the constructor.

class BuildUpPrepreg(*, name=None, thickness=None, representing=None)[source]#

Bases: Dielectric

D-BU — Isola Astra MT77 prepreg, 1035 glass, 76% resin.

Dk 2.96 / Df 0.0016 at 10 GHz. 3.94 mil pressed. The high resin content is what makes the layer laser-drillable; it is also why Rev B’s Dk dropped from 3.00 (1078 glass) and every controlled line width had to be re-solved.

Carries all ten build-up laminations, which is every dielectric adjacent to a controlled-impedance routing layer (L1/L3/L5 and their mirrors), so it is the only dielectric the impedance model uses. The spare L7/L9/L12/L14 striplines sit in bonding prepreg and have no structure specified.

Parameters:
material_name: ClassVar[str | None] = 'Isola Astra MT77 prepreg 1035'#

Name of the material. If not specified, the name of the class is used.

dielectric_coefficient: ClassVar[float | None] = 2.96#
loss_tangent: ClassVar[float | None] = 0.0016#
thickness: float | None = 0.1#

Thickness of the layer in mm. Is ideally specified as a class attribute. If no thickness is declared for this material, a thickness must be specified in the constructor.

class BondingPrepreg(*, name=None, thickness=None, representing=None)[source]#

Bases: Dielectric

D-BOND — Isola Astra MT77 prepreg, 1078 glass, 68% resin.

Dk 3.00 / Df 0.0017 at 10 GHz. 3.94 mil pressed. Bonds the sub-composite (L6..L15) around the core. Same nominal thickness as the build-up prepreg but a different glass style and a different Dk, so it is a distinct material — do not collapse the two.

Parameters:
material_name: ClassVar[str | None] = 'Isola Astra MT77 prepreg 1078'#

Name of the material. If not specified, the name of the class is used.

dielectric_coefficient: ClassVar[float | None] = 3.0#
loss_tangent: ClassVar[float | None] = 0.0017#
thickness: float | None = 0.1#

Thickness of the layer in mm. Is ideally specified as a class attribute. If no thickness is declared for this material, a thickness must be specified in the constructor.

class LaminateCore(*, name=None, thickness=None, representing=None)[source]#

Bases: Dielectric

D-CORE — Isola Astra MT77 laminate, 2x2116 glass, 52% resin.

Dk 3.12 / Df 0.0019 at 10 GHz. 31.50 mil. The higher glass content raises Dk above both prepregs. L10-L11 only.

Parameters:
material_name: ClassVar[str | None] = 'Isola Astra MT77 laminate 2x2116'#

Name of the material. If not specified, the name of the class is used.

dielectric_coefficient: ClassVar[float | None] = 3.12#
loss_tangent: ClassVar[float | None] = 0.0019#
thickness: float | None = 0.8#

Thickness of the layer in mm. Is ideally specified as a class attribute. If no thickness is declared for this material, a thickness must be specified in the constructor.

class OuterCopper(*, name=None, thickness=None, representing=None)[source]#

Bases: Conductor

CU-OUT — 1/2 oz HVLP-2 base foil plus 18 um panel plating.

Finished 35.5 um = 0.0355 mm. Rz matte 2.0 um -> roughness 0.0020 mm. L1 and L20. Note this is twice the base-foil weight: plating is why the surface layers are thicker than the inner signal layers.

Parameters:
material_name: ClassVar[str | None] = 'HVLP-2 0.5 oz + 18 um plate'#

Name of the material. If not specified, the name of the class is used.

thickness: float | None = 0.0355#

Thickness of the layer in mm. Is ideally specified as a class attribute. If no thickness is declared for this material, a thickness must be specified in the constructor.

roughness: ClassVar[float | None] = 0.002#
class SignalCopper(*, name=None, thickness=None, representing=None)[source]#

Bases: Conductor

CU-SIG — 1/2 oz HVLP-2 foil, unplated.

Finished 17.5 um = 0.0175 mm. Rz matte 2.0 um -> roughness 0.0020 mm. The eight inner signal layers.

Parameters:
material_name: ClassVar[str | None] = 'HVLP-2 0.5 oz'#

Name of the material. If not specified, the name of the class is used.

thickness: float | None = 0.0175#

Thickness of the layer in mm. Is ideally specified as a class attribute. If no thickness is declared for this material, a thickness must be specified in the constructor.

roughness: ClassVar[float | None] = 0.002#
class PlaneCopper(*, name=None, thickness=None, representing=None)[source]#

Bases: Conductor

CU-PLN — 1 oz reverse-treated foil, unplated.

Finished 35.0 um = 0.0350 mm. Rz matte 6.0 um -> roughness 0.0060 mm. The ten GND planes. Rougher and cheaper than the HVLP-2 signal foil, which is acceptable because plane copper carries return current over a wide area rather than a narrow trace.

Parameters:
material_name: ClassVar[str | None] = 'RTF 1 oz'#

Name of the material. If not specified, the name of the class is used.

thickness: float | None = 0.035#

Thickness of the layer in mm. Is ideally specified as a class attribute. If no thickness is declared for this material, a thickness must be specified in the constructor.

roughness: ClassVar[float | None] = 0.006#
class HDIStackup[source]#

Bases: Stackup

20 copper layers + 19 dielectrics + 2 soldermask = 41 entries.

Thickness accounting, which must reconcile with the report’s DOCUMENT section:

soldermask   2 x 0.0127 = 0.0254
outer copper 2 x 0.0355 = 0.0710
signal foil  8 x 0.0175 = 0.1400
plane foil  10 x 0.0350 = 0.3500
build-up    10 x 0.1000 = 1.0000
bonding      8 x 0.1000 = 0.8000
core         1 x 0.8000 = 0.8000
               overall  = 3.1864 mm  (report: 3.1864)
finished, excluding mask = 3.1610 mm  (report: 3.161)
name: str | None = 'ACME-Q26-0417 Rev B'#
top_mask = Soldermask(name='Soldermask-Top')#
L1 = OuterCopper(name='L1-Signal')#
d_1_2 = BuildUpPrepreg(name='Build-up-5-Top')#
L2 = PlaneCopper(name='L2-GND')#
d_2_3 = BuildUpPrepreg(name='Build-up-4-Top')#
L3 = SignalCopper(name='L3-Signal')#
d_3_4 = BuildUpPrepreg(name='Build-up-3-Top')#
L4 = PlaneCopper(name='L4-GND')#
d_4_5 = BuildUpPrepreg(name='Build-up-2-Top')#
L5 = SignalCopper(name='L5-Signal')#
d_5_6 = BuildUpPrepreg(name='Build-up-1-Top')#
L6 = PlaneCopper(name='L6-GND')#
d_6_7 = BondingPrepreg(name='Bond-L6-L7')#
L7 = SignalCopper(name='L7-Signal')#
d_7_8 = BondingPrepreg(name='Bond-L7-L8')#
L8 = PlaneCopper(name='L8-GND')#
d_8_9 = BondingPrepreg(name='Bond-L8-L9')#
L9 = SignalCopper(name='L9-Signal')#
d_9_10 = BondingPrepreg(name='Bond-L9-L10')#
L10 = PlaneCopper(name='L10-GND')#
d_10_11 = LaminateCore(name='Core')#
L11 = PlaneCopper(name='L11-GND')#
d_11_12 = BondingPrepreg(name='Bond-L11-L12')#
L12 = SignalCopper(name='L12-Signal')#
d_12_13 = BondingPrepreg(name='Bond-L12-L13')#
L13 = PlaneCopper(name='L13-GND')#
d_13_14 = BondingPrepreg(name='Bond-L13-L14')#
L14 = SignalCopper(name='L14-Signal')#
d_14_15 = BondingPrepreg(name='Bond-L14-L15')#
L15 = PlaneCopper(name='L15-GND')#
d_15_16 = BuildUpPrepreg(name='Build-up-1-Bottom')#
L16 = SignalCopper(name='L16-Signal')#
d_16_17 = BuildUpPrepreg(name='Build-up-2-Bottom')#
L17 = PlaneCopper(name='L17-GND')#
d_17_18 = BuildUpPrepreg(name='Build-up-3-Bottom')#
L18 = SignalCopper(name='L18-Signal')#
d_18_19 = BuildUpPrepreg(name='Build-up-4-Bottom')#
L19 = PlaneCopper(name='L19-GND')#
d_19_20 = BuildUpPrepreg(name='Build-up-5-Bottom')#
L20 = OuterCopper(name='L20-Signal')#
bottom_mask = Soldermask(name='Soldermask-Bottom')#
class HDIFabRules[source]#

Bases: FabricationConstraints

All 19 constraints JITX requires, from the report’s FAB_RULES rows.

Every field is mandatory — translation fails on a missing one. Only the four min_copper_* rules are engine-enforced and override trace width and clearance; the rest are recorded for query and documentation.

Five capability rows in the report have no JITX field and are enforced by review rather than by the engine:

  • the 0.200 mm minimum mechanical drill — min_drill_diameter below is the 0.100 mm laser minimum, the smaller of the two;

  • the 10:1 mechanical and 0.80:1 laser aspect-ratio ceilings;

  • the 5-level stacked-microvia limit;

  • the 0.075 mm minimum dielectric between adjacent copper layers — this quote uses 0.100 mm build-up prepreg, so there is 0.025 mm of headroom if the stack is ever re-issued thinner.

The tests assert the via table against the two aspect-ratio ceilings and the mechanical-drill minimum; the other two are review-only.

min_copper_width: float = 0.05#

Minimum permissible copper width. This constraint will be enforced by the engine for generated copper shapes and will take precedence over other constraints and rules, such as trace width.

min_copper_copper_space: float = 0.05#

Minimum permissible copper-to-copper spacing. This constraint will be enforced by the engine for generated copper shapes and will take precedence over other constraints and rules, such as clearance.

min_copper_hole_space: float = 0.075#

Minimum permissible copper-to-hole spacing. This constraint will be enforced by the engine for generated copper shapes and will take precedence over other constraints and rules, such as clearance.

min_copper_edge_space: float = 0.25#

Minimum permissible copper-to-board-edge spacing. This constraint will be enforced by the engine for generated copper shapes and will take precedence over other constraints and rules, such as clearance.

min_annular_ring: float = 0.05#

Minimum annular ring around a hole or via.

min_drill_diameter: float = 0.1#

Minimum diameter of a hole either in a pad or a via.

min_pitch_leaded: float = 0.4#

Minimum distance between pad centers for leaded packages.

min_pitch_bga: float = 0.35#

Minimum distance between pad centers for BGA packages.

max_board_width: float = 500.0#

Maximum width of a board.

max_board_height: float = 400.0#

Maximum height of a board.

min_silkscreen_width: float = 0.1#

Minimum width of silkscreen.

min_silk_solder_mask_space: float = 0.075#

Minimum distance between silkscreen and soldermask features.

min_silkscreen_text_height: float = 0.5#

Minimum height of silkscreen text.

solder_mask_registration: float = 0.025#

Minimum distance between soldermask and the edge of a copper pad.

min_soldermask_opening: float = 0.1#

Minimum size of a soldermask opening shape.

min_soldermask_bridge: float = 0.075#

Minimum distance between two soldermask features.

min_th_pad_expand_outer: float = 0.075#

Minimum through-hole pad expansion on outer layers.

min_hole_to_hole: float = 0.2#

Minimum distance between two holes, such as through-hole pads or vias.

min_pth_pin_solder_clearance: float = 0.25#

Minimum distance from the outer edge of a through-hole pad to the soldermask.

MICROSTRIP_LAYERS: tuple[int, ...] = (0, -1)#

L1 and L20 — coated microstrip, referenced to the plane immediately below.

STRIPLINE_LAYERS: tuple[int, ...] = (2, 4, -3, -5)#

L3, L5 and mirrors L18, L16 — symmetric stripline between two planes.

REFERENCE_PLANES: dict[int, tuple[int, ...]] = {-5: (-4, -6), -3: (-2, -4), -1: (-2,), 0: (1,), 2: (1, 3), 4: (3, 5)}#

Reference plane indices per routing layer, from the IMPEDANCE Ref_layers column. A design must supply ReferencePlanes covering these layers for any topology that uses one of these structures.

Passed to reference() with REF_PLANE_WIDTH as every desired width. The report states which planes reference each line and never how wide they are, and a None width is translation-fatal (module docstring, ambiguity 3), so the width is the substrate-modeler skill’s labeled default rather than a CSV value.

REF_PLANE_WIDTH = 0.30000000000000004#

the substrate-modeler skill’s engineering default of 3x the dielectric height between line and plane — a rule-backed default, not a CSV value, and labeled as such wherever it is used. Every plane in REFERENCE_PLANES sits exactly one build-up lamination from its line (BuildUpPrepreg.thickness, the D-BU row’s 0.1000 mm), so one constant covers all of them; a plane further away would sum the dielectric thicknesses between. test_reference_plane_widths_are_the_skill_default pins the arithmetic to the CSV row.

Type:

Desired reference-plane width

class HDISubstrate(*args, **kwargs)[source]#

Bases: Substrate

The stackup, fab rules, 12 via structures and 4 routing structures.

Every Via nested class here is registered on the board automatically — the substrate is walked by introspection, and there is no opt-in list. So this class contains exactly the 12 structures the report’s VIAS table offers and nothing held back “for later”. In particular the sub-composite’s internal L10-L11 core drill is part of ACME’s own process, is not offered to the designer, and is therefore absent.

stackup: Stackup = HDIStackup()#
constraints: FabricationConstraints = <jitxexamples.jumpstart_kits.js1_stackup_components.hdi_stackup.board.HDIFabRules object>#
class MicroVia_L1_L2(*args, **kwargs)[source]#

Bases: Via

UV-1 — build-up level 5. L1 -> L2.

type: ClassVar[ViaType] = 2#

MechanicalDrill or LaserDrill.

Type:

Type of via drilling method

start_layer: int = 0#

Starting layer for the via. Setting this to a layer index other than the top layer allows for creating buried or blind vias.

stop_layer: int = 1#

Ending layer for the via.

diameter: float | ViaDiameter = 0.275#

Pad diameter of the via, in mm. Can be overridden on a per-layer basis by diameters.

hole_diameter: float = 0.125#

Drilled or laser-cut hole diameter for the via, in mm.

filled: bool = True#

Whether the via is filled.

tented: set[Side] | Side | None | bool = True#

Whether the via is tented on Top, Bottom, or both sides. Untented sides will have a solder mask opening.

via_in_pad: bool = True#

Whether the via is allowed to be placed inside a component’s pads.

class MicroVia_L2_L3(*args, **kwargs)[source]#

Bases: Via

UV-2 — build-up level 4. L2 -> L3.

type: ClassVar[ViaType] = 2#

MechanicalDrill or LaserDrill.

Type:

Type of via drilling method

start_layer: int = 1#

Starting layer for the via. Setting this to a layer index other than the top layer allows for creating buried or blind vias.

stop_layer: int = 2#

Ending layer for the via.

diameter: float | ViaDiameter = 0.275#

Pad diameter of the via, in mm. Can be overridden on a per-layer basis by diameters.

hole_diameter: float = 0.125#

Drilled or laser-cut hole diameter for the via, in mm.

filled: bool = True#

Whether the via is filled.

tented: set[Side] | Side | None | bool = True#

Whether the via is tented on Top, Bottom, or both sides. Untented sides will have a solder mask opening.

via_in_pad: bool = True#

Whether the via is allowed to be placed inside a component’s pads.

class MicroVia_L3_L4(*args, **kwargs)[source]#

Bases: Via

UV-3 — build-up level 3. L3 -> L4.

type: ClassVar[ViaType] = 2#

MechanicalDrill or LaserDrill.

Type:

Type of via drilling method

start_layer: int = 2#

Starting layer for the via. Setting this to a layer index other than the top layer allows for creating buried or blind vias.

stop_layer: int = 3#

Ending layer for the via.

diameter: float | ViaDiameter = 0.275#

Pad diameter of the via, in mm. Can be overridden on a per-layer basis by diameters.

hole_diameter: float = 0.125#

Drilled or laser-cut hole diameter for the via, in mm.

filled: bool = True#

Whether the via is filled.

tented: set[Side] | Side | None | bool = True#

Whether the via is tented on Top, Bottom, or both sides. Untented sides will have a solder mask opening.

via_in_pad: bool = True#

Whether the via is allowed to be placed inside a component’s pads.

class MicroVia_L4_L5(*args, **kwargs)[source]#

Bases: Via

UV-4 — build-up level 2. L4 -> L5.

type: ClassVar[ViaType] = 2#

MechanicalDrill or LaserDrill.

Type:

Type of via drilling method

start_layer: int = 3#

Starting layer for the via. Setting this to a layer index other than the top layer allows for creating buried or blind vias.

stop_layer: int = 4#

Ending layer for the via.

diameter: float | ViaDiameter = 0.275#

Pad diameter of the via, in mm. Can be overridden on a per-layer basis by diameters.

hole_diameter: float = 0.125#

Drilled or laser-cut hole diameter for the via, in mm.

filled: bool = True#

Whether the via is filled.

tented: set[Side] | Side | None | bool = True#

Whether the via is tented on Top, Bottom, or both sides. Untented sides will have a solder mask opening.

via_in_pad: bool = True#

Whether the via is allowed to be placed inside a component’s pads.

class MicroVia_L5_L6(*args, **kwargs)[source]#

Bases: Via

UV-5 — build-up level 1. L5 -> L6.

type: ClassVar[ViaType] = 2#

MechanicalDrill or LaserDrill.

Type:

Type of via drilling method

start_layer: int = 4#

Starting layer for the via. Setting this to a layer index other than the top layer allows for creating buried or blind vias.

stop_layer: int = 5#

Ending layer for the via.

diameter: float | ViaDiameter = 0.275#

Pad diameter of the via, in mm. Can be overridden on a per-layer basis by diameters.

hole_diameter: float = 0.125#

Drilled or laser-cut hole diameter for the via, in mm.

filled: bool = True#

Whether the via is filled.

tented: set[Side] | Side | None | bool = True#

Whether the via is tented on Top, Bottom, or both sides. Untented sides will have a solder mask opening.

via_in_pad: bool = True#

Whether the via is allowed to be placed inside a component’s pads.

class MicroVia_L20_L19(*args, **kwargs)[source]#

Bases: Via

UV-6 — mirror of UV-1. L20 -> L19.

type: ClassVar[ViaType] = 2#

MechanicalDrill or LaserDrill.

Type:

Type of via drilling method

start_layer: int = -1#

Starting layer for the via. Setting this to a layer index other than the top layer allows for creating buried or blind vias.

stop_layer: int = -2#

Ending layer for the via.

diameter: float | ViaDiameter = 0.275#

Pad diameter of the via, in mm. Can be overridden on a per-layer basis by diameters.

hole_diameter: float = 0.125#

Drilled or laser-cut hole diameter for the via, in mm.

filled: bool = True#

Whether the via is filled.

tented: set[Side] | Side | None | bool = True#

Whether the via is tented on Top, Bottom, or both sides. Untented sides will have a solder mask opening.

via_in_pad: bool = True#

Whether the via is allowed to be placed inside a component’s pads.

class MicroVia_L19_L18(*args, **kwargs)[source]#

Bases: Via

UV-7 — mirror of UV-2. L19 -> L18.

type: ClassVar[ViaType] = 2#

MechanicalDrill or LaserDrill.

Type:

Type of via drilling method

start_layer: int = -2#

Starting layer for the via. Setting this to a layer index other than the top layer allows for creating buried or blind vias.

stop_layer: int = -3#

Ending layer for the via.

diameter: float | ViaDiameter = 0.275#

Pad diameter of the via, in mm. Can be overridden on a per-layer basis by diameters.

hole_diameter: float = 0.125#

Drilled or laser-cut hole diameter for the via, in mm.

filled: bool = True#

Whether the via is filled.

tented: set[Side] | Side | None | bool = True#

Whether the via is tented on Top, Bottom, or both sides. Untented sides will have a solder mask opening.

via_in_pad: bool = True#

Whether the via is allowed to be placed inside a component’s pads.

class MicroVia_L18_L17(*args, **kwargs)[source]#

Bases: Via

UV-8 — mirror of UV-3. L18 -> L17.

type: ClassVar[ViaType] = 2#

MechanicalDrill or LaserDrill.

Type:

Type of via drilling method

start_layer: int = -3#

Starting layer for the via. Setting this to a layer index other than the top layer allows for creating buried or blind vias.

stop_layer: int = -4#

Ending layer for the via.

diameter: float | ViaDiameter = 0.275#

Pad diameter of the via, in mm. Can be overridden on a per-layer basis by diameters.

hole_diameter: float = 0.125#

Drilled or laser-cut hole diameter for the via, in mm.

filled: bool = True#

Whether the via is filled.

tented: set[Side] | Side | None | bool = True#

Whether the via is tented on Top, Bottom, or both sides. Untented sides will have a solder mask opening.

via_in_pad: bool = True#

Whether the via is allowed to be placed inside a component’s pads.

class MicroVia_L17_L16(*args, **kwargs)[source]#

Bases: Via

UV-9 — mirror of UV-4. L17 -> L16.

type: ClassVar[ViaType] = 2#

MechanicalDrill or LaserDrill.

Type:

Type of via drilling method

start_layer: int = -4#

Starting layer for the via. Setting this to a layer index other than the top layer allows for creating buried or blind vias.

stop_layer: int = -5#

Ending layer for the via.

diameter: float | ViaDiameter = 0.275#

Pad diameter of the via, in mm. Can be overridden on a per-layer basis by diameters.

hole_diameter: float = 0.125#

Drilled or laser-cut hole diameter for the via, in mm.

filled: bool = True#

Whether the via is filled.

tented: set[Side] | Side | None | bool = True#

Whether the via is tented on Top, Bottom, or both sides. Untented sides will have a solder mask opening.

via_in_pad: bool = True#

Whether the via is allowed to be placed inside a component’s pads.

class MicroVia_L16_L15(*args, **kwargs)[source]#

Bases: Via

UV-10 — mirror of UV-5. L16 -> L15.

type: ClassVar[ViaType] = 2#

MechanicalDrill or LaserDrill.

Type:

Type of via drilling method

start_layer: int = -5#

Starting layer for the via. Setting this to a layer index other than the top layer allows for creating buried or blind vias.

stop_layer: int = -6#

Ending layer for the via.

diameter: float | ViaDiameter = 0.275#

Pad diameter of the via, in mm. Can be overridden on a per-layer basis by diameters.

hole_diameter: float = 0.125#

Drilled or laser-cut hole diameter for the via, in mm.

filled: bool = True#

Whether the via is filled.

tented: set[Side] | Side | None | bool = True#

Whether the via is tented on Top, Bottom, or both sides. Untented sides will have a solder mask opening.

via_in_pad: bool = True#

Whether the via is allowed to be placed inside a component’s pads.

class BuriedVia_L6_L15(*args, **kwargs)[source]#

Bases: Via

BV-1 — the sub-composite through-drill. L6 -> L15.

Drilled, plated, resin-filled and capped before the build-up laminations go on, so it is buried in the finished board. 1.880 mm deep on a 0.250 mm hole = aspect ratio 7.52. The only path between the two build-up regions other than the full-stack through hole, and the only way to reach the spare L7/L9/L12/L14 routing layers.

type: ClassVar[ViaType] = 1#

MechanicalDrill or LaserDrill.

Type:

Type of via drilling method

start_layer: int = 5#

Starting layer for the via. Setting this to a layer index other than the top layer allows for creating buried or blind vias.

stop_layer: int = 14#

Ending layer for the via.

diameter: float | ViaDiameter = 0.5#

Pad diameter of the via, in mm. Can be overridden on a per-layer basis by diameters.

hole_diameter: float = 0.25#

Drilled or laser-cut hole diameter for the via, in mm.

filled: bool = True#

Whether the via is filled.

tented: set[Side] | Side | None | bool = True#

Whether the via is tented on Top, Bottom, or both sides. Untented sides will have a solder mask opening.

class THVia_L1_L20(*args, **kwargs)[source]#

Bases: Via

TH-1 — full-stack plated through hole, drilled last. L1 -> L20.

3.161 mm deep on a 0.350 mm hole = aspect ratio 9.03, inside ACME’s 10:1 ceiling but only just — this is why the hole is 0.350 mm and not the 0.250 mm used for the buried drill. Too large to sit inside a 0.35 mm-pitch BGA pad, so via_in_pad stays off; use stacked microvias for pad escape.

type: ClassVar[ViaType] = 1#

MechanicalDrill or LaserDrill.

Type:

Type of via drilling method

start_layer: int = 0#

Starting layer for the via. Setting this to a layer index other than the top layer allows for creating buried or blind vias.

stop_layer: int = -1#

Ending layer for the via.

diameter: float | ViaDiameter = 0.65#

Pad diameter of the via, in mm. Can be overridden on a per-layer basis by diameters.

hole_diameter: float = 0.35#

Drilled or laser-cut hole diameter for the via, in mm.

filled: bool = True#

Whether the via is filled.

tented: set[Side] | Side | None | bool = True#

Whether the via is tented on Top, Bottom, or both sides. Untented sides will have a solder mask opening.

SE_Default = RoutingStructure(name='55 ohm general purpose (SE-DEFAULT)', impedance=<Quantity(55, 'ohm')>, layers={0: Layer(trace_width=0.1841, clearance=0.3682, velocity=192253668475.39417, insertion_loss=0.00907, neck_down=RoutingStructure.NeckDown(trace_width=0.15, clearance=0.15, insertion_loss=None, velocity=None)).reference({1: 0.30000000000000004}), -1: Layer(trace_width=0.1841, clearance=0.3682, velocity=192253668475.39417, insertion_loss=0.00907, neck_down=RoutingStructure.NeckDown(trace_width=0.15, clearance=0.15, insertion_loss=None, velocity=None)).reference({-2: 0.30000000000000004}), 2: Layer(trace_width=0.0876, clearance=0.1752, velocity=174250826755.11264, insertion_loss=0.01367, neck_down=RoutingStructure.NeckDown(trace_width=0.0762, clearance=0.0762, insertion_loss=None, velocity=None)).reference({1: 0.30000000000000004, 3: 0.30000000000000004}), 4: Layer(trace_width=0.0876, clearance=0.1752, velocity=174250826755.11264, insertion_loss=0.01367, neck_down=RoutingStructure.NeckDown(trace_width=0.0762, clearance=0.0762, insertion_loss=None, velocity=None)).reference({3: 0.30000000000000004, 5: 0.30000000000000004}), -3: Layer(trace_width=0.0876, clearance=0.1752, velocity=174250826755.11264, insertion_loss=0.01367, neck_down=RoutingStructure.NeckDown(trace_width=0.0762, clearance=0.0762, insertion_loss=None, velocity=None)).reference({-2: 0.30000000000000004, -4: 0.30000000000000004}), -5: Layer(trace_width=0.0876, clearance=0.1752, velocity=174250826755.11264, insertion_loss=0.01367, neck_down=RoutingStructure.NeckDown(trace_width=0.0762, clearance=0.0762, insertion_loss=None, velocity=None)).reference({-4: 0.30000000000000004, -6: 0.30000000000000004})})#

General-purpose single-ended structure — the one to reach for when a net has no specific impedance target.

Note there is no automatic default: Substrate.routing_structure() wraps its query in Toleranced.exact, so lookup matches exactly and a design has to ask for 55 * ohm by name.

ACME also quotes a STANDARD default line and space (0.150 mm / 0.150 mm) for nets with no impedance target. That geometry is deliberately not modelled here: applied unchanged on every routing layer it produces 41.87 ohm on the inner striplines and 60.99 ohm on the surface microstrip, and a RoutingStructure carries a single impedance, so declaring one value for it would be a fiction. A controlled 55 ohm target holds one impedance on all six routing layers, which is what the lookup needs.

SE_40 = RoutingStructure(name='40 ohm (SE-40)', impedance=<Quantity(40, 'ohm')>, layers={0: Layer(trace_width=0.3179, clearance=0.6358, velocity=189103051616.4028, insertion_loss=0.00884, neck_down=RoutingStructure.NeckDown(trace_width=0.15, clearance=0.15, insertion_loss=None, velocity=None)).reference({1: 0.30000000000000004}), -1: Layer(trace_width=0.3179, clearance=0.6358, velocity=189103051616.4028, insertion_loss=0.00884, neck_down=RoutingStructure.NeckDown(trace_width=0.15, clearance=0.15, insertion_loss=None, velocity=None)).reference({-2: 0.30000000000000004}), 2: Layer(trace_width=0.1622, clearance=0.3244, velocity=174250826755.11264, insertion_loss=0.01246, neck_down=RoutingStructure.NeckDown(trace_width=0.0762, clearance=0.0762, insertion_loss=None, velocity=None)).reference({1: 0.30000000000000004, 3: 0.30000000000000004}), 4: Layer(trace_width=0.1622, clearance=0.3244, velocity=174250826755.11264, insertion_loss=0.01246, neck_down=RoutingStructure.NeckDown(trace_width=0.0762, clearance=0.0762, insertion_loss=None, velocity=None)).reference({3: 0.30000000000000004, 5: 0.30000000000000004}), -3: Layer(trace_width=0.1622, clearance=0.3244, velocity=174250826755.11264, insertion_loss=0.01246, neck_down=RoutingStructure.NeckDown(trace_width=0.0762, clearance=0.0762, insertion_loss=None, velocity=None)).reference({-2: 0.30000000000000004, -4: 0.30000000000000004}), -5: Layer(trace_width=0.1622, clearance=0.3244, velocity=174250826755.11264, insertion_loss=0.01246, neck_down=RoutingStructure.NeckDown(trace_width=0.0762, clearance=0.0762, insertion_loss=None, velocity=None)).reference({-4: 0.30000000000000004, -6: 0.30000000000000004})})#

40 ohm single-ended.

Note the surface width: 40 ohm over 0.100 mm of Dk-2.96 build-up prepreg needs 0.3179 mm (12.5 mil) of copper. That is simply what the geometry gives, and it is why a dense escape on L1 necks down to 0.1500 mm — which is a ~61 ohm line, not a 40 ohm one. Keep necked runs short.

SE_50 = RoutingStructure(name='50 ohm (SE-50)', impedance=<Quantity(50, 'ohm')>, layers={0: Layer(trace_width=0.2193, clearance=0.4386, velocity=191249562150.0812, insertion_loss=0.00898, neck_down=RoutingStructure.NeckDown(trace_width=0.15, clearance=0.15, insertion_loss=None, velocity=None)).reference({1: 0.30000000000000004}), -1: Layer(trace_width=0.2193, clearance=0.4386, velocity=191249562150.0812, insertion_loss=0.00898, neck_down=RoutingStructure.NeckDown(trace_width=0.15, clearance=0.15, insertion_loss=None, velocity=None)).reference({-2: 0.30000000000000004}), 2: Layer(trace_width=0.1075, clearance=0.215, velocity=174250826755.11264, insertion_loss=0.01326, neck_down=RoutingStructure.NeckDown(trace_width=0.0762, clearance=0.0762, insertion_loss=None, velocity=None)).reference({1: 0.30000000000000004, 3: 0.30000000000000004}), 4: Layer(trace_width=0.1075, clearance=0.215, velocity=174250826755.11264, insertion_loss=0.01326, neck_down=RoutingStructure.NeckDown(trace_width=0.0762, clearance=0.0762, insertion_loss=None, velocity=None)).reference({3: 0.30000000000000004, 5: 0.30000000000000004}), -3: Layer(trace_width=0.1075, clearance=0.215, velocity=174250826755.11264, insertion_loss=0.01326, neck_down=RoutingStructure.NeckDown(trace_width=0.0762, clearance=0.0762, insertion_loss=None, velocity=None)).reference({-2: 0.30000000000000004, -4: 0.30000000000000004}), -5: Layer(trace_width=0.1075, clearance=0.215, velocity=174250826755.11264, insertion_loss=0.01326, neck_down=RoutingStructure.NeckDown(trace_width=0.0762, clearance=0.0762, insertion_loss=None, velocity=None)).reference({-4: 0.30000000000000004, -6: 0.30000000000000004})})#

50 ohm single-ended.

DRS_100 = DifferentialRoutingStructure(name='100 ohm differential (DIFF-100)', impedance=<Quantity(100, 'ohm')>, layers={0: Layer(trace_width=0.1486, pair_spacing=0.1, clearance=0.5944, velocity=193474896427.13745, insertion_loss=0.01082, neck_down=DifferentialRoutingStructure.NeckDown(trace_width=0.1, clearance=0.2, insertion_loss=None, velocity=None, pair_spacing=0.075)).reference({1: 0.30000000000000004}), -1: Layer(trace_width=0.1486, pair_spacing=0.1, clearance=0.5944, velocity=193474896427.13745, insertion_loss=0.01082, neck_down=DifferentialRoutingStructure.NeckDown(trace_width=0.1, clearance=0.2, insertion_loss=None, velocity=None, pair_spacing=0.075)).reference({-2: 0.30000000000000004}), 2: Layer(trace_width=0.0762, pair_spacing=0.0762, clearance=0.3048, velocity=174250826755.11264, insertion_loss=0.01579, neck_down=DifferentialRoutingStructure.NeckDown(trace_width=0.0635, clearance=0.127, insertion_loss=None, velocity=None, pair_spacing=0.0635)).reference({1: 0.30000000000000004, 3: 0.30000000000000004}), 4: Layer(trace_width=0.0762, pair_spacing=0.0762, clearance=0.3048, velocity=174250826755.11264, insertion_loss=0.01579, neck_down=DifferentialRoutingStructure.NeckDown(trace_width=0.0635, clearance=0.127, insertion_loss=None, velocity=None, pair_spacing=0.0635)).reference({3: 0.30000000000000004, 5: 0.30000000000000004}), -3: Layer(trace_width=0.0762, pair_spacing=0.0762, clearance=0.3048, velocity=174250826755.11264, insertion_loss=0.01579, neck_down=DifferentialRoutingStructure.NeckDown(trace_width=0.0635, clearance=0.127, insertion_loss=None, velocity=None, pair_spacing=0.0635)).reference({-2: 0.30000000000000004, -4: 0.30000000000000004}), -5: Layer(trace_width=0.0762, pair_spacing=0.0762, clearance=0.3048, velocity=174250826755.11264, insertion_loss=0.01579, neck_down=DifferentialRoutingStructure.NeckDown(trace_width=0.0635, clearance=0.127, insertion_loss=None, velocity=None, pair_spacing=0.0635)).reference({-4: 0.30000000000000004, -6: 0.30000000000000004})}, uncoupled_region=RoutingStructure(name='100 ohm differential, uncoupled (DIFF-100-UNC)', impedance=<Quantity(58, 'ohm')>, layers={0: Layer(trace_width=0.1486, clearance=0.5944, velocity=193474896427.13745, insertion_loss=0.00918, neck_down=None).reference({1: 0.30000000000000004}), -1: Layer(trace_width=0.1486, clearance=0.5944, velocity=193474896427.13745, insertion_loss=0.00918, neck_down=None).reference({-2: 0.30000000000000004}), 2: Layer(trace_width=0.0762, clearance=0.3048, velocity=174250826755.11264, insertion_loss=0.01394, neck_down=None).reference({1: 0.30000000000000004, 3: 0.30000000000000004}), 4: Layer(trace_width=0.0762, clearance=0.3048, velocity=174250826755.11264, insertion_loss=0.01394, neck_down=None).reference({3: 0.30000000000000004, 5: 0.30000000000000004}), -3: Layer(trace_width=0.0762, clearance=0.3048, velocity=174250826755.11264, insertion_loss=0.01394, neck_down=None).reference({-2: 0.30000000000000004, -4: 0.30000000000000004}), -5: Layer(trace_width=0.0762, clearance=0.3048, velocity=174250826755.11264, insertion_loss=0.01394, neck_down=None).reference({-4: 0.30000000000000004, -6: 0.30000000000000004})}))#

100 ohm differential, edge-coupled, gap stated edge to edge.

The uncoupled region keeps the coupled line width but each trace now behaves as an isolated single-ended line, which the report models at 58.35 ohm on stripline and 61.27 ohm on microstrip. It is declared at 58 ohm — the stripline value, since that is where the bulk of the routing runs — and not at half the differential target, because the geometry does not change when the pair splits.

BOARD_CORNER_RADIUS = 4.0#

Board outline, from the report’s DOCUMENT section (“Board size”).

class HDIBoard[source]#

Bases: Board

80 x 80 mm board on the 20-layer HDI substrate.

Board holds only shape and signal_area. Via registration and substrate binding are not board concerns — vias come from walking the substrate, and the substrate is bound on the Design.

shape: Shape = ArcPolygon(elements=(Arc(center=(36.0, 36.0), radius=4.0, start=0.0, arc=90.0), Arc(center=(-36.0, 36.0), radius=4.0, start=90.0, arc=90.0), Arc(center=(-36.0, -36.0), radius=4.0, start=180.0, arc=90.0), Arc(center=(36.0, -36.0), radius=4.0, start=270.0, arc=90.0)))#

The board outline shape.

signal_area: Shape | None = ArcPolygon(elements=(Arc(center=(36.0, 36.0), radius=3.75, start=0.0, arc=90.0), Arc(center=(-36.0, 36.0), radius=3.75, start=90.0, arc=90.0), Arc(center=(-36.0, -36.0), radius=3.75, start=180.0, arc=90.0), Arc(center=(36.0, -36.0), radius=3.75, start=270.0, arc=90.0)))#

Shape constraining component and routing placement area. If not provided, the board shape is used.