Files
mxpic_forge/mxpic/components/primitives/EC_dual_layer_px3.py
T
2026-05-17 11:58:41 +08:00

252 lines
11 KiB
Python

import nazca as nd
import numpy as np
from mxpic.structures import _my_polygon
# from ....structures import _my_polygon
class EC_dual_layer_px3():
"""
Dual-layer Edge Coupler (Spot Size Converter) for fiber-to-chip coupling.
This component manages the adiabatic transition between a high-index
contrast core and a secondary slab/cladding layer (e.g., SiN to SOI).
Parameters
----------
name : str
Unique identifier for the device cell.
w_in : float
Input waveguide width in microns.
L_in : float
Length of the initial input section.
Ltp1, Ltp2, Ltp3 : float
Lengths of the first, second, and third taper sections respectively.
L_end : float, optional
Length of the final facet extension (default is 0).
w_tip_core : float, optional
Width of the core taper tip in microns (default is 0.2).
w1_slab : float, optional
Initial width of the slab/cladding layer.
w_tip_slab : float, optional
Width of the slab taper tip at the facet.
w_mid_slab : float, optional
Width of the slab at the transition midpoint.
w_box, w_box_end, L_box_end : float, optional
Dimensions for the deep-trench/oxide box clearing.
w_DT : float, optional
Deep Trench width.
xs_SiN : str, optional
Cross-section name for the Nitride layer (default is "sin").
layer_SiN_slab : str, optional
GDS layer name for the SiN slab.
layer_DT : str, optional
GDS layer for the deep trench/oxide facet.
xs_Trench : str, optional
Cross-section name for the air trench.
layer_top_cover : str, optional
GDS layer for the optical pad/top cladding opening.
layer_dum_exl_be : str, optional
Layer for dummy exclusion (BEOL).
angle_tile : float, optional
Facet tilt angle in degrees to reduce back-reflection (default is 8).
R_bend : float, optional
Radius of curvature for associated routing bends (default is 50).
"""
def __init__(self,
name: str = None,
w_in: float = 1.0,
L_in: float = 15,
Ltp1: float = 100,
Ltp2: float = 200,
Ltp3: float = 400,
L_end: float = 0,
w_tip_core: float = 0.2,
w1_slab: float = 0.6,
w_tip_slab: float = 0.2,
w_mid_slab: float = 0.45,
w_box: float = 8,
w_box_end: float = 12,
L_box_end: float = 2,
w_DT: float = 12,
xs_SiN: str = "sin",
layer_SiN_slab: str = "SiN_Rib_WG",
layer_DT: str = "OXIDE_FACET",
xs_Trench: str = "air_trench",
layer_top_cover: str = "PAD_OPTICAL",
layer_dum_exl_be:str=None,
angle_tile: float = 8,
R_bend: float = 50,
):
""""""
""" This is the instruction for building a sample """
self.name = name
if (self.name is None): self.instantiate = False
else: self.instantiate = True
self.w_in = w_in
self.L_in = L_in
self.Ltp1 = Ltp1
self.Ltp2 = Ltp2
self.Ltp3 = Ltp3
self.L_end = L_end
self.w_tip_core = w_tip_core
self.w1_slab = w1_slab
self.w_tip_slab = w_tip_slab
self.w_mid_slab = w_mid_slab
self.w_box = w_box
self.w_box_end = w_box_end
self.L_box_end = L_box_end
self.w_DT = w_DT
self.xs_SiN = xs_SiN
self.layer_SiN_slab = layer_SiN_slab
self.layer_top_cover = layer_top_cover
self.layer_dum_exl_be = layer_dum_exl_be
self.layer_DT = layer_DT
self.xs_Trench = xs_Trench
self.angle_tile = angle_tile
self.R_bend = R_bend
self.cell = self.generate_gds()
def generate_gds(self):
""" central core """
with nd.Cell(instantiate=False) as EC_core:
""" === 1.1 Building core waveguide === """
t0 = nd.strt(xs=self.xs_SiN,length=self.L_in,width=self.w_in).put(0,0,0)
t1 = nd.taper(xs=self.xs_SiN,length=self.Ltp1,width1=self.w_in,width2=self.w1_slab).put()
t2 = nd.taper(xs=self.xs_SiN,length=self.Ltp2,width1=self.w1_slab,width2=self.w_mid_slab).put()
t3 = nd.taper(xs=self.xs_SiN,length=self.Ltp3,width1=self.w_mid_slab,width2=self.w_tip_slab).put()
nd.Pin(name="b0").put(t3.pin['b0'].x+self.L_end,t3.pin['b0'].y,0)
port = nd.bend(radius=self.R_bend,width=self.w_in,angle=self.angle_tile,xs=self.xs_SiN).put(t0.pin['a0'],flip=1)
""" Adding dummy exclusions """
L_EC = self.Ltp1+self.Ltp2+self.Ltp3+self.L_end
nd.strt(layer = self.layer_dum_exl_be,width=75,length=self.L_in + L_EC).put(0,0,0)
port.raise_pins(['b0','b0'],['a0','a1'])
""" === 1.2 Building cladding waveguide === """
w_etch = 4
y_shift = (self.w_tip_core/2-self.w_in/2)
## etching of the 400 nm region
nd.taper(layer=self.layer_SiN_slab,width1=w_etch,width2=w_etch,shift=y_shift,
length=self.Ltp1).put(t1.pin['b0'].x,t1.pin['b0'].y+self.w_tip_core/2+w_etch/2,180)
nd.taper(layer=self.layer_SiN_slab,width1=w_etch,width2=w_etch/2,shift=0,
length=self.L_in).put()
nd.taper(layer=self.layer_SiN_slab,width1=w_etch,width2=w_etch,shift=y_shift,
length=self.Ltp1).put(t1.pin['b0'].x,t1.pin['b0'].y-self.w_tip_core/2-w_etch/2,180,flip=1)
nd.taper(layer=self.layer_SiN_slab,width1=w_etch,width2=w_etch/2,shift=0,
length=self.L_in).put()
## etching of the 200 nm region
nd.taper(layer=self.layer_SiN_slab,width1=self.w_tip_core+2*w_etch,width2=w_etch+self.w_tip_slab,
length=self.Ltp2+self.Ltp3+1).put(t1.pin['b0'])
## patch
nd.strt(layer=self.layer_SiN_slab,width=self.w_tip_core+2*w_etch,
length=0.1).put(t1.pin['b0'].move(-0.05,0,0))
""" Corener patch """
angle_arc = self.angle_tile/180*np.pi
""" === 1.3 Building Air trenches === """
for layers,growx,growy,acc in nd.layeriter(xs=self.xs_Trench):
(a1,b1), (a2,b2),c1,c2 = growx
if (b1==0 and b2==0):
x_up = [0,
L_EC-self.L_box_end + self.w_box/2*np.tan(angle_arc),
L_EC-self.L_box_end + self.w_box_end/2*np.tan(angle_arc),
L_EC + self.w_box_end/2*np.tan(angle_arc),
L_EC + (self.w_box_end/2+self.w_DT)*np.tan(angle_arc),
L_EC + (self.w_box_end/2+self.w_DT)*np.tan(angle_arc) - self.L_box_end - self.w_DT,
L_EC + (self.w_box_end/2+self.w_DT)*np.tan(angle_arc) - self.L_box_end - self.w_DT - (self.w_box_end-self.w_box)/2*np.tan(angle_arc),
0]
y_up = [-self.w_DT/2,
-self.w_DT/2,
-self.w_DT/2 + (self.w_box_end-self.w_box)/2,
-self.w_DT/2 + (self.w_box_end-self.w_box)/2,
-self.w_DT/2 + (self.w_box_end-self.w_box)/2+self.w_DT,
-self.w_DT/2 + (self.w_box_end-self.w_box)/2+self.w_DT,
self.w_DT/2,
self.w_DT/2,
]
_my_polygon(layer_wg=layers,vtx=np.c_[x_up,y_up]).put(self.L_in,self.w_box/2+self.w_DT/2,0)
x_down = [0,
L_EC-self.L_box_end - self.w_box/2*np.tan(angle_arc),
L_EC-self.L_box_end - self.w_box_end/2*np.tan(angle_arc),
L_EC - self.w_box_end/2*np.tan(angle_arc),
L_EC - (self.w_box_end/2+self.w_DT)*np.tan(angle_arc),
L_EC - (self.w_box_end/2+self.w_DT)*np.tan(angle_arc) - self.L_box_end - self.w_DT,
L_EC - (self.w_box_end/2+self.w_DT)*np.tan(angle_arc) - self.L_box_end - self.w_DT + (self.w_box_end-self.w_box)/2*np.tan(angle_arc),
0]
y_down = [self.w_DT/2,
self.w_DT/2,
self.w_DT/2 - (self.w_box_end-self.w_box)/2,
self.w_DT/2 - (self.w_box_end-self.w_box)/2,
self.w_DT/2 - (self.w_box_end-self.w_box)/2-self.w_DT,
self.w_DT/2 - (self.w_box_end-self.w_box)/2-self.w_DT,
-self.w_DT/2,
-self.w_DT/2,
]
_my_polygon(layer_wg=layers,vtx=np.c_[x_down,y_down]).put(self.L_in,-self.w_box/2-self.w_DT/2,0)
else:
dx = (self.w_box_end/2 + self.w_DT+b1)*np.tan(angle_arc)
dy = self.w_DT+self.w_box_end/2+b1
if (self.angle_tile !=0 ):
x_up = [-b1,
L_EC - 2*dx-(dy*dy/dx),
L_EC - 2*dx,
L_EC - dx,
L_EC + dx,
-b1]
y_up = [-dy,
-dy,
-2*dy,
-dy,
dy,
dy,
]
else:
x_up = [-b1,
L_EC - dx,
L_EC + dx,
-b1]
y_up = [-dy,
-dy,
dy,
dy,
]
_my_polygon(layer_wg=layers,vtx=np.c_[x_up,y_up]).put(self.L_in,0,0)
""" Corener patch """
nd.strt(xs=self.xs_Trench)
with nd.Cell(name=self.name,instantiate=self.instantiate) as C:
inst = EC_core.put('b0',0,0,180+self.angle_tile)
inst.raise_pins(['a1','a1'])
nd.Pin(name="b0").put(0,0,0)
return C