Anton Potočnik

                            at ETH Zürich

Red Pitaya FPGA Project 4 – Frequency Counter


On the way to a powerful acquisition systems let us make a quick detour and create a useful and simple project – the Frequency Counter. Yes, to measure frequencies one can use Red Pitaya’s native apps such as Oscilloscope or Spectrum Analyzer, however, our program will be able to determine frequencies with much higher resolution and at the same time we will learn how to use Red Pitaya’s 125 Msamples/s 14-bit ADC and DAC peripherals in the FPGA program.

This project contains two separate parts: the data acquisition part with frequency counter and LED data display and the signal generator part. To communicate with these two parts we use the General Purpose IO block for setting configuration values and reading the counter output.

The frequency counter will be implemented in the reciprocal counting scheme where a period of time of a predefined number of signal oscillations is measured and then inverted and divided by the number of oscillations. Such scheme can yield a much better frequency resolution, especially for low frequency signals, compared to the conventional method where number of signal cycles are counted in a predefined gate time.

Building the Project

To start off download the project from the github. First, build the necessary custom IP cores by navigating to /redpitaya_guide base folder in Vivado’s tcl console and execute

source make_cores.tcl

This will create all custom IP cores in the /redpitaya_guide/tmp/cores folder. In this project we will use axis_red_pitaya_adc and axis_red_pitaya_dac IP cores from Pavel Demin which are handling fast ADCs and DACs peripherals on the Red Pitaya board. I only added a global clock buffer to the axis_red_pitaya_adc core for an optimal performance under Vivado 2016.3 and higher version.

Once the custom cores are created build the project by appropriately modifying the make_project.tcl script and execute it in Vivado’s tcl console with

source make_project.tcl

Project overview

The full block design of the frequency counter project is composed of six parts: Processing System, GPIO, Signal Generator, Data Acquisition, Frequency Counter and Signal Decoder block as shown in the figure below.

Block Design Overview

These parts will be described in detail below. You can skip the lengthy description and go directly to the fun part at the end of the post.

Processing system

Let’s start with the most common part – the processing system IP core. Together with the AXI Interconnect and Processor System Reset block these are the most common blocks in most of the Zynq 7000 FPGA applications. Since they take quite some space and have a lot of connections we will join them in a single hierarchy block, so they will take less space and make block design more transparent. To create a hierarchy select desired blocks, right click and select Create Hierarchy. From now on we will put in hierarchies most of the blocks with related functionality.

Processing System 7 Hierarchy

Processing System 7 Hierarchy

General Purpose Input-Output Core

In the previous project we have learned how to write and read from the FPGA logic. We will use the same approach here for setting configurations such as number of cycles and signal generator’s phase increment. We will use the first GPIO port as an input to make results of the frequency counter available to a program running on the Linux side. Second GPIO port will be used as an 32-bit output port containing 27-bit phase_inc value for the signal generator and 5-bit log2Ncycles value for the frequency counter:

gpio2_io_o[31:0] = 31[ {27-bit phase_inc} {5-bit log2Ncycles} ]0.

If you ever need more configuration output bits you can use Pavel Demin’s axi_configuration IP core with a custom number of bits in a single output port. axi_configuration can be found in the redpitaya_guide/core folder, which is automatically created with the make_cores.tcl script as described above.

Signal Generator

Signal Generator hierarchy creates a sin(ωt) and cos(ωt) signals at the two DAC output ports with a user defined frequency. The analog signal is generated with three blocks: DDS compiler for calculating 14-bit sinusoidal values, Clock Wizard to create a double clock frequency which allows setting the two DAC channels on each input clock cycle and AXI-4 Stream Red Pitaya DAC core for setting signal values to the external DAC unit. We will use 125 MHz adc_clock as input clock to achieve 125 Msamples/s data rate.

Signal Generator Hierarchy

Frequency, amplitude and other parameters can be set in the Direct Digital Synthesizer (DDS) re-customization dialog. Current DDS core settings will create sin(ωt) on one and cos(ωt) on the other DAC channel with maximal amplitude of +/- 1V (maximal range) on both channels.

The synthesized signal frequency is in the DDS compiler determined by a phase increment value at each clock cycle. A nice description of the signal synthesizer operation can be found in the DDS compiler product guide. The signal frequency can be set fixed at the design stage by choosing Fixed Phase Increment in the DDS re-customization dialog. In this case the dialog automatically calculates the required constant phase increment for a desired frequency and frequency resolution. Note that the output frequency will be a divisor of the clock frequency and might therefore deviate from the requested frequency.

Since we want to change the frequency during an operation we choose Streaming Phase Increment in the re-customization dialog, which requires a phase increment value to be continuously supplied to the S_AXIS_PHASE input interface. AXIS interface implements the AXI4-Stream protocol developed for fast directed data flow. It implements the basic handshake using at least tvalid and tready signals, however, we will neglect even those for our nearly constant phase increment value. To create a continuous stream of the user defined values we use Pavel Demin’s AXI4-Stream Constant IP core, which converts 32-bit input bus to the AXIS master interface. For the input we take 27-bit phase_inc value from the gpio2_io_o port using Slice IP core. Calculation of the phase_inc for a desired output frequency will be discussed in the last part of the post.

Data Acquisition

AXI4-Stream Red Pitaya ADC Core

The first block in the Data Acquisition hierarchy is the axis_red_pitaya_adc_v1_0 IP core with two main features. First, it converts the external 125 MHz clock from adc_clk_a and adc_clk_b differential external ports into our programmable logic as a adc_clk clock. Second, it reads the ADC data from two input channels which becomes available on each adc_clk clock cycle and makes it available over the AXI Stream (AXIS) interface M_AXIS. axis_red_pitaya_adc_v1_0 IP core uses two ports of the AXIS interface, the axis_tvalid port which is always asserted and the axis_tdata a 32-bit data port with new measurements available on every clock cycle. 32-bit axis_tdata contains 16-bit channel 2 value and 16-bit channel 1 value:

M_AXIS_tdata[31:0] = 31[ {16-bit ADC2 value} {16-bit ADC1 value} ]0.

Since Red Pitaya has 14-bit ADC the 16-bit value has two most significant bits set to either 00 or 11 depending on the sign of the measured value. It is instructive to have a look at the Verilog code of  AXI4-Stream Red Pitaya ADC core. Note that Red Pitaya’s ADC core has an additional output port (adc_csn) connected to the external port adc_csn_o for a clock duty cycle stabilization.

Data Acquisition Hierarchy

Signal Split  Module

The second block in the hierarchy is the signal_split RTL module. It transforms ADC output interface M_AXIS with two channel values into two M_AXIS output interfaces each containing a single channel value. The module has a very simple Verilog code, which can be found on github.

It is interesting to note that if you want to create an input or an output interface on a RTL module, simply name the input or output ports with a standard interface notation (see Vivado IP user guide). For example, in the signal_split RTL block port names: S_AXIS_PORT1_tdata and S_AXIS_PORT1_tvalid are automatically combined into an S_AXIS_PORT1 interface.

Frequency Counter Module

The frequency counter hierarchy is build around its main RTL module frequency_counter with two main inputs: S_AXIS_IN interface containing measured single channel ADC signal and Ncycles, a value that specifies a number of signal oscillation for time measurement. Since exact number for Ncycles is not important user specifies a 5-bit logarithmic value log2Ncycles via the gpio core. Ncycles is then calculated as

Ncycles = 2^log2Ncycles

using a pow2 RTL module. See the figure below.

Frequency Counter Hierarchy

The verilog code of the frequency_counter RTL module has three main parts. The first part directly wires the S_AXIS_IN to the M_AXIS_OUT interface so that data is  transferred to the next block for processing. Instead, we could split the AXIS interface before the module, however, this would require an additional IP core – the AXI3-Stream Broadcaster.

The second part of the code sets the state buffer depending on the measured signal value relative to the high or low threshold values. If the signal is above the high threshold value state buffer is set to one and if the signal is below the low threshold value state buffer is set to 0. Using two threshold values helps to prevent false state transitions in case of noisy data.

The third part increments counts register on each clock cycle, increments cycles register on each positive state transition and clears cycles and counter registers when cycles exceeds Ncycles. Before clearing the counter its value is copied to the counter_output register which is wired to the output port. The result of the frequency counter module is therefore a number of clock cycles in a time of Ncycles signal oscillations, updated on each Ncycles signal oscillations. The frequency is then calculated as

frequency = Ncycles/counts*125 MHz.

Signal Decode Module

The final block in the ADC signal chain and in the block design is the signal_decode RTL module. Its purpose is to display the ADC value on the Red Pitaya LED bar mostly for visual effects. The implementation is a simple 8-bit decoder from Vivado’s Language Templates. In signal_decoder.v the three MSBs of the ADC value are decoded and displayed on LEDs. However, if your ADC range jumpers are set to +/- 20 V instead of +/-1 V you will see no activity when connecting the output of the Red Pitaya’s DAC to the input of its ADC port. In this case BIT_OFFSET parameter can be set to 4 to decode 4th, 5th and 6th signal’s MSBs. Shifting the bit position is related to signal amplification by a factor of 2. You can play with this value if the range is not optimal.

Fun Part

We are ready to test the frequency counter. Connect the Red Pitaya’s OUT1 port to the IN1 port. Save the project, create bitstream and write it to the FPGA as described in previous projects.

Next, copy the counter.c program found in redpitaya_guide/4_frequency_counter/server folder to Red Pitaya’s  Linux, compile it and execute it as shown in the figure below.

Demonstration of counter.c program

The program can be used with the following parameters:

./counter {log2Ncycles} {frequency_Hz}

Keep in mind that the frequency resolution depends on the number of clock counts within Ncycles signal oscillations. Low frequency signals require small Ncycles and high frequencies signals require large Ncycles.  The maximal number of counts can be 2^32, the highest DAC frequency can be 125 MHz/4 = 31.25 MHz and the lowest frequency can be approx. 1 Hz. The conversion from the desired frequency into the phase_inc is done in the counter.c.

When setting the frequency to 2 Hz the LED bar on the Red Pitaya board looks very much like Knight Rider’s lights 🙂

<< Red Pitaya Project 3



  1. Mick Phillips Reply

    This works great with the ADC gain jumpers set to HV, but not when they’re set to LV. From the code comments, I saw that I needed to change the BIT_OFFSET on the signal decoder – this fixes the LEDs. However, the counts and reported frequencies are wrong.

    I think I also need to change the thresholds in the frequency counter. I can not figure out values that work (I tried scaling the existing values according to the full-scale ranges I see on the ADC at the two different gains, but it didn’t work). Can you tell me more about how those threshold values are chosen?

    Thanks for this great blog series, by the way! I have learned a lot.

    1. Anton Potočnik Reply

      Hi, the mean value of the thresholds I got from another application where I actually measure and display raw ADC data (project 5 at github) and then the difference between the high and the low value I chose to be larger than measurement uncertainty, but still of comparable order. I would probably do the same for the LV setting. Good starting values for the thresholds would be values close to zero with similar difference as in HV case.

  2. Pingback: SDR-WSPR with GPSDO 10MHz frequency stabilization – WSPRlive

  3. Kerrie Reply

    whoah this blog is great i love reading your posts. Keep up the
    good work! You understand, many people are searching around for
    this info, you could aid them greatly.

  4. Don Pablo Reply

    Hi!, I’ve been having problems, no matter what log2Ncycles or what frequency I input all I got is random numbers, I didn’t touch anything in the project, can you please tell me what may be wrong? This is some of the data I get:
    root@rp-f0514b:/tmp# ./counter 0 10
    Counts: 41, cycles: 1, frequency: 3048780.48780 Hz

    root@rp-f0514b:/tmp# ./counter 0 10
    Counts: 14, cycles: 1, frequency: 8928571.42857 Hz

    root@rp-f0514b:/tmp# ./counter 0 10
    Counts: 43351, cycles: 1, frequency: 2883.43983 Hz

    root@rp-f0514b:/tmp# ./counter 0 10
    Counts: 21, cycles: 1, frequency: 5952380.95238 Hz

    root@rp-f0514b:/tmp# ./counter 0 10
    Counts: 37, cycles: 1, frequency: 3378378.37838 Hz

      1. Anton Potočnik Reply

        Hi Youssef and Don Pablo,

        sorry for my inactivity lately. Mick’s suggestion is very good and it might indeed work. See our earlier comments.
        Let me know if this solves your problem so I can put a note in the text.

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top