Abstract 1 Introduction 2 Challenges of Multicore Certification 3 The Ada Approach to Multicore Safety 4 Implementation Details 5 Alignment with AC 20-193 Guidance 6 Conclusion References

A Certifiable Approach to Multicore Using Ada

José F. Ruiz ORCID AdaCore, 46 rue d’Amsterdam, 75009 Paris, France
Abstract

The adoption of multicore processors in safety-critical systems, such as avionics, automotive, and rail, introduces significant certification challenges, primarily related to determinism, predictability, and interference management. This paper examines how the Ada programming language, together with its restricted tasking profiles (Ravenscar and Jorvik) and bare-metal run-time libraries, provide a robust and certifiable execution model for multicore systems compliant with guidance such as AC 20-193.

By enforcing static task partitioning, fixed-priority scheduling, and a simple synchronization model, the Ada approach systematically limits interference channels and timing variability. This model enables accurate schedulability analysis, simplifies verification activities, and allows the multicore run-time library itself and the application built on top of it to be certified.

Keywords and phrases:
multicore, certification, Ada
Copyright and License:
[Uncaptioned image] © José F. Ruiz; licensed under Creative Commons License CC-BY 4.0
2012 ACM Subject Classification:
Software and its engineering Concurrent programming structures
; Software and its engineering Software safety
Editors:
Antonio Filieri and Peter Backeman

1 Introduction

The use of multicore hardware in safety-critical systems is no longer optional. Increasing software complexity and growing performance demands require additional computing power, while hardware obsolescence has made single-core processors increasingly scarce. Embedded real-time systems are therefore subject to the same multicore transition observed in general-purpose computing. However, in high-integrity systems, this transition must preserve essential properties such as determinism, predictability, and analyzability.

By the early 2010s, avionics suppliers began adopting multicore processors for both performance and obsolescence reasons. At that time, certification guidance and development practices were largely tailored to single-core architectures. Certification authorities and applicants alike lacked a consistent framework for addressing multicore-specific issues such as shared resource contention, timing interference, and parallel execution effects.

CAST-32A [19] was the first significant attempt to address these concerns by providing guidance for robustness under interference and timing analysis on multicore platforms. This effort culminated in the release of Advisory Circular AC 20-193 [2] in 2024, which provides the first comprehensive regulatory framework for multicore certification, complementing DO-178C [16], DO-254 [15], and ARP4754A [17].

In this context, the Ada programming language stands out as particularly well suited for the development of safety-critical embedded software. Ada 2005 [9] introduced the Ravenscar profile, a restricted subset of the language’s tasking model designed to support deterministic and time-analyzable applications on top of a streamlined run-time system. Originally targeting single-core processors, Ravenscar proved highly effective for building certifiable real-time systems.

Ada 2012 [10] extended this model to multicore systems using a fully partitioned approach that preserves the simplicity and analyzability of Ravenscar. Ada 2022 [11] further introduced the Jorvik profile, which slightly relaxed some Ravenscar restrictions and introduced CPU affinity for protected objects.

This paper examines how these language features and the tasking execution model enable compliance with stringent multicore certification guidance, with a particular focus on AC 20-193.

2 Challenges of Multicore Certification

Software certification in the aerospace domain is the formal process by which aviation authorities establish confidence that airborne software is safe, reliable, and compliant with regulatory requirements. Because software performs critical control and monitoring functions, certification objectives emphasize determinism, correctness, and the absence of unintended behavior.

DO-178C defines a set of objectives and activities to demonstrate that software behaves correctly under all operational conditions. Central to these objectives are the notions of predictability, controlled complexity, and verifiable behavior. All software behavior must be specified, reviewed, and verified, and execution-time properties must be justified where relevant.

Certifying software on multicore processors is substantially more complex than on single-core systems. On multicore platforms, several processors execute in parallel and compete for shared hardware resources such as memory buses, caches, and I/O controllers. This shared use introduces timing variability and interference effects that can compromise determinism.

AC 20-193 formalizes these concerns through the concept of interference channels: mechanisms by which activity on one processor can affect the timing or behavior of software executing on another. Many such channels originate deep within the hardware micro-architecture and are not directly manageable at the software level, making them difficult to model or analyze precisely.

As a consequence, traditional single-core analysis techniques, where tasks are analyzed largely in isolation, are no longer sufficient. Certification now requires evidence that the system remains robust under worst-case interference conditions, meaning that timing and functional requirements must be met even when all processors are active and contending for shared resources. Applicants need to:

  • identify all potential interference channels,

  • characterize worst-case contention scenarios,

  • implement mitigation strategies, and

  • provide verification or measurement evidence demonstrating their effectiveness.

Meeting these objectives requires a disciplined execution model, a deep understanding of the hardware platform, and software abstractions that minimize interference. Without such measures, establishing reliable worst-case execution-time bounds becomes impractical.

3 The Ada Approach to Multicore Safety

The approach presented in this paper leverages the Ada programming language and a certifiable bare-metal run-time library to define a simple, static execution model for multicore systems that aligns with AC 20-193 objectives. The model is characterized by the following principles:

  • Static task allocation: Tasks are statically bound to processors, and task migration is prohibited.

  • Fixed-priority scheduling: Each processor uses a preemptive, fixed-priority scheduler with no time slicing. The highest-priority ready task on each processor executes until it blocks or is preempted.

  • Independent schedulers: Each processor maintains its own ready and delay queues.

  • Deterministic synchronization: Library-level protected objects are used for inter- and intra-processor communication.

  • Static interrupt handling: Each external hardware interrupt is statically assigned to a designated processor; different interrupts may be assigned to different processors (see Section 4.5). Interrupt handlers use the protected object synchronization mechanisms to trigger actions on a different processor when needed.

  • High-resolution timing services: A single hardware clock provides a common time reference. Delay handling programs high-resolution timers on demand.

This approach directly addresses AC 20-193 concerns by limiting dynamic behavior, reducing and identifying interference channels, and enabling precise timing analysis.

3.1 Execution Model and Scheduling

The Ravenscar and Jorvik profiles define deterministic and analyzable execution models, supported by run-time systems of reduced size and complexity. These models enable precise schedulability analysis using established techniques such as Rate Monotonic Analysis (RMA) [13] and Response Time Analysis (RTA) [12].

Ada 2012 introduced a multicore execution model as a conservative extension of single-core fixed-priority preemptive scheduling. Tasks are statically allocated to processors, migration is prohibited, and each processor independently schedules its ready queue. This partitioned approach enables reuse of single-core analysis techniques and simplifies run-time support while maintaining schedulability [3]. Static partitioning provides physical and temporal separation between cores, reducing interference and supporting the determinism requirements of AC 20-193.

While both profiles share the same fundamental execution model (static tasks, fixed priorities, and library-level protected objects), they differ in their synchronization constraints. Ravenscar restricts each protected object to at most one entry with a simple Boolean barrier (a single Boolean variable or attribute), enabling straightforward schedulability analysis. Jorvik relaxes these restrictions by allowing multiple protected entries and general Boolean barriers per protected object, while preserving the bounded-blocking property required for timing analysis.

3.2 Certifiable Run-Time Architecture

This paper specifically refers to the GNAT Ada run-time library developed by AdaCore for bare-metal embedded applications. The runtime has evolved over the years to support multiple target platforms, with multicore support added in 2010 for Ada 2012 and continuously improved since then.

The Ada run-time library is composed of passive packages, meaning that its code executes only in response to explicit application calls or external events such as interrupts. It provides the fundamental services required to support Ada programs, in particular tasking and synchronization. The run-time library is statically linked into the embedded executable and must therefore be certified to the same assurance level as the application. It supports a symmetric multiprocessing (SMP) configuration, where there is a single copy of the run-time library and all memory is addressable by every processor.

The core of the run-time system implementing restricted tasking consists of approximately 100 Ada packages totaling about 11 KSLOC, with an average cyclomatic complexity of 2.57, complemented by approximately 1,000 lines of architecture-specific assembly code. These figures correspond to the GNAT Pro run-time library supporting the Ravenscar and Jorvik profiles for Ada 2022.

3.3 Single-Core and Multicore Configuration

The multicore run-time library is derived directly from the already certified single-core library and introduces only a minimal set of changes. The run-time system is parameterized by a static configuration constant defining the maximum number of processors. When this value is equal to one, all multiprocessor-specific code paths are statically disabled and optimized out by the compiler. As a result, the generated binary for single-core systems contains no deactivated multicore functionality.

For example, a synchronization operation may include code such as:

if Multiprocessor then
Fair_Locks.Lock (Any_Lock);
end if;

where the Multiprocessor condition is defined as:

Multiprocessor : constant Boolean := Max_Number_Of_CPUs /= 1;

Here, Max_Number_Of_CPUs is a configuration parameter representing the maximum number of processors available on the target platform.

This design ensures that the single-core and multicore run-time systems share the same code base, with differences limited to 15 conditional constructs. The majority of the run-time code is identical in both configurations, enabling extensive reuse of verification and certification artifacts, including structural coverage and low-level verification evidence, significantly simplifying compliance with standards such as DO-178C, ISO 26262 [8], and EN 50128 [6], and simplifying maintenance.

4 Implementation Details

This section describes the concrete implementation of the multicore execution model in the Ada bare-metal run-time system, focusing on system start-up, task activation, scheduling, and inter-processor coordination. The design aims to minimize complexity, limit dynamic behavior, and provide a clear mapping between software abstractions and the underlying hardware, thereby facilitating verification and certification.

4.1 Initialization

System initialization is orchestrated by a designated bootstrap processor to avoid race conditions. Upon reset, this processor executes the low-level assembly start-up code responsible for initializing the common hardware.

The bootstrap processor first initializes essential hardware registers and configures the execution level to run in privileged mode, thereby enabling unrestricted access to control registers. It then enables only the minimum hardware features required by the application, such as the floating-point unit (if used) and trap and exception handling mechanisms.

Memory initialization follows, including cache and MMU configuration where applicable, zero-initialization of the .bss section, initialization of global data, and setup of the main stack pointer.

Once memory and processor state have been initialized, the run-time system constructs the internal task control structures. Tasks are statically assigned to processors according to the configuration parameters, and every task is inserted into the ready queue of its statically-assigned processor.

If required by the application, peripheral devices such as system clocks, timers, and UART controllers are initialized at this point. All configured interrupt handlers are installed, including those used for timer expiration and inter-processor communication. Finally, control is transferred to the environment task, which represents the initial execution context of the application.

4.2 Secondary Processor Initialization

After the bootstrap processor completes global initialization, the remaining processors are released from reset and execute a simplified initialization sequence. Each secondary processor performs only processor-local setup, such as stack initialization and required interrupt masking.

Secondary processors do not perform global hardware initialization, which avoids duplication of effort and eliminates the risk of race conditions during start-up. Instead, they immediately enter the task dispatching loop.

Each processor includes a statically defined idle task, implemented as a low-power and wait-for-interrupt loop. This idle task has the lowest priority and executes whenever no other task is ready on that processor. The idle task is not part of the application scheduling model and serves only to maintain processor control in a waiting state.

4.3 Task Dispatching and Scheduling

Tasks are statically allocated to processors using the CPU aspect that determines the processor on which the task will activate and execute. If the aspect is not specified, or if the CPU value is Not_A_Specific_CPU, the task is allocated to the first processor. Each processor executes an independent fixed-priority preemptive scheduler. Ready queues are disjoint and processor-local, and tasks are dispatched strictly according to priority. When a task blocks or preempts, it is returned to the ready queue of its assigned processor. No mechanism exists for a task to appear in the ready queue of another processor.

When internal data in the run-time system can be modified by tasks executing in different processors, inter-processor locking mechanisms guarantee mutual exclusion.

The execution of a task (or an interrupt handler) on a given processor may require updates to another processor’s ready queue (and may also force the preemption of the running task). Such cross-processor updates are performed indirectly: an inter-processor interrupt (IPI) is sent to the target processor, which then performs the actual updates to its own data structures locally. This preserves the invariant that each processor is the sole modifier of its own ready queue.

4.4 Deterministic Synchronization

The restricted library-level protected objects defined by the Ravenscar and Jorvik profiles are used for inter- and intra-processor communication and synchronization. The same restrictions that exist in the Ravenscar and Jorvik profiles for single-core apply to the case of a multiprocessor. For multicore systems, protected objects rely on a Fair Lock [7, 18] mechanism that provides bounded blocking and predictable acquisition order.

The fair lock implementation ensures that all lock acquisitions are finite and analyzable, preventing starvation and supporting worst-case execution time analysis. The fair lock maintains a FIFO queue of waiting requestors: each acquisition attempt is assigned a ticket, and processors are granted access in ticket order. This guarantees that any waiting processor obtains the lock within a bounded number of other processors’ critical sections, making the worst-case blocking time statically computable as B=(n1)Ccs, where n is the number of processors and Ccs is the maximum critical-section execution time. Lock operations introduce minimal overhead and are invoked only at well-defined synchronization points, consistent with the restricted tasking model. Inter-processor communication is limited to explicit synchronization operations and inter-processor interrupts used to trigger rescheduling on the target processor.

To simplify timing analysis and facilitate efficient implementation when possible, protected objects used only by tasks within the same CPU can utilize the optimized single-core implementation. Ada 2022 introduces CPU affinity for protected objects (ALRM [11], D.16(15/5)), enabling the static optimization of protected operations.

Protected objects not assigned to a specific CPU (i.e., accessed by tasks on different processors) rely on the fair-lock mechanism for inter-processor mutual exclusion. Assigning a protected object to the same CPU as its client tasks eliminates the need for inter-processor synchronization; global protected objects with cross-processor clients are reserved for cases where explicit inter-processor communication is required, and their use introduces the synchronization overhead discussed above.

4.5 Interrupt Handling Model

In Ravenscar and Jorvik-compliant systems, the only mechanism to associate application-level interrupt handling code is by attaching a protected procedure to an interrupt. In a multiprocessor context, the fundamental semantic model remains unchanged; however, additional considerations arise with respect to mutual exclusion and the assignment of interrupts to processors.

Mutual exclusion for interrupt handlers is provided by the protected object semantics defined by Ada. As a result, the same design principles and constraints that apply to intra- and inter-processor synchronization of protected operations apply equally to interrupt handling. In particular, the avoidance of unnecessary inter-processor synchronization is essential to improve determinism and analyzability.

Multicore hardware typically allows interrupts to be delivered to one or more processors through the configuration of interrupt affinity masks. While dynamic or multicore delivery of interrupts may improve average responsiveness in general-purpose systems, it significantly complicates timing analysis and introduces additional sources of interference due to shared interrupt handling paths and cross-processor synchronization.

For high-integrity systems, and in alignment with the objectives of AC 20-193, interrupts are therefore statically assigned to a single processor. Each interrupt is handled by exactly one processor for the lifetime of the application, although different interrupts may be assigned to different processors. This approach eliminates interrupt migration, avoids contention between processors for interrupt handling, and ensures that interrupt-related interference remains strictly local to the processor to which the interrupt is assigned.

Furthermore, allocating interrupts to the same processor as the tasks that use the associated devices (using the Ada 2022 CPU affinity mechanism for protected objects) avoids the need for inter-processor synchronization when accessing shared data. This design reduces interference channels, simplifies worst-case execution-time and response-time analysis, and supports robust partitioning between processors.

Overall, this interrupt handling model directly supports the AC 20-193 objectives of determinism, interference containment, and analyzability by enforcing static interrupt allocation, eliminating dynamic affinity changes, and minimizing cross-processor interactions.

4.6 Timing Services

Timing services must be predictable, analyzable, and resilient to interference. The Ada run-time uses hardware timers and static software structures, limiting shared resources and avoiding dynamic behaviors. The approach follows [21] principles and adapts to hardware capabilities.

4.6.1 Partitioned Delay Queues

Delay queues are maintained on a per-processor basis. Each queue contains timed events (task delays and timing event callbacks) ordered by absolute expiration time. Only tasks and handlers executing on a given processor are permitted to insert or remove entries from that processor’s delay queue. When the timer interrupt occurs then the first event is removed from the queue, and the timer is reprogrammed for the next delay. Since its inception, the GNAT Ada run-time has used a single-shot timer to interrupt at the expiration time of the first pending delay.

This design removes the need for multiprocessor protection, reduces complexity, and more importantly, it limits interference among processors.

4.6.2 Per-Processor Timer Implementations

On platforms providing a private physical timer per processor (for example, Arm 64-bit architectures [5]), each processor independently programs its timer hardware based solely on its local delay queue.

Because the timer state is replicated per processor, no inter-processor synchronization is required when setting or updating alarms. Only local interrupt masking is used to protect against concurrent access by tasks executing on the same processor. This eliminates a class of interference channels related to shared timing hardware and directly supports AC 20-193 objectives for interference mitigation.

This per-processor, one-shot timer implementation is available on platforms that provide dedicated per-core timer hardware. On Arm 64-bit processors, for example, this is realized via the Generic Timer architecture extension. Platforms lacking per-processor timers require the shared-timer implementation described in the following section.

4.6.3 Shared Timer Implementations

Some architectures, such as LEON3MP [1], provide a single hardware timer shared across processors. In this configuration, additional measures are required to ensure safe operation.

When setting alarms, the run-time protects access to shared timer state using multiprocessor locks and computes the earliest expiration across all processors before reprogramming the hardware timer:

procedure Update_Alarm (Alarm : Time) is
Now : Time;
Time_Difference : Time;
begin
if Multiprocessor then
Lock (Alarm_Lock);
end if;
Now := Clock;
if Alarm <= Now then
Time_Difference := 1;
else
Time_Difference := Alarm - Now;
end if;
Time_Difference := TimeMin (Time_Difference, Max_Sleep);
if Alarm < Pending_Alarm then
Set_Hw_Alarm (Clock_Interval (Time_Difference));
Pending_Alarm := Alarm;
end if;
if Multiprocessor then
Unlock (Alarm_Lock);
end if;
end Update_Alarm;

The use of explicit locking bounds access to shared resources and makes interference explicit and analyzable.

The alarm interrupt is handled on a designated processor. The handler executes with interrupts disabled to protect local data structures and uses multiprocessor locking only when accessing shared timing state.

When alarms expire for tasks allocated to other processors, the handler does not directly modify remote ready queues. Instead, it sends an inter-processor interrupt to the affected processor, which performs the required updates locally:

procedure Alarm_Handler (Interrupt : Interrupts.Interrupt_ID) is
Now : Time;
Next_Alarm : Time;
begin
Enter_Kernel;
if Multiprocessor then
Lock (Alarm_Lock);
Pending_Alarm := TimeLast;
Unlock (Alarm_Lock);
else
Pending_Alarm := TimeLast;
end if;
Update_Clock (Now);
Next_Alarm := Now + Max_Sleep;
if Multiprocessor then
for CPU_Id in CPU loop
if CPU_Id /= Current_CPU then
declare
Alarm : constant Time := Get_Next_Timeout (CPU_Id);
begin
if Alarm <= Now then
Poke_CPU (CPU_Id);
elsif Alarm < Next_Alarm then
Next_Alarm := Alarm;
end if;
end;
end if;
end loop;
end if;
Execute_Expired_Timing_Events (Now);
Wakeup_Expired_Alarms (Now);
Next_Alarm :=
TimeMin (Get_Next_Timeout (Current_CPU), Next_Alarm);
Update_Alarm (Next_Alarm);
Leave_Kernel;
end Alarm_Handler;

Upon receiving the inter-processor interrupt generated by Poke_CPU, each target processor executes its own Execute_Expired_Timing_Events and Wakeup_Expired_Alarms procedures to process expired events in its local delay queue and release any tasks whose delays have elapsed. The alarm-handling processor executes those same procedures directly (for events belonging to its own queue).

While this mechanism makes interference explicit, it adds implementation complexity and increases the effort required for interference and timing analysis compared to private per-processor timers.

4.6.4 64-bit Clock Services

On systems providing a 64-bit free-running hardware counter (like the 64-bit Arm processor), the clock can be read atomically and coherently across processors without any interrupt protection. Such counters provide sufficient range and resolution to avoid wraparound concerns during system lifetime, so there are no interrupt-driven updates.

Because clock reads do not modify shared state and do not require synchronization, they introduce no additional interference channels and fully support deterministic timing analysis.

4.6.5 32-bit Clock Services

When only a 32-bit counter is available, the run-time extends the hardware counter using a software-maintained most significant part (MSP). Alarm interrupts ensure that the MSP is updated at least once per hardware counter period (no additional specific interrupt handler for the clock).

The split between hardware and memory has a consistency problem because it is not possible to read the whole clock as an atomic operation. On single-core systems, consistency is ensured by reading the combined hardware and software values twice and selecting a stable result.

On multicore systems, MSP updates may originate from multiple processors. The clock-reading algorithm therefore detects parallel updates and retries until a value is obtained that is guaranteed to lie between the start and end of the read operation, with a bounded number of iterations:

function Clock return Time is
First_MSP : Clock_Periods;
Before_MSP : Clock_Periods;
Before_LSP : Clock_Interval;
Now_LSP : Clock_Interval;
After_MSP : Clock_Periods;
begin
-- We can limit the iterations in the loop to 3. If the first
-- time we read 1, and then the MSP gets increased, we know
-- that the time is between 1 & X and 2 & X, and hence before
-- 3 & 0. In the second iteration we can read 2 and then it
-- gets increased again. Hence, actual time is between 2 & X
-- and 3 & X, so the actual time when we leave function clock
-- is at least 2 & 0. However, we do not know when between
-- 2 & 0 and 3 & 0. We then read a third time, and if we read
-- 3 and then a change, it means that the actual time is
-- between 3 & X and 4 & X (so at least 3 & 0), so at the end
-- of the third iteration, we can return 3 & 0 as a safe value
-- that is between the beginning and end of the execution of
-- this call to Clock.
for Iteration in 1 .. 3 loop
-- On multiprocessor systems there may be parallel updates
-- of the software clock (Update_In_Progress).
-- On monoprocessors the loop is performed only once.
loop
Before_MSP := Software_Clock.MSP;
exit when not Multiprocessor
or else Before_MSP /= Update_In_Progress;
end loop;
Before_LSP := Software_Clock.LSP;
Now_LSP := Clock_Interval (Read_Hw_Clock);
After_MSP := Software_Clock.MSP;
-- If MSP in Software_Clock has changed (or is changing),
-- we do not know the time at which the software clock was
-- updated. The implementation does not force the software
-- clock to be updated at a time close to LSP wraparound
-- (it needs to be done at least once per hardware clock
-- period, but we do not know when). Hence, returning
-- (Before_MSP + 1) & 0 is not correct because the updated
-- LSP in the Software_Clock does not need to be close to
-- zero.
-- Normal case, no updates in MSP
if Before_MSP = After_MSP then
-- If we know the value of the software clock at the
-- time of the read of the hardware clock, we know the
-- time of that read, because the software clock can
-- never be more than one period
-- behind.
return
Before_MSP + (if Now_LSP < Before_LSP then 1 else 0) &
Now_LSP;
-- After the first unsuccessful iteration, store the first
-- MSP value read to have a reference of the initial time
-- when we entered the clock function.
elsif Iteration = 1 then
First_MSP := Before_MSP;
-- During the second or third iteration, if the clock has
-- been increased by two or more then Before_MSP & 0 is
-- certainly within the beginning and end of the execution
-- of this call to Clock.
elsif Before_MSP - First_MSP >= 2 then
exit;
end if;
end loop;
return Before_MSP & 0;
end Clock;

This approach ensures monotonicity and correctness while avoiding locking or blocking.

5 Alignment with AC 20-193 Guidance

This section describes how the proposed execution model, based on the Ada programming language, restricted tasking profiles, and a certifiable bare-metal run-time system, addresses the primary concerns and objectives of AC 20-193.

5.1 Planning

AC 20-193 requires that the execution model, processor configuration, and dynamic hardware behaviors be clearly defined and reflected in planning data. The static execution model described in Section 3 (fixed tasks, static priorities and affinities, no dynamic task operations, and restricted synchronization) directly satisfies these requirements. It prevents race conditions and deadlocks by construction, eliminates dynamic scheduling decisions, and bounds all component interactions, making the system amenable to static analysis and straightforward to document in planning artifacts.

Where shared hardware resources are unavoidable, such as clocks and timers, their access mechanisms and associated interference are fully specified and bounded, as described in Section 4.6, allowing them to be addressed systematically in verification and timing analysis.

5.2 Setting of Multicore Processor Resources

AC 20-193 requires that all multicore processor resources be explicitly identified, configured, and controlled to ensure predictable and repeatable system behavior. This applies both to processing resources (e.g., processor cores and interrupt handling) and to shared hardware resources that may constitute interference channels (e.g., clocks, timers, caches, and interconnects).

In the proposed approach, multicore resources that directly affect the execution model are configured statically during system initialization. These include the set of active processors, interrupt affinity and routing, timing resources (clocks and timers), and basic I/O devices such as the UART. Once initialized, these configurations remain fixed for the duration of system operation and are not modified at run time, in accordance with the determinism and robustness objectives of AC 20-193.

Other multicore-related resources, in particular those associated with shared hardware structures, such as caches and DMA arbitration on the interconnect, are outside the scope of the run-time system and are therefore the responsibility of the application designer. AC 20-193 explicitly requires that the configuration and usage of such resources be defined, justified, and verified.

5.3 Interference Channels and Resource Usage

A central objective of AC 20-193 is the identification and mitigation of interference channels arising from shared hardware resources. Note that even if there is no explicit data or control flow between tasks, coupling exists on the platform level, which can cause interference between them. This analysis is mostly the responsibility of the system designer, but Ada provides mechanisms that minimize contention:

  • Tasks and protected objects are statically assigned to processors, minimizing inter-processor contention.

  • Interrupts are statically routed to designated processors using hardware affinity mechanisms, reducing asynchronous interference.

The run-time library reduces interference through architectural and configuration mechanisms. For example, the timing services implementation satisfies the following properties:

  • Interference identification and mitigation: Shared timing resources are either eliminated (per-processor timers) or explicitly protected (shared timers), making interference channels identifiable and analyzable.

  • Partitioned execution: Timing operations respect task-to-processor allocation, and remote effects are mediated exclusively through inter-processor interrupts.

  • Bounded execution time: All timing-related operations, including clock reads and alarm handling, execute with statically bounded complexity.

Platforms that provide per-processor timers and 64-bit counters further reduce certification effort by eliminating shared timing state and minimizing synchronization.

5.4 Software Verification

Applicants need to demonstrate that the hosted applications function correctly and have sufficient time to execute in the presence of the interference that occurs when all the software is executing on the multicore processor.

The static Ravenscar/Jorvik execution model, as described in Sections 3 and 4, directly supports these objectives. Precise response-time and worst-case execution-time analysis is possible because scheduling is fixed-priority and partitioned. Contention is bounded because synchronization is restricted and uses analyzable fair locks. The explicit specification of task priorities, processor affinities, and protected objects in the source code provides strong traceability from design decisions to verification evidence. The application developer uses these restrictions to simplify their own verification activities. The run-time library itself needs to provide the following verifications based on worst-case scenario for interference:

  • Detailed manual review. Ensure proper protection against parallel updates of internal run-time data.

  • Test functional behavior

    • Tasks from different processors using shared protected objects. Ensure data access protection and conditional blocking and unblocking of tasks.

    • Tasks from different cores reading clock and setting timers. Ensure Consistent reads and expected ordering of timing activations.

  • Verify timing behavior

    • Protected interrupt handler overhead ALRM [11], C.3.1(15). Measure the execution time incurred from the time that an interrupt is asserted until the first user statement of the attached protected interrupt handler.

    • Mutual exclusive access ALRM [11], D.12(6). Measure the time overhead incurred to seize mutually exclusive access to an entry-less protected object.

    • Null delay until lateness ALRM [11], D.9(11). Measure the time to treat a delay request that does not cause the requesting task to block.

    • Delay until lateness ALRM [11], D.9(13/5). Measure the difference between the requested time of delay expiration and the resumption time actually attained by a task following an absolute time suspension

5.5 Error Detection and Handling, and Safety Nets

Predictable error detection and containment are essential for high-integrity multicore systems. The Ada language, together with its restricted tasking profiles, provides a structured and analyzable framework for implementing robust safety nets. Application-level logic is responsible for handling timing and functional anomalies with the following mechanisms:

  • Task-level exceptions: Ada supports structured exception handling within each task. Exceptions arising from hardware events, run-time checks, or internal computation errors are confined to the task in which they occur, preventing propagation to unrelated tasks and maintaining system stability.

  • Static error prevention: The Ravenscar and Jorvik profiles eliminate dynamic task creation, uncontrolled access to shared data, and unbounded priorities. These restrictions reduce the likelihood of runtime errors and make error detection predictable and analyzable.

  • Protected object integrity: Synchronization primitives, including protected objects and fair locks, guarantee atomicity and mutual exclusion. Violations, such as attempts to enter a protected object in violation of the ceiling protocol, raise well-defined exceptions, ensuring that erroneous states are detected and contained.

Monitoring processor usage is possible using execution-time clocks in Ada [20]. In the multicore run-time, execution-time clocks are implemented by reading the global hardware counter at task dispatch and preemption points and accumulating the elapsed time per task. Because tasks are statically bound to processors, no migration adjustments are needed, simplifying the implementation. The static nature of the Ravenscar and Jorvik model does limit the range of possible responses to a budget overrun, but the monitoring capability itself is sufficient for detection and logging.

6 Conclusion

The combination of the Ada programming language, its restricted tasking profiles, and a certifiable bare-metal run-time system provides a robust and practical foundation for addressing the challenges of multicore certification. By enforcing a static, deterministic, and analyzable execution model, this approach systematically limits interference, enables sound worst-case execution-time and response-time analysis, and enforces strong partitioning between software components. These properties closely align with the objectives and recommended practices of AC 20-193, particularly regarding interference control, determinism, and verification credibility.

Ada is not merely an implementation language; it also serves as an explicit specification. Architectural decisions, such as task priorities and CPU affinities, are made explicit in the code, visible to developers, reviewers, and analysis tools alike. By making it visible to the compiler, all tasking data structures can be statically generated, removing the need for dynamic memory.

The language semantics, clearly defined in the Ada Reference Manual, including its tasking and multiprocessor behavior, combined with restricted profiles like Ravenscar and Jorvik, simplify the implementation, reduce nondeterminism, and foster predictability and certifiability.

The run-time libraries complement the language by providing documented, verifiable mechanisms for design choices not covered by Ada itself, such as scheduler partitioning, hardware timer usage, and assignment of interrupt-handling processors. These mechanisms, together with the static execution model, ensure that both application software and the underlying run-time library can be certified with manageable verification effort, lower cost, and reduced technical and programmatic risk, while maintaining a high level of confidence in timing correctness and system robustness.

Hardware remains a key enabler for certification. The mechanisms described in this paper mitigate hardware-induced complexity, but careful hardware selection further strengthens analyzability. In particular, 64-bit architectures simplify clock access and reduce interference, while per-processor timers eliminate shared-timer contention. These choices, combined with Ada’s static and deterministic execution model, provide a streamlined, certifiable strategy for high-integrity multicore systems.

For applications where stronger assurance is required, the SPARK subset of Ada [14] enables formal proof of functional correctness and absence of run-time errors via static analysis tools. SPARK complements the approach described in this paper: the run-time provides a certifiable execution platform, while SPARK provides a mechanism to formally verify the application code that runs on top of it.

An alternative strategy for multicore isolation is to use a hypervisor or separation kernel (such as an ARINC 653-compliant partitioning OS [4]) that enforces spatial and temporal isolation between partitions at the hardware level. Hypervisors provide stronger fault containment boundaries but introduce an additional software layer that must itself be certified and whose timing overhead must be accounted for in analysis. The bare-board Ada run-time approach trades partition-level hardware isolation for a simpler trusted computing base: the run-time and application are co-certified as a single unit, avoiding hypervisor overhead and reducing the overall certification scope when strong inter-partition isolation is not required by the system architecture.

References

  • [1] Aeroflex Gaisler. LEON3 Multiprocessing CPU Core, 2010.
  • [2] Airframe Section AIR-622. AC 20-193: Use Multi-Core Processors. Technical report, Federal Aviation Administration, 2024.
  • [3] B. Andersson, S. Baruah, and J. Jonsson. Static-priority scheduling on multiprocessors. In RTSS’01: Proceedings of the 22nd IEEE Real-Time Systems Symposium. IEEE Computer Society, 2001.
  • [4] ARINC. ARINC Specification 653, Avionics Application Software Standard Interface. Aeronautical Radio, Inc, 2005.
  • [5] Arm Ltd. Arm Architecture Reference Manual: for A-profile architecture, m.a.a edition, 2025. URL: https://developer.arm.com/documentation/ddi0487.
  • [6] CENELEC. EN 50128, Railway applications – Communications, signalling and processing systems – Software for railway control and protection systems, July 2011.
  • [7] Fabien Chouteau and José F. Ruiz. Design and implementation of a Ravenscar extension for multiprocessors. In A. Romanovsky and T. Vardanega, editors, Ada-Europe 2011, 16th International Conference on Reliable Software Technologies, volume 6652 of Lecture Notes in Computer Science (LNCS). Springer, 2011. doi:10.1007/978-3-642-21338-0_3.
  • [8] ISO. ISO 26262, Road Vehicules - Functional Safety, 2018.
  • [9] ISO/IEC/JTC1/SC22/WG9. Ada 2005 Reference Manual: Language and Standard Libraries. International Standard ANSI/ISO/IEC-8652:1995 (E) with Technical Corrigendum 1 and Amendment 1, 2006. Available from Springer-Verlag, LNCS no. 4348.
  • [10] ISO/IEC/JTC1/SC22/WG9. Ada 2012 Reference Manual: Language and Standard Libraries. International Standard ANSI/ISO/IEC-8652:2012/Cor 1:2016 with 2016 corrections, 2016.
  • [11] ISO/IEC/JTC1/SC22/WG9. Ada 2022 Reference Manual: Language and Standard Libraries. International Standard ANSI/ISO/IEC-8652:2023 (E) with Technical Corrigendum 1, 2023.
  • [12] M. Joseph and P. Pandya. Finding response times in real-time systems. BCS Computer Journal, 29(5):390–395, 1986. doi:10.1093/COMJNL/29.5.390.
  • [13] C. L. Liu and J. W. Layland. Scheduling algorithms for multiprogramming in a hard-real-time environment. Journal of the ACM, 20(1), 1973. doi:10.1145/321738.321743.
  • [14] John W. McCormick and Peter C. Chapin. Building High Integrity Applications with SPARK. Cambridge University Press, 2015.
  • [15] RTCA. DO-254: Design Assurance Guidance for Airborne Electronic Hardware, April 2000.
  • [16] RTCA. DO-178C: Software Considerations in Airborne Systems and Equipment Certification, December 2011.
  • [17] SAE International. ARP4754A: Guidelines for Development of Civil Aircraft and Systems, December 2010.
  • [18] S. Swaminathan, J. Stultz, J. F. Vogel, and Paul E. McKenney. Fairlocks – A high performance fair locking scheme. In International Conference on Parallel and Distributed Computing Systems, pages 241–246, 2002.
  • [19] Certification Authorities Software Team. CAST 32A Multi-core Processors. Technical report, Federal Aviation Administration, 2016.
  • [20] Juan Zamorano, Alejandro Alonso, José Antonio Pulido, and Juan Antonio de la Puente. Implementing execution-time clocks for the ada Ravenscar profile. In Albert Llamosí and Alfred Strohmeier, editors, Reliable Software Technologies - Ada-Europe 2004, pages 132–143, Berlin, Heidelberg, 2004. Springer Berlin Heidelberg. doi:10.1007/978-3-540-24841-5_10.
  • [21] Juan Zamorano, José F. Ruiz, and Juan A. de la Puente. Implementing Ada.Real_Time.Clock and absolute delays in real-time kernels. In D. Craeynest and A. Strohmeier, editors, Reliable Software Technologies – Ada-Europe 2001, number 2043 in Lecture Notes in Computer Science. Springer-Verlag, 2001.