Step 1: Layout level hierarchy: Two Rings¶
- how to use ChildCells. ChildCells are used to add sub-cells to a PCell.
- how to do transformations (Translation and VMirror).
- how to place layout instances of the ChildCells in the layout view.
- how to define default generation methods for ChildCells.
Files (see: tutorials/layout_advanced/01-two-ring)
There are three files contained in this step.
ring.py: this is a self-contained python file that contains a description of our ring resonator.
two_rings.py: this is a self-contained python file that contains a description of a pcell containing two ring resonators.
execute.py: this is the file that is executed by python. It instantiates TwoRings and performs operations on it.
How to run this example
To run the example, run ‘execute.py’.
Using ChildCellProperty to create Hierarchical PCells¶
A hierarchical PCell is a PCell that contains references to other PCells (which we call Child PCells). In this step we will create a PCell
that is composed of two
RingResonator cells. To declare that a PCell has child PCells we use the property
TwoRings has two child PCells, therefore we define
ring_2 as child cells:
class TwoRings(i3.PCell): # The rings are defined as ChildCellProperties. This is a special property # for defining hierarchical PCells. In each view of the PCell of a certain type (e.g. Layout), # the corresponding viewtype (e.g. Layout view) of the ChildCell will automatically be available. ring_1 = i3.ChildCellProperty() ring_2 = i3.ChildCellProperty() def some_method_using_ring_1(self): print(self.ring_1) # In this method, self.ring_1 refers to the PCell ring_1. class Layout(i3.LayoutView): def some_method_using_ring_1(self): self.ring_1 # In this method, self.ring_1 refers to the layout view of ring_1. self.cell.ring_1 # In this method, self.cell.ring_1 refers the cell of ring_1.
ChildCellProperties behave a bit different than other properties. They allow access to the views of the child cells in the corresponding views of the parent cell.
Here this means that for the PCell
tworings.ring_1-> refers to the PCell instance of
tworings.get_default_view(i3.LayoutView).ring_1-> refers to the Layout view of
In the cases where you do need the child cell itself and not the view, you can always access the cell through the cell attribute:
tworings.get_default_view(i3.LayoutView).cell.ring_1 -> refers to the PCell instance of
The following piece of code helps to understand this behavior.
>>> # first we specify a dummy ring, with an empty layout view >>> class DummyRing(i3.PCell): ... class Layout(i3.LayoutView): ... pass # pass just tells Python that this view is empty. It contains no methods other than those in i3.LayoutView. >>> ring_1 = DummyRing(name="myring1") >>> ring_2 = DummyRing(name="myring2") >>> # now we instantiate the TwoRings pcell >>> tworings = TwoRings(ring_1=ring_1, ring_2=ring_2) >>> tworings.ring_1 is ring_1 # Check if tworings.ring_1 is ring_1 True >>> tworings.ring_2 is ring_2 # Check if tworings.ring_2 is ring_2 True >>> # so far this is not surprising >>> tworings_layout = tworings.Layout() >>> # when we now retrieve ring_1 from the layout it will be layoutview of the ring_1 cell ... # and not the ring_1 cell itself ( as would be the case if we would use a normal property) >>> isinstance(tworings_layout.ring_1, DummyRing.Layout) # Check if two_ring_layout.ring_1 object is a python instance of DummyRing.Layout True >>> isinstance(tworings_layout.ring_1, DummyRing) # Check if two_ring_layout.ring_1 object is a python instance of DummyRing False >>> isinstance(tworings_layout.cell.ring_1, DummyRing) # Check if two_ring_layout.cell.ring_1 object is a python instance of DummyRing True
This behavior allows easy access to the correct views of your childcell, while maintaining access to the PCell inside the views.
Placing layout instances¶
To use hierarchy on the layout level we need to place an instance of the layout of each child ring in the layout
two_rings. Of course, for this to be useful, we need to be able to place the instance of each child ring
at any location in the layout of
two_rings. More generally, we need to apply a transformation to each child ring
before placing its instance into the layout of two_rings as illustrated below.
For IPKISS 2.x users, it is interesting to note that the layout instance is equivalent to the SRef.
The following code excerpt shows how to implement this using the
_generate_instances(self, insts) function in the Layout view.
class TwoRings(i3.PCell): ring_1 = i3.ChildCellProperty() ring_2 = i3.ChildCellProperty() # Layout view class Layout(i3.LayoutView): def _generate_instances(self, insts): # 1. calculate the transformations of the rings based on their properties t1 = i3.Translation((0.0, self.ring_1.ring_radius + 2.0)) t2 = i3.VMirror() + i3.Translation((0.0, -self.ring_2.ring_radius - 5.0)) # 2. Generating the instances # self.ring1 refers to the layout view of ring1. insts += i3.SRef(name="ring_1", reference=self.ring_1, transformation=t1) # self.ring1 refers to the layout view of ring1. insts += i3.SRef(name="ring_2", reference=self.ring_2, transformation=t2) return insts
- Here we first calculate the transformations
t1: is a translation which translates the entire ring along the vector
(0.0, self.ring_1.ring_radius + 2.0)
t2: is a sum of two transformations: First our ring is mirrored along the x-axis and then translated along
(0.0, -self.ring_2.ring_radius - 5.0)
- Here we use
SRefto create the instance and add it to the list of instances for this view. The layout view knows it should add all instances to the actual layout. Note that, as explained in the previous section, in this context,
self.ring_1points to the layout view of the
Give a sensible name to each instance. This makes it a lot easier later on when using information contained in those references.
Instantiating a hierarchical cell¶
Here we show how to instantiate a PCell that contains child cells.
from technologies import silicon_photonics # load the file with our RingResonator component from ring import RingResonator from two_rings import TwoRings, TwoRingsWithDefaults # 1. Creating the layouts of the two rings my_ring_1 = RingResonator() my_ring_1.Layout(ring_radius=5.0) my_ring_2 = RingResonator() my_ring_2.Layout(ring_radius=6.0) # 2. Creating the layout view of the TwoRings class my_two_rings = TwoRings(name="mytworings", ring_1=my_ring_1, ring_2=my_ring_2) layout = my_two_rings.Layout() # 3. Writing to GDSII and visualization layout.write_gdsii("tworings.gds") layout.visualize()
my_ring_2, two instances of RingResonator. For both instances we choose different layout parameters by instantiating their respective layout views.
The layout view that you instantiate here is the one that will be used inside TwoRings. If you do not explicitly instantiate the layout view, the default values for each property of the layout will be used.
We instantiate the TwoRings class passing the
We write the layout to GDSII and visualize the result.
In the last example, we created all the necessary childcells and explicitly passed them to the
hierarchical cell as a property. However, there are many use cases where we want to assign
default values to our childcells so that you don’t have to create them a run time. This is done through the use of
_default_xxx methods as illustrated in the following code.
class TwoRingsWithDefaults(i3.PCell): ring_1 = i3.ChildCellProperty() ring_2 = i3.ChildCellProperty() # Here, we provide a default value for ring_1 def _default_ring_1(self): ring_1 = RingResonator(name=self.name+"_ring1") return ring_1 # Here, we provide a default value for ring_2 def _default_ring_2(self): ring_2 = RingResonator(name=self.name+"_ring2") return ring_2 # Layout view class Layout(i3.LayoutView): # Here, we provide a default value for ring_1 def _default_ring_1(self): ring_1 = self.cell.ring_1.get_default_view(i3.LayoutView) # Retrieve the layout view of ring_1 ring_1.set(ring_radius=5.0) # Set the ring radius to 5.0 return ring_1 # Here, we provide a default value for ring_2 def _default_ring_2(self): ring_2 = self.cell.ring_2.get_default_view(i3.LayoutView) # Retrieve the layout view of ring_2 ring_2.set(ring_radius=4.0) # Set the ring radius to 4.0 return ring_2 def _generate_instances(self, insts): # ...
In the code above you can observe that we added two
default_xxx methods for each child cell. One at the the PCell level and one in the Layout view.
At the PCell level
_default_ring_1(self) creates the default value for the ChildCellProperty
ring_1, which is a cell of the
RingResonator() PCell type.
_default_ring_2(self) returns a cell of the
RingResonator() PCell type.
It is important to provide a unique name for these child cells, otherwise you
risk creating new child cells over and over again. A good practice is to use the name of the parent cell (
self.name) and adding a unique suffix, such as the name of the
property with an underscore (
At the layout level
_default_ring_1 sets the correct parameters on the Layout view of
ring_1. This happens in two steps:
ring_1 = self.cell.ring_1.get_default_view(i3.LayoutView): Retrieve the default Layout view of
ring_1(with its default properties).
ring_1.set(ring_radius=5.0): Set the
This approach for setting the parameters of the Layout view of the childcell is highly recommended (although no strictly required). If you keep this habit, it will be a lot easier later, when programming cells with multiple views.
You can now instantiate
TwoRingsWithDefaults without specifying any parameters. The values for the properties
automatically be calculated by
_default_ring_2 both at the PCell and the Layout view level.
# 4. Creating a PCell with ring using the default values my_two_rings_2 = TwoRingsWithDefaults(name="mytworings2") my_two_rings_2.Layout().visualize()
Of course you can also override the default values by explicitly setting the property.
# 5. Defaults can also be overridden. Here we override ring_2. my_two_rings_2.ring_2 = my_ring_2 my_two_rings_2.Layout().visualize()
_default_ring_2 methods at the PCell level and at the view level will not be executed. In that case, the overridden cell
ring_2 will be used
at the PCell level of
ring_2.get_default_view(i3.LayoutView) will be used in the layout View of
Caution setting properties of child cells of TwoRingsWithDefaults¶
If the child cells are automatically calculated by the parent cell, you should not modify properties on the child cell using the pattern below, since it may lead to undefined behaviour:
my_two_rings_2 = TwoRingsWithDefaults(name="mytworings2") lay = my_two_rings_2.Layout() lay.ring_1.ring_radius = 10.0 # This is not OK - undefined behaviour
The reason is that in this case, the parent cell is responsible of calculating its child cells. Since you are not the owner of that child cell, it is difficult to predict what would happen since the parent cell might have defined interdependencies.
If on the other hand you created the child cell yourself, it is possible to modify parameters:
ring = RingResonator(name="testring") ring_lay = ring.Layout(ring_radius=3.0) my_two_rings_2 = TwoRingsWithDefaults(name="mytworings2", ring_1=ring) ring_lay.ring_radius = 10.0 # this is OK
In this step we explained how we can create a hierarchical PCell using the ChildCellProperty and how to use
to calculate default values of properties.
Hierarchical PCells are an important concept to manage more complex PCells. It allows you split your PCells up into smaller and simpler subcomponents which are easier to understand.
Now that we understand how we can build hierarchical PCells, we’ll learn how we can connect our childcells together using waveguides.