In a previous article I wrote about migration from armasm (the legacy Arm assembler) to Arm Compiler 6 (AC6). This was useful for users who were already using the Arm toolchains for their builds. But what of gcc users?
Previously there was a reasonably high barrier to change, but Arm tools can now readily consume GNU assembler and C constructs, allowing you to more easily use the highly optimizing Arm Compiler in your existing projects.
To illustrate the steps necessary, I am using the DS-5 fireworks examples, which are conveniently supplied in AC6 and GCC flavors. I will use Eclipse based projects as they are easier to manage, but I will highlight necessary changes to makefiles as needed.
Though you do not actually need to have GCC installed, if you wish to, I recommend the latest Linaro toolchain, downloaded from here. There is also the GNU Arm embedded toolchain, for Cortex-R and Cortex-M processors, available from here (The fireworks examples are however written for Cortex-A9).
For information on adding gcc toolchains to your DS-5 installation, see my previous article here
We must first import the fireworks projects to the DS-5 Eclipse IDE.
Navigate the menu system to File → Import… → DS-5 → Examples & Programming Libraries, then locate the fireworks_A9x1-FVP_AC6 and fireworks_A9x1-FVP_GCC projects (the filter text box can simplify this step).
Select both, and click the finish button. We shall in this tutorial migrate the GCC project to AC6 (you can use the AC6 project as a reference). I recommend making a copy of the project, say, fireworks_A9x1-FVP_GCC2AC6, so that we keep the original file intact.
Navigate to the project Properties → C/C++ Build → Tool Chain Editor view, and select your latest available Arm Compiler 6 from the pull down (you will need to deselect “Display compatible toolchains only“).
Click on Apply. This will remove all the previous settings, and so we will need to restore these appropriately. Go to the Settings tab, and select Cortex-A9 in All Tools Settings → Target. This is necessary as the project contains code that is CPU specific.
Note for makefile users. Most of the standard command options are common for both gcc and AC6 (armclang). AC6 however supports many Arm architectures in a single executable, and so you will need update your compiler flags to specify which mode the compiler is running in.
For example, for an Armv7 CPU, such as Cortex-A9, you should use
armclang --target=arm-arm-none-eabi -mcpu=cortex-a9 …
For an Armv8 CPU, such as Cortex-A53, you should use
armclang --target=aarch64-arm-none-eabi -mcpu=cortex-a53 …
These settings are done automatically for Eclipse projects.
You will complete the compilation and assembler stages of the build, but it will fail at link time, with some undefined symbol errors. This is expected, as we have not specified any ld script, or the necessary equivalent… a scatter description file.
Within the fireworks_A9x1_GCC project you will find gcc.ld, a linker script for the GNU linker (ld). Within the fireworks_A9x1_AC6 project you will find scatter.scat, an equivalent scatter file for the Arm linker.
Comparing the two files shows a lot of similarities. They each define a number of code and data sections, defined within {} style braces, though the notation contained within is slightly different.
The gcc.ld script that we provide is in fact overly detailed for the needs of this example, explicitly defining regions which can be grouped into other catch-all regions. For convenience, here is a reduced version containing just those features used by this project.
ENTRY(Vectors) SECTIONS { .vectors 0x80000000: { __code_start = .; KEEP(*(StartUp)) *(.text*) *(.rodata .rodata.* .gnu.linkonce.r.*) *(.data .data.* .gnu.linkonce.d.*) *(.bss*) } .heap (NOLOAD): { . = ALIGN(64); __end__ = .; PROVIDE(end = .); . = . + 0xA0000; } .stack (NOLOAD): { . = ALIGN(64); . = . + 4 * 0x4000; __stack = .; } .irq_stacks (NOLOAD): { . = ALIGN(64); . = . + 4 * 256; __irq_stack = .; } .pagetable 0x80500000 (NOLOAD): { __pagetable_start = .; . = . + 0x00100000; } .framebuffer 0x80600000 (NOLOAD): { __framebuffer_start = .; . = . + 0x00100000; } }
Note that I have grouped together the contents of the .text, .rodata, .data, and .bss sections with the startup code (in the .vectors section).
.vectors 0x80000000: { __code_start = .; KEEP(*(StartUp)) *(.text*) *(.rodata .rodata.* .gnu.linkonce.r.*) *(.data .data.* .gnu.linkonce.d.*) *(.bss*) }
Other regions remain, as the code will make use of these explicitly.
In the ld script, the ordering that elements are listed within a section defines the ordering in which they will be placed within this section. Here we ensure that StartUp is at the beginning of the .vectors section, and hence is the first code to be executed after reset.
Scatter loading does not have this restriction, but instead uses +FIRST notation (also +LAST) to force the startup code to be located at the beginning of this section:
VECTORS +0 { startup.o (StartUp, +FIRST) * (+RO, +RW,+ZI) }
In the ld script, sections have an optional start address specified. If no address is specified, then that section immediately follows after the previous one. The scatter file equivalent is to specify the base address as a zero offset, +0. Other commands, such as ALIGN, NOLOAD, etc, are specified as region attributes in scatter loading. For example, the .pagetable region in the ld script:
.pagetable 0x80500000 (NOLOAD): { __pagetable_start = .; . = . + 0x00100000; }
can be specified in scatter loading as:
PAGETABLE 0x80500000 EMPTY 0x0010000 {}
Linker generated symbol names, such as __pagetable_start above, must be manually defined in an ld script file. With scatter loading, a selection of symbol names are implicitly defined, and can be used in the same manner. You can then use #define constructs to map the ld defined symbol to the implicit symbol.
#define __pagetable_start Image$$PAGETABLE$$ZI$$Base // ... LDR r0, =__pagetable_start // no change to code using the symbol
You can use a special naming for (User mode) stack and heap regions and let the C library initialization code set these up for you automatically (you can remove any other code setting these stacks ).
ARM_LIB_HEAP +0 ALIGN 64 EMPTY 0xA0000 {} ARM_LIB_STACK +0 ALIGN 64 EMPTY 0x10000 {}
If, as in this example, you have separate stack and heap regions, you must at some point in your source code import the __use_two_region_memory symbol:
.global __use_two_region_memory
The final scatter file to match the above ld script looks like:
LOAD 0x80000000 0x00100000 { VECTORS +0 { startup.o (StartUp, +FIRST) * (+RO,+RW,+ZI) } ARM_LIB_HEAP +0 ALIGN 64 EMPTY 0xA0000 {} ARM_LIB_STACK +0 ALIGN 64 EMPTY 0x10000 {} IRQ_STACK +0 ALIGN 64 EMPTY 0x1000 {} PAGETABLE 0x80500000 EMPTY 0x100000 {} FRAMEBUFFER 0x80600000 EMPTY 0x100000 {} }
We will need to specify the scatter loading file to the linker. Furthermore, the remaining ld commands map to armlink command line options. We specify these via the project properties pane for the linker:
If editing a makefile, to specify the scatterloading file itself, use
--scatter name_of_scatter_file
To specify the entry point of the code, use
--entry symbol_name
and to forcibly keep a section, use
--keep object_or_section_name
Sections containing the entry point do not need to be explicitly kept, and so there is no need to keep the StartUp section in this example.
The entry point of the GNU C library is labeled as _start. With the Arm C library, it is labeled __main. You must update the branch instruction appropriately, either explicitly, or via #define
#define _start __main // ... B _start
Advanced users may also wish to edit the inline assembler contained within C source code to use compiler intrinsic functions instead. This will make the code more portable to other Arm processors. For example, in the main function, you could make the following edit:
#include <arm_compat.h> // ... // asm("CPSIE i"); // Enable IRQ __enable_irq();
For a more thorough discussion of the source code features and command line options supported by Arm Compiler 6, I would recommend consulting the Compiler Reference Guide.
You should now have a project that builds and executes successfully. With these few simple steps the code is now built with a highly optimizing compiler, with a significantly reduced code footprint (from approximately 21KB of code with GCC, to under 9KB with Arm Compiler 6, version 6.9, both compiled at -O2 optimization level.
To learn more about DS-5, which contains the latest Arm Compiler, as well as the examples described in this article, please click on the link below. A free, fully featured, 30-day evaluation license is available.
[CTAToken URL = "https://developer.arm.com/products/software-development-tools/ds-5-development-studio" target="_blank" text="Get started with the DS-5 Development Studio" class ="green"]