# This code is part of KQCircuits
# Copyright (C) 2021 IQM Finland Oy
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not, see
# https://www.gnu.org/licenses/gpl-3.0.html.
#
# The software distribution should follow IQM trademark policy for open-source software
# (meetiqm.com/iqm-open-source-trademark-policy). IQM welcomes contributions to the code.
# Please see our contribution agreements for individuals (meetiqm.com/iqm-individual-contributor-license-agreement)
# and organizations (meetiqm.com/iqm-organization-contributor-license-agreement).
import logging
from kqcircuits.elements.element import Element
from kqcircuits.elements.finger_capacitor_square import FingerCapacitorSquare
from kqcircuits.elements.flip_chip_connectors.flip_chip_connector import FlipChipConnector
from kqcircuits.elements.launcher import Launcher
from kqcircuits.pya_resolver import pya
from kqcircuits.util.parameters import Param, pdt, add_parameters_from
[docs]
@add_parameters_from(FingerCapacitorSquare, "a2", "b2")
@add_parameters_from(FlipChipConnector, bump_type="Flip Chip Connector Dc")
class FlipChipConnectorRf(Element):
"""PCell declaration for an inter-chip rf connector.
Flip chip connector with two coplanar waveguide connections and different ground bump configurations.
The input port is on the first face and on the left. The output port is on the second face and rotated as chosen.
.. MARKERS_FOR_PNG 0,0 15,0 0,-40 -28,0
"""
inter_bump_distance = Param(pdt.TypeDouble, "Distance between In bumps", 100, unit="μm")
output_rotation = Param(pdt.TypeDouble, "Rotation of output port w.r.t. input port", 180, unit="degrees")
connector_a = Param(pdt.TypeDouble, "Conductor width at the connector area", 40, unit="μm")
connector_b = Param(pdt.TypeDouble, "Gap width at the connector area", 40, unit="μm")
round_connector = Param(pdt.TypeBoolean, "Use round connector shape", False)
n_center_bumps = Param(pdt.TypeInt, "Number of center bumps in series", 1)
[docs]
def build(self):
# Flip-chip bump
bump = self._get_bump()
for i in range(self.n_center_bumps):
self.insert_cell(bump, pya.DTrans((i - (self.n_center_bumps - 1) / 2) * self.inter_bump_distance, 0))
bump_ref = self.get_refpoints(bump)
logging.debug("bump_ref: %s", bump_ref)
a2 = self.a2 if self.a2 >= 0 else self.a
b2 = self.b2 if self.b2 >= 0 else self.b
if self.round_connector:
# Rounded geometry
def rounded_plate(center_x, width, length):
reg = pya.Region(
pya.DBox(center_x - length / 2, -width / 2, center_x + length / 2, width / 2).to_itype(
self.layout.dbu
)
)
return reg.rounded_corners(0, min(width, length) / (2 * self.layout.dbu), self.n)
def trace(start_x, width, length):
return pya.Region(pya.DBox(start_x, -width / 2, start_x + length, width / 2).to_itype(self.layout.dbu))
w = self.connector_a + 2 * self.connector_b
bumps_length = (self.n_center_bumps - 1) * self.inter_bump_distance
length = w + bumps_length
def produce_shape(face, a, b, rotation, trace_rotation):
# Base metal gap outline
trace_dtrans = pya.DCplxTrans(1, trace_rotation, False, -bumps_length / 2, 0)
trace_itrans = trace_dtrans.to_itrans(self.layout.dbu)
region = rounded_plate(0, w, length) + trace(0, a + 2 * b, -w / 2).transformed(trace_itrans)
# Ground grid avoidance
avoid_region = region.sized(self.margin / self.layout.dbu, self.margin / self.layout.dbu, 2)
# Subtract circles under bumps
for i in range(self.n_center_bumps):
region -= rounded_plate(
(i - (self.n_center_bumps - 1) / 2) * self.inter_bump_distance,
self.connector_a,
self.connector_a,
)
# Subtract input trace with possible rotation
region -= trace(0, a, -w / 2).transformed(trace_itrans)
# Subtract trace connecting the series bumps
region -= trace(-bumps_length / 2, a, bumps_length)
dtrans = pya.DCplxTrans(1, rotation, False, 0, 0)
itrans = dtrans.to_itrans(self.layout.dbu)
self.cell.shapes(self.get_layer("base_metal_gap_wo_grid", face)).insert(region.transformed(itrans))
self.add_protection(avoid_region.transformed(itrans), face)
self.add_port(
f"{self.face_ids[face]}_port",
dtrans * pya.DPoint(-bumps_length / 2, 0) + trace_dtrans * dtrans * pya.DVector(-w / 2, 0),
trace_dtrans * dtrans * pya.DVector(-1, 0),
face,
)
produce_shape(0, self.a, self.b, 0, 0)
produce_shape(1, a2, b2, 180, self.output_rotation - 180)
else:
# Taper geometry
s = self.connector_a + (self.n_center_bumps - 1) * self.inter_bump_distance
trans = pya.DCplxTrans(1, 0, False, bump_ref["base"] + pya.DPoint(-self.connector_a - s / 2, 0))
tt = pya.DCplxTrans(1, self.output_rotation, False, 0, 0)
self.insert_cell(
Launcher,
trans,
self.face_ids[0],
s=s,
l=self.connector_a,
a_launcher=self.connector_a,
b_launcher=self.connector_b,
launcher_frame_gap=self.connector_b,
)
# If there are many center bumps and output direction is not colinear with the input direction than a simple
# Launcher won't do. For arbitrary angles we use two Launcher objects one to cover the parallel part, like
# in the colinear case, but without the tapering part and we add a second short Launcher rotated to the
# right output direction. Additionally, we use the metal addition layer to get rid of the parts where one
# Launcher's gap overlaps with the others center conductor.
add_metal = False
if int(self.output_rotation) not in (0, 180) and self.n_center_bumps > 1:
ubm = self.connector_a
ibm = self.inter_bump_distance
l = (self.n_center_bumps - 1) * ibm / 2
pts = [
pya.DPoint(l - ibm, ubm / 2),
pya.DPoint(l, ubm / 2),
pya.DPoint(l, -ubm / 2),
pya.DPoint(l - ibm, -ubm / 2),
]
self.cell.shapes(self.get_layer("base_metal_addition", self.face_ids[1])).insert(pya.DPolygon(pts))
lc = ubm * 1.5
ts = pya.DCplxTrans(1, 180, False, -lc, 0)
self.insert_cell(
Launcher,
ts * trans,
None,
s=s - lc,
l=ubm,
a_launcher=self.connector_a,
b_launcher=self.connector_b,
launcher_frame_gap=self.connector_b,
face_ids=[self.face_ids[1], self.face_ids[0]],
a=ubm,
b=ubm,
)
s = ubm
trans = pya.DCplxTrans(1, 0, False, bump_ref["base"] + pya.DPoint(-ubm - s / 2, 0))
tt = pya.DCplxTrans(1, self.output_rotation, False, (self.n_center_bumps - 1) / 2 * ibm, 0)
add_metal = True
self.insert_cell(
Launcher,
tt * trans,
self.face_ids[1],
s=s,
l=self.connector_a,
a_launcher=self.connector_a,
b_launcher=self.connector_b,
launcher_frame_gap=self.connector_b,
face_ids=[self.face_ids[1], self.face_ids[0]],
a=a2,
b=b2,
add_metal=add_metal,
)
self._insert_ground_bumps(bump)
def _insert_ground_bumps(self, bump):
# Insert ground bumps
dist_y = 0.5**0.5 * self.inter_bump_distance
dist_x = dist_y + self.inter_bump_distance * (self.n_center_bumps - 1) / 2
self.insert_cell(bump, pya.DCplxTrans(1, 0, False, dist_x, dist_y))
self.insert_cell(bump, pya.DCplxTrans(1, 0, False, -dist_x, dist_y))
self.insert_cell(bump, pya.DCplxTrans(1, 0, False, dist_x, -dist_y))
self.insert_cell(bump, pya.DCplxTrans(1, 0, False, -dist_x, -dist_y))
def _get_bump(self):
return self.add_element(FlipChipConnector, face_ids=self.face_ids, bump_type=self.bump_type)
[docs]
@classmethod
def get_sim_ports(cls, simulation):
return Element.face_changer_waveguides(simulation)