A few weeks ago, I demonstrated how to create a project from scratch using the Arm Cortex-M1. In that flow, I demonstrated an approach which fuses the Vivado bit file with the ELF file generated by the Arm Keil MDK. This flow works well for simple applications, however when we want to create more complex designs we need to be able to debug the software running on the board.
Currently in order to debug an Arm Cortex-M1 or Cortex-M3 core within our Xilinx FPGA, we need to use a separate debugger such as a SEGGER J-Link which I will be using in this example. I do however understand this will be changing later in the year with the next Vitis/Vivado release.
I did not include any debugging when we created the original example so we need to make a couple of changes to the Arm Cortex-M1 instantiation and the block design back in Vivado.
The first thing we need to do is enable debug support in the Cortex-M1. I am going to be using JTAG for debug support so I re-customized the IP to support JTAG debugging.
re-customized the IP to support JTAG debugging
This is the only change we need to make to the Cortex-M1 but it is not the only change we need to make to the Vivado design. To be able to debug effectively, we need to update the reset system to allow the processor to be reset on request of the debugger.
This is indicated by the SysResetReq output from the Cortex-M1 which means we need to be able to reset the Cortex-M1, AXI peripherals, and AXI Interconnect when this signal is asserted. We also need to be able to assert the debug reset at power on in order to reset the debug system following power on.
As such, I introduced a new processor reset block and utility logic blocks to combine the overall reset and SysRestReq systems. This is shown in the diagram below where I have color-coded the reset signals as follows: Pink = SysResetReq, Green = Perhieral & Interconnect Reset, Red = Debug Reset, and Blue = System Reset.
To combine the peripheral and interconnect resets as active low, I used an AND block for the system reset. It uses the two MicroBlaze resets out of the processors reset system, which are then inverted to take into account the active low reset assertion.
The final thing to do is to make the JTAG pins, TMS, TDI, TDO, TMS, and TRST external and assign them to a suitable connector. I assigned these to IO0 to IO4 on the Arduino shield header.
We are then in a position to build the bitstream and program the FPGA. For this example I am targeting the Arty S7-50. Of course, this uses the built-in JTAG of the Arty S7-50 board.
The next step is to connect the JTAG pod to the headers. Remember we need to add in the Vcc and Gnd references to the JTAG pod as well.
Using the built-in JTAG of the Arty S7-50 board
The final stage is to open Keil and configure the debugging set up on the options for projects dialog.
Setting up on the options for projects dialog
Under the debugger we need to select settings and then select the JTAG debugger we are using. We may also want to slow the TClk rate if the signal integrity of the cabling is not ideal.
Selecting JTAG debugger
On the final flash download tab, we need to stop the download of flash as there is none to flash and ensure the RAM address is set to the correct location.
JTAG target driver setup
Once this is completed, we can save the settings and make changes to the applications as desired. In this example, I changed the statement (that?) printed out and recompiled the code.
To start a debugging session, we can select the Debug menu and select Start/Stop Debug Session.
Starting a debug session
If you see a warning about flash, click OK and the debug environment will open up. The updated program will be downloaded and the program then halted, awaiting execution.
Downloading the updated program
If we want, we can insert breakpoints in the code prior to running it. Once we click run, we should see the updated output on the terminal.
Now we are able to debug out Cortex-M1 and Cortex-M3 cores at ease in our Xilinx FPGA and create more advanced solutions.