Layout and simulation of a Mach-Zehnder interferometer with spiral delay line.

This example shows how to create a simple MZI with a long delay line and run a circuit simulation.

Purpose

This example demonstrates the use of

  • i3.place_insts() and i3.Join to easily join ports of instances together (butt-to-butt)
  • i3.NetlistFromLayout to automatically derive a netlist from the layout
  • i3.HierarchicalModel to automatically derive a model from the resulting netlist

The example consists of a simple Mach-Zehnder interferometer using directional couplers and a spiral delay line

Imports

from technologies import silicon_photonics
import ipkiss3.all as i3
from picazzo3.traces.wire_wg import WireWaveguideTemplate
from picazzo3.wg.dircoup import SBendDirectionalCoupler
from picazzo3.wg.spirals import FixedLengthSpiralRounded
import numpy as np
import pylab as plt

Define waveguide template and directional coupler

We use a waveguide template to describe the properties of the waveguide (cross-section, model parameters). Then we also define a SBend directional coupler

# 2a. Define the waveguide template for routing
wg_tmpl = WireWaveguideTemplate()
wg_tmpl.Layout(core_width=0.4)

# 2b. Create a directional coupler
coupler_length = 6.0
dc = SBendDirectionalCoupler(name="DC_L{:.3f}".format(coupler_length),
                             trace_template1=wg_tmpl,
                             coupler_length=coupler_length)

dc_lo = dc.Layout(coupler_spacing=0.6,
                  bend_angle=40.0)

# For the model, we use a simple wavelength-independent model with 36% coupled power
dc_cm = dc.CircuitModel(cross_coupling1=0.6 * 1j)

Define a MZI class with the spiral length as parameter

class MZI(i3.PCell):
    spiral_length = i3.PositiveNumberProperty(default=5000.0, doc="length of the spiral")

    class Layout(i3.LayoutView):
        def _generate_instances(self, insts):

            # 2c. Create a spiral
            spiral = FixedLengthSpiralRounded(name="{}_spiral".format(self.name),
                                              trace_template=wg_tmpl,
                                              total_length=self.spiral_length,
                                              n_o_loops=6)
            spiral_lo = spiral.Layout(bend_radius=10.0,
                                      spacing=5,
                                      stub_direction="H",
                                      growth_direction="V")

            # 2d. Create a waveguide which is as long as the horizontal width of the spiral
            spiral_length = spiral_lo.ports["out"].position.x - spiral_lo.ports["in"].position.x
            wg = i3.Waveguide(name="{}_wg".format(self.name),
                              trace_template=wg_tmpl)
            wg.Layout(shape=[(0.0, 0.0), (spiral_length, 0.0)])

            # 2e. Use place_insts() with Place, Join, FlipV to conveniently position everything
            insts += i3.place_insts(
                insts={
                    'splitter': dc,
                    'combiner': dc,
                    'delay': spiral,
                    'wg': wg
                },
                specs=[i3.Place("splitter", (0.0, 0.0)),
                       i3.FlipV('delay'),
                       i3.Join([("splitter:out2", "wg:in"),
                                ("splitter:out1", "delay:in"),
                                ("delay:out", "combiner:in1")])
                       ],
            )
            return insts

        def _generate_ports(self, ports):
            # 2f. Generate the ports from the splitter and combiner ports
            ports += i3.expose_ports(instances=self.instances,
                                     port_name_map={'splitter:in1': 'in1',
                                                    'splitter:in2': 'in2',
                                                    'combiner:out1': 'out1',
                                                    'combiner:out2': 'out2'})
            return ports

    class Netlist(i3.NetlistFromLayout):
        # 2g. Automatically derive the hierarchical netlist from the layout
        pass

    class CircuitModel(i3.CircuitModelView):
        def _generate_model(self):
            return i3.HierarchicalModel.from_netlistview(self.netlist_view)

Create the MZI, save the layout to GDSII and and visualize

mzi = MZI(name="mzi",
          spiral_length=5000.0)
mzi_lo = mzi.Layout()
mzi_lo.write_gdsii("example_mzi.gds")
mzi_lo.visualize(annotate=True)
../_images/sphx_glr_plot_mzi_spiral_001.png

Print the netlist, simulate for 1000 wavelength steps and plot

mzi_nl = mzi.Netlist()
print(mzi_nl.netlist)

mzi_cm = mzi.CircuitModel()
sim_wavelengths = np.linspace(1.548, 1.552, 1001)
mzi_S = mzi_cm.get_smatrix(wavelengths=sim_wavelengths)

plt.figure()
plt.title("MZI transmission")
plt.plot(sim_wavelengths, np.abs(mzi_S["out1", "in1"])**2, 'ro-', linewidth=3, markersize=7, label='in1->out1')
plt.plot(sim_wavelengths, np.abs(mzi_S["out2", "in1"])**2, 'bo-', linewidth=3, markersize=7, label='in1->out2')
plt.xlabel('wavelength [um]')
plt.ylabel('transmission')
plt.xlim([sim_wavelengths[0], sim_wavelengths[-1]])
plt.show()
../_images/sphx_glr_plot_mzi_spiral_002.png

Out:

netlist:
--------
instances:
        - delay : <Single instance in netlist of mzi_spiral>
        - splitter : <Single instance in netlist of DC_L6.000>
        - combiner : <Single instance in netlist of DC_L6.000>
        - wg : <Single instance in netlist of mzi_wg>

terms:
        - in1
        - out2
        - out1
        - in2

nets:
        - in2-splitter:in2: <OpticalLink in2 to splitter:in2>
        - combiner:in1-delay:out: <OpticalLink combiner:in1 to delay:out>
        - in1-splitter:in1: <OpticalLink in1 to splitter:in1>
        - combiner:in2-wg:out: <OpticalLink combiner:in2 to wg:out>
        - delay:in-splitter:out1: <OpticalLink delay:in to splitter:out1>
        - combiner:out1-out1: <OpticalLink out1 to combiner:out1>
        - combiner:out2-out2: <OpticalLink out2 to combiner:out2>
        - splitter:out2-wg:in: <OpticalLink splitter:out2 to wg:in>