diff --git a/README.md b/README.md index ba027f1..cdb3cd5 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/SQDMetal/Utilities/FullChipMaker.py b/SQDMetal/Utilities/FullChipMaker.py index 57b2650..9c87e96 100644 --- a/SQDMetal/Utilities/FullChipMaker.py +++ b/SQDMetal/Utilities/FullChipMaker.py @@ -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") @@ -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) @@ -168,13 +206,19 @@ 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=="": @@ -182,9 +226,9 @@ def make_resonator_chip(export_filename, 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}") \ No newline at end of file + print(f"Exported at {full_export_path}") + + return design \ No newline at end of file diff --git a/SQDMetal/Utilities/MakeGDS.py b/SQDMetal/Utilities/MakeGDS.py index bd952f8..e6e6965 100644 --- a/SQDMetal/Utilities/MakeGDS.py +++ b/SQDMetal/Utilities/MakeGDS.py @@ -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\"" @@ -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": diff --git a/SQDMetal/Utilities/ManipulateGDS.py b/SQDMetal/Utilities/ManipulateGDS.py new file mode 100644 index 0000000..51a6cad --- /dev/null +++ b/SQDMetal/Utilities/ManipulateGDS.py @@ -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() \ No newline at end of file diff --git a/SQDMetal/Utilities/QUtilities.py b/SQDMetal/Utilities/QUtilities.py index 23586da..5abd5ae 100644 --- a/SQDMetal/Utilities/QUtilities.py +++ b/SQDMetal/Utilities/QUtilities.py @@ -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'), @@ -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 diff --git a/docs/User/FullChipMaker.md b/docs/User/FullChipMaker.md new file mode 100644 index 0000000..d3b3ae6 --- /dev/null +++ b/docs/User/FullChipMaker.md @@ -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 + + + diff --git a/docs/User/FullChipMaker.png b/docs/User/FullChipMaker.png new file mode 100644 index 0000000..1cbda65 Binary files /dev/null and b/docs/User/FullChipMaker.png differ diff --git a/docs/User/GDS.md b/docs/User/GDS.md index 9a40b39..7e40356 100644 --- a/docs/User/GDS.md +++ b/docs/User/GDS.md @@ -4,6 +4,9 @@ The GDSII format is used in lithography. This is a better implementation of the - It eliminates potential creases or cracks forming due to floating-point errors - Provides additional functionality such as boolean operations to add/combine layers +- Adds export options for certain lithographic processes (i.e. positive or negative) +- Allows export of certain layers +- Add rendered text as a seperate layer on the exported GDS The basic usage is: @@ -15,7 +18,14 @@ leGDS = MakeGDS(design) #Optional boolean layer via a logical OR operation on layers 0 and 1 new_layer_ind = leGDS.add_boolean_layer(0, 1, "or") -leGDS.export('first.gds') +#Export all layers +leGDS.export('first.gds', export_type="all") + +#Flatten all layers and export the positive pattern +leGDS.export('first_positive.gds', export_type="positive") + +#Export only layer 0 +leGDS.export('first_layer0.gds', export_layers=[0]) ``` Note that the returned layer index can be used for further boolean operations. The geometry can also be rounded in post-processing: @@ -34,3 +44,25 @@ top_layer = gdsexp.add_boolean_layer(2,2,'or') leGDS.export('rounded.gds') ``` +### Adding text to the export + +You can also add text with the `add_text()` function as follows: + +```python +from SQDMetal.Utilities.MakeGDS import MakeGDS + +leGDS = MakeGDS(design) + +#Add a text label in the top left of the design +leGDS.add_text(text_label="Add your label here", layer=10, size=600, position=(0.1, 0.9)) + +#Export design with text label on layer 10 +leGDS.export('design_with_text.gds') +``` + +The `add_text()` function inputs are described as: +- `text_label` - Text label to add +- `layer` - (Defaults to 0) Layer number to export the text to (if none, write to layer 0) +- `size` - (defaults to 800) Text size +- `position` - (Optional) Tuple containing position (normalised to 1); e.g. (0,0) is bottom left, (1,1) is top right. The default position is in the bottom left. + diff --git a/docs/User/Readme.md b/docs/User/Readme.md index 273a6ad..1501c62 100644 --- a/docs/User/Readme.md +++ b/docs/User/Readme.md @@ -21,6 +21,7 @@ Utility modules: - [Accounting for shadow evaporation](PVD.md) - [GDS Exporter](GDS.md) - [Qubit Designer](Qubit_Designer.md) +- [Full Chip Maker](FullChipMaker.md) Modules to aid in simulations: - [Simulations using COMSOL](Sim_Comsol.md)