Layout-Aware Monte Carlo Simulations for Yield Estimation
Contents
Layout-Aware Monte Carlo Simulations for Yield Estimation#
Manufacturing variability can cause fabrication errors in waveguide width and thickness,
which can affect the device performance. Hence, incorporating manufacturing variability
into the photonic device design process is crucial. Simphony provides the ability to run
layout-aware Monte Carlo simulations for yield estimation to aid in robust photonic device design.
This tutorial will walk you through the codes found in examples/layout_aware.py of the Simphony repository.
We expect you to have read the previous tutorial, Mach-Zehnder interferometer.
The Workflow#
The workflow for running layout-aware Monte Carlo simulations is as follows:
instantiate the components
generate a layout using the components’
componentattributesconnect the components to form a circuit
run the simulation
extract and plot the results
Example - Mach-Zehnder Interferometer (MZI)#
We will run a layout-aware Monte Carlo simulation for the Mach-Zehnder Interferometer (MZI) described in the previous example.
First we need to import the necessary Simphony modules. We will need the siepic model library,
the Simulation, Laser, and Detector. We will also need to import gdsfactory to generate the layout.
We will also import matplotlib.pyplot, from outside of Simphony, to view the results
of our simulation.
import gdsfactory as gf
import matplotlib.pyplot as plt
import numpy as np
from simphony.libraries import siepic
from simphony.simulation import Detector, Laser, Simulation
We then create all the components and give them names. These include the grating couplers, the Y-branches, and the waveguides (which can be defined at any arbitrary length, on the condition that the two lengths are different).
gc_input = siepic.GratingCoupler(name="gcinput")
y_splitter = siepic.YBranch(name="ysplit")
wg_long = siepic.Waveguide(length=150e-6, name="wglong")
wg_short = siepic.Waveguide(length=50e-6, name="wgshort")
y_recombiner = siepic.YBranch(name="y_recombiner")
gc_output = siepic.GratingCoupler(name="gcoutput")
We then use the components’ component attributes to create the layout.
The component attributes are gdsfactory.Component objects, and so can be used to create a
layout. We will next define a Parametric Cell (PCell) for the MZI. We will connect
the components to form a circuit, route the Waveguides using gdsfactory's routing functions.
@gf.cell
def mzi():
c = gf.Component("mzi")
ysplit = c << y_splitter.component
gcin = c << gc_input.component
gcout = c << gc_output.component
yrecomb = c << y_recombiner.component
yrecomb.move(destination=(0, -55.5))
gcout.move(destination=(-20.4, -55.5))
gcin.move(destination=(-20.4, 0))
gc_input["pin1"].connect(y_splitter, gcin, ysplit)
gc_output["pin1"].connect(y_recombiner["pin1"], gcout, yrecomb)
y_splitter["pin2"].connect(wg_long)
y_recombiner["pin3"].connect(wg_long)
y_splitter["pin3"].connect(wg_short)
y_recombiner["pin2"].connect(wg_short)
wg_long_ref = gf.routing.get_route_from_steps(
ysplit.ports["pin2"],
yrecomb.ports["pin3"],
steps=[{"dx": 91.75 / 2}, {"dy": -61}],
)
wg_short_ref = gf.routing.get_route_from_steps(
ysplit.ports["pin3"],
yrecomb.ports["pin2"],
steps=[{"dx": 47.25 / 2}, {"dy": -50}],
)
wg_long.path = wg_long_ref
wg_short.path = wg_short_ref
c.add(wg_long_ref.references)
c.add(wg_short_ref.references)
c.add_port("o1", port=gcin.ports["pin2"])
c.add_port("o2", port=gcout.ports["pin2"])
return c
We can then call the function, and visualize the layout in KLayout.
c = mzi()
c.show()
We can also view a 3D representation of the layout.
c.to_3d().show("gl")
Layout-Aware Monte Carlo Simulation#
We use the Simulation class to run a simulation. We attach a Laser to one of the GratingCouplers,
and a Detector to the other GratingCoupler.
with Simulation() as sim:
l = Laser(power=1)
l.freqsweep(187370000000000.0, 199862000000000.0)
l.connect(gc_input["pin2"])
d = Detector()
d.connect(gc_output["pin2"])
results = sim.layout_aware_simulation(c)
Here, we can pass in the standard deviations of the widths and thicknesses, as well as the correlation length
as arguments to the layout_aware_simulation method. For this example, we use the default values.
After the simulation is run, we can extract the results, and plot them. We will see several, slightly different curves due to random variations incorporated into the components’ widths and thicknesses.
f = l.freqs
for run in results:
p = []
for sample in run:
for data_list in sample:
for data in data_list:
p.append(data)
plt.plot(f, p)
run_0 = results[0]
p = []
for sample in run_0:
for data_list in sample:
for data in data_list:
p.append(data)
plt.plot(f, p, "k")
plt.title("MZI Layout Aware Monte Carlo")
plt.show()
You should see something similar to this graph when you run your MZI now:
From our data, we can then compute various performance markers which are sensitive to width and thickness variations.