import nazca as nd import numpy as np from ..geometry 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