from typing import Any, Optional import nazca as nd import numpy as np from ..pic import taper_xs2xs from ...electronics import Vias import nazca.interconnects as IC from ...basic import __list_convert__,__array_convert__ class waveguide_PIN: """ waveguide PIN primitive component. This component builds the waveguide PIN layout cell. Parameters ---------- xs : str, optional Layer or cross-section name used by the device. Default is 'rib'. L_wg : float, optional Length parameter in microns. Default is 100. w_wg : float, optional Width parameter in microns. Default is 1.2. xs_pn_ct : str, optional Layer or cross-section name used by the device. Default is 'strip_cor'. w_itr : float, optional Width parameter in microns. Default is 2.0. w_p : float, optional Width parameter in microns. Default is 4.0. w_n : float, optional Width parameter in microns. Default is 4.0. w_p_ct : float, optional Width parameter in microns. Default is 4.0. w_n_ct : float, optional Width parameter in microns. Default is 4.0. xs_heater : str, optional Layer or cross-section name used by the device. Default is 'heater'. xs_via_h2m : str, optional Layer or cross-section name used by the device. Default is 'via_h2m'. w_metal_pn : float, optional Width parameter in microns. Default is 8. sz_via_i2m : float, optional Value for the sz_via_i2m parameter. Default is 0.25. sp_via_i2m : float, optional Spacing or gap parameter in microns. Default is 0.35. sp_sc : float, optional Spacing or gap parameter in microns. Default is 1. xs_p : str, optional Layer or cross-section name used by the device. Default is 'pw'. xs_n : str, optional Layer or cross-section name used by the device. Default is 'nw'. xs_pcont : str, optional Layer or cross-section name used by the device. Default is 'pp'. xs_ncont : str, optional Layer or cross-section name used by the device. Default is 'np'. xs_sa : str, optional Layer or cross-section name used by the device. Default is 'sa'. xs_via_s2m : str, optional Layer or cross-section name used by the device. Default is 'via_s2m'. xs_metal : str, optional Layer or cross-section name used by the device. Default is 'metal'. rib_taper : bool, optional Value for the rib_taper parameter. Default is True. L_taper : float, optional Length parameter in microns. Default is 30. xs_port : str, optional Layer or cross-section name used by the device. Default is 'strip'. show_pins : bool, optional Whether to draw pin markers in the generated layout. Default is True. """ def __init__(self, xs: str="rib", ## note, this xsection cannot directly mixed iwth 'rib' L_wg: float = 100, w_wg: float = 1.2, xs_pn_ct: str = 'strip_cor', w_itr : float = 2.0, w_p : float = 4.0, w_n : float = 4.0, w_p_ct : float = 4.0, w_n_ct : float = 4.0, xs_heater: str = 'heater', xs_via_h2m: str = 'via_h2m', w_metal_pn: float = 8, sz_via_i2m: float = 0.25, sp_via_i2m: float = 0.35, sp_sc: float = 1, xs_p : str = 'pw', xs_n : str = 'nw', xs_pcont : str = 'pp', xs_ncont : str = 'np', xs_sa : str = 'sa', xs_via_s2m: str = 'via_s2m', xs_metal: str = 'metal', rib_taper: bool = True, L_taper: float = 30, xs_port: str = 'strip', show_pins: bool = True, ) -> None: self.xs = xs self.L_wg = L_wg self.w_wg = w_wg self.w_itr = w_itr self.xs_p = __list_convert__(xs_p,'xs_p','mxpic::active::rings') self.xs_n = __list_convert__(xs_n,'xs_n','mxpic::active::rings') self.w_p = __array_convert__(w_p,'w_p','mxpic::active::rings') self.w_n = __array_convert__(w_n,'w_n','mxpic::active::rings') # self.w_p = w_p # self.w_n = w_n # self.xs_n = xs_n # self.xs_p = xs_p self.w_p_ct = w_p_ct self.w_n_ct = w_n_ct self.xs_pn_ct = xs_pn_ct self.xs_heater = xs_heater self.xs_via_h2m = xs_via_h2m if (w_metal_pn==None or w_metal_pn>min([w_n_ct,w_p_ct])-2*sp_sc): w_metal_pn = min([w_n_ct,w_p_ct])-2*sp_sc w_metal_pn = max([0,w_metal_pn]) self.w_metal_pn = w_metal_pn ## w_metal_pn <= w_p_ct - 2*sp_ct self.sz_via_i2m = sz_via_i2m self.sp_via_i2m = sp_via_i2m self.xs_p = xs_p self.xs_n = xs_n self.xs_pcont = xs_pcont self.xs_ncont = xs_ncont self.xs_sa = xs_sa self.sp_sc = sp_sc self.xs_via_s2m = xs_via_s2m self.xs_metal = xs_metal self.rib_taper = rib_taper self.L_taper = L_taper self.xs_port = xs_port self.cell = self.generate_gds(show_pins) def generate_gds(self,show_pins=False): with nd.Cell(instantiate=False) as C: nd.add_xsection(name='temp') for layers,growx,growy,acc in nd.layeriter(xs=self.xs): (a1,b1), (a2,b2),c1,c2 = growx if (b1==0 and b2==0 and layers.find("COR")!=-1): ## this is the core of the waveguide w_total = self.w_wg core = nd.strt(length=self.L_wg,width=self.w_wg,layer=layers).put(0,0,0) elif (layers.find("COR")!=-1): ## this is the core of the slab area ## revised in 2023.1.4, w_n chaned into list or array w_total = self.w_itr + sum(self.w_n) + sum(self.w_p) + self.w_n_ct + self.w_p_ct # w_total = self.w_itr + self.w_n + self.w_p + self.w_n_ct + self.w_p_ct y_center = (-(sum(self.w_n) + self.w_n_ct) + sum(self.w_p) + self.w_p_ct)/2 nd.strt(length=self.L_wg,width=w_total,layer=layers).put(0,y_center,0) else : w_total = (self.w_itr + sum(self.w_n) + sum(self.w_p) + self.w_n_ct + self.w_p_ct)*(a1-a2)+(b1-b2) # w_total = (self.w_itr + self.w_n + self.w_p + self.w_n_ct + self.w_p_ct)*(a1-a2)+(b1-b2) y_center = (-(sum(self.w_n) + self.w_n_ct) + sum(self.w_p) + self.w_p_ct)/2 nd.strt(length=self.L_wg,width=w_total,layer=layers).put(0,y_center,0) nd.add_layer2xsection(xsection='temp',layer=layers,leftedge=(a1,(w_total-self.w_wg)/2),rightedge=(a2,-(w_total-self.w_wg)/2)) if (self.rib_taper): tp_L = taper_xs2xs(xs_1='temp',xs_2=self.xs_port,L_taper=self.L_taper,w_1=self.w_wg,w_2=self.w_wg).cell.put(core.pin['a0']) tp_R = taper_xs2xs(xs_1='temp',xs_2=self.xs_port,L_taper=self.L_taper,w_1=self.w_wg,w_2=self.w_wg).cell.put(core.pin['b0']) ## revised in 2026.06.07 by Qin Yue # legacy: nd.Pin(name='a1',pin=tp_L.pin['b0']).put() nd.Pin(name='opt_a1',pin=tp_L.pin['b0'],type="optical:").put() ## revised in 2026.06.07 by Qin Yue # legacy: nd.Pin(name='b1',pin=tp_R.pin['b0']).put() nd.Pin(name='opt_b1',pin=tp_R.pin['b0'],type="optical:").put() else: ## revised in 2026.06.07 by Qin Yue # legacy: nd.Pin(name='a1',pin=core.pin['a0']).put() nd.Pin(name='opt_a1',pin=core.pin['a0'],type="optical:").put() ## revised in 2026.06.07 by Qin Yue # legacy: nd.Pin(name='b1',pin=core.pin['b0']).put() nd.Pin(name='opt_b1',pin=core.pin['b0'],type="optical:").put() ## adding p doping area _y_ = self.w_itr/2 for itn in range(0,len(self.w_p)): _y_ = _y_ + self.w_p[itn]/2 nd.strt(length=self.L_wg,width=self.w_p[itn],xs=self.xs_p[itn]).put(0,_y_,0) _y_ = _y_ + self.w_p[itn]/2 if (self.xs_pn_ct!=None): nd.strt(length=self.L_wg,width=self.w_p_ct,xs=self.xs_pn_ct).put(0, _y_+self.w_p_ct/2,0) _y_ = -self.w_itr/2 for itn in range(0,len(self.w_n)): _y_ = _y_ - self.w_n[itn]/2 nd.strt(length=self.L_wg,width=self.w_n[itn],xs=self.xs_n[itn]).put(0,_y_,0) _y_ = _y_ - self.w_n[itn]/2 if (self.xs_pn_ct!=None): nd.strt(length=self.L_wg,width=self.w_n_ct,xs=self.xs_pn_ct).put(0, _y_-self.w_p_ct/2,0) # nd.strt(length=self.L_wg,width=self.w_p_ct,xs=self.xs_pcont).put(0, self.w_itr/2+self.w_p+self.w_p_ct/2,0) # if (self.xs_pn_ct!=None): # nd.strt(length=self.L_wg,width=self.w_p_ct,xs=self.xs_pn_ct).put(0, self.w_itr/2+self.w_p+self.w_p_ct/2,0) # y_ct = self.w_itr/2+self.w_p+self.w_p_ct - self.w_metal_pn/2-self.sp_sc # via_p_ct = Vias(xs=self.xs_via_s2m,sz=self.sz_via_i2m,spacing=self.sp_via_i2m,area=[self.L_wg-self.sp_sc*2,self.w_metal_pn], # xs_l1=self.xs_sa,xs_l2=self.xs_metal).cell.put(self.L_wg/2,y_ct,0) # nd.Pin(name='ep1',pin=via_p_ct.pin['a0']).put() # ## adding n doping area # nd.strt(length=self.L_wg,width=self.w_n,xs=self.xs_n).put(0,-self.w_itr/2-self.w_n/2,0) # nd.strt(length=self.L_wg,width=self.w_n_ct,xs=self.xs_ncont).put(0,-self.w_itr/2-self.w_n-self.w_n_ct/2,0) # if (self.xs_pn_ct!=None): # nd.strt(length=self.L_wg,width=self.w_p_ct,xs=self.xs_pn_ct).put(0, -self.w_itr/2-self.w_n-self.w_n_ct/2,0) # y_ct = -self.w_itr/2-self.w_n-self.w_n_ct + self.w_metal_pn/2+self.sp_sc # via_n_ct = Vias(xs=self.xs_via_s2m,sz=self.sz_via_i2m,spacing=self.sp_via_i2m,area=[self.L_wg-self.sp_sc*2,self.w_metal_pn], # xs_l1=self.xs_sa,xs_l2=self.xs_metal).cell.put(self.L_wg/2,y_ct,0) # nd.Pin(name='en1',pin=via_n_ct.pin['a0']).put() if (show_pins): nd.put_stub() return C class WGDoped(): """ WGDoped primitive component. This component builds the WGDoped layout cell. Parameters ---------- name : Optional[str], optional Unique identifier for the device cell. Default is None. w_wg : float, optional Width parameter in microns. Default is 0.5. w_port : float, optional Width parameter in microns. Default is 0.5. Ltp_port : int, optional Length parameter in microns. Default is 10. L_wg : int, optional Length parameter in microns. Default is 200. xs_wg : str, optional Layer or cross-section name used by the device. Default is 'rib'. xs_n : list, optional Layer or cross-section name used by the device. Default is ['nld', 'np']. xs_p : list, optional Layer or cross-section name used by the device. Default is ['pld', 'pp']. w_n : list, optional Width parameter in microns. Default is [0.5, 1]. w_p : list, optional Width parameter in microns. Default is [0.5, 1]. w_ht : float, optional Width parameter in microns. Default is 0. L_ht : Any, optional Length parameter in microns. Default is None. xs_ht : str, optional Layer or cross-section name used by the device. Default is 'heater'. w_mt : Optional[float], optional Width parameter in microns. Default is None. xs_mt : str, optional Layer or cross-section name used by the device. Default is 'metal'. xs_cont_wg : Optional[str], optional Layer or cross-section name used by the device. Default is None. w_i : Optional[float], optional Width parameter in microns. Default is None. dope_offset : int, optional Value for the dope_offset parameter. Default is 0. via_s2m : Any, optional Value for the via_s2m parameter. Default is None. via_h2m : Any, optional Via definition used between heater and metal layers. Default is None. dope_ovlp : float, optional Value for the dope_ovlp parameter. Default is 0.2. cell_xs_transition : Any, optional Cell or component dependency used by this device. Default is None. """ def __init__(self, name: Optional[str]=None, w_wg: float = 0.5, w_port: float = 0.5, Ltp_port: int = 10, L_wg: int = 200, xs_wg: str = "rib", xs_n: list = ['nld','np'], xs_p: list = ['pld','pp'], w_n: list = [0.5,1], w_p: list = [0.5,1], w_ht: float = 0, L_ht: Any = None, xs_ht: str = "heater", w_mt: Optional[float] = None, xs_mt: str = "metal", xs_cont_wg: Optional[str] = None, w_i: Optional[float] = None, dope_offset: int = 0, via_s2m: Any = None, via_h2m: Any = None, dope_ovlp: float = 0.2, cell_xs_transition: Any = None, ) -> None: """_summary_ Args: w_wg (float, optional): Core width of the waveguide. Defaults to 0.5. xs_wg (str, optional): cross-section definition of the waveguide. Defaults to "rib". xs_n (list, optional): _description_. Defaults to ['nld','np']. xs_p (list, optional): _description_. Defaults to ['pld','pp']. w_n (list, optional): _description_. Defaults to [0.5,1]. w_p (list, optional): _description_. Defaults to [0.5,1]. w_i (_type_, optional): _description_. Defaults to None. dope_offset (int, optional): Offset value of the PN junction center to waveguide center. Defaults to 0. via_i2m (_type_, optional): _description_. Defaults to None. via_h2m (_type_, optional): _description_. Defaults to None. """ self.name = name if (name is None): self.instantiate = False else: self.instantiate = True """ Input value quality check """ if (len(xs_p)!=len(w_p)): raise Exception("In , input parameter doesn't have same length (w_p & xs_p)") if (len(xs_n)!=len(w_n)): raise Exception("In , input parameter doesn't have same length (w_n & xs_n)") self.w_wg = w_wg self.xs_wg = xs_wg self.xs_n = xs_n self.xs_p = xs_p self.w_n = w_n self.w_p = w_p self.xs_cont_wg = xs_cont_wg self.L_wg = L_wg self.w_mt = w_mt self.xs_mt = xs_mt self.w_ht = w_ht self.xs_ht = xs_ht self.L_ht = L_ht self.w_port = w_port self.Ltp_port = Ltp_port if (w_i is None): self.w_i = w_wg else : self.w_i = w_i self.dope_ovlp = dope_ovlp self.dope_offset = dope_offset self.via_s2m = via_s2m self.via_h2m = via_h2m self.cell_xs_transition = cell_xs_transition self.cell = self.generate_gds() def generate_gds(self): """ Generation of GDS pattern Returns: _type_: _description_ """ with nd.Cell(name=self.name,instantiate=self.instantiate) as C: Lstrt = self.L_wg - self.Ltp_port w_n_cont = self.w_n[-1] w_p_cont = self.w_p[-1] """ Placing Doping areas UPPER """ _y_ = self.w_i/2 + self.dope_offset for _idx_dop_ in range(len(self.w_n)): _y_ = _y_ + self.w_n[_idx_dop_]/2 nd.strt(xs=self.xs_n[_idx_dop_],width=self.w_n[_idx_dop_]+int(_idx_dop_>0)*int(_idx_dop_0)*int(_idx_dop_0 and self.xs_ht is not None): if (self.L_ht is None): L_ht = self.L_wg else: L_ht = self.L_ht ht = nd.strt(xs=self.xs_ht,width=self.w_ht,length=L_ht).put(self.L_wg/2-L_ht/2,0,0) if (isinstance(self.via_h2m,nd.Cell)): viaHT=self.via_h2m elif(hasattr(self.via_h2m,"cell")): viaHT=self.via_h2m.cell VL=viaHT.put(ht.pin['a0']) VR=viaHT.put(ht.pin['b0']) VL.raise_pins(['b0'],['ep3']) VR.raise_pins(['b0'],['en3']) """ Adding vias """ if (self.via_s2m is not None): via_s2m_N = Vias(xs=self.via_s2m.xs,sz=self.via_s2m.sz,spacing=self.via_s2m.spacing,sp_via_xs=self.via_s2m.sp_via_xs,xs_l1=self.via_s2m.xs_l1, area=[self.L_wg,w_n_cont]).cell.put(self.L_wg/2,yContN,0) via_s2m_P = Vias(xs=self.via_s2m.xs,sz=self.via_s2m.sz,spacing=self.via_s2m.spacing,sp_via_xs=self.via_s2m.sp_via_xs,xs_l1=self.via_s2m.xs_l1, area=[self.L_wg,w_p_cont]).cell.put(self.L_wg/2,yContP,0) yPvia = yContP yNvia = yContN wPvia = self.w_p[-1]-self.via_s2m.sp_via_xs*2 wNvia = self.w_n[-1]-self.via_s2m.sp_via_xs*2 """ Adding metals """ if (self.w_mt is not None): nd.strt(xs=self.xs_mt,length=self.w_mt,width=self.L_wg+0.2).put(self.L_wg/2,yContN-w_n_cont/2,90) nd.strt(xs=self.xs_mt,length=self.w_mt,width=self.L_wg+0.2).put(self.L_wg/2,yContP+w_p_cont/2,-90) yPvia = yContP+w_p_cont/2-self.w_mt/2 yNvia = yContN-w_n_cont/2+self.w_mt/2 wPvia = self.w_mt wNvia = self.w_mt nd.Pin(name="ep1",width=wPvia).put(0,yPvia,180) nd.Pin(name="ep2",width=wPvia).put(self.L_wg,yPvia,0) nd.Pin(name="en1",width=wNvia).put(0,yNvia,180) nd.Pin(name="en2",width=wNvia).put(self.L_wg,yNvia,0) if (self.cell_xs_transition is not None): xs1 = self.cell_xs_transition.put('a0',coreOUT.pin['b0']) xs2 = self.cell_xs_transition.put('a0',coreIN.pin['a0']) ## revised in 2026.06.07 by Qin Yue # legacy: xs1.raise_pins(['b0'],['a1']) xs1.raise_pins(['b0'],['opt_a1']) ## revised in 2026.06.07 by Qin Yue C.pin['opt_a1'].type = "optical:" ## revised in 2026.06.07 by Qin Yue # legacy: xs2.raise_pins(['b0'],['b1']) xs2.raise_pins(['b0'],['opt_b1']) ## revised in 2026.06.07 by Qin Yue C.pin['opt_b1'].type = "optical:" else: ## revised in 2026.06.07 by Qin Yue # legacy: coreIN.raise_pins(['a0'],['b1']) coreIN.raise_pins(['a0'],['opt_b1']) ## revised in 2026.06.07 by Qin Yue C.pin['opt_b1'].type = "optical:" ## revised in 2026.06.07 by Qin Yue # legacy: coreOUT.raise_pins(['b0'],['a1']) coreOUT.raise_pins(['b0'],['opt_a1']) ## revised in 2026.06.07 by Qin Yue C.pin['opt_a1'].type = "optical:" # nd.put_stub() return C