Porting from Ipkiss 2.4 to Ipkiss 3

Ipkiss received a major overhaul from version 2.4 to version 3.0. As a result, one can now not only describe the layout of components and circuits, but also their netlist, circuit models and any custom ‘views’. These views can also be coupled, so that the circuit model can be matched to the layout of a component, which will help to reduce design errors. With Ipkiss 3.0, a larger part of the design flow can be managed from one tool.

With some minor changes, it is possible to run Ipkiss 2.4 code on a Ipkiss 3.0 installation, see Running Ipkiss 2.4 code in Ipkiss 3. This guide, on the other hand, is about how to port your Ipkiss 2.4 code to native Ipkiss 3.0 code so your components can start taking advantage of the new Ipkiss 3.0 features.

Importing Ipkiss

Starting from Ipkiss 3, we advise to not use the from ipkiss.all import * pattern anymore. Rather, import the Ipkiss package under the alias i3 and explicitly use i3 with all Ipkiss classes, functions and global variables:

# Ipkiss 3.0
import ipkiss3.all as i3

my_rectangle = i3.ShapeRectangle(box_size=(10.0, 3.0))

During porting, please change all from ipkiss.all import * statements to import ipkiss3.all as i3 and prefix all Ipkiss primitives with i3..

Property defaults: define__default_

In Ipkiss 2.4, default value calculations had to be written in a define_ method:

# Ipkiss 2.4
from ipkiss.all import *

class MyClassWithProperties(StrongPropertyInitializer):
    a = PositiveNumberProperty(default=1.0)
    b = PositiveNumberProperty()

    def define_b(self):
        return self.a * 2.0

In Ipkiss 3.0, default values are written in a _default_ method:

 # Ipkiss 3.0
 import ipkiss3.all as i3

 class MyClassWithProperties(i3.StrongPropertyInitializer):
     a = i3.PositiveNumberProperty(default=1.0)
     b = i3.PositiveNumberProperty()

     def _default_b(self):
         return self.a * 2.0

During porting, rename all define_xxx methods to _default_xxx.

Structure (2.4) → PCell/LayoutView (3.0)

In Ipkiss 2.4, a parametric cell (pcell), the unit of re-use in a design, was defined by subclassing from Structure. This Structure defined the parameters (properties) of the PCell, and its layout information such as layout elements and ports:

# Ipkiss 2.4
from ipkiss.all import *

class MyStructure(Structure):
    width = PositiveNumberProperty(default=5.0, doc="width of the rectangle in X")

    def define_elements(self, elems):
        elems += Rectangle(layer=Layer(0), box_size=(self.width, 1.0))
        return elems

    def define_ports(self, ports):
        ports += OpticalPort(position=(-0.5 * self.width, 0.0), angle=180.0)
        ports += OpticalPort(position=(0.5 * self.width, 0.0), angle=0.0)
        return ports

In Ipkiss 3, a PCell is defined by subclassing from i3.PCell. The layout data is defined in a Layout View. This is done by specifying an ‘inner class’ (side inside a class) of the PCell, subclassed from i3.LayoutView. Such an inner class defines a specific View on the component.

# Ipkiss 3.0
import ipkiss3.all as i3

class MyPCell(i3.PCell):

    class Layout(i3.LayoutView):

        width = i3.PositiveNumberProperty(default=5.0)

        def _generate_elements(self, elems):
            elems += i3.Rectangle(layer=i3.Layer(0), box_size=(self.width, 1.0))
            return elems

        def _generate_ports(self, ports):
            ports += i3.OpticalPort(position=(-0.5*self.width, 0.0), angle=180.0, name="in")
            ports += i3.OpticalPort(position=(0.5*self.width, 0.0), angle=0.0, name="out")
            return ports

The reason for using this inner class construct is that it has become much easier to define new views, such as a compact model and netlist, and even specify different views of the same type (e.g. different compact models) on one component. View “data” such as the layout elements and ports are separated from the actual properties by using _generate_xxx instead of _default_xxx.

In order to port Ipkiss 2.4 Structures to Ipkiss 3 PCells,

  • Change Structure to i3.PCell

  • Move define_elements, define_ports and other Layout view information into an inner class derived from i3.LayoutView

  • Rename define_elements to _generate_elements and define_ports to _generate_ports

  • Specify names for ports

In the 2.4 compatibility layer, a Structure is effectively the Layout View of a 3.0 PCell.

Port names

As noted in the example above, make sure to always specify a name when defining a port. This will enable matching between physical ports and logical ports (terms) in the netlist. If no names are supplied to a part, they will automatically be named port1, port2,…

Using the Layout view

In Ipkiss 2.4, all operations on the layout were on the structure, e.g.

# Ipkiss 2.4
S = MyStructure(name="mystruct", width=4.0)
print(S.elements)
print(S.ports)
S.write_gdsii("mystruct.gds")

In Ipkiss 3.0, one has to explicitly set the state of the Layout view, and use the layout view for layout operations:

# Ipkiss 3.0
mycell = MyPCell(name="mycell")
mycell_lay = mycell.Layout(width=4.0)
print(mycell_lay.elements)
print(mycell_lay.ports)
mycell_lay.write_gdsii("mycell.gds")

Warning: if you call mycell.Layout(), the layout view will be reset to its default values (in this case the width property would be reset from 4.0 to its default value). If you want to just ask for the Layout view as is, use mycell.get_default_view(i3.LayoutView). Note that Structure/Cell names have to be unique, both in Ipkiss 3.0 as 2.4.

mycell_default_lay = mycell.get_default_view(i3.LayoutView) # gets the default layout view, in this case Layout but could be overridden by the user
mycell_lay = mycell.Layout.view # gets the view Layout
mycell_lay = mycell.Layout() # RESETS the view Layout to its default values

Netlist View

When using IPKISS 2.4 Structure classes in Ipkiss 3, they will automatically get a simple Netlist view, describing an atomic component with a list of “terms” that correspond to the “ports” in the Layout view.

When porting a Structure to a PCell, you should manually add a Netlist view, as an inner class.

The following IPKISS 2.4 code:

class MyStructure(Structure):
    width = PositiveNumberProperty(default=5.0, doc="width of the rectangle in X")

    def define_elements(self, elems):
        elems += Rectangle(layer=Layer(0), box_size=(self.width, 1.0))
        return elems

    def define_ports(self, ports):
        ports += OpticalPort(position=(-0.5 * self.width, 0.0), angle=180.0)
        ports += OpticalPort(position=(0.5 * self.width, 0.0), angle=0.0)
        return ports

Would need to add the following

class MyPCell(i3.PCell):

    class Layout(i3.LayoutView):

        ... # properties and elements from the previous example

        def _generate_ports(self, ports):
            ports += i3.OpticalPort(position=(-0.5*self.width, 0.0), angle=180.0, name="in")
            ports += i3.OpticalPort(position=(0.5*self.width, 0.0), angle=0.0, name="out")
            return ports

    class Netlist(i3.NetlistView):
        def _generate_terms(self, terms):
            terms += i3.OpticalTerm(name="in")
            terms += i3.OpticalTerm(name="out")
            return terms

This defines an ‘atomic’ netlist view, i.e. a component that has no circuit hierarchy. the terms correspond to the ports in the Layout. For quick porting, this can also be automatically generated from the layout:

class MyPCell(i3.PCell):

    class Layout(i3.LayoutView):

        ... # properties and elements from the previous example

        def _generate_ports(self, ports):
            ports += i3.OpticalPort(position=(-0.5*self.width, 0.0), angle=180.0, name="in")
            ports += i3.OpticalPort(position=(0.5*self.width, 0.0), angle=0.0, name="out")
            return ports

    class Netlist(i3.NetlistFromLayout):
        pass

For creating more complex netlists, including hierarchy and internal connectivity, consult the Netlist.

Hierarchy

In Ipkiss 2.4, Structures were hierarchically composed by using reference elements (SRef, ARef) and possibly StructureProperty:

# Ipkiss 2.4
from ipkiss.all import *

class MyChild(Structure):

    def define_elements(self, elems):
        elems += Rectangle(layer=Layer(0), box_size=(1.0, 1.0))
        return elems


class MyParent(Structure):
    child = StructureProperty()

    def define_elements(self, elems):
        elems += SRef(reference=self.child, position=(-10.0, 0.0))
        elems += SRef(reference=self.child, position=(+10.0, 0.0))
        return elems

In Ipkiss 3, the syntax is somewhat different, in order to support child cells with several views, and have a more clear hierarchical definition.

# Ipkiss 3.0
import ipkiss3.all as i3

class MyChild(i3.PCell):
    class Layout(i3.LayoutView):
        def _generate_elements(self, elems):
            elems += i3.Rectangle(layer=i3.Layer(0), box_size=(1.0, 1.0))
            return elems

class MyParent(i3.PCell):
    child = i3.ChildCellProperty()

    class Layout(i3.LayoutView):
        def _generate_instances(self, insts):
            insts += i3.SRef(reference=self.child, position=(-10.0, 0.0), name="c1")
            insts += i3.SRef(reference=self.child, position=(+10.0, 0.0), name="c2")
            return insts

Child instances of a view are specified in a _generate_instances method.

In Ipkiss3, the instances and elements are to be separated:

  • instances are references to subcomponents. By defining them in a separate method _default_instances, Ipkiss can walk through the layout hierarchy of a PCell without needing to calculate all the detailed layout elements.

  • elements are the actual layout data elements which define the layout of a device: polygons, paths, text labels.

Since MyParent has no further elements except references to its child cell, only the _generate_instances method is needed in the above example. In general, one can have both _generate_instances to add reference elements, as _generate_elements to add other elements (geometric data, text labels).

Make sure to always specify a name for an instance of a child cell. This will be used to match the layout and netlist views.

Default children

Already in Ipkiss 2.4, a Structure could specify the automatically generated default for its own child cells. We continue from the example above:

# Ipkiss 2.4
class MyParent(Structure):
    child = StructureProperty()

    def define_child(self):
        return MyChild(name="{}_child".format(self.name)) # make sure to always specify a name using this pattern

    def define_elements(self, elems):
        elems += SRef(reference=self.child, position=(-10.0, 0.0))
        elems += SRef(reference=self.child, position=(+10.0, 0.0))
        return elems

In that case, when no child is explicitly specified by the user when initializing a MyParent object, the default will be used:

parent = MyParent(name="myparent")
print(parent.child.name) # prints "myparent_child", the name of the default child as specified above

The same can be done within Ipkiss 3:

# Ipkiss 3.0
class MyParent(i3.PCell):
    child = i3.ChildCellProperty()

    def _default_child(self):
        return MyChild(name="{}_child".format(self.name))

    class Layout(i3.LayoutView):
        def _generate_instances(self, insts):
            insts += i3.SRef(reference=self.child, position=(-10.0, 0.0), name="c1")
            insts += i3.SRef(reference=self.child, position=(+10.0, 0.0), name="c2")
            return insts

The SRef in the Layout view will automatically know that it has to look for the layout view of child. Even more, when you ask for self.child from within the view, it will retrieve the corresponding view of the child directly, not the child cell. Ipkiss 3 implements some magic behind the scenes in order to accomplish this.

Make sure to always specify a name for the child according to the pattern shown above, in order to obtain an optimal reuse of cells.

Passing parameters from parent to default child

An often used pattern is for a parent cell to set the properties of its automatically generated child cells. In Ipkiss 2.4 an example could be:

# Ipkiss 2.4
from ipkiss.all import *

class MyChild(Structure):

    square_width = PositiveNumberProperty(default=1.0)

    def define_elements(self, elems):
        elems += Rectangle(layer=Layer(0), box_size=(self.square_width, self.square_width))
        return elems

class MyParent(Structure):
    child = StructureProperty()

    square_width = PositiveNumberProperty(default=1.0)

    def define_child(self):
        return MyChild(name="{}_child".format(self.name),
                       square_width=self.square_width)

    def define_elements(self, elems):
        elems += SRef(reference=self.child, position=(-10.0, 0.0))
        elems += SRef(reference=self.child, position=(+10.0, 0.0))
        return elems

In Ipkiss 3.0, the pattern becomes somewhat more complex since properties can be view-specific. The properties of the view of a default child are set in the corresponding view of the parent, as follows:

# Ipkiss 3.0
import ipkiss3.all as i3

class MyChild(i3.PCell):

    class Layout(i3.LayoutView):
        square_width = i3.PositiveNumberProperty(default=1.0)

        def _generate_elements(self, elems):
            elems += i3.Rectangle(layer=i3.Layer(0), box_size=(self.square_width, self.square_width))
            return elems

class MyParent(i3.PCell):
    child = i3.ChildCellProperty()

    def _default_child(self):
        return MyChild(name="{}_child".format(self.name))

    class Layout(i3.LayoutView):
        square_width = i3.PositiveNumberProperty(default=1.0)

        def _default_child(self):
            # fetch default layout view of the child, and set properties
            lv = self.cell.child.get_default_view(self)
            lv.set(square_width=self.square_width)
            return lv

        def _generate_instances(self, insts):
            insts += i3.SRef(reference=self.child, position=(-10.0, 0.0), name="c1")
            insts += i3.SRef(reference=self.child, position=(+10.0, 0.0), name="c2")
            return insts

Waveguide Definition (2.4) → Waveguide Template (3.0)

The concept of a waveguide definition still exists in Ipkiss 3, but has been renamed to WaveguideTemplate. This being the cross-section of the waveguide which can be extruded along the path of a waveguide. WaveguideTemplate is a special case of TraceTemplate. As a result, templates can be defined not only for optical waveguides but also electrical wires, fluidic channels, and so forth.

The concept of windows to build up a waveguide definition (WindowWaveguideDefinition) exists in Ipkiss 3.0 as well, but has been renamed to TraceWindow.

Custom window waveguide definitions (2.4) -> window trace templates (3.0)

In 2.4, a custom waveguide definition based on windows could be built like:

# Ipkiss 2.4
from ipkiss.all import *
from ipkiss.plugins.photonics.wg.window import WindowWaveguideDefinition
from ipkiss.path_definition.window import PathWindow

class MyWgDefinition(WindowWaveguideDefinition):
    width1 = PositiveNumberProperty(default=1.0)
    width2 = PositiveNumberProperty(default=1.2)

    def define_windows(self):
        windows = [PathWindow(layer=Layer(0),
                              start_offset=-0.5 * self.width1,
                              end_offset=+0.5 * self.width1),
                   PathWindow(layer=Layer(1),
                              start_offset=-0.5 * self.width2,
                              end_offset=+0.5 * self.width2)
                  ]
        return windows

In 3.0, this should become

# Ipkiss 3.0
import ipkiss3.all as i3

class MyWgTemplate(i3.WindowWaveguideTemplate):

    class Layout(i3.WindowWaveguideTemplate.Layout):

        width1 = i3.PositiveNumberProperty(default=1.0)
        width2 = i3.PositiveNumberProperty(default=1.2)

        def _default_windows(self):
            windows = [i3.PathTraceWindow(layer=i3.Layer(0),
                                          start_offset=-0.5 * self.width1,
                                          end_offset=+0.5 * self.width1),
                       i3.PathTraceWindow(layer=i3.Layer(1),
                                          start_offset=-0.5 * self.width2,
                                          end_offset=+0.5 * self.width2)
                      ]
            return windows

WgElDefinition

In Ipkiss 2.4, the most basic type of waveguide definition was WgElDefinition, which generated photonic waveguide elements. Subclassing from WgElDefinition frequently gave some issues in Ipkiss 2.4 however. In Ipkiss 3.0, the trace templates have been restructured. The photonics-specific trace templates are now housed in the picazzo3 library - there are various predefined templates, such as (strip)wires, rib and slot waveguides, which can be tailored by the user to a specific technology.

An important remark to be made about these photonic waveguide trace templates is that trench has been removed as a property and has been replaced by cladding. In Ipkiss 2.4, the basic waveguide definitions were tailored to silicon photonics in which a waveguide can be drawn as the waveguide core surrounded by a trench of a specific width along each side. The trench_width parameter specified the width of that trench (on either side). In Ipkiss 3, it was decided to map the basic trace templates to more general photonic concepts, and specify the width of the core (similar to 2.4) and the total width of the cladding in which the core is embedded. Much like for a single mode fiber, the core is specified as e.g. 10um diameter and the cladding as 125um diameter.

In Ipkiss 2.4, a simple wire waveguide definition was created as:

wgdef400nm = WgElDefinition(width=0.4, trench_width=3.0)

In Ipkiss 3.0, the corresponding code will be:

wgtmpl400nm = WireWaveguideTemplate(name="wgtmpl400nm")
wgtmpl400nm.Layout(core_width=0.4, cladding_width=0.4 + 2 * 3.0)

Waveguides: cells instead of layout elements

A second important change regarding waveguides is that a Trace Template now generates a cell representing the trace (waveguide), as opposed to just a group of layout elements in Ipkiss 2. This is more logical since a waveguide can be considered as a device on its own and has to be modelled as a device. A connection between two components can be hierarchically constructed as a waveguide with many subcomponents: straight waveguides, bends, tapers, …

Picazzo

The PCell library picazzo has been largely ported to Ipkiss 3.0 syntax. The components now have a netlist and Caphe model view in addition to the layout view that was already defined in Ipkiss 2.4. The Ipkiss 2.4 Picazzo library is still available unchanged, for compatibility reasons. The new library is in the picazzo3 package. The structure of the library has been updated and many classes have been rationalized and/or improved compared to the old Picazzo library. The Picazzo Library Reference is available for exploration.

Technology

Ipkiss comes with a baseline sample technology file for learning Ipkiss and as a basis for building your own technology files. In Ipkiss 2.4, various technologies were used. In Ipkiss 3.0, this has been reduced to two:

  • a baseline technology for designs based on generic Ipkiss primitives: default_ipkiss. This is automatically imported by import ipkiss3.all as i3 when no technology was imported before.

  • a silicon photonics baseline technology: silicon_photonics. This needs to be imported before anything else, whenever picazzo3 is loaded (and you did not load a technology file from a specific fab or your own).

The original technology si_photonics which was provided with Ipkiss 2.4 is still available. However, it can only be used in ‘backward compatibility’ mode, for running pure Ipkiss 2.4 scripts. When porting your Ipkiss 2.4 components to Ipkiss 3 components, you need also to port your technology files. It is best to base this on the silicon_photonics technology, rather than the si_photonics technology.

The important differences between these two technologies are

  • The technology folder has a more logical structure, grouping information of layers, display styles, design rules, etc in their own python files,

  • The technology now has an internal library containing default PCell classes and objects in TECH.PCELLS.LIB. The standard waveguide templates, fiber couplers, etc. are stored in this library.

  • Default waveguide templates are now stored in TECH.PCELLS.WG instead of TECH.WGDEF.

  • The technology now contains new subtrees for the default settings of blocks (TECH.BLOCKS), and if you want to use Picazzo 3, you should also define containers (TECH.CONTAINERS), and modulators (TECH.MODULATORS). You can just import the files from silicon_photonics or base_ipkiss.

  • The new technology does not contain a subtree WGDEF. If you want to use old Ipkiss 2.4 components that rely on this subtree, you should also do from technologies import compat24 immediately after importing your own technology.