Debugging an embedded system running on a Cortex M0 processor implemented in an FPGA can be challenging but is an essential step in developing robust and reliable devices. This guide will walk through the basic steps needed to set up and use a debugger with a Cortex M0 soft core processor on an FPGA development board.
Prerequisites
Before starting, there are a few requirements that need to be met:
- Cortex M0 processor IP core instantiated in an FPGA
- Debug module also instantiated in the FPGA to interface with processor
- JTAG interface exposed on FPGA development board
- Debug probe device that supports Cortex M0 and can connect to JTAG
- Integrated development environment (IDE) or debugger software installed on host computer
The first two items relate to the actual FPGA hardware design and ensuring debug capability is built into the processor subsystem. The Cortex M0 design should include a debug module that exports a JTAG interface. The JTAG pins need to be routed to an exposed port on the development board.
For the debugger probe, devices like Segger J-Link or ST-LINK are common choices. These probes connect to the JTAG port and translate the signals to USB for a host computer connection. Finally, compatible debugger software needs to be installed on the host computer to view and control the target being debugged. Example IDEs with integrated debuggers include Keil uVision, IAR Workbench, and Eclipse-based IDEs.
Connecting Debug Probe
With the prerequisites satisfied, the first part of setting up debugging is making the physical connection between the debug probe and the target FPGA board. The probe will connect to the exposed JTAG pins on the board, which then link internally to the Cortex M0 debug module.
It is important to verify the pinout and signal connections between the probe and the FPGA board. The JTAG pins should be labeled on the board and matched to the appropriate cable on the debug probe. Failure to match up these connections correctly could prevent proper communication.
Power is also a consideration – the target board must be powered on for debugging, and in some cases, the debug probe requires its own power connection as well.
Once the physical debug probe connections are made, the probe can be connected via USB to the host computer where the debugger software is running. The debug probe will enumerate on the host as a USB device and be recognized by the software.
Configuring Debugger Software
With the debug probe connected, the debugger software needs to be configured to interface with the Cortex M0 target processor. The software needs several key pieces of information:
- Debug probe selection – the software connects to the debug probe over USB
- Device architecture – this establishes the ARM Cortex M0 instruction set
- Processor speed – the clock speed the Cortex M0 is running at on the FPGA
- JTAG scan chain configuration – defines tap positions if using a boundary scan chain
The processor configuration, including clock speed, is important for the debugger to accurately reflect timing. A code profile or debugger performance measurements would be incorrect if the wrong speed is set.
For designs with multiple devices and complex JTAG chains, specifying the correct FPGA boundary scan information allows the debugger to isolate and connect only to the Cortex M0 tap.
With the debug probe selected and target device configured, the debugger software can interrogate the device, enumerate its resources, and establish a link to begin sending commands and collecting data.
Loading Executable Code
Before executing code, a binary image containing the compiled program code must be loaded into the target memory space. Debugger software typically provides multiple ways to accomplish this:
- Load an .elf, .axf, or .bin file directly into target memory
- Interface to a programmer to write flash memory on the board
- Load code sections at specified target addresses
For initial prototyping and debugging, loading a binary directly into RAM memory space may be the easiest approach. The debugger software would expose options specifying the target address, selecting a file, and then initiating the load. More advanced use cases might involve directly programming flash storage on the board itself.
With the executable program binary loaded, breakpoints can be set at important code locations. Breakpoints halt execution on the target Cortex M0 so memory and registers can be examined. This is a key step in debugging and understanding program flow.
Debugging Cortex M0 Program Flow
After loading code and setting breakpoints, execution on the target M0 processor can begin. The debugger will keep sync with the target, halting at breakpoints set by the user or at exceptions like code faults. When halted, the debugger offers visibility by updating registers, memory, and peripheral states.
Single-stepping can be used to increment through code carefully to follow function calls and key program sequences. Memory watch windows can be opened to monitor values in real time. And careful inspection of registers helps determine if code is behaving as intended.
In addition to simple program flow debugging, the debugger allows viewing interrupts and exceptions being triggered. This is useful for understanding complex peripheral interactions or looking for race conditions.
More advanced debuggers offer additional capabilities like graphing code profiling data across multiple halts to identify slow sections of code. There may also be scripts or macros to automate debug tasks as well.
In summary, the debugger gives visibility into the target Cortex M0 system allowing the user to control execution, inspect state changes, and iteratively improve their embedded software design.
Debugger Limitations and Workarounds
While incredibly useful, hardware-based debuggers do have limitations users should be aware of. Some common challenges include:
- Real-time interrupt debugging – cycles may be altered by debugger halting
- Resource contention – debugger competes for bandwidth resources
- Probe connection reliability – poor connections lead to data errors
- Code optimization interactions – compiler optimizations may lead to confusing results
Debuggers inherently rely on halting code execution which makes investigating timing-sensitive interrupts difficult. The debug module competes with target application code for bandwidth into the Cortex M0 core. And faulty probe connections can lead to signaling issues or unreliable data reporting.
Debugger users need to be aware of these limitations and employ workarounds when needed. Using simulator software or adding debug print capability to code are ways to augment hardware debugging. And being aware that the debugger itself affects the target system can explain some unusual results.
Conclusion
Debugging Cortex M0 processors running on FPGAs introduces challenges like interfacing debug probes and configuring soft-core IP parameters. But the power to control execution and deeply inspect internal processor state provides critical insight for firmware development and validation.
Following the guidance in this article, an embedded developer can connect a hardware debugger, configure it correctly for an FPGA target, load executable code, and leverage the tool to efficiently find bugs and improve program performance.