Step 2: Using waveguides in a layout


In IPKISS, waveguides are true PCells with their own views. In this example we introduce waveguides by restructuring the ring resonator example. We will use waveguides to construct the ring and the bus waveguides. Using waveguides is much more powerful than drawing waveguide-like shapes on the layout as we did in the last tutorial.

In IPKISS waveguides are constructed by using ‘WaveguideTemplates’. The use of templates makes it easier to instantiate waveguides PCells that contain all the relevant views. As the name suggests, a WaveguideTemplate is a blueprint that contains all the generic properties (such as waveguide width, layer properties and so on) of a waveguide.

In this example, we learn how to use the Layout view of waveguides and waveguide templates.

waveguide templates and waveguides

Rings based on waveguides. A waveguide template describes how the waveguides should be generated.


  • how to use waveguides templates.

  • how to use waveguides in the layout view.

Files (found in tutorials/layout_advanced/02-waveguides/)

There are two files contained in this step.

  • : this is a self-contained python file that contains a description of our ring resonator.

  • : this is the file that is executed by python. It instantiates a ring and performs operations on it.

How to run this example

To run the example, run ‘’.


Waveguides are the most fundamental building blocks in integrated photonic circuits as they guide light from one component to another. In IPKISS, waveguides are PCells which contain views just like any other component. Since waveguides are used so often, IPKISS provides waveguide templates that help the user to construct waveguide PCells without having to redefine the properties common to most waveguides. The templates contain all the generic information about the waveguide such as its width, which layers to use etc. These are properties that will be shared by many different waveguides, so they are conveniently put together in a template. The waveguides themselves are then created using the templates and by specifying the extra properties that are specific to single waveguides, such as the shape along which the waveguide has to be drawn.

Default waveguide template and ChildCells

To define a waveguide you therefore need to:
  • Define a new waveguide template, or use an existing template, and

  • apply the template to create waveguides.

Before digging deeper into the details of waveguide templates we will first show you how to create waveguides using the default waveguide template defined in the IPKISS TECH file. In a later step, we will you show you how to customize the templates themselves.

Similar to the way other hierarchical cell are constructed, waveguides are added to a parent PCell using ChildCells. Usually, the parent PCell is in charge of creating its own waveguides. Let’s now have a look on how this is done in a ring resonator, which consists of two waveguides: the bus and the ring itself:

class RingResonator(i3.PCell):

    # 1. Defining the waveguide templates as WaveguideTemplateProperties
    wg_template = i3.WaveguideTemplateProperty(default=i3.TECH.PCELLS.WG.DEFAULT, doc="trace template used for the bus and the ring")

    # 2. For the bus and the ring we use a ChildCellProperty. The waveguide PCells themselves are created in the default method.
    bus = i3.ChildCellProperty(doc="bus waveguide")
    ring = i3.ChildCellProperty(doc="ring waveguide")

    # 3.Definition of the default values of the waveguide PCells
    def _default_ring(self):
        return i3.Waveguide("_ring", trace_template=self.wg_template)

    def _default_bus(self):
        return i3.Waveguide("_bus", trace_template=self.wg_template)
  1. Here, we use the property WaveguideTemplateProperty, which is a property that accepts waveguide templates. We supply TECH.PCELL.WG.DEFAULT, which is the default waveguide template specified in the TECH file.

2. Here we create two ChildCell properties for the ring and the bus the property that will contain a waveguide each. As we want our cell to create its own waveguides, we use a _default_ring and _default_bus method to specify them. These will create the ring and bus cells using self.wg_template, with a name based on the name of the parent cell.


Be careful to specify a unique name for these cells,. The recommended practice is concatenating the name of the parent cell ( with a meaningful suffix that is unique within the cell.

Placing the waveguides in the Layout View

Just as for regular hierarchical pcells you need to place an instance of the Layout View of each waveguide in the Layout view of the parent cell (here RingResonator). As is done in the code below, we calculate the Layout view of the waveguide Pcells in _default_ring(self) and _default_bus(self) and use these layout views in _generate_instances just as we did with regular Childcells in the previous step of this tutorial.

class RingResonator(i3.PCell):


 class Layout(i3.LayoutView):

     ring_radius = i3.PositiveNumberProperty(default=i3.TECH.WG.BEND_RADIUS, doc="radius of the ring")
     coupler_spacing = i3.PositiveNumberProperty(default=i3.TECH.WG.DC_SPACING,
                                                 doc="spacing between centerline of bus waveguide and ring waveguide")

     # 4.Definition of the default values of the layout view of the waveguide PCells
     def _default_ring(self):

         # 5. We get the ring_cell and the bus_cell
         ring_cell = self.cell.ring
         wg_template = self.wg_template

         # 6. We get the default layout view of the ring waveguide
         ring_layout = ring_cell.get_default_view(i3.LayoutView)

         # 7. We assign a shape to our ring waveguide. Here we use ShapeCircle
                         shape=i3.ShapeCircle(center=(0, 0), radius=self.ring_radius))

         return ring_layout

     def _default_bus(self):

         # 8. We do the same for the bus waveguide.
         bus_cell = self.cell.bus
         wg_template = self.wg_template
         r = self.ring_radius
         s = self.coupler_spacing

         bus_layout = bus_cell.get_default_view(i3.LayoutView)

                        shape=[(-r, -r-s), (+r, -r-s)])  # Using a shape made of list of coordinates

         return bus_layout

     def _generate_instances(self, insts):

         # 9. Placing a ring instance in our layout.
         insts += i3.SRef(name="ring", reference=self.ring)
         # 10. Placing a bus instance in our layout.
         insts += i3.SRef(name="bus", reference=self.bus)

         return insts
  1. Inside _default_ring and _default_bus, we first create the Layout view of each waveguide.

  2. We first get the ring waveguide pcells by using self.cell.ring.

  3. We get the default layout view for our ring using ring_cell.get_default_view(i3.LayoutView). And we set the waveguide template (layout view) - do not forget this step!

  4. We set the shape of the waveguide along which the waveguide will be drawn (the other generic properties are contained within the waveguide template). Here we use ShapeCircle to define this shape for the ring.

  5. We do the exact same thing for the bus waveguide.

  6. In the _generate_instances, we first place the layout view of the ring and bus waveguides.

Instantiating the ring

Instantiating the ring is completely analogous to how we instantiated our previous ring in step 1 of this tutorial.

# 1. Importing the required TECH file.
from technologies import silicon_photonics  # noqa

from picazzo3.traces.wire_wg import WireWaveguideTemplate
from ring import RingResonator

# 3.  Create a new RingResonator object
#     Provide a unique name
my_ring = RingResonator(name="myring")
# 4. Instantiate the default layout view.
my_ring_layout = my_ring.Layout()

  1. We import the technology. This is needed as the default waveguide templates are contained in the TECH tree.

  2. We import and instantiate the default RingResonator and its layout view. The default waveguide templates will be used.


Layout of my_ring using the default waveguide template.

Modifying the width of the waveguide template

Now that our ring uses waveguide templates, it is relatively easy to change the width of all the waveguides that use the template. In order to do that you need to know that all the waveguide templates are also PCells with views. All the Layout-specific properties of the template are therefore contained in its Layout View. Consequently, if we want to modify the width of our waveguides, we need to change the wg_width property that is contained in the Layout view of the waveguide template.

Let’s see how this can be done:

# 5. Inspect the (default) waveguide template currently used for the ring

# 6. Create a new waveguide template with a non-standard width

new_wg_template = WireWaveguideTemplate(name="my_wire_t")
new_wg_template_layout = new_wg_template.Layout(core_width=0.1)

# 7.  Use the new template in a new ring
my_ring_2 = RingResonator(name="myring2", wg_template=new_wg_template)
# 8. Instantiate the layout.
my_ring_2_layout = my_ring_2.Layout()
  1. We still use the default template.

  2. We create a new waveguide template with a different layout view.

  3. We create a new ring with wg_template=new_wg_template.

  4. We instantiate and visualize the Layout.


Layout of my_ring using the modified waveguide template.


You have now learned how to use waveguides in a layout context.

You can read more on:
  • how properties work across different views at properties.

In the next step we will introduce routes: they make it easier to calculate the shape along which waveguides are drawn when connecting different cells.