Generic PDK#

GDSFactory includes a generic Process Design Kit PDK, which is a library of components associated to a generic foundry process gdsfactory.generic_tech. See components available in the generic component library that you can customize or adapt to create your own.

The generic process including layer numbers is based on the book “Silicon Photonics Design: From Devices to Systems Lukas Chrostowski, Michael Hochberg”. You can learn more about process design kits (PDKs) in this tutorial

LayerMap#

A layer map maps layer names to an integer numbers pair (GDSlayer, GDSpurpose)

Each foundry uses different GDS layer numbers for different process steps.

GDS (layer, purpose)

layer_name

Description

1 , 0

WG

220 nm Silicon core

2 , 0

SLAB150

150nm Silicon slab (70nm shallow Etch for grating couplers)

3 , 0

SLAB90

90nm Silicon slab (for modulators)

4, 0

DEEPTRENCH

Deep trench

47, 0

MH

heater

41, 0

M1

metal 1

45, 0

M2

metal 2

40, 0

VIAC

VIAC to contact Ge, NPP or PPP

44, 0

VIA1

VIA1

46, 0

PADOPEN

Bond pad opening

51, 0

UNDERCUT

Undercut

66, 0

TEXT

Text markup

64, 0

FLOORPLAN

Mask floorplan

Hide code cell source

from IPython.display import Code

import gdsfactory as gf
from gdsfactory.config import PATH
from gdsfactory.generic_tech import LAYER_STACK, get_generic_pdk
from gdsfactory.generic_tech.get_klayout_pyxs import get_klayout_pyxs
from gdsfactory.technology import LayerLevel, LayerMap, LayerStack, LayerViews
from gdsfactory.typings import Layer
class LAYER(LayerMap):
    """Generic layermap based on book.

    Lukas Chrostowski, Michael Hochberg, "Silicon Photonics Design",
    Cambridge University Press 2015, page 353
    You will need to create a new LayerMap with your specific foundry layers.
    """

    WAFER: Layer = (999, 0)

    WG: Layer = (1, 0)
    WGCLAD: Layer = (111, 0)
    SLAB150: Layer = (2, 0)
    SLAB90: Layer = (3, 0)
    DEEPTRENCH: Layer = (4, 0)
    GE: Layer = (5, 0)
    UNDERCUT: Layer = (6, 0)
    WGN: Layer = (34, 0)
    WGN_CLAD: Layer = (36, 0)

    N: Layer = (20, 0)
    NP: Layer = (22, 0)
    NPP: Layer = (24, 0)
    P: Layer = (21, 0)
    PP: Layer = (23, 0)
    PPP: Layer = (25, 0)
    GEN: Layer = (26, 0)
    GEP: Layer = (27, 0)

    HEATER: Layer = (47, 0)
    M1: Layer = (41, 0)
    M2: Layer = (45, 0)
    M3: Layer = (49, 0)
    VIAC: Layer = (40, 0)
    VIA1: Layer = (44, 0)
    VIA2: Layer = (43, 0)
    PADOPEN: Layer = (46, 0)

    DICING: Layer = (100, 0)
    NO_TILE_SI: Layer = (71, 0)
    PADDING: Layer = (67, 0)
    DEVREC: Layer = (68, 0)
    FLOORPLAN: Layer = (64, 0)
    TEXT: Layer = (66, 0)
    PORT: Layer = (1, 10)
    PORTE: Layer = (1, 11)
    PORTH: Layer = (70, 0)
    SHOW_PORTS: Layer = (1, 12)
    LABEL_SETTINGS: Layer = (202, 0)
    DRC_MARKER: Layer = (205, 0)
    LABEL_INSTANCE: Layer = (206, 0)

    SOURCE: Layer = (110, 0)
    MONITOR: Layer = (101, 0)


LAYER.WG
<LAYER.WG: 1>
layer_wg = (1, 0)
print(layer_wg)
(1, 0)

Extract layers#

You can also extract layers using the extract function. This function returns a new flattened component that contains the extracted layers. A flat component does not have references, and all the polygons are absorbed into the top cell.

# A PDK is a collection of pre-defined components, layers, and design rules for a specific manufacturing process. 
# This code loads a generic, open-source PDK and sets it as the active one for the current gdsfactory session.
PDK = get_generic_pdk()
PDK.activate()

# This line retrieves the layer_views object from the PDK. 
# This object contains information on how each layer in the PDK should be displayed, including its color, transparency, and name.
LAYER_VIEWS = PDK.layer_views

# This is a helper function that creates a gdsfactory component specifically designed to visualize the entire layer set.
# It draws a series of labeled, colored boxes, with each box representing a different layer from the PDK.
c = LAYER_VIEWS.preview_layerset()
c.plot()
../_images/e14db16b687638ea1c3ed13c5bcebf87ab200553aa88684487d6687fafa4e961.png
extract = c.extract(layers=((41, 0), (40, 0)))
extract.plot()
../_images/61041af37eb67a9f968f318d582ba1de9605cd070a3e9b12907f9b60de6c1403.png

Remove layers#

You can remove layers using the remove_layers() function.

# .extract(...): This is a method that acts like a filter.
# It goes through the component c and pulls out only the shapes that are on the layers specified in the layers argument.
# layers=((41, 0), (40, 0)): This tuple specifies which layers to keep. In this case, it will extract all geometry from layer (41, 0) and layer (40, 0).
# The result is a brand new component, assigned to the extract variable, that contains a copy of only the desired shapes.
removed = extract.remove_layers(layers=((40, 0),))
removed.plot()
../_images/30acd57599e1325edd3e3764533bdb04f678bfc3e0b5279fe2774e51562e9ebe.png

Remap layers#

You can remap (change the polygons from one layer to another layer) using the remap_layer, which will return a new Component

c = gf.components.rectangle(layer=(2, 0))
c.plot()
../_images/5133dee807585b82976e36ee85a73cef9b842aa61268d9b383cfe2948474acc7.png
c = c.copy() # This line creates a duplicate of the original component c. This is good practice to ensure that the original component remains unchanged.

# The remap_layers method goes through the component and reassigns layers based on the dictionary provided.
# The dictionary {(2, 0): (34, 0)} defines the mapping rule: "find all shapes on layer (2, 0) and move them to layer (34, 0)."
# The result is a new component, assigned to the remap variable, where the layer change has been applied.
remap = c.remap_layers({(2, 0): (34, 0)})
remap.plot()
../_images/e268b2f4b75a490a1b29c45b0edaafc98bd87452145d981428358e9a2361a51f.png

LayerViews#

Klayout shows each GDS layer with a color, style and transparency.

You can define your layerViews in a klayout Layer Properties file layers.lyp or in YAML format.

We recommend using YAML and then generate the lyp in klayout, as YAML is easier to modify than XML.

Code(filename=PATH.klayout_yaml)
LayerViews:
  Waveguide:
    layer: [1, 0]
    hatch_pattern: dotted
    width: 1
    color: "#ff9d9d"
  WGCLAD:
    layer: [111, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    visible: false
    width: 1
    color: "silver"
  SLAB150:
    layer: [2, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    transparent: true
    width: 1
    color: "cyan"
  SHALLOW_ETCH:
    layer: [2, 6]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    color: "blue"
  SLAB90:
    layer: [3, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    transparent: true
    width: 1
    color: "#805000"
  DEEP_ETCH:
    layer: [3, 6]
    layer_in_name: true
    hatch_pattern: left-hatched
    color: "#cc0000"
  SLAB150CLAD:
    layer: [2, 9]
    layer_in_name: true
    frame_color: "#9999cc"
    fill_color: "#80a8ff"
    hatch_pattern: coarsely dotted
    visible: false
    width: 1
  SLAB90CLAD:
    layer: [3, 1]
    layer_in_name: true
    frame_color: "#9999cc"
    fill_color: "#80a8ff"
    hatch_pattern: hollow
    visible: false
    width: 1
  Doping:
    group_members:
      N:
        layer: [20, 0]
        layer_in_name: true
        hatch_pattern: lightly left-hatched
        width: 1
        color: "red"
      NP:
        layer: [22, 0]
        layer_in_name: true
        hatch_pattern: lightly left-hatched
        width: 1
        color: "red"
      NPP:
        layer: [24, 0]
        layer_in_name: true
        hatch_pattern: coarsely dotted
        width: 1
        color: "red"
      P_210:
        layer: [21, 0]
        hatch_pattern: lightly left-hatched
        transparent: true
        width: 1
        color: "blue"
      PP:
        layer: [23, 0]
        layer_in_name: true
        hatch_pattern: lightly left-hatched
        width: 1
        color: "blue"
      PPP:
        layer: [25, 0]
        layer_in_name: true
        hatch_pattern: strongly left-hatched dense
        width: 1
        color: "blue"
      PDPP:
        layer: [27, 0]
        layer_in_name: true
        hatch_pattern: lightly cross-hatched
        width: 1
        color: "#ccb27f"
      GENPP:
        layer: [26, 0]
        layer_in_name: true
        hatch_pattern: plus
        width: 1
        color: "#cc00cc"
      GEPPP:
        layer: [29, 0]
        layer_in_name: true
        hatch_pattern: plus
        width: 1
        color: "#cc00cc"
  WGN_Nitride:
    layer: [34, 0]
    layer_in_name: true
    hatch_pattern: left-hatched
    transparent: true
    width: 1
    color: "#ff8000"
  WGclad_material:
    layer: [36, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    visible: false
    width: 1
    color: "silver"
  GE:
    layer: [5, 0]
    layer_in_name: true
    hatch_pattern: dotted
    width: 1
    color: "magenta"
  SILICIDE:
    layer: [39, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    width: 1
    color: "#cc4c00"
  MH:
    layer: [47, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    transparent: true
    width: 1
    color: "#ff8000"
  M1:
    layer: [41, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    width: 1
    color: "#01ff6b"
    brightness: -16
  M2:
    layer: [45, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    width: 1
    color: "#008050"
  M3:
    layer: [49, 0]
    layer_in_name: true
    frame_color: "teal"
    fill_color: "#800057"
    hatch_pattern: coarsely dotted
  VIAC:
    layer: [40, 0]
    layer_in_name: true
    hatch_pattern: solid
    width: 1
    color: "#cc4c00"
  VIA1:
    layer: [44, 0]
    layer_in_name: true
    hatch_pattern: solid
    width: 1
    color: "grey"
  VIA2:
    layer: [43, 0]
    layer_in_name: true
    hatch_pattern: solid
    width: 1
    color: "#805000"
  CAPACITOR:
    layer: [42, 0]
    layer_in_name: true
    hatch_pattern: dotted
    width: 1
    color: "#805000"
  METALOPEN:
    layer: [46, 0]
    layer_in_name: true
    hatch_pattern: cross-hatched
    width: 1
    color: "#606060"
  DEEPTRENCH:
    layer: [4, 0]
    layer_in_name: true
    hatch_pattern: lightly right-hatched
    width: 1
    color: "#9999cc"
  OXIDE_ETCH:
    layer: [6, 0]
    layer_in_name: true
    hatch_pattern: strongly left-hatched dense
    width: 1
    color: "grey"
  SITILES:
    layer: [190, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    width: 1
    color: "grey"
  M1TILES:
    layer: [191, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    width: 1
    color: "#91ff00"
  LABEL_OPTICAL_IO:
    layer: [201, 0]
    layer_in_name: true
    hatch_pattern: hollow
    width: 1
    color: "blue"
  LABEL_SETTINGS:
    layer: [202, 0]
    layer_in_name: true
    hatch_pattern: hollow
    visible: false
    width: 1
    color: "magenta"
  TE:
    layer: [203, 0]
    layer_in_name: true
    transparent: true
    width: 1
    color: "blue"
  TM:
    layer: [204, 0]
    layer_in_name: true
    width: 1
    color: "red"
  LABEL_INSTANCES:
    layer: [206, 0]
    layer_in_name: true
    hatch_pattern: lightly left-hatched
    color: "red"
  DICING:
    layer: [65, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    width: 1
    color: "#cc0000"
  DRC_EXCLUDE:
    layer: [67, 0]
    layer_in_name: true
    hatch_pattern: hollow
    visible: false
    width: 2
    color: "grey"
  FLOORPLAN:
    layer: [64, 0]
    layer_in_name: true
    hatch_pattern: hollow
    color: "pink"
  simulation:
    group_members:
      SIM_REGION:
        layer: [100, 0]
        layer_in_name: true
        hatch_pattern: hollow
        color: "grey"
      MONITOR:
        layer: [101, 0]
        layer_in_name: true
        hatch_pattern: hollow
        color: "blue"
      SOURCE:
        layer: [110, 0]
        layer_in_name: true
        hatch_pattern: hollow
        color: "red"
  Lumerical:
    layer: [733, 0]
    hatch_pattern: hollow
    width: 3
    color: "#800057"
  DevRec:
    layer: [68, 0]
    hatch_pattern: hollow
    visible: false
    transparent: true
    width: 1
    color: "#004080"
  PinRec:
    layer: [1, 10]
    hatch_pattern: hollow
    color: "#404040"
  FbrTgt:
    layer: [81, 0]
    hatch_pattern: lightly right-hatched
    width: 2
    color: "#004080"
  Text:
    layer: [66, 0]
    hatch_pattern: hollow
    width: 1
    color: "gray"
  Errors:
    layer: [69, 0]
    hatch_pattern: hollow
    width: 1
    color: "gray"
  PinRecM:
    layer: [1, 11]
    hatch_pattern: hollow
    width: 1
    color: "#004080"
  XSECTION:
    group_members:
      XS_BOX:
        layer: [300, 0]
        layer_in_name: true
        width: 1
        color: "#f3ff80"
        hatch_pattern: solid
      XS_SI:
        layer: [301, 0]
        layer_in_name: true
        width: 1
        color: "grey"
        hatch_pattern: solid
      XS_SIN:
        layer: [319, 0]
        layer_in_name: true
        width: 1
        color: "grey"
        hatch_pattern: solid
      XS_N:
        layer: [320, 0]
        layer_in_name: true
        width: 1
        color: "#008ca5"
        hatch_pattern: solid
      XS_NPP:
        layer: [321, 0]
        layer_in_name: true
        width: 1
        color: "#004ca5"
        hatch_pattern: solid
      XS_P:
        layer: [330, 0]
        layer_in_name: true
        width: 1
        color: "#00f233"
        hatch_pattern: solid
      XS_PDPP:
        layer: [327, 0]
        layer_in_name: true
        width: 1
        color: "#00f233"
        hatch_pattern: solid
      XS_PPP:
        layer: [331, 0]
        layer_in_name: true
        width: 1
        color: "#00b233"
        hatch_pattern: solid
      XS_SI_SLAB:
        layer: [313, 0]
        layer_in_name: true
        width: 1
        color: "#80a8ff"
        hatch_pattern: solid
      XS_OVERLAY:
        layer: [311, 0]
        layer_in_name: true
        width: 1
        color: "blue"
        hatch_pattern: solid
      XS_OX_SI:
        layer: [302, 0]
        layer_in_name: true
        width: 1
        color: "#f3ff80"
        hatch_pattern: solid
      XS_GE:
        layer: [315, 0]
        layer_in_name: true
        width: 1
        color: "#004ca5"
        hatch_pattern: solid
      XS_VIAC:
        layer: [303, 0]
        layer_in_name: true
        width: 1
        color: "grey"
        hatch_pattern: solid
      XS_OX_NIT_CLAD:
        layer: [304, 0]
        layer_in_name: true
        width: 1
        color: "#f3ff80"
        hatch_pattern: solid
      XS_OXIDE_M1:
        layer: [305, 0]
        layer_in_name: true
        width: 1
        color: "#f3ff80"
        hatch_pattern: solid
      XS_MH:
        layer: [306, 0]
        layer_in_name: true
        width: 1
        color: "green"
        hatch_pattern: solid
      XS_OXIDE_M2:
        layer: [307, 0]
        layer_in_name: true
        width: 1
        color: "#f3ff80"
        hatch_pattern: solid
      XS_OXIDE_MH:
        layer: [317, 0]
        layer_in_name: true
        width: 1
        color: "#f3ff80"
        hatch_pattern: solid
      XS_OXIDE_ML:
        layer: [309, 0]
        layer_in_name: true
        width: 1
        color: "#f3ff80"
        hatch_pattern: solid
      XS_VIA1:
        layer: [308, 0]
        layer_in_name: true
        width: 1
        color: "grey"
        hatch_pattern: solid
      XS_M2:
        layer: [399, 0]
        layer_in_name: true
        width: 1
        color: "#805000"
        hatch_pattern: solid
      XS_OXIDE_M3:
        layer: [311, 0]
        layer_in_name: true
        width: 1
        color: "#f3ff80"
        hatch_pattern: solid
      XS_VIA2:
        layer: [310, 0]
        layer_in_name: true
        width: 1
        color: "grey"
        hatch_pattern: solid
      XS_SIN2:
        layer: [305, 0]
        layer_in_name: true
        width: 1
        color: "#805000"
        hatch_pattern: solid
  DRC_MARKER:
    layer: [205, 0]
    layer_in_name: true
    transparent: true
    width: 3
    color: "red"
  NOTILE_M1:
    layer: [71, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    visible: false
    color: "grey"
  NOTILE_M2:
    layer: [72, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    visible: false
    color: "grey"
  NOTILE_M3:
    layer: [73, 0]
    layer_in_name: true
    hatch_pattern: coarsely dotted
    visible: false
    color: "#606060"
  gdsfactory:
    group_members:
      ERROR_PATH:
        layer: [1000, 0]
        layer_in_name: true
        transparent: true
        width: 3
        color: "red"
      SHOW_PORTS:
        layer: [1, 12]
        layer_in_name: true
        hatch_pattern: lightly left-hatched
        color: "#ff80a8"
      WAFER:
        layer: [999, 0]
        hatch_pattern: lightly left-hatched
        color: "#ff80a8"
        visible: false

Once you modify the YAML file you can easily write it to klayout layer properties lyp or the other way around.

YAML <---> LYP

The functions LayerView.to_lyp(filepath) and LayerView.to_yaml(filepath) allow you to convert from each other.

LYP is based on XML so it’s much easier to make changes and maintain the equivalent YAML file.

YAML -> LYP#

You can easily convert from YAML into Klayout Layer Properties.

# A KLayout Layer Properties (.lyp) file stores all the visual settings for your layers, such as color, fill pattern, name, and visibility.
# This line reads the settings from the .lyp file specified by PATH.klayout_lyp and loads them into a LayerViews object in memory.
LAYER_VIEWS = LayerViews(filepath=PATH.klayout_lyp)

# This line takes the LayerViews object, which now holds all the settings from the original file, 
# and writes them out to a new file named klayout_layers.lyp inside the extra directory.
LAYER_VIEWS.to_lyp("extra/klayout_layers.lyp")
PosixPath('extra/klayout_layers.lyp')

LYP -> YAML#

Sometimes you start from an LYP XML file. We recommend converting to YAML and using the YAML as the layer views source of truth.

Layers in YAML are easier to read and modify than doing it in klayout XML format.

LAYER_VIEWS = LayerViews(filepath=PATH.klayout_lyp)

# This line takes the LayerViews object and writes the settings to a new file named layers.yaml in YAML format.
# YAML is a text-based format that is easy for humans to read and edit, and it's also easily parsed by software.
LAYER_VIEWS.to_yaml("extra/layers.yaml")

Preview layerset#

You can preview all the layers defined in your LayerViews

c = LAYER_VIEWS.preview_layerset()
c.plot()
../_images/e14db16b687638ea1c3ed13c5bcebf87ab200553aa88684487d6687fafa4e961.png

By default the generic PDK has some layers that are not visible and therefore are not shown.

c_wg_clad = c.extract(layers=[(1, 0)])
c_wg_clad.plot()
../_images/def6c1b51fbf4863dab50fc7c2e91d5839899e2fd9d1cf3d5d8771288f840be4.png
# .layer_views: This is an attribute of the LAYER_VIEWS object that acts like a dictionary,
#  where the keys are the layer names (e.g., "WGCLAD", "SI") and the values are the display settings for each layer.
# ["WGCLAD"]: This is standard dictionary syntax to look up and return the settings associated with the key "WGCLAD".
LAYER_VIEWS.layer_views["WGCLAD"]
LayerView:
	info: None
	layer: (111, 0)
	layer_in_name: True
	frame_color: silver
	fill_color: silver
	frame_brightness: 0
	fill_brightness: 0
	hatch_pattern: coarsely dotted
	line_style: None
	valid: True
	visible: False
	transparent: False
	width: 1
	marked: False
	xfill: False
	animation: 0
	group_members: {}
# .visible: This is a boolean attribute of the layer view object that returns True if the layer is set to be visible in the KLayout viewer,
# and False if it is hidden.
LAYER_VIEWS.layer_views["WGCLAD"].visible
False

You can make it visible

# LAYER_VIEWS.layer_views["WGCLAD"].visible = True
# This line accesses the display settings for the layer named "WGCLAD" and sets its visible property to True.
# This would make the layer visible in the KLayout viewer when these layer properties are loaded.
LAYER_VIEWS.layer_views["WGCLAD"].visible = True
# LAYER_VIEWS.layer_views["WGCLAD"].visible
# In an interactive session, this line would retrieve and display the new visibility status, which would be True.
LAYER_VIEWS.layer_views["WGCLAD"].visible
True
c_ge = c.extract(layers=[(5, 0)])
c_ge.plot()
../_images/64511eb55f7ed4dbc0fbadda20f1ffcc07966f011a9d69fe77ca4a058e376b23.png

LayerStack#

Each layer also includes the information of thickness and position of each layer after fabrication.

This LayerStack can be used for creating a 3D model with Component.to_3d or running simulations.

A GDS has different layers to describe the different fabrication process steps. And each grown layer needs thickness information and a z-position in the stack.

layer stack

Let us define the layer stack for the generic layers in the generic_technology.

import gdsfactory as gf

# This imports a predefined layer map named LAYER.
# This is a convenient object that contains ready-to-use definitions for common layers in a generic fabrication process,
# such as LAYER.WG for waveguides or LAYER.SLAB90 for slabs.
from gdsfactory.generic_tech.layer_map import LAYER

# This imports the LogicalLayer class. This is a more structured way to define a layer,
#  allowing you to associate not just the GDSII layer and purpose numbers, but also other metadata like the material or the name of the layer.
from gdsfactory.technology import LogicalLayer

# This line sets up a convenient conversion factor. Since gdsfactory and most photonics tools work in micrometers (µm),
# this variable allows you to define thicknesses in nanometers (nm) and easily convert them to µm (1 nm = 0.001 µm).
nm = 1e-3

# This defines the total thickness of the main silicon waveguide layer as 220 nm (0.22 µm).
# This is a very common standard for Silicon-on-Insulator (SOI) wafers used in photonics.
# A deep etch is a microfabrication process characterized by its anisotropy, meaning it etches downwards much faster than sideways.
# A shallow etch is a microfabrication process that removes a thin, precisely controlled layer of material from the surface of a wafer,
# without cutting deep into it. It is the opposite of a deep etch.
thickness_wg = 220 * nm
thickness_slab_deep_etch = 90 * nm # thickness_slab_deep_etch = 90 * nm: Defines the slab thickness after a deep etch (a 90 nm slab remains).
thickness_slab_shallow_etch = 150 * nm # thickness_slab_shallow_etch = 150 * nm: Defines the slab thickness after a shallow etch (a 150 nm slab remains).

# This variable defines the sidewall angle of the waveguide in degrees.
# This parameter allows you to model the actual slope of the waveguide's sides. A value of 0 represents an ideal, perfectly vertical 90-degree etch.
sidewall_angle_wg = 0
layer_core = LogicalLayer(layer=LAYER.WG) #  Represents the main waveguide layer.
layer_shallow_etch = LogicalLayer(layer=LAYER.SHALLOW_ETCH) # Represents the areas to be shallowly etched.
layer_deep_etch = LogicalLayer(layer=LAYER.DEEP_ETCH) # Represents the areas to be deeply etched.


layers = {
    "core": LayerLevel(

        # This defines the final shape of the core.
        # It starts with the full waveguide shape (layer_core) and then "cuts away" the areas that are designated for deep etching and shallow etching.
        # The result is the geometry for the unetched, full-height part of the waveguide.
        layer=layer_core - layer_deep_etch - layer_shallow_etch,
        thickness=thickness_wg,
        zmin=0.0, # The vertical position where this layer starts
        material="si", # The material used is silicon
        mesh_order=2, # A priority setting used by simulation software when generating a mesh; higher numbers are processed first.
        sidewall_angle=sidewall_angle_wg,
        width_to_z=0.5, # For every 1 µm of height, the top surface is narrowed by 0.5 µm on each side. This creates a specific, sloped sidewall.
        derived_layer=layer_core,
    ),
    "shallow_etch": LayerLevel(
        layer=LogicalLayer(layer=LAYER.SHALLOW_ETCH),
        thickness=thickness_wg - thickness_slab_shallow_etch,
        zmin=0.0,
        material="si",
        mesh_order=1,
        derived_layer=LogicalLayer(layer=LAYER.SLAB150),
    ),
    "deep_etch": LayerLevel(
        layer=LogicalLayer(layer=LAYER.DEEP_ETCH),
        thickness=thickness_wg - thickness_slab_deep_etch,
        zmin=0.0,
        material="si",
        mesh_order=1,
        derived_layer=LogicalLayer(layer=LAYER.SLAB90),
    ),
    "slab150": LayerLevel(
        layer=LogicalLayer(layer=LAYER.SLAB150),
        thickness=150e-3,
        zmin=0,
        material="si",
        mesh_order=3,
    ),
    "slab90": LayerLevel(
        layer=LogicalLayer(layer=LAYER.SLAB90),
        thickness=thickness_slab_deep_etch,
        zmin=0.0,
        material="si",
        mesh_order=2,
    ),
}


layer_stack = LayerStack(layers=layers)

c = gf.c.grating_coupler_elliptical_trenches()
s = c.to_3d(layer_stack=layer_stack)
s.show()
from gdsfactory.generic_tech.layer_stack import get_layer_stack

layer_stack220 = get_layer_stack()

# Rib Waveguide: Unlike a strip waveguide, a rib waveguide has a central core on top of a thinner "slab" of the same material.
# This design offers a good balance between light confinement and lower propagation losses.
# On either side of the rib core, there are doped P and N regions. These act as a resistive heater.
# When a voltage is applied, current flows through these regions, generating heat to change the waveguide's refractive index.
# Length: The length=100 parameter sets the total length of the component to 100 µm.
c = gf.c.straight_heater_doped_rib(length=100)
c
../_images/a265e028c0a02802a5ec2bc2073e252af2368f08541d099a5cd06f8e1c81fa9f.png
scene = c.to_3d(layer_stack=layer_stack220)
scene.show()
c = gf.components.straight_heater_metal(length=90)
c.plot()
../_images/4c3077abf8f4ed62b6c27e223b429ce4b4703e5ae428ebd8912d360da04a7d09.png
scene = c.to_3d(layer_stack=layer_stack220)
scene.show()
# The taper_strip_to_ridge_trenches() component acts as a taper for the waveguide core and adds trenches on either side that define the rib structure.
# The trenches are etched into the silicon, leaving behind the central rib and the surrounding slab.
c = gf.components.taper_strip_to_ridge_trenches()
c.plot()
../_images/0fe4bed8e614154683513999d7ed7f023a93e00b6605fed34463a5dca62c6e19.png
scene = c.to_3d(layer_stack=layer_stack220)
scene.show()
# Let us assume we have 900nm silicon instead of 220nm, you will see a much thicker waveguide under the metal heater.
layer_stack900 = get_layer_stack(thickness_wg=900 * nm)
scene = c.to_3d(layer_stack=layer_stack900)
scene.show()
import gdsfactory as gf

c = gf.components.grating_coupler_elliptical_trenches()
c.plot()
../_images/87cd451fc08d84896baba54069e89ad85d40b83e54493a4f70bf1ff3cfa075ca.png
scene = c.to_3d()
scene.show()

3D rendering#

To render components in 3D you will need to define two things:

  1. LayerStack: for each layer contains thickness of each material and z position

  2. LayerViews: for each layer contains view (color, pattern, opacity). You can load it with gf.technology.LayerView.load_lyp()

heater = gf.components.straight_heater_metal(length=90)
heater.plot()
../_images/4c3077abf8f4ed62b6c27e223b429ce4b4703e5ae428ebd8912d360da04a7d09.png
scene = heater.to_3d()
scene.show()

Klayout 2.5D view#

From the LayerStack you can generate the KLayout 2.5D view script.

LAYER_STACK.get_klayout_3d_script()
'core = input(1, 0)\nshallow_etch = input(2, 0)\ndeep_etch = input(3, 0)\n\nsubstrate = input(999, 0)\nbox = input(999, 0)\ndeep_etch = input(3, 6)\nclad = input(999, 0)\nslab150 = input(2, 0)\nslab90 = input(3, 0)\nnitride = input(34, 0)\nge = input(5, 0)\nundercut = input(6, 0)\nvia_contact = input(40, 0)\nmetal1 = input(41, 0)\nheater = input(47, 0)\nvia1 = input(44, 0)\nmetal2 = input(45, 0)\nvia2 = input(43, 0)\nmetal3 = input(49, 0)\n\nunetched_WG = WG - core\nunetched_SLAB150 = SLAB150 - shallow_etch\n\nslab_WG_core = WG & core\nslab_SLAB150_shallow_etch = SLAB150 & shallow_etch\nslab_SLAB90_deep_etch = SLAB90 & deep_etch\n\n'

Then you go to Tools → Manage Technologies

klayout

and paste the 2.5D view script

paste

Klayout cross-section#

You can also install the KLayout cross-section plugin

xsection

This is not integrated with the LayerStack but you can customize the script in gdsfactory.generic_tech.get_klayout_pyxs for your technology.

nm = 1e-3
if __name__ == "__main__":

    # t_...: Defines the thickness of different material layers (e.g., t_si=220*nm for the silicon device layer, t_m1=0.5 for the first metal layer).
    # h_etch...: Defines the height or depth of different etching steps.
    # gap_...: Defines the vertical gap or spacing between layers (e.g., gap_m1_m2=0.6 for the space between metal 1 and metal 2).
    # The layer_ function help with layer assignments:
    # These parameters map the different logical parts of the design to specific GDSII layer numbers (e.g., LAYER.WG, LAYER.M1).
    # Waveguide Layers: layer_wg, layer_rib, layer_nitride.
    # Doping Layers: layer_n, layer_p, layer_npp, etc., for creating electronic junctions.
    # Germanium Layers: layer_Ge, layer_GePPp for photodetectors.
    # Metal and Via Layers: layer_m1, layer_via1, layer_m2, etc., for electrical routing.
    
    script = get_klayout_pyxs(
        t_box=2.0,
        t_slab=110 * nm,
        t_si=220 * nm,
        t_ge=400 * nm,
        t_nitride=400 * nm,
        h_etch1=0.07,
        h_etch2=0.06,
        h_etch3=0.09,
        t_clad=0.6,
        t_m1=0.5,
        t_m2=0.5,
        t_m3=2.0,
        gap_m1_m2=0.6,
        gap_m2_m3=0.3,
        t_heater=0.1,
        gap_oxide_nitride=0.82,
        t_m1_oxide=0.6,
        t_m2_oxide=2.0,
        t_m3_oxide=0.5,
        layer_wg=(1, 0),
        layer_fc=(2, 0),
        layer_rib=LAYER.SLAB90,
        layer_n=LAYER.N,
        layer_np=LAYER.NP,
        layer_npp=LAYER.NPP,
        layer_p=LAYER.P,
        layer_pp=LAYER.PP,
        layer_ppp=LAYER.PPP,
        layer_PDPP=LAYER.GEP,
        layer_nitride=LAYER.WGN,
        layer_Ge=LAYER.GE,
        layer_GePPp=LAYER.GEP,
        layer_GeNPP=LAYER.GEN,
        layer_viac=LAYER.VIAC,
        layer_viac_slot=LAYER.VIAC,
        layer_m1=LAYER.M1,
        layer_mh=LAYER.HEATER,
        layer_via1=LAYER.VIA1,
        layer_m2=LAYER.M2,
        layer_via2=LAYER.VIA2,
        layer_m3=LAYER.M3,
        layer_open=LAYER.PADOPEN,
    )

    # script_path = pathlib.Path(__file__).parent.absolute() / "xsection_planarized.pyxs".
    # script_path.write_text(script).
    print(script)
t_box=2.0
t_slab=0.11
t_si=0.22
t_ge=0.4
t_nitride=0.4
h_etch1=0.07
h_etch2=0.06
h_etch3=0.09
t_clad=0.6
t_m1=0.5
t_m2=0.5
t_m3=2.0
t_heater=0.1
gap_m1_m2=0.6
gap_m2_m3=0.3
gap_oxide_nitride=0.82
t_m1_oxide=0.6
t_m2_oxide=2.0
t_m3_oxide=0.5

l_wg = layer('1/0')
l_fc = layer('2/0')
l_rib = layer('3/0')

l_n = layer('20/0')
l_np = layer('22/0')
l_npp = layer('24/0')
l_p = layer('21/0')
l_pp = layer('23/0')
l_ppp = layer('25/0')
l_PDPP = layer('27/0')
l_bottom_implant = l_PDPP

l_nitride = layer('34/0')
l_Ge = layer('5/0')
l_GePPp = layer('27/0')
l_GeNPP = layer('26/0')

l_viac = layer('40/0')
l_viac_slot = layer('40/0')
l_m1 = layer('41/0')
l_mh = layer('47/0')
l_via1 = layer('44/0')
l_m2 = layer('45/0')
l_via2 = layer('43/0')
l_m3 = layer('49/0')
l_open = layer('46/0')

l_top_implant = l_GePPp.or_(l_GeNPP)
l_viac = l_viac.or_(l_viac_slot)

# Declare the basic accuracy used to remove artifacts for example: delta(5 * dbu)
delta(dbu)
depth(12.0)
height(12.0)

################ front-end

l_wg_etch1 = l_wg.inverted()  # protects ridge
l_wg_etch2 = (
    l_fc.or_(l_wg)
).inverted()  # protects ridge and grating couplers from the etch down to the slab (forms rib straights)
l_wg_etch3 = (
    l_rib.or_(l_fc).or_(l_wg)
).inverted()  # protects ridge, grating couplers and rib straights from the final etch to form strip straights


################ back-end
substrate = bulk
box = deposit(t_box)
si = deposit(t_si)

################ silicon etch to for the passives
mask(l_wg_etch1).etch(
    h_etch1, 0.0, mode="round", into=[si]
)  # 70nm etch for GC, rib and strip
mask(l_wg_etch2).etch(
    h_etch2, 0.0, mode="round", into=[si]
)  # 60nm etch after 70nm = 130nm etch (90nm slab)
mask(l_wg_etch3).etch(
    h_etch3, 0.0, mode="round", into=[si]
)  # etches the remaining 90nm slab for strip straights

output("300/0", box)
output("301/0", si)

############### doping
mask(l_bottom_implant).etch(t_si, 0.0, mode="round", into=[si])
bottom_implant = mask(l_bottom_implant).grow(t_si, 0.0, mode="round")

mask(l_n).etch(t_slab, 0.0, mode="round", into=[si])
n = mask(l_n).grow(t_slab, 0.0, mode="round")

mask(l_p).etch(t_slab, 0.0, mode="round", into=[si])
p = mask(l_p).grow(t_slab, 0.0, mode="round")

mask(l_np).etch(t_slab, 0.0, mode="round", into=[n, p, si, bottom_implant])
np = mask(l_np).grow(t_slab, 0.0, mode="round")

mask(l_pp).etch(t_slab, 0.0, mode="round", into=[n, p, si, bottom_implant])
pp = mask(l_pp).grow(t_slab, 0.0, mode="round")

mask(l_npp).etch(t_slab, 0.0, mode="round", into=[n, p, np, pp, si, bottom_implant])
npp = mask(l_npp).grow(t_slab, 0.0, mode="round")

mask(l_ppp).etch(t_slab, 0.0, mode="round", into=[n, p, np, pp, si, bottom_implant])
ppp = mask(l_ppp).grow(t_slab, 0.0, mode="round")

output("327/0", bottom_implant)
output("330/0", p)
output("320/0", n)
output("321/0", npp)
output("331/0", ppp)

################ Ge
Ge = mask(l_Ge).grow(t_ge, 0, bias=0.0, taper=10)
output("315/0", Ge)

################ Nitride
ox_nitride = deposit(2 * gap_oxide_nitride, 2 * gap_oxide_nitride)
planarize(less=gap_oxide_nitride, into=[ox_nitride])
output("302/0", ox_nitride)

nitride = mask(l_nitride).grow(t_nitride, 0, bias=0.0, taper=10)
output("305/0", nitride)

################# back-end
################# VIAC, M1 and MH
ox_nitride_clad = deposit(t_clad + t_ge + t_nitride, t_clad + t_ge + t_nitride, mode="round")

planarize(less=t_ge + t_nitride, into=[ox_nitride_clad])
mask(l_viac).etch(
    t_clad + t_ge + t_nitride + gap_oxide_nitride, taper=4, into=[ox_nitride_clad, ox_nitride]
)

viac = deposit(2 * t_clad, 2 * t_clad)
planarize(less=2 * t_clad, into=[viac])

mh = deposit(t_heater, t_heater)
mask(l_mh.inverted()).etch(t_heater + t_heater, into=[mh])
m1 = deposit(t_m1, t_m1)
mask(l_m1.inverted()).etch(t_m1 + t_m1, into=[m1])
output("306/0", mh)
output("399/0", m1)

output("304/0", ox_nitride_clad)
output("303/0", viac)

################# VIA1 and M2
ox_m1 = deposit(2 * t_m1_oxide, 2 * t_m1_oxide, mode="round")
planarize(less=t_m1_oxide, into=[ox_m1])

mask(l_via1).etch(t_m1_oxide + gap_m1_m2, taper=4, into=[ox_m1])
via1 = deposit(t_m2, t_m2)

mask(l_m2.inverted()).etch(t_m2, taper=4, into=[via1])
output("308/0", via1)

ox_m2 = deposit(2 * t_m2_oxide, 2 * t_m2_oxide, mode="round")
planarize(less=t_m2_oxide, into=[ox_m2])
output("309/0", ox_m2)
output("307/0", ox_m1)

################# VIA2 and M3
mask(l_via2).etch(t_m2_oxide + gap_m2_m3, taper=4, into=[ox_m2, ox_m2])
via2 = deposit(t_m3, t_m3)
mask(l_m3.inverted()).etch(t_m3, taper=4, into=[via2])
output("310/0", via2)

################# passivation and ML Open
ox_m3 = deposit(t_m3_oxide, t_m3_oxide, mode="round")
mask(l_open).etch(t_m3_oxide + t_m3_oxide, into=[ox_m3], taper=5)
output("311/0", ox_m3)

xsection generic

Process#

The LayerStack uses the GDS layers to generate a representation of the chip after fabrication.

The KLayout cross-section module uses the GDS layers to return a geometric approximation of the processed wafer.

Sometimes, however, physical process modeling is desired.

For these purposes, processes acting on an initial substrate “wafer stack” can be defined. The waferstack is a LayerStack representing the initial state of the wafer. The processes take in some combination of GDS layers (which may differ from their use in the resulting LayerStack), some processing parameters, and are then run in sequence.

For instance, the early step of the front-end-of-line of the generic process could be approximated as done in gdsfactory.technology.layer_stack (the process classes are described in gdsfactory.technology.processes):

import gdsfactory.technology.processes as gp


def get_process():
    """Returns generic process to generate LayerStack.

    Represents processing steps that will result in the GenericLayerStack, starting from the waferstack LayerStack.

    based on paper https://www.degruyter.com/document/doi/10.1515/nanoph-2013-0034/html
    """
    return (
        
        # The first, deeper etch:
        gp.Etch(
            name="strip_etch",
            layer=(1, 0),
            positive_tone=False, # Specifies a negative tone process, the areas covered by the mask are protected, and the surrounding material is etched away.
            
            # +0.01 signals a slight over-etch.
            # A slight overetch is the intentional removal of a small, extra amount of material during the chip fabrication process.
            # It is a planned "safety margin" to ensure that the etch is complete and uniform across the entire wafer.
            depth=0.22 + 0.01, 
            material="core",

            # The resist thickness must be precisely controlled for two main reasons:
            # Resolution: If the resist is too thick, it can be difficult to create fine, high-resolution features.
            # Durability: The resist must be thick enough to withstand the etching process without being completely eroded
            # before the underlying material has been fully etched.
            resist_thickness=1.0,
        ),
        
        # The second, shallower etch:
        gp.Etch(
            name="slab_etch",
            layer=LAYER.SLAB90,
            layers_diff=[(1, 0)],
            depth=0.22 - 0.09, # The etch depth is 0.13 µm. This is a partial etch, leaving a 90 nm slab of silicon (0.22 - 0.13 = 0.09).
            material="core",
            resist_thickness=1.0,
        ),
        # See gplugins.process.implant tables for ballpark numbers
        # Adjust to your process

        # This ImplantPhysical object models the process of implanting ions into a wafer to change its electrical properties (a process called doping).
        gp.ImplantPhysical(
            name="deep_n_implant",
            layer=LAYER.N, # The GDSII layer that acts as the mask. The implant will only occur in the areas defined by the shapes on this layer.
            energy=100, # The implantation energy in keV. Higher energy results in the ions being implanted deeper into the material.
            
            # The type of ion being implanted, in this case, Phosphorus (P).
            # Phosphorus has one more valence electron than silicon, so implanting it creates N-type doped silicon.
            ion="P", 
            dose=1e12, # The ion dose, in atoms per cm². This determines the concentration of dopant atoms that are implanted.
            resist_thickness=1.0,
        ),
        gp.ImplantPhysical(
            name="shallow_n_implant",
            layer=LAYER.N,
            energy=50,
            ion="P",
            dose=1e12,
            resist_thickness=1.0,
        ),
        gp.ImplantPhysical(
            name="deep_p_implant",
            layer=LAYER.P,
            energy=50,
            ion="B", # The type of ion being implanted is Boron (B), it has one less valence electron than silicon, this makes it a P-type dopant.
            dose=1e12,
            resist_thickness=1.0,
        ),
        gp.ImplantPhysical(
            name="shallow_p_implant",
            layer=LAYER.P,
            energy=15,
            ion="B",
            dose=1e12,
            resist_thickness=1.0,
        ),
        # "Temperatures of ~1000C for not more than a few seconds"
        # Adjust to your process
        # https://en.wikipedia.org/wiki/Rapid_thermal_processing
        gp.Anneal(
            name="dopant_activation",
            time=1,
            temperature=1000,
        ),
    )

# This code calls a function named get_process() to load a predefined semiconductor fabrication process.
process = get_process()

These process dataclasses can then be used in physical simulator plugins.