<jupyter_start><jupyter_text>Spectrum Analysis on RFSoC----Please use Jupyter labs http://board_ip_address/lab for this notebook.This notebook explores the frequency spectrum of the RF DCs. Aims* Learn how to inspect and manipulate captured data from the RF ADCs.* Apply a window to time domain data as a preprocessing step for spectrum analysis.* Create a spectrum plot for visualization. Table of Contents* [Hardware Setup](hardware-setup)* [Frequency Domain Analysis](frequency-domain-analysis)    * [Capture RF ADC Data](capture-rf-adc-data)    * [Time Domain Windowing](time-domain-windowing)    * [Convert to the Frequency Domain](convert-to-the-frequency-domain)    * [The Power Spectral Density](the-power-spectral-density)    * [Plotting the Spectrum](plotting-the-spectrum)* [Conclusion](conclusion) References* [Xilinx, Inc, "USP RF Data Converter: LogiCORE IP Product Guide", PG269, v2.3, June 2020](https://www.xilinx.com/support/documentation/ip_documentation/usp_rf_data_converter/v2_3/pg269-rf-data-converter.pdf)* [<NAME>.; <NAME>. (1965). "An algorithm for the machine calculation of complex Fourier series". Mathematics of Computation. 19 (90): 297–301.](https://www.ams.org/journals/mcom/1965-19-090/S0025-5718-1965-0178586-1/home.html) Revision History* v1.0 | 08/02/2021 | First notebook revision.----  Hardware Setup Your RFSoC2x2 board is dual-channel. Notice that there are only4 SMAs on your board, labeled DAC1, DAC2, ADC1, and ADC2.Only two sets of RF ADC and RF DAC channels are accessible onthe RFSoC2x2 board.You should create the loopback connection using SMA cablesas shown below:* Channel 0: DAC2 to ADC2* Channel 1: DAC1 to ADC1See the image below for a demonstration.      Warning:In this demo we are transmitting signals via cables.This device can also transmit wireless signals. Keep in mind that unlicensed transmission of wireless signalsmay be illegal in your location. Radio signals may also interfere with nearby devices,such as pacemakers and emergency radio equipment. If you are unsure, please seek professional support.----  Frequency Domain Analysis Frequency domain analysis is the process of inspecting propertiesof the radio spectrum. There are several spectrum analysis algorithms and tools;the most common is the FFT.In this notebook, we will use the FFT on datacaptured from the RF ADC frontend to analyze the spectral properties of thereceived signal. The RF DAC will be used to provide stimulus throughloopback and the mixer frequency can be changed to generate differentinput tones.Start by downloading the base overlay.<jupyter_code>from pynq.overlays.base import BaseOverlay

base = BaseOverlay('base.bit')<jupyter_output><empty_output><jupyter_text>The RFSoC2x2 has a sophisticated clocking network, which can generatelow-jitter clocks for the RF DC Phase-Locked Loops (PLLs). The base overlayhas a simple method to initialize these clocks. Run the cell below to setthe LMK and LMX clocks to 122.88MHz and 409.6MHz, respectively.<jupyter_code>base.init_rf_clks()<jupyter_output><empty_output><jupyter_text>Setting up the TransmitterThere are several interesting spectral properties to inspect.We will set up each RF DAC channel with a carrier frequency first.Then we can set the each channel to different frequencies and amplitudesto make our example more interesting.<jupyter_code>def set_transmitter_channel(channel, enable, gain, frequency):
    channel.control.enable = enable
    channel.control.gain = gain
    channel.dac_block.MixerSettings['Freq'] = frequency

set_transmitter_channel(base.radio.transmitter.channel[0], True, 0.8,  200)
set_transmitter_channel(base.radio.transmitter.channel[1], True, 0.1, 1500)<jupyter_output><empty_output><jupyter_text>The above code cell has set the transmitter for each channel as follows:* Transmitter Channel 0    * Frequency: 200 MHz    * Gain: 0.8* Transmitter Channel 1    * Frequency: 1500 MHz    * Gain: 0.15    Lets look out for these frequencies when we finally reach the spectrum analysis section.  Capture RF ADC Data In order to perform spectral analysis, we need to capture data from the RFADC. We can capture data using the radio hierarchy block.<jupyter_code>number_samples = 1024
sample_frequency = 2048e6
cdata = []
for i in range(0, len(base.radio.receiver.channel)):
    cdata.append(base.radio.receiver.channel[i].transfer(number_samples))<jupyter_output><empty_output><jupyter_text>Great, let's now create time domain plots for each segment of data capturedfrom the RF ADC. In the cell below, we have created a simple helper function `plot_complex_time`which can help us quickly create new complex time domain plots. We will use `plotly`, `numpy`, and `ipywidgets` to support our visualization.<jupyter_code>import plotly.graph_objs as go
import numpy as np
import ipywidgets as ipw

def plot_complex_time(data, n=number_samples, fs=sample_frequency, 
                      title='Complex Time Plot'):
    plt_re_temp = (go.Scatter(x = np.arange(0, n/fs, 1/fs),
                              y = np.real(data), name='Real'))
    plt_im_temp = (go.Scatter(x = np.arange(0, n/fs, 1/fs),
                              y = np.imag(data), name='Imag'))
    return go.FigureWidget(data = [plt_re_temp, plt_im_temp],
                           layout = {'title': title, 
                                     'xaxis': {
                                         'title': 'Seconds (s)',
                                         'autorange' : True},
                                     'yaxis': {
                                         'title': 'Amplitude (V)'}})

tfigs = []
for i in range(0, len(base.radio.receiver.channel)):
    tfigs.append(plot_complex_time(
            data=cdata[i], 
            title=''.join(['Time Domain Plot of ADC Channel ', str(i)])))

ipw.VBox(tfigs)<jupyter_output><empty_output><jupyter_text>Note it is quite difficult to look at the plots and determine the signal'soscillation frequency. This is where spectral analysis can prove to be veryuseful. Let's first apply a simple window to our time domain signals.  Time Domain Windowing Windowing is a useful tool in spectral analysis of a time domain signal. This technique is used on the time domain signal before FFT. Why do we need windowing? The FFT assumes that the input signal is periodic.To prevent unwanted frequency spurs and spectral leakage, the start of thesignal should also connect to the end of the signal.In this example, we will use a simple Blackmanfilter to illustrate windowing.<jupyter_code>window = np.array(np.blackman(number_samples)[:])<jupyter_output><empty_output><jupyter_text>The blackman window can be easily applied to the time domain datathrough element-wise multiplication. This is why the window created in thecell above, is the same size as our time domain data.<jupyter_code>wdata = []
wfigs = []

for i in range(0, len(base.radio.receiver.channel)):
    wdata.append(cdata[i]*window)
    wfigs.append(plot_complex_time(
            data=wdata[i], 
            title=''.join(['Windowed Plot of ADC Channel ', str(i)])))

ipw.VBox(wfigs)<jupyter_output><empty_output><jupyter_text>As you can see, the windowed data now starts and ends around an amplitude of 0.This reduces spectral leakage and unwanted spurs when we later performspectral analysis.  Convert to the Frequency Domain Converting to the frequency domain is very easy using the FFT. The `numpy`package performs most of the hard work for us. Let's obtain the magnitude ofthe frequency spectrum.<jupyter_code>fdata = []
for i in range(0, len(base.radio.receiver.channel)):
    fdata.append(np.fft.fftshift(np.fft.fft(wdata[i])))<jupyter_output><empty_output><jupyter_text>The FFT algorithm will always return the positive andnegative frequencies in reverse order. So we should swap these aroundusing `np.fft.fftshift` as given in the above cell. After shifting the frequency spectrum, we can plot to inspect the results.<jupyter_code>def plot_complex_spectrum(data, N=number_samples, fs=sample_frequency, 
                          title='Complex Spectrum Plot', units='dBW', fc=0):
    plt_temp = (go.Scatter(x = np.arange(-fs/2, fs/2, fs/N) + fc,
                           y = data, name='Spectrum'))
    return go.FigureWidget(data = plt_temp,
                           layout = {'title': title, 
                                     'xaxis': {
                                         'title': 'Frequency (Hz)',
                                         'autorange': True},
                                     'yaxis': {
                                         'title': units}})

magfigs = []
for i in range(len(base.radio.receiver.channel)):
    magfigs.append(plot_complex_spectrum(
            data=abs(fdata[i]),
            title=''.join(['Frequency Magnitude Plot of ADC Channel ', str(i)]),
            units='|Y(f)|',
            fc=round(abs(base.radio.receiver.channel[
                        i].adc_block.MixerSettings['Freq']))*1e6))

ipw.VBox(magfigs)<jupyter_output><empty_output><jupyter_text>The code cell above introduced a new custom function `plot_complex_spectrum`to help us plot complex frequency domain plots. This function has an optionalargument `fc` to shift the plot's x-axis around the RF ADC block center frequency.Inspecting the plots, you will see a peak around 200 MHz on channel 0, and 1500 MHz on channel 1. Notice the power of the signal for each channel.  The Power Spectral Density A common way engineers typically inspect the frequency spectrum is by calculating the Power Spectral Density (PSD).$PSD[n] = 10 \log_{10}(\frac{{|y[n]|^{2}}}{f_{s} \sum^{N_{w}}_{n=0}w^{2}[n]})$where $w$ is the window vector, $f_s$ is the sample frequency, $N_{w}$ is the length of the window, and $y_{n}$ is the output of the FFT derived above.Run the code cell below to calculate the PSD.<jupyter_code>def freq_to_psd(data, N=number_samples, fs=sample_frequency, 
                window=np.array(np.ones(number_samples)[:])):
    psd = (abs(data)**2)/(fs*np.sum(window**2))
    return 10*np.where(psd > 0, np.log10(psd), 0)

psd = []
for i in range(0, len(base.radio.receiver.channel)):
    psd.append(freq_to_psd(data=fdata[i], window=window))<jupyter_output><empty_output><jupyter_text>Plotting the Spectrum Finally, we can do the frequency plots. We will plot the PSD similar tobefore and inspect the spectrum for each channel.<jupyter_code>psdfigs = []
for i in range(0, len(base.radio.receiver.channel)):
    psdfigs.append(plot_complex_spectrum(
            data=psd[i],
            title=''.join(['Power Spectral Density Plot of ADC Channel ',
                           str(i)]),
            units='dB',
            fc=round(abs(base.radio.receiver.channel[
                        i].adc_block.MixerSettings['Freq']))*1e6))

ipw.VBox(psdfigs)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.0921539600296077, 'fraction_numerical': 0.01582161361954108, 'mean_word_length': 3.5956632653061225, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 0, '>': 48, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 19, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '413248', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 3259, 'n_tokens_neox': 3010, 'n_words': 1097, 'path': 'board/RFSoC2x2/base/notebooks/rfdc/02_rf_spectrum_analysis.ipynb', 'repo_name': 'Zacarhay/RFSoC2x2-PYNQ', 'size': '10824'}