Organizing your AWG design project

In this application example, you will learn how to use Luceda AWG Designer to organize a project for designing an AWG. The target application of our AWG is one for IEEE 400Gb Ethernet [1] standard 400GBase-LR8 [2]. We will consider a situation in which we are developing such an AWG and want to tape out test designs of the AWG to a foundry to test it. We will take the script for an AWG that we developed in the tutorial on Getting Started with AWG Designer and organize it into different functions so that it can be iterated upon and prepared for tape-out.

As an exercise, you will also learn how to change the parameters of your AWG and see how its performance varies, taking into account design trade-offs.

Organize your code in different files

Once you have created your code for designing your AWG, we recommend that you organize it into different functions that can be easily maintained. In this example, we have the following:

  • generate_awg: it executes all the steps for the AWG generation and it returns an AWG PCell.

  • simulate_awg: it takes the generated AWG PCell as an input, simulates it and returns its S-matrix.

  • analyze_awg: it takes an S-matrix as an input, performs the analysis and returns a report on the specifications.

  • finalize_awg: it takes an AWG PCell as an input and returns a routed and fab-ready AWG in GDSII format.

While you iterate on your designs, you would be able to customize them and choose the desired parameters that need to be varied.

Then, you would have a master script that goes through all these functions. In this example, we use the code below. It goes through all the steps and stores the results in the folder designs/awg_10400_0.4, which contains:

  • The finalized GDSII file of the AWG

  • The simulated spectrum

  • An analysis report

luceda-academy/training/topical_training/design_awg/example_generate_awg.py
import si_fab.all as pdk  # noqa: F401
import ipkiss3.all as i3
import numpy as np
import os

from rect_awg.generate import generate_awg
from rect_awg.simulate import simulate_awg
from rect_awg.finalize import finalize_awg
from rect_awg.analyze import analyze_awg

# Actions
generate = True
simulate = True
analyze = True
finalize = True
plot = True

# Specifications
wg_width = 0.4
fsr = 10400

# Creating a folder for results
this_dir = os.path.abspath(os.path.dirname(__file__))
designs_dir = os.path.join(this_dir, "designs")
if not os.path.exists(designs_dir):
    os.mkdir(designs_dir)
save_dir = os.path.join(designs_dir, f"awg_{fsr}_{wg_width:.1f}")
if not os.path.exists(save_dir):
    os.mkdir(save_dir)

# Simulation specs
wavelengths = np.linspace(1.26, 1.32, 1000)

# Bare AWG
if generate:
    awg = generate_awg(fsr=fsr, wg_width=wg_width, plot=plot, save_dir=save_dir, tag="bare")
if simulate:
    smat = simulate_awg(awg=awg, wavelengths=wavelengths, save_dir=save_dir, tag="bare")
else:
    path = os.path.join(save_dir, "smatrix_bare.s10p")
    smat = i3.device_sim.SMatrix1DSweep.from_touchstone(path, unit="um") if os.path.exists(path) else None

if analyze:
    analyze_awg(smat, plot=True, save_dir=save_dir, tag="bare")

# Finished AWG
if finalize:
    if generate:
        finalized_awg = finalize_awg(awg=awg, plot=plot, save_dir=save_dir, tag="finalized")

    if simulate:
        smat_finalized = simulate_awg(
            awg=finalized_awg.blocks[3],
            wavelengths=wavelengths,
            save_dir=save_dir,
            tag="finalized",
        )
    else:
        path = os.path.join(save_dir, "smatrix_finalized.s10p")
        smat_finalized = i3.device_sim.SMatrix1DSweep.from_touchstone(path, unit="um") if os.path.exists(path) else None

    if analyze:
        analyze_awg(smat_finalized, peak_threshold=-25, plot=plot, save_dir=save_dir, tag="finalized")

print("Done")
../../../_images/full_gds.png
../../../_images/spectrum_finalized.png

Analysis report: report_finalized.json

Let’s now take a look at generate_awg. As explained above, it executes all the steps for the AWG generation and returns an AWG PCell. In this project this function is defined in rect_awg/generate.py. Let’s have a look at the function signature.

luceda-academy/training/topical_training/cwdm_awg/rect_awg/generate.py
def generate_awg(fsr, wg_width=0.4, plot=True, save_dir=None, tag=""):
    """Custom AWG generating function. If you make an AWG, you should write one of your own.
    Parameters are the things you want to vary as well as options to control if you
    plot and where you save your data.

    Parameters
    ----------
    fsr : float
          FSR of the AWG in Hz
    wg_width : float, default: 0.4, optional
               Width of the waveguides in the array
    plot : bool, default: True, optional
           If true the AWG is plotted
    save_dir: str, default: None, optional
              If specified, a GDSII file is saved in this directory
    tag: str, optional
         String used to give a name to saved files

    Returns
    -------
    cwdm_awg : i3.PCell
               AWG PCell
    """

    center_frequency = 232200.0  # GHz
    n_channels = 9
    channel_spacing = 800.0  # GHz

As you can see, fsr and wg_width are provided as parameters that you can vary. These parameters are passed to the design function and to the waveguide template, respectively. These allow us to vary both of these parameters in our design in order to see how they affect it.

In your own AWG designs, you can define which parameters you want to expose.

The function generate_awg goes through all the steps of instantiating the AWG components, synthesising the AWG and assembling it that are detailed in the sections on synthesis and assembly of the tutorial on getting started with AWG Designer. The code is the same, it is simply restructured and wrapped into this function. It then returns an AWG PCell with the layout obtained by assembling the AWG.

Similarly, the functions simulate_awg and analyze_awg use the same code as in the section on simulation and analysis of that tutorial and wraps around them for usability.

The former takes the AWG PCell and a set of wavelengths as input and returns the simulated S-matrix.

luceda-academy/training/topical_training/cwdm_awg/rect_awg/simulate.py
from ipkiss3 import all as i3
import os


def simulate_awg(awg, wavelengths, save_dir=None, tag=""):
    """Custom function to simulate an AWG. If you make an AWG, you should write one of your own.
    Parameters are the awg cell you want to simulate and things that control your simulation as well as
    parameters that control where you save your data.

    Parameters
    ----------
    awg : i3.Pcell
          AWG PCell
    wavelengths: ndarray
                 Simulation wavelengths
    save_dir: str, default: None, optional
              If specified, a GDSII file is saved in this directory
    tag: str, optional
         String used to give a name to saved files

    Returns
    -------
    awg_s:
           S-matrix of the simulated AWG

    """
    cwdm_awg_cm = awg.get_default_view(i3.CircuitModelView)
    print("Simulating AWG...\n")
    awg_s = cwdm_awg_cm.get_smatrix(wavelengths)
    if save_dir:
        smatrix_path = os.path.join(save_dir, f"smatrix_{tag}.s{awg_s.data.shape[0]}p")
        awg_s.to_touchstone(smatrix_path)
        print(f"{smatrix_path} written")
    return awg_s

Whereas the latter takes the simulated s-matrix together with other optional parameters you can define (see the signature below), prints out an analysis report and displays the result of the simulation and and channel locations in a figure.

luceda-academy/training/topical_training/cwdm_awg/rect_awg/analyze.py
def analyze_awg(awg_s, peak_threshold=-10.0, plot=True, save_dir=None, tag=""):
    """Custom AWG analysis function. If you make an AWG, you should write an analysis function of your own.
    The goal of the analyze_awg function is to answer the question: how does the AWG perform
    according to my specs? In an analysis function you define the specs and check the behaviour
    of the AWG according to those specs using helper functions provided in the Luceda AWG Designer.
    The parameters are the things you want to vary as well as options to control whether you
    plot and where you save your data.

    Parameters
    ----------
    awg_s: SMatrix1DSweep
        S-matrix of the AWG
    peak_threshold: float, default: -10.0, optional
        The power threshold for peak detection (in dB)
    plot: bool, default: True, optional
        If true, the spectrum is plotted
    save_dir: str, default: None, optional
        If specified, a GDSII file is saved in this directory
    tag: str, optional
        String used to give a name to saved files

    Returns
    ------
    report: dict
        Dictionary containing analysis specs
    """

The only function with code that is substantially changed from the tutorial on Getting Started with Luceda AWG Designer is finalize_awg, whose function signature is shown below.

luceda-academy/training/topical_training/rect_awg/finalize.py
def finalize_awg(awg, plot=True, die_width=3000.0, save_dir=None, tag=""):
    """Custom function to finalize an AWG design, mostly for DRC fixing and routing.
    If you make an AWG, you should write one of your own.
    Parameters are the things you want to vary as well as options to control whether you
    plot and where you save your data.

    Parameters
    ----------
    awg: i3.PCell
         Input AWG cell
    plot : bool, default: True, optional
           If true the finilized awg is plotted
    die_width: float, default: 3000.0, optional
               Width of the die
    save_dir : str, default: None, optional
               If specified, a GDSII file is saved in this directory
    tag: str, optional
         String used to give a name to saved files

    Returns
    -------
    awg_block: i3.PCell
               Finalized AWG block
    """

It takes an AWG PCell as input together with whichever optional parameters you want, fixes common DRC errors, and includes it in a small test design that can be merged in a top-level layout as explained in the finalization step of that tutorial.

Final simulation and analysis

Now we can simulate the transmission of the devices on the chip to check that our test structures allow us to extract the information we need about the device under test (the AWG in this case).

As you may recall in the tutorial on the finalization step, the alignment waveguide is the second object we added to the block (index 1) and the AWG is the fourth (index 3). We retrieve their circuit models from which we simulate the spectra. All effects in the devices under test are taken into account, including the waveguide losses and the performance of the fiber grating couplers. In addition, we can use the previously defined simulate_awg and analyze_awg functions to analyze the S-matrix of the AWG, including the behavior from the fiber grating couplers. Since the transmitted power has considerably dropped due to the fiber grating couplers, we set the threshold for peak detection to -25 dB.

luceda-academy/training/topical_training/cwdm_awg/example_generate_awg.py
# Finished AWG
if finalize:
    if generate:
        finalized_awg = finalize_awg(awg=awg, plot=plot, save_dir=save_dir, tag="finalized")

    if simulate:
        smat_finalized = simulate_awg(
            awg=finalized_awg.blocks[3],
            wavelengths=wavelengths,
            save_dir=save_dir,
            tag="finalized",
        )
    else:
        path = os.path.join(save_dir, "smatrix_finalized.s10p")
        smat_finalized = i3.device_sim.SMatrix1DSweep.from_touchstone(path, unit="um") if os.path.exists(path) else None

    if analyze:
        analyze_awg(smat_finalized, peak_threshold=-25, plot=plot, save_dir=save_dir, tag="finalized")

print("Done")
../../../_images/spectrum_finalized.png

Report JSON file: report_finalized.json

We can clearly see the fiber coupler spectrum overlayed (twice, for the input and the output coupler) on the alignment waveguide and the AWG. The spectrum of the AWG test structure can then be normalized to the one of the alignment waveguide to get a relatively accurate estimate of the transmission of the AWG itself.

Conclusion

In this tutorial, we have organized code for designing an AWG so that it matches well the four design steps:

  1. AWG generation

  2. AWG simulation

  3. AWG analysis

  4. AWG finalization

We have made sure that the AWG adheres to layout design rules, and we’ve routed the AWG to the outside world to obtain a design ready for tape-out. Finally, a test structure was created and validated in simulation for testing and extracting the AWG performance.

Test your knowledge

In order to become familiar with the procedure for maintaining and iterating your designs, please run the following file:

luceda-academy/training/topical_training/organize_awg_project/example_generate_awg.py

As an exercise, try to change the FSR of the AWG to 12000 GHz and compare the result with the 10400 GHz.

  • Has the insertion loss increased or decreased?

  • Has the crosstalk increased or decreased?

You can try creating a parameter sweep using a for loop.