Without a doubt, how we use the Zynq SoC’s XADC in our developments is the one area I receive the most emails about. Just before Christmas, I received two questions that I thought would make for a pretty good blog. The first question asked how to use the AXI streaming output with DMA while the follow up question was about how to output the captured data for further analysis.
The AXI streaming output of the XADC is very useful when we want to create a signal-processing path, which might include filters, FFTs, or our own custom HLS processing block for example. It is also useful if we want to transfer many samples as efficiently as possible to the PS (processing system) memory for output or higher-level processing and decision making within the PS.
The XADC outputs samples on the AXI Stream for each of its channels when it is enabled. The AXI Streaming interface implements the optional TID bus that identifies the channel currently streaming on the AXI stream data output to allow downstream IP cores to correlate the AXI Stream data with an input channel. If we output only a single XADC channel, we do not need to monitor the TID signal information. However if we are outputting multiple channels in the AXI stream, we need to pay attention to the TID information to ensure that our processing blocks use the correct samples.
We need a more complex DMA architecture to support multi-channel XADC operation. The DMA IP Core must have its scatter-gather engine enabled to provide multi-channel support. Of course, this level of complexity is not required if we’re only using a single XADC channel.
For the following example, we will be using a single XADC output channel so that I can demonstrate these concepts simply. I will return to this example in a later blog and expand the design for multiple output channels.
We will use a DMA transfer from the PL to the PS to move XADC samples into the PS memory. However, we cannot connect the XADC and DMA AXI stream interfaces directly. That design won’t function correctly because the DMA IP Core requires the assertion of the optional AXI Stream signal TLast to signal AXI transfer completion. Unfortunately, the XADC’s AXI Streaming output does not contain this signal so we need to add a block to drive the TLast signal between the XADC and DMA.
This interface block is very simple. It should allow the user to define the size of the AXI transfer and it needs to assert the TLast signal once the AXI transfer size is reached. Rather helpfully, an IP block called Tlast_gen that implements this functionality has been provided here. All we need to do is add this IP core to the project IP repository and include it in our design.
We can use an AXI GPIO in the PL to control the size of the transfer dynamically at run time.
Creating the block diagram within Vivado for this example is very simple. In fact, most of the design can be automatically created using Vivado, as shown in this video:
The final block diagram is below. I have uploaded the TCL BD description to my GitHub to enable more detailed inspection and recreation of the project.
Once the project has been completed in Vivado, we can build the design and export it to SDK to create the application.
The software application performs the following functions:
Initialize the AXI GPIO
Initialize and configure the XADC to read a single channel (VP/VN on the Zedboard)
Reset the DMA channel
Loop forever performing the following
Simple DMA transfer from the PL to the PS
Flush the cache at the PS transfer address to ensure that we can see the data in the PS DDR memory
Flushing the cache is very important. If we don’t flush the cache, we will not see the captured ADC values in memory when we examine the PS DDR memory.
We also need to take care when setting the number of ADC samples transferred. The output Stream is 16 bits wide. The tlast_gen block counts these 16-bit (2-byte) transfers while the DMA transfer counts bytes. So we need we need to set the tlast_gen transfer size to be half the number of bytes the DMA is configured to transfer. If we fail to set this correctly, we will only be able to perform the transfer once. Then the DMA will hang because the tlast signal will not be generated.
Generation of the tlast signal
When I ran this software on the ZedBoard, I could see the ADC values changing as the DMA transfer occurred in a memory watch window.
Memory watch window showing the 256-byte DMA capture
Now that we can capture the ADC values in the PS memory, we may want to extract this information for further analysis—especially if we are currently verifying or validating the design. The simplest way to do this is to write out the values over an RS-232 port. However, this can be a slow process and it requires modification to the application software.
Another method we can use is the XSCT Console within the debug view in SDK. Using XSCT we can:
Read out a memory address range
Read out a memory address range as a TCL list
Read out a memory address range to a binary file
The simplest approach is to output a memory address range. To do this, we use the command:
mrd <address> <number of words>
Reading out 256 words from the address 0x00100000
While this technique outputs the data, the output format is not the easiest for an analysis program to work with because it contains both address and data values.
We can obtain a more useful data output by requesting the data to be output as a TCL list using the command:
mrd -value -size h 0x00100000 128
Reading out a TCL list
We can then use this TCL list with a program like Microsoft Excel, MATLAB, or Octave to further analyze the captured signal:
Captured Data analyzed and plotted in Excel.
Finally, if we want to download a binary file containing the memory contents we can use the command:
mrd -bin -file part233.bin 0x00100000 128
We can then read this binary file into an analysis program like Octave or MATLAB or into custom analysis software.
Hopefully by this point I have answered the questions posed and shared the answers more widely, enabling you to get your XADC designs up and running faster.