AWG generation: implementation

In the previous step we derived the implementation parameters from functional specifications. Now, we will assemble the AWG. This consists of three steps:

  1. Implement the star couplers

  2. Implement the waveguide array

  3. Assemble the AWG and inspect

Defining the star couplers

First, we define the star couplers, starting from the output star coupler. This is broken down into four steps:

  1. Add dummy apertures

  2. Generate the aperture mounting, positioning the apertures in a Rowland or confocal configuration

  3. Generate a contour drawing for the free propagation region

  4. Compose the star coupler and inspect

The reason why these four steps have been defined is to give the designer a large degree of control over the details of the implementation.

Several utility functions are defined in the Luceda AWG Designer to support the user. The following code excerpt shows the implementation of the output star coupler. The input star coupler follows the same flow.

luceda-academy/training/topical_training/design_awg/example_generate_awg.py
print("Defining output star coupler...\n")

# Set the apertures (sc_out_array) and their angles (sc_out_array_angles) together with dummy apertures.
# This is for the grating arms/waveguides.
sc_out_array, sc_out_array_angles = awg.get_apertures_angles_with_dummies(
    apertures=[aperture] * n_arms,
    angles=angles_arms_out,
    n_dummies=n_dummies,
)

# Set the apertures (sc_out_ports) and their angles (sc_out_ports_angles).
# This is for the I/O waveguides to the rest of the chip.
sc_out_ports, sc_out_ports_angles = awg.get_apertures_angles_with_dummies(
    apertures=[aperture] * len(angles_out),
    angles=angles_out,
    n_dummies=n_dummies,
)

# Generate aperture mounting
(
    sc_out_array_aperture,  # The MultiAperture on the arm/array side, of the chosen MultiAperture type
    sc_out_ports_aperture,  # The MultiAperture on the ports side, of the chosen MultiAperture type
    sc_out_array_xforms,  # The transformations of the arm/array apertures
    sc_out_ports_xforms,  # The transformations of the ports apertures
) = awg.get_star_coupler_apertures(
    apertures_arms=sc_out_array,  # The apertures for grating arms
    apertures_ports=sc_out_ports,  # The ports for I/O arms
    angles_arms=sc_out_array_angles,  # The angles for grating arms
    angles_ports=sc_out_ports_angles,  # The angles for I/O arms
    radius=grating_radius_output,  # Grating radius for the star coupler
    mounting="rowland",  # Rowland mounting for the aperture (refer to documentation for details)
    input=False,  # True if the star coupler is an input star coupler, False if it's an output star coupler
)

# Generate free propagation contour around the slab region.
# This will be used as an input in the next step to generate the star coupler.
contour = awg.get_star_coupler_extended_contour(
    apertures_in=sc_out_array,
    apertures_out=sc_out_ports,
    trans_in=sc_out_array_xforms,
    trans_out=sc_out_ports_xforms,
    radius_in=grating_radius_output,
    radius_out=grating_radius_output * 0.5,
    extension_angles=(10, 10),
    aperture_extension=(0.0, 0.1),
    layers_in=[i3.TECH.PPLAYER.SI],
    layers_out=[i3.TECH.PPLAYER.SI, i3.TECH.PPLAYER.SHALLOW],
)

# Compose the star coupler.
sc_out = awg.StarCoupler(
    aperture_in=sc_out_array_aperture,
    aperture_out=sc_out_ports_aperture,
)
sc_out.Layout(contour=contour)
sc_out.CircuitModel(simulation_wavelengths=[center_wavelength])

Note

We have set simulation_wavelengths=[center_wavelength] on the apertures. This limits the simulation of the apertures to a single wavelength, which is good enough for a first simulation. If you want to simulate the apertures for each wavelength, then set simulation_wavelengths=None but keep in mind that the simulation can take a long time.

The virtual fabrication of the resulting output star coupler is:

../../../_images/sc_out_vfab.png

Adding dummies

First, we create a list of apertures and their rotation angles, including a number of dummy apertures on either side of the I/O and arm apertures. They are important to make sure that the outer apertures see the same neighbours as the inner apertures. This affects both their fabrication (lithography, etching, planarization) and their optical behavior.

../../../_images/aperture_with_dummies.png

To that end, we call the AWG Designer’s function get_apertures_angles_with_dummies(apertures, angles, n_dummies), once for the input side and once for the output side. This function returns the final list of apertures and a list of their angles. Note that for simplicity we use a single aperture for the ports here. In a real design you might want to optimize each aperture for the target wavelength it will capture.

luceda-academy/training/topical_training/design_awg/example_generate_awg.py
# Set the apertures (sc_out_array) and their angles (sc_out_array_angles) together with dummy apertures.
# This is for the grating arms/waveguides.
sc_out_array, sc_out_array_angles = awg.get_apertures_angles_with_dummies(
    apertures=[aperture] * n_arms,
    angles=angles_arms_out,
    n_dummies=n_dummies,
)

# Set the apertures (sc_out_ports) and their angles (sc_out_ports_angles).
# This is for the I/O waveguides to the rest of the chip.
sc_out_ports, sc_out_ports_angles = awg.get_apertures_angles_with_dummies(
    apertures=[aperture] * len(angles_out),
    angles=angles_out,
    n_dummies=n_dummies,
)

Generating the aperture mounting

Next, the apertures will be mounted in the desired configuration to form a star coupler. Out-of-the-box provided mountings are rowland or confocal.

../../../_images/aperture_mountings.png

The AWG Designer provides the function get_star_coupler_apertures(apertures_arms, apertures_ports, angles_arms, angles_ports, radious, mounting, input) for this purpose.

This function returns two MultiAperture objects, one for the input side and one for the output side. Multi-apertures are an internal functionality used to join a list of apertures into one object, facilitating simulation. The function also returns their transformations (the input side will be mirrored compared to the array side).

luceda-academy/training/topical_training/design_awg/example_generate_awg.py
# Generate aperture mounting
(
    sc_out_array_aperture,  # The MultiAperture on the arm/array side, of the chosen MultiAperture type
    sc_out_ports_aperture,  # The MultiAperture on the ports side, of the chosen MultiAperture type
    sc_out_array_xforms,  # The transformations of the arm/array apertures
    sc_out_ports_xforms,  # The transformations of the ports apertures
) = awg.get_star_coupler_apertures(
    apertures_arms=sc_out_array,  # The apertures for grating arms
    apertures_ports=sc_out_ports,  # The ports for I/O arms
    angles_arms=sc_out_array_angles,  # The angles for grating arms
    angles_ports=sc_out_ports_angles,  # The angles for I/O arms
    radius=grating_radius_output,  # Grating radius for the star coupler
    mounting="rowland",  # Rowland mounting for the aperture (refer to documentation for details)
    input=False,  # True if the star coupler is an input star coupler, False if it's an output star coupler
)

The resulting mounted multi-apertures are:

../../../_images/sc_out_array_ap.png
../../../_images/sc_out_ports_ap.png

Generating the contour

Next, we need to draw the free propagation region to ensure this is a continuous piece of a slab waveguide. In the case of SiFab technology, a drawing on the SI layer is needed (otherwise, all silicon would be etched away). In addition, we need to ensure that this free propagation region contour perfectly matches the apertures.

To this end, we use the function get_star_coupler_extended_contour():

luceda-academy/training/topical_training/design_awg/example_generate_awg.py
# Generate free propagation contour around the slab region.
# This will be used as an input in the next step to generate the star coupler.
contour = awg.get_star_coupler_extended_contour(
    apertures_in=sc_out_array,
    apertures_out=sc_out_ports,
    trans_in=sc_out_array_xforms,
    trans_out=sc_out_ports_xforms,
    radius_in=grating_radius_output,
    radius_out=grating_radius_output * 0.5,
    extension_angles=(10, 10),
    aperture_extension=(0.0, 0.1),
    layers_in=[i3.TECH.PPLAYER.SI],
    layers_out=[i3.TECH.PPLAYER.SI, i3.TECH.PPLAYER.SHALLOW],
)

This function takes as inputs the mounted apertures generated in the previous step, and draws a contour which extends from them along a circle. Particularly during the first design iterations on a technology, drawing the contour a bit wider than the apertures can be important to avoid loss or reflections in case the light diffracts a bit wider than anticipated.

../../../_images/contour_parameters.png
  • The apertures_in, apertures_out, trans_in, trans_out arguments are the outputs of the previous step.

  • The radius_in is the radius of the circle of the extended drawing. For the output star coupler, this is the radius of the waveguide array aperture mounting.

  • The radius of the outer side radius_out can be drawn differently. In this example we choose to follow the Rowland mounting where half the waveguide array radius is used.

  • The argument extension_angles specifies how far along the aperture circles we want the contour to extend. In this example, we choose 10 degrees for both the input and output side of the star coupler.

  • The argument aperture_extension allows us to specify the drawing of additional vertices in between the two apertures. On the waveguide array side, we want the apertures to be very close together so we don’t need an additional vertex drawn. On the ports side (output side of the output star coupler), we want an additional vertex drawn in order to make sure that the aperture’s opening into the FPR is not distorted by drawing the contour.

../../../_images/contour_control.png

The resulting contour shape is:

../../../_images/sc_out_contour.png

Compose the star coupler

Finally, we are ready to put the pieces together in a StarCoupler object:

luceda-academy/training/topical_training/design_awg/example_generate_awg.py
# Compose the star coupler.
sc_out = awg.StarCoupler(
    aperture_in=sc_out_array_aperture,
    aperture_out=sc_out_ports_aperture,
)
sc_out.Layout(contour=contour)
sc_out.CircuitModel(simulation_wavelengths=[center_wavelength])

Here, for the example’s sake, we set the parameter simulation_wavelengths of the star coupler’s circuit model to a single wavelength. This will reduce the number of simulations needed to build up the model at the expense of less accurate results. After the initial design iterations, it can be set to None in order to simulate the AWG completely.

Routing the waveguide array

Now that the star couplers are generated, we can create the waveguide array. For this purpose, we need to find a way to route the waveguides with the given star couplers and delay length. However, there are several constraints and trade-offs to consider:

  • We want to keep a certain minimum waveguide spacing in order to avoid coupling between the waveguides.

  • We want the AWG to be as compact as possible to reduce the ‘chip real estate’ used and to reduce the impact of fabrication variations (e.g. layer thickness).

  • In a silicon photonics foundry technology, we’d like to avoid implementing the delay length with curvilinear waveguides. Therefore, we prefer Manhattan-style routing to implement the delay length in the straight parts as much as possible.

The delay length of 6.75 um, which resulted form the synthesis, is relatively short compared to the minimum waveguide spacing. Therefore, and because of the above considerations, we choose an S-shaped AWG. This is provided out-of-the-box in the Luceda AWG Designer (SWaveguideArray):

luceda-academy/training/topical_training/design_awg/example_generate_awg.py
print("Creating waveguide array...\n")

# Define the delay lengths based on previously calculated delay.
delays = [delay_length * i for i in range(n_arms)]

# Define the start ports as the east ports of the input star coupler
start_ports = sc_in_layout.east_ports

# Define an s-shaped waveguide array
wg_array = awg.SWaveguideArray(
    start_ports=sc_in_layout.east_ports.y_sorted(),
    delay_lengths=delays,
)
# Create the LayoutView of the waveguide array.
# Just as a standard waveguide route, can define various parameters.
# Here, we use spline bends for a lower loss.
wg_array.Layout(
    offset_output_ports_south=80.0,
    route_properties={
        "rounding_algorithm": i3.SplineRoundingAlgorithm(adiabatic_angles=(15.0, 15.0)),
    },
)

The offset_output_ports_south is used to determine the vertical offset between the ports of the output star coupler and those of the input star coupler To minimize the bending losses, we use adiabatic bends. We do this setting the argument route_properties to use i3.SplineRoundingAlgorithm as its rounding algorithm.

The result is the following waveguide array:

../../../_images/s_array.png

Note

In a different technology or for a different application, the constraints and trade-offs might be different. It is not difficult to implement your own custom waveguide array if it is not offered out-of-the-box by the Luceda AWG Designer yet. Alternatively, you can ask Luceda to help with the implementation by contacting us at support@lucedaphotonics.com.

AWG implementation

The final step of the implementation is probably the most straightforward: we make an ArrayedWaveguideGrating object out of the input star coupler, output star coupler and waveguide array.

luceda-academy/training/topical_training/design_awg/example_generate_awg.py
# Creates the AWG using our star couplers and waveguide array.
generated_awg = awg.ArrayedWaveguideGrating(
    star_coupler_in=sc_in,
    star_coupler_out=sc_out,
    waveguide_array=wg_array,
)

generated_awg_layout = generated_awg.get_default_view(i3.LayoutView)
generated_awg_netlist = generated_awg.get_default_view(i3.NetlistView)
generated_awg_circuit = generated_awg.get_default_view(i3.CircuitModelView)
../../../_images/awg_layout.png