772 lines
36 KiB
Python
772 lines
36 KiB
Python
|
|
from typing import Any, Optional
|
|
import nazca as nd
|
|
import numpy as np
|
|
|
|
from ...geometry import *
|
|
from ....technologies import *
|
|
from ...electronics import Vias
|
|
from ...electronics import ISL
|
|
|
|
import nazca.interconnects as IC
|
|
from ...basic import __xs_exist__,__cell_arg__
|
|
from ..pic import taper_xs2xs
|
|
from ...routing import Route
|
|
# class Route(IC.Interconnect):
|
|
# pass
|
|
|
|
|
|
class waveguide:
|
|
"""
|
|
waveguide primitive component.
|
|
|
|
This component builds the waveguide layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
w_heater : float, optional
|
|
Width parameter in microns. Default is 2.5.
|
|
L_wg : int, optional
|
|
Length parameter in microns. Default is 150.
|
|
L_heater : int, optional
|
|
Length parameter in microns. Default is 150.
|
|
w_metal : float, optional
|
|
Width parameter in microns. Default is 10.
|
|
xs_heater : str, optional
|
|
Layer or cross-section name used by the device. Default is 'heater'.
|
|
xs_metal : str, optional
|
|
Layer or cross-section name used by the device. Default is 'metal'.
|
|
xs_wg : str, optional
|
|
Layer or cross-section name used by the device. Default is 'strip'.
|
|
w_wg : float, optional
|
|
Width parameter in microns. Default is 0.45.
|
|
w_port : Optional[float], optional
|
|
Width parameter in microns. Default is None.
|
|
Ltp : Any, optional
|
|
Length parameter in microns. Default is None.
|
|
via_h2m : Any, optional
|
|
Via definition used between heater and metal layers. Default is None.
|
|
isl : Any, optional
|
|
Isolation-trench definition used by the electrical layout. Default is None.
|
|
euler_bend : bool, optional
|
|
Value for the euler_bend parameter. Default is False.
|
|
Rmin : int, optional
|
|
Radius parameter in microns. Default is 5.
|
|
thin_attach : bool, optional
|
|
Value for the thin_attach parameter. Default is False.
|
|
UPPER_ISL : bool, optional
|
|
Value for the UPPER_ISL parameter. Default is True.
|
|
LOWER_ISL : bool, optional
|
|
Length parameter in microns. Default is True.
|
|
shape : str, optional
|
|
Value for the shape parameter. Default is 'strip'.
|
|
R_bend : int, optional
|
|
Radius parameter in microns. Default is 10.
|
|
ubend_offset : int, optional
|
|
Value for the ubend_offset parameter. Default is 20.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
"""
|
|
def __init__(self,
|
|
w_heater: float=2.5,
|
|
L_wg: int=150,
|
|
L_heater: int=150,
|
|
w_metal: float=10,
|
|
|
|
xs_heater: str='heater',
|
|
xs_metal: str='metal',
|
|
xs_wg: str = 'strip',
|
|
|
|
w_wg: float = 0.45,
|
|
w_port: Optional[float] = None,
|
|
Ltp: Any = None,
|
|
|
|
via_h2m: Any = None,
|
|
isl: Any = None,
|
|
|
|
euler_bend: bool = False,
|
|
Rmin: int = 5,
|
|
thin_attach: bool = False,
|
|
UPPER_ISL: bool = True,
|
|
LOWER_ISL: bool = True,
|
|
shape: str = 'strip',
|
|
R_bend: int=10,
|
|
ubend_offset: int=20,
|
|
show_pins: bool=False) -> None:
|
|
|
|
|
|
""" Revised in 2022.12.30, to simplify the function logic """
|
|
if (w_heater>0 and xs_heater!=None and xs_metal!=None):
|
|
|
|
xs_heater = __xs_exist__(xs=xs_heater,para_name="xs_heater",func_name="mxpic::passive::waveguide")
|
|
xs_metal = __xs_exist__(xs=xs_metal,para_name="xs_metal",func_name="mxpic::passive::waveguide")
|
|
|
|
|
|
if (w_port==None):
|
|
w_port = w_wg
|
|
if (Ltp==None):
|
|
Ltp=0
|
|
|
|
""" Generating the basic via_h2m """
|
|
if (via_h2m==None):
|
|
vias = Vias(xs=None,area=w_metal,sz=0,spacing=0,xs_l1=xs_heater,xs_l2=xs_metal) ## only putting metal blocks
|
|
|
|
else:
|
|
if (hasattr(via_h2m,"cell")):
|
|
vias = via_h2m
|
|
else :
|
|
vias = Vias(xs=via_h2m.xs,area=w_metal,sz=via_h2m.sz,spacing=via_h2m.spacing,xs_l1=xs_heater,xs_l2=xs_metal) ## placing vias
|
|
|
|
if (L_heater==None):
|
|
L_heater = L_wg
|
|
### Adding the straight waveguide
|
|
if (shape=='strip'):
|
|
with nd.Cell(instantiate=False) as C:
|
|
nd.taper(length=Ltp,width1=w_port,width2=w_wg,xs=xs_wg).put(-L_wg/2,0,0)
|
|
nd.strt(length=L_wg-2*Ltp,width=w_wg,xs=xs_wg).put()
|
|
nd.taper(length=Ltp,width2=w_port,width1=w_wg,xs=xs_wg).put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',width=w_wg).put(-L_wg/2,0,180)
|
|
nd.Pin(name='opt_a1',width=w_wg,type="optical:").put(-L_wg/2,0,180)
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',width=w_wg).put( L_wg/2,0,0)
|
|
nd.Pin(name='opt_b1',width=w_wg,type="optical:").put( L_wg/2,0,0)
|
|
L_heater = np.min([L_wg,L_heater])
|
|
|
|
### placing heaters
|
|
if (w_heater>0 and xs_heater!=None and xs_metal!=None):
|
|
nd.strt(length=L_heater,width=w_heater,xs=xs_heater).put(-L_heater/2,0,0)
|
|
|
|
if (thin_attach==False):
|
|
# VIAL = vias.cell.put(-L_heater/2+w_metal/2,0,180,flip=1)
|
|
# VIAR = vias.cell.put( L_heater/2-w_metal/2,0,0)
|
|
VIAL = vias.cell.put(-L_heater/2,0,180,flip=1)
|
|
VIAR = vias.cell.put( L_heater/2,0,0)
|
|
|
|
else :
|
|
# VIAL = vias.cell.put(-L_heater/2+w_metal/2,0,180,flip=1)
|
|
# VIAR = vias.cell.put( L_heater/2-w_metal/2,0,0)
|
|
VIAL = vias.cell.put(-L_heater/2,0,180,flip=1)
|
|
VIAR = vias.cell.put( L_heater/2,0,0)
|
|
|
|
nd.Pin(name='ep1',width=w_metal,pin=VIAL.pin['b0']).put()
|
|
nd.Pin(name='en1',width=w_metal,pin=VIAR.pin['b0']).put()
|
|
|
|
if (isl!=None):
|
|
if (UPPER_ISL):
|
|
ISL(length=L_heater, ## variables
|
|
xs=isl.xs,width=isl.width,spacing=isl.spacing,Lmax=isl.Lmax ## default parameters
|
|
).cell.put(-L_heater/2,-w_metal/2-isl.width/2-isl.sp_isl_metal,0)
|
|
if (LOWER_ISL):
|
|
ISL(length=L_heater, ## variables
|
|
xs=isl.xs,width=isl.width,spacing=isl.spacing,Lmax=isl.Lmax ## default parameters
|
|
).cell.put(-L_heater/2,w_metal/2+isl.width/2+isl.sp_isl_metal,0)
|
|
|
|
### Adding the bend shape waveguide
|
|
elif (shape == 'ubend'):
|
|
with nd.Cell(instantiate=False) as C:
|
|
if (euler_bend):
|
|
bd = Clothoid(R=[R_bend,Rmin,R_bend],w=[w_wg,w_wg],A=[0,90,180],xs=xs_wg,n_points=64)
|
|
ubend_offset = bd.sz[1]
|
|
R_ht = bd.sz[1]/2
|
|
L_wg_mid = 0
|
|
L_wg_side = (L_wg-L_wg_mid)/2-R_bend
|
|
wg_in = nd.strt(length=L_wg_side,width=w_wg,xs=xs_wg).put(-ubend_offset/2,0,90)
|
|
bd.cell.put(flip=1)
|
|
wg_out = nd.strt(length=L_wg_side,width=w_wg,xs=xs_wg).put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',width=w_wg,pin=wg_in.pin['a0']).put()
|
|
nd.Pin(name='opt_a1',width=w_wg,pin=wg_in.pin['a0'],type="optical:").put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',width=w_wg,pin=wg_out.pin['b0']).put()
|
|
nd.Pin(name='opt_b1',width=w_wg,pin=wg_out.pin['b0'],type="optical:").put()
|
|
else:
|
|
R_ht = R_bend
|
|
L_wg_mid = ubend_offset-R_bend*2
|
|
L_wg_side = (L_wg-L_wg_mid-R_bend*np.pi)/2
|
|
wg_in = nd.strt(length=L_wg_side,width=w_wg,xs=xs_wg).put(-ubend_offset/2,0,90)
|
|
nd.bend(radius=R_bend,width=w_wg,xs=xs_wg).put(flip=1)
|
|
nd.strt(length=L_wg_mid,width=w_wg,xs=xs_wg).put()
|
|
nd.bend(radius=R_bend,width=w_wg,xs=xs_wg).put(flip=1)
|
|
wg_out = nd.strt(length=L_wg_side,width=w_wg,xs=xs_wg).put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',width=w_wg,pin=wg_in.pin['a0']).put()
|
|
nd.Pin(name='opt_a1',width=w_wg,pin=wg_in.pin['a0'],type="optical:").put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',width=w_wg,pin=wg_out.pin['b0']).put()
|
|
nd.Pin(name='opt_b1',width=w_wg,pin=wg_out.pin['b0'],type="optical:").put()
|
|
|
|
L_heater = np.max([L_wg_mid+R_bend+w_metal*2,L_heater])
|
|
L_heater = np.min([L_wg,L_heater])
|
|
|
|
if (w_heater>0):
|
|
|
|
L_ht_side = (L_heater-L_wg_mid)/2-R_bend
|
|
ht_in = nd.strt(length=L_ht_side,width=w_heater,xs=xs_heater).put(-ubend_offset/2,L_wg_side-L_ht_side,90)
|
|
nd.bend(radius=R_ht,width=w_heater,xs=xs_heater).put(flip=1)
|
|
nd.strt(length=(ubend_offset-R_ht*2),width=w_heater,xs=xs_heater).put()
|
|
nd.bend(radius=R_ht,width=w_heater,xs=xs_heater).put(flip=1)
|
|
ht_out = nd.strt(length=L_ht_side,width=w_heater,xs=xs_heater).put()
|
|
|
|
# nd.strt(length=w_metal,width=w_metal,xs=xs_metal).put(-ubend_offset/2,L_wg_side-L_ht_side,90)
|
|
# nd.strt(length=w_metal,width=w_metal,xs=xs_heater).put(-ubend_offset/2,L_wg_side-L_ht_side,90)
|
|
# nd.strt(length=w_metal,width=w_metal,xs=xs_metal).put( ubend_offset/2,L_wg_side-L_ht_side,90)
|
|
# nd.strt(length=w_metal,width=w_metal,xs=xs_heater).put( ubend_offset/2,L_wg_side-L_ht_side,90)
|
|
|
|
|
|
# print(xs_via,sz_via_h2m,sp_via_h2m)
|
|
VIAL = vias.cell.put(ubend_offset/2,L_wg_side-L_ht_side+w_metal/2,-90)
|
|
VIAR = vias.cell.put(-ubend_offset/2,L_wg_side-L_ht_side+w_metal/2,-90,flip=1)
|
|
|
|
nd.Pin(name='ep1',width=w_metal,pin=VIAL.pin['b0']).put()
|
|
nd.Pin(name='en1',width=w_metal,pin=VIAR.pin['b0']).put()
|
|
|
|
""" Placing Isolation trench with the parameter pack <isl> """
|
|
if (isl!=None):
|
|
## placing outer
|
|
L_side = L_ht_side + R_bend + w_heater/2 + isl.sp_isl_xs*2 - w_metal
|
|
ISL(xs=isl.xs,width=isl.w_idth,length=L_side).cell.put(-ubend_offset/2-w_metal/2-isl.sp_isl_xs,L_wg_side-L_ht_side+w_metal+isl.sp_isl_xs,90)
|
|
ISL(xs=isl.xs,width=isl.w_idth,length=L_side).cell.put( ubend_offset/2+w_metal/2+isl.sp_isl_xs,L_wg_side-L_ht_side+w_metal+isl.sp_isl_xs,90)
|
|
|
|
L_upper = isl.w_idth+2*(ubend_offset/2+w_metal/2+isl.sp_isl_xs)
|
|
Y_upper = L_wg_side-L_ht_side+w_metal+isl.sp_isl_xs+L_side-isl.w_idth/2
|
|
|
|
ISL(xs=isl.xs,width=isl.w_idth,length=L_upper).cell.put( -L_upper/2,Y_upper,0)
|
|
|
|
if ((ubend_offset/2-w_metal/2-isl.sp_isl_xs)*2-isl.w_idth>5):
|
|
L_side = L_ht_side
|
|
|
|
ISL(xs=isl.xs,width=isl.w_idth,length=L_side).cell.put(-ubend_offset/2+w_metal/2+(isl.sp_isl_xs),L_wg_side-L_ht_side,90)
|
|
ISL(xs=isl.xs,width=isl.w_idth,length=L_side).cell.put( ubend_offset/2-w_metal/2-(isl.sp_isl_xs),L_wg_side-L_ht_side,90)
|
|
|
|
|
|
if (show_pins):
|
|
nd.put_stub(pinsize=2)
|
|
self.cell = C
|
|
self.L = L_heater+w_metal*2
|
|
|
|
""" NEW CLASS:: 2023.03.21, two stage phase shifters, to replace PS_3wg """
|
|
class PS_2st:
|
|
"""
|
|
PS 2st primitive component.
|
|
|
|
This component builds the PS 2st layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
xs_wg : str, optional
|
|
Layer or cross-section name used by the device. Default is 'strip'.
|
|
w_wg : float, optional
|
|
Width parameter in microns. Default is 0.5.
|
|
w1 : float, optional
|
|
Width parameter in microns. Default is 0.7.
|
|
w2 : float, optional
|
|
Width parameter in microns. Default is 0.9.
|
|
L1 : int, optional
|
|
Length parameter in microns. Default is 10.
|
|
L2 : int, optional
|
|
Length parameter in microns. Default is 55.
|
|
L_wg : int, optional
|
|
Length parameter in microns. Default is 0.
|
|
L_tp : int, optional
|
|
Length parameter in microns. Default is 1.
|
|
L12 : Any, optional
|
|
Length parameter in microns. Default is None.
|
|
L_ht : Any, optional
|
|
Length parameter in microns. Default is None.
|
|
xs_heater : str, optional
|
|
Layer or cross-section name used by the device. Default is 'heater'.
|
|
xs_metal : str, optional
|
|
Layer or cross-section name used by the device. Default is 'metal'.
|
|
w_heater : float, optional
|
|
Width parameter in microns. Default is 2.5.
|
|
w_metal : float, optional
|
|
Width parameter in microns. Default is 8.
|
|
via_h2m : Any, optional
|
|
Via definition used between heater and metal layers. Default is None.
|
|
isl : Any, optional
|
|
Isolation-trench definition used by the electrical layout. Default is None.
|
|
UPPER_ISL : bool, optional
|
|
Value for the UPPER_ISL parameter. Default is True.
|
|
LOWER_ISL : bool, optional
|
|
Length parameter in microns. Default is True.
|
|
R_bend : int, optional
|
|
Radius parameter in microns. Default is 10.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
"""
|
|
def __init__(self,
|
|
xs_wg: str = 'strip',
|
|
w_wg: float = 0.5,
|
|
w1: float = 0.7,
|
|
w2: float = 0.9,
|
|
L1: int = 10,
|
|
L2: int = 55,
|
|
L_wg: int = 0,
|
|
L_tp: int = 1,
|
|
L12: Any = None,
|
|
|
|
L_ht: Any = None,
|
|
xs_heater: str='heater',
|
|
xs_metal: str='metal',
|
|
|
|
w_heater: float=2.5,
|
|
w_metal: float = 8,
|
|
|
|
via_h2m: Any = None,
|
|
isl: Any = None,
|
|
|
|
UPPER_ISL: bool = True,
|
|
LOWER_ISL: bool = True,
|
|
R_bend: int=10,
|
|
show_pins: bool=False) -> None:
|
|
|
|
self.xs_wg = xs_wg
|
|
self.w_wg = w_wg
|
|
self.w1 = w1
|
|
self.w2 = w2
|
|
self.L1 = L1
|
|
self.L2 = L2
|
|
self.L_wg = L_wg
|
|
self.L_tp = L_tp
|
|
self.L_ht = L_ht
|
|
self.xs_heater = xs_heater
|
|
self.xs_metal = xs_metal
|
|
self.w_heater = w_heater
|
|
self.w_metal = w_metal
|
|
self.via_h2m = via_h2m
|
|
self.isl = isl
|
|
self.R_bend = R_bend
|
|
self.show_pins = show_pins
|
|
self.R_bend = R_bend
|
|
|
|
if (L12==None):
|
|
L12 = L_tp
|
|
self.L12 = L12
|
|
self.cell = self.generate_gds()
|
|
|
|
def generate_gds(self):
|
|
with nd.Cell(instantiate=False) as C:
|
|
|
|
""" Generating the basic via_h2m """
|
|
if (self.via_h2m==None):
|
|
vias = Vias(xs=None,area=self.w_metal,sz=0,spacing=0,xs_l1=self.xs_heater,xs_l2=self.xs_metal) ## only putting metal blocks
|
|
|
|
else:
|
|
if (hasattr(self.via_h2m,"cell")):
|
|
vias = self.via_h2m
|
|
else :
|
|
vias = Vias(xs=self.via_h2m.xs,
|
|
area=self.w_metal,
|
|
sz=self.via_h2m.sz,
|
|
spacing=self.via_h2m.spacing,
|
|
xs_l1=self.xs_heater,
|
|
xs_l2=self.xs_metal) ## placing vias
|
|
|
|
|
|
pic_strip = Route(radius=self.R_bend,width=self.w_wg,xs=self.xs_wg)
|
|
|
|
start = nd.strt(length=self.L_wg,width=self.w_wg,xs=self.xs_wg).put(0,0,90)
|
|
nd.taper(length=self.L_tp,width1=self.w_wg,width2=self.w1,xs=self.xs_wg).put()
|
|
nd.strt(length=self.L1,width=self.w1,xs=self.xs_wg).put()
|
|
nd.taper(length=self.L12,width1=self.w1,width2=self.w2,xs=self.xs_wg).put()
|
|
nd.strt(length=self.L2,width=self.w2,xs=self.xs_wg).put()
|
|
nd.taper(length=self.L_tp,width1=self.w2,width2=self.w_wg,xs=self.xs_wg).put()
|
|
nd.strt(length=self.L_wg,width=self.w_wg,xs=self.xs_wg).put()
|
|
|
|
pic_strip.bend_route(angle=180).put(flip=1)
|
|
|
|
nd.strt(length=self.L_wg,width=self.w_wg,xs=self.xs_wg).put()
|
|
nd.taper(length=self.L_tp,width1=self.w_wg,width2=self.w2,xs=self.xs_wg).put()
|
|
nd.strt(length=self.L2,width=self.w2,xs=self.xs_wg).put()
|
|
nd.taper(length=self.L12,width1=self.w2,width2=self.w1,xs=self.xs_wg).put()
|
|
nd.strt(length=self.L1,width=self.w1,xs=self.xs_wg).put()
|
|
nd.taper(length=self.L_tp,width1=self.w1,width2=self.w_wg,xs=self.xs_wg).put()
|
|
end = nd.strt(length=self.L_wg,width=self.w_wg,xs=self.xs_wg).put()
|
|
|
|
L_arm = self.L_wg + self.L_tp + self.L1 + self.L12 + self.L2 + self.L_tp + self.L_wg
|
|
|
|
if (self.L_ht==None):
|
|
self.L_ht = (L_arm + self.R_bend*np.pi/2)*2
|
|
|
|
if (self.w_heater>0) :
|
|
# L_wg_side = (L_wg-L_wg_mid)/2-R_bend
|
|
|
|
# VIA = Vias(xs=self.via_h2m.xs,sz=self.via_h2m.sz,spacing=self.via_h2m.spacing,sp_via_xs=self.via_h2m.sp_via_xs,
|
|
# xs_l1=self.xs_heater,xs_l2=self.xs_metal,
|
|
# area=self.w_metal)
|
|
|
|
Ly_ht = (self.L_ht - self.R_bend*np.pi)/2
|
|
nd.strt(length=Ly_ht,width=self.w_heater,xs=self.xs_heater).put(0,L_arm-Ly_ht,90)
|
|
nd.bend(radius=self.R_bend,angle=180,xs=self.xs_heater,width=self.w_heater).put(flip=1)
|
|
nd.strt(length=Ly_ht,width=self.w_heater,xs=self.xs_heater).put()
|
|
|
|
VIA_L = vias.cell.put(0,L_arm-Ly_ht+self.w_metal/2,-90,flip=1)
|
|
VIA_R = vias.cell.put(self.R_bend*2,L_arm-Ly_ht+self.w_metal/2,-90)
|
|
|
|
# nd.Pin(name='ep1',pin=VIA_L.pin['b0'].move(0,0,-90)).put()
|
|
# nd.Pin(name='en1',pin=VIA_R.pin['b0'].move(0,0,90)).put()
|
|
nd.Pin(name='ep1',pin=VIA_L.pin['b0']).put()
|
|
nd.Pin(name='en1',pin=VIA_R.pin['b0']).put()
|
|
|
|
""" Placing Isolation trench with the parameter pack <isl> """
|
|
if (self.isl!=None):
|
|
## placing outer
|
|
L_side = abs(L_arm+self.R_bend+self.w_heater/2+self.isl.width/2+self.isl.sp_isl_metal - VIA_L.pin['b0'].y - self.w_metal/2-self.isl.sp_isl_metal)
|
|
ISL(xs=self.isl.xs,width=self.isl.width,length=L_side,spacing=self.isl.spacing,Lmax=self.isl.Lmax).cell.put( -self.isl.sp_isl_metal-self.w_heater/2-self.isl.width/2,
|
|
VIA_L.pin['b0'].y+self.w_metal/2+self.isl.sp_isl_metal,90)
|
|
|
|
ISL(xs=self.isl.xs,width=self.isl.width,length=L_side,spacing=self.isl.spacing,Lmax=self.isl.Lmax).cell.put( end.pin['b0'].x+self.isl.sp_isl_metal+self.w_heater/2+self.isl.width/2,
|
|
VIA_L.pin['b0'].y+self.w_metal/2+self.isl.sp_isl_metal,90)
|
|
|
|
L_top = abs(end.pin['b0'].x)+self.isl.width+(self.isl.sp_isl_metal+self.w_heater/2+self.isl.width/2)*2
|
|
ISL(xs=self.isl.xs,width=self.isl.width,length=L_top,spacing=self.isl.spacing,Lmax=self.isl.Lmax).cell.put( -self.isl.sp_isl_metal-self.w_heater/2-self.isl.width,
|
|
VIA_L.pin['b0'].y+self.w_metal/2+self.isl.sp_isl_metal + L_side,0)
|
|
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',pin=start.pin['a0']).put()
|
|
nd.Pin(name='opt_a1',pin=start.pin['a0'],type="optical:").put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',pin=end.pin['b0']).put()
|
|
nd.Pin(name='opt_b1',pin=end.pin['b0'],type="optical:").put()
|
|
|
|
return C
|
|
|
|
class PS_2st_Straight:
|
|
"""
|
|
PS 2st Straight primitive component.
|
|
|
|
This component builds the PS 2st Straight layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
xs_wg : str, optional
|
|
Layer or cross-section name used by the device. Default is 'strip'.
|
|
w_wg : float, optional
|
|
Width parameter in microns. Default is 0.5.
|
|
w1 : float, optional
|
|
Width parameter in microns. Default is 0.7.
|
|
w2 : float, optional
|
|
Width parameter in microns. Default is 0.9.
|
|
L1 : int, optional
|
|
Length parameter in microns. Default is 10.
|
|
L2 : int, optional
|
|
Length parameter in microns. Default is 55.
|
|
L_wg : int, optional
|
|
Length parameter in microns. Default is 0.
|
|
L_tp : int, optional
|
|
Length parameter in microns. Default is 1.
|
|
L12 : Any, optional
|
|
Length parameter in microns. Default is None.
|
|
L_ht : Any, optional
|
|
Length parameter in microns. Default is None.
|
|
xs_heater : str, optional
|
|
Layer or cross-section name used by the device. Default is 'heater'.
|
|
xs_metal : str, optional
|
|
Layer or cross-section name used by the device. Default is 'metal'.
|
|
w_heater : float, optional
|
|
Width parameter in microns. Default is 2.5.
|
|
w_metal : float, optional
|
|
Width parameter in microns. Default is 8.
|
|
via_h2m : Any, optional
|
|
Via definition used between heater and metal layers. Default is None.
|
|
isl : Any, optional
|
|
Isolation-trench definition used by the electrical layout. Default is None.
|
|
UPPER_ISL : bool, optional
|
|
Value for the UPPER_ISL parameter. Default is True.
|
|
LOWER_ISL : bool, optional
|
|
Length parameter in microns. Default is True.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
"""
|
|
def __init__(self,
|
|
xs_wg: str = 'strip',
|
|
w_wg: float = 0.5,
|
|
w1: float = 0.7,
|
|
w2: float = 0.9,
|
|
L1: int = 10,
|
|
L2: int = 55,
|
|
L_wg: int = 0,
|
|
L_tp: int = 1,
|
|
L12: Any = None,
|
|
|
|
L_ht: Any = None,
|
|
xs_heater: str='heater',
|
|
xs_metal: str='metal',
|
|
|
|
w_heater: float=2.5,
|
|
w_metal: float = 8,
|
|
|
|
via_h2m: Any = None,
|
|
isl: Any = None,
|
|
|
|
UPPER_ISL: bool = True,
|
|
LOWER_ISL: bool = True,
|
|
show_pins: bool=False) -> None:
|
|
|
|
self.xs_wg = xs_wg
|
|
self.w_wg = w_wg
|
|
self.w1 = w1
|
|
self.w2 = w2
|
|
self.L1 = L1
|
|
self.L2 = L2
|
|
self.L_wg = L_wg
|
|
self.L_tp = L_tp
|
|
self.L_ht = L_ht
|
|
self.xs_heater = xs_heater
|
|
self.xs_metal = xs_metal
|
|
self.w_heater = w_heater
|
|
self.w_metal = w_metal
|
|
self.via_h2m = via_h2m
|
|
self.isl = isl
|
|
self.show_pins = show_pins
|
|
|
|
if (L12==None):
|
|
L12 = L_tp
|
|
self.L12 = L12
|
|
self.cell = self.generate_gds()
|
|
|
|
def generate_gds(self):
|
|
with nd.Cell(instantiate=False) as C:
|
|
|
|
""" Generating the basic via_h2m """
|
|
# if (self.via_h2m==None):
|
|
# vias = Vias(xs=None,area=self.w_metal,sz=0,spacing=0,xs_l1=self.xs_heater,xs_l2=self.xs_metal).cell ## only putting metal blocks
|
|
|
|
# else:
|
|
# if (hasattr(self.via_h2m,"cell")):
|
|
# vias = self.via_h2m
|
|
# else :
|
|
# vias = Vias(xs=self.via_h2m.xs,area=self.w_metal,sz=self.via_h2m.sz,spacing=self.via_h2m.spacing,xs_l1=self.xs_heater,xs_l2=self.xs_metal) ## placing vias
|
|
|
|
|
|
|
|
start = nd.strt(length=self.L_wg,width=self.w_wg,xs=self.xs_wg).put(0,0,0)
|
|
nd.taper(length=self.L_tp,width1=self.w_wg,width2=self.w1,xs=self.xs_wg).put()
|
|
nd.strt(length=self.L1,width=self.w1,xs=self.xs_wg).put()
|
|
nd.taper(length=self.L12,width1=self.w1,width2=self.w2,xs=self.xs_wg).put()
|
|
nd.strt(length=self.L2,width=self.w2,xs=self.xs_wg).put()
|
|
nd.taper(length=self.L_tp,width1=self.w2,width2=self.w_wg,xs=self.xs_wg).put()
|
|
end = nd.strt(length=self.L_wg,width=self.w_wg,xs=self.xs_wg).put()
|
|
|
|
L_arm = self.L_wg + self.L_tp + self.L1 + self.L12 + self.L2 + self.L_tp + self.L_wg
|
|
self.L_arm = L_arm
|
|
if (self.L_ht==None):
|
|
self.L_ht = L_arm
|
|
|
|
if (self.w_heater>0) :
|
|
# L_wg_side = (L_wg-L_wg_mid)/2-R_bend
|
|
|
|
""" Generating the basic via_h2m """
|
|
if (self.via_h2m==None):
|
|
vias = Vias(xs=None,area=self.w_metal,sz=0,spacing=0,xs_l1=self.xs_heater,xs_l2=self.xs_metal) ## only putting metal blocks
|
|
|
|
else:
|
|
if (hasattr(self.via_h2m,"cell")):
|
|
vias = self.via_h2m
|
|
else :
|
|
vias = Vias(xs=self.via_h2m.xs,area=self.w_metal,sz=self.via_h2m.sz,spacing=self.via_h2m.spacing,xs_l1=self.xs_heater,xs_l2=self.xs_metal) ## placing vias
|
|
|
|
|
|
Ly_ht = (self.L_ht )/2
|
|
nd.strt(length=self.L_ht,width=self.w_heater,xs=self.xs_heater).put(self.L_arm/2 - self.L_ht/2,0,0)
|
|
|
|
VIA_L = vias.cell.put(self.L_arm/2 - self.L_ht/2,0,180)
|
|
VIA_R = vias.cell.put(self.L_arm/2 + self.L_ht/2,0,0)
|
|
|
|
nd.Pin(name='ep1',pin=VIA_L.pin['b0']).put()
|
|
nd.Pin(name='en1',pin=VIA_R.pin['b0']).put()
|
|
|
|
""" Placing Isolation trench with the parameter pack <isl> """
|
|
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',pin=start.pin['a0']).put()
|
|
nd.Pin(name='opt_a1',pin=start.pin['a0'],type="optical:").put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',pin=end.pin['b0']).put()
|
|
nd.Pin(name='opt_b1',pin=end.pin['b0'],type="optical:").put()
|
|
|
|
return C
|
|
|
|
class PSR_1x2:
|
|
"""
|
|
PSR 1x2 primitive component.
|
|
|
|
This component builds the PSR 1x2 layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
PSR : Any
|
|
Polarization splitter-rotator cell or component used by this composite.
|
|
MDM : Any
|
|
Mode multiplexer/demultiplexer cell or component used by this composite.
|
|
xs : str, optional
|
|
Layer or cross-section name used by the device. Default is 'strip'.
|
|
w_wg : float, optional
|
|
Width parameter in microns. Default is 0.45.
|
|
L_tp : int, optional
|
|
Length parameter in microns. Default is 15.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
"""
|
|
def __init__(self,PSR: Any,MDM: Any,xs: str='strip',w_wg: float=0.45,L_tp: int=15,show_pins: bool=False) -> None:
|
|
self.w_wg = w_wg
|
|
|
|
PSR_cell = __cell_arg__(arg=PSR,arg_name="PSR",func_name="mxpicp::functional::PSR_1x2")
|
|
MDM_cell = __cell_arg__(arg=MDM,arg_name="MDM",func_name="mxpicp::functional::PSR_1x2")
|
|
|
|
with nd.Cell(instantiate=False) as C:
|
|
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: PSR_inst = PSR_cell.put('a1',0,0,0)
|
|
PSR_inst = PSR_cell.put('opt_a1',0,0,0)
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: MDM_inst = MDM_cell.put('b1',PSR_inst.pin['b1'].x+L_tp,PSR_inst.pin['b1'].y,PSR_inst.pin['b1'].a)
|
|
MDM_inst = MDM_cell.put('opt_b1',PSR_inst.pin['opt_b1'].x+L_tp,PSR_inst.pin['opt_b1'].y,PSR_inst.pin['opt_b1'].a)
|
|
|
|
# taper_xs2xs(xs_1=MDM_inst.pin['b1'].xs,xs_2=PSR_inst.pin['b1'].xs,w_1=MDM_inst.pin['b1'].width,w_2=PSR_inst.pin['b1'].width,L_taper=L_tp).cell.put(MDM_inst.pin['b1'])
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.taper(length=L_tp,width1=PSR_inst.pin['b1'].width,width2=MDM_inst.pin['b1'].width,xs=xs).put(PSR_inst.pin['b1'])
|
|
nd.taper(length=L_tp,width1=PSR_inst.pin['opt_b1'].width,width2=MDM_inst.pin['opt_b1'].width,xs=xs).put(PSR_inst.pin['opt_b1'])
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',pin=PSR_inst.pin['a1']).put()
|
|
nd.Pin(name='opt_a1',pin=PSR_inst.pin['opt_a1'],type="optical:").put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',pin=MDM_inst.pin['a1']).put()
|
|
nd.Pin(name='opt_b1',pin=MDM_inst.pin['opt_a1'],type="optical:").put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b2',pin=MDM_inst.pin['a2']).put()
|
|
nd.Pin(name='opt_b2',pin=MDM_inst.pin['opt_a2'],type="optical:").put()
|
|
|
|
if (show_pins):
|
|
nd.put_stub(pinsize=2)
|
|
self.cell = C
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: self.L = np.abs(self.cell.pin['a1'].x - np.max([self.cell.pin['b1'].x,self.cell.pin['b2'].x]))
|
|
self.L = np.abs(self.cell.pin['opt_a1'].x - np.max([self.cell.pin['opt_b1'].x,self.cell.pin['opt_b2'].x]))
|
|
|
|
def generate_test_gds(self,gc,gc_IN=None,gc2gc_dX=140,gc2gc_dY=40):
|
|
with nd.Cell(name=self.cell.cell_name+"_test", instantiate=False) as C:
|
|
|
|
gc_cell = __cell_arg__(arg=gc,arg_name="gc",func_name="mxpicp::functional::PSR_1x2::generate_test_gds")
|
|
|
|
if (gc_IN==None):
|
|
gc_IN = gc
|
|
else :
|
|
gc_IN = __cell_arg__(arg=gc,arg_name="gc_IN",func_name="mxpicp::functional::PSR_1x2::generate_test_gds")
|
|
|
|
|
|
GC_I = gc_IN.put('g1',-gc2gc_dX/2,0,180)
|
|
GC_OU = gc.put('g1', gc2gc_dX/2,-gc2gc_dY/2,0)
|
|
GC_OD = gc.put('g1', gc2gc_dX/2,gc2gc_dY/2,0)
|
|
|
|
PSR_test = self.cell.put(-self.L/2,0,0)
|
|
stripe=Route(radius=10, width=self.w_wg, xs="strip")
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: stripe.taper_p2p(pin1=PSR_test.pin['a1'],pin2=GC_I.pin['g1'],arrow=False).put()
|
|
stripe.taper_p2p(pin1=PSR_test.pin['opt_a1'],pin2=GC_I.pin['g1'],arrow=False).put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: stripe.sbend_p2p(pin1=PSR_test.pin['b1'],pin2=GC_OU.pin['g1'],arrow=False).put()
|
|
stripe.sbend_p2p(pin1=PSR_test.pin['opt_b1'],pin2=GC_OU.pin['g1'],arrow=False).put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: stripe.sbend_p2p(pin1=PSR_test.pin['b2'],pin2=GC_OD.pin['g1'],arrow=False).put()
|
|
stripe.sbend_p2p(pin1=PSR_test.pin['opt_b2'],pin2=GC_OD.pin['g1'],arrow=False).put()
|
|
|
|
return C
|
|
|
|
class Brag_WDM:
|
|
"""
|
|
Brag WDM primitive component.
|
|
|
|
This component builds the Brag WDM layout cell.
|
|
|
|
Parameters
|
|
----------
|
|
Brag : Any
|
|
Bragg grating cell or component used by this composite.
|
|
MDM : Any
|
|
Mode multiplexer/demultiplexer cell or component used by this composite.
|
|
w_wg : float, optional
|
|
Width parameter in microns. Default is 0.45.
|
|
L_tp : int, optional
|
|
Length parameter in microns. Default is 30.
|
|
show_pins : bool, optional
|
|
Whether to draw pin markers in the generated layout. Default is False.
|
|
"""
|
|
def __init__(self,Brag: Any,MDM: Any,w_wg: float=0.45,L_tp: int=30,show_pins: bool=False) -> None:
|
|
self.w_wg = w_wg
|
|
|
|
Brag_cell = __cell_arg__(arg=Brag,arg_name="Brag",func_name="mxpicp::functional::Brag_WDM")
|
|
MDM_cell = __cell_arg__(arg=MDM,arg_name="MDM",func_name="mxpicp::functional::Brag_WDM")
|
|
|
|
|
|
with nd.Cell(instantiate=False) as C:
|
|
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: Brag_inst = Brag_cell.put('a1',0,0,0)
|
|
Brag_inst = Brag_cell.put('opt_a1',0,0,0)
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: MDM_inst = MDM_cell.put('b1',Brag_inst.pin['a1'].x-L_tp,Brag_inst.pin['a1'].y,Brag_inst.pin['a1'].a)
|
|
MDM_inst = MDM_cell.put('opt_b1',Brag_inst.pin['opt_a1'].x-L_tp,Brag_inst.pin['opt_a1'].y,Brag_inst.pin['opt_a1'].a)
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.taper(length=L_tp,width1=Brag_inst.pin['b1'].width,width2=MDM_inst.pin['b1'].width,xs='strip').put(Brag_inst.pin['a1'])
|
|
nd.taper(length=L_tp,width1=Brag_inst.pin['opt_b1'].width,width2=MDM_inst.pin['opt_b1'].width,xs='strip').put(Brag_inst.pin['opt_a1'])
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b1',pin=Brag_inst.pin['b1']).put()
|
|
nd.Pin(name='opt_b1',pin=Brag_inst.pin['opt_b1'],type="optical:").put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='a1',pin=MDM_inst.pin['a1']).put()
|
|
nd.Pin(name='opt_a1',pin=MDM_inst.pin['opt_a1'],type="optical:").put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: nd.Pin(name='b2',pin=MDM_inst.pin['a2']).put()
|
|
nd.Pin(name='opt_b2',pin=MDM_inst.pin['opt_a2'],type="optical:").put()
|
|
|
|
if (show_pins):
|
|
nd.put_stub(pinsize=2)
|
|
self.cell = C
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: self.L = np.abs(self.cell.pin['a1'].x - np.max([self.cell.pin['b1'].x,self.cell.pin['b2'].x]))
|
|
self.L = np.abs(self.cell.pin['opt_a1'].x - np.max([self.cell.pin['opt_b1'].x,self.cell.pin['opt_b2'].x]))
|
|
|
|
def generate_test_gds(self,gc,gc2gc_dX=140,gc2gc_dY=40,dX_offset=50):
|
|
with nd.Cell(name=self.cell.cell_name+"_test", instantiate=False) as C:
|
|
|
|
gc_cell = __cell_arg__(arg=gc,arg_name="gc",func_name="mxpicp::functional::Brag_WDM::generate_test_gds")
|
|
# if (isinstance(gc,nd.Cell)):
|
|
# gc_cell = gc
|
|
# elif (hasattr(gc,"cell")):
|
|
# gc_cell = gc.cell
|
|
|
|
GC_I = gc_cell.put('g1',-gc2gc_dX/2,0,180)
|
|
GC_O1 = gc_cell.put('g1', gc2gc_dX/2,0,0)
|
|
GC_O2 = gc_cell.put('g1', gc2gc_dX/2+dX_offset,-gc2gc_dY,0)
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: INSTR = self.cell.put('a1',-self.L/2,0,0)
|
|
INSTR = self.cell.put('opt_a1',-self.L/2,0,0)
|
|
stripe=Route(radius=10, width=self.w_wg, xs="strip")
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: stripe.taper_p2p(pin1=INSTR.pin['a1'],pin2=GC_I.pin['g1'],arrow=False).put()
|
|
stripe.taper_p2p(pin1=INSTR.pin['opt_a1'],pin2=GC_I.pin['g1'],arrow=False).put()
|
|
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: stripe.taper(pin=INSTR.pin['b1'],width1=INSTR.pin['b1'].width,width2=GC_O1.pin['g1'].width,length=10,arrow=False).put()
|
|
stripe.taper(pin=INSTR.pin['opt_b1'],width1=INSTR.pin['opt_b1'].width,width2=GC_O1.pin['g1'].width,length=10,arrow=False).put()
|
|
stripe.sbend_p2p(pin2=GC_O1.pin['g1'],arrow=False).put()
|
|
## revised in 2026.06.07 by Qin Yue
|
|
# legacy: stripe.ubend_p2p(pin1=INSTR.pin['b2'],pin2=GC_O2.pin['g1'],arrow=False).put()
|
|
stripe.ubend_p2p(pin1=INSTR.pin['opt_b2'],pin2=GC_O2.pin['g1'],arrow=False).put()
|
|
|
|
return C
|