Using the Cortex-M0 DesignStart IP core on non-ARM partner FPGAs can provide a low-cost way to prototype and evaluate ARM’s smallest Cortex-M processor. However, debugging these designs brings unique challenges compared to using ARM’s own FPGAs. This article provides guidance on setting up and troubleshooting debug connections for Cortex-M0 DesignStart cores implemented in non-ARM FPGAs.
Challenges of Debugging Cortex-M0 in Third-Party FPGAs
While the Cortex-M0 DesignStart IP is designed to be portable across various FPGA vendors, implementing the core in a non-ARM FPGA brings some key challenges:
- Lack of debug infrastructure – ARM’s own FPGAs like the Corstone-300 contain dedicated debug circuitry to support ARM cores. Third-party FPGAs lack this infrastructure.
- No embedded trace macrocell (ETM) – ETM provides instruction trace capabilities but is not included in the DesignStart IP.
- Limited observability – Third-party FPGAs have fewer observable signals available for debugging compared to ARM FPGAs.
- Tool incompatibilities – Vendor toolchains may not fully support ARM debug standards without additional effort.
These limitations mean more work is required to enable practical debug support. Careful planning is needed to expose the necessary debug features through the FPGA itself.
Preparing for Debug
Before implementing your Cortex-M0 design, ensure your FPGA design flow and tools support ARM CoreSight debug. Key prerequisites include:
- FPGA pins are assigned for debug connections – JTAG, SWD, and SWO interfaces need I/O
- dbg_uart output is routed to an available FPGA pin
- Design tools properly integrate the ARM debug IP
- The FPGA provides access to necessary debug control signals
- Clocks are configured correctly for debug logic
Pay special attention to the dbg_uart output from the Cortex-M0 core. This provides visibility into debug messages and printf output. Route this signal to an available FPGA pin through a synchronizer flip-flop.
Debug options are limited without access to dbg_uart. You may need to sacrifice a PWM or other peripheral output for this purpose.
Recommended Debug Hardware
To connect to your Cortex-M0 FPGA prototype, an ARM-compatible debug probe is required. The following options are recommended:
- ARM Keil ULINKpro – Supports JTAG and SWD host interfaces. Integrates RTT console for dbg_uart.
- Segger J-Link Pro – Similar wide support for ARM debug standards and RTT.
- STM32 ST-Link – Lower cost than above probes but still supports SWD debug.
Third-party adapters may have bugs or implementation issues. Using an official ARM debug probe ensures compatibility with the Cortex-M0 core.
A 4-pin JTAG/SWD cable is required to connect from the debug probe to your FPGA board. Ensure the cable supports 1.8V operation if your FPGA I/O is 1.8V.
Verifying the JTAG Interface
After making physical connections, the first debug step is confirming the JTAG interface works. Connect your debug probe and launch its debugging software.
For Keil and Segger tools, open the debug configuration and connect to the ARM core using the JTAG interface. If the FPGA JTAG pins are properly connected, the tools should detect and identify the Cortex-M0 core.
You can try interrogating ARM registers such as the DEMCR to confirm you have a connection. A stable JTAG link indicates the FPGA ports are correctly assigned to the ARM debug module.
Testing SWD Communication
With JTAG operational, attempt to connect using the faster Serial Wire Debug (SWD) protocol. SWD only uses two pins compared to JTAG’s 5+ wires.
In the debugger, select SWD as the debug interface and try connecting to the target core. Reliable SWD communication confirms the clocking and I/O for the debug module are functional.
Note that SWD has no built-in error detection. Failure to connect could indicate:
- SWD pins are incorrectly wired
- FPGA I/O pads not configured properly
- SWD signals not synchronized correctly
Double check your FPGA design if the debug probe fails to link via SWD. JTAG can help narrow down issues since it provides pin-level control.
Capturing dbg_uart Output
To view firmware printf output and other system debug messages, the dbg_uart signal must be routed to the debugger. The ARM Keil and Segger tools both integrate an RTT console to capture this output over SWD.
In your FPGA design, confirm the dbg_uart output is connected to a physical I/O pin. Connect this pin to the RTT pin on your debug probe, which is typically pin 15 on the 20-pin connector.
In the debugger, enable RTT output to forward dbg_uart to the console window. The RTT control block address may need to be configured manually based on your FPGA synthesis results.
With RTT configured, you should see text output in the debugger console once your Cortex-M0 firmware prints messages over dbg_uart. This provides essential visibility into early boot issues.
Verifying Core Initialization
As a first firmware test, load a basic Cortex-M0 program that blinks an LED Peripheral registers can be accessed to confirm the core is operational: /* Enable GPIOA clock */ RCC->AHBENR |= (1 << 17); /* Set GPIOA pin 5 as output */ GPIOA->MODER |= (1 << 10); /* Toggle pin to blink LED */ while (1) { GPIOA->ODR ^= (1 << 5); for (int i = 0; i < 100000; i++); }
This code blinks an LED connected to GPIOA pin 5 by directly accessing the Cortex-M0 peripheral registers. Pin 5 must be routed to an external FPGA pin.
Verify the LED toggles at a steady 1 Hz rate. If the LED blinks correctly, this confirms your FPGA design correctly integrates the Cortex-M0 core and enables firmware to run.
If no blinking occurs, debug messages over RTT can help identify issues:
- dbg_uart output detected – Core is running but LED not connected/configured properly
- No dbg_uart output – Core is likely not starting – step down to baremetal C and debug from reset
Running Baremetal Test Code
For deeper system debugging, baremetal C test programs are invaluable. Compile a simple program that outputs characters over dbg_uart: void main() { volatile int i; /* dbg_uart initialization */ … /* Output alphabet */ for(i = ‘A’; i <= ‘Z’; i++) { putchar(i); } }
This minimal code tests the reset setup and dbg_uart initialization. If the character sequence prints, you have confirmed:
- Reset configuration is correct
- System clocks are running
- dbg_uart peripheral is enabled
With basic I/O working, more advanced baremetal programs can be written to validate peripherals, memory, and interrupts. Test programs should output status messages over dbg_uart to debug issues.
Debugging Stalled Execution
A common problem is the Cortex-M0 stalling during reset or early in the boot sequence. This can occur due to:
- Incorrect clock configuration
- Faulty memory initialization
- Peripherals not enabled properly
- SWD synchronization issues
Use JTAG to halt and single step the processor when it stops executing. Examine memory contents and peripheral registers to debug the issue.
The ARM Keil and Segger debug tools include a System Viewer component which visualizes peripheral register contents. This can rapidly identify configuration errors.
For suspected clock issues, single step execution at the cycle level while monitoring the core clock and bus clocks. This can reveal clock skew problems.
Debugging Memory Access Faults
Invalid memory accesses are another common source of crashes. These are triggered by:
- Stack/heap corruption
- Uninitialized pointers
- Attempting to execute from invalid code addresses
Memory faults activate the Cortex-M0 memory management fault handler. By default, this causes a system halt.
When the core triggers a fault event, the fault status registers contain details on the fault reason and memory location. The Fault Status Register’s FSR bits indicate: 0x4 = Stack access fault 0x2 = Unstack’d exception entry fault 0x1 = Precise data access fault
The Fault Address Register indicates the memory address that triggered the fault.
With this information, you can trace back through the code and identify the bug causing the invalid memory access. Fault handlers can be customized to add more context if needed.
Conclusion
Debugging Cortex-M0 DesignStart cores in third-party FPGAs requires extra effort compared to ARM’s own hardware. But carefully validating the debug infrastructure and utilizing debug best practices can avoid many common pitfalls. With robust firmware test code and debug tools that provide low-level system visibility, the path to silicon success is much shorter.