Integrated Optical Coherence Tomography (OCT) system

Introduction

Optical Coherent Tomography (OCT) is a well known technique that has enabled the volumetric inspection of technical materials and biological tissues. In recent years, many integrated OCT systems have been published, since the integration is already a realistic path towards miniaturization, robustness, low loss and mass production among many other advantages. In this regard, mature CMOS processes together with the possibility of fully integrated circuits comprising both passive components and detectors make silicon platforms very attractive for the production of OCT systems.

In this document we will explain how to create an OCT design with Alcyon’s PDK tools. To this end, we will be using the Layout-Creator module. Said module has been specifically created to facilitate the scripting process.

The PIC design presented is based on the design by Simon Schneider et al.[1] Said design is the proof of concept of the first fully integrated silicon photonic OCT system. The operational principle of the OCT system is based on the principle of swept-source OCT where two signals from the same light source are splitted, so that one of them interacts with the sample. When both beams interefere in a second splitter in the circuit, the position and strength of the scattering centers along the light path in the sample are retrieved by the Fourier analysis of the interference pattern.

In this document we present a similar architecture, fully passive and set up to be used with external sources and detectors. This document’s objective is to demonstrate how Alcyon’s PDK tools and portfolio can be used to design and configure photonic circuits suitable for fast prototyping at Applied Nanotools’ foundry.
The OCT PIC combines building blocks included in the NanoSOI PDK and Alcyon’s PDK enabling the PIC to be fully parametrizable by the user.

OCT_PIC

Figure 1. Schematic of the OCT system.

Performance impact of using SWG-based building blocks

The design presented in this document is an example of what can be done with Alcyon’s PDK tools. Aside from said tools, Alcyon’s PDK package provides a growing portfolio of high performance blocks, as well as more complex solutions. The use of high performance blocks has a significant impact on the performance of any circuit. For the current example, if we use SWG-based MMIs as splitters and SWG-based delay lines in the system, the device will operate at unprecedented bandwidth, with very low losses and high phase robustness. For instance, the experimental performance of a SWG-MMI2x2 optimized for the Applied Nanotools process shows phase errors below 2º over a broad bandwidth that is limited by the used characterization laser (1495-1640 nm). Furthermore, the robustness of the SWG-based delay lines can be appreciated in the 90º phase shifter. In said block, the experimental phase error is below 2º along the same laser range. SWG-based phase shifters and MMI’s have nominal bandwidths above 400 nm. The reader will find the performance of both devices in their respective Datasheets.

Scripting process:

For further information on the Layout-Creator Module’s principles, please refer to the Alcyon PDK utils documentation.

Creating complex layouts in Klayout (c) through python scripts can be an arduous task. To ease this task, the Layout-Creator module provides classes and functionalities that make this process much easier and more intuitive. With photonic integrated circuits in mind, the Layout-Creator module enables the creation of circuit layouts by connecting blocks from the available libraries. The Layout-Creator module follows an approach based on nodes and branches. A non-divergent concatenation of nodes forms a Branch. The LayoutBranch class enables the creation of branches as described in the documentation. Said branches allow the user to place several cells in the layout’s TOP CELL with minimal effort.

It must be noted that a plethora of strategies are available to build the same PIC. In this case, the OCT layout has been divided into four branches as described in figure 2.

OCT_system


Figure 2. Picture of the passive circuit with branches.

Importing modules

In order to use KLayout’s modules in a python script, start by importing ``pya` <https://www.klayout.de/doc-qt5/programming/python.html>`_. LayoutBranch, as explained above, is a class in the layout_creator module that concatenates a list of nodes following a net-list definition. Import it by using the following notation:

import pya
from AlcyonPDK_utils.python.layout_creator import LayoutBranch

Creating a new layout and top cell:

pya.MainWindow.instance().create_layout(100)
layout = pya.CellView.active().layout()
TOP = layout.create_cell("TOP")
pya.CellView.active().cell = layout.cell("TOP")

Importing cells from libraries:

Importing cells (static and parametric) from the available libraries is as easy as declaring a python dictionary. Said dictionary’s keys are the building blocks’ names given by the user, while the values are dictionaries indicating the actual cell name and the library to which it belongs.
In this case, cells are imported form the libraries:

  • NanoSOI

  • NanoSOI_PCells

  • Alcyon-Basics

  • Alcyon-Users

Note that even if a block is used several times, it should only be imported once.

dict_of_pcells = {"pipe":{"cell_name": "Pipe", "lib": "Alcyon-Basics"},
                  "mmi":{"cell_name": "Mmi2x2", "lib": "Alcyon-Users"},
                  "gc":{"cell_name": "GratingCoupler_TE_Air_12degrees", "lib": "NanoSOI"},
                  "spiral":{"cell_name": "Spiral", "lib": "NanoSOI_PCells"},
                  "ec":{"cell_name": "EdgeCoupler_Subwavelength", "lib": "NanoSOI"}
                  }

NOTE: the library Ebeam from SiePIC PDK can be imported by using this method.

Add variables as needed:

The Deep Trenches template used for the MPW runs in the NanoSOI PDK is 8780.0 μm long. In this example we set the horizontal length at 400 μm in order to better visualize the system. While the length of the edge couplers (len_ec) is 44.65 μm, per the foundry’s design rules they must extend 10 μm over the limit of the template. So 10 μm are sustracted from the coupler length. The SWG-based MMI2x2 (len_mmi) from the Alcyon-Users library is 26.125 μm long.

For visualization purposes, we are using a shorter chip length, (Lx = 400 μm)

Lx= 400
len_ec= 44.65-10
len_mmi=26.125

NOTE: the length of the building blocks and the positions of the ports can be obtained using additional attributes of LayoutBranch class that are not shown here for the sake of clarity.

Branch 1:

This branch connects an edge coupler (ec_1), a pipe (pipe_1), an MMI2x2 (mmi_1), a second pipe (pipe_2) and a second edge coupler (ec_2).
In order to create branch_1, the LayoutBranch class is used. LayoutBranch has the attributes layout, node_list and net_list. Layout is the active layout defined above and node_list and net_list are lists describing the components to be connected and the connection ports.

Once we know the list of components to be connected, they must be introduced in the node_list. The node_list is a list of dictionaries containing the information of each component or node to be used in this branch. In this case, the node is indicated as an element in dict_of_pcells, but another branch can be also a node. Several parameters can be adjusted when calling a node. For example, the user can set the block’s “name”, rotation angle (measured in degrees), scaling factor, a dictionary populated with the node’s parameters, etc. A few examples of this procedure are as follows:

Using a dictionary populated with parameters: Since some of the nodes are parametric cells, their parameters can be introduced by using a dictionary such as that of pipe_1_params or spiral_params, where the keys are the actual parameter names and the values are the parameter values in the correct format. If no parameters are given through this method, the default values will be applied.

Adding a rotation: The nodes can be rotated by adding the key rotation_deg to the node dictionary with a value in degrees. Although some of the pcells from the Alcyon’s libraries have the atribute “rotation”, the use of rotation_deg is prefereable.

The net_list is a list of dictionaries with two keys whose length matches the node_list length. Each entry in the net_list refers to a node in node_list, and holds said node’s I/O information. The key “in” indicates which port of a given node is considered as the input port and the key “out” indicates which port of the node is considered as the ouput port. The node will be connected in such a way that the “in ” port is connected to the “out” port of the preceding node and so on. The “in” port of the first node and the “out” port of the last node will be the input and output ports of the resulting branch, respectively. If the node_list does not match thenet_list length, an error will be diplayed. Note that all the components in node_list are present in the dict_of_pcells.

NOTE: the nodes in the node_list can be pcells described in the dict_of_pcells as well as (other) branches. When a branch in introduced as a node, the corresponding netlist element must be an empty dictionary, since the “in” and “out” ports of the branch were defined already.

# Branch 1 : ec_1 + pipe_1 + mmi_1 + pipe_2 + ec_2
pipe_1_params={'list_of_paths_length':(150,20,7),'list_of_paths_directions':('sr','d','sr'),'path_width':0.5,'bend_radius':2}
pipe_2_params={'list_of_paths_length':(7,20,Lx-150-7-2*len_ec-len_mmi),'list_of_paths_directions':('sr','u','sr'),'path_width':0.5,'bend_radius':2}
branch_1= LayoutBranch(layout=layout,
                          node_list=[{"node":dict_of_pcells["ec"], "name": "ec_1", "rotation_deg":0},
                                      {"node": dict_of_pcells["pipe"], "name": "pipe_1", "dbu_scaling": 1, "pcell_params_dict":pipe_1_params},
                                      {"node": dict_of_pcells["mmi"], "name": "mmi_1", "dbu_scaling": 1},
                                      {"node": dict_of_pcells["pipe"], "name": "pipe_2", "dbu_scaling": 1, "pcell_params_dict":pipe_2_params},
                                      {"node":dict_of_pcells["ec"], "name": "ec_2", "rotation_deg":180}
                                      ],
                          net_list=[{"in":0,"out":0},
                                    {"in": 0, "out": 1},
                                    {"in": 0, "out": 2},
                                    {"in": 0, "out": 1},
                                    {"in": 0, "out": 0}])

Branch 2:

This branch connects pipe_3, spiral, pipe_4, mmi_2, pipe_5 and gc_1. All these components are parametric except gc_1 and mmi_2 and parameter dictionaries can be used, such as pipe_3_params or spiral_params. The process is analogous to that of branch_1.

# Branch 2: pipe_3 + spiral + pipe_4 + mmi_2 + pipe_gc1 + gc_1
spiral_params={"Length":1000, "Radius": 10, "Width":0.5,"Spacing":3}
spiral_ports_distance=spiral_params["Spacing"]+spiral_params["Width"]
pipe_3_params={'origin':(0,0),'list_of_paths_length':(7,60,20,20),'list_of_paths_directions':('sr','d','sr','u'),'path_width':0.5,'bend_radius':2}
pipe_4_params={'origin':(0,0),'list_of_paths_length':(40,spiral_ports_distance+7+20,7),'list_of_paths_directions':('d','sl','d'),'path_width':0.5,'bend_radius':2}
pipe_gc1_params={'origin':(0,0),'list_of_paths_length':(7,50,50),'list_of_paths_directions':('d','sr','d'),'path_width':0.5,'bend_radius':1}

branch_2= LayoutBranch(layout=layout,
                        node_list=[{"node": dict_of_pcells["pipe"], "name": "pipe_3", "dbu_scaling": 1, "pcell_params_dict":pipe_3_params},
                                    {"node": dict_of_pcells["spiral"], "name": "espiral", "dbu_scaling": 1, "pcell_params_dict":spiral_params, "rotation_deg":90},
                                    {"node":dict_of_pcells["pipe"], "name": "pipe_4", "dbu_scaling": 1, "pcell_params_dict":pipe_4_params},
                                    {"node": dict_of_pcells["mmi"], "name": "mmi_2", "dbu_scaling": 1, "rotation_deg":270},
                                    {"node":dict_of_pcells["pipe"], "name": "pipe_gc1", "dbu_scaling": 1, "pcell_params_dict":pipe_gc1_params},
                                    {"node":dict_of_pcells["gc"], "name": "gc_1", "rotation_deg":180}
                                    ],
                        net_list=[{"in":0,"out":1},
                                  {"in": 0, "out": 1},
                                  {"in": 0, "out": 1},
                                  {"in": 0, "out": 2},
                                  {"in": 0, "out": 1},
                                  {"in": 0, "out": 0}])

Branch 3: pipe_5

Branch_3 holds a single node.

# Branch 3: pipe_5
pipe_5_params={'list_of_paths_length':(7,80,7+len_mmi-2,7),'list_of_paths_directions':('sl','d','sr','d'),'path_width':0.5,'bend_radius':2}
branch_3 = LayoutBranch(layout=layout,
                                node_list=[{"node":dict_of_pcells["pipe"], "name": "pipe_5", "pcell_params_dict":pipe_5_params}],
                                net_list=[{"in":0,"out":1}])

Branch 4: pipe_gc_1 + gc_1

Branch_4 connects pipe_gc_1 with gc_1. Note that the grating couplers only have one port, although the net_list dictionaries have two keys. In this case, you can either use a single key, or give the same value to both keys.

pipe_gc2_params={'origin':(0,0),'list_of_paths_length':(7,50,50),'list_of_paths_directions':('d','sl','d'),'path_width':0.5,'bend_radius':2}
branch_4 = LayoutBranch(layout=layout,
                        node_list=[{"node":dict_of_pcells["pipe"], "name": "pipe_gc2", "pcell_params_dict":pipe_gc2_params},
                                  {"node":dict_of_pcells["gc"], "name": "gc_2", "rotation_deg":180}
                            ],
                        net_list=[{"in":0,"out":1},
                                  {"in":0,"out":0} ])

Connecting branches:

Finally, the branches are inserted and connected using the place_nodes() method. The top cell must be given as well as a position. The trans_dict dictionary contains the information needed to place the branch. The “from” key indicates whether the “in” or “out” pin of the branch will be placed at the location indicated with “dst_loc”. “dst_loc” can be given as a point(pya.DPoint(-,-)) or it can be speficied as a pin of another branch. The LayoutBranch.branch_nodes_c_point[][] attribute is used to get the location of a specific pin of a branch. The first bracket indicates the index of the node in the branch, while the second bracket indicates the pin index of that node. Additional and very usefull attritubtes of LayoutBranch can be found in the documentation.

Note: The pins of the blocks provided by Alcyon are numbered from top to bottom and from left to right.

branch_1.place_nodes(top_cell=TOP)
branch_2.place_nodes(top_cell=TOP, trans_dict={"from":"input","dst_loc":branch_1.branch_nodes_c_points[2][3]})
branch_3.place_nodes(top_cell=TOP, trans_dict={"from":"input","dst_loc":branch_1.branch_nodes_c_points[2][1]})
branch_4.place_nodes(top_cell=TOP, trans_dict={"from":"input","dst_loc":branch_2.branch_nodes_c_points[3][3]})

Running the full code.

Copy and paste the following code in a python file and save it. Place the file into the pymacros folder or into a subfolder. Run the code from the Klayout’s Macro Development.

NOTE: it is recommended to write the code in a separate IDE, but it is necessary to run it with Klayout’s Macro Development tool.

OCT_system
import pya

from AlcyonPDK_utils.python.layout_creator import LayoutBranch


# Create the layout and TOP cell:
pya.MainWindow.instance().create_layout(100)
layout = pya.CellView.active().layout()
TOP = layout.create_cell("TOP")
pya.CellView.active().cell = layout.cell("TOP")

# List of pcells
dict_of_pcells = {"pipe":{"cell_name": "Pipe", "lib": "Alcyon-Basics"},
                  "mmi":{"cell_name": "Mmi2x2", "lib": "Alcyon-Users"},
                  "gc":{"cell_name": "GratingCoupler_TE_Air_12degrees", "lib": "NanoSOI"},
                  "spiral":{"cell_name": "Spiral", "lib": "NanoSOI_PCells"},
                  "ec":{"cell_name": "EdgeCoupler_Subwavelength", "lib": "NanoSOI"}
                  }



# Chip size
Lx= 400
Ly=200.0

len_ec= 44.65-10
len_mmi=26.125

# Branch 1 : ec_1 + pipe_1 + mmi_1 + pipe_2 + ec_2
pipe_1_params={'list_of_paths_length':(150,20,7),'list_of_paths_directions':('sr','d','sr'),'path_width':0.5,'bend_radius':2}
pipe_2_params={'list_of_paths_length':(7,20,Lx-150-7-2*len_ec-len_mmi),'list_of_paths_directions':('sr','u','sr'),'path_width':0.5,'bend_radius':2}
branch_1= LayoutBranch(layout=layout,
                          node_list=[{"node":dict_of_pcells["ec"], "name": "ec_1", "rotation_deg":0},
                                      {"node": dict_of_pcells["pipe"], "name": "pipe_1", "dbu_scaling": 1, "pcell_params_dict":pipe_1_params},
                                      {"node": dict_of_pcells["mmi"], "name": "mmi_1", "dbu_scaling": 1},
                                      {"node": dict_of_pcells["pipe"], "name": "pipe_2", "dbu_scaling": 1, "pcell_params_dict":pipe_2_params},
                                      {"node":dict_of_pcells["ec"], "name": "ec_2", "rotation_deg":180}
                                      ],
                          net_list=[{"in":0,"out":0},
                                    {"in": 0, "out": 1},
                                    {"in": 0, "out": 2},
                                    {"in": 0, "out": 1},
                                    {"in": 0, "out": 0}])


# Branch 2: pipe_3 + spiral + pipe_4 + mmi_2 + pipe_gc1 + gc_1
spiral_params={"Length":1000, "Radius": 10, "Width":0.5,"Spacing":3}
spiral_ports_distance=spiral_params["Spacing"]+spiral_params["Width"]
pipe_3_params={'origin':(0,0),'list_of_paths_length':(7,60,20,20),'list_of_paths_directions':('sr','d','sr','u'),'path_width':0.5,'bend_radius':2}
pipe_4_params={'origin':(0,0),'list_of_paths_length':(40,spiral_ports_distance+7+20,7),'list_of_paths_directions':('d','sl','d'),'path_width':0.5,'bend_radius':2}
pipe_gc1_params={'origin':(0,0),'list_of_paths_length':(7,50,50),'list_of_paths_directions':('d','sr','d'),'path_width':0.5,'bend_radius':1}

branch_2= LayoutBranch(layout=layout,
                        node_list=[{"node": dict_of_pcells["pipe"], "name": "pipe_3", "dbu_scaling": 1, "pcell_params_dict":pipe_3_params},
                                    {"node": dict_of_pcells["spiral"], "name": "espiral", "dbu_scaling": 1, "pcell_params_dict":spiral_params, "rotation_deg":90},
                                    {"node":dict_of_pcells["pipe"], "name": "pipe_4", "dbu_scaling": 1, "pcell_params_dict":pipe_4_params},
                                    {"node": dict_of_pcells["mmi"], "name": "mmi_2", "dbu_scaling": 1, "rotation_deg":270},
                                    {"node":dict_of_pcells["pipe"], "name": "pipe_gc1", "dbu_scaling": 1, "pcell_params_dict":pipe_gc1_params},
                                    {"node":dict_of_pcells["gc"], "name": "gc_1", "rotation_deg":180}
                                    ],
                        net_list=[{"in":0,"out":1},
                                  {"in": 0, "out": 1},
                                  {"in": 0, "out": 1},
                                  {"in": 0, "out": 2},
                                  {"in": 0, "out": 1},
                                  {"in": 0, "out": 0}])

# Branch 3: pipe_5
pipe_5_params={'list_of_paths_length':(7,80,7+len_mmi-2,7),'list_of_paths_directions':('sl','d','sr','d'),'path_width':0.5,'bend_radius':2}
branch_3 = LayoutBranch(layout=layout,
                                node_list=[{"node":dict_of_pcells["pipe"], "name": "pipe_5", "pcell_params_dict":pipe_5_params}],
                                net_list=[{"in":0,"out":1}])

# Branch 4: pipe_gc2+ gc_2
pipe_gc2_params={'origin':(0,0),'list_of_paths_length':(7,50,50),'list_of_paths_directions':('d','sl','d'),'path_width':0.5,'bend_radius':2}
branch_4 = LayoutBranch(layout=layout,
                        node_list=[{"node":dict_of_pcells["pipe"], "name": "pipe_gc2", "pcell_params_dict":pipe_gc2_params},
                                  {"node":dict_of_pcells["gc"], "name": "gc_2", "rotation_deg":180}
                            ],
                        net_list=[{"in":0,"out":1},
                                  {"in":0,"out":0} ])


# Insert and connect all branches
branch_1.place_nodes(top_cell=TOP)
branch_2.place_nodes(top_cell=TOP, trans_dict={"from":"input","dst_loc":branch_1.branch_nodes_c_points[2][3]})
branch_3.place_nodes(top_cell=TOP, trans_dict={"from":"input","dst_loc":branch_1.branch_nodes_c_points[2][1]})
branch_4.place_nodes(top_cell=TOP, trans_dict={"from":"input","dst_loc":branch_2.branch_nodes_c_points[3][3]})

2- Alcyon Design services

Contact Alcyon Photonics at antonio.dias@alcyonphotonics.com

3- References

[1] Schneider, S. et al. Optical coherence tomography system mass-producible on a silicon photonic chip. Optics Express 24, 1573 (2016).