diff --git a/klayout_dot_config/python/SiEPIC/utils/layout.py b/klayout_dot_config/python/SiEPIC/utils/layout.py
index 14113b6d..af3925d3 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 7f910526..2067f4cb 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 8b9e66dc..08f948cc 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 75c64581..c667a20b 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
+
+ #004080
+ #004080
+ 0
+ 0
+ I0
+
+ true
+ true
+ false
+ 1
+ false
+ false
+ 0
+ BlackBox
+
+
+
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 00000000..57f9cb20
--- /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 00000000..2e4374bb
--- /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()