Corner analysis
Corner simulations are run using ca.monte_carlo_corners
.
In order to make your devices fab-corner aware, you need to set up your CircuitModels, which is explained below. Afterwards we’ll explain how to check whether the setup is correct, and how to load saved results.
Setting up a simulation for corner analysis
To set up a simulation for corner analysis, a user needs to set up a configuration. This configuration consists of the following 2 files and should be in the same folder:
models.yaml
A model file, typically models.py.
The models.yaml file looks as follows:
ipkiss3:
TemplatedWindowWaveguide:
parameters:
w:
doc: "Width of the waveguide"
default: 0
corners: [ -1, 0, 1 ]
generate_parameters: "models.waveguide_to_model_parameters"
The models.py contains methods that take (CircuitModelView, varied parameters) and output parameters for the CompactModel
.
For instance:
def waveguide_to_model_parameters(self: TemplatedWindowWaveguide.CircuitModel, **parameters):
def fn(x):
return 2.4 + x * 0.001
return {"n_eff": fn(parameters["w"])}
Circuit model based on S-parameters
Below is an example of a CircuitModel that will store its corners into touchstone S-parameters. Let’s start with importing the necessary functions / classes:
from ipkiss3.simulation.circuit.utils import convert_smatrix_units
Now let’s define/update the CircuitModel for the nominal case:
class MyDevice(i3.PCell):
...
class CircuitModel(i3.CircuitModelView):
def _generate_model(self):
smatrix_path = os.path.join(os.path.dirname(__file__), "corner_data", "mmi_data", "s300_t500_w450.s3p")
smat = convert_smatrix_units(
i3.device_sim.SMatrix1DSweep.from_touchstone(smatrix_path),
to_unit="um",
)
return i3.circuit_sim.BSplineSModel.from_smatrix(smat)
In the _generate_model
, we calculate the nominal BSplineSModel using the values that are set by the user.
If you already had a _generate_model defined, it typically doesn’t need to be changed, but
in this case we want to load the correct S-parameters from the correct touchstone file (mmi_data/s0_t0_w0.s3p
).
To enrich our component with information about the corners, we need to add a filename_format field to the models.yaml configuration: .. code-block:: yaml
- MyLibrary:
- MyDevice:
- parameters:
- s:
doc: “Slab height” default: 0 corners: [295, 300, 305]
- t:
doc: “Thickness of the MMI” default: 0 corners: [495, 500, 505]
- w:
doc: “Width of the waveguide” default: 0 corners: [445, 450, 455]
filename_format: “mmi_data/s{s}_t{t}_w{w}.s3p”
This way when running simulations where the corner context is provided, Circuit Analyzer will know how to vary them based on this field.
Checking whether simulation setup is OK
After you’ve set up the circuitmodel, your original simulations should still work (and provide the nominal result, i.e., where all corners would be their default values).
You can check the available corners in your circuitmodel using
ca.get_corners
. For the example above, it should print the three corners s, t and w in a dictionary, together with the possible values.You can run a single simulation in a specific set of corners (where each model is evaluated with the same corners):
S = corner_analysis(circuitmodel, wavelengths, s=295, t=505, w=450)
You can check all possible corners, with every component within your circuit having the same corner, with the following function:
S_matrices = corner_analysis_all_combinations(circuitmodel, wavelengths)
This returns a list of S-matrices.
Running monte carlo
Finally, you can run the monte carlo using ca.monte_carlo_corners
.
For each sample, all components will have randomly chosen corners.
For example, if there are 2 fab parameters; s
, t
with each 3 corners, then possible variations are:
s=300, t=500
(nominal)s=300, t=495
s=300, t=505
s=295, t=495
s=295, t=500
s=295, t=505
s=305, t=495
s=305, t=500
s=305, t=505
S_matrices = monte_carlo_corners(circuitmodel, wavelengths, save_path='corner_montecarlo.data')
The result is a list of i3.circuit_sim.SMatrix1DSweep
.
It has an extra attribute added called sampling
that contains the corners so you can verify afterwards what corners were specifically used.
print(S_matrices[0].sampling)
This will print a dictionary with the corners and it’s values.
Loading back results
Results are also stored on disk so you can load them later:
import joblib
S_matrices = joblib.load('MC_example.data')
Wafer Map Corner Analysis
For each fabrication parameter, we can also provide a wafer map (or rather a deviation map). These deviation maps can be based on actual data or be randomly generated. There are a couple of deviation maps available:
from circuit_analyzer.capheve.variability_maps import (
ConstantMap,
CoherentNoiseMap,
InterpolatedGridMap,
InterpolatedMap,
RandomMap,
)
By giving all the positions of the circuit on the wafer maps, we can sample according to the sample points (VE_sample_points) of each subcomponent of the circuit. We can visualize these sample points in the following way:
from circuit_analyzer.capheve import view
cell = SomeCircuit()
lay = cell.Layout()
cm = cell.CircuitModel()
cm.visualize_VE_sample_points()
We sample the deviation of the fab parameter on each of those sample points and calculate the model parameters for each. By default, waveguides will have multiple sampling points. In this case we take the average values for the model parameters.
An example of a wafer map:
map = CoherentNoiseMap(amplitude=0.1, radius=50.0)
map.visualize(show_contour=True, show=False)
plot_wafer_positions(positions, size_info=lay.size_info(), diameter=200000)
plt.show()
The actual function to run the simulation:
smats = sweep_wafermap(
circuit_model=cm,
wavelengths=wavelengths,
deviation_maps={
"s": map,
"t": map,
"w": CoherentNoiseMap(amplitude=0.1, radius=50.0),
},
positions=positions,
)
Each S-matrix in smats has its corresponding position and sampling:
for smat in smats:
print(smat.position)
print(smat.sampling)