Skip to main content
Department of Information Technology

Lab 1: Real-Time Programming using Ada


See the lab introduction slides.

1. Assignment Goals

The goal of this assignment is to gain basic understanding of the Ada programming language and some of its constructs. Some problems are related to the real-time aspects of programming in Ada, other problems should make the student familiar with the Ada programming style.

All problems address generic aspects of real-time programming.

2. Working in Groups

Solve this assignment in your groups. All students participating in the group shall be able to describe all parts of the solution. The lab should be done in groups of 3 people, or in exceptional cases in groups of 2 people. Submissions by a single student will normally not be accepted.

3. Hand-in

Your submission must consist of the following:

  1. A report with answers to questions asked in each part.
  2. Programs solving the individual assignments (well commented, well structured programs).
  3. Pointers to source code, executables and start-up scripts necessary to run the program (if you use compilers or IDEs different than the lab). Please observe file-access rights, i.e., all files must be readable by the examiner.

Solutions have to be submitted via the student portal, deadline for submission is September 23, 23:59.

Language and Compiler

This assignment should be solved in Ada95 and compiled with GNU's Ada compiler gnat, that can be installed for free either by downloading it directly or asking your favorite package manager. GNAT can be downloaded from sourceforge.For more installation instruction, please check this webpage. The Unix machines at the IT department also have gnat installed. Note that, you can install a later version of Ada compiler but can only use Ada95 features for solving this assignment.

When using gnat all files should have the suffixes .ads or .adb, for specifications and bodies respectively. Each file should contain exactly one compilation unit. A compilation unit is (for example) a package, a procedure or a function. The "main" procedure of your program should be a procedure which accepts no arguments and resides in a file with the same name as the procedure. Use lower case characters for file names. For instance a "hello world" program can look like this:

-- File: hello_word.adb
-- Purpose: Greet the world

with Text_Io;

procedure Hello_World is
    Text_Io.Put_Line("Hello World!");
end Hello_World;

1. Compilation on the UNIX Machines

To compile your program you use the command "gnatmake <main_procedure_name>". E.g., to compile and run the program above you should write:

$ gnatmake hello_world
$ ./hello_world

2. Compilation at home

To compile your program at home you should, after a successful installation, just type
"gnatmake <main_procedure_name>".

3. Language References

For a brief introduction to Ada, see Ada Quick Start. For more information about Ada 95, see the Ada 95 Reference Manual. Especially have a look at the Ada 95 Predefined Language Environment.

Part 1: Cyclic Scheduler

Write a cyclic scheduler which manages three procedures: F1, F2 and F3. We demand the following for the executions:

  1. F1 should be executed every second.
  2. F2 starts when F1 terminates.
  3. F3 should execute every other (varannan) second, starting 0.5 seconds after F1's start.

The execution times for the functions are not known. However, it can be assumed that F1 and F2 together execute for less than 0.5 seconds, and that F3 does not take more than 0.5 seconds to execute.

Let the functions print an informative message when they execute, e.g.

F1 executing, time is now: 0.00001
F2 executing, time is now: 0.00004
F3 executing, time is now: 0.50008
F1 executing, time is now: 1.00008
F2 executing, time is now: 1.00010
F1 executing, time is now: 2.00007
F2 executing, time is now: 2.00008
F3 executing, time is now: 2.50007

Make sure that the printed time has a resolution of as least 1/1000 sec.

You should start with this skeleton code: part1. Examine the code to understand how a simple Ada program is organized. You can compile and run this code to see output.

Try to modify it in places indicated by the comments.

Note: Some amount of jitter in start times for functions F1 and F3 cannot be avoided. However, the start times of the functions are not allowed to "drift" further and further away from the schedule.


  1. Explain the difference between relative delay and absolute delay using your own understanding.
  2. Suppose we know the exact execution times of F1, F2 and F3. Is it possible to use relative delay for avoiding drift? Why?


  • Use the package Calendar for access to the clock
  • Use the package Text_Io for printing messages
    • To print Duration variables you can instantiate the generic package Text_Io.Fixed_Io with a duration type: package DIO is new Text_Io.Fixed_Io(Duration); The DIO package will then export, among other things, the procedure DIO.Put(D:Duration, Fore:Field, Aft:Field) to print variable D of type Duration.
    • See Ada 95 Predefined Language Environment under Text_Io.Fixed_Io (A.10.1) for more information.
  • Another way of printing discrete types or subtypes is to use the Image attribute. It will return the character string that corresponds to the way we will see the type. E.g., if D is of type Duration we can print D using the package Text_Io like: Text_Io.Put(Duration'Image(D));
  • There is, again, no need to create any tasks in this part.

Part 2: Cyclic Scheduler with Watch-dogs

Modify F3 from Part 1 so that it occasionally takes more than 0.5 seconds to execute.

Augment the cyclic scheduler from Part 1 with a watchdog task to monitor F3's execution time. When F3 exceeds its deadline (0.5s), the watchdog task should immediately print a warning message. I.e., 0.5s after start of F3, either F3 has finished or the watchdog has printed a message. The watchdog should let F3 finish even if it misses its deadline.

The watchdog task should be started (released) at the same time as (or just before) F3 starts executing, and from that point measure the time that F3 uses.

When F3 misses its deadline the cyclic executive should re-synchronize so that F1 is started at whole seconds.

You should start with this skeleton code: part 2. Examine the code to understand how a task is declared inside an Ada program. Try to modify it in places indicated by the comments. You can also reuse your solution for part 1 as the initial code.


  1. How did you synchronize the watchdog task with the start and the end of a F3 execution?
  2. Does it make sense to use absolute delay in watchdog timeouts? Why/why not?
  3. Explain the way you re-synchronize the cyclic executive when F3 misses its deadline.


Part 3: Process Communication

Create three tasks:

  1. A task that act as a first in, first out (FIFO) buffer for integers. The buffer should block any task which makes calls which could overflow or underflow the buffer. The buffer should at least be able to buffer 10 integers.
  2. A task that puts integers in the range 0..25 into the buffer at irregular intervals (i.e. a producer of values).
  3. A task that pulls integers out of the buffer at irregular intervals (i.e. a consumer of values), and summarizes them. When the sum is above 100 the task should terminate the program. The information that the buffer and the producer should terminate should be spread using synchronization (not using global variables, producer should not count, ...). You are not allowed to use abort or terminate commands.

In your solution, the buffer should not print any messages, but the producer and consumer should print messages containing the integers that are given to/taken from the buffer.

You can start with this skeleton code: part 3.


  1. Show by a concrete scenario that the producer-consumer-buffer program using blocking rendezvous communication can have deadlocks and explain the mistake that can cause it.


  • You should not implement semaphores nor use the Semaphore_Package. Synchronization and mutual exclusion should be implemented by the task which manages the FIFO buffer.
  • You should not throw away any number produced by the producer, even if the buffer is full at the moment.


  • To detect errors, make sure your test run includes situations where both the producer and the consumer get blocked on the buffer. (Not at the same time, but each one eventually.)
  • The statements accept and when might be useful for the buffer task.

Part 4: Data Driven Synchronization

Re-implement the FIFO buffer in the previous part as a protected shared object.

You can start with this skeleton code: part 4.


  1. Does the producer-consumer-buffer program using protected object suffer from any potential deadlock? If not, please explain the main reason behind it.
Updated  2018-09-11 13:21:05 by Syed Md Jakaria Abdullah.