Objective
The world is analog, but it is best processed digitally, where the processing is immune from analog effects such as component tolerance, aging, temperature effects and drift. To enable that, we must implement a conversion between the analog and digital domains using ADC and DAC convertors.
The behavior of these converters is not as simple as one might at first consider, however. This Lab Note outlines, explains and discusses the performance considerations we need to understand to be able to assess how an ADC or DAC will operate in practice.
Context
There are many elements that have an impact on how an ADC or DAC performs overall, so we want to ensure we understand what they are. This allows us to determine the end-to-end performance of an FPGA design, which is critical for communications, RADAR, electronic warfare and instrumentation and measurement.
In this Lab Note we are going to explore what these parameters are, and how we are able to measure them using an FPGA. Along the way we will see how a perfectly healthy converter can measure just 6.25 effective bits, and why that number and the true figure of 11.26 bits are both correct.
FPGAs are ideal for applications which use ADC and DACs, and their flexible nature allows developers to load in test designs which can be used to characterize ADC and DAC implementation in the design.
Alternatively, FPGAs can be used to determine the performance of several ADC or DACs in consideration for a project. This is where FPGA development boards come in, especially with the FMC and SYZYGY interfaces which allow many different types of ADC and DAC to be easily connected to an FPGA and used for testing.
Key concepts
When selecting an ADC or DAC for an application, we must consider a range of data sheet parameters. We know the implementation on the board may impact these figures but we cannot improve upon the manufacturer’s performance figures. The key parameters are:
- Maximum sample frequency
- Signal to Noise Ratio (SNR)
- Spurious Free Dynamic Range (SFDR)
- Effective Number of Bits (ENOB)
- Total Harmonic Distortion (THD)
The sampling frequency is pretty simple, and is the maximum rate at which the ADC can sample its input. The ideal SNR is set by the quantization noise, which is assumed to be uncorrelated with the input signal and can be theoretically determined using the equation:
SNR = 6.02N + 1.76 dB Where N is the resolution
The actual SNR can be determined during the system test by taking an FFT of the output and measuring between the value of the input signal and the noise floor.
The SFDR is the ratio between the input signal and the next highest peak, which is usually a harmonic of the fundamental. Normally the SFDR is given in terms of dBc, and will degrade as the input signal power reduces.
The THD is the ratio of the summed power of the harmonics, typically the second through to the tenth, to the power of the fundamental. Closely related to both is the SINAD, the signal to noise and distortion ratio, which combines the noise and the harmonic distortion into a single figure. From the SINAD the engineer can calculate the effective number of bits using the ENOB equation:
ENOB = (SINAD – 1.76) / 6.02
It is worth noting that the SFDR is more than a single figure of merit, because the location of the worst spur is itself diagnostic. A spur at a harmonic of the input points to non-linearity in the front end or the converter transfer function. A spur at Fs/2 – Fin is the classic signature of a sample alternation or interleave mismatch, where alternate samples see slightly different gain, offset or timing.
A spur which stays at a fixed frequency as the input moves is coupling from an independent source such as a switching supply or a clock harmonic. Reading the spur locations tells us where in the signal chain to look, a point which will prove useful later in this Lab Note.
When performing this testing, we must take care to ensure that the FFT used is correctly sized to ensure that the noise floor shown is correct and not inadvertently incorrect due to the size of the FFT selected. The FFT processing gain spreads the noise across NFFT/2 bins, lowering the apparent floor of the plot below the carrier.
The depth of the displayed FFT noise floor below full scale for an ideal converter is given by:
FFT Noise Floor = 6.02N + 1.76 dB + 10 LOG10(FFT Size / 2)
A common mistake is to read the SNR directly as the distance between the fundamental and the displayed floor of the FFT plot. The displayed floor includes the processing gain term above, so a deeper FFT pushes the plotted floor down without changing the SNR at all. The SNR must instead be computed by integrating the noise power across all of the bins, which is exactly what the analysis software described later does.
These tests should be performed using a single tone test, normally a simple sine wave to reduce the complexity of the output spectrum. To get the best results, performing coherent samples of the output is a must. Coherent sampling occurs when there is an integer number of cycles within the data window. For this to be the case, the following applies:
Fin / Fs = Ncycles / NFFT
Test setup and methodology
Interfacing the ADC or DAC
Interfacing an ADC or DAC uses a variety of different standards, which can be either serial or parallel. For ADC and DACs with very high sampling rates, a high-speed serial link is used that leverages multigigabit bit transceivers and standards such as JESD-204B.
Intermediate sampling rates can leverage differential links such as LVDS to ensure signal integrity can be maintained. Slower sampling systems, meanwhile, leverage traditional LVCMOS single-ended interfaces, in parallel.
The Zmod Scope used in this Lab Note sits in the intermediate category but retains a parallel LVCMOS interface, which is a large part of its appeal for education and bring-up. Every signal on the bus can be probed with a standard oscilloscope or logic analyzer, and the capture logic is simple enough to understand completely.
Determining the ADC performance parameters
It takes four logical steps to determine the ADC performance parameters:
- Generate a clean single tone input into the ADC
- Capture several thousand samples from the ADC into a buffer in the FPGA
- Read out the captured samples
- Post-process using Python or an application such as MATLAB or Simulink
The data is captured within the FPGA and then read out, rather than being processed in the FPGA, because ADC testing is intended as a characterizing task. It’s something we do in the engineering development phase or during manufacturing test. We do not need a complex FPGA solution for high-performance, real-time signal processing of the captured data.
Good design practice leveraging standard interfaces for FPGA IP blocks such as AXI4 and AXI Streaming also enables the ADC interface block developed for the main application to be reused in the ADC test application.
When it comes to testing the ADC, we can combine the ADC capture block with a large FIFO made from Block RAMs within the FPGA. These Block RAMs can then be read out in several ways:
- Using a processor core (either soft or hard) to read out the BRAM and send it out over Ethernet
- Leveraging a protocol which allows a standard interface such as I2C, SPI, QSPI, etc, to access and read the internal AXI network and read out the data stored in the buffer
- Leveraging a JTAG to AXI master IP core which again provides AXI network access over JTAG
Hardware setup
A good example setup is the following design, which uses the Adiuvo Explorer board and the Digilent Zmod Scope. This offers an intermediate sampling rate ADC capable of 105 MSPS, presenting its 14-bit samples over a standard parallel LVCMOS interface. The dual channel AD9648BCPZ-105 is mounted onto the SYZYGY Pod, with clocking and configuration provided by the FPGA.
Figure 1: A bench setup showing the Adiuvo Explorer board with Zmod Scope and Analog Discovery 3 source
The setup uses the Adiuvo Explorer board to capture the samples along with an RPI (in this case a RP500+) to read out the samples from the sample buffer over SPI, and analyze the data. This is similar to a production test environment, where the calculation of the results is performed by a machine external to the FPGA.
The architecture of the design is outlined below. The scope controller IP core is instantiated, configured and clocked / reset as necessary. The output from this scope controller on an AXI Stream bus is the sample data.
This sample data is stored in a stream FIFO which enables data to be written in using AXI Stream but read out over AXI4 (Lite). This allows a simple protocol algorithm to convert a series of commands received over an SPI link to AXI transactions and read out the sample buffer. This dual personality FIFO is the trick which makes the whole approach work. The write side runs at the full sample rate for the duration of the capture, while the read side can be as slow as we like, with no rate-matching or flow control logic to design, because the record is frozen in the buffer the moment it fills.
Figure 2: A Vivado Block Design showing the full data-acquisition path for the Zmod ADC
Once the sample buffer has been read out it is possible to then analyze the results, in this case using Python scripts on the RPI 500+.
Software workflow
The software is split into three scripts, one for each stage of the test: stimulus, capture and analysis. Splitting the flow this way means the stimulus and capture can run on different machines if required, and the analysis can be re-run on a saved capture as often as we like without touching the hardware.
The first script, ad3_wavegen.py, drives the Analog Discovery 3 waveform generator using the Digilent WaveForms SDK, which is accessed from Python via ctypes. The script configures a single sine wave on W1 with the requested frequency, amplitude and offset. One useful detail is that the SDK is instructed to leave the output running when the device handle is closed, so the tone persists after the script exits and the capture can then be run independently.
The script also includes a helper which snaps the requested frequency to the nearest coherent frequency, Fin = M x Fs / NFFT with M odd, following the coherent sampling condition described above. In this setup, the AD3 and the FPGA sampling clock are free-running with respect to each other, so true coherence cannot be guaranteed. Instead, the analysis applies a window, but getting close to a coherent bin keeps the spectral leakage manageable.
The second script, adc_capture.py, runs on the RPi 500+ and performs the capture over SPI. It first writes to the control registers to reset the AXI Stream path and the sample FIFO, which arms a fresh one-shot capture. The FIFO then fills with contiguous samples at the full 40 MSPS sample rate, taking around 1.6 ms for the 65536 deep buffer, after which it holds its contents.
The script then drains the requested number of words, one 32-bit word per SPI transaction. Each transaction sends the read command followed by dummy bytes in a single transfer so that chip select remains low throughout, with the payload returned in the final four bytes. The data is transmitted least-significant bit first, with each byte bit-reversed in software before use.
The ADC sample occupies the upper 14 bits of the 32-bit word and is sign extended as a two’s complement value, giving samples in the range -8192 to +8191 with mid-scale at zero. An important point here is that the SPI drain rate has no effect on the signal fidelity. The samples were frozen in the FIFO at the full sample rate, so it does not matter that reading them out takes tens of seconds. What matters is that the record is contiguous.
The script saves the raw words and the extracted samples to a NumPy .npz file along with the sample rate and resolution, and performs two sanity checks before we spend time on analysis: the span of the data is checked to catch a stuck or empty FIFO, and the extremes are checked against the 14 bit rails to catch clipping.
The third script, adc_analysis.py, performs the measurement itself. The mean is removed, a window is applied and the FFT taken. The default window is the Blackman Harris window, chosen because the capture is not coherent; a rectangular window option is provided for setups where the source and sampling clocks are locked. The fundamental is located as the largest non-DC bin and its power integrated over a few bins either side to collect the window leakage.
Harmonics two through ten are then located, with each harmonic frequency folded back into the first Nyquist zone, since with a 1 MHz input at 40 MSPS the higher harmonics alias back in band. Everything not claimed by the DC term, the fundamental or the harmonics is counted as noise. From these powers the script reports the SNR, THD, SINAD and SFDR, along with the ENOB. The ENOB is reported twice, once directly from the SINAD and once corrected to full scale using ENOB = (SINAD – 1.76 + 20 log10(FS / A)) / 6.02, where A is the measured amplitude of the tone. As we will see in the results, this correction proved to be very useful.
Finally, the script produces an annotated spectrum plot normalized to the carrier in dBc, with the harmonic locations marked.
Results
On the testbench, the first captures produced a spectrum which was flat noise from DC to Nyquist, with no visible fundamental at all and a reported SNR of -18 dB. The cause was simple but instructive: the analysis requested 4096 samples while the FIFO at that point held far fewer, so the first portion of the record was a valid sine wave and the remainder was garbage read from an empty FIFO. Plotting the record in the time domain showed exactly where the valid data ended.
The lesson here is that the FFT-based metrics are only meaningful if every sample in the record is a contiguous capture at the sample rate, and the record length must never exceed the buffer depth.
Figure 3: The flat noise spectrum from the over length capture, N=4096 against the shallow FIFO
With the record length matched to the buffer, the next capture of a 1 MHz tone produced a clean fundamental but apparently poor numbers, with an SNR of 40.3 dB and an ENOB of just 6.25 bits. However, the reported input amplitude was only 254 counts, some 30 dB below full scale, and the full scale corrected ENOB was 11.26 bits.
This told us the converter itself was healthy and the problem was the test level. The arithmetic pointed directly at the cause: driving 0.8 V into the front end and reading 254 counts corresponds almost exactly to the Zmod Scope low gain, plus or minus 25 V, input range. The relay-controlled front end was sitting in its low gain state, attenuating the test tone 25 to 1. This is a good illustration of why the full-scale correction matters. The raw ENOB punishes the test setup, not the converter, because the noise floor of the converter is fixed while the signal power is not. Every dB given away in input amplitude comes straight off the SINAD.
With the front end switched to the high gain, plus or minus 1 V, range and the FIFO deepened to 65536 samples, the capture achieved -2.73 dBFS. The deeper record brings around 45 dB of FFT processing gain, placing the displayed noise floor near -115 dBc and resolving the spectrum into 610 Hz bins.
The results were an SNR of 55.5 dB, a THD of -76.4 dB with every individual harmonic below
-82 dBc, an SFDR of 63.1 dBc, and an ENOB of 8.92 bits. These numbers split the signal chain neatly in two. The distortion performance is excellent, telling us the front end and the converter transfer function are linear. The noise performance, however, is around 15 dB short of the approximately 74 dB the AD9648 data sheet leads us to expect, and the ENOB is correspondingly low.
Figure 4: 65536 point spectrum at -2.73 dBFS showing the Fs/2 – Fin spur at 18.999 MHz
The report contains a strong clue as to why. The worst spur sits at 18.999 MHz with a 1 MHz input, which is exactly Fs/2 – Fin. A spur at this frequency is the classic signature of a sample alternation mismatch. The AD9648 is a dual-channel device which multiplexes both channels onto a shared data bus, and if the capture logic in the FPGA has a timing or phase issue on that bus, for example the DDR capture registers sampling close to a data transition, the result is exactly what we observe: a spur at Fs/2 – Fin together with a raised noise floor from intermittent bit errors.
Investigation of this is ongoing, using three simple experiments. First, an idle channel capture with the input terminated measures the noise of everything except the source. Second, screening the record for sample-to-sample jumps larger than the sine wave can produce identifies corrupted samples directly. Third, moving the input frequency and observing whether the spur tracks Fs/2 – Fin or stays fixed separates a capture artefact from an independent fixed-frequency spur such as a supply harmonic.
It is also worth being honest about the limits of this test setup. The AD3 waveform generator is a general-purpose instrument and its own noise and distortion will ultimately bound what can be measured, typically some way short of the converter data sheet figures. For a formal characterization, a low-distortion signal generator followed by a passive band pass filter at the test frequency would be used. For an educational demonstration of the method, and as this Lab Note shows, for finding real integration issues in the capture path, the AD3 is more than sufficient.
Conclusion
This Lab Note has demonstrated a complete ADC dynamic test flow using an FPGA, featuring a single tone stimulus; a one-shot contiguous capture into Block RAM at the full sample rate; a slow readout over SPI; and FFT-based analysis producing SNR, THD, SINAD, SFDR and ENOB. Perhaps the most valuable outcome is not the numbers themselves, but what the process revealed at each step.
The flat spectrum exposed a record contiguity problem, the full-scale corrected ENOB separated a healthy converter from a front-end range error, and the location of the worst spur pointed directly at the data capture timing as the remaining suspect.
This is the real purpose of ADC testing: the measurements characterize the whole signal chain, and reading them carefully tells us which part of that chain to look at next. The investigation of the remaining noise shortfall and the Fs/2 – Fin spur is underway using the three experiments described above, and the findings, along with the fix, will be the subject of a follow-up Lab Note.
Revision history
August 2026 – v1.0 – Initial release





