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 ROACH, as well as a Python script to configure the ROACH 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/bandwidtheq12.png

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

../../_images/bandwidtheq22.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_eq2.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 bof file, which is generated from the model you just went through (tut3.bof) Copy this over to you ROACH boffiles directory, chmod it to a+x as in the other tutorials, then load up your ROACH. You don’t need to telnet in to the ROACH; all communication and configuration will be done by the python control script called tut3.py.

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

• You have your ADC in ZDOK0, which is the one nearest to the power supply.

• You have your clock source connected to clk_i on the ADC, which is the second on the right. It should be generating an 800MHz sine wave with 0dBm power.

The tut3.py spectrometer script

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

 ./tut3.py <roach IP or hostname> -b <boffile name>

replacing with the IP address of your ROACH and with your boffile. You should see a spectrum like this:

../../_images/Spectrometer.py_4.8.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. The digital gain (-g option) is set to maximum (0xffff_ffff) by default to observe the ADC noise floor. Reduce the gain (decrease the value (for a -10dBm input 0x100)) when you are feeding the ADC with a tone, as not to saturate the spectrum.

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 tut3.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. Our first command will be to import the python packages we’re going to use:

import corr,time,numpy,struct,sys,logging,pylab

Next, we set a few variables:

katcp_port = 7147
roach = 'enter IP address or hostname here'
timeout = 10

Which we can then use in FpgaClient() such that we can connect to the ROACH and issue commands to the FPGA:

fpga = corr.katcp_wrapper.FpgaClient(roach,katcp_port, timeout)

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

fpga.is_connected()

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

fpga.progdev('tut3.bof')

Now we need to configure the accumulation length and gain by writing values to their registers. For two seconds and maximum gain: accumulation length, 2*(2^28)/2048, or just under 2 seconds:

fpga.write_int('acc_len',2*(2**28)/2048)
fpga.write_int('gain',0xffffffff)

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 ROACH 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. tut3.py notes ==

Now you’re ready to have a closer look at the tut3.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 ROACH

To make a connection to the ROACH, we need to know what port to connect to, and the IP address or hostname of our ROACH. The connection is made on line 96:

fpga = corr.katcp_wrapper.FpgaClient(...)

The katcp_port variable is set on line 16, and the roach variable is passed to the script at the terminal (remember that you typed python tut3.py roachname). We can check if the connection worked by using fpga.is_connected(), which returns true or false:

if fpga.is_connected():

The next step is to get the right bitstream programmed onto the FPGA fabric. The bitstream is set on line 15:

bitstream = 'tut3.bof'

Then the progdev command is issued on line 108:

fpga.progdev(bitstream)

Passing variables to the script

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

from optparse import OptionParser

p = OptionParser()
p.set_usage('tut3.py <ROACH_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('-g', '--gain', dest='gain',
type='int',default=0xffffffff,
help='Set the digital gain (6bit quantisation scalar). Default is 0xffffffff (max), good for wideband noise. Set lower for CW tones.')

p.add_option('-s', '--skip', dest='skip', action='store_true',
help='Skip reprogramming the FPGA and configuring EQ.')

opts, args = p.parse_args(sys.argv[1:])

 if args==[]:
     print 'Please specify a ROACH board. Run with the -h flag to see 	all options.\nExiting.'
     exit()
 else:
     roach = args[0]

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 ROACH 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).