Source code for jitxexamples.jumpstart_kits.js1_stackup_components.versal_fpga.symbols

"""Symbol partitioning and pad-mapping helpers for the VP1002 component.

The generated component (:mod:`.xcvp1002`) stays declarative: it yields
``SymbolPartition`` records and ``(port, (row, col))`` ball assignments; the
two builders here turn those into jitxlib ``BoxSymbol`` instances and a
``PadMapping``. Keeping the builders handwritten (and tiny) means the
generated file contains data-shaped code only.

Ports are never *stored* in extra containers on the component — a ``Port``
may have only one home in the design tree (JITX raises "encountered
multiple times" otherwise). Partitions are therefore exposed as a method
that yields fresh records; tests assert symbol coverage by re-calling it.

Partitioning rules
    At 1369 balls there is no single readable symbol: the partition is the
    part's schematic, so it is stated as rules and held by checks. The
    policy lives in ``tools.generate_pinout.partition_symbols``.

    * One box per structural group the source document defines. The AMD
      package pinout file (``xcvp1002nfvi1369pkg.txt``) has a ``Bank``
      column, so every banked ball (I/O pins, the transceiver quad pins of
      the GTM and GTY banks, and the bank's own supplies) goes to its
      bank's box. On another part, take the grouping from the axis its pin table
      is organized by (I/O bank, transceiver quad, power domain, peripheral
      block). A grouping whose defining document cannot be named is
      invented.
    * A stated per-box ceiling for groups with no natural size. The
      bankless rails would otherwise be single boxes of hundreds of pins
      (689 ``GND`` balls, 84 ``VCCINT``); they are chunked at
      ``MAX_PINS_PER_BOX``, and the smaller bankless rails are packed
      together up to the same ceiling. ``SymbolPartition`` rejects a box
      over the cap at instantiation, and the test suite checks every box
      against it and checks that the generator's copy of the constant
      matches. Pick the number by looking at the rendered sheet at the
      schematic's page size, then keep the test that enforces it.
    * Every port in exactly one partition, asserted by a test
      (``test_symbol_coverage`` in ``tests/test_js1_versal_fpga.py``): no
      port seen twice, whether across boxes or inside one, and the union of
      all boxes equals every port of the component. The duplicate inside
      one box needs the test because nothing else reports it: ``BoxSymbol``
      maps each port to one pin, so a port listed twice in a box keeps only
      its last pin and leaves the first unmapped.
    * Pins that belong to no group get their own box. Bankless dedicated
      and analog pins share one "Dedicated / analog singletons" box rather
      than being spread across other boxes for convenience.
    * Regular fixed-size slices only for parts with no functional
      structure: generic connectors, board-to-board headers, pin arrays. An
      FPGA, MCU or memory is partitioned by the structure its datasheet
      gives first.

Schematic groups
    :class:`.circuit.XCVP1002Circuit` puts each box in its own
    ``SchematicGroup`` (one per ``Symbol`` from ``extract(self.fpga,
    Symbol)``) so banks and rail chunks can be placed on separate sheets.
    A rail spread over several boxes stays one named net, and named nets
    are labeled in the schematic, so the boxes connect through net labels
    rather than drawn wires.
"""

from collections.abc import Iterable
from dataclasses import dataclass

from jitx import PadMapping
from jitx.landpattern import Pad
from jitx.net import Port
from jitxlib.symbols.box import BoxSymbol, Column, PinGroup, Row

from .landpattern import VersalBGA

#: Readability cap — no symbol box gets more pins than this.
MAX_PINS_PER_BOX = 64


[docs] @dataclass(frozen=True) class SymbolPartition: """Ports of one schematic box: left/right rows, optional bottom column.""" left: tuple[Port, ...] = () right: tuple[Port, ...] = () down: tuple[Port, ...] = ()
[docs] def ports(self) -> tuple[Port, ...]: return self.left + self.right + self.down
def __post_init__(self) -> None: total = len(self.ports()) if total == 0: raise ValueError("empty symbol partition") if total > MAX_PINS_PER_BOX: raise ValueError(f"partition has {total} pins > {MAX_PINS_PER_BOX}")
[docs] def build_symbols(partitions: Iterable[SymbolPartition]) -> list[BoxSymbol]: """Turn partitions into one ``BoxSymbol`` each (empty sides skipped).""" symbols: list[BoxSymbol] = [] for part in partitions: row = Row( left=PinGroup(*part.left) if part.left else (), right=PinGroup(*part.right) if part.right else (), ) columns = Column(down=PinGroup(*part.down)) if part.down else () symbols.append(BoxSymbol(rows=row, columns=columns)) return symbols
[docs] def build_pad_mapping( lp: VersalBGA, assignments: Iterable[tuple[Port, tuple[int, int]]], expected_total: int, ) -> PadMapping: """Map every port to its BGA pad, reconciling the ball count. ``assignments`` carries zero-indexed ``(row, col)`` grid coordinates as emitted by the pinout generator; ``VersalBGA.get_pad`` takes the 1-indexed ball column, so the ``+ 1`` lives here and nowhere else. """ mapping: dict[Port, list[Pad]] = {} seen: set[tuple[int, int]] = set() for port, (row, col) in assignments: if (row, col) in seen: raise ValueError(f"duplicate ball assignment at {(row, col)}") seen.add((row, col)) if port in mapping: raise ValueError(f"port assigned twice: {port} at {(row, col)}") mapping[port] = [lp.get_pad(row, col)] if len(mapping) != expected_total: raise ValueError( f"ball count mismatch: {len(mapping)} assignments, " f"expected {expected_total}" ) return PadMapping(mapping)