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. Parameters ---------- w_wg : float, optional Width parameter in microns. Default is 0.45. slab_width : float, optional Value for the slab_width parameter. Default is 1.1. heater_length : int, optional Value for the heater_length parameter. Default is 100. heater_width : int, optional Value for the heater_width parameter. Default is 1. if_open : bool, optional Value for the if_open parameter. Default is True. show_pins : bool, optional Whether to draw pin markers in the generated layout. Default is False. """ 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']) ## revised in 2026.06.07 by Qin Yue # legacy: nd.Pin(name='a1', width=self.w_wg).put(wg_input.pin['b0']) nd.Pin(name='opt_a1', width=self.w_wg,type="optical:").put(wg_input.pin['b0']) ## revised in 2026.06.07 by Qin Yue # legacy: nd.Pin(name='b1', width=self.w_wg).put(wg_output.pin['b0']) nd.Pin(name='opt_b1', width=self.w_wg,type="optical:").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) ## revised in 2026.06.07 by Qin Yue # legacy: mmi_input = mmi.cell.put('a1',20,0,0) mmi_input = mmi.cell.put('opt_a1',20,0,0) ## revised in 2026.06.07 by Qin Yue # legacy: mmi_output = mmi.cell.put('a1', gc_output.pin['g1'].x-20,0, 180) mmi_output = mmi.cell.put('opt_a1', gc_output.pin['g1'].x-20,0, 180) stripe.sbend_p2p( pin1=gc_input.pin['g1'], ## revised in 2026.06.07 by Qin Yue # legacy: pin2=mmi_input.pin['a1'], pin2=mmi_input.pin['opt_a1'], Lstart=5, arrow=False ).put() stripe.sbend_p2p( pin1=gc_output.pin['g1'], ## revised in 2026.06.07 by Qin Yue # legacy: pin2=mmi_output.pin['a1'], pin2=mmi_output.pin['opt_a1'], Lstart=5, arrow=False ).put() # Connect upper arm ## revised in 2026.06.07 by Qin Yue # legacy: ps = self.cell.put('a1',mmi_input.pin['b1'].x+40,mzi_offset) ps = self.cell.put('opt_a1',mmi_input.pin['opt_b1'].x+40,mzi_offset) stripe.sbend_route_p2p( ## revised in 2026.06.07 by Qin Yue # legacy: pin1=mmi_input.pin['b1'], pin1=mmi_input.pin['opt_b1'], ## revised in 2026.06.07 by Qin Yue # legacy: pin2=ps.pin['a1'], pin2=ps.pin['opt_a1'], arrow=False ).put() stripe.sbend_route_p2p( ## revised in 2026.06.07 by Qin Yue # legacy: pin1=mmi_output.pin['b2'], pin1=mmi_output.pin['opt_b2'], ## revised in 2026.06.07 by Qin Yue # legacy: pin2=ps.pin['b1'], pin2=ps.pin['opt_b1'], arrow=False ).put() # Connect lower arm stripe.strt_p2p( ## revised in 2026.06.07 by Qin Yue # legacy: pin1=mmi_input.pin['b2'], pin1=mmi_input.pin['opt_b2'], ## revised in 2026.06.07 by Qin Yue # legacy: pin2=mmi_output.pin['b1'], pin2=mmi_output.pin['opt_b1'], arrow=False ).put() ## Put CT heater ## revised in 2026.06.07 by Qin Yue # legacy: ps_ct = self.cell.put('a1',ps.pin['a1'].x,ps.pin['a1'].y+ct_pitch,0) ps_ct = self.cell.put('opt_a1',ps.pin['opt_a1'].x,ps.pin['opt_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. Parameters ---------- w_wg : float, optional Width parameter in microns. Default is 0.45. w_wg_slab : float, optional Width parameter in microns. Default is 0.5. w_slab : float, optional Width parameter in microns. Default is 1. l_wg : float, optional Value for the l_wg parameter. Default is 800. d2wg_list : list, optional Value for the d2wg_list parameter. Default is [0.2, 1]. p_layer_list : list, optional Value for the p_layer_list parameter. Default is ['PW', 'PP']. n_layer_list : list, optional Value for the n_layer_list parameter. Default is ['NW', 'NP']. w_plus_max : float, optional Width parameter in microns. Default is 5.1. show_pins : bool, optional Whether to draw pin markers in the generated layout. Default is False. """ 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) ## revised in 2026.06.07 by Qin Yue # legacy: taper_input = taper_strip2slab.cell.put('a1',0,0,0) taper_input = taper_strip2slab.cell.put('opt_a1',0,0,0) ## revised in 2026.06.07 by Qin Yue # legacy: wg_ps = slab.strt(length=self.l_wg,arrow=False).put(taper_input.pin['b1']) wg_ps = slab.strt(length=self.l_wg,arrow=False).put(taper_input.pin['opt_b1']) ## revised in 2026.06.07 by Qin Yue # legacy: taper_output = taper_slab2strip.cell.put('a1',wg_ps.pin['b0']) taper_output = taper_slab2strip.cell.put('opt_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 = [ ## revised in 2026.06.07 by Qin Yue # legacy: (taper_input.pin['b1'].x+l_edge, self.w_wg_slab/2+d2wg_temp), (taper_input.pin['opt_b1'].x+l_edge, self.w_wg_slab/2+d2wg_temp), ## revised in 2026.06.07 by Qin Yue # legacy: (taper_output.pin['a1'].x-l_edge, self.w_wg_slab/2+d2wg_temp), (taper_output.pin['opt_a1'].x-l_edge, self.w_wg_slab/2+d2wg_temp), ## revised in 2026.06.07 by Qin Yue # legacy: (taper_output.pin['a1'].x-l_edge, self.w_plus_max), (taper_output.pin['opt_a1'].x-l_edge, self.w_plus_max), ## revised in 2026.06.07 by Qin Yue # legacy: (taper_input.pin['b1'].x+l_edge, self.w_plus_max) (taper_input.pin['opt_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( ## revised in 2026.06.07 by Qin Yue # legacy: taper_input.pin['b1'].x+self.l_wg/2, (self.w_plus_max+self.d2wg_list[-1]+self.w_wg_slab/2)/2 taper_input.pin['opt_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( ## revised in 2026.06.07 by Qin Yue # legacy: taper_input.pin['b1'].x+self.l_wg/2, -(self.w_plus_max+self.d2wg_list[-1]+self.w_wg_slab/2)/2 taper_input.pin['opt_b1'].x+self.l_wg/2, -(self.w_plus_max+self.d2wg_list[-1]+self.w_wg_slab/2)/2 ) ''' Add Pins ''' ## revised in 2026.06.07 by Qin Yue # legacy: nd.Pin(name='a1',width=self.w_wg).put(taper_input.pin['a1']) nd.Pin(name='opt_a1',width=self.w_wg,type="optical:").put(taper_input.pin['opt_a1']) ## revised in 2026.06.07 by Qin Yue # legacy: nd.Pin(name='b1',width=self.w_wg).put(taper_output.pin['b1']) nd.Pin(name='opt_b1',width=self.w_wg,type="optical:").put(taper_output.pin['opt_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 : ## revised in 2026.06.07 by Qin Yue # legacy: mmi_input = mmi.cell.put('a1',0,0,0) mmi_input = mmi.cell.put('opt_a1',0,0,0) ## revised in 2026.06.07 by Qin Yue # legacy: pin2test = self.cell.put('a1', mmi_input.pin['b1'].move(60,100,0)) pin2test = self.cell.put('opt_a1', mmi_input.pin['opt_b1'].move(60,100,0)) ## revised in 2026.06.07 by Qin Yue # legacy: mmi_bottom = mmi.cell.put('a1', pin2test.pin['b1'].x+10, mmi_input.pin['b2'].y, 0) mmi_bottom = mmi.cell.put('opt_a1', pin2test.pin['opt_b1'].x+10, mmi_input.pin['opt_b2'].y, 0) ## revised in 2026.06.07 by Qin Yue # legacy: mmi_up = mmi.cell.put('a1', pin2test.pin['b1'].move(10,0,0)) mmi_up = mmi.cell.put('opt_a1', pin2test.pin['opt_b1'].move(10,0,0)) mmi_middle = mmi.cell.put( ## revised in 2026.06.07 by Qin Yue # legacy: 'a1', 'opt_a1', ## revised in 2026.06.07 by Qin Yue # legacy: mmi_bottom.pin['b1'].x+mmi.length+25, mmi_bottom.pin['opt_b1'].x+mmi.length+25, ## revised in 2026.06.07 by Qin Yue # legacy: mmi_up.pin['b2'].y/2+mmi_bottom.pin['b1'].y/2, mmi_up.pin['opt_b2'].y/2+mmi_bottom.pin['opt_b1'].y/2, 180 ) ''' Wg routing ''' strip = Route(radius=10, width=self.w_wg, xs='strip') ## revised in 2026.06.07 by Qin Yue # legacy: strip.strt_p2p(pin1=mmi_input.pin['b2'],pin2=mmi_bottom.pin['a1'],arrow=False).put() strip.strt_p2p(pin1=mmi_input.pin['opt_b2'],pin2=mmi_bottom.pin['opt_a1'],arrow=False).put() # strip.sbend_p2p(pin1=mmi_input.pin['b1'],pin2=pin2test.pin['a1'],Lstart=1,arrow=False).put() strip.sbend_p2p( ## revised in 2026.06.07 by Qin Yue # legacy: pin1=mmi_input.pin['b1'],pin2=nd.Pin().put(mmi_input.pin['b1'].x+11,pin2test.pin['a1'].y,180), pin1=mmi_input.pin['opt_b1'],pin2=nd.Pin().put(mmi_input.pin['opt_b1'].x+11,pin2test.pin['opt_a1'].y,180), radius=5, Lstart=0.5, arrow=False ).put() strip.sbend_p2p( ## revised in 2026.06.07 by Qin Yue # legacy: pin1=nd.Pin().put(),pin2=nd.Pin().put(mmi_input.pin['b1'].x+22,mmi_input.pin['b1'].y+1,180), pin1=nd.Pin().put(),pin2=nd.Pin().put(mmi_input.pin['opt_b1'].x+22,mmi_input.pin['opt_b1'].y+1,180), radius=5, Lstart=0.5, arrow=False ).put() strip.sbend_p2p( ## revised in 2026.06.07 by Qin Yue # legacy: pin1=nd.Pin().put(),pin2=nd.Pin().put(mmi_input.pin['b1'].x+33,pin2test.pin['a1'].y,180), pin1=nd.Pin().put(),pin2=nd.Pin().put(mmi_input.pin['opt_b1'].x+33,pin2test.pin['opt_a1'].y,180), radius=5, Lstart=0.5, arrow=False ).put() strip.sbend_p2p( ## revised in 2026.06.07 by Qin Yue # legacy: pin1=nd.Pin().put(),pin2=nd.Pin().put(mmi_input.pin['b1'].x+44,mmi_input.pin['b1'].y+1,180), pin1=nd.Pin().put(),pin2=nd.Pin().put(mmi_input.pin['opt_b1'].x+44,mmi_input.pin['opt_b1'].y+1,180), radius=5, Lstart=0.5, arrow=False ).put() strip.sbend_p2p( ## revised in 2026.06.07 by Qin Yue # legacy: pin1=nd.Pin().put(),pin2=nd.Pin().put(mmi_input.pin['b1'].x+55,pin2test.pin['a1'].y,180), pin1=nd.Pin().put(),pin2=nd.Pin().put(mmi_input.pin['opt_b1'].x+55,pin2test.pin['opt_a1'].y,180), radius=5, Lstart=0.5, arrow=False ).put() ## revised in 2026.06.07 by Qin Yue # legacy: strip.strt_p2p(pin1=nd.Pin().put(),pin2=pin2test.pin['a1'],arrow=False).put() strip.strt_p2p(pin1=nd.Pin().put(),pin2=pin2test.pin['opt_a1'],arrow=False).put() # strip.sbend(radius=5,offset=-abs(pin2test.pin['a1'].y-mmi_input.pin['b1'].y),arrow=False).put() ## revised in 2026.06.07 by Qin Yue # legacy: strip.strt_p2p(pin1=mmi_up.pin['a1'],pin2=pin2test.pin['b1'],arrow=False).put() strip.strt_p2p(pin1=mmi_up.pin['opt_a1'],pin2=pin2test.pin['opt_b1'],arrow=False).put() ## revised in 2026.06.07 by Qin Yue # legacy: 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_up.pin['opt_b2'],pin2=mmi_middle.pin['opt_b2'],Lstart=0.5,radius=10,arrow=False).put() ## revised in 2026.06.07 by Qin Yue # legacy: strip.sbend_p2p(pin1=mmi_bottom.pin['b1'],pin2=mmi_middle.pin['b1'],Lstart=2.5,radius=10,arrow=False).put() strip.sbend_p2p(pin1=mmi_bottom.pin['opt_b1'],pin2=mmi_middle.pin['opt_b1'],Lstart=2.5,radius=10,arrow=False).put() ''' Add pads ''' pad_ground = pad.cell.put( ## revised in 2026.06.07 by Qin Yue # legacy: mmi_input.pin['b1'].x+40+30+50,mmi_input.pin['b1'].y+5+40-4,0 mmi_input.pin['opt_b1'].x+40+30+50,mmi_input.pin['opt_b1'].y+5+40-4,0 ) pad_signal = pad.cell.put( ## revised in 2026.06.07 by Qin Yue # legacy: mmi_input.pin['b1'].x+40+30+100+50,mmi_input.pin['b1'].y+5+40-4,0 mmi_input.pin['opt_b1'].x+40+30+100+50,mmi_input.pin['opt_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 ''' ## revised in 2026.06.07 by Qin Yue # legacy: strip.strt(length=60,arrow=False).put(mmi_input.pin['a1']) strip.strt(length=60,arrow=False).put(mmi_input.pin['opt_a1']) gc.cell.put('g1') ## revised in 2026.06.07 by Qin Yue # legacy: strip.strt(length=60,arrow=False).put(mmi_up.pin['b1']) strip.strt(length=60,arrow=False).put(mmi_up.pin['opt_b1']) gc.cell.put('g1') ## revised in 2026.06.07 by Qin Yue # legacy: strip.strt(length=100,arrow=False).put(mmi_middle.pin['a1']) strip.strt(length=100,arrow=False).put(mmi_middle.pin['opt_a1']) gc.cell.put('g1') ## revised in 2026.06.07 by Qin Yue # legacy: strip.strt(length=60,arrow=False).put(mmi_bottom.pin['b2']) strip.strt(length=60,arrow=False).put(mmi_bottom.pin['opt_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