You may be aware that Arm recently released the Arm Musca development platform, a dual-core Cortex-M33 platform, and reference platform for the Platform Security Architecture framework. Arm has had support for this platform within Keil MDK, and now adds similar support in DS-5 5.29 (and later). This is particularly beneficial for developers looking to utilize both CPUs simultaneously, as DS-5 provides a fully integrated multi-core environment. In this blog I will look at how to set up the development environment to support this platform. The configuration supports all DSTREAM and ULINK family of debug probes. I will use DSTREAM-ST in the below.The Musca board is powered via the USB port (connected to PC, or standard phone adapter type power supply). I connect my DSTREAM-ST to the 10-pin CoreSight Debug port on the board located next to the USB.
With everything powered on, the first thing you will notice is that the JTAG adapter does not detect any target voltage (shown by the Target LED on the DSTREAM(-ST) hardware). You must power-on the board with the ISP button held down. This is most easily achieved (especially if you have fingers as big as mine!) by holding ISP and then pressing the HWRST button beside it. This prevents the board from auto-running into whatever code example is programmed into flash.For my first test, I wanted to create a simple semaphore based application to switch control back and forth between the two CPUs. This would allow me to easily verify that debug capabilities are functioning correctly.The complete projects are appended to this blog, but the code is structured as follows, and linked to NS regions of SRAM. There is a shared memory location that is set to either 0 or 1 depending on which CPU has control, which then does a "hello world" type output and sets the semaphore to the opposite value, so that the other core can then do the same. Control goes back and forth indefinitely.
// Shared location defined in header file
volatile int *semaphore = (int *)0x2001A000;
// Core 0
// Core 1
With the code built, it is now time to create appropriate debug configurations for both CPUs. Switch to the DS-5 Debug perspective in DS-5, which you can do via the Window menu → Perspective → Open Perspective → Other... → DS-5 Debug.
Open Debug Configurations... from Run menu.Create a new DS-5 Debugger configuration, and give it a meaningful name (such as Musca-A Core 0). Use the filter platforms text box to search for the Musca configuration, and expand to see both Cortex-M33 CPUs listed. Select Cortex-M33_0.Select the appropriate JTAG adapter type from the pull-down (DSTREAM family in my case) and then Browse for your adapter. Your configuration should look something like this:
Navigate to the Files tab, and specify the Core 0 image to be downloaded.In Debugger tab, specify to debug from main.
When done, you can easily duplicate a similar configuration for Core 1. Click Close to save configurations. You should now see both listed in the Debug Control pane.
Double click on both configurations (the order you do this should not matter) and you will see that we successfully connect to both cores, and each is stopped at their individual main functions. I have rearranged the various panes in the debugger window below to allow easy visibility of the resources I am interested in. Note how you can open multiple versions of any given pane, and then lock that view to the named connection. This is one of the key features of DS-5 that makes it so useful for multi-core debug. You should be able to rearrange your workspace to look something like this:
I set a breakpoint in each application where the semaphore is set, and started each code running. However, neither core ever stops. What could be wrong? I should inspect the semaphore location.I point the memory window to 'semaphore', but nothing is shown, as the target is still running.
Of course, I could stop the CPU to inspect this address, but a better solution is to use the direct interface from the debug port to the bus inside the device to inspect the memory.In the command window, enter "info memory" to see the device structure. We see different buses available, the AHB buses are the ones connected to memory.
And so by prefixing semaphore with this bus name (AHB_M_0:semaphore), we read the address via that bus.
Setting this to zero immediately stops Core 0 at the breakpoint (note Core 1 is still running)
Press continue (F8) and the code runs, setting the semaphore to 1, thereby passing control to CPU1, which hits its breakpoint.
Repeated continues will show each core stopping while the other runs. But what if we wanted both cores to stop when a breakpoint is hit? Let's disconnect both debug configurations, and go back to the configuration set up pane. You may have noticed this DTSL Options Edit button before. This is where we can configure advanced options for the target.
Click on Edit..., and you will see that there is an option for Cross Trigger Interface (CTI) Synchronization. Create a new DTSL configuration with both cores enabled, and click OK with this selected.
Ensure that this new DTSL configuration is enabled
Repeat for the Core 1 configuration.
Note that as both of these connections are to the same target, you will get errors when you connect if there is a mismatch between DTSL options for both configurations.
Reset the board, and connect to both configurations as before, though this time, set the first connection running again before you connect to the second. Set the second one running, and you should now have both configurations running simultaneously. Our semaphore is no longer initialized (after the reset), and so neither breakpoint is hit (the breakpoints are remembered by the debugger from the previous connection).
Set semaphore back to zero, and the Core 0 breakpoint will be hit, which will then immediately also stop Core 1.
Run again, and we hit the Core 1 breakpoint, also stopping Core 0.Happy that debug control is working well, I now wish to try something more elaborate. At the time of writing, there are two Keil MDK examples available, a multi-core 'doorbell' example, and a secure/non-secure code example. The doorbell example is essentially a more complex version of the above, so I will focus on the secure/non-secure case. See more information on the steps necessary to create such an example
We have separate images for the secure and non-secure states of the core, which are isolated views of each other. For this reason, we must load the images separately. It is easiest to use a set up script, which can be specified in the Debugger pane of Debug Configurations. I recommend loading the non-secure image (and its debug symbols) first, followed by the secure image, so that debugging from start will go to the entrypoint of the secure image.
Here is a script to do this, setting a breakpoint at secure main, and running to there.
set debug-from *$ENTRYPOINT
With the image loaded, set a breakpoint at set_led (either from command line, or the functions view), and run to there:
From the Window menu, open Show View → MMU/MPU, and click on the Memory Map tab. Click on Show Memory Map to see the different regions defined. As you can see set_led, at 0x10001E80 is in a secure, executable, region (as expected).
I hope this blog introduced you to some of the capabilities that DS-5 offers with this platform (and any future Armv8-M platform). With security a key driver in the IoT and many other spaces, having capable tools to develop around PSA, Trusted Firmware, and other software frameworks will be a must.
If you want to develop secure software or combine the TF-M (Trusted Firmware for Cortex-M – part of PSA) with your software products, you can get a loan of an Arm Musca board at no cost below.
Register to request an Arm Musca board