from phidl import Device, LayerSet from phidl import quickplot as qp # Rename "quickplot()" to the easier "qp()" import phidl.geometry as pg import phidl.routing as pr import numpy as np from phidl import Device, Group import phidl.path as pp import phidl.utilities as pu import pickle import pandas as pd from pandas import DataFrame import sys import yaml sys.path.insert(1, '..') scale_factor = 1 with open('conf.yml', 'r') as file: parameters = yaml.safe_load(file) system_params = parameters['system-params'] inner_params = parameters['inner-params'] xmax_ext_box = system_params['width_external_box']/2 # Redefining so everything gets put with xmin_ext_box = -system_params['width_external_box']/2 # 0 as the center ymax_ext_box = 2*system_params['y_box_correction'] + system_params['height_external_box']/2 # Not quite sure why the ymin_ext_box = 2*system_params['y_box_correction'] - system_params['height_external_box']/2 # the correction is needed spacing_h_external_port = (system_params['width_external_box'] - system_params['N_ports_per_edge']*system_params['width_external_port'])/(system_params['N_ports_per_edge']+1) # Spacing between ports spacing_v_external_port = (system_params['height_external_box'] - system_params['N_ports_per_edge']*system_params['width_external_port'])/(system_params['N_ports_per_edge']+1) # Spacing between ports """ Defining the layer set needed to build everything. Since this is outside the inner design most of this is used for building the outer structure """ lys = LayerSet() lys.add_layer('die', gds_layer = 0, gds_datatype = 0, alpha = 0.15) lys.add_layer('marker', gds_layer = 1, gds_datatype = 0) lys.add_layer('SiO2', gds_layer = 2, gds_datatype = 0) lys.add_layer('wiring ohmic Al', gds_layer = 6, gds_datatype = 0) lys.add_layer('wiring GL1 Pd/Ti', gds_layer = 7, gds_datatype = 0) lys.add_layer('wiring GL2 Pd/Ti', gds_layer = 8, gds_datatype = 0) lys.add_layer('text', gds_layer = 19, gds_datatype = 0) layer_marker = lys['marker'] layer_protection_bonding_pad = lys['SiO2'] layer_die = lys['die'] layer_text = lys['text'] def place_sem_structure(device, marker_structure, cardinality, xmin, ymin): """ place_sem_structure is a function used to place the smaller structures around the chip, that we can use as dummy structures to perform SEM. :device: refers to the device the sem structure is added to :marker_structure: is the inner structure, currently without inner markers added. :cardinality: is a deprecated keyword, which is still necesarry to place them, but it no longer serves it original purpose. It used to be that this function required a reference device, from which you could then place the SEM structure via cardinality. :xmin: and ymin are simple coordinates used to place the structure. """ sw_marker = inner_marker_generator(2*system_params['width_inner_marker'], 2*system_params['length_inner_marker'], layer_marker, cardinal = "A") se_marker = inner_marker_generator(2*system_params['width_inner_marker'], 2*system_params['length_inner_marker'], layer_marker, cardinal = "B") nw_marker = inner_marker_generator(2*system_params['width_inner_marker'], 2*system_params['length_inner_marker'], layer_marker, cardinal = "C") ne_marker = inner_marker_generator(2*system_params['width_inner_marker'], 2*system_params['length_inner_marker'], layer_marker, cardinal = "D") if cardinality == "SE": ref_dummy_devices = device.add_ref(marker_structure) ref_dummy_devices.xmin = xmin ref_dummy_devices.ymin = ymin if cardinality == "SW": ref_dummy_devices = device.add_ref(marker_structure) ref_dummy_devices.ymin = ymin ref_dummy_devices.xmin = xmin if cardinality == "NE": ref_dummy_devices = device.add_ref(marker_structure) ref_dummy_devices.ymin = ymin ref_dummy_devices.xmin = xmin if cardinality == "NW": ref_dummy_devices = device.add_ref(marker_structure) ref_dummy_devices.ymin = ymin ref_dummy_devices.xmin = xmin device.add_ref(sw_marker).move(destination = (ref_dummy_devices.center[0] - 2*ref_dummy_devices.xsize, ref_dummy_devices.center[1] - 2*ref_dummy_devices.ysize)) device.add_ref(se_marker).move(destination = (ref_dummy_devices.center[0] + 2*ref_dummy_devices.xsize, ref_dummy_devices.center[1] - 2*ref_dummy_devices.ysize)) device.add_ref(nw_marker).move(destination = (ref_dummy_devices.center[0] - 2*ref_dummy_devices.xsize, ref_dummy_devices.center[1] + 2*ref_dummy_devices.ysize)) device.add_ref(ne_marker).move(destination = (ref_dummy_devices.center[0] + 2*ref_dummy_devices.xsize, ref_dummy_devices.center[1] + 2*ref_dummy_devices.ysize)) def create_and_place_dummy_bond_pads(device, amount_dummy_bonding_pads, width, length, spacing, direction, destination, layer_dummy_bonds): """ The function below is used to generate the dummy bond pads, on the outer skirts of the total design. :device: refers to the device the dummy pads will be added to. :amount_dummy_bonding_pads: refers to how many dummy pads there will be generated. :width: is the width of the dummy bond pads. :length: is the length of the dummy bond pad. :spacing: is the spacing in between each dummy bond pad. :direction: is whether the grouping will be directed horizontally or vertically. :destination: is the coordinate the grouping will be moved to """ dummy_bonding_pads = [] j = 0 for i in range(amount_dummy_bonding_pads): if j == 3: j = 0 dummy_bonding_pads.append(device << pg.rectangle(size = (length, width), layer = layer_dummy_bonds[j])) j += 1 dummy_bonding_pads_group = Group(dummy_bonding_pads) dummy_bonding_pads_group.distribute(direction = direction, spacing = spacing) dummy_bonding_pads_group.move(destination = destination) return dummy_bonding_pads def inner_marker_generator(width, length, layer, cardinal = ""): """ :inner_marker_generator: is used to generate the inner, smaller markers, both used on the inside of the device, but also inside the larger markers on the outskirts of the design. :width: is the width of each cross. :length: is the length of each cross. :cardinal: is used as an identifying marker. It can be empty, and will then not have any effect, however, it is currently used as an alpha-numerical ordering code, such that A, B, C, D are the lower left, lower right, upper left, and upper right corners of the device, respectively. The numbers are assigned such that each corner cross signifies the 0 position, and all other crosses in that alpha section are given relative coordinates to that. """ marker = Device() cross_inner_marker = marker << pg.cross(length = length, width = width, layer = layer) if cardinal: cardinal_text = marker << pg.text(cardinal, layer = layer, size = 400) cardinal_text.move(destination = (-1500, 1200)) inner_marker_joined = pg.union(marker, by_layer = True) return inner_marker_joined def outer_marker_generator(): """ Generate the outer markers, which are used on the outskirt of the design, for rough alignment. No parameters are given; if wanting to change their size, look at the source-code. The pg.boolean function is used to cut out a specfic size inside the outer marker, such that an inner marker can be placed there. """ cross_outer_marker = pg.cross(length = system_params['length_outer_marker'], width = system_params['width_outer_marker'], layer = layer_marker) outer_marker_device_joined = pg.union(cross_outer_marker, by_layer = True) rect_box_cut_outer_marker = pg.rectangle(size = (system_params['edge_box_cut_outer_marker'], system_params['edge_box_cut_outer_marker']), layer = layer_marker) rect_box_cut_outer_marker.center = [0,0] outer_marker_joined_cut = pg.boolean(outer_marker_device_joined,rect_box_cut_outer_marker,'A-B', layer = layer_marker) return outer_marker_joined_cut def marker_device_generator(x, y, cardinal): """ This function combines the functionality of the inner_marker_generator and the outer_marker_generator, to properly generate the inner/outer markers used on the outskirts of the design. :x: is the x direction numerical ordering code mentioned above. It is given relatively to the corner marker on each side of the device. :y: is the y direction numerical ordering code. :cardinal: is the absolute placement on the chip. Not functionally needed to place the chips, but needed for the text-generation. A = South west B = South east C = North west D = North east """ layer = lys['marker'] amount_of_markers = len(x) marker_spacing = 200000 marker_device = Device() inner_markers, outer_markers = [], [] position = [] cardinal_list = [] for i in range(amount_of_markers): position.append(str(x[i]) + str(y[i])) position[i] = marker_device << pg.text((str(x[i]) + str(y[i])), layer = layer, size = 400) cardinal_list.append(cardinal) cardinal_list[i] = marker_device << pg.text(cardinal, layer = layer, size = 400) inner_markers.append("inner_left_" + str(i)) outer_markers.append("outer_left_" + str(i)) inner_markers[i] = marker_device << inner_marker_generator(width = system_params['width_inner_marker'], length = system_params['length_inner_marker'], layer = layer) outer_markers[i] = marker_device << outer_marker_generator() def marker_mover(list1, list2, i, x, y): list1[i].move(destination = (x*marker_spacing, y *marker_spacing)) list2[i].move(destination = (x*marker_spacing, y *marker_spacing)) for j in range(amount_of_markers): marker_mover(inner_markers, outer_markers, j, x[j], y[j]) position[j].move(destination = (x[j]*marker_spacing + 700, y[j]*marker_spacing + 700)) cardinal_list[j].move(destination = (x[j]*marker_spacing - 1200, y[j]*marker_spacing + 700)) return marker_device """ Credit to Fabrizio Berritta for the two following functions. They calculate the midpoint for each of the bonding pads, simply from giving them the integer number of placements which the bond-pad is placed on. """ def x_midpoint_ext_port_h(N): x0 = spacing_h_external_port*N+system_params['width_external_port']*(N-1)+ system_params['width_external_port']/2 + xmin_ext_box return x0 def y_midpoint_ext_port_v(N): y0 = spacing_v_external_port*N+system_params['width_external_port']*(N-1)+ system_params['width_external_port']/2 + ymin_ext_box return y0 def protection_pads(device, width, length, destination): """ :protection_pads: is a function which builds the protection pads underneath the other bonding pads, except for the ohmics (user-defined). :device: refers to the device they are added onto. :width: is the width of the protection pad. :length: is the length of the protection pad. :destination: is the coordinate on the chip where the protection pad will be placed. """ protection_pad = device << pg.rectangle(size = (width, length), layer = layer_protection_bonding_pad) protection_pad.move(destination = destination) return protection_pad def cross_barriers(mid_point, width, length, layer, port_cardinal, port_routing_direction): """ :cross_barriers: is the function that builds the cross barriers seen on the inside of the 2xN device. :mid_point: is a touple with x and y coordinates. :width: is the width of each cross. :length: is the length of each cross. :layer: is the layer each cross is assigned to. :port_cardinal: is the designation for whether you the output port to be on the north or south side of the cross. :port_routing_direction: is a list of length two, with numerical values, which is meant to add a skew to the chosen output port. This is done so that no thinning of the gates occur, as they fan into the inner structure. """ if port_cardinal == '3': vertical = pg.polygon_ports(xpts = [ mid_point[0] - 0.5*width, mid_point[0] + 0.5*width, mid_point[0] + 0.5*width, mid_point[0] - 0.5*width], ypts = [ mid_point[1] - 0.5*length, mid_point[1] - 0.5*length, mid_point[1] + 0.5*length + port_routing_direction[1], mid_point[1] + 0.5*length + port_routing_direction[0]], layer = layer) horizontal = pg.polygon_ports(xpts = [ mid_point[0] - 0.5*length, mid_point[0] + 0.5*length, mid_point[0] + 0.5*length, mid_point[0] - 0.5*length], ypts = [ mid_point[1] - 0.5*width, mid_point[1] - 0.5*width, mid_point[1] + 0.5*width, mid_point[1] + 0.5*width], layer = layer) port = vertical.ports[port_cardinal] elif port_cardinal == '1': vertical = pg.polygon_ports(xpts = [ mid_point[0] - 0.5*width, mid_point[0] + 0.5*width, mid_point[0] + 0.5*width, mid_point[0] - 0.5*width], ypts = [ mid_point[1] - 0.5*length - port_routing_direction[1], mid_point[1] - 0.5*length - port_routing_direction[0], mid_point[1] + 0.5*length, mid_point[1] + 0.5*length], layer = layer) horizontal = pg.polygon_ports(xpts = [ mid_point[0] - 0.5*length, mid_point[0] + 0.5*length, mid_point[0] + 0.5*length, mid_point[0] - 0.5*length], ypts = [ mid_point[1] - 0.5*width, mid_point[1] - 0.5*width, mid_point[1] + 0.5*width, mid_point[1] + 0.5*width], layer = layer) port = vertical.ports[port_cardinal] else: raise Exception('The port cardinal direction you entered was not valid. Please make it 1 for the south port, and 3 for the north port.') return (horizontal, vertical), port def barrier_creation(dot, dot_radius, width_barrier, length_barrier, layer, rotations, routing_direction): """ :barrier_creation: Creates the barriers that are in between the sensor dots and the regular dots. It is only used in relation to the creation of the sensor dots and the positioning of this barrier is only relative to the sensor dots. :dot: is the dot from which the relative coordinates will be derived. :dot_radius: Is the radius of the dot above, which is used to determine the relative coordinates for the barrier. :width_barrier: is the width of the barrier. :length_barrier: is the length of the barrier. :layer: is the layer the barrier is assigned to. :rotations: is the rotation of the barrier, relative to the dot. For this specific purpose, all rectangle barriers are created such that they are aligned vertically, and then rotated around the dot, such that they are aligned horizontally. :routing_direction: is either "left" or "right", meaning which sensor dot the barrier belongs to. """ if routing_direction == 'left': if rotations == 90: polygon = pg.polygon_ports(xpts = [ dot.center[0] + 0.95*dot_radius, dot.center[0] + 0.95*dot_radius + width_barrier, dot.center[0] + 0.95*dot_radius + width_barrier, dot.center[0] + 0.95*dot_radius], ypts = [ dot.center[1] - 0.25*length_barrier, dot.center[1] - 0.25*length_barrier, dot.center[1] + 0.25*length_barrier, dot.center[1] + 0.25*length_barrier + 0.05*length_barrier], layer = layer) if rotations == -90: polygon = pg.polygon_ports(xpts = [ dot.center[0] + 0.95*dot_radius, dot.center[0] + 0.95*dot_radius + width_barrier, dot.center[0] + 0.95*dot_radius + width_barrier, dot.center[0] + 0.95*dot_radius], ypts = [ dot.center[1] - 0.25*length_barrier - 0.05*length_barrier, dot.center[1] - 0.25*length_barrier, dot.center[1] + 0.25*length_barrier, dot.center[1] + 0.25*length_barrier], layer = layer) else: if rotations == 90: polygon = pg.polygon_ports(xpts = [ dot.center[0] + 0.95*dot_radius, dot.center[0] + 0.95*dot_radius + width_barrier, dot.center[0] + 0.95*dot_radius + width_barrier, dot.center[0] + 0.95*dot_radius], ypts = [ dot.center[1] - 0.25*length_barrier - 0.05*length_barrier, dot.center[1] - 0.25*length_barrier, dot.center[1] + 0.25*length_barrier, dot.center[1] + 0.25*length_barrier], layer = layer) if rotations == -90: polygon = pg.polygon_ports(xpts = [ dot.center[0] + 0.95*dot_radius, dot.center[0] + 0.95*dot_radius + width_barrier, dot.center[0] + 0.95*dot_radius + width_barrier, dot.center[0] + 0.95*dot_radius], ypts = [ dot.center[1] - 0.25*length_barrier, dot.center[1] - 0.25*length_barrier, dot.center[1] + 0.25*length_barrier, dot.center[1] + 0.25*length_barrier + 0.05*length_barrier], layer = layer) return polygon, polygon.ports def dot_creation(gate_additional_length, center, radius, angle_resolution, layer, width_gate, left_tilt, right_tilt, angle_of_gate, barrier_layer, barrier_port_direction, barrier_rot = [], routing_directions = [], mirror = False): """ :dot_creation: Creates the dots that are used in the dot array. :gate_additional_length: is the additional length of the gate, which is used to ensure proper fan-out, even as the amount of dots become high. :center: is the coordinate for the center of the dot. :radius: is the radius of the dot. :angle_resolution: is the resolution of the angles in the gds file. :layer: is the layer the dot is assigned to. :width_gate: is the width of the gate. :left_tilt: is the assigned left skew on the gate, meant to ensure that no thinning of the gate happens as it is fanned out. :right_tilt: is the assigned right skew on the gate, meant to ensure that no thinning of the gate happens as it is fanned out. :angle_of_gate: is the angle by which the gate will be rotated around the dot. This is needed, since the gates need to fan out to the north and the south. :barrier_layer: This is currently only used for the creation of the sensor dots. It is the layer that the barriers created via the above function will be assigned to. :barrier_port_direction: This is currently only used for the creation of the sensor dots. It is the direction that the barriers will be fanned out of. :barrier_rot: This is currently only used for the creation of the sensor dots. It is the rotation of the barriers, relative to the sensor dot. :routing_directions: This is currently only used for the creation of the sensor dots. It is either "left" or "right" meaning which sensor dot the barrier belongs to. :mirror: This is used to mirror the gates. This is so that each dot gate can be made in the same manner, and then as they are rotated around, to be fanned out in the opposite direction, they can be mirrored to ensure that the gates fan out in the correct fashion. """ dot = pg.circle(radius = radius, angle_resolution = angle_resolution, layer = layer) dot.center = center gate = pg.polygon_ports(xpts = [dot.center[0] - 0.5*width_gate, dot.center[0] + 0.5*width_gate, dot.center[0] + 0.5*width_gate, dot.center[0] - 0.5*width_gate], ypts = [dot.center[1] + 0.95*radius, dot.center[1] + 0.95*radius, dot.center[1] + 0.95*radius + gate_additional_length + right_tilt, dot.center[1] + 0.95*radius + gate_additional_length + left_tilt], layer = layer) gate.rotate(angle = angle_of_gate, center = dot.center) if mirror: gate.mirror(p1 = gate.center, p2 = (gate.center[0], gate.center[1] + 1)) width_barrier = 40 length_barrier = 200 barrier_array = [] barrier_ports = [] j = 0 if len(barrier_rot) != 0: for i in barrier_rot: barrier_i, full_barrier_ports = barrier_creation(dot, radius, width_barrier, length_barrier, layer = barrier_layer, rotations = i, routing_direction = routing_directions) barrier_array.append(barrier_i) barrier_array[j].rotate(angle = i, center = dot.center) barrier_ports.append(full_barrier_ports[barrier_port_direction[j]]) j += 1 return dot, gate, barrier_array, gate.ports['3'], barrier_ports def build_2xN(params, layer, amount_of_dots, layer_dot, layer_barrier, layer_screening, layer_ohmic, layers_for_outer, name_of_chip, dose_test = False): """ :params: is a dictionary which contains all the parameters for the design. If no dictionary is given, the default parameters are used, as in the pre-defined dictionary above. The purpose of specific parameters can be read above. :layer: is a parameter meant to make it easier to create more of these design onto a single chip. For this purpose, if you want to create 4 of these designs onto a single chip, you create a 4-long list of layers for each subgroup of layers. This is done so that in fabrication, the layers are not confused with each other. This parameter is an integer representing the 1st, 2nd, 3rd, or 4th design on the chip. :amount_of_dots_ is the amount of dots in a single row. So if you give it 3, it will create a 2x3 design. :layer_dot: is a 4-long list of the layers meant for the dots. :layer_barrier: is a 4-long list of the layers meant for the barriers. :layer_screening: is a 4-long list of the layers meant for the screening gates. :layer_ohmic: is a 4-long list of the layers meant for the ohmic contacts. :layers_for_outer: is a 4-long list of the layers meant for the outermost gates. :name_of_chip: is the name of the chip, which is used to name the gds file. :dose_test: is a boolean which is used to determine whether it saves a file of the inner device without markers, for dose testing purposes. False by default. """ if params is None: # If no params dictionary is given, params = inner_params # use the pre-defined one. # Define all constants and parameters from params dictionary ------------------------------ dot_radius = params['dot_radius'] # Dot radius sensordot_radius = params['sensordot_radius'] # Sensordot radius dot_length_gate = params['dot_length_gate'] # Length of connecting dot gate dot_width_gate = params['dot_width_gate'] # width of connection dot gate angle_resolution = params['angle_resolution'] # Resolution of design sensor_length_gate = params['sensor_length_gate'] # Length of connecting sensor dot gate sensor_width_gate = params['sensor_width_gate'] # Width of connecting sensor dot gate spacing_dots = params['spacing_dots'] # Spacing between dots start_x = params['start_x'] # The starting x-coordinate dot 1,1 start_y = params['start_y'] # Starting y-coordinate of dot 1,1 screen_length = params['screen_length'] # Size of the screening gate above dots gate_length_screen = params['gate_length_screen'] # Length of the connecting screening gate width_barrier = params['width_barrier'] # Width of the barriers gate_length_start = params['gate_length_start'] # Starting point for list of increasingly large gate lengths, so ensure proper fan-out gate_length_end = params['gate_length_end'] # End point for list of increasingly large gate lengths, so ensure proper fan-out middle_gate_addition = params['middle_gate_addition'] # If uneven number of gates, the middle gate will be slightly larger appendage_length_addition = params['appendage_length_addition'] # Same as above, but for the appendages middle_cross_length_addition = params['middle_cross_length_addition'] # Same as above, but for the middle barrier crosses width_cross = params['width_cross'] # Width of the barrier crosses length_cross = params['length_cross'] # Length of the barrier crosses # ---------------------------------------------------------------------------------------- """ The following code creates empty arrays, which are filled with the values of the skew meant to be applied to the gates. The skew is meant to compensate for the thinning of gates as they are fanned out at non-0 degree angles. """ dot_tilts_right_2xN = np.zeros((2, amount_of_dots), dtype = float) dot_tilts_left_2xN = np.zeros((2, amount_of_dots), dtype = float) dot_rotations_2xN = np.zeros((2, amount_of_dots), dtype = float) tilt_start = -50 tilt_end = 20 dot_tilts_left_2xN[0] = np.linspace(tilt_start, tilt_end, num = amount_of_dots) dot_tilts_left_2xN[1] = np.linspace(tilt_start, tilt_end, num = amount_of_dots) dot_tilts_right_2xN[0] = np.linspace(tilt_end, tilt_start, num = amount_of_dots) dot_tilts_right_2xN[1] = np.linspace(tilt_end, tilt_start, num = amount_of_dots) dot_rotations_2xN[0] = np.linspace(180, 180, num = amount_of_dots) dot_rotations_2xN[1] = np.linspace(0, 0, num = amount_of_dots) sensor_tilts_right_2x2 = [30, 0] sensor_tilts_left_2x2 = [0, 30] sensor_rotations_2x2 = [90, -90] """ The following code initialises the additional lengths, added to the various gates, such that proper fan-out is achieved even at higher number of dots. """ middle_gate_length = gate_length_end + middle_gate_addition appendage_length_start = gate_length_start + appendage_length_addition appendage_length_end = gate_length_end + appendage_length_addition appendage_middle_gate_length = middle_gate_length + appendage_length_addition middle_cross_appendage_length_start = appendage_length_start middle_cross_middle_gate_length = appendage_middle_gate_length middle_cross_appendage_length_end = appendage_length_end + middle_cross_length_addition if (amount_of_dots - 1)%2 == 0: appendage_length_addition_1 = np.linspace(appendage_length_start, appendage_length_end, num = round(np.floor((amount_of_dots - 1)/2))) appendage_length_addition_2 = np.linspace(appendage_length_end, appendage_length_start, num = round(np.floor((amount_of_dots - 1)/2))) appendage_length_addition = np.hstack((appendage_length_addition_1, appendage_length_addition_2)) else: appendage_length_addition_1 = np.linspace(appendage_length_start, appendage_length_end, num = round(np.floor((amount_of_dots - 1)/2))) appendage_length_addition_2 = np.linspace(appendage_middle_gate_length, appendage_middle_gate_length, 1) appendage_length_addition_3 = np.linspace(appendage_length_start, appendage_length_end, num = round(np.floor((amount_of_dots - 1)/2)))[::-1] appendage_length_addition = np.hstack((appendage_length_addition_1, appendage_length_addition_2, appendage_length_addition_3)) if amount_of_dots%2 == 0: gate_length_addition_1 = np.linspace(gate_length_start, gate_length_end, num = round(np.floor(amount_of_dots/2))) gate_length_addition_2 = np.linspace(gate_length_start, gate_length_end, num = round(np.floor(amount_of_dots/2)))[::-1] gate_length_addition = np.hstack((gate_length_addition_1, gate_length_addition_2)) appendage_length_middle_addition_1 = np.linspace(middle_cross_appendage_length_start, middle_cross_appendage_length_end, num = round(np.floor((amount_of_dots)/2))) appendage_length_middle_addition_2 = np.linspace(middle_cross_appendage_length_end, middle_cross_appendage_length_start, num = round(np.floor((amount_of_dots)/2))) appendage_length_middle_addition = np.hstack((appendage_length_middle_addition_1, appendage_length_middle_addition_2)) else: gate_length_addition_1 = np.linspace(gate_length_start, gate_length_end, num = round(np.floor(amount_of_dots/2))) gate_length_addition_2 = np.linspace(middle_gate_length, middle_gate_length, 1) gate_length_addition_3 = np.linspace(gate_length_start, gate_length_end, num = round(np.floor(amount_of_dots/2)))[::-1] gate_length_addition = np.hstack((gate_length_addition_1, gate_length_addition_2, gate_length_addition_3)) appendage_length_middle_addition_1 = np.linspace(middle_cross_appendage_length_start, middle_cross_appendage_length_end, num = round(np.floor((amount_of_dots)/2))) appendage_length_middle_addition_2 = np.linspace(middle_cross_middle_gate_length, middle_cross_middle_gate_length, 1) appendage_length_middle_addition_3 = np.linspace(middle_cross_appendage_length_start, middle_cross_appendage_length_end, num = round(np.floor((amount_of_dots)/2)))[::-1] appendage_length_middle_addition = np.hstack((appendage_length_middle_addition_1, appendage_length_middle_addition_2, appendage_length_middle_addition_3)) sensor_dot_dot_radii_difference = sensordot_radius - dot_radius # Difference between the two sizes, so that relative spacing can be achieved """ Define dots and their positions, including initialising arrays for their ports and positions """ dot_positions_2xN = np.zeros((2, amount_of_dots), dtype = tuple) dot_2xN = np.zeros((2, amount_of_dots), dtype = tuple) dot_ports_2xN = np.zeros((2, amount_of_dots), dtype = tuple) for i in range(amount_of_dots): dot_positions_2xN[0, i] = [start_x + i*spacing_dots, start_y] dot_positions_2xN[1, i] = [start_x + i*spacing_dots, start_y + spacing_dots] """ Define the sensor dots and their positions, including initialising arrays for their ports and positions """ sensordot_positions_2xN = [[start_x - spacing_dots - sensor_dot_dot_radii_difference, start_y], [start_x + amount_of_dots*spacing_dots + sensor_dot_dot_radii_difference, start_y + spacing_dots]] sensor_dots_2xN = np.zeros((2), dtype = tuple) sensor_dot_ports_2xN = np.zeros((2), dtype = tuple) barrier_rotations_2x2 = [-90, 90] sensor_routing_directions = ['left', 'right'] barrier_port_direction = [('1', '3'), ('3', '1')] # The correct ports for the barriers on the side of the sensordots embedded_barrier_ports_2x2 = [] """ Build the actual dots based on the positions and parameters defined above. """ D = Device() for i in range(amount_of_dots): dot_2xN[0, i], gate_1, barrier_array_1, dot_ports_2xN[0, i], _ = dot_creation(gate_length_addition[i], dot_positions_2xN[0, i], dot_radius, angle_resolution, layer_dot[layer], dot_width_gate, dot_tilts_left_2xN[0, i], dot_tilts_right_2xN[0, i], dot_rotations_2xN[0, i], layer_barrier[layer], [], mirror = True) dot_2xN[1, i], gate_2, barrier_array_2, dot_ports_2xN[1, i], _ = dot_creation(gate_length_addition[i], dot_positions_2xN[1, i], dot_radius, angle_resolution, layer_dot[layer], dot_width_gate, dot_tilts_left_2xN[0, i], dot_tilts_right_2xN[0, i], dot_rotations_2xN[1, i], layer_barrier[layer], []) D << dot_2xN[0, i] D << gate_1 D << barrier_array_1 D << dot_2xN[1, i] D << gate_2 D << barrier_array_2 """ Build the sensor dots, and their barriers, based on the positions and parameters defined above. """ sensor_dot_gate_length = [300, 300] for i in range(len(sensordot_positions_2xN)): sensor_dots_2xN[i], gate, barrier_array, sensor_dot_ports_2xN[i], touple_barrier_ports = dot_creation(sensor_dot_gate_length[i], sensordot_positions_2xN[i], sensordot_radius, angle_resolution, layer_dot[layer], sensor_width_gate, sensor_tilts_left_2x2[i], sensor_tilts_right_2x2[i], sensor_rotations_2x2[i], layer_barrier[layer], barrier_port_direction[i], barrier_rotations_2x2, sensor_routing_directions[i]) D << sensor_dots_2xN[i] D << gate D << barrier_array embedded_barrier_ports_2x2.append(touple_barrier_ports[0]) embedded_barrier_ports_2x2.append(touple_barrier_ports[1]) """ Manually create the screening gates, and their ports. The number of screening gates is fixed, but some of the parameters are not, such as the length of it. """ center_dots_screening_1 = pg.polygon_ports(xpts =[dot_2xN[1, 0].center[0] - 0.35*dot_radius, dot_2xN[1, amount_of_dots - 1].center[0] + 0.35*dot_radius, dot_2xN[1, amount_of_dots - 1].center[0] + 0.35*dot_radius, dot_2xN[1, 0].center[0] - 0.35*dot_radius], ypts = [ dot_2xN[1, 0].center[1] + 1.5*dot_radius, dot_2xN[1, 0].center[1] + 1.5*dot_radius, dot_2xN[1, 0].center[1] + 1.5*dot_radius + screen_length, dot_2xN[1, 0].center[1] + 1.5*dot_radius + screen_length], layer = layer_screening[layer]) screening_gate_1 = pg.polygon_ports(xpts = [dot_2xN[1, 0].center[0] + dot_radius - 20, dot_2xN[1, 0].center[0] + dot_radius + 20, dot_2xN[1, 0].center[0] + dot_radius + 20, dot_2xN[1, 0].center[0] + dot_radius - 20], ypts = [ 0.95*center_dots_screening_1.ymax, 0.95*center_dots_screening_1.ymax, 0.95*center_dots_screening_1.ymax + gate_length_screen + 30, 0.95*center_dots_screening_1.ymax + gate_length_screen], layer = layer_screening[layer]) center_dots_screening_2 = pg.polygon_ports(xpts =[dot_2xN[0, 0].center[0] - 0.35*dot_radius, dot_2xN[0, amount_of_dots - 1].center[0] + 0.35*dot_radius, dot_2xN[0, amount_of_dots - 1].center[0] + 0.35*dot_radius, dot_2xN[0, 0].center[0] - 0.35*dot_radius], ypts = [ dot_2xN[0, 0].center[1] - 1.5*dot_radius, dot_2xN[0, 0].center[1] - 1.5*dot_radius, dot_2xN[0, 0].center[1] - 1.5*dot_radius - screen_length, dot_2xN[0, 0].center[1] - 1.5*dot_radius - screen_length], layer = layer_screening[layer]) screening_gate_2 = pg.polygon_ports(xpts = [dot_2xN[0, amount_of_dots - 1].center[0] - dot_radius - 20, dot_2xN[0, amount_of_dots - 1].center[0] - dot_radius + 20, dot_2xN[0, amount_of_dots - 1].center[0] - dot_radius + 20, dot_2xN[0, amount_of_dots - 1].center[0] - dot_radius - 20], ypts = [ 0.95*center_dots_screening_2.ymin, 0.95*center_dots_screening_2.ymin, 0.95*center_dots_screening_2.ymin - gate_length_screen, 0.95*center_dots_screening_2.ymin - gate_length_screen - 30], layer = layer_screening[layer]) sensor_dot_screening_1 = pg.polygon_ports(xpts = [sensor_dots_2xN[0].xmin - 70, sensor_dots_2xN[0].xmin - 20, sensor_dots_2xN[0].xmin - 20, sensor_dots_2xN[0].xmin - 90], ypts = [ sensor_dots_2xN[0].center[1] - dot_radius, sensor_dots_2xN[0].center[1] - dot_radius, sensor_dots_2xN[0].center[1] + 1.3*dot_radius, sensor_dots_2xN[0].center[1] + 0.7*dot_radius], layer = layer_screening[layer]) sensor_dot_screening_2 = pg.polygon_ports(xpts = [sensor_dots_2xN[1].xmax + 70, sensor_dots_2xN[1].xmax + 20, sensor_dots_2xN[1].xmax + 20, sensor_dots_2xN[1].xmax + 90], ypts = [ sensor_dots_2xN[1].center[1] - dot_radius, sensor_dots_2xN[1].center[1] - dot_radius, sensor_dots_2xN[1].center[1] + 1.3*dot_radius, sensor_dots_2xN[1].center[1] + 0.7*dot_radius], layer = layer_screening[layer]) screening_gates = [center_dots_screening_1, screening_gate_1, center_dots_screening_2, screening_gate_2, sensor_dot_screening_1, sensor_dot_screening_2] for i in screening_gates: D << i # Add the screening gates to the device screening_ports = [] # Add the screening gate ports to the screening_ports array, such that they can be routed out. screening_ports.append(sensor_dot_screening_1.ports['3']) screening_ports.append(sensor_dot_screening_2.ports['3']) screening_ports.append(screening_gate_1.ports['3']) screening_ports.append(screening_gate_2.ports['3']) """ Creating the arrays and such for the inner barrier crosses. """ upper_lower_cross_positions = np.zeros((2, amount_of_dots - 1), dtype = tuple) middle_cross_positions = np.zeros(amount_of_dots, dtype = tuple) upper_lower_crosses = np.zeros((2, amount_of_dots - 1), dtype = tuple) middle_crosses = np.zeros(amount_of_dots, dtype = tuple) upper_lower_cross_ports = np.zeros((2, amount_of_dots - 1), dtype = tuple) middle_cross_ports = np.zeros(amount_of_dots, dtype = tuple) upper_lower_cross_direction = np.zeros((2, amount_of_dots - 1), dtype = str) middle_cross_directions = np.zeros(amount_of_dots, dtype = str) upper_lower_routing_direction = np.zeros((2, amount_of_dots - 1), dtype = tuple) middle_routing_direction = np.zeros(amount_of_dots, dtype = tuple) routing_middle_temp = np.asarray(['1', '3']*amount_of_dots) # Make an alternating array of 1's and 3's, so that the routing for the inner crosses alternates between up and down middle_cross_directions[0] = '3' # Manually set the first two directions, since they anchored due to the sensor dots middle_cross_directions[amount_of_dots - 1] = '1' """ The code directly below sets all the middle routing directions for the cross barriers in the center. Below that, we set the positions of the upper and lower cross barriers, and we start setting their routing direction. On the end, middle cross barriers, we also set a skew on their ports, so no thinning of the gates occur. """ for i in range(1, amount_of_dots - 1): middle_cross_directions[i] = routing_middle_temp[i] for i in range(amount_of_dots): if i < amount_of_dots - 1: upper_lower_cross_positions[0, i] = [dot_2xN[0, i].center[0] + 0.5*spacing_dots, dot_2xN[0, i].center[1]] upper_lower_cross_positions[1, i] = [dot_2xN[1, i].center[0] + 0.5*spacing_dots, dot_2xN[1, i].center[1]] middle_cross_positions[i] = [dot_2xN[0, i].center[0], dot_2xN[0, i].center[1] + 0.5*spacing_dots] upper_lower_cross_direction[0, i] = '1' upper_lower_cross_direction[1, i] = '3' upper_lower_routing_direction[0, i] = [0, 0] upper_lower_routing_direction[1, i] = [0, 0] middle_routing_direction[i] = [0, 0] if i == 0: middle_routing_direction[i] = [0, 20] if i == amount_of_dots - 1: middle_cross_positions[i] = [dot_2xN[0, i].center[0], dot_2xN[0, i].center[1] + 0.5*spacing_dots] middle_routing_direction[i] = [0, 20] def make_appendage(object, cardinality, appendage_lengths, port_routing_direction = [0, 0]): """ A function to generate the appendage gates that are on the cross barriers, so that they can be routed out properly, and not directly from where they are in the center of the design. :object: The object that the appendage is attached to. :cardinality: The cardinality of the appendage. 1 is down, 3 is up. :appendage_lengths: The length of the appendage. :port_routing_direction: The skew of each appendage port, so ensure no thinning of gates. """ if cardinality == '3': appendage = pg.polygon_ports(xpts = [object[0].center[0] - 0.5*width_cross, object[0].center[0] + 0.5*width_cross, object[0].center[0] + 0.5*width_cross, object[0].center[0] - 0.5*width_cross], ypts = [ object[0].ymax, object[0].ymax, object[0].ymax + appendage_lengths + port_routing_direction[0], object[0].ymax + appendage_lengths + port_routing_direction[1]], layer = layer_barrier[layer]) appendage_port = appendage.ports['3'] else: appendage = pg.polygon_ports(xpts = [object[0].center[0] - 0.5*width_cross, object[0].center[0] + 0.5*width_cross, object[0].center[0] + 0.5*width_cross, object[0].center[0] - 0.5*width_cross], ypts = [ object[0].ymin - appendage_lengths - port_routing_direction[0], object[0].ymin - appendage_lengths - port_routing_direction[1], object[0].ymin, object[0].ymin], layer = layer_barrier[layer]) appendage_port = appendage.ports['1'] return appendage, appendage_port """ Going through the indeces 0 and 1, to indicate lower and upper crosses. The middle cross barriers are only created when the loop is at index 0, since there is only one row of them. """ for j in range(2): for i in range(amount_of_dots): if i < amount_of_dots - 1: cross_barrier_obj, cross_barrier_port = cross_barriers(mid_point = upper_lower_cross_positions[j, i], width = width_cross, length = length_cross, layer = layer_barrier[layer], port_cardinal = upper_lower_cross_direction[j, i], port_routing_direction = upper_lower_routing_direction[j, i]) upper_lower_crosses[j, i] = cross_barrier_obj appendage, appendage_port = make_appendage(cross_barrier_obj, upper_lower_cross_direction[j, i], appendage_length_addition[i]) upper_lower_cross_ports[j, i] = appendage_port if j == 0: cross_barrier_obj_middle, cross_barrier_port_middle = cross_barriers( mid_point = middle_cross_positions[i], width = width_cross, length = length_cross, layer = layer_barrier[layer], port_cardinal = middle_cross_directions[i], port_routing_direction = middle_routing_direction[i]) middle_crosses[i] = cross_barrier_obj_middle if i != 0 and i != amount_of_dots - 1: appendage_middle, appendage_port_middle = make_appendage(cross_barrier_obj_middle, middle_cross_directions[i], appendage_length_middle_addition[i]) middle_cross_ports[i] = appendage_port_middle D << appendage_middle else: middle_cross_ports[i] = cross_barrier_port_middle D << middle_crosses[i] D << upper_lower_crosses[j, i] D << appendage if i == amount_of_dots - 1 and j == 0: cross_barrier_obj_middle, cross_barrier_port_middle = cross_barriers( mid_point = middle_cross_positions[i], width = width_cross, length = length_cross, layer = layer_barrier[layer], port_cardinal = middle_cross_directions[i], port_routing_direction = middle_routing_direction[i]) middle_crosses[i] = cross_barrier_obj_middle middle_cross_ports[i] = cross_barrier_port_middle D << middle_crosses[i] """ Build the barriers in between the sensors dots and the dots and add their ports to a list. """ barriers_between_sensors_and_dots = np.zeros(2, dtype = tuple) barriers_between_sensors_and_dots[0] = pg.polygon_ports(xpts = [(dot_2xN[0, 0].center[0] - abs(sensor_dots_2xN[0].center[0]))/2 - 0.5*width_barrier + 0.5*sensor_dot_dot_radii_difference, (dot_2xN[0, 0].center[0] - abs(sensor_dots_2xN[0].center[0]))/2 + 0.5*width_barrier + 0.5*sensor_dot_dot_radii_difference, (dot_2xN[0, 0].center[0] - abs(sensor_dots_2xN[0].center[0]))/2 + 0.5*width_barrier + 0.5*sensor_dot_dot_radii_difference, (dot_2xN[0, 0].center[0] - abs(sensor_dots_2xN[0].center[0]))/2 - 0.5*width_barrier + 0.5*sensor_dot_dot_radii_difference], ypts = [ sensor_dots_2xN[0].center[1] - 200, sensor_dots_2xN[0].center[1] - 200, sensor_dots_2xN[0].center[1] + 0.7*dot_radius, sensor_dots_2xN[0].center[1] + 0.7*dot_radius], layer = layer_barrier[layer]) barriers_between_sensors_and_dots[1] = pg.polygon_ports(xpts = [(dot_2xN[1, amount_of_dots - 1].center[0] + abs(sensor_dots_2xN[1].center[0]))/2 - 0.5*width_barrier - 0.5*sensor_dot_dot_radii_difference, (dot_2xN[1, amount_of_dots - 1].center[0] + abs(sensor_dots_2xN[1].center[0]))/2 + 0.5*width_barrier - 0.5*sensor_dot_dot_radii_difference, (dot_2xN[1, amount_of_dots - 1].center[0] + abs(sensor_dots_2xN[1].center[0]))/2 + 0.5*width_barrier - 0.5*sensor_dot_dot_radii_difference, (dot_2xN[1, amount_of_dots - 1].center[0] + abs(sensor_dots_2xN[1].center[0]))/2 - 0.5*width_barrier - 0.5*sensor_dot_dot_radii_difference], ypts = [ sensor_dots_2xN[1].center[1] - 0.7*dot_radius, sensor_dots_2xN[1].center[1] - 0.7*dot_radius, sensor_dots_2xN[1].center[1] + 200, sensor_dots_2xN[1].center[1] + 200], layer = layer_barrier[layer]) barrier_ports = [] barrier_ports.append(barriers_between_sensors_and_dots[0].ports['1']) barrier_ports.append(barriers_between_sensors_and_dots[1].ports['3']) for i in embedded_barrier_ports_2x2: barrier_ports.append(i) for objec in barriers_between_sensors_and_dots: D << objec """ Build the ohmics and their ports and add them to a list. Again, in this design, the ohmics do not vary with the amount of dots, so they can be created manually. """ ohmics = np.zeros(4, dtype = tuple) ohmics[0] = pg.polygon_ports(xpts = [ sensor_dots_2xN[0].center[0] - 0.4*sensordot_radius, sensor_dots_2xN[0].center[0] + 0.3*sensordot_radius , sensor_dots_2xN[0].center[0] + 0.3*sensordot_radius, sensor_dots_2xN[0].center[0] - 0.8*sensordot_radius], ypts = [ sensor_dots_2xN[0].center[1] - 1.3*sensordot_radius, sensor_dots_2xN[0].center[1] - 1.3*sensordot_radius, sensor_dots_2xN[0].center[1] - 1.7*sensordot_radius, sensor_dots_2xN[0].center[1] - 2*sensordot_radius], layer = layer_ohmic[layer]) ohmics[1] = pg.polygon_ports(xpts = [ sensor_dots_2xN[0].center[0] - 0.8*sensordot_radius, sensor_dots_2xN[0].center[0] + 0.3*sensordot_radius , sensor_dots_2xN[0].center[0] + 0.3*sensordot_radius, sensor_dots_2xN[0].center[0] - 0.4*sensordot_radius], ypts = [ sensor_dots_2xN[0].center[1] + 2*sensordot_radius, sensor_dots_2xN[0].center[1] + 1.7*sensordot_radius, sensor_dots_2xN[0].center[1] + 1.3*sensordot_radius, sensor_dots_2xN[0].center[1] + 1.3*sensordot_radius], layer = layer_ohmic[layer]) ohmics[2] = pg.polygon_ports(xpts = [ sensor_dots_2xN[1].center[0] - 0.4*sensordot_radius, sensor_dots_2xN[1].center[0] - 0.4*sensordot_radius , sensor_dots_2xN[1].center[0] + 0.8*sensordot_radius, sensor_dots_2xN[1].center[0] + 0.3*sensordot_radius], ypts = [ sensor_dots_2xN[1].center[1] - 1.3*sensordot_radius, sensor_dots_2xN[1].center[1] - 1.7*sensordot_radius, sensor_dots_2xN[1].center[1] - 2*sensordot_radius, sensor_dots_2xN[1].center[1] - 1.3*sensordot_radius], layer = layer_ohmic[layer]) ohmics[3] = pg.polygon_ports(xpts = [ sensor_dots_2xN[1].center[0] - 0.4*sensordot_radius, sensor_dots_2xN[1].center[0] + 0.3*sensordot_radius , sensor_dots_2xN[1].center[0] + 0.8*sensordot_radius, sensor_dots_2xN[1].center[0] - 0.4*sensordot_radius], ypts = [ sensor_dots_2xN[1].center[1] + 1.3*sensordot_radius, sensor_dots_2xN[1].center[1] + 1.3*sensordot_radius, sensor_dots_2xN[1].center[1] + 2*sensordot_radius, sensor_dots_2xN[1].center[1] + 1.7*sensordot_radius], layer = layer_ohmic[layer]) for objec in ohmics: D << objec ohmic_ports = [ohmics[0].ports['4'], ohmics[1].ports['4'], ohmics[2].ports['3'], ohmics[3].ports['2']] ext_ports = [[], [], [], []] def calculate_placements(amount_of_dots): """ The function calculates the amount of routings needed to the south and north side based on the amount of dots input. It is functional for everything below 10 dots, but after that, more functionality is needed. It is based on an alternating counter, since each inner cross barrier is alternated between the north and south routing. :amount_of_dots: The amount of dots in the design. """ north_counter = 0 south_counter = 0 if amount_of_dots > 3: for i in range(3, amount_of_dots + 1): if i%2 == 0: north_counter += 1 south_counter += 1 if amount_of_dots == 1: north_addition = 2 south_addition = 2 elif amount_of_dots%2 == 0: north_addition = 2 south_addition = 2 elif amount_of_dots%2 != 0: south_addition = 2 north_addition = 3 north_end = 2*amount_of_dots + north_addition + north_counter south_end = 2*amount_of_dots + south_addition + south_counter west_end = 6 east_end = 6 north_left = 12 - north_end south_left = 12 - south_end west_left = 6 east_left = 6 north_placement = np.arange(north_left/2 + 1, north_end + north_left/2 + 1, dtype = int) south_placement = np.arange(south_left/2 + 1, south_end + south_left/2 + 1, dtype = int) west_placement = np.arange(west_left/2 + 1, west_end + west_left/2 + 1, dtype = int) east_placement = np.arange(east_left/2 + 1, east_end + east_left/2 + 1, dtype = int) return north_placement, south_placement, west_placement, east_placement """ Setting the actual placements for the ports. """ North_placements, South_placements, West_placements, East_placements = calculate_placements(amount_of_dots) N_bond_numbers, S_bond_numbers, W_bond_numbers, E_bond_numbers = calculate_placements(amount_of_dots) North_length = len(North_placements) # Having calculated the number of routings, # we use this number for all South_length = len(South_placements) # subsequent calculations. ext_port_N, ext_port_S, ext_port_W, ext_port_E = [], [], [], [] bond_numbers = [N_bond_numbers, S_bond_numbers, W_bond_numbers, E_bond_numbers] list_of_numbers = [North_placements, South_placements, West_placements, East_placements] """ Going through each list of numbers and adding the external ports. """ counter_1 = 0 for i in list_of_numbers: for j in range(len(i)): if counter_1 == 0: ext_port_N.append('EXT_N' + str(j)) ext_port_N[j] = D.add_port(name='EXT_N' + str(j), midpoint = [x_midpoint_ext_port_h(i[j]),ymax_ext_box], width = system_params['width_external_port'], orientation = 270) if counter_1 == 1: ext_port_S.append('EXT_S' + str(j)) ext_port_S[j] = D.add_port(name='EXT_S' + str(j), midpoint = [x_midpoint_ext_port_h(i[j]),ymin_ext_box], width = system_params['width_external_port'], orientation = 90) if counter_1 == 2: ext_port_W.append('EXT_W' + str(j)) ext_port_W[j] = D.add_port(name='EXT_W' + str(j), midpoint = [xmin_ext_box, y_midpoint_ext_port_v(i[j])], width = system_params['width_external_port'], orientation = 0) if counter_1 == 3: ext_port_E.append('EXT_E' + str(j)) ext_port_E[j] = D.add_port(name='EXT_E' + str(j), midpoint = [xmax_ext_box, y_midpoint_ext_port_v(i[j])], width = system_params['width_external_port'], orientation = 180) counter_1 += 1 """ Each routed port and adjoining gate needs a layer, so those are created below. """ layer_N = np.zeros(North_length, dtype = tuple) layer_S = np.zeros(South_length, dtype = tuple) layer_N_pads = np.zeros(North_length, dtype = tuple) layer_S_pads = np.zeros(South_length, dtype = tuple) layer_W_pads = np.zeros(North_length, dtype = tuple) layer_E_pads = np.zeros(South_length, dtype = tuple) layer_N[0] = layer_barrier[layer] layer_N[1] = layer_dot[layer] layer_N[2] = layer_screening[layer] """ The current set-up for the outer layers is as follows: layers_for_outer[0] = dot layers_for_outer[1] = barrier layers_for_outer[2] = screening layers_for_outer[3] = ohmic The outer layers is meant for the creation of the outermost gates, in each of the four designs that fit on a chip. This is done so that none of them overlap with layers from the inner gates. """ layer_N_pads[0] = layers_for_outer[1] layer_N_pads[1] = layers_for_outer[0] layer_N_pads[2] = layers_for_outer[2] layer_N[North_length - 1] = layer_barrier[layer] layer_N_pads[North_length - 1] = layers_for_outer[1] layer_S[0] = layer_barrier[layer] layer_S[1] = layer_dot[layer] layer_S_pads[0] = layers_for_outer[1] layer_S_pads[1] = layers_for_outer[0] """ Initialising the list of layers that we will draw from. This is the general form of the layers, so we simply multiply it by some integer to get a long, repeating list. """ North_layers_list = [layer_barrier[layer], layer_dot[layer], layer_barrier[layer], layer_barrier[layer], layer_dot[layer]]*20 North_layers_pad_list = [layers_for_outer[1], layers_for_outer[0], layers_for_outer[1], layers_for_outer[1], layers_for_outer[0]]*20 South_layers_list = [layer_barrier[layer], layer_dot[layer], layer_barrier[layer], layer_barrier[layer], layer_dot[layer]]*20 South_layers_pad_list = [layers_for_outer[1], layers_for_outer[0], layers_for_outer[1], layers_for_outer[1], layers_for_outer[0]]*20 """ Adding the various layers to each array in the correct order. Start by initialising each end of the arrays, as those to not vary based on the amount of dots. """ if amount_of_dots > 1: layer_N[North_length - 2] = layer_dot[layer] layer_S[South_length - 1] = layer_barrier[layer] layer_S[South_length - 2] = layer_dot[layer] layer_S[South_length - 3] = layer_screening[layer] layer_N_pads[North_length - 2] = layers_for_outer[0] layer_S_pads[South_length - 1] = layers_for_outer[1] layer_S_pads[South_length - 2] = layers_for_outer[0] layer_S_pads[South_length - 3] = layers_for_outer[2] counter = 0 for i in range(3, North_length - 2): layer_N[i] = North_layers_list[counter] layer_N_pads[i] = North_layers_pad_list[counter] counter += 1 counter = 0 for i in range(2, South_length - 3): layer_S[i] = South_layers_list[counter] layer_S_pads[i] = South_layers_pad_list[counter] counter += 1 else: layer_S[South_length - 1] = layer_screening[layer] layer_S_pads[South_length - 1] = layers_for_outer[2] layer_W = [layer_ohmic[layer], layer_barrier[layer], layer_dot[layer], layer_screening[layer], layer_barrier[layer], layer_ohmic[layer]] layer_E = [layer_ohmic[layer], layer_barrier[layer], layer_dot[layer], layer_screening[layer], layer_barrier[layer], layer_ohmic[layer]] layer_W_pads = [layers_for_outer[3], layers_for_outer[1], layers_for_outer[0], layers_for_outer[2], layers_for_outer[1], layers_for_outer[3]] layer_E_pads = [layers_for_outer[3], layers_for_outer[1], layers_for_outer[0], layers_for_outer[2], layers_for_outer[1], layers_for_outer[3]] if amount_of_dots == 1: North_objects_to_route = np.zeros(North_length - 1, dtype = tuple) South_objects_to_route = np.zeros(South_length - 1, dtype = tuple) else: North_objects_to_route = np.zeros(North_length, dtype = tuple) South_objects_to_route = np.zeros(South_length, dtype = tuple) North_objects_to_route[0], North_objects_to_route[1], North_objects_to_route[2] = middle_cross_ports[0], dot_ports_2xN[1, 0], screening_ports[2] South_objects_to_route[0], South_objects_to_route[1] = barrier_ports[0], dot_ports_2xN[0, 0] North_objects_to_route[len(North_objects_to_route) - 1] = barrier_ports[1] West_objects_to_route = [ohmic_ports[0], barrier_ports[2], sensor_dot_ports_2xN[0], screening_ports[0], barrier_ports[3], ohmic_ports[1]] East_objects_to_route = [ohmic_ports[2], barrier_ports[4], sensor_dot_ports_2xN[1], screening_ports[1], barrier_ports[5], ohmic_ports[3]] def port_lists(multiple): """ This function creates lists of ports for the North and South sides of the device. It works by looking at mupltiples of 5, since that is the repeating pattern I found in the current design. As it is, it works for a smaller amount of dots, but more functionalty is needed for amount_of_dots > 9. """ end = multiple*5 North_ports_list = np.zeros(North_length, dtype = tuple) South_ports_list = np.zeros(South_length, dtype = tuple) upper_lower_1_counter, upper_lower_0_counter, middle_cross_counter, dot_ports_0_2xN_counter = 0, 0, 1, 1 dot_ports_1_2xN_counter = 1 for i in range(0, end, 5): if upper_lower_1_counter < len(upper_lower_cross_ports[1]): North_ports_list[i] = upper_lower_cross_ports[1, upper_lower_1_counter] upper_lower_1_counter += 1 if upper_lower_0_counter < len(upper_lower_cross_ports[0]): South_ports_list[i] = upper_lower_cross_ports[0, upper_lower_0_counter] upper_lower_0_counter += 1 if dot_ports_1_2xN_counter < len(dot_ports_2xN[1]): North_ports_list[i + 1] = dot_ports_2xN[1, dot_ports_1_2xN_counter] dot_ports_1_2xN_counter += 1 if dot_ports_0_2xN_counter < len(dot_ports_2xN[1]): South_ports_list[i + 1] = dot_ports_2xN[0, dot_ports_0_2xN_counter] dot_ports_0_2xN_counter += 1 if middle_cross_counter < len(middle_cross_ports): North_ports_list[i + 2] = middle_cross_ports[middle_cross_counter] middle_cross_counter += 1 if upper_lower_0_counter < len(upper_lower_cross_ports[0]): South_ports_list[i + 2] = upper_lower_cross_ports[0, upper_lower_0_counter] upper_lower_0_counter += 1 if upper_lower_1_counter < len(upper_lower_cross_ports[1]): North_ports_list[i + 3] = upper_lower_cross_ports[1, upper_lower_1_counter] upper_lower_1_counter += 1 if middle_cross_counter < len(middle_cross_ports): South_ports_list[i + 3] = middle_cross_ports[middle_cross_counter] middle_cross_counter += 1 if dot_ports_1_2xN_counter < len(dot_ports_2xN[1]): North_ports_list[i + 4] = dot_ports_2xN[1, dot_ports_1_2xN_counter] dot_ports_1_2xN_counter += 1 if dot_ports_0_2xN_counter < len(dot_ports_2xN[0]): South_ports_list[i + 4] = dot_ports_2xN[0, dot_ports_0_2xN_counter] dot_ports_0_2xN_counter += 1 return North_ports_list, South_ports_list """ Creating the porting list and filling it. """ North_ports_list, South_ports_list = port_lists(2) """ Setting the end objects, which do not vary with the amount of dots. These are the 2-3 first objects on each end. """ if amount_of_dots > 1: North_objects_to_route[North_length - 2] = dot_ports_2xN[1, amount_of_dots - 1] South_objects_to_route[South_length - 1] = middle_cross_ports[len(middle_cross_ports) - 1] South_objects_to_route[South_length - 2] = dot_ports_2xN[0, amount_of_dots - 1] South_objects_to_route[South_length - 3] = screening_ports[3] counter = 0 for i in range(3, North_length - 2): North_objects_to_route[i] = North_ports_list[counter] counter += 1 counter = 0 for i in range(2, South_length - 3): South_objects_to_route[i] = South_ports_list[counter] counter += 1 else: South_objects_to_route[len(South_objects_to_route) - 1] = screening_ports[3] """ Creating the arrays of external ports to route to, filling it up based on the above defined objects, and finally routing them. """ if amount_of_dots == 1: ext_ports = [ext_port_N[:len(North_objects_to_route) - 1], ext_port_S[:len(South_objects_to_route) - 1], ext_port_W, ext_port_E] else: ext_ports = [ext_port_N, ext_port_S, ext_port_W, ext_port_E] route_ext_N = [] route_ext_S = [] route_ext_W = [] route_ext_E = [] for i in ext_ports: for j in range(len(i)): if i == ext_port_N: route_ext_N.append('route_ext_N' + str(j)) route_ext_N[j] = D.add_ref(pr.route_quad(North_objects_to_route[j], i[j], layer = layer_N[j])) if i == ext_port_S: route_ext_S.append('route_ext_S' + str(j)) route_ext_S[j] = D.add_ref(pr.route_quad(South_objects_to_route[j], i[j], layer = layer_S[j])) if i == ext_port_W: route_ext_W.append('route_ext_W' + str(j)) route_ext_W[j] = D.add_ref(pr.route_quad(West_objects_to_route[j], i[j], layer = layer_W[j])) if i == ext_port_E: route_ext_E.append('route_ext_E' + str(j)) route_ext_E[j] = D.add_ref(pr.route_quad(East_objects_to_route[j], i[j], layer = layer_E[j])) """ Making a new device, without markers, so that it can be output at the end of the function. """ D_without_local_markers = pg.union(D, by_layer = True) D_local_markers = Device() d_ref6 = D_local_markers.add_ref(D) bond_extentable = True # Boolean which decides whether or not # we constrain the x-length """ The code below is for added funtionality, in which the placement of the inner markers are dependent on whether or not we have constrained the x-length of each design. This is not generally meant for the completed designs, but for creating new designs. """ D_local_markers = Device() # Making the structure with local markers D_local_markers.add_ref(D) if bond_extentable == True: inner_marker_NE = inner_marker_generator(system_params['width_local_marker'], system_params['length_local_marker'], layer_marker, cardinal = 'D') inner_marker_NE.move(destination = (0.88*np.cos(45)*system_params['length_to_local'], 0.75*np.cos(45)*system_params['length_to_local'])) D_local_markers.add_ref(inner_marker_NE, alias = "inner_marker_NE") inner_marker_NW = inner_marker_generator(system_params['width_local_marker'], system_params['length_local_marker'], layer_marker, cardinal = 'C') inner_marker_NW.move(destination = (-0.88*np.cos(45)*system_params['length_to_local'], 0.75*np.cos(45)*system_params['length_to_local'])) D_local_markers.add_ref(inner_marker_NW, alias = "inner_marker_NW") inner_marker_SE = inner_marker_generator(system_params['width_local_marker'], system_params['length_local_marker'], layer_marker, cardinal = 'B') inner_marker_SE.move(destination = (0.9*np.cos(45)*system_params['length_to_local'], -0.7*np.cos(45)*system_params['length_to_local'])) D_local_markers.add_ref(inner_marker_SE, alias = "inner_marker_SE") inner_marker_SW = inner_marker_generator(system_params['width_local_marker'], system_params['length_local_marker'], layer_marker, cardinal = 'A') inner_marker_SW.move(destination = (-0.9*np.cos(45)*system_params['length_to_local'], -0.7*np.cos(45)*system_params['length_to_local'])) D_local_markers.add_ref(inner_marker_SW, alias = "inner_marker_SW") else: inner_marker_NE = inner_marker_generator(system_params['width_local_marker'], system_params['length_local_marker'], layer_marker, cardinal = 'D') inner_marker_NE.move(destination = (np.cos(45)*system_params['length_to_local'], 0.825*np.cos(45)*system_params['length_to_local'])) D_local_markers.add_ref(inner_marker_NE, alias = "inner_marker_NE") inner_marker_NW = inner_marker_generator(system_params['width_local_marker'], system_params['length_local_marker'], layer_marker, cardinal = 'C') inner_marker_NW.move(destination = (-np.cos(45)*system_params['length_to_local'], 0.825*np.cos(45)*system_params['length_to_local'])) D_local_markers.add_ref(inner_marker_SE, alias = "inner_marker_SE") inner_marker_SE = inner_marker_generator(system_params['width_local_marker'], system_params['length_local_marker'], layer_marker, cardinal = 'B') inner_marker_SE.move(destination = (np.cos(45)*system_params['length_to_local'], -0.71*np.cos(45)*system_params['length_to_local'])) D_local_markers.add_ref(inner_marker_SE, alias = "inner_marker_SE") inner_marker_SW = inner_marker_generator(system_params['width_local_marker'], system_params['length_local_marker'], layer_marker, cardinal = 'A') inner_marker_SW.move(destination = (-np.cos(45)*system_params['length_to_local'], -0.71*np.cos(45)*system_params['length_to_local'])) D_local_markers.add_ref(inner_marker_SW, alias = "inner_marker_SW") d_ref6 = D_local_markers.add_ref(D) """ Defining the protection pad area, based on how many bonding pads there are. The north and south vary, but the west and east stay the same, currently. """ prod_pads_N = [[len(bond_numbers[0]) - 1, 0]] prod_pads_S = [[len(bond_numbers[1]) - 1, 0]] prod_pads_W = [[4, 1]] prod_pads_E = [[4, 1]] prod_pads = [prod_pads_N, prod_pads_S, prod_pads_W, prod_pads_E] layer_pads = [layer_N_pads, layer_S_pads, layer_W_pads, layer_E_pads] D = pg.union(D, by_layer = True) # The dose test boolean, decides whether a dose test design to be made. The dose test # design is the exact same design, but with only the inner structure, and a # cropped fanout. if dose_test: dose_test_device = Device() dose_test_device.add_ref(D_without_local_markers) qp(dose_test_device) pu.write_lyp(f'2x{amount_of_dots}_dosetest.lyp', layerset = lys) dose_test_device.write_gds(filename = f'2x{amount_of_dots}_dosetest.gds', # Output GDS file name unit = 1e-9, # Base unit (1e-6 = microns) precision = 1e-9, # Precision / resolution (1e-9 = nanometers) auto_rename = True, # Automatically rename cells to avoid collisions max_cellname_length = 28, # Max length of cell names cellname = 'toplevel' # Name of output top-level cell ) return D_local_markers, D_without_local_markers, D, list_of_numbers, ext_ports, bond_numbers, layer_pads, prod_pads def build_and_route_bond_pads(device, list_of_numbers, bond_numbers, ext_ports, prod_pads, layer_pads, width_bonding_box = None, nested = False): """ The function builds the outer structure of the chip. So all the fan-outs above the medium step, all the bonding pads, the protection pads, the outer makers the text on the chip, and the larger, visible patterns. :device: is the device :list_of_numbers: is a list of numbers deciding the placement of the bonding pads. :bond_numbers: is a list of numbers deciding the placement of the bonding pads. :ext_ports: is a list of the placement of the innermost fan-outs and their placement. :prod_pads: The protection pads meant for the bonding pads, which are not ohmic contacts. :layer_pads: A 4xX array, where X is the length of each cardinal direction of bonding pads, which decides the layer of the bonding pads. :width_bonding_box: The width of the bonding box. If None, the width is set to the initial value. :nested: boolean which decides on an angular step-like nested fan-out or straight fan-out. """ local_marker_NE_center = pg.extract(device["inner_marker_NE"]).polygons[0].center local_marker_SE_center = pg.extract(device["inner_marker_SE"]).polygons[0].center local_marker_NW_center = pg.extract(device["inner_marker_NW"]).polygons[0].center local_marker_SW_center = pg.extract(device["inner_marker_SW"]).polygons[0].center if width_bonding_box == None: xmax_bonding_box = device.center[0] + system_params['width_bonding_box_init']/2 xmin_bonding_box = device.center[0] - system_params['width_bonding_box_init']/2 else: xmax_bonding_box = device.center[0] + width_bonding_box/2 xmin_bonding_box = device.center[0] - width_bonding_box/2 ymax_bonding_box = device.center[1] + system_params['height_bonding_box']/2 ymin_bonding_box = device.center[1] - system_params['height_bonding_box']/2 x_bond_edge = 100000 y_bond_edge = 400000 """ Creating the arrays that will evenly distribute the bonding pads in the allowed range. """ north_x0 = np.linspace(xmin_bonding_box, xmax_bonding_box - x_bond_edge, num = len(bond_numbers[0])) south_x0 = np.linspace(xmin_bonding_box, xmax_bonding_box - x_bond_edge, num = len(bond_numbers[1])) west_y0 = np.linspace(ymin_bonding_box + y_bond_edge, ymax_bonding_box - y_bond_edge, num = len(bond_numbers[2])) east_y0 = np.linspace(ymin_bonding_box + y_bond_edge, ymax_bonding_box - y_bond_edge, num = len(bond_numbers[3])) nest1_divider = 1.1 nest2_divider = 7 nesting_dist_1 = 1.2 nesting_dist_2 = 4 nest1_width = 0.7*system_params['length_bonding_pad'] nest2_width = 0.1*system_params['length_bonding_pad'] centering_rectifier = 0.5*system_params['length_bonding_pad'] nest1_north_loc = np.linspace((xmin_bonding_box + centering_rectifier)/nest1_divider, (xmax_bonding_box - centering_rectifier)/nest1_divider, num = len(bond_numbers[0])) nest1_south_loc = np.linspace((xmin_bonding_box + centering_rectifier)/nest1_divider, (xmax_bonding_box - centering_rectifier)/nest1_divider, num = len(bond_numbers[1])) nest1_west_loc = np.linspace((west_y0[0] + centering_rectifier)/nest1_divider, (west_y0[-1] + centering_rectifier)/nest1_divider, num = len(bond_numbers[2])) nest1_east_loc = np.linspace((east_y0[0] + centering_rectifier)/nest1_divider, (east_y0[-1] + centering_rectifier)/nest1_divider, num = len(bond_numbers[3])) nest2_north_loc = np.linspace(local_marker_NW_center[0], local_marker_NE_center[0], num = len(bond_numbers[0])) nest2_south_loc = np.linspace(local_marker_SW_center[0], local_marker_SE_center[0], num = len(bond_numbers[1])) nest2_west_loc = np.linspace(nest1_west_loc[0]/nest2_divider, nest1_west_loc[-1]/nest2_divider, num = len(bond_numbers[2])) nest2_east_loc = np.linspace(nest1_east_loc[0]/nest2_divider, nest1_east_loc[-1]/nest2_divider, num = len(bond_numbers[3])) bond_pads_N, bond_pads_S, bond_pads_W, bond_pads_E, bond_pads_port_N, bond_pads_port_S, bond_pads_port_W, bond_pads_port_E = [], [], [], [], [], [], [], [] nest1_N, nest1_S, nest1_W, nest1_E, nest1_port_N, nest1_port_S, nest1_port_W, nest1_port_E = [], [], [], [], [], [], [], [] nest2_N, nest2_S, nest2_W, nest2_E, nest2_port_N, nest2_port_S, nest2_port_W, nest2_port_E = [], [], [], [], [], [], [], [] mid_port_N, mid_port_S, mid_port_W, mid_port_E = [], [], [], [] """ This for loop is long and maybe hard to read, however, its function is fairly simple: Create the bonding pads, place them according the arrays defined above, create the ports in the wanted direction, and route the ports to the middle ports that are defined via the x_midpoint_ext_port_h and y_midpoint_ext_port_v functions. """ counter_1 = 0 for i in list_of_numbers: for j in range(len(i)): if counter_1 == 0: bond_pads_N.append("bonding_pad_N" + str(i[j])) bond_pads_port_N.append("bond_port_pad_N" + str(i[j])) bond_pads_N[j] = device << pg.rectangle(size = (system_params['length_bonding_pad'], system_params['width_bonding_pad']), layer = layer_pads[0][j]).rotate(90) bond_pads_N[j].ymin = ymax_bonding_box bond_pads_N[j].xmin = north_x0[j] bond_pads_port_N[j] = device.add_port(name='B_N' + str(i[j]), midpoint = [bond_pads_N[j].center[0], bond_pads_N[j].ymin], width = system_params['width_port_bonding_pad'], orientation = 270) if nested: nest1_N.append("nest1_N" + str(i[j])) nest1_port_N.append("nest1_port_N" + str(i[j])) nest2_N.append("nest2_N" + str(i[j])) nest2_port_N.append("nest2_port_N" + str(i[j])) nest1_port_N[j] = device.add_port(name='nest1_N' + str(i[j]), midpoint = [nest1_north_loc[j], ymax_bonding_box/nesting_dist_1], width = nest1_width, orientation = 270) nest2_port_N[j] = device.add_port(name='nest2_N' + str(i[j]), midpoint = [nest2_north_loc[j], ymax_bonding_box/nesting_dist_2], width = nest2_width, orientation = 270) device.add_ref(pr.route_quad(bond_pads_port_N[j], nest1_port_N[j], layer = layer_pads[0][j])) device.add_ref(pr.route_quad(nest1_port_N[j], nest2_port_N[j], layer = layer_pads[0][j])) device.add_ref(pr.route_quad(nest2_port_N[j], ext_ports[0][j], layer = layer_pads[0][j])) else: device.add_ref(pr.route_quad(bond_pads_port_N[j], ext_ports[0][j], layer = layer_pads[0][j])) mid_port_N.append('mid_port_N' + str(j)) mid_port_N[j] = device.add_port(name='MID_N' + str(j), midpoint = [x_midpoint_ext_port_h(i[j]) + 70*(5.5 - i[j]), ymax_ext_box - system_params['closing_distance']], width = system_params['width_middle_port'], orientation = 270) device.add_ref(pr.route_quad(ext_ports[0][j], mid_port_N[j], layer = layer_pads[0][j])) if counter_1 == 1: bond_pads_S.append("bonding_pad_S" + str(i[j])) bond_pads_port_S.append("bond_port_pad_S" + str(i[j])) bond_pads_S[j] = device << pg.rectangle(size = (system_params['length_bonding_pad'], system_params['width_bonding_pad']), layer = layer_pads[1][j]).rotate(90) bond_pads_S[j].ymax = ymin_bonding_box bond_pads_S[j].xmin = south_x0[j] bond_pads_port_S[j] = device.add_port(name='B_S' + str(i[j]), midpoint = [bond_pads_S[j].center[0], bond_pads_S[j].ymax], width = system_params['width_port_bonding_pad'], orientation = 90) if nested: nest1_S.append("nest1_S" + str(i[j])) nest1_port_S.append("nest1_port_S" + str(i[j])) nest2_S.append("nest2_S" + str(i[j])) nest2_port_S.append("nest2_port_S" + str(i[j])) nest1_port_S[j] = device.add_port(name='nest1_S' + str(i[j]), midpoint = [nest1_south_loc[j], ymin_bonding_box/nesting_dist_1], width = nest1_width, orientation = 90) nest2_port_S[j] = device.add_port(name='nest2_S' + str(i[j]), midpoint = [nest2_south_loc[j], ymin_bonding_box/nesting_dist_2], width = nest2_width, orientation = 90) device.add_ref(pr.route_quad(bond_pads_port_S[j], nest1_port_S[j], layer = layer_pads[1][j])) device.add_ref(pr.route_quad(nest1_port_S[j], nest2_port_S[j], layer = layer_pads[1][j])) device.add_ref(pr.route_quad(nest2_port_S[j], ext_ports[1][j], layer = layer_pads[1][j])) else: device.add_ref(pr.route_quad(bond_pads_port_S[j], ext_ports[1][j], layer = layer_pads[1][j])) mid_port_S.append('mid_port_S' + str(j)) mid_port_S[j] = device.add_port(name='MID_S' + str(j), midpoint = [x_midpoint_ext_port_h(i[j]) + 70*(5.5 - i[j]), ymin_ext_box + system_params['closing_distance']], width = system_params['width_middle_port'], orientation = 90) device.add_ref(pr.route_quad(ext_ports[1][j], mid_port_S[j], layer = layer_pads[1][j])) if counter_1 == 2: bond_pads_W.append("bonding_pad_W" + str(i[j])) bond_pads_port_W.append("bond_port_pad_W" + str(i[j])) bond_pads_W[j] = device << pg.rectangle(size = (system_params['length_bonding_pad'], system_params['width_bonding_pad']), layer = layer_pads[2][j]) bond_pads_W[j].xmax = xmin_bonding_box bond_pads_W[j].ymin = west_y0[j] bond_pads_port_W[j] = device.add_port(name='B_W' + str(i[j]), midpoint = [bond_pads_W[j].xmax, bond_pads_W[j].center[1]], width = system_params['width_port_bonding_pad'], orientation = 0) if nested: nest1_W.append("nest1_W" + str(i[j])) nest1_port_W.append("nest1_port_W" + str(i[j])) nest2_W.append("nest2_W" + str(i[j])) nest2_port_W.append("nest2_port_W" + str(i[j])) nest1_port_W[j] = device.add_port(name='nest1_W' + str(i[j]), midpoint = [xmin_bonding_box/nesting_dist_1, nest1_west_loc[j]], width = nest1_width, orientation = 0) nest2_port_W[j] = device.add_port(name='nest2_W' + str(i[j]), midpoint = [xmin_bonding_box/nesting_dist_2, nest2_west_loc[j]], width = nest2_width, orientation = 0) device.add_ref(pr.route_quad(bond_pads_port_W[j], nest1_port_W[j], layer = layer_pads[2][j])) device.add_ref(pr.route_quad(nest1_port_W[j], nest2_port_W[j], layer = layer_pads[2][j])) device.add_ref(pr.route_quad(nest2_port_W[j], ext_ports[2][j], layer = layer_pads[2][j])) else: device.add_ref(pr.route_quad(bond_pads_port_W[j], ext_ports[2][j], layer = layer_pads[2][j])) mid_port_W.append('mid_port_W' + str(j)) mid_port_W[j] = device.add_port(name='MID_W' + str(j), midpoint = [xmin_ext_box + system_params['closing_distance'], y_midpoint_ext_port_v(i[j])+ 70*(5.5 - i[j])], width = system_params['width_middle_port'], orientation = 0) device.add_ref(pr.route_quad(ext_ports[2][j], mid_port_W[j], layer = layer_pads[2][j])) if counter_1 == 3: bond_pads_E.append("bonding_pad_E" + str(i[j])) bond_pads_port_E.append("bond_port_pad_E" + str(i[j])) bond_pads_E[j] = device << pg.rectangle(size = (system_params['length_bonding_pad'], system_params['width_bonding_pad']), layer = layer_pads[3][j]) bond_pads_E[j].xmin = xmax_bonding_box bond_pads_E[j].ymin = east_y0[j] bond_pads_port_E[j] = device.add_port(name='B_E' + str(i[j]), midpoint = [bond_pads_E[j].xmin, bond_pads_E[j].center[1]], width = system_params['width_port_bonding_pad'], orientation = 180) if nested: nest1_E.append("nest1_E" + str(i[j])) nest1_port_E.append("nest1_port_E" + str(i[j])) nest2_E.append("nest2_E" + str(i[j])) nest2_port_E.append("nest2_port_E" + str(i[j])) nest1_port_E[j] = device.add_port(name='nest1_E' + str(i[j]), midpoint = [xmax_bonding_box/nesting_dist_1, nest1_east_loc[j]], width = nest1_width, orientation = 180) nest2_port_E[j] = device.add_port(name='nest2_E' + str(i[j]), midpoint = [xmax_bonding_box/nesting_dist_2, nest2_east_loc[j]], width = nest2_width, orientation = 180) device.add_ref(pr.route_quad(bond_pads_port_E[j], nest1_port_E[j], layer = layer_pads[3][j])) device.add_ref(pr.route_quad(nest1_port_E[j], nest2_port_E[j], layer = layer_pads[3][j])) device.add_ref(pr.route_quad(nest2_port_E[j], ext_ports[3][j], layer = layer_pads[3][j])) else: device.add_ref(pr.route_quad(bond_pads_port_E[j], ext_ports[3][j], layer = layer_pads[3][j])) mid_port_E.append('mid_port_E' + str(j)) mid_port_E[j] = device.add_port(name='MID_E' + str(j), midpoint = [xmax_ext_box - system_params['closing_distance'], y_midpoint_ext_port_v(i[j]) + 70*(5.5 - i[j])], width = system_params['width_middle_port'], orientation = 180) device.add_ref(pr.route_quad(ext_ports[3][j], mid_port_E[j], layer = layer_pads[3][j])) counter_1 += 1 counter_1 = 0 prod_pads_bool = True """ Given the boolean prod_pads_bool, create protection pads for the bonding pads in each cardinal direction. """ if prod_pads_bool: for i in prod_pads: for j in range(len(i)): if counter_1 == 0: protection_pads_N = protection_pads(device, (bond_pads_N[i[j][0]].xmax + system_params['protection_extra'] - (bond_pads_N[i[j][1]].xmin - system_params['protection_extra'])), system_params['length_bonding_pad'], destination = (bond_pads_N[i[j][1]].xmin - system_params['protection_extra'], bond_pads_N[i[j][1]].ymin + system_params['protection_extra'])) if counter_1 == 1: protection_pads_S = protection_pads(device, (bond_pads_S[i[j][0]].xmax + system_params['protection_extra'] - (bond_pads_S[i[j][1]].xmin - system_params['protection_extra'])), system_params['length_bonding_pad'], destination = (bond_pads_S[i[j][1]].xmin - system_params['protection_extra'], bond_pads_S[i[j][1]].ymin - system_params['protection_extra'])) if counter_1 == 2: protection_pads_W = protection_pads(device, system_params['length_bonding_pad'], (bond_pads_W[i[j][0]].ymax + system_params['protection_extra'] - (bond_pads_W[i[j][1]].ymin - system_params['protection_extra'])), destination = (bond_pads_W[i[j][1]].xmin - system_params['protection_extra'], bond_pads_W[i[j][1]].ymin - system_params['protection_extra'])) if counter_1 == 3: protection_pad_E = protection_pads(device, system_params['length_bonding_pad'], (bond_pads_E[i[j][0]].ymax + system_params['protection_extra'] - (bond_pads_E[i[j][1]].ymin - system_params['protection_extra'])), destination = (bond_pads_E[i[j][1]].xmin + system_params['protection_extra'], bond_pads_E[i[j][1]].ymin - system_params['protection_extra'])) counter_1 += 1 device_joined_by_layer = pg.union(device, by_layer = True) return device_joined_by_layer def join_structures_on_chip(devices, sem_devices, bottom_left, bottom_right, top_left, top_right, layer_dummy_bonds, full = True): """ Make the full device by joining the structures on the chip. Then creates all the various other things, such as the dummy pads, the visual markers, the name on the chip. :outer_params: The set of parameters, which defines how the chip will look. If None, then the default parameters, defined above, will be used. :devices: is a list of the devices you want on the chip. It is set to be 4 long. :sem_devices: is a list of the SEM structures you want on the chip. It is set to be 4 long. :bottom_left: is a list of the relative coordinates for the bottom left grouping of markers. It is defined such that the corner marker is at position 0,0 and the rest are at integer position in relation to that. :bottom_right: is a list of the relative coordinates for the bottom right grouping of markers. It is defined such that the corner marker is at position 0,0 and the rest are at integer position in relation to that. :top_left: read above :top_right_ read above. """ outer_params = parameters['outer-params'] min_distance_text_from_border = outer_params['min_distance_text_from_border'] min_distance_marker_from_border = outer_params['min_distance_marker_from_border'] visual_marker_length = outer_params['visual_marker_length'] visual_marker_width = outer_params['visual_marker_width'] protection_extra = outer_params['protection_extra'] width_bonding_pad = outer_params['width_bonding_pad'] length_bonding_pad = outer_params['length_bonding_pad'] distance_structure_from_center = outer_params['distance_structure_from_center'] width_die = outer_params['width_die'] length_die = outer_params['length_die'] name_of_chip = outer_params['name_of_chip'] layer_slice_visuals = layer_marker if not full: length_die = 3.2e6 if outer_params == None: outer_params = outer_params full_device = Device() full_refs = np.zeros(4, dtype = tuple) die = pg.rectangle(size = (length_die, width_die), layer = layer_die) """ Place all the structures on the device, subsequently moce their positions and create and place the name of the chip. """ for i in range(len(devices)): full_refs[i] = full_device.add_ref(devices[i]) full_refs[0].xmin = die.center[0] - distance_structure_from_center*width_die - 0.5*(full_refs[0].xmax - full_refs[0].xmin) full_refs[0].ymax = die.center[1] - distance_structure_from_center*width_die + 0.5*(full_refs[0].ymax - full_refs[0].ymin) full_refs[1].xmax = die.center[0] - distance_structure_from_center*width_die + 0.5*(full_refs[1].xmax - full_refs[1].xmin) full_refs[1].ymin = die.center[1] + distance_structure_from_center*width_die - 0.5*(full_refs[1].ymax - full_refs[1].ymin) if full: full_refs[2].xmin = die.center[0] + distance_structure_from_center*width_die - 0.5*(full_refs[2].xmax - full_refs[2].xmin) full_refs[2].ymin = die.center[1] + distance_structure_from_center*width_die - 0.5*(full_refs[2].ymax - full_refs[2].ymin) full_refs[3].xmin = die.center[0] + distance_structure_from_center*width_die - 0.5*(full_refs[3].xmax - full_refs[3].xmin) full_refs[3].ymax = die.center[1] - distance_structure_from_center*width_die + 0.5*(full_refs[3].ymax - full_refs[3].ymin) if not full: full_refs[0].xmin += 0.5*(full_refs[0].xmax - full_refs[0].xmin)*1.1 full_refs[1].xmax += 0.5*(full_refs[1].xmax - full_refs[1].xmin)*1.1 text_die = full_device << pg.text(text=name_of_chip, size=1.8e5, justify='center', layer=layer_text, font='DEPLOF') text_die.ymax = die.ymax - 0.7*min_distance_text_from_border text_die.xmin = die.center[0] + 1.5*min_distance_text_from_border if not full: text_die.xmin = die.center[0] - min_distance_text_from_border """ The below crosses are created as a symbol on the top right of the chip. This is done to break symmetry. The first cross is placed according to absolute values on the chip, the rest are placed relative to the first cross. """ if full: symbol_length = visual_marker_length/5 symbol_width = visual_marker_width/5 symbol_1 = full_device << pg.cross(symbol_length, symbol_width, layer = layer_slice_visuals) symbol_2 = full_device << pg.cross(symbol_length, symbol_width, layer = layer_slice_visuals) symbol_3 = full_device << pg.cross(symbol_length, symbol_width, layer = layer_slice_visuals) symbol_4 = full_device << pg.cross(symbol_length, symbol_width, layer = layer_slice_visuals) symbol_5 = full_device << pg.cross(symbol_length, symbol_width, layer = layer_slice_visuals) symbol_6 = full_device << pg.cross(symbol_length, symbol_width, layer = layer_slice_visuals) symbol_1.center = (die.center[0] + 9*min_distance_text_from_border, die.ymax - 0.8*min_distance_text_from_border) symbol_2.center = (symbol_1.center[0] - symbol_length, symbol_1.center[1]) symbol_3.center = (symbol_2.center[0] - symbol_length, symbol_2.center[1]) symbol_4.center = (symbol_1.center[0], symbol_1.center[1] - symbol_length) symbol_5.center = (symbol_4.center[0] - symbol_length, symbol_4.center[1]) symbol_6.center = (symbol_5.center[0] - symbol_length, symbol_5.center[1]) """ Generate and place the markers in each corner of the chip. """ top_left_markers = marker_device_generator(top_left[0], top_left[1], 'C') top_right_markers = marker_device_generator(top_right[0], top_right[1], 'D') bottom_left_markers = marker_device_generator(bottom_left[0], bottom_left[1], 'A') bottom_right_markers = marker_device_generator(bottom_right[0], bottom_right[1], 'B') ref_markers_1 = full_device.add_ref(top_left_markers) ref_markers_2 = full_device.add_ref(top_right_markers) ref_markers_3 = full_device.add_ref(bottom_right_markers) ref_markers_4 = full_device.add_ref(bottom_left_markers) ref_markers_1.xmin = die.xmin + min_distance_marker_from_border ref_markers_1.ymax = die.ymax - min_distance_marker_from_border ref_markers_2.xmax = die.xmax - min_distance_marker_from_border ref_markers_2.ymax = die.ymax - min_distance_marker_from_border ref_markers_3.xmax = die.xmax - min_distance_marker_from_border ref_markers_3.ymin = die.ymin + min_distance_marker_from_border ref_markers_4.xmin = die.xmin + min_distance_marker_from_border ref_markers_4.ymin = die.ymin + min_distance_marker_from_border """ Set parameters for the dummy bond pads and utilise the dummy_pad function. See above documentation for further information on the :create_and_place_dumme_bond_pads: function. """ spacing_dummy_bonding_pads = 130e3 #nm amount_dummy_bonding_pads = 6 dummy_pads_distance_from_center = 250e3 dummy_pads_distance_from_side = 200e3 if full: dummy_pads_NN = create_and_place_dummy_bond_pads(full_device, amount_dummy_bonding_pads, length_bonding_pad, width_bonding_pad, spacing_dummy_bonding_pads, 'x', destination = (die.center[0] - 1.5*dummy_pads_distance_from_center - 5*spacing_dummy_bonding_pads - 6*width_bonding_pad, die.ymax - dummy_pads_distance_from_center), layer_dummy_bonds = layer_dummy_bonds) dummy_pads_SSW = create_and_place_dummy_bond_pads(full_device, amount_dummy_bonding_pads, length_bonding_pad, width_bonding_pad, spacing_dummy_bonding_pads, 'x', destination = (die.center[0] - 1.5*dummy_pads_distance_from_center - 5*spacing_dummy_bonding_pads - 6*width_bonding_pad, die.ymin + dummy_pads_distance_from_center/1.5), layer_dummy_bonds = layer_dummy_bonds) dummy_pads_SSE = create_and_place_dummy_bond_pads(full_device, amount_dummy_bonding_pads, length_bonding_pad, width_bonding_pad, spacing_dummy_bonding_pads, 'x', destination = (die.center[0] + 1.5*dummy_pads_distance_from_center, die.ymin + dummy_pads_distance_from_center/1.5), layer_dummy_bonds = layer_dummy_bonds) dummy_pads_SE = create_and_place_dummy_bond_pads(full_device, amount_dummy_bonding_pads, width_bonding_pad, length_bonding_pad, spacing_dummy_bonding_pads, 'y', destination = (die.xmax - dummy_pads_distance_from_side - length_bonding_pad, die.center[1] - dummy_pads_distance_from_center - 5*spacing_dummy_bonding_pads - 6*width_bonding_pad), layer_dummy_bonds = layer_dummy_bonds) dummy_pads_NE = create_and_place_dummy_bond_pads(full_device, amount_dummy_bonding_pads, width_bonding_pad, length_bonding_pad, spacing_dummy_bonding_pads, 'y', destination = (die.xmax - dummy_pads_distance_from_side - length_bonding_pad, die.center[1] + dummy_pads_distance_from_center), layer_dummy_bonds = layer_dummy_bonds) dummy_pads_NW = create_and_place_dummy_bond_pads(full_device, amount_dummy_bonding_pads, width_bonding_pad, length_bonding_pad, spacing_dummy_bonding_pads, 'y', destination = ((die.xmin + dummy_pads_distance_from_side, die.center[1] + dummy_pads_distance_from_center)), layer_dummy_bonds = layer_dummy_bonds) dummy_pads_SW = create_and_place_dummy_bond_pads(full_device, amount_dummy_bonding_pads, width_bonding_pad, length_bonding_pad, spacing_dummy_bonding_pads, 'y', destination = (die.xmin + dummy_pads_distance_from_side, die.center[1] - dummy_pads_distance_from_center - 5*spacing_dummy_bonding_pads - 6*width_bonding_pad), layer_dummy_bonds = layer_dummy_bonds) """ Define the bond pads that need protection, iterate through all cardinal directions, and create a protectional layer for the barriers, dots, and screening gates. The ohmics are not given protectional layers, as it does not matter. See above documentation for more information about the :protection_pads: function. """ if full: for_protection_N = [dummy_pads_NN] for_protection_SS = [dummy_pads_SSW, dummy_pads_SSE] for_protection_W = [dummy_pads_SW, dummy_pads_NW] for_protection_E = [dummy_pads_SE, dummy_pads_NE] if full: for pads in for_protection_N: protection_pad = protection_pads(full_device, (pads[2].xmax + protection_extra - (pads[1].xmin - protection_extra)), length_bonding_pad, destination = (pads[1].xmin - protection_extra, pads[1].ymin + protection_extra)) protection_pad = protection_pads(full_device, (pads[5].xmax + protection_extra - (pads[4].xmin - protection_extra)), length_bonding_pad, destination = (pads[4].xmin - protection_extra, pads[4].ymin + protection_extra)) for pads in for_protection_SS: protection_pad = protection_pads(full_device, (pads[2].xmax + protection_extra - (pads[1].xmin - protection_extra)), length_bonding_pad, destination = (pads[1].xmin - protection_extra, pads[1].ymin - protection_extra)) protection_pad = protection_pads(full_device, (pads[5].xmax + protection_extra - (pads[4].xmin - protection_extra)), length_bonding_pad, destination = (pads[4].xmin - protection_extra, pads[4].ymin - protection_extra)) for pads in for_protection_E: protection_pad = protection_pads(full_device, (pads[2].xmax - protection_extra - (pads[1].xmin - protection_extra)), 2*width_bonding_pad + spacing_dummy_bonding_pads + 2*protection_extra, destination = (pads[1].xmin + protection_extra, pads[1].ymin - protection_extra)) protection_pad = protection_pads(full_device, (pads[5].xmax - protection_extra - (pads[4].xmin - protection_extra)), 2*width_bonding_pad + spacing_dummy_bonding_pads + 2*protection_extra, destination = (pads[4].xmin + protection_extra, pads[4].ymin - protection_extra)) for pads in for_protection_W: protection_pad = protection_pads(full_device, (pads[2].xmax - protection_extra - (pads[1].xmin - protection_extra)), 2*width_bonding_pad + spacing_dummy_bonding_pads + 2*protection_extra, destination = (pads[1].xmin - protection_extra, pads[1].ymin - protection_extra)) protection_pad = protection_pads(full_device, (pads[5].xmax - protection_extra - (pads[4].xmin - protection_extra)), 2*width_bonding_pad + spacing_dummy_bonding_pads + 2*protection_extra, destination = (pads[4].xmin - protection_extra, pads[4].ymin - protection_extra)) """ Adding the SEM structures. Their x- and y-placement is set manually. """ if full: place_sem_structure(full_device, sem_devices[0], 'SW', xmin = die.center[0] - 0.5*sem_devices[0].xsize, ymin = die.center[1] + 10*sem_devices[0].ysize) place_sem_structure(full_device, sem_devices[1], 'SW', xmin = die.center[0] - 0.5*sem_devices[1].xsize, ymin = die.center[1] + 5*sem_devices[1].ysize) place_sem_structure(full_device, sem_devices[2], 'SW', xmin = die.center[0] - 0.5*sem_devices[2].xsize, ymin = die.center[1]) place_sem_structure(full_device, sem_devices[3], 'SW', xmin = die.center[0] - 0.5*sem_devices[3].xsize, ymin = die.center[1] - 5*sem_devices[3].ysize) """ Adding visual platinum markers, because Will said it was smart. """ visual_marker_west = full_device << pg.rectangle(size = (visual_marker_length, visual_marker_width), layer = layer_slice_visuals) visual_marker_west.move(destination = (die.center[0] - 0.5*width_die, die.center[1] - 0.5*visual_marker_width)) visual_marker_east = full_device << pg.rectangle(size = (visual_marker_length, visual_marker_width), layer = layer_slice_visuals) visual_marker_east.move(destination = (die.center[0] + 0.5*width_die - visual_marker_length, die.center[1] - 0.5*visual_marker_width)) if not full: visual_marker_west.move(destination = (die.center[0], die.center[1] - 0.5*width_die)) visual_marker_east.move(destination = (die.center[0] - 0.53*width_die, die.center[1] - 0.5*width_die)) if full: visual_marker_south = full_device << pg.rectangle(size = (visual_marker_width, visual_marker_length), layer = layer_slice_visuals) visual_marker_south.move(destination = (die.center[0] - 0.5*visual_marker_width, die.center[1] - 0.5*length_die)) visual_marker_north = full_device << pg.rectangle(size = (visual_marker_width, visual_marker_length), layer = layer_slice_visuals) visual_marker_north.move(destination = (die.center[0] - 0.5*visual_marker_width, die.center[1] + 0.5*length_die - visual_marker_length)) full_device = pg.union(full_device, by_layer = True) qp(full_device) """ Saving the design, along with it layer properties, and the parameters that created it. We save the parameters using pickling. """ pu.write_lyp(f'{name_of_chip}_layerinfo.lyp', layerset = lys) full_device.write_gds(filename = f'{name_of_chip}.gds', # Output GDS file name unit = 1e-9, # Base unit (1e-6 = microns) precision = 1e-9, # Precision / resolution (1e-9 = nanometers) auto_rename = True, # Automatically rename cells to avoid collisions max_cellname_length = 28, # Max length of cell names cellname = 'toplevel' # Name of output top-level cell )