"""
The BGA Landpattern Generator
=============================
BGA (Ball Grid Array) packages use an array of solder balls on the bottom
surface instead of peripheral leads. This module provides generators for
creating BGA landpatterns with customizable grid configurations.
The BGA landpattern has three entry points:
- :py:class:`~BGA`: Complete generator with BGA-style alphanumeric
numbering (row letters from ``A``, columns from 1, e.g. ``.A[1]``,
``.B[3]``)
- :py:class:`~BGADecorated`: With silkscreen but no numbering scheme
- :py:class:`~BGABase`: Basic pads only
Constructor Parameters:
num_rows: Number of rows in the ball grid (vertical)
num_cols: Number of columns in the ball grid (horizontal)
ball_diameter: Copper land diameter from the PCB design guidelines, not the drawn ball (see :py:class:`~BGABase`)
pitch: Center-to-center spacing between balls (typical: 0.4-1.27mm)
>>> from jitx import Component, Port
>>> from jitx.landpattern import PadMapping
>>> from jitxlib.landpatterns.generators.bga import BGA
>>> from jitxlib.landpatterns.pads import SMDPadConfig
>>>
>>> class MyBGA(Component):
... # 3x3 BGA, rows A to C, columns 1 to 3
... balls = [[Port() for _ in range(3)] for _ in range(3)]
... def __init__(self):
... self.landpattern = BGA(
... num_rows=3,
... num_cols=3,
... ball_diameter=0.4, # copper land diameter
... pitch=0.8,
... ).pad_config(SMDPadConfig())
... rows = (self.landpattern.A, self.landpattern.B, self.landpattern.C)
... self.mapping = PadMapping(
... {self.balls[r][c]: rows[r][c + 1] for r in range(3) for c in range(3)}
... )
Note:
BGA packages require careful PCB design for routing escape paths.
Consider via-in-pad or dog-bone patterns for dense arrays.
"""
from collections.abc import Iterable
from jitx.anchor import Anchor
from jitx.shapes.primitive import Circle
from jitx.transform import Transform
from ..courtyard import ExcessCourtyard
from ..grid_layout import A1, AlphaDictNumbering, GridLandpatternGenerator, GridPosition
from ..grid_planner import GridPlannerMixin
from ..pads import GridPadShapeGeneratorMixin
from ..silkscreen.labels import ReferenceDesignatorMixin
from ..silkscreen.marker import Pad1Marker
from ..silkscreen.outlines import SilkscreenOutline
[docs]
class BGABase(
GridPlannerMixin,
GridPadShapeGeneratorMixin,
GridLandpatternGenerator,
):
"""BGA Landpattern Generator Base
Pads only, on a regular rectangular grid centered on the origin, with no
silkscreen, courtyard or numbering scheme.
"""
def __init__(
self,
num_rows: int,
num_cols: int,
ball_diameter: float,
pitch: float | tuple[float, float],
):
"""Set up the ball grid.
Args:
num_rows: Number of rows in the grid. Row 0 is the top row, at
the largest y.
num_cols: Number of columns in the grid. Column 0 is the left
column, at the smallest x.
ball_diameter: Diameter of the circular copper land drawn for
every ball: the PCB land diameter, not the solder ball
diameter on the mechanical drawing.
pitch: Center-to-center ball spacing, or a ``(horizontal,
vertical)`` pair when the two differ.
"""
super().__init__()
self._num_rows = num_rows
self._num_cols = num_cols
# self.__ball_diameter = ball_diameter
if not isinstance(pitch, tuple):
pitch = (pitch, pitch)
assert len(pitch) == 2, "pitch must be a tuple of two values"
self.__pitch = pitch
self.pad_shape(Circle(diameter=ball_diameter))
def _generate_layout(self) -> Iterable[GridPosition]:
num_rows = self._num_rows
num_cols = self._num_cols
hpitch, vpitch = self.__pitch
center_row = (num_rows - 1) / 2.0
center_col = (num_cols - 1) / 2.0
# TODO: not sure how to handle this, anchoring is useful for creating
# sub-landpatterns that can be placed relative to an anchor; but I
# don't know what the anchor point should be. Center of the grid
# position at the appropriate corner?
# half_width = pitch * (num_cols - 1) / 2.0
# half_height = pitch * (num_rows - 1) / 2.0
# bounds = (-half_width, -half_height, half_width, half_height)
# center_x, center_y = self._get_anchor().flip().to_point(bounds)
center_x, center_y = 0, 0
for r in range(num_rows):
row_y = (center_row - r) * vpitch + center_y
for c in range(num_cols):
x = (c - center_col) * hpitch + center_x
yield GridPosition(r, c, Transform.translate(x, row_y))
[docs]
class BGADecorated(
SilkscreenOutline, Pad1Marker, ReferenceDesignatorMixin, ExcessCourtyard, BGABase
):
"""Decorated BGA Landpattern Generator, with no pad numbering scheme.
Adds a silkscreen outline, a pad 1 marker, a reference designator and a
courtyard to :py:class:`~BGABase`. Subclass it to build a package whose
balls are not all on one regular grid (a second pitch in one region,
offset ball groups, a staggered or split grid): override
``_generate_layout`` to yield one
:py:class:`~jitxlib.landpatterns.grid_layout.GridPosition` per ball, then
compose a numbering scheme in front of the subclass the same way
:py:class:`~BGA` does. ``row`` and ``column`` in each position are the
zero-indexed indices the numbering uses, and ``pose`` places the ball.
Row 0 is lettered ``A`` and is the top row, at the largest y, so compute y
as ``(center_row - r) * pitch`` as :py:class:`~BGABase` does. Any
``.grid_planner(...)`` is still applied to the yielded positions.
>>> class StaggeredLayout(BGADecorated):
... # Odd rows shifted right by half a pitch.
... def _generate_layout(self) -> Iterable[GridPosition]:
... pitch = 1.0
... center_row = (self._num_rows - 1) / 2.0
... center_col = (self._num_cols - 1) / 2.0
... for r in range(self._num_rows):
... y = (center_row - r) * pitch
... shift = pitch / 2 if r % 2 else 0.0
... for c in range(self._num_cols):
... x = (c - center_col) * pitch + shift
... yield GridPosition(r, c, Transform.translate(x, y))
>>> class StaggeredBGA(A1, AlphaDictNumbering, StaggeredLayout):
... pass
>>> lp = StaggeredBGA(num_rows=4, num_cols=4, pitch=1.0, ball_diameter=0.5).pad_config(
... SMDPadConfig()
... )
"""
def __base_init__(self):
super().__base_init__()
self.pad_1_marker_direction(Anchor.W)
[docs]
class BGA(A1, AlphaDictNumbering, BGADecorated):
"""BGA Landpattern Generator
A regular ball grid with BGA-style pad names. Rows are lettered from
``A`` at the top (largest y) and columns are numbered from 1 at the left,
so pads are reached as ``lp.A[1]`` or ``lp.B[12]``. Row letters skip I,
O, Q, S, X and Z and continue with two letters after ``Y``; see
:py:class:`~jitxlib.landpatterns.grid_layout.AlphaDictNumbering`.
The constructor takes ``num_rows``, ``num_cols``, ``ball_diameter`` and
``pitch`` (see :py:class:`~BGABase`); ``ball_diameter`` is the copper land
diameter, not the solder ball. Everything else is set by chaining:
- ``.pad_config(SMDPadConfig())`` is required; the generator has no
default pad configuration.
- ``.grid_planner(...)`` removes depopulated positions; see
:py:class:`~jitxlib.landpatterns.grid_planner.GridPlanner`. Without a
planner every grid position gets a pad.
- ``.package_body(RectanglePackage(...))`` is optional; when set, the
courtyard covers the body as well as the pads.
For balls off a single regular grid, subclass :py:class:`~BGADecorated`
and override ``_generate_layout``.
>>> class MyBGA(Component):
... def __init__(self):
... self.landpattern = (
... BGA(num_rows=12, num_cols=12, pitch=0.5, ball_diameter=0.25)
... .pad_config(SMDPadConfig())
... .grid_planner(PerimeterGridPlanner(3))
... .package_body(
... RectanglePackage(
... width=Toleranced.exact(7.0),
... length=Toleranced.exact(7.0),
... height=Toleranced.min_max(0.8, 1.0),
... )
... )
... )
"""