New forge coding added

This commit is contained in:
=
2026-06-04 23:21:39 +08:00
parent 518eb06591
commit 8da92ced57
288 changed files with 52017 additions and 1913 deletions
@@ -0,0 +1,7 @@
from .dev_ps import Heater_NDoped,PS_PIN
from .pin_wg import waveguide_PIN,WGDoped
from .rings import STD_Ring_PIN,AED_Ring_PIN,PIN_MRR_STD_Allpass,PIN_MRR_MM_Allpass,PIN_MRR_MM_Adddrop
@@ -0,0 +1,445 @@
import nazca as nd
import numpy as np
from ..pic import taper_xs2xs
import nazca.interconnects as IC
class Route(IC.Interconnect):
pass
# from ...routing import Route
from ...electronics import Vias
class Heater_NDoped():
'''
This is the class for N-doped heater as a phase shifter.
'''
def __init__(
self,
w_wg: float=0.45,
slab_width: float=1.1,
heater_length: int=100,
heater_width: int=1,
if_open: bool=True,
show_pins: bool=False
) -> None:
'''
__Summary__: Initilization of N Doped Heater.
Args:
1. w_wg [um] Width of input waveguide
2. slab_width [um] Distance betwee wg edge and N doped region edge
3. heater_length[um] Length of heater
4. heater_width [um] Width of heater
5. if_open [Bool] If add an open region to decrease the thickness of cladding
'''
self.w_wg = w_wg
self.slab_width = slab_width
self.heater_length = heater_length
self.heater_width = heater_width
self.pitch = self.heater_width + self.w_wg + self.slab_width*2
self.if_open = if_open
self.show_pins = show_pins
self.cell = self.generate_gds()
def generate_gds(self):
with nd.Cell(name="Heater_NDoped", instantiate=False) as C:
nd.add_xsection(name="rib_narrow")
nd.add_layer2xsection(xsection="rib_narrow", layer="RIB_COR")
nd.add_layer2xsection(xsection="rib_narrow", layer="RIB_CLD", leftedge=(0.5, self.slab_width), rightedge=(-0.5, -self.slab_width))
nd.add_layer2xsection(xsection="rib_narrow", layer="STRIP_COR", leftedge=(0.5, self.slab_width), rightedge=(-0.5, -self.slab_width))
nd.add_layer2xsection(xsection="rib_narrow", layer="STRIP_CLD", leftedge=(0.5, 2), rightedge=(-0.5, -2))
layer_cld = nd.get_layer("STRIP_CLD")
if layer_cld=="dump" :
nd.text(text="==This device is not compatible with this foundry==",height=5,layer=(1005)).put(3,-20)
print("==Active::dev_ps::Heater_NDoped is not compatible with this tapeout.")
L_taper = 10
taper = taper_xs2xs(
xs_1='rib_narrow',
xs_2='strip',
L_taper=L_taper,
w_1=self.w_wg,
w_2=self.w_wg,
L_port=0
).cell
taper_l1 = taper.put('b0', 0, 0)
bend_radius_stripe = 5
stripe = Route(xs='strip', width=self.w_wg, radius=bend_radius_stripe)
rib = Route(xs='rib_narrow', width=self.w_wg)
## Build waveguide first
rib_strt_up = rib.strt(length=self.heater_length, arrow=False).put(taper_l1.pin['a0'])
taper_r1 = taper.put('a0', rib_strt_up.pin['b0'])
stripe.bend(angle=90, arrow=False).put(taper_r1.pin['b0'])
stripe.bend(angle=-180, arrow=False).put()
stripe.strt(length=self.pitch, arrow=False).put()
bend_out = stripe.bend(angle=-90, arrow=False).put()
stripe.strt_p2p(pin1=bend_out.pin['b0'], pin2=(taper_r1.pin['b0'].x, taper_r1.pin['b0'].y-self.pitch, taper_r1.pin['b0'].a+180), arrow=False).put()
taper_r2 = taper.put('b0')
rib_strt_mid = rib.strt(length=self.heater_length, arrow=False).put(taper_r2.pin['a0'])
taper_l2 = taper.put('a0', rib_strt_mid.pin['b0'])
stripe.strt(length=bend_radius_stripe*2, arrow=False).put(taper_l2.pin['b0'])
stripe.bend(angle=90, arrow=False).put()
stripe.strt(length=self.pitch, arrow=False).put()
stripe.bend(angle=180, arrow=False).put()
stripe.bend(angle=-90, arrow=False).put()
taper_l3 = taper.put('b0')
rib_strt_low = rib.strt(length=self.heater_length, arrow=False).put(taper_l3.pin['a0'])
taper_r3 = taper.put('a0', rib_strt_low.pin['b0'])
# Add input and output waveguide
length_wg = bend_radius_stripe*3+2+self.w_wg/2
wg_input = stripe.strt(length=length_wg, arrow=False).put(taper_l1.pin['b0'])
wg_output =stripe.strt(length=length_wg, arrow=False).put(taper_r3.pin['b0'])
## Add N-doped heater region
heater_strip_cor = nd.strt(length=self.heater_length, width=self.heater_width, layer='STRIP_COR')
heater_Ndoped = nd.strt(length=self.heater_length, width=self.heater_width, layer='NP')
heater_strip_cor.put(rib_strt_up.pin['a0'].x, (rib_strt_up.pin['a0'].y+rib_strt_mid.pin['b0'].y)/2, 0)
heater_strip_cor.put(rib_strt_up.pin['a0'].x, (rib_strt_low.pin['a0'].y+rib_strt_mid.pin['b0'].y)/2, 0)
heater_Ndoped.put(rib_strt_up.pin['a0'].x, (rib_strt_up.pin['a0'].y+rib_strt_mid.pin['b0'].y)/2, 0)
heater_Ndoped.put(rib_strt_up.pin['a0'].x, (rib_strt_low.pin['a0'].y+rib_strt_mid.pin['b0'].y)/2, 0)
vias_width = 6
vias = Vias(xs='via_s2m', area=[vias_width, self.heater_width-0.2*2], sz=[0.25, 0.25], spacing=[0.35, 0.35], xs_l1='sa', xs_l2='metal').cell
vias_l1 = vias.put(rib_strt_up.pin['a0'].x+0.2+vias_width/2, (rib_strt_up.pin['a0'].y+rib_strt_mid.pin['b0'].y)/2, 0)
vias_l2 = vias.put(rib_strt_up.pin['a0'].x+0.2+vias_width/2, (rib_strt_low.pin['a0'].y+rib_strt_mid.pin['b0'].y)/2, 0)
vias_r1 = vias.put(rib_strt_up.pin['b0'].x-0.2-vias_width/2, (rib_strt_up.pin['a0'].y+rib_strt_mid.pin['b0'].y)/2, 0)
vias_r2 = vias.put(rib_strt_up.pin['b0'].x-0.2-vias_width/2, (rib_strt_low.pin['a0'].y+rib_strt_mid.pin['b0'].y)/2, 0)
metal1 = Route(xs='metal', radius=0)
metal_l=metal1.strt_p2p(pin1=vias_l1.pin['a0'], pin2=vias_l2.pin['b0'], width=vias_width, arrow=False).put()
metal_r=metal1.strt_p2p(pin1=vias_r1.pin['a0'], pin2=vias_r2.pin['b0'], width=vias_width, arrow=False).put()
## Add open region to decrease the thickness of cladding
if self.if_open:
length_open = self.heater_length - 0.4 - vias_width*2 - 6*2
nd.strt(length=length_open, width=20, layer='GC_OPEN').put(
rib_strt_up.pin['a0'].x+0.2+vias_width+6,
(metal_r.pin['a0'].y+metal_r.pin['b0'].y)/2,
0
)
## Add dummy to avoid sharp angle
poly = [
(0, 0),
(0, self.pitch+self.w_wg+4+bend_radius_stripe*2),
(bend_radius_stripe*3+self.w_wg/2+2, self.pitch+self.w_wg+4+bend_radius_stripe*2),
(bend_radius_stripe*3+self.w_wg/2+2, 0)
]
nd.Polygon(points=poly, layer="STRIP_CLD").put(
taper_l1.pin['b0'].x, taper_l1.pin['b0'].y-self.pitch+self.w_wg/2+2, flip=True, flop=True
)
nd.Polygon(points=poly, layer="STRIP_CLD").put(
taper_r1.pin['b0'].x, taper_r2.pin['b0'].y-self.w_wg/2-2
)
## Add pin
nd.Pin(name='a0').put(wg_input.pin['b0'])
nd.Pin(name='a1', width=self.w_wg).put(wg_input.pin['b0'])
nd.Pin(name='b1', width=self.w_wg).put(wg_output.pin['b0'])
nd.Pin(name='ep1', width=abs(metal_l.pin['a0'].y-metal_l.pin['b0'].y)).put(
metal_l.pin['a0'].x, (metal_l.pin['a0'].y+metal_l.pin['b0'].y)/2, 180
)
nd.Pin(name='en1', width=abs(metal_r.pin['a0'].y-metal_r.pin['b0'].y)).put(
metal_r.pin['a0'].x, (metal_r.pin['a0'].y+metal_r.pin['b0'].y)/2, 0
)
if self.show_pins:
nd.put_stub()
return C
def generate_test_gds(self, gc, mmi, mzi_offset=60, ct_pitch=20, gc2gc_length=500, cell_name=None, gc_offset=0):
if cell_name==None:
cell_name = self.cell.cell_name + "_test"
with nd.Cell(name=cell_name, instantiate=False) as C:
stripe = Route(radius=5, width=gc.w_wg, xs='strip')
gc_input = gc.cell.put('g1',0,gc_offset,180)
gc_output = gc.cell.put('g1',gc2gc_length,gc_offset,0)
mmi_input = mmi.cell.put('a1',20,0,0)
mmi_output = mmi.cell.put('a1', gc_output.pin['g1'].x-20,0, 180)
stripe.sbend_p2p(
pin1=gc_input.pin['g1'],
pin2=mmi_input.pin['a1'],
Lstart=5,
arrow=False
).put()
stripe.sbend_p2p(
pin1=gc_output.pin['g1'],
pin2=mmi_output.pin['a1'],
Lstart=5,
arrow=False
).put()
# Connect upper arm
ps = self.cell.put('a1',mmi_input.pin['b1'].x+40,mzi_offset)
stripe.sbend_route_p2p(
pin1=mmi_input.pin['b1'],
pin2=ps.pin['a1'],
arrow=False
).put()
stripe.sbend_route_p2p(
pin1=mmi_output.pin['b2'],
pin2=ps.pin['b1'],
arrow=False
).put()
# Connect lower arm
stripe.strt_p2p(
pin1=mmi_input.pin['b2'],
pin2=mmi_output.pin['b1'],
arrow=False
).put()
## Put CT heater
ps_ct = self.cell.put('a1',ps.pin['a1'].x,ps.pin['a1'].y+ct_pitch,0)
## Put pins
nd.Pin(name="ep1", pin=ps.pin['ep1']).put()
nd.Pin(name="en1", pin=ps.pin['en1']).put()
nd.Pin(name="ep2", pin=ps_ct.pin['ep1']).put()
nd.Pin(name="en2", pin=ps_ct.pin['en1']).put()
return C
class PS_PIN() :
'''
PIN junction for a high-speed phase shift.
'''
def __init__(
self,
w_wg: float=0.45,
w_wg_slab: float=0.5,
w_slab: float=1,
l_wg: float=800,
d2wg_list: list = [0.2, 1],
p_layer_list: list = ['PW', 'PP'],
n_layer_list: list = ['NW', 'NP'],
w_plus_max: float = 5.1,
show_pins: bool=False,
# # Parameters to be abandoned
# d2wg=0.4,
# layer_pld='PLD_2',
# layer_nld='NLD_2'
) -> None:
'''
Initilization of PS_PIN.
Args:
- w_wg [um] Width of the input waveguide.
- w_wg_slab [um] Width of the slab waveguide.
- w_slab [um] Width of the slab region of slab waveguides.
- l_wg [um] Length of the PIN phase shifter.
- d2wg_list [list[um]] Distance to the waveguide edge of corresponding doping regions.
- p_layer_list [list[strt]] Layer names of P doped regions, with same length as 'd2wg_list'.
- n_layer_list [list[strt]] Layer names of N doped regions, with same length as 'd2wg_list'.
- w_plus_max[um] Maximum distance of doping regions to the center.
- show_pins [bool] If show connecting pins, with a1, b1, ep1, en1.
'''
self.w_wg = w_wg
self.w_wg_slab = w_wg_slab
self.w_slab = w_slab
self.l_wg = l_wg
self.d2wg_list = d2wg_list
self.p_layer_list = p_layer_list
self.n_layer_list = n_layer_list
self.w_plus_max = w_plus_max
if len(self.d2wg_list) != len(self.p_layer_list) :
print("==Warning==PIN:: len(d2wg_list) != len(self.p_layer_list). ")
if len(self.d2wg_list) != len(self.n_layer_list) :
print("==Warning==PIN:: len(d2wg_list) != len(self.n_layer_list). ")
self.show_pins = show_pins
self.cell = self.generate_gds()
''' Parameters to be abandoned '''
# self.d2wg = d2wg
# self.layer_pld = layer_pld
# self.layer_nld = layer_nld
def generate_gds(self) :
cell_name = "ps_pin"
for _index_ in range(len(self.d2wg_list)) :
cell_name = cell_name + "_" + self.p_layer_list[_index_] + str(int(self.d2wg_list[_index_]*1000)) + "nm"
self.cell_name = cell_name
with nd.Cell(name=self.cell_name, instantiate=True) as ICell :
''' Add The Waveguide '''
strip = Route(radius=10,width=self.w_wg,xs='strip')
nd.add_xsection(name='slab_pin')
nd.add_layer2xsection(xsection='slab_pin', layer='RIB_COR', leftedge=(0.5,0), rightedge=(-0.5,0), overwrite=True)
nd.add_layer2xsection(xsection='slab_pin', layer='RIB_CLD', leftedge=(0.5,self.w_slab), rightedge=(-0.5,-self.w_slab), overwrite=True)
slab = Route(radius=10,width=self.w_wg_slab,xs='slab_pin')
nd.add_xsection(name='slab2strip_pin')
nd.add_layer2xsection(xsection='slab2strip_pin', layer='RIB_COR', leftedge=(0.5,0), rightedge=(-0.5,0), overwrite=True)
nd.add_layer2xsection(xsection='slab2strip_pin', layer='RIB_CLD', leftedge=(0.5,self.w_slab), rightedge=(-0.5,-self.w_slab), overwrite=True)
nd.add_layer2xsection(xsection='slab2strip_pin', layer='STRIP_COR', leftedge=(0.5,self.w_slab), rightedge=(-0.5,-self.w_slab), overwrite=True)
nd.add_layer2xsection(xsection='slab2strip_pin', layer='STRIP_CLD', leftedge=(0.5,self.w_slab*2), rightedge=(-0.5,-self.w_slab*2), overwrite=True)
taper_strip2slab = taper_xs2xs(xs_1='strip',xs_2='slab2strip_pin',L_taper=10,w_1=self.w_wg,w_2=self.w_wg_slab, L_port=0.2)
taper_slab2strip = taper_xs2xs(xs_1='slab2strip_pin',xs_2='strip',L_taper=10,w_1=self.w_wg_slab, w_2=self.w_wg,L_port=0.2)
taper_input = taper_strip2slab.cell.put('a1',0,0,0)
wg_ps = slab.strt(length=self.l_wg,arrow=False).put(taper_input.pin['b1'])
taper_output = taper_slab2strip.cell.put('a1',wg_ps.pin['b0'])
''' Add doping area near the waveguide '''
l_edge = 1
for _index_ in range(len(self.d2wg_list)) :
d2wg_temp = self.d2wg_list[_index_]
p_layer_temp = self.p_layer_list[_index_]
n_layer_temp = self.n_layer_list[_index_]
doping_polygon = [
(taper_input.pin['b1'].x+l_edge, self.w_wg_slab/2+d2wg_temp),
(taper_output.pin['a1'].x-l_edge, self.w_wg_slab/2+d2wg_temp),
(taper_output.pin['a1'].x-l_edge, self.w_plus_max),
(taper_input.pin['b1'].x+l_edge, self.w_plus_max)
]
nd.Polygon(points=doping_polygon, layer=p_layer_temp).put(0,0)
nd.Polygon(points=doping_polygon, layer=n_layer_temp).put(0,0,flop=True)
''' Add Vias '''
vias = Vias(
xs='via_s2m', area=[self.l_wg-l_edge*2-0.2*2, self.w_plus_max-(self.d2wg_list[-1]+self.w_wg_slab/2)-0.2*2],
sz=[0.25, 0.25], spacing=[0.35, 0.35],
xs_l1="sa", xs_l2="metal", show_pins=False
)
vias_p = vias.cell.put(
taper_input.pin['b1'].x+self.l_wg/2, (self.w_plus_max+self.d2wg_list[-1]+self.w_wg_slab/2)/2
)
vias_n = vias.cell.put(
taper_input.pin['b1'].x+self.l_wg/2, -(self.w_plus_max+self.d2wg_list[-1]+self.w_wg_slab/2)/2
)
''' Add Pins '''
nd.Pin(name='a1',width=self.w_wg).put(taper_input.pin['a1'])
nd.Pin(name='b1',width=self.w_wg).put(taper_output.pin['b1'])
nd.Pin(name="ep1", width=self.w_plus_max-(self.d2wg_list[-1]+self.w_wg_slab/2)-0.2*2).put(vias_p.pin['a0'])
nd.Pin(name="en1", width=self.w_plus_max-(self.d2wg_list[-1]+self.w_wg_slab/2)-0.2*2).put(vias_n.pin['a0'])
# nd.put_stub()
return ICell
def generate_mzi_gds(self, gc, mmi, pad) :
'''
Generating a Mach-Zehnder Interferometer for testing the PIN phase shifter.
'''
with nd.Cell(name=self.cell_name+"_MZI", instantiate=False) as ICell :
mmi_input = mmi.cell.put('a1',0,0,0)
pin2test = self.cell.put('a1', mmi_input.pin['b1'].move(60,100,0))
mmi_bottom = mmi.cell.put('a1', pin2test.pin['b1'].x+10, mmi_input.pin['b2'].y, 0)
mmi_up = mmi.cell.put('a1', pin2test.pin['b1'].move(10,0,0))
mmi_middle = mmi.cell.put(
'a1',
mmi_bottom.pin['b1'].x+mmi.length+25,
mmi_up.pin['b2'].y/2+mmi_bottom.pin['b1'].y/2,
180
)
''' Wg routing '''
strip = Route(radius=10, width=self.w_wg, xs='strip')
strip.strt_p2p(pin1=mmi_input.pin['b2'],pin2=mmi_bottom.pin['a1'],arrow=False).put()
# strip.sbend_p2p(pin1=mmi_input.pin['b1'],pin2=pin2test.pin['a1'],Lstart=1,arrow=False).put()
strip.sbend_p2p(
pin1=mmi_input.pin['b1'],pin2=nd.Pin().put(mmi_input.pin['b1'].x+11,pin2test.pin['a1'].y,180),
radius=5, Lstart=0.5, arrow=False
).put()
strip.sbend_p2p(
pin1=nd.Pin().put(),pin2=nd.Pin().put(mmi_input.pin['b1'].x+22,mmi_input.pin['b1'].y+1,180),
radius=5, Lstart=0.5, arrow=False
).put()
strip.sbend_p2p(
pin1=nd.Pin().put(),pin2=nd.Pin().put(mmi_input.pin['b1'].x+33,pin2test.pin['a1'].y,180),
radius=5, Lstart=0.5, arrow=False
).put()
strip.sbend_p2p(
pin1=nd.Pin().put(),pin2=nd.Pin().put(mmi_input.pin['b1'].x+44,mmi_input.pin['b1'].y+1,180),
radius=5, Lstart=0.5, arrow=False
).put()
strip.sbend_p2p(
pin1=nd.Pin().put(),pin2=nd.Pin().put(mmi_input.pin['b1'].x+55,pin2test.pin['a1'].y,180),
radius=5, Lstart=0.5, arrow=False
).put()
strip.strt_p2p(pin1=nd.Pin().put(),pin2=pin2test.pin['a1'],arrow=False).put()
# strip.sbend(radius=5,offset=-abs(pin2test.pin['a1'].y-mmi_input.pin['b1'].y),arrow=False).put()
strip.strt_p2p(pin1=mmi_up.pin['a1'],pin2=pin2test.pin['b1'],arrow=False).put()
strip.sbend_p2p(pin1=mmi_up.pin['b2'],pin2=mmi_middle.pin['b2'],Lstart=0.5,radius=10,arrow=False).put()
strip.sbend_p2p(pin1=mmi_bottom.pin['b1'],pin2=mmi_middle.pin['b1'],Lstart=2.5,radius=10,arrow=False).put()
''' Add pads '''
pad_ground = pad.cell.put(
mmi_input.pin['b1'].x+40+30+50,mmi_input.pin['b1'].y+5+40-4,0
)
pad_signal = pad.cell.put(
mmi_input.pin['b1'].x+40+30+100+50,mmi_input.pin['b1'].y+5+40-4,0
)
''' Metal Connecting '''
metal = Route(radius=5,width=10,xs='metal')
metal_2 = Route(radius=5,width=10,xs='metal_2')
vias_m2m = Vias(
xs='via_m2m', area=[self.l_wg+0.4, 10], sz=[0.6, 0.6], spacing=[0.6, 0.6],
xs_l1='metal', xs_l2='metal_2', instantiate=True, show_pins=False
)
metal.strt_p2p(
pin1=pad_ground.pin['p1'],
pin2=nd.Pin().put(pad_ground.pin['p1'].x,pin2test.pin['en1'].y),
arrow=False
).put()
vias = vias_m2m.cell.put(
pin2test.pin['ep1'].move(
0,-5+(self.w_plus_max-self.d2wg_list[-1]-self.w_wg_slab/2)/2,0
)
)
metal_2.strt_p2p(
pin1=pad_signal.pin['p1'],
pin2=nd.Pin().put(pad_signal.pin['p1'].x,vias.pin['a0'].y),
arrow=False
).put()
metal.strt(length=self.l_wg+0.4,width=10,arrow=False).put(
pin2test.pin['en1'].move(
-self.l_wg/2-0.2,5-(self.w_plus_max-self.d2wg_list[-1]-self.w_wg_slab/2)/2,0
)
)
''' Put output gratings '''
strip.strt(length=60,arrow=False).put(mmi_input.pin['a1'])
gc.cell.put('g1')
strip.strt(length=60,arrow=False).put(mmi_up.pin['b1'])
gc.cell.put('g1')
strip.strt(length=100,arrow=False).put(mmi_middle.pin['a1'])
gc.cell.put('g1')
strip.strt(length=60,arrow=False).put(mmi_bottom.pin['b2'])
gc.cell.put('g1')
return ICell
# class N_Doped_strt() :
# '''
# Class for a strt N doped region
# '''
# def __init__(self, tapeout, width=2.5, length=100, sa_length=6):
# self.tapeout = tapeout
# self.width = width
# self.length = length
# self.sa_length = sa_length
# self.generate_gds()
# def generate_gds(self):
# with nd.Cell(name="N_Doped_strt", instantiate=False) as C:
# nd.strt(length=self.length, width=self.width, layer="STRIP_COR").put(0,0)
# nd.strt(length=self.length, width=self.width, layer="NP").put(0,0)
# nd.strt(length=self.length, width=self.width, layer="STRIP_COR").put(0,0)
# nd.strt(length=self.length+4, width=self.width+4, layer="STRIP_CLD").put(-2,0)
# sa_ep1 = nd.strt(length=self.sa_length, width=self.width-0.4, xs="sa").put(0.2,0)
# sa_ep2 = nd.strt(length=self.sa_length, width=self.width-0.4, xs="sa").put(self.length-0.2,0,180)
# sa_en1 = nd.strt(length=self.sa_length, width=self.width-0.4, xs="sa").put(self.length/2-self.sa_length/2,0)
# # vias = Vias(
# # xs="via_h2m",
# # area=[self.sa_length, self.width-0.4],
# # sz=[0.6, 0.6],
# # spacing=[0.6, 0.6],
# # xs="ct",
# # area=[self.sa_length, self.width-0.4],
# # sz=[0.25, 0.25],
# # spacing=[0.35, 0.35],
# # xs_l1='sa',
# # xs_l2='metal_1'
# # )
# # vias_ep1 = vias.cell.put(sa_ep1.pin['a0'].move(-self.sa_length/2,0))
# # vias_ep2 = vias.cell.put(sa_ep2.pin['a0'].move(-self.sa_length/2,0))
# # vias_en1 = vias.cell.put(sa_en1.pin['a0'].move(-self.sa_length/2,0))
# # nd.Pin(name="ep1", pin=vias_ep1.pin['b0']).put()
# # nd.Pin(name="ep2", pin=vias_ep2.pin['a0']).put()
# # nd.Pin(name="en1", pin=vias_en1.pin['b0'].move(0,0,90)).put()
# # nd.put_stub(pinname=["ep1", "ep2", "en1"])
# self.cell = C
# return C
@@ -0,0 +1,401 @@
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
File diff suppressed because it is too large Load Diff