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.
Conductorhas no copper-weight concept. Foil weight is expressed only asthickness: 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.roughnessis 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.
Dielectricstores bare scalars. The report quotes Dk and Df at 10 GHz.Roughness model.
Conductor.roughnessis one scalar, with no Huray or Hammerstad selection. The drum-side Rz is recorded in each foil class’s docstring beside the matte Rz; it and any Cannonball-Huray nodule parameters go to the EM exporter or field solver, which takes its own roughness models.Fill material.
Via.filledis a boolean. The report distinguishes copper-filled microvias from resin-filled mechanical drills; both becomefilled = 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.referencerequires a desired plane width per layer. The report names which planes reference each line, in the IMPEDANCERef_layerscolumn, 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. SeeREF_PLANE_WIDTH.Impedance and thickness tolerances. Both are ±10% in the report.
RoutingStructure.impedanceandMaterial.thicknessare scalars.Copper weight.
Conductorhas noozconcept; see the conversions above. The nominal weight survives only in the foil-class docstrings.Neck impedance per geometry. The report models each neck geometry on its own row, in
Neck_modelled_Z_ohm: 60.99 ohm on microstrip and 58.35 ohm on stripline forNeck_SE, 111.71 and 106.54 ohm forNeck_DIFF_100.RoutingStructure.impedanceis one scalar, so each neck structure declares its stripline figure, and 60.99 and 111.71 survive only in 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.
Neck regions#
The IMPEDANCE Neck_* columns give a narrower geometry for short runs where
a line has to squeeze through, such as a BGA escape. Each neck geometry is a
routing structure of its own on its target’s layers, carrying the report’s
neck width, pair gap and clearance: Neck_SE for SE-DEFAULT, SE-40 and
SE-50, whose rows quote the same neck geometry, and Neck_DIFF_100 for
DIFF-100. The report quotes a modelled impedance for a neck but no Eps_eff or
loss, so each neck structure carries its parent row’s velocity and loss as a
labelled default.
A design applies each structure by a tag rule. The parent structure goes on
its net’s tag, a pair included, as in
jitxexamples.demos.si_bga_optimization.bga_escape. A neck structure goes
on the code-placed Route that covers the necked span (for a pair, the
pair’s Route), tagged with the design’s own Tag subclass, by a rule
whose priority sits above the parent’s, for example
design_constraint(NeckTag(), priority=1).routing_structure(HDISubstrate.Neck_SE, ref_net=self.GND).
No build has confirmed a structure applied by a Route tag, so that step
is an open item. main.py routes nothing, so no structure here, neck or
parent, is applied yet.
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.
``Conductor.roughness`` has no documented unit.
jitxlib.materialsuses mm-scale values (0.003–0.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.``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.
A reference plane of unstated width still needs a width.
RoutingStructure.Layer.referencetakes either a scalar layer plus adesired_width, or a mapping of layers to widths. The scalar form without a width raisesTypeError: Must specify desired_width if layer is not a mapping. The mapping form acceptsNonevalues at construction time, but they are translation-fatal:jitx._translate.routingassignsdesired_widthinto a protobuf float with noNoneguard (verified on 4.2.2 and 4.4.0). The failure is latent in this kit because routing structures only translate when a net, topology or rule applies them, andmain.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 theMapping[int, float]annotation — nopyrightsuppression 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:
DielectricD-SM — LPI soldermask. Dk 3.80 / Df 0.0200. 0.50 mil.
- 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#
Dielectric constant, the Dk of a datasheet (relative permittivity).
- loss_tangent: ClassVar[float | None] = 0.02#
Loss tangent, the Df (dissipation factor) of a datasheet.
- 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. Set it on the class or pass it to the constructor, never both: the constructor raises ValueError if both are given, and also if neither is.
- class BuildUpPrepreg(*, name=None, thickness=None, representing=None)[source]#
Bases:
DielectricD-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.
- 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#
Dielectric constant, the Dk of a datasheet (relative permittivity).
- loss_tangent: ClassVar[float | None] = 0.0016#
Loss tangent, the Df (dissipation factor) of a datasheet.
- 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. Set it on the class or pass it to the constructor, never both: the constructor raises ValueError if both are given, and also if neither is.
- class BondingPrepreg(*, name=None, thickness=None, representing=None)[source]#
Bases:
DielectricD-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.
- 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#
Dielectric constant, the Dk of a datasheet (relative permittivity).
- loss_tangent: ClassVar[float | None] = 0.0017#
Loss tangent, the Df (dissipation factor) of a datasheet.
- 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. Set it on the class or pass it to the constructor, never both: the constructor raises ValueError if both are given, and also if neither is.
- class LaminateCore(*, name=None, thickness=None, representing=None)[source]#
Bases:
DielectricD-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.
- 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#
Dielectric constant, the Dk of a datasheet (relative permittivity).
- loss_tangent: ClassVar[float | None] = 0.0019#
Loss tangent, the Df (dissipation factor) of a datasheet.
- 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. Set it on the class or pass it to the constructor, never both: the constructor raises ValueError if both are given, and also if neither is.
- class OuterCopper(*, name=None, thickness=None, representing=None)[source]#
Bases:
ConductorCU-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, drum 0.7 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.
- 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. Set it on the class or pass it to the constructor, never both: the constructor raises ValueError if both are given, and also if neither is.
- roughness: ClassVar[float | None] = 0.002#
Surface roughness of the conductor, a peak-to-valley Rz in mm. jitx does not use this value yet: it carries it unchanged in the design’s material data, as metadata for exporters and field solvers. It holds a single number, not a multi-parameter model such as Cannonball-Huray’s.
- class SignalCopper(*, name=None, thickness=None, representing=None)[source]#
Bases:
ConductorCU-SIG — 1/2 oz HVLP-2 foil, unplated.
Finished 17.5 um = 0.0175 mm. Rz matte 2.0 um, drum 0.7 um -> roughness 0.0020 mm. The eight inner signal layers.
- 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. Set it on the class or pass it to the constructor, never both: the constructor raises ValueError if both are given, and also if neither is.
- roughness: ClassVar[float | None] = 0.002#
Surface roughness of the conductor, a peak-to-valley Rz in mm. jitx does not use this value yet: it carries it unchanged in the design’s material data, as metadata for exporters and field solvers. It holds a single number, not a multi-parameter model such as Cannonball-Huray’s.
- class PlaneCopper(*, name=None, thickness=None, representing=None)[source]#
Bases:
ConductorCU-PLN — 1 oz reverse-treated foil, unplated.
Finished 35.0 um = 0.0350 mm. Rz matte 6.0 um, drum 3.5 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.
- 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. Set it on the class or pass it to the constructor, never both: the constructor raises ValueError if both are given, and also if neither is.
- roughness: ClassVar[float | None] = 0.006#
Surface roughness of the conductor, a peak-to-valley Rz in mm. jitx does not use this value yet: it carries it unchanged in the design’s material data, as metadata for exporters and field solvers. It holds a single number, not a multi-parameter model such as Cannonball-Huray’s.
- class HDIStackup[source]#
Bases:
Stackup20 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)
- 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:
FabricationConstraintsAll 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_diameterbelow 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_silk_solder_mask_space: float = 0.075#
Minimum distance between silkscreen and soldermask features.
- solder_mask_registration: float = 0.025#
Minimum distance between soldermask and the edge of a copper pad.
- 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
ReferencePlanescovering these layers for any topology that uses one of these structures.Passed to
reference()withREF_PLANE_WIDTHas every desired width. The report states which planes reference each line and never how wide they are, and aNonewidth 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_PLANESsits 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_defaultpins the arithmetic to the CSV row.- Type:
Desired reference-plane width
- class HDISubstrate(*args, **kwargs)[source]#
Bases:
SubstrateThe stackup, fab rules, 12 via structures, 4 routing structures and 2 neck-region structures.
Every
Vianested 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.- constraints: FabricationConstraints = <jitxexamples.jumpstart_kits.js1_stackup_components.hdi_stackup.board.HDIFabRules object>#
- class MicroVia_L1_L2(*args, **kwargs)[source]#
Bases:
ViaUV-1 — build-up level 5. L1 -> L2.
- 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.
- diameter: float | ViaDiameter = 0.275#
Pad diameter of the via, in mm. Can be overridden on a per-layer basis by
diameters.
- class MicroVia_L2_L3(*args, **kwargs)[source]#
Bases:
ViaUV-2 — build-up level 4. L2 -> L3.
- 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.
- diameter: float | ViaDiameter = 0.275#
Pad diameter of the via, in mm. Can be overridden on a per-layer basis by
diameters.
- class MicroVia_L3_L4(*args, **kwargs)[source]#
Bases:
ViaUV-3 — build-up level 3. L3 -> L4.
- 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.
- diameter: float | ViaDiameter = 0.275#
Pad diameter of the via, in mm. Can be overridden on a per-layer basis by
diameters.
- class MicroVia_L4_L5(*args, **kwargs)[source]#
Bases:
ViaUV-4 — build-up level 2. L4 -> L5.
- 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.
- diameter: float | ViaDiameter = 0.275#
Pad diameter of the via, in mm. Can be overridden on a per-layer basis by
diameters.
- class MicroVia_L5_L6(*args, **kwargs)[source]#
Bases:
ViaUV-5 — build-up level 1. L5 -> L6.
- 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.
- diameter: float | ViaDiameter = 0.275#
Pad diameter of the via, in mm. Can be overridden on a per-layer basis by
diameters.
- class MicroVia_L20_L19(*args, **kwargs)[source]#
Bases:
ViaUV-6 — mirror of UV-1. L20 -> L19.
- 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.
- diameter: float | ViaDiameter = 0.275#
Pad diameter of the via, in mm. Can be overridden on a per-layer basis by
diameters.
- class MicroVia_L19_L18(*args, **kwargs)[source]#
Bases:
ViaUV-7 — mirror of UV-2. L19 -> L18.
- 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.
- diameter: float | ViaDiameter = 0.275#
Pad diameter of the via, in mm. Can be overridden on a per-layer basis by
diameters.
- class MicroVia_L18_L17(*args, **kwargs)[source]#
Bases:
ViaUV-8 — mirror of UV-3. L18 -> L17.
- 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.
- diameter: float | ViaDiameter = 0.275#
Pad diameter of the via, in mm. Can be overridden on a per-layer basis by
diameters.
- class MicroVia_L17_L16(*args, **kwargs)[source]#
Bases:
ViaUV-9 — mirror of UV-4. L17 -> L16.
- 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.
- diameter: float | ViaDiameter = 0.275#
Pad diameter of the via, in mm. Can be overridden on a per-layer basis by
diameters.
- class MicroVia_L16_L15(*args, **kwargs)[source]#
Bases:
ViaUV-10 — mirror of UV-5. L16 -> L15.
- 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.
- diameter: float | ViaDiameter = 0.275#
Pad diameter of the via, in mm. Can be overridden on a per-layer basis by
diameters.
- class BuriedVia_L6_L15(*args, **kwargs)[source]#
Bases:
ViaBV-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.
- 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.
- diameter: float | ViaDiameter = 0.5#
Pad diameter of the via, in mm. Can be overridden on a per-layer basis by
diameters.
- class THVia_L1_L20(*args, **kwargs)[source]#
Bases:
ViaTH-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_padstays off; use stacked microvias for pad escape.- 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.
- diameter: float | ViaDiameter = 0.65#
Pad diameter of the via, in mm. Can be overridden on a per-layer basis by
diameters.
- 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).reference({1: 0.30000000000000004}), -1: Layer(trace_width=0.1841, clearance=0.3682, velocity=192253668475.39417, insertion_loss=0.00907).reference({-2: 0.30000000000000004}), 2: Layer(trace_width=0.0876, clearance=0.1752, velocity=174250826755.11264, insertion_loss=0.01367).reference({1: 0.30000000000000004, 3: 0.30000000000000004}), 4: Layer(trace_width=0.0876, clearance=0.1752, velocity=174250826755.11264, insertion_loss=0.01367).reference({3: 0.30000000000000004, 5: 0.30000000000000004}), -3: Layer(trace_width=0.0876, clearance=0.1752, velocity=174250826755.11264, insertion_loss=0.01367).reference({-2: 0.30000000000000004, -4: 0.30000000000000004}), -5: Layer(trace_width=0.0876, clearance=0.1752, velocity=174250826755.11264, insertion_loss=0.01367).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 a float orQuantityquery inToleranced.exact, so that lookup matches exactly and a design has to ask for55 * ohmby name. ATolerancedquery passes through unchanged and returns the closest structure in its range: for a query centred near 58 ohm that isNeck_SE, not this one.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
RoutingStructurecarries 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).reference({1: 0.30000000000000004}), -1: Layer(trace_width=0.3179, clearance=0.6358, velocity=189103051616.4028, insertion_loss=0.00884).reference({-2: 0.30000000000000004}), 2: Layer(trace_width=0.1622, clearance=0.3244, velocity=174250826755.11264, insertion_loss=0.01246).reference({1: 0.30000000000000004, 3: 0.30000000000000004}), 4: Layer(trace_width=0.1622, clearance=0.3244, velocity=174250826755.11264, insertion_loss=0.01246).reference({3: 0.30000000000000004, 5: 0.30000000000000004}), -3: Layer(trace_width=0.1622, clearance=0.3244, velocity=174250826755.11264, insertion_loss=0.01246).reference({-2: 0.30000000000000004, -4: 0.30000000000000004}), -5: Layer(trace_width=0.1622, clearance=0.3244, velocity=174250826755.11264, insertion_loss=0.01246).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 narrows to the 0.1500 mm of
Neck_SE, which the report models at 60.99 ohm, not 40. 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).reference({1: 0.30000000000000004}), -1: Layer(trace_width=0.2193, clearance=0.4386, velocity=191249562150.0812, insertion_loss=0.00898).reference({-2: 0.30000000000000004}), 2: Layer(trace_width=0.1075, clearance=0.215, velocity=174250826755.11264, insertion_loss=0.01326).reference({1: 0.30000000000000004, 3: 0.30000000000000004}), 4: Layer(trace_width=0.1075, clearance=0.215, velocity=174250826755.11264, insertion_loss=0.01326).reference({3: 0.30000000000000004, 5: 0.30000000000000004}), -3: Layer(trace_width=0.1075, clearance=0.215, velocity=174250826755.11264, insertion_loss=0.01326).reference({-2: 0.30000000000000004, -4: 0.30000000000000004}), -5: Layer(trace_width=0.1075, clearance=0.215, velocity=174250826755.11264, insertion_loss=0.01326).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).reference({1: 0.30000000000000004}), -1: Layer(trace_width=0.1486, pair_spacing=0.1, clearance=0.5944, velocity=193474896427.13745, insertion_loss=0.01082).reference({-2: 0.30000000000000004}), 2: Layer(trace_width=0.0762, pair_spacing=0.0762, clearance=0.3048, velocity=174250826755.11264, insertion_loss=0.01579).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).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).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).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).reference({1: 0.30000000000000004}), -1: Layer(trace_width=0.1486, clearance=0.5944, velocity=193474896427.13745, insertion_loss=0.00918).reference({-2: 0.30000000000000004}), 2: Layer(trace_width=0.0762, clearance=0.3048, velocity=174250826755.11264, insertion_loss=0.01394).reference({1: 0.30000000000000004, 3: 0.30000000000000004}), 4: Layer(trace_width=0.0762, clearance=0.3048, velocity=174250826755.11264, insertion_loss=0.01394).reference({3: 0.30000000000000004, 5: 0.30000000000000004}), -3: Layer(trace_width=0.0762, clearance=0.3048, velocity=174250826755.11264, insertion_loss=0.01394).reference({-2: 0.30000000000000004, -4: 0.30000000000000004}), -5: Layer(trace_width=0.0762, clearance=0.3048, velocity=174250826755.11264, insertion_loss=0.01394).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 DIFF-100-UNC rows’ target, and not at half the differential target, because the geometry does not change when the pair splits.
- Neck_SE = RoutingStructure(name='neck region (SE-DEFAULT, SE-40, SE-50)', impedance=<Quantity(58.35, 'ohm')>, layers={0: Layer(trace_width=0.15, clearance=0.15, velocity=192253668475.39417, insertion_loss=0.00907).reference({1: 0.30000000000000004}), -1: Layer(trace_width=0.15, clearance=0.15, velocity=192253668475.39417, insertion_loss=0.00907).reference({-2: 0.30000000000000004}), 2: Layer(trace_width=0.0762, clearance=0.0762, velocity=174250826755.11264, insertion_loss=0.01367).reference({1: 0.30000000000000004, 3: 0.30000000000000004}), 4: Layer(trace_width=0.0762, clearance=0.0762, velocity=174250826755.11264, insertion_loss=0.01367).reference({3: 0.30000000000000004, 5: 0.30000000000000004}), -3: Layer(trace_width=0.0762, clearance=0.0762, velocity=174250826755.11264, insertion_loss=0.01367).reference({-2: 0.30000000000000004, -4: 0.30000000000000004}), -5: Layer(trace_width=0.0762, clearance=0.0762, velocity=174250826755.11264, insertion_loss=0.01367).reference({-4: 0.30000000000000004, -6: 0.30000000000000004})})#
Neck region of SE-DEFAULT, SE-40 and SE-50, which quote the same neck geometry, so one structure serves all three.
The report models this geometry at 58.35 ohm on stripline and 60.99 ohm on microstrip, and gives a neck no target.
Substrate.routing_structure()looks structures up by impedance, so the neck cannot carry its parents’ targets without colliding with them; it carries a modelled figure instead, the stripline row’s 58.35 ohm, labelled as modelled. One value serves here where the STANDARD row’s does not (seeSE_Default) because the two neck figures are 4.5% apart, while STANDARD’s single geometry spans 41.87 to 60.99 ohm, 46% apart. Velocity and loss are SE-DEFAULT’s, a labelled default.A design applies it by tagging the code-placed
Routeover the necked span, with a rule at a priority above the parent’s net-tag rule (see “Neck regions” in the module docstring). No build has confirmed this.
- Neck_DIFF_100 = DifferentialRoutingStructure(name='neck region (DIFF-100)', impedance=<Quantity(106.54, 'ohm')>, layers={0: Layer(trace_width=0.1, pair_spacing=0.075, clearance=0.2, velocity=193474896427.13745, insertion_loss=0.01082).reference({1: 0.30000000000000004}), -1: Layer(trace_width=0.1, pair_spacing=0.075, clearance=0.2, velocity=193474896427.13745, insertion_loss=0.01082).reference({-2: 0.30000000000000004}), 2: Layer(trace_width=0.0635, pair_spacing=0.0635, clearance=0.127, velocity=174250826755.11264, insertion_loss=0.01579).reference({1: 0.30000000000000004, 3: 0.30000000000000004}), 4: Layer(trace_width=0.0635, pair_spacing=0.0635, clearance=0.127, velocity=174250826755.11264, insertion_loss=0.01579).reference({3: 0.30000000000000004, 5: 0.30000000000000004}), -3: Layer(trace_width=0.0635, pair_spacing=0.0635, clearance=0.127, velocity=174250826755.11264, insertion_loss=0.01579).reference({-2: 0.30000000000000004, -4: 0.30000000000000004}), -5: Layer(trace_width=0.0635, pair_spacing=0.0635, clearance=0.127, velocity=174250826755.11264, insertion_loss=0.01579).reference({-4: 0.30000000000000004, -6: 0.30000000000000004})})#
Neck region of DIFF-100, gap stated edge to edge.
The report models this geometry at 106.54 ohm on stripline and 111.71 ohm on microstrip, 4.9% apart. It is declared at 106.54 ohm, the stripline row’s modelled figure, labelled, for the reasons given on
Neck_SE. Velocity and loss are DIFF-100’s, a labelled default.A design applies it by tagging the pair’s code-placed
Routeover the necked span, with a rule at a priority above theDRS_100rule on the pair’s net tag. No build has confirmed this.
- BOARD_CORNER_RADIUS = 4.0#
Board outline, from the report’s DOCUMENT section (“Board size”).
- class HDIBoard[source]#
Bases:
Board80 x 80 mm board on the 20-layer HDI substrate.
Boardholds onlyshapeandsignal_area. Via registration and substrate binding are not board concerns — vias come from walking the substrate, and the substrate is bound on theDesign.- 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.