PDK#

gdsfactory includes a generic PDK, that you can use as an inspiration to create your own pdks.

The PDK allows you to register: - cell functions that return Components from a ComponentSpec (string, Component, ComponentFactory or dict). Also known as PCells (parametric cells). - cross_section functions that return CrossSection from a CrossSection Spec (string, CrossSection, CrossSectionFactory or dict). - layers that return a GDS Layer from a string, an int or a Tuple[int, int].

You can only have one active PDK at a time. Thanks to PDK you can access components, cross_sections or layers using a string.

Depending on the active pdk:

  • get_layer returns a Layer from the registered layers.

  • get_component returns a Component from the registered cells or containers.

  • get_cross_section returns a CrossSection from the registered cross_sections.

layers#

GDS layers are a tuple of two integer number gdslayer/gdspurpose

You can define all the layers from your PDK:

  1. From a Klayout lyp (layer properties file).

  2. From scratch, adding all your layers into a class.

Lets generate the layers definition code from a klayout lyp file.

[1]:
import gdsfactory as gf
from gdsfactory.layers import lyp_to_dataclass
from gdsfactory.config import PATH

print(lyp_to_dataclass(PATH.klayout_lyp))
2022-06-28 17:02:07.970 | INFO     | gdsfactory.config:<module>:52 - Load '/home/runner/work/gdsfactory/gdsfactory/gdsfactory' 5.11.4

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()

[2]:
from pydantic import BaseModel
from gdsfactory.types import Layer


class LayerMap(BaseModel):
    WG: Layer = (1, 0)
    DEVREC: Layer = (68, 0)
    PORT: Layer = (1, 10)
    PORTE: Layer = (1, 11)
    LABEL: Layer = (201, 0)
    LABEL_INSTANCES: Layer = (206, 0)
    LABEL_SETTINGS: Layer = (202, 0)
    LUMERICAL: Layer = (733, 0)
    M1: Layer = (41, 0)
    M2: Layer = (45, 0)
    M3: Layer = (49, 0)
    N: Layer = (20, 0)
    NP: Layer = (22, 0)
    NPP: Layer = (24, 0)
    OXIDE_ETCH: Layer = (6, 0)
    P: Layer = (21, 0)
    PDPP: Layer = (27, 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)
    TEXT: Layer = (66, 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)

    class Config:
        frozen = True
        extra = "forbid"


LAYER = LayerMap()

There are some default layers in some generic components and cross_sections, that it may be convenient adding.

Layer

Purpose

DEVREC

device recognition layer. For connectivity checks.

PORT

optical port pins. For connectivity checks.

PORTE

electrical port pins. For connectivity checks.

SHOW_PORTS

add port pin markers when Component.show(show_ports=True)

LABEL

for adding labels to grating couplers for automatic testing.

LABEL_INSTANCE

for adding instance labels on gf.read.from_yaml

TE

for TE polarization fiber marker.

TM

for TM polarization fiber marker.

class LayersConvenient(BaseModel):
    DEVREC: Layer = (68, 0)
    PORT: Layer = (1, 10)  # PinRec optical
    PORTE: Layer = (1, 11)  # PinRec electrical
    SHOW_PORTS: Layer = (1, 12)

    LABEL: Layer = (201, 0)
    LABEL_INSTANCE: Layer = (66, 0)
    TE: Layer = (203, 0)
    TM: Layer = (204, 0)

cross_sections#

You can create a CrossSection from scratch or you can customize the cross_section functions in gf.cross_section

[3]:
import gdsfactory as gf

strip2 = gf.partial(gf.cross_section.strip, layer=(2, 0))
[4]:
c = gf.components.straight(cross_section=strip2)
c
../_images/notebooks_08_pdk_7_0.png
[4]:
straight_bf73fda8: uid 0, ports ['o1', 'o2'], aliases [], 4 polygons, 0 references
[5]:
import gdsfactory as gf

pin = gf.partial(
    gf.cross_section.strip,
    sections=(
        gf.tech.Section(width=2, layer=gf.LAYER.N, offset=+1),
        gf.tech.Section(width=2, layer=gf.LAYER.P, offset=-1),
    ),
)
c = gf.components.straight(cross_section=pin)
c
../_images/notebooks_08_pdk_8_0.png
[5]:
straight_0cfc69af: uid 2, ports ['o1', 'o2'], aliases [], 6 polygons, 0 references
[6]:
strip_wide = gf.partial(gf.cross_section.strip, width=3)
[7]:
strip = gf.partial(
    gf.cross_section.strip, auto_widen=True
)  # auto_widen tapers to wider waveguides for lower loss in long straight sections.
[8]:
cross_sections = dict(strip_wide=strip_wide, pin=pin, strip=strip)

cells#

Cells are functions that return Components. They are parametrized and accept also cells as parameters, so you can build many levels of complexity. Cells are also known as PCells or parametric cells.

You can customize the function default arguments easily thanks to functools.partial Lets customize the default arguments of a library of cells.

For example, you can make some wide MMIs for a particular technology. Lets say the best MMI width you found it to be 9um.

[9]:
import gdsfactory as gf

mmi1x2 = gf.partial(gf.components.mmi1x2, width_mmi=9)
mmi2x2 = gf.partial(gf.components.mmi2x2, width_mmi=9)

cells = dict(mmi1x2=mmi1x2, mmi2x2=mmi2x2)

LayerSpec, ComponentSpec, CrossSectionSpec#

When you register Layers, ComponentFactories (cells) and CrossSectionFactories (cross_sections), you can access them by a string.

LayerSpec#

You can access layers from the active Pdk using the layer name or a tuple/list of two numbers.

[10]:
pdk1 = gf.Pdk(
    name="fab1",
    layers=LAYER.dict(),
    cross_sections=cross_sections,
    cells=cells,
    base_pdk=gf.pdk.GENERIC,
    sparameters_path=gf.config.sparameters_path,
    layer_colors=gf.layers.LAYER_COLORS,
)
pdk1.activate()
[11]:
pdk1.get_layer("WG")
[11]:
(1, 0)
[12]:
pdk1.get_layer([1, 0])
[12]:
[1, 0]

CrossSectionSpec#

You can access cross_sections from the pdk from the cross_section name, or using a dict to customize the CrossSection

[13]:
pdk1.get_cross_section("pin")
[13]:
CrossSection(layer='WG', width=0.5, offset=0.0, radius=10.0, width_wide=None, auto_widen=False, auto_widen_minimum_length=200.0, taper_length=10.0, bbox_layers=[], bbox_offsets=[], cladding_layers=['DEVREC'], cladding_offsets=(0.0,), sections=[Section(width=2.0, offset=1.0, layer=(20, 0), port_names=(None, None), port_types=('optical', 'optical'), name=None, hidden=False), Section(width=2.0, offset=-1.0, layer=(21, 0), port_names=(None, None), port_types=('optical', 'optical'), name=None, hidden=False)], port_names=('o1', 'o2'), port_types=('optical', 'optical'), min_length=0.01, start_straight_length=0.01, end_straight_length=0.01, snap_to_grid=None, decorator=None, add_pins=functools.partial(<function add_pins_siepic at 0x7fcaa5f8ea60>, pin_length=0.002), add_bbox=<function add_bbox_siepic at 0x7fcaa5f8eaf0>, info={}, name=None)
[14]:
cross_section_spec_string = "pin"
gf.components.straight(cross_section=cross_section_spec_string)
../_images/notebooks_08_pdk_20_0.png
[14]:
straight_cross_sectionpin: uid 4, ports ['o1', 'o2'], aliases [], 6 polygons, 0 references
[15]:
cross_section_spec_dict = dict(cross_section="pin", settings=dict(width=2))
print(pdk1.get_cross_section(cross_section_spec_dict))
wg_pin = gf.components.straight(cross_section=cross_section_spec_dict)
wg_pin
layer='WG' width=2.0 offset=0.0 radius=10.0 width_wide=None auto_widen=False auto_widen_minimum_length=200.0 taper_length=10.0 bbox_layers=[] bbox_offsets=[] cladding_layers=['DEVREC'] cladding_offsets=(0.0,) sections=[Section(width=2.0, offset=1.0, layer=(20, 0), port_names=(None, None), port_types=('optical', 'optical'), name=None, hidden=False), Section(width=2.0, offset=-1.0, layer=(21, 0), port_names=(None, None), port_types=('optical', 'optical'), name=None, hidden=False)] port_names=('o1', 'o2') port_types=('optical', 'optical') min_length=0.01 start_straight_length=0.01 end_straight_length=0.01 snap_to_grid=None decorator=None add_pins=functools.partial(<function add_pins_siepic at 0x7fcaa5f8ea60>, pin_length=0.002) add_bbox=<function add_bbox_siepic at 0x7fcaa5f8eaf0> info={} name=None
../_images/notebooks_08_pdk_21_1.png
[15]:
straight_361ab848: uid 5, ports ['o1', 'o2'], aliases [], 6 polygons, 0 references

ComponentSpec#

You can get Component from the pdk using the cell name (string) or a dict.

[16]:
pdk1.get_component("mmi1x2")
../_images/notebooks_08_pdk_23_0.png
[16]:
mmi1x2_width_mmi9: uid 7, ports ['o1', 'o2', 'o3'], aliases [], 19 polygons, 0 references
[17]:
pdk1.get_component(dict(component="mmi1x2", settings=dict(length_mmi=10)))
../_images/notebooks_08_pdk_24_0.png
[17]:
mmi1x2_length_mmi10_width_mmi9: uid 11, ports ['o1', 'o2', 'o3'], aliases [], 19 polygons, 0 references

Now you can define PDKs for different Fabs

FabA#

FabA only has one waveguide layer available that is defined in GDS layer (30, 0)

The waveguide traces are 2um wide.

[18]:
import gdsfactory as gf
from gdsfactory.types import Layer, LayerColors, LayerColor, LayerStack, LayerLevel
from pydantic import BaseModel

nm = 1e-3


class LayerMap(BaseModel):
    WG: Layer = (34, 0)
    SLAB150: Layer = (2, 0)
    DEVREC: Layer = (68, 0)
    PORT: Layer = (1, 10)
    PORTE: Layer = (1, 11)
    TE: Layer = (203, 0)
    TM: Layer = (204, 0)
    TEXT: Layer = (66, 0)


LAYER = LayerMap()

layer_colors = dict(
    WG=LayerColor(gds_layer=34, gds_datatype=0, color="gold"),
    SLAB150=LayerColor(gds_layer=2, gds_datatype=0, color="red"),
    TE=LayerColor(gds_layer=203, gds_datatype=0, color="green"),
)
LAYER_COLORS = LayerColors(layers=layer_colors)


def get_layer_stack_faba(
    thickness_wg: float = 500 * nm, thickness_slab: float = 150 * nm
) -> LayerStack:
    """Returns fabA LayerStack"""
    ## TODO: Translate xml in lumerical process file
    return LayerStack(
        layers=dict(
            strip=LayerLevel(
                layer=LAYER.WG,
                thickness=thickness_wg,
                zmin=0.0,
                material="si",
            ),
            strip2=LayerLevel(
                layer=LAYER.SLAB150,
                thickness=thickness_slab,
                zmin=0.0,
                material="si",
            ),
        )
    )


LAYER_STACK = get_layer_stack_faba()

WIDTH = 2

# Specify a cross_section to use
strip = gf.partial(gf.cross_section.cross_section, width=WIDTH, layer=LAYER.WG)

mmi1x2 = gf.partial(
    gf.components.mmi1x2,
    width=WIDTH,
    width_taper=WIDTH,
    width_mmi=3 * WIDTH,
    cross_section=strip,
)

generic_pdk = gf.pdk.GENERIC

fab_a = gf.Pdk(
    name="Fab_A",
    cells=dict(mmi1x2=mmi1x2),
    cross_sections=dict(strip=strip),
    layers=LAYER.dict(),
    base_pdk=generic_pdk,
    sparameters_path=gf.config.sparameters_path,
    layer_colors=LAYER_COLORS,
    layer_stack=LAYER_STACK,
)
fab_a.activate()

gc = gf.partial(
    gf.components.grating_coupler_elliptical_te, layer=LAYER.WG, cross_section=strip
)

c = gf.components.mzi()
c_gc = gf.routing.add_fiber_array(component=c, grating_coupler=gc, with_loopback=False)
c_gc
../_images/notebooks_08_pdk_26_0.png
[18]:
mzi_add_fiber_array_be7d7aa9: uid 36, ports ['vertical_te_00', 'vertical_te_01'], aliases [], 0 polygons, 9 references
[19]:
c = c_gc.to_3d()
c.show(show_ports=True)
[19]: