Source code for qpdk.cells.tsv

r"""Through-silicon via (TSV) component library for gdsfactory.

The geometry follows the superconducting TSVs of
:cite:`mallekFabricationSuperconductingThroughsilicon2021`: slot-shaped vias
(:math:`10 \times 20\,\text{µm}`) lined with superconducting TiN, connecting
the front-side metal (side A, ``M1``) to the backside metal (side B, ``MB``).
Backside geometry is drawn in the same (front-side, top-down) coordinate frame
as ``M1``, so the backside mask is the mirror image of the drawn ``MB`` layers.

Every cell takes a ``chip`` argument. ``"bottom"`` (default) is the base chip
above; ``"top"`` puts the via through the flip-chip top chip instead, joining
``M2`` to its backside metal ``MB2`` through ``TSV2``. The layers of each chip
come from :data:`~qpdk.tech.CHIP_LAYERS`.
"""

from __future__ import annotations

from typing import cast

import gdsfactory as gf
import numpy as np
from gdsfactory.component import Component
from gdsfactory.typings import CrossSectionSpec

from qpdk.cells._schematic import (
    tsv_schematic,
    tsv_transition_double_sided_schematic,
)
from qpdk.cells.waveguides import add_etch_gap, straight, taper_cross_section
from qpdk.tech import CHIP_LAYERS, Chip, get_etch_section


def _stadium_points(width: float, length: float, n: int = 32) -> np.ndarray:
    """Points of a stadium (slot) of total ``length`` along x, centred at the origin."""
    r = width / 2
    dx = max(length - width, 0.0) / 2
    t = np.linspace(-np.pi / 2, np.pi / 2, n)
    right = np.column_stack([dx + r * np.cos(t), r * np.sin(t)])
    left = np.column_stack([-dx - r * np.cos(t), -r * np.sin(t)])
    return np.vstack([right, left])


[docs] @gf.cell(tags=["interconnects", "3d-integration"], schematic_function=tsv_schematic) def tsv( width: float = 10.0, length: float = 20.0, pad_margin: float = 2.0, chip: Chip = "bottom", ) -> Component: r"""Creates a slot-shaped superconducting through-silicon via (TSV). The default :math:`10 \times 20\,\text{µm}` slot is the TSV of :cite:`mallekFabricationSuperconductingThroughsilicon2021`, see also :cite:`yostSolidstateQubitsIntegrated2020`. Use ``width == length`` for a round via. Landing pads on the front- and backside metal enclose the via by ``pad_margin``, so the via stays covered by metal even when placed next to an etched gap. With ``chip="top"`` the via runs through the flip-chip top chip, on ``TSV2`` with pads on ``M2`` and ``MB2``, see :data:`~qpdk.tech.LAYER_STACK_FLIP_CHIP_BACKSIDE`. .. svgbob:: .-----------. ( TSV slot ) ── center '-----------' Args: width: Via width (short axis, along y) in µm. length: Via length (long axis, along x) in µm. pad_margin: Landing-pad enclosure of the via on each metal layer in µm. ``0`` omits the pads. chip: Chip the via goes through, ``"bottom"`` or ``"top"``. Returns: A gdsfactory Component representing the TSV. Raises: ValueError: If ``length`` is smaller than ``width``. """ if length < width: msg = f"TSV length ({length}) must be at least its width ({width})." raise ValueError(msg) layers = CHIP_LAYERS[chip] c = Component() c.add_polygon(_stadium_points(width, length), layer=layers.tsv) if pad_margin > 0: for pad_layer in (layers.front_draw, layers.back_draw): c.add_polygon( _stadium_points(width + 2 * pad_margin, length + 2 * pad_margin), layer=pad_layer, ) c.add_port( name="center", center=(0, 0), orientation=0, layer=layers.tsv, width=width, port_type="placement", ) return c
def _cpw_pad_cross_section( cross_section: CrossSectionSpec, width: float, gap: float ) -> CrossSectionSpec: """CPW cross-section with the layers of ``cross_section`` and a new width and gap. Returns: A spec of the registered ``coplanar_waveguide`` rather than a ``CrossSection`` object, so that netlists record it by name and settings and circuit models (e.g. ``taper_cross_section``) can resolve it after serialization. """ xs = gf.get_cross_section(cross_section) return { "cross_section": "coplanar_waveguide", "settings": { "width": width, "gap": gap, "waveguide_layer": gf.get_layer_name(xs.layer), "etch_layer": gf.get_layer_name(get_etch_section(xs).layer), "radius": None, }, } def _add_tapered_pad( c: Component, cross_section: CrossSectionSpec, pad_width: float, pad_gap: float, pad_length: float, taper_length: float, mirror: bool, ) -> gf.Port: """Add a CPW taper into a closed pad centred at the origin. The taper enters from ``-x`` (``+x`` when ``mirror``) and the pad is closed by an etch gap on the far side. Returns: The CPW port at the narrow end of the taper. """ pad_xs = _cpw_pad_cross_section(cross_section, pad_width, pad_gap) pad = c << straight(length=pad_length, cross_section=pad_xs) pad.move((-pad_length / 2, 0)) if mirror: pad.mirror_x(0) taper = c << taper_cross_section( length=taper_length, cross_section1=cross_section, cross_section2=pad_xs, linear=True, ) taper.connect("o2", pad.ports["o1"]) # The pad ends in a closing gap, so it has no external port. Flatten it (and # the gap) so their instance ports cannot coincide with those of the pad on # the other side of the substrate. cast(gf.ComponentReference, add_etch_gap(c, pad.ports["o2"], pad_xs)).flatten() pad.flatten() return taper.ports["o1"] def _add_transition_vias( c: Component, pad_width: float, pad_gap: float, pad_length: float, via_width: float, via_length: float, via_pad_margin: float, n_signal_vias: int, signal_via_pitch: float, n_ground_vias_per_side: int, ground_via_pitch: float, ground_via_distance: float, chip: Chip, ) -> None: """Add the signal TSVs inside the pad and the ground-stitching TSVs around it.""" signal_span = (n_signal_vias - 1) * signal_via_pitch + via_width if ( signal_span + 2 * via_pad_margin > pad_width or via_length + 2 * via_pad_margin > pad_length ): msg = ( f"{n_signal_vias} signal TSV(s) of {via_width}x{via_length} µm at pitch " f"{signal_via_pitch} µm do not fit in a {pad_length}x{pad_width} µm pad." ) raise ValueError(msg) if ground_via_distance < via_width / 2 + via_pad_margin: msg = ( f"ground_via_distance={ground_via_distance} µm puts the ground TSV " "landing pads into the CPW gap." ) raise ValueError(msg) via = tsv(width=via_width, length=via_length, pad_margin=via_pad_margin, chip=chip) for y in (np.arange(n_signal_vias) - (n_signal_vias - 1) / 2) * signal_via_pitch: (c << via).move((0, float(y))) y_ground = pad_width / 2 + pad_gap + ground_via_distance xs = ( np.arange(n_ground_vias_per_side) - (n_ground_vias_per_side - 1) / 2 ) * ground_via_pitch for x in xs: for y in (y_ground, -y_ground): (c << via).move((float(x), y))
[docs] @gf.cell(tags=["interconnects", "3d-integration", "waveguides"]) def tsv_transition( cross_section: CrossSectionSpec | None = None, taper_length: float = 100.0, pad_width: float = 40.0, pad_gap: float = 24.0, pad_length: float = 30.0, via_width: float = 10.0, via_length: float = 20.0, via_pad_margin: float = 2.0, n_signal_vias: int = 2, signal_via_pitch: float = 20.0, n_ground_vias_per_side: int = 4, ground_via_pitch: float = 30.0, ground_via_distance: float = 15.0, chip: Chip = "bottom", ) -> Component: r"""Front-side CPW tapered into a TSV landing pad. The CPW widens linearly over ``taper_length`` into a pad carrying ``n_signal_vias`` parallel signal TSVs, surrounded by ``2 * n_ground_vias_per_side`` TSVs stitching the front and back ground planes. On the backside, the signal TSVs land on an isolated ``MB`` pad of the same size, clear of the backside ground by ``pad_gap``. Connect other backside structures at the ``backside`` placement port. With ``chip="top"`` the same transition is built on the flip-chip top chip, from ``M2`` to ``MB2``. The defaults follow the transition of :cite:`mallekFabricationSuperconductingThroughsilicon2021`, Fig. 6: two signal TSVs in parallel (a single TSV gives a large impedance mismatch) and eight ground TSVs. The pad gap keeps the pad close to the aspect ratio :math:`w / (w + 2s)` of the default :math:`50\,\Omega` CPW; the taper and pad dimensions are the knobs to optimise the transition impedance, see the ``palace_tsv_transition`` notebook. .. svgbob:: o o o o <- ground TSVs ___________________ o1 ── / [==] | \ [==] | <- signal TSVs in pad ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ o o o o Args: cross_section: Front-side CPW cross-section at the ``o1`` port. ``None`` uses the front cross-section of ``chip``. taper_length: Length of the linear taper from the CPW to the pad in µm. pad_width: Centre-conductor width of the pad in µm. pad_gap: Gap between the pad and ground in µm. pad_length: Length of the pad along the propagation direction in µm. via_width: TSV width in µm. via_length: TSV length in µm. via_pad_margin: TSV landing-pad enclosure in µm. n_signal_vias: Number of parallel signal TSVs in the pad. signal_via_pitch: Centre-to-centre pitch of the signal TSVs (along y) in µm. n_ground_vias_per_side: Number of ground TSVs on each side of the pad. ground_via_pitch: Centre-to-centre pitch of the ground TSVs (along x) in µm. ground_via_distance: Distance from the ground-plane edge to the ground TSV centres in µm. chip: Chip the TSVs go through, ``"bottom"`` or ``"top"``. Returns: Component with the CPW port ``o1`` and the ``backside`` placement port at the signal TSVs. """ layers = CHIP_LAYERS[chip] c = Component() port = _add_tapered_pad( c, cross_section or layers.front_cross_section, pad_width, pad_gap, pad_length, taper_length, mirror=False, ) # Isolated backside landing pad for the signal TSVs backside_pad_xs = _cpw_pad_cross_section( layers.back_cross_section, pad_width, pad_gap ) backside_pad = c << straight(length=pad_length, cross_section=backside_pad_xs) backside_pad.move((-pad_length / 2, 0)) for p in backside_pad.ports: cast(gf.ComponentReference, add_etch_gap(c, p, backside_pad_xs)).flatten() # The pad is only reached through the TSVs, so it contributes polygons but # no ports (which would otherwise overlap the frontside taper port). backside_pad.flatten() _add_transition_vias( c, pad_width, pad_gap, pad_length, via_width, via_length, via_pad_margin, n_signal_vias, signal_via_pitch, n_ground_vias_per_side, ground_via_pitch, ground_via_distance, chip, ) c.add_port(name="o1", port=port) c.add_port( name="backside", center=(0, 0), orientation=0, layer=layers.back_draw, width=pad_width, port_type="placement", ) return c
[docs] @gf.cell( tags=["interconnects", "3d-integration", "waveguides"], schematic_function=tsv_transition_double_sided_schematic, ) def tsv_transition_double_sided( cross_section: CrossSectionSpec | None = None, cross_section_backside: CrossSectionSpec | None = None, taper_length: float = 100.0, taper_length_backside: float = 100.0, pad_width: float = 40.0, pad_gap: float = 24.0, pad_length: float = 30.0, via_width: float = 10.0, via_length: float = 20.0, via_pad_margin: float = 2.0, n_signal_vias: int = 2, signal_via_pitch: float = 20.0, n_ground_vias_per_side: int = 4, ground_via_pitch: float = 30.0, ground_via_distance: float = 15.0, chip: Chip = "bottom", ) -> Component: r"""CPW transition from the front side to the backside through TSVs. A front-side CPW (``o1``, on ``M1``) tapers into a pad, the signal passes through ``n_signal_vias`` parallel TSVs, and a backside CPW (``o2``, on ``MB``) tapers out of an identical pad in the opposite direction. ``2 * n_ground_vias_per_side`` TSVs stitch the front and back ground planes around the pad. This is the side A to side B transition of :cite:`mallekFabricationSuperconductingThroughsilicon2021`, Fig. 6. Simulate it with :data:`~qpdk.tech.LAYER_STACK_BACKSIDE`; the backside metal also turns every CPW on the chip into a conductor-backed CPW, see the ``conductor_backed`` option of :func:`~qpdk.models.cpw_parameters`. With ``chip="top"`` it runs from ``M2`` to ``MB2`` through the flip-chip top chip instead, see :data:`~qpdk.tech.LAYER_STACK_FLIP_CHIP_BACKSIDE`. .. svgbob:: front (M1) back (MB) ______________________ o1 ── / [==] \ ── o2 \ [==] / ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ Args: cross_section: Front-side CPW cross-section at ``o1``. ``None`` uses the front cross-section of ``chip``. cross_section_backside: Backside CPW cross-section at ``o2``. ``None`` uses the back cross-section of ``chip``. taper_length: Length of the front-side taper in µm. taper_length_backside: Length of the backside taper in µm. pad_width: Centre-conductor width of both pads in µm. pad_gap: Gap between the pads and ground in µm. pad_length: Length of the pads along the propagation direction in µm. via_width: TSV width in µm. via_length: TSV length in µm. via_pad_margin: TSV landing-pad enclosure in µm. n_signal_vias: Number of parallel signal TSVs. signal_via_pitch: Centre-to-centre pitch of the signal TSVs (along y) in µm. n_ground_vias_per_side: Number of ground TSVs on each side of the pads. ground_via_pitch: Centre-to-centre pitch of the ground TSVs (along x) in µm. ground_via_distance: Distance from the ground-plane edge to the ground TSV centres in µm. chip: Chip the TSVs go through, ``"bottom"`` or ``"top"``. Returns: Component with the front-side port ``o1`` and the backside port ``o2``. """ layers = CHIP_LAYERS[chip] c = Component() port_front = _add_tapered_pad( c, cross_section or layers.front_cross_section, pad_width, pad_gap, pad_length, taper_length, mirror=False, ) port_back = _add_tapered_pad( c, cross_section_backside or layers.back_cross_section, pad_width, pad_gap, pad_length, taper_length_backside, mirror=True, ) _add_transition_vias( c, pad_width, pad_gap, pad_length, via_width, via_length, via_pad_margin, n_signal_vias, signal_via_pitch, n_ground_vias_per_side, ground_via_pitch, ground_via_distance, chip, ) c.add_port(name="o1", port=port_front) c.add_port(name="o2", port=port_back) return c