diff --git a/Handbook.md b/Handbook.md deleted file mode 100644 index 5e17c80..0000000 --- a/Handbook.md +++ /dev/null @@ -1,23 +0,0 @@ -# mxPIC Quick-Reference Style Guide - - -
- mxPIC Quick-Reference Style Guide -
- -Welcome to the collaboration of the repository of **mxPIC** program. This repository is aimed for a self-designed PDK that based on ***nazca***, ***gdstk*** and other libraries for the functional build up of photonic integrated circuits (PIC). - -This is the fast-track guide to contributing to **mxPIC**. Adherence to these rules is mandatory to pass the automated CI/CD pipeline. - -### 1. Primitives - -#### 1.1 edge_couplers -##### 1.1.1 EC_px3 -Three segement dual-layer etching edge coupler. Previously designed for silicon nitrides. - - - - |Attribute| description |type | default value | -|---|---|---|---| - - diff --git a/README.md b/README.md index ee6e1ef..bc49e8a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# mxPIC Quick-Reference Style Guide +# mxPIC Quick-Reference Style Guide Welcome to the collaboration of the repository of **mxPIC** program. This repository is aimed for a self-designed PDK that based on ***nazca***, ***gdstk*** and other libraries for the functional build up of photonic integrated circuits (PIC). @@ -11,7 +11,35 @@ This is the fast-track guide to contributing to **mxPIC**. Adherence to these ru - pandas 0.12.0 - -## 1. Annotation & Documentation +## 1. File & architecture +The repository have an archtecture like this. + +``` +mxpic_forge/ +├── primitives/ +│ ├── edge_couplers/ +│ │ └── EC_dual_layer_px3.py +│ ├── multimode_interferomters/ +│ ├── directional_couplers/ +│ ├── grating_couplers/ +│ └── microring_modulators/ +├── composite/ +│ └── mach_zender_modulators/ +├── electronics/ +│ ├── resistors/ +│ ├── inductors/ +│ ├── vias/ +│ └── pads/ +├── others/ +├── structures/ +├── routing/ +├── foundries/ +└── docs/ + └── source/ + └── conf.py +``` + +## 2. Annotation & Documentation We strictly use **Type Hints** (PEP 484) and **NumPy-Style Docstrings**. * **Rule:** Every function MUST have its parameter types and return type explicitly declared. @@ -42,7 +70,7 @@ We strictly use **Type Hints** (PEP 484) and **NumPy-Style Docstrings**. ``` --- -## 2. Class mangement of devices +## 3. Class mangement of devices The devices are divided into four major types, which is **primitives, composite, electronics, and others**. All photonic and electronic devices are built within the for types. In the definitial of classes, compulsary keys are required, including **name** , **show_pins** , all defaults are None. All classes are cored at the **cell** class of **nazca**, which is generated through an internal method named "generate_gds" @@ -62,7 +90,7 @@ class GratingCoupler(): return C ``` --- -## 3. Port information formatting +## 4. Port information formatting The basic routing algorthium is based the information between nodes, which is the **pin** attribute inside each file. The formatting of **pin** name will be important. **Note**: The pin name should only be related to different functionalities instead of material, layer or shaped. Say, the pin name of a *silicon directional coupler* is the same as a *silicon nitride directional coupler*. @@ -77,3 +105,11 @@ The basic routing algorthium is based the information between nodes, which is th | Transistors (MOS)| ele_ | gt/su/dr | 1~x | ele_gt1 | MOSFET +## 5. Layer and cross-sections +Inside **nazca**, an important feature is the concept of **cross-sections (xs)**. Typically, **xs** is a conbination of different layers which is required by the foundry DRC. Below is a example in Silterra tapeout. +``` python +nazca.add_xsection("strip") +nazca.add_layer_to_xsection("WG_HM",growx=2,growy=2) +nazca.add_layer_to_xsection("WG_STRIP",growx=2,growy=2) +``` + diff --git a/README.pdf b/README.pdf index cd7c187..353d600 100644 Binary files a/README.pdf and b/README.pdf differ diff --git a/index.md b/index.md deleted file mode 100644 index 1fa0f19..0000000 --- a/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Welcome to mxPIC Handbook - -```{toctree} -:maxdepth: 2 -:caption: Components: - -primitives/edge_couplers/edge_couplers diff --git a/primitives/directional_couplers/directional_couplers.py b/primitives/directional_couplers/directional_couplers.py new file mode 100644 index 0000000..0b97e4a --- /dev/null +++ b/primitives/directional_couplers/directional_couplers.py @@ -0,0 +1,1076 @@ + +import nazca as nd +import numpy as np + +from ..pic import taper + +from ..structures import * + +from ..routing import Route +# import nazca.interconnects as IC +# class Route(IC.Interconnect): +# pass + +from ..structures import _my_polygon + +from ..basic import __cell_arg__ + +class ring_bus_wg : + ## two types: + ## DC, BDC + def __init__(self, + xs='strip', + R_cp = 20, + w_bus = 0.5, + bend_DC = True, + w_wg = 0.5, + dLc = 10, + dAc = 10, + + euler_transistion = False, + dL_trans = 10, + dA_trans = 30, + R_max_trans = 100, + w_trans = 0.5, + + euler_anti_bend = False, + R_max_anti = 100, + R_min_anti = 10, + A_anti = None, + res = 0.1, + wg_Ltp = 5, + dL_p2p = None, + sharp_patch = True, + show_pins = False, + end_patch = False, + clothoid_order = 1, + ) -> None: + """_summary_ + + Args: + xs (str, optional): waveguide xsection. Defaults to 'strip'. + R_cp (int, optional): coupling waveguide radius, for bend coupling . Defaults to 20. + w_bus (float, optional): coupling waveguide width. Defaults to 0.5. + w_wg (float, optional): waveugide port width. Defaults to 0.5. + bend_DC (bool, optional): BDC or DC. Defaults to True. + dLc (int, optional): for DC, the coupling length. Defaults to 10. + dAc (int, optional): for BDC, the coupling angle. Defaults to 10. + n_points (int, optional): _description_. Defaults to 512. + euler_transistion (bool, optional): _description_. Defaults to False. + dL_trans (int, optional): _description_. Defaults to 10. + dA_trans (int, optional): _description_. Defaults to 30. + R_max_trans (int, optional): _description_. Defaults to 100. + w_trans (float, optional): _description_. Defaults to 0.5. + euler_anti_bend (bool, optional): _description_. Defaults to False. + R_max_anti (int, optional): _description_. Defaults to 100. + R_min_anti (int, optional): _description_. Defaults to 10. + A_anti (_type_, optional): _description_. Defaults to None. + res (float, optional): _description_. Defaults to 0.1. + wg_Ltp (int, optional): _description_. Defaults to 5. + dL_p2p (_type_, optional): _description_. Defaults to None. + sharp_patch (bool, optional): _description_. Defaults to True. + show_pins (bool, optional): _description_. Defaults to False. + """ + + self.xs = xs + self.R_cp = R_cp + self.w_bus = w_bus + self.dLc = dLc + self.dAc = dAc + # self.n_points = n_points + self.w_wg = w_wg + self.bend_DC = bend_DC + self.euler_transistion = euler_transistion + self.dL_trans = dL_trans + self.dA_trans = dA_trans + self.R_max_trans = R_max_trans + self.w_trans = w_trans + # self.euler_anti_bend = euler_anti_bend ## parameter abondond + self.R_max_anti = R_max_anti + self.R_min_anti = R_min_anti + self.A_anti = A_anti + self.wg_Ltp = wg_Ltp + self.dL_p2p = dL_p2p + self.res = res + + self.end_patch = end_patch + + self.L = 0 + self.clothoid_order = clothoid_order + self.cell = self.generate_gds(sharp_patch=sharp_patch,show_pins=show_pins) + + def generate_gds(self,sharp_patch,show_pins=False): + with nd.Cell(instantiate=False) as C: + + w_crack = 0.002 + if (self.bend_DC and self.euler_transistion): + if (self.A_anti == None): + self.A_anti = self.dAc/2+self.dA_trans + + cp = Clothoid(R=[self.R_cp,self.R_cp,self.R_max_trans,self.R_min_anti,self.R_max_anti], + A=[0, self.dAc/2,self.dAc/2+self.dA_trans,(self.dAc/2+self.dA_trans) - self.A_anti/2,(self.dAc/2+self.dA_trans) - self.A_anti], + w=[self.w_bus,self.w_bus,self.w_trans,(self.w_trans+self.w_wg)/2,self.w_wg],xs=self.xs, + spiral_order=[1,self.clothoid_order,1,1], + sharp_patch=sharp_patch,end_patch=self.end_patch) + ar = cp.cell.put('a1',0,0,0).pin['b1'] + al = cp.cell.put('a1',0,0,180,flip=1).pin['b1'] + + nd.strt(length=w_crack,width=self.w_bus,xs=self.xs).put(-w_crack/2,0,0) + nd.strt(length=w_crack,width=self.w_wg,xs=self.xs).put(ar.x-w_crack/2,ar.y,0) + nd.strt(length=w_crack,width=self.w_wg,xs=self.xs).put(al.x-w_crack/2,al.y,0) + + self.L = self.L + cp.L0 + + elif (self.bend_DC): + """ Bend DC without Euler transision """ + + # if (self.bend_DC): + cp = circle(xs=self.xs,radius=self.R_cp, width = self.w_bus, theta_start = 270-self.dAc/2, theta_stop=270+self.dAc/2,res=self.res, + # n_points=self.n_points, + sharp_patch=sharp_patch).cell.put(0,self.R_cp,0) + al = cp.pin['a1'] + ar = cp.pin['b1'] + + self.L = self.L + self.R_cp*self.dAc/180*np.pi + + self.w_trans = self.w_bus + TL = nd.strt(length=self.dL_trans,width=self.w_bus,xs=self.xs).put('a0',al) + TR = nd.strt(length=self.dL_trans,width=self.w_bus,xs=self.xs).put('a0',ar) + Ainner = self.dAc/2 + + self.L = self.L + self.dL_trans*2 + + if (self.A_anti == None): + self.A_anti = Ainner + + Anti = circle(xs=self.xs,radius=self.R_max_anti, width = self.w_trans, theta_start = 90, theta_stop=90+self.A_anti,res=self.res, + # n_points=self.n_points, + sharp_patch=sharp_patch) + ar = Anti.cell.put('b1',TR.pin['b0']).pin['a1'] + al = Anti.cell.put('b1',TL.pin['b0'],flip=1).pin['a1'] + + self.L = self.L + self.R_max_anti*self.A_anti*2*180/np.pi + + + TPR = nd.taper(length=self.wg_Ltp,width1=ar.width,width2=self.w_wg,xs=self.xs).put('a0',ar) + TPL = nd.taper(length=self.wg_Ltp,width1=al.width,width2=self.w_wg,xs=self.xs).put('a0',al) + self.L = self.L + self.wg_Ltp*2 + + L = (TPR.pin['b0'].x - TPL.pin['b0'].x) ## L distance pin2pin + if (self.dL_p2p!=None): + if (L, is not recongized as a cell") + + inst = self.cell.put('a1',-self.L/2,self.cell.pin['a1'].y,0) + pic_strip = Route(radius=15,width=self.wu_in,xs=self.xs) + + GT_U_In = gc_cell.put('g1',-dX_gc2gc/2,dY_gc2gc/2,180) + nd.taper(width1=GT_U_In.pin['g1'].width,width2=self.wu_in,length=5,xs=self.xs).put(GT_U_In.pin['g1']) + pic_strip.sbend_p2p(original_function=not sharp_patch,pin2=inst.pin['a1']).put() + + GT_D_In = gc_cell.put('g1',-dX_gc2gc/2,-dY_gc2gc/2,180) + nd.taper(width1=GT_D_In.pin['g1'].width,width2=self.wd_in,length=5,xs=self.xs).put(GT_D_In.pin['g1']) + pic_strip.sbend_p2p(original_function=not sharp_patch,pin2=inst.pin['a2'],width=self.wd_in).put() + + GT_U_Out = gc_cell.put('g1', dX_gc2gc/2,dY_gc2gc/2,0) + nd.taper(width1=GT_U_Out.pin['g1'].width,width2=self.wu_out,length=5,xs=self.xs).put(GT_U_Out.pin['g1']) + pic_strip.sbend_p2p(original_function=not sharp_patch,pin2=inst.pin['b1'],width=self.wu_out).put() + + GT_D_Out = gc_cell.put('g1', dX_gc2gc/2,-dY_gc2gc/2,0) + nd.taper(width1=GT_D_Out.pin['g1'].width,width2=self.wd_out,length=5,xs=self.xs).put(GT_D_Out.pin['g1']) + pic_strip.sbend_p2p(original_function=not sharp_patch,pin2=inst.pin['b2'],width=self.wd_out).put() + + return C + + +class DC(ADC_STD_2x2): + def __init__(self, + name = None, + xs:str='strip', + w_cp:float=0.45, + w_wg:float=0.45, + L_cp:float=30, + angle:float=20, + gap:float=0.2, + sbend_type:str='circular', + Rmax:float=None, + Rmin:float=5, + R0:float=10, + tp_angle:float=2, + sharp_patch:bool=True, + show_pins:bool=False): + """_summary_ + + Args: + tapeout (_type_): _description_ + xs (str, 'strip' | 'rib' | ...): Nazca xsection of the waveguide. Defaults to 'strip'. + w_cp (float): Width of the coupling area. Defaults to 0.45. + w_wg (float): Width of the port waveguide. Defaults to 0.45. + L_cp (float): Length of the coupling area. Defaults to 30. + angle (float): Bned angle of the port. Defaults to 20. + gap (float): Gap width of the coupling area. Defaults to 0.2. + sbend_type (str, 'euler' | 'circular'): Bend type of the ouput port. Defaults to 'euler'. + Rmax (_type_, optional): Max bending radius of euler. Defaults to None. + Rmin (int, optional): Mini bending radius of euler. Defaults to 5. + R0 (int, optional): Bending radius. Defaults to 10. + tp_angle (int, optional): Taper angle of the w_cp to w_wg tapering. Defaults to 2. + sharp_patch (bool, optional): Add patch to avoid sharp angle. Defaults to True. + """ + super().__init__(name, xs, wu0=w_cp, wu1=w_cp, + wu_in=w_wg, wu_out=w_wg, + wd0=w_cp, wd1=w_cp, + wd_in=w_wg, wd_out=w_wg, + Lu=L_cp, Ld=L_cp, angle=angle, + g0=gap, g1=gap, + sbend_type=sbend_type, Rmax=Rmax, Rmin=Rmin, + Ru0=R0, Ru1=R0, Rd0=R0, Rd1=R0, tp_angle=tp_angle, sharp_patch=sharp_patch,show_pins=show_pins) + + def generate_test_gds(self,gc,dX_gc2gc=300,dY_gc2gc=40,sharp_patch=True): + with nd.Cell(name=self.cell.cell_name+"_test", instantiate=False) as C: + + gc_cell = __cell_arg__(arg=gc,arg_name="gc",func_name="mxpic::DC::generate_test_gds") + + gc_ID = gc_cell.put('g1',0,-dY_gc2gc,180) + gc_IU = gc_cell.put('g1',0,0,180) + gc_OU = gc_cell.put('g1',dX_gc2gc,-dY_gc2gc,0) + gc_OD = gc_cell.put('g1',dX_gc2gc,0,0) + # Put DC + inst = self.cell.put('a1',-self.L/2+dX_gc2gc/2,self.cell.pin['a1'].y-dY_gc2gc/2,0) + + # Connect all the ports + stripe=Route(radius=self.Ru0, width=self.wu_in, xs="strip") + + if (abs(inst.pin['b1'].y - inst.pin['b2'].y)<10) : + temp = stripe.sbend_route(pin=inst.pin['a1'],offset=5).put(flip=1) + stripe.sbend_p2p(pin1=gc_IU.pin['g1'],pin2=temp.pin['b0'],arrow=False,original_function=not sharp_patch).put() + + temp = stripe.sbend_route(pin=inst.pin['a2'],offset=5).put() + stripe.sbend_p2p(pin1=gc_ID.pin['g1'],pin2=temp.pin['b0'],arrow=False,original_function=not sharp_patch).put() + + temp = stripe.sbend_route(pin=inst.pin['b1'],offset=5).put() + stripe.sbend_p2p(pin1=gc_OD.pin['g1'],pin2=temp.pin['b0'],arrow=False,original_function=not sharp_patch).put() + + temp = stripe.sbend_route(pin=inst.pin['b2'],offset=5).put(flip=1) + stripe.sbend_p2p(pin1=gc_OU.pin['g1'],pin2=temp.pin['b0'],arrow=False,original_function=not sharp_patch).put() + else : + stripe.sbend_p2p(pin1=gc_IU.pin['g1'],pin2=inst.pin['a1'],arrow=False,original_function=not sharp_patch).put() + stripe.sbend_p2p(pin1=gc_ID.pin['g1'],pin2=inst.pin['a2'],arrow=False,original_function=not sharp_patch).put() + stripe.sbend_p2p(pin1=gc_OD.pin['g1'],pin2=inst.pin['b1'],arrow=False,original_function=not sharp_patch).put() + stripe.sbend_p2p(pin1=gc_OU.pin['g1'],pin2=inst.pin['b2'],arrow=False,original_function=not sharp_patch).put() + return C + +class BS_tdc(ADC_STD_2x2): + def __init__(self, + name=None, + xs:str ='strip', + wa0:float = 0.35, + wa1:float = 0.45, + wb0:float = 0.55, + wb1:float = 0.45, + w_wg:float = 0.45, + gap:float =0.2, + Lt:float =20, + R0:float =30, + angle:float =15, + sbend_type:str ='circle', + ): + """_summary_ + + Args: + tapeout (class): foundry used in your design + xs (str, optional): nazca xsection used for the coupler. Defaults to 'strip'. + wa0 (float, optional): Upper waveguide input port width. Defaults to 0.35. + wa1 (float, optional): Upper waveguide ouput port width. Defaults to 0.45. + wb0 (float, optional): Lower waveguide input port width. Defaults to 0.55. + wb1 (float, optional): Lower waveguide ouput port width. Defaults to 0.45. + w_wg (float, optional): The width of the waveguide interface. Defaults to 0.45. + gap (float, optional): Gap width between two waveguides. Defaults to 0.2. + Lt (int, optional): Taper Length of the coupler. Defaults to 20. + R0 (int, optional): The bending radius of the output/input port. Defaults to 30. + angle (int, optional): The angle of the bending of the output/input port. Defaults to 15. + sbend_type (str, optional): The type of the output/input bending, Euler or Circular. Defaults to 'circle'. + """ + super().__init__(name = name, + xs=xs, + wu0=wa0,wu1=wa1,wu_in=w_wg,wu_out=w_wg, + wd0=wb0,wd1=wb1,wd_in=w_wg,wd_out=w_wg, + g0=gap,g1=gap, + Ru0=R0,Ru1=R0,Rmin=5, + Rd0=R0,Rd1=R0,angle=angle, + Ld=Lt,Lu=Lt, + sbend_type=sbend_type) + + +class MDM(ADC_STD_2x2): + def __init__(self, + name = None, + xs:str='strip', + wb0:float =0.45, + wb1:float =0.61, + wb_in:float =0.45, + wb_out:float =0.88, + w_wg:float =0.45, + w0:float =0.33, + w1:float =0.2, + gap0:float =0.2, + Lt_bus:float =20, + R0:float =40, + angle:float =22.5, + Lt_cp:float =None, + gap1:float =None, + Lb0:float =None, + symmetric_BUS:bool =True, + single_end:bool =True, + Rmin:float =8 + ): + """_summary_ + + Args: + tapeout (_type_): _description_ + xs (str): Device waveguide xsection. Defaults to 'strip'. + wb0 (float): Coupling region, **BUS** waveguide starting width. Defaults to 0.45. + wb1 (float): Coupling region, **BUS** waveguide ending width. Defaults to 0.61. + wb_in (float): **BUS** waveguide input width. Defaults to 0.45. + wb_out (float): **BUS** waveguide output width. Defaults to 0.88. + w_wg (float): **coupler** waveguide input width. Defaults to 0.45. + w0 (float): Coupling region, **coupler** waveguide starting width. Defaults to 0.33. + w1 (float): Coupling region, **coupler** waveguide ending width. Defaults to 0.2. + gap0 (float): Gap width at starting. Defaults to 0.2. + Lt_bus (float): Taper length for **BUS** from stating to ending. Defaults to 20. + R0 (float): _description_. Defaults to 40. + angle (float): _description_. Defaults to 22.5. + Lt_cp (float | None): Taper length for **coupler** from stating to ending. Defaults to None. + gap1 (float | None): Gap width at ending, usually not used. Defaults to None. + name (_type_ | None): _description_. Defaults to None. + Lb0 (_type_ | None): _description_. Defaults to None. + symmetric_BUS (bool): **BUS** waveguide type selection, symmetric or not. Defaults to True. + single_end (bool): _description_. Defaults to True. + Rmin (int): For euler bend, the minimum radius. Defaults to 8. + """ + + self.wb0=wb0 ## BUS waveguide width on the input + + if (wb1!=None): ## BUS waveguide width on the output + self.wb1=wb1 + else: + self.wb1=wb0 + wb1 = wb0 + self.w0=w0 + if (w1!=None): + self.w1=w1 + else: + self.w1=w0 + w1= w0 + self.w_wg=w_wg + self.gap0=gap0 + if (gap1!=None): + self.gap1=gap1 + else: + self.gap1=gap0 + gap1 = gap0 + + if (Lt_cp==None): + Lt_cp = Lt_bus + + self.Lt_bus = Lt_bus + self.Lt_cp = Lt_cp + + self.xs = xs + self.Lt_cp = Lt_cp + self.R0 = R0 + self.angle = angle + self.symmetric_BUS = symmetric_BUS ## defining the type of bus waveguide + self.Rmin = Rmin + super().__init__(name = name,xs=xs, + wu0=wb0,wu1=wb1,wu_in=wb_in,wu_out=wb_out, + wd0=w0,wd1=w1,wd_in=w_wg,wd_out=w1, + Lu=Lt_bus,Ld=Lt_cp, + g0=gap0,g1=gap1, + Ru0=0,Ru1=0, + Rmin=self.Rmin, + Rd0=R0,Rd1=R0, + angle=angle) + + self.L = np.abs(self.cell.pin['a1'].x-self.cell.pin['b1'].x) + + def generate_test_gds(self,gc,dX_gc2gc=300,dY_gc2gc=40,sharp_patch=True): + with nd.Cell(name=self.cell.cell_name+"_test", instantiate=False) as C: + + gc_cell = __cell_arg__(arg=gc,arg_name="gc",func_name="mxpic::MDM::generate_test_gds") + + # Put DC + L_taper = (np.abs(self.cell.pin['a1'].width-gc_cell.pin['g1'].width))/np.tan(2/180*pi) + + mdm_In = self.cell.put('b1',-dX_gc2gc/2 + self.L + 25+L_taper,0,180) + mdm_Out = self.cell.put('b1', dX_gc2gc/2 - self.L - 25-L_taper,0,0,flip=1) + + + GC_IU = gc_cell.put('g1',-dX_gc2gc/2,dY_gc2gc/2,180) + GC_ID = gc_cell.put('g1',-dX_gc2gc/2,-dY_gc2gc/2,180) + GC_OU = gc_cell.put('g1', dX_gc2gc/2,dY_gc2gc/2,0) + GC_OD = gc_cell.put('g1', dX_gc2gc/2,-dY_gc2gc/2,0) + + # # Connect all the ports + stripe=Route(radius=10, width=self.w_wg, xs="strip") + nd.taper(width1=mdm_In.pin['a1'].width,width2=gc_cell.pin['g1'].width,length=L_taper,xs='strip').put(mdm_In.pin['a1']) + stripe.sbend_p2p(pin2=GC_IU.pin['g1'],arrow=False,original_function=not sharp_patch).put() + + stripe.sbend_p2p(pin1=GC_ID.pin['g1'],pin2=mdm_In.pin['a2'],arrow=False,original_function=not sharp_patch).put() + + nd.taper(width1=mdm_Out.pin['a1'].width,width2=gc_cell.pin['g1'].width,length=L_taper,xs='strip').put(mdm_Out.pin['a1']) + stripe.sbend_p2p(pin1=GC_OU.pin['g1'],arrow=False,original_function=not sharp_patch).put() + stripe.sbend_p2p(pin1=GC_OD.pin['g1'],pin2=mdm_Out.pin['a2'],arrow=False,original_function=not sharp_patch).put() + + stripe.taper_p2p(pin1=mdm_In.pin['b1'],pin2=mdm_Out.pin['b1'],arrow=False).put() + + return C + + + + +class DC_bend : + ''' + This is a class for bend directional coupler for broadband and fabrication tolerant power splitting. + Written by HU GAOLEI at 2022.5.15. + ''' + + def __init__( + self, + name = None, + w_in=0.45, + w_out=0.45, + gap=0.2, + r_in=40, + theta_arc=30, + w_wg=0.45, + theta_ext=15, + xs_wg='strip', + + sharp_patch=True, + show_pins=False + ): + ''' + Initilization bend directional coupler. + + Args: + - w_in [um] Width of the inner waveguide in the coupling region + - w2 [um] Width of the outer waveguide in the coupling region + - gap [um] Gap between two waveguide in the coupling region + - r_in [um] Bend radius of the inner waveguide + - theta_arc [degree] Angle of the coupling region + - w_wg [um] Width of input and output waveguide + ''' + + self.name = name + if (self.name==None): + self.instantiate = False + else : + self.instantiate = True + + self.w_in = w_in + self.w_out = w_out + self.gap = gap + self.r_in = r_in + self.r_out = self.r_in+(self.w_in+self.w_out)/2+self.gap + self.theta_arc = theta_arc + self.theta_ext = theta_ext + self.w_wg = w_wg + self.xs_wg = xs_wg + self.sharp_patch = sharp_patch + self.show_pins = show_pins + self.cell = self.generate_gds(name) + + def generate_gds(self, cellname=""): + ''' + Generate GDS. + ''' + with nd.Cell(name="DC_Bend"+str(cellname), instantiate=self.instantiate) as C: + wg = Route(width=self.w_wg, radius=10, xs=self.xs_wg) + ## Put Outer Bend Region first + + bent_out_coup_r = wg.bend(width=self.w_out, radius=self.r_out, angle=self.theta_arc/2, arrow=False).put(0, 0, 0) + bend_out_connect_r = wg.bend(width=self.w_out, radius=self.r_out, angle=-self.theta_arc/2, arrow=False).put(bent_out_coup_r.pin['b0']) + + bent_out_coup_l = wg.bend(width=self.w_out, radius=self.r_out, angle=self.theta_arc/2, arrow=False).put(0, 0, 0, flop=True) + bend_out_connect_l = wg.bend(width=self.w_out, radius=self.r_out, angle=self.theta_arc/2, arrow=False).put(bent_out_coup_l.pin['b0']) + ## Put Inner Bend Region + bent_in_coup_r = wg.bend(width=self.w_in, radius=self.r_in, angle=self.theta_arc/2, arrow=False).put(0, (self.w_in+self.w_out)/2+self.gap, 0) + wg.bend(width=self.w_in, radius=self.r_in, angle=self.theta_ext, arrow=False).put(bent_in_coup_r.pin['b0']) + bend_in_connect_r = wg.bend(width=self.w_in, angle=-self.theta_arc/2-self.theta_ext, arrow=False).put() + + bend_in_coup_l = wg.bend(width=self.w_in, radius=self.r_in, angle=self.theta_arc/2, arrow=False).put(0, (self.w_in+self.w_out)/2+self.gap, 0, flop=True) + wg.bend(width=self.w_in, radius=self.r_in, angle=-self.theta_ext, arrow=False).put(bend_in_coup_l.pin['b0']) + bend_in_connect_l = wg.bend(width=self.w_in, angle=self.theta_arc/2+self.theta_ext, arrow=False).put() + + ## Make upper and lower waveguide's output at the same x-plane + if bend_out_connect_r.pin['b0'].x > bend_in_connect_r.pin['b0'].x: + l_extra = bend_out_connect_r.pin['b0'].x-bend_in_connect_r.pin['b0'].x + bend_in_connect_r = wg.strt(length=l_extra, width=self.w_in, arrow=False).put(bend_in_connect_r.pin['b0']) + bend_in_connect_l = wg.strt(length=l_extra, width=self.w_in, arrow=False).put(bend_in_connect_l.pin['b0']) + elif bend_out_connect_r.pin['b0'].x < bend_in_connect_r.pin['b0'].x: + l_extra = bend_in_connect_r.pin['b0'].x-bend_out_connect_r.pin['b0'].x + bend_out_connect_r = wg.strt(length=l_extra, width=self.w_out, arrow=False).put(bend_out_connect_r.pin['b0']) + bend_out_connect_l = wg.strt(length=l_extra, width=self.w_out, arrow=False).put(bend_out_connect_l.pin['b0']) + + ## Add taper to make the width in the coupling region connect with normal wg + l_taper = 2 + port_out1 = wg.taper(width1=self.w_in, width2=self.w_wg, length=l_taper, arrow=False).put(bend_in_connect_r.pin['b0']) + port_in1 = wg.taper(width1=self.w_in, width2=self.w_wg, length=l_taper, arrow=False).put(bend_in_connect_l.pin['b0']) + port_out2 = wg.taper(width1=self.w_out, width2=self.w_wg, length=l_taper, arrow=False).put(bend_out_connect_r.pin['b0']) + port_in2 = wg.taper(width1=self.w_out, width2=self.w_wg, length=l_taper, arrow=False).put(bend_out_connect_l.pin['b0']) + + ## Put pins + nd.Pin(name="a0", width=self.w_wg).put((port_in1.pin['b0'].x+port_in2.pin['b0'].x)/2, (port_in1.pin['b0'].y+port_in2.pin['b0'].y)/2, 180) + nd.Pin(name="a1", width=self.w_wg).put(port_in1.pin['b0']) + nd.Pin(name="a2", width=self.w_wg).put(port_in2.pin['b0']) + nd.Pin(name="b1", width=self.w_wg).put(port_out1.pin['b0']) + nd.Pin(name="b2", width=self.w_wg).put(port_out2.pin['b0']) + self.width = np.abs(port_out1.pin['b0'].y - port_out2.pin['b0'].y) + self.length = np.abs(port_out1.pin['b0'].x - port_in1.pin['b0'].x) + if self.show_pins: + nd.put_stub() + + pin_a1 = port_in1.pin['b0'] + pin_a2 = port_in2.pin['b0'] + pin_b1 = port_out1.pin['b0'] + pin_b2 = port_out2.pin['b0'] + dY1 = np.abs(pin_a1.y-pin_a2.y) + dX1 = np.abs(pin_a1.x-pin_a2.x) + dY2 = np.abs(pin_b1.y-pin_b2.y) + dX2 = np.abs(pin_b1.x-pin_b2.x) + + if (self.sharp_patch==True): + for layers,growx,growy,acc in nd.layeriter(xs=self.xs_wg): + (a1,b1), (a2,b2),c1,c2 = growx + if (b1!=0 and b2!=0): + L_patch = (dX1+5)*(a1-a2)+(b1-b2) + W_patch = (dY1+self.w_wg)*(a1-a2)+(b1-b2) + nd.strt(length=L_patch,width=W_patch,layer=layers).put(np.max([pin_a1.x,pin_a2.x]),(pin_a1.y+pin_a2.y)/2,180) + + + L_patch = (dX2+5)*(a1-a2)+(b1-b2) + W_patch = (dY2+self.w_wg)*(a1-a2)+(b1-b2) + nd.strt(length=L_patch,width=W_patch,layer=layers).put(np.min([pin_b1.x,pin_b2.x]),(pin_b1.y+pin_b2.y)/2,0) + return C + + def generate_test_gds(self,gc,dX_gc2gc=300,dY_gc2gc=40,sharp_patch=True): + with nd.Cell(name=self.cell.cell_name+"_test", instantiate=False) as C: + + gc_cell = __cell_arg__(arg=gc,arg_name="gc",func_name="mxpic::DC_bend::generate_test_gds") + + # gc_ID = gc_cell.put('g1',-dX_gc2gc/2,-dY_gc2gc/2,180) + # gc_IU = gc_cell.put('g1',-dX_gc2gc/2,dY_gc2gc/2,180) + # gc_OU = gc_cell.put('g1',dX_gc2gc/2,-dY_gc2gc/2,0) + # gc_OD = gc_cell.put('g1',dX_gc2gc/2,dY_gc2gc/2,0) + # # Put DC + # dc = self.cell.put('a1',-self.length/2,0,0) + + gc_ID = gc_cell.put('g1',0,-dY_gc2gc,180) + gc_IU = gc_cell.put('g1',0,0,180) + gc_OU = gc_cell.put('g1',dX_gc2gc,-dY_gc2gc,0) + gc_OD = gc_cell.put('g1',dX_gc2gc,0,0) + # Put DC + dL_DC = self.cell.pin['b1'].x - self.cell.pin['a1'].x + inst = self.cell.put('a1',-dL_DC/2+dX_gc2gc/2,self.cell.pin['a1'].y-dY_gc2gc/2,0) + + # Connect all the ports + stripe=Route(radius=10, width=self.w_wg, xs="strip") + stripe.sbend_p2p(pin1=gc_IU.pin['g1'],pin2=inst.pin['a1'],arrow=False,original_function=not sharp_patch).put() + stripe.sbend_p2p(pin1=gc_ID.pin['g1'],pin2=inst.pin['a2'],arrow=False,original_function=not sharp_patch).put() + stripe.sbend_p2p(pin1=gc_OD.pin['g1'],pin2=inst.pin['b1'],arrow=False,original_function=not sharp_patch).put() + stripe.sbend_p2p(pin1=gc_OU.pin['g1'],pin2=inst.pin['b2'],arrow=False,original_function=not sharp_patch).put() + return C + + +class DC_pX_3sg: + def __init__(self, + name = None, + xs_wg:str='strip', + Lc1:float=10, + Lp1:float=5, + Lc2:float=10, + Lt:float=1, + w_cp:float=0.5, + dw:float=0.1, + gap:float=0.2, + R0:float=10, + A:float=15, + w_wg:float=0.45, + pX_type:str="symmetric", + port_symmetric:bool=True, + sharp_patch:bool=True): + + self.name = name + if (self.name==None): + self.instantiate = False + else : + self.instantiate = True + + self.xs_wg=xs_wg + self.Lc1=Lc1 + self.Lp1=Lp1 + self.Lc2=Lc2 + self.Lt=Lt + self.w_cp=w_cp + self.dw=dw + self.gap=gap + self.R0=R0 + self.A=A + self.w_wg=w_wg + self.sharp_patch=sharp_patch + self.pX_type=pX_type + self.port_symmetric=port_symmetric + cells = self.generate_gds(err=0) + self.cell = cells[0] + self.cellU = cells[2] + self.cellD = cells[1] + + self.L = np.abs(self.cell.pin['a1'].x - self.cell.pin['b1'].x) + self.length = self.L + + def generate_gds(self,err=0): + if (self.name is not None): + nameUP = self.name + "_up" + nameDOWN = self.name + "_down" + else: + nameUP = None + nameDOWN = None + + w_cp = self.w_cp + err + gap = self.gap - err + + with nd.Cell(instantiate=False) as CUP: + + ## first segment coupler + cp_u = nd.strt(length=self.Lc1,width=w_cp,xs=self.xs_wg).put(0, w_cp/2+gap/2,0) + nd.Pin(name='a0').put(cp_u.pin['a0']) + + cp_u_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(cp_u.pin['a0'],flip=1) + cp_u_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(flip=0) + cp_u_r = nd.taper(width1=w_cp,width2=self.w_wg,xs=self.xs_wg,length=2).put() + + nd.Pin(name='a1',pin=cp_u_r.pin['b0'],width=cp_u_r.pin['b0'].width).put() + + ## middle segment phase shifter + if self.pX_type == "symmetric": + cp_u = nd.taper(length=self.Lt,width1=self.w_cp,width2=self.w_cp+self.dw,shift=0,xs=self.xs_wg).put(cp_u.pin['b0']) + cp_u = nd.strt(length=self.Lp1,width=w_cp+self.dw,xs=self.xs_wg).put() + cp_u = nd.taper(length=self.Lt,width1=self.w_cp+self.dw,width2=self.w_cp,shift=0,xs=self.xs_wg).put() + + else: + cp_u = nd.taper(length=self.Lt,width1=self.w_cp,width2=self.w_cp+self.dw,shift=self.dw/2,xs=self.xs_wg).put(cp_u.pin['b0']) + cp_u = nd.strt(length=self.Lp1,width=w_cp+self.dw,xs=self.xs_wg).put() + cp_u = nd.taper(length=self.Lt,width1=self.w_cp+self.dw,width2=self.w_cp,shift=-self.dw/2,xs=self.xs_wg).put() + ## second segment coupler + cp_u = nd.strt(length=self.Lc2,width=w_cp,xs=self.xs_wg).put(cp_u.pin['b0']) + + cp_u_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(cp_u.pin['b0']) + cp_u_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(flip=1) + cp_u_r = nd.taper(width1=w_cp,width2=self.w_wg,xs=self.xs_wg,length=2).put() + + nd.Pin(name='b1',pin=cp_u_r.pin['b0'],width=cp_u_r.pin['b0'].width).put() + + nd.Pin(name='b0').put(cp_u.pin['b0']) + + + with nd.Cell(instantiate=False) as CDOWN: + ## first segment coupler + cp_d = nd.strt(length=self.Lc1,width=w_cp,xs=self.xs_wg).put(0,-(w_cp/2+gap/2),0) + nd.Pin(name='a0').put(cp_d.pin['a0']) + + cp_d_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(cp_d.pin['a0'],flip=0) + + if (self.port_symmetric): + cp_d_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(flip=1) + cp_d_r = nd.taper(width1=w_cp,width2=self.w_wg,xs=self.xs_wg,length=2).put() + else: + cp_d_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put() + # cp_d_r = nd.taper(width1=w_cp,width2=self.w_wg,xs=self.xs_wg,length=2).put() + + nd.Pin(name='a2',pin=cp_d_r.pin['b0'],width=cp_d_r.pin['b0'].width).put() + + ## middle segment phase shifter + if self.pX_type == "symmetric": + + cp_d = nd.taper(length=self.Lt,width1=self.w_cp,width2=self.w_cp-self.dw,shift=0,xs=self.xs_wg).put(cp_d.pin['b0']) + cp_d = nd.strt(length=self.Lp1,width=w_cp-self.dw,xs=self.xs_wg).put() + cp_d = nd.taper(length=self.Lt,width1=self.w_cp-self.dw,width2=self.w_cp,shift=0,xs=self.xs_wg).put() + + else: + cp_d = nd.taper(length=self.Lt,width1=self.w_cp,width2=self.w_cp-self.dw,shift=-self.dw/2,xs=self.xs_wg).put(cp_d.pin['b0']) + cp_d = nd.strt(length=self.Lp1,width=w_cp-self.dw,xs=self.xs_wg).put() + cp_d = nd.taper(length=self.Lt,width1=self.w_cp-self.dw,width2=self.w_cp,shift=self.dw/2,xs=self.xs_wg).put() + + ## second segment coupler + cp_d = nd.strt(length=self.Lc2,width=w_cp,xs=self.xs_wg).put(cp_d.pin['b0']) + + cp_d_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(cp_d.pin['b0'],flip=1) + if (self.port_symmetric): + cp_d_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(flip=0) + cp_d_r = nd.taper(width1=w_cp,width2=self.w_wg,xs=self.xs_wg,length=2).put() + else: + cp_d_r = nd.bend(radius=self.R0,angle=self.A,xs=self.xs_wg,width=w_cp).put(flip=1) + + nd.Pin(name='b2',pin=cp_d_r.pin['b0'],width=cp_d_r.pin['b0'].width).put() + nd.Pin(name='b0').put(cp_d.pin['b0']) + + + with nd.Cell(instantiate=self.instantiate,name=self.name) as C: + wgUp = CUP.put(0,(w_cp/2+gap/2),0) + wgDown = CDOWN.put(0,-(w_cp/2+gap/2),0) + + wgUp.raise_pins(['a1','b1'],['a1','b1']) + wgDown.raise_pins(['a2','b2'],['a2','b2']) + nd.Pin(name='a0').put((self.Lc1+self.Lc2+self.Lp1+self.Lt*2)/2,0,180) + nd.Pin(name='b0').put((self.Lc1+self.Lc2+self.Lp1+self.Lt*2)/2,0,0) + + return (C,CDOWN,CUP) + + def generate_test_gds(self,gc,dX_gc2gc=300,dY_gc2gc=40,sharp_patch=True): + with nd.Cell(name=self.cell.cell_name+"_test", instantiate=False) as C: + + gc_cell = __cell_arg__(arg=gc,arg_name="gc",func_name="mxpic::DC_pX_3sg::generate_test_gds") + + # Put DC + + # GC_IU = gc_cell.put('g1',-dX_gc2gc/2,dY_gc2gc/2,180) + # GC_ID = gc_cell.put('g1',-dX_gc2gc/2,-dY_gc2gc/2,180) + # GC_OU = gc_cell.put('g1', dX_gc2gc/2,dY_gc2gc/2,0) + # GC_OD = gc_cell.put('g1', dX_gc2gc/2,-dY_gc2gc/2,0) + + gc_ID = gc_cell.put('g1',0,-dY_gc2gc,180) + gc_IU = gc_cell.put('g1',0,0,180) + gc_OU = gc_cell.put('g1',dX_gc2gc,-dY_gc2gc,0) + gc_OD = gc_cell.put('g1',dX_gc2gc,0,0) + # Put DC + dL_DC = self.cell.pin['b1'].x - self.cell.pin['a1'].x + # inst = self.cell.put('a1',-dL_DC/2+dX_gc2gc/2,self.cell.pin['a1'].y-dY_gc2gc/2,0) + DC_pX3 = self.cell.put('a1',-dL_DC/2+dX_gc2gc/2,self.cell.pin['a1'].y-dY_gc2gc/2,0) + + # Connect all the ports + stripe=Route(radius=10, width=self.w_wg, xs="strip") + stripe.sbend_p2p(pin1=gc_IU.pin['g1'],pin2=DC_pX3.pin['a1'],arrow=False,original_function=not sharp_patch).put() + stripe.sbend_p2p(pin1=gc_ID.pin['g1'],pin2=DC_pX3.pin['a2'],arrow=False,original_function=not sharp_patch).put() + stripe.sbend_p2p(pin1=gc_OD.pin['g1'],pin2=DC_pX3.pin['b1'],arrow=False,original_function=not sharp_patch).put() + stripe.sbend_p2p(pin1=gc_OU.pin['g1'],pin2=DC_pX3.pin['b2'],arrow=False,original_function=not sharp_patch).put() + return C + diff --git a/primitives/edge_couplers/EC_dual_layer_px3.py b/primitives/edge_couplers/EC_dual_layer_px3.py index ca295df..497bf6d 100644 --- a/primitives/edge_couplers/EC_dual_layer_px3.py +++ b/primitives/edge_couplers/EC_dual_layer_px3.py @@ -245,8 +245,5 @@ class EC_dual_layer_px3(): return C -if __name__ == "__main__": - - pass diff --git a/primitives/grating_couplers/grating_couplers.py b/primitives/grating_couplers/grating_couplers.py new file mode 100644 index 0000000..02c7fc3 --- /dev/null +++ b/primitives/grating_couplers/grating_couplers.py @@ -0,0 +1,1116 @@ +import nazca as nd +import numpy as np +import math + +from ..structures import * +from ..structures import _my_polygon +from ..basic import __cell_arg__ +from ..routing import Route + +import pandas as pd + +''' Class for nanoantenna ''' +class Nano_ant(): + + """Class of nanoantenna for optical phased array. + + This is the class of nanoantenna for optical phased array. GDS cell can be generated using this class. Simulation structure generation and simulation results analysis is going to be added in the future. + + Args: + - tapeout [class] (Default: CUMEC_CSiP130Cu) + - w_wg [um] (Default: 0.5um) + Width of input waveguide + - vector [um] (Default: [0.5,..,0.5]]) + Vectors to define the length of each teeth + - taper_length [um] (Default: 1um) + Length of the linear taper region + - width [um] (Default: 3um) + Width of the nanoantenna + - max_theta [degree](Default: 110) + Open degree of linear taper + - define_type [str] (Default: non-periodic) + Way to define the antenna, including: "non-periodic", "periodic" + - etch_depth [str] (Default: "DETCH") + Define the etch depth, including: "FETCH", "METCH", "SETCH" + """ + + def __init__( + self, + w_wg: float = 0.41, + xs_wg: str = "strip", + + define_type: str = "non-periodic", + vector: float = [0.5, 0.5, 0.5, 0.5, 0.5, 0.5], + taper_length: float = 3, + width: float = 6, + max_theta: float = 110, + pitch: float = 0.6, + duty_cycle: float = 0.3, + teeth_number: float = 6, + + etch_depth: str = ["METCH"], + show_pins: bool = True + ): + # Init and save the input parameters + self.w_wg = w_wg + self.xs_wg = xs_wg + # Determine the etch type + if len(etch_depth)==1: self.etch_type = "single" + elif len(etch_depth)==2: self.etch_type = "dual" + if define_type=="non-periodic": + self.vector = vector + self.teeth_number = len(vector)/2 + elif define_type=="periodic": + # Parameters necessary when the ant is defined by "periodic" way + if self.etch_type=="single": + self.vector = [pitch*((2*duty_cycle-1)*(index%2)+1-duty_cycle) for index in range(teeth_number*2)] + self.pitch = pitch + self.duty_cycle = duty_cycle + self.teeth_number = teeth_number + elif self.etch_type=="dual": + self.vector = [ + pitch[1]*((2*duty_cycle[1]-1)*(index%2)+1-duty_cycle[1]) for index in range(teeth_number*2) + ] + self.vector[0] = pitch[0]*(1-duty_cycle[0]) + self.vector[1] = pitch[0]*duty_cycle[0] + self.pitch = pitch + self.duty_cycle = duty_cycle + self.teeth_number = teeth_number + + self.taper_length = taper_length + self.ant_length = self.taper_length + sum(self.vector) + self.width = width + self.max_theta = max_theta + self.define_type = define_type + # Here, I should change the name-type according to the difinition in the foundry.py + self.etch_depth = [] + for etch in etch_depth: + if etch=="FETCH": self.etch_depth = self.etch_depth+["STRIP"] # self.etch_depth.append("STRIP") + elif etch=="METCH": self.etch_depth = self.etch_depth+["RIB"] # self.etch_depth.append("RIB") + elif etch=="SETCH": self.etch_depth = self.etch_depth+["SRIB"] # self.etch_depth.append("SRIB") + + self.show_pins = show_pins + + self.cell = self.generate_gds() + + def generate_gds(self, sample_step=0.1, cell_name="Nanoantenna"): + with nd.Cell(name=cell_name, instantiate=False) as nano_ant: + layer_tre = nd.get_layer("STRIP_TRE") + if layer_tre == "STRIP_TRE" : + self.generate_gds_positive(sample_step=sample_step) + else : ## TO DO + self.generate_gds_error() + # Add pins + nd.Pin(name="a0", width=self.w_wg).put(0, 0, 180) + nd.Pin(name="g1", width=self.w_wg).put(0, 0, 180) + if self.show_pins: + nd.put_stub(pinname="g1") + return nano_ant + + def generate_gds_positive(self, sample_step=0.1): + """ + Generate a gds cell based on the logic of positive photoresistance. + + | Positive: Define the etched region using GETCH_TRE layer. + """ + width_extra_trench = 0.1 + theta_rad_max = self.max_theta * math.pi / 180 + ## Check if the input is appropriate or not + if math.floor(self.teeth_number) != self.teeth_number: + print("WARNNING :: Please re-check the vector of your antenna and make sure the length of vector is even.") + message = 'Inappropriate Definition of antenna.' + nd.text(text=message, height=5, layer=(96, 0), align='cc').put(0, 0) + return 0 + ## Build the structure + # Add input waveguide + nd.strt(length=self.taper_length-0.5, width=self.w_wg, xs=self.xs_wg).put(0, 0) + # Add the fan polygon region + radius_max = self.taper_length + sum(self.vector) + if self.width/2 > radius_max: theta_rad = theta_rad_max/2 + elif math.asin(self.width/2/radius_max) > theta_rad_max/2: theta_rad = theta_rad_max/2 + elif math.asin(self.width/2/radius_max) <= theta_rad_max/2: theta_rad = math.asin(self.width/2/radius_max) + theta_list = np.linspace(-theta_rad, theta_rad, math.floor(theta_rad*2*radius_max/sample_step)) + fan_polygon = [(radius_max*math.cos(theta), radius_max*math.sin(theta)) for theta in theta_list] + # fan_polygon = fan_polygon + [(self.width/2/math.tan(theta_rad_max/2), self.width/2), (0, self.w_wg/2), + # (0, -self.w_wg/2), (self.width/2/math.tan(theta_rad_max/2), -self.width/2)] + fan_polygon = fan_polygon + [(self.width/2/math.tan(theta_rad_max/2), self.width/2), (0, 0), + (self.width/2/math.tan(theta_rad_max/2), -self.width/2)] + nd.Polygon(points=fan_polygon, layer="STRIP_COR").put(0, 0) + layer_cld = nd.get_layer("STRIP_CLD") + if layer_cld == "STRIP_CLD": # Add CLD region if necessary + nd.strt(length=self.ant_length+1, width=max(self.width+1, self.w_wg+4), + layer=layer_cld).put(0, 0) + # Add the teeth + radius_cur = self.taper_length + for teeth_index in range(0, int(self.teeth_number)): + ## Determine the angular region first + if teeth_index == 0: radius_ref = radius_cur + else: radius_ref = radius_cur + self.vector[teeth_index*2-1] + + if (self.width+width_extra_trench)/2 > radius_ref: + theta_rad = theta_rad_max/2 + width_extra_trench/radius_ref + elif math.asin((self.width+width_extra_trench)/2/radius_ref) > theta_rad_max/2: + theta_rad = theta_rad_max/2 + width_extra_trench/radius_ref + else: + theta_rad = math.asin((self.width+width_extra_trench)/2/radius_ref) + + theta_step = sample_step / radius_ref + theta_list = np.linspace(-theta_rad, theta_rad, math.floor(2*theta_rad/theta_step)) + ## Construct the inner radius curve + if teeth_index == 0: radius_cur = radius_cur + else: radius_cur = radius_cur + self.vector[teeth_index*2-1] + inner_radius_curve = [(radius_cur*math.cos(theta), radius_cur*math.sin(theta)) for theta in theta_list] + ## Construct the outer radius curve + radius_cur = radius_cur + self.vector[teeth_index*2] + outer_radius_curve = [(radius_cur*math.cos(theta), radius_cur*math.sin(theta)) for theta in theta_list] + outer_radius_curve.reverse() + ## Add two dummy points to avoid sharp angle + offset_length = 0.015 / 2 + minimum_etch = 0.2 + radius_inner = radius_cur - self.vector[teeth_index*2] + radius_outer = radius_inner + self.vector[teeth_index*2] + x_1 = radius_inner * math.cos(theta_rad) + offset_length * math.cos(theta_rad) + y_1 = radius_inner * math.sin(theta_rad) + offset_length * math.sin(theta_rad) + vertical_length = math.sqrt(np.power(minimum_etch, 2) - np.power(offset_length, 2)) + dummy1_x = x_1 - vertical_length * math.sin(theta_rad) + dummy1_y = y_1 + vertical_length * math.cos(theta_rad) + x_2 = radius_outer * math.cos(theta_rad) - offset_length * math.cos(theta_rad) + y_2 = radius_outer * math.sin(theta_rad) - offset_length * math.sin(theta_rad) + dummy2_x = x_2 - vertical_length * math.sin(theta_rad) + dummy2_y = y_2 + vertical_length * math.cos(theta_rad) + ## Construct the teeth polygon + teeth_polygon = inner_radius_curve+[(dummy1_x,dummy1_y),(dummy2_x,dummy2_y)]+outer_radius_curve+[(dummy2_x, -dummy2_y),(dummy1_x,-dummy1_y)] + if self.etch_type == "single": + nd.Polygon(points=teeth_polygon, layer=self.etch_depth[0]+"_TRE").put(0, 0) + if self.etch_type == "dual": + if teeth_index==0: nd.Polygon(points=teeth_polygon, layer=self.etch_depth[0]+"_TRE").put(0, 0) + else: nd.Polygon(points=teeth_polygon, layer=self.etch_depth[1]+"_TRE").put(0, 0) + + def generate_gds_error(self): + nd.text(text="This foundry is not compatiable with current device. Please check.", height=10, layer=1001).put(0, 0) + +''' Class for 2D antenna array for FMF grating ''' +class Taper() : + + def __init__(self, width1=4, width2=0.45, length=30, type="linear", show_pins=False) -> None: + self.width1 = width1 + self.width2 = width2 + self.length = length + self.type = type + + if self.type == "parabolic" : self.order = 2 + elif self.type == "linear" : self.order = 1 + self.show_pins = show_pins + + self.cell = self.generate_gds() + + def generate_gds(self) : + with nd.Cell(name="taper", instantiate=False) as ic : + if self.order == 1 : + strip = Route(radius=10,width=self.width1,xs='strip') + linear_taper = strip.taper( + length=self.length,width1=self.width1,width2=self.width2,patch=True).put(0,0,0) + output_strt = strip.strt(length=0.5,width=self.width2).put() + nd.Pin(name="a1",width=self.width1).put(linear_taper.pin['a0']) + nd.Pin(name="b1",width=self.width2).put(output_strt.pin['b0']) + else : + c2 = self.width1/2 + c1 = (c2 - self.width2/2) / np.power(self.length, self.order) + x_list = np.linspace(0, self.length, int(np.floor(self.length/0.2))) + + taper_up_poly = [(x, -c1*np.power(x, self.order)+c2) for x in x_list] + taper_down_poly = [(x, -(-c1*np.power(x, self.order)+c2)) for x in x_list] + taper_down_poly.reverse() + taper_poly = taper_up_poly + taper_down_poly + nd.Polygon(points=taper_poly, layer='STRIP_COR').put(0,0) + + c2 = (self.width1+4)/2 + c1 = (c2 - (self.width2+4)/2) / np.power(self.length, self.order) + x_list = np.linspace(0, self.length, int(np.floor(self.length/0.2))) + + taper_up_poly = [(x, -c1*np.power(x, self.order)+c2) for x in x_list] + taper_down_poly = [(x, -(-c1*np.power(x, self.order)+c2)) for x in x_list] + taper_down_poly.reverse() + taper_poly = taper_up_poly + taper_down_poly + nd.Polygon(points=taper_poly, layer='STRIP_CLD').put(0,0) + + width_max = np.max(np.array([self.width1, self.width2])) + taper_poly = [ + (0, width_max/2+2), (0, -width_max/2-2), + (self.length, -width_max/2-2), (self.length, width_max/2+2) + ] + nd.Polygon(points=taper_poly, layer='STRIP_CLD').put(0,0) + + nd.strt(length=0.5, width=self.width2, xs='strip').put(self.length,0,0) + + nd.Pin(name='a1',width=self.width1).put(0,0,180) + nd.Pin(name="b1",width=self.width2).put(self.length+0.5,0,0) + if self.show_pins : + nd.put_stub() + return ic + + +class Grating_2D_Hole() : + ''' + This is a class for 2D Grating in IMEC. + ''' + def __init__( + self, + w_wg=0.5, + w_gt=5, l_taper=30, type_taper="parabolic", + gt_vector=[0.5,0.5,0.5,0.5,0.5,], gt_diameter=0.4, gt_layer="STRIP_COR", + polysi_vector=[0.5,0.5,0.5,0.5,0.5], polysi_diameter=0.4, polysi_layer="FCW_TRE", + reflector_vector=[0.3,0.3,0.3,0.3,0.3,0.3], + l_field_center = 1 + ) -> None: + self.w_wg = w_wg + self.w_gt = w_gt + self.l_taper = l_taper + self.type_taper = type_taper + + self.gt_vector = gt_vector + self.gt_num = len(self.gt_vector) + self.gt_diameter = gt_diameter + self.gt_layer =gt_layer + + self.polysi_vector = polysi_vector + self.polysi_num = len(self.polysi_vector) + self.polysi_diameter = polysi_diameter + self.polysi_layer = polysi_layer + + self.reflector_vector = reflector_vector + + self.l_field_center = l_field_center + + self.cell = self.generate_gds() + + def generate_gds(self) : + with nd.Cell(name="2D_Grating", instantiate=False) as ic : + '''Generate the diffraction region first.''' + strip_cor_poly = [ + (self.w_gt/2, self.w_gt/2), (self.w_gt/2, -self.w_gt/2), + (-self.w_gt/2, -self.w_gt/2), (-self.w_gt/2, self.w_gt/2) + ] + nd.Polygon(points=strip_cor_poly, layer='STRIP_COR').put(0,0,0) + strip_cld_poly = [ + (self.w_gt/2+2, self.w_gt/2+2), (self.w_gt/2+2, -self.w_gt/2-2), + (-self.w_gt/2-2, -self.w_gt/2-2), (-self.w_gt/2-2, self.w_gt/2+2) + ] + nd.Polygon(points=strip_cld_poly, layer='STRIP_CLD').put(0,0,0) + '''Generate the reflection region.''' + self.reflector_num = int(len(self.reflector_vector)/2) + for index in range(self.reflector_num) : + loc = self.w_gt/2 + sum(self.reflector_vector[0:2*index+1]) + self.reflector_vector[2*index+1]/2 + nd.strt(length=self.w_gt, width=self.reflector_vector[2*index+1], xs='strip').put( + -loc, -(self.w_gt)/2, 90 + ) + nd.strt(length=self.w_gt, width=self.reflector_vector[2*index+1], xs='strip').put( + -(self.w_gt)/2, -loc, 0 + ) + '''Generate the taper output.''' + taper = Taper(width1=self.w_gt, width2=self.w_wg, length=self.l_taper, type=self.type_taper) + taper_horizontal = taper.cell.put('a1', self.w_gt/2,0,0) + taper_vertical = taper.cell.put('a1',0,self.w_gt/2,90) + '''Generate the diffraction etched region.''' + theta_list = np.linspace(0, 2*np.pi, 32) + gt_ring_poly = [ + (self.gt_diameter/2*np.cos(theta), self.gt_diameter/2*np.sin(theta)) for theta in theta_list + ] + polysi_ring_poly = [ + (self.polysi_diameter/2*np.cos(theta), self.polysi_diameter/2*np.sin(theta)) for theta in theta_list + ] + self._generate_hole_array_( + polygon=gt_ring_poly, vector=self.gt_vector, layer=self.gt_layer + ).put(self.w_gt/2, self.w_gt/2) + self._generate_hole_array_( + polygon=polysi_ring_poly, vector=self.polysi_vector, layer=self.polysi_layer + ).put(self.w_gt/2, self.w_gt/2) + '''Put the pin location''' + nd.Pin(name='g1').put(taper_horizontal.pin['b1']) + nd.Pin(name='g2').put(taper_vertical.pin['b1']) + + # nd.put_stub() + return ic + + def _generate_hole_array_(self,polygon,vector,layer) : + with nd.Cell(name="diffration_"+layer, instantiate=False) as ic : + for lateral_index in range(len(vector)) : + for vertical_index in range(len(vector)) : + nd.Polygon(points=polygon,layer=layer).put( + -sum(vector[0:lateral_index+1]), -sum(vector[0:vertical_index+1]) + ) + return ic + +class Grating_2D_Hole_4Rec() : + + def __init__(self, grating_unit, mode_radius=8, cell_name=None, show_pins=False) -> None: + self.gt_2D_class = grating_unit + self.cell_unit = grating_unit.cell + self.mode_radius = mode_radius + + # Calculate the field center location + # radius = np.sqrt(2)/2 * ( + # self.mode_radius + np.sqrt(2)/2*(grating_unit.w_gt/2-grating_unit.l_field_center) - + # np.sqrt(np.power(self.mode_radius, 2) - 1/2*np.power(grating_unit.w_gt/2-grating_unit.l_field_center, 2)) + # ) + # print("---------------------"+str(radius)+"------------------------------") + l_field_center = grating_unit.l_field_center + w_gt = grating_unit.w_gt + x0 = ( + 2*(w_gt/2-l_field_center)-np.sqrt( + 8*mode_radius**2 - 4 * (w_gt/2 - l_field_center)**2 + ) + ) / 4 + self.field_center = ( + x0 + mode_radius*np.cos(np.pi/4), + x0 + mode_radius*np.cos(np.pi/4), + 180 + ) + self.cell_unit._put_pin(name='g0', connect=self.field_center) + + self.show_pins = show_pins + self.cell_name = cell_name + self.cell = self.generate_gds() + + def generate_gds(self) : + if self.cell_name is not None : self.cell_name = "TwoD_Grating_" + self.cell_name + else : self.cell_name = "TwoD_Grating" + with nd.Cell(name=self.cell_name, instantiate=False) as ic : + gt_1 = self.cell_unit.put( + 'g0', self.mode_radius*np.cos(np.pi/4), self.mode_radius*np.sin(np.pi/4) + ) + gt_2 = self.cell_unit.put( + 'g0', self.mode_radius*np.cos(np.pi/4), -self.mode_radius*np.sin(np.pi/4), flip=True + ) + gt_3 = self.cell_unit.put( + 'g0', -self.mode_radius*np.cos(np.pi/4), -self.mode_radius*np.sin(np.pi/4), flip=True, flop=True + ) + gt_4 = self.cell_unit.put( + 'g0', -self.mode_radius*np.cos(np.pi/4), self.mode_radius*np.sin(np.pi/4), flip=False, flop=True + ) + '''Put OPEN and PATH region if necessary.''' + if nd.get_layer(layer="GC_OPEN") == "GC_OPEN" : + nd.Polygon( + points=nd.geom.circle(radius=self.mode_radius+20, N=int(np.floor((self.mode_radius+20)/0.1))), + layer="GC_OPEN" + ).put(0,0) + if nd.get_layer(layer="STRIP_CLD") == "STRIP_CLD" : + nd.Polygon( + points=nd.geom.circle(radius=self.mode_radius+10, N=int(np.floor((self.mode_radius+20)/0.1))), + layer="STRIP_CLD" + ).put(0,0) + ''' Put Pins ''' + nd.Pin(name='g1').put(gt_1.pin['g1']) + nd.Pin(name='g2').put(gt_1.pin['g2']) + nd.Pin(name='g3').put(gt_2.pin['g1']) + nd.Pin(name='g4').put(gt_2.pin['g2']) + nd.Pin(name='g5').put(gt_3.pin['g1']) + nd.Pin(name='g6').put(gt_3.pin['g2']) + nd.Pin(name='g7').put(gt_4.pin['g1']) + nd.Pin(name='g8').put(gt_4.pin['g2']) + nd.Pin(name='a0').put(gt_1.pin['g1'].x, 0, 0) + if self.show_pins : + nd.put_stub() + return ic + +class Grating_2D_Hole_3Rec() : + + def __init__(self, grating_unit, mode_radius=6.5, cell_name=None, show_pins=False) -> None: + self.gt_2D_class = grating_unit + self.cell_unit = grating_unit.cell + self.mode_radius = mode_radius + self.cell_name = cell_name + + # Calculate the field center location + radius = np.sqrt(2)/2 * ( + self.mode_radius + np.sqrt(2)/2*(grating_unit.w_gt/2-grating_unit.l_field_center) - + np.sqrt(np.power(self.mode_radius, 2) - 1/2*np.power(grating_unit.w_gt/2-grating_unit.l_field_center, 2)) + ) + self.field_center = ( + radius*np.cos(np.pi/4), + radius*np.cos(np.pi/4), + 45 + ) + self.cell_unit._put_pin(name='g0', connect=self.field_center) + self.show_pins = show_pins + + self.cell = self.generate_gds() + + def generate_gds(self) : + if self.cell_name is not None : self.cell_name = "TwoD_Grating_" + self.cell_name + else : self.cell_name = "TwoD_Grating" + with nd.Cell(name=self.cell_name, instantiate=False) as ic : + rotation_angle = 2*np.pi/3*0 + gt_1 = self.cell_unit.put( + 'g0', + self.mode_radius*np.cos(rotation_angle), self.mode_radius*np.sin(rotation_angle), + 180 + rotation_angle*180/np.pi + ) + rotation_angle = 2*np.pi/3*1 + gt_2 = self.cell_unit.put( + 'g0', + self.mode_radius*np.cos(rotation_angle), self.mode_radius*np.sin(rotation_angle), + 180 + rotation_angle*180/np.pi + ) + rotation_angle = 2*np.pi/3*2 + gt_3 = self.cell_unit.put( + 'g0', + self.mode_radius*np.cos(rotation_angle), self.mode_radius*np.sin(rotation_angle), + 180 + rotation_angle*180/np.pi + ) + '''Put OPEN and PATH region if necessary.''' + if nd.get_layer(layer="GC_OPEN") == "GC_OPEN" : + nd.Polygon( + points=nd.geom.circle(radius=self.mode_radius+20, N=int(np.floor((self.mode_radius+20)/0.1))), + layer="GC_OPEN" + ).put(0,0) + if nd.get_layer(layer="STRIP_CLD") == "STRIP_CLD" : + nd.Polygon( + points=nd.geom.circle(radius=self.mode_radius+10, N=int(np.floor((self.mode_radius+20)/0.1))), + layer="STRIP_CLD" + ).put(0,0) + '''Put pins''' + nd.Pin(name='g1').put(gt_1.pin['g1']) + nd.Pin(name='g2').put(gt_1.pin['g2']) + nd.Pin(name='g3').put(gt_2.pin['g1']) + nd.Pin(name='g4').put(gt_2.pin['g2']) + nd.Pin(name='g5').put(gt_3.pin['g1']) + nd.Pin(name='g6').put(gt_3.pin['g2']) + if self.show_pins : + nd.put_stub() + return ic + + +""" Renamed for simplification in 2023.04.02 """ +class GC_STD_2D: + def __init__(self, + name=None, + etch_type :str = 'FETCH', + xs_wg:str='grating', + Dx_hole:float=0.3, + Dy_hole:float=0.3, + hole_shape :str= 'circle', + shape:str = 'circle', + xs_open:str = None, + Px:float=0.57, + Py:float=0.57, + num_x:float=25, + num_y:float=25, + Lx_taper:float = 50, + Ly_taper:float = 0, + Lx_end:float = 1, + Ly_end:float = 1, + Lx_side:float = 0.5, + Ly_side:float = 0.5, + Lx_port:float=5, + Ly_port:float=5, + w_wg:float=0.5, + show_pins:bool=False, + P_AR: float = 0.6, + L_AR: float = 1, + ): + """_summary_ + + Args: + etch_type (str, optional): Three etch depth for election , full-etch = "FETCH", middle etch = "METCH", shallow etch = "ETCH". Defaults to 'FETCH'. + xs_wg (str, optional): xsection of the grating and also the output waveguide. Defaults to 'grating'. + Dx_hole (float, optional): size X of the hole, when in 'circle' hole selection ,this is the Diameter of your hole . Defaults to 0.3. + Dy_hole (float, optional): size Y of the hole, when in 'circle' hole selection ,this is the Diameter of your hole . Defaults to 0.3. + hole_shape (str, 'circle' | 'rectangel'): shape of the hole. Defaults to 'circle'. + shape (str, 'circle' | 'rectangel'): shape of the grating. Defaults to 'circle'. + Px (float, optional): Period distance X. Defaults to 0.57. + Py (float, optional): Period distance Y. Defaults to 0.57. + num_x (int, optional): number of pitches. Defaults to 25. + num_y (int, optional): number of pitches. Defaults to 25. + Lx_taper (int, optional): taper connection to the port. Defaults to 50. + Ly_taper (int, optional): taper connection to the port. Defaults to 0. + Lx_end (int, optional): length arratched to the end. Defaults to 5. + Ly_end (int, optional): length arratched to the end. Defaults to 3. + Lx_side (float, optional): side expansion. Defaults to 0.5. + Ly_side (float, optional): side expansion. Defaults to 0.5. + Lx_port (int, optional): output port length expansion. Defaults to 5. + Ly_port (int, optional): output port length expansion. Defaults to 5. + w_wg (float, optional): output port width. Defaults to 0.5. + show_pins (bool, optional): _description_. Defaults to False. + + Raises: + Exception: Period do not match D_hole + """ + self.name = name + if (self.name==None): + self.instantiate = False + else : + self.instantiate = True + + if (isinstance(Px,int) or isinstance(Px,float)) : Px = Px * np.ones(num_x) + if (isinstance(Py,int) or isinstance(Py,float)) : Py = Py * np.ones(num_y) + if (isinstance(Dx_hole,int) or isinstance(Dx_hole,float)) : Dx_hole = Dx_hole * np.ones((num_x)) + if (isinstance(Dy_hole,int) or isinstance(Dy_hole,float)) : Dy_hole = Dy_hole * np.ones((num_y)) + + self.num_x = len(Px) + self.num_y = len(Py) + if (len(Px)!=len(Dx_hole) or len(Py)!=len(Dy_hole)): + raise Exception("In Grating define : [Period] length not matching [D_hole] length") + + self.Lx_taper = Lx_taper + self.Ly_taper = Ly_taper + self.Lx_end = Lx_end + self.Ly_end = Ly_end + self.Lx_side = Lx_side + self.Ly_side = Ly_side + self.Lx_port = Lx_port + self.Ly_port = Ly_port + + self.xs_open = xs_open + + self.w_wg = w_wg + self.xs_wg = xs_wg + self.etch_type = etch_type + self.shape = shape + + self.hole_shape = hole_shape + self.Dx_hole = Dx_hole + self.Dy_hole = Dy_hole + self.Px = Px + self.Py = Py + + self.P_AR = P_AR + self.L_AR = L_AR + + + self.show_pins = show_pins + + + if (nd.get_layer(layer="STRIP_TRE") == "STRIP_TRE"): + self.positive = False + if (hole_shape=='circle'): + if (etch_type=="FETCH"): + layer_etch = "STRIP_HOL" + elif (etch_type=="METCH"): + layer_etch = "RIB_HOL" + elif (etch_type=="SETCH"): + layer_etch = "SRIB_HOL" + + elif (hole_shape=='rectangle'): + if (etch_type=="FETCH"): + layer_etch = "STRIP_TRE" + elif (etch_type=="METCH"): + layer_etch = "RIB_TRE" + elif (etch_type=="SETCH"): + layer_etch = "SRIB_TRE" + else : + self.positive = True + + if (etch_type=="FETCH"): + layer_etch = None + elif (etch_type=="METCH"): + layer_etch = "RIB_COR" + elif (etch_type=="SETCH"): + layer_etch = "SRIB_COR" + + self.layer_etch = layer_etch + + if (layer_etch!=None): + if (nd.get_layer(layer_etch)!=layer_etch): + layer_etch=None + print("WARNING: In mxpic::passive::GC_STD_1D, ::",layer_etch," not defined in tapeout") + + if (self.positive): + self.cell = self.generate_positive() + else: + self.cell = self.generate_negative() + + def generate_negative(self): + with nd.Cell(instantiate=self.instantiate,name=self.name) as C: + + ## arc shape grating + if (self.shape=='circle' or self.shape=='arc'): + print("Sorry, this function has not been built up") + + ## retangular grating + elif (self.shape=='rectangle'): + Lx = sum(self.Px)+self.Lx_side*2 + # if (self.Ly_taper==0): + Ly = sum(self.Py)+self.Ly_side*2 + # else: + # Ly = sum(self.Py) + y_offset = sum(self.Py)/2 + x_offset = sum(self.Px)/2 + nd.strt(length=Lx,width=Ly,xs=self.xs_wg).put(-Lx/2,0,0) + + + if (self.xs_open!=None): + circle(radius=max([Lx,Ly])*2/2,width=max([Lx,Ly])*2,xs=self.xs_open, + # n_points=32 + ).cell.put(0,0,0) + + + for _x_ in range(0,self.num_x): + for _y_ in range(0,self.num_y): + pos_x = np.sum(self.Px[0:_x_+1])-self.Px[0]/2-x_offset + pos_y = np.sum(self.Py[0:_y_+1])-y_offset-self.Py[0]/2 + if (self.hole_shape=='circle'): + circle(radius=self.Dx_hole[_x_]/4,width=self.Dx_hole[_x_]/2,layer=self.layer_etch, + # n_points=32, + sharp_patch=False).cell.put(pos_x,pos_y,0) + elif (self.hole_shape=='rectangle'): + nd.strt(length=self.Dx_hole[_x_],width=self.Dy_hole[_y_],layer=self.layer_etch).put(pos_x-self.Dx_hole[_x_]/2,pos_y,0) + else : + raise Exception("ERROR: In , is not defined, please input [circle | rectangle]") + if (self.Ly_taper!=0): + nd.strt(length=self.Ly_end,width=Lx,xs=self.xs_wg).put(0,Ly/2,90) + + if (self.P_AR>0): + _num_AR_ = int(np.floor(self.L_AR/self.P_AR)+1) + for _idx_ in range(0,_num_AR_): + # nd.strt(xs=self.xs_wg,width=Lx,length=self.P_AR/2).put(0,self.P_AR/2+Ly/2+self.Ly_end+self.P_AR*_idx_,90) + nd.strt(xs=self.xs_wg,width=self.P_AR/2,length=Lx).put(-Lx/2,self.P_AR+Ly/2+self.Ly_end+self.P_AR*_idx_,0) + # if (self.P_AR >0 and self.L_AR>0): + # _num_AR_ = int(np.floor(Ly/self.P_AR)) + # for _idx_ in range(0,_num_AR_): + # nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(_idx_*self.P_AR - (_num_AR_-1)/2*self.P_AR+Lx/2,Ly/2+self.Ly_end,90) + + nd.strt(length=self.Ly_end,width=Lx,xs=self.xs_wg).put(0,-Ly/2,-90) + y_port = nd.taper(width1=Ly,width2=self.w_wg,xs=self.xs_wg,length=self.Ly_taper).put() + y_port = nd.strt(length=self.Ly_port,width=self.w_wg,xs=self.xs_wg).put() + nd.Pin(name='g2',pin=y_port.pin['b0']).put() + + nd.strt(length=self.Lx_end,width=Ly,xs=self.xs_wg).put(Lx/2,0,0) + + ## adding anti reflection + if (self.Lx_taper!=0): + if (self.P_AR>0): + _num_AR_ = int(np.floor(self.L_AR/self.P_AR)+1) + for _idx_ in range(0,_num_AR_): + # nd.strt(xs=self.xs_wg,width=Lx,length=self.P_AR/2).put(self.P_AR/2+Lx/2+self.Lx_end+self.P_AR*_idx_,0,0) + nd.strt(xs=self.xs_wg,length=Ly,width=self.P_AR/2).put(self.P_AR+Lx/2+self.Lx_end+self.P_AR*_idx_,-Ly/2,90) + + # _num_AR_ = int(np.floor(Ly/self.P_AR)) + # for _idx_ in range(0,_num_AR_): + # nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(Lx+self.Lx_end,_idx_*self.P_AR - (_num_AR_-1)/2*self.P_AR,0) + + nd.strt(length=self.Lx_end,width=Ly,xs=self.xs_wg).put(-Lx/2,0,180) + x_port = nd.taper(width1=Ly,width2=self.w_wg,xs=self.xs_wg,length=self.Lx_taper).put() + x_port = nd.strt(length=self.Lx_port,width=self.w_wg,xs=self.xs_wg).put() + nd.Pin(name='g1',pin=x_port.pin['b0']).put() + + # print("Sorry, this function has not been built up") + + else : + raise Exception("In Grating define : [shape] not defined") + + if (self.show_pins): + nd.put_stub(pinsize=3) + return C + + def generate_positive(self): + with nd.Cell(instantiate=self.instantiate,name=self.name) as C: + + ## arc shape grating + if (self.shape=='circle' or self.shape=='arc'): + print("Sorry, this function has not been built up") + pass + + ## retangular grating + elif (self.shape=='rectangle'): + Lx = sum(self.Px) + if (self.Ly_taper==0): + Ly = sum(self.Py)+self.Ly_side*2 + nd.strt(length=Lx,width=self.Ly_side,xs=self.xs_wg).put(0,sum(self.Py)/2+self.Lx_side/2,0) + nd.strt(length=Lx,width=self.Ly_side,xs=self.xs_wg).put(0,-sum(self.Py)/2-self.Lx_side/2,0) + else: + Ly = sum(self.Py) + y_offset = sum(self.Py)/2 + if (self.layer_etch!=None): + nd.strt(length=Lx,width=Ly,layer=self.layer_etch).put(0,0,0) + if (self.xs_open!=None): + nd.strt(length=Lx*2,width=Ly*2,xs=self.xs_open).put(-Lx/2,0,0) + + for _x_ in range(0,self.num_x): + for _y_ in range(0,self.num_y): + pos_x = np.sum(self.Px[0:_x_+1])-self.Px[0]/2 + pos_y = np.sum(self.Py[0:_y_+1])-y_offset-self.Py[0]/2 + hole(r_hole=self.Dx_hole[_x_]/2,Lx_sq=self.Px[_x_],Ly_sq=self.Py[_y_], + Dx_hole=self.Dx_hole[_x_],Dy_hole=self.Dy_hole[_y_], + # n_points=12, + xs=self.xs_wg,hole_shape=self.hole_shape).cell.put(pos_x,pos_y,0) + + if (self.Ly_taper!=0): + nd.strt(length=self.Ly_end,width=Lx,xs=self.xs_wg).put(Lx/2,Ly/2,90) + _num_AR_ = int(np.floor(Ly/self.P_AR)) + for _idx_ in range(0,_num_AR_): + nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(_idx_*self.P_AR - (_num_AR_-1)/2*self.P_AR+Lx/2,Ly/2+self.Ly_end,90) + + nd.strt(length=self.Ly_end,width=Lx,xs=self.xs_wg).put(Lx/2,-Ly/2,-90) + y_port = nd.taper(width1=Ly,width2=self.w_wg,xs=self.xs_wg,length=self.Lx_taper).put() + y_port = nd.strt(length=self.Ly_port,width=self.w_wg,xs=self.xs_wg).put() + nd.Pin(name='g2',pin=y_port.pin['b0']).put() + + nd.strt(length=self.Lx_end,width=Ly,xs=self.xs_wg).put(Lx,0,0) + ## adding anti reflection + _num_AR_ = int(np.floor(Ly/self.P_AR)) + for _idx_ in range(0,_num_AR_): + nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(Lx+self.Lx_end,_idx_*self.P_AR - (_num_AR_-1)/2*self.P_AR,0) + + nd.strt(length=self.Lx_end,width=Ly,xs=self.xs_wg).put(0,0,180) + x_port = nd.taper(width1=Ly,width2=self.w_wg,xs=self.xs_wg).put() + x_port = nd.strt(length=self.Lx_port,width=self.w_wg,xs=self.xs_wg).put() + nd.Pin(name='g1',pin=x_port.pin['b0']).put() + else : + raise Exception("In Grating define : [shape] not defined") + if (self.show_pins): + nd.put_stub(pinsize=3) + return C + + def generate_test_gds(self,dX_gc2gc=300): + with nd.Cell(instantiate=False) as C: + self.cell.put('g1',-dX_gc2gc/2,0,180) + self.cell.put('g1', dX_gc2gc/2,0,0) + + nd.strt(xs=self.xs_wg,width=self.w_wg,length=dX_gc2gc).put(-dX_gc2gc/2,0,0) + + return C + +class GC_STD_1D: + def __init__ (self, + name=None, + xs_wg : str = 'strip', + w_wg : float = 0.5, + etch_type :str = 'FETCH', + xs_open :str=None, + L_taper :float = 10, + L_end :float = 2, + A_taper :float = 30, + Period :float = 0.5, + eta_etch :float = 0.5, + num :float = 20, ### note, when Period and eta is defined as list, this is not usefull + sector_gc :bool =True, + show_pins=False, + L_tail = 2, + # n_points = 64, + P_AR: float = 1, ### adding anti reflection pitches + L_AR: float = 2, + ): + + self.name = name + if (self.name==None): + self.instantiate = False + else : + self.instantiate = True + + if (xs_open!=None): + try: nd.get_xsection(xs_open) + except: + xs_open=None + print("WARNING:In , ::",xs_open," not defined in tapeout") + self.xs_open=xs_open + + + self.xs_wg=xs_wg + self.w_wg=w_wg + self.L_taper=L_taper + self.L_end=L_end + self.A_taper=A_taper + self.show_pins = show_pins + self.L_tail = L_tail + + if (isinstance(eta_etch,list) or isinstance(eta_etch,np.ndarray)): + num = len(eta_etch) + + if (isinstance(Period,list) or isinstance(Period,np.ndarray)): + num = len(Period) + + + + if (isinstance(Period,int) or isinstance(Period,float)): + Period = Period*np.ones(num) + + if (isinstance(eta_etch,int) or isinstance(eta_etch,float)): + eta_etch = eta_etch*np.ones(num) + + """ Generate ERROR """ + if (len(Period)!=len(eta_etch)): + raise Exception("ERROR: In : [Period] length not matching [eta_etch] length") + + if (nd.get_layer(layer="STRIP_TRE") == "STRIP_TRE"): + self.positive = False + if (etch_type=="FETCH"): + layer_etch = "STRIP_TRE" + elif (etch_type=="METCH"): + layer_etch = "RIB_TRE" + elif (etch_type=="SETCH"): + layer_etch = "SRIB_TRE" + else : + self.positive = True + + if (etch_type=="FETCH"): + layer_etch = None + elif (etch_type=="METCH"): + layer_etch = "RIB_COR" + elif (etch_type=="SETCH"): + layer_etch = ["SRIB_COR","RIB_COR"] + + self.Period=Period + self.eta_etch=eta_etch + self.num=len(Period) + self.sector_gc=sector_gc + + # self.n_points = n_points ## revise 2022.08.18 + + self.L_AR = L_AR + self.P_AR = P_AR + + if (layer_etch!=None): + if (isinstance(layer_etch,str)): + if (nd.get_layer(layer_etch)!=layer_etch): + layer_etch=None + print("WARNING: In mxpic::passive::GC_STD_1D, ::",layer_etch," not defined in tapeout") + else : + for _layer_ in layer_etch: + if (nd.get_layer(_layer_)!=_layer_): + layer_etch=None + print("WARNING: In mxpic::passive::GC_STD_1D, ::",layer_etch," not defined in tapeout") + self.layer_etch = layer_etch + + if (self.positive): + self.cell = self.generate_positive() + else: + self.cell = self.generate_negative() + + def generate_negative(self): + with nd.Cell(instantiate=self.instantiate,name=self.name) as C: + + ## arc shape grating + if (self.sector_gc == True): + L_total = np.sum(self.Period) + self.L_taper + self.L_end + L_tail = self.L_tail + for layers,growx,growy,acc in nd.layeriter(xs=self.xs_wg): + (a1,b1), (a2,b2),c1,c2 = growx + x_offset = -b1/np.sin(self.A_taper/2*np.pi/180) + + if (self.P_AR>0 and self.L_AR>0): ## anti reflection + r_tap = L_total*1.3 - b1 - x_offset + else : + r_tap = L_total + b1 - x_offset + + circle(radius=r_tap/2,width=r_tap, + theta_start=-self.A_taper/2,theta_stop=self.A_taper/2, + # n_points=self.n_points, + layer=layers).cell.put(x_offset,0,0) + _L_tail_ = np.abs(x_offset) + if _L_tail_ > L_tail: + L_tail = _L_tail_ + + + r_grat_inner = self.L_taper + for _idx_ in range(0,self.num): + d_pitch = self.Period[_idx_]*self.eta_etch[_idx_] + circle(radius=r_grat_inner + d_pitch/2, + width=d_pitch, + theta_start=-self.A_taper/2-5,theta_stop=self.A_taper/2+5, + # n_points=self.n_points, + layer=self.layer_etch).cell.put(0,0,0) + r_grat_inner = r_grat_inner + self.Period[_idx_] + + L_open = 1.5*(L_total-self.L_taper) + W_open = 1.1*(L_total*np.tan(self.A_taper/2*np.pi/180))*2 + x_open = (L_total+self.L_taper)/2 - L_open/2 + + if (self.xs_open!=None): + nd.strt(length=L_open,width=W_open,xs=self.xs_open).put(x_open,0,0) + + nd.strt(length=L_tail,width=self.w_wg,xs=self.xs_wg).put(-L_tail,0,0) + nd.Pin(name='g1',width=self.w_wg).put(-L_tail,0,180) + nd.strt(length=np.abs(self.w_wg/2/np.tan(self.A_taper/2/180*np.pi)),width=self.w_wg,xs=self.xs_wg).put(0,0,0) + + + ## retangular grating + else: + + L_total = np.sum(self.Period) + self.L_taper + self.L_end + + L_grat = sum(self.Period)+self.L_end + W_grat = self.w_wg + self.L_taper*np.tan(self.A_taper/2*np.pi/180)*2 + nd.taper(length=self.L_taper,width1=self.w_wg,width2=W_grat,xs=self.xs_wg).put(0,0,0) + nd.strt(length=L_grat,width=W_grat,xs=self.xs_wg).put(self.L_taper,0,0) + + ### adding Anti-reflection + + # if (self.P_AR>0 and self.L_AR>0): + # _num_AR_ = int(np.floor(W_grat/self.P_AR)) + # for _idx_ in range(0,_num_AR_): + # nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(self.L_taper+L_grat, (_idx_ - (_num_AR_-1)/2)*self.P_AR,0) + + + x_grat = self.L_taper + for _idx_ in range(0,self.num): + nd.strt(length=self.Period[_idx_]*self.eta_etch[_idx_],width=W_grat+2,layer=self.layer_etch).put(x_grat,0,0) + x_grat = x_grat + self.Period[_idx_] + + nd.strt(length=10,width=self.w_wg,xs=self.xs_wg).put(-5,0,0) + nd.Pin(name='g1',width=self.w_wg).put(-5,0,180) + + L_open = 1.5*(L_total-self.L_taper) + W_open = 1.1*(L_total*np.tan(self.A_taper/2*np.pi/180))*2 + x_open = (L_total+self.L_taper)/2 - L_open/2 + if (self.xs_open!=None): + nd.strt(length=L_open,width=W_open,xs=self.xs_open).put(x_open,0,0) + pass + + + if (self.show_pins): + nd.put_stub() + return C + + def generate_positive(self): + with nd.Cell(instantiate=False) as C: + ## arc shape grating + if (self.sector_gc==True): + L_tail = self.L_tail + + for layers,growx,growy,acc in nd.layeriter(xs=self.xs_wg): + (a1,b1), (a2,b2),c1,c2 = growx + x_offset = -b1/np.sin(self.A_taper/2*np.pi/180) + + r_tap = self.L_taper + b1 - x_offset + + circle(radius=r_tap/2,width=r_tap, + theta_start=-self.A_taper/2,theta_stop=self.A_taper/2, + # n_points=self.n_points, + layer=layers).cell.put(x_offset,0,0) + + r_grat_inner = self.L_taper - x_offset + + _L_tail_ = np.abs(x_offset) + if _L_tail_ > L_tail: + L_tail = _L_tail_ + + for _idx_ in range(0,self.num): + + d_pitch = self.Period[_idx_]*(1-self.eta_etch[_idx_])+b1*2 + + + circle(radius=r_grat_inner + self.Period[_idx_]*self.eta_etch[_idx_] + d_pitch/2, + width=d_pitch, + theta_start=-self.A_taper/2,theta_stop=self.A_taper/2, + # n_points=self.n_points, + layer=layers).cell.put(x_offset,0,0) + + r_grat_inner = r_grat_inner + self.Period[_idx_] + + d_pitch = self.L_end+b1*2 + r_grat_inner = r_grat_inner + self.Period[-1]*self.eta_etch[-1] + + circle(radius=r_grat_inner + d_pitch/2, + width=d_pitch, + theta_start=-self.A_taper/2,theta_stop=self.A_taper/2, + # n_points=64, + layer=layers).cell.put(x_offset,0,0) + nd.strt(length=L_tail*2,width=self.w_wg,xs=self.xs_wg).put(-L_tail,0,0) + nd.Pin(name='g1',width=self.w_wg).put(-L_tail,0,180) + + L_total = np.sum(self.Period) + self.L_taper + self.L_end + L_open = 1.5*(L_total-self.L_taper) + W_open = 1.2*(L_total*np.tan(self.A_taper/2*np.pi/180))*2 + x_open = (L_total+self.L_taper)/2 - L_open/2 + if (self.layer_etch!=None): + if (isinstance(self.layer_etch,str)): + nd.strt(length=L_open,width=W_open,layer=self.layer_etch).put(x_open,0,0) + elif(isinstance(self.layer_etch,list)): + for _layer_ in self.layer_etch: + nd.strt(length=L_open,width=W_open,layer=_layer_).put(x_open,0,0) + + if (self.xs_open!=None): + nd.strt(length=L_open,width=W_open,xs=self.xs_open).put(x_open,0,0) + + + + ## retangular grating + elif (self.shape=='rectangle'): + + L_grat = sum(self.Period) + W_grat = self.w_wg + self.L_taper*np.tan(self.A_taper/2*np.pi/180)*2 + nd.taper(length=self.L_taper,width1=self.w_wg,width2=W_grat,xs=self.xs_wg).put(0,0,0) + + x_grat = self.L_taper + for _idx_ in range(0,self.num): + nd.strt(length=self.Period[_idx_]*(1-self.eta_etch[_idx_]),width=W_grat,xs=self.xs_wg).put(x_grat+self.Period[_idx_]*self.eta_etch[_idx_],0,0) + x_grat = x_grat + self.Period[_idx_] + + nd.strt(length=10,width=self.w_wg,xs=self.xs_wg).put(-5,0,0) + nd.Pin(name='g1',width=self.w_wg).put(-5,0,180) + + ### adding Anti-reflection + if (self.P_AR>0 and self.L_AR>0): + _num_AR_ = int(np.floor(W_grat/self.P_AR)) + for _idx_ in range(0,_num_AR_): + nd.taper(xs=self.xs_wg,width1=self.P_AR-0.2,width2=0.2,length=self.L_AR).put(self.L_taper+L_grat, (_idx_ - (_num_AR_-1)/2)*self.P_AR,0) + + + L_total = np.sum(self.Period) + self.L_taper + self.L_end + L_open = 1.5*(L_total-self.L_taper) + W_open = 1.1*(L_total*np.tan(self.A_taper/2*np.pi/180))*2 + x_open = (L_total+self.L_taper)/2 - L_open/2 + if (self.layer_etch!=None): + nd.strt(length=L_open,width=W_open,layer=self.layer_etch).put(x_open,0,0) + + if (self.xs_open!=None): + nd.strt(length=L_open,width=W_open,xs=self.xs_open).put(x_open,0,0) + + + pass + else : + raise Exception("In Grating define : [shape] not defined") + if (self.show_pins): + nd.put_stub() + return C + + def generate_test_dev(self,dX_gc2gc): + with nd.Cell(instantiate=False) as C: + self.cell.put('g1',-dX_gc2gc/2,0,180) + self.cell.put('g1', dX_gc2gc/2,0,0) + nd.strt(xs=self.xs_wg,width=self.w_wg,length=dX_gc2gc).put(-dX_gc2gc/2,0,0) + return C + +class FA: + def __init__(self,fiber_coupler,pitch,number,show_pins=False): + + # if (isinstance(fiber_coupler,nd.Cell)): + # fiber_cell = fiber_coupler + + # elif (hasattr(fiber_coupler,'cell')): + # fiber_cell = fiber_coupler.cell + # else: + # raise Exception("ERROR: In , not recongized, please input nazca.cell or classes that has nazca.cell") + + fiber_cell = __cell_arg__(arg=fiber_coupler,arg_name="fiber_coupler",func_name="mxpic::FA") + + + pin_in_name = [] + for name,Pin in fiber_cell.ic_pins(): + + pin_in_name = pin_in_name+[name] + # pin_in_name.append(name) + + if ('g1' in pin_in_name): + pin_name = 'g1' + else: + pin_name = 'a0' + print("WARNING: In , dose not contain 'g1' pin, using 'a0' in default") + + self.pitch = pitch + self.number = number + + with nd.Cell(instantiate=False) as C: + + for idx in range(1,number+1): + port = fiber_cell.put(pin_name,0,pitch*(idx-number/2-1/2),0) + nd.Pin('g'+str(idx),pin=port.pin[pin_name]).put() + + x_out = port.pin['b0'].x + nd.Pin(name='b0').put(x_out,0,180) + + if (show_pins): + nd.put_stub(pinsize=3) + self.cell = C +