One of the most interesting industry statistics is that 84% of FPGA designs make it into the field with at least one bug.
Of course, we can try to minimize this by following a good FPGA development process which enables a structured approach. Such an approach enables a high-quality design to be completed. Typically, this development process will contain several distinct stages, processes, and reviews to enforce the structured approach, including:
- System Requirements Review – Review of the system requirements and verification approaches, along with compliance/non-compliance matrices.
- Preliminary Design Review – Review of the architectural design against the requirements to ensure they are addressed. Review the architecture for feasibility and technical risk.
- Critical Design Review – Review of the implemented design and its verification package, simulation, and timing closure/implementation reports against the requirements and architecture.
Following this process, of course, requires checklists for review stages and an excellent coding/design guideline which aligns with the vendor rules, such as the Ultrafast Coding Methodology.
Once the FPGA design is completed, the project is still a long way from being completed.
The design must be integrated into the hardware, interfacing with real-world sensors, actuators, drives, and application software.
During this stage, prior to production release, there are several technical challenges/risks which may result in the need to perform debugging on the hardware.
This debugging on hardware will require the ability to understand interactions between the logic design, application software, and the real world — as sadly, sometimes the real world is different from how the design was verified — for example, different timing, modes of operation, or unexpected HW/SW interactions.
When this occurs, we need to be able to find the root cause of the issues and take corrective action. Anything found during integration should be fed back into simulations to ensure future product updates/regression testing do not forget the hard-learned lessons of the lab.
To do this, as engineers, we have several tools — from simple multimeters to measure voltage and current, to logic analysers/pattern generators and oscilloscopes.
However, sometimes we want to look internally into our SoC/FPGA to understand the interaction between IP cores or embedded hard and soft-core processors.
AMD ChipScope is an on-chip debugging and verification toolkit integrated into the Vivado Design Suite. It enables developers to observe, capture, and interact with internal signals of an FPGA or SoC design in real time—without the need for external probes or logic analyzers.
One of the key elements of the ChipScope tool kit is the Integrated Logic Analyser.
What is an ILAThe Integrated Logic Analyzer (ILA) is a hardware debugging tool embedded within AMD FPGAs and SoCs that allows engineers to capture and analyze internal digital signals.
The ILA provides real-time hardware debugging capabilities by allowing users to set trigger conditions and capture signal data directly on the FPGA fabric. The ILA is able to capture signals with a range of interface definitions, from simple logic signals and vectors to AXI and AXI Streaming interfaces.
The System ILA is an enhanced version that enables more comprehensive system-level debugging across multiple interfaces and clock domains. It provides capabilities for monitoring AXI interfaces, memory controllers, and other system interconnects.
The System ILA can capture longer trace buffers, support more complex trigger conditions, and monitor a greater number of signals simultaneously than the basic ILA. This makes it particularly valuable for debugging complex system-on-chip (SoC) designs, where multiple components interact with each other across different interfaces and protocols.
When it comes to debugging applications, we need to take a logical approach. As such, when using the System ILA or ILA, we should understand that FPGA design is an iterative process, which is most successful if we solve problems logically.
The best approach to this is to:
- Break a problem into smaller parts
- Simplify by reducing variables and variation
- Make a prediction, then verify the results
- Plan how and where to debug early in the design cycle
When using the Integrated Logic Analyzer (ILA) in real-world designs, it’s important to be strategic about what you instrument to balance debug visibility with FPGA resource usage.
ILAs consume logic and block RAM, which becomes a critical concern in devices already near capacity.
Capture depth and the width of monitored signals directly impact BRAM usage—wider probes and longer capture windows can quickly exhaust available memory.
In early revisions, follow the methodology outlined above, focusing on key control signals, reset lines, and status or error indicators, as these often provide immediate insight into system behaviour and help identify issues early in the debug cycle.
Critical AXI buses within the system—particularly those closest to the processing system interfaces, such as M_AXI_HPM_FPD (High Performance Master) and S_AXI_HP_FPD (High Performance Slave) on a Zynq UltraScale+ MPSoC—should also be considered for instrumentation.
While these wide buses are resource-intensive, they carry essential traffic between the programmable logic and processing system, and can reveal subtle issues in data handling and system integration.
Starting with a well-chosen mix of control, reset, status/error, and these key AXI interfaces at moderate capture depths provides valuable visibility into system-level behaviour while preserving headroom for iterative debug refinement.
Demo ProjectTo demonstrate how we can leverage the capability provided by ILAs within our FPGA design, we are going to create a simple demo project.
This project will be targeted at an Avnet ZU Board and connect to a Digilent ZMod AWG to generate simple analog outputs.
In this project, I will use two different kinds of ILA to help ensure correct operation.
- ILA - To monitor the clock wizard locked signal, the DAC Initialization and the DAC error signals
- System ILA - To monitor the AXI and AXI Stream data in the system
The first step in this is to create a new project. If the ZU Board is not installed, then download it using the Boards tab in Vivado.
Once the project is created, the next step is to create the Vivado design. To do this, the first step is to create a block design.
With the block design open, add in a Zynq UltraScale+ MPSoC Processing System.
Run the Block Automation to configure the Processing System for the ZU Board definition.
After the Processing System is configured, we can add in the ZMod AWG controller from the Vivado library.
Double-click on the IP and select “Add IP to Block Design.”
The ZMod AWG requires an AXI-Stream input, and the Processing System has an AXI4 output. However, Vivado is able to implement the necessary memory-mapped-to-stream conversion architecture. Connect the ZMod AWG input data stream port to the HP0-FPD port on the Processing System.
This will open a Connection Automation dialog. For the clock, select the 100 MHz output clock from the Processing System.
This will result in a diagram as below. Connect the system clock to the same clock as the DAC I/O clock, as both are going to be at 100 MHz.
Recustomize the AXI-Stream FIFO and uncheck the Enable Transmit Control option.
Add in a new Clocking Wizard and configure as shown below; we want a 100 MHz output offset 90 degrees from the input.
Connect the clock as shown to the DAC CLK input of the ZMod AWG.
Connect the resets.
Add a Constant block set to logic high for the DAC Enable.
Add a Constant block set to logic low to disable Test Mode.
Except the top two ports on the ZMod AWG block and mark them as external.
Add in a System ILA; we will use this to monitor the AXI and AXI-Stream data in the system.
Configure the System ILA properties as below; enable its trigger input and output ports. This will allow us to cross-probe with the software development in Vitis Unified.
Leave Slot 0 as it is currently configured.
Change Slot 1's properties to monitor AXI-Stream.
Connect the System ILA Slot 0 and Slot 1 as shown below, along with clocking and resets.
Re-customize the Processing System to disable HPM1 and to enable PS to PL and PL to PS cross-triggering. This cross-triggering allows the ILA and Vitis Unified environment to trigger each other.
Output Triggering means when a breakpoint is hit in Vitis, the connected ILA can also be triggered at the same time to observe the hardware's behavior when the breakpoint triggered.
Input Triggering means when the ILA hits its trigger, the software execution on the processor is also stopped, enabling the software's state to be observed as well.
Connect the Trigger ports as shown.
Add an ILA; we will use this to monitor some simple logic signals in the design.
Configure the ILA as shown below with three input probes.
Connect the ILA as shown below; we will be monitoring the Clock Wizard Locked signal, the DAC Initialization signal, and the DAC Error signal.
The final design should look as shown below.
Validate the design, create the wrapper, and build the bitstream using the XDC file provided below.
set_property IOSTANDARD LVCMOS18 [get_ports ZmodDAC_ClkIn_0]
set_property IOSTANDARD LVCMOS18 [get_ports ZmodDAC_ClkIO_0]
set_property IOSTANDARD LVCMOS18 [get_ports sZmodDAC_Reset_0]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[13]}]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[12]}]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[11]}]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[10]}]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[9]}]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[8]}]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[7]}]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[6]}]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[5]}]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[4]}]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[3]}]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[2]}]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[1]}]
set_property IOSTANDARD LVCMOS18 [get_ports {dZmodDAC_Data_0[0]}]
set_property IOSTANDARD LVCMOS18 [get_ports sZmodDAC_CS_0]
set_property IOSTANDARD LVCMOS18 [get_ports sZmodDAC_EnOut_0]
set_property IOSTANDARD LVCMOS18 [get_ports sZmodDAC_SCLK_0]
set_property IOSTANDARD LVCMOS18 [get_ports sZmodDAC_SetFS1_0]
set_property IOSTANDARD LVCMOS18 [get_ports sZmodDAC_SetFS2_0]
set_property PACKAGE_PIN H5 [get_ports sZmodDAC_CS_0]
set_property PACKAGE_PIN G1 [get_ports sZmodDAC_EnOut_0]
set_property PACKAGE_PIN G4 [get_ports sZmodDAC_SCLK_0]
set_property PACKAGE_PIN J5 [get_ports sZmodDAC_SDIO_0]
set_property PACKAGE_PIN H2 [get_ports sZmodDAC_SetFS1_0]
set_property PACKAGE_PIN G2 [get_ports sZmodDAC_SetFS2_0]
set_property PACKAGE_PIN J3 [get_ports ZmodDAC_ClkIn_0]
set_property PACKAGE_PIN L4 [get_ports ZmodDAC_ClkIO_0]
set_property PACKAGE_PIN H4 [get_ports sZmodDAC_Reset_0]
set_property PACKAGE_PIN N2 [get_ports {dZmodDAC_Data_0[13]}]
set_property PACKAGE_PIN P1 [get_ports {dZmodDAC_Data_0[12]}]
set_property PACKAGE_PIN N5 [get_ports {dZmodDAC_Data_0[11]}]
set_property PACKAGE_PIN N4 [get_ports {dZmodDAC_Data_0[10]}]
set_property PACKAGE_PIN M2 [get_ports {dZmodDAC_Data_0[9]}]
set_property PACKAGE_PIN M1 [get_ports {dZmodDAC_Data_0[8]}]
set_property PACKAGE_PIN M5 [get_ports {dZmodDAC_Data_0[7]}]
set_property PACKAGE_PIN M4 [get_ports {dZmodDAC_Data_0[6]}]
set_property PACKAGE_PIN L2 [get_ports {dZmodDAC_Data_0[5]}]
set_property PACKAGE_PIN E4 [get_ports {dZmodDAC_Data_0[4]}]
set_property PACKAGE_PIN E1 [get_ports {dZmodDAC_Data_0[3]}]
set_property PACKAGE_PIN F1 [get_ports {dZmodDAC_Data_0[2]}]
set_property PACKAGE_PIN E3 [get_ports {dZmodDAC_Data_0[1]}]
set_property PACKAGE_PIN L1 [get_ports {dZmodDAC_Data_0[0]}]
set_property C_CLK_INPUT_FREQ_HZ 300000000 [get_debug_cores dbg_hub]
set_property C_ENABLE_CLK_DIVIDER false [get_debug_cores dbg_hub]
set_property C_USER_SCAN_CHAIN 1 [get_debug_cores dbg_hub]
connect_debug_port dbg_hub/clk [get_nets clk]
HW Set UpEnsure the hardware setup is as shown below, with the ZU Board connected to the ZMod DAC over the PL HSIO port. The output from DAC Channel 1 SMA should be connected to the oscilloscope using an SMA-to-BNC cable.
Also, ensure the ZU Board is powered over its USB-C connection and is connected to the development machine via the USB JTAG.
To get started with the software design, we first need to ensure the boot mode on the ZU Board is set to JTAG. We will be using the JTAG port to access the ILAs. If you are using PetaLinux, you can access over Ethernet as well, as indicated in this project; however, that is outside the scope of this project.
Within Vitis Unified, create a new hardware platform based on the XSA just exported.
Select the XSA just exported from Vivado.
Select a standalone operating system, and the first A53 processor.
Finish the creation of the platform.
The next step is to create the application project. For this project, we will use the Hello World template and name it AWG_Example.
Select the platform just created.
Leave the domain as indicated.
Finish the creation of the application.
The next step is to build the application and configure the cross-probing using the application's launch.json file.
To add a cross-probe (we need to add two, one in each direction), select the Add Item option.
The first cross probe we will set up enables triggers to go from the A53-0 processor to the Programmable Logic using the FTM, and hence to the System ILA.
Once the first cross-probe is set up, we need to add another cross-probe going from the Programmable Logic to the A53 processor.
We can now debug the standard Hello World application to ensure the process is working as expected and to take an early look at the ZMod AWG status.
We will see the output in a terminal; this helps demonstrate that we have a build with basic functionality.
Before we develop the main application software, the next step is to confirm the ZMod AWG has been correctly configured and initialized. Any errors here would indicate something wrong with the clocking or control signal mapping.
In Vivado, open the Hardware Manager, and on ILA1, double-check that the Clock Wizard is locked and the DAC is initialized. As these are static signals, we can trigger the ILA immediately.
If we see a configuration error, we will need to further investigate the interfacing and clocking of the ZMod AWG.
The next step is to enable the trigger in, on the system ILA and arm it.
We can then develop the main application software, which for this example will write a ramp count into the DAC. This is the sort of test typically performed early in integration to ensure the DAC has no missed bits or stuck codes.
The application code can be seen below:
/**
* @file main.c
* @brief AXI Stream FIFO Test Application for ZYNQ MPSoC
*
* This application configures an AXI Stream FIFO and writes 256 samples
* in a ramp waveform pattern to test for lost bits.
*
* Uses Vitis unified flow approach with base address initialization.
*/
#include <stdio.h>
#include <stdlib.h>
#include "xparameters.h"
#include "xil_printf.h"
#include "xllfifo.h"
#include "sleep.h"
/* AXI FIFO base address - update based on your hardware design */
#define FIFO_BASE_ADDR XPAR_XLLFIFO_0_BASEADDR
/* Number of samples to write to FIFO */
#define NUM_SAMPLES 256
/* Buffer for transmission data */
u32 TxBuffer[NUM_SAMPLES];
/**
* Generate a ramp waveform pattern
*/
static void GenerateRampWave(u32 *Buffer, u32 NumSamples)
{
u32 i;
u32 rampValue = 0;
u32 shiftedValue = 0;
float scaleFactor;
/* 14-bit counter max value */
const u32 MAX_COUNT = 0x3FFF; // 2^14 - 1 = 16383
/* Calculate scale factor to reach MAX_COUNT in (NumSamples-1) steps */
scaleFactor = (float)MAX_COUNT / (NumSamples - 2);
for (i = 0; i < NumSamples; i++) {
if (i == NumSamples - 1) {
/* Last sample is 0 */
rampValue = 0;
shiftedValue = 0;
} else {
/* Generate scaled ramp to cover 0 to 16383 over the available samples */
rampValue = (u32)((i * scaleFactor) + 0.5f);
/* Ensure we don't exceed 14 bits */
if (rampValue > MAX_COUNT) {
rampValue = MAX_COUNT;
}
/* Shift to bits 31:18 */
shiftedValue = rampValue << 18;
}
/* Store sample in buffer */
Buffer[i] = shiftedValue;
}
}
/**
* Main function for AXI FIFO Triangular Wave Test
*/
int main(void)
{
XLlFifo FifoInstance;
int Status;
u32 TxFrameLength;
u32 i;
xil_printf("\r\n--- AXI Stream FIFO Triangular Wave Test ---\r\n");
/* Initialize the AXI Stream FIFO device using base address */
XLlFifo_Initialize(&FifoInstance, FIFO_BASE_ADDR);
/* Reset the device to get it into its initial state */
XLlFifo_Reset(&FifoInstance);
XLlFifo_IntClear(&FifoInstance,0xffffffff);
usleep(1000);
/* Generate the ramp wave pattern */
xil_printf("Generating ramp wave pattern...\r\n");
GenerateRampWave(TxBuffer, NUM_SAMPLES);
/* Calculate frame length in bytes (4 bytes per word) */
TxFrameLength = NUM_SAMPLES * 4;
while(1){
/* Check if there's enough room in the FIFO */
Status = XLlFifo_iTxVacancy(&FifoInstance);
//xil_printf("TX FIFO vacancy: %d words\r\n", Status);
if (Status < NUM_SAMPLES) {
xil_printf("Not enough space in TX FIFO. Need %d words, have %d words.\r\n",
NUM_SAMPLES, Status);
return XST_FAILURE;
}
/* Write data to FIFO TxD */
for (i = 0; i < NUM_SAMPLES; i++) {
XLlFifo_TxPutWord(&FifoInstance, TxBuffer[i]);
}
/* Write the frame length to the FIFO transmit length register */
//xil_printf("Writing %d samples (%d bytes) to AXI Stream FIFO...\r\n", NUM_SAMPLES, TxFrameLength);
XLlFifo_TxSetLen(&FifoInstance, TxFrameLength);
/* Check for any errors */
Status = XLlFifo_Status(&FifoInstance);
if (Status & XLLF_INT_ERROR_MASK) {
xil_printf("ERROR: FIFO transmission error occurred. Status: 0x%x\r\n", Status);
/* Clear errors */
XLlFifo_IntClear(&FifoInstance, XLLF_INT_ERROR_MASK);
return XST_FAILURE;
}
}
xil_printf("AXI Stream FIFO test completed successfully\r\n");
return XST_SUCCESS;
}
The next step is to check, using the System ILA, that the data is transferred correctly into the AXI-Stream FIFO.
Add a breakpoint on the for loop that performs the write.
When the breakpoint is hit, the ILA will also trigger, and the AXI write can be observed.
This proves the data is being written into the AXI-Stream FIFO. Now we need to check that data is being output from the FIFO.
In Vivado, change the System ILA trigger out, and set it to trigger on the rising edge of data valid on the AXI-Stream, then arm the ILA.
Restart the application software, and the ILA will trigger along with the software being stopped, as it would at a breakpoint. We can see several hundred packets being output.
Zooming in, you can see the value of the ramp and the values incrementing as expected.
In Vitis, you will also see that the software has been stopped.
As we are driving an output to the DAC, this should result in an analog output being present on the DAC output terminal. However, on the scope connected to the hardware, there is no signal observed.
We know the Processing System is generating data, we know it is being sent to the ZMod DAC driver, and we know the ZMod was initialized correctly — but nothing is appearing on the scope.
Let’s look at the ZMod AWG User Guide to check if there’s something we’re missing. The link to the guide is here.
Reading the document, we see the issue: currently, the test waveform is being placed in the lower bits, but the ZMod AWG expects it in the upper bits.
Correcting this (helpfully, it is a software fix) can again be verified in the ILA; however, we are now able to see the result on the scope output.
Since the DAC code is signed, ramping the counter from zero to max scale will cause the scope to show a positive ramp, then a negative ramp once the MSB is set.
This project has demonstrated how we can work with the AMD ChipScope debugging solutions, ILAs and System ILAs to find issues in a live project and quickly identify them. The project also outlines how hardware and software interaction can be debugged by using the cross-probing capabilities, which allow easy analysis of integration issues.
ChipScope, ILA and System ILA are great elements that developers can use to quickly and easily debug an application.
Comments
Please log in or sign up to comment.