From 678ec01d5813d1f9c6b343a9656fce6d35fa5b2b Mon Sep 17 00:00:00 2001 From: Lukas Chrostowski Date: Sat, 26 Oct 2024 10:26:36 -0700 Subject: [PATCH] FaML function and tests - new PCell GSiP.FaML_Si_1550_BB, for a facet-attached micro-lens - new function SiEPIC.utils.layout.FaML_two() to create a facet-attached micro-lens array (two channels), with test labels - new layer BlackBox - new unit test for the function and PCell - fix WAVEGUIDES.xml, by adding DevRec layer to Strip waveguide --- .../python/SiEPIC/utils/layout.py | 37 ++++ klayout_dot_config/tech/GSiP/GSiP.lyt | 19 +- klayout_dot_config/tech/GSiP/WAVEGUIDES.xml | 5 + .../tech/GSiP/klayout_Layers_GSiP.lyp | 18 ++ .../pymacros/pcells_GSiP/FaML_Si_1550_BB.py | 184 ++++++++++++++++++ .../tech/GSiP/pymacros/tests/test_FaML.py | 111 +++++++++++ 6 files changed, 358 insertions(+), 16 deletions(-) create mode 100644 klayout_dot_config/tech/GSiP/pymacros/pcells_GSiP/FaML_Si_1550_BB.py create mode 100644 klayout_dot_config/tech/GSiP/pymacros/tests/test_FaML.py diff --git a/klayout_dot_config/python/SiEPIC/utils/layout.py b/klayout_dot_config/python/SiEPIC/utils/layout.py index 14113b6d5..af3925d3f 100644 --- a/klayout_dot_config/python/SiEPIC/utils/layout.py +++ b/klayout_dot_config/python/SiEPIC/utils/layout.py @@ -1332,3 +1332,40 @@ def strip2rib(cell, trans, w_slab, w_rib, w_slab_tip, w_strip, length, LayerRib, nLayerSlab = cell.layout().layer(cell.layout().TECHNOLOGY[LayerSlab]) poly = DPolygon([DPoint(length,-w_slab/2), DPoint(length,w_slab/2), DPoint(0, w_slab_tip/2), DPoint(0, -w_slab_tip/2)]) cell.shapes(nLayerSlab).insert(poly.transformed(trans)) + + +def FaML_two(cell, + label='opt_in_TE_1550_FaML_TestCircuit', + x_offset=0, + y_offset=127e3/2-5e3, + pitch = 127e3, + cell_name = 'ebeam_dream_FaML_SiN_1550_BB', + cell_library = 'EBeam-Dream', + cell_params = {'num_channels':1, + 'ref_wg':False}, + ): + ''' + Create a layout consisting of two facet-attached micro-lenses (FaML) + return the two instances + ''' + from pya import Trans, CellInstArray, Text + ly = cell.layout() + # Load cell from library + if cell_params: + cell_ebeam_faml = ly.create_cell(cell_name, cell_library, cell_params) + else: + cell_ebeam_faml = ly.create_cell(cell_name, cell_library) + if not cell_ebeam_faml: + raise Exception ('Cannot load cell (%s) from library (%s) with parameters (%s).' % (cell_name, cell_library, cell_params)) + # lens for the output to the detector + t = Trans(Trans.R0, x_offset, y_offset) + inst_faml2 = cell.insert(CellInstArray(cell_ebeam_faml.cell_index(), t)) + # lens for the input from the laser + t = Trans(Trans.R0,x_offset,pitch + y_offset) + inst_faml1 = cell.insert(CellInstArray(cell_ebeam_faml.cell_index(), t)) + # automated test label + text = Text (label, t) + cell.shapes(ly.layer(ly.TECHNOLOGY['Text'])).insert(text).text_size = 5/ly.dbu + return [inst_faml1, inst_faml2] + + diff --git a/klayout_dot_config/tech/GSiP/GSiP.lyt b/klayout_dot_config/tech/GSiP/GSiP.lyt index 7f9105269..2067f4cbd 100644 --- a/klayout_dot_config/tech/GSiP/GSiP.lyt +++ b/klayout_dot_config/tech/GSiP/GSiP.lyt @@ -4,8 +4,9 @@ 0.001 + - /home/lukasc/Documents/GitHub/SiEPIC-Tools/klayout_dot_config/tech/GSiP + /Users/lukasc/Documents/GitHub/SiEPIC-Tools/klayout_dot_config/tech/GSiP klayout_Layers_GSiP.lyp true @@ -70,23 +71,9 @@ true default false + false - - false - true - true - 64 - 0 - 1 - 0 - DATA - 0 - 0 - BORDER - layer_map() - true - 0.001 1 diff --git a/klayout_dot_config/tech/GSiP/WAVEGUIDES.xml b/klayout_dot_config/tech/GSiP/WAVEGUIDES.xml index 8b9e66dcf..08f948ccf 100755 --- a/klayout_dot_config/tech/GSiP/WAVEGUIDES.xml +++ b/klayout_dot_config/tech/GSiP/WAVEGUIDES.xml @@ -11,6 +11,11 @@ 0.5 0.0 + + DevRec + 1.5 + 0.0 + Slot diff --git a/klayout_dot_config/tech/GSiP/klayout_Layers_GSiP.lyp b/klayout_dot_config/tech/GSiP/klayout_Layers_GSiP.lyp index 75c645812..c667a20bb 100644 --- a/klayout_dot_config/tech/GSiP/klayout_Layers_GSiP.lyp +++ b/klayout_dot_config/tech/GSiP/klayout_Layers_GSiP.lyp @@ -528,6 +528,24 @@ DevRec 68/0@1 + + #004080 + #004080 + 0 + 0 + I0 + + true + true + false + 1 + false + false + 0 + BlackBox + 998/0@1 + + diff --git a/klayout_dot_config/tech/GSiP/pymacros/pcells_GSiP/FaML_Si_1550_BB.py b/klayout_dot_config/tech/GSiP/pymacros/pcells_GSiP/FaML_Si_1550_BB.py new file mode 100644 index 000000000..57f9cb205 --- /dev/null +++ b/klayout_dot_config/tech/GSiP/pymacros/pcells_GSiP/FaML_Si_1550_BB.py @@ -0,0 +1,184 @@ +import pya +import math +from SiEPIC._globals import PIN_LENGTH as pin_length +from SiEPIC.extend import to_itype +from SiEPIC.scripts import path_to_waveguide, connect_pins_with_waveguide, connect_cell +from SiEPIC.utils import get_technology_by_name, load_Waveguides_by_Tech, get_layout_variables + +class FaML_Si_1550_BB(pya.PCellDeclarationHelper): + """ + The PCell declaration for black box cell + Facet-attached Micro-Lens (FaML) + for Silicon + for 1550 nm operation + + Authors: Dream Photonics + """ + + def __init__(self): + + # Important: initialize the super class + super(FaML_Si_1550_BB, self).__init__() + + self.technology_name = 'GSiP' + TECHNOLOGY = get_technology_by_name(self.technology_name) + + # declare the parameters + self.param("num_channels", self.TypeInt, "Number of Channels (0 - 16)", default = 2) + + self.param("ref_wg", self.TypeBoolean, "Include reference waveguide", default=False) + + #declare the layers + self.param("silayer", self.TypeLayer, "Si Layer", default = TECHNOLOGY['Si'], hidden=False) + self.param("pinrec", self.TypeLayer, "PinRec Layer", default = TECHNOLOGY['PinRec'], hidden=True) + self.param("devrec", self.TypeLayer, "DevRec Layer", default = TECHNOLOGY['DevRec'], hidden=True) + self.param("fibertarget", self.TypeLayer, "Fiber Target Layer", default=TECHNOLOGY['FbrTgt'], hidden=True) + self.param("textl", self.TypeLayer, "Text Layer", default=TECHNOLOGY['Text'], hidden=True) + self.param("bb",self.TypeLayer,"BB Layer", default=TECHNOLOGY['BlackBox'], hidden=True) + + def can_create_from_shape_impl(self): + return False + + + def produce(self, layout, layers, parameters, cell): + # This is the main part of the implementation: create the layout + self.cell = cell + self._param_values = parameters + self.layout = layout + + #fetch the parameters + dbu = self.layout.dbu + ly = self.layout + shapes = self.cell.shapes + + LayerSiN = ly.layer(self.silayer) + LayerPinRecN = ly.layer(self.pinrec) + LayerDevRecN = ly.layer(self.devrec) + LayerFbrTgtN = ly.layer(self.fibertarget) + LayerTEXTN = ly.layer(self.textl) + LayerBBN = ly.layer(self.bb) + + num_channels = self.num_channels + offset = to_itype(0,dbu) + pitch = to_itype(127,dbu) + l_taper = 60e3 + Lw2 = to_itype(15,dbu) + Lw3 = Lw2 + to_itype(20,dbu) + + wavelength = 1550 + + waveguide_type = 'Strip' + w_waveguide = 500 # nm + + if num_channels < 0: + num_channels = 0 + if num_channels > 16: + num_channels = 16 + + def circle(x,y,r): + npts = 180 + theta = 2*math.pi/npts + pts = [] + for i in range(0,npts): + pts.append(pya.Point.from_dpoint(pya.DPoint((x+r*math.cos(i*theta))/1,(y+r*math.sin(i*theta))/1))) + return pts + + #draw one loopback device + if self.ref_wg: + for ref_loop in range(2): + #draw fibre target circle + align_circle = circle(offset,-pitch*(ref_loop+1),2/dbu) + #place fibre target circle + shapes(LayerFbrTgtN).insert(pya.Polygon(align_circle)) + + if self.ref_wg: + #create waveguide to for loopback + loopback_path = pya.DPath([pya.DPoint(0,-127),pya.DPoint((offset + l_taper + Lw2)*dbu+15,-127),pya.DPoint((offset + l_taper + Lw2)*dbu+15,-254),pya.DPoint(0,-254)],0.5) + self.layout.technology_name = self.technology_name #required otherwise "create_cell" doesn't load + pcell = self.layout.create_cell("Waveguide",self.technology_name,{"path": loopback_path, "waveguide_type": waveguide_type}) + t = pya.Trans(pya.Trans.R0,0,0) + self.cell.copy(pcell,LayerSiN,LayerBBN) + wg = self.cell.insert(pya.CellInstArray(pcell.cell_index(),t)) + wg.flatten() + self.cell.clear(LayerDevRecN) + self.cell.clear(LayerPinRecN) + self.cell.clear(LayerSiN) + + + ########################################################################################################################################################################## + #draw N tapers + x = offset + l_taper + Lw3 + for n_ch in range(int(num_channels)): + + #draw the taper + taper_pts = [pya.Point(0,-w_waveguide/2+pitch*n_ch),pya.Point(0,w_waveguide/2+pitch*n_ch),pya.Point(offset + l_taper + Lw3,w_waveguide/2+pitch*n_ch),pya.Point(offset + l_taper + Lw3,-w_waveguide/2+pitch*n_ch)] + + #place the taper + shapes(LayerBBN).insert(pya.Polygon(taper_pts)) + + #draw and place pin on the waveguide: + t = pya.Trans(pya.Trans.R0, x, pitch*n_ch) + pin = pya.Path([pya.Point(-pin_length/2,0),pya.Point(pin_length/2,0)], w_waveguide) + pin_t = pin.transformed(t) + shapes(LayerPinRecN).insert(pin_t) + text = pya.Text(f"opt{n_ch+1}",t) + shape = shapes(LayerPinRecN).insert(text) + shape.text_size = 3/dbu + + #draw fibre target circle + align_circle = circle(offset,pitch*n_ch,2/dbu) + + #place fibre target circle + shapes(LayerFbrTgtN).insert(pya.Polygon(align_circle)) + + #draw devrec box + n_ch = (num_channels-1) + if self.ref_wg: + devrec_pts = [pya.Point(0,pitch*n_ch+30/dbu),pya.Point(x,pitch*n_ch+30/dbu),pya.Point(x,-pitch*2-30/dbu),pya.Point(0,-pitch*2-30/dbu)] + else: + devrec_pts = [pya.Point(0,pitch*n_ch+30/dbu),pya.Point(x,pitch*n_ch+30/dbu),pya.Point(x,-30/dbu),pya.Point(0,-30/dbu)] + + #place devrec box + shapes(LayerDevRecN).insert(pya.Polygon(devrec_pts)) + + #edge of chip text + t = pya.Trans(pya.Trans.R0,0,1/dbu) + text = pya.Text("<- Edge of chip",t) + shape = shapes(LayerTEXTN).insert(text) + shape.text_size = 3/dbu + + #BB description + t = pya.Trans(pya.Trans.R0,0,-15/dbu) + text = pya.Text(" Number of Channel(s): " + str(num_channels) + "\n Center Wavelength: " + str(wavelength) + " nm",t) + shape = shapes(LayerTEXTN).insert(text) + shape.text_size = 3/dbu + + #BB description + t = pya.Trans(pya.Trans.R0, 0,-25/dbu) + text = pya.Text("<- 25 MFD lens",t) + shape = shapes(LayerTEXTN).insert(text) + shape.text_size = 3/dbu + + #draw lenses + width_lens = to_itype(50, dbu) + length_lens = to_itype(50, dbu) + + if self.ref_wg: + for n_ch in range(int(num_channels+2)): + lens_pts = [pya.Point(0,-width_lens/2+pitch*n_ch-2*pitch), pya.Point(0,width_lens/2+pitch*n_ch-2*pitch), pya.Point(-length_lens,width_lens/2+pitch*n_ch-2*pitch),pya.Point(-length_lens,-width_lens/2+pitch*n_ch-2*pitch)] + shapes(LayerBBN).insert(pya.Polygon(lens_pts)) + lens = circle(-length_lens,pitch*n_ch-2*pitch,25/dbu) + shapes(LayerBBN).insert(pya.Polygon(lens)) + else: + for n_ch in range(int(num_channels)): + lens_pts = [pya.Point(0,-width_lens/2+pitch*n_ch), pya.Point(0,width_lens/2+pitch*n_ch), pya.Point(-length_lens,width_lens/2+pitch*n_ch),pya.Point(-length_lens,-width_lens/2+pitch*n_ch)] + shapes(LayerBBN).insert(pya.Polygon(lens_pts)) + lens = circle(-length_lens,pitch*n_ch,25/dbu) + shapes(LayerBBN).insert(pya.Polygon(lens)) + + def display_text_impl(self): + # Provide a descriptive text for the cell + return "FaML_Si_1550_BB_%s" % ( + self.num_channels, + ) + diff --git a/klayout_dot_config/tech/GSiP/pymacros/tests/test_FaML.py b/klayout_dot_config/tech/GSiP/pymacros/tests/test_FaML.py new file mode 100644 index 000000000..2e4374bbf --- /dev/null +++ b/klayout_dot_config/tech/GSiP/pymacros/tests/test_FaML.py @@ -0,0 +1,111 @@ +""" +Test for FaML + +by Lukas Chrostowski 2024 + +""" + +def test_FaML_two(): + ''' + --- Simple MZI, tested using Facet-Attached Micro Lenses (FaML) --- + + by Lukas Chrostowski, 2024 + + Example simple script to + - use the GSiP technology + - using KLayout and SiEPIC-Tools, with function including connect_pins_with_waveguide and connect_cell + - create a new layout with a top cell + - create a Mach-Zehnder Interferometer (MZI) circuits + - export to OASIS for submission to fabrication + - display the layout in KLayout using KLive + + Test plan + - count lenses from the bottom up (bottom is 1, top is 6, in this design) + - laser input on bottom lens (1), detector on second (2), for alignment + - MZI1: laser on 3, detector on 4, sweep + - MZI2: laser on 5, detector on 6, sweep + + + Use instructions: + + Run in Python, e.g., VSCode + + pip install required packages: + - klayout, SiEPIC, siepic_ebeam_pdk, numpy + + ''' + + designer_name = 'LukasChrostowski' + top_cell_name = 'EBeam_%s_MZI2_FaML' % designer_name + export_type = 'static' # static: for fabrication, PCell: include PCells in file + #export_type = 'PCell' # static: for fabrication, PCell: include PCells in file + + import pya + + import SiEPIC + from SiEPIC._globals import Python_Env + from SiEPIC.scripts import connect_cell, connect_pins_with_waveguide, zoom_out, export_layout + from SiEPIC.utils.layout import new_layout, floorplan, FaML_two + from SiEPIC.extend import to_itype + from SiEPIC.verification import layout_check + + import os + + if Python_Env == 'Script': + # For external Python mode, when installed using pip install siepic_ebeam_pdk + import GSiP + + print('EBeam_LukasChrostowski_MZI2 layout script') + + tech_name = 'GSiP' + + from packaging import version + if version.parse(SiEPIC.__version__) < version.parse("0.5.4"): + raise Exception("Errors", "This example requires SiEPIC-Tools version 0.5.4 or greater.") + + ''' + Create a new layout using the EBeam technology, + with a top cell + and Draw the floor plan + ''' + cell, ly = new_layout(tech_name, top_cell_name, GUI=True, overwrite = True) + floorplan(cell, 1000e3, 244e3) + + waveguide_type1='Strip' + + ####################### + # Circuit #1 – Loopback + ####################### + # draw two edge couplers for facet-attached micro-lenses + inst_faml = FaML_two(cell, + label = "opt_in_TE_1550_FaML_%s_loopback" % designer_name, + cell_name = 'FaML_Si_1550_BB', + cell_library = 'GSiP', + ) + # loopback waveguide + connect_pins_with_waveguide(inst_faml[0], 'opt1', inst_faml[1], 'opt1', waveguide_type=waveguide_type1) + + # Export for fabrication, removing PCells + path = os.path.dirname(os.path.realpath(__file__)) + filename, extension = os.path.splitext(os.path.basename(__file__)) + if export_type == 'static': + file_out = export_layout(cell, path, filename, relative_path = '..', format='oas', screenshot=True) + else: + file_out = os.path.join(path,'..',filename+'.oas') + ly.write(file_out) + + # Verify + file_lyrdb = os.path.join(path,filename+'.lyrdb') + num_errors = layout_check(cell = cell, verbose=False, GUI=True, file_rdb=file_lyrdb) + print('Number of errors: %s' % num_errors) + + # Display the layout in KLayout, using KLayout Package "klive", which needs to be installed in the KLayout Application + if Python_Env == 'Script': + from SiEPIC.utils import klive + klive.show(file_out, lyrdb_filename=file_lyrdb, technology=tech_name) + + if num_errors > 0: + raise Exception ('Errors found in test_FaML_two') + +if __name__ == "__main__": + test_FaML_two()