Laying out a Mach-Zehnder Interferometer

Laying out a Mach-Zehnder Interferometer#

This page will walk you through the steps of laying out an MZI for fabrication. In the last tutorial we learned how to use Simphony to simulate an MZI, and now we will learn to prepare a GDS file using the SiEPIC PDK. First we need to install the ubcpdk package. Following the installation instructions, we add it to our environment.

conda install -c conda-forge gdstk -y
pip install ubcpdk --upgrade

Then we can play with the components.

import gdsfactory as gf

import ubcpdk
import ubcpdk.components as uc

gf.CONF.display_type = "klayout"
2024-01-29 10:08:41.400 | INFO     | gdsfactory.technology.layer_views:__init__:790 - Importing LayerViews from YAML file: '/home/parkeraa/miniconda3/envs/mp/lib/python3.11/site-packages/ubcpdk/layers.yaml'.
2024-01-29 10:08:42.302 | INFO     | ubcpdk:<module>:30 - Found UBCpdk '2.3.4' installed at PosixPath('/home/parkeraa/miniconda3/envs/mp/lib/python3.11/site-packages/ubcpdk')
2024-01-29 10:08:42.375 | INFO     | gdsfactory.technology.layer_views:__init__:790 - Importing LayerViews from YAML file: '/home/parkeraa/miniconda3/envs/mp/lib/python3.11/site-packages/gdsfactory/generic_tech/layer_views.yaml'.
2024-01-29 10:08:42.379 | INFO     | gdsfactory.pdk:activate:307 - 'ubcpdk' PDK is now active

First, we can get a basic idea of what is going on by looking at a Y splitter. The code below creates the splitter, then plots it. pretty simple. The Y splitter is a fixed component in the PDK, which in this case means that it doesn’t take any arguments and we can’t change its geometry. All the components available in the PDK can be found in the docs.

y = uc.ebeam_y_1550()
y.plot()
../_images/fa25817bc16eb00c0e2820296328c2c8574e9d904f855fab955c0acb90b52485.png

Now let’s look at a component we can play with. We’ll use a single-coupled ring. The ring has many features we can adjust, which are all listed in the docs. For now we’ll just play with the gap, the radius (of the bends), the length_x (which is the coupler length), and the length_y. Try a few of these values to try them out.

ring = uc.ring_single(gap=0.2, length_x=10, length_y=5, radius=10)
ring.plot()
../_images/62be762608586a3300bdd40bb2a76f0f6829b1fbbde8acd3ee2d9ac95e862ce4.png

Now let’s make an MZI. First we will just see what the default parameters give us.

mzi = uc.mzi()
mzi.plot()
../_images/97d04a193fd86808be449b919a9338de314cebb2e763201da07fe0e746e0201f.png

Now we can play around a little bit. First, we can change various lengths. We find the parameters in the docs. There is an ASCII drawing there that is helpful to understand what the different length parameters do.

mzi = uc.mzi(delta_length=100, length_x=1, length_y=20)
mzi.plot()
../_images/62fa2a534bc712557bf1c70de7b7345c1a2db001d5ff2d61cfc69cd9dbccc0cf.png

Finally, we can easily add a grating array to any component. Note that the component we want to add gratings to will be rotated so that its ports are oriented vertically. Also, though we can’t see it in the plot, adding the grating couplers also adds a label to one of the gratings, marking it as the input.

Warning

The label is essential for the automated testing that is part of the OpenEBL fabrication run. The label tells the tester what polarization to use, which port is the input, and what to name the data set it collects.

We can set the device name on this label usig the ‘component_name’ argument. The docs are always helpful. However, we also need to set our username for the labels. The username will be used to help you find your data after testing. To set your username, you have to create a configuration (or YAML) file in your working directory. To do this, create a new file named config.yml in the same directory as your code. This file will contain only one line:

username: "YourName"

You may need to restart your kernel after creating the configuration file.

Warning

The file must be named config.yml and must be placed in the same directory as your code. It will contain the line username: "YourName"

TE_MZI = uc.add_fiber_array(component=mzi, component_name='MyCoolMZI1')
TE_MZI.plot()
../_images/ceef6e8f28233ab9522122a4f4571bbb52466070e4f18e6cfca9bcd2fb1f9c1a.png

We can also change the grating coupler to a TM coupler by passing in a TM coupler. We also can flip the direction of the mzi. This isn’t really useful for the MZI but can help us make sure the right port is the input in other cases.

TM_MZI = uc.add_fiber_array(component=mzi.rotate(180), grating_coupler=uc.gc_tm1550, component_name='MyCoolMZITM')
TM_MZI.plot()
../_images/d1a7e1df664b1e3e5992fa8e01f6c15ac021cc6a21fcf72f064132afdadd001c.png

Now we should put them together to make our ‘final’ design. We can use the distribute and align functions.

mzi_together = gf.Component()
mzi_together.add_ref(TE_MZI)
mzi_together.add_ref(TM_MZI)

mzi_together.distribute(direction='x', spacing=10)
mzu_together = mzi_together.align(alignment='ymax')
mzi_together.plot()
/home/parkeraa/miniconda3/envs/mp/lib/python3.11/site-packages/gdsfactory/component.py:1569: UserWarning: Unnamed cells, 1 in 'Unnamed_a784d514'
  gdspath = component.write_gds(logging=False)
../_images/53579cfbce97dff92e8655f428a25885f458eb7131ed783ee0c90c708a3e218d.png

We can export our design using the write_gds function. If you zoom in on where the lower coupler meets the waveguide, you can see the text label. It should say:

opt_in_TE_1550_device_YourName_MyCoolMZI1
mzi_together.write_gds("mzi.gds")
/tmp/ipykernel_322436/1677396279.py:1: UserWarning: Unnamed cells, 1 in 'Unnamed_a784d514'
  mzi_together.write_gds("mzi.gds")
2024-01-29 10:09:05.044 | INFO     | gdsfactory.component:_write_library:1913 - Wrote to 'mzi.gds'
PosixPath('mzi.gds')