3. Design Project Management: Working as a Team

3.1. Introduction

Combining the designs from within a design team or even across design teams is a non-trivial task. Whether a proper automation flow is in place determines if the final mask assembly process is efficient and reliable.

In this tutorial, we propose a design project organization method which facilitates the final mask merging. Together with the IPKISS design framework, and a version control-based design process, it guarantees maximal individual and collective productivity.

3.2. A Design Project Example

Let’s take a team design project as example. Contributions to this project come from four designers: Chiara, Jiejun, Pierre, and Ruping. Each of them has some test structure designs to be loaded onto the next tape-out run, and they’re sharing the same design block area (a 2.5 mm * 1.25 mm block). Some examples of the designs are as follows:

../../../_images/splitter_tree.png ../../../_images/spiral.png ../../../_images/zig_zag.png ../../../_images/splitter_tree2.png

Someone needs to collect and combine those designs before submitting the mask layout to the foundry for tape-out. Ruping volunteers to perform this task. She faces several challenges:

  • Collecting the designs from her colleagues presents a huge communication burden.
  • She lacks a tool to write the final GDS mask fast.
  • Maintaining the merged GDS mask is tedious and error-prone (e.g. with colleagues making last-minute design changes).

Luckily, the team has set up a good project structure from the beginning, before everyone started their own designs, and everyone follows the common practice to carry out designs using a version control system (e.g. Github).

The structure of the tapeout_202008_si_fab project looks like this:

├── chiara
├── jiejun
├── pierre
├── ruping
├── template
└── merge.py

Each designer has a design folder. The merge.py script takes the GDS files from each designer folder and merges them into a single mask layout, including the template folder which provides the die template being used as the frame.

3.2.1. The regenerate script

Before using the merge.py script, we need to have the GDS files ready from each of the designers. Ruping will be responsible for merging the designs, as she knows which version of the designs to use. It is recommended that the entire code to generate and merge the designs would be tracked by Version Control System (e.g. Git) to ensure that you have complete traceability and repeatability.

On top of that, it is good practise to have a one single script for the regeneration each design so that anybody in your organization would be able to replicate earlier work.

For example, in chiara folder, the IPKISS design scripts are located in splitter_height_sweep and splitter_length_sweep as well regenerate.py script for each design.

class SplitterRouted(CircuitCell):
    """This routes a 2-stage splitter made of 1 by 2 splitter elements.
    The input and output of the splitters are routed to fiber grating couplers."""
    splitter = i3.ChildCellProperty(doc="Splitter used")
    spacing_x = i3.PositiveNumberProperty(default=80.0, doc="Horizontal spacing between components")
    splitter_length = i3.PositiveNumberProperty(default=4.0, doc="Length of the MMI splitter")
    splitter_height = i3.PositiveNumberProperty(default=10.0, doc="Height of the MMI splitter")
    trace_template = i3.TraceTemplateProperty(doc="Trace template for connectors")
    grating_coupler = i3.ChildCellProperty(doc="Grating coupler used")
    grating_spacing = i3.PositiveNumberProperty(default=60.0, doc="Vertical spacing between output gratings")

    def _default_trace_template(self):
        tt = pdk.SiWireWaveguideTemplate()
        return tt

from si_fab import all as pdk
from ipkiss3 import all as i3
from splitter_routed import SplitterHeightSweep
import numpy
import os

output_folder = os.path.join(os.pardir, "gds_to_merge")
design_name = "splitter_tree_height_sweep"  # Name of your design

print("Regenerate {}".format(design_name))

# Instantiate the design and export it to gds
cell = SplitterHeightSweep(name=design_name.upper())
cell_lv = cell.Layout()
cell_lv.write_gdsii(os.path.join(output_folder, design_name + ".gds"))

# Extract file info and export it
si = cell_lv.size_info()
size_info_file = os.path.join(output_folder, design_name + "_si.txt")
numpy.savetxt(size_info_file, si.bounding_box.points)


In this instance Ruping will regenerate all the designs for this run using the regenerate.py scripts provided by every designer. Alternatively, the designers could regenerate their designs and push the final version of the GDSII to the VCS.

The generated files include the .gds files and the .txt files which store design block size information for use when arranging the design blocks relative to each other. In the regenerate.py script, after instantiating the IPKISS design cell, the GDSII format files are written, and its size information is written to a text file:

After executing regenerate.py, the folder structure now look like this:

├── chiara
    ├── gds_to_merge
    │   ├── splitter_tree_height_sweep.gds
    │   ├── splitter_tree_height_sweep_si.txt
    │   ├── splitter_tree_length_sweep.gds
    │   └── splitter_tree_length_sweep_si.txt
    ├── splitter_height_sweep
    │   ├── regenerate.py
    │   └── splitter_routed.py
    └── splitter_length_sweep
        ├── regenerate.py
        └── splitter_routed.py

Designers are free to change their designs. The latest designs are available to Ruping via the version control system. All needs to be ensured is that the new GDS files are regenerated at the final mask merging.

3.2.2. The merge script

To merge we use the merge.py script. We start by appending the list designs, which contains the designs to be merged. The list element needs to be dictionary type, with definitions of:

  • source_path: the path to the GDS file of the design to be merged.
  • prefix: the prefix name to be assigned to the GDS cell.
  • transformations: the list of transformations of the cell. Each list element of transformation is written as a list of translation in x direciton, translation in y direction, rotation, and mirroring.

The following first code block in merge.py add the die template to the designs:

# Add the template frame
si_info_template = i3.size_info_from_numpyarray(numpy.loadtxt(
    open(os.path.join(base_dir, "template", "template_si.txt"))))
designs.append({"source_path": os.path.join(base_dir, "template", "template.gds"),
                "prefix": "TEMPLATE",
                "transformations": [[0.0, 0.0, 0.0, 0.0]]})

We also notice si_info_template from the code, which stores the cell layout size information such as the cell bounding box west-most/east-most x coordinate value, and north-most/south-most y coordinate value. It’s loaded from a .txt file which is written with cell layout size information (we will mention later with the regenerate.py script in each of the designer folder).

With the same principle, we add the designs from each of the designers, for example, Chiara’s splitter designs:

# User 1: Chiara
designer_name = "chiara"
user_path = os.path.join(base_dir, designer_name, "gds_to_merge")
gds_files = [fl for fl in os.listdir(user_path) if fl.endswith('.gds')]
si_info_chiara = [i3.size_info_from_numpyarray(numpy.loadtxt(
    open(os.path.join(user_path, os.path.splitext(fl)[0]) + "_si.txt"))) for fl in gds_files]
spacing = 50  # Desired spacing between designs
margin = 50  # Margin to the block edge

# Design 1
designs.append({"source_path": os.path.join(user_path, gds_files[0]),
                "prefix": designer_name.upper()+"_DESIGN_1",
                "transformations": [[0.0 + margin, 0.0 - si_info_chiara[0].south + margin, 0.0, 0.0]]

# Design 2
designs.append({"source_path": os.path.join(user_path, gds_files[1]),
                "prefix": designer_name.upper()+"_DESIGN_2",
                "transformations": [[0.0 + margin,
                                     si_info_chiara[0].height - si_info_chiara[1].south + spacing + margin,
                                     0.0, 0.0]]

In the end, we will update the final merged GDS with the appended designs list:

                   unprefixed_cells=["FC_TE_1550", "TEMPLATE_2500_1250", "MMI_1X2_OPTIMIZED"],


Note that you will need the installation of KLayout in order to run the merge.py script, as it uses KLayout as the interpreter to run the Ruby script which we created to perform fast mask merging.

3.2.3. Running the merging script

Ruping runs the merge script to update the merged design, and moves the blocks around if necessary.

In this process, she is confident that she always uses the latest designs from each designer, and the updated merged mask can be written in a matter of a few seconds.

We obtain our final merged mask:


3.3. Test your knowledge

Now, it’s up to you to take a try and add your own design project next to Chiara, Jiejun, Pierre, and Ruping’s ones. For example, you may need to organize the project following the same structure as we mentioned:

├── chiara
├── jiejun
├── pierre
├── ruping
├── template
├── <my_tapeout>
│    ├── gds_to_merge
│    │   ...
│    ├── <my_design_1>
│    │   ├── regenerate.py
│    │   └── <my_design_1>.py
│   ...
└── merge.py

Aside from adding your own IPKISS code to the project, remind that you will also need to adapt the regenerate.py and the merge.py file.