Programmable Photonic Integrated Circuit

Programmable photonic circuits are an important discipline in the development of photonics, and this section focuses on how to build reconfigurable programmable photonic circuits in detail.

This section aims to build reconfigurable rectangular optical networks using rectangular networks as an example. The reconfigurable parameters include:

  1. Basic Module

  2. Network Size

  3. Design Parameters

Part I. Building a Rectangular Network

Importing library files:

from typing import Mapping, cast

from fnpcell import all as fp
from gpdk.technology import get_technology
from ring_modulator import RingModulator
from h_fanout import HFanout
from gpdk.technology.waveguide_factory import EulerBendFactory

Define class PICircuit:

class PICircuit(fp.PCell):
    def build(self):
        global spacing
        insts, elems, ports = super().build()
        TECH = get_technology()

        row_number = 100
        column_number = 100
        basic_comp = RingModulator()
        basic_comp_y = RingModulator(transform=fp.rotate(degrees=90))

        xy_spacing = []
        xx_spacing = []
        yy_spacing = []
        yx_spacing = []

        for i in range(row_number + 1):
            xy_spacing.append(spacing * i)

        for i in range(column_number):
            xx_spacing.append(spacing * (i + 0.5))

        for i in range(row_number):
            yy_spacing.append(spacing * (i + 0.5))

        for i in range(column_number + 1):
            yx_spacing.append(spacing * i)

        x_straight_top_right = []
        x_straight_btm_right = []
        x_straight_top_left = []
        x_straight_btm_left = []
        straight_index_min_x = 0
        straight_index_max_x = 0
        straight_index_min_xt = 0
        straight_index_max_xt = 0

        for i in range(row_number + 1):
            for j in range(column_number):
                x = xx_spacing[j]
                y = xy_spacing[i]
                x_dcoupler = basic_comp["op_0"].repositioned(at=(x, y)).owner
                insts += x_dcoupler, f"{0},{i},{j}"
                if (i == 0) and (j == 0):
                    get_position_y = basic_comp["op_1"].position[y]
                    x_straight_btm_left.append(pdk.Straight(name = "xbl_straight"+str(straight_index_min_x) ,length=20, waveguide_type=TECH.WG.FWG.C.WIRE)["op_0"].repositioned(at=(x-30-0.5*spacing, get_position_y)).owner)
                    insts += x_straight_btm_left[straight_index_min_x], f"{4},{0},{straight_index_min_x}"
                    straight_index_min_x = straight_index_min_x+1
                if (i == 0) and (j == (column_number-1)):
                    get_position_y = basic_comp["op_1"].position[y]
                    x_straight_btm_right.append(pdk.Straight(name = "xbr_straight"+str(straight_index_max_x) ,length=20, waveguide_type=TECH.WG.FWG.C.WIRE)["op_0"].repositioned(at=(x+60+0.5*spacing, get_position_y)).owner)
                    insts += x_straight_btm_right[straight_index_max_x], f"{4},{1},{straight_index_max_x}"
                    straight_index_max_x = straight_index_max_x+1
                if (i == row_number) and (j == 0):
                    x_straight_top_left.append(pdk.Straight(name = "xtl_straight"+str(straight_index_min_xt) ,length=20, waveguide_type=TECH.WG.FWG.C.WIRE)["op_0"].repositioned(at=(x-30-0.5*spacing, y)).owner)
                    insts += x_straight_top_left[straight_index_min_xt], f"{5},{0},{straight_index_min_xt}"
                    straight_index_min_xt = straight_index_min_xt+1
                if (i == row_number) and (j == (column_number-1)):
                    x_straight_top_right.append(pdk.Straight(name = "xtr_straight"+str(straight_index_max_xt) ,length=20, waveguide_type=TECH.WG.FWG.C.WIRE)["op_0"].repositioned(at=(x+60+0.5*spacing,y)).owner)
                    insts += x_straight_top_right[straight_index_max_xt], f"{5},{1},{straight_index_max_xt}"
                    straight_index_max_xt = straight_index_max_xt+1


        y_straight_top_right = []
        y_straight_btm_right = []
        y_straight_top_left = []
        y_straight_btm_left = []
        straight_index_min = 0
        straight_index_max = 0

        for i in range(row_number):
            for j in range(column_number + 1):
                x = yx_spacing[j]
                y = yy_spacing[i]
                y_dcoupler = basic_comp_y["op_0"].repositioned(at=(x, y)).owner
                insts += y_dcoupler, f"{1},{i},{j}"
                if (j == 0) :
                    y_straight_top_right.append(pdk.Straight(name = "tr_straight"+str(straight_index_min) ,length=20, waveguide_type=TECH.WG.FWG.C.WIRE)["op_0"].repositioned(at=(x-30, y+60)).owner)
                    y_straight_btm_right.append(pdk.Straight(name = "br_straight"+str(straight_index_min) ,length=20, waveguide_type=TECH.WG.FWG.C.WIRE)["op_0"].repositioned(at=(x-30, y-10)).owner)
                    insts += y_straight_top_right[straight_index_min], f"{2},{0},{straight_index_min}"
                    insts += y_straight_btm_right[straight_index_min], f"{2},{1},{straight_index_min}"
                    straight_index_min = straight_index_min+1
                if (j == (column_number)) :
                    y_straight_top_left.append(pdk.Straight(name = "tl_straight"+str(straight_index_max) ,length=10, waveguide_type=TECH.WG.FWG.C.WIRE)["op_0"].repositioned(at=(x+60, y+60)).owner)
                    y_straight_btm_left.append(pdk.Straight(name = "bl_straight"+str(straight_index_max) ,length=10, waveguide_type=TECH.WG.FWG.C.WIRE)["op_0"].repositioned(at=(x+60, y-10)).owner)
                    insts += y_straight_top_left[straight_index_max], f"{3},{0},{straight_index_max}"
                    insts += y_straight_btm_left[straight_index_max], f"{3},{1},{straight_index_max}"
                    straight_index_max = straight_index_max+1

        progcuit = cast(Mapping[str, fp.ICellRef], insts)
        # p = progcuit["0,1,3"]["op_2"].position[0]

        for i in range(row_number + 1):
            for j in range(column_number):
                if i == 0 and (j < column_number - 1):
                    link1 = fp.LinkBetween(start=progcuit[f"{0},{i},{j}"]["op_2"],
                                           end=progcuit[f"{0},{i},{j + 1}"]["op_1"],
                                           bend_factory=EulerBendFactory(radius_min=5, l_max=5, waveguide_type=TECH.WG.FWG.C.WIRE))
                    insts += link1
                if i == row_number and (j < column_number - 1):
                    link2 = fp.LinkBetween(start=progcuit[f"{0},{i},{j}"]["op_3"],
                                           end=progcuit[f"{0},{i},{j + 1}"]["op_0"],
                                           bend_factory=EulerBendFactory(radius_min=5, l_max=15, waveguide_type=TECH.WG.FWG.C.WIRE))
                    insts += link2
                if (i < row_number) and (j < column_number):
                    link3 = fp.LinkBetween(start=progcuit[f"{0},{i},{j}"]["op_0"],
                                           end=progcuit[f"{1},{i},{j}"]["op_1"],
                                           bend_factory=EulerBendFactory(radius_min=5, l_max=15, waveguide_type=TECH.WG.FWG.C.WIRE))
                    insts += link3
                    link4 = fp.LinkBetween(start=progcuit[f"{0},{i},{j}"]["op_3"],
                                           end=progcuit[f"{1},{i},{j + 1}"]["op_0"],
                                           bend_factory=EulerBendFactory(radius_min=15, l_max=15, waveguide_type=TECH.WG.FWG.C.WIRE))
                    insts += link4
                    link5 = fp.LinkBetween(start=progcuit[f"{0},{i + 1},{j}"]["op_1"],
                                           end=progcuit[f"{1},{i},{j}"]["op_2"],
                                           bend_factory=EulerBendFactory(radius_min=15, l_max=15, waveguide_type=TECH.WG.FWG.C.WIRE))
                    insts += link5
                    link6 = fp.LinkBetween(start=progcuit[f"{0},{i + 1},{j}"]["op_2"],
                                           end=progcuit[f"{1},{i},{j + 1}"]["op_3"],
                                           bend_factory=EulerBendFactory(radius_min=15, l_max=15, waveguide_type=TECH.WG.FWG.C.WIRE))
                    insts += link6
        n = 0
        link7 = fp.LinkBetween(start=progcuit[f"{0},{0},{column_number-1}"]["op_2"],
                               end=progcuit[f"{4},{1},{0}"]["op_0"],
                               bend_factory=TECH.WG.FWG.C.WIRE.BEND_CIRCULAR)
        insts += link7
        ports += progcuit[f"{4},{1},{0}"]["op_1"].with_name("op_"+str(n))

        for i in range(row_number):
            link8 = fp.LinkBetween(start=progcuit[f"1,{i},{column_number}"]["op_1"],
                                   end=progcuit[f"{3},{1},{i}"]["op_0"],
                                   bend_factory=EulerBendFactory(radius_min=15, l_max=15, waveguide_type=TECH.WG.FWG.C.WIRE))
            insts += link8
            n = n + 1
            ports += progcuit[f"{3},{1},{i}"]["op_1"].with_name("op_"+str(n))
            link9 = fp.LinkBetween(start=progcuit[f"1,{i},{column_number}"]["op_2"],
                                   end=progcuit[f"{3},{0},{i}"]["op_0"],
                                   bend_factory=EulerBendFactory(radius_min=15, l_max=15, waveguide_type=TECH.WG.FWG.C.WIRE))
            insts += link9
            n = n + 1
            ports += progcuit[f"{3},{0},{i}"]["op_1"].with_name("op_"+str(n))

        n = n + 1
        link10 = fp.LinkBetween(start=progcuit[f"{0},{row_number},{column_number-1}"]["op_3"],
                               end=progcuit[f"{5},{1},{0}"]["op_0"],
                               bend_factory=EulerBendFactory(radius_min=15, l_max=15, waveguide_type=TECH.WG.FWG.C.WIRE))
        insts += link10
        ports += progcuit[f"{5},{1},{0}"]["op_1"].with_name("op_"+str(n))

        n = n+1
        link10 = fp.LinkBetween(start=progcuit[f"{0},{row_number},0"]["op_0"],
                               end=progcuit[f"{5},{0},{0}"]["op_1"],
                               bend_factory=EulerBendFactory(radius_min=15, l_max=15, waveguide_type=TECH.WG.FWG.C.WIRE))
        insts += link10
        ports += progcuit[f"{5},{0},{0}"]["op_0"].with_name("op_"+str(n))

        for i in range(row_number-1,-1,-1):
            n = n + 1
            link11 = fp.LinkBetween(start=progcuit[f"1,{i},0"]["op_3"],
                                    end=progcuit[f"{2},{0},{i}"]["op_1"],
                                    bend_factory=EulerBendFactory(radius_min=15, l_max=15,
                                                                  waveguide_type=TECH.WG.FWG.C.WIRE))
            insts += link11
            ports += progcuit[f"{2},{0},{i}"]["op_0"].with_name("op_"+str(n))
            n = n + 1
            link12 = fp.LinkBetween(start=progcuit[f"1,{i},0"]["op_0"],
                                    end=progcuit[f"{2},{1},{i}"]["op_1"],
                                    bend_factory=EulerBendFactory(radius_min=15, l_max=15,
                                                                  waveguide_type=TECH.WG.FWG.C.WIRE))
            insts += link12
            ports += progcuit[f"{2},{1},{i}"]["op_0"].with_name("op_"+str(n))

        n = n+1
        link13 = fp.LinkBetween(start=progcuit["0,0,0"]["op_1"],
                               end=progcuit[f"{4},{0},{0}"]["op_1"],
                               bend_factory=TECH.WG.FWG.C.WIRE.BEND_CIRCULAR)
        insts += link13
        ports += progcuit[f"{4},{0},{0}"]["op_0"].with_name("op_"+str(n))

        # fmt: on
        return insts, elems, ports

Call the main function and use the Hfanout function to implement the grating coupler design:

if __name__ == "__main__":
    from pathlib import Path

    gds_file = Path(__file__).parent / "local" / Path(__file__).with_suffix(".gds").name
    library = fp.Library()

    TECH = get_technology()
    # =============================================================
    # fmt: off

    def bend_factories(waveguide_type: fp.IWaveguideType):
        if waveguide_type == TECH.WG.FWG.C.WIRE:
            return EulerBendFactory(radius_min=15, l_max=15, waveguide_type=waveguide_type)
        return waveguide_type.bend_factory

    library += [
            HFanout(name="mmi",
                    device=PICircuit(),
                    left_spacing=100,
                    right_spacing=100,
                    left_distance=100,
                    right_distance=100,
                    bend_factories=bend_factories,
                    left_waveguide_type=TECH.WG.SWG.C.WIRE,
                    right_waveguide_type=TECH.WG.SWG.C.WIRE)
        ]

    # fmt: on
    # =============================================================
    fp.export_gds(library, file=gds_file)
    fp.plot(library)

Part II. Parameter Description

  1. row_number : Number of horizontal grids of optical network

  2. column_number : Number of vertical grids of optical network

  3. basic_comp : The base module device, chosen here is the microring modulator

  4. spacing : Component spacing setting

Part III. Test Description

First, a 2*2 square network:

../_images/PIC_square_22.png

The automatic generation of the layout is shown below:

../_images/PIC_square_22GDS.png

Continue testing 5*4 rectangular network:

../_images/PIC_square_54.png

The automatic generation of the layout is shown below:

../_images/PIC_square_54GDS.png

Finally, to demonstrate the great potential of PhotoCAD in the field of programmable photonic integrated link design, we designed a 20*30 optical network as well as a 100*100 optical network.

../_images/PIC_square_3020.png ../_images/PIC_square_100100.png

Part IV. Summary

PhotoCAD designs massively programmable photonic circuit plates with excellent performance, and according to the official reconfigurable code provided, we can easily implement various complex networks.