Chapter 3. Real-Time Programs

Table of Contents
3.1. The Ravenscar Profile
3.2. Additional Predefined Packages
3.3. Interrupts without Tasks

LEON Ada is highly suitable for hard real-time applications that require accurate timing and a fast and predictable response to interrupts from peripheral devices. This is achieved with the following features:

LEON Ada also offers reduced program size by:

This chapter describes how to use Ada tasks, and the associated language features, in an example real-time program.

3.1. The Ravenscar Profile

In support of safety-critcal applications, Ada 95 offers various restrictions that can be invoked by the programmer to prevent the use of language features that are thought to be unsafe. Restrictions can be set individually, or can be set collectively in what is called a profile. XGC Ada supports all the Ada 95 restrictions and supports the implementation-defined pragma Profile. To get the compiler to work with the Ravenscar profile, you should place the following line at the top of each compilation unit.

pragma Profile (Ravenscar);

By default, LEON Ada supports a limited form of tasking that is a superset of what is supported by the Ravenscar profile. The built-in restrictions allow for statically declared tasks to communicate using protected types, the Ada 83 rendezvous or the predefined package Ada.Synchronous_Task_Control.

The Ravenscar profile prohibits the rendezvous and several other unsafe features. When using this profile, application programs are guaranteed to be deterministic and may be analyzed using static analysis tools.

The relevant Ada language features are as follows:

3.1.1. The Main Task

The main subprogram, which contains the entry point, and which is at the root of the compilation unit graph, runs as task number 1. This is known as the main task. The TCB[2] for this task is declared in the run-time system, and its stack is the main stack declared in the linker script file. Other tasks are numbered from 2 in the order in which they are elaborated.

For other than a trivial program, the main task should probably be regarded as the idle task or background task. You can make sure that it runs at the lowest priority by the use of the pragma Priority in the declarative part of the main subprogram.

Example 3-1. Main Subprogram with Idle Loop

procedure T1 is
   pragma Priority (0);
begin
   loop
      null;
   end loop;
end T1;

You might want the background task to continuously run some built-in tests, or you may wish to switch the CPU into low power mode until the next interrupt is raised.

Here is an example main subprogram that goes into low-power mode when there is nothing else to do. Note that the function __xgc_set_pwdn is included in the standard library libc.

Example 3-2. Idle Loop with Power-Down

pragma Profile (Ravenscar);

procedure T1 is
   pragma Priority (0);
   procedure Power_Down;
   pragma Import (C, Power_Down, "__xgc_set_pwdn");
begin
   loop
      Power_Down;
   end loop;
end T1;

The rest of the program comprises periodic and aperiodic tasks that are declared in packages mentioned in the with list of the main subprogram.

Important: In LEON Ada, there is no default idle task. If all of your application tasks become blocked, then the program will fail with Program_Error.

3.1.2. Time Types

The package Ada.Real_Time declares types and subprograms for use by real-time application programs. In LEON Ada, this package is implemented to offer maximum timing precision with minimum overhead.

The resolution of the time-related types is one microsecond. With a 32-bit word size, the range is approximately +/- 35 minutes. This is far greater than the maximum delay period likely to be needed in practice. For a 20 MHz processor, the lateness of a delay is approximately 55 microseconds. That means that given a delay statement that expires at time T, and given that the delayed task has a higher priority than any ready task, then the delayed task will restart at T + 55 microseconds. This lateness is independent of the duration of the delay, and represents the time for a context switch plus the overhead of executing the delay mechanism.

It is therefore possible to run tasks at quite high frequencies, without an excessive overhead. On a 20 MHz LEON, you can run a task at 1000Hz, with an overhead (in terms of CPU time) of approximately 10 percent, leaving 90 percent for the application program.

3.1.3. Form of a Periodic Task

The general form of a periodic task is given in the following example. You should note that tasks and protected objects must be declared in a library package, and not in a subprogram.

In this example, the task's three scheduling parameters are declared as constants, giving the example task a frequency of 100 Hz, and a phase lag of 3 milliseconds, and a priority of 3. You will have computed these parameters by hand, or using a commercial scheduling tool.

Example 3-3. A Periodic Task

T0 : constant Time := Clock;
--  Gets set at elaboration time

Task1_Priority : constant System.Priority := 3;
Task1_Period : constant Time_Span := To_Time_Span (0.010);
Task1_Offset : constant Time_Span := To_Time_Span (0.003);

task Task1 is
   pragma Priority (Task1_Priority);
end Task1;

task body Task1 is
   Next_Time : Time := T0 + Task1_Offset;
begin
   loop

      --  Do something

      Next_Time := Next_Time + Task1_Period;
      delay until Next_Time;
   end loop;
end Task1;

The task must have an outer loop that runs for ever. The periodic running of the task is controlled by the delay statement, which gives the task a time slot defined by Offset, Period, and the execution time of the rest of the body.

The value of Task1_Period should be a whole number of microseconds, otherwise, through the accumulation of rounding errors, you may experience a gradual change in phase that may invalidate the scheduling analysis you did earlier.

3.1.4. Aperiodic Tasks

Like periodic tasks, aperiodic tasks have an outer loop and a single statement to invoke the task body.

In the following example, we declare a task that runs in response to an interrupt. You can use this code with a main subprogram to build a complete application that will run on the LEON simulator.

Here is the code for the package and its body:

Example 3-4. An Interrupt-Driven Task

package EG4_Pack is
   task Task2 is
      pragma Priority (1);
   end Task2;
end EG4_Pack;

with Ada.Interrupts.Names;
with Interfaces;
with Text_IO;

package body EG4_Pack is
   use Ada.Interrupts.Names;
   use Interfaces;
   use Text_IO;

   protected IO is
      procedure Handler;
      pragma Attach_Handler (Handler, UART_A_RX_TX);
      entry Get (C : out Character);
   private
      Rx_Ready : Boolean := False;
   end IO;

   protected body IO is
      procedure Handler is
         Status_Word : Unsigned_32;
         for Status_Word'Address use 16#01F800E8#;
      begin
         Rx_Ready := (Status_Word and 16#00000001#) /= 0;
      end Handler;

      entry Get (C : out Character) when Rx_Ready is
         Data_Word : Unsigned_32;
         for Data_Word'Address use 16#01F800E0#;
      begin
         C := Character'Val (Data_Word and 16#0000007f#);
         Rx_Ready := False;
      end Get;
   end IO;

   task body Task2 is
      C : Character;
   begin
      loop
        IO.Get (C);

        --  Do something with the character
        Put ("C = '"); Put (C); Put (''');
        New_Line;

      end loop;
   end Task2;

end EG4_Pack;

Points to note are as follows:

  • The package Ada.Interrupts.Names declares the names of the 15 LEON interrupts.

  • We use address clauses to declare memory-mapped IO locations.

  • The type Unsigned_32 permits bitwise operators such as 'and' and 'or'.

  • The interrupt handler runs in supervisor mode with the Processor Interrupt Level (PIL) set to the level of the interrupt.

Notes

[1]

Simulated TSC695 at 20 MHz

[2]

Task Control Block, which holds the task state