DO-178C Compliance in Lyx

DO-178C („Software Considerations in Airborne Systems and Equipment Certification“) ist der Industriestandard der Luftfahrtbehörden EASA (Europa) und FAA (USA) für Software in zugelassenen Luftfahrzeugen. Ohne Compliance keine Musterzulassung (Type Certificate). Lyx ist die erste eigenständige Systemsprache, die DO-178C-Konformität als integralen Bestandteil der Sprache — nicht als nachgelagertes Werkzeug — implementiert.

→ Unterseiten: TMR · Software Lockstep · Memory Scrubbing · .meta_safe ELF-Sektion
→ Ergänzend: Aerospace Safety · CLI-Referenz · Embedded & RTOS


1. Design Assurance Levels (DAL)

DO-178C teilt Software nach der Auswirkung eines Ausfalls auf das Flugzeug und seine Insassen ein. Je schwerwiegender der mögliche Schaden, desto strenger die Anforderungen.

DAL Auswirkung Coverage-Anforderung Lyx-Anforderung
A Katastrophal — Totalverlust Statement + Branch + MC/DC + Objektcode-Struktur @dal(A), @flight_crit, @integrity, @redundant
B Gefährlich — schwere Verletzungen Statement + Branch + MC/DC @dal(B), @flight_crit, @wcet
C Major — reduzierte Sicherheitsmarge Statement + Branch @dal(C), @stack_limit
D Minor — keine Sicherheitsrelevanz Statement @dal(D)
E No Safety Effect Keine Coverage-Pflicht Keine Lyx-Annotationen erforderlich
Wichtig: DAL gilt immer für die Funktion, die den kritischsten Pfad im Fehlerbaum besetzt — nicht für das Gesamtsystem. Ein Telemetrie-Subsystem kann DAL-D sein, während der Autopilotkern DAL-A ist.

DAL in Lyx annotieren

// DAL-A: automatischer Pilot — katastrophaler Ausfall
@dal(A)
@flight_crit
@integrity(mode: software_lockstep, interval: 50)
@stack_limit(4096)
@wcet(500)
fn ComputeFlightPath(state: ^FlightState): FlightCommands {
    // ...
}

// DAL-C: Bordunterhaltung
@dal(C)
fn UpdateDisplayBrightness(lux: int64): void {
    // ...
}

Der Compiler verweigert den Build, wenn @dal(A) oder @dal(B) gesetzt ist, aber fehlende Safety-Pragmas die geforderte Absicherung nicht erfüllen.


2. @flight_crit — Floating-Point Determinismus

Fließkommaberechnungen sind auf verschiedenen Prozessoren, Compiler-Versionen und Optimierungsstufen nicht garantiert reproduzierbar. Selbst dasselbe Programm kann auf einem x86_64 mit AVX und auf ARM64 ohne FMA leicht abweichende Ergebnisse liefern — ein gravierendes Problem für Flugregelungssysteme.

@flight_crit erzwingt auf annotierte Funktionen:

Maßnahme Effekt
FPU-Modus IEEE 754 round-to-nearest Konsistentes Rundungsverhalten auf allen Targets
Kein FMA (Fused Multiply-Add) Kein Unterschied zwischen Plattformen mit/ohne FMA
Kein Constant-Folding für FP Berechnung zur Laufzeit, nicht zur Compile-Zeit
Kein FP-CSE (Common Subexpression Elimination) Kein Zusammenlegen von FP-Operationen
–no-fp-fold implizit aktiv Äquivalent zum Compiler-Flag für diese Funktion

@dal(B)
@flight_crit
@wcet(200)
fn ComputeHeading(lat1: f64, lon1: f64, lat2: f64, lon2: f64): f64 {
    // Alle FP-Operationen hier sind IEEE-754-strikt und plattformidentisch.
    // Der Compiler darf KEINE FP-Optimierungen anwenden.
    let dlat: f64 := lat2 - lat1;
    let dlon: f64 := lon2 - lon1;
    return Atan2(dlat, dlon) * (180.0 / Pi);
}

Ohne @flight_crit kann Lyx FP-Ausdrücke zusammenfalten, was auf manchen Plattformen zu Abweichungen in der letzten Dezimalstelle führt. Bei Flugregelungssystemen reicht das aus, um nach 1000 Iteration eine Abweichung von mehreren Metern zu erzeugen.

→ Praxisbeispiele mit @flight_crit, @redundant und @integrity in einer vollständigen Flugsteuerungsfunktion: Aerospace & Safety


3. WCET & Stack-Limit

@wcet(N) — Worst-Case Execution Time

@wcet(N) deklariert das maximale Zeitbudget einer Funktion in Mikrosekunden. Der Compiler analysiert Kontrollfluss und Schleifengrenzen und verweigert den Build, wenn die statisch ermittelte WCET das Budget überschreitet.

@dal(A)
@flight_crit
@wcet(100)          // max. 100 µs auf der Zielplattform
@stack_limit(512)
fn ReadIMU(imu: ^IMUState): SensorReading {
    // Der Compiler prüft: Kann diese Funktion in 100 µs abgeschlossen werden?
    // Scheitern → Compile-Error mit Pfad-Angabe der teuersten Instruktionssequenz
    return SensorReading { ax: imu.raw_ax, ay: imu.raw_ay, az: imu.raw_az };
}

Ausgabe bei Überschreitung (Beispiel):

error[E0412]: WCET budget exceeded in ReadIMU
  --> imu_driver.lyx:12:1
   |
12 | fn ReadIMU(imu: ^IMUState): SensorReading {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   | Estimated WCET: 143 µs (budget: 100 µs)
   | Critical path: ReadIMU → FilterKalman → MatMul3x3
   |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ 98 µs
hint: Split FilterKalman into a separate @wcet-annotated function or raise the budget.

@stack_limit(N) — Maximaler Stack-Verbrauch

Stack-Overflows gehören zu den häufigsten Ausfallursachen in eingebetteten Systemen. @stack_limit(N) lässt den Compiler den maximalen Stack-Verbrauch der Funktion und aller von ihr direkt aufgerufenen (nicht-externen) Funktionen berechnen:

@dal(A)
@flight_crit
@stack_limit(2048)   // max. 2048 Byte Stack (inkl. Callees)
fn AutopilotCycle(state: ^FlightState): FlightCommands {
    // Compiler addiert: eigener Frame + alle Callees-Frames
    // Bei Überschreitung: Compile-Error
}

Auf DO-178C DAL-A ist @stack_limit zwingend für alle Funktionen, die im Avionik-Partitionsspeicher laufen. Der Nachweis, dass kein Stack-Overflow auftreten kann, ist Bestandteil der Structural Coverage Analysis.

4. Bounded Loops — WCET-Voraussetzung

WCET-Analyse ist nur möglich, wenn alle Schleifen eine nachweisbare Obergrenze haben. Lyx erzwingt das mit dem limit-Schlüsselwort. Schleifen ohne limit in @dal(A/B)-Funktionen sind Compile-Errors.

@dal(A)
@flight_crit
@wcet(50)
fn FindNearestWaypoint(wps: [16]Waypoint, n: int64, pos: GeoPoint): int64 {
    var best: int64 := 0;
    var best_dist: f64 := 1.0e18;

    for i := 0 to n - 1 do {
        var d: f64 := GeoDistance(pos, wps[i].pos);
        if (d < best_dist) {
            best_dist := d;
            best := i;
        }
    }
    // for...to impliziert limit(n) — aber n muss zur Compile-Zeit bounded sein
    // Statische Arrays [16] sind bounded: Der Compiler kennt n <= 16

    return best;
}

Für while-Schleifen ist limit explizit erforderlich:

// Sensor-Polling mit explizitem Limit
while (ReadSensorReady() = false) limit(1000) {
    // Maximal 1000 Iterationen — WCET berechenbar
}


5. Range-Typen

Out-of-Range-Werte sind eine Hauptursache für Flugunfälle (z.B. falscher Einheitenname, falsche Skala). Lyx-Range-Typen machen solche Fehler zur Compile-Zeit oder kontrollierten Laufzeit-Exceptions.

// Physikalisch sinnvolle Bereiche
type Altitude    = int64 range -1000..60000;   // Meter über MSL (Mariana-Graben bis Max.-Strat.)
type Speed       = int64 range 0..1000;         // Knoten
type Heading     = f64   range 0.0..360.0;      // Grad (echt: wrap-around — separate Logik)
type BankAngle   = f64   range -90.0..90.0;     // Grad
type ThrottlePct = int64 range 0..100;          // Prozent

fn ClimbTo(target: Altitude): void {
    // Compile-Fehler: Literal außerhalb des Bereichs
    // var bad: Altitude := 70000;   // Error: 70000 > 60000
}

fn ProcessSensor(raw_meters: int64): void {
    var alt: Altitude := raw_meters as Altitude;
    // Runtime-Prüfung: Falls raw_meters außerhalb [-1000, 60000] → panic
    // In DAL-A: panic löst kontrollierten Recovery-Pfad aus statt Absturz
}

Range-Typen sind kompatibel mit dem Basis-Ganzzahltyp — Arithmetik funktioniert transparent, das Ergebnis wird erneut geprüft:

var current_alt: Altitude := 1000;
var delta:       int64    := 500;
var new_alt:     Altitude := (current_alt + delta) as Altitude;   // Prüfung bei Zuweisung


6. Hardware-Fehlertoleranz

DO-178C verlangt für DAL-A, dass single-point failures im Hardware (Bit-Flips durch kosmische Strahlung, Speicherfehler) erkannt oder toleriert werden.

@redundant — TMR für Variablen

@redundant legt eine Variable dreifach im RAM an. Jeder Lesezugriff führt einen Mehrheitsentscheid durch:

@redundant
var thrust_setpoint: int64 := 0;   // Intern: 3 physisch getrennte Kopien

// Schreiben: alle drei Kopien werden simultan aktualisiert
thrust_setpoint := 850;

// Lesen: Compiler fügt Majority-Vote-Code ein
var t: int64 := thrust_setpoint;   // → vote(copy1, copy2, copy3)

Eine abweichende Kopie wird zur Laufzeit automatisch repariert (Self-Healing).

→ Vollständige Dokumentation: Triple Modular Redundancy

@integrity — Code- und Berechnungsintegrität

@integrity schützt entweder die Rechenlogik (software_lockstep) oder das Code-Segment im RAM (scrubbed):

// Schutz der Berechnung: Jede ALU/FPU-Operation wird dupliziert und verglichen
@dal(A)
@flight_crit
@integrity(mode: software_lockstep, interval: 50)
fn ComputeFlightPath(state: ^FlightState): FlightCommands {
    // Compiler generiert: Primar- und Redundanz-Berechnung, Vergleich vor return
}

// Schutz des Code-Segments: periodischer CRC32-Sweep alle 100 ms
@integrity(mode: scrubbed, interval: 100)
unit nav.core;

Modi im Überblick:

Modus Schützt Overhead Wann einsetzen
software_lockstep ALU/FPU-Berechnungen ~2× WCET DAL-A Kernberechnungen
scrubbed Code-Segment im RAM (Bit-Flips) Gering (Hintergrund) DAL-A/B Units auf Embedded
hardware_ecc RAM (durch ECC-Hardware) Keiner (Hardware) DAL-A mit ECC-RAM

Software Lockstep — vollständige Dokumentation
Memory Scrubbing — vollständige Dokumentation


7. MC/DC Coverage

Modified Condition/Decision Coverage (MC/DC) ist die härteste Software-Coverage-Anforderung im kommerziellen Bereich. DO-178C verlangt MC/DC für DAL-A und DAL-B.

MC/DC bedeutet: Für jede boolesche Bedingung in jeder Entscheidung muss ein Testfall existieren, der zeigt, dass diese eine Bedingung allein das Ergebnis der Entscheidung ändern kann — unabhängig von allen anderen Bedingungen.

Beispiel: MC/DC-Analyse

// Entscheidung mit drei Bedingungen
fn IsLandingAllowed(gear_down: bool, speed_ok: bool, runway_clear: bool): bool {
    return gear_down & speed_ok & runway_clear;
}

Für MC/DC dieser Funktion braucht man 4 Testfälle (nicht 8 wie für volle Branch Coverage):

Test gear_down speed_ok runway_clear Ergebnis Zeigt MC/DC für
T1 true true true true Basis
T2 false true true false gear_down unabhängig
T3 true false true false speed_ok unabhängig
T4 true true false false runway_clear unabhängig

MC/DC mit Lyx-Compiler

// MC/DC-Instrumentierung einschalten:
// lyxc --mcdc-instrument --coverage-report=coverage.json  src/flight_control.lyx

// Nach dem Testlauf:
// lyxc --coverage-report-html=report/ coverage.json
// → Zeigt für jede Bedingung: welche Test-Vektoren sie abdecken

Der –mcdc-instrument-Flag fügt Instrumentierungs-Code ein, der zur Laufzeit für jeden ausgeführten Zweig einen Eintrag in eine Coverage-Datenbank schreibt. –coverage-report liest diese Datenbank und erzeugt den Nachweis für die Zertifizierungsbehörde.


8. Compiler-Flags für DO-178C

Ein vollständiger DO-178C-konformer Build setzt mehrere Flags zusammen:

DAL-A Build (Produktion)

// lyxc \
//   --target=arm64 \
//   --opt=2 \
//   --flight-crit \           # Alle FP-Operationen IEEE-754-strikt
//   --stack-check \           # Stack-Limit aller @stack_limit-Funktionen prüfen
//   --wcet \                  # WCET aller @wcet-Funktionen analysieren
//   --call-graph \            # Call-Graph erzeugen (für WCET-Pfad-Nachweis)
//   --mcdc-instrument \       # MC/DC-Instrumentierung einbetten
//   --no-fp-fold \            # FP-Constant-Folding global deaktivieren
//   --provenance \            # Quellzuordnung für jede Instruktion (Traceability)
//   --verify-tmr \            # @redundant-Variablen auf korrekte TMR-Implementierung prüfen
//   --emit-asm \              # Assembler-Output für DO-178C Objective Review
//   src/flight_control.lyx

DAL-C Build (Testsystem)

// lyxc \
//   --target=x86_64 \
//   --opt=0 \                 # Keine Optimierungen — Coverage bleibt sauber
//   --stack-check \
//   --mcdc-instrument \
//   --coverage-report=cov.json \
//   src/flight_control.lyx

Coverage-Nachweis erzeugen

// 1. Testlauf:      ./flight_control_test
//    Erzeugt:       coverage_raw.bin

// 2. Report bauen:
//    lyxc --coverage-report=cov.json coverage_raw.bin
//    lyxc --coverage-report-html=report/ cov.json

// 3. Ergebnis:      report/index.html
//    Enthält:       Statement-, Branch- und MC/DC-Abdeckung pro Funktion
//                   Vollständige Quellzuordnung via --provenance

Vollständige Flag-Referenz: → CLI-Referenz


9. Qualifikations-Evidenz — Was Lyx erzeugt

DO-178C verlangt eine strukturierte Menge von Nachweis-Dokumenten (Evidence). Lyx-Compiler-Outputs decken die technischen Artefakte ab:

DO-178C Artefakt Lyx-Quelle Flag
Source Code Traceability Quellcode + @dal-Annotationen –provenance
WCET Analysis Compiler-WCET-Report –wcet + –call-graph
Stack Usage Analysis Stack-Check-Report –stack-check
MC/DC Coverage Report Coverage-Datenbank → HTML –mcdc-instrument + –coverage-report
Assembly Listing (Object Review) Assembler-Output –emit-asm
Symbol Sizes Symbol-Tabelle –symbol-sizes
TMR Verification TMR-Prüf-Report –verify-tmr
Call Graph Aufruf-Graph (für Pfadanalyse) –call-graph

Die Planung- und Prozess-Dokumente (PSAC, SDP, SVP, SCI) entstehen außerhalb des Compilers — Lyx liefert die technischen Daten, auf die diese Dokumente verweisen.


10. DO-178C in Lyx — Gesamtbild

unit flight_control;
import std.io;

// Unit-Level: Code-Segment-Integrität
@integrity(mode: scrubbed, interval: 100)

// Typen mit physikalischen Grenzen
type Altitude = int64 range -1000..60000;
type Speed    = int64 range 0..1000;

// Kritische Zustandsvariable — dreifach im RAM
@redundant
var autopilot_active: bool := false;

// DAL-A Kernfunktion — alle Safety-Pragmas gesetzt
@dal(A)
@flight_crit
@integrity(mode: software_lockstep, interval: 50)
@stack_limit(4096)
@wcet(500)
fn AutopilotCycle(state: ^FlightState, cmd: ^FlightCommands): void {
    if (autopilot_active = false) { return; }

    var target_alt: Altitude := state.target_altitude as Altitude;
    var cur_alt:    Altitude := state.altitude        as Altitude;
    var delta:      int64    := target_alt - cur_alt;

    // Berechnung — dupliziert und verglichen (software_lockstep)
    cmd.elevator := Clamp(delta / 100, -30, 30) as int64;
    cmd.throttle := Clamp(500 + delta / 10, 0, 1000) as int64;
}

// DAL-B Navigationsfunktion
@dal(B)
@flight_crit
@wcet(200)
@stack_limit(2048)
fn FindNearestWaypoint(wps: [32]Waypoint, n: int64, pos: GeoPoint): int64 {
    var best: int64 := 0;
    var best_d: f64 := 1.0e18;
    for i := 0 to n - 1 do {
        var d: f64 := GeoDistance(pos, wps[i].pos);
        if (d < best_d) { best_d := d; best := i; }
    }
    return best;
}

fn main(): int64 {
    // Integritätsprüfung beim Start
    if (VerifyIntegrity() = false) {
        panic("Code corruption detected — switching to backup computer");
    }

    // ... Hauptschleife ...
    return 0;
}


Aerospace & Safety — Safety-Attribute, Range-Typen, Bounded Loops, Best-Practices-Tabelle und vollständiges Regelkreis-Beispiel
Aerospace Tutorial: Von der Anforderung zum Nachweis

Letzte Aktualisierung: 2026-05-22