Files
mxpic_forge/mxpic/components/primitives/active/pin_wg.py
T
2026-06-04 23:21:39 +08:00

402 lines
16 KiB
Python

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:
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'])
nd.Pin(name='a1',pin=tp_L.pin['b0']).put()
nd.Pin(name='b1',pin=tp_R.pin['b0']).put()
else:
nd.Pin(name='a1',pin=core.pin['a0']).put()
nd.Pin(name='b1',pin=core.pin['b0']).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():
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 <WGDoped>, input parameter doesn't have same length (w_p & xs_p)")
if (len(xs_n)!=len(w_n)):
raise Exception("In <WGDoped>, 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_<len(self.w_n)-1)*self.dope_ovlp,length=self.L_wg).put(0,_y_)
_y_ = _y_ + self.w_n[_idx_dop_]/2
yContN = _y_ - self.w_n[-1]/2
""" Placing Doping areas LOWER """
_y_ =-self.w_i/2 + self.dope_offset
for _idx_dop_ in range(len(self.w_p)):
_y_ = _y_ - self.w_p[_idx_dop_]/2
nd.strt(xs=self.xs_p[_idx_dop_],width=self.w_p[_idx_dop_]+int(_idx_dop_>0)*int(_idx_dop_<len(self.w_p)-1)*self.dope_ovlp,length=self.L_wg).put(0,_y_)
_y_ = _y_ - self.w_p[_idx_dop_]/2
yContP = _y_ + self.w_p[-1]/2
""" Adding contact waveguide """
if (self.xs_cont_wg is not None):
nd.strt(xs=self.xs_cont_wg,width=w_n_cont,length=self.L_wg).put(0,yContN,0)
nd.strt(xs=self.xs_cont_wg,width=w_p_cont,length=self.L_wg).put(0,yContP,0)
""" Adding central waveguide """
w_slab_max = 2*np.max([np.sum(self.w_n)+self.dope_offset,np.sum(self.w_p)-self.dope_offset]) + self.w_i
for layers,growx,growy,acc in nd.layeriter(xs=self.xs_wg):
(a1,b1), (a2,b2),c1,c2 = growx
print(growy)
if (b1==0 and b2==0 and layers.find("COR")!=-1): ## this is the core of the waveguide
coreIN = nd.taper(length=self.Ltp_port,width1=self.w_port,width2=self.w_wg,layer=layers).put(0,0,0)
core = nd.strt(length=self.L_wg - self.Ltp_port*2,width=self.w_wg,layer=layers).put()
coreOUT = nd.taper(length=self.Ltp_port,width2=self.w_port,width1=self.w_wg,layer=layers).put()
elif (layers.find("COR")!=-1): ## this is the core of the slab area
clad = nd.strt(length=self.L_wg,width=np.max([w_slab_max+b1-b2,self.w_wg+b1-b2]),layer=layers).put(0,0,0)
""" Adding Heaters """
if (self.w_ht>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'])
xs1.raise_pins(['b0'],['a1'])
xs2.raise_pins(['b0'],['b1'])
else:
coreIN.raise_pins(['a0'],['b1'])
coreOUT.raise_pins(['b0'],['a1'])
# nd.put_stub()
return C