402 lines
16 KiB
Python
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|