Note

This page was generated from tut/3-Renderers/3.2-Export-your-design-to-GDS.ipynb.

3.2 Export your design to GDS

💡 Using this tutorial without the Qt GUI

This tutorial uses the desktop MetalGUI. To follow along on Colab, Binder, JupyterHub, or any environment where Qt isn’t available, replace any ``gui.rebuild()`` / ``gui.screenshot()`` call with ``qm.view(design)`` — it renders the design to a matplotlib Figure you can display inline or save with fig.savefig(...).

See 1.4 Headless quick view for a complete runnable walkthrough and `docs/headless-usage.rst <../../docs/headless-usage.rst>`__ for the full reference.

For convenience, let’s begin by enabling automatic reloading of modules when they change.

What does export_to_gds() put in my GDS file?

The cells below walk through the complete export pipeline:

  • Metal geometry (ground plane, qubit pockets, CPW traces) on the component’s layer number

  • Junction cells imported from an external GDS file and placed at each junction slot

  • Cheesing geometry (hole grid and keepout regions) on configurable sub-layers

  • Positive or negative mask output, selectable per layer

By the end you will be able to: export a full or partial design, inspect the layer/datatype structure, configure cheesing and mask polarity, and use the fabricate flag to strip debug intermediates from the final file.

[2]:
%load_ext autoreload
%autoreload 2
The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload

Import Qiskit Metal

[4]:
import qiskit_metal as metal
from qiskit_metal import designs, draw
from qiskit_metal import MetalGUI, Dict, Headings
from qiskit_metal.qlibrary.qubits.transmon_pocket import TransmonPocket
from qiskit_metal.qlibrary.qubits.transmon_cross import TransmonCross
[5]:
design = designs.DesignPlanar()
gui = MetalGUI(design)
[6]:
design.overwrite_enabled = True
design.delete_all_components()
gui.rebuild()  # refresh
[7]:
Headings.h1("Populate QDesign to demonstrate exporting to GDS format.")

Populate QDesign to demonstrate exporting to GDS format.

[8]:
from qiskit_metal.qlibrary.qubits.transmon_pocket import TransmonPocket

# Allow running the same cell here multiple times to overwrite changes.
design.overwrite_enabled = True

## Custom options for all the transmons.
options = dict(
    # Some options we want to modify from the defaults.
    # (see below for defaults)
    pad_gap="80 um",
    pad_width="425 um",
    pocket_height="650um",
    # Adding 4 connectors (see below for defaults)
    connection_pads=dict(
        a=dict(loc_W=+1, loc_H=+1),
        b=dict(loc_W=-1, loc_H=+1, pad_height="30um"),
        c=dict(loc_W=+1, loc_H=-1, pad_width="200um"),
        d=dict(loc_W=-1, loc_H=-1, pad_height="50um"),
    ),
)

``gds_cell_name`` — each TransmonPocket carries a gds_cell_name option that tells the GDS renderer which cell to pull from gds.options.path_filename and place at the junction slot. Using different names here (FakeJunction_01, _02, my_other_junction) lets you see all three cells in one export and verify orientation.

[9]:
## Create 4 TransmonPockets

# For variety and demonstartion, use different gds_cell_names.
q1 = TransmonPocket(
    design,
    "Q1",
    options=dict(
        pos_x="+2.55mm", pos_y="+0.0mm", gds_cell_name="FakeJunction_02", **options
    ),
)
q2 = TransmonPocket(
    design,
    "Q2",
    options=dict(
        pos_x="+0.0mm",
        pos_y="-0.9mm",
        orientation="90",
        gds_cell_name="FakeJunction_01",
        **options,
    ),
)
q3 = TransmonPocket(
    design,
    "Q3",
    options=dict(
        pos_x="-2.55mm", pos_y="+0.0mm", gds_cell_name="FakeJunction_01", **options
    ),
)
q4 = TransmonPocket(
    design,
    "Q4",
    options=dict(
        pos_x="+0.0mm",
        pos_y="+0.9mm",
        orientation="90",
        gds_cell_name="my_other_junction",
        **options,
    ),
)

## Rebuild the design
gui.rebuild()
gui.autoscale()
[10]:
gui.screenshot()
# Headless alternative: qm.view(design)
../../_images/tut_3-Renderers_3.2-Export-your-design-to-GDS_13_0.png

Connecting QPins with coplanar waveguides (CPWs) as described in earlier notebooks.

[11]:
from qiskit_metal.qlibrary.tlines.meandered import RouteMeander

RouteMeander.get_template_options(design)

options = Dict(meander=Dict(lead_start="0.1mm", lead_end="0.1mm", asymmetry="0 um"))


def connect(
    component_name: str,
    component1: str,
    pin1: str,
    component2: str,
    pin2: str,
    length: str,
    asymmetry="0 um",
    flip=False,
    fillet="50um",
):
    """Connect two pins with a CPW."""
    myoptions = Dict(
        fillet=fillet,
        pin_inputs=Dict(
            start_pin=Dict(component=component1, pin=pin1),
            end_pin=Dict(component=component2, pin=pin2),
        ),
        lead=Dict(start_straight="0.13mm", end_straight="0.13mm"),
        total_length=length,
    )
    myoptions.update(options)
    myoptions.meander.asymmetry = asymmetry
    myoptions.meander.lead_direction_inverted = "true" if flip else "false"
    return RouteMeander(design, component_name, myoptions)


asym = 90

# For variety in output, use different fillet values.
cpw1 = connect("cpw1", "Q1", "d", "Q2", "c", "5.7 mm", f"+{asym}um", fillet="25um")
cpw2 = connect(
    "cpw2", "Q3", "c", "Q2", "a", "5.4 mm", f"-{asym}um", flip=True, fillet="100um"
)
cpw3 = connect("cpw3", "Q3", "a", "Q4", "b", "5.3 mm", f"+{asym}um", fillet="75um")
cpw4 = connect("cpw4", "Q1", "b", "Q4", "d", "5.5 mm", f"-{asym}um", flip=True)

gui.rebuild()
gui.autoscale()
[12]:
gui.screenshot()
# Headless alternative: qm.view(design)
../../_images/tut_3-Renderers_3.2-Export-your-design-to-GDS_16_0.png
[13]:
Headings.h1("Exporting to a GDS file.")

Exporting to a GDS file.

[14]:
# QDesign registers GDS renderer during init of QDesign.
a_gds = design.renderers.gds
# An alternate way to invoke gds commands without using a_gds:
# design.renderers.gds.export_to_gds()

# Show the options for GDS
a_gds.options
[14]:
{'short_segments_to_not_fillet': 'True',
 'check_short_segments_by_scaling_fillet': '2.0',
 'gds_unit': 0.001,
 'ground_plane': 'True',
 'negative_mask': {'main': []},
 'fabricate': 'False',
 'corners': 'natural',
 'tolerance': '0.00001',
 'precision': '0.000000001',
 'width_LineString': '10um',
 'path_filename': '../resources/Fake_Junctions.GDS',
 'junction_pad_overlap': '5um',
 'max_points': '199',
 'cheese': {'datatype': '100',
  'shape': '0',
  'cheese_0_x': '25um',
  'cheese_0_y': '25um',
  'cheese_1_radius': '100um',
  'view_in_file': {'main': {1: True}},
  'delta_x': '100um',
  'delta_y': '100um',
  'edge_nocheese': '200um'},
 'no_cheese': {'datatype': '99',
  'buffer': '25um',
  'cap_style': '2',
  'join_style': '2',
  'view_in_file': {'main': {1: True}}},
 'bounding_box_scale_x': '1.2',
 'bounding_box_scale_y': '1.2'}

Will export_to_gds() add datatype for Polygon and Flexpath to my GDS file.

Yes. If you see a datatype being added to your positive mask, datatype=10 means a Polygon was added to the layer, datatype=11 means Flexpath was added to the layer. However, for each layer that has a negative mask, due to boolean subtract command done by export_to_gds, the 10 and 11 datatypes will not be in the gds file.

For positive mask, datatype=0 is for the ground plane.

To make junction table work correctly, GDS Renderer needs a correct path to a gds file, which has cells.

Each cell is a junction, to be placed, in a Transmon. A sample gds file is provided in directory qiskit_metal/tutorials/resources. There are three cells with names “Fake_Junction_01”, “Fake_Junction_01”, and “my_other_junction”. The default name used by GDS Render is “my_other_junction”. If you want to customize and select a junction, through the QComponent’s options, you can pass it when a qcomponent is being added to QDesign.

If your added junctions to a qcomponent.

The junctions are imported from file at gds.options.path_filename. The cell from path_filename which is denoted by TransmonPocket.options.gds_cell_name is imported and placed at location determined by the qcomponent developer. The method export_to_gds will place the imported cell at the same layers which are identified in the file at gds.options.path_filename.

[15]:
a_gds.options["path_filename"] = "../resources/Fake_Junctions.GDS"

Do you want GDS Renderer to fix any short-segments in your QDesign when using fillet?’

[16]:
# If you have a fillet_value and there are LineSegments that are shorter than 2*fillet_value,
# When true, the short segments will not be fillet'd.
a_gds.options["short_segments_to_not_fillet"] = "False"
[17]:
scale_fillet = 2.0
a_gds.options["check_short_segments_by_scaling_fillet"] = scale_fillet

What criteria will be used for identifying a short segment?

If a segment is smaller than (fillet length * scale_fillet)

What if a segment of LineString has few short segments?

If option ‘short_segments_to_not_fillet’ == ‘True’, QGDSRenderer will break the LineString into shorter Linestrings to make smaller LineStrings that will be either fillet’d or not, based on if the segment is short.

[18]:
# If you want to have the short segments not be fillet'd.
a_gds.options["short_segments_to_not_fillet"] = "True"
[19]:
# Export to a GDS formatted file for all components in design.
# def export_to_gds(self, file_name: str, highlight_qcomponents: list = []) -> int:

a_gds.export_to_gds("GDS QRenderer Notebook.gds")
# You can also specify a different path. Example:
#   a_gds.export_to_gds("../../../gds-files/GDS QRenderer Notebook.gds")
11:31PM 26s INFO [import_junction_gds_file]: Rescaling imported junction library from unit=1e-06 to unit=0.001 (scale factor=0.001).
[19]:
1
[20]:
# Export a GDS file which contains only few components.

# You will probably want to put the exported file in a specific directory.
# Please give the full path for output.
a_gds.export_to_gds(
    "four_qcomponents.gds", highlight_qcomponents=["cpw1", "cpw4", "Q1", "Q3"]
)
11:31PM 28s INFO [import_junction_gds_file]: Rescaling imported junction library from unit=1e-06 to unit=0.001 (scale factor=0.001).
[20]:
1
[21]:
# Export a GDS file using explicit path and cpw1.name vs typing string.
# You will probably want to put the exported file in a specific directory.
# Please give the full path for output.
a_gds.export_to_gds(
    "four_same_qcomponents.gds",
    highlight_qcomponents=[cpw1.name, "cpw4", q1.name, "Q3"],
)
11:31PM 28s INFO [import_junction_gds_file]: Rescaling imported junction library from unit=1e-06 to unit=0.001 (scale factor=0.001).
[21]:
1
[22]:
Headings.h1("QUESTION:  Where is the geometry of a QComponent placed?")

QUESTION: Where is the geometry of a QComponent placed?

Answer: QGeometry tables!

QGeometry tables are covered in detail in 2.4 — QRenderer Introduction. The short answer: every polygon, path, and junction slot that a component creates lands in the poly, path, or junction table respectively, and all renderers read from those tables on export.

[23]:
Headings.h1('What does GDS do with "junction" table?')

What does GDS do with "junction" table?

The junction table is handled differently by each QRenderer.

GDS QRenderer gets a cell, with the name, equal to “gds_cell_name” and places the cell into the QDesign before exporting the entire QDesign to GDS. In file: a_gds.options['path_filename'] = '../resources/Fake_Junctions.GDS', the gds_cell_name is searched. The cell is placed into QDesign using LINESTRING and width information.

The cell within “path_filename”, should be “x-axis” aligned and then GDS rotates based on LineString. The LineString should be two vertexes and it denotes two things.

  1. The midpoint of segment is the center of cell.

  2. The angle made by (second tuple - fist tuple), for delta y/ delta x, is used to rotate the cell.

When the cell from default_options.path_filename does not fit the width of LineString,

  • QGDSRender will create two pads and add to cell, which is denoted in junction table, to fill the width of LineString. The length of the additional pads is the value of “width” from the junction table.

  • The option a_gds.options["junction_pad_overlap"]='5um' is the amount the new pads will overlap the cell. The final width of the cell plus two pads is equal to the magnitude of LineString.

[24]:
# View every entry in junction table.
design.qgeometry.tables["junction"]
[24]:
component name geometry layer subtract helper chip width hfss_inductance hfss_capacitance ... hfss_mesh_kw_jj q3d_inductance q3d_capacitance q3d_resistance q3d_mesh_kw_jj gds_cell_name aedt_q3d_inductance aedt_q3d_capacitance aedt_hfss_inductance aedt_hfss_capacitance
0 1 rect_jj LINESTRING (2.55 -0.04, 2.55 0.04) 1 False False main 0.02 10nH 0 ... 0.000007 10nH 0 0 0.000007 FakeJunction_02 1.000000e-08 0 1.000000e-08 0
1 2 rect_jj LINESTRING (0.04 -0.9, -0.04 -0.9) 1 False False main 0.02 10nH 0 ... 0.000007 10nH 0 0 0.000007 FakeJunction_01 1.000000e-08 0 1.000000e-08 0
2 3 rect_jj LINESTRING (-2.55 -0.04, -2.55 0.04) 1 False False main 0.02 10nH 0 ... 0.000007 10nH 0 0 0.000007 FakeJunction_01 1.000000e-08 0 1.000000e-08 0
3 4 rect_jj LINESTRING (0.04 0.9, -0.04 0.9) 1 False False main 0.02 10nH 0 ... 0.000007 10nH 0 0 0.000007 my_other_junction 1.000000e-08 0 1.000000e-08 0

4 rows × 21 columns

[25]:
# View the juction table for component "q1".

q1.qgeometry_table("junction")
[25]:
component name geometry layer subtract helper chip width hfss_inductance hfss_capacitance ... hfss_mesh_kw_jj q3d_inductance q3d_capacitance q3d_resistance q3d_mesh_kw_jj gds_cell_name aedt_q3d_inductance aedt_q3d_capacitance aedt_hfss_inductance aedt_hfss_capacitance
0 1 rect_jj LINESTRING (2.55 -0.04, 2.55 0.04) 1 False False main 0.02 10nH 0 ... 0.000007 10nH 0 0 0.000007 FakeJunction_02 1.000000e-08 0 1.000000e-08 0

1 rows × 21 columns

Geometric boundary of a QComponent

q1.qgeometry_bounds() returns (minx, miny, maxx, maxy) for all geometry belonging to that component. Covered in full in 2.4 — QRenderer Introduction.

[26]:
# The current value of all the options for GDS QRenderer.
a_gds.options
[26]:
{'short_segments_to_not_fillet': 'True',
 'check_short_segments_by_scaling_fillet': 2.0,
 'gds_unit': 0.001,
 'ground_plane': 'True',
 'negative_mask': {'main': []},
 'fabricate': 'False',
 'corners': 'natural',
 'tolerance': '0.00001',
 'precision': '0.000000001',
 'width_LineString': '10um',
 'path_filename': '../resources/Fake_Junctions.GDS',
 'junction_pad_overlap': '5um',
 'max_points': '199',
 'cheese': {'datatype': '100',
  'shape': '0',
  'cheese_0_x': '25um',
  'cheese_0_y': '25um',
  'cheese_1_radius': '100um',
  'view_in_file': {'main': {1: True}},
  'delta_x': '100um',
  'delta_y': '100um',
  'edge_nocheese': '200um'},
 'no_cheese': {'datatype': '99',
  'buffer': '25um',
  'cap_style': '2',
  'join_style': '2',
  'view_in_file': {'main': {1: True}}},
 'bounding_box_scale_x': '1.2',
 'bounding_box_scale_y': '1.2'}

Number of vertices for linestrings.

The option max_points has default of 199. You can set it to a number no higher than 8191.

[27]:
### For demo, set max_points to 8191 and look at the GDS output.
a_gds.options["max_points"] = "8191"
a_gds.export_to_gds("GDS QRenderer Notebook maxpoints8191.gds")
11:31PM 35s INFO [import_junction_gds_file]: Rescaling imported junction library from unit=1e-06 to unit=0.001 (scale factor=0.001).
[27]:
1

Changing options ‘precision’ vs ‘tolerance’ ratio can impact how fillet will look.

The below numbers are used to create a gdstk.FlexPath when there is a fillet value in QGeometry table.

bend_radius – Bend radii for each path when corners is ‘circular bend’. It has no effect for other corner types. QGDSRender uses ‘circular bend’ as a default for corners. The fillet value used here.

Ensure that tolerance > precision.

tolerance (number) – Tolerance used to draw the paths and calculate joins.

precision (number) – Precision for rounding the coordinates of vertices when fracturing the final polygonal boundary.

[28]:
# Restore previous example.
a_gds.options["max_points"] = "199"

# For Demo, change tolerance value.
a_gds.options["tolerance"] = "0.01"
# This exported file will not look as desired.
a_gds.export_to_gds("GDS QRenderer Notebook change tolerance.gds")
11:31PM 36s INFO [import_junction_gds_file]: Rescaling imported junction library from unit=1e-06 to unit=0.001 (scale factor=0.001).
[28]:
1
[29]:
# change it back to was it was.
a_gds.options["tolerance"] = "0.00001"
[30]:
Headings.h1("Layer numbers: cheese and mask")

Layer numbers: cheese and mask

What can I control by layer number?

  • positive mask

  • negative mask

  • cheese

  • no_cheese

If you wanted cheese or no_cheese in the GDS file.

The reason you choose to use cheesing is up to the developer. There are considerations for both manufacturing and EM design. This refers to the holes in the output GDS ground plane (cheese = holes). From an EM stand-point https://www.degruyter.com/document/doi/10.1515/psr-2017-8000/html.

Cheesing refers to the holes added to the chip. The “cheese” output will correspond to the mask (positive or negative) selected. Both cheese and no_cheese output can be generated for any layer. You can choose to add the no_cheese to the exported GDS file; adding no_cheese information in GDS file, is just to show the intermediate step.

In the cheese dict, edge_nocheese option denotes a buffer around the perimeter of the chip. You can identify which layer has the cheesing by using the key view_in_file. To identify a datatype to store the geometry for cheesing, edit the key named datatype.

The option no_cheese can be thought of as a “keep-out” region for cheesing. Based on the components on the chip, a no-cheese region is determined. The no_cheese region is created by adding a buffer around all the components. In no_cheese dict, there is a key called buffer, that denotes the size around all the components for no_cheese. Even if view_in_file may be True for cheese dict, user can make it false for no_cheese dict. If the user wants to view the no_cheese region in the GDS output, keep it as True for each layer you are interested it.

The location of cheesing is determined by

  1. generating a grid of holes for the size of chip

  2. subtract the no_cheese region

  3. subtract the edge_nocheese region

The method export_to_gds, uses the datatype in cheese dict, default is 100, and no_cheese dict, default is 99; then uses few extra datatypes by adding one or two. The extra datatypes are to show some intermediate steps. If you choose to view_in_file, then you will see cheese or no-cheese in the gds file.

TOP_chipname_layernumber_one_hole is a cell with just one hole. The rest of the holes are made as a reference from the one hole. The hole is placed with datatype_cheese+2. This is expected to change when we agree to some convention.

TOP_chipname_layernumber_NoCheese_datatype is a cell that holds the “keep-out” region for cheesing.

TOP_chipname_layernumber_Cheese_datatype is a cell that holds the cheesing region.

TOP_chipname_layernumber holds the geometry taken from Metal GUI.

TOP_chipname_layernumber_Cheese_diff is a cell that holds the difference between grid of holes on chip, minus the no_cheese region. It is placed in datatype_cheese+1. This is expected to change when we agree to some convention.

For negative mask: TOP_chipname_layernumber_Cheese_diff is placed under TOP_chipname_layernumber.

For positive mask: TOP_chipname_layernumber_Cheese_diff is subtracted from ground and placed under TOP_chipname_layernumber.

Changing Cheesing options

There are two dicts in default_options for cheesing. One for selecting the no_cheese (keep-out region) for cheesing, the other for cheesing. Use them to determine if you would like the cheesing cell and the no_cheese cell to be added to the GDS file.

To create the no-cheese region, we take the existing components and add a buffer around the perimeter. The size of the buffer is an option.

The output of the cells can be placed with a data type. The user can select a different data type for both the no-cheese region and the cheese region.

Regarding the cheese option, there is room for expansion, presently, the shape that available for a hole is a shape=0, which is a square.

The present placement of holes allows the user to apply edge_nocheese to the perimeter of the chip. This allows the user to place holes at a perimeter smaller than the chip size. Then a grid of holes is made using delta_x and delta_y.

[31]:
a_gds.options.no_cheese
[31]:
{'datatype': '99',
 'buffer': '25um',
 'cap_style': '2',
 'join_style': '2',
 'view_in_file': {'main': {1: True}}}
[32]:
a_gds.options.cheese
[32]:
{'datatype': '100',
 'shape': '0',
 'cheese_0_x': '25um',
 'cheese_0_y': '25um',
 'cheese_1_radius': '100um',
 'view_in_file': {'main': {1: True}},
 'delta_x': '100um',
 'delta_y': '100um',
 'edge_nocheese': '200um'}

There is a dict option called view_in_file that denotes if the cells are added to GDS file. The first sub-option is the chip name, the second sub-option is layer number, the third sub-option is bool True/False.

In our example, the chip is ‘main’ and the layer is 1. We allow for expansion for multiple chips, and multiple ground layers.

[33]:
a_gds.options["cheese"]["view_in_file"]["main"][1] = True
a_gds.options["no_cheese"]["view_in_file"]["main"][1] = True
a_gds.export_to_gds("GDS QRender_cheese_keepout.gds")
11:31PM 44s INFO [import_junction_gds_file]: Rescaling imported junction library from unit=1e-06 to unit=0.001 (scale factor=0.001).
[33]:
1
[34]:
a_gds.options["cheese"]["view_in_file"]["main"][1] = True
a_gds.options["no_cheese"]["view_in_file"]["main"][1] = False
a_gds.export_to_gds("GDS QRender_cheese_only.gds")
11:31PM 48s INFO [import_junction_gds_file]: Rescaling imported junction library from unit=1e-06 to unit=0.001 (scale factor=0.001).
[34]:
1
[35]:
# For example, just move the cpws to layer=14.
cpw1.options.layer = 14
cpw2.options.layer = 14
cpw3.options.layer = 14
cpw4.options.layer = 14
[36]:
gui.rebuild()

# Get a list of all the qcomponents in QDesign and then zoom on them.
all_component_names = design.components.keys()
gui.zoom_on_components(all_component_names)
# Headless alternative: qm.view(design, components=list(all_component_names))
[37]:
gui.screenshot()
../../_images/tut_3-Renderers_3.2-Export-your-design-to-GDS_56_0.png
[38]:
# Turn on/off keep-out and cheesing.
a_gds.options["cheese"]["view_in_file"]["main"][1] = True
a_gds.options["no_cheese"]["view_in_file"]["main"][1] = True
a_gds.options["cheese"]["view_in_file"]["main"][14] = True
a_gds.options["no_cheese"]["view_in_file"]["main"][14] = True

If you want a positive and/or negative masks.

User can request a positive or negative mask by chip-name and layer-number. The default is to use a positive mask. If user wants to use a negative mask, then just identify the layers that should be a negative mask. The user is allowed to mix the output per chip, per layer. Particularly, a chip can have both positive and negative masks; the granularity is by layer. This is exemplified in this notebook by editing option “negative_mask”. This feature was implemented for, potential use, for “flip-chip”.

[39]:
# Export GDS file with negative masks for layers 1 and 14.
a_gds.options["negative_mask"] = Dict(main=[1, 14])
a_gds.export_to_gds("GDS QRenderer Notebook_neg_mask_for_layer_1_14.gds")
11:31PM 56s INFO [import_junction_gds_file]: Rescaling imported junction library from unit=1e-06 to unit=0.001 (scale factor=0.001).
[39]:
1
[40]:
# Export GDS files with positive mask for layer 1 and negative mask for layer 14.
a_gds.options["negative_mask"] = Dict(main=[14])
a_gds.export_to_gds("GDS QRenderer Notebook_neg_mask_for_layer_14.gds")
11:31PM 58s INFO [import_junction_gds_file]: Rescaling imported junction library from unit=1e-06 to unit=0.001 (scale factor=0.001).
[40]:
1
[58]:
# Load and inspect the exported GDS.
# show=True renders the design as an embedded SVG in this cell output
# (the image saves with the notebook — no external file needed).
# scale: increase for larger chips or more detail (default 100)
# width: display width in pixels (default 800)

import gdstk

lib = gdstk.read_gds("GDS QRenderer Notebook_neg_mask_for_layer_1_14.gds")
gds.debug_summarize_gds_library(lib, show=True, scale=100, width=900)

# lib = gdstk.read_gds("GDS QRenderer Notebook_neg_mask_for_layer_14.gds")
# gds.debug_summarize_gds_library(lib, show=True, scale=100, width=900)
LayerDTypeDescriptionPolygonsPaths
00metal (boolean result)4 poly0 paths
1994 poly0 paths
11014618 poly0 paths
11021 poly0 paths
140metal (boolean result)64 poly0 paths
14994 poly0 paths
141014640 poly0 paths
141021 poly0 paths
530metal (boolean result)13 poly0 paths
540metal (boolean result)3 poly0 paths

=== GDS LIBRARY SUMMARY ===
name:      library
unit:      0.001
precision: 1e-09
cells:     14

CELLS:
  - TOP                            geom=True bbox=((-4.3125, -2.8125), (4.2124999999999995, 2.7125))
  - TOP_main                       geom=True bbox=((-4.3125, -2.8125), (4.2124999999999995, 2.7125))
  - TOP_main_14                    geom=True bbox=((-4.3125, -2.8125), (4.2124999999999995, 2.7125))
  - my_other_junction              geom=True bbox=((-0.015, -0.0015), (0.015, 0.0015))
  - FakeJunction_02                geom=True bbox=((-0.015, -0.0015), (0.015, 0.0015))
  - FakeJunction_01                geom=True bbox=((-0.015, -0.001503), (0.015, 0.0015))
  - all_jj_imported_1              geom=True bbox=((-2.5515, -0.9015), (2.5515, 0.9015))
  - TOP_main_1                     geom=True bbox=((-4.3125, -2.8125), (4.2124999999999995, 2.7125))
  - TOP_main_1_NoCheese_99         geom=True bbox=((-3.0, -1.3499999999999999), (3.0, 1.3499999999999999))
  - TOP_main_14_NoCheese_99        geom=True bbox=((-2.15, -0.5941029999999999), (2.15, 0.580476))
  - TOP_main_1_one_hole            geom=True bbox=((-0.012499999999999999, -0.012499999999999999), (0.012499999999999999, 0.012499999999999999))
  - TOP_main_1_Cheese_diff         geom=True bbox=((-4.3125, -2.8125), (4.2124999999999995, 2.7125))
  - TOP_main_14_one_hole           geom=True bbox=((-0.012499999999999999, -0.012499999999999999), (0.012499999999999999, 0.012499999999999999))
  - TOP_main_14_Cheese_diff        geom=True bbox=((-4.3125, -2.8125), (4.2124999999999995, 2.7125))

LAYER / DATATYPE USAGE:
  layer  dtype   polys   paths
  -----  -----   -----   -----
      0      0       4       0
      1     99       4       0
      1    101    4618       0
      1    102       1       0
     14      0      64       0
     14     99       4       0
     14    101    4640       0
     14    102       1       0
     53      0      13       0
     54      0       3       0

=== END SUMMARY ===

[41]:
Headings.h1("Fabrication")

Fabrication

There is no universally accepted method to prepare for fabrication of a chip. If this flag is True, cells created as part of intermittent steps would not be in the gds file. For example cells with names similar to “TOP_main_1_NoCheese_99” , “TOP_main_1_one_hole” and “ground_main_1”.

[42]:
# By default, the fabrication flag is False.
a_gds.options["fabricate"] = "True"
[43]:
# Export GDS files with positive mask for layer 1 and negative mask for layer 14 with fabricate flag as True.
a_gds.options["negative_mask"] = Dict(main=[14])
a_gds.export_to_gds("GDS QRenderer Notebook_neg_mask_for_layer_14_fabricate.gds")
11:32PM 00s INFO [import_junction_gds_file]: Rescaling imported junction library from unit=1e-06 to unit=0.001 (scale factor=0.001).
[43]:
1
[56]:
# Load and inspect the exported GDS.
# Re-run the export cell above first if awesome_design.gds looks like an older design.
import gdstk

lib = gdstk.read_gds("GDS QRenderer Notebook_neg_mask_for_layer_14_fabricate.gds")

# show=True renders the design as an embedded SVG in this cell output
# (the image saves with the notebook — no external file needed).
# scale: increase for larger chips or more detail (default 100)
# width: display width in pixels (default 800)
gds.debug_summarize_gds_library(lib, show=True, scale=100, width=900)
[GDSTK] Missing referenced cell ground_main_1
[GDSTK] Missing referenced cell TOP_main_1_one_hole
[GDSTK] Missing referenced cell TOP_main_1_Cheese_diff
[GDSTK] Missing referenced cell TOP_main_14_one_hole
LayerDTypeDescriptionPolygonsPaths
110component polygon input32 poly0 paths
111CPW FlexPath trace16 poly0 paths
11003293 poly0 paths
140metal (boolean result)64 poly0 paths
141014640 poly0 paths
530metal (boolean result)13 poly0 paths
540metal (boolean result)3 poly0 paths

=== GDS LIBRARY SUMMARY ===
name:      library
unit:      0.001
precision: 1e-09
cells:     13

CELLS:
  - TOP                            geom=True bbox=((-4.5, -3.0), (4.5, 3.0))
  - TOP_main                       geom=True bbox=((-4.5, -3.0), (4.5, 3.0))
  - TOP_main_1                     geom=True bbox=((-4.5, -3.0), (4.5, 3.0))
  - TOP_main_14                    geom=True bbox=((-4.3125, -2.8125), (4.2124999999999995, 2.7125))
  - my_other_junction              geom=True bbox=((-0.015, -0.0015), (0.015, 0.0015))
  - FakeJunction_02                geom=True bbox=((-0.015, -0.0015), (0.015, 0.0015))
  - FakeJunction_01                geom=True bbox=((-0.015, -0.001503), (0.015, 0.0015))
  - pads_FakeJunction_02_QComponent_is_1_name_is_rect_jj geom=True bbox=((-0.04, -0.01), (0.04, 0.01))
  - pads_FakeJunction_01_QComponent_is_2_name_is_rect_jj geom=True bbox=((-0.04, -0.010001999999999999), (0.04, 0.009999))
  - pads_FakeJunction_01_QComponent_is_3_name_is_rect_jj geom=True bbox=((-0.04, -0.010001999999999999), (0.04, 0.009999))
  - pads_my_other_junction_QComponent_is_4_name_is_rect_jj geom=True bbox=((-0.04, -0.01), (0.04, 0.01))
  - TOP_main_1_Cheese_100          geom=True bbox=((-4.5, -3.0), (4.5, 3.0))
  - TOP_main_14_Cheese_diff        geom=True bbox=((-4.3125, -2.8125), (4.2124999999999995, 2.7125))

LAYER / DATATYPE USAGE:
  layer  dtype   polys   paths
  -----  -----   -----   -----
      1     10      32       0
      1     11      16       0
      1    100    3293       0
     14      0      64       0
     14    101    4640       0
     53      0      13       0
     54      0       3       0

=== END SUMMARY ===

Qiskit Metal Version

[44]:
metal.about();

Qiskit Metal        0.6.2

Basic
____________________________________
 Python              3.11.14 (main, Dec  5 2025, 21:28:33) [Clang 21.1.4 ]
 Platform            Darwin arm64
 Installation path   /Users/zlatkominev/CODE_REPOS/quantum_hardware/qiskit-metal/src/qiskit_metal

Packages
____________________________________
 Numpy               1.26.4
 Qutip               5.2.2

Rendering
____________________________________
 Matplotlib          3.10.8

GUI
____________________________________
 PySide6 version     6.10.1
 Qt version          6.10.1
 SIP version         Not installed

IBM Quantum Team
[ ]:
# Uncomment to close the GUI window when done.
# The next section (junction import details) re-uses the same design
# and renderer — leave the GUI open if you want to keep inspecting the design.
# gui.main_window.close()

Deep dive: junction import and cheesing

The sections above covered the end-to-end export flow. The rest of this notebook goes deeper on two topics that often need tuning before tapeout:

  1. Junction GDS files — how cells are imported, unit-rescaled, and placed

  2. Cheesing geometry — what the algorithm produces and how to read the output layers

[45]:
gds = design.renderers.gds
gds.options.path_filename
[45]:
'../resources/Fake_Junctions.GDS'
[46]:
gds.options.path_filename = "../resources/Fake_Junctions.GDS"
[47]:
q1.options
[47]:
{'pos_x': '+2.55mm',
 'pos_y': '+0.0mm',
 'orientation': '0.0',
 'chip': 'main',
 'layer': '1',
 'connection_pads': {'a': {'pad_gap': '15um',
   'pad_width': '125um',
   'pad_height': '30um',
   'pad_cpw_shift': '5um',
   'pad_cpw_extent': '25um',
   'cpw_width': 'cpw_width',
   'cpw_gap': 'cpw_gap',
   'cpw_extend': '100um',
   'pocket_extent': '5um',
   'pocket_rise': '65um',
   'loc_W': 1,
   'loc_H': 1},
  'b': {'pad_gap': '15um',
   'pad_width': '125um',
   'pad_height': '30um',
   'pad_cpw_shift': '5um',
   'pad_cpw_extent': '25um',
   'cpw_width': 'cpw_width',
   'cpw_gap': 'cpw_gap',
   'cpw_extend': '100um',
   'pocket_extent': '5um',
   'pocket_rise': '65um',
   'loc_W': -1,
   'loc_H': 1},
  'c': {'pad_gap': '15um',
   'pad_width': '200um',
   'pad_height': '30um',
   'pad_cpw_shift': '5um',
   'pad_cpw_extent': '25um',
   'cpw_width': 'cpw_width',
   'cpw_gap': 'cpw_gap',
   'cpw_extend': '100um',
   'pocket_extent': '5um',
   'pocket_rise': '65um',
   'loc_W': 1,
   'loc_H': -1},
  'd': {'pad_gap': '15um',
   'pad_width': '125um',
   'pad_height': '50um',
   'pad_cpw_shift': '5um',
   'pad_cpw_extent': '25um',
   'cpw_width': 'cpw_width',
   'cpw_gap': 'cpw_gap',
   'cpw_extend': '100um',
   'pocket_extent': '5um',
   'pocket_rise': '65um',
   'loc_W': -1,
   'loc_H': -1}},
 'pad_gap': '80 um',
 'inductor_width': '20um',
 'pad_width': '425 um',
 'pad_height': '90um',
 'pocket_width': '650um',
 'pocket_height': '650um',
 'hfss_wire_bonds': False,
 'q3d_wire_bonds': False,
 'aedt_q3d_wire_bonds': False,
 'aedt_hfss_wire_bonds': False,
 'hfss_inductance': '10nH',
 'hfss_capacitance': 0,
 'hfss_resistance': 0,
 'hfss_mesh_kw_jj': 7e-06,
 'q3d_inductance': '10nH',
 'q3d_capacitance': 0,
 'q3d_resistance': 0,
 'q3d_mesh_kw_jj': 7e-06,
 'gds_cell_name': 'FakeJunction_02',
 'aedt_q3d_inductance': 1e-08,
 'aedt_q3d_capacitance': 0,
 'aedt_hfss_inductance': 1e-08,
 'aedt_hfss_capacitance': 0}

Note the GDS entry for the JJ cell name from a GDS external file

'gds_cell_name': 'FakeJunction_01'

This cell name is pulled from the file set in gds.options.path_filename and placed inside the qubit pocket on export.

Junction GDS unit convention Your junction GDS file can use any unit (1e-6 for µm, 1e-9 for nm, etc.). > The renderer reads the file’s own unit field and automatically rescales all > junction geometry so it lands at the correct physical size in the exported design. > You do not need to pre-convert your junction file to match the design’s unit (mm). > The only requirement is that coordinates inside the GDS file are consistent with > its declared unit.

Josephon Junction GDS file path: gds.options.path_filename

Using your own junction GDS file

Set gds.options.path_filename to any GDS file that contains your junction cells. The renderer will:

  1. Read the file and detect its unit (e.g. 1e-6 for µm, 1e-9 for nm).

  2. Compute a scale factor = junction_file_unit / design_unit and rescale all junction geometry so physical dimensions are preserved.

  3. Merge the scaled cells into the exported GDS — no manual unit conversion needed.

The example file resources/Fake_Junctions.GDS ships with the repo and contains three placeholder cells (in µm coordinates): FakeJunction_01, FakeJunction_02, and my_other_junction. Real fab junction files work exactly the same way as long as their GDS unit field matches the coordinate values inside them.

Common pitfall: if you author a junction GDS in a tool that writes coordinates > in nm but sets unit=1e-6 (µm), the cell will import at 1000× the intended size. > Always verify with gds.debug_summarize_gds_library() that junction cell bounding > boxes match expected physical dimensions after export.

[48]:
gds.lib
[48]:
<gdstk.Library at 0x3178f2150>
[49]:
gds.options.path_filename = "../resources/Fake_Junctions.GDS"
gds.import_junction_gds_file(gds.lib)  # load into pour library
[49]:
True

This function will read the file again, and print out the cells in it. It will also render the cells as an SVG inline if run in a Jupyter notebook.

[50]:
gds.show_imported_junction_gds()  # Shows gds.imported_junction_gds
imported_lib name: LIB
imported_lib unit: 1e-06
imported_lib precision: 1e-09
../../_images/tut_3-Renderers_3.2-Export-your-design-to-GDS_80_1.svg

=== my_other_junction ===
bbox: ((-15.0, -1.5), (15.0, 1.5))
paths: 0
polygons: 1
  - poly(n=4)
refs: 0
props: 0
=== end ===

../../_images/tut_3-Renderers_3.2-Export-your-design-to-GDS_80_3.svg

=== FakeJunction_02 ===
bbox: ((-15.0, -1.5), (15.0, 1.5))
paths: 0
polygons: 6
  - poly(n=4)
  - poly(n=4)
  - poly(n=4)
  - poly(n=4)
  - poly(n=4)
  - poly(n=4)
refs: 0
props: 0
=== end ===

../../_images/tut_3-Renderers_3.2-Export-your-design-to-GDS_80_5.svg

=== FakeJunction_01 ===
bbox: ((-15.0, -1.5030000000000001), (15.0, 1.5))
paths: 0
polygons: 9
  - poly(n=4)
  - poly(n=4)
  - poly(n=4)
  - poly(n=4)
  - poly(n=4)
  - poly(n=4)
  - poly(n=4)
  - poly(n=4)
  - poly(n=4)
refs: 0
props: 0
=== end ===

[51]:
# Disable cheesing output for this export.
# view_in_file controls whether cheese/no-cheese geometry appears in the GDS file.
# {1: False} = layer 1 present but suppress cheese holes from the output.
# Using Dict(main={}) triggers a warning — always specify layers explicitly.
gds.options.cheese.view_in_file = Dict(main={1: False})
gds.options.no_cheese.view_in_file = Dict(main={1: False})
[52]:
design.renderers.gds.export_to_gds("awesome_design.gds")
11:32PM 17s INFO [import_junction_gds_file]: Rescaling imported junction library from unit=1e-06 to unit=0.001 (scale factor=0.001).
11:32PM 17s WARNING [_check_either_cheese]: layer=14 is not in chip=main either in no_cheese_view_in_file or cheese_view_in_file from self.options.
[52]:
1
[53]:
# Load and inspect the exported GDS.
# Re-run the export cell above first if awesome_design.gds looks like an older design.
import gdstk

lib = gdstk.read_gds("awesome_design.gds")

# show=True renders the design as an embedded SVG in this cell output
# (the image saves with the notebook — no external file needed).
# scale: increase for larger chips or more detail (default 100)
# width: display width in pixels (default 800)
gds.debug_summarize_gds_library(lib, show=True, scale=100, width=900)
LayerDTypeDescriptionPolygonsPaths
10metal (boolean result)1 poly0 paths
110component polygon input32 poly0 paths
111CPW FlexPath trace16 poly0 paths
140metal (boolean result)64 poly0 paths
530metal (boolean result)13 poly0 paths
540metal (boolean result)3 poly0 paths

=== GDS LIBRARY SUMMARY ===
name:      library
unit:      0.001
precision: 1e-09
cells:     12

CELLS:
  - TOP                            geom=True bbox=((-4.5, -3.0), (4.5, 3.0))
  - TOP_main                       geom=True bbox=((-4.5, -3.0), (4.5, 3.0))
  - TOP_main_1                     geom=True bbox=((-4.5, -3.0), (4.5, 3.0))
  - ground_main_1                  geom=True bbox=((-4.5, -3.0), (4.5, 3.0))
  - TOP_main_14                    geom=True bbox=((-2.125, -0.569103), (2.125, 0.555476))
  - my_other_junction              geom=True bbox=((-0.015, -0.0015), (0.015, 0.0015))
  - FakeJunction_02                geom=True bbox=((-0.015, -0.0015), (0.015, 0.0015))
  - FakeJunction_01                geom=True bbox=((-0.015, -0.001503), (0.015, 0.0015))
  - pads_FakeJunction_02_QComponent_is_1_name_is_rect_jj geom=True bbox=((-0.04, -0.01), (0.04, 0.01))
  - pads_FakeJunction_01_QComponent_is_2_name_is_rect_jj geom=True bbox=((-0.04, -0.010001999999999999), (0.04, 0.009999))
  - pads_FakeJunction_01_QComponent_is_3_name_is_rect_jj geom=True bbox=((-0.04, -0.010001999999999999), (0.04, 0.009999))
  - pads_my_other_junction_QComponent_is_4_name_is_rect_jj geom=True bbox=((-0.04, -0.01), (0.04, 0.01))

LAYER / DATATYPE USAGE:
  layer  dtype   polys   paths
  -----  -----   -----   -----
      1      0       1       0
      1     10      32       0
      1     11      16       0
     14      0      64       0
     53      0      13       0
     54      0       3       0

=== END SUMMARY ===

[54]:
%matplotlib inline
[55]:
import matplotlib.pyplot as plt

# Q1 (orientation=0°)  → junction slot is vertical
# Q2 (orientation=90°) → junction slot is horizontal
# Both use FakeJunction_01 placed in the pocket centre.
fig, axes = plt.subplots(1, 2, figsize=(13, 6))
fig.suptitle("FakeJunction_01 — vertical vs horizontal placement", fontsize=13)

gds.plot_gds_zoom(
    lib,
    center_mm=(2.55, 0.0),
    span_mm=0.05,
    title="Q1 at (2.55, 0) mm — orientation 0° → vertical junction",
    ax=axes[0],
)
gds.plot_gds_zoom(
    lib,
    center_mm=(0.0, -0.9),
    span_mm=0.05,
    title="Q2 at (0, −0.9) mm — orientation 90° → horizontal junction",
    ax=axes[1],
)
plt.tight_layout()
# plt.close(fig)
# display(fig)
../../_images/tut_3-Renderers_3.2-Export-your-design-to-GDS_85_0.png

Status (v0.6.x+): The gdspy → gdstk migration is complete and validated. > Junction unit rescaling is applied automatically on import — junction cells from > any-unit GDS files (µm, nm, etc.) are scaled to the correct physical size in the > exported design. If you see junction geometry that looks too large or too small, > check that your junction GDS file’s unit field matches its internal coordinates > (see the note above), then re-export.

Appendix - GDS Cheesing: What It Is and How the Renderer Implements It

what “cheesing” means in mask design

image-2.png

Cheesing is the practice of punching a regular pattern of small holes into large metal areas (typically the ground plane). the holes make the metal look like swiss cheese, hence the name.

cheesing reduces fabrication issues such as:

  • film stress and cracking,

  • charging during lithography,

  • non-uniform etching,

  • rule violations caused by large continuous metal regions.

small holes mitigate these problems while leaving microwave behavior essentially unchanged.

the two components: cheese and no_cheese

cheesing relies on two option blocks in the renderer:

  1. ``cheese``: defines where holes should be created.

  2. ``no_cheese``: defines regions where holes are not allowed.

both have their own datatypes, sizes, spacing, and visibility settings:

gds.options.cheese
gds.options.no_cheese

how the renderer decides whether to cheese a layer

cheesing is activated only if the layer is marked in:

gds.options.cheese.view_in_file[chip][layer]
gds.options.no_cheese.view_in_file[chip][layer]

if neither entry exists for a given layer, cheesing is skipped.

step 1: determine ground-plane regions

before cheesing starts, the renderer constructs the metal ground plane geometry for each chip and layer:

  • shapes with subtract=True cut holes in the ground plane,

  • shapes with subtract=False sit on top of it.

this yields a unified understanding of the metal layout for the layer.

step 2: generate the “no-cheese” halo

this step computes regions where holes must not be placed.

it works by:

  • collecting all shapes that cut into the ground plane,

  • converting polylines into buffered polygons when needed,

  • unifying them into a single shape,

  • expanding them outward using no_cheese.buffer to create a halo,

  • clipping this halo to the chip boundary.

the resulting multipolygon forms a keep-out mask for cheesing.

step 3: tile the chip with holes

if cheesing is enabled, the renderer lays out a hole grid across the chip:

  • grid spacing is defined by delta_x and delta_y,

  • hole size comes from cheese_0_x, cheese_0_y or cheese_1_radius,

  • hole shape is rectangular or circular based on shape,

  • holes near the chip edges (edge_nocheese) are removed,

  • holes that fall inside “no-cheese” regions are removed.

the resulting holes are added to gds on the appropriate datatype.

step 4: integrate holes into the final mask

cheese holes are merged differently depending on mask type:

  • positive mask: ground plane minus holes,

  • negative mask: inverse metal + hole logic.

this is done at the cell level so each chip_layer cell gets the correct geometry.

how to disable cheesing entirely

no renderer code needs modification. simply clear the visibility settings:

gds.options.cheese.view_in_file = Dict(main={})
gds.options.no_cheese.view_in_file = Dict(main={})

with no layers listed, cheesing logic never runs.

summary

the renderer’s cheesing pipeline is:

  1. compute the ground plane,

  2. compute no-cheese keep-out zones,

  3. generate holes and filter them,

  4. merge into positive or negative mask.

cheesing improves manufacturability while keeping electrical performance intact, and can be turned on/off per chip and per layer via simple configuration options.

image.png
[ ]:



For more information, review the Introduction to Quantum Computing and Quantum Hardware lectures below

  • Superconducting Qubits I: Quantizing a Harmonic Oscillator, Josephson Junctions Part 1
Lecture Video Lecture Notes Lab
  • Superconducting Qubits I: Quantizing a Harmonic Oscillator, Josephson Junctions Part 2
Lecture Video Lecture Notes Lab
  • Superconducting Qubits I: Quantizing a Harmonic Oscillator, Josephson Junctions Part 3
Lecture Video Lecture Notes Lab
  • Superconducting Qubits II: Circuit Quantum Electrodynamics, Readout and Calibration Methods Part 1
Lecture Video Lecture Notes Lab
  • Superconducting Qubits II: Circuit Quantum Electrodynamics, Readout and Calibration Methods Part 2
Lecture Video Lecture Notes Lab
  • Superconducting Qubits II: Circuit Quantum Electrodynamics, Readout and Calibration Methods Part 3
Lecture Video Lecture Notes Lab