Step 2: Using waveguides in a layout
Result
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.
Illustrates
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.
ring.py
: this is a self-contained python file that contains a description of our ring resonator.execute.py
: 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 ‘execute.py’.
Waveguides
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(name=self.name+"_ring", trace_template=self.wg_template)
def _default_bus(self):
return i3.Waveguide(name=self.name+"_bus", trace_template=self.wg_template)
Here, we use the property
WaveguideTemplateProperty
, which is a property that accepts waveguide templates. We supplyTECH.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.
Caution
Be careful to specify a unique name for these cells,. The recommended practice is concatenating the name of the parent cell (self.name
) 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
ring_layout.set(trace_template=wg_template,
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)
bus_layout.set(trace_template=wg_template,
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
Inside
_default_ring
and_default_bus
, we first create the Layout view of each waveguide.We first get the ring waveguide pcells by using
self.cell.ring
.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!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.We do the exact same thing for the bus waveguide.
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.
import si_fab.all as pdk # 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()
my_ring_layout.write_gdsii("myring.gds")
my_ring_layout.visualize()
We import the technology. This is needed as the default waveguide templates are contained in the TECH tree.
We import and instantiate the default RingResonator and its layout view. The default waveguide templates will be used.
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
my_ring_layout.wg_template.cross_section().visualize()
# 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)
new_wg_template_layout.cross_section().visualize()
# 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()
my_ring_2_layout.write_gdsii("myring2.gds")
my_ring_2_layout.visualize()
We still use the default template.
We create a new waveguide template with a different layout view.
We create a new ring with
wg_template=new_wg_template
.We instantiate and visualize the Layout.
Recap
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.