Stève & Sophie

Cécile

Je tiens tout d'abord à remercier mon cher trio d'encadrants : Frédéric BONIOL pour sa sagesse, sa culture du domaine et pour son optimisme inébranlable, Claire PAGETTI pour sa rigueur, son investissement, sa tenacité et pour avoir été une co-bureau fort sympathique, David LESENS pour m'avoir transmis une part de sa connaissance de son milieu industriel, pour m'avoir réorienté quand nécessaire et pour sa constante modestie.

Merci à Alain GIRAULT et Yvon TRINQUET pour avoir accepté de rapporter cette thèse, à Yves SOREL pour avoir présidé mon jury et pour m'avoir mis sur la voie il y a sept ans, à Mario ALDEA RIVAS, Paul LE GUERNIC et Nicolas NAVET pour avoir accepté de faire partie de mon jury.

Un grand merci à mes anciens collègues, les éternels Nicolas, Arnaud, Cyril et Liliana, les experts Bruno et Jean-Louis, et bien sûr tous les chaleureux membres du DTIM. Ils ont supporté avec patience toutes mes questions, mes bavardages et sont tout simplement devenus des amis. Merci aussi à mes compagnons de tablées qui ont agréablement animé ces trois années.

Mes pensées affectueuses à ma deuxième famille croquante, soutien infaillible et inspiration, quel que soit l'éloignement géographique : Anne-Lise, Etienne, Ludovic, Nicolas,

Systèmes réactifs

Un système réactif [START_REF] Harel | Logics and models of concurrent systems, chapter On the development of reactive systems[END_REF] est un type particulier de système embarqué qui maintient une interaction permanente avec son environnement physique. Il attend des entrées (stimuli), effectue des calculs et produits des sorties (commandes) en réaction aux entrées. Les systèmes de contrôle ou les systèmes de surveillance sont de bons exemples de systèmes réactifs.

Une caractéristique particulière des systèmes réactifs est que le temps de réponse du système doit respecter des contraintes temps réel induites par leur environnement. Si la réponse arrive trop tard, le système peut rater des entrées en provenance de son environnement et répondre à des entrées ne correspondant pas à l'état actuel de l'environnement. Par ailleurs, les systèmes réactifs sont souvent hautement critiques (par exemple, le système de pilotage d'un avion ou le système de contrôle d'une centrale atomique) et les erreurs ne peuvent donc pas être tolérées.

Systèmes événementiels contre systèmes échantillonnés

On distingue généralement deux classes de systèmes réactifs : les systèmes événementiels et les systèmes échantillonnés. La différence réside dans la manière dont leurs entrées sont acquises. Dans le cas d'un système événementiel (parfois qualifié de système purement réactif ), le système attend l'arrivée d'un événement d'entrée et ensuite calcule sa réaction. A l'inverse, un système échantillonné acquiert ses entrées à intervalles de temps réguliers et calcule sa réaction pour chaque échantillon de ses entrées (et n'effectue éventuellement qu'une partie des calculs si seulement une partie des entrées est disponible pour l'échantillon courant). Bien que les systèmes événementiels correspondent à une plus grande classe de systèmes, les systèmes échantillonnés sont généralement plus simples à analyser et leur comportement est plus prédictible, en particulier en ce qui concerne les aspects temps réel. Dans la suite, nous nous consacrons aux systèmes échantillonnés.

Systèmes de contrôle-commande

Notre travail est plus particulièrement destiné aux systèmes de contrôle-commande. Ces systèmes consistent en une boucle de contrôle incluant capteurs, algorithmes de contrôle et actionneurs, ayant pour rôle de réguler l'état d'un système dans son environnement et d'accomplir une mission donnée. Les logiciels de commandes de vol d'un aéronef sont des exemples typiques de systèmes de contrôlecommande ayant pour objectif de contrôler la position, la vitesse et l'attitude de l'aéronef durant le vol à l'aide d'équipements physiques tels que les surfaces de contrôle pour un avion ou les propulseurs pour un véhicule spatial. Le système opère en boucle fermée avec son environnement car les commandes appliquées sur le véhicule modifient son état (position, vitesse, attitude), ce qui affecte ensuite les entrées du système et nécessite de calculer de nouvelles commandes et ainsi de suite.

Les calculs effectués par ces systèmes sont très réguliers : par exemple, il n'y pas de création dynamique de processus et le nombre d'itérations des instructions de boucles est borné statiquement. Ceci permet de produire des implantations optimisées sur le plan logiciel et matériel dont le comportement est prédictible, une caractéristique essentielle pour des systèmes hautement critiques.

Les systèmes de contrôle-commande doivent respecter un certain nombre de contraintes temps réel liées à des contraintes physiques. Tout d'abord, le rythme auquel un équipement doit être contrôlé (et donc le rythme auquel les opérations exécutées pour contrôler cet équipement doivent s'effectuer) doit être supérieur à une fréquence minimale, liée à l'inertie et autres caractéristiques physiques du véhicule. Au-dessous de cette fréquence, la sécurité du véhicule n'est plus assurée. Ces fréquences minimales varient entre les équipements. Par exemple, les dispositifs de propulsion d'un véhicule spatial doivent être contrôlés à un rythme très élevé pour assurer la stabilité du véhicule, tandis que la position des panneaux solaires peut être contrôlée légèrement plus lentement car une petite perte d'énergie a un impact moins important sur le véhicule. Ensuite, le rythme de contrôle d'un équipement doit être contrôlé doit aussi être inférieur à une fréquence maximale au-dessus de laquelle l'équipement n'est pas capable d'appliquer les commandes suffisamment rapidement ou peut être endommagé. Ce paramètre varie lui aussi d'un équipement à l'autre. Le rythme de contrôle d'un équipement est généralement choisi le plus proche possible de la fréquence minimale, afin d'éviter les calculs inutiles et ainsi permettre d'utiliser un matériel moins puissant (qui sera donc moins coûteux, consommera moins d'énergie, ...)

Comme pour tout système réactif, le temps de réponse d'un système de contrôle-commande est borné. Le délai entre une observation (entrées) et la réaction correspondante (sorties) doit respecter une contrainte de latence maximale, c'est-à-dire une contrainte d'échéance relative à la date des observations. L'échéance d'une sortie n'est pas nécessairement égale à sa période et peut lui être inférieure afin de répondre rapidement à une entrée.

Cas d'étude : The Flight Application Software

Dans cette section, nous présentons le Flight Application Software (FAS), système de contrôle de vol du Véhicule de Transfert Automatique (ATV), un véhicule spatial réalisé par EADS Astrium Space Transportation pour le ravitaillement de la Station Spatiale Internationale (ISS). C'est un exemple typique de système de contrôle-commande. Nous présentons une version simplifiée et adaptée du FAS dans la Fig. 1. Cette version ne correspond pas exactement au système réel mais donne une bonne vision globale de ses caractéristiques principales.

Le FAS consiste principalement en un ensemble d'opérations d'acquisition, d'opérations de calcul et d'opérations de commande, chacune schématisée par une boîte dans la figure. Les flèches représentent des dépendances de données. Une flèche sans source correspond à une entrée du système et une flèche sans destination à une sortie du système. Les dépendances de données définissent un ordre partiel entre les opérations du systèmes, le consommateur d'une donnée devant s'exécuter après le producteur de la donnée. Le FAS commence par acquérir les entrées du système : l'orientation et la vitesse à partir des capteurs gyroscopiques (GyroAcq), la position à partir du GPS (GPS Acq) et du star tracker (Str Acq), et les télécommandes en provenance de la station sol (TM/TC). La fonction Guidance Navigation and Control (divisée en une partie flot montant, GNC_US, et flot descendant, GNC_DS) calcule ensuite les commandes à appliquer tandis que la fonction Failure Detection Isolation and Recovery (FDIR) vérifie l'état du FAS afin d'assurer l'absence de pannes. Pour finir, les commandes sont calculées et envoyées aux équipements de contrôle : les commandes propulseurs pour le Propulsion Drive Electronics (PDE), les commandes de distribution d'énergie pour le Solar Generation System (SGS), les commandes de poxviii sitionnement des panneaux solaires pour le Solar Generation System, les télémétries à destination de la station sol (TM/TC), etc.

Chaque opération s'effectue à son propre rythme, situé entre 0.1Hz et 10Hz. On impose une contrainte d'échéance sur la donnée produite par le GNC Upstream (300ms au lieu de la période de 1s). Des opérations de rythmes différents peuvent communiquer, ce qui est une caractéristique essentielle de ce type de système et ce qui a un impact important sur la complexité des processus de spécification et d'implantation.

FIG. 1 -Le Flight Application Software

Contribution

La programmation d'un système de contrôle-commande est un processus complexe qui ne se résume pas à implanter les aspects fonctionnels d'un système. Ces systèmes étant hautement critiques, une exigence fondamentale sur le programme contrôlant le système est d'assurer que son comportement est prédictible. Ceci requiert un fort déterminisme fonctionnel, c'est-à-dire que le programme produise toujours la même séquence de sorties en réponse à une séquence donnée d'entrées. Le programme doit aussi être déterministe sur le plan temporel et respecter des contraintes temps réel dures. Le système doit de plus être optimisé, en termes de consommation mémoire, coût matériel, consommation énergétique ou poids par exemple.

La complexité du processus de développement et la criticité des systèmes motivent l'utilisation de langages de programmation haut-niveau, couvrant complètement le cycle de développement, de la spécification à l'implémentation. La génération de code automatique, à partir d'un programme haut-niveau vers un programme bas-niveau, constitue une bonne solution à ce problème, car elle procure une grande confiance dans le programme final du fait que chaque étape de la génération est définie formellement.

Dans cette thèse, nous proposons un tel langage haut-niveau pour la programmation de systèmes de contrôle-commande. Le langage est basé sur les langages synchrones [START_REF] Benveniste | The synchronous approach to reactive and realtime systems[END_REF] et hérite de leurs propriétés formelles. Il ajoute des primitives temps réel afin de permettre la spécification de systèmes multi-périodiques. L'objectif du langage n'est pas de remplacer les langages synchrones existants, mais plutôt de fournir une couche d'abstraction supplémentaire. Il peut être considéré comme un langage de description d'architecture logicielle temps réel permettant d'assembler plusieurs systèmes synchrones localement mono-périodiques en un système global multi-périodique. Le langage est supporté par un compilateur générant un code C multi-tâches synchronisé. Le code généré s'exécute sur un système d'exploitation temps réel classique disposant d'une politique d'ordonnancement préemptive. Les synchronisations entre tâches sont assurées par un protocole de communication à base de mémoires tampons qui ne nécessite pas de primitives de synchronisation (telles que les sémaphores). Nous allons maintenant motiver la définition de notre langage en montrant que l'état de l'art du domaine ne traite pas complètement le problème exposé ci-dessus. Nous situons aussi notre langage par rapport à cet existant.

Etat de l'art

Dans ce chapitre, nous présentons un résumé des langages existant concernant la programmation de systèmes de contrôle-commande. Les caractéristiques souhaitables d'un tel langage sont :

-La possibilité de spécifier un ensemble d'opérations ; -La possibilité de spécifier les échanges de données entre opérations et en particulier entre opérations de rythmes différents ; -La possibilité de spécifier des contraintes temps réel, telles que des contraintes de périodicité ou d'échéance ; -Une définition formelle du langage, afin d'assurer que la sémantique d'un programme est définie de manière non ambiguë ; -Une génération de code automatique, afin de couvrir le processus de développement de la spécification jusqu'à l'implémentation ; -Le déterminisme fonctionnel du code généré ; -Le déterminisme temporel du code généré ; -Pour finir : la simplicité, caractéristique essentielle permettant au programmeur de mieux comprendre et analyser un programme.

Langages de programmation bas-niveau

La technique probablement la plus répandue pour programmer un système temps réel est de le programmer à l'aide des langages impératifs traditionnels (C, ADA, JAVA). Ces langages n'étant à l'origine pas destinés à la programmation de systèmes temps réel, les aspects temps réel sont principalement gérés à l'aide d'interfaces de programmation dédiées (API), très proches du système d'exploitation temps réel (RTOS) sous-jacent.

API temps réel

Une API temps réel définit l'interface d'un ensemble de services (par exemple des fonctions ou des structures de données) liés à la programmation temps réel. L'objectif d'une telle API est de définir un standard permettant la portabilité d'une application entre différentes plates-formes d'exécution respectant ce standard. On peut par exemple citer les extensions temps réel de POSIX [START_REF]IEEE Std[END_REF][START_REF]POSIX, Threads extensions[END_REF], pour les langages C et ADA, la Spécification Temps Réel pour Java (RTSJ), OSEK dans l'industrie automobile [START_REF] Osek | OSEX/VDX Operating System Specification 2.2.1[END_REF] ou APEX dans le domaine avionique.

Ces API partagent les mêmes principes généraux : chaque opération du système est tout d'abord programmée dans une fonction séparée et les fonctions sont ensuite regroupées en threads sur lesquels xx le programmeur peut spécifier des contraintes temps réel. Un programme est ensuite constitué d'un ensemble de threads ordonnancés de manière concurrente par le système d'exploitation. Les threads étant reliés par des dépendances de données, le programmeur doit ajouter des primitives de synchronisation assurant que le producteur de la donnée s'exécute avant le consommateur. Ces synchronisations sont essentielles dans le cas des systèmes critiques afin d'assurer la prédictibilité du système. Les synchronisations peuvent être implémentées soit en contrôlant précisément les dates d'exécution des différents threads (communications time-triggered), soit à l'aide de primitives de synchronisation bloquantes telles que les sémaphores (communications par rendez-vous).

Systèmes d'exploitation temps réel

Un système d'exploitation temps réel est un système d'exploitation multi-tâches destiné spécifiquement à l'exécution d'applications temps réel. Le système est capable d'exécuter plusieurs tâches de manière concurrente, alternant entre les différentes tâches. La (ou les) politique d'ordonnancement du système d'exploitation fournit un algorithme général permettant de déterminer l'ordre dans lequel les tâches doivent s'exécuter en fonction de leurs caractéristiques temps réel (période, échéance). L'ordonnancement est souvent préemptif (mais pas nécessairement), ce qui signifie qu'une tâche peut être suspendue en cours d'exécution, pour exécuter une tâche plus urgente, puis restaurée plus tard.

Un RTOS tente généralement de suivre une des APIs temps réel citées précédemment. On peut citer comme exemple VxWorks par Wind River Systems [START_REF] Vxworks | VxWorks Programmer's Guide: Algorithms for Real-Time Scheduling Problems[END_REF], QNX Neutrino [START_REF] Hildebrand | An architectural overview of qnx[END_REF] et OSE [START_REF] Ose | OSE Real-Time Operating System[END_REF] pour les systèmes commerciaux, RTLinux (www.rtlinuxfree.com) et RTAI (www.rtai.org) pour les systèmes open-source, ou encore SHARK [START_REF] Gai | A new kernel approach for modular real-time systems development[END_REF], MaRTE [START_REF] Rivas | POSIX-Compatible Application-Defined Scheduling in MaRTE OS[END_REF] et ERIKA [GLA + 00], des noyaux de recherche tentant de fournir un niveau d'abstraction plus élevé que les systèmes classiques.

Vers un plus haut niveau d'abstraction

Programmer des systèmes temps réel à l'aide de langages bas-niveau permet certes d'implémenter une grande classe de systèmes mais présente des inconvénients majeurs. Tout d'abord, dans le cas d'applications complexes, les mécanismes de synchronisation sont fastidieux à mettre en place et difficile à maîtriser. Plus généralement, le faible niveau d'abstraction rend la correction du programme difficile à prouver. Enfin, du fait de ce faible niveau d'abstraction, il est difficile de distinguer au sein d'un programme les éléments liés à de réelles exigences de conception de ceux liés à des soucis d'implémentation, ce qui rend la maintenance compliquée. Ces considérations ne peuvent bien entendue pas être évitées, cependant il est beaucoup plus simple de se reposer sur un langage de programmation avec un niveau d'abstraction plus élevé, pour lequel les problèmes de synchronisation et d'ordonnancement sont gérés par le compilateur du langage et non par le programmeur.

Les langages synchrones

Forts de ces considérations, les développeurs de systèmes de Contrôle-Commande s'orientent progressivement vers des langages avec un niveau d'abstraction plus élevé. Bien que ceux-ci soient pour l'instant principalement utilisés durant les premières phases du cycle de développement (spécification, vérification, simulation), plusieurs langages ont pour objectif de couvrir le cycle complet, de la spécification jusqu'à l'implémentation, à l'aide de processus de génération automatique de code. Une telle approche permet de réduire la durée du cycle de développement, d'écrire des programmes plus simples à comprendre et à analyser, et enfin de produire des programmes bas-niveau dont la correction est assurée par la compilation.

L'approche synchrone [START_REF] Benveniste | The synchronous approach to reactive and realtime systems[END_REF] propose un haut-niveau d'abstraction, basé sur des fondations mathématiques à la fois simples et solides, qui permettent de gérer la vérification et la compilation d'un programme de manière formelle. L'approche synchrone simplifie la programmation des systèmes temps réel en faisant abstraction de ce temps réel pour le remplacer par un temps logique, défini comme une suite d'instants, où chaque instant représente l'exécution d'une réaction du système. Selon l'hypothèse synchrone, le développeur peut faire abstraction des dates auxquelles les calculs sont effectués durant un instant, à condition que ces calculs se terminent avant le début du prochain instant.

Cette approche a été implantée dans plusieurs langages, on distingue souvent les langages synchrones équationnels tels que LUSTRE [START_REF] Halbwachs | The synchronous data-flow programming language LUSTRE[END_REF] ou SIGNAL [START_REF] Benveniste | Synchronous programming with events and relations: the Signal language and its semantics[END_REF], des langages synchrones impératifs tels que ESTEREL [START_REF] Boussinot | The Esterel language[END_REF].

Langages synchrones équationnels

Les variables et expressions manipulées par un langage synchrone équationnel correspondent à des séquences de valeurs infinies (appelées flots ou signaux) décrivant les valeurs que prennent ces variables ou expressions à chaque instant du programme. L'horloge d'une séquence définit les instants ou cette séquence porte une valeur (est présente) ou pas (est absente). Un programme consiste en un ensemble d'équations, structurées de manière hiérarchique (à l'aide de noeuds en LUSTRE ou processus en SI-GNAL). Les équations définissent les séquences de sortie du programme en fonction de ses séquences d'entrées. Un exemple simple de programme LUSTRE est donné Fig. 2. Son comportement est illustré Fig. 3 où l'on donne la valeur de chaque flot à chaque instant. Les opérateurs arithmétiques et logiques sont simplement étendus point-à-point (v1+1). L'opérateur pre permet d'accéder à la valeur d'un flot à l'instant précédent. x->y vaut x au premier instant puis y aux instants suivants. L'expression x when c vaut x uniquement quand c vaut vrai et ne porte pas de valeur sinon. A l'inverse, current(x) permet de remplacer les valeurs absentes introduites par un opérateur when par la dernière valeur de x.

node N( c : b o o l ; i : i n t ) r e t u r n s ( o : i n t ) var v1 : i n t ; v2 : i n t when c ; l e t v1=0->pre ( i ) ; v2 = ( v1 + 1 ) when ( t r u e ->c ) ; o= c u r r e n t ( v2 ) ; t e l FIG. 2

i 0 i 1 i 2 i 3 . . . v1=0->pre(i) 0 i 0 i 1 i 2 i 3 . . . v1+1 1 i 0 + 1 i 1 + 1 i 2 + 1 i 3 + 1 . . . v2=(v1+1) when (true->c) 1 i 0 + 1 i 3 + 1 . . . o=current(v2) 1 i 0 + 1 i 0 + 1 i 0 + 1 i 3 + 1 . . .

FIG. 3 -Comportement d'un programme LUSTRE

Un programme synchrone équationnel décrit le comportement d'un système à chaque itération de base. Cependant, comment définir une itération de base dans le cas des systèmes multi-périodiques ? Dans la Fig. 4, on considère le cas d'un système constitué d'une opération rapide F, de rythme 10Hz, et d'une opération lente S, de rythme 2Hz. La durée d'une itération de base doit-elle être de 100ms xxii (10Hz) et comprendre une itération de l'opération rapide F ainsi qu'un fragment de l'opération lente S qui variera suivant les itérations, ou bien doit-elle être de 500ms (2Hz), et comprendre une itération complète de l'opération lente et 5 itérations de l'opération rapide ? Ce type de problèmes a motivé l'introduction des aspects temps réel dans LUSTRE dans [START_REF] Curic | Implementing Lustre programs on distributed platforms with real-time constraints[END_REF]. Des hypothèses permettent de spécifier le rythme de base d'un programme (la durée temps réel d'un instant) ainsi que les durées d'exécution des noeuds. Des exigences permettent de contraindre la date à laquelle un flot doit être produit ainsi que la latence maximale entre deux flots. Enfin, ces extensions permettent de définir des horloges périodiques, qui valent vrai une fois toutes les n itérations de base. Le compilateur assure ensuite à l'aide de techniques d'ordonnancement statiques que les exigences seront satisfaites à condition que les hypothèses soient respectées. L'ordonnancement est très spécifique à la plateforme d'exécution considérée, TTA (Time-Triggered Architecture) [START_REF] Kopetz | The Time-Triggered Architecture[END_REF].

Ce travail a été étendu dans [START_REF] Alras | Model-based design of embedded control systems by means of a synchronous intermediate model[END_REF], où sont proposés des opérateurs spécifiant des conditions d'activation périodiques et des primitives permettant de gérer plusieurs modes d'exécution. Les auteurs proposent aussi un processus de compilation plus simple et plus générique générant un ensemble de tâches temps réel concurrentes. Les communications inter-tâches sont gérées par le protocole proposé dans [START_REF] Sofronis | A memory-optimal buffering protocol for preservation of synchronous semantics under preemptive scheduling[END_REF] qui assure la conservation de la sémantique synchrone de tâches concurrentes et communicantes, à l'aide de mécanismes de mémoires tampon basés sur les priorités des tâches. Le calcul des priorités des tâches n'est pas détaillé dans [START_REF] Alras | Model-based design of embedded control systems by means of a synchronous intermediate model[END_REF]. Notre travail s'intéresse particulièrement à ce problème et calcule les priorités des tâches de manière automatique.

Dans [START_REF] Smarandache | A canonical form for affine relations in signal[END_REF], une extension du calcul d'horloge est proposée à travers la définition des relations affines d'horloges. Plus récemment, [START_REF] Metivier | Analysis of periodic clock relations in polychronous systems[END_REF] considère une restriction de ces relations aux systèmes multi-périodiques. Ce travail porte principalement sur la vérification formelle et pour l'instant la compilation d'un programme vers un ensemble de tâches temps réel ne semble pas avoir été étudiée.

Langages synchrones impératifs

Le principal représentant des langages synchrones impératifs est le langage ESTEREL [START_REF] Boussinot | The Esterel language[END_REF], qui s'intéresse principalement à la description du flot de contrôle d'un système. Un programme ESTEREL consiste en un ensemble de processus concurrents communiquant par l'intermédiaire de signaux et structurés en modules. Un signal émis par un processus est instantanément perçu par les tous les processus qui surveillent ce signal. Les processus exécutés par un module sont décrits comme une composition d'instructions. Les instructions peuvent être composées séquentiellement ou en parallèle (de manière concurrente). La plupart des instructions sont considérées immédiates, exceptées les instructions contenant des pauses explicites. A chaque réaction du programme, le flot de contrôle avance dans chaque processus jusqu'à atteindre une instruction de pause ou de terminaison. A la réaction suivante, les processus reprennent là où ils s'étaient arrêtés. Un exemple de programme est donné Fig. 5. Son comportement est illustré Fig. 6 où nous détaillons les signaux reçus et émis par le programme à chaque réaction. Le programme répète indéfiniment le même comportement (loop..end). Il commence par effectuer deux branches en parallèle (||), la première branche reste en pause jusqu'à l'occurrence de A (await A) et la seconde jusqu'à l'occurrence de B. Cette instruction parallèle est ensuite composée en séquence avec une instruction émettant le signal O (emit O). Le programme reste ensuite en pause jusqu'à l'occurrence de R. Le programme revient ensuite au début de la boucle. Les pragmas sont démarqués par %{ et }%. Le pragma (10, 15) spécifie que la durée d'exécution de la procédure C est comprise entre 10 et 15. Le pragma%{PP:=age(PulsePeriod)}% déclare une variable de temps continue, représentant le temps écoulé depuis l'émission du signal PulsePeriod. Le pragma PP<=40 spécifie une contrainte temps réel, qui exige que la procédure C s'achève au plus tard 40 unités de temps après l'émission du signal PulsePeriod.

L'outil TAXYS combine le compilateur ESTEREL SAXO-RT [WBC + 00] avec l'outil de modelchecking KRONOS [START_REF] Daws | The tool KRONOS[END_REF]. Le compilateur traduit le programme annoté en un automate temporisé [START_REF] Alur | A theory of timed automata[END_REF]. L'automate est ensuite analysé à l'aide du model-checker afin de valider les contraintes temps réel du programme. Le programme ESTEREL est traduit en un code C séquentiel et l'objectif de ce travail n'est pas d'utiliser un système d'exploitation temps réel préemptif.

SYNDEX et la méthodologie AAA

La méthodologie Adéquation-Algorithm-Architecture (AAA) [START_REF] Thierry Grandpierre | Optimized rapid prototyping for real-time embedded heterogeneous multiprocessors[END_REF] et l'outil associé SYNDEX fournissent un cadre de développement pour les systèmes temps réel embarqués distribués. Elle repose sur une représentation sous forme de graphe des langages synchrones flots de données. Les aspects fonctionnels d'un système sont décrits dans la partie Algorithme (opérations et flots de données) tandis que les aspects matériels sont décrits dans la partie Architecture (opérateurs de calculs et média de communication). Le développeur spécifie ensuite le temps d'exécution des différentes opérations de xxiv calcul et la durée nécessaire aux différents transferts de données. L'Adéquation consiste alors à chercher une implantation optimisée de l'Algorithme sur l'Architecture.

L'introduction des systèmes multi-périodiques dans la méthodologie AAA a tout d'abord été étudiée dans [START_REF] Cucu | Non-preemptive scheduling and schedulability condition for embedded systems with real-time constraints[END_REF][START_REF] Cucu | Real-time scheduling for systems with precedence, periodicity and latency constraints[END_REF] puis dans [START_REF] Kermia | Non-preemptive multiprocessor static scheduling for systems with precedence and strict periodicity constraints[END_REF][START_REF] Kermia | Ordonnancement temps réel multiprocesseur de tâches non préemptives avec contraintes de précédence, de périodicité stricte et de latence[END_REF]. Les systèmes multi-périodiques sont officiellement supportés par SYNDEX depuis sa version 7, diffusée en Août 2009. D'après des expérimentations encore incomplètes, le développeur ne peut spécifier des dépendances de données entre deux opérations de rythmes différents que si la période d'une opération est un multiple de la période de l'autre. Les motifs de communication multi-rythme sont uniquement sans perte. Par exemple, si le producteur d'une donnée est n fois plus rapide que le consommateur, chaque instance du consommateur consomme les données produites par n répétitions du producteur.

Synchronous Data-Flow (SDF)

Un graphe Synchrone Flot de Données (SDF) [START_REF] Lee | Synchronous Data Flow[END_REF] est un graphe flot de données constitué d'un ensemble de noeuds représentant des calculs, reliés par des dépendances de données. Chaque entrée ou sortie d'un noeud est annotée par un entier représentant le nombre de données consommées ou produites par le noeud. Les nombres de données n produites par la source d'une dépendance de donnée et n consommées par la destination de la dépendance ne sont pas nécessairement identiques. L'exécution d'un graphe nécessite donc de répéter la source de la dépendance k fois pour chaque k répétitions de la destination de la dépendance, de sorte que kn = k n .

La figure 7 montre un exemple de graphe SDF. Afin de respecter les rythmes d'échantillonnage de chaque noeud, une répétition du graphe correspond à une répétition de C et OUT, 2 répétitions de B (pour chaque C), 6 répétitions de IN et A (3 pour chaque répétition de B).

FIG. 7 -Un graphe SDF

Etant donné que les rythmes des différentes opérations sont déterminés par les ratios entre opérations dépendantes, les motifs de communication que l'on peut spécifier sont restreints à des communications sans perte (du même style que ceux décrits pour SYNDEX). De plus, les graphes SDF spécifient bien des systèmes multi-rythmes mais pas multi-périodiques. En effet, rien n'oblige les répétitions successives d'un noeud à s'exécuter à des intervalles de temps réguliers, donc d'un point de vue temps réel, les noeuds ne sont pas périodiques.

Les systèmes Globalement Asynchrones Localement Synchrones (GALS)

Alors que l'étude des systèmes multi-périodiques dans le cadre synchrone est relativement récente, la classe plus générale des systèmes Globalement Asynchrone Localement Synchrones (GALS) est nettement plus étudiée. Dans un système GALS, plusieurs systèmes localement synchrones sont assemblés à l'aide de mécanismes de communication asynchrones afin de produire un système complet globalement asynchrone. La classe générale des systèmes GALS est par exemple étudiée dans ESTEREL multi-horloge [START_REF] Berry | Multiclock esterel[END_REF] et POLYCHRONY [START_REF] Le | Polychrony for system design[END_REF]. L'approche Quasi-synchrone [START_REF] Caspi | About the design of distributed control systems: The quasi-synchronous approach[END_REF], l'approche N -Synchrone [CDE + 06, CMPP08] ou encore les Latency Insensitive Designs [START_REF] Luca | Theory of Latency-Insensitive Design[END_REF], ne s'intéressent qu'à des classes particulières de systèmes localement synchrones pouvant être composés de manière « quasiment » synchrone à l'aide de mécanismes de communication à mémoires bornées.

Il serait donc possible de considérer les systèmes de Contrôle-Commande comme un cas particulier de GALS et de réutiliser le travail existant. Cependant, les systèmes que nous considérons sont par xxv essence complètement synchrones et les traiter en tant que tels permet de mieux identifier leurs caractéristiques temps réel (lors de la spécification), de mieux les analyser (prouver la bonne synchronisation des opérations, effectuer des tests d'ordonnançabilité) et de produire un code plus optimisé (en utilisant des protocoles de communication spécifiques).

Matlab/Simulink

SIMULINK [Mat] est un langage de modélisation haut-niveau développé par The Mathworks et très utilisé dans de nombreux domaines d'application industriels. SIMULINK était à l'origine uniquement destiné à la spécification et à la simulation mais des générateurs de code commerciaux ont depuis été développés, notamment REAL-TIME WORKSHOP EMBEDDED CODER par The Mathworks, ou TARGET-LINK par dSpace. La difficulté majeure pour programmer des systèmes critiques à l'aide de SIMULINK est que SIMULINK et les outils associés ne sont pas définis de manière formelle. Par exemple, SIMULINK dispose de plusieurs sémantiques, qui varient suivant des options configurées par l'utilisateur et qui ne sont définies qu'informellement. De même, la conservation de la sémantique entre le modèle simulé et le code généré n'est pas clairement assurée.

Une traduction de SIMULINK vers LUSTRE a été proposée dans [START_REF] Tripakis | Translating discretetime Simulink to Lustre[END_REF]. Cette traduction ne considère qu'un sous-ensemble bien défini des modèles SIMULINK et permet de bénéficier de la définition formelle de LUSTRE. Ceci permet d'effectuer des vérifications formelles et de profiter de la génération de code automatique de LUSTRE permettant de conserver la sémantique du modèle SIMULINK original.

Les langages de description d'architecture (ADL)

Un langage de description d'architecture (ADL) fournit une couche d'abstraction supplémentaire en comparaison des langages présentés précédemment. Un tel langage se concentre sur la modélisation des interactions entre les composants haut-niveau d'un système. Typiquement, dans notre contexte, un ADL se concentre sur la description des interactions entre tâches plutôt qu'entre fonctions. Les fonctions doivent certes toujours être implémentées quelque part, mais ce niveau de détail est caché dans une spécification ADL. Une telle approche multi-niveau reflète l'organisation du processus de développement pour des systèmes complexes tels que les systèmes de Contrôle-Commande. Plusieurs ingénieurs développent séparément les différentes fonctionnalités du système et ensuite une personne différente, l'intégrateur, assemble ces différentes fonctionnalités pour produire le système complet. L'intégrateur a besoin d'un niveau d'abstraction élevé pour se coordonner avec les autres ingénieurs : les ADLs proposent ce niveau d'abstraction. AADL L'Architecture Analysis and Design Language (AADL) [START_REF] Peter | The architecture analysis & design language (AADL): An introduction[END_REF] est un ADL destiné à la modélisation de l'architecture logicielle et matérielle d'un système embarqué temps réel. Une spécification AADL est constituée d'un ensemble de composants, qui interagissent par l'intermédiaire d'interfaces. Les composants incluent des composants logiciels tels que les threads mais aussi des composants matériels tels qu'un processeur. De nombreuses propriétés peuvent être spécifiées selon le type de composant, on retiendra la possibilité de spécifier pour un thread sont protocole de déclenchement (périodique, apériodique, sporadique), sa période, sa durée d'exécution et son échéance.

Les composants interagissent par des connexions entre les ports de leurs interfaces. Une connexion peut être immédiate ou retardée (de manière similaire aux langages synchrones). Des connexions peuvent aussi relier des threads de rythme différent, avec ou sans délai. Les motifs de communication obtenus pour les différents types de connexions sont illustrés Fig. 8.

REACT/CLARA

Le projet REACT [START_REF] Faucou | An ADL centric approach for the formal design of real-time systems[END_REF] propose une suite d'outils dédiés à la conception de systèmes temps réel. Il combine la modélisation formelle, à l'aide de l'ADL CLARA [START_REF] Durand | Description et vérification d'architectures d'application temps réel : CLARA et les réseaux de Petri temporels[END_REF], avec des techniques de vérification formelle basées sur les Réseaux de Pétri Temporisés [START_REF] Berthomieu | Modeling and verification of time dependent systems using time petri nets[END_REF]. Le langage CLARA est dédié à la description de l'architecture fonctionnelle d'un système réactif.

Tout comme AADL, CLARA permet de décrire un système comme un ensemble de composants haut-niveau communicant appelés activités. Une activité est déclenchée de manière répétitive par la règle d'activation spécifiée sur son port de début et signale sa terminaison sur son port de fin. A chaque activation, une activité exécute une suite d'actions : acquisition d'entrées, appels de fonctions externes (dont la durée est spécifiée dans le programme), production de sorties. Des générateurs d'occurrences permettent de spécifier les données d'entrée ou signaux d'entrée d'un système. Il peuvent être soit périodiques, soit sporadiques.

Les ports des différentes activités son connectés par l'intermédiaire de liens sur lesquels le programmeur peut spécifier différents protocoles de communication : rendez-vous (protocole bloquant qui synchronise le producteur et le consommateur), tableau noir (protocole non-bloquant, le consommateur utilise simplement la dernière valeur produite) ou boîte aux lettres (communication par l'intermédiaire d'une FIFO). La taille d'une boîte aux lettres peut être bornée, auquel cas le producteur doit attendre lorsqu'elle est pleine et le consommateur doit attendre lorsqu'elle est vide. Il est aussi possible de spécifier des opérateurs sur des liens entre activités de rythmes différents. L'opérateur (n) spécifie que n répétitions du consommateur lisent la même valeur. L'opérateur (1/n) spécifie que seule une valeur parmi n valeurs successives sera consommée.

Un exemple de programme est donné Fig. 9. Le système est constitué de deux activités FDIR et GNC_US, GNC_US étant 10 fois plus lente que FDIR. Le générateur d'occurrence ck émet un signal toutes les 100ms. Ce signal est communiqué à l'aide d'une boîte aux lettres de taille 1 vers le port de début FDIR, ce qui a pour effet de déclencher FDIR toutes les 100ms. L'opérateur (1/10) situé sur le lien du port de début de FDIR vers le port de fin de GNC_US permet de déclencher GNC_US une fois toutes les 10 terminaisons de FDIR. De même, GNC_US ne consomme qu'une donnée sur 10 produites par FDIR. A l'inverse, 10 itérations successives de FDIR consomment la même donnée produite par GNC_US (du fait de l'opérateur (10)).

FIG. 9 -Une boucle de communication multi-rythme en CLARA

Notre langage retient certains des concepts présentés ici mais repose sur une sémantique synchrone. De plus, il semble que CLARA soit pour l'instant uniquement destiné à la vérification formelle et que la génération de code n'est pas encore supportée.

UML et CCSL

Le Unified Modeling Language (UML) [START_REF]Unified Modeling Language, Superstructure[END_REF] est un langage de modélisation à but général, constitué d'un ensemble de notations graphiques permettant de créer des modèles abstraits d'un système. Sa sémantique est sciemment lâche et peut être raffinée par la définition de profils spécifiques à un domaine. C'est le cas du profil UML pour la modélisation et l'analyse de systèmes temps réel embarqués (MARTE) [START_REF]UML Profile for MARTE, beta 1[END_REF]. Ce profil contient en particulier le langage de spécification de contraintes d'horloges (CCSL) [START_REF] André | Combining CCSL and Esterel to specify and verify time requirements[END_REF] qui permet de spécifier les propriétés temps réel d'un système. Ce langage est inspiré des langages synchrones mais se destine à la classe plus générale des GALS.

CCSL fournit un ensemble de constructions basées sur les horloges et les contraintes d'horloges afin de spécifier des propriétés temporelles. Les horloges peuvent être soit denses, soit discrètes. Dans notre contexte, nous ne considérons que les horloges discrètes, qui sont constituées de séquences d'instants de durées abstraites. Le langage propose des contraintes d'horloges très riches, qui spécifient des contraintes sur l'ordre des instants de différentes horloges. Il est en particulier possible de définir des horloges périodiques et des échantillonnages d'horloges périodiques. CCSL est un langage très récent et n'est pour l'instant pas destiné à la génération de code mais à la vérification formelle. Comme il est très général, une génération de code efficace nécessiterait vraisemblablement de ne retenir qu'un sous-ensemble des contraintes proposées par le langage.

GIOTTO

GIOTTO [START_REF] Henzinger | Giotto: A timetriggered language for embedded programming[END_REF] est un ADL de type time-triggered destiné à l'implantation de systèmes temps réel. Un programme GIOTTO est constitué d'un ensemble de tâches multi-périodiques groupées en modes, les tâches étant implantées à l'extérieur de GIOTTO. Chaque tâche possède une fréquence rexxviii 2. Horloges périodiques temps réel lative au mode la contenant : si le mode a pour période P et la tâche pour fréquence f , alors la tâche a pour période P/f . Les données produites par une tâche sont considérées disponibles uniquement à la fin de sa période. A l'inverse, lorsqu'une tâche s'exécute, elle ne peut consommer que la dernière valeur de chaque entrée, produite au début de sa période. En pratique, cela revient à dire que les tâches communiquent toujours avec un délai à la LUSTRE. Ce motif de communication est très restrictif et implique des latences importantes entre l'acquisition d'une entrée et la production de la sortie correspondante. Ce type de communications n'est clairement pas adapté aux systèmes que nous considérons.

Motivation de notre travail

Comme nous pouvons le constater, les langages existants ne permettent que partiellement d'implanter des systèmes de Contrôle-Commande soumis à des contraintes temps réel multiples. Nous proposons un langage se présentant comme un langage d'architecture logicielle temps réel qui transpose la sémantique synchrone au niveau d'un ADL. Définir le langage en tant qu'ADL permet de disposer d'un haut niveau d'abstraction, tout en reposant sur la sémantique synchrone afin de profiter de propriétés formelles bien établies.

Un programme est constitué d'un ensemble de noeuds importés, implémentés à l'extérieur du programme à l'aide de langages existants (C ou LUSTRE par exemple), et de flots de données entre les noeuds importés. Le langage permet de spécifier de multiples contraintes d'échéance et de périodicité sur les flots et sur les noeuds. Il définit également un ensemble restreint d'opérateurs de transition de rythme permettant au développeur de définir de manière précise les motifs de communication entre noeuds de rythmes différents. Un programme est traduit automatiquement en un ensemble de tâches concurrentes implantées en C. Ces tâches peuvent être exécutées à l'aide de systèmes d'exploitation temps réel existants.

Le langage peut se comparer avec les langages existants comme suit :

-ADLs : Le niveau d'abstraction du langage est similaire à celui des ADLs mais le langage repose sur une sémantique différente (l'approche synchrone) et permet au développeur de définir ses propres motifs de communication entre tâches ; -Langages Synchrones : Bien que le langage ait une sémantique synchrone, il possède un niveau d'abstraction plus élevé, ce qui permet une traduction efficace en un ensemble de tâches temps réel ; -Langages Impératifs : Bien que le programme soit au final traduit en code impératif, le code généré est correct-par-construction. Les analyses peuvent donc être effectuées directement sur le programme d'origine plutôt que sur le code généré.

Horloges périodiques temps réel

A première vue, tenir compte du temps réel dans un langage synchrone peut sembler contradictoire, étant donné que l'hypothèse synchrone est souvent qualifiée d'hypothèse de temps zéro. Dans cette section, nous montrons que le temps réel peut néanmoins être introduit dans l'approche synchrone, tout en gardant sa sémantique claire et intuitive.

Un modèle reliant instants et temps réel

Nous avons vu précédemment que l'abstraction complète du temps réel par les langages synchrones rend la programmation de systèmes multi-périodiques difficile. Nous présentons dans cette section un modèle synchrone reliant les instants au temps réel, tout en conservant les bases de l'approche synchrone. xxix Echelles de temps logique multiples L'idée centrale de notre modèle est qu'un système multi-périodique peut être considéré comme un ensemble de systèmes localement synchrones et mono-périodiques assemblés pour former un système global synchrone multi-périodique. Localement, chaque sous-système possède sa propre échelle de temps logique, sa propre notion d'instant et doit respecter l'hypothèse synchrone habituelle, qui nécessite de terminer les traitements avant la fin de l'instant courant de cette échelle de temps logique. Lorsque plusieurs sous-systèmes de rythmes différents sont assemblés, l'assemblage concerne des systèmes possédant des échelles de temps logique différentes. Ceci nécessite donc une référence commune afin de comparer des instants appartenant à des échelle de temps logique différentes. Le temps réel constitue cette référence commune.

Par exemple, dans le FAS, les opérations Gyro Acq, FDIR et PDE partagent la même échelle de temps logique, dans laquelle un instant a une durée de 100ms, tandis que Str Acq et TM/TC partagent une autre échelle de temps logique, dans laquelle les instants ont une durée de 10s. Le développeur peut décrire les opérations séparément à l'aide de l'approche synchrone habituelle mais lors de l'assemblage des opérations il doit pouvoir comparer des instants « rapides » avec des instants « lents ». Lorsqu'on considère la durée réelle d'un instant, il est naturel de considérer qu'un instant lent correspond à 100 instants rapides. Notre modèle formalise cette idée en reliant instants et temps réel.

Flots temps réel

Notre modèle synchrone temps réel repose sur le Tagged-Signal Model [START_REF] Lee | Comparing models of computation[END_REF]. Etant donné un ensemble de valeurs V, un flot est une séquence de couples (v i , t i ) i∈N où v i est une valeur dans V et t i est une étiquette dans Q, telle que pour tout i, t i < t i+1 . L'horloge d'un flot f , notée ck(f ), est sa projection sur Q. De même, val(f ) représente la séquence de valeurs de f (sa projection sur V). Deux flots sont synchrones s'ils ont la même horloge. Une étiquette représente la quantité de temps écoulée depuis le début de l'exécution du programme. On dit qu'un flot est présent à la date t si t apparaît dans son horloge, absent sinon.

Selon l'hypothèse synchrone relâchée de [START_REF] Curic | Implementing Lustre programs on distributed platforms with real-time constraints[END_REF], à chaque activation la valeur d'un flot doit être calculée avant sa prochaine activation. Ainsi, chaque flot a sa propre notion d'instant et la durée de l'instant t i est t i+1 -t i . Deux flots sont synchrones si les durées de leurs instants sont identiques. Il est possible de comparer la durée des instants de deux flots pour déterminer si un flot est plus rapide que l'autre et, surtout, il est possible de déterminer de combien ce flot est plus rapide. Par exemple, dans le FAS, les sorties de Gyro Acq ont pour horloge (0, 100, 200, . . .), tandis que les sorties de Str Acq ont pour horloge (0, 10000, 20000, . . .). Ainsi les sorties de Gyro Acq sont 100 fois plus rapides.

Horloges Strictement Périodiques

Dans cette section nous introduisons une nouvelle classe d'horloges, les horloges strictement périodiques, ainsi que des transformations spécifiques permettant de gérer les transitions entre échelles de temps logique différentes.

Définition

Une horloge est une séquence d'étiquettes, nous définissons une classe particulière d'horloges appelées horloges strictement périodiques de la manière suivante :

Définition 1. (Horloge strictement périodique). Une horloge h = (t i ) i∈N , t i ∈ Q, est strictement pério- dique si et seulement si : ∃n ∈ Q + * , ∀i ∈ N, t i+1 -t i = n xxx 2.
Horloges périodiques temps réel n est la période de h, notée π(h) et t 0 est la phase de h, notée ϕ(h).

Une horloge strictement périodique définit le rythme temps réel d'un flot. La notion de phase définie ici est un peu plus générale qu'habituellement étant donnée que nous n'imposons pas que ϕ(h) < π(h). Une horloge strictement périodique est caractérisée de manière unique par sa phase et sa période :

Définition 2. Le terme (n, p) ∈ Q + * × Q représente l'horloge strictement périodique α telle que : π(α) = n, ϕ(α) = π(α) * p

Transformations périodiques d'horloges

Nous définissons ensuite des transformations d'horloges spécifiques aux horloges strictement périodiques produisant de nouvelles horloges strictement périodiques : Définition 3. (Division périodique d'horloge). Soit α une horloge strictement périodique et k ∈ Q + * . "α/ . k" est une horloge strictement périodique telle que :

π(α/ . k) = k * π(α), ϕ(α/ . k) = ϕ(α) Définition 4. (Multiplication périodique d'horloge). Soit α une horloge strictement périodique et k ∈ Q + * . "α * . k" est une horloge strictement périodique telle que : π(α * . k) = π(α)/k, ϕ(α * . k) = ϕ(α) Définition 5. (Décalage de phase). Soit α une horloge strictement périodique et k ∈ Q. "α → . k" est une horloge strictement périodique telle que : π(α → . k) = π(α), ϕ(α → . k) = ϕ(α) + k * π(α)
Les divisions réduisent et les multiplications augmentent la fréquence d'une horloge, tandis que les décalages de phase changent la phase d'une horloge. Ceci est illustré Fig. 10. A nouveau, nous n'exigeons pas que ϕ(α → . k) < π(α). Soit P l'ensemble des horloges strictement périodiques. Des définitions précédentes, nous déduisons les Propriétés 1, 2 et 3.

Propriété 1. ∀α, β ∈ P : -∀k ∈ Q, α = β → . k ⇔ β = α → . -k ; -∀k ∈ Q + * , α = β/ . k ⇔ β = α * . k. Propriété 2. ∀(n, p) ∈ Q + * × Q : -∀k ∈ Q + * , (n, p) * . k = ( n k , kp) ; -∀k ∈ Q + * , (n, p)/ . k = (nk, p k ) ; -∀k ∈ Q, (n, p) → . k = (n, p + k).
Propriété 3. L'ensemble des horloges strictement périodiques P est fermé par les opérations * . , / . et → . .

Horloges strictement périodiques entières

Nous avons défini le modèle général des horloges strictement périodiques en utilisant des dates dans Q. Cependant, afin de définir un langage compilable, nous devons nous restreindre à des dates dans N. En effet, les systèmes d'exploitation reposent sur un modèle de temps discret et il existe toujours une borne inférieure du niveau de granularité pouvant être utilisée pour décrire le temps. Par exemple, la date 1/3 n'existe pas dans un système réel, ou alors est une approximation. Pour les mêmes raisons, la théorie de l'ordonnancement, que nous utilisons pour implanter nos programmes, ne s'applique le plus souvent qu'à des dates et durées dans N. Par conséquent, nous nous restreignons à des étiquettes dans N et nous considérons que les paramètres k utilisés dans les divisions périodiques d'horloges ou les multiplications périodiques d'horloges doivent être des éléments de N * . Le paramètre k utilisé dans les décalages de phase peut par contre être un élément de Q. Dans la suite de ce mémoire, nous ne considérerons que les horloges strictement périodiques entières que nous qualifierons simplement d'horloges strictement périodiques.

Dans l'ensemble restreint des horloges strictement périodiques entières, l'horloge (n, p) n'est une horloge valide qu'à la condition suivante : n ∈ N + * , n * p ∈ N + . En effet, les horloges ne vérifiant pas cette propriété font référence à des dates n'étant pas dans N. Cet ensemble restreint n'est plus clos par les opérations * . et → . (mais reste clos par / . ). Notons k|k lorsque k divise k (le reste de la division entière est 0). Tout d'abord, soit (n, p) une horloge strictement périodique, si k|n alors (n, p) * . k est une horloge strictement périodique valide, sinon elle n'est pas valide, car elle fait référence à des dates qui ne sont pas dans N. Ensuite, si (p + k)n ∈ N + alors (n, p) → . k est une horloge strictement périodique valide, sinon elle n'est pas valide, car elle fait référence à des dates négatives. Le calcul d'horloges, présenté Sect. 4, vérifie que de telles horloges invalides ne sont pas utilisées dans un programme.

Horloges strictement périodiques et horloges booléennes

Les horloges strictement périodiques et les transformations associées ne sont pas définies dans le but de remplacer les horloges booléennes classiques des langages synchrones. Notre langage se base d'ailleurs sur ces deux classes d'horloges car elles correspondent à des notions complémentaires : les horloges strictement périodiques définissent la fréquence temps réel d'un flot, tandis que les horloges booléennes définissent la condition d'activation d'un flot.

Afin d'ajouter les horloges booléennes dans notre langage, nous adaptons la définition de l'opérateur d'horloge booléen on de [START_REF] Colaço | Clocks as first class abstract types[END_REF] à notre modèle. Le comportement de cet opérateur est défini inductivement sur la séquence des étiquettes d'une horloge. Le terme t.s représente l'horloge dont la tête est l'étiquette t et dont la queue est la séquence s. De même, le terme (v, t).s représente le flot dont la tête a la valeur v et l'étiquette t et dont la queue est la séquence s. Pour toute horloge s, pour tout flot booléen c, α on c est l'horloge définie inductivement de la manière suivante :

(t.ck) on ((true, t).c) = t.(ck on c) (t.ck) on ((f alse, t).c) = ck on c
L'opération α on c produit une nouvelle horloge contenant uniquement les étiquettes de α pour lesquelles c vaut vrai. L'opération inverse α on not c ne conserve que les étiquettes pour lesquelles c vaut faux. Les opérations α on c ou α on not c ne sont valides que si c a pour horloge α. Le chapitre suivant illustre la manière dont les horloges booléennes et les horloges strictement périodiques peuvent être combinées dans un même programme.

Présentation informelle du langage

Ce chapitre présente le langage de manière informelle. Celui-ci est construit comme une extension de LUSTRE et emprunte certaines constructions à LUCID SYNCHRONE [START_REF] Pouzet | Lucid Synchrone, version 3. Tutorial and reference manual[END_REF] et aux versions récentes de SCADE. Il étend les langages synchrones flots de données avec des primitives temps réel haut-niveau basées sur les horloges strictement périodiques. La sémantique formelle du langage est disponible dans la partie anglaise du mémoire. node inst(i:int; j:bool) returns (o,p,q,r) let (o,p)=poly(i,j); (q,r)=poly(j,i); tel Le compilateur infère que o a le type int, p a le type bool, q a le type bool et r a le type int. x: before d spécifie que l'entrée x doit être acquise par le programme avant l'échéance d (ie une échéance pour la « production » de l'entrée). En pratique, cela signifie qu'une opération capteur doit s'effectuer avant cette échéance. Les opérations effectuant des opérations à partir de cette entrée pourront lire la valeur de l'entrée à partir de la sortie du capteur.

Typage polymorphe

Primitives temps réel

Les contraintes d'échéance n'ont pas d'impact sur l'horloge d'un flot et donc sur les contraintes de synchronisation entre flots. Si un flot f a pour horloge (n, p) et pour échéance d et si un flot f a pour horloge (n, p) et pour échéance d (avec d = d ), les flots sont tout de même considérés synchrones et peuvent être combinés car ils sont produits durant les même instants logiques. Les échéances ne sont prises en compte que durant la phase d'ordonnancement du programme.

Opérateurs de transition de rythme

Nous définissons des opérateurs de transition de rythme, basés sur les transformations périodiques d'horloges, servant à gérer les transitions entre flots de rythmes différents. Ces opérateurs permettent de définir des motifs de communication entre noeuds de rythmes différents. Notre objectif est de fournir un ensemble réduit d'opérateur simples, qui peuvent être combinés afin de produire des motifs de communication complexes. La sémantique de ces opérateurs est définie formellement et leur comportement est parfaitement déterministe.

Tout d'abord, il est possible de sous-échantillonner un flot à l'aide de l'opérateur /ˆ: node under_sample(i: int rate (5,0)) returns (o: int rate (10,0)) let o=i/^2; tel e/ˆk ne conserve que la première valeur parmi k valeurs successives de e. Si e a pour horloge α, alors e/ˆk a pour horloge α/ . k. Ce comportement est illustré Fig. 11.

A l'inverse, il est possible de sur-échantillonner un flot à l'aide de l'opérateur * ˆ: xxxiv date 0 5 10 15 20 25 30 ...

i i 0 i 1 i 2 i 3 i 4 i 5 i 6 ... o i 0 i 2 i 4 i 6 ...
i i 0 i 1 i 2 i 3 ... o i 0 i 0 i 1 i 1 i 2 i 2 i 3 ...

FIG. 12 -Sur-échantillonnage périodique

Opérateurs de décalage de phase

Nous définissons trois opérateurs basés sur les décalages de phase. L'ensemble des valeurs d'un flot peut être décalée d'une fraction de sa période à l'aide de l'opérateur ∼> : node offset(i: int rate (10,0)) returns (o: int rate (10,1/2)) let o=i~>1/2; tel Si e a pour horloge α, e ∼> q retarde chaque valeur de e de q * π(α) (avec q ∈ Q + ). L'horloge de e ∼> q est α → . q. Cet opérateur est le plus souvent utilisé avec q inférieur à 1, mais les valeurs supérieures à 1 sont autorisées. Le comportement de cet opérateur est illustré Fig. 13. date 0 5 10 15 20 25 30 ...

i i 0 i 1 i 2 i 3 ... o i 0 i 1 i 2 ...

Présentation informelle du langage

Ce programme spécifie simplement que le rythme de o est deux fois plus lent que celui de i, quel que soit le rythme de i. Le véritable rythme des flots ne sera calculé qu'à l'instanciation du noeud. Le noeud peut d'ailleurs être instancié à différents rythmes dans un même programme, par exemple : node poly(i: int rate (10, 0); j: int rate (5, 0)) returns (o, p: int) let o=under_sample(i); p=under_sample(j); tel L'horloge inférée pour o est (20, 0), tandis que l'horloge inférée pour p est (10, 0). Ce polymorphisme d'horloges augmente le niveau de modularité avec lequel un système peut être programmé.

Capteurs et actionneurs

Le développeur doit spécifier le wcet de l'acquisition de chaque entrée du système (chaque entrée du noeud principal), appelée opération capteur, et le wcet de la production de chaque sortie du système (chaque sortie du noeud principal), appelée opération actionneur. Par exemple, le programme suivant spécifie que l'acquisition de i prend 1 unité de temps, l'acquisition de j prend 2 unités de temps et la production de o prend 2 unités de temps : imported node A(i, j: int) returns (o: int) wcet 3; sensor i wcet 1; sensor j wcet 2; actuator o wcet 2; node N(i, j: int rate (10, 0)) returns (o: int rate (10, 0)) let o=A(i, j); tel

Retards

Un flot peut être retardé à l'aide de l'opérateur fby défini dans LUCID SYNCHRONE : node delay(i: int) returns (o: int) let o=0 fby i; tel cst fby e produit tout d'abord cst puis les valeurs de e chacune retardée d'une période de e. L'horloge de cst fby e est égale à celle de e. Le comportement de cet opérateur est illustré Fig. 16. On peut noter que cst fby e est strictement équivalent à cst :: (e ∼> 1) et peut être considéré comme du sucre syntaxique. Le processus de compilation ne tire cependant pas profit de cette possible réécriture car elle ne permet pas des simplifications significatives.

i i 0 i 1 i 2 i 3 i 4 i 5 i 6 ... o 0 i 0 i 1 i 2 i 3 i 4 i 5 ...

FIG. 16 -Retarder un flot xxxvii

Conditions d'activation Opérateurs d'échantillonnage booléens

Un flot peut être sous-échantillonné suivant une condition booléenne à l'aide de l'opérateur classique when présenté précédement. Si e a pour horloge α, alors e when c a pour horloge α on c.

Un flot peut être sur-échantillonné suivant une condition booléenne à l'aide de l'opérateur merge, défini dans LUCID SYNCHRONE. L'opérateur merge combine des flots sur des horloges booléennes complémentaires pour produire un flot plus rapide. node boolean_clocks(c: bool;i, j: int) returns (o: int) let o=merge(c, i when c, j whennot c); tel merge (c, e 1 , e 2 ) produit la valeur de e 1 lorsque c vaut vrai puis la valeur de e 2 lorsque c vaut faux. Si c a pour horloge α, alors merge (c, e 1 , e 2 ) a aussi pour horloge α. Le comportement de ce programme est décrit Fig. 17.

i i 0 i 1 i 2 i 3 i 4 i 5 i 6 ... j j 0 j 1 j 2 j 3 j 4 j 5 j 6 ... c
true true false true false false true ...

i when c i 0 i 1 i 3 i 6 ... j whennot c j 2 j 4 j 5 ... o i 0 i 1 j 2 i 3 j 4 j 5 i 6 ...
i i 0 i 1 i 2 ... i * ^2 i 0 i 0 i 1 i 1 i 2 i 2 ... o=(i * ^2) when c i 0 i 0 i 2 ...

Calcul d'horloges

Le langage étant destiné à des systèmes embarqués critiques, un accent particulier est porté sur la vérification de la correction du programme à compiler. Une série d'analyses statiques est effectuée avant de compiler un programme vers du code bas-niveau. Ces analyses permettent d'assurer que la sémantique du programme à compiler est bien définie. Chaque analyse statique assure une propriété de correction différente du programme. L'analyse de causalité vérifie qu'il existe un ordre d'exécution pour l'évaluation des expressions du programme respectant toutes les dépendances de données du programme (c'est-à-dire que les dépendances de données ne sont pas cycliques). Le typage vérifie que seuls des flots de mêmes types sont combinés. Le calcul d'horloge vérifie que seuls des flots synchrones sont combinés. Lorsque qu'un programme passe toutes les analyses statiques, sa sémantique est bien définie. Le typage et l'analyse de causalité du langage sont relativement standards, nous ne détaillons donc ici que le calcul d'horloge (plus de détails sont disponibles dans la partie anglaise du mémoire).

Le calcul d'horloges vérifie qu'un programme ne combine que des flots de même horloge. Lorsque deux flots ont la même horloge, ils sont synchrones (toujours présents aux mêmes instants). Combiner des flots non-synchrones produit des programmes non-déterministes accédant à des valeurs non-définies. On dira qu'une expression est bien-synchronisée si elle ne combine que des flots de même horloge, malsynchronisée sinon. L'objectif du calcul d'horloges est de vérifier qu'un programme n'utilise que des expressions bien-synchronisées.

Pour chaque expression du programme, le calcul d'horloges produit des contraintes devant être respectées pour que l'expression soit bien synchronisée. Comme cela a été montré dans [START_REF] Colaço | Clocks as first class abstract types[END_REF], le calcul d'horloges peut utiliser un système de types pour générer ces contraintes. La structure globale du calcul d'horloges est donc très similaire à celle d'un système de type. Les horloges sont représentées par des types appelés types d'horloges. Des règles d'inférence permettent de représenter les contraintes que les horloges d'une expression doivent respecter pour être bien synchronisée. L'unification d'horloges tente ensuite de résoudre l'ensemble des contraintes générées pour un programme. Si une solution existe, le programme est bien-synchronisé, sinon il est mal-synchronisé.

Types d'horloges

Le calcul d'horloges produit des jugements de la forme H e : cl, signifiant « l'expression e a l'horloge cl dans l'environnement H ». La grammaire des types d'horloges est définie ci-dessous :

σ ::= ∀α 1 <: C 1 , ..., α m <: C m .cl cl ::= cl → cl | cl × cl | ck | c : ck ck ::= ck on c | ck on not c | pck pck ::= (n, p) | α | pck * . k | pck/ . k | pck → . q c ::= nm | X H ::= [x 1 : σ 1 , .
inst(∀α <: C.cl) = cl[(cl ∈ C)/α] gen H (cl) = ∀α 1 <: C 1 , ..., α m <: C m .cl, avec α 1 , ..., α m = F T V (cl)\F T V (H)
Un schéma d'horloges est instancié en remplaçant ses variables d'horloges par des horloges appartenant aux bons sous-types d'horloges. Une variable d'horloge peut être généralisée si elle n'apparaît pas libre dans l'environnement.

Une variable d'horloge α peut être contrainte à appartenir à un certain sous-type d'horloges C i , ce qui est noté α <: C i . Une telle contrainte signifie que seules des horloges appartenant à C i peuvent être substituées à α. Par exemple, si x a pour horloge α, alors x * ˆk n'est bien synchronisée que si k|π(α), sinon la période de x * ˆk n'est pas entière. Ces contraintes correspondent à la quantification de types bornés de [START_REF] Pierce | Types and programming languages[END_REF].

Soit C l'ensemble de tous les types d'horloges. Les deux principaux sous-types de C sont les horloges booléennes B (les horloges contenant l'opérateur on) et les horloges strictement périodiques P. Le type d'horloge P est ensuite séparé en sous-types P k, a b , k ∈ N * , a b ∈ Q + , avec a ∧ b = 1 (a et b sont premiers entre eux) et b|k, avec :

P k, a b = {(n, p)|(k|n) ∧ p ≥ a b }
En d'autres termes, l'ensemble P k, a b contient toutes les horloges strictement périodiques α pour lesquelles α * . k et α → . -a b produisent des horloges strictement périodiques valides. On note que P = P 1,0 . La relation de sous-typage <: sur les types d'horloges se définit comme suite : -B <: C, P <: C ; -P k,q <: P k ,q ⇔ k |k ∧ q ≥ q .

Inférence d'horloges

Parent strictement périodique Notre objectif étant d'ordonnancer un programme de manière efficace, il est nécessaire de calculer la période de chaque flot du programme. Lorsque l'horloge d'un flot contient des transformations booléennes, il faut donc approximer l'horloge à une horloge strictement périodique pour pouvoir calculer la période du flot. Par conséquent, nous définissons la sur-approximation d'une horloge à son plus proche parent strictement périodique de la manière suivante :

Règles d'inférence

Les contraintes à vérifier pour qu'une expression soit bien synchronisée sont exprimées à l'aide de règles d'inférence. Une règle -

∀k, k ∈ N + * , α * . k * . k = α * . kk ; -∀k, k ∈ N + * , α/ . k/ . k = α/ . kk ; -∀q, q ∈ Q, α → . q → . q = α → . (q + q ) ; -∀k, k ∈ N + * , α/ . k * . k = α * . k / . k ; -∀q ∈ Q, ∀k ∈ N + * , α → . q * . k = α * . k → . qk ; -∀q ∈ Q, ∀k ∈ N + * , α → . q/ . k = α/ . k → . (q/k).
On définit ensuite un système de réécriture R P permettant de simplifier les expressions de E P basé sur ces propriétés : Définition 7. Le système de réécriture R P sur les expressions de E P se définit comme suit :

α * . k * . k → α * . kk (1) α/ . k/ . k → α/ . kk (2) α → . q → . q → α → . (q + q ) (3) α/ . k * . k → α * . k / . k (4) α → . q * . k → α * . k → . qk (5) α → . q/ . k → α/ . k → . (q/k) (6)
Propriété 5. Le système R P est convergent.

R P étant convergent, toute expression e de E P possède une unique forme normale, notée N F R P (e).

De plus :

Lemme 1. Pour toute expression e ∈ E P , N F R P (e) est de la forme :

N F R P (e) = α * . k/ . k → . q
avec k, k ∈ N and q ∈ Q Dans certains cas, il est possible de simplifier à nouveau la forme normale d'une expression d'horloge strictement périodique. On définit la forme canonique d'une expression d'horloge strictement périodique à partir de la propriété suivante : Propriété 6. Pour tout m, k, k dans N * , pour tout q dans Q, si m|k et m|k , alors :

α * . k/ . k → . q = α * . (k/m)/ . (k /m) → . q xlii 4. Calcul d'horloges
On en déduit la relation d'équivalence sur les expressions sous forme normale : Propriété 7. Soit e 1 , e 2 des expressions d'horloges strictement périodiques sous forme normale, avec e 1 = α * . k 0 / . k 1 → . q et e 2 = α * . k 0 / . k 1 → . q . Alors e 1 et e 2 sont équivalentes si et seulement si :

q = q ∧ k 0 * k 1 = k 1 * k 0
Définition 8. De cette relation d'équivalence, pour toute expression e = α * . k/ . k → . q, l'élément représentatif e rep = α * . k r / . k r → . q r de sa classe d'équivalence [e] est tel que :

q = q r ∧ ∃g ∈ N * , k = gk r , k = gk
CF (α * . k/ . k → . q) = α * . k gcd(k, k ) / . k gcd(k, k ) → . q
On obtient finalement le théorème suivant par construction :

Théorème 1. Soit e 1 , e 2 des expressions de E P . Alors :

(e 1 ⇔ e 2 ) ⇔ CF (N F R P (e 1 )) = CF (N F R P (e 2 ))

Règles d'unification

Pour toute expression d'horloge ck, on note concrete(ck) le prédicat retournant vrai si ck est une horloge concrète et abstract(ck) le prédicat retournant vrai si ck est une horloge abstraite. Les règles d'unification, qui reposent en partie sur le test d'équivalence défini dans la section précédente, sont données Fig. 20.

(→) cl 1 = cl 1 cl 2 = cl 2 cl 1 → cl 2 = cl 1 → cl 2 (×) cl 1 = cl 1 cl 2 = cl 2 cl 1 × cl 2 = cl 1 × cl 2 (ON) ck 1 = ck 2 c 1 = c 2 ck 1 on c 1 = ck 2 on c 2 (VAR) α ∈ F T V (pck) α <: P k,q ∩ P k ,q α = CF (N F (pck)) α <: P k,q = ck <: P k ,q (VAR') α ∈ F T V (ck) ck <: P k,q ∩ P k ,q α → N F (ck) α <: P k,q = ck <: P k ,q (CONST) concrete(pck 1 ) concrete(pck 2 ) π(pck 1 ) = π(pck 2 ) ϕ(pck 1 ) = ϕ(pck 2 ) pck 1 =

Subsomption

A plusieurs endroits, les règles d'inférence et d'unification nécessitent de vérifier qu'une expression d'horloge appartient à un certain sous-type. Afin de vérifier que ck est un sous-type de C i (ie que ck subsume C i ), on utilise les propriétés suivantes : Propriété 8. ∀α ∈ P, ∀k, k ∈ N + * , ∀q, q ∈ Q :

α * . k <: P k,q ⇔ α <: P k * k ,q/k ; -α/ . k <: P k,q ⇔ α <: P k/gcd(k,k ),q * k ; -α → . q <: P k,q ⇔ α <: P k,max(0,(q-q )) ; -α <: P k,q ∧ α <: P k ,q ⇔ α <: P k,q ∩ P k ,q ⇔ α <: P lcm(k,k ),max(q,q ) .

Lues de gauche à droite, ces propriétés forment des règles de subsomption pouvant être appliquées pour transférer des contraintes de sous-typage sur un type d'horloge vers la variable d'horloge apparaissant dans ce type (pour une horloge abstraite) ou vers l'horloge constante apparaissant dans ce type (pour une horloge abstraite). Dans le premier cas, la contrainte est simplement ajoutée aux contraintes portant déjà sur cette variable. Dans le second cas, on peut directement vérifier que l'horloge contrainte satisfait cette contrainte.

Nous allons maintenant expliquer comment, une fois les analyses statiques réalisées, un programme du langage est compilé en un programme bas-niveau.

Mode de compilation

Nous présentons ici le mode de compilation d'un programme vers un code bas-niveau. Ce code pourra ensuite être traduit en un code exécutable à l'aide d'un compilateur existant. Le processus de compilation est défini formellement, ce qui assure que le code généré se comporte comme spécifié par sa sémantique formelle.

Limitation de la génération de code classique en « boucle simple »

Comme pour les autres langages synchrones, notre compilateur traduit le programme d'entrée en un code de niveau intermédiaire (code C). Cependant, le mode de compilation diffère de la compilation classique de code séquentiel en « boucle simple », afin de mieux gérer les systèmes multi-périodiques. Considérons l'exemple ci-dessous :

imported

Génération de code multi-tâche

Si les opérations A et B sont implémentées comme deux tâches séparées et exécutées de manière concurrente par un système d'exploitation temps réel (c'est-à-dire avec préemption), il est possible de trouver un ordonnancement pour l'exécution des tâches respectant les contraintes de périodicité des deux opérations.

Chaque opération (chaque appel de noeud importé) est traduit en une tâche τ i , possédant un ensemble d'attributs temps réel (T i , C i , r i , d i ). τ i est instanciée périodiquement avec la période T i , τ i [j] représente la j ème itération de la tâche τ i . Une instance de tâche ne peut pas commencer son exécution avant que tous ses prédécesseurs, définis par les dépendances de données du programme, aient terminé leur exécution. C i est le wcet de la tâche. r i est la date de réveil de la première instance de la tâche. Les dates de réveil des instances suivantes sont r i +T i , r i +2T i , etc. d i est l'échéance relative de la tâche. L'échéance absolue D i [j] de l'instance j de la tâche τ i est égale à la date de réveil R i [j] de cette instance plus l'échéance relative de la tâche :

D i [j] = R i [j] + d i .
Ces définitions sont illustrées Fig. 22.

r i , R i[0] C i D i[0] d i R i[1] C i D i[1] d i R i[2] 0 P i P i FIG.
22 -Caractéristiques temps réel de τ i Notre exemple se traduit donc en deux tâches τ A d'attributs (3, 1, 0, 3) et τ B d'attributs (9, 5, 0, 9), avec une précédence de τ A vers τ B . A l'aide de préemptions, il est ensuite possible de trouver un ordonnancement respectant les contraintes des deux tâches, comme indiqué Fig. 23 (les croix sur l'axe de temps de B représentent les préemptions/restaurations de tâches). Une fois les analyses statiques terminées, le compilateur traduit donc le programme en un code basniveau constitué d'un ensemble de tâches temps réel concurrentes. La première étape de la traduction consiste à extraire le graphe de tâches correspondant au programme. remplacer l'instance de noeud dans N par le tuple constitué par ses sorties 13: end for Définition Soit g = (V, E) un graphe de tâches, où V est l'ensemble des tâches du graphe et E l'ensemble des précédences du graphe (un sous-ensemble de V × V ). Les précédences sont annotées par des opérateurs prédéfinis du langage, qui décrivent le motif de communication utilisé pour communiquer. L'union de deux graphes g = (V,

Expansion de programme

E) et g = (V , E ) est défini par g ∪ g = (V ∪ V , E ∪ E ). last(g) représente l'ensemble des tâches de g n'ayant pas de successeurs, c'est-à-dire last(V, E) = {v ∈ V |¬(∃v ∈ V, v → v )}.
Chaque noeud importé du programme est traduit en une tâche. La fonction exécutée par la tâche est la fonction externe fournie pour le noeud importé. Cette fonction doit s'exécuter séquentiellement et ne pas contenir de point de synchronisation. Dans un premier temps, chaque variable du programme expansé et chaque appel à un opérateur prédéfini est aussi transformé en un sommet du graphe. Ces sommets particuliers seront ensuite réduits pour simplifier le graphe.

Une tâche doit s'exécuter après toutes les tâches produisant des données nécessaires à son exécution. Les dépendances de données entre tâches définissent ainsi des contraintes de précédence entre tâches. Ces précédentes sont déduites de la relation de dépendance entre les expressions du programme. On étend la notion de dépendance syntaxique de [START_REF] Halbwachs | Generating efficient code from data-flow programs[END_REF] à notre langage. On note e ≺ e lorsque e dépend syntaxiquement de e. Par exemple, l'expression N (e, e ) dépend de e et e . L'expression e when c dépend aussi syntaxiquement de e et c, bien qu'elle ne dépende sémantiquement de e que lorsque c vaut vrai.

La relation de dépendance syntaxique est surchargée pour les équations et définie Fig. 24.

Graphe intermédiaire

On extrait d'abord un graphe intermédiaire (contenant des variables et des opérateurs prédéfinis dans ses sommets) à partir d'un programme à l'aide de la fonction G(e) définie Fig. 25

G(cst) = (∅, ∅) G(x) = ({x}, ∅) G(N (e)) = G(

Conditions d'activation

La condition d'activation d'une tâche dépend des conditions d'activation de ses entrées/sorties. A chaque instance de tâche on teste la condition d'activation de la tâche. Si la condition est fausse, l'instance termine immédiatement, sinon elle se comporte normalement.

cond i = x∈in i cond(x)
7 Traduction de tâches dépendantes en tâches indépendantes Les tâches du graphe de tâche décrit précédemment sont dépendantes, car liées par des contraintes de précédences. Dans ce chapitre nous montrons comment traduire cet ensemble de tâches dépendantes en un ensemble de tâches indépendantes. Les précédences sont tout d'abord encodées par ajustement des caractéristiques temps réel des tâches. Un protocole de communication non bloquant basé sur des mémoires tampons assure ensuite la conservation de la sémantique formelle du programme transformé.

Encodage des précédences

Ajustement des attributs temps réel Précédences simples La technique d'encodage sur laquelle nous nous basons est définie dans [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF]. Elle consiste à modifier les dates de réveil et les échéances des tâches de manière à refléter les contraintes de précédences, puis à utiliser EDF pour ordonnancer l'ensemble des tâches encodées. Cette technique est optimale, dans le sens où s'il existe un ordonnancement à priorité dynamique respectant les contraintes de l'ensemble de tâches d'origine, alors EDF appliqué à l'ensemble encodé trouvera un ordonnancement valide. Cette technique est initialement prévue pour des tâches apériodiques reliées par des contraintes de précédence. Soit succ(τ i ) les successeurs de τ i et pred(τ i ) ses prédécesseurs. Les précédences peuvent être encodées de la manière suivante :

-Une tâche τ i doit se terminer suffisamment tôt pour que ses successeurs puissent respecter leurs échéances, donc l'échéance absolue ajustée d'une tâche D * i est : D * i = min(D i , min(D * j -C j )) (pour tout τ j ∈ succ(τ i )) ; -L'ajustement des dates de réveil proposé dans [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF] est : R * i = max(R i , max(R * j +C j )) (pour tout τ j ∈ pred(τ i )). Ceci peut se simplifier dans notre contexte en : R * i = max(R i , max(R * j )) (pour tout τ j ∈ pred(τ i )). En effet, cette condition est suffisante pour qu'EDF affecte une priorité à la source d'une précédence supérieure à celle de la destination de la précédence. Cette technique s'étend directement à des précédences entre tâches périodiques ayant la même période, que nous qualifierons de précédences simples.

Précédences étendues Dans notre cas, un graphe de tâches contient des précédences étendues entre tâches de rythmes différents. Comme illustré Fig. 29, l'encodage des précédences étendues nécessite d'ajuster les attributs d'une tâche de manière différente entre ses différentes instances. La suite de cette section étend donc la technique d'encodage au cas des précédences étendues Précédences entre instances de tâches Le principe de notre extension est que les précédences étendues correspondent à des motifs de précédences réguliers. Notre extension tient donc compte de cette régularité afin d'effectuer l'encodage de manière compacte et factorisée. l 7. Traduction de tâches dépendantes en tâches indépendantes

(a) A /ˆ2 → B (b) A * ˆ2 → B (c) A fby → B (d) A ∼>1/2 → B (e) A tail → B (f) A :: → B

FIG. 29 -Encodage des précédences étendues

Nous commençons par définir l'ensemble des précédences entre instances de tâches :

τ i ops → τ j ⇒ ∀n, τ i [n] → τ j [g ops (n)], avec g ops défini comme suit : g * ˆk.ops (n) =g ops (kn) g /ˆk.ops (n) =g ops ( n/k ) g ∼>q.ops (n) =g ops (n) g tail.ops (n) =
g ops (0) si n=0 g ops (n -1) sinon g ::.ops =g ops (n + 1) g fby.ops (n) =g ops (n + 1)

g when.ops (n) =g ops (n) g whennot.ops (n) =g ops (n) g (n) =n
Le terme r , avec r ∈ Q + , désigne le plus petit entier supérieur ou égal à r (par exemple 3 pour 5 2 ). La li fonction g ops est illustrée Fig. 30

Ajustement des dates de réveil dans le contexte synchrone

Nous montrons dans cette section que la sémantique synchrone du langage permet de ne pas ajuster les dates de réveil des tâches.

Nous définissons tout d'abord la fonction ∆ ops (n, T i , T j , r i , r j ) qui joue un rôle essentiel dans notre technique d'encodage des précédences :

Définition 12. Soit ∆ ops (n, T i , T j , r i , r j ) = g ops (n)T j -nT i + r j -r i . D'après la définition de R i [n], on a : R j [g ops (n)] = R i [n] + ∆ ops (n, T j , r j )
∆ ops représente donc la différence entre les dates de réveil de deux instances de tâches reliées par une relation de précédence. Ceci est illustré Fig. 31, où nous montrons les dates de réveil des instances lii 7. Traduction de tâches dépendantes en tâches indépendantes successives de deux tâches τ i et τ j reliées par une précédence τ i ops → τ j . On ne représente pas les cas pour lesquels ∆ ops (n, T i , T j , r i , r j ) = 0. ∆ ops (1, T j , r j ) ∆ ops (4, T j , r j ) ∆ ops (2, T j , r j ) ∆ ops (5, T j , r j )

(a) Pour τi /ˆ3
→ τj ∆ ops (0, ...) ∆ ops (1, ...) ∆ ops (2, ...) On note que la séquence des valeurs ∆ ops (n, T i , T j , r i , r j ) est ultimement périodique (elle suit un motif répétitif après un préfixe initial). Notre technique d'ajustement repose sur cette propriété essentielle qui nous permet de représenter la séquence des échéances ajustées des instances d'une tâche sous forme d'un motif ultimement périodique.

(b) Pour τi ∼>1/2 → τj ∆ ops (0, T j , r j ) (c) Pour τi tail → τj R j ∆ ops (0, ...) ∆ ops (1, ...) ∆ ops (2, ...) (d) Pour τi fby → τj R j ∆ ops (1, T j , r j ) ∆ ops (4, T j , r j ) ∆ ops (2, T j , r j ) ∆ ops (5, T j , r j ) (e)
A partir des définitions ci-dessus, il est possible de prouver la propriété suivante :

Théorème 2. Pour toute précédence τ i ops → τ j , pour tout n :

R τ j [gops(n)] ≥ R i [n]
Par conséquence :

Corollaire 1. Pour tout τ j ∈ pred(τ i ) :

R i ≥ max(R i , max(R j ))
Ainsi, l'encodage des dates de réveil des tâches est directement assuré par les contraintes de synchronisation imposées par le calcul d'horloges. Par ailleurs, on note la propriété suivante qui nous permettra d'ignorer les précédences contenant un retard dans leurs annotations durant la phase d'encodage :

Propriété 9. Pour une précédence τ i ops → τ j telle que ops contient un délai, R j [g ops (n)] ≥ R i [n] + T i .

Calcul d'échéances

Nous passons maintenant à l'encodage des échéances des tâches appelé calcul d'échéances. Le

D i [n] ≤ D j [g ops (n)] -C j ⇔ d i [n] ≤ d j [g ops (n)] + ∆ ops (n, T i , T j , r i , r j ) -C j

Traduction de tâches dépendantes en tâches indépendantes

Calcul des mots d'échéances Soit w i le mot d'échéances représentant la séquence des échéances unitaires de τ i . D'après la Propriété 10, chaque précédence τ i ops → τ j peut s'encoder par une contrainte reliant w i à w j de la forme : w i ≤ W ops (w j ) + ∆ ops (T i , T j , r i , r j ) -C j avec W ops (w j ) le mot tel que W ops (w j )[n] = w j [g ops (n)] pour tout n et ∆ ops (T i , T j , r i , r j ) le mot tel que ∆ ops (T i , T j , r i , r j )[n] = ∆ ops (n, T i , T j , r i , r j ) pour tout n.

Il est possible de montrer à partir des définitions de ∆ ops et g ops que ces deux mots sont bien ultimement périodiques. Ils peuvent donc simplement être calculés en calculant leur valeur en chaque point de leur préfixe et de leur motif répétitif. La valeur de w i se calcule ensuite comme la combinaison de toutes les contraintes le concernant :

w i = min(w i , min(∆ ops (T j , r j ) + W ops (w j ) -C j ))(pour tout τ j , τ i ops → τ j )
On en déduit l'Alg. 2 permettant de calculer les mots d'échéances d'un graphe de tâches. Les précédences contenant un délai sont ignorées dans le cadre de cet algorithme.

Algorithm 2 Calcul des mots d'échéances d'un graphe de tâches 1: for Tout τ i do 2:

if τ i est un capteur ou un actionneur possédant une contrainte d'échéance then 3:

w i ← (n) ω 4: else w i ← (T i ) ω 5:
end if 6: end for 7: S ← Liste des tâches sans successeurs 8: while S n'est pas vide do

∀n, n , τ i [n] → τ j [n ] ⇒ w i [n] ≤ W ops (w j )[n] + ∆ ops (w j , r j )[n] -C j ∧ n = g ops (n)
Ceci, combiné aux propriétés démontrées dans [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF], nous permet d'obtenir le théorème suivant

Théorème 3. Soit g = ({τ i (T i , C i , r i , d i ), 1 ≤ i ≤ n}, E) un graphe de tâches réduit. Soit ensuite s = {τ i (T i , C i , r i , w i ), 1 ≤ i ≤ n}
un ensemble de tâches tel que pour tout τ i , w i est le mot d'échéances calculé par l'Alg. 2 pour τ i . Alors : g est ordonnançable si et seulement si s est ordonnançable par EDF.

Analyse d'ordonnançabilité

En ce qui concerne l'analyse d'ordonnançabilité, étant donné que le graphe de tâches est ordonnançable si et seulement si l'ensemble encodé est ordonnançable par EDF, nous pouvons réutiliser les résultats existants pour EDF. On cherche donc un test d'ordonnançabilité pour un ensemble de tâches périodiques, ayant des dates de réveil différentes de 0, des échéances inférieures à leur période, ordonnancé par EDF. [START_REF] Leung | A note on preemptive scheduling of periodic, real-time tasks[END_REF] donne une condition nécessaire et suffisante pour ce problème qui consiste à calculer l'ordonnancement du système sur l'intervalle de temps [0, 2HP + max(r i )] (où HP désigne l'hyperpériode du système) et vérifier que toutes les contraintes temps réel sont satisfaites sur cet intervalle.

Communications inter-tâches

Dans cette section nous proposons un protocole de communication assurant que les communications inter-tâches respectent la sémantique synchrone du programme.

Assurer la consistance des communications

Un mécanisme de communication simple à mettre en place consiste à allouer une mémoire tampon dans un espace mémoire global, le producteur de la donnée écrit dans ce tampon lors de son exécution et le consommateur de la donnée lit à partir de ce même tampon lors de son exécution. Afin d'assurer le respect de la sémantique synchrone, il faut assurer pour chaque dépendance que :

1. Le producteur écrit avant que le consommateur lise.

2. Etant donné que les tâches sont périodiques il faut aussi assurer que l'instance courante du consommateur lit avant que l'instance suivante du producteur recouvre la donnée produite par son instance précédente.

L'encodage des précédences présenté dans la section précédente assure le respect du premier point. Cependant il n'assure pas le second point. En effet, pour une précédence τ i ops → τ j , la donnée produite par τ i [n] peut éventuellement être consommée par τ j [g ops (n)] après le début de τ i [n + 1]. Ceci est illustré Fig. 32, où l'on montre l'ordonnancement de deux tâches reliées par des précédences étendues. Les croix sur les axes de temps représentent des préemptions. Une flèche de A à la date t vers B à la date t signifie que la tâche B peut lire à la date t la valeur produite par A à la date t.

A A B B (a) A /ˆ2 → B A A B B B B (b) A * ˆ2 → B A A B B (c) A fby → B A A B B B CC C C C C (d) A ∼>1/2 → B, A * ˆ4 → C A A A B B (e) A tail → B A A B B B (f) A :: → B

FIG. 32 -Communications pour des précédences étendues lvi

Traduction de tâches dépendantes en tâches indépendantes

Communications entre instances de tâches Définition Les dépendances de données entre instances de tâches peuvent être définies comme suit :

Propriété 11. Notons τ i [n] → d τ j [n ] une dépendance de donnée de τ i [n] vers τ j [n ]. Pour toute précé- dence τ i ops → τ j : τ i [n] → d τ j [n ] ⇔ n ∈ [g ops (n), g ops (n + 1)[ D'après cette définition, τ i [n] peut donc :
-Ne pas être consommée du tout : lorsque g ops (n + 1) = g ops (n) ; -Etre consommée exactement une fois : lorsque g ops (n + 1) = g ops (n) + 1 ; -Etre consommée n fois : lorsque g ops (n + 1) = g ops (n) + n.

Mots de dépendances Tout comme pour les mots d'échéances, les dépendances de données peuvent se représenter sous forme de motifs répétitifs. On définit conso ops (n) = g ops (n + 1) -g ops (n) pour tout n ∈ N. Pour toute précédence, τ i ops → τ j , conso ops (n) représente le nombre d'instances de τ j consommant la donnée produite par τ i [n] (éventuellement 0). La séquence des valeurs de conso ops peut être représentée par des motifs répétitifs appelés mots de dépendances, définis de manière similaire aux mots d'échéances :

w ::= u.(u) ω u ::= i | i.u
A nouveau, d'après la définition de g ops on peut prouver que conso ops est ultimement périodique et peut donc être représenté sous forme de mot de dépendances.

Durées de vie La durée de vie d'une donnée produite par une instance de tâche est l'intervalle de temps séparant sa date de production de sa date de consommation. Plus formellement : Définition 13. Pour toute précédence τ i ops → τ j , pour tout n, la durée de vie de τ i [n] est :

sp i,j (n) = [R i [n], R i [n][ si conso ops (n) = 0 [R i [n], R j [g ops (n)] + conso ops (n) * T j [ sinon Pour un intervalle I = [a, b[, on note |I| = b -
a la taille de I. On peut de nouveau prouver que la séquence des valeurs |sp i,j (n)| est ultimement périodique.

Protocole de communication

Nous proposons un protocole de communication basé sur les mots de dépendances, qui assure que les entrées d'une tâche restent disponibles jusqu'à l'échéance de la tâche, afin de satisfaire l'exigence 2. de la section précédente. Ce protocole ne nécessite pas de primitives de synchronisation bloquantes (telles que des sémaphores) et repose sur des copies dans des mémoires tampons. Le protocole assure le respect de la sémantique du langage.

Un tampon de communication est alloué pour chaque précédence τ i ops → τ j . Les mots de dépendances permettent de savoir quelles instances de τ i écrivent dans le tampon et quelles instances de τ j lisent la valeur correspondante. Les durées de vie permettent de déterminer quelles instances peuvent écrire dans la même case du tampon.

Nombre de cases de la mémoire de communication Soit cell i [n] la case du tampon dans laquelle τ i [n] écrit. Le nombre de cases du tampon doit être tel que pour tout n, n tels que sp i,j (n

) et sp i,j (n ) ont une intersection, cell i [n] et cell i [n ] peuvent être choisis de manière à ce que cell i [n] = cell i [n ].
Pour une durée de vie donnée, il est possible de borner le nombre de données dont les durées de vie ont une intersection avec celle-ci : Propriété 12. Pour tout τ i ops → τ j , pour tout n :

|{sp i,j (n ), sp i,j (n ) ∩ sp i,j (n) = {}}| ≤ sp i,j (n)/T i
La taille du tampon est donc déterminée en parcourant le mot ultimement périodique formé par |sp i,j (n)| et en prenant le nombre d'intersections maximum d'une durée de vie avec les autres durées de vie.

Le protocole Nous définissons maintenant le protocole de communication pour une précédence étendue τ i ops → τ j . Soit dep i,j le mot de dépendances de cette précédence. Soit buf i,j la mémoire de communication de cette précédence. On commence par calculer quelles instances de τ i doivent écrire dans buf i,j à l'aide de l'Alg. 3. Le protocole de communication pour le producteur d'une donnée est ensuite décrit Alg. 4.

Algorithm 3 Calcul des instances du producteur devant écrire, pour τ i ops → τ j 1: write ← ; 2: for k = 0 to max(0, pref(ops)) + P (ops) -1 do 3:

if dep i,j [k] ≥ 1 then 4:
write ← write.true

if write[inst] then 4: Ecrire dans buf i,j [cell] 5: cell ← (cell + 1) mod |buf i,j | 6: end if 7: inst ← inst + 1 8: end while
On calcule pour chaque instance du consommateur la case où elle doit lire à l'aide de l'Alg. 5. Le protocole de communication du consommateur est décrit Alg. 6.

Exemple

Ce protocole de communication est illustré sur l'exemple de la Fig. 33 (les flèches représentent des dépendances de données). Tout d'abord, pour la précédence τ i * ˆ3./ˆ2 → τ j , avec T i = 3, T j = 2 : lviii

Traduction de tâches dépendantes en tâches indépendantes

Algorithm 5 Calcul des motifs de lecture τ i ops → τ j 1: change_read ← ; 2: for k = 0 à max(0, pref(ops)) + P (ops) -1 do 3:

if dep i,j [k] ≥ 1 then 4: for i = 0 à dep i,j [k] -2 do
if change[inst] then 5: cell ← (cell + 1) mod |buf i,j | 6: end if 7: inst ← inst + 1 8: end while -dep i,j (n) = (2.1) ω ; -sp i,j (n) = [0, 4], [3, 6], [6, 10], [9, 12], . . . ; -|buf i,j | = 2 ; -write = (true) ω ; -change = (f alse.true.true) ω . Donc :
-La séquence des cases dans lesquelles τ i écrit est : 0, 1, 0, 1, 0, 1, . . .

-La séquence des cases à partir desquelles τ j lit est : 0, 0, 1, 0, 0, 1, . . .

Ensuite, pour la précédence τ

i * ˆ2./ˆ3 → τ j , avec T i = 2, T j = 3 : -dep i,j (n) = (1.1.0) ω ; -sp i,j (n) = [0, 3[, [2, 6[, [4, 4[, [6, 9[, [8, 12[, . . . ; -|buf i,j | = 2 ; -write = (true.true.f alse) ω ; -change = (true) ω . Donc :
-La séquence des cases dans lesquelles τ i écrit est : 0, 1, none, 0, 1, none, . . . -La séquence des cases à partir desquelles τ j lit est : 0, 1, 0, 1, 0, 1, . . .

Enfin, pour la précédence τ

i ∼>2 → τ j , avec T i = T j = 2 : -dep i,j (n) = (1) ω ; -sp i,j (n) = [0, 6[, [2, 8[, [4, 10[, [6, 12[, [8, 14[, . . . ; -|buf i,j | = 3 ; -write = (true) ω ; -change = (true) ω . Donc :
-La séquence des cases dans lesquelles τ i écrit est : 0, 1, 2, 0, 1, 2, . . .

lix

Résumé étendu

-La séquence des cases à partir desquelles τ j lit est : 0, 1, 2, 0, 1, 2, . . .

sp i,j (0) sp i,j (2)
sp i,j (1) sp i,j (3) (a) Pour τi * ˆ3./ˆ2 → τj sp i,j (0) sp i,j (3)
sp i,j (1) sp i,j (4) (b) Pour τi * ˆ2./ˆ3 → τj sp i,j (0)
sp i,j (1)
sp i,j (2) Une fois que l'ensemble des tâches indépendantes correspondant à un programme est généré, il est ensuite très simple de le traduire en code C. Un prototype du compilateur a été implanté en OCAML. Il génère un code C utilisant les extensions temps réel de POSIX [POS98]. Le programme généré consiste en un ensemble de threads communicant. Chaque thread est constitué d'une boucle infinie qui empaquette le code de la fonction du noeud importé correspondant avec le protocole de communication défini ci-dessus. La politique EDF modifiée afin de supporter les mot d'échéances à été implantée à l'aide de MARTE OS [START_REF] Rivas | POSIX-Compatible Application-Defined Scheduling in MaRTE OS[END_REF]. Une étude de cas réaliste, basée sur une version étendue du FAS décrit en introduction, a été implantée et a permis de valider le langage et son compilateur.

sp i,j (3)

Conclusion

Nous avons proposé un langage pour la programmation de systèmes de contrôle-commande. Il se présente comme un langage d'architecture logicielle temps réel possédant une sémantique synchrone. lx

Conclusion

Il fournit un haut niveau d'abstraction et une sémantique formelle, ce qui permet de spécifier de manière non ambiguë l'intégration de plusieurs systèmes synchrones localement périodiques en un système synchrone global multi-périodique.

Le langage repose sur une classe particulière d'horloges, les horloges strictement périodiques, qui permettent de relier le temps logique (la séquence d'instants d'un programme) au temps réel. Une horloge strictement périodique se caractérise de manière unique par sa période et par sa phase et définit le rythme temps réel d'un flot. Les transformations périodiques d'horloges permettent de créer de nouvelles horloges strictement périodiques à partir d'horloges strictement périodiques existantes, soit en accélérant l'horloge, soit en la ralentissant, soit en appliquant un décalage de phase. Le langage définit des primitives temps réel basées sur les horloges strictement périodiques et les transformations périodiques d'horloges. Ces primitives permettent de spécifier des contraintes de périodicité, des contraintes d'échéance et des opérateurs de transition de rythme, afin de définir de manière précise des motifs de communication entre opérations de rythmes différents. Un programme consiste ensuite en un ensemble de noeuds importés (implantés à l'extérieur du programme) assemblés à l'aide d'opérations de transition de rythme. La sémantique du langage est définie formellement. Les analyses statiques vérifient que la sémantique du programme est bien définie avant de le traduire vers un code bas niveau.

Lorsqu'un programme est accepté par les analyses statiques, sa sémantique est bien définie. Il est alors traduit en un ensemble de tâches temps réel concurrentes et communicantes, programmé en C. La première étape de la traduction consiste à extraire un ensemble de tâches temps réel à partir du programme, les tâches étant reliées par des contraintes de précédence dûes aux dépendances de données. Les précédences sont ensuite encodées dans les attributs temps réel des tâches, en ajustant les échéances des tâches reliées par des contraintes de précédence. Lorsqu'une précédence relie deux tâches de rythmes différents, elle ne relie pas toutes les instances des deux tâches et il est donc nécessaire d'affecter différentes échéances pour différentes instances d'une même tâche. L'encodage repose alors sur l'utilisation de mots d'échéances, un mot d'échéances définissant la séquence des échéances d'une tâche sous la forme d'un motif répétitif fini. L'ensemble de tâches obtenu après ajustement des échéances est ordonnancé à l'aide de la politique EDF. Cette technique d'ordonnancement (encodage + EDF) est optimale. Un protocole de communication inter-tâches est ensuite défini afin d'assurer le respect de la sémantique formelle du programme d'origine. Ce protocole est basé sur des mécanismes de mémoire tampons et ne nécessite pas de primitives de synchronisations (telles que des sémaphores).

Introduction

The work presented in this dissertation was funded by EADS Astrium Space Transportation.

Reactive Systems

A reactive system [HP85] is a particular type of embedded system that maintains permanent interaction with its physical environment. The system waits for stimuli, then performs computations and finally produces outputs (commands) as a reaction to the input stimuli. Control systems, monitoring systems or signal processing systems are classical examples of reactive systems.

A distinctive characteristic of reactive systems is that the response time of the system must respect real-time constraints induced by the environment. If the response comes too late the system may miss environment inputs and respond to inputs that may not correspond to the current state of the environment. In addition, reactive systems are often highly critical (e.g. aircraft piloting system, nuclear plant control system) and faults cannot be tolerated.

Event-driven systems vs sampled systems

Reactive systems can be separated into two sub-classes: event-driven systems and sampled systems. The difference stands in the way inputs are acquired. In an event-driven system (sometimes referred to as purely reactive system), the system waits for an input event to occur and then computes its reaction. On the opposite, a sampled system acquires its inputs at regular intervals of time and computes its reaction for each sample of its inputs (possibly doing only part of the computations if only part of the inputs are present for the current sample). While event-driven systems correspond to a larger class of systems, sampled systems are usually easier to analyze and their behaviour is more predictable, especially concerning the real-time aspects. In the following we only consider sampled systems.

Embedded control systems

Our work is specifically targeted for embedded control systems. By embedded control systems, we mean linear feedback systems, that is to say control loops including sensors, control algorithms and actuators that regulate the state of the system in its environment. Spacecraft and aircraft flight control systems are good examples of control systems and their study originally motivated our work. The aim of these systems is to control the position, speed and attitude of the vehicle thanks to physical devices, such as control surfaces for an aircraft or thrusters for a spacecraft. The system operates in a closed loop with its environment as commands applied to the vehicle modify its state (position, speed and attitude), which modifies the inputs of the system and require to compute new commands and so on.

The computations performed by such systems are highly regular: for instance, there is no dynamic creation of processes and only statically bounded loops. This enables the production of optimized software and hardware, the behaviour of which is predictable, an essential feature for highly critical system.

Control systems must respect several real-time constraints related to physical constraints. First, the control rate of a device (and thus the rate of the operations executed to control this device) must be higher than a minimal frequency, related to the inertia and physical characteristics of the vehicle. Under this frequency, the safety of the vehicle is not ensured. The minimal frequencies are not the same for all the physical devices of the controlled system, for instance the propulsion devices of a space vehicle must be controlled very fast to ensure the stability of the vehicle, while the position of its solar panels can be controlled a little slower as a little energy loss has less impact on the vehicle. Second, the control rate of a device must be lower than a maximal frequency requirement, over which the device will not be able to apply the commands fast enough or may get damaged. This parameter again changes from one device to another. The actual control rate of a device is usually chosen as close to the minimal frequency as possible, in order to spare unnecessary computations and to enable the use of less powerful hardware (which is less expensive, consumes less energy, ...).

As for any reactive system, the response time of a control system is bounded. The delay between an observation (inputs) and the corresponding reaction (outputs) must respect a maximum latency, ie must be lower than a deadline relative to the date of the observation. The deadline of an output is not necessarily equal to its period, it can be smaller, so as to follow closely the corresponding inputs.

Case study: The Flight Application Software

In this section we present the Flight Application Software (FAS), the flight control system of the Automated Transfer Vehicle (ATV), a space vehicle designed by EADS Astrium Space Transportation for resupplying the International Space Station (ISS). It is a typical example of control system. A simplified and adapted version of the FAS is described in Fig. 1. It does not exactly correspond to the real system, but gives a good overview of its characteristics.

The FAS consists mainly of acquisition operations, processing operations and command operations, each represented by a different box in the figure. Arrows between boxes represent data-dependencies. Arrows without sources represent system inputs and arrows without destination represent system outputs. Data-dependencies define a partial order between the different operations of the system, as the consumer of a data-flow must execute after the producer of the data-flow. The FAS first acquires data: the orientation and speed from gyroscopic sensors (GyroAcq), the position from the GPS (GPS Acq) and from the star tracker (Str Acq) and telecommands from the ground station (TM/TC). The Guidance Navigation and Control function (divided into an upstream part, GNC_US, and a downstream part, GNC_DS) then computes the commands to apply while the Failure Detection Isolation and Recovery function (FDIR) verifies the state of the FAS and looks for possible failures. Finally commands are computed and sent to the control devices: thrusters orders for the Propulsion Drive Electronics (PDE), power distribution order for the Power System (PWS), solar panel positioning orders for the Solar Generation System (SGS) and telemetry towards the ground station (TM/TC), etc.

Each operation has its own rate, ranging from 0.1Hz to 10Hz. An intermediate deadline constraint is imposed on data produced by the GNC Upstream (300ms while the period is 1s). Operations of different rate can communicate, which is an essential feature of such systems and has very important impacts on the complexity of the design and implementation process.

Contribution

As we can see, programming an embedded control system is a complex task since this does not only consist in implementing the functional aspects of the system. As the system is highly critical, the main Figure 1: The Flight Application Software concern for the program supervising the system is a need for predictability. This requires strong functional determinism, which means that the program will always produce the same output sequence with respect to the same input sequence. The program must be temporally deterministic as well, always having the same temporal behaviour and respecting multiple hard real-time constraints. While respecting these constraints, the system still needs to be optimized, in terms of memory consumption, hardware cost, power consumption or weight for instance.

The complexity of the development process and the criticity of the systems call for high-level programming languages, which cover the complete development process from design to implementation. Automated code generation process is the key, it provides high confidence in the final program as every step of the generation can be defined formally.

In this Ph. D. thesis, we propose such a high-level language for programming embedded control systems (extending our work in [START_REF] Forget | A multi-periodic synchronous data-flow language[END_REF]). The language is built upon Synchronous Languages [START_REF] Benveniste | The synchronous approach to reactive and realtime systems[END_REF] and inherits their formal properties. It adds real-time primitives to enable the programming of multi-periodic systems. The objective of the language is not to replace other synchronous languages but instead to provide a higher layer of abstraction, on top of classic synchronous languages. It can be considered as a real-time software architecture language that enables to assemble locally mono-periodic synchronous systems into a globally multi-periodic synchronous system. The language is supported by a compiler that generates synchronized multi-task C code. The generated code executes on a standard real-time operating system with a preemptive scheduling policy. Synchronization is achieved by a buffering communication protocol (a refined version of [START_REF] Boniol | Implantation multitâche de programmes synchrones multipériodiques[END_REF]) that requires no explicit synchronization primitives (like semaphores). The compilation is defined formally and produces completely deterministic code, which respects the real-time semantics of the original program (period, deadlines, release dates and precedences) as well as its functional semantics (respect of variables consumption). In the next chapter we motivate the definition of this language by showing that the state of the art on the domain does not completely address the problems emphasized above. We also detail the distinctive features of the language.

Chapter 1

State of the Art

In this chapter we present a survey of existing languages related to the programming of embedded control systems (this was in part presented in [FBL + 08]). The desirable features of the language are:

• The ability to specify a set of operations;

• The ability to specify data-communications between operations, in particular between operations of different rates;

• The ability to specify multiple real-time constraints, such as periods, release dates and deadlines;

• A formal definition of the language, to ensure that the semantics of a program is unambiguously defined;

• Automated code generation, to cover the process from design to implementation;

• Functional determinism of the generated code;

• Temporal determinism of the generated code;

• And last: simplicity. This is essential for the programmer to better understand and analyze a program.

Low-level programming

Probably the most widely used technique for programming real-time systems is to directly program the system with traditional imperative languages. These languages were not originally designed for real-time systems and the real-time aspects are mostly supported by Application Programming Interfaces (API) for real-time, which are very close to the underlying Real-Time Operating System (RTOS).

Real-time API

A real-time API defines the interface for a set of services (for instance functions and data-structures) related to real-time programming. The objective of a real-time API is to define a standard, which should allow the portability of an application between different execution platforms that respect this standard.

Real-time APIs are not necessarily related to a single language. For instance, the widely adopted POSIX extensions for real-time [START_REF]IEEE Std[END_REF][START_REF]POSIX, Threads extensions[END_REF], have been implemented for C as well as for ADA. On the opposite, the Real-Time Specification for Java (RTSJ) for instance is specifically defined for JAVA and is a combination of APIs and modifications to apply to the JAVA virtual machine. Other popular realtime APIs include OSEK [START_REF] Osek | OSEX/VDX Operating System Specification 2.2.1[END_REF], for the automotive industry, and APEX, for avionics systems. The general principles are similar for the different APIs: each operation of the system is first programmed as a separate function and functions are grouped into threads, on which the programmer can specify real-time constraints. The program then consists of several threads, scheduled concurrently by the OS.

The different threads are related by data-dependencies, as data produced in one thread may be needed in another thread. In critical systems, the programmer must control the order in which data is produced and consumed (otherwise the behaviour of the system might not be predictable), which requires to introduce synchronization mechanisms between threads. Synchronizations can be implemented either by precisely controlling the dates at which threads execute so that data is produced and consumed in the right order (time-triggered communications) or by using blocking synchronization primitives like semaphores (rendez-vous communications).

Real-time Operating Systems

A real-time operating system is an operating system with multi-tasking capabilities designed specifically for executing real-time applications. Multi-tasking means that the OS is able to execute multiple tasks concurrently, alternating between the different tasks. The scheduling policy of the RTOS provides a general algorithm for deciding the order in which tasks must be executed, according to their real-time characteristics (periods and deadlines). Scheduling is often preemptive (but not always), meaning that the OS can suspend the execution of the currently executing task to execute a more urgent one and restore the execution of the suspended task later.

A RTOS tries to follow one of the aforementioned real-time APIs and usually provides (among other things):

• Structures to define tasks (threads);

• One or several scheduling policies;

• Data-buffers shared between communicating tasks (global memory);

• Locking mechanisms (semaphores), when a task needs exclusive access to a resource (data-buffer or peripheral access for instance);

• Explicit time manipulation (accurate timestamps, timers, ...).

Popular commercial real-time operating systems include VxWorks by Wind River Systems [VxW95] and QNX Neutrino [START_REF] Hildebrand | An architectural overview of qnx[END_REF], which both conform to the POSIX standard, and OSE [START_REF] Ose | OSE Real-Time Operating System[END_REF], which defines four different APIs, depending on the number of features required for a given system. Several extensions for Linux provide real-time features, including RTLinux (www.rtlinuxfree.com) and RTAI (www.rtai.org), which have the major advantage of being open-source projects. Finally, a number of research kernels try to provide a new generation of operating systems with a higher level of abstraction, including more explicit timing constraints (task deadlines instead of task priorities for instance), better guarantees that allow to test in advance whether real-time constraints can be met or not and so on. This includes SHARK [START_REF] Gai | A new kernel approach for modular real-time systems development[END_REF], MaRTE [START_REF] Rivas | POSIX-Compatible Application-Defined Scheduling in MaRTE OS[END_REF] and ERIKA [GLA + 00].

Towards a higher level of abstraction

Programming real-time systems with low-level languages allows to implement a very large class of applications but has important drawbacks. First, in the case of large applications, synchronizing threads is a tedious and error-prone task. Time-triggered communications are hard to implement as it amounts to manually scheduling part of the system. Rendez-vous communications may lead to deadlocks where two tasks are indirectly waiting for each other to continue their execution. Second, the correctness of the program is hard to verify (either manually or using automated verification tools) due to the low-level of abstraction with which the system was programmed. Finally, the low-level of abstraction also makes it hard to analyze which parts of the program correspond to real design constraints and which parts only correspond to implementation concerns and thus during maintenance phases it is hard to know which part of the program can be modified without risk and which cannot. While dealing with such considerations cannot be avoided, it is much easier to rely on programming languages with a higher level of abstraction, where synchronization and scheduling concerns are handled by the compiler of the language.

Synchronous Languages

The difficulty for programming embedded systems directly with low-level languages progressively leads programmers to consider languages with a higher-level of abstraction. Such languages are currently still mainly used only for the early phases of the development process, namely specification, verification and simulation. Some languages though aim at covering the whole process, from specification to implementation, relying on automated code generation where the input program is automatically translated into lower level code. This approach offers important advantages. First, the duration of the development process is reduced, as low-level implementation concerns are handled directly by the code generator. Second, a higher-level program is usually easier to understand (by other programmers) and to analyze (by verification tools). Finally, the correctness of the lower-level program into which the program is translated is ensured by the translation process.

The synchronous approach [START_REF] Benveniste | The synchronous approach to reactive and realtime systems[END_REF] proposes such a high level of abstraction. It is based on solid, elegant and yet simple mathematical foundations, which make it possible to handle the compilation and the verification of a program in a formal way. The synchronous approach simplifies the programming of real-time systems by abstracting from real-time and reasoning instead on a logical-time scale, defined as a succession of instants, where each instant corresponds to a reaction of the system. The synchronous hypothesis says that the programmer does not need to consider the dates at which computations occur during an instant, as long as the computations finish before the beginning of the next instant. This approach led to the definition of three synchronous languages approximately simultaneously: LUSTRE, SIGNAL and ESTEREL. The behaviour of the program is illustrated in Fig. 1.1, where we give the value of each flow at each instant. The node computes one output (o) from its two inputs (c, i). The variables v1 and v2 are used for intermediate computations. The equation v1=0->pre(i) means that v1 equals 0 at the first instant and for the subsequent instants it equals the value of i at the previous instant. The equation defining v2 says that it is present (bears a value) only when true->c is true and that for the instants when it is present, its value is the value of v1 at this instant plus 1. The output o is finally defined as the current value of v2: the value of o is the value of v2 when it was last present. Programming multi-periodic systems LUSTRE usually only considers systems where all the processes share the same logical time scale, that is systems where all the processes can be sampled and executed at the same real-time periodic rate. Some processes can be deactivated at some instants using clocks, but when they execute they still need to complete before the end of the instant, so within the same period. Clocks serve as activation conditions but not as rate modifiers.

LUSTRE

i i 0 i 1 i 2 i 3 i 4 . . . c false true false false true . . . pre(i) i 0 i 1 i 2 i 3 . . . v1=0->pre(i) 0 i 0 i 1 i 2 i 3 . . . v1+1 1 i 0 + 1 i 1 + 1 i 2 + 1 i 3 + 1 . . . v2=(v1+1) when (true->c) 1 i 0 + 1 i 3 + 1 . . . o=current(v2) 1 i 0 + 1 i 0 + 1 i 0 + 1 i 3 + 1 . . .
Multi-rate systems can be specified using a single logical time scale but this is quite tedious in practice. First, the programmer needs to choose the base sample rate of his program among the different rates of the system. In the following, we will consider two opposite choices: programming the system using the fastest rate as the base rate or programming the system using the slowest rate as the base rate. We could choose any rate in between, but this would not change the problem much. For simplification, in the following we take an extract of the case study of the FAS presented previously in Fig. 1 We give a first version of the program with a fast base rate in Fig. 1.5. This program assumes that the main node FAS_fast is activated with a sample rate of 100ms. The node FDIR corresponds to a fast operation, it is performed at each instant, thus at 10Hz. The node GNC_US is activated only when the condition clock10 is true, that is every 10 instants, so at 1Hz. The data communication from the fast operation to the slow operation is performed instantaneously (flow vfdir) and only one out of 10 successive produced values are consumed (due to when clock10). Data communication from the slow operation to the fast operation (flow vgnc) is delayed using the pre operator. This avoids a causality loop. Indeed, the node instance FDIR depends on a value produced by the node instance GNC_US (cur_vgnc), which itself depends on a value produced by FDIR (vfdir). Thus the dependencies cannot both be instantaneous and one of them must be delayed. The main default of this version is that it assumes that it is possible to execute the two operations in less time than the sample period (100ms). Indeed, during instants when both the slow and fast operations are activated, they must all finish within the instant.

This assumption is far too restrictive and leads us to a new version given Fig. 1.6. This time we split the processes performed by the slow operation into several nodes (GNC_US0, . . ., GNC_US9) and distribute these nodes between 10 successive instants, which correspond to one slow period. Each of these nodes is activated only once every 10 instants, with an offset of one instant between the different nodes. Consequently, the complete slow operation is executed with a frequency of 1Hz and can spread over more than one instant. For a complete system, manually distributing processes performed by slow operations between successive fast instants can be tedious and error-prone. First, this requires the programmer to determine a fair distribution between instants in terms of execution times, which amounts to manual scheduling and should be automatized. Second, this assumes that the software architecture of the processes allows such a distribution. For instance, if a slow operation is split into two sub-nodes, as side effects are forbidden in synchronous languages, data produced by the first sub-node at the end of the first instant explicitly needs to be passed on to the next sub-node of the operation at the next instant (flow vgnc0 between GNC_US0 and GNC_US1). Such data communications can be numerous if the computations performed by one operation are very interdependent. This leads to high memory consumption and again is a tedious task that should be automatized.

Programming with a slow base rate

We now consider a third version using the slow rate as the base rate of the program. The corresponding code is given in Fig. 1.7. This program assumes that the main node FAS_slow is activated with a sample period of 1s. The node corresponding to the slow operation (GNC_US) is executed at each instant, thus with a frequency of 1Hz. The node corresponding to the fast operation (FDIR) is instantiated 10 times in order to be executed with a frequency of 10Hz. The inputs and outputs of the program are respectively consumed and produced by the fast operation, thus they also need to be duplicated 10 times, as they are acquired and produced 10 times in an instant. For the communication from GNC_US to FDIR, we choose to consume the value produced by the third iteration of GNC_US.

node FAS_slow ( i 0 , i 1 , i 2 , i 3 , i 4 , i 5 , i 6 , i 7 , i 8 , i 9 : i n t ) r e t u r n s ( o0 , o1 , o2 , o3 , o4 , o5 , o6 , o7 , o8 , o9 : i n t ) This version is incorrect because even if the fast operations are performed 10 times during each slow period, nothing forces the different repetitions to execute at the right time during one instant. Let t 0 be the start date of the instant. The duration of the instant is 1s. We expect operation FDIR to execute once during each of the 10 time intervals [t 0 , t 0 + 100[, [t 0 + 100, t 0 + 200[, . . ., [t 0 + 900, t 0 + 1000[. This behaviour is in no way specified in this program. Similarly, the inputs of the system will all be consumed simultaneously at the beginning of the instant and the outputs of the system will all be produced simultaneously at the end of the instant (thus with a rate of 1Hz), which is not the expected behavior. Finally, instantiating a node 10 times does not produce the same result as repeating the same node instance 10 times, if the node contains memories (pre). 10 instances of the same node use 10 different memories, while 10 repetitions of the same node use the same memories. For instance, repeating a counter 10 times does not produce the same result (the value 10) as repeating one time 10 different counter instances (the value 1 for each counter).

var vgnc : i n t ; v f d i r 0 , v f d i r 1 , v f d i r 2 , v f d i r 3 , v f d i r 4 , v f d i r 5 , v f d i r 6 , v f d i r 7 , v f d i r 8 , v f d i r 9 : i n t ; l e t vgnc =GNC_US( v f d i r 3 ) ; ( o0 , v f d i r 0 ) = FDIR ( i ,0-> pre ( vgnc ) ) ; ( o1 , v f d i r 1 ) = FDIR ( i ,0-> pre ( vgnc ) ) ; ( o2 , v f d i r 2 ) = FDIR ( i ,0-> pre ( vgnc ) ) ; ( o3 , v f d i r 3 ) = FDIR ( i ,0-> pre ( vgnc ) ) ; ( o4 , v f d i r 4 ) = FDIR ( i ,0-> pre ( vgnc ) ) ; ( o5 , v f d i r 5 ) = FDIR ( i ,0-> pre ( vgnc ) ) ; ( o6 , v f d i r 6 ) = FDIR ( i ,0-> pre ( vgnc ) ) ; ( o7 , v f d i r 7 ) = FDIR ( i ,0-> pre ( vgnc ) ) ; ( o8 , v f d i r 8 ) = FDIR ( i ,0-> pre ( vgnc ) ) ; ( o9 , v f d i r 9 ) = FDIR ( i ,0-> pre ( vgnc ) ) ; t e l

Extensions for real-time

As they completely abstract from real-time, the synchronous languages are not well-adapted to handling systems with multiple real-time constraints such as the FAS. Recent work proposed to introduce real-time aspects in LUSTRE through the use of assumptions made about the program environment and requirements about the program behaviour [START_REF] Curic | Implementing Lustre programs on distributed platforms with real-time constraints[END_REF]. Assumptions specify the base rate of the program (for instance basic_period=5) as well as node execution times (for instance exec_time N in [3,4]). Requirements constrain the availability date of a flow (for instance date(x)<5) or the latency between two flows (for instance date(x)-date(y)>4). In addition, the primitive periodic_clock(k,p) defines a clock of period k and of phase p. This clock is false during the p -1 first instants and then true once every k instants.

The compiler ensures, using static scheduling techniques, that if the assumptions hold the requirements will be satisfied at execution. It compiles a program into a set of communicating tasks for a Time Triggered Architecture (TTA) [START_REF] Kopetz | The Time-Triggered Architecture[END_REF]. The scheduling is based on a LP solver and specific to TTA. The synchronous hypothesis is relaxed for flows the clock of which is periodic and the constraint becomes that the computation of a periodic flow must end before the next activation of the flow instead of before the end of the instant.

Using these extensions, we give an improved version of our example programmed on a fast base rate in Fig. 1.8. The assumptions specify the base rate of the program and the duration of each node. The slow operations are activated once every 10 instants using a periodic clock. This new version is better adapted to our case study and our work retains some of the concepts introduced here, in particular the relaxed synchronous hypothesis. The main drawback of this approach is that the compilation scheme is very specific to the TTA architecture.

This work was extended in [START_REF] Alras | Model-based design of embedded control systems by means of a synchronous intermediate model[END_REF], which proposes periodic activation condition operators based on periodic clocks and primitives to handle exclusive execution modes. The authors also propose a simpler, more generic compilation scheme, which generates a set of concurrent real-time tasks. A compiler was implemented for the Xenomai RTOS (www.xenomai.org). Task communications are handled using the protocol proposed by [START_REF] Sofronis | A memory-optimal buffering protocol for preservation of synchronous semantics under preemptive scheduling[END_REF]. This protocol ensures the conservation of the synchronous semantics of communicating tasks thanks to data-buffering mechanisms based on task priorities. The computation of task priorities is not detailed in [START_REF] Alras | Model-based design of embedded control systems by means of a synchronous intermediate model[END_REF]. Our work focuses especially on this point and automates the computation of task priorities.

SIGNAL SIGNAL [BLGJ91, ABLG95,

LGTLL03] is a data-flow synchronous language in many points similar to LUSTRE but also with important differences. It retains the declarative style of LUSTRE, specifying systems by equations over synchronized streams (signals instead of flows). The first important difference with respect to LUSTRE lies in the fact that in SIGNAL the user can manipulate clocks explicitly as firstclass objects: the programmer can declare clock variables (variables of type event), extract the clock of a signal (event1=when s), specify clock relations (for instance x^=y^+z says that the clock of x is the union of the clocks of y and z), . . . In LUSTRE, a clock is not a first-class object, it is manipulated through the flow that contains it.

An important consequence of this clock model is that clocks can be synthesized by the compiler. Behind a SIGNAL program lies a system of clock equations (Boolean equations) that drives this synthesis. The synthesis, called clock calculus, transforms the set of equations into a set of equalities where every clock is defined with respect to the free clocks of the system. The free clocks correspond to clocks that will be provided by the environment of the program as input variables. Though this clock synthesis is very powerful, the synthesis problem is NP-hard, so the SIGNAL compiler does not seek completeness but instead efficiency for commonly encountered systems, meaning that some correct programs may be rejected.

The second important difference, which is in part a consequence of the first, is that SIGNAL programs can be polychronous. This means that the clocks hierarchy of a program can form a forest (the free clocks of the program are the roots of the different trees of the forest). In other words, the different clocks of a program do not necessarily have a common ancestor. The clocks hierarachy in LUSTRE forms a single tree, LUSTRE programs are thus called endochronous.

For the most part, programming the FAS example with SIGNAL faces the same difficulties as when programming it with LUSTRE, so we do not illustrate this in details here. In [START_REF] Smarandache | A canonical form for affine relations in signal[END_REF], an extension of clock relations called affine clock relations was proposed. An affine transformation of parameters (n, ϕ, d) applied to a clock H produces a clock K, obtained by inserting n -1 instants between two successive instants of H to produce an intermediate clock H and then by taking each d th instant on H , starting from d. It allows to specify multi-rate systems more explicitly than with core SIGNAL and the clock calculus problem can be solved efficiently for this class of clock relations. This work is not specifically targeted for multi-periodic systems. More recently, [START_REF] Metivier | Analysis of periodic clock relations in polychronous systems[END_REF] studied this model on the particular case of periodic systems. This work focuses on formal verification and, as far as we know, the compilation of a program into a set of concurrent real-time tasks has not been studied yet.

ESTEREL

Overview ESTEREL [START_REF] Boussinot | The Esterel language[END_REF] is an imperative synchronous language and focuses mainly on the description of the control-flow in a system. An ESTEREL program consists of several concurrent processes communicating by means of broadcasted signals and structured into modules. When a signal is emitted by a process, it is instantaneously perceived by all other processes that listen to it. The processes executed by a module are described as a composition of statements. Statements can either be composed sequentially or in parallel (concurrently). Most statements are considered to be instantaneous, except for explicit pause statements. At each reaction of a program, control flows through its processes until it arrives to a pause or termination instruction. At the next reaction, processes resume where they stopped. The example below illustrates the basic features of the language:

module ABRO : input A,B,R; output O; loop [ await A || await B ]; emit O; await R end end
The program consists of a single module ABRO, which has three input signals A, B and R and one output signal O. The body of the module consists of an infinite loop statement (loop..end). The body of the loop first consists of two statements composed in parallel (using ||). The first statement (await A) causes the control to pause in this branch of the composition until A is emitted. The second statement (await B) causes the control to pause in the other branch of the composition until B is emitted. The control flows to the rest of the loop only after both A and B have been emitted (not necessarily simultaneously). This parallel statement is then composed sequentially (using ;) with statement emit O, which causes signal O to be emitted. The control then continues to flow (instantaneously) to the next statement (await R), which causes it to pause until R is emitted. The control then immediately loops back to the begining of the loop..end statement. The behaviour of this program is illustrated in Fig. 1.9, in which we detail the input received by the program and the output signals emitted by the program. The ESTEREL program for the example of Fig. 1.1 is given in Fig. 1.10. Implicitly, the program is activated at the rate of its input i, which is assumed to be 10Hz. The FDIR operation is declared as an external procedure. The first group of parameters of the procedure is the outputs and the second group is the inputs. As operation GNC_US can take longer than 100ms to execute (the duration of a program reaction), it is declared as an external task. The task can then be executed asynchronously using the statement exec. The statement exec is said asynchronous because it can last longer than a reaction. The reaction in which it will complete is unknown a priori. When the statement completes, the process simply resumes to the next statement. The two operations FDIR and GNC_US are executed in parallel. Operation FDIR executes every time an input occurs (every i), while operation GNC_US executes once every 10 inputs (loop .. each 10 i). In the first branch of the parallel composition, task GNC_US produces the value xv1, which is then emitted via signal s1 (statement emit s1(xv1)). The procedure FDIR can then consume the value contained by the signal (?s1). Similarly, FDIR produces values xo and xv2, emits the output signal o with value xo and the signal v2 with value xv2. The statement await immediate s2 in the first branch forces task GNC_US to execute after FDIR. The statement await s2 requires the control flow to pause for at least one instant, even if s2 is present when the control flow reaches this statement, while await immediate s2 allows the control flow to directly continue if s2 is present. On the opposite, FDIR does not need to wait for the completion of GNC_US to start its execution, as the signal s1 is emitted before executing GNC_US. This way, FDIR consumes the previous value produced by GNC_US.

module FAS : i n p u t i ; o u t p u t o ; p r o c e d u r e FDIR ( i n t e g e r , i n t e g e r ) ( i n t e g e r ) ; t a s k GNC_US( i n t e g e r ) ( i n t e g e r ) ; s i g n a l s 1 : i n t e g e r , s 2 : i n t e g e r i n var xv1 : = 0 : i n t e g e r , xv2 , xo : i n t e g e r i n The main concern with this program is that the absence of explicit real-time information does not allow automated real-time analyses of the program (schedulability analysis) or concurrent scheduling based on task real-time properties.

Synchronous Languages

Real-time extensions

The project TAXYS [BCP + 01] introduced real-time aspects in ESTEREL by means of pragmas (code annotations) as shown in the following program: loop await PulsePeriod %{PP:=age(PulsePeriod)}%; call C() %{(10,15), PP<=40}%; emit Pulse; end loop Pragmas appear between %{ and }%. The pragma (10, 15) next to statement call C() specifies that the execution time of C is 10 in the best case (BCET) and 15 in the worst case (wcet). The pragma %{PP:=age(PulsePeriod)}% declares a continuous time variable, which value is the time elapsed since the emission of signal PulsePeriod. The pragma PP<=40 specifies a real-time constraints, which requires procedure C to complete no later than 40 units of time after the emission of signal PulsePeriod.

Using these extensions, we can add real-time characteristics and real-time constraints to our previous example, as shown in Fig. 1.11. module FAS : i n p u t i ; o u t p u t o ; p r o c e d u r e FDIR ( i n t e g e r , i n t e g e r ) ( i n t e g e r ) ; t a s k GNC_US( i n t e g e r ) ( i n t e g e r ) ; s i g n a l s 1 : i n t e g e r , s 2 : i n t e g e r i n var xv1 : = 0 : i n t e g e r , xv2 , xo : i n t e g e r i n

SYNDEX and the AAA methodology

The Algorithm-Architecture Adequation methodology (AAA) [START_REF] Thierry Grandpierre | Optimized rapid prototyping for real-time embedded heterogeneous multiprocessors[END_REF] and the associated tool SYN-DEX provide a framework for programming distributed real-time embedded systems, based on a graphs formalism with synchronous semantics. The functional aspects of a system are described by the Algorithm part, representing computations as a data-flow graph repeated infinitely. For the most part, the Algorithm part transposes synchronous languages to a graphs formalism (close to SIGNAL or LUSTRE). In addition, this methodology allows to specify the hardware Architecture on which the Algorithm must be implemented. The Architecture is a graph where nodes correspond to either computation Operators or communication Media and edges correspond to connections between Operators and Media. The programmer must also provide execution times for the computation of the operations of the Algorithm on the different Operators of the Architecture and the transfer times for the different data-types that can be transferred by the media. The Adequation then consists in searching for an optimized implantation of the Algorithm on the Architecture. As the distributed scheduling problem is NP-complete, the Adequation is an heuristic and not an exact algorithm (it may not always find the optimal implantation but only a good implantation).

Programming our example with SYNDEX is very similar to what we did with LUSTRE. We can however give a better version of the example programmed with a slow base rate as shown in Fig. 1.12. The Algorithm part retains the structure of the LUSTRE version (Fig. 1.7): during a graph iteration (a slow period), the input is acquired 10 times (i0, i1, . . ., i9), the output is produced 10 times (o0, o1, . . ., o9) and the FDIR is repeated 10 times. Slow to fast communications are performed using a delay (pre). Operations t100, t200, . . ., t900 are "timing operations". They have a duration of 100ms and are executed on a specific timer processor (timerProc in the Architecture part), while the other operations are all executed on the main processor (mainProc). By adding a precedence from timing operations to acquisition operations (i0, i1, . . .), we force fast operations to execute after a certain date. For instance, the acquisition i1 cannot be performed before date 100ms. This gives a better control on the date at which fast operations take place and thus improves the previous LUSTRE version.

This version still does not completely respect the requirements. Indeed, while we ensure that fast operations do not execute too early (before their period), nothing in the program ensures that they will not execute too late (after their period), so the Adequation may choose a schedule that does not respect these constraints. Furthermore, implementing the timing operator and timing operations may not be simple in practice.

The support of multi-periodic systems for the AAA methodology has first been studied in [START_REF] Cucu | Non-preemptive scheduling and schedulability condition for embedded systems with real-time constraints[END_REF][START_REF] Cucu | Real-time scheduling for systems with precedence, periodicity and latency constraints[END_REF] and then in [START_REF] Kermia | Non-preemptive multiprocessor static scheduling for systems with precedence and strict periodicity constraints[END_REF][START_REF] Kermia | Ordonnancement temps réel multiprocesseur de tâches non préemptives avec contraintes de précédence, de périodicité stricte et de latence[END_REF]. Multi-periodic systems are officially supported by SYNDEX since the version 7 of the software, which was released in August 2009. We had very little time to experiment, according to these early experiments, the programmer can specify a data-dependency between two operations of different periods if the period of one task is a multiple of the other. Communication schemes are restricted to communications without loss. For instance, if the producer is n times faster than the consumer, each instance of the consumer consumes data produced by n repetitions of the producer.

Synchronous Data-Flow (SDF)

Synchronous Data Flow [START_REF] Lee | Synchronous Data Flow[END_REF] is a special case of data-flow graphs where the number of data-samples produced and consumed by each node are known a priory, originally designed for programing concurrent Digital Signal Processing applications. A SDF graph consists of a set of nodes representing computations, related by edges representing data-dependencies. As for synchronous languages, edges represent infinite streams of values and nodes represent computations repeated infinitely. Each input or output of a node is annotated with a number specifying the number of data-samples the node must consume before performing its computations and the number of data-samples it produces at the end of the computation. The number of data-samples n produced by the source node of an edge and the number of data-samples n consumed by the destination node of the edge are not necessarily the same, thus the execution of the graph requires to repeat the source node k times for each k repetitions of the destination node, such that kn = k n .

The figure 1.13 gives a simple example of SDF graph. To satisfy data-sample rates, a repetition of • And many others.

Synchronous Languages

The SDF graph for the example of Fig. 1.2 is given in Fig. 1.14. Each time the node GNC_US executes, it consumes 10 data-samples produced by the node FDIR. As the node FDIR only produces one data-sample at each execution, there must be 10 repetitions of FDIR before each execution of GNC_US. GNC_US produces 10 data-samples at each execution, one for each repetition of FDIR. Communication from GNC_US to FDIR is delayed (the D on the edge) to prevent a causality loop. As node rates are determined by data-sample ratios between related nodes, the communication patterns we can specify are quite restricted. It is for instance not possible to specify a program where GNC_US samples only one out of ten successive values produced by FDIR and where FDIR still executes 10 times more often than GNC_US. More importantly, while SDF graphs specify multi-rate systems, they do not necessarily specify multi-periodic systems. Indeed, nothing requires the successive repetitions of a node to be executed at regular intervals of time, so from the real-time point of view nodes are not periodic. For instance, in the second schedule we gave for the example of Fig. 1.13, nodes clearly do not execute periodically.

Globally Asynchronous Locally Synchronous (GALS) Systems

While extending synchronous languages to support multi-periodic systems is quite a recent concern, the more general class of globally asynchronous and locally synchronous systems is a well-studied topic. In a GALS system, several locally synchronous systems are assembled using asynchronous communication mechanisms to produce a globally asynchronous complete system. Synchronous languages targeted for GALS systems are also often referred to as multi-clock/polychronous languages. For instance, multi-clock ESTEREL [START_REF] Berry | Multiclock esterel[END_REF] and POLYCHRONY [START_REF] Le | Polychrony for system design[END_REF] study GALS systems in general. The Quasi-Synchronous approach [CMP01], the N -Synchronous approach [CDE + 06, CMPP08] or Latency Insensitive Designs [START_REF] Luca | Theory of Latency-Insensitive Design[END_REF] focus on more specific classes of locally synchronous systems that can be composed "nearly" synchronously using bounded memory communication mechanisms. While these systems are not completely synchronous, they retain a globally predictable behaviour, which makes them suitable for formal verification and/or efficient compilation.

A possible approach to our problem would be to consider multi-periodic systems as a special case of GALS systems to reuse existing work. However, multi-periodic systems are in essence completely synchronous, treating them as such allows to more clearly identify the real-time properties of a system (during the specification), provides better analysis possibilities (proving the correct synchronization of the operations, performing schedulability analysis) and allows the production of more optimized code (specific communication protocols).

Matlab/Simulink

SIMULINK [Mat] is a high-level modelling language developed by The Mathworks, widely used in many industrial application domains. SIMULINK was originally designed for specification and simulation pur-1.4. Architecture description languages poses but commercial code generators have been developed since, such as REAL-TIME WORKSHOP EMBEDDED CODER from Mathworks, or TARGETLINK from dSpace. The major difficulty for using SIMULINK for programming critical systems is that SIMULINK and the associated tools lack formal definition. For instance, SIMULINK has several semantics, which depend on user-configurable options and are only informally defined. Similarly, the conservation of the semantics between the model simulation and the generated code (either for RT WORKSHOP or TARGETLINK) is unclear.

As SIMULINK models are mainly data-flow graphs, a translation from SIMULINK to LUSTRE has been proposed in [START_REF] Tripakis | Translating discretetime Simulink to Lustre[END_REF]. This translation only considers a well-defined subset of SIMULINK models and allows to benefit from the formal definition of LUSTRE. This enables formal verification and automated code generation that preserves the semantics of the original SIMULINK model.

Architecture description languages

An Architecture Description Language (ADL) provides an additional layer of abstraction compared to the languages studied previously, by focusing on modelling the interactions between high-level components of a system. Typically, in our context, ADLs focus on the specification of interactions between tasks instead of interactions between functions or nodes. Function implementations still need to be provided at some level but this level of detail is hidden in an ADL specification. Tasks specified in an ADL specification are considered as "black-boxes", implemented outside the ADL specification. Such a multi-layer approach reflects the organization of the development process for complex systems such as embedded control systems. Different engineers first develop separately the different functionalities of the system and then a different person, the integrator, assembles the different functionalities to produce the complete system. The integrator needs a high level of abstraction to coordinate with other engineers: ADLs can address this issue.

AADL

The Architecture Analysis and Design Language (AADL) [START_REF] Peter | The architecture analysis & design language (AADL): An introduction[END_REF] is an architecture description language, used for modelling the software and hardware architecture of an embedded real-time system. An AADL design consists of a set of components, which interact through interfaces. Components can be separated into three categories: application software, execution platform (hardware) or composite components (composition of other components). A large variety of distinguishing properties can be specified on each component. We will only detail the software components as handling the hardware aspects of a system is out of the scope of our work.

A software component can be a thread (a schedulable unit of concurrent execution), a thread group (a compositional unit for organizing threads), a process (a protected address space), data (data types and static data in source text) or a subprogram (callable sequentially executable code). In our case, the different operations of a system can be modelled as threads grouped in a single process. A component type defines the externally visible characteristics of the component while a component implementation specifies the internal structure of a component in terms of sub-components, interactions between subcomponents and component properties. Component properties allow to specify component real-time characteristics, including dispatch protocol (periodic, aperiodic, sporadic), periods, worst-case execution times and deadlines.

Components can interact through connections between elements defined in their interface, in particular data-communications can be specified through connections between component ports. Connections can either be immediate (between simultaneous dispatches of the communicating threads) or delayed (from the current dispatch of the producer to the next dispatch of the consumer). Immediate connections impose a precedence order from the producer to the consumer of the data, while in the case of delayed connections the two threads can be scheduled concurrently. Connections can also relate threads of different periods, either immediately or with a delay. These different communications schemes are illustrated in Fig. 1.15. Finally, end-to-end latency constraints can be imposed on flows, where a flow is a path through sub-components of a component. The AADL program for our example is given below: The package FAS_DATA declares the data types used in the program. The package FAS_SW specifies the software architecture of the program. It consists of one thread declaration for each operation of the program (GNC_US and FDIR), the declaration of the global system as a process (FAS) and its implementation (FAS.FAS_impl). Each thread declaration specifies the inputs and outputs of the corresponding operation along with its period and execution time (declared with an upper and a lower bound). The implementation FAS.FAS_Impl specifies the connections (data-dependencies) between the inputs and outputs of the system and the inputs and outputs of the threads. All connections are immediate except the connection gnc_fdir_cnx, which is delayed (this is denoted using the double arrow ->>).

The AADL approach enables to design complex real-time systems with a high-level of abstraction. The main restriction of this approach is that the programmer only has a limited choice of communication schemes and cannot define its own schemes.

REACT/CLARA

The REACT project [START_REF] Faucou | An ADL centric approach for the formal design of real-time systems[END_REF] proposes a toolkit dedicated to real-time system design. It combines formal modelling, using the Architecture Design Language CLARA [START_REF] Durand | Description et vérification d'architectures d'application temps réel : CLARA et les réseaux de Petri temporels[END_REF], with formal verification techniques based on Timed Petri Nets [START_REF] Berthomieu | Modeling and verification of time dependent systems using time petri nets[END_REF]. The language CLARA is dedicated to the description of the functional architecture of a reactive system.

Similarly to AADL, CLARA describes a system as a set of high level interacting components. A system component consists of a set of communicating activity components. An activity is triggered repeatedly by the activation law defined for its start port and signals its completion on its end port. An activity communicates with other activities by means of input or output exchange ports. Each time it is triggered, the activity executes a finite sequence of actions. An action either corresponds to an external function call (the execution duration of which is specified in the action), to an input acquisition or to an output production.

Occurrence generator components specify the input data or input signals of the system. They occur either periodically or sporadically. Activities can also access to shared resources or shared variables components.

A link connects output ports to input ports. To simplify, here we consider that a link connects an input port to an output port using only a protocol block and possibly an operator block. CLARA actually allows more complex link definitions connecting more than two ports together. The protocol of a link specifies the policy used for transferring data or signals between the ports of the link. The protocol can be rendez-vous (the consumer and the producer must wait for each other before executing the transfer), blackboard (the activities are not synchronized, the consumer simply uses the last value produced by the producer) or mailbox (the producer and the consumer communicate through a FIFO). The size of the mailbox can be bounded or unbounded. When the mailbox is bounded, the producer must wait if the mailbox is full (until the consumer consumes a value) and the consumer must wait if the mailbox is empty (until the producer produces a value). Operators can be combined with protocols to modify their behaviour when a link connects two activities of different rates. The operator (n) appears on the producer side and specifies that n repetitions of the consumer read the same value. The operator (1/n) appears on the consumer side and specifies that only one out of n successive produced values will be consumed.

Our example can be programmed as shown in Fig. 1.16. The system consists of two activities FDIR and GNC_US. It reads one data source from the environment (i) and produces one output value (o). The start port of an activity is represented on its top and the end port on its bottom. The other ports are data exchange ports. The occurrence generator ck emits a signal every 100ms. This signal is consumed by FDIR using a bounded mailbox of size 1. This triggers FDIR each 100ms. The end port of FDIR is linked to the start port of GNC_US using a bounded mailbox of size 1. As there is an operator (1/10) on the link, GNC_US is triggered only once every 10 repetitions of FDIR. Similarly, only one out of 10 values produced by FDIR (port vfdir) are consumed by GNC_US. On the opposite, the operator (10) on the link from GNC_US to FDIR specifies that 10 repetitions of FDIR consume the same value produced by GNC_US.

Figure 1.16: A multi-rate communication loop in CLARA

This approach enables the specification of multi-periodic systems. Our language retains some of the concept introduced here but relies on a different semantics (the synchronous approach). As far as we know, the tools developed in the REACT context use CLARA for formal verification but automated code generation is not supported yet.

UML and CCSL

The Unified Modeling Language (UML) [START_REF]Unified Modeling Language, Superstructure[END_REF] is a general-purpose modeling language, consisting of a set of graphical notations for the creation of abstract models of a system. Its semantics is purposely loose and can be refined through the definition of domain specific profiles. Such a profile has recently been defined for real-time embedded systems: the UML Profile for Modeling and Analysis of Real-Time and Embedded systems (MARTE) [START_REF]UML Profile for MARTE, beta 1[END_REF]. This profile includes the Clock Constraint Specification Language (CCSL) [START_REF] André | Combining CCSL and Esterel to specify and verify time requirements[END_REF] for specifying the timing properties of the system. This language is inspired from Synchronous Languages but is targeted for the more general context of Globally Asynchronous and Locally Synchronous (GALS) systems.

CCSL provides constructs based on clocks and clock constraints to specify timing properties. Clocks can either be dense or discrete. In our context, we only consider discrete clocks, which consist of a sequence of instants with abstract durations. The language supports a rich variety of clock constraints, which specify constraints on the ordering of instants of different clocks. This includes in particular the possibility to define periodic clocks and periodic clock sampling. CCSL is very recent and for now it is targeted for system specification and system verification, not for automated code generation. As it allows to specify very general clock constraints, efficient code generation would most likely require to only consider a subset of the clock constraints provided by the language.

1.4.4 GIOTTO GIOTTO [START_REF] Henzinger | Giotto: A timetriggered language for embedded programming[END_REF] is a time-triggered architecture language designed for the implementation of real-time embedded systems. A GIOTTO program consists of a set a multi-periodic tasks grouped into modes, where each task is implemented outside GIOTTO.

A task is defined by a set of input and output ports, by the external function implementing it and by a frequency, relative to the mode containing the task. A mode contains a set of tasks and data-dependencies between the ports of the tasks. A period P is specified for each mode and a frequency f is specified for each task of the mode. The period of a task is then P/f . A GIOTTO program finally consists of a set of modes and mode switches (Boolean conditions).

Data produced by a task is considered available only at the end of its period. On the opposite, when a task executes it can only consume the last values of its inputs produced before the beginning of its period, not the values produced during the current period. This is illustrated in Fig. 1.17. In this example, B consumes data produced by A and the two tasks have the same period (depicted by vertical dashed lines). Data production and consumption dates are depicted by vertical arrows. We can see that B consumes the value produced by A during the previous period, not during the current period. This communication pattern amounts to always using a delay in LUSTRE.

A B A B B reads init A writes A 0 B reads A 0 A writes A 1 Figure 1.17: Communications in GIOTTO
The GIOTTO program for our example is given in Fig. 1.18, it consists of two tasks (FDIR and GNC_US) and a single mode. The period of the mode is 1s, the frequency of GNC_US is 1 so its period is 1s, the frequency of FDIR is 10, so its period is 100ms. Task communications are cyclic but this does not imply a causality loop as all communications are delayed.

The implicitly delayed communications of GIOTTO are very restrictive and imply high latencies between an input acquisition and the corresponding output production. In the example of Fig. 1.17, if a system input i is acquired by A and processed by the sequence of tasks A, B, to produce a system output o, the latency between i and o equals two periods of A (or B) instead of the sum of the execution times of A and B. Such communication patterns are clearly not adapted to the systems we consider.

Motivation of our work

As we can see, existing programming languages only partially address the implementation of embedded control systems with multiple real-time constraints. Programming with traditional imperative languages coupled with real-time APIs allows to implement a very large class of applications but the low-level of abstraction makes the task tedious and error-prone. Synchronous languages provide a higher level of abstraction with formal semantics, which makes them well suited for critical systems. They support automated and formally defined low-level code generation, which greatly simplifies the development process and produces correct-by-construction low-level code. The level of abstraction of synchronous languages is unfortunately not very well-adapted for systems with multiple real-time constraints. SIMULINK provides a data-flow model similar to synchronous languages but lacks formal semantics and automated code generation tools do not ensure that the generated code preserves the semantics of the original model. Compared to synchronous languages, architecture design languages propose an additional level of abstraction, by focusing on the description of the real-time software architecture of a system in terms of tasks and task interactions. ADLs combined with lower-level programming languages to implement the system tasks are well-suited for the design of embedded control systems with multiple real-time constraints. The description of task interactions however remains quite limited in existing ADLs, which calls for a more expressive language.

Based on these observations, we propose a language built as a real-time software architecture design language that transposes the synchronous semantics to the architecture design level. Defining the language as an ADL provides a high-level of abstraction, while relying on a synchronous semantics benefits from well-established formal properties.

A program of this language consists of a set of imported nodes, implemented outside the program using other existing languages (C or LUSTRE for instance), and data-flows between the imported nodes. The language allows to specify multiple deadline and periodicity constraints on flows and nodes. It also defines a small set of rate transition operators that allow the programmer to precisely define the communication patterns between nodes of different rates. A program is automatically translated into a set of concurrent tasks implemented in C, which can be executed with existing real-time operating systems.

The language can be compared with other languages as follows:

• ADLs: The level of abstraction of the language is the same as that of existing ADLs but the language relies on a different semantics (the synchronous approach) and allows the programmer to define its own communication patterns between tasks;

• Synchronous languages: Though the language has a synchronous semantics, it addresses a differ-1.6. Outline of the thesis ent phase of the development (the integration) compared to existing synchronous languages, and enables efficient translation into a set of real-time tasks;

• Imperative languages: Though a program is in the end translated into imperative code, the generated code is correct-by-construction. Thus, analyses can directly be performed on the original program rather than on the generated code.

Outline of the thesis

The first part of the thesis focuses on the language definition. First, we present the synchronous approach in details in Chapter 2, as it is the fundamental basis of our work. Then, in Chapter 3 we introduce realtime aspects in the synchronous approach by defining a particular class of clocks called strictly periodic clocks, which enable the definition of multi-periodic systems. Chapter 4 presents the language in detail, with its complete syntax and formal semantics. The real-time primitives of the language are based on strictly periodic clocks. The Chapter 5 illustrates the expressiveness of the language by providing a series of examples of different multi-periodic communication patterns. Finally, the static analyses of the language, which ensure that the semantics of a program is well-defined, are detailed in Chapter 6.

The second part of the thesis details the compilation of the language into a set of real-time tasks. Chapter 9 first describes how dependent real-time tasks (task related by data-dependencies) are extracted from a program. In Chapter 10, we propose to translate the set of dependent tasks into an equivalent set of independent tasks, where precedences are encoded in task real-time attributes and where a buffering communication protocol ensures communication consistency. The independent task set can then easily be translated into low-level code as explained in Chapter 11. We present a prototype implementation of the compiler in Chapter 12.

The third part of the thesis studies the implementation of the complete FAS.

Part I

Language Definition

Chapter 2

The Synchronous Approach

In this chapter we present the basic notions of the synchronous approach and focus more particularly on data-flow synchronous languages. We take LUSTRE [START_REF] Halbwachs | The synchronous data-flow programming language LUSTRE[END_REF] as example because it is the fundamental basis of the commercial development environment SCADE [Est], often used for programming aeronautic and aerospace systems.

Logical time and instants

The key feature of the synchronous approach [START_REF] Benveniste | The synchronous approach to reactive and realtime systems[END_REF] is that it abstracts from real-time and instead describes the behaviour of a system on a logical time scale. The computations performed by the system are split into a succession of instants. At each instant, the system repeats the same behaviour: acquire inputs and perform computations on them to produce outputs (a reaction of the system). For the system to respect the synchronous hypothesis, at each instant computations must complete before the current instant ends and the next one begins. The exact real-time dates at which computations take place during the instant are irrelevant, as long as computations respect this hypothesis. As far as logical time is concerned, computations are performed instantaneously (literally during the instant). This is however only a conceptual abstraction and does of course not require computations to have no duration in practice. By abstracting from real-time, the synchronous approach greatly simplifies system design. The designer does not need to specify when computations occur but only the order in which they must be performed (during each instant). This order is specified by the description of the data-dependencies between the different operations the system is composed of. The formal semantics of synchronous languages along with a series of static analysis performed by their compilers require the designer to give a precise and non-ambiguous description of the system (in particular of the data-dependencies), which allows the compilers to generate completely deterministic code.

Verifying the synchronous hypothesis

Though the synchronous approach aims at abstracting from real-time for system design, real-time must still be taken into account at some point of the development process. The programmer has to verify that his program respects the synchronous hypothesis. This means that he must ensure that computations always complete before the end of the instant.

The duration of an instant is defined differently depending on the class of systems we consider:

• In purely reactive systems, the duration of an instant is determined by the arrival of some event that triggers the beginning of the instant. The next instant starts at the next arrival of an event. In practice the durations of instants in a purely reactive system are hard to predict and can vary from one instant to the next.

• In sampled reactive systems, the duration of an instant is fixed to a given sampling period and is the same for every instant. A new instant is triggered at each period.

In any case, the programmer needs to determine the worst case execution time (wcet) of the computations performed during each instant and to check that it is lower than the duration of this instant. Computing this wcet may not be a trivial task and can be simplified as follows:

• Synchronous languages do not allow computations with unbound execution times such as loops or recursion (with the exception of loops the size of which can be determined statically [START_REF] Maraninchi | Arrays and contracts for the specification and analysis of regular systems[END_REF]).

• If some processes are activated only during certain instants, according to a condition that cannot be evaluated statically, the process is considered as always active and its execution time must be taken into account for every instant.

• The duration of the different processes of the system are over-approximated.

These restrictions may not always be negligible, but are acceptable for critical systems as they enable to produce a deterministic implementation of the system.

Overview of synchronous languages

The synchronous approach has been implemented in several programming languages. A complete survey can be found in [BCE + 03]. To summarize, synchronous languages can be separated in two different classes:

• Imperative synchronous languages, among which ESTEREL [START_REF] Boussinot | The Esterel language[END_REF], mainly focus on the description of the control-flow in the system. A program consists of a set of nested and concurrent threads, synchronized by a single global clock and communicating by means of broadcasted signals. At each reaction, control flows through each thread, until it arrives to a pause or termination instruction. At the next reaction, threads resume were they previously stopped.

• Declarative synchronous languages, among which LUSTRE [START_REF] Halbwachs | The synchronous data-flow programming language LUSTRE[END_REF] and SIGNAL [START_REF] Benveniste | Synchronous programming with events and relations: the Signal language and its semantics[END_REF], mainly focus on the description of the data-flow in the system. A program consists of a set of equations that define infinite sequences of values called flows. At each reaction, the program computes the current value in the sequence of each output flow, depending on the current or previous values of the input flows.

Basic operators

Classic arithmetical and logical operators are extended point wisely over flows. For instance, let x and y be two flows of the same clock. Let (x 1 , x 2 , . . . , x n ) denote the sequence of values of x and (y 1 , y 2 , . . . , y n ) denote the sequence of values of y. The sum of the two flows x+y has the following sequence of values: (x 1 + y 1 , x 2 + y 2 , . . . , x n + y n ).

In the same way, if-then-else is the point wise conditional operator. For instance, the absolute value of a flow x can be defined by the following equation:

abs=if x<0 then -x else x;
The reader should not mistake this operator for its homonym instruction of imperative languages like C. This operator always requires a then and a else branch and both branches are always activated, whether the condition is true or not. The condition only decides whether the current value of the flow is the value of the then or of the else branch. Therefore, this operator cannot be used to activate or deactivate flow computations, instead clock operators are designed for this purpose (see below).

The pre operator is an instant delay operator, it stands for the previous value of a flow. It is often used along with the -> initialisation operator, as the value of pre x at its first activation is undefined. The flow x->y takes the value of x at its first activation and then the value of y for the remaining activations. For instance, a counter can be defined as follows (its sequence of values is 0, 1, 2, 3, . . .):

count=0->(pre(count)+1);
Notice that a flow can depend on itself, as long as the dependency is not instantaneous (a pre is required).

Clock operators

The clock operator when under-samples a flow using a Boolean condition. The flow x when c is present and takes the value of x only when c is true. When c is false, the flow is absent, its value is undefined and cannot be accessed. Conversely, the current operator over-samples a flow, replacing the absent values introduced by when by the last present value of the flow. This is illustrated in Fig. 2.1, in which we give the value of each flow at each instant. Notice that in general, x =current(x when c). The operator when is often used as an activation condition to activate or deactivate some processes of the system. Indeed, on the contrary to the if-then-else operator, in the expression x when c, the flow x is not computed at all if c is false. A flow can be under-sampled several times and then over-sampled several times. The over-sampling of a flow produces a flow on the clock it had before its last under-sampling. This is illustrated in Fig. 2.2. If the flow is already always present, applying a current is considered as an error.

Nodes

LUSTRE programs are structured into nodes. A node contains a set of equations, which defines how its outputs are computed from its inputs. An example is given below with the node threshold. The output reached of the node is true when more than N instants have occurred since the last time reset was true: The set of equations of a node can contain calls to other nodes, so a LUSTRE program consists in a hierarchy of nodes. Among those nodes, one is chosen as the main node, the entry point of the program.

Automated code generation

Synchronous languages compilers do not directly generate assembly code, but instead intermediate level code (C code) [START_REF] Halbwachs | Synchronous programming of reactive systems[END_REF]. This generated code can then be compiled for a variety of embedded systems as micro-processor manufacturers usually provide a C compiler for their processors.

Single-loop

The most intuitive way to generate imperative code from a synchronous program is to translate the program into a single loop, the body of which corresponds to the computations performed during an instant. To do so, the compiler must translate the set of equations of the input program into a sequence of instructions.

The first step is to find an order for the evaluation of the expressions used in the equations that respects data-dependencies between variables. For instance, for the set of equations: o=v * 2;v=i+1;, we need to evaluate v before evaluating o. This often requires to introduce some auxiliary variables and to split complex expressions into several simpler expressions.

Once expressions are ordered, the compiler generates a variable allocation for each variable of the program and translates each expression into a C instruction. The translation of LUSTRE operators into C is fairly straightforward:

• Arithmetic and logic operators are translated into their C equivalent.

• The pre operator introduces an additional variable to store the previous value of a flow. This variable copies the current value of the flow at the end of the loop.

• The -> operator requires to test if the program is in its initial phase.

• The when operator is translated into an if.

• The current operator does not generate code at all. contains several calls to the same node. This generates shorter code, though the trade-off is that causality analysis may reject some correct programs (which can be expanded to solve the problem).

Compiling into automata

The Verimag LUSTRE compiler supports a second compilation technique, which translates a program into automata. This technique borrows from the ESTEREL compiler. It has gradually been abandoned and will not be detailed here. The basic idea is that each state of the automaton generated for a program corresponds to a different valuation for the state variables of the program, while transitions, from one program state to another, are associated with the code corresponding to a program reaction. State variables represent the current state of the program.

Chapter 3

Real-Time Periodic Clocks

At first glance, taking real-time into account in a synchronous language may seem contradictory, given that the synchronous hypothesis is often qualified as "zero-time" hypothesis. In this chapter we show that real-time can however be introduced in the synchronous approach, while still retaining its clean and intuitive semantics.

A model relating instants to real-time

We have seen in Chap. 1 that completely abstracting from real-time renders the programming of multiperiodic systems difficult. Therefore, in this section we present a synchronous model that relates instants to real-time, while retaining the basis of the synchronous approach.

Multiple logical time scales

The main idea of our model is that a multi-periodic system can be considered as a set of locally monoperiodic synchronous processes assembled together to form a globally multi-periodic synchronous system. Locally, each process has its own logical time scale, its own notion of instant and must respect the usual synchronous hypothesis, which requires it to complete its computations before the end of the current instant of this logical time scale. When we assemble processes of different rates, we assemble processes of different logical time scales so we need a common reference to compare instants belonging to different logical time scales. This common reference is the real-time scale. For instance, for the FAS, operations GyroAcq, FDIR and PDE share the same logical time scale, where instants have a duration of 100ms, while Str Acq and TM/TC share another one, where instants have a duration of 10s. The programmer can describe the behaviour of the different operations separately with the usual synchronous approach, but when he assembles the operations he needs to be able to compare "fast" instants with "slow" instants. When we consider the real-time duration of instants, it is natural to say that one slow instant corresponds to 100 fast instants. Our model formalizes this idea by relating instants to real-time.

Real-time flows

Our synchronous real-time model relies on the Tagged-Signal Model [START_REF] Lee | Comparing models of computation[END_REF]. Given a set of values V, a flow is a sequence of pairs (v i , t i ) i∈N where v i is a value in V and t i is a tag in Q, such that for all i, t i < t i+1 . The clock of a flow f , denoted ck(f ), is its projection on Q. Similarly, val(f ) denotes the sequence of values of f (its projection on V). Two flows are synchronous if they have the same clock. A tag represents an amount of time elapsed since the beginning of the execution of the program. We say that a flow is present at date t if t appears in its clock, absent otherwise.

Following the relaxed synchronous hypothesis of [START_REF] Curic | Implementing Lustre programs on distributed platforms with real-time constraints[END_REF], at each activation a flow is required to be computed before its next activation. Thus, each flow has its own notion of instant and the duration of the instant t i is t i+1 -t i . Two flows are synchronous if the durations of their instants are the same. We can compare the durations of the instants of two flows to determine if a flow is faster than the other and more importantly, we can determine how much faster this flow is. For instance, in the example of the FAS, the outputs of Gyro Acq have clock (0, 100, 200, . . .), while the outputs of Str Acq have clock (0, 10000, 20000, . . .), so the outputs of Gyro Acq are 100 times faster.

Strictly Periodic Clocks

In this section we introduce a new class of clocks, strictly periodic clocks, along with specific transformations to handle transitions between different logical time scales.

Definition

A clock is a sequence of tags, we define a particular class of clocks called strictly periodic clocks as follows:

Definition 1. (Strictly periodic clock). A clock h = (t i ) i∈N , t i ∈ Q, is strictly periodic if and only if: ∃n ∈ Q + * , ∀i ∈ N, t i+1 -t i = n
n is the period of h, denoted π(h) and t 0 is the phase of h, denoted ϕ(h).

A strictly periodic clock defines the real-time rate of a flow. The notion of phase defined above is a little more general than usual as we do not impose that ϕ(h) < π(h). A strictly periodic clock is uniquely characterized by its phase and by its period: Definition 2. The term (n, p) ∈ Q + * × Q denotes the strictly periodic clock α such that:

π(α) = n, ϕ(α) = π(α) * p

Periodic clock transformations

We then define clock transformations specific to strictly periodic clocks, which produce new strictly periodic clocks: Definition 3. (Periodic clock division). Let α be a strictly periodic clock and k ∈ Q + * . "α/ . k" is a strictly periodic clock such that:

π(α/ . k) = k * π(α), ϕ(α/ . k) = ϕ(α)
Definition 4. (Periodic clock multiplication). Let α be a strictly periodic clock and k ∈ Q + * . "α * . k" is a strictly periodic clock such that:

π(α * . k) = π(α)/k, ϕ(α * . k) = ϕ(α)
Definition 5. (Phase offset). Let α be a strictly periodic clock and k ∈ Q. "α → . k" is a strictly periodic clock such that:

π(α → . k) = π(α), ϕ(α → . k) = ϕ(α) + k * π(α)

Strictly Periodic Clocks

Divisions and multiplications respectively decrease or increase the frequency of a clock while phase offsets shift the phase of a clock. This is illustrated in Fig. 3.1. Again, we do not constrain that ϕ(α → . k) < π(α). Let P be the set of strictly periodic clocks. From previous definitions we directly obtain Properties 1, 2 and 3.

Property 1. ∀α, β ∈ P:

• ∀k ∈ Q, α = β → . k ⇔ β = α → . -k; • ∀k ∈ Q + * , α = β/ . k ⇔ β = α * . k. Property 2. ∀(n, p) ∈ Q + * × Q: • ∀k ∈ Q + * , (n, p) * . k = ( n k , kp); • ∀k ∈ Q + * , (n, p)/ . k = (nk, p k ); • ∀k ∈ Q, (n, p) → . k = (n, p + k).
Property 3. The set of strictly periodic clocks P is closed under operations * . , / . and → . .

Integer Strictly Periodic Clocks

We defined the general model of strictly periodic clocks using dates in Q. However, to define a compilable language, we need to restrain to dates in N. Indeed operating systems rely on a discrete time model and there is always a lower bound on the level of granularity that can be used to describe time. For instance the date 1/3 does not exist in a real system, or is approximated. For the same reasons, scheduling theory, needed to implement our systems, only applies to dates and durations in N. We consequently constrain flow tags to be elements of N and the parameter k used in periodic clock divisions and periodic clock multiplications to be an element of N * . The parameter k used in phase offsets remains an element of Q. In the rest of the document we will only consider such integer strictly periodic clocks and we will refer to them simply as strictly periodic clocks.

In the set of integer strictly periodic clocks, clock (n, p) is a valid strictly periodic clock if and only if: n ∈ N + * , n * p ∈ N + . Indeed, clocks that do not verify this property refer to dates that are not in N. This restricted set is not closed under operations * . and → . anymore (but is still closed under / . ). We use notation k|k to say that k divides k (the rest of the integer division is 0). First, let (n, p) be a strictly periodic clock, if k|n then (n, p) * . k is a valid strictly periodic clock, but otherwise it is not, as this clock refers to dates which value is not in N. Second, if (p + k)n ∈ N + then (n, p) → . k is a valid strictly periodic clock, but otherwise it is not, as this clock refers to negative dates. The clock calculus, presented in Chap. 6.3, will check that no such invalid clock is used in a program.

Strictly periodic clocks and Boolean clocks

Strictly periodic clocks and their associated transformations are not meant for replacing the usual Boolean clocks of the synchronous languages. Our language actually uses both classes of clocks. These two classes correspond to complementary notions: strictly periodic clocks define the real-time frequency of a flow, while Boolean clocks define the activation condition of a flow.

To support Boolean clocks in our language, we adapt the definition of the Boolean clock operator on of [START_REF] Colaço | Clocks as first class abstract types[END_REF] to our model. The behaviour of operator on is defined inductively on the sequence of tags of a clock. Let the term t.s denotes the clock whose head is the tag t and whose tail is the sequence s. Similarly, let the term (v, t).s denotes the flow whose head has value v and tag t and whose tail is sequence s. For all clock α, for all Boolean flow c, α on c is a clock defined inductively as follows:

(t.ck) on ((true, t).c) = t.(ck on c)

(t.ck) on ((f alse, t).c) = ck on c
The operation α on c produces a new clock containing only the tags of α at which c is true. The opposite operation α on not c only keeps tags when c is false. The operations α on c or α on not c are valid only if c has clock α. The next chapter will illustrate how Boolean clocks and strictly periodic clocks can be combined in the same program.

Summary

In this chapter, we have defined:

• A synchronous model that explicitly relates instants to real-time;

• The class of strictly periodic clocks, which allow to define the real-time rate of a flow;

• Periodic clock transformations, which allow to compare the rates of two flows. This enables the specification of real-time properties in a synchronous context. These new concepts serve as the basis for the real-time operators defined in the next chapter.

Chapter 4 Language Syntax and Semantics

This chapter details the syntax and the semantics of our language. It is built as an extension of LUSTRE and borrows some constructions from LUCID SYNCHRONE [START_REF] Pouzet | Lucid Synchrone, version 3. Tutorial and reference manual[END_REF] and from recent versions of the SCADE language. It extends data-flow synchronous languages with high-level real-time primitives based on strictly periodic clocks.

Types polymorphism

The language supports types polymorphism, which is now a pretty common feature in many programming languages and in particular in recent synchronous languages. The type of the variables of the program can be left unspecified, in which case they will be inferred by the compiler (see Chap. 6). For instance: node infer(i:int) returns (o) let o=i; tel In this example, the compiler infers that the type of o is int.

The following example introduces types polymorphism: node poly(i,j) returns (o,p) let o=i; p=j; tel All the compiler can say is that o and i have the same type (say α), and that p and j have the same type (say β). The node poly can then be instantiated with different types, for instance the following program is correct: node inst(i:int; j:bool) returns (o,p,q,r) let (o,p)=poly(i,j); (q,r)=poly(j,i); tel

The compiler infers that o has type int, p has type bool, q has type bool and r has type int.

Real-time Primitives

Declaring real-time constraints

The language aims at specifying real-time constraints with a high-level of abstraction. The primitives we introduce are targeted for specifying constraints related to the environment of the system (physical constraints for instance) rather than constraints related to implementation concerns. The inputs and outputs of a node represent its interface with the node environment. Therefore, real-time constraints are specified on node inputs and outputs: they represent environmental constraints.

Periodicity constraints

The period of a node input or output can be specified as follows: node periodic(i: int rate (10,0)) returns (o: int rate (5,0)) let ... tel

x: rate (n,p) specifies that the clock of x is the strictly periodic clock (n, p). Thus, i has period 10 and o has period 5.

The user can also specify the phase of a periodic flow:

node phased(i: int rate (10,1/2)) returns (o: int rate (10,0)) let ... tel

In this example, i has period 10 and phase 1 2 , so its clock sequence is (5, 15, 25, 35, . . .).

Deadline constraints

A deadline constraint defines a deadline for the computation of a flow relative to the beginning of the period of the flow. Deadline constraints can be specified on node outputs: node deadline(i: int rate (10,0)) returns (o: int rate (10,0) due 8) let ... tel

x: due d specifies that x has a deadline constraint of d. If x is present at date t, then x must computed before date t + d.

Deadline constraints can also be specified on node inputs, though they have a slightly different meaning in this case: node deadline(i: int rate (10,0) before 2) returns (o: int) let ... tel

x: before d specifies that x should be acquired by the program before the deadline d (ie a deadline for the "production" of this input). In practice, this means that a sensor operation will have to fetch the input before this deadline. Operations that perform computations that require the input will read from the output of this sensor.

Rate transition operators

We define a series of rate transition operators, based on periodic clock transformations, that handle transitions between flows of different rates. These operators enable the definition of user-specified communication patterns between nodes of different rates. Our objective is to provide a small set of simple operators, which can be combined to produce complex communication patterns. Furthermore, the semantics of these operators is defined formally and their behaviour is completely deterministic.

First, we can under-sample a flow using operator /ˆ:

i i 0 i 1 i 2 i 3 i 4 i 5 i 6 ... o i 0 i 2 i 4 i 6 ...
i i 0 i 1 i 2 i 3 ... o i 0 i 0 i 1 i 1 i 2 i 2 i 3 ...

Phase offset operators

We define three different operators based on clock phase offsets. We can offset all the values of a flow by a fraction of its period using operator ∼>: node offset(i: int rate (10,0)) returns (o: int rate (10,1/2)) let o=i~>1/2; tel If e has clock α, e ∼> q delays each value of e by q * π(α) (with q ∈ Q + ). The clock of e ∼> q is α → . q. This operator will most often be used with q lower than 1, but values greater than 1 are accepted. The behaviour of this operator is illustrated in Fig. 4.3. date 0 5 10 15 20 25 30 ... The language also contains a concatenation operator "::": node init(i: int rate(10,0)) returns (o1: int rate(10,0); o2: int rate(10,0)) var v1,v2; let (v1,v2)=tail_twice(i); o1=0::v1; o2=0::0::v2; tel cst :: e produces cst one period earlier than the first value of e and then produces e. If e has clock α, then cst :: e has clock α → . -1. This is illustrated in Fig. 4.5. date 0 10 20 30 40 50 60 ... i i 0 i 1 i 2 i 3 i 4 i 5 i 6 ... v1 i 1 i 2 i 3 i 4 i 5 i 6 ... v2 i 2 i 3 i 4 i 5 i 6 ... o1 0 i 1 i 2 i 3 i 4 i 5 i 6 ... o2 0 0 i 2 i 3 i 4 i 5 i 6 ...

i i 0 i 1 i 2 i 3 ... o i 0 i 1 i 2 ...
i i 0 i 1 i 2 i 3 i 4 i 5 i 6 ... o1 i 1 i 2 i 3 i 4 i 5 i 6 ... o2 i 2 i 3 i 4 i 5 i 6 ...

Rate transitions on unspecified rates

The programmer can leave the rates of the inputs or outputs of a node unspecified, they will be inferred by the clock calculus. For instance:

node under_sample(i: int rate(10,0)) returns (o: int) let o=i/^2; tel

The clock calculus infers that o has clock (20, 0).

Furthermore, rate transition and phase offset operators can be applied to flows the rate of which is unknown. For instance, the following node definition is perfectly valid: node under_sample(i: int) returns (o: int) let o=i/^2; tel This program simply specifies that the rate of o is two times slower than the rate of i, regardless of what the rate of i may be. The actual rate of the flows will only be computed when instantiating the node. The node can even be instantiated with different rates in the same program, for instance: node poly(i: int rate (10, 0); j: int rate (5, 0)) returns (o, p: int) let o=under_sample(i); p=under_sample(j); tel

The clock inferred for o is (20, 0), while the clock inferred for p is (10, 0). Such clock polymorphism increases the modularity with which systems can be programmed.

Sensors and actuators

The programmer must specify the wcet for the acquisition of each system input (each input of the main node of the program), called a sensor operation, and for the production of each system output (each output of the main node of the program), called an actuator operation. For instance, the following program specifies that the acquisition of i takes 1 unit of time, the acquisition of j takes 2 units of time and the production of o takes 2 units of time: imported node A(i, j: int) returns (o: int) wcet 3; sensor i wcet 1; sensor j wcet 2; actuator o wcet 2; node N(i, j: int rate (10, 0)) returns (o: int rate (10, 0)) let o=A(i, j); tel

Delays

A flow can be delayed using the operator fby introduced in LUCID SYNCHRONE: node delay(i: int) returns (o: int) let o=0 fby i; tel cst fby e first produces cst and then the values of e delayed by the period of e. The clock of cst fby e is the same as the clock of e. This operator was borrowed from LUCID SYNCHRONE. Its behaviour is illustrated in Fig. 4.6. Notice that cst fby e is strictly equivalent to cst :: (e ∼> 1) and could be considered as syntactic sugar. We do however not use this rewriting duration compilation as it does not simplify the compilation much.

i i 0 i 1 i 2 i 3 i 4 i 5 i 6 ... o 0 i 0 i 1 i 2 i 3 i 4 i 5 ...

Combining Boolean and strictly periodic clock flow operators

The operator when can be applied to flows the clock of which is Boolean but also to flows the clock of which is strictly periodic. For instance, the following program is perfectly valid:

i i 0 i 1 i 2 i 3 i 4 i 5 i 6 ... j j 0 j 1 j 2 j 3 j 4 j 5 j 6 ... c
true true false true false false true ... i when c i 0 i 1 i 3 i 6 ... j whennot c j 2 j 4 j 5 ... o i 0 i 1 j 2 i 3 j 4 j 5 i 6 ... However, the opposite is not allowed, flow operators based on strictly periodic clocks cannot be applied to flows the clock of which contains a Boolean clock transformation. For instance the flow (x when c) * ^2 is rejected by the compiler. Indeed, this would require to apply a periodic clock transformation to a clock that is not strictly periodic, the semantics of which is undefined.

i i 0 i 1 i 2 ... i * ^2 i 0 i 0 i 1 i 1 i 2 i 2 ... o=(i * ^2) when c i 0 i 0 i 2 ...
By combining both classes of clocks, the programmer can first specify the base frequency of a process using strictly periodic clocks and then specify that on this frequency the process is activated only if a certain condition holds, using Boolean clocks.

Syntax

This section details the complete syntax of the language. It is close to LUSTRE, however we do not impose to declare types and clocks, which are computed automatically, similarly to LUCID SYNCHRONE (see Chap. 6). The language grammar is given below: A program consists of a list of declarations (decl). A declaration can either be a node, an actuator or a sensor declaration. Nodes can either be defined in the program (node) or implemented outside (imported node), for instance by a C function. Node durations must be provided for each imported node, more precisely the worst case execution times (wcet). The external code provided for imported nodes can itself be generated by a standard synchronous language compiler (like LUSTRE), in case developers want to program the whole system using synchronous languages. Sensor and actuator declarations specify the wcet of the actuators and sensors of the program (the main node inputs and outputs, see Sect. 9.2.5 for details).

The clock of an input/output parameter (in/out) can be declared strictly periodic (x : rate(n, p), x then has clock (n, p)) or left unspecified. The type of an input/output parameter can be integer (int), Boolean (bool) or unspecified. A deadline constraint can be imposed on input/output parameters (x : before n or x : due n, the deadline is n, relatively to the beginning of the period of the flow).

The body of a node consists of an optional list of local variables (var) and a list of equations (eq). Each equation defines the value of one or several variables using an expression on flows (var = e). Expressions may be immediate constants (cst), variables (x), pairs ((e, e)), initialised delays (cst fby e), applications (N (e)), Boolean sub-sampling (e when e), combination of flows on complementary Boolean clocks (merge (e, e, e)) or expressions using strictly periodic clocks. e/ˆk under-samples e using a periodic clock division and e * ˆk over-samples e using a periodic clock multiplication (k ∈ N * ). e ∼> q offsets the values of e by a factor q (q ∈ Q + ). tail(e) drops the first value of e. cst :: e concatenates cst to e. Values k, n, q must be statically evaluable.

Synchronous Temporised Semantics

The section gives the formal semantics of the language. The compilation process is defined so as to respect this semantics. Thus, the behaviour of the code generated for a program is completely predictable (it is the one defined by the present semantics), it is known before executing the actual program. This is an essential feature of formal languages.

Kahn's semantics

We provide a semantics based on Kahn's semantics on sequences [START_REF] Kahn | The semantics of simple language for parallel programming[END_REF]. It is a direct adaptation of the synchronous semantics presented in [START_REF] Colaço | Clocks as first class abstract types[END_REF] to the Tagged-Signal model. For any operator , # (s 1 , ..., s n ) = s means that the operator applied to sequences s 1 , ..., s n produces the sequence s . Term (v, t).s denotes the flow whose head has value v and tag t and whose tail is sequence s. Operator semantics is defined inductively on the argument sequences. We omit rules on empty flows, which all return an empty flow.

We will see that the semantics of some operators (for instance operator fby or when) is defined only for synchronous flows. This implies that if the flows are not synchronous, the semantics of the program is ill-defined. Therefore, we will later define a verification, called the clock calculus (Sect. 6.3), which will check that the program only combines synchronous flows and consequently that the semantics of the program is well-defined. Verifying that two flows are synchronous amounts to verifying that their clocks correspond to the same sequence of tags. As the sequence of tags of a clock is increasing, we can express the constraint that the arguments of an operator must be synchronous by requiring that at each induction step, the tags of the heads of the arguments are the same.

Semantics of classic synchronous operators

We start by redefining the semantics of classic synchronous operators in Fig. 4.9.

Synchronous Temporised Semantics

fby # (v, (v , t).s) = (v, t). fby # (v , s) when # ((v, t).s, (T, t).cs) = (v, t). when # (s, cs) when # ((v, t).s, (F, t).cs) = when # (s, cs) whennot # ((v, t).s, (T, t).cs) = whennot # (s, cs) whennot # ((v, t).s, (F, t).cs) = (v, t). whennot # (s, cs) merge # ((T, t).s, (v, t).s 1 , s 2 )) = (v, t).merge # (s, s 1 , s 2 )) merge # ((F, t).s, s 1 , (v, t).s 2 )) = (v, t).merge # (s, s 1 , s 2 ) Figure 4.9: Synchronous semantics of classical operators • x fby y concatenates the head of x to y, delaying the values of y by one tag;

• x when c produces a sub-sequence of x, only producing values of x when c is true;

• merge (c, x, y) combines flows x and y which are on complementary Boolean clocks: when c is true, x is present and y is absent. When c is false, x is absent and y is present. The result is present every time c is present (i.e. on the clock of c). It replaces the operator current of LUSTRE.

Semantics of strictly periodic clock operators

The synchronous semantics of operators on strictly periodic clocks is given in Fig. 4.10. These semantic rules are temporised in addition to being synchronous. Operator * ˆproduces values on tags that do not appear in the flow on which they are applied. These new tags must respect some specific temporal properties relating them to the tags of the parameter flow. Function π is extended to flows, π(f ) = π(α) where α is the clock of flow f . * ˆ#((v, t).s, k)

= k-1 i=0 (v, t i ). * ˆ#(s, k)
(where t 0 = t and t i+1 - • x/ˆk produces a flow k times slower than x, dropping part of the values of x;

t i = π(s)/k) /ˆ#((v, t).s, k) = (v, t)./ˆ#(s, k) if k * π(s)|t /ˆ#(s, k) otherwise ∼> # ((v, t).s, q) = (v, t ).∼> # (s, q) (where t = t + qπ(s)) tail # ((v, t).s) = s :: # (cst, s) = (cst, ϕ(s) -π(s)).s
• x ∼> q delays each value of x by qπ(x) temporal units;

• tail(x) produces a flow equal to x except that it drops the first value of x;

• cst :: e produces a flow resulting of the concatenation of value cst to e. The value cst is produced at date ϕ(e) -π(e).

Denotational semantics

The flows of a program are defined with respect to some flow variables, so we need to define how a program behaves when flow values (ie sequences of pairs (v i , t i )) are assigned to flow variables. To this intent, we define a denotational semantics [START_REF] Reynolds | Theories of Programming Languages[END_REF] for the rest of the constructions of the language. This denotational semantics is very close to that of [START_REF] Colaço | Clocks as first class abstract types[END_REF]. Let ρ denote an assignment mapping flow values to variable names. For instance, ρ(v) denotes the flow mapped to variable v. We define the denotation of an expression e by S ρ (e). Intuitively, S ρ (e) denotes the sequence obtained for e under assignment ρ. The notation is overloaded for equations. S ρ (e) is defined inductively in Fig. 4.11.

S ρ (x) = ρ(x) S ρ (cst fby e) = fby # (cst, S ρ (e))
S ρ (e 1 when e 2 ) = when # (S ρ (e 1 ), S ρ (e 2 )) S ρ (e 1 whennot e 2 ) = whennot # (S ρ (e 1 ), S ρ (e 2 )) S ρ (merge (e 1 , e 2 , e 3 ) = merge # (S ρ (e 1 ), S ρ (e 2 ), S ρ (e 3 )) S ρ (e 1 , e 2 ) = (S ρ (e 1 ), S ρ (e 2 )) • The denotation of a variable is the flow mapped to the variable in the assignment;

S ρ (x = e) = ρ[x ← x ∞ ] where x ∞ = f ix λy.S ρ[x←y] (e) S ρ (x 1 = e 1 ; x 2 = e 2 ) = S Sρ(x 1 =e 1 [e 2 /x 2 ]) (x 2 = e 2 ) S ρ ( node N (in) returns (out) let eqs tel ) = λy.S ρ[in←y] (eqs) S ρ (e 1 (e 2 )) = S ρ (e 1 )(S ρ (e 2 ))
• The denotation of an operator (fby, when, whennot, merge, * ˆ, / ˆ, ∼>, tail, ::) is the sequence obtained when applying the operator to the denotation of its arguments;

Chapter 5

Multi-periodic Communication Patterns

A key feature of the language is that it gives the programmer the freedom to choose the pattern used for communicating between operations of different rates, while most languages only allow a small set of predefined patterns. Rate transition operators apply very simple flow transformations but can be combined to produce various communication patterns. The formal definition of the language and the static analyses performed by the compiler ensure that only deterministic patterns are used. For instance the informal communication pattern "each operation consumes the last value produced before its execution" can lead to undeterministic program behaviours, as values produced by the program can vary depending on the actual scheduling of the program. It is thus not supported by our language. This chapter illustrates the expressiveness of the language through a series of communication patterns and shows that common patterns can easily be defined as generic library operators that can be reused in different programs. We illustrate the different communication patterns on the simple example of Fig. 5.1, where a fast operation F (of period 10ms), exchanges data with a slow operation S (of period 30ms). In addition, operation F exchanges data with the environment of the program.

Sampling

The simplest communication pattern that can be implemented with our language is based on data sampling. For instance, for the example of The slow operation under-samples the data produced by the fast operation, consuming only the first out of 3 successive values of the flow. On the opposite, the fast operation over-samples the data produced by the slow operation, each group of 3 successive repetitions of the fast operation consumes the same value. The slow to fast communication is delayed for two reasons. First, we need a delay either from S to F or from F to S, otherwise there is a causality loop between F and S, the compiler cannot find an order for the execution of the program that respects all the data-dependencies. Second, the delay avoids the reduction of the deadline of operation S. Indeed, let us consider an alternative implementation of the previous example: node sampling2(i: rate (10, 0)) returns (o) var vf, vs; let (o, vf)=F(i, vs * ^3); vs=S((0 fby vf)/^3); tel This version is also correct but an important difference with the previous version is that S must end before a relative deadline of 10ms, as it must end early enough for F to end before its own deadline (10ms). In the previous version, the delay avoids this restrictive deadline. The behaviour of these two examples is illustrated in Fig. 5 vs 0 vs 0 vs 0 vs 1 vs 1 vs 1 vs 2 vs 2 vs 2 ...

Queuing

Combining rate transition operators and LUSTRE arrays, we can implement different communication patterns based on data queuing. Though arrays are not directly present in the syntax we gave in Sect. 4.5, they can be implemented using a pre-processing phase, which expands arrays into independent variables [START_REF] Rocheteau | Pollux, a Lustre-based hardware design environment[END_REF] (an array of size n is expanded into n independent variables). This pre-processing is performed ... (0 fby vs) * ^3 0 0 0 vs 0 vs 0 vs 0 vs 1 vs 1 ... 0::((0 fby vs) * ^3) 0 0 0 0 vs 0 vs 0 vs 0 vs 1 vs 1 ... The node count_n used in this example is a counter modulo n (its sequence of values is 0, 1,

i i 0 i 1 i 2 i 3 i 4 ... A [0, 0, i 0 ] [0, i 0 , i 1 ] [i 0 , i 1 , i 2 ] [i 1 , i 2 , i 3 ] [i 2 , i 3 , i 4 ] ...
• • • , n - 1, 0, 1, • • • , n -1, • • • ).
The node split handles communications from slow to fast operations. It takes an array of size n as input and splits it into n successive values. The outputs are produced n times faster than the input. The node join handles communications from fast to slow operations. It joins n successive values of its input into an array. The output is n times slower than the input. This behaviour is illustrated below:

i i 0 i 1 i 2 i 3 i 4 i 5 i 6 ... i'=join(3,i,0) [0, 0, i 0 ] [i 1 , i 2 , i 3 ] [i 4 , i 5 , i 6 ] ... split(3,i') 0 0 i 0 i 1 i 2 i 3 i 4 ...
We can then define a different version of the example of Fig. 5.1 that uses a queuing communication pattern:

node queuing(i: rate (10, 0)) returns (o) var vf, vs; let (o, vf)=F(i, split(3, 0 fby vs)); vs=S(join(3, vf, 0)); tel This version requires flows vf and vs to be arrays of integers instead of simple integers and thus node S must return an array, not just an integer.

Mean value

We give a third example of communication pattern, which is an intermediate between sampling and queuing. Instead of queuing the successive values produced by the fast operations, we take their mean value: node mean_n(const n: int;i, init) returns (m: int) var n_values, n_sum: int^n;

let n_values=store_n(n, i, init); n_sum[0]=n_values[0]; n_sum[1..n-1]=n_values[1..n-1]+n_sum[0..n-2]; m=(n_sum[n-1]/n)/^n; tel
Notice that there is no causality loop in the third equation as each n_sum[i] depends on n_sum[i-1], which are independent variables. The behaviour of this node is illustrated in Fig. 5.5.

i 1 2 3 4 5 6 7 ... n_values [0, 0, 1] [0, 1, 2] [1, 2, 3] [2, 3, 4] [3, 4, 5] [4, 5, 6] [5, 6, 7] ... n_sum [0, 0, 1] [0, 1, 3] [1, 3, 6] [2,

Non-harmonic periods

The language is not restricted to systems where all the processes have harmonic periods. For instance, the following node is perfectly valid: The only constraints on task periods are those imposed by the clock calculus, ie that only flows of the same clock can be combined. For instance, we would not be allowed to directly compute the sum of o1 and o2, we would first have to use rate transition operators to bring the flows to the same clock. Each operation of the system is first declared as an imported node. The program then specifies the wcet of each sensor and each actuator of the system (for each input/output of the node FAS).

i i 0 i 1 i 2 ... i * ^2 i 0 i 0 i 1 i 1 i 2 ... i * ^3 i 0 i 0 i 0 i 1 i 1 i 1 i 2 ... i * ^3/^4 i 0 i 1 ...

A complete example

The node FAS is the main node of the program. It first specifies the inputs (gyro, gps, str, tc) and outputs (pde, sgs, gnc, pws, tm) of the system with their rates (for instance gyro has period 100) and deadline constraints (for instance gnc has deadline 300). The body of the node then specifies the real-time architecture of the system. For instance, the first equation says that the node Gyro_Acq computes the variable gyro_acq from the input gyro and from the variable tm. As tm is produced with a period of 10s while Gyro_Acq has a period of 100ms, we over-sample tm by a factor 100. We use a delay before performing the over-sampling, to avoid reducing the deadline of node TM_TC. Some flows are over-sampled without using a delay, for instance when the outputs of the acquisition nodes are consumed by faster nodes (gps_acq * ^10 in the equation of the FDIR for instance). This implies that the acquisition nodes will have a deadline much shorter than their period, which means that they execute less frequently but they must respond fast. Communications from fast to slow nodes are simply handled using the under-sampling operator /^. For instance, the node TM_TC has a period of 10s and consumes the flow fdir_tm, which is produced with a period of 100ms, so we under-sample fdir_tm by a factor 100. Finally, in the equation of node PWS we apply a phase offset of one half to its input gnc_pws, which causes the node PWS to execute with a phase of half its period (it has period 1000 and phase 500).

imported node Gyro_Acq ( gyro , t c : i n t ) r e t u r n s ( o : i n t ) wcet 3 ; imported node GPS_Acq ( gps , t c : i n t ) r e t u r n s ( o : i n t ) wcet 3 ; imported node S t r _ A c q ( s t r , t c : i n t ) r e t u r n s ( o : i n t ) wcet 3 ; imported node FDIR ( gyr , gps , s t r , gnc : i n t ) r e t u r n s ( t o _ p d e , t o _ g n c , t o _ t m : i n t ) wcet 1 5 ; imported node GNC_US( f d i r , gyr , gps , s t r : i n t ) r e t u r n s ( o : i n t ) wcet 2 1 0 ; imported node GNC_DS( u s : i n t ) r e t u r n s ( pde , s g s , pws : i n t ) wcet 3 0 0 ; imported node TM_TC( f r o m _ g r , f d i r : i n t ) r e t u r n s ( cmd : i n t ) wcet 1 0 0 0 ; imported node PDE ( f d i r , gnc : i n t ) r e t u r n s ( p d e _ o r d e r : i n t ) wcet 3 ; imported node SGS ( gnc : i n t ) r e t u r n s ( s g s _ o r d e r : i n t ) wcet 3 ; imported node PWS( gnc : i n t ) r e t u r n s ( p w s _ o r d e r : i n t ) wcet 3 ; s e n s o r g y r o wcet 1 ; s e n s o r g p s wcet 1 ; s e n s o r s t r wcet 1 ; s e n s o r t c wcet 1 ; a c t u a t o r pde wcet 1 ; a c t u a t o r s g s wcet 1 ; a c t u a t o r gnc wcet 1 ; a c t u a t o r pws wcet 1 ; a c t u a t o r tm wcet 1 ; node FAS ( g y r o : r a t e ( 1 0 0 , 0 ) ; g p s : r a t e ( 1 0 0 0 , 0 ) ; s t r : r a t e ( 1 0 0 0 0 , 0 ) ; t c : r a t e ( 1 0 0 0 0 , 0 ) ) r e t u r n s ( pde , s g s ; gnc : due 3 0 0 ; pws , tm ) var g y r o _ a c q , g p s _ a c q , s t r _ a c q , f d i r _ p d e , f d i r _ g n c , f d i r _ t m , gnc_pde , g n c _ s g s , gnc_pws ; l e t g y r o _ a c q = Gyro_Acq ( gyro , ( 0 f b y tm ) * ^1 0 0 ) ; g p s _ a c q = GPS_Acq ( gps , ( 0 f b y tm ) * ^1 0 ) ; s t r _ a c q = S t r _ A c q ( s t r , 0 f b y tm ) ; ( f d i r _ p d e , f d i r _ g n c , f d i r _ t m ) = FDIR ( g y r o _ a c q , g p s _ a c q * ^1 0 , s t r _ a c q * ^1 0 0 , ( 0 f b y gnc ) * ^1 0 ) ; gnc =GNC_US( f d i r _ g n c / ^1 0 , g y r o _ a c q / ^1 0 , g p s _ a c q , s t r _ a c q * ^1 0 ) ; This version assumes different signatures for the imported nodes of the system. Basically, nodes executing at 10Hz are assumed to consume and produce simple integer values, nodes executing at 1Hz are assumed to consume and produce arrays of 10 integers and nodes executing at 0.1Hz are assumed to consume and produce arrays of 100 integers. Then, over-sampling operators ( * ˆ) are replaced by calls to the node split, while under-sampling operators (/ˆ) are replaced by calls to the node join. Of course, we could have mixed different communications patterns in the same program (for instance, mixing data-queuing and data-sampling).

Summary

In this chapter, we have illustrated the expressiveness of the language and emphasized the ability to define various communication patterns instead of being restricted to a limited set of predefined patterns. Complex patterns (such as data-sampling for instance) could easily be defined as library nodes and reused in different program. This reusability relies on types polymorphism and more importantly on 5.6. Summary imported node Gyro_Acq ( gyro , t c : i n t ) r e t u r n s ( o : i n t ) wcet 3 ; imported node GPS_Acq ( gps , t c : i n t ^1 0 ) r e t u r n s ( o : i n t ^1 0 ) wcet 3 ; imported node S t r _ A c q ( s t r , t c : i n t ^1 0 0 ) r e t u r n s ( o : i n t ^1 0 0 ) wcet 3 ; imported node FDIR ( gyr , gps , s t r , gnc : i n t ) r e t u r n s ( t o _ p d e , t o _ g n c , t o _ t m : i n t ) wcet 1 5 ; imported node GNC_US( f d i r , gyr , gps , s t r : i n t ^1 0 ) r e t u r n s ( o : i n t ^1 0 ) wcet 2 1 0 ; imported node GNC_DS( u s : i n t ^1 0 ) r e t u r n s ( pde , s g s , pws : i n t ^1 0 ) wcet 3 0 0 ; imported node TM_TC( f r o m _ g r , f d i r : i n t ^1 0 0 ) r e t u r n s ( cmd : i n t ^1 0 0 ) wcet 1 0 0 0 ; imported node PDE ( f d i r , gnc : i n t ) r e t u r n s ( p d e _ o r d e r : i n t ) wcet 3 ; imported node SGS ( gnc : i n t ^1 0 ) r e t u r n s ( s g s _ o r d e r : i n t ^1 0 ) wcet 3 ; imported node PWS( gnc : i n t ^1 0 0 ) r e t u r n s ( p w s _ o r d e r : i n t ^1 0 0 ) wcet 3 ; s e n s o r g y r o wcet 1 ; s e n s o r g p s wcet 1 ; s e n s o r s t r wcet 1 ; s e n s o r t c wcet 1 ; a c t u a t o r pde wcet 1 ; a c t u a t o r s g s wcet 1 ; a c t u a t o r gnc wcet 1 ; a c t u a t o r pws wcet 1 ; a c t u a t o r tm wcet 1 ; node FAS ( g y r o : r a t e ( 1 0 0 , 0 ) ; g p s : r a t e ( 1 0 0 0 , 0 ) ; s t r : r a t e ( 1 0 0 0 0 , 0 ) ; t c : r a t e ( 1 0 0 0 0 , 0 ) ) r e t u r n s ( pde , s g s ; gnc : due 3 0 0 ; pws , tm ) var g y r o _ a c q , g p s _ a c q , s t r _ a c q , f d i r _ p d e , f d i r _ g n c , f d i r _ t m , gnc_pde , g n c _ s g s , gnc_pws ; l e t g y r o _ a c q = Gyro_Acq ( gyro , s p l i t ( 1 0 0 , s p l i t ( 1 0 0 , ( 0 f b y tm ) ) ; g p s _ a c q = GPS_Acq ( gps , s p l i t ( 1 0 , ( 0 f b y tm ) ) ; s t r _ a c q = S t r _ A c q ( s t r , 0 f b y tm ) ; ( f d i r _ p d e , f d i r _ g n c , f d i r _ t m ) = FDIR ( g y r o _ a c q , s p l i t ( 1 0 , g p s _ a c q ) , s p l i t ( 1 0 0 , s t r _ a c q ) , s p l i t ( 1 0 , ( 0 f b y gnc ) ) ; gnc =GNC_US( j o i n ( 1 0 , f d i r _ g n c , 0 ) , j o i n ( 1 0 , g y r o _ a c q , 0 ) , g p s _ a c q , s p l i t ( 1 0 , s t r _ a c q ) ) ; ( gnc_pde , g n c _ s g s , gnc_pws ) =GNC_DS( gnc ) ; pde = PDE ( f d i r _ p d e , s p l i t ( 1 0 , ( 0 f b y g n c _ p d e ) ) ; s g s = SGS ( g n c _ s g s ) ; pws=PWS( gnc_pws ~> 1 / 2 ) ; tm = TM_TC( t c , j o i n ( 1 0 0 , f d i r _ t m , 0 ) ; t e l Chapter 6

Static Analyses

The language is targeted for critical embedded systems, therefore strong emphasis is put on the verification of the correctness of the program that must be compiled. Before compiling the input program into lower-level code, a series of static analyses is performed, which ensure that the semantics of the program to compile is well-defined. Each static analysis ensures different correctness properties for the program. The causality analysis verifies that there exists an order for the evaluation of the expressions of the program that respects all the data-dependencies of the program (ie that data-dependencies are not cyclic). The typing verifies that only flows of the same type are combined. The clock calculus verifies that only synchronous flows are combined. Finally, the initialisation analysis verifies that the program does not access to values that are not initialised. If all the static analyses succeed, then the program has a well-defined semantics, specified by the semantic rules of Sect. 4.6.

Causality Analysis

The order in which the variables of a program must be computed depends on the data-dependencies between the variables. For instance, the variable defined by the equation x = e cannot be evaluated before the variables appearing in e have been evaluated. Equations are ordered according to the syntactic data-dependencies of the program [START_REF] Halbwachs | Generating efficient code from data-flow programs[END_REF]: an expression is considered to depend on all the variables appearing in it, except if the variable appears as an argument of a fby operator. Thus, an equation of the form x = e can only be evaluated after the equations defining the variables e syntactically depends on have been evaluated.

The causality analysis verifies that the program does not contain cyclic definitions: a variable cannot instantaneously depend on itself (i.e. not without a fby in the dependencies). For instance, if imp is an imported node, the equation x=imp(x,1); is incorrect. It is similar to a deadlock since we need to evaluate imp(x,1) to evaluate x and we need to evaluate x to evaluate imp(x,1).

The causality analysis is based on syntactic data-dependencies instead of semantic data-dependencies. This restriction may reject correct programs such as the following, which is actually causal: x=merge(c,y when c,3); y=merge(c,2,x whennot c);

Unfortunately, in the general case, a semantic causality analysis is undecidable.

Typing

The language is strongly typed in the sense of [START_REF] Cardelli | Typeful programming[END_REF], that is the execution of a program cannot produce a run-time type error. Each flow has a single, well-defined type and only flows of the same type can be combined. The typing of the language is fairly standard, it relies on well-known type-inference techniques that can be found in [START_REF] Pierce | Types and programming languages[END_REF]. We present the typing in details as the same techniques will be used for the clock calculus in a more complex setting.

Types

The typing produces type judgements of two different forms. First, E e : t, means that "the expression e has type t in environment E". Second, E eq means that "the equation eq is well-typed", but no type is associated to it. This second form is also used for sets of equations, untyped declarations (sensors and actuators) and sequences of declarations. The grammar of types is given below:

σ ::= ∀α 1 , ..., α m .t t ::= t → t | t × t | base | α base ::= int | bool | rat E ::= [x 1 : σ 1 , ..., x m : σ m ]
Type schemes (σ) are type expressions (t) quantified over a set of type variables (α). Type expressions can be functional types (t → t), type products (t × t), base types (base), or type variables (α). Base types are either integers (int), Booleans (bool) or rationals (rat). Typing environments (E) associate types to flow variables and node definitions. They can be considered as functions, which associate a type to variable names or node names. Thus, E(x) denotes the type associated to x in environment E and Dom(E), the domain of E, is the set of names that are defined in E (that have an associated type).

Types may be generalized (at a node definition) and instantiated (at a node call) as follows:

inst(∀α.t) = t[t /α] gen E (t) = ∀α 1 , ..., α m .t, where α 1 , ..., α m = F T V (t)\F T V (E)
This states that a type scheme is instantiated by replacing type variables by type expressions and that any type variable can be generalized if it does not appear free in the environment (F T V stands for "free type variables"). For instance, the type scheme ∀α.α × α → α represents the type of a polymorphic node that takes two arguments of the same type and returns a value of this type. If this node is called with a first argument of type int, then the type scheme is instantiated to α × α → α[int/α] = int × int → int, so the two arguments of the node call must be integers and it returns an integer. The same node can later be called with arguments of a different type, which will produce a different type instance (bool × bool → bool for example). This type scheme is the type of operator fby for instance.

Types inference

For each expression of the program, the typing produces constraints that have to be met for the expression to be well-typed. These constraints are produced by inference rules. A rule A B states that the type judgement B, called the conclusion, holds if the condition A, called the premise holds. The inference rules of the language are given in Fig. 6.1. Programs are typed in an initial environment E init that contains the constants of the language with their types.

Typing

(CONST) cst ∈ dom(E) E cst : E(cst) (VAR) x ∈ dom(E) E x : E(x) (×) E e 1 :
(IMP-NODE) in : t 1 out : t 2 gen ∅ (t 1 ) = t 1 gen ∅ (t 2 ) = t 2 E imported node N (in) returns (out) wcet n : t 1 → t 2 (SENSOR) E sensor x wcet n (ACTUATOR) E actuator x wcet n (DECLS-NODE) E [ imported ] node N [...] : σ E, N : σ decl 2 E [ imported ] node N [...] decl 2 (DECLS-OTHER) E decl 1 E decl 2 E decl 1 decl 2 Figure 6
.1: Type inference rules

• A constant is well-typed if it is declared in the (initial) typing environment (rule (CONST));

• The same is true for variables (rule (VAR)). Variables are added to the typing environment by rule (NODE) (see below);

• The type of a pair is the product of the types of its components (rule (×));

• Operator fby takes two arguments of the same type and the result is a value of this type;

• The first argument of operator when can have any type t, while the second must have type bool.

The result has type t;

• The first argument of operator merge must have type bool. Its second and third arguments must have the same type t and the result also has type t;

• The first argument of operator /ˆcan have any type t, while the second must have type int. The result has type t. The same goes for operator * ˆ;

• Operator ∼> has the same type as /ˆor * ˆ, except that its second argument must have type rat;

• The result of operator tail has the same type as the argument;

• Operator :: takes two arguments of the same type and the result is a value of this type;

• The rule ((APP)) states that the arguments of a node call must have the type expected for the inputs of the node. The result of the node call has the type of the outputs of the node. The type of the node is instantiated during the node call, in case it is polymorphic (quantified);

• An equation is well-typed if its left hand side and its right hand side have the same type (rule (EQ));

• A set of equations is well-typed if each equation of the set is well-typed;

• The rule (NODE) states that to type a node declaration, we first add the input, output and local variables of the node in the typing environment. The type of each variable in the environment is the type declared in the node if any, or a fresh type variable otherwise. Then, we type the set of equations of the node in this new typing environment. Finally, the type of the node is a functional type, from the type of its inputs to the type of its outputs. This type is generalized to support types polymorphism;

• The rule (IMP-NODE) states that an imported node cannot have a polymorphic type. This is imposed by the premises gen ∅ (t 1 ) = t 1 and gen ∅ (t 2 ) = t 2 , which imply that t 1 and t 2 contain no type variable. The type of the node is a functional type, from the type of its inputs to the type of its outputs;

• Sensor (SENSOR) and actuator (ACTUATOR) declarations are always well-typed (no premisse);

• The rule (DECLS-NODE) states that a sequence of declarations consisting of the declaration of a node N (imported or not) followed by another declaration is well-typed if the node declaration is well-typed, has type σ, and if the second declaration is well-typed in an environment where the node N has type σ;

• For other kinds of declarations (DECLS-OTHER), a sequence of declarations is well-typed if the different declarations of the sequence are well-typed.

Types unification

The inference rules produce constraints for each expression of the program. The set of constraints produced for a program is then solved following the technique of [START_REF] Rémy | Extending ML type system with a sorted equational theory[END_REF], where typing judgements are treated as unificands, i.e. typing problems are reduced to unification problems. The resolution algorithm consists in trying to find substitutions σ such that σ(E) e : σ(t), ie substitutions that unify E e : t.

Unification rules are given in Fig. 6.2. A rule

A B
means that to solve B we must solve A. t 1 ∼ t 2 means that t 1 and t 2 are unifiable, that is to say, there exists a substitution σ such that σ(t 1 ) = σ(t 2 ).

(→) • Rules (→) and (×) state that unification is performed recursively;

t 1 ∼ t 1 t 2 ∼ t 2 t 1 → t 2 ∼ t 1 → t 2 (×) t 1 ∼ t 1 t 2 ∼ t 2 t 1 × t 2 ∼ t 1 × t 2 (VAR) α ∈ F T V (t) t → α α ∼ t
• Rule (VAR) states that we can unify a type variable with a type t if α does not appear (free) in t.

The unification then consists in substituting t for α (t → α);

Typing

• Of course, int = int is unifiable and the same goes for bool and rat.

The typing fails if we cannot find a substitution that unifies all the constraints produced by the inference rules. Otherwise, the result of the substitution produces the validated types of the program.

Examples

We consider the simple example below: imported node imp(i, j: int) returns (o: bool) wcet 5; node works(i, j) returns (o: bool) let o=imp(i, j); tel

The rule (IMP-NODE) can be applied to infer the type of node imp:

(IMP-NODE) i, j : int × int o : bool gen ∅ (int × int) = int × int gen ∅ (bool) = bool E init imported node imp(i, j) returns (o) wcet n : int × int → bool
This produces a new typing environment E 0 = imp : int × int → bool, E init . Then, for node works, the rule (NODE) says that we must type the set of equations of the node in environment E 1 = i : t 1 , j : t 2 , o : bool, E 0 , where t 1 and t 2 are fresh type variables:

(EQ) E 1 o : bool (APP) E 1 imp : int × int → bool E 1 i, j : int × int E 1 imp(i, j) : bool E 1 o = imp(i, j)
When we apply rule (APP), we unify type t 1 × t 2 with type int × int (the type of (i, j)), which produces the substitutions {int/t 1 , int/t 2 }. The typing of the body of node works succeeds and the type of the node after applying substitutions is int × int → bool.

We consider the slightly different node definition:

node fails(i, j) returns (o) var v; let v=imp(i, j); o=imp(v, i); tel
The equations must be typed in environment E 2 = i : t 1 , j : t 2 , o : t 3 , v : t 4 , E 0 . For the first equation:

(EQ) E 2 v : bool (APP) E 2 imp : int × int → bool E 2 i, j : int × int E 2 imp(i, j) : bool E 2 v = imp(i, j)
This produces substitutions {int/t 1 , int/t 2 , bool/t 4 }. So, we type the second equation in environment

E 3 = i : int, j : int, o : t 3 , v : bool, E 0 : (EQ) E 3 o : bool (APP) E 3 imp : int × int → bool E 3 v, j :? E 3 imp(v, j) : bool E 3 o = imp(v, j)
The top-right premise cannot be proved. It requires v, j to have type int × int, while E 3 states that v, j has type bool × int. The typing fails because types int × int and bool × int are not unifiable.

As a last example, the type inferred for the main node of the FAS program of Fig. 5.7 is:

(int × int × int × int) → (int × int × int × int × int)

Clock Calculus

The clock calculus verifies that a program only combines flows that have the same clock. When two flows have the same clock, they are synchronous (always present at the same instants). Combining nonsynchronous flows leads to non-deterministic programs that access to undefined values. We say that an expression is well-synchronized if it only combines flows on the same clocks, ill-synchronized otherwise.

The clock calculus verifies that a program only uses well-synchronized expressions.

For each expression of the program, the clock calculus produces constraints that have to be met for the expression to be well-synchronized. As shown in [START_REF] Colaço | Clocks as first class abstract types[END_REF], the clock calculus can use a type system to generate these constraints. The overall structure of the clock calculus is therefore very similar to that of the typing presented in Sect. 6.2. Clocks are represented by types, called clock types. Inference rules represent the constraints the clocks of an expression need to respect for the expression to be wellsynchronized. Finally, clocks unification tries to solve the set of all the constraints generated for the program. If a solution exists, the program is well-synchronized, otherwise it is ill-synchronized.

Clock Types

The clock calculus produces type judgements of two different forms. First, H e : cl, means that "the expression e has clock type cl in environment H". Second, H eq means that "the equation eq is wellsynchronized", but no clock type is associated to it. This second form is also used for sets of equations, untyped declarations (sensors and actuators) and sequences of declarations. The grammar of clock types is given below: A clock sequence can be the result of a Boolean clock under-sampling (ck on c, ck on not c) or a strictly periodic clock (pck). A strictly periodic clock can be a constant strictly periodic clock ((n, p)), a clock variable (α), a strictly periodic clock over-sampled periodically (pck * . k), a strictly periodic clock under-sampled periodically (pck/ . k), or the result of a phase offset (pck → . k). In other words, a clock sequence ck is a linear expression resulting of a succession of clock transformations (either strictly periodic or Boolean) on another clock sequence ck . In the following, ck is called an abstract clock if ck is a clock variable. It is called a concrete clock if ck is a constant strictly periodic clock. The values n and p are constant integer values. Integer values k and rational values q must be computable statically.

A carrier (c) can be a name (nm) or a carrier variable (X). For instance, in the node declaration node N(c: bool; i: (10,0) when c) returns (o), c is a carrier variable, it de-notes a clock condition that will be known only when instantiating the node N. In the set of equations c=and(c1,c2); o=i when c, c is a carrier name, it denotes the clock condition and(c1,c2). This approach, used in [START_REF] Colaço | Clocks as first class abstract types[END_REF], allows to simplify the clock calculus by considering clock conditions as mere names instead of arbitrary complex Boolean expressions.

Environments (H) associate clock types to flow variables and node definitions. As for typing environments, they can be considered as functions, which associate a clock type to variable names or node names.

Finally, clocks can be generalized (at a node definition) and instantiated (at a node call) as follows:

inst(∀α <: C.cl) = cl[(cl ∈ C)/α] gen H (cl) = ∀α 1 <: C 1 , ..., α m <: C m .cl, where α 1 , ..., α m = F T V (cl)\F T V (H)
This states that a clock scheme is instantiated by replacing clock variables by clocks belonging to the correct clock subtypes and that any clock variable can be generalized if it does not appear free in the environment.

A clock variable α can be constrained to belong to some clock subtype C i , which is denoted by α <: C i . Such a clock subtyping constraint states that only clock types belonging to C i can be substituted for α. For instance, if x has clock α, then x * ˆk is well-synchronized only if k|π(α), because otherwise the period of x * ˆk is not an integer (see Sect. 3.2.3). Such sub-typing constraints are called bounded types quantifications in [START_REF] Pierce | Types and programming languages[END_REF].

Let C denote the set of all clock types. The two main clock subtypes of C are Boolean clocks B (clocks containing the operator on) and strictly periodic clocks P. Clock type P contains subtypes

P k, a b , k ∈ N * , a b ∈ Q + ,
with a ∧ b = 1 (a and b are co-prime) and b|k, where:

P k, a b = {(n, p)|(k|n) ∧ p ≥ a b }
In other words, the set P k, a b contains all the strictly periodic clocks α for which transformations α * . k and α → . -a b produce valid strictly periodic clocks. Notice that P = P 1,0 . The sub-typing relation <: on clock types is defined as follows:

• B <: C, P <: C;

• P k,q <: P k ,q ⇔ k |k ∧ q ≥ q .
The set of all subsets of P ordered by the subset inclusion <: forms a lattice. This allows to solve subtyping constraints efficiently, by replacing two constraints t <: C 1 and t <: C 2 by t <: C 1 ∩ C 2 ie the least upper bound of the sets C 1 and C 2 (see Sect. 6.3.4 for details).

Clock inference Strictly periodic clock parent

The language allows to mix freely Boolean and strictly periodic clocks. As our objective is to schedule programs efficiently, we need to be able to compute a period for each flow of the program. When the clock of a flow contains Boolean transformations, we need to approximate the clock to a strictly periodic clock to be able to compute the period of the flow. To this intent, we first define the over-approximation of a clock to its closest strictly periodic clock parent: Definition 6. The strictly periodic clock parent of a clock is defined recursively as follows:

pparent(α on c) = pparent(α) pparent(pck) = pck
Then, we need to ensure that the clocks of a program are always derived, at least indirectly, from a strictly periodic clock. All the clocks of a program are derived indirectly from the clocks of the inputs of the main node, thus, in addition to the inference rules below, the clock calculus requires the clocks of the inputs of the main node of a program to be strictly periodic clocks. This ensures that for any clock ck, pparent(ck) always returns a strictly periodic clock. So, we are able to approximate the clock of any flow to a strictly periodic clock and we can compute a period for each flow of the program.

Inference rules

We give the clock inference rules in Fig. 6 • A constant can have any clock (rule (CONST));

• A variable must be declared in the environment (rule (VAR));

• The clock of a pair is the product of the clock of its components (rule (×));

• Operator fby takes two arguments of the same clock and the result also has this clock;

• The two arguments of operator when must be present at the same instants (on clock ck). The judgement H c : (c : ck) states that the expression c defines a clock condition c and that this condition has clock ck. This notation makes the clock condition transported by the expression appear clearly in its clock, which allows to use this condition in other clock expressions (ck on c for instance). The result of e when c is present only at the dates defined by ck at which c is true, which is denoted ck on c;

• The constraints are the same for operator whennot, except that the result is present only at the dates defined by ck at which c is false, which is denoted ck on not c;

• The first argument of operator merge specifies a sampling condition c that has clock ck. The two other arguments have complementary clocks: the second argument is present at the dates defined by clock ck for which c is true and the third is present at the dates defined by clock ck for which c is false. The result is present on clock ck;

• Operator * ˆcan only be applied to an expression the clock of which is a subtype of P k,0 . This ensures that the period of the resulting clock is an integer. The result is k times faster than the argument;

• Operator / ˆcan only be applied to any expression the clock of which is strictly periodic. The result is k times slower than the argument;

• The parameter q of operator ∼> denotes a rational a b for which a ∧ b = 1. The constraint pck <: P b,0 ensures that the phase of pck → . q is an integer;

• Operator tail can be applied to any expression the clock of which is strictly periodic. The phase of the result is increased by 1;

• Operator :: can only be applied to an expression the clock of which is a subtype of P 1,1 . This ensures that the phase of the result is positive as the phase of the result is decreased by 1;

• The rule (APP) states that the arguments of a node call must have the clock expected for the inputs of the node. The result of the node call has the clock of the outputs of the node. The clock of the node is instantiated during the node call, in case it is polymorphic (quantified). During the instantiation, the clock calculus verifies that the clocks of the inputs provided for the node belong to the correct clock subtype (see below for details);

• An equation is well-synchronized if its left hand side and its right hand side have the same clock (rule EQ);

• A set of equations is well-synchronized if each equation of the set is well-synchronized;

• The rule (NODE) states that to perform the clock calculus of a node declaration, we first add the input, output and local variables of the node in the environment. The clock of each variable in the environment is the clock declared in the node if any, or a fresh clock variable otherwise. Then, we perform the clock calculus of the set of equations of the node in this new environment. Finally, the clock of the node is a functional clock, from the clock of its inputs to the clock of its outputs;

• The rule (IMP-NODE) requires the strictly periodic clock parent of all the inputs and outputs of an imported node to be the same. This implies that the inputs and outputs can have different clock activation conditions but if we do not consider clock conditions, they have the same strictly periodic clocks. For instance: imported node I(c: bool; i: int when c) returns (o: int) has clock (c : α) × α on c → α, which is valid as the strictly periodic clock parent of every input/output is α. On the opposite: imported node I(i: int rate(10,0)) returns (o: int rate (5,0)) has clock (10, 0) × (5, 0), which is invalid. This restriction allows to compute a single activation rate (period and phase) for each imported node instantiation;

• Sensor (SENSOR) and actuator (ACTUATOR) declarations are always well-synchronized (no premisse);

• The rule (DECLS-NODE) states that a sequence of declarations consisting of the declaration of a node N (imported or not) followed by another declaration is well-synchronized if the node declaration is well-synchronized, has clock σ, and if the second declaration is well-synchronized in an environment where the node N has clock σ;

• For other kinds of declarations (DECLS-OTHER), a sequence of declarations is well-synchronized if the different declarations of the sequence are well-synchronized.

Clocks unification

Equivalence of strictly periodic clock expressions Periodic clock transformations are such that different strictly periodic clock expressions may correspond to the same clock. Thus, clocks unification requires the definition of an equivalence test on strictly periodic clock expressions. This test relies on a term rewriting system. We show that testing the equivalence of two strictly periodic clock expressions can be reduced to testing the equality of their normal forms. The terminology and proof techniques we use in this section can be found in [START_REF] Baader | Term rewriting and all that[END_REF]. Let E P denote the set of expressions on strictly periodic clocks, that is expressions obtained by applying a succession of periodic clock transformations on a strictly periodic clock. The set E P is the set of clocks types pck defined in the clock types grammar of Sect. 6.3.1. The following arithmetical properties are derived from the definition of periodic clock transformations: Property 4. ∀α ∈ P:

• ∀k, k ∈ N + * , α * . k * . k = α * . kk ; • ∀k, k ∈ N + * , α/ . k/ . k = α/ . kk ; • ∀q, q ∈ Q, α → . q → . q = α → . (q + q ); • ∀k, k ∈ N + * , α/ . k * . k = α * . k / . k; • ∀q ∈ Q, ∀k ∈ N + * , α → . q * . k = α * . k → . qk; • ∀q ∈ Q, ∀k ∈ N + * , α → . q/ . k = α/ . k → . (q/k).
We can define a rewriting system R P to simplify expressions in E P based on these properties: Definition 7. The rewriting system R P on expressions in E P is defined as:

α * . k * . k → α * . kk (6.1) α/ . k/ . k → α/ . kk (6.2) α → . q → . q → α → . (q + q ) (6.3) α/ . k * . k → α * . k / . k (6.4) α → . q * . k → α * . k → . qk (6.5) α → . q/ . k → α/ . k → . (q/k) (6.6)
Property 5. The rewriting system R P is convergent.

For R P to be convergent, it must terminate and it must be confluent.

Termination. The principle of the proof is to define a strict order < E P on E P and to prove that for all rewriting rule l → r, r < E P l. This is a sufficient condition for R P to terminate (see [START_REF] Baader | Term rewriting and all that[END_REF] for details).

To this intent, we introduce the following notations, for e in E P :

• |e| * . denotes the number of operations * . appearing in e;

• |e| /. denotes the number of operations / . appearing in e;

• |e| →. denotes the number of operations → . appearing in e;

• [e] * . denotes the number of operations preceding the first operation * . appearing in e. For instance, [α/ . n → . q * . n / . n * . n ] * . = 2;

• Similarly, [e] /. denotes the position of the first operation / . appearing in e.

< E P is then the order defined as: It is then trivial to prove that for all rewriting rule l → r of R P , we have r < E P l. Thus, R P terminates.

Confluence. As R P terminates, to prove its confluence we simply prove the local confluence of its critical pairs (see [START_REF] Baader | Term rewriting and all that[END_REF] for details). Two rules l 1 → r 1 and l 2 → r 2 form a critical pair if they overlap, that is to say if there exists a sub-term s 1 of l 1 (resp. s 2 of l 2 ) that is not a variable, and a unifier (a term substitution) θ such that θ(s 1 ) = θ(r 2 ) (resp. θ(s 2 ) = θ(r 1 )). Unification is applied after renaming variables such that V ars(l 1 , r 1 ) ∩ V ars(l 2 , r 2 ) = {}. There are only two critical pairs in the system R P :

• Rule 6.3 and Rule 6.5 overlap. Indeed, we can substitute β → . q → . q for α → . q in the second rule. We must then verify that the different possible rewritings for β → . q → . q * . k lead to the same final result. If we start by applying the first rule:

β → . q → . q * . k → β → . (q + q ) * . k → β * . k → . (q + q )k
If we start by applying the second rule:

β → . q → . q * . k → β → . q * . k → . (q k) → β * . k → . (q k) → . (q k) → β * . k → . (q + q )k
So this critical pair is locally confluent.

• Similarly, Rule 6.3 and Rule 6.6 overlap by substituting β → . q → . q for α → . q in the second rule. We must check the different rewritings for β → . q → . q / . k. If we start by applying the first rule:

β → . q → . q / . k → β → . (q + q )/ . k → β/ . k → . (q + q )/k
If we start by applying the second rule:

β → . q → . q / . k → β → . q / . k → . (q /k) → β/ . k → . (q /k) → . (q /k) → β/ . k → . (q + q )/k
All the critical pairs of R P are locally confluent, so R P is confluent.

As R P is convergent, any expression e of E P has a unique normal form, denoted N F R P (e). Furthermore:

Lemma 1. For all clock expression e ∈ E P , N F R P (e) is of the form:

N F R P (e) = α * . k/ . k → . q
with k, k ∈ N and q ∈ Q Proof. This amounts to saying that for any expression e in E P , we can always apply a rewriting rule of R P if the following condition holds:

|e| * . > 1 ∨ |e| /. > 1 ∨ |e| →. > 1 ∨ [e] * . > 0 ∨ [e] /. > 1
We consider each possibility separately:

• |e| * . > 1: then either two operations * . appear next to each other in the expression and we can apply rule 6.1 or no operation * . appear next to each other in the expression and we can apply rules 6.5 or 6.4;

• |e| /. > 1: then either two operations / . appear next to each other in the expression and we can apply rule 6.2 or no operation / . appear next to each other in the expression and we can apply rules 6.6;

• [e] * . > 0: then we can apply rule 6.2, 6.1, 6.3, 6.4 or 6.5;

• [e] /. > 1: then we can apply rule 6.2, 6.1, 6.3 or 6.6;

In some cases, we can further simplify the normal form of a strictly periodic clock expression. We use the following property to define the canonical form of a strictly periodic clock expression in normal form: Property 6. For all m, k, k in N * , for all q in Q, if m|k and m|k , then:

α * . k/ . k → . q = α * . (k/m)/ . (k /m) → . q
Proof. Using the definitions of periodic clock transformations and Property. 4, we have:

α * . (k/m)/ . (k /m) → . q = α * . k/ . m/ . k * . m → . q = α * . k/ . m * . m/ . k → . q = α * . k/ . k → . q
From this property, we deduce an equivalence relation on clocks in normal forms: Property 7. Let e 1 , e 2 be strictly periodic clock expression in normal forms, with e 1 = α * . k 0 / . k 1 → . q and e 2 = α * . k 0 / . k 1 → . q . Then e 1 and e 2 are equivalent if and only if:

q = q ∧ k 0 * k 1 = k 1 * k 0
Definition 8. From this equivalence relation, for any expression e = α * . k/ . k → . q, the representative element e rep = α * . k r / . k r → . q r of its equivalence class [e] is such that:

q = q r ∧ ∃g ∈ N * , k = gk r , k = gk r
Definition 9. The canonical form CF (e) of a strictly periodic clock expression e in normal form is the representative of its equivalence class. In other words:

CF (α * . k/ . k → . q) = α * . k gcd(k, k ) / . k gcd(k, k ) → . q
Let ≡ denote the (semantic) equivalence of strictly periodic clocks. Then, we obtain the following theorem by construction: Theorem 1. Let e 1 , e 2 be expressions of E P . Then:

(e 1 ≡ e 2 ) ⇔ CF (N F R P (e 1 )) = CF (N F R P (e 2 )) (→) cl 1 ∼ cl 1 cl 2 ∼ cl 2 cl 1 → cl 2 ∼ cl 1 → cl 2 (×) cl 1 ∼ cl 1 cl 2 ∼ cl 2 cl 1 × cl 2 ∼ cl 1 × cl 2 (ON) ck 1 ∼ ck 2 c 1 = c 2 ck 1 on c 1 ∼ ck 2 on c 2 (VAR) α ∈ F T V (pck) α <: P k,q ∩ P k ,q α = CF (N F (pck)) α <: P k,q ∼ ck <: P k ,q (VAR') α ∈ F T V (ck) ck <: P k,q ∩ P k ,q α → CF (N F (ck)) α <: P k,q ∼ ck <: P k ,q (CONST) concrete(pck 1 ) concrete(pck 2 ) π(pck 1 ) = π(pck 2 ) ϕ(pck 1 ) = ϕ(pck 2 ) pck 1 ∼ pck 2 ( * . ) abstract(pck) pck ∼ pck / . k pck * . k ∼ pck (/ . ) abstract(pck) pck ∼ pck * . k pck <: P k,0 pck/ . k ∼ pck (→ . ) abstract(pck) pck ∼ pck → . (- a b ) pck <: P b, a b pck → . a b ∼ pck

Unification rules

For two clocks ck 1 , ck 2 , we denote ck 1 ∼ ck 2 when ck 1 and ck 2 are unifiable. For any clock ck, let concrete(ck) denote the predicate that returns true if ck is a concrete clock, false otherwise and let abstract(ck) denote the predicate that returns true if ck is abstract, false otherwise. Now that we defined this equivalence test, clock unification rules are given in Fig. 6.4. An example of clocks unification is detailed in Sect. 6.3.5.

• We use structural unification for clocks → and ×;

• Two clocks ck 1 on c 1 and ck 2 on c 2 can be unified if ck 1 and ck 2 can be unified and if c 1 and c 2 are syntactically equal. This implies that sometimes two semantically equivalent clocks will be considered non-unifiable. For instance if c 1 = a ∧ b and c 2 = b ∧ a, the clocks α on c 1 and α on c 2 are not unifiable. This is however a reasonable restriction in practice as the programmer should simply replace any expression x when c 2 by x when c 1 to solve the problem;

• The unification of a clock variable α with a clock expression ck has two variants. If α appears in ck and ck is a strictly periodic clock (rule VAR), then the normal form of ck must be α for the two clocks to be unifiable. If α does not occur in ck (rule VAR'), N F (ck) can be substituted for α (denoted α → N F (ck)). In both cases, ck and α must be subtypes of the intersection of their subtyping constraints. Rule (VAR') is the only rule in which substitutions take place;

• Rule (CONST) says that two concrete periodic clocks can be unified if their periods and phases are equal. The period and phase of a concrete strictly periodic clock is easily computed using the definition of periodic clock transformations given in Sect. 3.2;

• Rules ( * . ), (/ . ) and (→ . ) rely on Property. 1 to rewrite the unification problem. Rules (/ . ) and (→ . ) also introduce sub-typing constraints, the verification of which relies on Property. 8.

Unification is illustrated in Sect. 6.3.5.

Subsumption

At several points, clock inference rules and clocks unification require to verify that a clock expression belongs to some subtype. To verify that ck is a subtype of C i (ie that ck subsumes C i ), we use the following properties:

Property 8. ∀α ∈ P, ∀k, k ∈ N + * , ∀q, q ∈ Q :

• α * . k <: P k,q ⇔ α <: P k * k ,q/k ;

• α/ . k <: P k,q ⇔ α <: P k/gcd(k,k ),q * k ;

• α → . q <: P k,q ⇔ α <: P k,max(0,(q-q )) ;

• α <: P k,q ∧ α <: P k ,q ⇔ α <: P k,q ∩ P k ,q ⇔ α <: P lcm(k,k ),max(q,q ) .

Proof. Let α = (n, p). Then, from Property. 2 and from the definition of P k,q :

α * . k <: P k,q ⇔ ( n k , k p) <: P k,q ⇔ k| n k ∧ k p ≥ q ⇔ k * k |n ∧ p ≥ q/k ⇔ α <: P k * k ,q/k
Similarly:

α/ . k <: P k,q ⇔ (nk , p k ) <: P k,q ⇔ k|nk ∧ p k ≥ q ⇔ k/gcd(k, k )|n ∧ p ≥ q * k ⇔ α <: P k/gcd(k,k ),q * k
Similarly:

α → . q <: P k,q ⇔ (n, p + q ) <: P k,q ⇔ k|n ∧ p + q ≥ q ⇔ k|n ∧ p ≥ q -q ∧ p ≥ 0 ⇔ α <: P k,max(0,(q-q ))

And last:

α <: P k,q ∧ α <: P k ,q ⇔ k|n ∧ k |n ∧ p ≥ q ∧ p ≥ q ⇔ lcm(k, k )|n ∧ p ≥ max(q, q )
⇔ α <: P lcm(k,k ),max(q,q )

When read from left to right, these properties form subsumption rules that can be applied to transfer sub-typing constraints on a clock type to the clock variable appearing in it (for an abstract clock) or to the constant clock appearing in it (for a concrete clock). In the former case, the constraints are just added to the constraints already existing on this clock variable. In the latter case, we can directly check that the constant clock verifies the constraints. This is illustrated in the next section.

Example

We consider the following example: imported node imp(i, j: int) returns (o: int) wcet 10; node N(i, j, c) returns (o) let o=imp((i * ^2) when c, (j/^4 * ^3) when c); tel

The rule (IMP-NODE) can be applied to infer the type of node imp:

(IMP-NODE) i, j : cl 1 × cl 1 o : cl 1 H init imported node imp(i, j) returns (o) wcet 10 : gen H (cl 1 × cl 1 → cl 1 ) = ∀α.α × α → α
As the clock parent of the node inputs and outputs must be unifiable, the clocks of the inputs and outputs are all equal to the same clock variable (cl 1 ). This clock variable can then be generalized (to clock α). This produces a new clock environment H 0 = imp : ∀α.α × α → α, H init , which states that the node imp can only be applied to two arguments of the same clock and that the result of the application has the clock of the arguments.

Then, for node N, the rule (NODE) says that we must perform the clock calculus of the equations of the node in environment H 1 = i : cl 2 , j : cl 3 , c : cl 4 , o : cl 5 , H 0 , where cl 2 , cl 3 , cl 4 , cl 5 are fresh clock type variables:

(EQ) H 1 o : cl 5 H 1 imp : ∀α.α × α → α cl 5 × cl 5 → cl 5 = inst(∀α.α × α → α) H 1 (i * ˆ2) when c : cl 5 H 1 (j/ˆ4 * ˆ3) when c : cl 5 H 1 imp((i * ˆ2) when c, (j/ˆ4 * ˆ3) when c) : cl 5 (APP)
H 1 o = imp((i * ˆ2) when c, (j/ˆ4 * ˆ3) when c);

Now, we must compute the clock of the two arguments of imp and verify that they are unifiable, as they must both be equal to the clock type of o, ie cl 5 :

(WHEN) ( * ˆ) H 1 i : pck 1 pck 1 <: P 2,0 H 1 i * ˆ2 : pck 1 * . 2 H 1 c : (c : pck 1 * . 2) H 1 (i * ˆ2) when c : pck 1 * . 2 on c
And:

(WHEN)

( * ˆ) (/ˆ) H 1 j : pck 2 H 1 j/ˆ4 : pck 2 / . 4 pck 2 / . 4 <: P 3,0 H 1 j/ˆ4 * ˆ3 : pck 2 / . 4 * . 3 H 1 c : (c : pck 2 / . 4 * . 3) H 1 (j/ˆ4 * ˆ3) when c : pck 2 / . 4 * . 3 on c
We can see that cl 2 and cl 3 are actually strictly periodic clocks, so they are replaced by pck 1 and pck 2 . Furthermore, we have pck 2 / . 4 <: P 3,0 , so subsumption rules tell us that pck 2 <: P 3,0 . Now we must unify clocks pck 1 * . 2 on c and pck 2 / . 4 * . 3 on c. From the unification rules, we have:

pck 1 * . 2 on c = pck 2 / . 4 * . 3 on c ⇔ pck 1 * . 2 = pck 2 / . 4 * . 3 ∧ c = c ⇔ pck 1 * . 2/ . 3 = pck 2 / . 4 (keep most ancient variable) ⇔ pck 1 * . 2/ . 3 * . 4 = pck 2 ⇔ pck 1 * . 8/ . 3 = pck 2 (after normalisation)
So we can substitute pck 1 * . 8/ . 3 for pck 2 . As pck 2 <: P 3,0 , when applying the substitution we have the constraint pck 1 * . 8/ . 3 <: P 3,0 . Using subsumption rules, we have:

pck 1 * . 8/ . 3 <: P 3,0 ⇔ pck 1 * . 8 <: P gcd(3,3),0 ⇔ pck 1 * . 8 <: P 1,0 ⇔ pck 1 <: P 8,0
We already had the constraint pck 1 <: P 2,0 , so we must take the intersection between the two constraints, which is pck 1 <: P 8,0 .

We can now give the type of node N:

H 0 N : gen H 0 (pck 1 × pck 1 * . 8/ . 3 × (c : pck 1 * . 2) → pck 1 * . 2 on c)
And after generalisation:

H 0 N : ∀α <: P 8,0 .α × α * . 8/ . 3 × (c : α * . 2) → α * . 2 on c
The subtyping constraint tells us that when applying the node N, the period of the clock of the first argument of the node must be a multiple of 8. Now, the following node correctly instantiates the node N:

node inst1(i: rate (16, 0); j: rate(6, 0); c) returns (o) let o=N(i, j, c); tel

The clock of inst1 is: (16, 0) × (6, 0) × (8, 0) → (8, 0) on c.

The following instantiation is incorrect: node inst2(i: rate (16, 0); j: rate(4, 0); c: rate(8, 0)) returns (o) let o=N(i, j, c); tel

The clock calculus cannot unify clock (4, 0) with clock (16, 0) * . 8/ . 3 = (6, 0) (when computing the clock of the second argument of N).

Finally, the following instantiation is also incorrect: node inst3(i: rate (7, 0); j; c) returns (o) let o=N(i, j, c); tel Subsumption fails as (7, 0) is not a subtype of P 8,0 .

As a last example, the clock inferred for the main node of the FAS program of Fig. 5.7 is:

((100, 0) × (1000, 0) × (10000, 0) × (10000, 0)) → ((100, 0) × (1000, 0) × (1000, 0) × (1000, 1/2) × (10000, 0))

Correctness

As we stated before, the aim of the clock calculus is to ensure that the semantics of a program is welldefined. To prove this property, we need to relate clock types to the semantics given in Sect. 4.6. To this intent, we define interpretation functions I(.) relating clock types and sets of flow values (each flow value is a sequence of pairs (v i , t i )). Let s[i] denote the i th value of a sequence s (with i ∈ N). The interpretation I(t) of a clock type t is the set of values defined inductively as follows:

v ∈ I((n, p)) ⇔ ∀i, ck(v)[i] = p * n + i * n v ∈ I(cl 1 → cl 2 ) ⇔ ∀v 1 ∈ I(cl 1 ), v(v 1 ) ∈ I(cl 2 ) (v 1 , v 2 ) ∈ I(cl 1 × cl 2 ) ⇔ v 1 ∈ I(cl 1 ) ∧ v 2 ∈ I(cl 2 ) v ∈ I(ck on c) ⇔ ∀v ∈ I(ck), ∀i, ck(v )[i] ∈ ck(v) iff val(c)[i] = true v ∈ I(ck on not c) ⇔ ∀v ∈ I(ck), ∀i, ck(v )[i] ∈ ck(v) iff val(c)[i] = f alse v ∈ I(c : ck) ⇔ v ∈ I(ck) ∧ v = c v ∈ I(pck * . k) ⇔ ∀v ∈ I(pck), ck(v) = i∈N {ck(v )[i], ck(v )[i] + π(v )/k, ck(v )[i] + 2π(v )/k, . . . , ck(v )[i] + (k -1)π(v )/k} v ∈ I(pck/ . k) ⇔ ∀v ∈ I(pck), ck(v) = {ck(v )[i]|(k|ck(v )[i])} v ∈ I(pck → . q) ⇔ ∀v ∈ I(pck), ∀i, ck(v)[i] = ck(v )[i] + q * π(v ) v ∈ I(∀α 1 <: C 1 , . . . , α m <: C m .cl) ⇔ ∀ck 1 , . . . , ck m , (I(ck 1 ) ⊆ I(C 1 ) ∧ . . . ∧ I(ck m ) ⊆ I(C m )) ⇒ v ∈ I(cl[ck 1 /α 1 , . . . ck m /α m ]) v ∈ I(P k,q ) ⇔ ck(v) ∈ P k,q
In other words, v ∈ I(t) is a formal definition of the statement "v is a value of type t". The correctness of the clock calculus is then stated in the following theorem. The theorem says that if the clock calculus computes a clock cl for an expression e in an environment H, then if we take a variable assignment ρ that maps for every variable x in H a value v of the right clock type (ie v ∈ I(H(x))), then we obtain a value the clock type of which is cl (ie we have S ρ (e) ∈ I(cl), with S ρ (e) defined in Sect. 4.11).

Theorem 2. If [x 1 : σ 1 , . . . , x m : σ m ] e : cl, then for all v 1 , . . . , v m , such that v 1 ∈ I(σ 1 ), . . ., v m ∈ I(σ m ), we have S [v 1 /x 1 ,...,vm/xm] (e) ∈ I(cl).

Proof. By induction on the structure of expressions e. For each inference rule of Fig. 6.3, we prove that if the premises of the rule hold then the property holds for the conclusion.

First, proving the base of the induction amounts to proving that the property holds for the expressions of the language that are not defined inductively. There are two cases:

• Case e is cst: The rule has no premise. We have cst : ck, so for any assignment ρ (that does actually not concern cst), S ρ (cst) ∈ I(ck);

• Case e is x: If x ∈ dom(H) (premise of the inference rule (VAR)), that is to say there is some H and cl such that H = H , x : cl, then H x : cl. Now, if we consider a valuation that maps a value v to x such that v ∈ I(cl), then obviously S [v/x] (x) ∈ I(cl).

So we have proved the base of the induction. Now we can proceed to the induction step for the remaining inference rules. For each inference rule, we assume that the property holds for the premises of the rule (induction hypothesis) and prove that in this case the property holds for the conclusion of the rule.

• Case e is (e 1 , e 2 ): We assume that S ρ (e 1 ) ∈ I(cl 1 ) and S ρ (e 2 ) ∈ I(cl 2 ). Then by definition of I(cl 1 × cl 2 ), we have S ρ ((e 1 , e 2 )) ∈ I(cl 1 × cl 2 );

• Case e is cst fby e : We assume that S ρ (cst) ∈ I(cl) and S ρ (e ) ∈ I(cl). Then from the semantics of operator fby, the expression cst fby e produces values exactly at the same tags as cst and e , so S ρ (cst fby e ) ∈ I(cl);

• Case e is e when c: We assume that S ρ (e ) ∈ I(ck) and S ρ (c) ∈ I(c : ck). From the semantics of operator when, for all i, ck(S ρ (e ))[i] ∈ ck(S ρ (e)) if and only if val(c)[i] = true. So by definition of I(ck on c), we haveS ρ (e) ∈ I(ck on c);

• Case e is e whennot c: same as previous case;

• Case e is merge (c, e 1 , e 2 ): We assume that S ρ (c) ∈ I(c : ck), S ρ (e 1 ) ∈ I(ck on c) and S ρ (e 2 ) ∈ I(ck on not c). Then from the semantics of operator merge, the expression merge (c, e 1 , e 2 ) produces values exactly at the same tags as c, so S ρ (merge (c, e 1 , e 2 )) ∈ I(ck);

• Case e is e * ˆk: We assume that S ρ (e ) ∈ I(pck). The semantics of operator * ˆtells us that for all i, we have pck • Case e is e ∼> q: We assume that S ρ (e ) ∈ I(pck). The semantics of operator ∼> tells us that for all i, we have pck[i] + q * π(pck) ∈ ck(S ρ (e)). Furthermore, the right premise of the rule ensures that pck[i] + q * π(pck) is in N. So by definition of I(pck → . q), we have S ρ (e) ∈ I(pck → . q);

[i] ∈ ck(S ρ (e)), pck[i] + π(pck)/k ∈ ck(S ρ (e)), . . ., pck[i] + π(pck)/k * (k -1) ∈ ck(S ρ (e)).
• Case e is tail (e ): We assume that S ρ (e ) ∈ I(pck). The semantics of operator tail tells us that for all i, we have pck[i] + π(pck) ∈ ck(S ρ (e)). So by definition of I(pck → . q), we have S ρ (e) ∈ I(pck → . 1);

• Case e is cst :: e : We assume that S ρ (e ) ∈ I(pck). The semantics of operator :: tells us that for all i, we have pck[i] -π(pck) ∈ ck(S ρ (e)). Furthermore, the right premise of the rule ensures that pck[i] -q * π(pck) is positive. So by definition of I(pck → . q), we have S ρ (e) ∈ I(pck → . -1);

• Case e is N (e): Let σ = ∀α <: C.cl. Let cl 1 → cl 2 = cl[(cl ∈ C)/α]. Let v ∈ I(σ).
According to the definition of I, v ∈ I(cl 1 → cl 2 ). Now, if we assume that S ρ (N ) ∈ I(σ), then S ρ (N ) ∈ I(cl 1 → cl 2 ). We also assume that S ρ (e) ∈ I(cl 1 ). According to the definition of

I(cl 1 → cl 2 ), for any v ∈ I(cl 1 ), S ρ (N (v)) ∈ I(cl 2 ), so S ρ (N (S ρ (e))) = S ρ (N (e)) ∈ I(cl 2 );
• Case x = e: We assume that S ρ (x) ∈ I(cl) and S ρ (e) ∈ I(cl). Then the semantics of the equation is well-defined;

• Case eq 1 ; eq 2 : Well-defined provided eq 1 and eq 2 are well-defined;

• Case node N (in) returns (out) [ var var; ] let eqs tel : The induction hypothesis applied to the premises of the inference rule (NODE) says that if S ρ (in) ∈ I(cl 1 ), S ρ (out) ∈ I(cl 2 ) and S ρ (var) ∈ I(cl 3 ), then the semantics of the equations eqs is well-defined. So for all v ∈ I(cl 1 ), S [v/in] (N (in)) ∈ I(cl 2 ). Thus: S ρ ( node N (in) returns (out) [ var var; ] let eqs tel ) ∈ I(cl 1 → cl 2 );

• Case imported node N (in) returns (out) wcet n: True by definition;

• Last, for the subsumption rule. We assume that S ρ (e) ∈ I(ck). We also assume that ck <: C, so I(ck) ⊆ I(C). As a consequence, we have S ρ (e) ∈ I(C).

About Flows Initialisation

In LUSTRE, the use of the pre operator produces flows the values of which are undefined at the first instant. This requires an initialisation analysis [START_REF] Colaço | Type-based initialization analysis of a synchronous dataflow language[END_REF] to verify that the program does not access to uninitialised flows. Indeed, the program would access to undefined values and its behaviour would not be deterministic. For instance the program consisting of the following simple node is rejected as its output is undefined at the first instant:

node non_init(i: int) returns (o: int) let o=i+pre(i); tel

In our language, delays are handled using operator fby and are always initialised, so we do not need an initialisation analysis. Flows can be deactivated during initialisation steps using operator tail or ∼> but this directly appears in the clock, which defines when the flow begins to be active. Thus, the clock calculus directly ensures that two flows can only be combined if they start being active at the same dates and we do not need an initialisation analysis.

Summary

In this chapter, we have defined:

• The typing of the language;

• The clock calculus of the language.

The causality analysis of the language is standard and was not detailed here. We have also shown that no initialisation analysis is required in our language because the properties it usually verifies are already verified by our clock calculus.

When all the static analyses succeed, the semantics of the program is well-defined and the compiler can generate the code corresponding to the program. This will be detailed in the next part of this dissertation. Before that, the next chapter concludes on the language definition.

Chapter 7

Conclusion on language definition

We proposed a language for programming embedded control systems with multiple real-time constraints. The language is defined as a real-time software architecture language with synchronous semantics. It defines real-time primitives based on a specific class of clocks called strictly periodic clocks, which are triggered at regular intervals of time (the period of the clock). Periodic clock transformations allow to produce a faster or a slower strictly periodic clock from an existing strictly periodic clock or to produce a strictly periodic clock with a different phase (ie to shift the initial activation date of a clock). Periodicity constraints can then be imposed in a program by specifying that the clock of a flow is strictly periodic. We defined rate transition operators based on periodic clock transformations, which can be used to combine flows of different rates. The programmer can implement various multi-rate communication patterns using rate transition operators and the semantics of the language ensures that the patterns are deterministic.

For the most part, strictly periodic clocks and their associated primitives could be programmed using Boolean clocks, as we did for the FAS in Fig. 1.6, using counter clocks. From this point of view, strictly periodic clocks and the associated clock transformations are a subclass of the usual Boolean clocks of synchronous languages. However, in the general case, the set of activations dates of a flow the clock of which is defined by Boolean activation conditions cannot be evaluated statically. The activation conditions can for instance depend on the value of an input of the program, which is known only at the execution of the program. On the contrary, strictly periodic clocks are only defined by their phase and by their period, thus we can easily determine the activation dates of a flow the clock of which is strictly periodic. The first consequence is that the problem of testing the synchronism of two flows is completely decidable and can be solved efficiently. The second consequence, as we will see in the next part of this dissertation, is that restricting to the specific class of strictly periodic clocks allows to compile the corresponding class of systems more efficiently, by reusing existing results of the scheduling theory of multi-periodic task sets.

Strictly periodic clocks can also be considered as a particular case of the N -synchronous approach [CDE + 06, CMPP08] or of the affine clock relations [START_REF] Smarandache | A canonical form for affine relations in signal[END_REF]. On the contrary to the case of Boolean clocks, these approaches allow to evaluate statically the activation dates of a flow. The systems considered by these approaches remain more general than multi-periodic systems, so, again, by considering a smaller class of systems we are able to compile programs more efficiently. The same argument holds for more general GALS approaches [START_REF] Berry | Multiclock esterel[END_REF][START_REF] Le | Polychrony for system design[END_REF].

Rate transition primitives improve the previous real-time extensions of LUSTRE [START_REF] Curic | Implementing Lustre programs on distributed platforms with real-time constraints[END_REF] by providing more powerful operators. Compared to [ACGR09] however, our language does not support mode automata. It would be interesting to include them in our language.

Compared to other ADLs, we have seen that our language allows to specify more complex communication patterns.

Chapter 8

Overview of the Compilation Process

In this part of the dissertation, we detail the compilation of the language, which translates a program into lower-level code. This code is then translated into executable code by an existing compiler. The complete compilation process is defined formally, in order to ensure that the generated code behaves according to the formal semantics of the language.

Limitation of the classic "single-loop" code generation

As for other synchronous languages, the compiler translates the input program into intermediate level code (C code). However, the translation differs from the classic "single-loop" sequential code generation, because we deal with multi-periodic systems. For instance, consider the example below: imported node A(i: int) returns (o: int) wcet 1; imported node B(i: int) returns (o: int) wcet 5; node multi(i: rate(3, 0)) returns (o) var v; let v=A(i); o=B(v/^3); tel As shown in Fig. 8.1, a sequential execution of the program cannot respect all the deadline constraints of the program, the second repetition of operation A will miss its deadline (at date 6). This example cannot be implemented using sequential code generation, we cannot find a sequence of execution for operations A and B that will respect the periodicity constraints of the two operations.

Multi-task code generation

However, if we implement operations A and B as two separate tasks and execute them concurrently with a real-time operating system with multi-tasking capabilities (ie the RTOS is preemptive), we can find a schedule for the execution of the tasks that respects the periodicity constraints of the two operations. Each operation (each imported node call) is translated into a task τ i , which has a set of real-time attributes (T i , C i , r i , d i ). τ i is instantiated periodically with period T i , τ i [j] denotes the j th iteration of task τ i . A task instance cannot start its execution before all its predecessors, defined by the precedence constraints due to data-dependencies between tasks, complete their execution. C i is the (worst case) execution time of the task. r i is the release date of the first instance of the task. The subsequent release dates are r i + T i , r i + 2T i , etc. d i is the relative deadline of the task. The absolute deadline D i [j] of the instance j of a task τ i is the release date R i [j] of this instance plus the relative deadline of the task:

D i [j] = R i [j] + d i .
These definitions are illustrated in Fig. 8.2.

r i , R i[0] C i D i[0] d i R i[1] C i D i[1] d i R i[2] 0 T i T i Figure 8.2: Real-time characteristics of task τ i
Our example can be translated into two tasks τ A of attributes (3, 1, 0, 3) and τ B of attributes (9, 5, 0, 9), with a precedence from τ A to τ B . Using preemption, it is then possible to find a schedule that respects the deadline constraints of both tasks, as shown in Fig. 8.3. Marks on the time axis of B represent task preemptions/restorations: at date 3, B is suspended to execute the second repetition of A. After A completes, B is restored (at date 4) and resumes its execution where it stopped.

Compilation chain

The global structure of the compilation chain is described in Fig. 8.4. The Chapter 9 first details the translation of the program into a set of tasks related by precedence constraints called a dependent task set, where precedences are due to data-dependencies between tasks. The Chapter 10 describes the translation of this dependent task set into an equivalent independent task set, schedulable by an EDF scheduler. First, precedence constraints are encoded by adjusting the task real-time attributes of the tasks (by extending the work of [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF] to the case of multi-periodic dependent tasks). Then, a communication protocol is proposed to ensure the conservation of the synchronous semantics of the task set. The algorithm performing the expansion of the content of a node is described in Alg. 7. This algorithm is applied recursively, starting from the main node of the program, to completely expand the program. The result of this expansion is a single node (the main node), in which node calls correspond to either predefined nodes or imported nodes (the leaves of the nodes hierarchy). replace the node instance in N by a tuple made up of its outputs 13: end for

Task Graph

The expanded program is first translated into a task graph. A task graph consists of a set of tasks (the vertices of the graph) and a set of precedences between the tasks (the edges of the graph).

Definition

Let g = (V, E) denote a task graph, where V is the set of tasks of the graph and E is the set of task precedences of the graph (a subset of V × V ). Precedences are annotated with the predefined operators of the language, which describe the communication pattern used for communicating from the source to the destination of the precedence. The union of two graphs g = (V, E) and g = (V , E ) is defined as g ∪ g = (V ∪ V , E ∪ E ). last(g) denotes the set of tasks of g that have no successors, that is to say

last(V, E) = {v ∈ V |¬(∃v ∈ V, v → v )}.
Fig. 9.1 gives a task graph example. Boxes represent tasks and edges represent precedences. Task A communicates with task B using the rate transition operator /^3, which means that only the first out of three successive instances of A send data to B and implies that A has a period three times shorter than that of B. The other precedences have no annotations, which means that they denote simple precedences that relate tasks of the same period.

Tasks

Each task τ i has a set of functional attributes (in i , out i , f i ). Tasks are instantiated periodically and when a task instance executes, it applies its function f i to its input variables in i to compute its output variables Extracting tasks from the expanded program is pretty straightforward: each imported node call appearing in the expanded main node is translated into a task. The inputs (in i ) and outputs (out i ) of the task are the inputs and outputs of the node call. The function of the task (f i ) is the function provided by the user to implement the imported node. Each variable of the expanded main node and predefined operator call appearing in it is also translated into a vertex but will later be reduced to simplify the graph (see Sect. 9.2.5).

Task Graph

Precedences

When a variable v is such that v ∈ out i and v ∈ in j , there is a data-dependency from τ i to τ j . To respect the synchronous semantics, we must add a precedence constraint from τ i to τ j in the task graph, to force τ i to execute before τ j . A precedence from a task τ i to a task τ j will be denoted τ i → τ j . Task precedences are deduced from data dependencies between expressions of the program.

We extend the notion of syntactic dependency of [START_REF] Halbwachs | Generating efficient code from data-flow programs[END_REF] to our language. We write e ≺ e when e syntactically depends on e. For instance, the expression N (e, e ) syntactically depends on e and e . The expression e when c syntactically depends on e and c, even though when c is false the expression does not semantically depend on e.

The syntactic dependency relation is overloaded for equations. The corresponding definition is given in Fig. 9.2. In the rule on equations, the x on the left denotes the definition of variable x while the x on the right denotes the expression x (as for instance the sub-expression of expression x + 1). Remember that calls to intermediate nodes are inlined before computing the task graph, therefore node applications appearing in the definition of the dependency relation are only applications of imported nodes. Notice that cst fby e syntactically depends on e. This implies that we allow syntactic dependency loops (though the causality analysis checks that a program does not contain semantic dependency loops). Such dependencies will only be discarded later in the compilation process (see in Sect. 10.2). Task precedences are deduced from this definition as described in the next section. To avoid cumbersome notations, we consider that precedences directly relate tasks, while they actually relate one output of a task to one input of another task. For instance, we simply write i → F and * ˆ3 → F , while In practice i precedes the first input of F and * ˆ3 precedes the second input of F . We do not make this distinction in the rest of the dissertation as it has only minor impacts. It was however taken into account in the implementation of our compiler prototype.

Intermediate graph

Finally, the intermediate graph of the complete program is given in Fig. 9.4. Notice that even though the precedences form a loop, the computation of the graph terminates as we simply take the union of the graphs of the two equations.

Graph reduction

Variables

We apply a series of simplifications to the intermediate graph structure. First, each input of the main node is translated into a (sensor) task and each output of the main node is translated into a (actuator) task. The inputs and outputs of the program are implemented as tasks so that they can directly be activated by the generated code. Second, we replace recursively each pair of precedence N → x → M , where x is a local variable (not an input or output), by a single precedence N → M . When all such pairs have been replaced, we remove variables from the graph. On our previous example, we obtain the graph of Fig. 9.5.

Predefined operators and extended precedences

We define a graph transformation system that removes predefined nodes from the graph. It translates predefined nodes into precedence annotations (labels on the graph edges). A precedence τ i ops → τ j represents an extended precedence, where the precedence annotation ops is a list of operators op, with op ∈ {/ˆk, * ˆk, ∼> q, tail, cst ::, cst fby, when c, whennot c}. The annotation specifies that the precedence relates tasks of different rates or phases ( * ˆk, /ˆk, ∼> q), or that the precedence is delayed (cst fby) or conditioned (when c). In the following, op.ops denotes the list whose head is op and whose tail is ops and denotes the empty list.

A survey of graph transformation systems and associated concepts can be found in [AEH + 99]. A graph transformation system consists of a set of rules. In our context, a graph transformation rule r = (L, R, K) simply consists of a left-hand side graph L, a right-hand side graph R and an interface graph K (actually only a set of vertices) occurring both in L and R. The application of rule r to a graph G consists of:

1. Finding a sub-graph S of G corresponding to graph L.

2. Removing S from G except for the interface K. This produces a context graph D.

3. Constructing the disjoint union of D and R. This is illustrated in Fig. 9.6. The transformation rule to apply is given in Fig. 9.6(a). The vertices with thick borders are the interface K of the rule. In Fig. 9.6(b), the graph in the dashed rectangle is an occurrence of the right-hand side of the rule. This occurrence is then replaced by the right-hand side of the rule and we obtain the graph of Fig. 9.6(c). The rules of the graph transformation system R G that translates predefined operators to precedence annotations are defined in Fig. 9.7. Notice that the transformation of the when operator requires to add the condition of the when as an input of the task τ j . We will indeed need the value of this condition at each repetition of the task to test if the task τ j needs to be executed for the current repetition or not (see Sect. 9.4). This is handled transparently by the compiler and does not require the programmer to modify the code provided for the corresponding imported node.

• When a precedence relates τ i to τ j through operator when c, when c is transformed into an annotation on the precedence from τ i to τ j . A precedence is added from c to τ j as τ j will need to test if c is true before consuming the data produced by τ i . The transformation rule for operator whennot c is similar;

• Rate transition and phase offset operators are similarly transformed into precedence annotations; • The merge is simply removed from the graph and its predecessors become predecessors of its successor. Even though the successor will as a result have several predecessors for the same input, the clock of those inputs tell us when to use one or the other.

Property 9.

• The rewriting system R G is convergent;

• When the rewriting terminates, every vertex of the graph corresponds to either an imported node or a sensor or an actuator.

Proof. The proof relies on the same principles as for term rewriting systems (see Sect. 6.3.3 and [START_REF] Baader | Term rewriting and all that[END_REF]). The termination is straightforward to prove: as every rule reduces the number of vertices of the graph, the rewriting system terminates. The confluence is simple too. As the system terminates, we need to prove the local confluence of its critical pairs. The system does not contain critical pairs, so it is locally confluent and thus confluent.

The reduced task graph of the previous example is given in Fig. 9.8. As the causality analysis rejects programs that contain cycles without delays, if we ignore delayed precedences the task graph corresponding to a program is a Directed Acyclic Graph (DAG).

In addition, we reject programs which contain a precedence τ i ops → τ j where ops contains an operator * ˆk that precedes the first operator fby appearing in ops. For instance, B(0 fby (A(i) * ˆ2)), which gives A * ˆ2.fby → B should be replaced by B((0 fby A(i)) * ˆ2), which gives A fby. * ˆ2 → B (though the semantics is not exactly the same). This constraint does not seem too restrictive and we will see that it allows important simplifications in the precedence encoding algorithm (Sect. 10.2). This verification is actually performed ealier in the compilation process, during the static analyses of the program. It is only detailled now for better readability.

Task clustering

The task extraction process basically translates each imported node call into a separate task. For complex examples, this might produce many tasks, which can be problematic for two reasons. First, the number of tasks supported by a RTOS is generally bounded and does not exceed a few hundreds at best. Second, scheduling a large set of tasks can result in an important run-time overhead, due to the time required to compute scheduling decisions.

Therefore, it would be interesting to group several node calls in the same task, which is referred to as task clustering. Task clustering is however not trivial. Reducing the number of tasks of the system indeed reduces the time required to compute scheduling decisions, but it may in return increase the number of preemptions, as longer tasks are more likely to require preemption, and also increase the cost of each preemption, as the size of the context of a task also increases. Clustering can also cause a system to become unschedulable. For instance, data produced by clustered tasks is only available at the end of the execution of the task cluster, while it would have been available earlier if the tasks had not been clustered.

Task clustering is not studied in the present dissertation, though it is clearly an important subject for future work. A first bibliographical study seems to suggest that this topic has not been studied much until now. Some interesting strategies have been studied in [START_REF] Curic | Implementing Lustre programs on distributed platforms with real-time constraints[END_REF] and guidelines have been proposed in [START_REF] Alras | Model-based design of embedded control systems by means of a synchronous intermediate model[END_REF]. We have not considered their integration in our work yet.

Real-time characteristics

For each task τ i , we must extract its real-time characteristics (T i , C i , r i , d i ) (see Chap. 8 for definitions). The clock calculus ensures that all the inputs and outputs of an imported node have the same strictly periodic clock parent pck i . By extension, we will call pck i the strictly periodic clock parent of τ i . The real-time characteristics of a task can be extracted from a program as follows:

Periods The period of a task is obtained from its strictly periodic clock parent. We have T i = π(pck i ).

Deadlines By default, the deadline of a task is its period (d i = T i ). Deadline constraints can also be specified on sensors or actuators (eg o: due n), then the deadline of the task is the minimum of the deadlines of its outputs.

Release dates

The initial release date of a task is the phase of its strictly periodic clock parent:

r i = ϕ(pck i ).
Execution times The execution time C i of a task is specified by the wcet of the corresponding imported node, sensor or actuator declaration.

Activation conditions

If the clocks of the inputs/outputs of a task contain Boolean clock transformations, then the task may have an activation condition. At each task instantiation, the program must test the activation condition. If this condition is false, then the task instance completes immediately, otherwise it behaves normally.

Chapter 10

From Dependent to Independent Tasks

In the previous chapter, we detailed the translation of a program into a set of real-time tasks related by precedence constraints, represented by a task graph. Tasks are inter-dependent, due to precedence constraints, which makes the compilation problem more complex than in the case of independent tasks. First, we need a scheduling policy for periodic tasks with precedence constraints: a task instance cannot be scheduled until all its predecessors complete their execution. Second, we need to ensure data consistency for inter-task communications: when a task τ j consumes data produced by a task τ i , we need to ensure that the data produced by τ i is available when τ j starts and remains available until τ j completes its execution.

Scheduling Dependent Tasks

We need a scheduling policy for a set of tasks with the following constraints:

1. Tasks are instantiated periodically.

2. Tasks have a relative deadline lower than their period.

3. Tasks can have an initial release date greater than 0.

4. Tasks are related by precedence constraints.

In the rest of this section, we review the possible scheduling policies for this problem.

With/without preemption

With non-preemptive scheduling, once the scheduler starts the execution of a task, the task executes until its completion. With preemptive scheduling, the execution of the currently running task can be suspended to execute another task and resumed later.

The scheduling problem we consider is NP-hard when preemption is not allowed [START_REF] Stankovic | Implications of classical scheduling results for real-time systems[END_REF]. Allowing preemption yields polynomial complexity and enables the scheduling of a larger class of problems (see Fig. 8.3 for instance). Thus we choose a preemptive scheduling policy.

This choice does however have a noticeable drawback: each preemption has a cost in terms of execution time due to context switching. Preempting a task requires to save the context of the running task, to replace it by the context of the preempting task and to later restore the context of the preempted task when it resumes its execution. Usually, this overhead is either neglected or task wcets are over-approximated to include this overhead. We assume the latter. A possible extension would take advantage of the work of [START_REF] Meumeu | Extending Rate Monotonic analysis with exact cost of preemptions for hard real-time systems[END_REF], which allows to compute the exact cost of task preemptions.

Dynamic/static priority

Periodic tasks scheduling can be solved efficiently by using a priority-based scheduling approach. Each task is affected a priority and scheduling decisions are taken based on this priority. Task priorities can either be static or dynamic. By static priority, we mean that the priority of a task remains the same for the complete program execution. By dynamic priorities, we mean that the priority of a task can change during execution. Typically, with dynamic priorities, different task instances of the same task can have different priorities.

The fundamental work of [START_REF] Cheng | Scheduling algorithms for multiprogramming in a hard-real-time environment[END_REF] proposed the rate-monotonic (RM) static priority policy, where tasks with a shorter period are affected a higher priority and the earliest-deadline-first (EDF) dynamic priority policy, where task instances with a shorter absolute deadline are affected a higher priority. RM is optimal1 in the class of static priority policies for scheduling a set of periodic tasks with deadlines equal to the period and release dates equal to 0. It can be extended to the deadline-monotonic policy (DM) [START_REF] Joseph | On the complexity of fixed-priority scheduling of periodic, real-time tasks[END_REF], to schedule optimally a set of tasks with relative deadlines lower than the task period.

[Aud91] defines an optimal algorithm based on DM for the case with release dates different from 0. EDF is optimal in the class of dynamic priority policies for scheduling a set of periodic tasks with relative deadlines lower than the task period and release dates different from 0.

RM tends to be favored instead of EDF by the developers of real-time applications. However, as emphasized in [START_REF] Buttazzo | Rate Monotonic vs. EDF: Judgement Day[END_REF], most of the claims that justify this choice are either false or do not hold in the general case. Though the author mentions DM only marginally, it seems that most remarks on RM also stand for DM. The results of the comparison between RM and EDF are the following:

• RM is easier to implement than EDF. With RM, task priorities can be computed before run-time, while with EDF they must be computed by the scheduler at run-time, each time a task is released;

• EDF does not have a higher run-time overhead than RM. EDF requires to compute task priorities at run-time, which introduces a run-time overhead that does not exist with RM. However, when context switches are taken into account, EDF introduces less overhead than RM because the number of preemptions that occur with EDF is usually much lower than with RM;

• EDF does not lead to higher task jitter than RM on average (the task jitter is the variation between the response times of different instances of the task, the response time being the time elapsed between task instance release and task instance completion). RM reduces the jitter of high priority tasks but increases that of low priority tasks, while EDF treats tasks more equally. As a result, when the utilization factor of the processor is high (ie when the processor has few idle times), the average task jitter of the task set is lower with EDF than with RM;

• RM is not more robust than EDF in the case of overloads (when the total execution time demand of tasks exceeds the processor capacity). This criterion is not of much interest in our context as the criticity of the systems requires that overloads never occur ;

• The class of complexity of the schedulability analysis is the same for EDF and RM, though in average EDF requires a little more execution steps;

• EDF enables better processor utilization than RM, thus allowing more complex computations to be performed.

In conclusion, the choice between DM and EDF should actually be made as follows:

• If the programmer needs a policy easier to implement, he should choose DM;

• If the programmer needs better resource exploitation, he should choose EDF.

In this dissertation, we will only consider EDF scheduling, to enable better resource exploitation. We are currently working on a solution relying on DM.

10.1.3 Processing precedences before run-time/at run-time EDF or DM were not originally designed for tasks with precedence constraints. There are two main different approaches to support precedence constraints. The first approach relies on semaphores to handle task synchronization. For instance, the Stack Resource Policy (SRP) [START_REF] Baker | Stack-based scheduling for realtime processes[END_REF] was designed for scheduling tasks with shared resources. It can be used to handle binary semaphore and thus to handle task precedences: to each precedence τ i → τ j corresponds a binary semaphore and τ j must wait for τ i to unlock the semaphore before it can be scheduled. The SRP protocol ensures that access to shared resources does not lead to deadlocks and provides a tight bound for the duration of task priority inversions (the duration of blocking that a task may experience due to resources held by lower priority jobs). It handles either static or dynamic priorities policies.

In the second approach, the real-time characteristics of the tasks are adjusted before run-time so that the scheduler will respect task precedences at run-time, without needing explicit synchronizations (semaphores). Such a method is detailed in [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF], where the authors propose to encode precedences by adjusting the release dates and deadlines of the tasks and to schedule the adjusted task set with the standard EDF policy. This technique is optimal in the sense that a valid schedule can be found for the original task set if and only if a valid schedule can be found for the adjusted task set. The main advantage of this approach is that no synchronization primitive is required. This is particularly interesting in the context of critical systems as semaphores are usually not well accepted (due to the risk of deadlocks).

Encoding Task Precedences

This section first details the precedence encoding technique of [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF] and then describes how we adapt it to the extended precedences of the reduced task graphs of Sect. 9.2.5 and to the synchronous context.

Adjusting real-time characteristics

Simple precedences

The encoding technique was originally designed for aperiodic tasks related by precedence constraints. Let succ(τ i ) denote the successors of τ i and pred(τ i ) denote its predecessors. Precedences are encoded by adjusting the release date and deadline of each task as follows:

• A task τ i must end early enough for its successors to complete before their own deadlines so the adjusted absolute deadline D * i is:

D * i = min(D i , min(D * j -C j )
) (for all τ j ∈ succ(τ i ));

• The release date adjustment proposed in [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF] is: R * i = max(R i , max(R * j + C j )) (for all τ j ∈ pred(τ i )). This formulae can be simplified in our context as: R * i = max(R i , max(R * j )) (for all τ j ∈ pred(τ i )). Indeed, let A precede B, after deadline adjustment, we get D * A < D * B , so EDF will affect a higher priority to A. Consequently, to ensure that A is scheduled before B, we only need to make sure that B is not released before A, thus the second encoding. The first encoding enabled more efficient schedulability analysis in the context of [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF], but as we will see in Sect. 10.3 this is of no use in our context as the schedulability analysis is a co-NP hard problem.

This technique can directly be extended to precedences relating tasks of the same period, which we will call simple precedences. Let τ i [n] → τ j [m] denote a precedence from the instance n of task τ i to the instance m of task τ j . A simple precedence τ i → τ j is such that for all n, τ i [n] → τ j [n]. Thus, adjusting the relative deadline and initial release date of a task is equivalent to adjusting the absolute deadline and release date of all its instances.

Extended precedences

In the case of reduced task graphs as defined in Sect. 9.2.5, we need to encode extended precedences. We can see in Fig. 10.1 that extended precedences correspond to more complex task instance precedence patterns and that the release dates and deadlines adjustment to apply may vary from one task instance to the next. For instance in Fig. 10.1(a), the adjusted relative deadline of A[0] is not the same as the one of A simple solution consists in unfolding the reduced task graph on the hyperperiod of the tasks and performing the encoding on the unfolded graph. The hyperperiod of a task set, which we will denote HP , is defined as the least common multiple of the periods of the tasks. [START_REF] Leung | A note on preemptive scheduling of periodic, real-time tasks[END_REF] proved that the schedule produced by EDF for a set of periodic tasks first goes through a transitory phase of length max(r i )+HP (for all τ i ) and then repeats a pattern of length HP . Thus, a simple solution to the problem of scheduling tasks related by extended precedences is to unfold the extended precedence graph on the hyperperiod of the tasks (see [START_REF] Richard | Validation temporelle d'un logiciel temps réel : application à un laminoir industriel[END_REF] for instance) and to apply the encoding on the unfolded graph, which only contains simple precedences.

A[1]. (a) A /ˆ2 → B (b) A * ˆ2 → B (c) A fby → B (d) A ∼>1/2 → B (
This solution replaces each task τ i by HP/T i duplicates (if we do not consider the transitory phase) in the unfolded graph. This can lead to important computation overhead at execution as the scheduler needs to make its decisions according to a task set that will contain much more tasks than the original task set. In the following sections, we propose a different solution that does not duplicate any task, so that the scheduler takes less time to make its decisions. This also implies less memory consumption and smaller code size.

Definition of task instance precedences

We propose an extension of the encoding technique proposed by [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF] to the case of extended precedences, which does not require to unfold the reduced task graph. The intuition is that extended precedences correspond to regular patterns of simple precedences and thus that we should take advantage of this regularity to perform the encoding in a compact and factorized manner.

First, we need an exploitable definition of the set of task instance precedences corresponding to extended precedences. For precedences with a list of operators in the precedence annotation, we have

τ i ops → τ j ⇒ ∀n, τ i [n] → τ j [g ops (n)]
, with g ops defined as follows: g * ˆk.ops (n) =g ops (kn) g /ˆk.ops (n) =g ops ( n/k ) g ∼>q.ops (n) =g ops (n) g tail.ops (n) = g ops (0) if n=0 g ops (n -1) otherwise g ::.ops =g ops (n + 1) g fby.ops (n) =g ops (n + 1) g when.ops (n) =g ops (n)

g whennot.ops (n) =g ops (n) g (n) =n
The term r , with r ∈ Q + , denotes the smallest integer no less than r (for instance 3 for 5 2 ). Implicitly, when we write n/k , here n/k denotes the rational n k . In all other contexts, n/k denotes the division on integers, or more exactly the quotient of the division (for instance 4/3 = 1). When required, we can use the following property to switch from n/k to n/k:

Property 10. n/k = (n -1)/k + 1 n/k = n/k + ((n -1) mod k) + 1 k - 1
Proof. The property on the left is easily obtain from the definition of n/k and n/k .

To prove the property on the right, we have two case. First, if k = 1, then n/k = n and:

n/k + ((n -1) mod k) + 1 k -1 = n + (0 + 1)/1 -1 = n = n/k
So the property is true for k = 1. Otherwise (k > 1), we first notice that:

(a + b)/k = a/k + b/k + ((a mod k) + (b mod k))/k Indeed, let a = q 1 k + r 1 (with 0 ≤ r 1 < k) and b = q 2 k + r 2 (with 0 ≤ r 2 < k). We have a + b = (q 1 + q 2 )k + r 1 + r 2 . Let r 1 + r 2 = q 3 k + r 3 (with 0 ≤ r 3 < k).
We have:

r 1 + r 2 = q 3 k + r 3 ⇔ q 3 = (r 1 + r 2 )/k ⇔ q 3 = ((a mod k) + (b mod k))/k
We have a + b = (q 1 + q 2 + q 3 )k + r 3 , so:

(a + b)/k = q 1 + q 2 + q 3 = a/k + b/k + ((a mod k) + (b mod k))/k Thus: n/k = (n -1)/k + 1/k + ((n -1) mod k + 1 mod k)/k = ((n -1)/k + 1) -1 + 1/k + ((n -1) mod k + 1 mod k)/k = n/k + 1/k + ((n -1) mod k + 1 mod k)/k -1
As k > 1, we have 1/k = 0 and:

n/k = n/k + ((n -1) mod k + 1 mod k)/k -1
The function g ops is illustrated in Fig. 10.2. The two time axes of each subfigure show the release dates of two tasks τ i and τ j related by a precedence τ i τ → j . The arrows describe which instance of τ i precedes which instance of τ j , according to the definition of g ops .

• For τ i * ˆ3 → τ j , g ops (n) = 0, 3, 6, 9, . . ., so τ i [0] → τ j [0], τ i [1] → τ j [3], τ i [2] → τ j [6], τ i [3] → τ j [9]
and so on;

• For τ i /ˆ3 → τ j , g ops (n) = 0, 3, 3, 3, 6, 6, 6, . . ., so τ i [0] → τ j [0], τ i [1] → τ j [3], τ i [2] → τ j [3], τ i [3] → τ j [3], τ i [4] → τ j [6], τ i [5] → τ j [6], τ i [6] → τ j [6]
and so on. → τ j , g ops (n) = 0, 1, 2, 2, 3, 4, 4, . . .

• For τ i ∼>1/2 → τ j , g ops (n) = 0, 1, 2, 3, 4, . . . • For τ i tail → τ j , g ops (n) = 0, 0, 1, 2, 3, 4, . . . • For τ i :: → τ j , g ops (n) = 1, 2, 3, 4, . . . • For τ i fby → τ j , g ops (n) = 1, 2, 3, 4, . . .

Encoding Task Precedences

• For τ i /ˆ2.tail.tail → τ j , g ops (n) = 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4 . . .

Notice that with this definition of extended precedences, τ i [n] → τ j [n ] does not necessarily imply that τ j [n ] consumes data produced by τ i [n]. For instance, according to this definition, for the precedence

τ i * ˆ2 → τ j , we have τ i [1] → τ j [1] and τ i [2] → τ j [1], while there is no data-dependency from τ i [1] to τ j [1]. The precedence τ i [1] → τ j [1] is not really necessary, because implicitly τ i [1] → τ i [2] and so transitively due to precedence τ i [2] → τ j [1] we have τ i [1] → τ j [1]. However, even though the precedence τ i [1] → τ j [1]
is unnecessary, it is still correct to say it exists and this allows us to give a simple definition for function g ops . Data-dependencies will be defined more precisely in Sect. 10.4.2. In the following sections, we use function g ops to define a factorized encoding of extended precedences.

Release dates adjustment in the synchronous context

The synchronous semantics of the language has important consequences on the relationship between release dates of communicating tasks, which simplifies the encoding. Actually, we can prove that in our context we do not need to adjust task release dates.

∆ ops (1, T j , r j ) ∆ ops (4, T j , r j ) ∆ ops (2, T j , r j ) ∆ ops (5, T j , r j )

(a) For τi /ˆ3
→ τj ∆ ops (0, ...) ∆ ops (1, ...) ∆ ops (2, ...) • If we take T i = T j = 2, r j = 0, ops = fby, we have (for n = 0, 1, 2, . . .): ∆ ops (n, T j , r j ) = 2, 2, 2, 2, 2, 2, . . .

(b) For τi ∼>1/2 → τj ∆ ops (0, T j , r j ) (c) For τi tail → τj R j ∆ ops (0, ...) ∆ ops (1, ...) ∆ ops (2, ...) (d) For τi fby → τj R j ∆ ops (1, T j , r j ) ∆ ops (4, T j , r j ) ∆ ops (2, T j , r j ) ∆ ops (5, T j , r j ) (e
• If we take T i = 2, T j = 3, r j = 0, ops = * ˆ2./ˆ3, we have (for n = 0, 1, 2, . . .): ∆ ops (n, T j , r j ) = 0, 4, 2, 0, 4, 2, 0, 4, 2, . . .

• If we take T i = 1, T j = 2, r j = 4, ops = /ˆ2.tail.tail, we have (for n = 0, 1, 2, . . .): ∆ ops (n, T j , r j ) = 4, 3, 2, 1, 0, 1, 0, 1, 0, . . .

Notice that the sequence of values of ∆ ops (n, T j , r j ), for increasing values of n, is ultimately periodic (it follows a repetitive pattern after an initial prefix). Our deadlines adjustment technique relies on this key property (see next section), it allows us to represent the sequence of adjusted deadlines of a task as an ultimately periodic pattern (this will be proved in the next section).

As our proofs will heavily rely on function ∆ ops , we notice the following properties:

• Case tail: If n = 0: ∆ tail.ops (0, T j , r j ) = g ops (0) + r j -r ops (r j , T j ) + T ops (T j ) = ∆ ops (0, T j , r j ) + T ops (T j )

Otherwise:

∆ tail .ops (n, T j , r j ) = g ops (n -1)T j -nT ops (T j ) + r j -r ops (r j , T j ) + T ops (T j ) = g ops (n -1)T j -(n -1)T ops (T j ) -T ops (T j ) + r j -r ops (r j , T j ) + T ops (T j ) = ∆ ops (n -1, T j , r j )

• Case ::: ∆ ::.ops (n, T j , r j ) = g ops (n + 1) -nT ops (T j ) + r j -r ops (r j , T j ) -T ops (T j ) = g ops (n + 1) -(n + 1)T ops (T j ) + T ops (T j ) + r j -r ops (r j , T j ) -T ops (T j ) = ∆ ops (n + 1, T j , r j )

• Cases when and whennot are trivial.

Main property

We are now ready to prove the main property:

Theorem 3. For all precedence τ i ops → τ j , for all n:

R τ j [gops(n)] ≥ R i [n]
Proof. This is equivalent to proving that ∆ ops (n, T j , r j ) ≥ 0 for all n. The proof is done by induction on ops. First, for the base of the induction. We have ∆ (n, T j , r j ) = 0, so this case is proved. Now, the induction hypothesis is ∆ ops (n, T j , r j ) ≥ 0.

• We then prove the induction steps. For case /ˆk, we have:

∆ /ˆk.ops (n, T j , r j ) = ∆ ops ( n/k , T j , r j ) + (1 - ((n -1) mod k) + 1 k )T ops (T j )
Notice that (n -1) mod k ≤ k -1 and:

(n -1) mod k ≤ k -1 ⇔ (n -1) mod k + 1 ≤ k ⇔ (n -1) mod k + 1 k ≤ 1 ⇔ 1 - (n -1) mod k + 1 k ≥ 0
As T ops (T j ) ≥ 0, from the induction hypothesis this induction step is proved.

• For case ∼>, we have:

∆ ∼>q.ops (n, T j , r j ) = ∆ ops (n, T j , r j ) + q * T ops (T j )

which is positive from the induction hypothesis (as q can only be positive).

• For case fby, we have:

∆ fby.ops (n, T j , r j ) = ∆ ops (n + 1, T j , r j ) + T ops (T j )

which is positive from the induction hypothesis (as T ops (T j ) is positive).

• For case tail, in the case n = 0, we have:

∆ tail.ops (0, T j , r j ) = ∆ ops (0, T j , r j ) + T ops (T j )

which is positive from the induction hypothesis (as T ops (T j ) is positive).

Otherwise (n > 0):

∆ tail .ops (n, T j , r j ) = ∆ ops (n -1, T j , r j )
which is positive from the induction hypothesis.

• The remaining cases are deduced trivially from the induction hypothesis and from Property. 11, so the property is proved.

As an immediate consequence, we have:

Corollary 1. For all τ j ∈ pred(τ i ):

R i ≥ max(R i , max(R j ))
As a consequence, release dates adjustment is directly ensured by the synchronization constraints imposed by the clock calculus.

Particular case: delays

The following property allows us to ignore precedences for which ops contains a delay during deadlines adjustment:

Property 12. For a precedence τ i ops → τ j such that ops contains a delay, R j

[g ops (n)] ≥ R i [n] + T i .
Proof. This is equivalent to proving that for all, ∆ ops (n, T j , r j ) ≥ T i for all n, if ops contains a delay. The proof is done by induction on ops.

• First, for the base of the induction: ∆ fby.ops (n, T j , r j ) = ∆ ops (n + 1, T j , r j ) + T ops (T j )

We just proved that ∆ ops (n + 1, T j , r j ) ≥ 0, so: ∆ fby.ops (n, T j , r j ) ≥ T ops (T j ) So the property holds in this case;

• Then, the induction hypothesis is that ops contains a fby and that the property holds for ops.

Assuming this hypothesis, we prove that we can deduce that the property holds for op.ops for any op, except if op is an operation * ˆk (hypothesis of Sect. 9.2.5). First for case /ˆk:

∆ /ˆk.ops (n, T j , r j ) = ∆ ops ( n/k , T j , r j ) + (1 - ((n -1) mod k) + 1 k )T ops (T j )
We proved that (1 -((n-1) mod k)+1 k )T ops (T j ) ≥ 0, so from the induction hypothesis:

∆ /ˆk.ops (n, T j , r j ) ≥ T ops (T j )

• For the case ∼>:

∆ ∼>q.ops (n, T j , r j ) = ∆ ops (n, T j , r j ) + q * T ops (T j )

We have q * T ops (T j ) ≥ 0, so from the induction hypothesis:

∆ ∼>q.ops (n, T j , r j ) ≥ T ops (T j )

• For the case tail, in the case n = 0: ∆ tail.ops (0, T j , r j ) = ∆ ops (0, T j , r j ) + T ops (T j )

So, from the induction hypothesis we have:

∆ tail.ops (0, T j , r j ) ≥ T ops (T j )

Otherwise:

∆ tail .ops (n, T j , r j ) = ∆ ops (n -1, T j , r j )
So the property is true from the induction hypothesis;

• The remaining cases are deduced trivially from the induction hypothesis and from Property. 11, so the property is proved.

Deadline calculus

We have proved that release dates adjustment is not required in our context. We can now proceed to deadlines adjustment, which we call deadline calculus. The principle of the deadline calculus come from the observation that the constraint relating d i [n] to d j [g ops (n)] is a periodic constraint. We will show that there always exists some integers b, p such that for all n > b, d

* i [n] = d * i [n + p].
As a consequence, the sequence of the relative deadlines of the instances of a task is ultimately periodic. We call such patterns deadline words. The deadline calculus performs the encoding of task precedences by computing a deadline word for each task of the task graph.

Transposing the adjustment to relative deadlines

First, we need to transpose the absolute deadlines adjustment formula to relative deadlines adjustment to fit our task model. The function ∆ ops plays a key role in this transposition. Indeed: Property 13. For all precedence τ i ops → τ j :

D i [n] ≤ D j [g ops (n)] -C j ⇔ d i [n] ≤ d j [g ops (n)] + ∆ ops (n, T j , r j ) -C j Proof. D i [n] ≤ D j [g ops (n)] -C j ⇔ R i [n] + d i [n] ≤ R j [g ops (n)] + d j [g ops (n)] -C j ⇔ d i [n] ≤ d j [g ops (n)] + R j [g ops (n)] -R i [n] -C j ⇔ d i [n] ≤ d j [g ops (n)] + ∆ ops (n, T j , r j ) -C j
The function ∆ ops thus represents the difference between the dates which d i and d j are relative to. Thanks to Property 12, we notice that for a precedence τ i ops → τ j , if ops contains a delay we do not need to adjust d i [n] (for any n). Indeed:

D i [n] ≤ D j [g ops (n)] -C j ⇔ d i [n] ≤ R j [g ops (n)] -R i [n] + d j [g ops (n)] -C j By default, we have d i [n] ≤ T i , so from Property 12, by default d i [n] ≤ R j [g ops (n)] -R i [n]. Fur- thermore, d j [g ops (n)] -C j ≥ 0 (otherwise the system is not schedulable), so by default d i [n] ≤ R j [g ops (n)] -R i [n] + d j [g ops (n)] -C j
and thus we do not need to encode precedences that contain a delay.

Task deadlines vs task instance deadlines

Figure 10.4 shows an example that is schedulable when we set different deadlines for different instances of a task, while it is not when we use the same deadline for every instance of the task. Indeed, as shown in Fig. 10

.4(b), if we set d B [n] = 6 for n ∈ N, d A [n] = 2 for n ∈ 2N and d A [n] = 4 for n ∈ 2N + 1, the task set {A, B} is schedulable: A[0] is scheduled during time interval [0, 2[, B[0] during [2, 6[, A[1]
during [6, 8[ and this schedule repeats indefinitely. Now, if we set the same deadline for all instances of A, that is d A [n] = 2 for n ∈ N, then either A[1] or B[0] will miss their deadline (the absolute deadline 6). This motivates the introduction of task deadline words.

imported node A( i : i n t ) r e t u r n s ( o : i n t ) wcet 2 ; imported node B ( i : i n t ) r e t u r n s ( o : i n t ) wcet 4 ; node M( i : r a t e ( 4 , 0 ) ) r e t u r n s ( o : r a t e ( 8 , 0 ) due 6 ) l e t o=B (A( i ) / ^2 ) ; t e l So we can apply the induction hypothesis and:

∆ * ˆk.ops (n + P (ops)/gcd(P (ops), k), T j , r j ) = ∆ ops (kn, T j , r j ) = ∆ * ˆk.ops (n, T j , r j )

• Then, for case tail, in the case n = 0: ∆ tail.ops (0 + P (ops), T j , r j ) = ∆ ops (0 + P (ops), T j , r j ) + T ops (T j )

So from the induction hypothesis:

∆ tail.ops (0 + P (ops), T j , r j ) = ∆ ops (0, T j , r j ) + T ops (T j ) = ∆ tail.ops (0, T j , r j )

So the property is true for this case.

Otherwise:

∆ tail .ops (n + P (ops), T j , r j ) = ∆ ops (n -1 + P (ops), T j , r j )

Notice that:

n ≥ pref(tail.ops) ⇔ n ≥ pref(ops) + 1 ⇔ (n -1) ≥ pref(ops)
So we can apply the induction hypothesis and:

∆ tail .ops (n + P (ops), T j , r j ) = ∆ ops (n -1, T j , r j ) = ∆ tail .ops (n, T j , r j )

• Then, for case ::: ∆ ::.ops (n + P (ops), T j , r j ) = ∆ ops (n + 1 + P (ops), T j , r j )

Notice that:

n ≥ pref(::.ops) ⇒ n ≥ max(pref(ops) -1, 0) ⇒ n + 1 ≥ pref(ops)
So we can apply the induction hypothesis and:

∆ ::.ops (n + P (ops), T j , r j ) = ∆ ops (n + 1, T j , r j ) = ∆ ::.ops (n, T j , r j )

• Then, for case fby:

∆ fby.ops (n + P (ops), T j , r j ) = ∆ ops (n + 1 + P (ops), T j , r j ) + T ops (T j , r j )

Notice that:

n ≥ pref(fby.ops) ⇒ n ≥ max(pref(ops) -1, 0) ⇒ n + 1 ≥ pref(ops)
So we can apply the induction hypothesis and:

∆ fby.ops (n + P (ops), T j , r j ) = ∆ ops (n + 1, T j , r j ) + T ops (T j ) = ∆ fby.ops (n, T j , r j )

Algorithm 8 Computing task deadline words of a reduced task graph 1: for Every τ i do 2:

if τ i is asensor or an actuator with a deadline constraint (eg τ i due n) then 3:

w i ← (n) ω 4: else w i ← (T i ) ω 5:
end if 6: end for 7: S ← List of tasks without successors 8: while S is not empty do 9:

Remove the head τ j of S 10:

for Each τ i such that τ i ops → τ j and fby ∈ ops do 11:

w i ← min(w i , ∆ ops (T j , r j ) + W ops (w j ) -C j ) 12:
Remove the precedence τ i ops → τ j from the graph 13:

if τ i has no other successor then → B. We have pref(/ ˆ2) = 0, P (/ ˆ2) = 2 and ∆ /ˆ2 (T B , r B ) = (0.4) ω . We also have W /ˆ2 (w B ) = (6) ω . So, this precedence imposes that w A ≤ (0.4) ω + (6) ω -4 ie w A ≤ (2.6) ω . w A was initialised to (4) ω first, so we have w A = min((4) ω , (2.6) ω = (2.4) ω .

For concision, let d n denote the sequence consisting of n repetitions of d (for instance, 5 3 = 5.5.5). On the example of the FAS of Fig. 5

Optimality

By construction, our precedence encoding algorithm satisfies the following property:

Lemma 2. For all τ i ops → τ j , the deadline words w i and w j computed by Alg. 8 satisfy the following property:

∀n, n , τ i [n] → τ j [n ] ⇒ w i [n] ≤ W ops (w j )[n] + ∆ ops (w j , r j )[n] -C j ∧ n = g ops (n)
As a consequence, we have: Corollary 2. For all reduced task graph g = (V, E), for all τ i in V , let w i denote the deadline word computed by Alg. 8 for τ i , let

D * i [n] = R i [n] + w i [n].
Then we have:

D * i [n] = min(D i [n], min(D * j [n ] -C j )) (for all τ j [n ] ∈ succ(τ i [n])
This property states that for every task instance of a reduced task graph, our encoding algorithm respects the deadline adjustment formulae of [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF]. We have proved in Sect. 10.2.3 that the release date of every task instance of a reduced task graph also respects the release date adjustment formulae of [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF]. Therefore, the result established in [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF] hold for our encoding technique and so:

Theorem 4. Let g = ({τ i (T i , C i , r i , d i ), 1 ≤ i ≤ n}, E) be a reduced task graph. Then, let s = {τ i (T i , C i , r i , w i ), 1 ≤ i ≤
n} be a task set such that for all τ i , w i is the deadline word computed by Alg. 8 for τ i . Then: g is schedulable if and only if s is schedulable.

EDF is optimal for scheduling the encoded task set, so as a corollary we have: Corollary 3. Let g be a reduced task graph and s be the task set computed by our encoding algorithm. Then:

g is schedulable if and only if s is schedulable with EDF.

Complexity

Property 16. For every w i = u i .(v i ) ω computed by Alg. 8:

|u i | = max({0, pref (
ops), pref'(ops), for all τ i ops → τ j }} (10.1)

|v i | = lcm({P (ops), P (ops, w j ), for all τ i ops → τ j }) (10.2) |v i | ≤ lcm({T k , for all τ k }) (10.3)
Proof. Properties 10.1 and 10.2 directly come from the definition of the corresponding operations. For part 10.3, let us first prove that for all τ i ops → τ j , P (ops)|T j . Let h -1 ops denote the inverse of function T ops , ie T j = h -1 ops (T i ). We prove by induction that P (ops)|h -1 ops (T i ) and so that P (ops)|T j .

• The base of the induction is obviously true: P ( ) = 1 and 1|T j ;

• Then, the induction hypothesis is P (ops)|h -1 ops (T i ) for some ops. We first prove the induction step for case * ˆk. We have h -1 * ˆk.ops (T j ) = h -1 ops (T j )/k, which implies k|h -1 ops (T j ). From the definition of gcd, there exists q, q , with q ∧ q = 1 such that:

q = P (ops)/gcd(P (ops), k);

Task instances communications Definition

We mentioned that the definition of task instance precedences of Sect. 10.2.2 is an over-approximation of task instance data-dependencies. Task instance data-dependencies can be deduced from the definition of task instance precedences as follows:

Property 18. Let τ i [n] → d τ j [n ] denote a data-dependency from τ i [n] to τ j [n ]. For any precedence τ i ops → τ j : τ i [n] → d τ j [n ] ⇔ n ∈ [g ops (n), g ops (n + 1)[ Proof.
From the definition of g ops and from the semantics of predefined operators.

According to this definition, τ i [n] may:

• Not be consumed at all: when g ops (n + 1) = g ops (n);

• Be consumed exactly once: when g ops (n + 1) = g ops (n) + 1;

• Be consumed n times: when g ops (n + 1) = g ops (n) + n. This is illustrated in Fig. 10.6. The first line gives the values produced by the task τ i and the following lines give the values consumed by the task τ j for different extended precedences. date 0 5 10 15 20 25 30 35 40 ... This implies the following data-dependencies:

τ i x 0 x 1 x 2 x 3 x 4 ... τ i * ˆ2 → τ j x 0 x 0 x 1 x 1 x 2 x 2 x 3 x 3 x 4 ... τ i /ˆ2 → τ j x 0 x 2 x 4 ... τ i ∼>1/2 → τ j x 0 x 1 x 2 x 3 ... τ i tail → τ j x 1 x 2 x 3 x 4 ... τ i tail .0 :: → τ j 0 x 1 x 2 x 3 x 4 ...
• τ i * ˆ2 → τ j , we have (for n = 0, 1, 2, . . .): g * ˆ2 (n) = 0, 2, 4, 6, . . . Thus, τ i [0] → d τ j [0], τ i [0] → d τ j [1], τ i [1] → d τ j [2], τ i [1] → d τ j [3], ... • τ i /ˆ2 → τ j , we have (for n = 0, 1, 2, . . .): g /ˆ2 (n) = 0, 1, 1, 2, 2, 4, . . . Thus, τ i [0] → d τ j [0], τ i [2] → d τ j [1], τ i [2] → d τ j [4], ... • τ i ∼>1/2 → τ j , we have (for n = 0, 1, 2, . . .): g ∼>1/2 (n) = 0, 1, 2, 3, . . .. Thus, τ i [0] → d τ j [0], τ i [1] → d τ j [1], τ i [2] → d τ j [2], ... • τ i tail → τ j , we have (for n = 0, 1, 2, . . .): g tail 1/2 (n) = 0, 0, 1, 2, 3, . . .. Thus τ i [1] → d τ j [0], τ i [2] → d τ j [1], τ i [3] → d τ j [2], ... • τ i tail .0 ::
→ τ j , we have (for n = 0, 1, 2, . . .): g tail .0 ::

(n) = 1, 1, 2, 3, 4, . . .. Thus, τ i [1] → d τ j [1], τ i [2] → d τ j [2], τ i [3] → d τ j [3], ... Proof. Let [a n , b n [ denote sp i,j (n). Then: [a n , b n [∩[a n , b n [ = ∅ ⇔ a n < b n ⇔ R i [n ] < b n As τ i is periodic, we have for all p, R i [p + 1] = R i [p] + T i . Let k be such that R i [n ] = R i [n] + kT i . Then: [a n , b n [∩[a n , b n [ = ∅ ⇔ R i [n] + kT i < b n ⇔ kT i < b n -R i [n] ⇔ k < (b n -R i [n])/T i ⇔ k < |sp i,j (n)|/T i

Thus our property.

For any precedence τ i ops → τ j , as the sequence of values of |sp i,j (n)| is ultimately periodic (Property 20), we can choose the size of the corresponding communication buffer as follows:

max 0≤n≤pref(ops))+P (ops) ( |sp i,j (n)|/T i )
The protocol Now, we define the communication protocol for an extended precedence τ i ops → τ j . Let dep i,j denote the dependence word for this precedence. Let buf i,j be the communication buffer for this precedence (the size of which is computed as described above). First, we compute the sequence of instances of τ i that must write to buf i,j as described in Alg. 9. This is represented by the sequence write: τ i [n] writes to buf i,j only if write[n] is true. The communication protocol for the writer is then described in Alg. 10. This protocol tests whether the current instance of the producer must write to the buffer or not. If so, it writes to the cell buf i,j [cell] and the next time it will write in buf i,j [cell + 1], modulo the size of the buffer.

Algorithm 9 Computing when the producer must write, for τ i ops → τ j 1: write ← ; 2: for k = 0 to max(0, pref(ops)) + P (ops) -1 do 3:

if dep i,j [k] ≥ 1 then 4:
write ← write.true end if 8: end for We compute when the reader must change the cell of the buffer from which it reads as described in Alg. 11: τ j [n] changes the cell of the buffer from which it reads only if change[n] is true. The communication protocol for the reader is then described in Alg. 12. Each instance of the consumer reads a value from the current cell of the buffer. After each reading, the protocol tests whether the next instance must read from a different cell of the buffer and if so, it advances to the next cell modulo the size of the buffer. First, for the precedence τ i * ˆ3./ˆ2 → τ j , with T i = 3, T j = 2, we have:

• dep i,j (n) = (2.1) ω ; • sp i,j (n) = [0, 4], [3, 6], [6, 10], [9, 12], . . . • |buf i,j | = 2;
• write = (true) ω ;

• change = (f alse.true.true) ω . So:

• The sequence of cells τ i writes to is: 0, 1, 0, 1, 0, 1, . . .

• The sequence of cells τ j reads from is: 0, 0, 1, 0, 0, 1, . . . Then, for precedence τ i * ˆ2./ˆ3 → τ j , with T i = 2, T j = 3, we have:

• dep i,j (n) = (1.1.0) ω ; • sp i,j (n) = [0, 3[, [2, 6[, [4, 4[, [6, 9[, [8, 12[, . . . • |buf i,j | = 2;
• write = (true.true.f alse) ω ;

• change = (true) ω .

So:

• The sequence of cells τ i writes to is: 0, 1, none, 0, 1, none, . . .

• The sequence of cells τ j reads from are: 0, 1, 0, 1, 0, 1, . . . Finally, for precedence τ i ∼>2 → τ j , with T i = T j = 2, we have: [2, 8[, [4, 10[, [6, 12[, [8, 14[, . . .

• dep i,j (n) = (1) ω ; • sp i,j (n) = [0, 6[,
• |buf i,j | = 3; • write = (true) ω ; • change = (true) ω .

So:

• The sequence of cells τ i writes to is: 0, 1, 2, 0, 1, 2, . . .

• The sequence of cells τ j reads from are: 0, 1, 2, 0, 1, 2, . . . 10.5. Summary sp i,j (0) sp i,j (2)

sp i,j (1) sp i,j (3)
(a) For τi * ˆ3./ˆ2 → τj sp i,j (0) sp i,j (3)
sp i,j (1) sp i,j (4) (b) For τi * ˆ2./ˆ3 → τj sp i,j (0)
sp i,j (1)
sp i,j (2)
sp i,j (3)

Summary

In this chapter we have detailed how to translate the dependent task set extracted from a program into an equivalent independent task set.

• Precedences are encoded into task real-time attributes by adapting the technique proposed in [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF] to the case of extended precedences between tasks of different periods;

• The independent task set can then be scheduled optimally with EDF (scheduling the independent task set with EDF is equivalent to scheduling the dependent task set);

• Communication consistency is ensured by a non-blocking communication protocol based on databuffering;

• Schedulability analysis is standard and relies on existing results.

In the next chapter, we show that the independent task set can easily be translated into C code.

Chapter 11

Code generation

The last step of the compilation process translates the independent task set obtained for a program into C code. The real-time aspects of the C program are handled by the real-time primitives of the underlying OS. We use the real-time extensions of the POSIX standard [POS98], to be as independent from the OS choice as possible. Data-communications are handled by the communication protocol we described in the previous chapter. The task set is implemented as a set of communicating threads, contained in a single file. The file contains:

1. The allocation of the inter-task communication buffers.

2. One function (or more precisely one function pointer) for each task, which describes the computations performed by the task. This corresponds to the "step" function of classic synchronous languages.

3. The main function, which creates one thread for each task, initializes the scheduler and attaches the threads to the scheduler.

Communication buffers

We allocate one buffer for each communication. The size of the buffer is computed as described in Sect. 10.4.3. The buffer is named after its producer and its consumer. For instance, the buffer i_F_i corresponds to the communication from the sensor i to the input i of F (which we will denote F.i). For the communication from S.o to F.j, we have the declaration: int S_o_F_j[2]. The size of the buffer is 2 due to the delay between the two tasks.

Task functions

The function generated for a task mainly consists of an infinite loop, that wraps the function of the corresponding imported node with the code of the communication protocol. One step of the loop corresponds to the execution of one instance of the task. When it reaches the end of the current step of the loop, the function signals to the scheduler that the current task instance completed its execution so that it can schedule another task.

For instance, the code generated for the node S of our example is the following: • The variable cell_to_S_o_F_j tells in which cell of the buffer the value of S.o must be copied for F.j;

• The first instruction of the loop calls the external function provided by the programmer for node S ;

• Then, S alternatively copies the value produced by this function call in the cell 0 or in the cell 1 of the communication buffer for F (S_o_F_j[cell]=S_o). This way, S can write as soon as it executes and F can still read the previous value produced by S when it executes;

Threads creation

The main function creates one thread for each task:

• The computations performed by a thread are those described in the function pointer previously generated for the corresponding task ;

• The real-time attributes of the task are specified as attributes of the task.

For instance, the task corresponding to the imported node call F is created as follows:

pthread_t tF; pthread_attr_t attr; struct edf_sched_param user_param; pthread_attr_setappschedparam (&attr, &user_param); // system dependent pthread_create (&tF, &attr, F_fun, NULL) );

The way the attributes of a task are attached to the task actually depends a lot on the target OS. Here, we give an example with MARTE OS [START_REF] Rivas | POSIX-Compatible Application-Defined Scheduling in MaRTE OS[END_REF].

The threads are then attached to the scheduler. This part also depends on the the chosen OS. For instance, in MARTE OS, the scheduler is itself implemented as a thread and a specific primitive allows to attach the threads to the scheduler thread. This will be detailled in the next chapter.

Summary

In this chapter, we have shown that the translation of the set of independent tasks generated for a program into C code is pretty straightforward. This translation is the last phase of the compilation process.

The next chapter describes a prototype implementation of the complete compilation scheme. In particular, it details the implementation of an EDF scheduler supporting deadline words.

Chapter 12

Prototype implementation 12.1 The compiler A prototype of the compiler has been implemented in OCAML [START_REF] Leroy | The Objective Caml system release 3.09, Documentation and user's manual[END_REF]. It supports the language defined in Sec. 4.5 and implements the complete compilation chain, with proper error handling, in approximately 4000 lines of code. The current version of the prototype does not support array constructs and is restricted to constant parameters for rate transition operators (instead of static parameters like 3 * 2 for instance). The compiler performs the following sequence of computations: We chose to prototype the scheduler using MARTE Operating System [START_REF] Rivas | POSIX-Compatible Application-Defined Scheduling in MaRTE OS[END_REF], as this Operating System was designed to ease the implementation of application-specific schedulers while remaining close to the POSIX model. This OS also has the advantage of being executable on top of a standard LINUX station, which simplifies the debugging and test phases. The implementation can easily be adapted to another RTOS, as long as the RTOS allows to dynamically modify the priority of a task.

Application-defined schedulers in MARTE OS

MARTE OS supports application-defined scheduling policies implemented by a special kind of threads (scheduler threads), which can activate or suspend other threads. In this approach, scheduling is handled hierarchically. The root scheduler (the system scheduler) follows a classic fixed priority policy. Application-defined schedulers allow the definition of more complex, dynamic priority, applicationdependent policies, and are themselves scheduled by the root scheduler. Threads can then either be scheduled by the root scheduler (called system-scheduled threads) or by application-defined schedulers (called application-scheduled thread). An application-scheduled thread always has lower priority than its scheduler. MARTE OS proposes an API that extends the POSIX standard with a small set of extra functionalities, which allow an application to:

• Handle scheduling events;

• Execute scheduling actions;

• Create schedulers and application-scheduled threads.

An application scheduler is activated only when a scheduling event occurs, instead of actively testing the set of threads it must schedule at regular intervals of time to take scheduling decisions. Amongst other things, scheduling events will signal the scheduler that a thread has been created, has terminated, is blocked or is ready. Threads can also explicitly invoke the scheduler using a specific scheduling event.

The code of the scheduler is usually a loop where it waits for a scheduling event to be notified to it by the system, and then performs scheduling actions. Scheduling actions cause application-scheduled threads to be activated or suspended. They are prepared and stored in a list by the scheduler thread and then reported to the system. After reporting the scheduling actions, the scheduler becomes inactive until the next scheduling event.

EDF implementation

The MARTE distribution provides an implementation of the EDF scheduling policy that follows the principles described above. To summarize, each time a new task instance is released, the scheduler computes its absolute deadline and compares it with other ready task instances (the task instances that are released and unfinished), to sort ready task instances as a queue ordered by increasing deadlines. If the head of the queue (the most urgent task instance) is not the currently running task instance, the scheduler suspends the currently running task instance and activates the task instance at the head of the queue. Before extending this implementation to support deadline words, we had to make a few simplification to extend it so that it supports initial release dates greater than zero and relative deadlines lower than the task period.

Chapter 13

Conclusion on language compilation

We detailed the compilation of the language defined in the previous part of this dissertation. A program is first translated into a set of dependent real-time tasks, related by precedence constraints. The dependent task set can then be translated into an equivalent set of independent tasks. To this intent, we showed that task precedences can be encoded in the real-time attributes of the tasks. Scheduling the encoded independent task set is equivalent to scheduling the original dependent task set. Therefore, EDF provides an optimal scheduling policy for our systems. Once precedences are encoded, we proposed a communication protocol based on data-buffering, which ensures that inter-task communications respect the synchronous semantics defined for the program. An important feature of the protocol is that it does not require synchronization primitives such as semaphores. The independent task set can then easily be translated into a C program and can be executed on an existing RTOS, on the condition that the RTOS supports dynamic priority scheduling.

Several approaches have been proposed to translate synchronous programs into a set of communicating tasks, prior to our work. Automatic distribution of LUSTRE programs into several "computation locations" has been studied in [START_REF] Girault | Clock-driven automatic distribution of Lustre programs[END_REF][START_REF] Girault | Automatic rate desynchronization of embedded reactive programs[END_REF], where different computation location can either correspond to different processors or different tasks. In this approach, the distribution is driven by the clocks of the program. The programmer classifies the clocks of the program into different partitions. A program is then generated for each partition, it contains the computations of all the expressions the clock of which belongs to this partition. Computation locations are synchronized by blocking communications (semaphores). A similar approach was proposed for SIGNAL [START_REF] Aubry | Synchronous distribution of Signal programs[END_REF]. A SIGNAL program is translated into a graph representing the clock hierarchy of the program, where each vertex of the graph corresponds to the computations that must be performed for a given clock of the hierarchy and edges correspond to dependencies between the vertices. This graph can then be partitioned into several subgraph, each subgraph is translated into sequential code and encapsulated into a task. Finally, the problem of preserving the synchronous semantics of a set of concurrent task scheduled with a preemptive policy has been studied in [START_REF] Sofronis | A memory-optimal buffering protocol for preservation of synchronous semantics under preemptive scheduling[END_REF][START_REF] Sofronis | Embedded Code Generation from High-level Heterogeneous Components[END_REF]. The authors propose a buffering protocol called Dynamic Buffering Protocol similar to ours. The protocol allows to compute a sufficient number of cells2 required for the communication buffer and determines for each task instance in which cell of the buffer the task instance must write (for the producing task) or read (for a consuming task). The computation of the priorities of the tasks generated for a program is out of the scope of all these approaches, while we describe how to encode task precedences such that task priorities can be affected automatically by an EDF scheduler.

A lot of work has been done on the problem of scheduling multi-rate data-flow programs in the context of SDF graphs, for instance [LM87a, BBHL93, ZUE00, ODH06]. In particular, [START_REF] Ziegenbein | Dynamic response time optimization for SDF graphs[END_REF] studies the implementation of SDF graphs with a dynamic scheduler using preemption, which may seem close

Part III

Software architecture

The 30 software functionalities of the FAS are grouped into three real-time tasks. The task frequencies are 0.1Hz, 1Hz and 10Hz. The tasks are scheduled concurrently by a Rate-Monotonic scheduler. The frequency of the faster task (10Hz) is defined by the clock signal occurring every 100ms on the 1553 bus.

The computations performed by each task consist of a sequence of operations called services. The sequence of services of each task is computed statically and manually by the FAS developers. The 10Hz task does not only contain services activated with a frequency of 10Hz, it also contains slower services, for instance services with a frequency of 1Hz, which are activated only one out of 10 successive instances of the 10Hz task. The wcet of each service is very stable, thanks to algorithmic simplifications. For instance loops always execute the same number of steps.

The partial static scheduling of the system leads to a very predictable behaviour and to minimal task jitters. The main drawback to this approach is that it is not very flexible. Small changes in the system, such as increasing the wcet of a service to include new functionalities for instance, can sometimes require to completely recompute the schedule of the tasks. The automation of this scheduling would clearly simplify the design process.

Bus communications

The communications on the 1553 bus follow a pattern called major frame, which is repeated with a period of 10s. Communications can have a frequency of 0.1Hz, 1Hz, 2Hz or 5Hz. A major frame is then split into 10 minor frames of 1s each. A minor frame is then split into 10 processing frames (PCF) of 100ms each. Finally, each PCF is split into 8 subframes of 12.5ms each.

Each subframe corresponds to 8 transfers on the 1553 bus (either read or write). All the PCFs have 14.4. Summary the same format, for instance input acquisitions with a rate faster than 1Hz are performed in subframe 1, while input acquisitions with a rate slower than 1Hz are performed in subframe 3. Each input or output can only be transferred once in a PCF (ie not several times in different subframes). The major frame describes for a cycle of 10s which communications are performed in which slots.

Summary

In this chapter we described the current implementation of the FAS by EADS Astrium Space Transportation. The architecture of the system is simplified as much as possible to produce a predictable behaviour. We can see that the implementation of the system would greatly benefit from the automation of the scheduling of the system. The next chapter shows that this can be addressed with our language.

Chapter 15

Case study Implementation

In this chapter we study the implementation of a more detailed version of the Flight Application Software presented in the Introduction of this dissertation. We show that the language has sufficient expressiveness to specify the different aspects of the system and that the compiler supports easily such a larger example. Due to the complexity of the system, our objective is not to provide a complete implementation of the system but only to show that such an implementation is feasible. We emphasize the main difficulties for programming the system and detail how they can be solved with our language.

System specification

Our implementation is mainly based on two design documents provided by EADS Astrium Space Transportation. The first one is a chapter of the ATV-AS-ADD document, which specifies the real-time constraints of the different services of the FAS. The aim of this chapter of the document is to specify the (partial) order between the services of the system. For each service, it provides:

• The name of the service;

• The task that contains the service;

• The frequency of the service. The document also specifies acyclic services (activated only for a certain condition);

• The transfers between the service and the bus;

• The services before which it must be executed;

• The services after which it must be executed;

• Other information not relevant to our implementation.

From this document, we extract:

• The different services of the system;

• The frequency of each service: As we mentioned in the previous chapter, the frequency of the task that contains a service can be different from the real frequency of the service. In our case, we are only interested in the real frequency of the service. We discard asynchronous services as they are out of the scope of our language;

• Data-dependencies: To simplify, we consider that precedence constraints between services (when a service must be called before or after another) are always due to data-dependencies. Thus, for each precedence we have a data-dependency from the source service of the precedence to the destination service of the precedence. The document does not provide the complete data-flow between services of the system, which is very complex. Therefore, our implementation only considers part of the data-flow of the real system;

• System inputs/outputs: In the context of this case study, values read from and written to the bus are considered as inputs and outputs of the system. From the point of view of the program, the bus is part of the environment of the software.

The second document on which our implementation is based is the ATV Data Bus Profile Definition, which describes the bus traffic related to the DPUs. Among other things:

• It specifies for each bus transfer if the transfer is an input or an output of the DPUs;

• It details the structure of a major communication frame, specifying during which sub-frames transfers take place and at which frequency.

From this document, we extract:

• Inputs or outputs: We can determine whether the bus transfers specified in the previous documents correspond to inputs or outputs of the system;

• The period of inputs or outputs: Transfer frequencies directly provide the period of each input or output of the system;

• The phase of the inputs or outputs: For transfers that do not take place in every PCF (transfers of rate 1Hz or 0.1Hz), the PCF number determines the phase of the corresponding input/output. For instance, if an input has a rate of 1Hz and occurs in frame 2, its phase is 200ms.

Implementation overview

We write a program for a simplified version of the FAS based on the information detailed in the previous section. The code of the main node of the program is given in Appendix. A (the name of the services and of the program input and outputs have been changed for confidentiality reasons). To summarize:

• Each service is implemented as an imported node. To simplify, we only have one output for each imported node. For each imported node, we have one input for each service that precedes the service corresponding to the imported node. The exact wcet of the different services is unfortunately not available. We tried different values to check when the system is or is not schedulable. For instance, the service S4, which has four predecessors, is implemented as follows: imported node S4 (i0, i1, i2, i3 : int) returns (o: int) wcet 5;

• The main node assembles the different services together. We have one input in the main node for each transfer from the bus to the program. The strictly periodic clock of the input is determined by the frequency and the phase of the transfer. For instance, the following input corresponds to a transfer of frequency 1Hz that takes place in PCF 1: i4 : int rate(1000,1/10)

Specific requirements

• We have one output in the main node for each transfer from the program to the bus. The strictly periodic clock of an output is determined exactly as that of an input.

• We have one local variable in the main node for each output of an intermediate service, that is to say a service that does not directly produce an output of the program.

• Then we have one equation for each service call:

-The left-hand side of the equation is either a local variable if the service is an intermediate service, or an output of the main node if the service produces data that must be sent to the bus.

-The right-hand side of the equation is a node call of the imported node corresponding to the service. The arguments of the node call are either outputs produced by the services preceding this service or inputs of the main node if the service reads data from the bus.

-We use rate transition operators when the arguments of a node call do not have the same rate as the corresponding service. For instance, the equation for the service S4 is: x4=S4( x1/^10, x3/^10, x9, (0 fby x10) * ^10); For data produced by faster operations (x1 and x3) we use the under-sampling operator (/^10). For data produced by slower operations (x10) we use the over-sampling operator ( * ^10).

Specific requirements

In this section, we detail some specific requirements we encountered when programming the FAS case study or during our discussions with EADS Astrium Space Transportation.

Slow-to-fast communications

When data produced by a slow service is consumed by a fast service, we use the over-sampling operator * ˆ.

We have seen in Chap. 5 that it is usually preferable to insert a delay in the communication to avoid the reduction of the deadline of the slow service due to the over-sampling. For instance, if A is the slow service and B is 10 times faster, we write: node slow-fast1(i: rate (1000,0)) returns (o) let o=B((0 fby A(i)) * ^10); tel In this case the end-to-end latency, from the acquisition of a value of i to the production of the corresponding value of o, is one period of A plus one period of B, thus 1100ms.

We can achieve a better latency if B starts right after A completes. For instance, if A can complete in less than 600ms, we can write: node slow-fast2(i: rate (1000,0)) returns (o) let o=B((A(i)~>6/10) * ^10); tel In this case, the clock of B is (100, 6/10) and its deadline is 100 + 6 * 100 = 700ms. The deadline for A is 700 -C B . Thus the end-to-end latency is 700ms.

Results

Clocks are computed exactly as for our previous example. Concerning deadlines, the output o has relative deadline 0, so it must be produced at dates 400, 1400, 2400, . . . The first value of o, produced at date 400, is computed from the value 0. The second value of o, produced at date 1400 is computed from the first value of v (denoted v[0]). This requires v[0] to be produced before date 1400. However, v has clock (1000, 2/10), so v[0] cannot have a deadline bigger than 1200 (the end of its period). Thus v must be computed during time interval [200, 1200[ (instead of [200, 1400[). Similarly, the third value of o produced at date 2400 is computed from the second value of v, which is computed during time interval [1200, 2200[ and so on.

The reduction of the interval during which v must be computed does not seem too restrictive. Indeed, suppose we allow v[0] to be computed during time interval [200, 1400[, v[1] to be computed during time interval [1200, 2400[, and so on. As v=N(i), this means that, for instance, during time interval [1200, 1400[, N [0] and N [1] can be scheduled concurrently by the processor. This requires tasks to be reentrant, which is not supported by many RTOS or scheduling policies. Furthermore, it is not obvious that this concurrency is likely to lead to a system that has a better chance of being schedulable.

Results

We kept 180 services out of the 240 periodic services specified in the ATV-AS-ADD document, discarding services for which neither bus transfers nor precedences with other services were specified. The main node has 70 inputs and 9 outputs. The complete program is about 500 lines of code long. On a modest PC (AMD Athlon XP 2000+ with 512Mb of RAM), the compilation of the program takes about 1s to complete. Most of the compilation time is spent writing the C file, the other compilation phases are almost immediate. The code generated for the program is about 10000 lines long. It consists of 400 lines of communication buffer allocations, 4000 lines for the definition of the task functions and 6000 lines for threads creation (in the main function).

We did a simulation of the system with MARTE OS to check its schedulability. We also added an option to the compiler that produces a xml file describing the task set computed for a program. This output can then be taken as input by existing schedulability analysis tools. For instance, we analyzed the FAS program using CHEDDAR [START_REF] Singhoff | Cheddar: a flexible real time scheduling framework[END_REF]. We verified that with some wcet configurations the program produced by the compiler is schedulable, according to the simulation with MARTE OS, while if we take the same deadline for each instance of a task, that is to say we replace deadline words by simple deadlines, according to CHEDDAR the task set is not schedulable.

Summary

In this chapter we have shown that complex systems can be programmed with our language and that the compiler easily supports such systems. The next Chapter concludes this dissertation and details some possible perspectives for our work.

Conclusion 1 Summary

We proposed a language for the design of embedded control systems. It is built as a real-time software architecture language with synchronous semantics. It provides a high-level of abstraction with formal semantics, which allows to specify without ambiguities the integration of several locally mono-periodic systems into a globally multi-periodic system.

The language relies on a particular class of clocks, called strictly periodic clocks, which relate logical time (the sequence of instants of a program) to real-time. A strictly periodic clock is defined uniquely by its period and its phase and defines the real-time rate of a flow. Periodic clock transformations create new strictly periodic clocks from existing strictly periodic clocks, either by accelerating a clock, slowing it down or applying a phase offset. The language defines real-time primitives based on strictly periodic clocks and periodic clock transformations. These primitives enable the specification of multiple periodicity constraints, deadline constraints and rate transition operators, to precisely define communication patterns between operations of different rates. A program then consists of a set of imported nodes (implemented outside the program) assembled using rate transition operators. The semantics of the language is defined formally. Static analyses check that the semantics of a program is well-defined before translating it to lower-level code (mainly, they verify that the program is well-typed and well-synchronized).

If the static analyses succeed, the semantics of the program is well-defined. It is then translated into a set of communicating and concurrent real-time tasks, programmed in C. The translation first consists in extracting a set of real-time tasks from the program, related by precedence constraints due to datadependencies. Second, the precedences are encoded in the real-time attributes of the tasks, by adjusting the deadlines of tasks related by a precedence constraint. When a precedence relates two tasks of different rates, it does not relate all the instances of the related tasks, thus we need to set different deadlines for different instances of the same task. We introduce deadline words for this purpose, which define the sequence of deadlines of the instances of a task as a finite repetitive pattern. The set of tasks obtained after the adjustment of real-time attributes is scheduled using the EDF policy. Encoding precedences and using the EDF policy yields an optimal scheduling policy, meaning that if a schedule exists that satisfies all the real-time constraints of the original task set (periods, deadlines and precedences), using a preemptive dynamic priority policy, then our scheduling technique finds it. An inter-task communication protocol is defined to ensure that the task set respects the formal semantics of the original program. The protocol is based on data-buffering mechanisms and requires no synchronization primitives (like semaphores). The complete compilation scheme was prototyped in OCAML. It generates C code that uses the realtime extensions of POSIX. A modified EDF scheduler supporting deadline words was implemented with MARTE OS. The expressiveness of the language and the efficiency of the compiler have been validated on a realistic Flight Application Software provided by EADS Astrium Space Transportation.

In this section, we give an overview of some improvements or extensions, from which our work would benefit.

Task clustering

For now, each imported node of the program is translated into a separate task. This can lead to an important number of tasks (about 180 for the FAS case study). Such a large set of tasks may not be supported by the target RTOS or may result in an important run-time overhead, due to the time required to compute scheduling decisions.

This could be improved by grouping several imported nodes in the same task, referred to as task clustering. However, task clustering is not trivial and its impact on the number of task preemptions is hard to predict. This is clearly a subject for future work, all the more so as few results seem to be available on the subject.

Static priority scheduling

Though EDF enables better processor utilization, developers of real-time critical application often do not trust the dynamic aspects of EDF and prefer the DM policy. We are currently studying a compilation scheme based on a static priority scheduling policy, where task priorities are affected at compile time, taking task precedences into account. It seems that we could reuse the communication protocol defined in this dissertation with this new scheduling policy. Indeed, the protocol only requires task priorities to be affected so that the source task of a precedence is scheduled before the destination task of the precedence, which should be ensured by this new scheduling policy.

Modes

In many embedded control systems, the behaviour of the system is made up of different modes, corresponding to different phases of the mission of the system. For instance, the ATV does not have the same behaviour when it is moving towards International Space Station as when it is performing the docking maneuver. The behaviour of the system can be represented as a an automaton: the computations performed by the program depend on the current state of the automaton and on certain conditions, the systems transits from one state to another.

LUSTRE has been extended with State Machines (in the style of SYNCCHARTS) in [START_REF] Colaço | A conservative extension of synchronous data-flow with state machines[END_REF] to express such mode automata. The central idea of this translation is to translate the new State Machine constructs into (almost) standard synchronous data-flow, by relying on the use of clocks to translate the different states and transitions of the State Machines. The main benefit of this approach is that it allows to reuse existing synchronous data-flow compilers.

This approach could be transposed to our language, using the same constructs and the same translation techniques. This does however require to extend periodic clock transformations to Boolean clocks. Let us consider a simple example: The automaton A has two modes, active and idle, active being the initial state of the automaton. When the automaton is in state active it computes the output o by calling the node do_the_job. When it is in state idle, o simply takes the value of i. The automaton transits from state active to state idle whenever c is true.

The translation proposed in [START_REF] Colaço | A conservative extension of synchronous data-flow with state machines[END_REF] creates a clock condition vstate, which describes the current state of the automaton (its value can either be true for active or false for idle). The value of the clock changes whenever c is true. The equation defining o is then translated into: o=merge(vstate, do_the_job(i when vstate), i whennot vstate); So, the clock of the input of do_the_job is (100, 0) on vstate. In the definition of node do_the_job, the operation /^3 is applied to i. The clock of flow i/^3 must then be the result of the application of transformation / . 3 to clock (100, 0) on c. To support this example, we thus need to extend periodic clock transformations so that they can be applied to Boolean clocks.

This can be done by defining the following transformations (α is a clock, c a clock condition):

• (α on c)/ . k = α/ . k on c/ˆk, k ∈ N + * ;

• (α on c) * . k = α * . k on c * ˆk, k ∈ N + * ;

• (α on c) → . q = α → . q on c → . q, q ∈ Q.

This definition respects the constraint that in clock α on c, c must have clock α. In the example above, the clock of i/^3 would be ((100, 0) on c)/ . 3 or equivalently (300, 0) on c/ˆ3. This extension does however make clocks unification significantly more complex as clock conditions are not just names anymore but can be flow expressions. Furthermore, the semantics of multi-periodic nodes activated inside an automaton can be tricky. For instance, in the example above, if c is false at date 200, F will not execute between dates 200 and 300 but the first instance of S, which was released at date 0, can still execute until date 300. x27=S27 ( i 5 5 / ^1 0 ) ; x28=S28 ( i 1 1 ~> 3 / 1 0 , i 1 2 , i 1 3 , i 1 0 ~> 3 / 1 0 , i 9 ~> 3 / 1 0 , i 5 ~> 3 / 1 0 , i 6 ~> 3 / 1 0 , i 1 4 ~> 3 / 1 0 ) ; x29=S29 ( i 1 1 ~> 3 / 1 0 , i 1 2 , i 1 3 , i 1 0 ~> 3 / 1 0 , i 5 ~> 3 / 1 0 , i 6 ~> 3 / 1 0 , i 1 4 ~> 3 / 1 0 , x15 ~> 4 / 1 0 ) ; x65=S65 ( i 1 9 , i 9 , i 2 0 ) ; x66=S66 ( i 9 / ^1 0 , i 2 0 / ^1 0 , i 5 / ^1 0 , i 6 / ^1 0 ) ; x67=S67 ( i 2 4 , i 2 5 , i 2 6 , i 2 7 , i 2 8 , i 2 9 , i 3 0 , i 3 1 , i 3 2 , i 3 3 , i 3 4 , i 3 5 ) ; x94=S94 ( i 1 3 ~> 1 / 1 0 , i 1 2 ~> 1 / 1 0 , i 3 8 , i 8 ~> 4 / 1 0 , i 9 ~> 4 / 1 0 , x15 ~> 5 / 1 0 ) ; x95=S95 ( i 3 9 , i 1 3 / ^1 0 ~> 4 / 1 0 0 , i 3 8 / ^1 0 ~> 3 / 1 0 0 , i 1 2 / ^1 0 ~> 4 / 1 0 0 , x122 ) ; x124=S124 ( i 4 4 , i 4 5 , i 4 6 , i 3 9 , i 4 7 , x120 ~> 8 / 1 0 0 , x123 ~> 8 / 1 0 0 ) ; x125=S125 ( i 1 3 / ^1 0 ~> 1 / 1 0 0 , i 1 2 / ^1 0 ~> 1 / 1 0 0 , i 3 8 / ^1 0 , x120 ~> 5 / 1 0 0 , x123 ~> 5 / 1 0 0 ) ; x126=S126 ( i 5 / ^1 0 ~> 7 / 1 0 0 , i 6 / ^1 0 ~> 7 / 1 0 0 , i 8 / ^1 0 ~> 7 / 1 0 0 , i 9 / ^1 0 ~> 7 / 1 0 0 , i 1 0 / ^1 0 ~> 7 / 1 0 0 , i 1 4 / ^1 0 ~> 7 / 1 0 0 , i 4 8 , x120 ~> 8 / 1 0 0 , x123 ~> 8 / 1 0 0 ) ; x127=S127 ( x129 ) ;

FIG. 4 -

 4 FIG. 4 -Rythme de base dans le cas multi-périodique

FIG. 6 -

 6 FIG. 5 -Un programme ESTEREL

  xxvi

  FIG. 8 -Motifs de communication en AADL, pour une connexion de A vers B

  FIG. 10 -Horloges strictement périodiques.

FIG. 11 -

 11 FIG. 11 -Sous-échantillonnage périodique

FIG. 17 -

 17 FIG. 17 -Opérateurs d'horloges booléens

FIG. 18 -

 18 FIG. 18 -Application de transformations d'horloges booléennes à des horloges strictement périodiques Cependant, l'inverse n'est pas autorisé, les opérateurs basés sur les horloges strictement périodiques ne peuvent pas être appliqués à des flots dont l'horloge contient des transformations d'horloges booléennes. Par exemple, le flot (x when c) * ^2 est rejeté par le compilateur. En effet, ceci nécessiterait d'appliquer une transformation périodique d'horloge à une horloge non périodique.

xl 4 .

 4 Calcul d'horloges Définition 6. L'horloge strictement périodique parente d'une horloge est définie récursivement comme suit : pparent(α on c) = pparent(α) pparent(pck) = pck Nous imposons de plus que les horloges de toutes les entrées du noeud principal d'un programme soient strictement périodiques. Les horloges d'un programme dérivant toutes indirectement de ces horloges, ceci assure que pparent(ck) est une horloge strictement périodique pour toute horloge ck.

  FIG. 23 -Ordonnancement préemptif du noeud multi

  FIG. 25 -Extraction d'un graphe de tâche intermédiaire

F

Définition 10 .

 10 La condition d'activation cond(ck) d'une horloge ck se définit comme suit : cond(ck on c) = cond(ck) and c cond(pck) = true La condition d'activation d'une variable est la condition d'activation de son horloge. La condition d'activation d'une tâche se définit ensuite de la manière suivante : Définition 11. La condition d'activation cond i de τ i est :

D

  'après la propriété 9, on peut déduire que pour une précédence τ i ops → τ j , telle que ops contient un retard, il n'est pas nécessaire d'ajuster d i [n] (quel que soit n). Mots d'échéances Une échéance unitaire spécifie l'échéance relative d'une unique instance de tâche (un entier d). Un mot d'échéances définit la séquence des échéances unitaires d'une tâche. L'ensemble des mots d'échéances W d se définit par la grammaire suivante : w ::= u.(u) ω u ::= d | d.u Pour un mot w = u.(v) ω , u est le préfixe de w et (v) ω représente la répétition infinie du mot v, appelée le motif répétitif de w. De plus : -|u| représente la taille du mot fini u ; -w[n] représente la n ème échéance unitaire du mot w (n ∈ N). Si w = u.(v) ω alors : w[n] = u[n] si n < |u| v[(n -|u|) mod |v|] sinon -La relation d'ordre sur les mots d'échéances est l'ordre lexicographique ; -Le minimum de deux mots d'échéances est le minimum point-à-point des deux mots (ce qui nécessite éventuellement de déplier les deux mots pour obtenir des mots de même taille) ; -La somme de deux mots d'échéances est la somme point-à-point des deux mots ; -Le mot w = w i + k, avec w i ∈ W d et k ∈ N est tel que pour tout n, w[n] = w i [n] + k.

  liv

  τ j de S 10: for Chaque τ i tel que τ i ops → τ j et fby ∈ ops do 11: w i ← min(w i , ∆ ops (T j , r j ) + W ops (w j ) -C j ) 12: Supprimer la précédence τ i ops → τ j du graphe 13: if τ i n'a pas d'autre successeur then 14: Insérer τ i dans S notre algorithme d'encodage respecte la propriété suivante : Lemme 2. Pour tout τ i ops → τ j , les mots d'échéances w i et w j calculés par l'Alg. 2 vérifient la propriété suivante :

  Protocole de communication pour τ i ops → τ j , côté producteur 1: inst ← 0 ; cell ← 0 ; 2: while true do 3:

  Protocole de communication pour τ i ops → τ j , côté consommateur 1: inst ← 0 ; cell ← 0 2: while true do 3:Lire à partir de buf i,j[cell] 4:

  FIG. 33 -Dépendances de données

Overview

  LUSTRE[START_REF] Halbwachs | The synchronous data-flow programming language LUSTRE[END_REF] is a data-flow synchronous language. Each variable or expression of a program denotes a flow, an infinite sequence of values. The clock of a flow defines the instants when the flow is present or absent (bears no value). A LUSTRE program consists of a set of equations, structured into nodes, which define the values of the different variables of the program at each instant. The example below illustrates the different features of the language: node N(c: bool; i:int) returns (o:int) var v1: int; v2: int when c; let v1=0->pre(i); v2=(v1+1) when (true->c); o=current(v2); tel

Figure 1

 1 Figure 1.1: Behaviour of a simple LUSTRE program

  Figure 1.2: Simplified communication loop between operations FDIR and GNC US

  Figure 1.3: Auxiliary LUSTRE nodes

Figure 1 . 4 :

 14 Figure 1.4: Behaviour of auxiliary nodes

  Figure 1.5: Programming a multi-rate communication loop in LUSTRE, with a fast base rate (10Hz)

  Figure 1.6: Programming a multi-rate communication loop in LUSTRE, with a fast base rate (10Hz), splitting the slow operation

Figure 1

 1 Figure 1.7: Programming a multi-ate communication loop in LUSTRE, with a slow base rate (1Hz)

  Figure 1.8: Programming a multi-rate communication loop in LUSTRE, with a fast base rate, using realtime extensions

Figure 1 . 9 :

 19 Figure 1.9: Behaviour of a simple ESTEREL program

  Figure 1.10: Programming a multi-rate communication loop in ESTEREL

  Figure 1.11: Programming a multi-rate communication loop in ESTEREL/TAXYSThe tool TAXYS integrates the ESTEREL SAXO-RT compiler [WBC + 00] and the model checking tool KRONOS[START_REF] Daws | The tool KRONOS[END_REF]. The compiler translates the annotated program into a timed automaton[START_REF] Alur | A theory of timed automata[END_REF] and the automaton is then analyzed by the model checker to validate the real-time constraints of the program. As far as we know, the ESTEREL program is translated into sequential C code and the objective of this work is not to take advantage of a preemptive RTOS.

Figure 1

 1 Figure 1.12: Programming a multi-rate communication loop in SYNDEX, with a slow base rate

Figure 1 .

 1 Figure 1.13: A SDF graph

Figure 1 .

 1 Figure 1.14: SDF graph for a multi-rate communication loop

  Figure 1.15: Communications schemes in AADL, for a connection from A to B

Figure 1 .

 1 Figure 1.18: Programming a multi-rate communication loop in GIOTTO

2. 4

 4 Data-flow synchronous languages, an example: LUSTRE All the variables and expressions used in a LUSTRE program are flows. A flow consists of a sequence of values, potentially infinite, and of a clock. The clock of a flow defines the instants when the flow is present (defined). Each time the flow is present, it takes the next value in its sequence of values. For instance, the constant value 0 stands for an infinite constant sequence. Each time it is present, it has value 0. A LUSTRE program consists of a set of equations, which define the value of flows at each instant.

Figure 2 . 1 :

 21 Figure 2.1: Behaviour of clock operators

Figure 3 . 1 :

 31 Figure 3.1: Strictly periodic clocks.

  Deadline constraints have no impact on the clock of a flow and thus on synchronization constraints between two flows. If a flow f has clock (n, p) and deadline d and a flow f has clock (n, p) and deadline d (with d = d ), the flows are still considered synchronous and can be combined as they are produced during the same logical instants. Deadlines are only taken into account during the scheduling of the program.

  node under_sample(i: int rate (5,0)) returns (o: int rate (10,0)) let o=i/^2; tel e/ˆk only keeps the first value out of each k successive values of e. If e has clock α then e/ˆk has clock α/ . k. This behaviour is illustrated in Fig. 4.1. date 0 5 10 15 20 25 30 ...

Figure 4 . 1 :

 41 Figure 4.1: Periodic under-sampling

Figure 4 . 2 :

 42 Figure 4.2: Periodic over-sampling

Figure 4

 4 Figure 4.3: Phase offset

Figure 4 . 4 :

 44 Figure 4.4: Keeping the tail of a flow

Figure

  Figure 4.5: Flow concatenation

Figure 4 . 6 :

 46 Figure 4.6: Delaying a flow

Figure 4 . 7 :

 47 Figure 4.7: Boolean clock operators

Figure 4 . 8 :

 48 Figure 4.8: Applying Boolean clock transformations to strictly periodic clocks

  cst ::= true | false | 0 | ... var ::= x | var, var e ::= cst | x | (e, e) | cst fby e | N (e) | e when e | merge (e, e, e) | e/ˆk | e * ˆk | e ∼> q | tail (e) | cst :: e eq ::= var = e | eq; eq typ ::= int | bool in ::= x [: [typ] [rate(n, p)] [ before n ]] | in; in out ::= x [: [typ] [rate(n, p)] [ due n ]] | out; out decl ::= node N (in) returns (out) [ var var; ] let eq tel | imported node N (in) returns (out) wcet n; | sensor x wcet n | actuator x wcet n

Figure 4 .

 4 Figure 4.10: Synchronous temporised semantics of operators on strictly periodic clocks

S

  ρ (e * ˆk) = * ˆ#(S ρ (e), k) S ρ (e/ˆk) = /ˆ#(S ρ (e), k) S ρ (e ∼> k) = ∼> # (S ρ (e), k) S ρ (tail (e)) = tail # (S ρ (e)) S ρ (cst :: e) = :: # (cst, S ρ (e))

Figure 4 .

 4 Figure 4.11: Denotational semantics

  Figure 5.1: A simple example to illustrate the different communication patterns

  Fig. 5.1: node sampling(i: rate (10, 0)) returns (o) var vf, vs; let (o, vf)=F(i, (0 fby vs) * ^3); vs=S(vf/^3); tel

Figure 5 . 2 :

 52 Figure 5.2: Data sampling: choosing the place of the delay

Figure 5 . 3 :

 53 Figure 5.3: Data sampling, starting the sample on the second value

Figure 5 . 4 :

 54 Figure 5.4: Storing successive values of a flow (n = 3, init = 0)

  5, 9] [3, 7, 12] [4, 9, 15] [5, 11, 18] ...

Figure 5 . 5 :

 55 Figure 5.5: Computing the mean value of 3 successive "fast" values, (with init = 0)

  node non_harmonic(i: rate (60, 0)) returns (o1, o2, o3) let o1=A(i * ^3); o2=B(i * ^2); o3=C(i * ^3/^4); tel A has clock (20, 0), B has clock (30, 0) and C has clock (80, 0). The values consumed by the different nodes are given in Fig. 5.6. date 0 10 20 30 40 50 60 70 80 90 100 110 120 ...

Figure 5 . 6 :

 56 Figure 5.6: Flows of non-harmonic periods

Fig. 5 .

 5 Fig. 5.7 gives a program for the FAS of Fig. 1 using communication patterns based on data sampling.Each operation of the system is first declared as an imported node. The program then specifies the wcet of each sensor and each actuator of the system (for each input/output of the node FAS).The node FAS is the main node of the program. It first specifies the inputs (gyro, gps, str, tc) and outputs (pde, sgs, gnc, pws, tm) of the system with their rates (for instance gyro has period 100) and deadline constraints (for instance gnc has deadline 300). The body of the node then specifies the real-time architecture of the system. For instance, the first equation says that the node Gyro_Acq computes the variable gyro_acq from the input gyro and from the variable tm. As tm is produced with a period of 10s while Gyro_Acq has a period of 100ms, we over-sample tm by a factor 100. We use a delay before performing the over-sampling, to avoid reducing the deadline of node TM_TC. Some flows are over-sampled without using a delay, for instance when the outputs of the acquisition nodes are consumed by faster nodes (gps_acq * ^10 in the equation of the FDIR for instance).

  Figure 5.7: Programming the FAS with data-sampling

Figure 5 . 8 :

 58 Figure 5.8: Programming the FAS with data-queuing

Figure 6 . 2 :

 62 Figure 6.2: Types unification rules

  σ ::= ∀α 1 <: C 1 , ..., α m <: C m .cl cl ::= cl → cl | cl × cl | ck | c : ck ck ::= ck on c | ck on not c | pck pck ::= (n, p) | α | pck * . k | pck/ . k | pck → . q c ::= nm | X H ::= [x 1 : σ 1 , ..., x m : σ m ] Clock types are clock schemes (σ) quantified over a set of clock variables, which belong to different subtypes of clocks (C 1 , ..., C m ). A clock expression can be a functional clock (cl → cl), a clock product (cl × cl), a clock sequence (ck) or a dependency (c : ck).

  e < E P e ⇔|e| * . < |e | * . ∨ (|e| * . = |e | * . ∧ |e| /. < |e | /. )∨ (|e| * . = |e | * . ∧ |e| /. = |e | /. ∧ |e| →. < |e | →. )∨ (|e| * . = |e | * . ∧ |e| /. = |e | /. ∧ |e| →. = |e | →. ∧ [e] * . < [e ] * . )∨ (|e| * . = |e | * . ∧ |e| /. = |e | /. ∧ |e| →. = |e | →. ∧ [e] * . = [e ] * . ∧ [e] /. < [e ] /. )

Figure 6 . 4 :

 64 Figure 6.4: Clock unification rules

Figure 8

 8 Figure 8.1: Sequential execution of node multi

Figure 8 . 3 :

 83 Figure 8.3: Preemptive scheduling of node multi

Algorithm 7

 7 Expanding the content of a node N 1: for each node instance M (args) in the body of N do 2: for each argument arg of args do 3: add a local variable v to N 4: add an equation v = arg to N 5: end for 6: for each local or output variable of the node definition of M do 7: add a local variable to N 8: end for 9: for each equation of the node definition M do 10: add the equation to set of equations of N

Figure 9

 9 Figure 9.1: A task graph

  Figure 9.2: Expression dependencies

  Figure 9.4: An intermediate graph

Figure 9 . 5 :

 95 Figure 9.5: Graph after variable reduction

  Figure 9.6: Application of a graph transformation rule

  i .when τ c .ops j ops c .ops j (a) when transformation (same for whennot).

Figure 9 . 7 :

 97 Figure 9.7: The graph transformation system

  Figure 9.8: A reduced task graph

Figure 10

 10 Figure 10.1: Encoding extended precedences

  Figure 10.2: Task instance precedences, as defined by g ops

)

  Figure 10.3: Illustration of ∆ ops

  Figure 10.4: Scheduling the program requires the instances of A to have different deadlines

  the deadline calculus of the example of Fig.10.4. To simplify, we consider that the durations of the main node (M ) inputs and outputs are null. The calculus first computes w B = (6) ω due to the deadline constraint on o. Then it considers the precedence A /ˆ2

Figure 10

 10 Figure 10.6: Data-dependencies between task instances

10. 4 .

 4 Inter-Task Communications Algorithm 10 Communication protocol for τ i ops → τ j , on the producer side 1: inst ← 0; cell ← 0; 2: while true do 3: if write[inst] then 4: Write to buf i,j [cell] 5: cell ← (cell + 1) mod |buf i,j | Computing reading patterns for τ i ops → τ j 1: change ← ; 2: for k = 0 to max(0, pref(ops)) + P (ops) -1 do 3:if dep i,j [k] ≥ 1 then 4: for i = 0 to dep i,j [k] -2 do Communication protocol for τ i ops → τ j , on the consumer side 1: inst ← 0; cell ← 0 2: while true do 3: Read from buf i,j [cell] 4: if change[inst]then5: cell ← (cell + 1) mod |buf i,j | We illustrate the communication protocol on the examples of Fig. 10.7 (arrows denote data-dependencies).

  Figure 10.7: Data-dependencies

1.Figure 12

 12 Figure 12.1: The different parts of the prototype

Figure 14 . 1 :

 141 Figure 14.1: Hardware architecture of the ATV (FAS+MSU)

  node do_the_job(i) returns (o) var vf, vs; let (o, vf)=F(i, (0 fby vs) * ^3); vs=S(vf/^3, i/^3); tel node modes (i: (100, 0), c: (100, 0)) returns (o) let automaton A initial state active let o=do_the_job(i); tel until c do resume idle; state idle o=i; until c do resume active; tel

  x4=S4 ( x1 / ^1 0 , x3 / ^1 0 , x9 , ( 0 f b y x10 ) * ^1 0 ) ; x5=S5 ( ( 0 f b y x4 ) * ^1 0 ) ; x6=S6 ( x1 / ^1 0 ) ; x7=S7 ( i 5 5 / ^1 0 ) ; x8=S8 ( i 5 5 / ^1 0 0 ) ; x9=S9 ( x7 ) ; x10=S10 ( x8 ) ; x11=S11 ( i 5 5 ) ; x12=S12 ( x11 , ( 0 f b y x13 ) * ^1 0 ) ; x13=S13 ( i 5 5 / ^1 0 ) ; x14=S14 ( i 5 5 / ^1 0 ) ; x15=S15 ( x14 , x17 ) ; x16=S16 ( x14 , x17 ) ; x17=S17 ( i 5 5 / ^1 0 ) ; x18=S18 ( i 5 , i 6 , x15 ~> 1 / 1 0 ) ; x19=S19 ( i 5 5 ) ; x20=S20 ( x15 ~> 1 / 1 0 , x18 ) ; x21=S21 ( ( 0 f b y x15 ) * ^1 0 ~> 1 / 1 , ( 0 f b y x18 ) * ^1 0 , x19 ~> 1 / 1 ) ; x22=S22 ( i 7 ~> 1 / 1 0 , i 8 , i 9 , i 1 0 , x15 ~> 1 / 1 0 ) ; x23=S23 ( x22 , x27 ~> 1 / 1 0 ) ; x24=S24 ( x23 , x27 ~> 1 / 1 0 ) ; x25=S25 ( x24 , x27 ~> 1 / 1 0 ) ; x26=S26 ( x22 ) ;

  8 , i 1 9 , i 9 , i 2 0 , i 1 0 , i 2 1 , i 1 4 , i 2 2 , i 5 , i 6 , x15 ~> 1 / 1 0 ) ; x45=S45 ( x58 ) ; x46=S46 ( i 5 5 / ^1 0 ) ; x47=S47 ( i 5 5 / ^1 0 ) ; x48=S48 ( x32 ~> 9 / 1 0 , x34 ~> 9 / 1 0 , x36 ~> 6 / 1 0 , x38 ~> 9 / 1 0 , x42 ) ; x49=S49 ( i 5 5 / ^1 0 ) ; x50=S50 ( i 5 5 / ^1 0 , x62 , ( 0 f b y x63 ) * ^1 0 ~> 1 / 1 0 , ( 0 f b y x64 ) * ^1 0 ~> 1 / 1 0 ) ; x61=S61 ( x15 ~> 1 / 1 0 , x65 , ( 0 f b y x66 ) * ^1 0 ) ; x62=S62 ( x15 ~> 1 / 1 0 , x65 , ( 0 f b y x66 ) * ^1 0 ) ; x63=S63 ( x15 / ^1 0 ) ; x64=S64 ( x15 / ^1 0 ) ;

  , x74 , x75 ) ; x76=S76 ( i 3 7 / ^1 0 ~> 1 / 1 0 , i 1 9 , i 9 , i 2 0 , i 5 , i 6 , x15 ~> 1 / 1 0 ) ; x77=S77 ( x83 / ^1 0 ) ; x78=S78 ( i 3 7 ) ; x79=S79 ( i 1 9 / ^1 0 , i 9 / ^1 0 , i 2 0 / ^1 0 , x81 ~> 1 / 1 0 0 ) ; x80=S80 ( x77 ) ; x81=S81 ( i 5 5 / ^1 0 0 ) ; x82=S82 ( i 3 7 , ( 0 f b y x80 ) * ^1 0 , x83 ) ; x83=S83 ( i 5 5 ) ; x84=S84 ( x83 / ^1 0 0 , x152 / ^1 0 ) ; x85=S85 ( x83 ) ; x86=S86 ( x156 ) ; x87=S87 ( x83 / ^2 ) ; x88=S88 ( i 5 5 ) ; o3=x88 ; x89=S89 ( i 5 5 / ^1 0 0 ) ; x90=S90 ( x55 , x83 , ( 0 f b y x84 ) * ^1 0 0 , x85 , ( 0 f b y x86 ) * ^2 , ( 0 f b y x87 ) * ^2 , x88 ) ; x91=S91 ( x90 / ^1 0 ) ; x92=S92 ( x83 / ^1 0 0 , x89 , x91 / ^1 0 ) ; x93=S93 ( x90 ) ; o4=x93 ;

  i 5 ~> 4 / 1 0 , i 6 ~> 4 / 1 0 ) ; x104=S104 ( i 4 1 ~> 4 / 1 0 , i 4 0 , i 8 ~> 4 / 1 0 , i 1 0 ~> 4 / 1 0 ) ; x105=S105 ( i 5 5 / ^1 0 ) ; x106=S106 ( x103 , x105 ~> 5 / 1 0 ) ; x107=S107 ( x106 ) ; o5=x107 /^10~> 1 1 / 2 0 ; x108=S108 ( x15 ~> 5 / 1 0 , x104 , x106 ) ; x109=S109 ( i 5 / ^1 0 , i 6 / ^1 0 ) ; x110=S110 ( x15 / ^1 0 ~> 1 / 1 0 0 , x109 ) ; x111=S111 ( x108 , ( 0 f b y x110 ) * ^1 0 ~> 4 / 1 0 ) ; x112=S112 ( i 4 2 , i 4

  x128=S128 ( i 5 5 / ^1 0 0 ) ; x129=S129 ( x122 ) ; x130=S130 ( x128 ) ; x131=S131 ( x130 ) ; o6 = ( 0 f b y x131 ) * ^1 0 ~> 6 / 1 0 ; x132=S132 ( i 4 9 , i 5 0 , i 5 1 ) ; x133=S133 ( i 5 5 / ^1 0 ) ; x134=S134 ( x133 ) ; x135=S135 ( x134 ) ; x136=S136 ( x135 ) ; x137=S137 ( i 5 2 , ( 0 f b y i 5 3 ) * ^1 0 , ( 0 f b y i 5 4 ) * ^1 0 ) ; x138=S138 ( x137 ) ; x139=S139 ( x138 ) ; x140=S140 ( x139 ) ; x141=S141 ( x140 ) ; x142=S142 ( x15 / ^1 0 ) ; x143=S143 ( x15 / ^1 0 ) ; x144=S144 ( i 5 / ^1 0 , i 5 / ^1 0 ) ; x145=S145 ( x15 / ^1 0 ) ; x146=S146 ( x151 ) ; x147=S147 ( i 5 5 / ^1 0 ) ; x148=S148 ( x153 ) ; x149=S149 ( x155 ) ; x150=S150 ( x149 / ^1 0 ) ; x151=S151 ( i 5 5 / ^1 0 ) ; x152=S152 ( i 5 5 / ^1 0 ) ; x153=S153 ( x147 ) ; x154=S154 ( x156 / ^5 ) ; x155=S155 ( x147 ) ; x156=S156 ( i 5 5 / ^2 ) ; o7=x156 ~> 1 / 1 0 ; x157=S157 ( ( 0 f b y x49 ) * ^5 , x51 / ^2 , ( 0 f b y x52 ) * ^5 , ( 0 f b y x53 ) * ^5 , ( 0 f b y x56 ) * ^5 , x87 )

  IntroductionLe travail présenté dans ce mémoire est financé par EADS Astrium Space Transportation.

	Contents
	List of Figures	xi
	Résumé	xv
	Résumé étendu	xvii

1 Etat de l'art . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xx 1.1 Langages de programmation bas-niveau . . . . . . . . . . . . . . . . . . . xx 1.2 Les langages synchrones . . . . . . . . . . . . . . . . . . . . . . . . . . . xxi 1.3

  Le langage fournit un typage polymorphe, ce qui est désormais une caractéristique relativement courante pour un langage de programmation. Les types des variables du programme peuvent être laissés non-spécifiés, auquel cas ils seront inférés par le compilateur. Par exemple :

node infer(i:int) returns (o) let o=i; tel Dans cet exemple, le compilateur infère que le type de o est int. Un exemple de polymorphisme est donné ci-dessous : node poly(i,j) returns (o,p) let o=i; p=j; tel Le compilateur peut seulement déduire que o et i ont le même type (disons α), et que p et j ont le même type (disons β). Le noeud poly peut être instancié avec différents types, par exemple le programme suivant est correct :

  Le langage a pour but de permettre de spécifier des contraintes temps réel avec un niveau d'abstraction élevé. Les primitives que nous introduisons sont destinées à spécifier des contraintes relatives à l'environnement du système (des contraintes physiques par exemple) plutôt que des contraintes liées à des considérations d'implémentation. Les entrées et sorties d'un noeud représentent son interface avec l'environnement. Les contraintes temps réel sont donc spécifiées sur les entrées et sorties d'un noeud : ce sont des contraintes venant de l'environnement du système.La période d'une entrée ou d'une sortie d'un noeud peut être spécifiée de la manière suivante :Dans cet exemple, i a pour période 10 et pour phase 1 2 , donc sa séquence d'horloge est (5, 15, 25, 35, . . .). Une contrainte d'échéance définit une échéance à respecter pour le calcul d'un flot, relative au début de la période du flot. Les contraintes d'échéance peuvent être spécifiées sur des sorties de noeuds : spécifie que x a pour contrainte d'échéance d. Si x est présent à la date t, alors x doit être calculé avant la date t + d.Des contraintes d'échéance peuvent aussi être spécifiées sur des entrées, mais leur signification est alors légèrement différente :

	node deadline(i: int rate (10,0)) returns (o: int rate (10,0) due 8)
	let
	...
	tel
	x: due d node deadline(i: int rate (10,0) before 2) returns (o: int)
	let
	...
	tel
	node periodic(i: int rate (10,0)) returns (o: int rate (5,0))
	let
	...
	tel

x: rate (n,p) spécifie que l'horloge de x est l'horloge strictement périodique (n, p). Ainsi, i a pour période 10 et o a pour période 5.

Le développeur peut aussi spécifier la phase d'un flot périodique : node phased(i: int rate (10,1/2)) returns (o: int rate (10,0)) let ... tel

  élimine la première valeur de e. Si e a pour horloge α, alors tail (e) pour horloge α → . 1, donc tail (e) est actif pour la première fois une période plus tard que e. Plusieurs tail éliminent plusieurs valeurs du flot. Ceci est illustré Fig.14. date 0 10 20 30 40 50 60 ...i i 0 i 1 i 2 i 3 i 4 i 5 i 6 ... o1 i 1 i 2 i 3 i 4 i 5 i 6 ... o2 i 2 i 3 i 4 i 5 i 6 ...Le développeur peut laisser les rythmes des entrées ou sorties d'un noeud non spécifiés, ils seront alors inférés par le calcul d'horloges. Par exemple : Le calcul d'horloges infère que o a pour horloge (20, 0).Les opérateurs de transition de rythme et les opérateurs de décalage de phase peuvent être appliqués sur des flots dont le rythme est inconnu. Par exemple, la définition suivante est tout à fait valide :

	Résumé étendu
	tail (e) FIG. 14 -Eliminer la tête d'un flot
	Le langage contient aussi un opérateur de concaténation « :: » :
	node init(i: int rate(10,0))
	returns (o1: int rate(10,0); o2: int rate(10,0))
	var v1,v2;
	let
	(v1,v2)=tail_twice(i);
	o1=0::v1;
	o2=0::0::v2;
	tel
	node under_sample(i: int rate(10,0)) returns (o: int)
	let
	o=i/^2;	FIG. 13 -Décalage de phase
	tel
		L'opérateur tail renvoie la queue d'un flot :
		node tail_twice(i: int rate(10,0))
		returns (o1: int rate(10,1); o2: int rate(10,2))
	let node under_sample(i: int) returns (o: int)
	let	o1=tail(i);
	o2=tail(o1); o=i/^2;
	tel	tel
	xxxvi		xxxv

cst :: e produit cst une période avant la première valeur de e et produit ensuite e. Si e a pour horloge α, alors cst :: e a pour horloge α → . -1. Ceci est illustré Fig.

15

. date 0 10 20 30 40 50 60 ... i i 0 i 1 i 2 i 3 i 4 i 5 i 6 ... v1 i 1 i 2 i 3 i 4 i 5 i 6 ... v2 i 2 i 3 i 4 i 5 i 6 ... o1 0 i 1 i 2 i 3 i 4 i 5 i 6 ... o2 0 0 i 2 i 3 i 4 i 5 i 6 ...

FIG. 15 -Concaténation de flots

Transitions de rythmes sur des rythmes non spécifiés

  .., x m : σ m ] Une séquence d'horloge est soit le résultat d'un sous-échantillonnage booléen (ck on c, ck on not c) soit une horloge strictement périodique (pck). Une horloge strictement périodique est soit une horloge strictement périodique constante ((n, p)), soit une variable d'horloge (α), soit une horloge strictement périodique sur-échantillonnée périodiquement (pck * . k), soit une horloge strictement périodique souséchantillonnée périodiquement (pck/ . k), soit le résultat d'un décalage de phase (pck → . k). En d'autres termes, une séquence d'horloges ck est une expression linéaire résultant d'une suite de transformations appliquées à une autre séquence d'horloge ck . Dans la suite, ck est qualifiée d'horloge abstraite si ck est une variable d'horloge. Elle est qualifiée d'horloge concrète si ck est une horloge strictement pério-

	dique constante. Une porteuse (c) est soit un nom de flot (nm), soit une variable porteuse (X) (lorsqu'il
	s'agit d'une entrée du noeud).
	Un environnement (H) associe des types d'horloges aux variables et définitions de noeuds. Un en-
	vironnement peut donc être considéré comme une fonction associant un type d'horloge à un nom de
	variable ou de noeud.
	Les horloges peuvent être généralisées (lors d'une définition de noeud) et instanciées (lors d'un appel
	de noeud) de la manière suivante :
	Un type d'horloge peut être soit un schéma d'horloges (σ) quantifié sur un ensemble de variables d'hor-
	loges appartenant à différents sous-types d'horloges (C 1 , ..., C m ), soit une expression d'horloge non-
	quantifiée (cl). Une expression d'horloge peut être soit une horloge fonctionnelle (cl → cl), soit un
	produit d'horloges (cl × cl), soit une séquence d'horloges (ck), soit une dépendance (c : ck).

r

  Définition 9. La forme canonique CF (e) d'une expression d'horloge strictement périodique e sous forme normale est le représentant de sa classe d'équivalence. En d'autres termes :

imported node A(i: int) returns (o: int) wcet 5; imported node B(i: int) returns (o: int) wcet 10;

  Le programme expansé est tout d'abord traduit en un graphe de tâches. Un graphe de tâches est constitué d'un ensemble de tâches et d'un ensemble de précédences entre les tâches. Expansion du contenu du noeud N 1: for chaque instance de noeud M (args) dans le corps de N do

		6. Extraction de tâches dépendantes
	Algorithm 1 2: for chaque argument arg de args do
	3:	ajouter une variable locale v à N
	4:	ajouter une équation v = arg à N
	5:	end for
	6:	for chaque variable locale ou chaque variable de sortie de la définition deM do
	7:	ajouter une variable locale à N
	8:	end for
	Un programme est constitué d'un hiérarchie de noeuds, dont les feuilles sont soit des opérateurs 9: for chaque équation de la définition de M do
	prédéfinis, soit des noeuds importés. Le processus de génération de code commence par remplacer ré-10: ajouter l'équation à l'ensemble des équations de N
	cursivement chaque appel de noeud par l'ensemble d'équations de sa définition. Par exemple, le noeud 11: end for
	suivant : 12:
	imported node A(i: int) returns (o: int) wcet 5;
	imported node B(i: int) returns (o: int) wcet 10;
	node sub (i, j) returns (o, p)
	let
	o=A(i);
	p=B(j);
	tel
	node toexpand (i, j: rate (20, 0)) returns (o, p)
	let
	(o, p)=sub(i, j);
	tel
	S'expanse en :
	node expanded (i, j: rate (20, 0)) returns (o, p)
	var isub, jsub, osub, psub;
	let
	isub=i; jsub=j;
	osub=A(isub);
	psub=B(jsub);
	(o, p)=(osub, psub);
	tel
	L'algorithme d'expansion est décrit dans l'Alg. 1.
	6.2 Graphe de tâches
	xlvi

  représente une précédence étendue, l'annotation ops est une liste d'opérateurs op, avec op ∈ {/ˆk, * ˆk, ∼> q, tail, cst ::, cst fby, when c, whennot c}. L'annotation spécifie que la précédence relie des tâches de rythmes ou phases différents ( * ˆk, /ˆk, ∼> q), est retardée (cst fby) ou conditionnée (when c). Dans la suite, op.ops représente la liste dont la tête est op et la queue est ops et représente la liste vide.Pour chaque tâche τ i , nous devons extraire les caractéristiques temps réel (T i , C i , r i , d i ). Le calcul d'horloges assure que les entrées et sorties d'un noeud importé ont toutes la même horloge strictement périodique parente. Par extension, on note pck i l'horloge strictement périodique parente de τ i . Les caractéristiques temps réel d'une tâche sont extraites de la manière suivante d'un programme :

	i o	F	/ˆ3 * ˆ3	fby	S
	FIG. 27 -Graphe après réduction des variables
	Les opérateurs prédéfinis du graphe sont ensuite transformés en annotations de précédences. Une
	précédence τ i → τ j Le graphe réduit de l'exemple précédent est donné Fig. 28. ops
			/ˆ3
	i o	F	fby. * ˆ3	S
		FIG. 28 -Un graphe de tâches réduit
	6.3 Caractéristiques temps réel
	Périodes La période d'une tâche se déduit de son horloge strictement périodique parente. On a T i =
	π(pck i ).
	Echéances Par défaut, l'échéance d'une tâche est sa période (d i = T i ). Des contraintes d'échéance
	peuvent aussi être spécifiées sur les capteurs ou actionneurs (ex : o: due n).
	Dates de réveil La date de réveil initiale d'une tâche est la phase de son horloge strictement périodique
	i o parente : r i = ϕ(pck i ). Durée d'exécution La durée d'exécution C i d'une tâche est le wcet du noeud importé, capteur ou ac-/ˆ3 vf * ˆ3 fby S vs tionneur correspondant.
	FIG. 26 -Un graphe intermédiaire
	Réduction du graphe
	Une suite de simplifications est ensuite appliquée sur ce graphe intermédiaire. Tout d'abord, chaque
	variable d'entrée du programme devient une opération capteur et chaque variable de sortie du programme
	devient une opération actionneur.
	Ensuite chaque paire de précédence N → x → M , pour laquelle x est une variable locale, est
	remplacée par une seule précédence N → M . Lorsque toutes les paires ont été remplacées, les variables
	sont supprimées du graphe. Pour l'exemple précédent, on obtient le graphe Fig. 27.
	xlviii

  où l'on précise les dates de réveil de deux tâches τ i et τ j reliées par une précédence τ i τ → j . Les flèches décrivent quelle instance de τ i précède quelle instance de τ j , d'après la définition de g ops .

	(a) Pour τi	* ˆ3 → τj		(b) Pour τi	/ˆ3 → τj
	(c) Pour τi	∼>1/2 → τj		(d) Pour τi	tail → τj
	(e) Pour τi	:: → τj		(f) Pour τi	fby → τj
			(g) Pour τi	* ˆ2./ˆ3 → τj
			(h) Pour τi	/ˆ2.tail.tail →	τj
	FIG. 30 -Précédences entre instances de tâches, selon la définition de g ops
	On note que τ i [n] → τ j [n ] n'implique pas nécessairement que la donnée produite par τ j [n] est
	consommée par τ i [n ]. Les dépendances de données entre instances de tâches seront définies de manière
	plus précise Sect. 7.3.

  principe de ce calcul vient de l'observation que la contrainte reliant d i [n] à d j [g ops (n)] est périodique. Nous allons montrer qu'il existe toujours deux entiers b, p tels que pour tout n > b, d * i [n] = d * i [n + p]. La séquence des échéances relatives d'une tâche est donc ultimement périodique et peut se représenter sous la forme d'un motif répétitif appelé mot d'échéances. Le calcul d'échéances effectue l'encodage des précédences en calculant le mot d'échéances de chaque tâche du graphe.

	Transposition de l'ajustement aux échéances relatives Tout d'abord, nous transposons la formule
	d'ajustement des échéances absolues aux échéances relatives :
	Propriété 10. Pour toute précédence τ i	ops → τ j :

  vf 1 vf 2 vf 3 vf 4 vf 5 vf 6 vf 7 vf 8 ... vs 0 vs 0 vs 1 vs 1 vs 1 ... vf 1 vf 2 vf 3 vf 4 vf 5 vf 6 vf 7 ...

	.2.
	date	0	10	20	30	40	50	60	70	80 ...
	vf vf 0 vf/^3 vf 0			vf 3			vf 6		...
	vs	vs 0			vs 1			vs 2		...
	0 fby vs	0			vs 0			vs 1		...
	(0 fby vs) * ^3 vs 0 0 fby vf 0 0 0 0 vf 0 (0 fby vf)/^3 0 vf 2			vf 5		...
	vs * ^3

  vf 1 vf 2 vf 3 vf 4 vf 5 vf 6 vf 7 vf 8 ...

										5.2. Queuing
	date	0	10	20	30	40	50	60	70	80 ...
	vf vf 0 tail(vf)/^3	vf 1			vf 4			vf 7	...
	vs		vs 0			vs 1			vs 2

  t 1 E e 2 : t 2 E (e 1 , e 2 ) : t 1 × t 2

			(FBY)	E cst : t E cst fby e : t E e : t	(WHEN)	E e 1 : t E e 1 when e 2 : t E e 2 : bool
	(MERGE)	E e 1 : bool E merge (e 1 , e 2 , e 3 ) : t E e 2 : t E e 3 : t	(/ˆ)	E e : t E e/ˆk : t E k : int
	( * ˆ)	E e : t E e * ˆk : t E k : int	(∼>)	E e : t E e ∼> q : t E q : rat	(tail )	E e : t E tail (e) : t
	( :: )	E cst : t E cst :: e : t E e : t	(APP)	E N : σ	t 1 → t 2 = inst(σ) E N (e) : t 2	E e : t 1
					(EQ)	E x : t E x = e E e : t	(EQS)	E eq 1 E eq 1 ; eq 2 E eq 2
	(NODE)

in : t 1 , out : t 2 , var : t 3 , E eqs E node N (in) returns (out) [ var var; ] let eqs tel : gen E (t 1 → t 2 )

  .3.

	(SUB)	H e : ck H e : C H ck <: C	(CONST)	H cst : ck	(VAR)	x ∈ dom(H) H x : H(x)
			(×)	H e 1 : cl 1	H e 2 : cl 2
	(APP)	H N : σ	cl 1 → cl 2 = inst(σ) H N (e) : cl 2	H e : cl 1	(EQ)	H x : cl H x = e H e : cl
						(EQS)	H eq 1 H eq 1 ; eq 2 H eq 2
	(NODE)
					(DECLS-OTHER)	H decl 1 H decl 1 decl 2 H decl 2
						Figure 6.3: Clock inference rules

H (e 1 , e 2 ) : cl 1 × cl 2 (FBY) H cst : cl H e : cl H cst fby e : cl (WHEN) H e : ck H c : (c : ck) H e when c : c on ck (WHEN-NOT) H e : ck H c : (c : ck) H e whennot c : c on not ck (MERGE) H c : (c : ck) H e 1 : ck on c H e 2 : ck on not c H merge (c, e 1 , e 2 ) : ck ( * ˆ) H e : pck pck <: P k,0 k = 0 H e * ˆk : pck * . k (/ˆ) H e : pck k = 0 H e/ˆk : pck/ . k (∼>) H e : pck pck <: P b,0 (q = a b ), a ∧ b = 1 H e ∼> q : pck → . q (tail) H e : pck H tail (e) : pck → . 1 (::) H cst : pck H e : pck pck <: P 1,1 H cst :: e : pck → . -1 in : cl 1 , out : cl 2 , var : cl 3 , H eqs H node N (in) returns (out) [ var var; ] let eqs tel : gen H (cl 1 → cl 2 ) (IMP-NODE) in : cl 1 out : cl 2 pparent(cl 1 ) = pparent(cl 2 ) H imported node N (in) returns (out) wcet n : gen H (cl 1 → cl 2 ) (SENSOR) H sensor x wcet n (ACTUATOR) H actuator x wcet n (DECLS-NODE) H [ imported ] node N [...] : σ H, N : σ decl 2 H [ imported ] node N [...] decl 2 • The (SUB) rule is the classical subsumption rule of sub-typing [Pie02] applied to clock types. It states that if ck is a clock subtype of C then any expression of clock ck also has clock type C;

  Furthermore, the other premises of the inference rule ensure that k|π(pck). So by definition of I(pck * . k), we have S

ρ (e) ∈ I(pck * . k); • Case e is e /ˆk: We assume that S ρ (e ) ∈ I(pck). The semantics of operator /ˆtells us that for all i, we have pck[i] ∈ ck(S ρ (e)) if and only if k|pck[i]. So by definition of I(pck/ . k), we have S ρ (e) ∈ I(pck/ . k);

  Indeed, let us consider our previous example: data produced by the task instance τ A [0] must be consumed by τ B [0]. Due to 8.3. Compilation chain preemptions, τ A [1] executes before τ B [0] completes its execution. To respect the synchronous semantics, data produced by τ A [0] must remain available for τ B [0] during its complete execution, thus after the execution of τ A [1] has started. As a consequence, we need to define a communication protocol that makes sure that data produced by τ A [0] is not overwritten by τ A [1]. The Chapter 11 then details the translation of the independent task set into a C program consisting of a set of communicating threads. Finally, Chapter 12 presents the implementation of a prototype of the compiler that generates code executable on the MARTE RTOS.

	psub=B(jsub);
	(o, p)=(osub, psub);
	tel
	Synchronous program
	Real-time tasks + precedences
	Real-time tasks without precedences + communication protocol
	Multi-threaded C code
	Executable code for RTOS with EDF scheduler
	Figure 8.4: Compilation chain

  .7, we obtain the following deadlines:

	Gyro_Acq (74.81 9 ) ω
	gyro	(71.78 9 ) ω
	gps	(71) ω
	gnc	(300) ω
	Str_Acq	(74) ω
	pde	(100) ω
	GP S_Acq (74) ω
	T M _T C	(9999) ω
	tc	(8999) ω
	P W S	(999) ω
	SGS	(999) ω
	GN C_U S (299) ω
	F DIR	(89.96 9 ) ω
	sgs	(1000) ω
	pws	(1000) ω

  As explained in Sect. 9.2.5, the reduced task graph for this example is that given in Fig.11.1.

	i o	i o	F	p j	/ˆ3 fby. * ˆ3	i o	S
		Figure 11.1: Reduced task graph
	int S_o_F_j[2];
	int i_F_i;
	int F_o_o;
	int F_p_S_i;
							As an example, we consider
	the simple multi-periodic communication loop with data-sampling communication patterns presented
	previously in Sect. 5.1:
	imported node F(i, j: int) returns (o, p: int) wcet 20;
	imported node S(i: int) returns (o: int) wcet 30;
	sensor i wcet 1;
	actuator o wcet 1;
	node sampling(i: rate (100, 0)) returns (o)
	var vf, vs;
	let
	(o, vf)=F(i, (0 fby vs) * ^3);
	vs=S(vf/^3);
	tel
	The buffers allocated for this program are given below:

Here, optimality means that if there exists a priority affectation that schedules the task set correctly, then the algorithm finds one.

If the tasks are all periodic, the number of cells computed by the protocol is proved minimal.

Remerciements

For instance, the following node programs a reinitialisable counter: node counter(reset:bool) returns (count:int) let count=0->if reset then 0 else pre(count)+1; tel

The C code generated for this program is the following (

Node expansion

Node expansion replaces a node call appearing in an equation by the body of the node definition. It is performed as a preliminary step of the code generation. Expanding node calls enables to accept programs that would otherwise be rejected by the syntactic causality analysis. For instance, without expansion the following program is rejected: node invert(i1, i2: int) returns (o1, o2: int) let (o1, o2) = (i2, i1); tel node N(i: int) returns (o1, o2) let (o1, o2)=invert(i, o2); tel However, after expansion (and a few simplifications) this program is clearly causal: node N(i: int) returns (o1, o2) let (o1, o2)=(o2, i); tel

The Verimag LUSTRE compiler expands recursively each node call, while this is only performed on demand in SCADE as it supports modular compilation of nodes. The SCADE compiler generates a C procedure for each non-expanded node definition and the same procedure can be reused if the program 4.7. Summary

• The denotation of a tuple is the tuple made up of the denotations of its different members;

• The denotation of an equation is defined as an assignment. The denotation of equation x = e for assignment ρ replaces x by x ∞ in ρ. We cannot directly replace x by S ρ (e) because x may appear in e (for instance x = 0 fby plus(x, 1)) so we need to compute a fix point;

• The denotation of two equations is also an assignment. We first compute the assignment ρ obtained for equation x 1 = e 1 [e 2 /x 2 ], where e 1 [e 2 /x 2 ] is the expression obtained by substituting expression e 2 for variable x 2 in e 1 . Then we compute S ρ (x 2 = e 2 ), that is to say the assignment obtained for equation x 2 = e 2 in assignment ρ );

• The denotation of a node definition is the assignment obtained for its equations. This assignment is parameterized (λy) by the values assigned to the inputs of the node (in ← y);

• The denotation of a node application is the denotation of the node definition applied to the denotation of the arguments of the application.

Summary

In this chapter, we have defined high-level real-time synchronous primitives based on strictly periodic clocks. Using these primitives, the programmer can specify:

• Periodicity constraints;

• Deadline constraints;

• Phase offsets for periodic flows;

• Rate transitions between flows of different rates.

We have also shown that the existing delay and Boolean clock sampling operators easily fit in our language.

In the next chapter, we illustrate the expressiveness of this language and show how it can be used to program multi-periodic systems.

Part II

Compiling into a Set of Real-Time Tasks Chapter 9

Extracting Dependent Real-Time Tasks Once static analyses succeed, the compiler can translate the program into lower-level code. As we mentioned in Chap. 8, the program is translated into a set of communicating tasks, which will be scheduled concurrently by a multi-task RTOS. This chapter details the first step of this translation, the extraction of the task graph corresponding to a program. The activation condition of a variable x is the activation condition of its clock, cond(x) = cond(ck(x)). We now define the activation condition of a task.

Program Expansion

Definition 11. The activation condition cond i of a task τ i is defined as:

In this definition, the input variables in i correspond to the arguments provided for the corresponding imported node instance, not to the inputs specified in the definition of the imported node. Consider the following example: The activation condition of the node instance of N in inst is: true ∨ c ∨ ¬c. Thus, this node instance of N is executed normally at each task instantiation.

Summary

In this chapter we have detailed the extraction of a set of dependent real-time tasks from a program. The tasks are dependent because they are related by precedence constraints due to inter-task datadependencies.

In the next chapter, we propose to transform the dependent task set into an equivalent independent task set.

Preliminary definitions

We first introduce some intermediate definitions to prove this property. As the proof will be done by induction, we start by defining for a precedence τ i ops → τ j , T i inductively from T j and r i inductively from r j . Remember that the real-time attributes of a task (T

) have been defined in Sect. 9.3. Definition 12. For all τ i ops → τ j , we have for all n, τ i [n] → τ j [g ops (n)] by definition. Let T ops and r ops be two functions such that for all precedence τ i ops → τ j , T i = T ops (T j ) and r i = r ops (r j , T j ). These functions can be defined inductively as follows:

Then we define the function ∆ ops (n, T j , r j ), which will play a key role in our precedence encoding algorithm:

Definition 13. Let ∆ ops (n, T j , r j ) = g ops (n)T j -nT ops (T j ) + r j -r ops (r j , T j ). From the definition of R i [n], we have:

∆ ops represents the difference between the release dates of two task instances related by a precedence relation. This operator is illustrated in Fig. 10.3, where we show the release dates of the successive instances of two tasks τ i and τ j related by a precedence τ i ops → τ j . When ∆ ops (n, T j , r j ) = 0, it is not represented in the figure .   • If we take T i = 3, T j = 1, r j = 0, ops = * ˆ3, we have (for n = 0, 1, 2, . . .):

∆ ops (n, T j , r j ) = 0, 0, 0, 0, 0, 0, . . .

• If we take T i = 1, T j = 3, r j = 0, ops = /ˆ3, we have (for n = 0, 1, 2, . . .): ∆ ops (n, T j , r j ) = 0, 2, 1, 0, 2, 1, . . .

• If we take T i = T j = 2, r j = 1, ops =∼> 1/2, we have (for n = 0, 1, 2, . . .): ∆ ops (n, T j , r j ) = 1, 1, 1, 1, 1, 1, . . .

• If we take T i = T j = 2, r j = 2, ops = tail, we have (for n = 0, 1, 2, . . .): ∆ ops (n, T j , r j ) = 2, 0, 0, 0, 0, 0, . . .

• If we take T i = T j = 2, r j = 0, ops = ::, we have (for n = 0, 1, 2, . . .): ∆ ops (n, T j , r j ) = 0, 0, 0, 0, 0, 0, . . . Property 11. The function ∆ ops has the following properties:

• ∆ (n, T j , r j ) = 0;

• ∆ * ˆk.ops (n, T j , r j ) = ∆ ops (kn, T j , r j );

• ∆ /ˆk.ops (n, T j , r j ) = ∆ ops ( n/k , T j ) + (1 -((n-1) mod k)+1 k )T ops (T j , r j );

• ∆ ∼>q.ops (n, T j , r j ) = ∆ ops (n, T j , r j ) + q * T ops (T j , r j );

• ∆ fby.ops (n, T j , r j ) = ∆ ops (n + 1, T j , r j ) + T ops (T j , r j );

• ∆ tail .ops (n, T j , r j ) = ∆ ops (0) + T ops (n, T j , r j ) if n = 0 ∆ ops (n -1, T j , r j ) otherwise

• ∆ ::.ops (n, T j ) = ∆ ops (n + 1, T j , r j );

• ∆ when.ops (n, T j , r j ) = ∆ whennot.ops (n, T j ) = ∆ ops (n, T j , r j )

Proof. Case by case:

• Case : ∆ (n, T j , r j ) = nT j -nT j + r j -r j = 0;

• Case * ˆk:

∆ * ˆk.ops (n, T j , r j ) = g ops (kn)T j -knT ops (T j ) + r j -r ops (r j , T j ) = ∆ ops (kn, T j , r j )

• Case /ˆk:

• Case ∼> q:

∆ ∼>q.ops (n, T j , r j ) = g ops (n, T j ) -nT ops (T j ) + r j -r ops (r j , T j ) + q * T ops (T j ) = ∆ ops (n, T j , r j ) + q * T ops (T j )

• Case fby:

∆ fby.ops (n, T j , r j ) = g ops (n + 1)T j -nT ops (T j ) + r j -r ops (r j , T j ) = g ops (n + 1)T j -(n + 1)T ops (T j ) + T ops (T j ) + r j -r ops (r j , T j ) = ∆ ops (n + 1, T j , r j ) + T ops (T j )

Deadline Words

A unitary deadline specifies the relative deadline for the computation of a single instance of a task. A unitary deadline is simply an integer value d. An ultimately periodic deadline word, or simply a deadline word defines the sequence of unitary deadlines of a task. The set of deadline words W d is defined by the following grammar:

For a deadline word w = u.(v) ω , u is the prefix of w and (v) ω denotes the infinite repetition of word v, called the repetitive pattern of w. For instance, the deadline word of A in Fig. 10.4 is (2.4) ω , so

and so on. We introduce some additional notations:

• |u| denotes the length of the finite word u (the number of unitary deadlines in u);

• w[n] denotes the n th unitary deadline of deadline word w (n ∈ N). If w = u.(v) ω then:

• The ordering relation on deadline words is the lexicographic order:

• The minimum of two deadline words w m = min(w i , w j ) is such that for all n, we have

We then simply compute the minimum of the two unfolded words point by point on |w i |;

• The sum of two deadline words w = w i + w j is such that for all n, w

It can be computed by unfolding w i and w j as we did to compute the minimum of two deadline words;

Computing Task Deadline Words

Let w i denote the deadline word representing the sequence of unitary deadlines of task τ i . From Sect. 10.2.2, we know that each task precedence τ i ops → τ j can be encoded as a constraint relating w i to w j of the form:

for all n and ∆ ops (T j , r j ) is a word such that ∆ ops (T j , r j )[n] = ∆ ops (n, T j , r j ) for all n. We must now prove that W ops (w j ) and ∆ ops (T j , r j ) are both ultimately periodic words.

Property 14. For all ops:

∀n ≥ max(0, pref(ops)), ∆ ops (n, T j , r j ) = ∆ ops (n + P (ops), T j , r j )

The length of the prefix of ∆ ops (T j , r j ) is max(0, pref(ops)), while the length of its periodic pattern is P (ops). Proof. By induction on the structure of ops and using the Property 11. Notice that we do not prove that P (ops) is the smallest possible period of ∆ ops or that pref(ops) is the smallest possible prefix of ∆ ops .

We only find a bound so as to compute ∆ ops efficiently.

• First, for the base of the induction, ∆ (n, T j , r j ) = 0, so ∆ (n, T j , r j ) = ∆ (n + 1, T j , r j ) for all n ≥ 0;

• Then, the induction hypothesis is: for all n ≥ pref(ops), ∆ ops (n, T j , r j ) = ∆ ops (n+P (ops), T j , r j ).

We first prove the induction step for case /ˆ. First, notice that:

Then:

∆ /ˆk.ops (n + P (/ˆk.ops), T j , r j ) = ∆ /ˆk.ops (n + k * P (ops), T j , r j ) = ∆ ops ( (n + k * P (ops))/k , T j , r j )+

(1 -

We have:

We proved that n/k ≥ pref(ops) so we can apply the induction hypothesis and:

= ∆ /ˆk.ops (n, T j , r j )

• The remaining cases are deduced trivially from the induction hypothesis and from Property. 11, so the property is proved.

Property 15. For all w j = u j .(v j ) ω , for all ops:

∀n ≥ max(0, pref'(ops, w j )), W ops (w j )[n] = W ops (w j )[n + P (ops, w j )]

The length of the prefix of W ops (w j ) is max(0, pref'(ops, w j )), while the length of its periodic pattern is P (ops, w j ).

pref'(ops, w j ) and P (ops, w j ) are defined as follows: pref'(::.ops) = max(pref'(ops) -1, 0)

pref'(fby.ops) = max(pref'(ops) -1, 0)

pref'(op.ops) = pref'(ops) otherwise

Proof. We prove that W ops (w j )[n] = W ops (w j )[n + P (ops, w j )], by induction on the structure of ops.

The definitions of P (ops) and pref'(ops) are exactly the same as those of P (ops) and pref(ops), except for the base of the induction (case ops = ). So we only prove the base of the induction. The rest of the proof is exactly that of Property. 14. For the base of the induction, for n

Thanks to these properties, ∆ ops (T j , r j ) and W ops (w j ) are indeed deadline words and can simply be computed by computing each value of their prefix and of their repetitive pattern. The value of w i can finally be computed as the combination of all the constraints on w i :

We give an algorithm for computing the task deadline words of a reduced task graph in Alg. 8. For this algorithm, delayed precedences are removed from the task graph. As a reduced task graph is a DAG (when we do not consider delayed precedences), the algorithm is basically a topological sort [START_REF] Kahn | Topological sorting of large networks[END_REF] working backwards, starting from the end of the graph and computing deadline constraints at each step. This algorithm is similar to the one presented in [START_REF] Chetto | Dynamic scheduling of real-time tasks under precedence constraints[END_REF] but applied to extended precedences.

If we accept programs which contain a precedence τ i ops → τ j where ops contains an operator * ˆk that precedes the first operator fby appearing in ops (which we rejected in Sect. 9.2.5), then we can have a delayed precedence τ i ops → τ j where w i depends on w j . As a consequence, we cannot compute task deadline words using a topological sort on tasks, but instead we must use a topological sort on task instances. This can be done by unfolding the task graph into a task instance graph. This will not be detailed here.

Schedulability analysis

q = k/gcd(P (ops), k).

So:

-As P (ops)|h -1 ops (T j ) (from the induction hypothesis), then q|(h -1 ops (T j )/gcd(P (ops), k)); -As k|h -1 ops (T j ), then q |(h -1 ops (T j )/gcd(P (ops), k)).

And thus qq |(h -1 ops (T j )/gcd(P (ops), k)), so (qq * gcd(P (ops), k))|h -1 ops (T j ). On the other hand: P ( * ˆk.ops) * k = P (ops)/gcd(P (ops), k) * k = q * k = qq * gcd(P (ops), k)

ops (T j ) and (P ( * ˆk.ops))|(h -1 ops (T j )/k).

• Then we prove the induction step for case /ˆk. We have: P (/ˆk.ops) = kP (ops) and h -1 /ˆk.ops = kh -1 ops (T j ). From the induction hypothesis kP (ops)|kh -1 ops (T j ), so the induction step is proved; • The induction steps for cases ∼>, when, tail, fby and :: are trivial, so the property is proved by induction.

Likewise, we can prove by induction that P ops (w j )|(|v j | * T j ). Only the base of the induction changes:

From these two properties and from part 10.2, we have

Schedulability analysis

As we consider critical systems, we would like to know before starting the system execution if the system is schedulable, that is to say if all real-time constraints will be met. From Corollary 3, we know that the system is schedulable if and only if the corresponding encoded task set is schedulable with EDF. We can therefore reuse existing results on the schedulability analysis of EDF.

We want to perform a schedulability analysis (also called a feasibility test) for a set of independent periodic tasks, with deadlines lower than task periods and with release dates different from 0, scheduled with EDF. A necessary and sufficient condition for such a system to be feasible was given in [START_REF] Leung | A note on preemptive scheduling of periodic, real-time tasks[END_REF], which established that the feasibility of such a task set can be checked by examining the schedule computed by EDF on the time interval [0, 2HP + max(r i )] (where HP denotes the hyperperiod of the task set) and verifying that real-time constraints are met on this interval. The value HP can be exponential with respect to the periods of the system (the worst-case being when the periods of the system are coprime) and the problem we consider was proved co-NP-hard in the strong sense in [START_REF] Sanjoy | Algorithms and complexity concerning the preemptive scheduling of periodic, real-time tasks on one processor[END_REF]. In practice however, systems with co-prime periods do not occur that much and in many cases the test should not have such a dramatic complexity.

Inter-Task Communications

In this section we propose a communication scheme, which ensures that inter-task data communications respect the synchronous semantics of Sect. 4.6.

Ensuring communication consistency

A simple way to handle inter-task communications is to allocate a buffer in a global memory, the producer of the data writes to this buffer when it executes and the consumer reads from this buffer when it executes. To respect the synchronous semantics, we must ensure that, for each data-dependency:

1. The producer writes before the consumer reads.

2. As we deal with periodic tasks, we must also ensure that the current instance of the reader reads before the next instance of the writer overwrites the data produced by its previous instance.

The precedence encoding presented in the previous section ensures that for any data-dependency the producer of the data will be scheduled before the consumer of the data. So, we know that when a task is scheduled and starts its execution, all the data it consumes has already been produced and the requirement (1). is satisfied.

However, the precedence encoding does not ensure that the requirement (2). is satisfied. For a precedence τ i ops → τ j , data produced by τ i [n] may be consumed by τ j [g ops (n)] after τ i [n + 1] has started. This is illustrated in Fig. 10.5, which shows the schedule of two tasks related by extended precedences. prio(τ i ) denotes the priority affected to task τ i . Vertical lines on the time axis represent task periods. Marks on the time axis represent task preemptions. An arrow from A at date t to B at date t means that task B may read at time t from the value produced by A at time t. In Fig. 10.5(a), 10.5(c) and 10.5(d), when A[1] executes, it must not overwrite the data produced by A[0] because it is consumed by B[0] and B[0] is not complete yet. Therefore, we need a buffer to keep the value of A[n] after A[n + 1] has started. In Fig. 10.5(b), the same data is consumed several times but A[1] can freely overwrite the data produced by A[0], so no specific communication scheme is required. In Fig. 10.5(e) and Fig. 10.5(f), we only need to be careful with the initial value of the communication.

Dependence words

As we did for deadline words, data-dependencies can be represented by finite repetitive patterns. Let conso ops (n) = g ops (n + 1) -g ops (n) for all n ∈ N. According to Property. 18, for any precedence τ i ops → τ j , conso ops (n) denotes the number of instances of τ j that consume data produced by τ i [n] (possibly 0). We will prove that the sequence of values of conso ops can be represented by finite repetitive patterns, which we call dependence words, defined similarly to deadline words:

For a deadline word w = u.(v) ω , u is the prefix of w and (v) ω denotes the infinite repetition of word v, called the repetitive pattern of w.

We now prove that the function conso ops is ultimately periodic and can thus be represented by a dependence word. The length of the prefix and the length of the repetitive pattern of conso ops are actually the same as those of ∆ ops (defined in Property. 14):

Property 19. For all ops: ∀n ≥ max(0, pref(ops)), conso ops (n) = conso ops (n + P (ops))

Proof. We proved previously that for all n ≥ pref(ops), ∆ ops (n, T j , r j ) = ∆ ops (n + P ops, T j , r j ). Thus, for all n ≥ pref(ops):

∆ ops (n, T j , r j ) = ∆ ops (n + P (ops), T j , r j ) ⇔g ops (n)T j -nT ops (T j ) = g ops (n + P (ops))T j -(n + P (ops))T ops (T j ) ⇔T j * (g ops (n + P (ops)) -g ops (n)) = P (ops)T i ⇔g ops (n + P (ops)) -g ops (n) = P (ops)T i /T j As n ≥ P (ops) ⇒ n + 1 ≥ P (ops), this property is true for n + 1 also and we have: g ops (n + P (ops)) -g ops (n) = g ops (n + 1 + P (ops)) -g ops (n + 1) ⇔g ops (n + 1) -g ops (n) = g ops (n + 1 + P (ops)) -g ops (n + P (ops)) ⇔conso ops (n) = conso ops (n + P (ops))

Thanks to this property, we can compute conso ops as a dependence word, by computing each value of conso ops (n) on its prefix and on its period. For instance, for the example of Fig. 10.6:

→ τ j : we have conso ops = (2) ω , meaning that each value produced by τ i is consumed by two instances of τ j ;

→ τ j : we have conso ops = (1.0) ω , meaning that only one out of two successive values produced by τ i is consumed by τ j ;

→ τ j : we have conso ops = (1) ω , meaning that each value produced by τ i is consumed by an instance of τ j ;

• τ i tail → τ j : we have conso ops = 0.(1) ω , meaning that the first value produced by τ i is not consumed;

• τ i tail .0 :: → τ j : we also have conso ops = 0.(1) ω .

Lifespans

We define the lifespan of a data-sample produced by a task as the interval of time from the date at which it is produced to the date at which it can last be consumed. More formally: Definition 14. For any precedence τ i ops → τ j , for any n, the lifespan of τ i [n] is: Proof. We have:

So (the integer part of the division replaces the if-then-else of Def. 14):

Thus our property.

Communication protocol

We propose a communication protocol based on dependence words, which ensures that the inputs of a task remain available until its deadline and so which satisfies the requirement (2). of the previous section. An important feature of this protocol is that it requires no specific synchronization primitives (like semaphores), which allows to use a minimal OS. Still, the protocol strictly respects the formal semantics of the language and ensures that the functional behaviour of the program is exactly the one specified in Sect. 4.6 (it ensures the functional determinism of the system).

The protocol is based on dependence words and lifespans. A communication buffer is allocated for each precedence τ i ops → τ j . Dependence words tell us which instance of τ i must write in the buffer and which instances of τ j must read the corresponding value. Lifespans tell us which instances of τ i can write in the same cell of the buffer: if the lifespans of τ i [n] and τ i [n ] have an intersection, then they cannot be stored in the same cell of the buffer.

Number of cells of the communication buffer

Let cell i [n] denote the buffer cell in which τ i [n] will write. The number of cells of the buffer must be such that for all n, n such that sp i,j (n) and sp i,j (n ) intersect, cell i [n] and cell i [n ] can be chosen so that

can start its execution while some instances of τ j still need the value produced by τ i [n], so τ i [n] and τ i [n ] cannot write their values in the same cell. For a given lifespan, we can bound the number of lifespans that intersect it as follows:

Property 21. For all τ i ops → τ j , for all n:

Task functions

• The function call invoke_scheduler (0) signals the end of the task instance execution to the scheduler.

On the other hand, the code generated for the node F is the following:

F_p_S_i=F_fun_outs.p; instance++; invoke_scheduler (0); } }

• The variable cell_from_S_o_F_j tells from which cell of the buffer the value of S.o must be read. Notice that we start by reading the initialisation value of the delay contained by the cell 1 of the buffer;

• The variable write_F_p_S_i tells whether the buffer F_p_S_i must be updated or not ;

• The first instruction of the loop calls the external function provided for F;

• The second argument of the function is read from the communication buffer S_o_F_j. According to variable change_S_o_F_j, each 3 iterations, F must change the cell from which it reads;

• As F returns several values, the values are produced in a structure passed as an additional argument to the function (&F_fun_outs);

• The field o of the structure is copied at every instance of F for the actuator o;

• As described by variable write_F_p_S_i, the field F_fun_outs.p is copied in the buffer for S only once every 3 instances of F.

We produce a similar code for the sensor i:

Task real-time attributes

We define data structures to describe the real-time attributes of a task. We first define a type structure to describe deadline words:

The field ddpref corresponds to the prefix of the deadline word and prefsize is the size of the prefix. The field ddpat correspond to the repetitive pattern of the deadline word and patsize is the size of the pattern.

The real-time attributes of a task are then defined as follows:

struct edf_sched_param { char * name; struct timespec period; struct timespec initial_release; dword_t dword; }; 12.3. Summary

Deadline words support

The support of deadline words requires few modifications to be made to the existing EDF scheduler. First, we define an auxiliary function that computes the value of the n th unitary deadline of a deadline word: Then, we only need to modify the function that programs the next instance of a task when the current instance completes. For a task τ i , the attributes of which are described by the value t_data, the release date and the deadline of its new instance are computed as described below

dword_t dw = t_data->dword; struct timespec dd; t_data->instance++; get_nth_ud(&dd,dw,instance); incr_timespec (&t_data->next_release, &t_data->period); add_timespec (&t_data->next_deadline, &t_data->next_release, &dd);

The function incr_timespec(t1,t2) increments t1 by t2 and the function add_timespec(t1,t2,t3) sets t1 to t2+t3.

Summary

In this chapter, we described the implementation of a prototype supporting the compilation scheme described earlier:

• The compiler was implemented in about 4000 lines of code of OCAML;

• An EDF scheduler extended to support deadline words was implemented for the MARTE Operating System, which only required minor modifications to the existing EDF scheduler included in the OS distribution.

The next chapter concludes on the compilation scheme proposed in this part of the dissertation.

Chapter 13. Conclusion on language compilation to our work. However, as we have seen in Chap. 1, SDF operations are not periodic. As a consequence, these results cannot be directly applied to our problem.

Chapter 14

The Flight Application Software: Current Implementation

Before studying the implementation of the FAS with our language, we give an overview of the real implementation of the system provided by EADS Astrium Space Transportation.

The control system of the Automated Transfer Vehicle is to this date the most complex critical embedded software developed by EADS Astrium Space Transportation (it is also the most complex space software developed for ESA). The software consists of about 450.000 lines of ADA code, split into approximately 30 high level software functionalities (such as attitude control, navigation, rendez-vous with the International Space Station, ...).

The control system is made up of two parts: the Flight Application Software (FAS) and the Mission Safing Unit (MSU). The FAS is the main part of the system, it handles all the software functionalities of the system as long as no fault is detected. The MSU watches the FAS to detect if it is faulty. If a fault occurs, the MSU disables the FAS and starts a safety procedure, which stops the current ATV actions and moves it to a safe orbit, with respect to the station, waiting for further instructions from the ground station. In the rest of this chapter, we give an overview of the implementation of the FAS by EADS Astrium Space Transportation.

Hardware architecture

The FAS is a mission-critical system, which means that any failure in the system may cause the vehicle to be lost. In order to make the system more robust, the computations performed by the FAS are replicated on three identical processors called Data Processing Units, as shown in Fig. 14.1.

The DPUs are activated synchronously every 100ms by a clock signal transmitted by the 1553 communication bus. The clocks of the different processors are independent, which may cause some jitter between the production dates of the same output on different processors. As processors are resynchronized every 100ms, this jitter remains small (no more than 1ms).

The three DPUs acquire the same inputs, perform the same computations and produce the same outputs. All the inputs and all the critical outputs (except the telemetry) are voted. The voting mechanism compares the inputs received and the outputs produced by the different processors. Transfers required for the votes are performed by dedicated connections between the processors. If two processors agree on a value and the third one disagrees, the value produced by the third processor is considered erroneous. If three errors are detected on the same processor in less than 1s, the processor is declared faulty and is ignored until a correction is performed by the ground station. The MSU initiates the safety procedure when two processors are declared faulty.

Producing outputs at exact dates

Most outputs of the FAS must be sent to the bus communication driver (ie produced by the program) at precise dates, instead of being allowed to be sent anywhere during the output period. This can be programmed as follows: node exact(i: int rate (1000, 0)) returns (o: rate (1000, 6/10) due 0) var v: int; let v=N(i); o=v~>6/10; tel

The output o has clock (1000, 6/10) and relative deadline 0. Thus, it must be produced exactly at dates 600, 1600, 2600, . . . The node N has clock (1000, 0) and relative deadline 600. Thus, it must execute during time intervals [0, 600[, [1000, 1600[, [2000, 2600[, . . .

Output with a phase smaller than the related input

In some cases an output o is computed from an input i and has a smaller phase than i. This can be programmed as follows: node phases(i: int rate (1000, 3/10)) returns (o: rate (1000, 2/10) due 0) var v: int; let v=N(i); o=0::(v~>9/10); tel First, concerning the clocks of the program, the variable v has clock (1000, 3/10), so the expression v~>9/10 has clock (1000, 12/10) and the expression 0::(v~>9/10) has clock (1000, 2/10).

Concerning deadlines, o has relative deadline 0, so it must be produced at dates 200,1200,2200,. . . However, the first value of o, produced at date 200, is computed from the value 0, not from the variable v. Then, the second value of o produced at date 1200 is computed from the first value of v (denoted v[0]). Thus, the deadline for the production of v[0] is 1200 and it can be computed during time interval [300, 1200[. Similarly, the third value of o, produced at date 2200, is computed from the second value of v, which is computed during time interval [1300, 2200[ and so on.

End-to-end latency longer than the period

Some programs require the definition of an end-to-end latency constraint, from one input i to an output o of the same period, where the latency is longer than this period. This can be programmed exactly as our previous example but deadlines calculus are slightly different: node latency(i: rate (1000,2/10)) returns (o: rate (1000,4/10) due 0) var v: int; let v=N(i); o=0::(v~>12/10); tel Appendix A

Code for the FAS Case Study

This appendix details the code of the main node of our program for the FAS case study.

[ . . . ] node f a s ( i 1 : i n t r a t e ( 5 0 0 , 0 ) ; i 2 : i n t r a t e ( 1 0 0 , 0 ) ; i 3 : i n t r a t e ( 2 0 0 , 1 / 2 ) ; i 4 : i n t r a t e ( 2 0 0 , 1 / 2 ) ; i 5 : i n t r a t e ( 1 0 0 0 , 1 / 1 0 ) ; i 6 : i n t r a t e ( 1 0 0 0 , 1 / 1 0 ) ; i 7 : i n t r a t e ( 1 0 0 0 , 0 ) ; i 8 : i n t r a t e ( 1 0 0 0 , 1 / 1 0 ) ; i 9 : i n t r a t e ( 1 0 0 0 , 1 / 1 0 ) ; i 1 0 : i n t r a t e ( 1 0 0 0 , 1 / 1 0 ) ; i 1 1 : i n t r a t e ( 1 0 0 0 , 1 / 1 0 ) ; i 1 2 : i n t r a t e ( 1 0 0 0 , 4 / 1 0 ) ; i 1 3 : i n t r a t e ( 1 0 0 0 , 4 / 1 0 ) ; i 1 4 : i n t r a t e ( 1 0 0 0 , 1 / 1 0 ) ; i 2 3 : i n t r a t e ( 5 0 0 , 0 ) ; i 1 5 : i n t r a t e ( 1 0 0 0 , 0 ) ; i 1 6 : i n t r a t e ( 1 0 0 0 , 3 / 1 0 ) ; i 1 7 : i n t r a t e ( 1 0 0 0 , 7 / 1 0 ) ; i 1 8 : i n t r a t e ( 1 0 0 0 , 9 / 1 0 ) ; i 1 9 : i n t r a t e ( 1 0 0 0 , 1 / 1 0 ) ; i 2 0 : i n t r a t e ( 1 0 0 0 , 1 / 1 0 ) ; i 2 1 : i n t r a t e ( 1 0 0 0 , 1 / 1 0 ) ; i 2 2 : i n t r a t e ( 1 0 0 0 , 1 / 1 0 ) ; i 2 4 : i n t r a t e ( 1 0 0 0 , 1 0 / 1 0 ) ; i 2 5 : i n t r a t e ( 1 0 0 0 , 1 0 / 1 0 ) ; i 2 6 : i n t r a t e ( 1 0 0 0 , 1 0 / 1 0 ) ; i 2 7 : i n t r a t e ( 1 0 0 0 , 1 0 / 1 0 ) ; i 2 8 : i n t r a t e ( 1 0 0 0 , 1 0 / 1 0 ) ; i 2 9 : i n t r a t e ( 1 0 0 0 , 1 0 / 1 0 ) ; i 3 0 : i n t r a t e ( 1 0 0 0 , 1 0 / 1 0 ) ; i 3 1 : i n t r a t e ( 1 0 0 0 , 1 0 / 1 0 ) ; i 3 2 : i n t r a t e ( 1 0 0 0 , 1 0 / 1 0 ) ; i 3 3 : i n t r a t e ( 1 0 0 0 , 1 0 / 1 0 ) ; i 3 4 : i n t r a t e ( 1 0 0 0 , 1 0 / 1 0 ) ; i 3 5 : i n t r a t e ( 1 0 0 0 , 1 0 / 1 0 ) ; i 3 6 : i n t r a t e ( 1 0 0 , 0 ) ; i 3 7 : i n t r a t e ( 1 0 0 , 0 ) ; i 3 8 : i n t r a t e ( 1 0 0 0 , 5 / 1 0 ) ; i 3 9 : i n t r a t e ( 1 0 0 0 0 , 8 / 1 0 0 ) ; i 4 0 : i n t r a t e ( 1 0 0 0 , 5 / 1 0 ) ; i 4 1 : i n t r a t e ( 1 0 0 0 , 1 / 1 0 ) ; i 4 2 : i n t r a t e ( 1 0 0 0 , 9 / 1 0 ) ; i 4 3 : i n t r a t e ( 1 0 0 0 , 9 / 1 0 ) ; i 4 4 : i n t r a t e ( 1 0 0 0 0 , 8 / 1 0 0 ) ; i 4 5 : i n t r a t e ( 1 0 0 0 0 , 8 / 1 0 0 ) ; i 4 6 : i n t r a t e ( 1 0 0 0 0 , 8 / 1 0 0 ) ; i 4 7 : i n t r a t e ( 1 0 0 0 0 , 8 / 1 0 0 ) ; i 4 8 : i n t r a t e ( 1 0 0 0 0 , 8 / 1 0 0 ) ; i 4 9 : i n t r a t e ( 1 0 0 0 , 0 ) ; i 5 0 : i n t r a t e ( 1 0 0 0 , 0 ) ; i 5 1 : i n t r a t e ( 1 0 0 0 , 0 ) ; i 5 2 : i n t r a t e ( 1 0 0 0 , 0 ) ; i 5 3 : i n t r a t e ( 1 0 0 0 0 , 0 ) ; i 5 4 : i n t r a t e ( 1 0 0 0 0 , 0 ) ; i 5 5 : i n t r a t e ( 1 0 0 , 0 ) ) r e t u r n s ( o1 : i n t r a t e ( 1 0 0 0 , 6 / 1 0 ) due 0 ; o2 : i n t r a t e ( 1 0 0 0 , 2 / 1 0 ) due 0 ; o3 : i n t r a t e ( 1 0 0 , 0 ) ; o4 : i n t r a t e ( 1 0 0 , 0 ) ; o5 : i n t r a t e ( 1 0 0 0 0 , 6 / 1 0 ) due 0 ; o6 : i n t r a t e ( 1 0 0 0 , 6 / 1 0 ) due 0 ; o7 : i n t r a t e ( 2 0 0 , 1 / 1 0 ) due 0 ; o8 : i n t r a t e ( 2 0 0 , 0 ) ; o9 : i n t r a t e ( 5 0 0 , 2 / 1 0 ) due 0 )