Source code for jitxexamples.demos.si_bga_optimization.substrate

"""BGA-escape substrate specialization.

``BGAEscapeSubstrate`` extends the generic 20-layer substrate with the
85 ohm differential stripline routing structure and the per-signal-layer
launch profile table (``PerSignalLayerSpec``), and anchors the antipad
fence-via rules defined in ``constraints`` into the design tree. The
substrate base file stays truly generic; everything BGA-escape-specific
attaches here.
"""

from dataclasses import dataclass

from jitx import KeepOut, LayerSet
from jitx.constraints import ViaFencePattern
from jitx.si import (
    DifferentialRoutingStructure,
    RoutingStructure,
    symmetric_routing_layers,
)
from jitx.units import ohm
from jitxexamples.substrates.generic_20layer import (
    VEL_STRIPLINE,
    Generic_Substrate,
)
from . import constraints
from .constraints import (
    BGA_ANTIPAD_FENCE_PITCH,
    AntipadFenceTag,
    DeskewAntipadFenceTag,
    Ground2AntipadFenceTag,
    Ground2DeskewAntipadFenceTag,
    Ground3AntipadFenceTag,
    Ground3DeskewAntipadFenceTag,
    Ground4DeskewAntipadFenceTag,
)


# -----------------------------------------------------------------------------
# Stripline geometry constants.
# -----------------------------------------------------------------------------

BGA_MAIN_TRUNK_DP_TO_GND = 0.25

# Diff-pair stripline geometry for every 85 ohm signal layer (L2/L4/L6/L8).
DIFFPAIR_TRACE_WIDTH = 0.115
DIFFPAIR_PAIR_SPACING = 0.118

# Route-following pour keepout corridor width on the signal layer.
# ``geometry(KeepOut, width, ...)`` is applied per-trace centered on
# each leg's centerline; the two per-leg corridors union together. So
# the per-leg width is just trace_width + 2 x clearance -- adding the
# pair span here double-counts pair_spacing + trace_width on each side.
_DP_TO_GND_POUR_CORRIDOR = DIFFPAIR_TRACE_WIDTH + 2 * BGA_MAIN_TRUNK_DP_TO_GND


def _drs_layer_85(
    signal_layer: int, upper_ref: int, lower_ref: int, fence_via_cls
) -> "DifferentialRoutingStructure.Layer":
    """85 ohm coplanar differential stripline layer entry.

    Parameterized by signal layer index, the two reference plane layer
    indices, and the fence-via class that ties them. References on three
    layers make this a coplanar stripline: the upper and lower ground
    planes (stripline) plus a coplanar GND reference on the signal layer
    itself. The DP-to-GND-pour (0.25 mm) clearance is the route-following
    KeepOut corridor on the signal layer (so the coplanar GND keeps its
    distance), expressed here rather than via a tag-keyed rule.
    """
    return (
        DifferentialRoutingStructure.Layer(
            trace_width=DIFFPAIR_TRACE_WIDTH,
            pair_spacing=DIFFPAIR_PAIR_SPACING,
            clearance=0.05,  # fab floor
            velocity=VEL_STRIPLINE,
            insertion_loss=0.018,
        )
        .reference(upper_ref, 1.0)
        .reference(lower_ref, 1.0)
        .reference(signal_layer, 1.0)  # coplanar GND on the signal layer
        .fence(fence_via_cls, _FENCE_PATTERN, reference_layer=lower_ref)
        .geometry(
            KeepOut, _DP_TO_GND_POUR_CORRIDOR, layers=LayerSet(signal_layer), pour=True
        )
    )


def _rs_uncoupled_layer(signal_layer: int) -> "RoutingStructure.Layer":
    """Single-ended uncoupled-region layer entry for the deskew/escape.

    References match the coplanar stripline: upper plane (signal-1),
    lower plane (signal+1), and the coplanar GND on the signal layer.
    ``symmetric_routing_layers`` mirrors these for the bottom half.
    """
    return (
        RoutingStructure.Layer(
            trace_width=0.10,
            clearance=0.135,
            velocity=VEL_STRIPLINE,
            insertion_loss=0.018,
            neck_down=RoutingStructure.NeckDown(trace_width=0.10, clearance=0.135),
        )
        .reference(signal_layer - 1, 1.0)
        .reference(signal_layer + 1, 1.0)
        .reference(signal_layer, 1.0)
    )


# Diff-pair trunk fence: shares the antipad fence pitch; offset 0.43 mm =
# trace-half 0.051 + DP-to-GND 0.25 + via-pad-radius 0.125.
_FENCE_PATTERN = ViaFencePattern(
    pitch=BGA_ANTIPAD_FENCE_PITCH,
    offset=0.43,
    num_rows=1,
)


# -----------------------------------------------------------------------------
# Per-signal-layer launch profile.
# -----------------------------------------------------------------------------


[docs] @dataclass(frozen=True) class PerSignalLayerSpec: """Layer-dependent geometry knobs for one signal-layer launch. The deskew layer index ``d`` is ``2 * signal_layer - 1`` (Signal1 -> 1, Signal2 -> 3, Signal3 -> 5, Signal4 -> 7). Antipad keepouts span layers in ``antipad_layers``; if ``split_antipad_layers`` is set, the keepout on those layers is split into per-via circular cuts instead of a single capsule covering both vias. The signal-via antipad fence pour sits on the upper reference plane (idx d-1); the deskew antipad fence pour sits on the lower reference plane (idx d+1). Signal1 (d=1) has no upper-reference fence pour (only L1 sits above); those fields are ``None`` for that spec. """ signal_layer: int deskew_layer: int antipad_layers: LayerSet upper_ref_fence_pour_layer: int | None upper_ref_fence_tag: type | None deskew_antipad_layers: LayerSet deskew_antipad_fence_pour_layer: int deskew_antipad_fence_tag: type split_antipad_layers: LayerSet | None = None
# ----------------------------------------------------------------------------- # Substrate specialization: BGA-escape design-specific routing structure, # anchored design_constraint rules, and per-signal-layer launch profiles. # -----------------------------------------------------------------------------
[docs] class BGAEscapeSubstrate(Generic_Substrate): """Generic 20-layer substrate specialized for the BGA escape design. Adds the 85 ohm differential stripline routing structure (one entry per signal layer plus its symmetric mirror) and the per-signal-layer launch profile table (queried by each ``EscapeLane``), and anchors the ``constraints`` fence-via rules that drive antipad / fence-via generation around the BGA breakout cavity. """ # 85 ohm differential stripline: one entry per signal layer plus # its symmetric mirror. Top + bottom halves are listed explicitly # because ``symmetric_routing_layers`` can't auto-mirror the fence # uVia's layer endpoints. DRS_DiffPair_85 = DifferentialRoutingStructure( name="85 ohm Differential Stripline", impedance=85 * ohm, layers={ # Top half (Signal1..Signal4) 1: _drs_layer_85(1, 0, 2, Generic_Substrate.uFence_L1_L3), # L2 3: _drs_layer_85(3, 2, 4, Generic_Substrate.uFence_L3_L5), # L4 5: _drs_layer_85(5, 4, 6, Generic_Substrate.uFence_L5_L7), # L6 7: _drs_layer_85(7, 6, 8, Generic_Substrate.uFence_L7_L9), # L8 # Bottom-half mirrors -2: _drs_layer_85(-2, -1, -3, Generic_Substrate.uFence_L18_L20), -4: _drs_layer_85(-4, -3, -5, Generic_Substrate.uFence_L16_L18), -6: _drs_layer_85(-6, -5, -7, Generic_Substrate.uFence_L14_L16), -8: _drs_layer_85(-8, -7, -9, Generic_Substrate.uFence_L12_L14), }, uncoupled_region=RoutingStructure( name="85 ohm Uncoupled (~42 ohm SE)", impedance=42 * ohm, # Fresh per-layer instance (with references) so the four signal # layers don't share one Layer object under JITX's structural # identity; ``symmetric_routing_layers`` mirrors the bottom half. layers=symmetric_routing_layers( {i: _rs_uncoupled_layer(i) for i in (1, 3, 5, 7)} ), ), ) # Anchor the antipad fence-via rules defined in ``constraints``: # JITX discovers ``DesignConstraint`` rules by walking the design # tree, so each rule must be reachable as a substrate (or circuit) # attribute to take effect. signal_via_antipad_fence = constraints.signal_via_antipad_fence signal_via_antipad_fence_g3 = constraints.signal_via_antipad_fence_g3 signal_via_antipad_fence_g2 = constraints.signal_via_antipad_fence_g2 deskew_antipad_fence = constraints.deskew_antipad_fence deskew_antipad_fence_g4 = constraints.deskew_antipad_fence_g4 deskew_antipad_fence_g3 = constraints.deskew_antipad_fence_g3 deskew_antipad_fence_g2 = constraints.deskew_antipad_fence_g2 # Per-signal-layer launch profile table. Queried by each EscapeLane # via ``spec_for_signal_layer``. Keyed by signal layer index (1..4). _SIGNAL_LAYER_PROFILES: dict[int, PerSignalLayerSpec] = { 1: PerSignalLayerSpec( signal_layer=1, deskew_layer=1, antipad_layers=LayerSet(0, 1), upper_ref_fence_pour_layer=None, upper_ref_fence_tag=None, deskew_antipad_layers=LayerSet(1), deskew_antipad_fence_pour_layer=2, deskew_antipad_fence_tag=Ground2DeskewAntipadFenceTag, ), # Signal2: capsule keepout retained only on L4 (the trace breakout # needs a single shared opening); L1/L2/L3 are split into per-via # circular cuts by default. The instrumented lane overrides L1 with # mirrored D cuts while keeping L2/L3 circular. 2: PerSignalLayerSpec( signal_layer=2, deskew_layer=3, antipad_layers=LayerSet(3), split_antipad_layers=LayerSet(0, 1, 2), upper_ref_fence_pour_layer=2, upper_ref_fence_tag=Ground2AntipadFenceTag, deskew_antipad_layers=LayerSet(3), deskew_antipad_fence_pour_layer=4, deskew_antipad_fence_tag=Ground3DeskewAntipadFenceTag, ), 3: PerSignalLayerSpec( signal_layer=3, deskew_layer=5, antipad_layers=LayerSet(0, 1, 2, 3, 4, 5), upper_ref_fence_pour_layer=4, upper_ref_fence_tag=Ground3AntipadFenceTag, deskew_antipad_layers=LayerSet(5), deskew_antipad_fence_pour_layer=6, deskew_antipad_fence_tag=Ground4DeskewAntipadFenceTag, ), 4: PerSignalLayerSpec( signal_layer=4, deskew_layer=7, antipad_layers=LayerSet(0, 1, 2, 3, 4, 5, 6, 7), upper_ref_fence_pour_layer=6, upper_ref_fence_tag=AntipadFenceTag, deskew_antipad_layers=LayerSet(7), deskew_antipad_fence_pour_layer=8, deskew_antipad_fence_tag=DeskewAntipadFenceTag, ), }
[docs] @classmethod def spec_for_signal_layer(cls, signal_layer: int) -> PerSignalLayerSpec: """Launch profile for a signal layer (1..4).""" return cls._SIGNAL_LAYER_PROFILES[signal_layer]