Source code for qpdk.cells.bump

"""Indium bump components for 3D integration."""

from __future__ import annotations

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

from qpdk.cells._schematic import bump_transition_schematic, indium_bump_schematic
from qpdk.cells.tsv import _add_tapered_pad
from qpdk.tech import LAYER


[docs] @gf.cell( tags=["interconnects", "flip-chip", "3d-integration"], schematic_function=indium_bump_schematic, ) def indium_bump(diameter: float = 15.0) -> Component: """Creates an indium bump component for 3D integration. See :cite:`rosenberg3DIntegratedSuperconducting2017` for details. Args: diameter: Diameter of the indium bump in µm. Returns: A gdsfactory Component representing the indium bump. """ c = Component() circle = gf.components.circle(radius=diameter / 2, layer=LAYER.IND) ref = c.add_ref(circle) ref.move((0.0, 0.0)) c.add_port( name="center", center=( 0, 0, ), orientation=0, layer=LAYER.IND, width=diameter, port_type="placement", ) return c
[docs] @gf.cell( tags=["interconnects", "flip-chip", "3d-integration"], schematic_function=indium_bump_schematic, ) def indium_bump_array( columns: int = 4, rows: int = 4, pitch: float = 200.0, diameter: float = 15.0, ) -> Component: """Creates a rectangular field of indium bumps centred at the origin. Use it to tie the ground planes of the two chips of a flip-chip stack together, away from any CPW gaps. .. svgbob:: (o) (o) (o) (o) (o) (o) (o) (o) <- rows x columns bumps at pitch (o) (o) (o) (o) Args: columns: Number of bumps along x. rows: Number of bumps along y. pitch: Centre-to-centre pitch of the bumps along both axes in µm. diameter: Diameter of each indium bump in µm. Returns: A gdsfactory Component with ``columns * rows`` indium bumps. """ c = Component() c.add_ref( indium_bump(diameter=diameter), columns=columns, rows=rows, column_pitch=pitch, row_pitch=pitch, ).move((-(columns - 1) * pitch / 2, -(rows - 1) * pitch / 2)) return c
[docs] @gf.cell( tags=["interconnects", "flip-chip", "3d-integration", "waveguides"], schematic_function=bump_transition_schematic, ) def bump_transition( cross_section: CrossSectionSpec = "cpw", cross_section_top: CrossSectionSpec = "cpw_top", taper_length: float = 10.0, taper_length_top: float = 10.0, pad_width: float = 30.0, pad_gap: float = 6.0, pad_length: float = 30.0, bump_diameter: float = 15.0, n_ground_bumps_per_side: int = 0, ground_bump_pitch: float = 30.0, ground_bump_distance: float = 15.0, ) -> Component: r"""CPW transition between the two chips of a flip-chip stack through one indium bump. Follows the indium transition of :cite:`richardAccurateGapControl2026` (Fig. A5b) and the bump-interrupted resonators of :cite:`rosenberg3DIntegratedSuperconducting2017` (Fig. 4b): the CPW is routed from one chip to the other with the centre conductor kept galvanically continuous through a *single* indium bump. The bottom-chip CPW (``o1``, on ``M1``) widens over a short jog into a closed pad, and the top-chip CPW (``o2``, on ``M2``) leaves an identical pad in the opposite direction. The defaults use a 15 µm bump on 30 µm square pads, the landing pad size :cite:`foxenQubitCompatibleSuperconducting2017` found sufficient for 15 µm indium pillars at ±2 µm bonder alignment. In those works the ground planes are tied together by a field of bumps across the whole chip rather than by bumps dedicated to each transition, so place an :func:`indium_bump_array` alongside. ``n_ground_bumps_per_side`` optionally adds ground bumps next to the pads. Both chips are drawn in the same (bottom-chip, top-down) coordinate frame, so the pads overlap and the bump lands on both. .. svgbob:: bottom (M1) top (M2) _________ o1 ─────/ (o) \───── o2 \_________/ Args: cross_section: Bottom-chip CPW cross-section at ``o1``. cross_section_top: Top-chip CPW cross-section at ``o2``. taper_length: Length of the bottom-chip jog from the CPW into the pad in µm. taper_length_top: Length of the top-chip jog from the pad into the CPW 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. bump_diameter: Indium bump diameter in µm. n_ground_bumps_per_side: Number of optional ground bumps on each side of the pads. ground_bump_pitch: Centre-to-centre pitch of the ground bumps (along x) in µm. ground_bump_distance: Distance from the ground-plane edge to the ground bump centres in µm. Returns: Component with the bottom-chip port ``o1`` and the top-chip port ``o2``. Raises: ValueError: If the bump does not fit in the pad, or the ground bumps reach into the CPW gap. """ if bump_diameter > min(pad_width, pad_length): msg = ( f"A {bump_diameter} µm bump does not fit in a " f"{pad_length}x{pad_width} µm pad." ) raise ValueError(msg) if n_ground_bumps_per_side and ground_bump_distance < bump_diameter / 2: msg = ( f"ground_bump_distance={ground_bump_distance} µm puts the ground bumps " "into the CPW gap." ) raise ValueError(msg) c = Component() port_bottom = _add_tapered_pad( c, cross_section, pad_width, pad_gap, pad_length, taper_length, mirror=False ) port_top = _add_tapered_pad( c, cross_section_top, pad_width, pad_gap, pad_length, taper_length_top, mirror=True, ) bump = indium_bump(diameter=bump_diameter) c << bump y_ground = pad_width / 2 + pad_gap + ground_bump_distance xs = ( np.arange(n_ground_bumps_per_side) - (n_ground_bumps_per_side - 1) / 2 ) * ground_bump_pitch for x in xs: for y in (y_ground, -y_ground): (c << bump).move((float(x), y)) c.add_port(name="o1", port=port_bottom) c.add_port(name="o2", port=port_top) return c