5.1. Splitter tree with two levels

In this section, we are going to design and simulate a simple circuit, made of three connected splitters. The result is the following filter with one input and four outputs.


The main building block of this circuit is an MMI with 1 input and 2 outputs, therefore we can use the optimized 1x2 MMI that we designed in the previous sections and that is included in the SiFab PDK. The first step is to instantiate this MMI.

splitter = pdk.MMI1x2Optimized()
splitter_lv = splitter.Layout()

We already know that the layout of this MMI is optimized and the correct data is used for the circuit simulation, therefore we can immediately go ahead and see how to use it to design and simulate a circuit.

5.1.1. Ports

The three MMIs that are part of the final circuit are connected to each other using waveguides. Each MMI contains information about ports in the LayoutView. This information is very important, because ports are used to indicate how the waveguides have to connect the different components to each other. Each port contains the following properties:

  • name: name of the port.
  • position: position of the port.
  • angle: direction a waveguide leaving the port has to go to. 0 degrees is parallel to the x-axis, going towards east.
  • trace_template: trace template of the waveguide used at the port.

The component MMI1x2Optimized has three ports: in1, out1 and out2. The names of the ports can be visualized on the LayoutView with the command splitter_lv.visualize(annotate=True) or they can be extracted by executing print(splitter_lv.ports).

5.1.2. Building a circuit with CircuitCell

A simple way to build a circuit in IPKISS is to use ‘CircuitCell’, which allows you to easily define connectivity between a number of child cells. CircuitCell is an additional feature built on IPKISS and provided with this training material as part of ‘additional_utils’. When placing these child cells, CircuitCell will generate all the waveguides (as separate PCells) needed to connect the child cells together.

In order to define a CircuitCell, we need the following ingredients:

  • Child cells. This is a Python dictionary containing the names of the child cell instances as keys. In this case, we have three child cells, one for each splitter.
child_cells = {"sp_0_0": splitter,
               "sp_1_0": splitter,
               "sp_1_1": splitter}

The first number indicates the level at which we are going to place the splitter, while the second number indicates the number of the splitter in a specific level.

  • Connectors. This is a list of tuples, each containing the names of the ports to be connected and the routing function used to draw the waveguide between them. The ports are identified by the name of the child cell and the name of the port, as child_cell:port. Various functions can be used to connect ports, and you can even write your own. All the connectors that were introduced in the previous section Waveguides and waveguide connectors can be used here.

    connectors = [("sp_0_0:out1", "sp_1_0:in1", bezier_sbend),
                  ("sp_0_0:out2", "sp_1_1:in1", bezier_sbend)]
  • Place specs. This is a list containing all the layout specifications that apply to each component. Specifications can control the distance between components, transformations (e.g. mirroring) and alignment. Click here for a full list of available specs.

spacing_x = 100.0
spacing_y = 50.0
place_specs = [i3.Place("sp_0_0:in1", (0, 0)),
               i3.PlaceRelative("sp_1_0:in1", "sp_0_0:out1", (spacing_x, -spacing_y / 2)),
               i3.PlaceRelative("sp_1_1:in1", "sp_0_0:out2", (spacing_x, +spacing_y / 2))]
  • External port names. In this dictionary, we expose the ports that we want to access once the circuit is completed. This is useful to expose only the ports that need to be routed on the upper hierarchical level as external ports, and to rename those external ports.

    external_port_names = {"sp_0_0:in1": "in",
                           "sp_1_0:out1": "out1",
                           "sp_1_0:out2": "out2",
                           "sp_1_1:out1": "out3",
                           "sp_1_1:out2": "out4"}

Now we can create the circuit by instantiating CircuitCell and passing the child cells, connectors, specs and external port names we have just defined.

my_circuit = CircuitCell(name="splittertree_2levels",
#    * CircuitCell Layout
my_circuit_layout = my_circuit.Layout()

5.1.3. Performing a circuit simulation

The circuit we have just built can be simulated as a whole because all its components, MMIs and waveguides, each has a circuit model. Caphe, the circuit simulator included in IPKISS, takes care of putting together the models and simulating the full circuit. All we have to do is to instantiate the circuit model.

my_circuit_cm = my_circuit.CircuitModel()
wavelengths = np.linspace(1.5, 1.6, 501)
S_total = my_circuit_cm.get_smatrix(wavelengths=wavelengths)
#    * Plotting
plt.plot(wavelengths, 20 * np.log10(np.abs(S_total['out1', 'in'])), '-', linewidth=2.2, label="out1")
plt.plot(wavelengths, 20 * np.log10(np.abs(S_total['out2', 'in'])), '-', linewidth=2.2, label="out2")
plt.plot(wavelengths, 20 * np.log10(np.abs(S_total['out3', 'in'])), '-', linewidth=2.2, label="out3")
plt.plot(wavelengths, 20 * np.log10(np.abs(S_total['out4', 'in'])), '-', linewidth=2.2, label="out4")
plt.plot(wavelengths, 20 * np.log10(np.abs(S_total['in', 'in'])), '-', linewidth=2.2, label="in")
plt.ylim(-70, 0)
plt.xlabel('Wavelength [um]', fontsize=16)
plt.ylabel('Transmission [dB]', fontsize=16)
plt.legend(fontsize=14, loc=4)