2. Adding fiber grating couplers

In the previous section, we have learned how to create a circuit, specifically a two-level splitter tree with three splitters. We have placed its subcomponents, the MMIs, in specified locations and we have connected them using ‘connectors’. In this section, we will find out how to connect this circuit to the outside world by adding fiber grating couplers to it.

2.1. Building the circuit

To build the two-level splitter tree, including fiber grating couplers, we use CircuitCell as we have done previously. The differences are the following:

  • We add one input and four output fiber grating couplers to the dictionary of child cells. We use the fiber grating coupler from the SiFab PDK, optimized for operation around 1550 nm wavelength (FC_TE_1550).
  • When defining connectors, we connect the fiber grating couplers to the correct ports of the splitters.
  • We place the grating couplers so they don’t overlap with the rest of the circuit. We also have to make sure to rotate the output grating couplers by 180 degrees.
  • We updated the external ports to refer to the vertical ports of the fiber grating couplers.

The new SplitterTree2Levels including input and output fiber grating couplers is defined in the code below.

Listing 2.1 luceda-academy/training/getting_started/circuit_splitter_tree/splitter_tree_2levels_with_fgc.py
class SplitterTree2Levels(CircuitCell):

    # 1. We define the properties of the PCell.
    splitter = i3.ChildCellProperty()
    fgc = i3.ChildCellProperty()
    spacing_x = i3.PositiveNumberProperty(default=100.0)
    spacing_y = i3.PositiveNumberProperty(default=50.0)

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

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

    # 2. We define the child cells of our circuit. We have 3 splitters, 1 for the first level and 2 for the
    # second level.
    def _default_child_cells(self):
        child_cells = {
            "sp_0_0": self.splitter,
            "sp_1_0": self.splitter,
            "sp_1_1": self.splitter,
            "fgc_in": self.fgc,
            "fgc_out_0": self.fgc,
            "fgc_out_1": self.fgc,
            "fgc_out_2": self.fgc,
            "fgc_out_3": self.fgc,
        }
        return child_cells

    # 3. We define connectors (list of tuples): ports to be connected + algorithm to connect them (here a Bezier s-bend)
    # Optionally we can tune the default parameters of the connector function as a fourth argument. In this
    # case we change the adiabatic angle for one of the bezier_sbends (default is 15).
    def _default_connectors(self):
        connectors = [
            ("fgc_in:out", "sp_0_0:in1", bezier_sbend),
            ("sp_0_0:out1", "sp_1_0:in1", bezier_sbend),
            ("sp_0_0:out2", "sp_1_1:in1", bezier_sbend),
            ("sp_1_0:out1", "fgc_out_0:out", bezier_sbend),
            ("sp_1_0:out2", "fgc_out_1:out", bezier_sbend),
            ("sp_1_1:out1", "fgc_out_2:out", bezier_sbend),
            ("sp_1_1:out2", "fgc_out_3:out", bezier_sbend),
        ]
        return connectors

    # 4. We define specs, containing all the transformations that apply to each component.
    def _default_place_specs(self):
        sp_x = self.spacing_x
        sp_y = self.spacing_y
        place_specs = [
            i3.Place("sp_0_0:in1", (0, 0)),
            i3.PlaceRelative("fgc_in:out", "sp_0_0:in1", (-sp_x / 2, 0.0)),
            i3.PlaceRelative("sp_1_0:in1", "sp_0_0:out1", (sp_x, -sp_y / 2)),
            i3.PlaceRelative("sp_1_1:in1", "sp_0_0:out2", (sp_x, sp_y / 2)),
            i3.PlaceRelative("fgc_out_0:out", "sp_1_0:out1", (sp_x, -sp_y / 4), angle=180),
            i3.PlaceRelative("fgc_out_1:out", "sp_1_0:out2", (sp_x, sp_y / 4), angle=180),
            i3.PlaceRelative("fgc_out_2:out", "sp_1_1:out1", (sp_x, -sp_y / 4), angle=180),
            i3.PlaceRelative("fgc_out_3:out", "sp_1_1:out2", (sp_x, sp_y / 4), angle=180),
        ]
        return place_specs

    # 5. We define the names of the external ports.
    def _default_external_port_names(self):
        external_port_names = {
            "fgc_in:vertical_in": "in",
            "fgc_out_0:vertical_in": "out1",
            "fgc_out_1:vertical_in": "out2",
            "fgc_out_2:vertical_in": "out3",
            "fgc_out_3:vertical_in": "out4",
        }
        return external_port_names

2.2. Layout and simulation

We can now instantiate and simulate the new splitter tree. As before, we do this at the bottom of the same file, splitter_tree_2levels_with_fgc.py. You can open it in your Luceda Academy project, run it, and explore it by changing the parameters. The layout and simulation results are reported here below.

../../../_images/splitter_tree_two_levels_fgc_layout.png ../../../_images/splitter_tree_two_levels_fgc_circuitsim.png