Source code for ubcpdk.tech

"""Technology definitions.

- LayerStack
- cross_sections (xs_)
- constants (WIDTH, CLADDING_OFFSET ...)
"""

import sys
from functools import partial

import gdsfactory as gf
from gdsfactory.add_pins import add_pin_path
from gdsfactory.component import Component
from gdsfactory.cross_section import get_cross_sections
from gdsfactory.technology import LayerLevel, LayerStack
from gdsfactory.typings import Callable, Layer, LayerSpec, Optional
from pydantic import BaseModel

from ubcpdk.config import PATH

nm = 1e-3
pin_length = 10 * nm


class LayerMapUbc(BaseModel):
    WG: Layer = (1, 0)
    WG2: Layer = (31, 0)
    M1_HEATER: Layer = (11, 0)
    M2_ROUTER: Layer = (12, 0)
    MTOP: Layer = (12, 0)
    PAD_OPEN: Layer = (13, 0)

    DEVREC: Layer = (68, 0)
    PORT: Layer = (1, 10)  # PinRec
    PORTE: Layer = (1, 11)  # PinRecM
    FLOORPLAN: Layer = (99, 0)

    TEXT: Layer = (10, 0)
    SHOW_PORTS: Layer = (1, 13)
    PADDING: Layer = (67, 0)
    SLAB150: Layer = (2, 0)
    WAFER: Layer = (99999, 0)

    class Config:
        frozen = True
        extra = "forbid"


LAYER = LayerMapUbc()


def add_labels_to_ports_optical(
    component: Component,
    label_layer: LayerSpec = LAYER.TEXT,
    port_type: Optional[str] = "optical",
    **kwargs,
) -> Component:
    """Add labels to component ports.

    Args:
        component: to add labels.
        label_layer: layer spec for the label.
        port_type: to select ports.

    keyword Args:
        layer: select ports with GDS layer.
        prefix: select ports with prefix in port name.
        orientation: select ports with orientation in degrees.
        width: select ports with port width.
        layers_excluded: List of layers to exclude.
        port_type: select ports with port_type (optical, electrical, vertical_te).
        clockwise: if True, sort ports clockwise, False: counter-clockwise.
    """
    suffix = "o3_0" if len(component.ports) == 4 else "o2_0"
    ports = component.get_ports_list(port_type=port_type, suffix=suffix, **kwargs)
    for port in ports:
        component.add_label(text=port.name, position=port.center, layer=label_layer)

    return component


margin = 0.5
add_bbox_siepic = partial(gf.add_padding, layers=(LAYER.DEVREC,), default=0)
add_bbox_siepic_top_bot = partial(
    gf.add_padding, layers=(LAYER.DEVREC,), default=0, top=margin, bottom=margin
)
add_bbox_siepic_bot_right = partial(
    gf.add_padding, layers=(LAYER.DEVREC,), default=0, right=margin, bottom=margin
)


def add_pins_siepic(
    component: Component,
    function: Callable = add_pin_path,
    port_type: str = "optical",
    layer_pin: LayerSpec = "PORT",
    pin_length: float = pin_length,
    **kwargs,
) -> Component:
    """Add pins.

    Enables you to run SiEPIC verification tools:
    To Run verification install SiEPIC-tools KLayout package
    then hit V shortcut in KLayout to run verification

    - ensure no disconnected pins
    - netlist extraction

    Args:
        component: to add pins.
        function: to add pin.
        port_type: optical, electrical, ...
        layer_pin: pin layer.
        pin_length: length of the pin marker for the port.

    Keyword Args:
        layer: select ports with GDS layer.
        prefix: select ports with port name.
        orientation: select ports with orientation in degrees.
        width: select ports with port width.
        layers_excluded: List of layers to exclude.
        port_type: select ports with port_type (optical, electrical, vertical_te).
        clockwise: if True, sort ports clockwise, False: counter-clockwise.
    """
    for p in component.get_ports_list(port_type=port_type, **kwargs):
        function(component=component, port=p, layer=layer_pin, pin_length=pin_length)

    return component


add_pins_siepic_metal = partial(
    add_pins_siepic, port_type="placement", layer_pin=LAYER.PORTE
)


[docs]def add_pins_bbox_siepic( component: Component, function: Callable = add_pin_path, port_type: str = "optical", layer_pin: Layer = LAYER.PORT, pin_length: float = pin_length, bbox_layer: Layer = LAYER.DEVREC, padding: float = 0, remove_layers: bool = False, ) -> Component: """Add bounding box device recognition layer. Args: component: to add pins. function: to add pins. port_type: optical, electrical... layer_pin: for pin. pin_length: in um. bbox_layer: bounding box layer. padding: around device. remove_layers: removes old layers. """ c = component if remove_layers: remove_layers = (layer_pin, bbox_layer, "TEXT") c = c.remove_layers(layers=remove_layers) if bbox_layer not in c.layers: c.add_padding(default=padding, layers=(bbox_layer,)) if layer_pin not in c.layers: c = add_pins_siepic( component=component, function=function, port_type=port_type, layer_pin=layer_pin, pin_length=pin_length, ) return c
add_pins_bbox_siepic_metal = partial( add_pins_bbox_siepic, port_type="placement", layer_pin=LAYER.PORTE ) def get_layer_stack( thickness_wg: float = 220 * nm, zmin_heater: float = 1.1, thickness_heater: float = 700 * nm, thickness_metal2: float = 700 * nm, substrate_thickness: float = 10.0, box_thickness: float = 3.0, ) -> LayerStack: """Returns generic LayerStack. based on paper https://www.degruyter.com/document/doi/10.1515/nanoph-2013-0034/html Args: thickness_wg: waveguide thickness in um. zmin_heater: TiN heater. thickness_heater: TiN thickness. zmin_metal2: metal2. thickness_metal2: metal2 thickness. substrate_thickness: substrate thickness in um. box_thickness: bottom oxide thickness in um. """ return LayerStack( layers=dict( substrate=LayerLevel( layer=LAYER.WAFER, thickness=substrate_thickness, zmin=-substrate_thickness - box_thickness, material="si", info={"mesh_order": 99}, ), box=LayerLevel( layer=LAYER.WAFER, thickness=box_thickness, zmin=-box_thickness, material="sio2", info={"mesh_order": 99}, ), clad=LayerLevel( layer=LAYER.WAFER, thickness=zmin_heater + thickness_heater, zmin=0, material="sio2", info={"mesh_order": 100}, ), core=LayerLevel( layer=LAYER.WG, thickness=thickness_wg, zmin=0.0, material="si", info={"mesh_order": 1}, sidewall_angle=10, width_to_z=0.5, ), core2=LayerLevel( layer=LAYER.WG2, thickness=thickness_wg, zmin=0.0, material="si", info={"mesh_order": 1}, sidewall_angle=10, width_to_z=0.5, ), heater=LayerLevel( layer=LAYER.M1_HEATER, thickness=750e-3, zmin=zmin_heater, material="TiN", info={"mesh_order": 1}, ), metal2=LayerLevel( layer=LAYER.M2_ROUTER, thickness=thickness_metal2, zmin=zmin_heater + thickness_heater, material="Aluminum", info={"mesh_order": 2}, ), ) ) class Tech(BaseModel): fiber_array_spacing: float = 250.0 wg_width: float = 0.5 TECH = Tech() LAYER_STACK = get_layer_stack() LAYER_VIEWS = gf.technology.LayerViews(PATH.lyp_yaml) strip_wg_simulation_info = dict( model="ebeam_wg_integral_1550", layout_model_property_pairs=dict( # interconnect_property_name=(layout_property_name, scaling_value) wg_length=("length", 1e-6), wg_width=("width", 1e-6), ), layout_model_port_pairs=dict(o1="port 1", o2="port 2"), properties=dict(annotate=False), ) # cladding_layers_optical_siepic = ("DEVREC",) # for SiEPIC verification # cladding_offsets_optical_siepic = (0.5,) # for SiEPIC verification cladding_layers_optical_siepic = None cladding_offsets_optical_siepic = None ############################ # Cross-sections functions ############################ cross_section = partial( gf.cross_section.cross_section, radius_min=5, ) strip = partial( cross_section, cladding_layers=cladding_layers_optical_siepic, cladding_offsets=cladding_offsets_optical_siepic, ) strip_unclad = strip_simple = cross_section strip_heater_metal = partial( gf.cross_section.strip_heater_metal, layer="WG", heater_width=2.5, layer_heater=LAYER.M1_HEATER, cladding_layers=cladding_layers_optical_siepic, cladding_offsets=cladding_offsets_optical_siepic, ) strip_bbox = partial( cross_section, bbox_layers=cladding_layers_optical_siepic, bbox_offsets=cladding_offsets_optical_siepic, ) metal_routing = partial( cross_section, layer=LAYER.M2_ROUTER, width=10.0, port_names=gf.cross_section.port_names_electrical, port_types=gf.cross_section.port_types_electrical, radius=None, ) heater_metal = partial(metal_routing, width=4, layer=LAYER.M1_HEATER) ############################ # Cross-sections ############################ xs_sc = strip() xs_sc_heater_metal = strip_heater_metal() xs_sc_bbox = strip_bbox() xs_metal_routing = metal_routing() xs_heater_metal = heater_metal() xs_sc_unclad = strip_unclad() xs_sc_simple = strip_simple() xs_sc_devrec = strip(cladding_layers=("DEVREC",), cladding_offsets=(0.5,)) cross_sections = get_cross_sections(sys.modules[__name__]) if __name__ == "__main__": # LAYER_VIEWS = gf.technology.LayerViews(filepath=PATH.lyp) # LAYER_VIEWS.to_yaml(PATH.layers_yaml) # LAYER_VIEWS = gf.technology.LayerViews(PATH.lyp_yaml) LAYER_VIEWS.to_lyp(PATH.lyp) # c = gf.c.mzi() # c = gf.c.straight(length=1, cross_section=strip) # c = gf.c.bend_euler(cross_section=strip) c = gf.c.mzi(delta_length=10, cross_section=strip) c.show(show_ports=False)