Skip to content

Commit

Permalink
Merge pull request #2 from 6biscuits/zd_additions
Browse files Browse the repository at this point in the history
Zd additions
  • Loading branch information
6biscuits authored Aug 28, 2024
2 parents 4c25104 + 6c1202b commit b5dee43
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 18 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Tools to aid in simulating and fabricating superconducting quantum devices. The
- Visualising and simulating effects of shadow evaporation techniques used to fabricate qubits
- RF and DC simulations using **COMSOL** (including calculation of capacitance matrices, ~~inductance matrices~~ and RF s-parameters)
- RF and DC simulations using cluster-friendly **AWS PALACE** (including **meshing via either COMSOL or Gmsh**)
- GDS export and manipulation techniques to help with fabrication setup for multi-die wafers, arrayed structures, and more

There are two classes of documentation provided for this stack:

Expand Down
62 changes: 53 additions & 9 deletions SQDMetal/Utilities/FullChipMaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,54 @@ def make_resonator_chip(export_filename,
film_thickness="100nm",
chip_dimension=("20mm", "20mm"),
chip_border="500um",
die_dimension=("7.0mm", "4.0mm"),
die_dimension=("7.1mm", "4.4mm"),
die_num=[1,1],
fill_chip=True,
markers_on=True,
text_label="",
text_size=800,
text_size=600,
text_position=None,
print_all_infos=True
):
'''
Creates a `.gds` full-wafer layout file for a simple coplanar waveguide $\lambda/4$ resonator chip containing a number of resonators (usually 5) capacitively coupled to a transmission line.
Inputs:
- export_filename - Filename for gds export (e.g. "test")
- export_path - Path for export (e.g. 'exports'); the file will then be output to /exports/test.gds
- export_type - (Defaults to "all") Export type for lithography as per `MakeGDS` (options: "all", "positive", "negative")
- frequency_range - (Defaults to (6e9, 7e9)) Tuple containing minimum and maximum resonator frequencies in Hz
- num_resonators - (Defaults to 5) Number of resonators per die
- cpw_width - (Defaults to "9um") Width of the central trace on the feedline and resonators. The gap will be automatically calculated for 50 Ohm impedance based on the `substrate_material`
- coupling_gap - (Defaults to "20um") Amount of ground plane in the coupling gap between the feedline and the resonator
- tl_y - (Defaults to "0um") The die-relative y-value for the main straight of the feedline (NOTE: currently only "0um" is supported)
- res_vertical - (Defaults to "1500um") Vertical length of resonator meanders
- lp_to_res - (Defaults to "300um") Minimum distance between the launchpad taper and the coupling length of the left-most resonator
- lp_inset - (Defaults to "0um") Inset of the launchpads along the x-axis relative to the die boundaries
- lp_dimension - (Defaults to "600um") Width of the launchpads' conductive centre pad (the launchpad gap scales accordingly)
- lp_taper - (Defaults to "300um") Length of the taper from launchpad to feedline
- substrate_material - (Defaults to "silicon") Substrate material (currently only "silicon" and "sapphire" are supported)
- substrate_thickness - (Defaults to "0.5mm") Substrate thickness
- film_thickness - (Defaults to "100nm") Film thickness
- chip_dimension - (Defaults to ("20mm", "20mm")) Dimensions of the chip as an (x, y) Tuple
- chip_border - (Defaults to "500um") Chip border to leave un-patterned
- die_dimension - (Defaults to ("7.1mm", "4.4mm")) Dimensions of the die as an (x, y) Tuple
- die_num - (Defaults to [1, 1])) Die layout in [x, y] as a length-2 list
- fill_chip - (Defaults to True) Boolean to choose whether the full chip is automatically populated with dies (over-rides die_num if True)
- markers_on - (Defaults to True) Print dicing markers on export
- text_label - (Optional) Text label to print on chip
- text_size - (Defaults to 600) Text size
- text_position - (Optional) Tuple of text label location as normalised (x, y) (e.g. (0.1, 0.9) will place the text label 1/10th of the way along the chip in the x-direction, and 9/10ths of the way up the chip in the y-direction)
- print_all_infos - (Defaults to True) Choose whether to print info as the `.gds` is being generated
Outputs:
- design - Qiskit Metal design object for the generated chip
'''

# TODO: add option for scaling of resonator dimensions compared to feedline dimensions (i.e. so the feedline can be larger)

# TODO: add support for other `tl_y` values

t = datetime.now().strftime("%Y%m%d_%H%M") # add timestamp to export

print(f"{t}\nBuilding chip \"{export_filename}\" with the following options:\n")
Expand Down Expand Up @@ -150,7 +188,7 @@ def make_resonator_chip(export_filename,
launchpad_to_res="250um",
min_res_gap="50um",
LC_calculations=True,
print_statements=True
print_statements=print_all_infos
)

resonators_list.append(resonators)
Expand All @@ -168,23 +206,29 @@ def make_resonator_chip(export_filename,
die_index=i))

# draw markers
QUtilities.place_markers(design=design, die_origin=origin, die_dim=die_dimension)
if markers_on:
QUtilities.place_markers(design=design, die_origin=origin, die_dim=die_dimension)

print('\n\n~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~\n')

# setup GDS export (positive)
gds_export = MakeGDS(design, export_type='positive')
gds_export = MakeGDS(design, export_type=export_type)

# add text label
gds_export.add_text(text_label="Full chip maker test", size=600, position=(0.05, 0.93))
if text_position==None:
gds_export.add_text(text_label=text_label, size=text_size, position=(0.05, 0.93))
else:
gds_export.add_text(text_label=text_label, size=text_size, position=text_position)

# setup export path based on user inputs
if export_path=="":
full_export_path = os.path.join(f"{export_filename}_{t}.gds")
else:
full_export_path = os.path.join(export_path, f"{export_filename}_{t}.gds")

print('\n\n~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~^~\n')

# do export
gds_export.export(full_export_path)

print(f"Exported at {full_export_path}")
print(f"Exported at {full_export_path}")

return design
4 changes: 2 additions & 2 deletions SQDMetal/Utilities/MakeGDS.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def _round_corners(self, gds_metal, output_layer):

def refresh(self):
'''
Deletes everything and rebuilds metallic layers from scratch with the ground plane being in layer zero. The negative versions of the layers are given in layers starting from highest layer index plus 10. Can choose to export all layers (negative and positive patterns), or only positive or negative patterns.
Deletes everything and rebuilds metallic layers from scratch with the ground plane being in layer zero. The negative versions of the layers are given in layers starting from highest layer index plus 10. Export all layers (negative plus positive patterns), or only positive or negative patterns.
'''
assert self.export_type in ["all", "negative", "positive"], "Export type must be: \"all\", \"negative\", or \"positive\""

Expand Down Expand Up @@ -136,7 +136,7 @@ def refresh(self):
self._layer_metals[layer] = gds_metal

# fuse layers for a single-layer design (GND, metal)
if (len(all_layers)==2) and (self.export_type in ["posi tive", "negative"]):
if (len(all_layers)==2) and (self.export_type in ["positive", "negative"]):
if self.export_type=="positive":
self.add_boolean_layer(0, 0, "and", output_layer=0)
if self.export_type=="negative":
Expand Down
118 changes: 118 additions & 0 deletions SQDMetal/Utilities/ManipulateGDS.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import gdspy
import os
from SQDMetal.Utilities.MakeGDS import MakeGDS
from SQDMetal.Utilities.QUtilities import QUtilities

class ManipulateGDS:
def __init__(self, import_path_gds, export_path_gds, import_cells=None, origin=(0,0)):
'''
Class that will load a GDS file and perform actions such as making an array of the passed design (or a subsection of)
Inputs:
import_path_gds - path to GDS file for import
'''
# clear gds library
gdspy.current_library.cells.clear()
# initialise ManipulateGDS
self.infile = import_path_gds
self.outfile = export_path_gds
self.import_cells = import_cells
self.origin = origin
self.gds_units = 1e-6
# initiate gds cell and temp cell to help with copying
self.lib = gdspy.GdsLibrary()
# self.cell = self.lib.new_cell('Array')
self.gds_in = gdspy.GdsLibrary(infile=import_path_gds)
# list available cells and import cells
print(f'\n\nAvailable cells:\n\t{list(self.gds_in.cells.keys())}\n')
if import_cells == None:
print(f'Imported: All\n')
else:
print(f'Imported:\n\t{import_cells}\n')

def flatten_cells(self, cell_keys=None, export=False):
"""
Takes `cell_keys` (or all cells by default) from a GdsLibrary object and flattens them as seperate layers to a single cell export object.
"""

self.cell_flattened = self.lib.new_cell('Flattened')
self.cell_temp = self.lib.new_cell('Temp')

if not isinstance(cell_keys, list):
print(f'Single cell: {cell_keys}')
# get list of import cells (default: all)
if cell_keys==None:
cell_keys = list(self.gds_in.cells.keys())
else:
cell_keys = list(cell_keys)
# flatten all cells to single cell with different layers
for layer_idx, key in enumerate(cell_keys):
print(f"Copying cell '{key}' --> layer {layer_idx}")
# copy polygons to temp cell (and shift origin if needed)
self.cell_temp.add((gdspy.CellReference(self.gds_in.cells[key], origin=self.origin)).get_polygonsets())

self.cell_temp.flatten(single_layer=layer_idx)
# add to cell 'Flattened'
self.cell_flattened.add(self.cell_temp.get_polygonsets())
# reset temp cell
self.cell_temp.remove_polygons(lambda pts, layer, datatype: layer==layer_idx)
self.lib.remove(cell='Temp')
if export:
print(f"\nOutputting cell 'Flattened' ({len(self.cell_flattened.get_polygonsets())} polygons)\n\t--> {self.outfile}\n\n")

self.lib.write_gds(self.outfile, cells=['Flattened'])



def make_array_onChip(self, columns, rows,
spacing=("0um", "0um"), chip_dimension=("20mm", "20mm"), export=True, export_path=None, use_cells=None):
# import assertions
assert (isinstance(chip_dimension, tuple) and isinstance(chip_dimension[0], str)), r"Input argument 'chip_dimension' should be a tuple contining strings of the chip's (x, y) dimensions with units - e.g. 'chip_dimension=('20mm', '20mm')', as in qiskit and SQDMetal."

# default: copy all cells. Else use function input (if any), finally use class init input (if any)
if use_cells != None:
self.flatten_cells(cell_keys=use_cells)
elif (self.import_cells == None) and (use_cells == None):
self.flatten_cells(cell_keys=None)
else:
self.flatten_cells(cell_keys=self.import_cells)

# initialise an array cell
self.cell_array = self.lib.new_cell('Array')

# do arraying
arrays_of_polygonsets = gdspy.CellArray(self.cell_flattened,
columns=columns,
rows=rows,
spacing=(QUtilities.parse_value_length(spacing[0])/self.gds_units,QUtilities.parse_value_length(spacing[1])/self.gds_units)
).get_polygonsets()
self.cell_array.add(arrays_of_polygonsets)

# export if option is set
if export:
if export_path == None:
array_export = self.outfile
else:
array_export = export_path
self.lib.write_gds(array_export, cells=['Array'])
print(f"\nOutputting cell 'Array' ({len(self.cell_array.get_polygonsets())} polygons)\n\t--> {array_export}\n\n")

# TODO: add centering by default (i.e. so that the whole array is centred on (0,0))

return self.cell_array


f = ManipulateGDS(
import_path_gds="/Users/uqzdegna/Documents/Uni/PhD/qDesignDesk/junctions/manhattan_one_geo_v1.gds",
export_path_gds="/Users/uqzdegna/Documents/Uni/PhD/qDesignDesk/junctions/ManipulateGDS_test.gds",
origin=(0, 4000)
)

m = f.make_array_onChip(columns=16,
rows=16,
export=True,
spacing=("500um", "500um"),
use_cells=['TOP_main_2', 'TOP_main_1'],
export_path="/Users/uqzdegna/Documents/Uni/PhD/qDesignDesk/junctions/ManipulateGDS_array.gds")

exit()
10 changes: 4 additions & 6 deletions SQDMetal/Utilities/QUtilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -1134,8 +1134,7 @@ def place_transmission_line_from_launchpads(design, tl_y, gap, width, die_index)
# TODO: add automatic component/pin naming from launchpad list which should be passed as an input

# draw straight tranmission line if in the center of chip vertically (lining up with launchpads)
if tl_y == "0nm" or "0um" or "0mm" or "0cm" or "0m":

if tl_y in ["0nm", "0um", "0mm", "0cm", "0m"]:
tl_options = Dict(
pin_inputs=Dict(
start_pin=Dict(component=f'lp_L_die{die_index+1}', pin='tie'),
Expand All @@ -1144,14 +1143,13 @@ def place_transmission_line_from_launchpads(design, tl_y, gap, width, die_index)
trace_width=width,
trace_gap=gap,
)

tl = RouteStraight(design=design,
name=f"tl_die{die_index}",
options=tl_options)

else:
elif tl_y not in ["0nm", "0um", "0mm", "0cm", "0m"]:
raise Exception("Sorry, for now the transmission line must be at y=0um. Functionality for other placements coming soon")
# TODO: add functionality for non-centred transmission line



return tl

Expand Down
63 changes: 63 additions & 0 deletions docs/User/FullChipMaker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Full chip maker

Modules found under `SQDMetal.Utilities.FullChipMaker` used for semi-automated generation of circuits commonly used for research of superconducting quantum circuits, taking inputs for important wafer-scale parameters (wafer size, die size, die layout), and all relevant circuit parameters (frequencies, materials, width dimension, etc.).

This module relies on `SQDMetal` modules such as `SQDMetal.Utilities.QUtilities`, `SQDMetal.Utilities.MakeGDS`, `SQDMetal.Utilities.Material`, and functions for the calculation of resonator parameters located in `SQDMetal.Utilities.QubitDesigner`.

Currently, the supported circuits are:

- `MultiDieChip.make_resonator_chip()` - creates a `.gds` full-wafer layout file for a simple coplanar waveguide $\lambda/4$ resonator chip containing a number of resonators (usually 5) capacitively coupled to a transmission line


## Circuit types

The circuits listed within the section are called as functions within the `MultiDieChip` class.


### Hanger-mode $\lambda/4$ resonator chip

To import, use:
```python
from SQDMetal.Utilities.FullChipMaker import MultiDieChip
```

You can now generate a `.gds` file for the full multi-die chip with the function `make_resonator_chip` as follows:

```python
d = MultiDieChip.make_resonator_chip(export_filename="FullChipMakerDemo", export_path='gds_designs', cpw_width="25um",export_type="positive", text_label="Just a demo :)", chip_dimension=("8.4mm", "10.6mm"), text_size=400, text_position=(0.2, 0.9))
```

The above call mostly uses the default function values (which can be found in the source code), and user-input arguments. This produces the `.gds` shown below.

![Example output from the make_resonator_chip function in the MultiDieChip class.](FullChipMaker.png)

The full list of inputs are as follows:
- `export_filename` - Filename for gds export (e.g. "test")
- `export_path` - Path for export (e.g. 'exports'); the file will then be output to `/exports/test.gds`
- `export_type` - (Defaults to "all") Export type for lithography as per `MakeGDS` (options: "all", "positive", "negative")
- `frequency_range` - (Defaults to (6e9, 7e9)) Tuple containing minimum and maximum resonator frequencies in Hz
- `num_resonators` - (Defaults to 5) Number of resonators per die
- `cpw_width` - (Defaults to "9um") Width of the central trace on the feedline and resonators. The gap will be automatically calculated for 50 Ohm impedance based on the `substrate_material`
- `coupling_gap` - (Defaults to "20um") Amount of ground plane in the coupling gap between the feedline and the resonator
- `tl_y` - (Defaults to "0um") The die-relative y-value for the main straight of the feedline (NOTE: currently only "0um" is supported)
- `res_vertical` - (Defaults to "1500um") Vertical length of resonator meanders
- `lp_to_res` - (Defaults to "300um") Minimum distance between the launchpad taper and the coupling length of the left-most resonator
- `lp_inset` - (Defaults to "0um") Inset of the launchpads along the x-axis relative to the die boundaries
- `lp_dimension` - (Defaults to "600um") Width of the launchpads' conductive centre pad (the launchpad gap scales accordingly)
- `lp_taper` - (Defaults to "300um") Length of the taper from launchpad to feedline
- `substrate_material` - (Defaults to "silicon") Substrate material (currently only "silicon" and "sapphire" are supported)
- `substrate_thickness` - (Defaults to "0.5mm") Substrate thickness
- `film_thickness` - (Defaults to "100nm") Film thickness
- `chip_dimension` - (Defaults to ("20mm", "20mm")) Dimensions of the chip as an (x, y) Tuple
- `chip_border` - (Defaults to "500um") Chip border to leave un-patterned
- `die_dimension` - (Defaults to ("7.1mm", "4.4mm")) Dimensions of the die as an (x, y) Tuple
- `die_num` - (Defaults to [1, 1]) Die layout in [x, y] as a length-2 list
- `fill_chip` - (Defaults to True) Boolean to choose whether the full chip is automatically populated with dies (over-rides `die_num` if True)
- `markers_on` - (Defaults to True) Print dicing markers on export
- `text_label` - (Optional) Text label to print on chip
- `text_size` - (Defaults to 600) Text size
- `text_position` - (Optional) Tuple of text label location as normalised (x, y) (e.g. (0.1, 0.9) will place the text label 1/10th of the way along the chip in the x-direction, and 9/10ths of the way up the chip in the y-direction)
- `print_all_infos` - (Defaults to True) Choose whether to print info as the `.gds` is being generated



Binary file added docs/User/FullChipMaker.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit b5dee43

Please sign in to comment.