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.
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:
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.
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:
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.
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.
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.
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.
There are only a couple of different types of API used in this driver:
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.
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.
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.
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:
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.
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.
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.)
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:
A final check shows the the data from the RF-ADC is getting passed to the System ILA.
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!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.