Source code for kqcircuits.masks.mask_layout

# 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/developers/osstmpolicy). IQM welcomes contributions to the code. Please see our contribution agreements
# for individuals (meetiqm.com/developers/clas/individual) and organizations (meetiqm.com/developers/clas/organization).
import math
from autologging import logged
from tqdm import tqdm

from kqcircuits.pya_resolver import pya
from kqcircuits.defaults import default_layers, default_brand, default_faces, default_mask_parameters, \
    default_layers_to_mask, default_covered_region_excluded_layers, default_mask_export_layers, default_bar_format
from kqcircuits.elements.markers.marker import Marker
from kqcircuits.elements.mask_marker_fc import MaskMarkerFc
from kqcircuits.util.label import produce_label, LabelOrigin
from kqcircuits.util.merge import merge_layout_layers_on_face, convert_child_instances_to_static


[docs]@logged class MaskLayout: """Class representing the mask for a certain face. A MaskLayout is used to create the cells for the mask. Attributes: layout: pya.Layout for this mask name: Name of the mask version: Mask version with_grid: Boolean determining if ground grid is generated face_id: face_id of this mask layout, "1t1" | "2b1" | "2t1" layers_to_mask: dictionary of layers with mask label postfix for mask label and mask covered region creation covered_region_excluded_layers: list of layers in `layers_to_mask` for which mask covered region is not created chips_map: List of lists (2D-array) of strings, each string is a chip name (or --- for no chip) chips_map_legend: Dictionary where keys are chip names, values are chip cells wafer_rad: Wafer radius wafer_center: Wafer center as a pya.DVector chips_map_offset: Offset to make chips_map centered on wafer wafer_top_flat_length: length of flat edge at the top of the wafer wafer_bottom_flat_length: length of flat edge at the bottom of the wafer dice_width: Dicing width for this mask layout text_margin: Text margin for this mask layout chip_size: side width of the chips (assuming square chips) edge_clearance: minimum clearance of outer chips from the edge of the mask chip_box_offset: Offset (pya.DVector) from chip origin of the chip frame boxes for this face chip_trans: DTrans applied to all chips mask_name_offset: mask name label offset from default position (DPoint) mask_name_scale: text scaling factor for mask name label (float) mask_text_scale: text scaling factor for graphical representation layer (float) mask_marker_offset: offset of mask markers from wafer center in horizontal and vertical directions (float) mask_export_layers: list of layer names (without face_ids) to be exported as individual mask `.oas` files mask_export_density_layers: list of layer names (without face_ids) for which we want to calculate the coverage density submasks: list of submasks, each element is a tuple (submask mask_layout, submask position) extra_id: extra string used to create unique name for mask layouts with the same face_id extra_chips: List of tuples (name, position, trans) for chips placed outside chips_map, trans is an optional transformation to use in place of self.chip_trans top_cell: Top cell of this mask layout added_chips: List of (chip name, chip position, chip bounding box, chip dtrans) populated by chips added during build() """ def __init__(self, layout, name, version, with_grid, chips_map, face_id, **kwargs): self.layout: pya.Layout = layout self.name = name self.version = version self.with_grid = with_grid self.face_id = face_id self.chips_map = chips_map self.chips_map_legend = None self.layers_to_mask = kwargs.get("layers_to_mask", default_layers_to_mask) self.covered_region_excluded_layers = kwargs.get("covered_region_excluded_layers", default_covered_region_excluded_layers) + ["mask_edge"] self.wafer_rad = kwargs.get("wafer_rad", default_mask_parameters[self.face_id]["wafer_rad"]) self.wafer_center = (pya.DVector(0, 0)) self.chips_map_offset = kwargs.get("chips_map_offset", default_mask_parameters[self.face_id]["chips_map_offset"]) self.wafer_top_flat_length = kwargs.get("wafer_top_flat_length", 0) self.wafer_bottom_flat_length = kwargs.get("wafer_bottom_flat_length", 0) self.dice_width = kwargs.get("dice_width", default_mask_parameters[self.face_id]["dice_width"]) self.text_margin = kwargs.get("text_margin", default_mask_parameters[self.face_id]["text_margin"]) self.chip_size = kwargs.get("chip_size", default_mask_parameters[self.face_id]["chip_size"]) self.edge_clearance = kwargs.get("edge_clearance", self.chip_size / 2) self.chip_box_offset = kwargs.get("chip_box_offset", default_mask_parameters[self.face_id]["chip_box_offset"]) self.chip_trans = kwargs.get("chip_trans", default_mask_parameters[self.face_id]["chip_trans"]) self.mask_name_offset = kwargs.get("mask_name_offset", default_mask_parameters[self.face_id][ "mask_name_offset"]) self.mask_name_scale = kwargs.get("mask_name_scale", 1) self.mask_text_scale = kwargs.get("mask_text_scale", default_mask_parameters[self.face_id]["mask_text_scale"]) self.mask_markers_type = kwargs.get("mask_markers_type", "all") self.mask_marker_offset = kwargs.get("mask_marker_offset", default_mask_parameters[self.face_id][ "mask_marker_offset"]) self.mask_export_layers = kwargs.get("mask_export_layers", default_mask_export_layers) self.mask_export_density_layers = kwargs.get("mask_export_density_layers", []) self.submasks = kwargs.get("submasks", []) self.extra_id = kwargs.get("extra_id", "") self.extra_chips = kwargs.get("extra_chips", []) self.top_cell = self.layout.create_cell(f"{self.name} {self.face_id}") self.added_chips = []
[docs] def build(self, chips_map_legend): """Builds the cell hierarchy for this mask layout. Inserts cells copied from chips_map_legend to self.top_cell at positions determined by self.chips_map. The copied cells are modified to only have layers corresponding to self.face_id, and they are translated and/or mirrored correctly based on self.face_id. Also inserts cells for mask markers, mask name label, and the circular area covered by the mask. Args: chips_map_legend: Dictionary where keys are chip names, values are chip cells """ self.chips_map_legend = {} for name, cell in tqdm(chips_map_legend.items(), desc='Building cell hierarchy', bar_format=default_bar_format): # pylint: disable=use-a-generator if any([name in row for row in self.chips_map] + [chip[0] == name for chip in self.extra_chips]): # create copies of the chips, so that modifying these only affects the ones in this MaskLayout new_cell = self.layout.create_cell(name) new_cell.copy_tree(cell) # remove layers belonging to another face for face_id, face_dictionary in default_faces.items(): if face_id != self.face_id: for layer_info in face_dictionary.values(): shapes_iter = new_cell.begin_shapes_rec(self.layout.layer(layer_info)) # iterating shapes using shapes_iter.at_end() fails: # https://www.klayout.de/forum/discussion/comment/4275 # solution is to use a separate buffer list to iterate shapes = [] while not shapes_iter.at_end(): try: shapes.append(shapes_iter.shape()) shapes_iter.next() except ValueError: print("error occurs at %s at %s" % (name, face_id)) for shapes_to_remove in shapes: shapes_to_remove.delete() self.chips_map_legend[name] = new_cell region_covered = self._mask_create_geometry() if len(self.submasks) > 0: region_covered = pya.Region() # don't fill with metal gap layer if using submasks for submask_layout, submask_pos in self.submasks: self.top_cell.insert( pya.DCellInstArray(submask_layout.top_cell.cell_index(), pya.DTrans(submask_pos - submask_layout.wafer_center + self.wafer_center)) ) # add chips from chips_map for (i, row) in enumerate(tqdm(self.chips_map, desc='Adding chips to mask', bar_format=default_bar_format)): for (j, chip_name) in enumerate(row): if chip_name == "---": continue position = pya.DPoint(self.chip_size * j, -self.chip_size * (i + 1)) - self.chips_map_offset + \ pya.DVector(-self.wafer_rad, self.wafer_rad) pos = position - self.wafer_center test_x = self.chip_size if pos.x + self.chip_size / 2 > 0 else 0 test_y = self.chip_size if pos.y + self.chip_size / 2 > 0 else 0 d_edge = self.wafer_rad - (pos + pya.DVector(test_x, test_y)).abs() if d_edge > self.edge_clearance: added_chip, region_chip = self._add_chip(chip_name, position, self.chip_trans) region_covered -= region_chip else: print(f" Warning, dropping chip {chip_name} at ({i}, {j}), '{self.face_id}' - too close to edge:" f" {d_edge:.2f} < {self.edge_clearance}") added_chip = False if not added_chip: self.chips_map[i][j] = "---" # add chips outside chips_map for name, pos, *trans in self.extra_chips: # trans is optional if name in chips_map_legend: region_covered -= self._add_chip(name, pos, trans[0] if trans else self.chip_trans)[1] self.chips_map.append([name]) # to get correct amount of chips in mask documentation maskextra_cell: pya.Cell = self.layout.create_cell("MaskExtra") self._insert_mask_name_label(self.top_cell, default_layers["mask_graphical_rep"]) self._mask_create_covered_region(maskextra_cell, region_covered, self.layers_to_mask) convert_child_instances_to_static(self.layout, maskextra_cell, only_elements=True, prune=True) merge_layout_layers_on_face(self.layout, maskextra_cell, self.face())
[docs] def insert_chip_copy_labels(self, labels_cell, layers): """Inserts chip copy labels to all chips in this mask layout and its submasks Args: labels_cell: Cell to which the labels are inserted layers: list of layer names (without face_ids) where the labels are produced """ # find labels_cell for this mask and each submask labels_cells = {self: labels_cell} for submask_layout, submask_pos in self.submasks: for inst in submask_layout.top_cell.each_inst(): # workaround for getting the cell due to KLayout bug, see # https://www.klayout.de/forum/discussion/1191 # TODO: replace by `inst_cell = inst.cell` once KLayout bug is fixed inst_cell = submask_layout.layout.cell(inst.cell_index) if inst_cell.name.startswith("ChipLabels"): labels_cells[submask_layout] = inst_cell break # find all unique x and y coords of chips and place them in the corresponding keys in chips_dict chips_dict = {} # {(pos_x, pos_y): chip_name, chip_pos (in submask coordinates), chip_inst, mask_layout} xvals = set() yvals = set() for chip_name, pos, bbox, dtrans in self.added_chips: xvals.add(pos.x) yvals.add(pos.y) chips_dict[(pos.x, pos.y)] = chip_name, pos, bbox, dtrans, self for submask_layout, submask_pos in self.submasks: for chip_name, pos, bbox, dtrans in submask_layout.added_chips: pos2 = pos + submask_pos xvals.add(pos2.x) yvals.add(pos2.y) chips_dict[(pos2.x, pos2.y)] = chip_name, pos, bbox, dtrans, submask_layout # produce the labels such that chips with identical x-coordinate (y-coordinate) have identical number (letter) for i, y in enumerate(sorted(yvals, reverse=True)): for j, x in enumerate(sorted(xvals)): if (x, y) in chips_dict: chip_name, _, bbox, dtrans, mask_layout = chips_dict[(x, y)] labels_cell_2 = labels_cells[mask_layout] pos_index_name = chr(ord("A") + i) + ("{:02d}".format(j)) bbox_x1 = bbox.left if dtrans.is_mirror() else bbox.right produce_label(labels_cell_2, pos_index_name, dtrans*(pya.DPoint(bbox_x1, bbox.bottom)), LabelOrigin.BOTTOMRIGHT, mask_layout.dice_width, mask_layout.text_margin, [mask_layout.face()[layer] for layer in layers], mask_layout.face()["ground_grid_avoidance"]) bbox_x2 = bbox.right if dtrans.is_mirror() else bbox.left mask_layout._add_chip_graphical_representation_layer(chip_name, dtrans*(pya.DPoint(bbox_x2, bbox.bottom)), pos_index_name, bbox.width(), labels_cell_2)
[docs] def face(self): """Returns the face dictionary for this mask layout""" return default_faces[self.face_id]
def _mask_create_geometry(self): y_clip = -14.5e4 points = [] for a in range(0, 256 + 1): x = math.cos(a / 128 * math.pi) * self.wafer_rad y = max(math.sin(a / 128 * math.pi) * self.wafer_rad, y_clip) if (y > 0 and (x > self.wafer_top_flat_length/2 or x < -self.wafer_top_flat_length/2)) or \ (y < 0 and (x > self.wafer_bottom_flat_length/2 or x < -self.wafer_bottom_flat_length/2)): points.append(pya.DPoint(self.wafer_center.x + x, self.wafer_center.y + y)) region_covered = pya.Region(pya.DPolygon(points).to_itype(self.layout.dbu)) return region_covered def _add_chip(self, name, position, trans): """Returns a tuple (Boolean telling if the chip was added, Region which the chip covers).""" chip_region = pya.Region() if name in self.chips_map_legend.keys(): chip_cell, bounding_box, bbox_offset = self._get_chip_cell_and_bbox(name) trans = pya.DTrans(position + pya.DVector(bbox_offset, 0) - self.chip_box_offset)*trans self.top_cell.insert(pya.DCellInstArray(chip_cell.cell_index(), trans)) chip_region = pya.Region(pya.Box(trans*bounding_box*(1/self.layout.dbu))) self.added_chips.append((name, position, bounding_box, trans)) return True, chip_region return False, chip_region def _mask_create_covered_region(self, maskextra_cell, region_covered, layers_dict): dbu = self.layout.dbu leftmost_label_x = 1e10 labels = [] for layer, postfix in layers_dict.items(): label_cell, label_trans = self._create_mask_name_label(self.face()[layer], postfix) if label_trans.disp.x < leftmost_label_x: leftmost_label_x = label_trans.disp.x labels.append((label_cell, label_trans)) # align left edges of mask name labels in different layers for (label_cell, label_trans) in labels: label_trans = pya.DTrans(label_trans.rot, label_trans.is_mirror(), leftmost_label_x, label_trans.disp.y) inst = maskextra_cell.insert(pya.DCellInstArray(label_cell.cell_index(), label_trans)) region_covered -= pya.Region(inst.bbox()).extents(1e3 / dbu) circle = pya.DTrans(self.wafer_center) * pya.DPath( [pya.DPoint(math.cos(a / 32 * math.pi) * self.wafer_rad, math.sin(a / 32 * math.pi) * self.wafer_rad) for a in range(0, 64 + 1)], 100) maskextra_cell.shapes(self.layout.layer(default_layers["mask_graphical_rep"])).insert(circle) offset = self.mask_marker_offset # Corner mask markers if self.mask_markers_type in ["all", "only_corners"]: cell_marker = Marker.create(self.layout, window=True, face_ids=[self.face_id]) marker_transes = [pya.DTrans(self.wafer_center.x - offset, self.wafer_center.y - offset) * pya.DTrans.R180, pya.DTrans(self.wafer_center.x + offset, self.wafer_center.y - offset) * pya.DTrans.R270, pya.DTrans(self.wafer_center.x - offset, self.wafer_center.y + offset) * pya.DTrans.R90, pya.DTrans(self.wafer_center.x + offset, self.wafer_center.y + offset) * pya.DTrans.R0] self._add_markers(maskextra_cell, region_covered, cell_marker, marker_transes) # Side mask markers if self.mask_markers_type in ["all", "only_sides"]: cell_marker = MaskMarkerFc.create(self.layout, window=True, face_ids=[self.face_id]) marker_transes = [ pya.DTrans(self.wafer_center.x - offset * 1.9**0.5, self.wafer_center.y) * pya.DTrans.M90, pya.DTrans(self.wafer_center.x + offset * 1.9**0.5, self.wafer_center.y) * pya.DTrans.R0] self._add_markers(maskextra_cell, region_covered, cell_marker, marker_transes) maskextra_cell.shapes(self.layout.layer(default_layers["mask_graphical_rep"])).insert(region_covered) # remove unwanted circle boundary and filling from `layers_to_mask` which have been excluded in # `covered_region_excluded_layers` for layer_name in layers_dict.keys(): if layer_name not in self.covered_region_excluded_layers: maskextra_cell.shapes(self.layout.layer(self.face()[layer_name])).insert(region_covered) self.top_cell.insert(pya.DCellInstArray(maskextra_cell.cell_index(), pya.DTrans())) def _add_markers(self, maskextra_cell, region_covered, cell_marker, marker_transes): for trans in marker_transes: inst = maskextra_cell.insert(pya.DCellInstArray(cell_marker.cell_index(), trans)) region_covered -= pya.Region(inst.bbox()).extents(1e3/self.layout.dbu) def _get_chip_name(self, search_cell): for chip_name, cell in self.chips_map_legend.items(): if search_cell == cell: return chip_name return "" def _get_chip_cell_and_bbox(self, chip_name): chip_cell = self.chips_map_legend[chip_name] bounding_box = chip_cell.dbbox_per_layer(self.layout.layer(self.face()["base_metal_gap_wo_grid"])) bbox_offset = self.chip_size - bounding_box.width() # for chips that are smaller than self.chip_size return chip_cell, bounding_box, bbox_offset def _add_chip_graphical_representation_layer(self, chip_name, position, pos_index_name, chip_size, cell): chip_name_text = self.layout.create_cell("TEXT", "Basic", { "layer": default_layers["mask_graphical_rep"], "text": chip_name, "mag": 15000 * self.mask_text_scale / len(chip_name), }) pos_index_name_text = self.layout.create_cell("TEXT", "Basic", { "layer": default_layers["mask_graphical_rep"], "text": pos_index_name, "mag": 4000 * self.mask_text_scale, }) chip_name_trans = pya.DTrans(position + pya.DVector((chip_size - chip_name_text.dbbox().width()) / 2, self.mask_text_scale * 750)) cell.insert(pya.DCellInstArray(chip_name_text.cell_index(), chip_name_trans)) pos_index_trans = pya.DTrans(position + pya.DVector((chip_size - pos_index_name_text.dbbox().width()) / 2, self.mask_text_scale * 6000)) cell.insert(pya.DCellInstArray(pos_index_name_text.cell_index(), pos_index_trans)) def _insert_mask_name_label(self, cell, layer, postfix=""): cell_mask_name, trans = self._create_mask_name_label(layer, postfix) inst = cell.insert(pya.DCellInstArray(cell_mask_name.cell_index(), trans)) return inst def _create_mask_name_label(self, layer, postfix=""): if postfix != "": postfix = "-" + postfix cell_mask_name = self.layout.create_cell("TEXT", "Basic", { "layer": layer, "text": default_brand + "-" + self.name + "v" + str(self.version) + postfix, "mag": self.mask_name_scale*5000.0, }) cell_mask_name_h = cell_mask_name.dbbox().height() cell_mask_name_w = cell_mask_name.dbbox().width() trans = pya.DTrans(self.wafer_center.x + self.mask_name_offset.x - cell_mask_name_w / 2, self.wafer_rad + self.mask_name_offset.y - cell_mask_name_h / 2) return cell_mask_name, trans