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 using the Alcyon Utilities. To this end, we will be using the Layout Creator module. Such 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] This design is the proof of concept of the first fully integrated silicon photonic OCT system. The operation 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. The schematic of the OCT system is depicted in the image below. The objective is to demonstrate how Alcyon’s portfolio and tools can be used to design and configure photonic circuits.

OCT_PIC

The OCT PIC will combine building blocks included in the Alcyon Demo library and NanoSOI PDK as an example of integrating components from different PDKs.

NanoSOI PDK is the kit offered by the fast prototyping foundry Applied NanoTools. It can be downloaded from the foundry website. Components from custom libraries or other foundries’ PDKs could be also used with the Alcyon’s Toolkit, enabling the user to fully parametrize the PIC.

Scripting process

The Layout Creator module enables the creation of circuit layouts by connecting blocks from the available libraries. It follows an approach based on graphs. A graph is a collection of nodes, which eventually are photonic components, and connection edges, which define how the different components are connected.

NOTE: Detailed information about the Layout Creator module principles can be found within the Modules documentation.

The following code will serve to generate the layout illustrated below:

OCT_layout

1. Import modules

In order to use KLayout modules in a Python script, start by importing the pya Python module.

We should also import the LayoutGraph class from the layout_creator module, since it will be used to create the OCT layout graph.

import pya
from AlcyonPDK_utils.python.layout_creator import LayoutGraph

2. Create 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")

3. Import cells from libraries

Importing cells (static and parametric) from the available libraries is as easy as declaring a Python dictionary. The keys of such dictionary 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 Si

  • NanoSOI_PCells

  • Alcyon Demo library

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

# create a dictionary containing the cells which will be used
dict_of_pcells = {"pipe": {"cell_name": "Pipe", "lib": "Demo"},
                  "mmi": {"cell_name": f"Mmi2x2", "lib": "Demo"},
                  "gc": {"cell_name": "GratingCoupler_TE_Air_12degrees", "lib": "NanoSOI Si"},
                  "spiral": {"cell_name": "Spiral", "lib": "NanoSOI_PCells"},
                  "ec": {"cell_name": "EdgeCoupler_Subwavelength", "lib": "NanoSOI Si"},
                  "text": {"cell_name": "TEXT", "lib": "Basic"},
                  "sem": {"cell_name": "SEM_large", "lib": "NanoSOI Si"},
                  }

4. Add variables as needed

Although chip dimensions can be quite large, in this example we set the horizontal chip length (Lx) at 400 μm for a good visualization of the system. The length of the edge couplers (len_ec) from NanoSOI Si library is 44.65 μm. However, because of the foundry design rules, they must extend 10 μm over the limit of the template. For this reason, 10 μm are sustracted from the coupler length. The MMI2x2 length (len_mmi) from the Alcyon Demo library is 74 μm long.

Lx= 400
len_ec= 44.65-10
len_mmi=74

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

5. Graph definition

In order to demonstrate how to create graphs and how to interconnect them, two graphs for the OCT will be created.

Graph 1:

  • text_1: a text pcell from Basic library

  • ec_1: an edge coupler from NanoSOI Si library

  • pipe: a pipe pcell from Demo library

  • mmi_1: a Mmi2x2 pcell from Demo library

  • pipe_2: a pipe pcell from Demo library

  • ec_2: an edge coupler from NanoSOI Si library

  • pipe_3: a pipe pcell from Demo library

  • spiral: a spiral pcell from NanoSOI_PCells library

  • pipe_4: a pipe pcell from Demo library

  • mmi_3: a Mmi2x2 pcell from Demo library

  • pipe_6: a pipe pcell from Demo library

  • pipe_7: a pipe pcell from Demo library

  • gc2: a grating coupler from NanoSOI Si library

Graph 2:

  • pipe_5: a pipe pcell from Demo library

  • g1: a grating coupler from NanoSOI Si library

The nodes will be connected as follows:

Graph 1:

  • text_1: will not be connected to any other node. Instead, it will be placed at the location given by its unique contact point

  • ec_1: will not be connected to any other node. Instead, it will be placed at the location given by its contact point 0

  • pipe: connected to ec_1 contact point 0 from its contact point 0

  • mmi_1: connected to pipe contact point 1 from its contact point 0

  • pipe_2: connected to mmi_1 contact point 2 from its contact point 0

  • ec_2: connected to pipe_2 contact point 1 from its contact point 0

  • pipe_3: connected to mmi_1 contact point 3 from its contact point 0

  • spiral: connected to pipe_3 contact point 1 from its contact point 0

  • pipe_4: connected to spiral contact point 1 from its contact point 0

  • mmi_3: connected to pipe_4 contact point 1 from its contact point 0

  • pipe_6: connected to mmi_1 contact point 1 from its contact point 0

  • pipe_7: connected to mmi_3 contact point 3 from its contact point 0

  • gc2: connected to pipe_7 contact point 1 from its contact point 0

Graph 2:

  • pipe_5: connected to Graph1’s mmi_3 contact point 2 from its contact point 0

  • g1: connected to pipe_5 contact point 1 from its contact point 0


Additional geometric variations are also possible:

  • PCell parameters dictionary. Since some of the nodes are parametric cells, their parameters can be set by using dictionaries. Some examples can be pipe_1_params or spiral_params, where the keys are the parameters names and the values are the parameters values in the correct format. If no parameters are given through this method, the default values will be applied.

  • 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 Alcyon’s libraries have the attribute “rotation”, the use of rotation_deg is prefereable.

The following code summarizes it all:

# Create pcell parameters

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}
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}
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}
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}
txt_layer = pya.LayerInfo(10, 0)
layout.layer(txt_layer)
text_pcell_params = { "layer": txt_layer, "text":"OCT demo - Alcyon Photonics", "mag": 10 }

# Create the graph_def dictionary
graph_def_1 = {"text_1":
                   ({"node": dict_of_pcells["text"], "name": "text_1", "rotation_deg": 0, "dst_technology": "Alcyon",
                     "pcell_params_dict": text_pcell_params},
                    None),
               "ec_1":
                   ({"node": dict_of_pcells["ec"], "name": "ec_1", "rotation_deg": 0, "dst_technology": "Alcyon"},
                    None),
               "pipe":
                   ({"node": dict_of_pcells["pipe"], "name": "pipe_1", "dbu_scaling": 1,
                     "pcell_params_dict": pipe_1_params, "dst_technology": "Alcyon"},
                    {"to_node": "ec_1", "from_p": 0, "to_p": 0}),
               "mmi_1":
                   ({"node": dict_of_pcells["mmi"], "name": "mmi_1", "dbu_scaling": 1, "dst_technology": "Alcyon"},
                    {"to_node": "pipe", "from_p": 0, "to_p": 1}),
               "pipe_2":
                   ({"node": dict_of_pcells["pipe"], "name": "pipe_1", "dbu_scaling": 1,
                     "pcell_params_dict": pipe_2_params},
                    {"to_node": "mmi_1", "from_p": 0, "to_p": 2}),
               "ec_2":
                   ({"node": dict_of_pcells["ec"], "name": "ec_1", "rotation_deg": 180},
                    {"to_node": "pipe_2", "from_p": 0, "to_p": 1}),
               "pipe_3":
                   ({"node": dict_of_pcells["pipe"], "name": "pipe_3", "dbu_scaling": 1,
                     "pcell_params_dict": pipe_3_params},
                    {"to_node": "mmi_1", "from_p": 0, "to_p": 3}),
               "spiral":
                   ({"node": dict_of_pcells["spiral"], "name": "espiral", "dbu_scaling": 1,
                     "pcell_params_dict": spiral_params, "rotation_deg": 90},
                    {"to_node": "pipe_3", "from_p": 0, "to_p": 1}),
               "pipe_4":
                   ({"node": dict_of_pcells["pipe"], "name": "pipe_4", "dbu_scaling": 1,
                     "pcell_params_dict": pipe_4_params},
                    {"to_node": "spiral", "from_p": 0, "to_p": 1}),
               "mmi_3":
                   ({"node": dict_of_pcells["mmi"], "name": "mmi_2", "dbu_scaling": 1,
                     "rotation_deg": 270},
                    {"to_node": "pipe_4", "from_p": 0, "to_p": 1}),
               "pipe_6":
                   ({"node": dict_of_pcells["pipe"], "name": "pipe_5", "pcell_params_dict": pipe_5_params},
                    {"to_node": "mmi_1", "from_p": 0, "to_p": 1}),
               "pipe_7":
                   ({"node": dict_of_pcells["pipe"], "name": "pipe_gc2", "pcell_params_dict": pipe_gc2_params},
                    {"to_node": "mmi_3", "from_p": 0, "to_p": 3}),
               "gc2":
                   ({"node": dict_of_pcells["gc"], "name": "gc_2", "rotation_deg": 180},
                    {"to_node": "pipe_7", "from_p": 0, "to_p": 1
                     }),
               }

graph_def_2 = {
    "pipe_5":
        ({"node": dict_of_pcells["pipe"], "name": "pipe_gc1", "dbu_scaling": 1,
          "pcell_params_dict": pipe_gc1_params},
         None),
    "g1":
        ({"node": dict_of_pcells["gc"], "name": "gc_1", "rotation_deg": 180},
         {"to_node": "pipe_5", "from_p": 0, "to_p": 1})

}

6. Graph placing

Graph1 will be placed so that the contact point 0 of the mmi_1 node will be placed at the (0, 0) point.

graph1 = LayoutGraph(layout=layout, graph_def=graph_def_1)
graph1.place_nodes(top_cell=TOP, trans_dict={"src_node_name": "mmi_1", "c_point_index": 0, "dst_loc": pya.DPoint(0, 0)})

Graph2 will be placed so that the contact point 0 of the pipe_5 node will be placed at the contact point 2 of Graph1’s mmi1_3 node.

graph2 = LayoutGraph(layout=layout, graph_def=graph_def_2)
graph2.place_nodes(top_cell=TOP, trans_dict={"src_node_name": "pipe_5", "c_point_index": 0,
                                             "dst_loc": graph1.nodes_c_points["mmi_3"][2]})

7. Running the full code

Copy and paste the following code in a Python file and save it. Place the file into the KLayout pymacros folder or into a subfolder. Run the code from the Klayout Macro Development tool.

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_macro_development
import pya
from AlcyonPDK_utils.python.layout_creator import LayoutGraph

# 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")



# create a dictionary containing the cells which will be used
dict_of_pcells = {"pipe": {"cell_name": "Pipe", "lib": "Demo"},
                  "mmi": {"cell_name": f"Mmi2x2", "lib": "Demo"},
                  "gc": {"cell_name": "GratingCoupler_TE_Air_12degrees", "lib": "NanoSOI Si"},
                  "spiral": {"cell_name": "Spiral", "lib": "NanoSOI_PCells"},
                  "ec": {"cell_name": "EdgeCoupler_Subwavelength", "lib": "NanoSOI Si"},
                  "text": {"cell_name": "TEXT", "lib": "Basic"},
                  "sem": {"cell_name": "SEM_large", "lib": "NanoSOI Si"},
                  }

# Chip size
Lx = 400
len_ec = 44.65 - 10
len_mmi = 74

# Pcells parameters
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}
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}
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}
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}

txt_layer = pya.LayerInfo(10, 0)
layout.layer(txt_layer)
text_pcell_params = {"layer": txt_layer, "text": "OCT demo - Alcyon Photonics", "mag": 10}

graph_def_1 = {"text_1":
                   ({"node": dict_of_pcells["text"], "name": "text_1", "rotation_deg": 0, "dst_technology": "Alcyon",
                     "pcell_params_dict": text_pcell_params},
                    None),
               "ec_1":
                   ({"node": dict_of_pcells["ec"], "name": "ec_1", "rotation_deg": 0, "dst_technology": "Alcyon"},
                    None),
               "pipe":
                   ({"node": dict_of_pcells["pipe"], "name": "pipe_1", "dbu_scaling": 1,
                     "pcell_params_dict": pipe_1_params, "dst_technology": "Alcyon"},
                    {"to_node": "ec_1", "from_p": 0, "to_p": 0}),
               "mmi_1":
                   ({"node": dict_of_pcells["mmi"], "name": "mmi_1", "dbu_scaling": 1, "dst_technology": "Alcyon"},
                    {"to_node": "pipe", "from_p": 0, "to_p": 1}),
               "pipe_2":
                   ({"node": dict_of_pcells["pipe"], "name": "pipe_1", "dbu_scaling": 1,
                     "pcell_params_dict": pipe_2_params},
                    {"to_node": "mmi_1", "from_p": 0, "to_p": 2}),
               "ec_2":
                   ({"node": dict_of_pcells["ec"], "name": "ec_1", "rotation_deg": 180},
                    {"to_node": "pipe_2", "from_p": 0, "to_p": 1}),
               "pipe_3":
                   ({"node": dict_of_pcells["pipe"], "name": "pipe_3", "dbu_scaling": 1,
                     "pcell_params_dict": pipe_3_params},
                    {"to_node": "mmi_1", "from_p": 0, "to_p": 3}),
               "spiral":
                   ({"node": dict_of_pcells["spiral"], "name": "espiral", "dbu_scaling": 1,
                     "pcell_params_dict": spiral_params, "rotation_deg": 90},
                    {"to_node": "pipe_3", "from_p": 0, "to_p": 1}),
               "pipe_4":
                   ({"node": dict_of_pcells["pipe"], "name": "pipe_4", "dbu_scaling": 1,
                     "pcell_params_dict": pipe_4_params},
                    {"to_node": "spiral", "from_p": 0, "to_p": 1}),
               "mmi_3":
                   ({"node": dict_of_pcells["mmi"], "name": "mmi_2", "dbu_scaling": 1,
                     "rotation_deg": 270},
                    {"to_node": "pipe_4", "from_p": 0, "to_p": 1}),
               "pipe_6":
                   ({"node": dict_of_pcells["pipe"], "name": "pipe_5", "pcell_params_dict": pipe_5_params},
                    {"to_node": "mmi_1", "from_p": 0, "to_p": 1}),
               "pipe_7":
                   ({"node": dict_of_pcells["pipe"], "name": "pipe_gc2", "pcell_params_dict": pipe_gc2_params},
                    {"to_node": "mmi_3", "from_p": 0, "to_p": 3}),
               "gc2":
                   ({"node": dict_of_pcells["gc"], "name": "gc_2", "rotation_deg": 180},
                    {"to_node": "pipe_7", "from_p": 0, "to_p": 1
                     }),
               }

graph_def_2 = {
    "pipe_5":
        ({"node": dict_of_pcells["pipe"], "name": "pipe_gc1", "dbu_scaling": 1,
          "pcell_params_dict": pipe_gc1_params},
         None),
    "g1":
        ({"node": dict_of_pcells["gc"], "name": "gc_1", "rotation_deg": 180},
         {"to_node": "pipe_5", "from_p": 0, "to_p": 1})

}

graph1 = LayoutGraph(layout=layout, graph_def=graph_def_1)
graph1.place_nodes(top_cell=TOP, trans_dict={"src_node_name": "mmi_1", "c_point_index": 0, "dst_loc": pya.DPoint(0, 0)})

graph2 = LayoutGraph(layout=layout, graph_def=graph_def_2)
graph2.place_nodes(top_cell=TOP, trans_dict={"src_node_name": "pipe_5", "c_point_index": 0,
                                             "dst_loc": graph1.nodes_c_points["mmi_3"][2]})

References

  1. Schneider, S. et al. Optics Express 24, 1573, 2016.