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 — 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.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.
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.
``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.0rc2). 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.
- 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.
- 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.
- 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.
- 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 -> 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.
- 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 -> 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.
- 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 -> 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.
- 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 and 4 routing 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, 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 inToleranced.exact, so lookup matches exactly and a design has to ask for55 * ohmby 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
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, 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:
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.