Interfacing with DS CST Studio Suite

When you setup an electromagnetic simulation using DS CST Studio Suite from within IPKISS, you go through the following steps:

  1. Define the geometry that represents your component.
  2. Specify the simulation job, consisting of the geometry, the expected outputs and simulation settings
  3. Inspect the exported geometry and optionally modify the generic or solver-specific settings.
  4. Retrieve the simulation results

Using a simple crossing we’ll show how to complete each of these steps. First let us start with defining the layout of this crossing:

1. Define the Simulation Geometry

Layout

We assume you already know how to define the layout of a component using IPKISS. If not, you can have a look at our tutorial. Below we’ve prepared the implementation of the layout of our crossing, you can either paste it into a new file called crossing_pcell.py, or find the file in the device_sim folder in the samples.

# Parametric cell for device exploration/optimization

__all__ = ["Crossing"]

from picazzo3.traces.wire_wg import WireWaveguideTemplate
import ipkiss3.all as i3


class Crossing(i3.PCell):
    """ Low-loss waveguide crossing
        See DOI: 10.1364/OE.21.029374
    """
    _name_prefix = "CROSSING"
    # design parameters
    widths = i3.ListProperty(default=[0.5, 0.6, 0.7, 0.8, 0.5], restriction=i3.RestrictTypeList([float]), doc="list of widths [um].")
    segment_length = i3.PositiveNumberProperty(default=0.375, doc="length of each segment [um].")

    # internal
    wg_template = i3.WaveguideTemplateProperty(name="waveguide template of the ports", locked=True)

    def _default_wg_template(self):
        return WireWaveguideTemplate(name="{}_wgtmpl".format(self.name))

    class Layout(i3.LayoutView):
        min_straight = i3.NonNegativeNumberProperty(default=0.1, doc="Minimum straight segment [um].")
        discretisation = i3.PositiveNumberProperty(default=0.01, doc="Step size of the interpolated curves.")

        def _default_wg_template(self):
            lv = self.cell.wg_template.get_default_view(self)
            lv.set(core_width=self.widths[0])
            return lv

        def _generate_elements(self, elems):
            import numpy as np

            ylist = 0.5 * np.asarray(self.widths)
            waypoint_shape = i3.Shape(
                [
                    (self.min_straight + n * self.segment_length, y)
                    for n, y in enumerate(ylist)
                ],
                start_face_angle=0.0,
                end_face_angle=0.0
            )
            spl_shape = i3.ShapeFitClampedCubicSpline(original_shape=waypoint_shape, discretisation=self.discretisation)
            arm_top_shape = i3.Shape([(0.0, ylist[0])]) + spl_shape
            L = (len(self.widths) - 1) * self.segment_length + self.min_straight
            arm_top_shape.transform(i3.Translation((-L, 0.0)))
            arm_shape = arm_top_shape + arm_top_shape.v_mirror_copy().reversed()
            for n in range(4):
                elems += i3.Boundary(layer=i3.TECH.PPLAYER.WG.CORE,
                                     shape=arm_shape.rotate_copy(rotation=n*90.0))
            elems += i3.Rectangle(layer=i3.TECH.PPLAYER.WG.CLADDING,
                                  box_size=(2 * L, 2 * L))
            return elems

        def _generate_ports(self, ports):
            L = (len(self.widths) - 1) * self.segment_length + self.min_straight
            ports += i3.OpticalPort(name="west", position=(-L, 0.0), angle=180.0, trace_template=self.wg_template)
            ports += i3.OpticalPort(name="south", position=(0.0, -L), angle=-90.0, trace_template=self.wg_template)
            ports += i3.OpticalPort(name="east", position=(L, 0.0), angle=0.0, trace_template=self.wg_template)
            ports += i3.OpticalPort(name="north", position=(0.0, L), angle=90.0, trace_template=self.wg_template)
            return ports

(download link)

Next we’ll first visualize the layout of our crossing, create example1_layout.py with the following content:

from technologies import silicon_photonics
import ipkiss3.all as i3
from crossing_pcell import Crossing

c = Crossing(widths=[0.5, 0.6, 0.95, 1.32, 1.44, 1.46, 1.466, 1.52, 1.58, 1.62, 1.76, 2.15, 0.5])
c_lo = c.Layout()

c_lo.visualize()
Visualization of the 2D layout of the crossing.

Fig. 66 Visualization of the 2D layout of the crossing we’ll use for running EM simulations.

Verifying the device geometry in IPKISS

Before simulating, you can verify the virtual fabrication of a device in IPKISS.

Two functions (methods of any layout view) are available for that:

  • visualize_2d() shows a top-down view of the device geometry based on material stacks
  • cross_section() shows a cross-section of a device along a given path

We can visualize the geometry of the crossing example defined above as:

c = Crossing(widths=[0.5, 0.6, 0.95, 1.32, 1.44, 1.46, 1.466, 1.52, 1.58, 1.62, 1.76, 2.15, 0.5])
c_lo = c.Layout()

c_lo.visualize_2d()
xs = c_lo.cross_section(
  cross_section_path=i3.Shape([(-2.0, -2.0), (-2.0, 2.0)]),
  path_origin=-2.0
)

xs.visualize()
../../_images/ds_cst_studio_suite-2_00.png
../../_images/ds_cst_studio_suite-2_01.png

To use the cross_section() method, you need to specify the path along which to take the cross-section. This needs to be an IPKISS Shape. You can also specify the path_origin argument in order to have a meaningful x axis. The method returns on object which you can visualize with its visualize() method.

Both visualize_2d() and cross_section() use the default virtual fabrication process. You can override this to a custom virtual fabrication process, by specifying vfabrication_process_flow in visualize_2d() and specifying process_flow in cross_section():

c = Crossing(widths=[0.5, 0.6, 0.95, 1.32, 1.44, 1.46, 1.466, 1.52, 1.58, 1.62, 1.76, 2.15, 0.5])
c_lo = c.Layout()

c_lo.visualize_2d(vfabrication_process_flow=process_flow_soi_oxide)
xs = c_lo.cross_section(
     cross_section_path=i3.Shape([(-2.0, -2.0), (-2.0, 2.0)]),
     process_flow=process_flow_soi_oxide,
     path_origin=-2.0
)

xs.visualize()
../../_images/ds_cst_studio_suite-3_00.png
../../_images/ds_cst_studio_suite-3_01.png

Simulation Geometry

Now that we have a layout for our component, we can use it to define the geometry of our simulation. IPKISS reuses the information it has to provide reasonable defaults, this way the initial declaration is straightforward.

sim_geom = i3.device_sim.SimulationGeometry(
    layout=c_lo,
    waveguide_growth=0.1,
)

That’s all you have to do when it comes to building the simulation geometry, IPKISS can now:

  1. Extend your waveguides with 0.1 micrometer, to make sure the ports are not at the edge of the simulation bounding box.
  2. Transform the layer information in a three dimension representation.
  3. Derive default dimensions for the simulation bounding box
  4. Derive defaults for the position and size of the ports

However with just a geometry we can’t do much, to put it to use we have to pass it to i3.device_sim.CSTTDSimulation, that’s what we’ll do next.

2. Define the simulation

To create a simulation using the geometry we’ve just defined, we’ll instantiate a i3.device_sim.CSTTDSimulation object:

simulation = i3.device_sim.CSTTDSimulation(
    geometry=sim_geom,
    outputs=[
       i3.device_sim.SMatrixOutput(
          name='smatrix',
          wavelength_range=(1.5, 1.6, 100)
       )
    ]
)

You see that we’ve added an SMatrixOutput. In this way IPKISS knows what kind of simulations to run. When we add an SMatrixOutput we know we have to perform an S-parameter sweep. With an output defined, your component is ready to be exported and simulated in your preferred simulation tool.

You can also see that the syntax for running defining the simulation is very similar for both tools, even though what happens in the background is very different.

3. Inspect the simulation job

To see what it looks like, you can use the inspect method:

simulation.inspect()

If your simulation tool has a graphical user interface, it will open and the 3D representation of your component will appear. You can now explore the geometry, ports, outputs and other settings as exported by IPKISS. You can make changes and modify settings from here. See Export additional settings below on how to make those settings available for future projects.

After execution, the CST Studio Suite GUI will open and look like this:

../../_images/cst_inspect.png

4. Retrieve and plot simulation results

When you’ve confirmed that the simulation setup is correct, you can continue with running the simulation and retrieving the results from the outputs. You don’t have to launch the simulation explicitly: when you request the results, IPKISS will run the simulation when required.

import numpy as np
smat = simulation.get_result(name='smatrix')
transmission = np.abs(smat['east', 'west']) ** 2

Since not all tools use the same physics representation conventions (e.g. \(\exp(-j\omega t)\) vs \(\exp(j \omega t)\)), IPKISS will ensure that the S-parameters are converted from the tool conventions into IPKISS conventions (\(\exp(-j\omega t)\)).

You can now plot these S-parameters with Matplotlib:

from scipy.constants import speed_of_light
import numpy as np
import matplotlib.pyplot as plt
wavelengths = speed_of_light / smatrix.sweep_parameter_values * 1e-3
dB = lambda x : 20*np.log10(np.abs(x))
plt.figure()
plt.plot(wavelengths, dB(smatrix["east", "west"]), label='transmission', linewidth=5)
plt.plot(wavelengths, dB(smatrix["north", "west"]), label='crosstalk', linewidth=5)
plt.plot(wavelengths, dB(smatrix["west", "west"]), label='reflection', linewidth=5)
plt.legend()
plt.xlabel('wavelength [$\mu m$]')
plt.ylabel('S-parameter magnitude')
plt.show()
../../_images/cst_crossing_sparamresult.png

S matrices can be stored to disk, S-parameter data handling.

5. Tune the geometry settings

Geometry Configuration

IPKISS provides reasonable defaults for your simulation. These are sufficient for a first step when you want to get a qualitative idea of the performance of your component. To improve the accuracy or efficiency, you’ll want to tune the settings of your simulation. In the crossing example we’ve demonstrated how to extend the waveguides. If we don’t do so, the ports will fall at the edge of the simulation region. This is one example of how you can change how IPKISS exports your component.

Process Flow

A Process Flow describes how we turn layout elements defined on layers, into a 3D representation. To create this process flow, you need information from the foundry on the relation between layers and the fabricated device. Many of the IPKISS PDKs offered by foundries will contain a process flow definition. If that’s not the case or you’re using your own PDK, you can build your own. We’ve written documentation to help you doing so.

By default IPKISS will use the process flow defined in the Technology of your PDK, which is assumed to be available under i3.TECH.VFABRICATION.PROCESS_FLOW. You can override this by providing a value for the process_flow attribute when initializing i3.device_sim.SimulationGeometry. This allows you to experiment with variations of the material properties or the material thicknesses.

Excluding Layers

When you define the layout of a component, you sometimes use layers that you don’t want export to the simulation tool. There might be various reasons for this, these layers might be logical layers like a device recognition layer, or you might want to exclude metal layers from your simulation. You can do this with the excluded_layers attribute. Here’s a small example for the PhaseModulator included in Picazzo3.

from technologies.silicon_photonics import TECH
from picazzo3.modulators.phase import PhaseModulator
import ipkiss3.all as i3

pmod = PhaseModulator()
pmod_lay = pmod.Layout(length=40.)

# we declare here that we exclude all the layers that don't belong to the
# waveguide, though in a real simulation, you'll most likely do want include those.
sim_geom = i3.device_sim.SimulationGeometry(
  layout=pmod_lay,
  excluded_layers=[
      i3.TECH.PPLAYER.P.LINE,
      i3.TECH.PPLAYER.N.LINE,
      i3.TECH.PPLAYER.PPLUS.LINE,
      i3.TECH.PPLAYER.NPLUS.LINE,
      i3.TECH.PPLAYER.M1,
      i3.TECH.PPLAYER.SIL.LINE,
      i3.TECH.PPLAYER.CONTACT.PILLAR,
  ]
)

In the case of our PhaseModulator example, you’ll probably want to only list the layers you’re interested in. That’s something you can do as well:

from technologies.silicon_photonics import TECH
from picazzo3.modulators.phase import PhaseModulator
import ipkiss3.all as i3

pmod = PhaseModulator()
pmod_lay = pmod.Layout(length=40.)

sim_geom = i3.device_sim.SimulationGeometry(
  layout=pmod_lay,
  layers=[
      i3.TECH.PPLAYER.WG.CORE,
      i3.TECH.PPLAYER.WG.CLADDING,
      i3.TECH.PPLAYER.RWG.CORE,
      i3.TECH.PPLAYER.RWG.CLADDING,
  ]
)

Note

Using the layers attribute, will override the excluded_layers setting.

Bounding Box

When you don’t specify the bounding box manually, a default is calculated. This is done in such a way that all ports and elements fit within the bounding box. You can modify this bounding box incrementally, this means that you only need to set those values you wish to set, when not set the default value is kept. The example below illustrates this:

from technologies.silicon_photonics import TECH
import ipkiss3.all as i3

wg = i3.Waveguide()
wg_layout = wg.Layout(shape=[
  (0, 0),
  (10, 0),
])

sz_info = wg_layout.size_info()

sim_geom = i3.device_sim.SimulationGeometry(
  layout=wg_layout,
  bounding_box=[
    [sz_info.west - 1.0, sz_info.east + 0.5],  # x-span of the bounding box
    None, # y-span of the bounding box, here we use the default calculated by IPKISS
    [0.3, None] # z-span of the bbox, here we only specify a value for the lower end of the bbox
  ]
)

Similar to what we did for the crossing earlier, you can use the inspect on the simulation definition to inspect the simulation setup within CST Studio Suite:

simulation = i3.device_sim.CSTTDSimulation(
    geometry=sim_geom,
    outputs=[
       i3.device_sim.SMatrixOutput(
          name='smatrix',
          wavelength_range=(1.5, 1.6, 100)
       )
    ]
)

simulation.inspect()
../../_images/cst_mws_bbox.png

6. Tune the generic simulation settings

Just as is the case with the geometry, IPKISS sets reasonable defaults for the port monitors. When you don’t specify any ports, defaults will automatically be added. The default position, direction are directly taken from the OpticalPorts of the layout, the dimensions of the Ports are calculated with a heuristic. Specifically, the width of the port is taken from the core_width attribute of the trace_template of the IPKISS OpticalPort. Any waveguide template that is a WindowWaveguideTemplate will have this attribute. For the majority of waveguide templates this is the case. The heuristic will add a fixed margin of 1um to this core_width. The height of the port is derived from the Material Stack used to virtually fabricate the layer of the core of the Waveguide, searching for the highest refractive index region but excluding the bottom, top, left and right boundary materials.

You can override these defaults in a similar way as we did for the geometry:

from technologies.silicon_photonics import TECH
import ipkiss3.all as i3

wg = i3.Waveguide()
wg_layout = wg.Layout(shape=[
  (0, 0),
  (10, 0),
])

sz_info = wg_layout.size_info()

in_port = wg_layout.ports['in']
in_pos = in_port.position.move_polar_copy(1., 0)


sim_geom = i3.device_sim.SimulationGeometry(
    layout=wg_layout,
)

simjob = i3.device_sim.CSTTDSimulation(
    geometry=sim_geom,
    monitors=[
        i3.device_sim.Port(
            name='in',
            # for the z-coordinate we keep the default
            # by setting the value to 'None'
            position=(in_pos.x, in_pos.y, None)
        ),
        i3.device_sim.Port(
            name='out',
            # we make the box of the port slightly wider
            box_size=(1.50, None)
        )
    ]
)

simjob.inspect()

When you inspect the simulation definition within CST Studio Suite, you’ll see something similar to the picture below:

../../_images/cst_studio_suite_ports.png

Multimode waveguides

By default, waveguide ports will be simulated with a single mode (the ground mode). You can override this in order to take multiple modes into account:

simjob = i3.device_sim.CSTTDSimulation(
   geometry=geometry,
   monitors=[i3.device_sim.Port(name="in", n_modes=2),
             i3.device_sim.Port(name="out", n_modes=2)],
   outputs=[
      i3.device_sim.SMatrixOutput(
         name='smatrix',
         wavelength_range=(1.5, 1.6, 100)
      )
   ]
)

The expectation is that the tool will order the modes according to descending propagation constant, the ground mode being the first mode, the mode with the second largest propagation constant second, and so forth.

The simulation results will then contain the S-parameters for each port-mode combination:

smat = simjob.get_result(name='smatrix')

import matplotlib.pyplot as plt
import numpy as np
plt.figure()
plt.plot(smat.sweep_parameter_values, 10 * np.log10(np.abs(smat['out:0', 'in:0'])**2), 'b-', label='transmission mode 0', linewidth=5)
plt.plot(smat.sweep_parameter_values, 10 * np.log10(np.abs(smat['out:1', 'in:1'])**2), 'b-.', label='transmission mode 1', linewidth=5)
plt.plot(smat.sweep_parameter_values, 10 * np.log10(np.abs(smat['in:0', 'in:0'])**2), 'r-', label='reflection mode 0', linewidth=5)
plt.plot(smat.sweep_parameter_values, 10 * np.log10(np.abs(smat['in:1', 'in:1'])**2), 'r-.', label='reflection mode 1', linewidth=5)
plt.xlabel(smat.sweep_parameter_name)
plt.ylabel('S-parameter [dB]')
plt.legend()
plt.show()

7. Use tool-specific settings

In addition to generic settings which can be applied to multiple solvers, the IPKISS device simulation interface allows also to use the full power of the solver tool. Tool-specific materials can be used and tool-specific macros can be defined.

Using materials defined by the simulation tool

By default, IPKISS exports materials for each material used in the process flow definition.

You can also reuse materials which are already defined by the device solvers by specifying a dictionary solver_material_map to the simulation object (i3.device_sim.CSTTDSimulation). It maps IPKISS materials onto materials defined by the tool (name-based).

For example:

simjob = i3.device_sim.CSTTDSimulation(
    ....
    solver_material_map={i3.TECH.MATERIALS.SILICON: 'Silicon (lossy)'}
)

This will map the TECH.MATERIALS.SILICON onto a material defined by the electromagnetic solver. The material name should be know by the solver. Please check the tool documentation to find out which materials are available. In CST, materials are stored in several locations:

  • <CST INSTALL PATH>\Library\Materials
  • %AppData%\CST AG\CST STUDIO SUITE\Library\Materials : contains material defined by the user
  • C:\Program Files (x86)\CST Library Extensions\Materials 1.0\Materials\ : materials installed through the library extensions. Please contact CST Studio Suite to learn how to obtain these library extensions.

Export additional settings from CST Studio Suite to IPKISS

Often you will want to tweak certain settings (simulation settings, materials, …) using very tool-specific commands or actions. Since it is not feasible to abstract everything, IPKISS provides a way to store and apply tool-specific settings.

When inspecting the simulation project from the CST Studio Suite GUI, one can easily tweak any desired settings from the GUI. Almost anything can be modified, really. Consult the CST manual for information.

As an example, we modify the S-parameter symmetry settings:

../../_images/cst_modify_settings.png

Now, we can store all the additional settings which were made back into the IPKISS model. These will then automatically be re-applied when you run the same simulation next time, also when you make changes to the device geometry, virtual fabrication process or simulation settings!

Save the settings to IPKISS through the menu item Macros>Results>-Import and Export>Export Additional Settings to Luceda IPKISS:

../../_images/cst_export_ipkiss.png

Multiphysics simulations

CST Studio Suite can also perform multiphysics simulations. A project can be set up to contain several type of simulations, including electromagnetic, thermal, etc.

Please contact either CST or us (support@lucedaphotonics.com) if you want to learn more on how to set up this workflow.