Tutorial 3: Wideband Spectrometer

Introduction

A spectrometer is something that takes a signal in the time domain and converts it to the frequency domain. In digital systems, this is generally achieved by utilising the FFT (Fast Fourier Transform) algorithm. However, with a little bit more effort, the signal to noise performance can be increased greatly by using a Polyphase Filter Bank (PFB) based approach.

When designing a spectrometer for astronomical applications, it’s important to consider the science case behind it. For example, pulsar timing searches will need a spectrometer which can dump spectra on short timescales, so the rate of change of the spectra can be observed. In contrast, a deep field HI survey will accumulate multiple spectra to increase the signal to noise ratio. It’s also important to note that “bigger isn’t always better”; the higher your spectral and time resolution are, the more data your computer (and scientist on the other end) will have to deal with. For now, let’s skip the science case and familiarize ourselves with an example spectrometer.

Setup

This tutorial comes with a completed model file, a compiled bitstream, ready for execution on SNAP, as well as a Python script to configure the SNAP and make plots. Here

Spectrometer Basics

When designing a spectrometer there are a few main parameters of note:

  • Bandwidth: The width of your frequency spectrum, in Hz. This depends on the sampling rate; for complex sampled data this is equivalent to:

../../_images/bandwidtheq11.png

In contrast, for real or Nyquist sampled data the rate is half this:

../../_images/bandwidtheq21.png

as two samples are required to reconstruct a given waveform .

  • Frequency resolution: The frequency resolution of a spectrometer, Δf, is given by

../../_images/freq_eq1.png,

and is the width of each frequency bin. Correspondingly, Δf is a measure of how precise you can measure a frequency.

  • Time resolution: Time resolution is simply the spectral dump rate of your instrument. We generally accumulate multiple spectra to average out noise; the more accumulations we do, the lower the time resolution. For looking at short timescale events, such as pulsar bursts, higher time resolution is necessary; conversely, if we want to look at a weak HI signal, a long accumulation time is required, so time resolution is less important.

Configuration and Control

Hardware Configuration

The tutorial comes with a pre-compiled fpg file, which is generated from the model you just went through. Copy this over to you SNAP fpg directory, then load it onto your SNAP. All communication and configuration will be done by the python control script called snap_tut_spec.py.

Next, you need to set up your SNAP. Switch it on, making sure that:

  • You have your clock source connected to the ADC (3rd SMA input from left). It should be generating an 80 0MHz sine wave with 0 dBm power.

The snap_tut_spec.py spectrometer script

Once you’ve got that done, it’s time to run the script. First, check that the clock source is connected to clk_i of the ADC. Now, if you’re in linux, browse to where the snap_tut_spec.py file is in a terminal and at the prompt type

 ./snap_tut_spec.py <SNAP IP or hostname> -b <fpg name>

replacing with the IP address of your SNAP and with your fpg file. You should see a spectrum like this:

../../_images/Spectrometer.py_4.81.png

In the plot, there should be a fixed DC offset spike; and if you’re putting in a tone, you should also see a spike at the correct input frequency. If you’d like to take a closer look, click the icon that is below your plot and third from the right, then select a section you’d like to zoom in to.

Now you’ve seen the python script running, let’s go under the hood and have a look at how the FPGA is programmed and how data is interrogated. To stop the python script running, go back to the terminal and press ctrl + c a few times.

iPython walkthrough

The snap_tut_spec.py script has quite a few lines of code, which you might find daunting at first. Fear not though, it’s all pretty easy. To whet your whistle, let’s start off by operating the spectrometer through iPython. Open up a terminal and type:

ipython

and press enter. You’ll be transported into the magical world of iPython, where we can do our scripting line by line, similar to MATLAB (you can also use jupyter if you’re familiar with that). Our first command will be to import the python packages we’re going to use:

import casperfpga,casperfpga.snapadc,time,numpy,struct,sys,logging,pylab,matplotlib

Next, we set a few variables:

snap='192.168.0.1'  # Put your SNAP IP here
katcp_port=7147     # This is default KATCP port
bitstream='snap_tut_spec.fpg'  # Path to the fpg file
sample_rate = 800.0 # Sample rate in MHz
freq_range_mhz = numpy.linspace(0., sample_rate/2, 2048)

Which we can then use to connect to the SNAP board using casperfpga:

print('Connecting to server %s on port %i... '%(snap,katcp_port)),
fpga = casperfpga.CasperFpga(snap)

We now have an fpga object to play around with. To check if you managed to connect to your SNAP, type:

fpga.is_connected()

Let’s set the bitstream running using the upload_to_ram_and_program() command:

fpga.upload_to_ram_and_program(bitstream) 

Next, we need to initialize the ADC. Note in the future this will be done automatically, but for now, we need to:

# Create an ADC object
adc = casperfpga.snapadc.SNAPADC(fpga, ref=10) # reference at 10MHz
# We want a sample rate of 800 Mhz, with 1 channel per ADC chip, using 8-bit ADCs
adc.init(samplingRate=sample_rate, numChannel=1, resolution=8)
adc.selectADC(0)
# Since we're in 4-way interleaving mode (i.e., one input per snap chip) we should configure
# the ADC inputs accordingly
adc.selectADC(0) # send commands to the first ADC chip
adc.adc.selectInput([1,1,1,1]) # Interleave four ADCs all pointing to the first input

Now we need to configure the accumulation length by writing values to the acc_len register. For two seconds of integration, the accumulation length is: 2 [seconds] * 4096 [samples per spectrum] / 800e6 [ADC sample rate]. In nice powers-of-two, this is approximately 2*(2^30)/4096

fpga.write_int('acc_len',2*(2**30)/4096)

Finally, we reset the counters:

fpga.write_int('cnt_rst',1)
fpga.write_int('cnt_rst',0)

To read out the integration number, we use fpga.read_uint():

acc_n = fpga.read_uint('acc_cnt')

Do this a few times, waiting a few seconds in between. You should be able to see this slowly rising. Now we’re ready to plot a spectrum. We want to grab the even and odd registers of our PFB:

a_0=struct.unpack('>1024l',fpga.read('even',1024*4,0))
a_1=struct.unpack('>1024l',fpga.read('odd',1024*4,0))

These need to be interleaved, so we can plot the spectrum. We can use a for loop to do this:

interleave_a=[]

for i in range(1024):
	interleave_a.append(a_0[i])
	interleave_a.append(a_1[i])

This gives us a 2048 channel spectrum. Finally, we can plot the spectrum using pyLab:

pylab.figure(num=1,figsize=(10,10))
pylab.plot(interleave_a)
pylab.title('Integration number %i.'%acc_n)
pylab.ylabel('Power (arbitrary units)')
pylab.grid()
pylab.xlabel('Channel')
pylab.xlim(0,2048)
pylab.show()

Voila! You have successfully controlled the SNAP spectrometer using python, and plotted a spectrum. Bravo! You should now have enough of an idea of what’s going on to tackle the python script. Type exit() to quit ipython.

snap_spec_tut.py notes

Now you’re ready to have a closer look at the snap_spec_tut.py script. Open it with your favorite editor. Again, line by line is the only way to fully understand it, but to give you a head start, here’s a few notes:

Connecting to the SNAP

To make a connection to the SNAP, we need to know what port to connect to, and the IP address or hostname of our SNAP.

Starting from line 47, you’ll see the following code:

    p = OptionParser()
    p.set_usage('spectrometer.py <SNAP_HOSTNAME_or_IP> [options]')
    p.set_description(__doc__)
    p.add_option('-l', '--acc_len', dest='acc_len', type='int',default=2*(2**28)/2048,
        help='Set the number of vectors to accumulate between dumps. default is 2*(2^28)/2048, or just under 2 seconds.')
    p.add_option('-s', '--skip', dest='skip', action='store_true',
        help='Skip reprogramming the FPGA and configuring EQ.')
    p.add_option('-b', '--fpg', dest='fpgfile',type='str', default='',
        help='Specify the fpg file to load')
    opts, args = p.parse_args(sys.argv[1:])

    if args==[]:
        print 'Please specify a SNAP board. Run with the -h flag to see all options.\nExiting.'
        exit()
    else:
        snap = args[0] 
    if opts.fpgfile != '':
bitstream = opts.fpgfile

What this code does is set up some defaults parameters which we can pass to the script from the command line. If the flags aren’t present, it will default to the values set here.

Conclusion

If you have followed this tutorial faithfully, you should now know:

  • What a spectrometer is and what the important parameters for astronomy are.
  • Which CASPER blocks you might want to use to make a spectrometer, and how to connect them up in Simulink.
  • How to connect to and control a SNAP spectrometer using python scripting.

In the following tutorials, you will learn to build a correlator, and a polyphase filtering spectrometer using an FPGA in conjunction with a Graphics Processing Unit (GPU).