ERC32 / LEON / LEON3 Topics

This appendix describes topics that are specific to the GNAT for ERC32 / LEON / LEON3 cross configurations.

Getting Started with GNAT for ERC32 / LEON / LEON3

This section describes topics that are relevant to GNAT for ERC32 / LEON / LEON3, a cross-development system supporting the Ravenscar tasking model on top of bare ERC32 / LEON / LEON3 computers.

ERC32 is a highly integrated, high-performance 32-bit RISC embedded processor implementing the SPARC architecture V7 specification. LEON is a 32-bit synthesizable processor core based on the SPARC V8 architecture. They have been developed with the support of the European Space Agency (ESA) as the current standard processors for spacecraft on-board computer systems.

The Ravenscar profile defines a subset of the tasking features of Ada which is amenable to static analysis for high integrity system certification, and that can be supported by a small, reliable run-time system. This profile is founded on state-of-the-art, deterministic concurrency constructs that define a model which has the required expressing power for constructing most types of real-time software.

The tasking model defined by the profile includes a fixed set of library level tasks and protected types and objects, a maximum of one protected entry with a simple boolean barrier for synchronization, a real-time clock, absolute delays, deterministic fixed-priority preemptive scheduling with ceiling locking access to protected objects, and protected procedure interrupt handlers, as well as some other features. Other features, such as dynamic tasks and protected objects, task entries, dynamic priorities, select statements, asynchronous transfer of control, relative delays, or calendar clock, are forbidden.

Using the cross-compilation system is very similar to its use in a native environment. The major difference is that the target name of the cross tools must be specified: leon3-elf / leon-elf / erc32-elf.

$ gprbuild --target=leon-elf -P prj.gpr

The result of this compilation is an ELF-32 SPARC executable. The tool chain has been tested to work on the following environments:

  • A stand-alone ERC32 (TSC695F Rad-Hard 32-bit Embedded Processor) computer based board.
  • A stand-alone LEON2-FT (AT697E Rad-Hard 32-bit SPARC V8 Processor) computer based board.
  • A stand-alone LEON3-FT (UT699 Rad-Hard 32-bit SPARC V8 Processor) computer based board.
  • A SIS ERC32 simulator (SPARC Instruction Set Simulator) running on the development workstation.
  • TSIM ERC32 (TSIM Simulator User’s Manual) running on the development workstation.
  • TSIM LEON (TSIM Simulator User’s Manual) running on the development workstation.
  • TSIM LEON3 (TSIM Simulator User’s Manual) running on the development workstation.
  • QEMU (QEMU Emulator User Documentation) running on the development workstation.

The embedded multiplier/divider block in LEON2 AT697E can generate wrong values when negative operands are used (see AT697E Errata Sheet, erratum #1). The -mcpu=v7 compiler switch must be used to avoid the generation of these potentially problematic instructions.

There is also a hardware problem of data dependency not properly checked in some double-precision FPU operations (see AT697E Errata Sheet, erratum #13) that affects LEON2 AT697E and AT697F boards. The -mfix-at697f compiler switch must be used to insert a NOP before the double-precision floating-point instruction.

The -mfix-ut699 compiler switch must be used to work around the known hardware problems in the GRFPU/FPC Floating Point Unit present in GRLIB revision 1996 (see UT699 LEON 3FT Microprocessor GRFPU/GPC Floating Point Errata).

It is expected that other ERC32 / LEON / LEON3 boards, emulators or simulators could also be used with minor adaptations without problems.

Executing and Debugging on Emulators and Simulators

The program generated by the compilation toolchain can be executed using a LEON QEMU emulator on the development platform.

$ gprbuild --target=leon-elf --RTS=ravenscar-sfp-leon -P prj.gpr
$ leon-elf-gnatemu main

The same executable can also be run using the TSIM simulator.

$ tsim -freq 50 main

Typing go from the command prompt starts program execution.

There is another simulator for ERC32, called SIS, which supports the Motorola S-Record and not the ELF-32 SPARC executable format. Therefore, it requires an additional step to change the format of the executable.

$ erc32-elf-objcopy -O srec main main.srec
$ sis -freq 20 main.srec

Remote debugging using emulators/simulators is also possible. A remote debugger stub is needed in order to interact with GDB; this remote monitor can be either the one embedded into the emulators/simulator or the one provided with the GNAT development environment (leon-stub / leon-stub-prom.srec, erc32-stub / erc32-stub-prom.srec).

Both QEMU and TSIM embed a remote protocol for communicating with the debugger through an IP port (1234 by default) which is activated when using the -s switch on QEMU (the -S switch should be added to prevent the application from starting), and the -gdb switch or the gdb command on TSIM.

$ leon-elf-gnatemu -g main

The debugger can be launched directly from the command line or from GPS. Before loading the program to debug, GDB must connect to the simulator using the remote protocol and the required IP port.

$ leon-elf-gdb main
(gdb) target remote localhost:1234
(gdb) continue
(gdb) detach

Executing and Debugging on Hardware Platforms

The compiler generates ELF executables that can be converted into S-record files using the objcopy command (details are in the binutils documentation). For example:

$ leon-elf-objcopy -O srec main main.srec
$ grmon
grlib> load main.srec
grlib> run

Applications are linked to run from beginning of RAM, at address 0x40000000 for LEON (0x2000000 for ERC32). A boot-PROM can be created containing the application to be executed on a standalone target by using the mkprom2 utility. It will create a boot image that will initialize the board and memory, load the application into the beginning of RAM, and it will finally start the application. This utility will set all target dependent parameters, such as memory size, number of memory banks, waitstates, baudrate, and system clock. Applications do not set these parameters themselves, and thus do not need to be relinked for different board architectures.

The example below creates a boot-PROM for a system with 4 Mbyte RAM, 128 Kbyte ROM, a 50 MHz system clock, and UARTs programmed for 115200 baud rate. A file called main.srec is created, whose format is Motorola S-record, which is usually accepted by PROM recorders. It is possible to use an LEON / ERC32 simulator (SIS or TSIM) to load and test the resulting file.

$ mkprom2 -ramsize 4096 -romsize 128 -freq 50 -baud 115200 \
     main -o main.srec

Debugging software on the LEON / ERC32 board with GDB is possible by either using the provided GDB stub or using the remote target capability provided by GRMON.

A GDB stub is an embedded executable that provides the debugging API required by GDB, implementing the GDB remote protocol across a serial line.

The provided GDB stubs can be found at <install-dir>/(erc32|leon)-elf/lib. There are versions that can be loaded in memory and executed (leon-stub / erc32-stub), and others that can be copied into the boot-PROM (leon-stub-prom.srec / erc32-stub-prom.srec). At startup, this monitor installs itself into the upper 32K of RAM, and waits for GDB to be attached.

Remote target debugging requires a serial link between UART 2 (debugging link) on the remote target and a serial device on the host station. If the program produces some output to the console, another serial link connection is required from UART 1 (console on the target) to a terminal emulator on the host to display it.

The monitor supports break-in into a running program by pressing Ctrl-C in GDB, or interrupt in GPS. The two timers are stopped during monitor operation in order to preserve the notion of time for the application.

$ leon-elf-gdb main
(gdb) set remotebaud 115200
(gdb) target remote /dev/ttyS0
(gdb) load
(gdb) continue
(gdb) detach

GRMON can act as a remote target for GDB allowing for symbolic debugging of target applications. This functionality is activated by launching the monitor with the -gdb switch or using the GRMON gdb command:

$ grmon -gdb

By default, GRMON listens on port 2222 for GDB connections:

$ leon-elf-gdb main
(gdb) set remotebaud 115200
(gdb) target remote :2222
(gdb) load
(gdb) continue
(gdb) detach

SMP Support

The LEON3 “ravenscar-full” and “ravenscar-sfp” runtime libraries now support symmetric multi-processing (SMP). SMP support is enabled by default, i.e., no switch is required.

The runtime libraries are pre-configured to support two processors. Processors are numbered beginning at one, therefore applications can assign tasks to either CPU #1 or CPU #2.

The number of processors suported can be increased by changing the value of Max_Number_Of_CPUs in package System.BB.Board_Parameters and then rebuilding the runtime library.

Per the Ada language standard (RM D.13/11), under the Ravenscar profile all tasks execute on a single processor unless the application explicitly uses aspect “CPU” to assign tasks to processors. (This is due to the No_Dynamic_CPU_Assignment restriction.) Therefore, CPU #1 is used by default.

All runtime and library unit elaboration is done on CPU #1. After that code completes CPU #2 is started. Note that the configuration pragma Partition_Elaboration_Policy (RM H.6) can be used, as usual, to control how library unit elaboration is performed regarding task activations and interrupt handler attachment, but a single processor is used nonetheless.

The implementation of delay statements is such that the resulting timer interrupt always goes to CPU #1, then to CPU #2 (via software interrupt) if that’s where the task requesting the delay is located. Thus there will be some overhead in that case.

Adapting the Run-Time System

There may be some variations among the different boards. Some (e.g., clock frequency) require changing the sources of the BSP and rebuilding the runtime library (see Adapting and Porting the Run-Time for Bareboard Platforms). Others can be changed by modifying linker scripts.

The linker scripts, such as (erc32|leon).ld, define memory size and stack size. If these parameters need to be adapted, a local copy of this script can be created, and the following characteristics can be modified:

  • Memory size. The _RAM_SIZE variable defines the size of the RAM memory installed in the board. There is also the memory map that needs to be tailored:

      ram (rwx) : ORIGIN = 0x40000000, LENGTH = New_Length
  • Stack size for the environment task. The stack area allocated to the environment task is defined by the variable _STACK_SIZE.

The local copy of the linker script can be selected by setting the environment variable LDSCRIPT to point to the custom linker script to use.

User specific hardware initialization can be performed before elaboration of Ada code. This is done by __gnat_hw_initialize, whose default behaviour is to return immediately. This routine is called before memory initialization, so the routine should be written in assembly. Routine calls are possible as long as there is no window overflow. Finally, be sure that your routine is linked in the executable (double check with objdump). If needed, force the link when using gprbuild by using a 'globl pseudo assembly instruction, like this:

with System.Machine_Code;

   System.Machine_Code.Asm(".globl __gnat_hw_initialize",
                           Volatile => True);

Run-Time Restrictions

The run time supports the tasking model defined by the Ravenscar profile; it has been specifically designed to take full advantage of this profile, that allows for a streamlined implementation of the Ada run-time library directly on top of bare ERC32 / LEON / LEON3 computer.

There are some other additional restrictions due to the bare board constraints, such as the removal of file system support, and complex text input-output. The exception handling mechanism that is implemented supports the full semantics of Ada 83 exceptions; Ada 95 enhancements are not included, but it supports propagation of exceptions and handlers for multiple tasks. It supports also limited Ada 95 exception occurrences, and Ada.Exceptions.Exception_Name. Mapping of the usual traps for hardware exceptions (division by zero, data access error, etc.) to Ada exceptions is also done.

In terms of Annexes supported, the status is the following:

  • Annex A (Predefined Language Environment) is partially supported. Those functionalities related to file systems (A.7, A.8, A.14), complex text input-output (A.10, A.11, A.12), and command line access (A.15) are excluded.
  • Annex B (Interface to Other Languages) is fully supported.
  • Annex C (System Programming) is fully supported.
  • Annex D (Real-Time Systems) is fully supported, excluding those features that are forbidden by the Ravenscar profile (D.4, D.5, D.11).
  • Annex E (Distributed Systems) is fully excluded.
  • Annex F (Information Systems) is partially supported. Those features related to text input-output (F.3.3, F.3.4) are excluded.
  • Annex G (Numerics) is fully supported.
  • Annex H (Safety and Security) is fully supported.
  • Annex J (Obsolescent Features) is partially supported. Those functionalities related to text input-output, Ada.Calendar, and interrupt entries (J.7.1) are excluded.

Console Output

There is no console output support in the ZFP run-time library to avoid the inclusion of object code from this library. Ravenscar run-time libraries do provide this support.

The UART A on ERC32, or the UART 1 on LEON2 and LEON3, is used as the target console. When using the hardware board, a serial link connection is required from this UART port to a terminal emulator on the host (such as tip, minicom, HyperTerminal, etc.) to display it.

The Ravenscar profiles include the package GNAT.IO which can be used for displaying Character, Integer, and String.

with GNAT.IO;

procedure Main is
   GNAT.IO.Put_Line ("Hello world");
end Main;

Stack Overflow Checking

GNAT does not perform stack overflow checking by default. This means that if the main environment task or some other task exceeds the available stack space, then unpredictable behavior will occur.

To activate stack checking, compile all units with the gcc option -fstack-check. For example:

$ leon-elf-gcc -c -fstack-check package1.adb

Units compiled with this option will generate extra instructions to check that any use of the stack (for procedure calls or for declaring local variables in declare blocks) do not exceed the available stack space. If the space is exceeded, then a Storage_Error exception is raised.

For declared tasks, the stack size is always controlled by the size given in an applicable Storage_Size pragma (or is set to the default size if no pragma is used). For the environment task, the stack size is defined by the linker script, and can be modified as described in Adapting the Run-Time System.

Interrupt Handling

As defined in Annex C.3.1 of the Ada Reference Manual, interrupt handlers are declared as parameterless protected procedures attached to interrupt sources. All ERC32 / LEON / LEON3 interrupt sources are identified in package Ada.Interrupts.Names, the package defined by the language for that purpose.

Interrupt handlers are statically attached to interrupt sources in the Ravenscar profile, via the aspect (or pragma) Attach_Handler. (The code fragment below illustrates the usage.)

In addition, interrupt handlers execute at a priority in the range of System.Interrupt_Priority. This priority is specified for the entire enclosing protected object, via aspect (or pragma) Interrupt_Priority. The priority applied by default is the value of System.Interrupt_Priority'Last if no explicit value is specified. Note that, as the highest priority value, handlers executing at System.Interrupt_Priority'Last will block all other maskable interrupts, including the timer interrupt.

Interrupt priorities are statically defined by the interrupt number. Specifically, for any interrupt number n, the corresponding priority is the value System.Interrupt_Priority'First + n - 1. Therefore, interrupt number 1 has priority System.Interrupt_Priority'First, interrupt number 2 has priority System.Interrupt_Priority'First + 1, and so on.

For example, the interrupt source UART_RX_TX_Interrupt is interrupt number 2 on LEON3. That means the corresponding priority is System.Interrupt_Priority'First + 1.

The following code fragment illustrates the declaration of a protected object containing an interrupt handler:

protected Interrupt_Semaphore is
   pragma Interrupt_Priority
     (System.Interrupt_Priority'First + 1);

   entry Wait;

   procedure Signal;
   pragma Attach_Handler
     (Signal, Ada.Interrupts.Names.UART_RX_TX_Interrupt);

   Signaled : Boolean := False;
end Interrupt_Semaphore;

For convenience, declarations for the interrupt priorities are also included in some versions of Ada.Interrupts.Names.

The interrupt handler is executed on its own stack, at the priority given by the Interrupt_Priority pragma.

In order to reduce interrupt latency, the floating-point context is not automatically saved and restored when executing interrupt handlers. Floating-point computations are usually performed outside the protected handler, although if the handler is to execute floating-point instructions, the statements involved must save and restore the floating-point registers being used, in addition to enabling and disabling the floating-point unit.

An example of an interrupt handler that computes a square root using floating-point instructions is the following:

with System.Machine_Code; use System.Machine_Code;

procedure Handler is

   type Register_32 is mod 2 ** 32;
   for Register_32'Size use  32;

   PSR : Register_32;
   --  Processor State Register

   FSR : Register_32;
   --  Floating-point State Register

   F0  : Register_32;
   --  Floating-point register

   Operand : Float := 10.0;
   Result  : Float;

   --  First we enable the floating point unit, by means of setting
   --  the PSR's Enable FPU bit.

   Asm ("rd %%psr, %%l0" & ASCII.LF & ASCII.HT &
        "st %%l0, %0",
        Outputs => Register_32'Asm_Output ("=m", PSR),
        Clobber => "l0");

   PSR := PSR or 16#00001000#;

   Asm ("ld %0, %%l0" & ASCII.LF & ASCII.HT &
        "wr %%l0, %%psr",
        Inputs => Register_32'Asm_Input ("m", PSR),
        Clobber => "l0");

   --  Save the floating point registers that will be used (the
   --  Floating-point State Register and one of the Floating-point
   --  registers).

   Asm ("st %%fsr, %0",
        Outputs => Register_32'Asm_Output ("=m", FSR));

   Asm ("st %%f0, %0",
        Outputs => Register_32'Asm_Output ("=m", F0));

   --  User code (compute the square root)

   Asm ("ld %1, %%f0"       & ASCII.LF & ASCII.HT &
        "fsqrts %%f0, %%f0" & ASCII.LF & ASCII.HT &
        "st %%f0, %0",
        Inputs  => Float'Asm_Input  ("m", Operand),
        Outputs => Float'Asm_Output ("=m", Result),
        Clobber => "f0");

   --  Restore the floating-point registers previously saved

   Asm ("ld %0, %%fsr",
        Inputs => Register_32'Asm_Input ("m", FSR));

   Asm ("ld %0, %%f0",
        Inputs => Register_32'Asm_Input ("m", F0));

   --  Disable floating point operations (clearing
   --  the PSR's Enable FPU bit).

   PSR := PSR and not 16#00001000#;

   Asm ("ld %0, %%l0" & ASCII.LF & ASCII.HT &
        "wr %%l0, %%psr",
        Inputs => Register_32'Asm_Input ("m", PSR),
        Clobber => "l0");

end Handler;

It is the user’s responsibility to unmask the required interrupts in the Interrupt Mask Register. This way, the user has control over the point in time from which the interrupt will be acknowledged.

Non-Symbolic Traceback

A non-symbolic traceback is a list of addresses of call instructions. To enable this feature you must use the -E gnatbind‘s option. With this option a stack traceback is stored as part of the exception occurrence.

Here is a simple example:

with GNAT.IO;
with Ada.Exceptions.Traceback;
with GNAT.Debug_Utilities;

procedure STB is

   procedure P1 is
      K : Positive := 1;
      K := K - 1;
      when E : others =>
            Buffer : constant Ada.Exceptions.Traceback.Tracebacks_Array :=
              Ada.Exceptions.Traceback.Tracebacks (E);
            GNAT.IO.Put_Line ("Call stack traceback locations:");

            for J in Buffer'Range loop
               GNAT.IO.Put (GNAT.Debug_Utilities.Image_C (Buffer (J)));
               GNAT.IO.Put (" ");
            end loop;
   end P1;

   procedure P2 is
   end P2;

end STB;
$ gprbuild -g --target=leon-elf --RTS=ravenscar-full-leon stb -bargs -E
$ leon-elf-gnatemu stb
Call stack traceback locations:
0x4000194C 0x40001518 0x400014F4 0x400014C0 0x4000116C

The location of these call instructions can be inspected with standard binary oriented tools, such as nm or objdump, or with the addr2line tool, that converts addresses into file names and line numbers. It is also possible to use GDB with these traceback addresses to debug the program. For example, we can break at a given code location, as reported in the stack traceback.

$ leon-elf-addr2line -e stb 0x4000194C 0x40001518 0x400014F4 \
     0x400014C0 0x4000116C


$ leon-elf-gdb stb
(gdb) break *0x4000194c
Breakpoint 1 at 0x4000194c: file stb.adb, line 10.

It is important to note that the stack traceback addresses do not change when debug information is included. This is particularly useful because it makes it possible to release software without debug information (to minimize object size), get a field report that includes a stack traceback whenever an internal bug occurs, and then be able to retrieve the sequence of calls with the same program compiled with debug information.

By default, unhandled exceptions display the stack traceback information stored within the exception occurrence.