2. Working as a team: Develop and distribute your component library

As your team iterates design cycles, you will develop components, new routing mechanisms and simulation recipes on top of foundry PDKs that you use for your tape-outs. It’s commonplace that the knowledge driving these developments is poorly maintained and scattered across different designers. The goal of this tutorial is to teach you simple practices that will allow you to have a well-maintained internal IPKISS library, allowing your team to:

  • Share components and design practices between designers
  • Enable your team to use integrated photonic design practices created by the experts
  • Enable electronic designers to use IPKISS in an EDA tool through OpenAccess (OA) libraries (Requires IPKISS.eda module)
  • Exploit the power of the new IPKISS 3.4, both in a Python-scripting environment and in an EDA tool

2.1. Introduction

In this tutorial you will learn how to:

  • set up an IPKISS library,
  • use that library in multiple designs,
  • export it to an OpenAccess library, and
  • use that OpenAccess library in IPKISS.eda.

Imagine a team of photonics designers. We’ll name them the P-Team. This team uses the SiFab PDK as a basis to design their components. Their goal is to build their own library, named the pteam_library_si_fab, consisting of their own custom components. By collaborating together on a shared library, the entire team is able to use and reuse everything in it.

2.2. Folder structure of the library

Even if the library is not a PDK (What’s a PDK?), it should be structured like a PDK. The folder structure should then look as follows:

├── building
│   └── build.py
└── ipkiss
    └── pteam_library_si_fab
        ├── components
        │   ├── mzi
        │   │   ├── doc
        │   │   │   └── example_mux2heated.py
        │   │   ├── pcell
        │   │   │   ├── __init__.py
        │   │   │   ├── lattice_utils.py
        │   │   │   ├── mux2.py
        │   │   │   └── mzi_lattice_filter.py
        │   │   ├── simulate
        │   │   │   ├── __init__.py
        │   │   │   └── simulate_mux2.py
        │   │   └── __init__.py
        │   │
        │   ├── splitter_tree
        │   │   ├── __init__.py
        │   │   └── cell.py
        │   │
        │   └── __init__.py
        ├── technology
        │   └── __init__.py
        ├── __init__.py
        └── all.py

You can find this library in luceda-academy/libraries.

The ‘ipkiss’ folder

The ipkiss folder contains all the IPKISS-related files. It contains the full IPKISS pteam_library_si_fab library with its components and technology. The former contains all the components based on the SiFab PDK, those being a simple splitter tree (SplitterTree) and a more complicated heated two-way multiplexer (Mux2). The latter refers to the technology of the SiFab PDK. Because pteam_library_si_fab is not a PDK, it does not have a technology of its own. This is the content of the __init__.py file in the technology folder:

Listing 2.1 luceda-academy/libraries/pteam_library_si_fab/ipkiss/pteam_library_si_fab/technology/__init__.py
# Note: this file may contain additional technology layers that are required for our small pcells library.
# Currently we need to specify this file and load TECH so the library builder knows how to build this library.

from si_fab import technology
TECH = technology.TECH

The ‘components’ folder

The splitter_tree folder under the components folder has two files: cell.py and __init__.py. The first contains the code of the splitter tree PCell:

Listing 2.2 luceda-academy/libraries/pteam_library_si_fab/ipkiss/pteam_library_si_fab/components/splitter_tree/cell.py
from si_fab import all as pdk
from ipkiss3 import all as i3
from circuit.all import CircuitCell, sbend, wide_manhattan, manhattan
import os
import numpy as np
import pylab as plt

class SplitterTree(CircuitCell):
    """This creates a splitter tree PCell, made of 1x2 splitter elements.

    Users can specify the number of tree levels as PCell parameter.
    splitter = i3.ChildCellProperty(doc="Splitter used")
    levels = i3.IntProperty(default=3, doc="Number of levels")
    spacing_x = i3.PositiveNumberProperty(
        doc="Spacing between the splitters in x-direction in the last level"
    spacing_y = i3.PositiveNumberProperty(default=50.0, doc="Spacing in y-direction")
    bend_radius = i3.PositiveNumberProperty()

    def _default_bend_radius(self):
        return 5.0

    def _default_splitter(self):
        return pdk.MMI1x2Optimized()

    def _default_child_cells(self):
        childs = dict()
        for level in range(self.levels):
            for sp in range(int(2 ** level)):
                childs["sp_{}_{}".format(level, sp)] = self.splitter

        return childs

    def _default_connectors(self):
        conn = []
        for level in range(1, self.levels):
            for sp in range(int(2 ** level)):
                if sp % 2 == 0:
                    in_port = "sp_{}_{}:out1".format(level - 1, int(sp / 2.0))
                    in_port = "sp_{}_{}:out2".format(level - 1, int(sp / 2.0))

                out_port = "sp_{}_{}:in1".format(level, sp)

                conn.append((in_port, out_port, manhattan, {"bend_radius": self.bend_radius}))

        return conn

    def _default_external_port_names(self):
        epn = dict()
        cnt = 1
        level = self.levels - 1
        for sp in range(int(2 ** level)):
            epn["sp_{}_{}:out1".format(level, sp)] = "out{}".format(cnt)
            cnt = cnt + 1
            epn["sp_{}_{}:out2".format(level, sp)] = "out{}".format(cnt)
            cnt = cnt + 1

        epn["sp_{}_{}:in1".format(0, 0)] = "in"
        return epn

    def _default_place_specs(self):
        specs = []
        for level in range(self.levels):
            for sp in range(int(2 ** level)):
                sp_y = self.spacing_y * 2 ** (self.levels - level - 1)
                        "sp_{}_{}".format(level, sp),
                        (level * self.spacing_x, - 0.5 * (2 ** level - 1) * sp_y + sp * sp_y)
        return specs

while the __init__.py file looks like this:

Listing 2.3 luceda-academy/libraries/pteam_library_si_fab/ipkiss/pteam_library_si_fab/components/splitter_tree/__init__.py
from .cell import SplitterTree

A more advanced tunable Mach Zehnder interferometer can be found in the mzi folder. This folder has a doc folder, containing a script example_mux2heated.py that visualizes the Mux2 and simulates it. Then there is the simulate folder, which contains the simulation recipe for the Mux2 in simulate_mux2.py and an empty __init__.py. The code defining the PCell itself can be found in the pcell folder. The last file in the mzi folder is an __init__.py file with the code: from .pcell.cell import Mux2Heated.

All the other __init__.py files in the folder structure should be empty.

The ‘all.py’ file

There is one file we haven’t discussed yet, the all.py file in the ipkiss/pteam_library_si_fab folder. In this file all the components that are part of the custom library pteam_library_si_fab are imported.

Listing 2.4 luceda-academy/libraries/pteam_library_si_fab/ipkiss/pteam_library_si_fab/all.py
from .components.mzi import Mux2Heated
from .components.splitter_tree import SplitterTree

__all__ = ['Mux2Heated', 'SplitterTree']

This makes it possible to import components from pteam_library_si_fab into other projects, using the import statement from pteam_library_si_fab.all import NameOfComponent.

Components that might be designed in the future, by any member of the P-Team, are added to the components folder and imported in the all.py file. This way it’s easy for the P-Team to keep track of each other’s designs and reuse them.

2.3. Using the library in multiple designs

It is now possible to use this pteam_library_si_fab like any other PDK in IPKISS.

2.3.1. IPKISS

To create a new design in IPKISS with the components that are in the pteam_library_si_fab you need to add this library to your PYTHONPATH, as you would do for any other PDK. In PyCharm, you can do this by right-clicking on the luceda-academy/libraries/pteam_library_si_fab/ipkiss folder and selecting Mark Directory as / Sources Root. Alternatively, you can run the following command from the terminal:

set PYTHONPATH="path-to-pteam_library_si_fab/ipkiss";%PYTHONPATH%

You can now use the import statement from pteam_library_si_fab.all import Mux2 in your new design (or any other cell that is in the pteam_library_si_fab).

For instance, in the Working as a team: Several contributions to one tape-out run tutorial, Pierre used the splitter tree from pteam_library_si_fab (SplitterTree) by importing it at the top of his design.

Listing 2.5 luceda-academy/training/topical_training/design_project_management/tapeout_202008_si_fab/pierre/splitter_tree/splitter_tree_routed_north.py
import si_fab.all as pdk
from pteam_library_si_fab.all import SplitterTree
from ipkiss3 import all as i3
from circuit.circuitcell import CircuitCell
from circuit.connector_functions import sbend, manhattan
from circuit.combine_connectors import combine_connectors
import numpy as np

class RouteSplitterTreeNorth(CircuitCell):
    """Routed Splitter tree with grating couplers placed north.
    dut = i3.ChildCellProperty(doc="splitter used")
    grating = i3.ChildCellProperty(doc="Splitter")
    grating_sep = i3.PositiveNumberProperty(default=50.0, doc="Separation between the gratings")
    bend_radius = i3.PositiveNumberProperty(default=20.0, doc="Bend Radius")
    wav_sep = i3.PositiveNumberProperty(default=5.0, doc="Separation between waveguides.")

    def _default_grating(self):
        return pdk.FC_TE_1550()

    def _default_bend_radius(self):
        return 20.0

    def _default_dut(self):
        return SplitterTree(name=self.name + "_DUT", levels=6)

2.3.2. IPKISS.eda

Building the OpenAccess library

To be able to use the pteam_library_si_fab in IPKISS.eda, it must first be exported to OpenAccess. This can be done with the build_library function of oatools.library.build. oatools is a submodule of IPKISS.eda that contains OpenAccess related tools. In the pteam_library_si_fab folder structure, next to the ipkiss folder, there is a building folder containing the build.py script. This script contains the following code:

Listing 2.6 luceda-academy/libraries/pteam_library_si_fab/building/build.py
"""Export the ipkiss components in pteam_library_si_fab to OpenAccess.
from oatools.library import build
import os
import argparse

def build_pteam_library_si_fab(output_dir=None, exported_si_fab=None):

    curdir = os.path.dirname(os.path.abspath(__file__))
    academy_dir = os.path.abspath(os.path.join(curdir, 3 * (os.path.pardir + os.path.sep)))  # 5 directory levels up

    pteam_library_src_path = os.path.join(curdir, os.pardir, 'ipkiss', 'pteam_library_si_fab')
    if output_dir is None:
        output_dir = os.path.join(curdir, os.pardir, 'builds', 'pteam_library_si_fab')
    if exported_si_fab is None:
        exported_si_fab = os.path.join(academy_dir, 'pdks', 'si_fab', 'openaccess')

        libdefs_path=os.path.join(exported_si_fab, 'lib.defs'),

    print("Done, exported library to {}".format(os.path.abspath(output_dir)))

parser = argparse.ArgumentParser(description="build_pteam_library_si_fab")
parser.add_argument('-o', '--output-dir', dest='output_dir')
parser.add_argument('-r', '--reference', dest='exported_si_fab')
args = parser.parse_args()
build_pteam_library_si_fab(args.output_dir, args.exported_si_fab)

This script/function only requires the following information:

  • the path of the original pteam_library_si_fab folder,
  • the destination path of the built pteam_library_si_fab, and
  • the path of the OpenAccess library of the SiFab PDK.

The destination path is in this case chosen to be pteam_library_si_fab/builds, right next to the ipkiss and building folder. The OpenAccess library is built by running this build script.

Creating a new design in L-Edit

Now that the OpenAccess library exists, how can it be used to create new designs?

Dennis is one of the members of the P-Team, he has a tape-out in November 2020. He wants to create a new design in IPKISS.eda based on SiFab and on the pteam_library_si_fab. Just like in the previous tutorial (Working as a team: Several contributions to one tape-out run), he makes a folder with his name in the tapeout_202011_si_fab folder. First, he must create a new design in L-Edit.

Creating a new design

Fig. 2.1 Creating a new design.

He gives the design the name filters_ledit and chooses the path to be the folder filter under tapeout_202011_si_fab/dennis. He makes sure the technology reference is set to ‘OpenAccess referenced libraries’ and clicks ‘OK’.

In the following window he selects the libraries he wants to base his new design on. He clicks on ‘Add’ and browses to the OpenAccess folder of the library and selects it. In this case he wants to use the pteam_library_si_fab library.

Adding pteam_library_si_fab

Fig. 2.2 Adding pteam_library_si_fab.

Once selected, a window should pop up that says Unknown technology library “si_fab”. Please browse to it. As pteam_library_si_fab is built on top of the SiFab PDK and uses its technology, the OpenAccess library of SiFab must be added as well.

Unknown technology

Fig. 2.3 Unknown technology.

Now both the P-Team library and the PDK are included:

included reference libs

Fig. 2.4 Referenced libraries.

The new design should now have been made and should be located in the filter folder This folder (tapeout_202011_si_fab/dennis/filter) should contain an OpenAccess library called filters_ledit and a lib.defs file.

Created design

Fig. 2.5 The created design and the content of the lib.defs file.

As you can see, the lib.defs file refers to the SiFab PDK, the built library pteam_library_si_fab and the new design library filters_ledit.

Dennis now restarts L-Edit and loads this lib.defs file by either dragging it to L-Edit or just by double clicking it. The libraries will be loaded automatically and he can continue working with them in L-Edit.

L-Edit-IPKISS integration.

Fig. 2.6 L-Edit-IPKISS integration.

You will also notice the message in the command line in L-Edit that states that the IPKISS-L-Edit integration has been loaded. If you don’t see this message, that means that the PDK has not been loaded properly. The autoload.tanner file within the PDK enables the integration between IPKISS and L-Edit and it should therefore be executed successfully for the integration to work.

In L-Edit, Dennis unlocks all the layers in the Layer Palette and creates a new cell in his filters_ledit library. He can now drag and drop any cell from the SiFab PDK and the pteam_library_si_fab into that new cell. He can now also draw all the routing that he needs to.

Design created with IPKISS.eda

Fig. 2.7 Design created with IPKISS.eda.

Now it’s your turn to experiment with your custom library and create amazing designs using IPKISS.eda!

2.4. Test your knowledge

Imagine you’re a member of the P-Team and your colleague has sent you a cluttered file of a heated ring resonator (heated_ring_resonator.py). It is now up to you to organize that file and add a proper component module that could be added to the to pteam_library_si_fab/components. Be mindful of the following:

  • Keep PCell, simulation recipes and examples in separate folders
  • Add your component to all.py
  • Rebuild the pteam_library_si_fab PDK to update the OA library
  • Try to import the newly added component in another design.
  • Create a new project in Ledit and start a new design using the ring (Requires IPKISS.eda module).

The component in question is a heated ring resonator:


This is the simulation result at 0 V and 1 V:



You can find the solution to this exercise in exercise/solution. Copy-paste the exercise/solution/ring folder into the components folder of pteam_library_si_fab. Make sure you adjust the all.py file to include the HeatedRing. See video starting at 10:00.