Skip to content

Download notebook (.ipynb)

Technology & Layer Stack

A LayerStack describes the vertical cross-section of your fabrication process: which physical materials are deposited on which GDS layers, at what height (zmin), and with what thickness. This information is used by simulation exporters (e.g. Tidy3D, MEEP, Lumerical) that consume kfactory layouts.

This page covers:

Topic What you'll learn
LayerLevel One physical layer in the process
LayerStack Collection of LayerLevels
Info Extra metadata (mesh order, refractive index, etch type, …)
Attaching a stack to a PDK Best-practice pattern
Querying the stack Per-layer lookups

1 · LayerLevel — one process step

LayerLevel describes a single physical layer:

Parameter Type Meaning
layer (int, int) or kdb.LayerInfo GDS layer / datatype
zmin float (µm) Bottom of the slab
thickness float (µm) Vertical thickness
material str Material name (passed to simulator)
sidewall_angle float (°) 0° = vertical, 90° = horizontal
info Info Optional simulation metadata
import kfactory as kf
from kfactory.layer import Info, LayerLevel, LayerStack

# Silicon waveguide core: 220 nm thick, starts at z=0
wg_level = LayerLevel(
    layer=(1, 0),
    zmin=0.0,
    thickness=0.22,  # µm
    material="si",
    sidewall_angle=80.0,  # near-vertical etch
)

print(wg_level)
layer=(1, 0) thickness=0.22 thickness_tolerance=None zmin=0.0 material='si' sidewall_angle=80.0 z_to_bias=None info=Info()

Info — simulation metadata

Pass an Info object to attach extra metadata that simulators can consume. Common fields:

Field Meaning
mesh_order Lower = higher priority in mesher (1 overrides 2)
refractive_index float (int/float only; complex must be stored as a string)
type "grow", "etch", "implant", or "background"
wg_level_with_info = LayerLevel(
    layer=(1, 0),
    zmin=0.0,
    thickness=0.22,
    material="si",
    sidewall_angle=80.0,
    info=Info(
        mesh_order=1,
        refractive_index=3.47,
        type="grow",
    ),
)
print(wg_level_with_info.info)
Info(mesh_order=1, refractive_index=3.47, type='grow')

2 · LayerStack — the full process

LayerStack is a named collection of LayerLevels. Pass levels as keyword arguments; the keyword name becomes the layer's logical name in the stack.

stack = LayerStack(
    # --- core waveguide layers ---
    wg=LayerLevel(
        layer=(1, 0),
        zmin=0.0,
        thickness=0.22,
        material="si",
        sidewall_angle=80.0,
        info=Info(mesh_order=1, refractive_index=3.47, type="grow"),
    ),
    slab=LayerLevel(
        layer=(3, 0),
        zmin=0.0,
        thickness=0.09,  # partial etch leaves 90 nm slab
        material="si",
        sidewall_angle=70.0,
        info=Info(mesh_order=2, refractive_index=3.47, type="grow"),
    ),
    # --- oxide cladding ---
    clad=LayerLevel(
        layer=(111, 0),
        zmin=-3.0,
        thickness=3.22,  # 3 µm below + 0.22 µm above wg top
        material="sio2",
        info=Info(mesh_order=3, refractive_index=1.44, type="background"),
    ),
    # --- metal ---
    metal=LayerLevel(
        layer=(41, 0),
        zmin=0.5,
        thickness=1.0,
        material="al",
        info=Info(mesh_order=1, type="grow"),
    ),
)

print(stack)
layers={'wg': LayerLevel(layer=(1, 0), thickness=0.22, thickness_tolerance=None, zmin=0.0, material='si', sidewall_angle=80.0, z_to_bias=None, info=Info(mesh_order=1, refractive_index=3.47, type='grow')), 'slab': LayerLevel(layer=(3, 0), thickness=0.09, thickness_tolerance=None, zmin=0.0, material='si', sidewall_angle=70.0, z_to_bias=None, info=Info(mesh_order=2, refractive_index=3.47, type='grow')), 'clad': LayerLevel(layer=(111, 0), thickness=3.22, thickness_tolerance=None, zmin=-3.0, material='sio2', sidewall_angle=0.0, z_to_bias=None, info=Info(mesh_order=3, refractive_index=1.44, type='background')), 'metal': LayerLevel(layer=(41, 0), thickness=1.0, thickness_tolerance=None, zmin=0.5, material='al', sidewall_angle=0.0, z_to_bias=None, info=Info(mesh_order=1, type='grow'))}

3 · Querying the stack

LayerStack exposes several lookup helpers. Keys are (layer, datatype) tuples.

print("Thicknesses:")
for layer_tuple, t in stack.get_layer_to_thickness().items():
    print(f"  {layer_tuple}: {t} µm")

print("\nMaterials:")
for layer_tuple, mat in stack.get_layer_to_material().items():
    print(f"  {layer_tuple}: {mat}")

print("\nZ-min positions:")
for layer_tuple, zmin in stack.get_layer_to_zmin().items():
    print(f"  {layer_tuple}: {zmin} µm")

print("\nSidewall angles:")
for layer_tuple, angle in stack.get_layer_to_sidewall_angle().items():
    print(f"  {layer_tuple}: {angle}°")
Thicknesses:
  (1, 0): 0.22 µm
  (3, 0): 0.09 µm
  (111, 0): 3.22 µm
  (41, 0): 1.0 µm

Materials:
  (1, 0): si
  (3, 0): si
  (111, 0): sio2
  (41, 0): al

Z-min positions:
  (1, 0): 0.0 µm
  (3, 0): 0.0 µm
  (111, 0): -3.0 µm
  (41, 0): 0.5 µm

Sidewall angles:
  (1, 0): 80.0°
  (3, 0): 70.0°
  (111, 0): 0.0°
  (41, 0): 0.0°

Access individual levels by name or by index:

wg = stack["wg"]
print(
    f"wg zmin={wg.zmin} µm, thickness={wg.thickness} µm, top={wg.zmin + wg.thickness} µm"
)
wg zmin=0.0 µm, thickness=0.22 µm, top=0.22 µm

4 · Attaching a stack to a PDK

A LayerStack is not built into KCLayout — you attach it to your PDK module as a module-level constant alongside LAYER and the layout object.

Best practice: define everything in one module and import pdk, LAYER, and STACK from it.

# --- pdk_with_stack.py (inline for demo) ---


class LAYER(kf.LayerInfos):
    WG: kf.kdb.LayerInfo = kf.kdb.LayerInfo(1, 0)
    SLAB: kf.kdb.LayerInfo = kf.kdb.LayerInfo(3, 0)
    WGCLAD: kf.kdb.LayerInfo = kf.kdb.LayerInfo(111, 0)
    METAL: kf.kdb.LayerInfo = kf.kdb.LayerInfo(41, 0)
    FLOORPLAN: kf.kdb.LayerInfo = kf.kdb.LayerInfo(99, 0)


pdk = kf.KCLayout("DEMO_TECH_PDK", infos=LAYER)
L = pdk.infos  # LayerInfos instance; use for layer objects

STACK = LayerStack(
    wg=LayerLevel(
        layer=(L.WG.layer, L.WG.datatype),
        zmin=0.0,
        thickness=0.22,
        material="si",
        sidewall_angle=80.0,
        info=Info(mesh_order=1, refractive_index=3.47, type="grow"),
    ),
    clad=LayerLevel(
        layer=(L.WGCLAD.layer, L.WGCLAD.datatype),
        zmin=-3.0,
        thickness=3.22,
        material="sio2",
        info=Info(mesh_order=3, refractive_index=1.44, type="background"),
    ),
)

print(f"PDK:   {pdk}")
print(f"Stack: {list(STACK.layers.keys())} layers defined")
PDK:   name='DEMO_TECH_PDK' layout=<klayout.dbcore.Layout object at 0x7fe0a956b650> layer_enclosures=LayerEnclosureModel(root={}) cross_sections={} enclosure=KCellEnclosure(enclosures=LayerEnclosureCollection(enclosures=[])) library=<klayout.dbcore.Library object at 0x7fe0a93cd310> factories=<kfactory.layout.Factories object at 0x7fe0a9589bd0> virtual_factories=<kfactory.layout.Factories object at 0x7fe0a976fbb0> tkcells={} layers=<aenum 'LAYER'> infos=LAYER(WG=WG (1/0), SLAB=SLAB (3/0), WGCLAD=WGCLAD (111/0), METAL=METAL (41/0), FLOORPLAN=FLOORPLAN (99/0)) layer_stack=LayerStack(layers={}) netlist_layer_mapping={} sparameters_path=None interconnect_cml_path=None constants=Constants() rename_function=<function rename_clockwise_multi at 0x7fe0b1db2c40> thread_lock=<unlocked _thread.RLock object owner=0 count=0 at 0x7fe0aaca6620> info=Info() settings=KCellSettings(version='3.0.0rc1', klayout_version='0.30.8', meta_format='v3') future_cell_name=None decorators=<kfactory.decorators.Decorators object at 0x7fe0a958a850> default_cell_output_type=<class 'kfactory.kcell.KCell'> default_vcell_output_type=<class 'kfactory.kcell.VKCell'> connectivity=[] routing_strategies={} technology_file=None
Stack: ['wg', 'clad'] layers defined

5 · Serialising the stack

to_dict() returns a plain Python dict — useful for saving to YAML or JSON, or for passing to simulators that don't import kfactory directly.

import json

d = stack.to_dict()
print(json.dumps(d, indent=2, default=str))
{
  "wg": {
    "layer": [
      1,
      0
    ],
    "thickness": 0.22,
    "thickness_tolerance": null,
    "zmin": 0.0,
    "material": "si",
    "sidewall_angle": 80.0,
    "z_to_bias": null,
    "info": {
      "mesh_order": 1,
      "refractive_index": 3.47,
      "type": "grow"
    }
  },
  "slab": {
    "layer": [
      3,
      0
    ],
    "thickness": 0.09,
    "thickness_tolerance": null,
    "zmin": 0.0,
    "material": "si",
    "sidewall_angle": 70.0,
    "z_to_bias": null,
    "info": {
      "mesh_order": 2,
      "refractive_index": 3.47,
      "type": "grow"
    }
  },
  "clad": {
    "layer": [
      111,
      0
    ],
    "thickness": 3.22,
    "thickness_tolerance": null,
    "zmin": -3.0,
    "material": "sio2",
    "sidewall_angle": 0.0,
    "z_to_bias": null,
    "info": {
      "mesh_order": 3,
      "refractive_index": 1.44,
      "type": "background"
    }
  },
  "metal": {
    "layer": [
      41,
      0
    ],
    "thickness": 1.0,
    "thickness_tolerance": null,
    "zmin": 0.5,
    "material": "al",
    "sidewall_angle": 0.0,
    "z_to_bias": null,
    "info": {
      "mesh_order": 1,
      "type": "grow"
    }
  }
}

Summary

Class Key role
LayerLevel Single process step: layer, z position, thickness, material
Info Simulation extras: mesh order, refractive index, etch type
LayerStack Named dict of LayerLevels; exposes per-layer lookup helpers

Keep STACK as a module-level constant in your PDK module alongside pdk and LAYER. Simulators can then from my_pdk import pdk, LAYER, STACK and get everything they need from a single import.

See Also

Topic Where
Creating a full PDK PDK: Creating a PDK
Layer definitions Core Concepts: Layers
Cross-sections (port geometry) Cross-Sections
KCLayout (owns the cell DB) Core Concepts: KCLayout