world leader in high performance signal processing
Trace: » compile

GCC toolchain & Compilation process

The compilation process with the gcc toolchain is detailed in the below figure. The compiler does more than source code compilation; it can also implicitly invoke the assembler, and often times the linker. It can automatically parse the file types and call the right tool (for e.g. *.S files need to be pre-processed) with the correct options meant for each stage (-Wl,option for passing to Linker).

It is beyond the scope of this documentation to address all tools and their usage, and is mostly targeted for users who do not come from a GCC or FOSS background. One of the biggest advantages of GCC is that most of its features are target independent, thus allowing easy portability across targets. Few of the below reference links originate from bare metal topics Bare Metal Toolchain and the rest are part of the generic documentation available throughout this Wiki and GNU official docs.

GCC Toolchain Compilation process diagram

The input file types are explained here: File Types

The GCC Assembler can also operate independently, when it is fed with the input file types: *.s or *.S or *.asm. The intermediate files *.o are object files that are generated from the Assembler. The Linker takes these object files along with the Linker script to generate an ELF output file. The ELF files are the final deliverables for development and debugging (through JTAG or through Simulator tool). The Loader will generate the standalone image.

The GNU archiver (not shown in the above image) “bfin-elf-ar” combines a collection of object files into a single archive file, also known as a static library:

bfin-elf-as -> *.o -> bfin-elf-ar -> *.a -> bfin-elf-ld

For details on inline-assembly within C code, refer to: inline_assembly

GCC also supports multiple level of optimization. Refer to these links for more information:

Basic Compilation

Let's take a bare simple code such as below and build the program for a BF533 Blackfin Processor.

Only few options are listed here with examples.

int main(void)
{
	return 0;
}

The basic requirements with the toolchain would be: compilation, linkage, and debugging. The following options provide a bare minimum set of options for this approach.


bfin-elf-gcc -mcpu=bf533-0.3 -g test_gcc_options.c -o test.gcc_options

Where:

  • -mcpu → specify the exact processor part. “bf533-0.3” would mean that you are choosing BF533 with compilation specific to 0.3 Silicon.
  • -g → specify that you are preparing the output for debugging also - with gdb.
  • -o → ELF output file name.

Now look at a more complete usage when compiling in a development environment such as Eclipse. The default Makefiles will invoke the toolchain with a set of important options as given below.

Default Compiling options


bfin-elf-gcc -O0 -g3 -Wall -c -fmessage-length=0 -mcpu=bf533-any \
    -MMD -MP -MFsrc/test_gcc_options.d -MTsrc/test_gcc_options.d \
    -o src/test_gcc_options.o ../src/test_gcc_options.c

Default Linking options


bfin-elf-gcc -mcpu=bf533-any -otest_gcc_options ./src/test_gcc_options.o 

Where:

  • -Wall → All warning you would need while building program
  • -O0 → Compiler optimization level - 0 indicates optimization turned OFF.
  • -c → This informs compiler that the linker should not run and only object files should be produced.
  • -g3 → Include debug information to the highest level.
  • -fmessage-length=0 → Error formating - 0 indicates all error thrown in single line.
  • -MMD/-MP/-MF → Options Controlling the Preprocessor

Assembly programming

The assembler can also work independently, with input file types *.s, *.S and *.asm

  • *.s → original assembler file of GCC.
  • *.S → assembler file that requires pre-processing (pre-processing by gas alone is terribly limited; for e.g. MACROs do not work).
  • *.asm → assembler file similar to that of VDSP. Use -x assembler-with-cpp option.

Refer to the links in main page for assembly programming with GCC. A quick example is given below. It just shows a basic ASM example and also demonstrates how to intermix C and ASM coding.

int main(void);
extern void asm_function(void);
short c_func(short data);
 
int main(void) {
 
	asm_function();
 
	return 0;
 
}
short c_func(short data)
{
	data = 0x5678;
 
	return 0;
 
}
.section .rodata
.align 4
asm_data: .byte2 0x1234
 
.text;
.align 4;
.global _asm_function;
.extern _c_func;
_asm_function:
 
        link 8;
 
	[--SP] = R0;
	[--SP] = RETS;
 
	P0.L = asm_data;
	P0.H = asm_data;
	R0 = [P0];
 
	CALL _c_func;
 
	RETS = [SP++];
	R0 = [SP++];
 
	unlink;
 
	RTS;

Other Compiler Options

Few options with direct influence on Bare-metal applications are explained below. For detailed information, refer to Blackfin GCC options. Note that most of these options directly rely on the default Linker Script provided to user - if you use your own Linker Script, you are on your own.

-msdram
With this option, the Linker places everything, including stack space to SDRAM. Note that the SDRAM block is hardcoded as (*default*: Start: 0x00000000 Length: 0xffffffff?), so it doesn't include any board specific SDRAM size parameter. A user Linker Script is suitable for such requirements.

The SDRAM Controller needs to be initialized by user (in standalone systems, it is done by use of an initialization code) before any code or data can reside in SDRAM.

-mmulticore/-mcorea/-mcoreb
These are options for pulling in the right startup files and linker script for BF561 dual core processor. Using only -mmulticore would indicate a single application/dual core programming model is used. Using -mcorea/-mcoreb along with -mmulticore indicate one application per core programming model is used. The Linker will choose the appropriate scripts from bf561m.ld, bf561a.ld, and bf561b.ld in the toolchain installation.

Note that Core A needs to turn on Core B execution explicitly, by writing a *0* in to CoreB_SRAM_INIT bit of SICA_SYSCR MMR. -mcoreb option will only enable you to build an image specifically for loading into Core B of BF561 processor.

Refer to Startup code in GCC for the default Startup code that includes BF561 specific code also. Refer to Linker Scripts for details on Linker Scripts on Blackfin.

-mspecld-anomaly & -mcsync-anomaly
These options must be enabled for inserting the right workaround for two silicon anomalies in Blackfin. -specld-anomaly enables _WORKAROUND_SPECULATIVE_SYNCS for Anomaly number #05000244 and -mcsync-anomaly enables anomaly _WORKAROUND_SPECULATIVE_LOADS for Anomaly number #05000245. Refer to Blackfin anomaly list to see the detailed description for each part : Anomalies

-mlong-calls
When a function is called, normally the CALL _subroutine; instruction is used. However, the PC-relative offset of the target address should be within the dynamic range of –16,777,216 to +16,777,214 bytes. Enabling -mlong-calls will make sure that the user can call addresses beyond this range - compiler does this by loading the address of the function into a register and then performing a subroutine call on this register.

-msim
Prepares the ELF Image for running in a Simulator environment. By default, all code and data goes in to external memory, but the environment can be changed by specifying --environment option when running the bfin-elf-run tool. When msim is used, crt0.S will be included instead of basiccrt.S file, but it also includes a library (libsim.a) which in turn includes the libgloss syscalls.

-fstack-limit-symbol

If this option is used, the compiler will insert instructions for checking stack consumption, for every compiled function. Note that since there is no OS to take action based on the output of inserted instructions, the feature can only be used for debugging crashes inside an exception handler. The Linker Script must have defined proper symbols for this:

__stack_start = ORIGIN(MEM_L1_SCRATCH);
__stack_end   = ORIGIN(MEM_L1_SCRATCH) + LENGTH(MEM_L1_SCRATCH);

A sample code dis-assembly will look like below (note the comparison of run-time stack with the compile-time calculated data):

ffa00554:   R3 = P2;
ffa00556:   P2.H = 0xffb0;          /* (-80)        P2=0x0xffb00005 */
ffa0055a:   P2.L = 0x1f48;          /* (8008)       P2=0x0xff801f48 */
ffa0055e:   CC = SP < P2;
ffa00560:   IF !CC JUMP 0x0xffa00564 <test_func+16> (BP);
ffa00562:   EXCPT 0x3;
ffa00564:   P2 = R3;
ffa00566:   LINK 0x1f40; 

Compiler Attributes

As given in the gnu pages, “The keyword attribute allows you to specify special attributes when making a declaration.”. Some important attributes for bare metal development are given below. For function attributes, the compiler will generate the function entry and exit sequences suitable for these handlers. Data attributes are mostly used for memory mapping information.

Attribute for specifying variable / array in to a specific section

int sdram_data __attribute__((__section__(".sdram.data")));

Attribute for saving and restoring all core registers

int saveall_func(void) __attribute__((saveall_func));

Example dis-assembly output: The (NOP;) is just an indicator for the C code inside the function - the main idea is to throw the function entry and exit instructions.

          saveall_func:
ffa00280:   [--SP] = ASTAT;
ffa00282:   [--SP] = LC0;
ffa00284:   [--SP] = LC1;
ffa00286:   [--SP] = (R7:0, P5:0);
ffa00288:   [--SP] = I0;
ffa0028a:   [--SP] = I1;
ffa0028c:   [--SP] = I2;
ffa0028e:   [--SP] = I3;
ffa00290:   [--SP] = B0;
ffa00292:   [--SP] = B1;
ffa00294:   [--SP] = B2;
ffa00296:   [--SP] = B3;
ffa00298:   [--SP] = L0;
ffa0029a:   [--SP] = L1;
ffa0029c:   [--SP] = L2;
ffa0029e:   [--SP] = L3;
ffa002a0:   [--SP] = M0;
ffa002a2:   [--SP] = M1;
ffa002a4:   [--SP] = M2;
ffa002a6:   [--SP] = M3;
ffa002a8:   [--SP] = A0.X;
ffa002aa:   [--SP] = A0.W;
ffa002ac:   [--SP] = A1.X;
ffa002ae:   [--SP] = A1.W;
ffa002b0:   LINK 0x4;               /* (4) */
ffa002b4:   NOP; 
ffa002b6:   UNLINK; 
ffa002ba:   A1.W = [SP++];
ffa002bc:   A1.X = [SP++];
ffa002be:   A0.W = [SP++];
ffa002c0:   A0.X = [SP++];
ffa002c2:   M3 = [SP++];
ffa002c4:   M2 = [SP++];
ffa002c6:   M1 = [SP++];
ffa002c8:   M0 = [SP++];
ffa002ca:   L3 = [SP++];
ffa002cc:   L2 = [SP++];
ffa002ce:   L1 = [SP++];
ffa002d0:   L0 = [SP++];
ffa002d2:   B3 = [SP++];
ffa002d4:   B2 = [SP++];
ffa002d6:   B1 = [SP++];
ffa002d8:   B0 = [SP++];
ffa002da:   I3 = [SP++];
ffa002dc:   I2 = [SP++];
ffa002de:   I1 = [SP++];
ffa002e0:   I0 = [SP++];
ffa002e2:   (R7:0, P5:0) = [SP++];
ffa002e4:   LC1 = [SP++];
ffa002e6:   LC0 = [SP++];
ffa002e8:   ASTAT = [SP++];
ffa002ea:   RTS; 

Attribute for specifying a interrupt handler routine

int interrupt_func(void) __attribute__((interrupt_handler));

Example output: The (NOP;) is just an indicator for the C code inside the function. - the main idea is to throw the function entry and exit instructions. Notice the return using RTI.

          interrupt_func:
ffa0032c:   [--SP] = ASTAT;
ffa0032e:   [--SP] = LC0;
ffa00330:   [--SP] = LC1;
ffa00332:   [--SP] = (P5:5);
ffa00334:   [--SP] = R0;
ffa00336:   CC = R0 == R0;
ffa00338:   P5.H = 0xffc0;          /* (-64)        P5=0x0xffc00014(-4194284) */
ffa0033c:   P5.L = 0x14;            /* ( 20)        P5=0x0xffc00014(-4194284) */
ffa00340:   IF CC JUMP 0x0xffa00344 <interrupt_func+24>;
ffa00342:   R7 = [P5];
ffa00344:   LINK 0x4;               /* (4) */
ffa00348:   NOP; 
ffa0034a:   UNLINK; 
ffa0034e:   R0 = [SP++];
ffa00350:   (P5:5) = [SP++];
ffa00352:   LC1 = [SP++];
ffa00354:   LC0 = [SP++];
ffa00356:   ASTAT = [SP++];
ffa00358:   RTI; 
ffa0035a:   NOP; 

Attribute for specifying a nested interrupt handler routine

int interrupt__nesting_func(void) __attribute__((interrupt_handler,nesting));

Example output: The (NOP;) is just an indicator for the C code inside the function. - the main idea is to throw the function entry and exit instructions. Notice the RETI being saved in stack. This ensure that the global interrupt disable bit in IPEND register is cleared, so that a higher priority handler can preempt a lower priority handler. Return address is restored to RETI before RTI instruction is executed.

          interrupt_nesting_func:
ffa0035c:   [--SP] = ASTAT;
ffa0035e:   [--SP] = LC0;
ffa00360:   [--SP] = LC1;
ffa00362:   [--SP] = (P5:5);
ffa00364:   [--SP] = R0;
ffa00366:   CC = R0 == R0;
ffa00368:   P5.H = 0xffc0;          /* (-64)        P5=0x0xffc00014(-4194284) */
ffa0036c:   P5.L = 0x14;            /* ( 20)        P5=0x0xffc00014(-4194284) */
ffa00370:   IF CC JUMP 0x0xffa00374 <interrupt_nesting_func+24>;
ffa00372:   R7 = [P5];
ffa00374:   [--SP] = RETI;
ffa00376:   LINK 0x4;               /* (4) */
ffa0037a:   NOP; 
ffa0037c:   UNLINK; 
ffa00380:   RETI = [SP++];
ffa00382:   R0 = [SP++];
ffa00384:   (P5:5) = [SP++];
ffa00386:   LC1 = [SP++];
ffa00388:   LC0 = [SP++];
ffa0038a:   ASTAT = [SP++];
ffa0038c:   RTI; 
ffa0038e:   NOP; 

Attribute for specifying an exception handler routine

int exception_func(void) __attribute__((exception_handler));

Example output: The (NOP;) is just an indicator for the C code inside the function. - the main idea is to throw the function entry and exit instructions. Inserting this attribute can only *catch* the exception - it does not correct the exception by itself - it is left to the subsequent instructions to do this. The handler returns to the excepting instruction if it was a error exception (such as data mis-alignment) and it returns to the immediate instruction if it was a service exception (such as EXCPT 5;). Please refer to Blackfin Programming Reference Manual for the complete list of exception causes (or excause). The excause value shall be available from the SEQSTAT register.

          exception_func:
ffa002ec:   SP += -0xc;             /* (-12) */
ffa002ee:   [--SP] = ASTAT;
ffa002f0:   [--SP] = LC0;
ffa002f2:   [--SP] = LC1;
ffa002f4:   [--SP] = (P5:5);
ffa002f6:   [--SP] = R0;
ffa002f8:   CC = R0 == R0;
ffa002fa:   P5.H = 0xffc0;          /* (-64)        P5=0x0xffc00000(-4194304) */
ffa002fe:   P5.L = 0x14;            /* ( 20)        P5=0x0xffc00014(-4194284) */
ffa00302:   IF CC JUMP 0x0xffa00306 <exception_func+26>;
ffa00304:   R7 = [P5];
ffa00306:   LINK 0x4;               /* (4) */
ffa0030a:   R0 = SEQSTAT;
ffa0030c:   R0 >>>= 0x1a;
ffa0030e:   R0 <<= 0x1a;
ffa00310:   R1 = SP;
ffa00312:   R2 = FP;
ffa00314:   R2 += 0x8;              /* (  8) */
ffa00316:   NOP; 
ffa00318:   UNLINK; 
ffa0031c:   R0 = [SP++];
ffa0031e:   (P5:5) = [SP++];
ffa00320:   LC1 = [SP++];
ffa00322:   LC0 = [SP++];
ffa00324:   ASTAT = [SP++];
ffa00326:   SP += 0xc;              /* ( 12) */
ffa00328:   RTX; 
ffa0032a:   NOP;