Showing results for 
Show  only  | Search instead for 
Did you mean: 

RF Data Converter Software Drivers - Really Foolproof, not Really Frustrating

16 18 12.8K

Hi, I’m Keith Lumsden and I am a Xilinx Applications Engineer.

I am very excited to have been asked to contribute to the brand new Design and Debug Techniques Blog for the Xilinx Community.

My main focus is providing support to customers working with RF Data Converters integrated into the Zynq® UltraScale™ RFSoC Product.

I’ve spent a lot of my career working with Analog and Mixed Signal Systems, FPGA Architecture, and I/O and Signal Integrity.  So I’m really a hardware guy, and I guess I’ve sometimes been guilty of thinking that embedded software was for other people.

This has changed with the arrival of the RF Data Converters. We've integrated world class RF ADCs and DACs into the Zynq UltraScale+ Architecture. So inevitably, traditional RF and Analog Engineers are being exposed to embedded systems in a way that they weren't before.

RF Data Converter Solution:

If you are familiar with the data converter solution, you’ll know that it is packaged as an IP Core in Vivado Design Suite. This allows you to manage status and control for the RF Analog-to-Digital Converter (RF-ADC) and RF Digital-to-Analog Converter (RF-DAC) tiles through the Software Driver provided by Xilinx.

(PG269) The Zynq UltraScale+ RFSoC RF Data Converter IP Product Guide has all of the details on the IP, and also has a detailed appendix on the driver.solution.JPG


Getting started with debug on the RF-ADC and RF-DAC

A great place to start is with the RF Analyzer tool.

The RF Analyzer is a MicroBlaze™ based design with a communications layer that can be deployed on any device on any board. It also comes with a GUI that allows you to visualize what is being received by the RF-ADC, and enables stimulus generation and transmit via the RF-DAC. Crucially, the application is built using the software driver.

The RF Analyzer is very powerful if you are trying to track down a problem in your RF system, and it can be used to great effect to validate the RF portion of the system because it works standalone and is not design or board dependent.

A common use-case is that you would like to debug the RF-ADC and RF-DAC in your system, and you need to write a small application to test at run time. Given that both RF Analyzer and custom designs need to use the software driver, I decided to write a blog entry that will get you familiar with the driver, and show how to get started with using it for debugging. In an upcoming blog I will do an un-boxing and walk through of the RF Analyzer tool.

Chances are you already have a good understanding of the RF Data Converter system, so think of learning about the driver as adding a layer to your knowledge rather than a complete departure into something unknown.

In this blog I’ll cover the following:

  • How the driver is built
  • Data Structures
  • Using the Application Programming Interface (API) to make a simple application

We will stick to making a Baremetal application for now. In a later blog we will build on this and cover how to make a Linux application.

Driver Construction:

One of the nice things about the RFDC driver is that is built using Libmetal. Libmetal is a Xilinx developed open source software stack that provides common user APIs to access devices, handle device interrupts, and request memory across Linux, Realtime OS, and baremetal.

What does this mean for us? Well, it means that the parts of the driver we are really interested in are implemented in the user space, so we don’t really have to worry about the mechanics of talking to the hardware. It also means that the APIs are common across Linux and Baremetal applications, so you do not need to learn two sets of API calls, or worry about porting code from Baremetal to Linux.


The XRFdc Driver source code is shown in more detail in the image below. The source for the driver can be found in the Xilinx SDK install, or here on Github.


In this diagram you can see the xrfdc_hw.h file on the bottom. This header file contains what you might understand as the register address map for the RF ADC and DAC tiles. This file is not really intended for the user, it is used for the internal mechanics of the driver. There should be no need to use the identifiers in this file or even study them as they cannot really help you when writing an application.

The other header files xrfdc.h and xrfdc_mts.h are more important because they contain the following:

  • All of the data structures needed by the API calls.
  • In-line (helper) functions that are used in the code that implements the API calls.
  • Macros for use in the Application (this is useful because rather than pass the value of 0 for an ADC Tile or 1 for a DAC tile to an API call,  you can use the macro XRFDC_ADC_TILE/XRFDC_DAC_TILE where appropriate. This makes it really easy to read and understand the code.)
  • Most importantly, these files show the prototypes for the API calls that will be used in your application.

Next you will see the source code for the API calls. This is where we implement the functionality of the user API calls. Normally, there should be no need to study the implementation of the individual API calls in great detail.

  • xrfdc.c: This is the main body of APIs for use in the application
  • xrfdc_intr.c: This implements the API calls needed to manage interrupts from the RF Data Converters.
  • xrfdc_mixer.c: Contains the interface functions of the Mixer Settings in the XRFdc driver.
  • xrfdc_mts.c: Contains the multi tile synchronization functions of the XRFdc driver.

Working with the XRFdc Software Driver

Now that we have seen what is contained in the driver source, let’s talk practically about what you need to work with it.

As mentioned above, the xrfdc.h header file should be what you refer to most. It contains the data structures and API function prototypes. The data structures and API calls are also fully documented in Appendix D of (PG269).

First let’s talk about data structures.

The data structure is a way to organise information about the RF Data Converters into meaningful groups. I like to think of the data structures as a “container”. A full time software developer would probably say that this is a C++ term and I should not be using it this way, but the analogy works for me.

An example data structure I like to point to is for the Phase Locked Loop (PLL) in the RF-ADC or RF-DAC tile. Looking at it we can see that it describes everything we might need to know about the PLL, such as whether is it enabled or not, its input clock, the sample clock it is outputting, etc. pll_struct.png

Once a structure like this exists, we can pass it back and forth easily between APIs, and potentially read from it in one, or modify it in another.

You can also have a structure inside another structure.

For instance, XRFdc_PLL_Settings is a member of XRFdc_DAC_Tile


The structure is also transparent, so you can modify one member of it on its own in your code. An example is a change to the frequency of the numerically controlled oscillator in the Complex Mixer.

The MixerSettings structure has a member called Freq (for frequency) so we can just change it as follows in our code.

MixerSettings.Freq = 2000;//MHz

Once you have understood the basics of data structures, we need to get to grips with the API calls you can use. These are the building blocks for the application.

The mechanics of the individual API calls are abstracted to the user. They are implemented in the XRFdc.c file.

You only need to know three things if you want to use a call.

  • The functionality in the ADC or DAC tile that you are targeting.
  • What you will need to pass it as input.
    This usually means you tell it the RF Tile type, the tile ID and individual block you need in the tile.
    Potentially the API will need to be passed a structure to work with.
  • What you will get back as an output.
    For example it might give you back the contents of a structure.


There are only a couple of different types of API used in this driver:

  • There are management calls that can be used to control the tiles, for example XRFdc_StartUp/XRFdc_Shutdown which is used for bringing up or shutting down individual RF-ADC or RF-DAC tiles.
  • There are API calls to enable high level status reporting such as XRFdc_GetIPStatus/XRFdc_GetBlockStatus
  • There are also Get and Set API calls for individual sub blocks. For example you can use both get and set for the complex mixer settings using XRFdc_GetMixerSettings and XRFdc_SetMixerSettings respectively.

It is important to note that some Get and Set calls are also configured in the IP, for example the Complex Mixer Settings. Some are dedicated to being done at run time. An example would be the RF-ADC Thresholds Flags and the Quadrature Modulation Correction (QMC).

Finally, some of these changes will require a tile restart. Changing the PLL setup is one example.

Hello RFDC

Now that we have gotten a feel for the driver, we can make a really simple first application for the Zynq UltraScale+ RFSoC ZCU111 Evaluation board.

In this case I am just going to capture ADC data. We can always expand on the design for later blogs.  

I am sending in some IQ data patterns and I am going to use the mixer to mix it down to baseband and output it to the System Integrated Logic Analyser block in the design.hw_design.JPG

For now we just want to showcase some of the high level status and management APIs and also some of the get and set APIs we talked about earlier.

In this case we are just going to check on the IP's status and make sure that the tile is started properly.

Then we are going to read back the status of the data path.

Finally we are going to check on the mixer settings.

After we implement this design and get a bitstream, we can export the hardware hand-off file (HDF) and launch SDK. It might be worth your time to check out this tutorial if you want to know more about embedded design and SDK.

The HDF file creates the hardware platform in SDK, so it knows all of the peripherals used in the design and their addresses. The next thing to do is make a board support package. This takes the hardware definition, and pulls in all of the relevant drivers and libraries you need.

Select File > New > Board Support Package.

Pick the hardware platform that you just created and click next.bsp1.JPG

You will be prompted to include some libraries. Make sure that you tick to include libmetal and click OK. At this stage we have everything we need to make our application.


You can select File > New > Application Project now to create the example.

Make sure to point at the hardware platform and the BSP you just created.


Click next and select a blank project.

I have included the source code for my application with this blog entry. You can import this and use it as a template.

Let’s walk through some of the main features.

First we need to include the xparameters.h file which contains all the hardware parameters we need, and the xrfdc.h file that we mentioned earlier, which contains the driver structures and the function prototypes for the API calls.

In my case I have a ZCU111 board and I need to program the clocks at start-up. To enable this, I add some files from the “examples” folder in the driver source.

You will see me make a static instance of the XRFdc top level structure. The idea here is that we have one instance of the structure and we can point to it from any API or function that we might need.

static XRFdc RFdcInst;  /* RFdc driver instance */

Inside the main function we declare all of the structures that we need:

       int Status;

       XRFdc_Config *ConfigPtr;

       XRFdc *RFdcInstPtr = &RFdcInst;

       XRFdc_BlockStatus BlockStatus;

       XRFdc_IPStatus myIPStatus;

       XRFdc_Mixer_Settings MixerSettings = {0};

Note that we are just make a pointer to the static instance of the driver we just made.

The next step is to initialize the driver. This has to be done every time. Basically we are going to take the config table that is in the xrfdc_g file and use the XRFdc_LookupConfig function to populated the table with the values and settings from the xparameters. Then we will store it in the config pointer, ConfigPtr. After this is done, we call the XRFdc_CfgInitialize API and this populates the RFdcInstPtr with the configuration.   

Now we can really use the driver in our application.

You will see that I program the input clocks to my ADC tile.

I use the XRFdc_GetIPStatus to check on the status of the ADC tile I have enabled.

Status = XRFdc_GetIPStatus(RFdcInstPtr, &myIPStatus);

                    if (Status != XRFDC_SUCCESS) {

                       return XRFDC_FAILURE;


int powerup_status;

int tile_state;

powerup_status = myIPStatus.ADCTileStatus[0].PowerUpState;

tile_state = myIPStatus.ADCTileStatus[0].TileState;

printf("ADC PowerUp Status: %u\n", powerup_status);

printf("ADC Tile State: %u\n", tile_state);

In this case I expect to see that the tile state is 15. This indicates that the block has reached the end of its start-up state machine, and the power up state is 1, which means that it is enabled and active.

The next API call I use is XRFdc_GetBlockStatus. This should tell us what the sampling frequency is set to, and how the digital data path is configured. Note that I am using the XRFDC_ADC_TILE to abstract the tile type for this API call.


Status = XRFdc_GetBlockStatus(RFdcInstPtr, XRFDC_ADC_TILE, 0, 0, &BlockStatus);

       if (Status != XRFDC_SUCCESS) {

       return XRFDC_FAILURE;



Lastly, I use XRFdc_GetMixerSettings and print some details on the mixer.

I make a change, then use XRFdc_SetMixerSettings to write the new settings in.

After this I generate a tile event and apply the changes to the hardware.

Subsequently, XRFdc_GetMixerSettings should show that we have changed the mixer scale from 0 to 2 or AUTO to 1.0 in the hardware and that my mixer setting has also changed.

So, let’s run the application in the debugger in SDK.

Right Click on the application and select “Run As…” then “Run Configurations”. I use the system debugger and select the option to program the FPGA as well.

(You could also select “Debug As”, and this would enable the debug perspective and accommodate stepping through the code etc.)run_config.JPG

Let’s examine what comes out of the UART serial console now.

(I use the built-in SDK terminal to connect, but any terminal emulator can be used.)


You can see it do the following:

  • Say Hello!
  • Program the Clocks on the ZCU111 board
  • Give you the tile state @15 which means that it is fully started and that there is valid data coming out on the AXI stream.
  • Report the block status for the digital data path.
  • Shows the read of the mixer settings, and confirm the modifications.

A final check shows the the data from the RF-ADC is getting passed to the System ILA.



Final Thoughts:

So now you have the start point to write some applications. I have attached the Tcl script to build my hardware block design and the XDC file for my ZCU111 project. The C code for the application is also attached.

It should be a good start point for any application you might want to write to talk to the RF Data Converters. I would encourage you to practice with your own baremetal application.

I plan to build on this blog and highlight some other nice design and debug features of the RFSoC. Next I plan to do an unboxing and walk through of the RF Analyzer Tool, which allows you to debug any RFSoC device on any platform. 

See you next time!


Hi Keith, I'm Armando an RFSoC Board newbie.

I followed your steps and the example you provide works until I reach the ILA analysis portion. I try to open a new dashboard by first connecting to the board, but am having trouble displaying any data. It almost seems that there is a communication issue. What is your configuration type when you open the ILA in Vivado? Do you have a particular frequency that you recommend?




Hello @klumsde , thanks for the clear instructions! Still I can't see where is the ADC data coming from (vin0_01_0 port is conected to the ADC input in the Block design, but where is it being loaded with an analog signal?) Is the SDK script in charge of that? In short, I do not see how the ADC input can be accessed.  

Thank you



ADC analog input comes from a signal generator on my bench. 





I'm connecting the stream outputs of the RF Data converter block to the DMA Subsystem PCI Express IP (4.1) block through a stream clock converter block. However, the data is not being received by my application on the host machine. I've tested the PCI block with my application by using custom blocks that stream incrementing data at the same speed and width as the RF Data converter block. This works fine which tells me the issue lies in the RF Data converter and PCI block. May I ask what the correct way to interface the RF data converter block to a slave on a different clock is? One thing I noticed is that the data is seen to move in the ila in hardware manager when triggered by my application. Another thing I've noticed is that the RF converter block data lines change even when the ready signal is low. Should this happen?


I get this error: WARNING: [Labtools 27-3361] The debug hub core was not detected. I get this error when connecting the debug hub core to either the pl_clk0 out of the Zynq or the clk_adc0 out of the data_converter_0 core. How did you get this to work?


Hi @vbatyuk 

You need to start the application on the ZynqMP (with psu_init enabled or fsbl run first) to have the clock from the PS running and thus get the debug core detected


I fixed my issue, was due to the fact I wasnt toggling TLAST

Hi, your post on the RFSoC have been very helpful. I'm just starting out with the Xilinx development environment. I downloaded the files for this post and began working with them. Initially I had the error discussed here: The specific error is: [Common 17-70] Application Exception: Top module not set for fileset 'sources_1'. Please ensure that a valid value is provided for 'top'. The value for 'top' can be set/changed using the 'Top Module Name' field under 'Project Settings', or using the 'set_property top' Tcl command (e.g. set_property top [current_fileset]). I followed the solution discussed in that post which was to generate an HDL wrapper. This solved the issue above but as discussed later in the post above a bitstream was not generated due to errors relating to the pin assignments. I used the Vivado tcl shell in 2018.3 to generate the project. I'm wondering if something is incorrect with my configuration since none of this was discussed in your blog post. Should these issues exist if the project was generated correctly from the script? Thanks for the posts on the RFSoC, they are very helpful.
After doing some further reading I was able to add the pinout constraints file to the project and generate a bitstream. Thanks for putting this together.

Thanks for the post. 

Where do we get xparameters.h and xrfdc.h files from? And do we include them in hello_rfdc/src folder ?


Thanks Klumsde for this post. I am trying to open the attached design with v2019.1 vivado tools, and it does not work. Do you have the same example project which can run on the Vivado 2019.1



thanks for your sharing.

I am able to generate the block design and export it to SDK.

However, in the SDK when I try to build the hello_rfdc project, it tells me the LMK04208ClockConfig and LMX2594ClockConfig functions cannot be found.

In addition,

xrfdc_clk.h and xrfdc_clk.c files cannot be found.

I work on SDK 2019.1

How can I obtain them? 

Thanks in advance.



I am finally able to bring all files together and get the samples from the system ILA. I have a final question. The setting of the data converter in the PL project sets the decimation factor to 2. However, i want no decimation. When i do this, it says the required AXI clock frequency is doubled. From the SDK project i try to double the LMK freq. as required. When i do this, it says LMK is configured but I get no access to debug core so I think the clock is not configured. Do you have a solution to this?



I had a tough time with this one and I figured I should share my discovery.

After compiling everything and getting it on the hardware, I noticed the ADC wasn't making it past the 7th power-on state and the "LMX configuration" line was absent. I eventually found the issue.

The xrfdc_clk files are buried pretty deep in the SDK install directory.


I'm not sure if this is due to an update of those files, but there are two errors in the xrfdc_clk.c file. First the LMX22594ClockConfig function only iterates through 20 of the 27 LMX configuration options in the file.  

-- for(XFreqIndex=0 ; XFreqIndex<20; XFreqIndex++) {

The 20 in the line should be changed to 27 to reflect the array.

Second, the array itself isn't allocated properly- stating there are only 26 configuration sets.

-- XClockingLmx ClockingLmx[26] = {

This should be changed to a 27 to reflect that there are 27 configurations in the array.


After updating these things - everything works. Hope this helps someone some day.




Hello Keith,

Please can you provide generic guideline for how to change ADC and DAC reference clock for all ADC and DAC in bare metal application. I am able to  use reference design with 3.93216Gsps sample rate and 245.76MHz reference clock using bare metal application.




I have the same problem as you had.

1) LMK04208ClockConfig and LMX2594ClockConfig functions cannot be found.

2) xrfdc_clk.h and xrfdc_clk.c files cannot be found.

What did you do to fix that?
Could you tell me?

Best Regards,

Pedro Baeta


Dear @klumsde 

Have you got the clock configuration files for Hitech Global ZRF8 boards or any example projects?