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:
Implement the star couplers
Implement the waveguide array
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:
Add dummy apertures
Generate the aperture mounting, positioning the apertures in a Rowland or confocal configuration
Generate a contour drawing for the free propagation region
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.
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:
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.
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.
# 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.
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).
# 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:
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()
:
# 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.
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.
The resulting contour shape is:
Compose the star coupler
Finally, we are ready to put the pieces together in a StarCoupler
object:
# 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
):
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:
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.
# 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)