Bareboard and Custom Kernel Topics

This appendix describes how to use the GNAT cross tools to build programs that do not require support from any kernel running on the target hardware.


Since GNAT version 7.3, bareboard run-times are provided as customized run-times. They target only specific boards so unless you are using the same board they must be ported to your target. This simplifies the build of a user program as explained in this appendix. However this requires (as before) an initial porting effort. Note that any such changes must be revisited (and likely re-ported) if you update the compiler. See Customized Run-Time Libraries for adapting a run-time to your board.

Bareboard Run-Time Library Names

The pre-built run-time libraries have names that reflect their characteristics. For example, those that include ‘ravenscar’ in their names provide a subset of the Ada tasking facilities defined by the Ravenscar profile. Similarly, those libraries with ‘zfp’ in the name implement the ‘Zero Foot Print’ profile.

The pre-built run-time libraries also include the name of the target board they support. The names therefore vary with the target and corresponding toolchain.

For example, the name ‘ravenscar-full-tms570’ denotes a run-time library targeted to the Texas Instruments TM570 board. The library will provide the Ravenscar Profile tasking subset. The ‘full’ in the name indicates that the full sequential (i.e., non-tasking) part of Ada is supported. This library could be specified as follows:

project Demo is
   for Runtime ("Ada") use "ravenscar-full-tms570";
end Demo;

See Selecting the Run-Time Library for instructions on how to specify your selection of a run-time library.

Getting Started

To get started on GNAT Pro for a bareboard target, let’s imagine we would like to build a program whose purpose is to print “Hello World” on standard output. For this, you’ll need to update your path so as to add both GNAT Pro and GPS Pro.

Once this is done, let’s start GPS. At startup, GPS opens a window allowing you to choose between using an existing project file, or creating a new one. Let’s select “Start with default project”, which will create one for us with the default settings.

Next, we need to tell GPS which platform we want to target. Select Menu “Edit->Project Properties...”. Click on the “Build->Toolchain” tab and select the GNAT Pro toolchain corresponding to your target.

The second element that needs to be provided is the runtime you want to use. In the “Build->Toolchain” tab we just opened, just click on the down arrow to the right of the “Ada Runtime” field to list all the runtimes which were automatically detected, and select the one you’d like to use. Our Hello World program is simple enough that any runtime, including the smaller (ZFP) runtime, is sufficient.

Click on “Save” to exit the project editor.

Now that this is done, we can write our program. We want that program to be part of our project, so let’s use the project view to create the file. The project view is a tab showing a blue folder named “Default” (the name of our project), and you should see two yellow folders underneath. Right-click on the first one, which should not have a small ‘o’ inside, and select menu “New->Ada Main Unit”. Let’s name our program “hello”, so enter “Hello” in the window that GPS then pops up.

GPS has opened an editor for file “hello.adb”, and provided an implementation skeleton. We can complete that skeleton to contain the following program:

with GNAT.IO; use GNAT.IO;
procedure Hello is
   Put_Line ("Hello World");
end Hello;

Once done, we also need to tell GPS to include this unit as one of the executables we want to build when building the project. Select menu “Edit->Project Properties” again, the select the “Sources->Main” tab, click on the “+” sign in the “Main files” sections and select “hello.adb”. Click on “Save” to exit the project editor.

We’re now ready to build our program! The fastest way to trigger the build is to press the F4 key. A window allowing some customization of the build pops up, but the default settings should be good enough, so either click on “Execute” or just press Enter. You should see the output of the build in the “Messages” window, confirming that “hello” was succesfully built.

GNAT Pro for bareboard comes with GNATemulator, AdaCore’s simulator. To run our program under that simulator, just select menu “Build->Emulator->Run with Emulator->hello.adb”. A new output window labeled “[...]-gnatemu” should be opened, and should show the ouput of our program.

It is also possible to debug the program while being run under GNATemulator, simply by selecting menu “Build->Emulator->Debug with Emulator->hello.adb”. This starts the debugging session after which it is possible to debug the program as usual.

It should also be possible to run your program on the real hardware, rather than GNATemulator. However, given the many variations on the host and target environment that you may be using, it is not possible to formulate a universally applicable sequence of commands for building, loading, and running a program.

Minimizing Object Code and Data Sizes

Bareboard targets typically have stringent constraints on available storage, both for data as well as object code. In addition to careful coding practices, such as sharing generic package instantiations as much as possible, you can request the removal of unused code and data at build time. The technique uses standard GCC switches.

To achieve this reduction, you must specify switches for both the compiler and the linker.

In the compiler switches, request removal of unused code by applying the "-ffunction-sections" switch. In the same way, request removal of unused data by applying the "-fdata-sections" switch. Specifying both switches is allowed.

In the linker switches, specify "-Wl,--gc-sections" to have the linker remove (“garbage collect”) unused code and/or data. Both this linker switch and the compiler switch(s) are essential for any reduction to take effect.

You can get a list of what is removed by including the "-Wl,--print-gc-sections" linker switch.

For example:

package Compiler is
   for Default_Switches ("Ada") use ("-O2", "-ffunction-sections", "-fdata-sections");
end Compiler;


package Linker is
   for Default_Switches ("Ada") use (
end Linker;

Viewing Object Code and Data Sizes

You can have the linker display the amounts of storage used for both code and data, as well as the total amounts available and the resulting usage percentages.

To get this information specify the "-Wl,--print-memory-usage" linker switch.

For example:

package Linker is
   for Default_Switches ("Ada") use ("-Wl,--print-memory-usage");
end Linker;

Of course, you can combine this switch with the switch requesting removal of unused sections. The Linker package in the project file would then appear as follows:

package Linker is
   for Default_Switches ("Ada") use (
end Linker;

With this switch applied the linker will print the information to standard output. The results will thus appear in the GPS Messages window.

For example:

Memory region         Used Size  Region Size  %age Used
           flash:       44268 B         2 MB      2.11%
            sram:       48784 B       256 KB     18.61%

Character Input/Output

The bareboard run-time libraries provide a customized version of package Ada.Text_IO that exports a minimal set of facilities. The package declaration of this customized version is as follows, for all the bareboard run-time libraries:

package Ada.Text_IO is

   procedure Get (C : out Character);
   --  Read from console

   procedure Put (Item : Character);
   --  Output character to the console

   procedure Put (Item : String);
   --  Output string to the console

   procedure Put_Line (Item : String);
   --  Output string followed by new line to the console

   procedure New_Line;
   --  Output new line character to the console

end Ada.Text_IO;

The cut-down package is provided for the sake of initial board check-out and development and is not intended to be a production I/O capability.

The implementations of the package body are target dependent.

For example, on some targets the implementation drives a USART for this purpose. You can use a USB-to-Serial conversion cable to connect those pins (and a ground pin) to a USB port on the host computer, and run an application on the host to monitor that port in order to interact with the board.

In contrast, on the ARM STM32F4x Discovery boards the implementation uses “semihosting”, in which the output goes to the screen showing the st-util outputs. No USB-to-Serial conversion cable is required in that case.

The package GNAT.IO is also provided by the bareboard run-time libraries. The interface is slightly different, as shown below:

package GNAT.IO is
   pragma Preelaborate;

   procedure Put (X : Integer);
   --  Output integer to specified file, or to current output file, same
   --  output as if Ada.Text_IO.Integer_IO had been instantiated for Integer.

   procedure Put (C : Character);
   --  Output character to specified file, or to current output file

   procedure Put (S : String);
   --  Output string to specified file, or to current output file

   procedure Put_Line (S : String);
   --  Output string followed by new line to specified file, or to
   --  current output file.

   procedure New_Line (Spacing : Positive := 1);
   --  Output new line character to specified file, or to current output file

end GNAT.IO;

Note, in particular, the ability to output an integer value, and the absence of any input routine.

In both the GNAT.IO and Ada.Text_IO cases, the underlying board-specific implementation part is in an internal System.Text_IO package provided by the run-time library. See s-textio.adb in particular. You can change that package to reflect your target’s hardware I/O capabilities. However, since the exported interfaces are minimal, you may want to consider using a more target-specific facility as an alternative to GNAT.IO and Ada.Text_IO.

Bareboard Debugging

Debugging applications on bareboard targets involves connecting gdb to the board. You can then either use gdb directly, on the command line, or use GPS to drive it. For some targets, you can also use an emulator (or simulator) instead of an actual target board. Typically a JTAG probe or something similar is involved. The specific mechanisms for connecting, and whether an emulator and/or simulator is available, are target-specific and are discussed in the corresponding sections within this document.