Calibrating and Synchronizing ROACH2 ADCs

Introduction

Our ROACH2 is equipped with 2 ASIAA ADC5G, which are 8 bits 5GSPS ADCs. These ADC have high specs in terms dynamic range and bandwidth, but they present some caveats when you want to use them at their full potential, namely they must be calibrated and synchronized before they are used. In this tutorial we'll show how to do both of these procedures and we'll compare the results before and after. For more information on the ADC operation, check the ADC chip (EV8AQ160) datasheet.


ADC5G Calibration

The ADC5G has 3 types of calibrations: MMCM, OGP and INL. All three calibrations must be performed in order to achieve the ADC maximum performance. In this section we'll briefly overview each type of calibrations, but we'll focus mostly in how to perform them.


MMCM Calibration

MMCM stands for Mixed Mode Clock Manager. According to NRAO's ADC5G repository:

[The MMCM calibration] is used for correcting the phase of the capture clock between the four cores. Depending on where the rising edge of the capture clock lies, glitches in the ADC samples can occur. The ADC has the ability to provide test ramps that they use to detect glitches and adjust the MMCM appropriately to eliminate them.

The MMCM calibration must be run first whenever you are trying to run all the ADC calibrations, and it must be rerun for every ROACH power cycle or reprogramming.

To run a MMCM calibration you must first install this package. The model you want to calibrate for requires to have snapshot block at the output of the ADCs, as is done in the Tutorial 2. If you want to calibrate both ADCs, the two need their independent snapshot blocks. Furthermore, if you use the ADC in 'two channel mode' you need a snapshot for each channel, a and c.

To run the MMCM calibration in python do as explained in the README from the repo:

from adc5g import *

set_test_mode(roach, 0)

set_test_mode(roach, 1)

sync_adc(roach)

opt0, glitches0 = calibrate_mmcm_phase(roach, 0, ['snapshot0',])

opt1, glitches1 = calibrate_mmcm_phase(roach, 1, ['snapshot1',])

unset_test_mode(roach, 0)

unset_test_mode(roach, 1)

Where arguments of the calibrate_mmcm_phase() are the following:

  1. The corr's FpgaClient object used to communicate with the ROACH

  2. The ZDOK port of the ADC to calibrate

  3. A list with the snapshot block names for the ADC (one name for one channel mode, and two names for two channel mode).


OGP Calibration

The picture below is a block diagram of the ADC5G chip:

You can see that the 5GSPS ADC is actually implemented using 4 1.25GSPS ADC cores which are delayed by 90° (clock phase) each. This is a very smart way to design high sampling rate ADCs, however, it comes with some difficulties: if any of the ADC cores is unmatched with the others in Offset, Gain or Phase, this will produce harmonic distortions in the ADC output. To understand in more detail the effects of OGP mismatch check this e2v document. The Offset-Gain-Phase (OGP) calibration set some internal registers that control the output and clock of the ADC cores in order to adjust for imbalances that they may present.


INL Calibration

Ideally an ADC transfer function should be straight ladder with steps of equal width as shown here. In reality ADCs present steps of sightly different sized due to the analog components imperfections and mismatch. The Integral Non-Linearity is a measure of the ADC performance degradation due to these imperfections. There are multiple ways to define the INL of an ADC, but they are usually related with the maximum difference between the ideal and actual ADC transfer function. The ADC5G provides additional register to correct INL effects produces by the cores mismatch.


Performing OGP and INL Calibrations

Luckily for you, there are already scripts implemented to perform OGP and INL calibrations for the ADC5G. The most complete calibration routine I have found is the one from NRAO's repository. We took the scripts files relevant for our purposes and add them to the calandigital library, removing unnecessary steps and simplifying the calibration procedure. If you want to use the original NRAO scripts or the ones at calandigital, is your choice. Here we'll explain the calandigital method.

To perform the calibration you need a model with snapshot blocks to receive the ADC raw data, like is done in Tutorial 2. For this calibrations the ADC5G must be used with internal demux 1:1 (to get 8 bits data samples). If you are using the ADC5G block in two channel mode, you need to add one snapshot per channel (the OGP and INL will use only the first channel for calibration).

The calibration script is called calibrate_adc5g.py. Run calibrate_adc5g.py --help to read all the necessary command arguments of the script. Aside of the typical IP and boffile required, the script also needs the names of the snapshots (one or two names) for each ADC5G block, given after the command flags --zdok0snaps and --zdok1snaps. You can choose to do the each individual calibration (using the --do_mmcm/--do_ogp/--do_inl flags) which calibrate the ADCs and save the calibration results in a compressed file (only OGP and INL calibrations produce calibration data), or to load calibration from a previous test (using the --load_opg/--load_inl flags). Loading calibration is useful because you don't need to inject a test tone for loading and a single calibration can be used for several days even after power cycles (MMCM calibration must be done after every FPGA reprogramming but is doesn't need a test tone). An example of a calibration command in an executable file is shown here.

Now connect a signal generator to the ROACH2 ADC input. If you want that the script controls the generator you must set correctly the use the --genname flag to give the instrument IP in VISA format , and make sure that can communicate to the generator via the visa protocol. If you want to manually set the generator simply omit the flag. The test tone frequency and power are controlled (if the --genname flag is used) with the --genfreq and --genpow flags respectively. The recommended frequency for calibration is 10MHz, you can test with different frequencies for better performance. The signal level should be as high as possible without saturating the ADCs, for the ADC5C a signal power of -3dBm to -4dBm should be good. According to NRAO the ADC5G shoud be recalibrated "on the time scale of a few times a year".

Here is a summary of the different calibration characteristics:

Calibration Results

The figures below show a snapshot and spectrum of an uncalibrated ADC model with a 10MHz sine wave as input. Spikes of 4 samples of periods are clearly seen in the snapshots, indicating a OGP mismatch between cores (most predominantly offset mismatch). In the spectrum this is translated to a strong tone at half the bandwidth. ZDOK0 spectrum is affected by additional distortion produced by glitches in the ADC (the ones corrected by the MMCM calibration), ZDOK1 does not present this distortion.

The figures below show the same model after all the calibration has been performed. The snapshot images look much more cleaner, with no noticeable distortion. The spectra also present less distortion. Even though the undesired tones are not eliminated completely, they are reduced around 20dB, that is 20dB of improvement in the Spurious Free Dynamic Range. Cool right?

ADC5G Synchronization

There are many applications in which you would want to measure two signal with two synchronized ADCs, and compare the signals in time in some manner. In radio astronomy this is usually done by computing the phase difference of two input signals. You would expect that two identical ADCs driven by the same clock would be synced in time, that is, their digital output at some clock edge would be two samples taken at the same time. Unfortunately that is not the case, for reasons unknown to me the ADC5G presents a variable digital delay every power cycle or reprogramming. The situation is illustrated in the following image:

Where N != M. In practice we've seen variable delay difference from 1 to 20 (just to be clear, this occur when you have two ADC boards, no for two channels of the same board). To fix this situation your model needs two things:

  • Somehow compute the delay difference between the two ADCs

  • Adjust for that difference to sync up the ADCs

There are two ways to compute the delay, in the time domain and in the frequency domain, so let's review the two.

Time Domain Synchronization

You can compute the ADCs delay by taking an synchronized snapshot from both ADC while a tone is injected, and computing the phase difference of both sines measured. Is important that both snapshots must be synchronized because otherwise you can't be sure the computed phase difference is correct. To get sync snapshots you need a models like this one:

Notice that the 'trig' input of the snapshot blocks are connected to a register. This input control when the block to take a snapshot. To actually take a sync snapshot you need to use the snapshot_get function from the FpgaClient object from the corr package (the one generated with the initialize_roach function form calandigital). You first load a one in the trig register, and then call the function with the the man_trig parameter as True and the arm parameter as False (this is from memory so I might be wrong respect this combination). Then the result of snapshot_get is a snapshot from each ADC in sync. With this info you can device a formula to compute the delay difference. Just a little help, the difference in clock cycles (d) between two sine tones of frequency f_t sampled at frequency F_s is:

Where p_d is the phase difference between tones, that you can compute from the snapshots with a FFT function for example.

To actually apply the delay computed the delay_wideband_prog is used after every ADC output in the model. This block is basically a programmable delay that can be set via a register. You simply set the correct delay value to the delay_wideband_prog, and you are done. Since either ADC can be ahead of the other, be sure to set the delay to the correct ADC (which depends on how you define the phase difference). Also be sure to input a signal to both ADCs with 0° phase difference, this is easily done with a simple splitter. Unfortunately I done have a script for this type of synchronization so you'll have to implement your own.

Frequency Domain Synchronization

In the frequency domain synchronization method, the delay difference between the ADCs is computed inside the FPGA using a correlator model. A correlator is a system that not only computes the FFT power of two input signals, but also conjugated multiplication of the FFT of the signals (which is the cross-correlation of the signal in the frequency domain). Complex multiplication gives you for free the angle difference between two complex numbers, and therefore the phase difference of the frequency channels. If you sweep a tone though different frequencies and plot the phase vs frequency between two ADC with different delays you get a plot like this:

This is because the same clock delay in an ADC translates into different phase differences for each frequency. By computing the slope of this curve (properly unwrapped around the 360°), one can get the appropriate delay to set in the ADCs. The advantage of this method is that it doesn't require exact 0° phase difference at the inputs, so if you have a receiver that has, for example, a 90° phase difference that input into the FPGA, you can still synchronize the ROACH without switching the receiver (assuming you can inject a tone into the receiver). Another advantage is that is a more precise synchronization, as it used the delay difference info at many frequencies, instead of just one frequency.

For this method a script is already available in the calandigital library by the name of synchronize_adcs.py. Notice that the model necessary for this synchronization is rather complicated, that must compute the power FFT and the conjugated multiplication of FFTs. The advantage is that standard astronomical correlators already have these implemented. For an example of a correlator model check here. Check synchronize_adcs.py --help to to understand the necessary flags for synchronization. The most important ones are:

  • --zdok0brams/--zdok1brams: list of brams where the power spectral data is saved for ADC0 and ADC1

  • --crossbramsreal/--crossbramimag: list of brams where the cross-spectrum (complex conjugated) is saved, real and imaginary part

  • --delayregs: list of regs where the delay for the delay_wideband_prog is loaded.

  • --startchnl/--stopchnl/--chnlstep: start, end and step of the channels sweep to be made for the synchronization

The rest of the flags are similar to the other scripts. For this method the same delay_wideband_prog block to apply the delays is used. Also in this case is mandatory to use and control a signal generator via VISA, the flags for that are genname, genpow and lofreq. The script iterates until it achieves a reasonably flat phase vs freq plot. to compute the applied delay a similar formula from the time synchronization is used:

Where Δϕ/Δf represents the slope of the phase vs frequency curve and Fs is the sampling frequency.

As an example, the figures below show a snapshot comparison between ADCs before and after synchronization. It can be seen that before synchronization the ADC are sightly out of phase. After synchronization the ADC are perfectly in phase.

Before synchronization (19 samples desync):

After synchronization (0 samples desyc):

The common practice when perfoming phase sensitive experiment in ROACH2 is the following:

  1. First, calibrate the ADCs in MMCM, OGP, and INL (or load previously computed calibrations).

  2. Then, Synchronize ADC injecting a tone of known frequency using a splitter.

  3. Perform your experiment.