Customized Bareboard Run-Time Libraries

This appendix describes how to customize, build, and install run-time libraries for use on bareboard targets.

GNAT Pro includes one or more preinstalled run-time libraries for a specific board of the ISA and ABI targeted by the compiler and ancillary tools. In some cases, multiple boards with multiple run-time libraries may be supported.

The files required for building these run-times are included with the installation and may be modified by developers. The files may be used to create a customized run-time, either as a relatively minor modification to an existing library, or as a distinct new run-time library for a new board.

Note: When making permanent changes to a run-time library, ensure that you can repeat the process because you may need to do it more than once. In particular, you may need to do it again when you upgrade to a newer version of the compiler because the compiler and the run-time library are tightly coupled. Consequently, the run-time may have changed. If so, you will need to reapply your changes to this new run-time library instance. Therefore, we suggest you manage the differences, in addition to the resulting files, under configuration control. For example, you could generate and keep “patch” files that can be reapplied to the files of the run-time library.

The run-time library consists of files that are dependent on the target board, as well as files that are target-independent. The latter implement language-defined functionality whereas the former are responsible for configuring and interacting with the hardware.

The target-specific files are known as a Board Support Package (BSP). As such, they may require modifications when adapting a run-time library for an existing target or when porting the run-time library to a new target. In contrast, the files implementing language-defined functionality will almost certainly not be changed.

The BSP files are located in the source directories of each run-time library. Modifications to the files of any one run-time library do not affect any other library.

To adapt or port a run-time library:

  1. Copy an existing run-time library directory tree
  2. Modify or augment the files within the new run-time library
  3. Build the run-time library.

The sections below will provide the details and an example of performing these steps.

Rationale for A New Run-Time Library

The following subsections discuss the various considerations that would lead one to create a new run-time library.

Hardware Considerations

The run-time libraries tested and shipped with GNAT are targeted to specific boards and setup configurations. Because microcontrollers have a range of on-chip configuration options, for example memory and clock configurations, a supplied run-time library supporting your microcontroller may not be configured optimally for your specific board.

For instance, the STM32F4 run-times shipped with GNAT are configured to support the STM32F4 Discovery kit and its STM32F407 microcontroller. Even if you use the STM32F407 in your own project you may need to customize the STM32F4 run-time to support your particular setup. For example, the STM32F4 run-times are configured to use the 8MHz external clock provided by the STM32F4 Discovery kit whereas your setup may use a different clock source, such as the internal RC oscillator.

However, not all provided run-times are necessarily tied to a particular microcontroller. Continuing the STM32F4 example, the STM32F4 run-times support both the STM32F4 Discovery and STM32F429 Discovery kits, which share similar hardware setups. But without tailoring the STM32F4 run-time you cannot take advantage of the higher clock frequencies and larger memory available on the STM32F429 microcontroller.

One could alter the run-time library for the F4 to support the F429 but then it would no longer work for an actual F4 target. In that case it makes sense to create a new run-time library and tailor it specifically to the distinctive hardware, with a name indicating the new target.

Other hardware differences also justify a new run-time. For example, the number and priority of interrupts may differ, or the startup-code may be required to set up additional devices or configure them in a different manner. In general, changes that would not work on a supplied run-time’s target justify a new run-time library.

Software Considerations

Application dependence on language-defined functionality is also a reason to create a new run-time library. This dependence is a potential problem because all the bareboard run-times supply subsets of the language-defined features. Different run-times provide different subsets. For example, some run-times do not include the Ada standard math packages. You may decide to augment a supplied run-time’s subset accordingly, in which case a new run-time library is arguably appropriate.

Specifically, you can add language facilities that are implemented by stand-alone program units that do not require integration with the rest of the run-time library. Such units are not implemented directly by the compiler and are part of the run-time library only as a means of making them available to the builder. The language-defined math packages are good examples, as are some of the various container packages.

You cannot add language-defined features that are not supported by the Ravenscar-Full runtimes.

Another reason for a new run-time library concerns dynamic memory allocation. The various run-times implement it differently, and you may want to change how that is done. A new run-time library is justified in that case too, even if nothing else is changed.

The Run-Time Library Structure

The run-time implementations are partitioned into two major parts. The “GNAT” part implements the sequential portion of the language not otherwise implemented directly by the compiler. The “GNARL” part implements the tasking portion of the language. Because the ZFP run-times do not include tasking there is no GNARL part to those run-time libraries.

The run-time library directory tree structure reflects these logical partitions. For the Ravenscar-based run-times that include one or both of the tasking profiles (Ravenscar and/or Jorvik), there is both a gnat subdirectory and a gnarl subdirectory, each containing corresponding source files. For the ZFP run-times, which contain no tasking, there is only the gnat subdirectory. These subdirectories are located in the root of the run-time directory tree.

Additionally, the supplied linker scripts are located in the ld subdirectory, also off the run-time root.

The files that constitute the BSP are located in these directories.

For each run-time, there are further subdirectories intended to hold files modified by you, if any. (Initially these subdirectories are empty.) The directory names have a “_user” suffix to reflect the intent. There will always be gnat_user and ld_user subdirectories. For the Ravenscar run-times there will also be a gnarl_user subdirectory.

Any files found in these “user” directories automatically take precedence over those in the other directories when the run-time library is built. As a result, we recommend you copy the files you intend to modify into those subdirectories, rather than modify them in place. The latter approach will work but separating them will facilitate keeping track of the changes.

Building A Run-Time Library

Ultimately, a modified run-time library must be built and installed before the tool-chain (compiler etc.) can use it to create an executable image.

Predefined builder options defining various switch settings are declared as scenario variables in the GNAT project files (“gpr” files) used to build the run-time library. Loader options are controlled by a scenario variable defined in an XML file. These options are discussed in dedicated sections below. Users can modify these definitions as needed.

Important: you must use the cross compiler for the target specified by the run-time project file. If you have multiple GNAT compilers installed, ensure that your environment path is set so that the cross compiler for your target takes precedence over other compilers. If building a run-time library fails, this is likely the problem.

Building

Run-time libraries are built using the gprbuild tool, with a primary GNAT project file specified as the principle parameter. These primary builder project files are named as follows:

  • For the Ravenscar run-times, the name is ravenscar_build.gpr for both the “Full” and “SFP” run-times.
  • For the ZFP run-times, the name is runtime_build.gpr instead.

These project files reference one or more other project files but users can ignore the others unless their content requires modification.

All the project files are located in the roots of the individual subdirectory trees containing the run-time sources and other files for each individual run-time library.

For example, to build the Ravenscar-SFP run-time for the stm32f4 target (STM32F4 Discovery kit), we would go to the subdirectory for that specific run-time library and then issue the following command:

$ gprbuild -P ravenscar_build.gpr

The built run-time library is located in the subdirectory tree containing the GNAT project file.

You may apply additional gprbuild command-line switches if needed. For example, to have gprbuild utilize all processors on your computer to build the run-time you would also specify the -j0 switch:

$ gprbuild -j0 -P runtime_build.gpr

Refer to the gprbuild documentation for all the switches available.

As mentioned, various project build options are defined within the project files. These options are specified via the BUILD project scenario variable and are useful when debugging or profiling run-times:

  • Production: (default) no debug information, optimized, no assertions
  • Debug: debug information, no optimization, no assertions
  • Assert: assertions within the run-time library code are enabled
  • Gnatcov: sets the proper flags for use with the gnatcov tool

To specify the scenario variable include -XBUILD=<value> on the command line when invoking gprbuild. For example, to build the run-time with debugging information use:

$ gprbuild -j0 -P ravenscar_build.gpr -XBUILD=Debug

Be advised that enabling assertions within the run-time will significantly increase the execution and memory overhead. We recommend you only enable run-time assertions when requested by AdaCore support.

If you want to remove the build artifacts use the gprclean tool, again specifying the primary GNAT project file. In addition, though, apply the “-r” switch to clean “recursively” because the primary project references other projects. For example:

$ gprclean -r -P ravenscar_build.gpr

Choosing the Location

Building a run-time library also effectively installs it because all you must do to employ it is pass its location to the builder and other tools. No particular location is required. The builder will be able to use it via the standard approaches documented in Specifying the Run-Time Library, including the :switch:--RTS switch on the command line and the Runtime attribute in GNAT project files.

However, as that section indicates, some locations are more convenient than others. In particular, if a run-time library is placed in the same location as the run-times provided with the GNAT compiler, the run-time can be specified to the GNAT tools by the name alone (e.g., “ravenscar-full-stm32f4”). If installed outside this folder, the tools require the full path for the run-time.

The run-times provided with the GNAT bareboard compilers are located in a subdirectory under the GNAT installation root, structured like so:

<gnat-installation-root>:
  \--> <target>
         |--> lib
               |--> gnat
                     |--> <run-time>
                     |--> ...
                     \--> <run-time>

In the above, “<target>” is the name of the ISA supported by the compiler, and is the same string used to specify the Target attribute in the GNAT project file. For the ARM EABI targets, for example, the target is “arm-eabi” so the tree is as follows:

<gnat-installation-root>:
  \--> arm-eabi
         |--> lib
               |--> gnat
                     |--> ravenscar-full-stm32f4
                     |--> ravenscar-full-tms570
                     |--> ...
                     |--> ravenscar-sfp-stm32f4
                     |--> ravenscar-sfp-tms570
                     |--> ...
                     |--> zfp-stm32f4
                     |--> zfp-tms570
                     \--> ...

The PowerPC ELF compiler would have “powerpc-elf” for the <target> folder name, with run-time library names such as “ravenscar-full-mpc8641” and “zfp-mpc8641” to indicate the run-time and board supported.

If you are rebuilding a run-time to simply apply different switches, invoke the builder as described above and specify the existing GNAT project file and the intended scenario variables and values. If you want a new set of switches – in effect a new scenario – we suggest you augment the existing project files to define that scenario and its switches, then build. Doing so will support repeatability when building in the future.

Example: Creating An STM32F429 Run-Time

To illustrate the process of modifying a run-time to a specific board this section will illustrate tailoring the run-time library for the STM32F4 Discovery kit into a new run-time supporting the STM32F429 Discovery kit.

The STM32F429 Discovery kit is similar to the STM32F4 Discovery kit but contains more memory and supports a faster clock. Therefore, the new run-time will be created from the existing STM32F4 run-time library. We will also base our run-time off the Ravenscar-SFP run-time, so the new run-time library will be called ravenscar-sfp-stm32f429 to reflect the different target.

Initial Library Creation

Any bareboard run-time library supplied with GNAT can be used as the basis for a new, customized run-time library. You must choose a run-time that has the same ISA and ABI, obviously (e.g., powerpc-elf), but the closer the existing run-time library’s configuration is to your platform, the better.

We suggest creating your new run-time library as a copy of an existing library if there is one sufficiently close to your platform. To do so, copy the entire run-time subdirectory tree and make modifications in that copy. Starting from a copy enables you to use or refer to the original run-time without having to reinstall GNAT. We also suggest the copy be located in the file system along with the run-times supplied with the compiler, for the reference convenience described above.

Contact AdaCore if there is no shipped GNAT run-time that is close enough to be a good starting point.

Using a Unix shell for example, we first go to the GNAT subdirectory containing the supplied run-times:

$ cd <GNAT-install-directory>/arm-eabi/gnat/lib

Be sure to use the path indicated, under your GNAT installation root directory. There are other folders with the same names, including some that have the names of the run-times, so some care is required.

Next, copy the entire directory tree representing the existing run-time library:

$ cp -r ravenscar-sfp-stm32f4 ravenscar-sfp-stm32f429

And then go into that new directory tree:

$ cd ravenscar-sfp-stm32f429

We will first clean the library and then build it, as described in the Building A Run-Time Library section. This is a Ravenscar run-time so the name of the primary project file is ravenscar_build.gpr as shown below:

$ gprclean -r -P ravenscar_build.gpr

Note the -r switch to recursively clean.

And now the build, just to verify that we’re starting with something that builds successfully:

$ gprbuild -P ravenscar_build.gpr

Although you could start using this run-time immediately, it is functionally equivalent to the original STM32F4 run-time at this point.

Tailoring the Library

Having verified that we are starting with a run-time that can be built successfully, we can now begin making hardware-oriented changes to the BSP files located within our copy. We recommend modifying copies of the BSP files so we will be copying from one part of our run-time to another, instead of from some other run-time library.

Changing Clock Frequency

The BSP package System.BB.Parameters (located in gnat/s-bbpara.ads) declares numerous parameters used by the run-time library to set up the microcontroller and describe the features of the microcontroller available to the run-time. This includes the clock source and frequency.

When a run-time supports multiple boards under one processor family, many of these parameters will come from board-specific parameters declared in the System.BB.Board_Parameters package (in file gnat/s-bbbopa.ads).

The STM32F4 run-times illustrate this structure. The processor clock frequency is defined via the Main_Clock_Frequency named number in the System.BB.Board_Parameters package:

1
2
3
package System.BB.Parameters is

   Clock_Frequency : constant := Board_Parameters.Main_Clock_Frequency;

The System.BB.Board_Parameters package for the STM32F4 microcontroller is located within the run-time in the gnat/s-bbbopa.ads file. Because this file was copied from the STM32F4 run-time, the current value of the Main_Clock_Frequency is that of the maximum frequency of the STM32F4 Discovery kit:

1
2
3
package System.BB.Board_Parameters is

   Main_Clock_Frequency : constant := 168_000_000;

However, we would like our STM32F429 run-time to run applications at the maximum speed of the STM32F429 Discovery boards: 180MHz. Therefore, we copy the spec file gnat/s-bbbopa.ads to the gnat_user subdirectory and make the modification there:

1
2
3
package System.BB.Board_Parameters is

   Main_Clock_Frequency : constant := 180_000_000;

Once rebuilt, the new run-time will run applications on STM32F429 Discovery boards at the higher clock frequency.

Tailoring the Memory Layout

Changing the physical memory configuration for a board requires modification to linker scripts located in the ld directory. The names of the scripts reflect their purpose. For example, the PowerPC targets include the following:

  • ram.ld
  • qemu-ROM.ld

When target families are supported, such as the STM32F4 family, common scripts are defined, with additional scripts tailoring them to specific boards. For example, the STM32 target scripts include the following:

  • common-RAM.ld
  • common-ROM.ld

These common scripts are tailored by additional linker scripts that define the actual sizes and locations of the memory regions defined in the common scripts. For instance, the STM32F4 target contains ld/memory-map.ld that describes the memory sizes and locations of the RAM and Flash on the STM32F407.

For our new STM32F429 Discovery target, the common linker scripts will work because their memory layout is suitable for the STM32F429.

However, the STM32F429 has more Flash and RAM than the STM32F407. Therefore we will need to update the board-specific linker script to take advantage of the larger memory. To do so, we copy the linker script ld/memory-map.ld to ld_user and modify it to reflect the amount of memory available on the STM32F429. The change is from the original:

MEMORY
{
  flash (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
  sram (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
  ...
}

to:

MEMORY
{
  flash (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
  sram (rwx) : ORIGIN = 0x20000000, LENGTH = 192K
  ...
}

With this change in place, our applications will be able to take advantage of the increased memory available on the STM32F429. Note that we do not need to modify the origin of each memory region as they are common between the STM32F407 and STM32F429.

Selecting Linker Scripts

Multiple linker scripts may be included with a run-time to support different memory layout and loading scenarios. For example, scripts for the ARM Cortex-M support for loading a program to Flash (common-ROM.ld) or to RAM (common-RAM.ld).

The LOADER scenario variable selects which linker script is used when building an application project with gprbuild. The variable is defined within an XML file named run-time.xml: a file that provides target-specific project settings to gprbuild and can be found at the root of every run-time directory tree.

As can be seen in the abridged version of this XML file, the scenario variable LOADER controls the choice of linker script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
   type Loaders is ("ROM", "RAM", "USER");
   Loader : Loaders := external("LOADER", "ROM");

   ...

   package Linker is
      for Required_Switches use Linker'Required_Switches &
        ("-Wl,-L${RUNTIME_DIR(Ada)}/adalib",
         "-nostartfiles", "-lc", "-lgnat",
         "-L${RUNTIME_DIR(ada)}/ld_user",
         "-L${RUNTIME_DIR(ada)}/ld") &
         Compiler.Common_Required_Switches;

      case Loader is
         when "ROM" =>
            for Required_Switches use Linker'Required_Switches &
              ("-T", "common-ROM.ld");
         when "RAM" =>
            for Required_Switches use Linker'Required_Switches &
              ("-T", "common-RAM.ld");
         when "USER" =>
      end case;
   end Linker;

You can specify the value of this scenario variable on the command line when invoking gprbuild, thereby controlling which script is applied. The default value (ROM) is used if no explicit argument is specified, while the value USER allows a project specific linker script to be specified in the gpr file of the project.

For example, given a project file app.gpr describing the application, we can invoke gprbuild and supply one or more scenario variable values:

$ gprbuild -P app.gpr -XLOADER=RAM

Or to take the default:

$ gprbuild -P app.gpr

Increased Heap Size

Our target STM32F429 has more memory than the STM32F4 but we need not change the run-time to make it available for dynamic memory allocation. The “heap” will automatically get the additional memory because this is a Ravenscar-SFP run-time.

See Dynamic Memory Allocation for the details of how the run-times implement dynamic memory allocation and deallocation.

Configuring Interrupts

When tailoring a run-time to a new processor the interrupt configuration will require review because the type and number of interrupts may have changed. This is important even when porting a run-time within the same microcontroller family because the number of interrupts will vary based on the peripherals offered by an individual family member. The files to review are located in the gnarl folder in the run-time root directory.

At a minimum the interrupt trap vectors and interrupt names will require review. Typically interrupt trap vectors are defined in an interrupt vector table located in an assembly file: gnarl/handler.s. When porting within a microcontroller family you will only need to ensure the correct number of trap vectors are defined. When porting to a different processor family you will need to refer to that family’s reference manual to determine how to define the interrupt vector table.

Interrupt names and their corresponding Interrupt_ID values are declared in the package Ada.Interrupts.Names (in file gnarl/a-intnam.ads). This file will require changes if the interrupt names or the number of interrupts differ on the new target.

See package System.BB.MCU_Parameters (in file gnat/s-bbmcpa.ads) for the number of interrupts defined. That number must reflect the contents of the package Ada.Interrupts.Names.

The STM32F429 Discovery kit used in this example has more interrupts than the STM32F4 Discovery kit, due to additional devices, so our new run-time requires modification.

We copy gnat/s-bbmcpa.ads to gnat_user folder and change the value of Number_of_Interrupts from 81 to 92:

Number_Of_Interrupts : constant := 92;

We next copy gnarl/a-intnam.ads to gnarl_user and add the following:

--  Flash global interrupt
FLASH_Interrupt                   : constant Interrupt_ID := 4;

Note that the F4 version of this file skips interrupt number 4.

We then add the additional interrupts:

--  UART 7 global interrupt
UART7_Interrupt                   : constant Interrupt_ID := 82;

--  UART 8 global interrupt
UART8_Interrupt                   : constant Interrupt_ID := 83;

--  SPI 4 global interrupt
SPI4_Interrupt                    : constant Interrupt_ID := 84;

--  SPI 5 global interrupt
SPI5_Interrupt                    : constant Interrupt_ID := 85;

--  SPI 6 global interrupt
SPI6_Interrupt                    : constant Interrupt_ID := 86;

--  SAI1 global interrupt
SAI1_Interrupt                    : constant Interrupt_ID := 87;

--  LTDC global interrupt
LTDC_Interrupt                    : constant Interrupt_ID := 88;

--  LTDC global error interrupt
LTDC_ER_Interrupt                 : constant Interrupt_ID := 89;

--  DMA2D global interrupt
DMA2D_Interrupt                   : constant Interrupt_ID := 90;

Likewise, in handler.S we add the additional trap vector table entries:

.word __gnat_irq_trap        /* 98 UART7_IRQ */
.word __gnat_irq_trap        /* 99 UART8_IRQ */
.word __gnat_irq_trap        /* 100 SPI4_IRQ */
.word __gnat_irq_trap        /* 101 SPI5_IRQ */
.word __gnat_irq_trap        /* 102 SPI6_IRQ */
.word __gnat_irq_trap        /* 103 SAI1 */
.word __gnat_irq_trap        /* 104 LTDC */
.word __gnat_irq_trap        /* 105 LTDC_ER */
.word __gnat_irq_trap        /* 106 DMA2D */

We then rebuild the library to get these updates in place.

Interrupt Priorities

The new target hardware may support a different number of hardware interrupt priorities than the processor in the original run-time. In this case the Interrupt_Priority subtype defined in the System package must be changed. (Take note of the guidelines for modifying priority ranges and their language-defined requirements as documented in this section.)

The file for package System is located in gnat/system.ads. The content is highly dependent on the run-time profile (each run-time profile requires its own version).

Within the System package are declarations of priority subtypes and constants defined in accordance with the Ada Reference Manual. The STM32F4 Ravenscar-SFP run-time, for example, contains the following definitions:

1
2
3
4
5
6
7
8
Max_Interrupt_Priority   : constant Positive := 255;
Max_Priority             : constant Positive := 240;

subtype Any_Priority       is Integer range         0 .. 255;
subtype Priority           is Any_Priority range    0 .. 240;
subtype Interrupt_Priority is Any_Priority range  241 .. 255;

Default_Priority : constant Priority := 120;

Note: you should not modify anything else in the package.

The Ada Reference Manual defines the subtype Any_Priority to represent all priority values, with higher numbers indicating higher priorities. This subtype is divided into to two subranges: Priority and Interrupt_Priority. Interrupt_Priority corresponds to the priorities used by interrupt handlers, while tasks, including the environment task, typically use the priorities from the Priority subtype. This assignment of priorities ensures interrupts can interrupt the normal execution of tasks.

Although Ada permits assigning tasks priorities from the Interrupt_Priority range this is not common and may be prevented on some targets, such as ARM. (Tasks are no longer intended to be interrupt handlers, after the initial update to the Ada language standard.)

The range 0 .. 255 is recommended for the Any_Priority subtype because, in practice, 256 levels of priority are sufficient for any application and a smaller range does not reduce the memory requirements of the run-time library. However, you may use whatever bounds correspond to the hardware. Although the Ada standard requires that System.Priority include at least 30 values, that is subject to hardware realities, like all such requirements.

The range of System.Interrupt_Priority should map to the range of interrupt hardware priority levels so that there is one-to-one correspondence between them. Thus, if your new target supports a different number of hardware interrupt priority levels, update Interrupt_Priority to reflect this number and adjust the range of Priority accordingly. For example, if your new target only supports a single hardware interrupt priority, update System to the following:

1
2
3
4
5
6
7
8
Max_Interrupt_Priority   : constant Positive := 255;
Max_Priority             : constant Positive := 254;

subtype Any_Priority       is Integer range         0 .. 255;
subtype Priority           is Any_Priority range    0 .. 254;
subtype Interrupt_Priority is Any_Priority range  255 .. 255;

Default_Priority : constant Priority := 127;

Our STM32F429 target has the same interrupt priorities as the STM32F4 so we need not make any changes in this example.

Startup Code

The STM32F4 startup code is in multiple assembly language files located in the gnat subdirectory of the run-time library.

There are two assembly language files for the startup code, one each for executing from RAM or ROM, plus a common file shared by both (that starts the FPU, for example). These files are named start-ram.S, start-rom.S, and start-common.S.

The specific startup code is selected by the linker scripts’ references to the unique symbols defined in the assembly files, via the entry point definitions.

For the Ravenscar run-times, the vector table is initialized by assembly code in gnarl/handler.S. The Ada code in package System.BB.CPU_Primitives (gnat/s-bbcppr.adb) installs GNAT-specific handlers that raise exceptions for the traps. (In the ZFP run-time the startup code initializes the vector table.)

Our STM32F429 target has the same startup requirements as the STM32F4 so we need not make any changes in this example.

Floating-Point Co-processor

Package System.BB.Parameters (in file :file`gnat/s-bbpara.ads`) specifies whether a floating-point unit (FPU) is present, but this is used in conditional code in the context switch routine, not to decide whether to enable the FPU. The supported STM32 targets all have an FPU so the unit is enabled in the common startup code (start-common.S). Therefore we need not change the startup code for our F429 target.

Augmenting the Library

As described in the Software Considerations section you may want to create a new run-time for the sake of software changes. This section is focused on the specific case of adding software functionality by augmenting the run-time library.

We recommend you create a new run-time library for this purpose. If you have already done so for the sake of hardware changes then you will continue to work with that new run-time in this section. Otherwise, first see Initial Library Creation for how to create a new run-time.

Note that extending a run-time library only makes sense if your new run-time is not a copy of a Ravenscar-Full run-time. Otherwise, the new run-time already contains all possible functionality.

Augmenting a run-time library involves copying individual files from a more functionally capable run-time. If you are augmenting a ZFP run-time then you can copy the files from either one of the two Ravenscar run-times. Copying from a Ravenscar-Full run-time may be more convenient because those run-times include all the Ada language-defined entities GNAT can support in a bare-board run-time. If you are augmenting a Ravenscar-SFP run-time then only a Ravenscar-Full run-time has more functionality available to copy.

Not just any run-time code can be copied into a new library. You must adhere to the limitations described in the Software Considerations section.

The candidate source files, and their dependencies, are located in the gnat directory. They should be copied into the gnat_user directory in your run-time. See the The Run-Time Library Structure section if you are not familiar with these directories.

You must ensure that the dependencies, if any, are also copied into the new run-time. As you will see below, these additional units can be numerous.

Because this is run-time source code, it is possible that the copied code has specific hardware requirements. This is especially true for the lower-level units depended upon by language-defined library implementation code. These requirements must be met by the actual hardware used by your applications. For example, the run-time units implementing floating-point functionality may be implemented with the expectation of hardware support (e.g., a co-processor). You must ensure that the expected floating-point support is available. Otherwise, choose a different set of implementation files to copy.

Once the additional sources are copied over, you simply build the run-time to include the new functionality. The language-defined units can then be used by the application. The lower-level dependencies should not be referenced by the application, typically, for the sake of portability.

Note: If building an application fails with an error message indicating that the GNAT builder has no knowledge of a language-defined unit you think you’ve added to the run-time, either you didn’t copy the correct source files or you did not rebuild the run-time library after copying the files.

As an example of augmenting the run-time, let’s say that the application requires some of the trigonometric functions for application-defined floating-point types. As a result, the application instantiates the generic package Ada.Numerics.Generic_Elementary_Functions.

The ZFP and Ravenscar-SFP run-times do not include the math libraries because these run-times are intended for use in certified systems. A math library might not be required and in that domain the expense of certifying the unused library implementation would not be justified. However, GNAT does have certifiable math library implementations included in the Ravenscar-Full run-times. Therefore, we will copy the files for the math packages that the application requires, along with their dependencies, from a Ravenscar-Full run-time into the gnat_user subdirectory of our run-time.

Locating the source files corresponding to a unit name can be somewhat difficult at first because the source files in the run-times are named in the “8.3” encoded format. The file names do not use the full unit names, unlike the default naming scheme used for application sources, as you will see when we start copying all the files required.

We will first copy the spec and body for the Ada.Numerics.Generic_Elementary_Functions generic package, located in the a-ngelfu.ads and a-ngelfu.adb files, where the ‘a’ stands for “Ada” and “ngelfu” is an encoding for “Numerics.Generic_Elementary_Functions.” And of course we need the Ada.Numerics parent package, so we will have these files copied into the gnat_user subdirectory:

  • a-ngelfu.ads
  • a-ngelfu.adb
  • a-numeri.ads

Next, we must traverse the transitive closure of the dependencies and copy their sources as well. If we look at the generic package body we see the following:

with Ada.Numerics.Elementary_Functions;
with Ada.Numerics.Long_Elementary_Functions;
with Ada.Numerics.Long_Long_Elementary_Functions;

use ...

package body Ada.Numerics.Generic_Elementary_Functions is

Therefore, we need the sources for Ada.Numerics.Elementary_Functions, Ada.Numerics.Long_Elementary_Functions, and Ada.Numerics.Long_Long_Elementary_Functions. The files for those units are:

  • a-nuelfu.ads (Ada.Numerics.Elementary_Functions)
  • a-nlelfu.ads (Ada.Numerics.Long_Elementary_Functions)
  • a-nllefu.ads (Ada.Numerics.Long_Long_Elementary_Functions)

These units are also language-defined so they can be referenced by the application, if needed.

The packages don’t have bodies because each is an instantiation of the System.Generic_C_Math_Interface generic package. We must copy it as well:

  • s-gcmain.ads
  • s-gcmain.adb

These instantiations also reference the single-precision and double-precision libm packages System.Libm_Single and System.Libm_Double. We therefore copy those files as well:

  • s-libsin.adb (System.Libm_Single)
  • s-libsin.ads
  • s-libdou.adb (System.Libm_Double)
  • s-libdou.ads

Each of these reference package System.Libm_Prefix, along with System.Libm, System.Libm_Single.Squareroot, and System.Libm_Double.Squareroot:

  • s-libpre.ads (System.Libm_Prefix)
  • s-libm.ads ((System.Libm)
  • s-libm.adb
  • s-lisisq.ads (System.Libm_Single.Squareroot)
  • s-lisisq.adb
  • s-lidosq.ads (System.Libm_Double.Squareroot)
  • s-lidosq.adb

These low-level system packages are compatible with our new F429 target because our target uses the same floating-point configuration (in this case, hardware support via floating-point co-processor).

The total set of files copied is as follows:

  • a-ngelfu.ads (Ada.Numerics.Generic_Elementary_Functions)
  • a-ngelfu.adb
  • a-numeri.ads (Ada.Numerics)
  • a-nuelfu.ads (Ada.Numerics.Elementary_Functions)
  • a-nlelfu.ads (Ada.Numerics.Long_Elementary_Functions)
  • a-nllefu.ads (Ada.Numerics.Long_Long_Elementary_Functions)
  • s-gcmain.ads (System.Generic_C_Math_Interface)
  • s-gcmain.adb
  • s-libsin.adb (System.Libm_Single)
  • s-libsin.ads
  • s-libdou.adb (System.Libm_Double)
  • s-libdou.ads
  • s-libpre.ads (System.Libm_Prefix)
  • s-libm.ads ((System.Libm)
  • s-libm.adb
  • s-lisisq.ads (System.Libm_Single.Squareroot)
  • s-lisisq.adb
  • s-lidosq.ads (System.Libm_Double.Squareroot)
  • s-lidosq.adb

Although the list is rather long it supports most of the math packages, not just the ones we wanted initially, so adding other math packages would not require many more additions.