{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# FFT + Average Firmware\n",
    "This firmware has one input ADC and one output DAC. ADC is connected to Tile 224 Channel 0. DAC is connected to Tile 229 Channel 3.\n",
    "\n",
    "### Input data path\n",
    "Sampling frequency of the ADC is set to 1.024 GSPS. This covers an input spectrum from -512 to +512 MHz. Inside the ADC block, signal is down-converted and decimated. The user can control the frequency of the NCO that is used for the down-conversion process inside the ADC.\n",
    "\n",
    "Data leaves the ADC block at a speed 8 times slower, which means the signal going into the logic is spanning from -64 MHz to +64 MHz. This data goes into the DDS + CIC block. The output of the DDS + CIC is connected to a harwdare implemented 64k points FFT. The output of the spectrum is fed into the Accumulator block, which will stream the data to the PS side of the FPGA.\n",
    "\n",
    "#### DDS + CIC block\n",
    "This block receives complex data sampled at 128 MSPS, coming from the output of the ADC. This block has two main components: DDS and CIC. DDS is used to apply an extra digital-down conversion process before decimation or spectrum analysis. This DDS has a frequency resolution of 30 mHz.\n",
    "\n",
    "The block has options to select which data is routed into its output. Input data, DDS data or Product data can be selected. Input means the Digital product is by-passed and the ADC data is sent down the chain. DDS option is useful for debugging, as it sends the high-quality, internally generated complex exponential signal to the output. Product is the options that applies the frequency modulation.\n",
    "\n",
    "Data from the previous DDS block is fed into the CIC filter, which applies a varying decimation. The user can chose any decimation from 1 to 1000. For high decimation factors, DDS down-conversion may be needed to fine tune the signal location.\n",
    "\n",
    "#### Windowed FFT block\n",
    "This block computes the hardware FFT with 65536-points. Data is applied a window before entering the FFT computation, which is useful for sine wave inputs. Available windows are \"rect\" and \"hanning\". These could be replace by any function as the coefficients are generated by numpy and uploaded into the window memory.\n",
    "\n",
    "#### Accumulator block\n",
    "This block receives the data from the FFT, computes the absolute value of the complex bins and accumulates the value. The number of accumulated values can be set by the provided functions. The block is always running, which means after the current accumulation is finished, the data can be retrieved and that will automatically launch the next averaging process.\n",
    "\n",
    "### Sine Wave Loop Back Example\n",
    "This example sends a output signal using the DAC, which is read back at the ADC side. The example shows how to properly configure both the DAC and ADC mixer. The user can play with different number of averages, decimation values and DDS frequencies. The example scales down the amplitude of the output tone to allow visualizing quantization noise at the FFT output.\n",
    "\n",
    "### Noise Example\n",
    "This example uses a external wide-band noise source with ~25 dB analog amplification to provide a good noise power to the ADC input. The user can play with different number of averages to check the noise variance is decreased."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2022-06-13T14:19:59.160231Z",
     "start_time": "2022-06-13T14:19:56.090847Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "\n",
       "try {\n",
       "require(['notebook/js/codecell'], function(codecell) {\n",
       "  codecell.CodeCell.options_default.highlight_modes[\n",
       "      'magic_text/x-csrc'] = {'reg':[/^%%pybind11/]};\n",
       "  Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n",
       "      Jupyter.notebook.get_cells().map(function(cell){\n",
       "          if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n",
       "  });\n",
       "});\n",
       "} catch (e) {};\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from fft_low_avg import *\n",
    "\n",
    "import numpy as np\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "from numpy.fft import fft, fftshift"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2022-06-13T14:36:21.362236Z",
     "start_time": "2022-06-13T14:20:00.065821Z"
    }
   },
   "outputs": [
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-2-b44ef4da5e05>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0;31m# Load bitstream with custom overlay\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0msoc\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mTopSoc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'./fft_low_avg.bit'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mignore_version\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mforce_init_clks\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m/home/xilinx/jupyter_notebooks/qick_20220531/20220613/pynq/fft_low_avg.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, bitfile, force_init_clks, ignore_version, **kwargs)\u001b[0m\n\u001b[1;32m    481\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfft\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconfigure\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0maxi_dma_coef\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    482\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfft\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mscale\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfft\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mSCALE_NORM\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 483\u001b[0;31m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfft\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwindow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mwtype\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"hanning\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    484\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    485\u001b[0m         \u001b[0;31m# Accumulator.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m/home/xilinx/jupyter_notebooks/qick_20220531/20220613/pynq/fft_low_avg.py\u001b[0m in \u001b[0;36mwindow\u001b[0;34m(self, wtype)\u001b[0m\n\u001b[1;32m    179\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mwindow\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwtype\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"hanning\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    180\u001b[0m         \u001b[0mw\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgen_window\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mwtype\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 181\u001b[0;31m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mwin\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mw\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    182\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    183\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mgen_window\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwtype\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"hanning\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m/home/xilinx/jupyter_notebooks/qick_20220531/20220613/pynq/fft_low_avg.py\u001b[0m in \u001b[0;36mload\u001b[0;34m(self, win, addr)\u001b[0m\n\u001b[1;32m    216\u001b[0m         \u001b[0;31m# DMA data.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    217\u001b[0m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdma\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msendchannel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtransfer\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbuff\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 218\u001b[0;31m         \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdma\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msendchannel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mwait\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    219\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    220\u001b[0m         \u001b[0;31m# Disable writes.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m/usr/local/lib/python3.6/dist-packages/pynq/lib/dma.py\u001b[0m in \u001b[0;36mwait\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m    206\u001b[0m         \u001b[0;32mwhile\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    207\u001b[0m             \u001b[0merror\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_mmio\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_offset\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m4\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 208\u001b[0;31m             \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0merror\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    209\u001b[0m                 \u001b[0;32mif\u001b[0m \u001b[0merror\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0;36m0x10\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    210\u001b[0m                     raise RuntimeError(\n",
      "\u001b[0;32m/usr/local/lib/python3.6/dist-packages/pynq/lib/dma.py\u001b[0m in \u001b[0;36merror\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m    115\u001b[0m         \"\"\"True if DMA engine is in an error state\n\u001b[1;32m    116\u001b[0m         \"\"\"\n\u001b[0;32m--> 117\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_mmio\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mread\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_offset\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m4\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m&\u001b[0m \u001b[0;36m0x70\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;36m0x0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    118\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    119\u001b[0m     \u001b[0;32mdef\u001b[0m \u001b[0mstart\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mKeyboardInterrupt\u001b[0m: "
     ]
    }
   ],
   "source": [
    "# Load bitstream with custom overlay\n",
    "soc = TopSoc('./fft_low_avg.bit', ignore_version=True, force_init_clks=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2022-06-13T14:19:43.956085Z",
     "start_time": "2022-06-13T14:17:52.644Z"
    }
   },
   "outputs": [],
   "source": [
    "def findPeak(F,Y,fmin=-1,fmax=-1):\n",
    "    if fmin == -1:\n",
    "        fmin = np.min(F)        \n",
    "    if fmax == -1:\n",
    "        fmax = np.max(F)        \n",
    "        \n",
    "    imin = np.argwhere(F <= fmin)\n",
    "    imin = imin[-1].item()\n",
    "    imax = np.argwhere(F >= fmax)\n",
    "    imax = imax[0].item()\n",
    "    \n",
    "    # Find max.\n",
    "    idxmax = np.argmax(Y[imin:imax]) + imin\n",
    "    \n",
    "    # Frequency, Amplitude.\n",
    "    Fmax = F[idxmax].item()\n",
    "    Ymax = Y[idxmax].item()\n",
    "    \n",
    "    return Fmax, Ymax"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#######################################################\n",
    "### Change Decimation Factor and Number of Averages ###\n",
    "#######################################################\n",
    "# Number of averages.\n",
    "N = 100\n",
    "\n",
    "# Decimation.\n",
    "D = 5\n",
    "\n",
    "# Data source.\n",
    "src = \"input\"\n",
    "\n",
    "# Check if accumulator is running...\n",
    "if soc.acc.transmitting():\n",
    "    # Stop block.\n",
    "    soc.acc.stop()\n",
    "    \n",
    "    # Dummy transfer to stop block.\n",
    "    soc.acc.transfer()\n",
    "    \n",
    "# Change decimation/source.\n",
    "if D == 1:\n",
    "    soc.ddscic.outsel(data=src, cic=\"no\")\n",
    "else:\n",
    "    soc.ddscic.outsel(data=src, cic=\"yes\")\n",
    "    soc.ddscic.decimation(D)\n",
    "\n",
    "    \n",
    "# Configure DDS + CIC.\n",
    "soc.ddscic.ddsfreq(f=9.8e6)\n",
    "soc.ddscic.set_qprod(14)\n",
    "#soc.ddscic.set_qcic(soc.ddscic.get_qcic()-2)\n",
    "\n",
    "# Configure Number of Averages.\n",
    "soc.acc.setavg(N=N)\n",
    "\n",
    "# Start accumulator block.\n",
    "soc.acc.start()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "###########################\n",
    "### Loopback Experiment ###\n",
    "###########################\n",
    "# This experiment sends an output tone on the DAC and reads it back on the ADC.\n",
    "# The DAC/ADC mixing frequency is shift by 10 MHz.\n",
    "# Output tone power is set low to emphasize quantization noise.\n",
    "\n",
    "########################\n",
    "### Set Output Mixer ###\n",
    "########################\n",
    "# DAC tile and block.\n",
    "tile = soc.dacs['13']['tile']\n",
    "block = soc.dacs['13']['block']\n",
    "\n",
    "# Frequency and gain.\n",
    "fout = 90\n",
    "g = 0.002\n",
    "soc.mixer.set_freq(tile=tile, block=block, f=fout, btype=\"dac\")\n",
    "soc.iq.set_iq(i=g,q=g)\n",
    "\n",
    "#######################\n",
    "### Set Input Mixer ###\n",
    "#######################\n",
    "# ADC tile and block.\n",
    "tile = soc.adcs['00']['tile']\n",
    "block = soc.adcs['00']['block']\n",
    "\n",
    "# Frequency.\n",
    "fin = 100\n",
    "soc.mixer.set_freq(tile=tile, block=block, f=fin, btype=\"adc\")\n",
    "\n",
    "\n",
    "################################\n",
    "### Get Accumulated Spectrum ###\n",
    "################################\n",
    "# FFT scaling.\n",
    "#soc.fft.scale(soc.fft.SCALE_NORM)\n",
    "soc.fft.scale(0x5555)\n",
    "\n",
    "# Sampling frequency after CIC.\n",
    "fs = soc.fs/D\n",
    "ts = 1/fs\n",
    "\n",
    "# Get data.\n",
    "[x,Navg] = soc.acc.transfer()\n",
    "x = x/Navg\n",
    "print(\"Number of averages = {}\".format(Navg))\n",
    "\n",
    "# Spectrum.\n",
    "r = np.random.normal(scale=1/2**20,size=len(x))\n",
    "#x = x + r\n",
    "Y = x\n",
    "Y = 20*np.log10(Y/np.max(Y))\n",
    "F = np.linspace(-fs/2,fs/2,len(Y))\n",
    "\n",
    "# Find peaks.\n",
    "#[Fmax1, Ymax1] = findPeak(F,Y)\n",
    "#[Fmax2, Ymax2] = findPeak(F,Y,fmin=-4,fmax=-2)\n",
    "#print('Peak 1: f = {}, A = {}'.format(Fmax1,Ymax1))\n",
    "#print('Peak 2: f = {}, A = {}'.format(Fmax2,Ymax2))\n",
    "#print('SNR = {} dB'.format(Ymax1-Ymax2))\n",
    "\n",
    "# Plot.\n",
    "plt.figure(2,dpi=150)\n",
    "plt.plot(F*1000,Y)\n",
    "#plt.plot(Fmax1*1000, Ymax1,'*r')\n",
    "#plt.plot(Fmax2*1000, Ymax2,'*r')\n",
    "plt.xlabel('F [kHz]');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "###########################\n",
    "### Noise Experiment ###\n",
    "###########################\n",
    "# This experiment has a noise source connected into the ADC input.\n",
    "# Noise is amplified to provide a good power into the ADC.\n",
    "\n",
    "#######################\n",
    "### Set Input Mixer ###\n",
    "#######################\n",
    "# ADC tile and block.\n",
    "tile = soc.adcs['00']['tile']\n",
    "block = soc.adcs['00']['block']\n",
    "\n",
    "# Frequency.\n",
    "fin = 250\n",
    "soc.mixer.set_freq(tile=tile, block=block, f=fin, btype=\"adc\")\n",
    "\n",
    "\n",
    "################################\n",
    "### Get Accumulated Spectrum ###\n",
    "################################\n",
    "# FFT scaling.\n",
    "soc.fft.scale(soc.fft.SCALE_MIN)\n",
    "\n",
    "# Sampling frequency after CIC.\n",
    "fs = soc.fs/D\n",
    "ts = 1/fs\n",
    "\n",
    "# Get data.\n",
    "[x,Navg] = soc.acc.transfer()\n",
    "x = x/Navg\n",
    "print(\"Number of averages = {}\".format(Navg))\n",
    "\n",
    "# Spectrum.\n",
    "r = np.random.normal(scale=1/2**20,size=len(x))\n",
    "x = x + r\n",
    "Y = x\n",
    "Y = 20*np.log10(Y/np.max(Y))\n",
    "F = np.linspace(-fs/2,fs/2,len(Y))\n",
    "\n",
    "# Plot.\n",
    "plt.figure(2,dpi=150)\n",
    "plt.plot(F*1000,Y)\n",
    "plt.xlabel('F [kHz]');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
