Import PDK#

To import a PDK in gdsfactory you need 2 things:

  • GDS file with all the cells that you want to import in the PDK (or separate GDS files, one per cell)

  • Klayout layer properties files, to define the Layers that you can use when creating new custom Components.

GDS#

GDS files are great for describing geometry thanks to the concept of References, where you store any geometry only once in memory.

For storing device metadata (settings, port locations, port widths, port angles …) there is no clear standard.

gdsfactory stores the that metadata in YAML files, and also has functions to add pins

  • Component.write_gds() saves GDS

  • Component.write_gds_metadata() save GDS + YAML metadata

[1]:
import gdsfactory as gf

c = gf.components.mzi()
c
2022-06-28 17:02:15.774 | INFO     | gdsfactory.config:<module>:52 - Load '/home/runner/work/gdsfactory/gdsfactory/gdsfactory' 5.11.4
../_images/notebooks_09_pdk_import_1_1.png
[1]:
mzi: uid 2, ports ['o1', 'o2'], aliases [], 0 polygons, 20 references

You can write GDS files only

[2]:
gdspath = c.write_gds("extra/mzi.gds")
2022-06-28 17:02:17.074 | INFO     | gdsfactory.component:write_gds:1062 - Write GDS to 'extra/mzi.gds'

Or GDS with YAML metadata information (ports, settings, cells …)

[3]:
gdspath = c.write_gds_with_metadata("extra/mzi.gds")
2022-06-28 17:02:17.083 | INFO     | gdsfactory.component:write_gds:1062 - Write GDS to 'extra/mzi.gds'
2022-06-28 17:02:17.152 | INFO     | gdsfactory.component:write_gds_with_metadata:1070 - Write YAML metadata to 'extra/mzi.yml'

This created a mzi.yml file that contains: - ports - cells (flat list of cells) - info (function name, module, changed settings, full settings, default settings)

[4]:
c.metadata.keys()
[4]:
dict_keys(['name', 'module', 'function_name', 'info', 'info_version', 'full', 'changed', 'default', 'child'])

import_gds#

You can read GDS files into gdsfactory thanks to the import_gds function

import_gds with YAML metadata#

import_gds reads the same GDS file from disk without losing any information

[5]:
gf.clear_cache()

c = gf.import_gds(gdspath)
c
2022-06-28 17:02:17.176 | INFO     | gdsfactory.read.import_gds:import_gds:139 - Read YAML metadata from extra/mzi.yml
../_images/notebooks_09_pdk_import_9_1.png
[5]:
mzi: uid 20, ports ['o1', 'o2'], aliases [], 0 polygons, 20 references
[6]:
import gdsfactory as gf

c2 = gf.import_gds(gdspath, name="mzi_sample")
c2
2022-06-28 17:02:17.394 | INFO     | gdsfactory.read.import_gds:import_gds:139 - Read YAML metadata from extra/mzi.yml
../_images/notebooks_09_pdk_import_10_1.png
[6]:
mzi: uid 30, ports ['o1', 'o2'], aliases [], 0 polygons, 20 references
[7]:
c2.name
[7]:
'mzi'
[8]:
c3 = gf.routing.add_fiber_single(c2)
c3
../_images/notebooks_09_pdk_import_12_0.png
[8]:
mzi_move_7c3d6401_add_f_fa0c5d57: uid 43, ports ['vertical_te_00', 'vertical_te_1', 'loopback1', 'loopback2'], aliases [], 0 polygons, 8 references
[9]:
gdspath = c3.write_gds_with_metadata("extra/pdk.gds")
2022-06-28 17:02:17.865 | INFO     | gdsfactory.component:write_gds:1062 - Write GDS to 'extra/pdk.gds'
2022-06-28 17:02:18.017 | INFO     | gdsfactory.component:write_gds_with_metadata:1070 - Write YAML metadata to 'extra/pdk.yml'
[10]:
gf.mask.write_labels(gdspath, layer_label=gf.LAYER.LABEL)
2022-06-28 17:02:18.099 | INFO     | gdsfactory.mask.write_labels:write_labels:84 - Wrote 4 labels to CSV /home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/pdk.csv
[10]:
PosixPath('extra/pdk.csv')

import_gds and add ports from pins#

Sometimes the GDS does not have YAML metadata, therefore you need to figure out the port locations, widths and orientations.

gdsfactory provides you with functions that will add ports to the component by looking for pins shapes on a specific layers (port_markers or pins)

There are different pin standards supported to automatically add ports to components:

  • PINs towards the inside of the port (port at the outer part of the PIN)

  • PINs with half of the pin inside and half outside (port at the center of the PIN)

  • PIN with only labels (no shapes). You have to manually specify the width of the port.

Lets add pins, save a GDS and then import it back.

[11]:
import gdsfactory as gf

c = gf.components.straight(
    decorator=gf.add_pins.add_pins
)  # add pins inside the component
c
../_images/notebooks_09_pdk_import_16_0.png
[11]:
straight_1514d1a3: uid 57, ports ['o1', 'o2'], aliases [], 6 polygons, 0 references
[12]:
gdspath = c.write_gds("extra/wg.gds")
2022-06-28 17:02:18.236 | INFO     | gdsfactory.component:write_gds:1062 - Write GDS to 'extra/wg.gds'
[13]:
gf.clear_cache()
c2 = gf.import_gds(gdspath)
c2
../_images/notebooks_09_pdk_import_18_0.png
[13]:
straight_1514d1a3: uid 59, ports [], aliases [], 6 polygons, 0 references
[14]:
c2.ports  # import_gds does not automatically add the pins
[14]:
{}
[15]:
c3 = gf.import_gds(gdspath, decorator=gf.add_ports.add_ports_from_markers_inside)
c3
../_images/notebooks_09_pdk_import_20_0.png
[15]:
straight_1514d1a3: uid 60, ports ['o1', 'o2', 'o3', 'o4'], aliases [], 6 polygons, 0 references
[16]:
c3.ports
[16]:
{'o1': Port (name o1, midpoint [-0.001  0.   ], width 0.5, orientation 180, layer PORT, port_type optical),
 'o2': Port (name o2, midpoint [0. 0.], width 0.5, orientation 180, layer PORT, port_type optical),
 'o3': Port (name o3, midpoint [10.001  0.   ], width 0.5, orientation 0, layer PORT, port_type optical),
 'o4': Port (name o4, midpoint [10.  0.], width 0.5, orientation 0, layer PORT, port_type optical)}

Import PDK#

Foundries provide PDKs in different formats and commercial tools.

The easiest way to import a PDK into gdsfactory is to

  1. have each GDS cell into a separate GDS file

  2. have one GDS file with all the cells inside

  3. Have a klayout layermap. Makes easier to create the layermap.

With that you can easily create the PDK as as python package.

Thanks to having a gdsfactory PDK as a python package you can

  • version control your PDK using GIT to keep track of changes and work on a team

    • write tests of your pdk components to avoid unwanted changes from one component to another.

    • ensure you maintain the quality of the PDK with continuous integration checks

    • pin the version of gdsfactory, so new updates of gdsfactory won’t affect your code

  • name your PDK version using semantic versioning. For example patches increase the last number (0.0.1 -> 0.0.2)

  • install your PDK easily pip install pdk_fab_a and easily interface with other tools

To create a Python package you can start from a customizable template (thanks to cookiecutter)

I usually create a python package by running this 2 commands inside a terminal

pip install cookiecutter
cookiecutter https://github.com/joamatab/cookiecutter-pypackage-minimal

It will ask you some questions to fill in the template (name of the package being the most important)

Then you can add the information about the GDS files and the Layers inside that package

[17]:
import gdsfactory as gf
from gdsfactory.layers import lyp_to_dataclass
from gdsfactory.config import PATH
[18]:
print(lyp_to_dataclass(PATH.klayout_lyp))

from pydantic import BaseModel
from gdsfactory.types import Layer


class LayerMap(BaseModel):
    1_12_1: Layer = (1, 12)
    31_0_1: Layer = (31, 0)
    99_0_1: Layer = (99, 0)
    CAPACITOR: Layer = (42, 0)
    DEEPETCH: Layer = (3, 6)
    DEEPTRENCH: Layer = (4, 0)
    DICING: Layer = (65, 0)
    DRC_EXCLUDE: Layer = (67, 0)
    DRC_MARKER: Layer = (205, 0)
    DevRec: Layer = (68, 0)
    ERROR_MARKER: Layer = (207, 0)
    Errors: Layer = (69, 0)
    FLOORPLAN: Layer = (64, 0)
    FbrTgt: Layer = (81, 0)
    GE: Layer = (5, 0)
    GENPP: Layer = (26, 0)
    GEPPP: Layer = (29, 0)
    LABEL: Layer = (201, 0)
    LABEL_INSTANCES: Layer = (206, 0)
    LABEL_SETTINGS: Layer = (202, 0)
    Lumerical: Layer = (733, 0)
    M1: Layer = (41, 0)
    M1TILES: Layer = (191, 0)
    M2: Layer = (45, 0)
    M3: Layer = (49, 0)
    METALOPEN: Layer = (46, 0)
    MH: Layer = (47, 0)
    N: Layer = (20, 0)
    NOTILE_M1: Layer = (71, 0)
    NOTILE_M2: Layer = (72, 0)
    NOTILE_M3: Layer = (73, 0)
    NP: Layer = (22, 0)
    NPP: Layer = (24, 0)
    OXIDE_ETCH: Layer = (6, 0)
    P: Layer = (21, 0)
    PDPP: Layer = (27, 0)
    PORT1: Layer = (101, 0)
    PORT2: Layer = (102, 0)
    PORT3: Layer = (103, 0)
    PORT4: Layer = (104, 0)
    PP: Layer = (23, 0)
    PPP: Layer = (25, 0)
    PinRec: Layer = (1, 10)
    PinRecM: Layer = (1, 11)
    SHALLOWETCH: Layer = (2, 6)
    SILICIDE: Layer = (39, 0)
    SIM_REGION: Layer = (100, 0)
    SITILES: Layer = (190, 0)
    SLAB150: Layer = (2, 0)
    SLAB150CLAD: Layer = (2, 9)
    SLAB90: Layer = (3, 0)
    SLAB90CLAD: Layer = (3, 1)
    SOURCE: Layer = (110, 0)
    TE: Layer = (203, 0)
    TM: Layer = (204, 0)
    Text: Layer = (66, 0)
    VIA1: Layer = (44, 0)
    VIA2: Layer = (43, 0)
    VIAC: Layer = (40, 0)
    WGCLAD: Layer = (111, 0)
    WGN: Layer = (34, 0)
    WGNCLAD: Layer = (36, 0)
    Waveguide: Layer = (1, 0)
    XS_BOX: Layer = (300, 0)
    XS_GE: Layer = (315, 0)
    XS_M1: Layer = (304, 0)
    XS_M2: Layer = (399, 0)
    XS_MH: Layer = (306, 0)
    XS_N: Layer = (320, 0)
    XS_NPP: Layer = (321, 0)
    XS_OVERLAY: Layer = (311, 0)
    XS_OXIDE_M1: Layer = (305, 0)
    XS_OXIDE_M2: Layer = (307, 0)
    XS_OXIDE_M3: Layer = (311, 0)
    XS_OXIDE_MH: Layer = (317, 0)
    XS_OXIDE_ML: Layer = (309, 0)
    XS_OX_SI: Layer = (302, 0)
    XS_P: Layer = (330, 0)
    XS_PDPP: Layer = (327, 0)
    XS_PPP: Layer = (331, 0)
    XS_SI: Layer = (301, 0)
    XS_SIN: Layer = (319, 0)
    XS_SIN2: Layer = (305, 0)
    XS_SI_SLAB: Layer = (313, 0)
    XS_VIA1: Layer = (308, 0)
    XS_VIA2: Layer = (310, 0)
    XS_VIAC: Layer = (303, 0)

    class Config:
        frozen = True
        extra = "forbid"


LAYER = LayerMap()

[19]:
# lets create a sample PDK (for demo purposes only) using GDSfactory
# if the PDK is in a commercial tool you can also do this. Make sure you save a single pdk.gds
sample_pdk_cells = gf.grid(
    [
        gf.components.straight,
        gf.components.bend_euler,
        gf.components.grating_coupler_elliptical,
    ]
)
sample_pdk_cells.write_gds("extra/pdk.gds")
sample_pdk_cells
2022-06-28 17:02:18.662 | INFO     | gdsfactory.component:write_gds:1062 - Write GDS to 'extra/pdk.gds'
../_images/notebooks_09_pdk_import_25_1.png
[19]:
grid_d9610e5f: uid 62, ports [], aliases [(0, 0), (1, 0), (2, 0)], 0 polygons, 3 references
[20]:
sample_pdk_cells.get_dependencies()
[20]:
{bend_euler: uid 66, ports ['o1', 'o2'], aliases [], 4 polygons, 0 references,
 grating_coupler_elliptical: uid 68, ports ['vertical_te', 'o1'], aliases [], 33 polygons, 1 references,
 straight: uid 64, ports ['o1', 'o2'], aliases [], 4 polygons, 0 references}
[21]:
# we write the sample PDK into a single GDS file
gf.clear_cache()
gf.write_cells.write_cells(gdspath="extra/pdk.gds", dirpath="extra/gds")
  0%|          | 0/1 [00:00<?, ?it/s]2022-06-28 17:02:18.831 | INFO     | gdsfactory.read.import_gds:import_gds:139 - Read YAML metadata from extra/pdk.yml
2022-06-28 17:02:19.027 | INFO     | gdsfactory.write_cells:write_cells:73 - Write 'grid_d9610e5f' to extra/gds/grid_d9610e5f.gds
2022-06-28 17:02:19.032 | INFO     | gdsfactory.component:write_gds:1062 - Write GDS to 'extra/gds/grid_d9610e5f.gds'
100%|██████████| 1/1 [00:00<00:00,  4.80it/s]
[22]:
# Lets generate the script that we need to have to each GDS cell into gdsfactory

import gdsfactory as gf

print(gf.write_cells.get_import_gds_script("extra/gds"))

from pathlib import PosixPath
from functools import partial
import gdsfactory as gf

add_ports_optical = gf.partial(gf.add_ports.add_ports_from_markers_inside, pin_layer=(1, 0), port_layer=(2, 0))
add_ports_electrical = gf.partial(gf.add_ports.add_ports_from_markers_inside, pin_layer=(41, 0), port_layer=(1, 0))
add_ports = gf.compose(add_ports_optical, add_ports_electrical)


gdsdir = PosixPath('/home/runner/work/gdsfactory/gdsfactory/docs/notebooks/extra/gds')

import_gds = partial(gf.import_gds, gdsdir=gdsdir, decorator=add_ports)

grid_d9610e5f = partial(import_gds, 'grid_d9610e5f.gds')