AWG simulation and analysis

Simulation

At this point we have a complete AWG with a layout, a logical netlist and a simulation model. We can now simply ask Caphe (Luceda IPKISS’ built-in circuit simulator) to calculate its S-parameters and save the results. This is done by instantiating a circuit model and using CircuitModelView.get_smatrix as for any other circuit. We also export the S-matrix to a Touchstone file so that we can inspect it afterwards.

luceda-academy/training/topical_training/design_awg/example_generate_awg.py
print("Simulating AWG...")
awg_s = generated_awg_circuit.get_smatrix(simulation_wavelengths_awg)  # Note, fanout not simulated
smatrix_path = os.path.join(save_dir, f"smatrix.s{awg_s.data.shape[0]}p")
awg_s.to_touchstone(smatrix_path)
print(f"{smatrix_path} written")

Analysis

Next, we will take the S-matrix and check it against the figures of merit. We check the behaviour of the AWG according to the same specifications we defined at the top of the file.

We start by explicitly calculating the wavelengths of the different channels. The helper function awg.frequency_to_wavelength defined in Luceda AWG Designer helps us convert these channels from frequency to wavelength domain.

luceda-academy/training/topical_training/design_awg/example_generate_awg.py
# Calculate the frequencies of the different channels.
channel_half_span = channel_spacing * (n_channels - 1) / 2
channel_freq = np.linspace(center_frequency - channel_half_span, center_frequency + channel_half_span, n_channels)
channel_wav = awg.frequency_to_wavelength(channel_freq)

channel_width = 10e-4
output_ports = [f"out{p + 1}" for p in range(len(channel_wav))]

print("Analyzing AWG...")

Initialize the spectrum analyzer

The measurements are done using i3.SpectrumAnalyzer. We initialize the spectrum analyzer with

  • the S-matrix data of the AWG,

  • the input port (or port:mode) to consider,

  • the output ports (or port:mode) which we want to analyze,

  • whether we want to analyze in dB scale (True) or linear (False),

  • and the threshold we set for deciding about peaks: -10 (dB) by default. We made this a parameter that you can define at the top of the file.

Measure key parameters

Now we use the spectrum analyzer to measure different properties of the AWG we want to validate against specifications:

  • The peak wavelengths

  • The channel widths

  • The (minimum) insertion loss within the channel

  • The nearest-neighbour crosstalk

  • The crosstalk from further neighbours (excluding nearest-neighbour contributions)

  • The 3dB passbands of the channels

luceda-academy/training/topical_training/design_awg/example_generate_awg.py
print("Measuring Peaks...")
peaks = spec_analyzer.peaks()
peak_wavelengths = [peak["wavelength"][0] for peak in peaks.values()]
peak_error = np.abs(channel_wav - peak_wavelengths)

print("Measuring Crosstalk Bands...")
bands = spec_analyzer.width_passbands(channel_width)
insertion_loss = list(spec_analyzer.min_insertion_losses(bands=bands).values())
cross_talk_near = list(spec_analyzer.near_crosstalk(bands=bands).values())
cross_talk_far = list(spec_analyzer.far_crosstalk(bands=bands).values())
passbands_3dB = list(spec_analyzer.cutoff_passbands(cutoff=-3.0).values())

Plotting and report generation

Next, we write a report containing the analyzed specifications. For later reference, this is stored in a file.

luceda-academy/training/topical_training/design_awg/example_generate_awg.py
report = dict()
band_values = list(bands.values())
for cnt, port in enumerate(output_ports):
    channel_report = {
        "peak_expected": channel_wav[cnt],
        "peak_simulated": peak_wavelengths[cnt],
        "peak_error": peak_error[cnt],
        "band": band_values[cnt][0],
        "insertion_loss": insertion_loss[cnt][0],
        "cross_talk": cross_talk_far[cnt] - insertion_loss[cnt][0],
        "cross_talk_nn": cross_talk_near[cnt] - insertion_loss[cnt][0],
        "passbands_3dB": passbands_3dB[cnt],
    }
    report[port] = channel_report


def serialize_ndarray(obj):
    return obj.tolist() if isinstance(obj, np.ndarray) else obj


report_path = os.path.join(save_dir, "report.json")

# open file for writing, "w"
with open(report_path, "w") as f:
    json.dump(report, f, sort_keys=True, default=serialize_ndarray, indent=0)
print(f"{report_path} written")

Finally, we plot the transmission spectrum and save it.

luceda-academy/training/topical_training/design_awg/example_generate_awg.py
    print("Plotting Spectrum...")
    fig = spec_analyzer.visualize(title="Output spectrum of AWG", show=False)
    for spec_wavelength in channel_wav:
        plt.axvline(x=spec_wavelength)

    for passband in passbands_3dB:
        plt.axvline(x=passband[0][0], color="k", linestyle=":")
        plt.axvline(x=passband[0][1], color="k", linestyle=":")

    plt.xlabel(r"Wavelength [$\mu$m]")

    fig.savefig(os.path.join(save_dir, "output_spectrum.png"), bbox_inches="tight")

    # Plot transmission spectrum
    plt.show()

../../../_images/output_spectrum1.png

Report JSON file: report_finalized.json