# 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 math
from kqcircuits.pya_resolver import pya
from kqcircuits.util.parameters import Param, pdt
from kqcircuits.elements.element import Element
[docs]
class MaskMarkerFc(Element):
"""The PCell declaration for a MaskMarkerFc.
Mask alignment marker for flip-chip masks
"""
window = Param(pdt.TypeBoolean, "Window in airbridge flyover and UBM layer", False)
arrow_number = Param(pdt.TypeInt, "Number of arrow pairs in the marker", 3)
[docs]
@staticmethod
def create_cross(arm_length, arm_width):
m = arm_length / 2
n = arm_width / 2
cross = pya.DPolygon(
[
pya.DPoint(-n, -m),
pya.DPoint(-n, -n),
pya.DPoint(-m, -n),
pya.DPoint(-m, n),
pya.DPoint(-n, n),
pya.DPoint(-n, m),
pya.DPoint(n, m),
pya.DPoint(n, n),
pya.DPoint(m, n),
pya.DPoint(m, -n),
pya.DPoint(n, -n),
pya.DPoint(n, -m),
]
)
return cross
[docs]
def build(self):
arrow = pya.DPolygon(
[
pya.DPoint(0, 0),
pya.DPoint(-20, 20),
pya.DPoint(-11, 25),
pya.DPoint(-5, 19),
pya.DPoint(-5, 62),
pya.DPoint(5, 62),
pya.DPoint(5, 19),
pya.DPoint(11, 25),
pya.DPoint(20, 20),
]
)
layer_gap = self.get_layer("base_metal_gap_wo_grid")
layer_pads = self.get_layer("airbridge_pads")
layer_flyover = self.get_layer("airbridge_flyover")
layer_ubm = self.get_layer("underbump_metallization")
layer_indium_bump = self.get_layer("indium_bump")
layer_protection = self.get_layer("ground_grid_avoidance")
def insert_to_main_layers(shape):
self.cell.shapes(layer_gap).insert(shape)
if not self.window:
self.cell.shapes(layer_flyover).insert(shape)
# protection for the box
protection_box = pya.DBox(pya.DPoint(200, 350), pya.DPoint(-200, -350))
self.cell.shapes(layer_protection).insert(protection_box)
negative_layer = pya.Region(protection_box.to_itype(self.layout.dbu))
# crosses
arm_widths = [20, 35, 70]
arm_lengths = [70, 124, 250]
offset_width = [30, 43, 85]
offset_length = [80, 132, 265]
shift = pya.DPoint(0, 250)
dislocation = 0
for i, (cross_width, cross_length) in enumerate(zip(arm_widths, arm_lengths)):
if i != 0:
dislocation = arm_lengths[i - 1] + arm_lengths[i] + 100
shift += pya.DPoint(0, -dislocation / 2)
inner_shapes = pya.DCplxTrans(1, 0, False, pya.DVector(shift)) * self.create_cross(
offset_length[i], offset_width[i]
)
insert_to_main_layers(inner_shapes)
inner_corner_shape = pya.DCplxTrans(1, 0, False, pya.DVector(shift)) * self.create_cross(
cross_length, cross_width
)
for layer_insert in [layer_pads]:
self.cell.shapes(layer_insert).insert(inner_corner_shape)
negative_offset = pya.DCplxTrans(1, 0, False, pya.DVector(shift)) * self.create_cross(
arm_lengths[i], arm_widths[i]
)
negative_layer -= pya.Region(negative_offset.to_itype(self.layout.dbu))
inner_shapes_offset = pya.DCplxTrans(1, 0, False, pya.DVector(shift)) * self.create_cross(
arm_lengths[i], arm_widths[i]
)
self.cell.shapes(layer_indium_bump).insert(inner_shapes_offset)
self.cell.shapes(layer_ubm).insert(negative_layer)
# marker arrow
for i in range(self.arrow_number):
for j in [-1, 1]:
arrows_shape = pya.DCplxTrans(1, 0, j != 1, j * pya.DVector(0, 200 * i + 400)) * arrow
insert_to_main_layers(arrows_shape)
for layer_insert in [layer_pads, layer_indium_bump]:
self.cell.shapes(layer_insert).insert(arrows_shape)
[docs]
@classmethod
def get_marker_locations(cls, cell_marker, **kwargs):
# set markers to the edge clearance
wafer_center_x = kwargs.get("wafer_center_x", 0)
wafer_center_y = kwargs.get("wafer_center_y", 0)
wafer_rad = kwargs.get("wafer_rad", 75000)
edge_clearance = kwargs.get("edge_clearance", 1000)
margin = kwargs.get("box_margin", 1000)
_h = cell_marker.dbbox().height()
_w = cell_marker.dbbox().width()
coordinate = math.sqrt((wafer_rad - edge_clearance) ** 2 - (_h / 2 + margin) ** 2)
return [
pya.DTrans(wafer_center_x - coordinate + (margin + _w / 2), wafer_center_y) * pya.DTrans.M90,
pya.DTrans(wafer_center_x + coordinate - (margin + _w / 2), wafer_center_y) * pya.DTrans.R0,
]
[docs]
@classmethod # TODO: this is a direct copy from marker.py Will be fixed in future issue
def get_marker_region(cls, inst, **kwargs):
margin = kwargs.get("box_margin", 1000)
return pya.Region(inst.bbox()).extents(margin / inst.cell.layout().dbu)