• Aucun résultat trouvé

Lustre et Signal : des langages de programmation synchrone déclaratifs

1.3 Programmation synchrone

1.3.4 Lustre et Signal : des langages de programmation synchrone déclaratifs

À une époque très proche de celle du développement d’Esterel, d’autres langages de programmation synchrone ont été développés, s’inspirant de modèles toutefois différents. En particulier, le langage Lustre [CPHP87] vit le jour lui aussi au début des années 1980, développé par des chercheurs du laboratoire grenoblois VERIMAG. Ce langage de programmation synchrone, à la différence d’Esterel, ne repose pas sur la réaction à des évènements ponctuels, mais sur un modèle de programmation à flots de données, permettant de représenter l’évolution de valeurs au fil du temps. Lustre fut initialement développé dans l’optique d’offrir un langage de programmation manipulable par des ingénieurs de contrôle habitués au formalisme déclaratif d’un modèle à flots de données [Hal05].

Sur le modèle du langage Lucid [AW77]5, toutes les valeurs manipulées par un programme Lustre sont des flots de données : des séquences de valeurs qui peuvent varier au fil de l’exécution d’un programme. Chaque flot possède ainsi, par défaut, une valeur à chaque instant du programme, et cette valeur est susceptible de changer d’un instant synchrone à l’autre. De ce fait, une variable x dans le langage Lustre correspond au flot de toutes les valeurs que prend x d’instant en instant :

x≡ (x0, x1, x2, x3, . . . , xi. . . ) Une constante correspond alors à un flot invariant de valeurs :

2≡ (2, 2, 2, 2, . . . , 2, . . . )

Les opérateurs arithmétiques et logiques du langage s’appliquent point à point aux valeurs que prennent les flots lors de l’exécution d’un programme :

x+ y ≡ (x0+ y0, x1+ y1, x2+ y2, x3+ y3, . . . , xi+ yi, . . . )

De manière semblable aux modules d’Esterel, le composant logiciel de base d’un programme Lustre est le nœud. Un nœud peut être considéré comme une fonction qui associe un flot de sorties à un flot d’entrées. Le corps d’un nœud est un système d’équations, semblables à des fonctions temporelles [CH86], qui déclarent des variables dont la valeur est susceptible de changer à chaque instant d’exécution. Chacun de ces flots est calculé dans le même instant synchrone, et le modèle synchrone flot de données de Lustre est alors une classe restreinte des réseaux de processus de Kahn (Kahn Process Networks - KPN [Kah74]) dans laquelle les communications peuvent être réalisées sans buffers [MPP10].

Le style déclaratif d’un programme Lustre est assez semblable à la structure d’un programme fonc-tionnel : chaque équation définit une variable dont la valeur dépend de l’instant, et le système d’équation du corps d’un nœud est un ensemble de déclarations de variables. Les considérations « impératives » d’exécution du programmes sont absentes de la sémantique du langage : le sens de lecture des équations ne traduit pas leur « ordre » de calcul.

Par exemple, la figure 1.13 correspond à la définition d’un nœud nommé BOOLOPS, qui reçoit en entrée deux flots booléens A et B, et calcule en sortie les flots ANDB, ORB et XORB, qui correspondent, respectivement, au calcul de A∧ B, A ∨ B et A ⊕ B.

node BOOLOPS (A: bool ; B: bool ) returns ( ANDB : bool ; ORB : bool ; XORB : bool );

let

XORB = ORB and ( not ANDB ); ANDB = if A then B else false; ORB = if A then true else B;

tel;

Figure 1.13 – Un nœud Lustre qui calcule le « et », le « ou » , et le « ou exclusif » de ses entrées

Dans la version originelle du langage Lustre, les opérateurs disponibles sont les classiques opérateurs arithmétiques et logiques, ainsi que plusieurs opérateurs temporels :

— L’opérateur de mémoire pre qui permet d’avoir accès à la valeur d’un flot à l’instant précédent :

pre a≡ (nil, a0, a1, a2, . . . , ai−1, . . . )

— L’opérateur d’initialisation -> qui permet de définir un flot à partir de la valeur d’un flots pour le premier instant et d’un autre flot de valeurs pour les instants suivants :

a -> b≡ (a0, b1, b2, ..., bi, ...)

Il est alors possible de définir en Lustre la suite des entiers positifs de la sorte :

1.3. Programmation synchrone 35

– Enfin, l’opérateur d’échantillonnage when, qui permet de ralentir l’exécution de composants du programme en conditionnant la présence de flots en fonction de valeurs booléennes (les horloges) :

(a when x)n≡  

a⊥ sinonnsi xn= true Le symbole⊥ représente l’absence de valeur.

Chaque flot de données Lustre est implicitement couplé à une horloge qui est par défaut l’horloge globale, la plus rapide. On peut explicitement le restreindre à une horloge plus lente grâce à l’opérateur when, par exemple l’extrait de programme suivant force le flot x à n’être défini que quand b est vrai :

x = 4 when b;

Le tableau de la figure 1.14 représente une simulation de l’exécution de ces trois opérateurs :

instant 0 1 2 3 4 5 . . .

c true false true true false true . . .

x x0 x1 x2 x3 x4 x5 . . .

y y0 y1 y2 y3 y4 y5 . . .

prey nil y0 y1 y2 y3 y4 . . .

x -> pre y x0 y0 y1 y2 y3 y4 . . .

x when c x0x2 x3x5 . . .

Figure 1.14 – Les opérateurs temporels de Lustre

La compilation d’un programme Lustre nommée compilation en boucle simple consiste à transformer le programme en un programme séquentiel composé d’une seule boucle qui réalise la scrutation de la valeur de ses flots d’entrée en début d’instant synchrone, calcule la valeur de ses flots de sortie, et émet la valeur de ces derniers en fin d’instant.

Signal

Basé sur un modèle déclaratif proche de Lustre, le langage Signal [GG87] est un langage de program-mation synchrone à flots de données, destiné à la programprogram-mation de systèmes temps réel. Développé dès les années 1980 par une équipe du laboratoire IRISA de Rennes, il constitue le troisième grand langage synchrone développé en France à cette époque.

Signal est un langage relationnel qui combine en quelque sorte la vision flot de données de Lustre, et la vision évènementielle des signaux d’Esterel. En effet, un programme dans le langage Signal définit (sous une forme équationnelle comparable à celle de Lustre) des signaux, qui sont vus comme des séquences de données dont les valeurs ne sont pas systématiquement présentes à tous les instants d’exécution du programme. Pour représenter cette absence, Signal partage la notion d’horloge avec le langage Lustre : l’ensemble des instants pendant lesquels un signal a une valeur correspond à son horloge. Signal intègre

une notion de synchronicité permettant d’établir le fait que deux signaux sont sur la même horloge, et donc qu’ils sont synchrones, avec l’opérateur ^=.

Signal définit des opérateurs temporels assez comparables à ceux de Lustre. Par exemple, l’opérateur when est semblable à celui de Lustre, et l’opérateur $, placé après le nom d’un signal, correspond à l’opérateur pre de Lustre. On peut ainsi par exemple définir un compteur CPT qui s’incrémente à chaque instant, et qui est remis à zéro dès l’arrivée d’un évènement RESET de la façon suivante :

(| CPT := 0 when RESET default CPT$ init 0 + 1 |)

L’opérateur default permet de définir une valeur pour CPT quand RESET est absent (il fait partie des opérateurs dits polychrones de par le fait qu’il s’applique à des signaux dont les horloges sont différentes), et le mot-clé init permet d’indiquer la valeur d’initialisation du signal pour le premier instant d’exécution. La figure 1.15 correspond à un processus Signal (équivalent à un nœud Lustre) nommé COUNTERS, qui reçoit un signal en entrée (dénoté par le symbole « ? ») nommé RESET, et retourne deux signaux en sortie (dénotés par « ! ») appelés CPT1 et CPT2. Le premier est un compteur croissant, initialisé à 0, le second un compteur décroissant, initialisé à 100, et tous deux peuvent être réinitialisés dès que le signal RESET est présent. L’expression CPT1 ^= CPT2 permet de contraindre ces deux signaux à avoir la même horloge.

process COUNTERS =

( ? event RESET ; ! integer CPT1 ;

integer CPT2 ; )

(| CPT1 := 0 when RESET default CPT1$ init 0 + 1 | CPT2 := 100 when RESET default CPT2$ init 100 - 1 | CPT1 ^= CPT2

|);

Figure 1.15 – Un processus Signal

Langages synchrones dérivés

Quelques années après la réalisation de Lustre, le cœur du langage fut repris pour la définition d’un ensemble d’outils industriels, nommé SCADE [CPP17], permettant la programmation graphique de sys-tèmes synchrones, via une représentation des éléments d’un programme sous forme de « planches ». Cette suite logicielle est aujourd’hui utilisée dans de nombreuses applications critiques, par exemple dans le système de contrôle des avions Airbus de la série A300. Cette utilisation repose sur la particu-larité de SCADE de posséder un générateur de code, nommé KCG, qui a reçu la certification DO-178C niveau A [⚓2], nécessaire à la production de code embarqué critique destiné au vol d’un avion. Cette certification libère le processus de développement de l’obligation de réaliser de nombreux tests afin de vérifier la correction du code généré, et représente ainsi un avantage de poids pour l’utilisation de SCADE [PAM+09]. Cette factorisation de la certification permet à des développeurs d’une application de n’avoir alors qu’à démontrer la traçabilité entre depuis le cahier des charges jusqu’au programme SCADE, et non pas jusqu’au code exécuté. De manière similaire, d’autres certifications de KCG permettent d’uti-liser SCADE pour la programmation de systèmes à fortes exigences de sûreté, comme la certification européenne EN 50128 ayant trait aux transports ferroviaires, la norme internationale CEI 61508 portant

1.3. Programmation synchrone 37

sur les composants programmables d’un système électronique, ou la norme CEI 60880 qui concerne les systèmes de contrôle des centrales nucléaires.

Figure 1.16 – Une planche SCADE (image tirée de [CHP06])

De nombreux langages synchrones académiques, inspirés du modèle de langages à flots de données, et en particulier de Lustre (qui continue lui-même de s’enrichir dans sa version 6), ont été développés depuis son apparition. Parmi ces différents langages, le langage Lucid Synchrone [CP99, Pou06] est une extension du langage Lustre à l’ordre supérieur : en effet, les paramètres des nœuds dans Lucid Synchrone peuvent eux-mêmes être des nœuds synchrones. Lucid Synchrone représente une combinaison puissante de langages synchrones « à la Lustre » et de langages fonctionnels « à la ML » : les flots peuvent par exemple correspondre à des valeurs de types produits, utilisables avec un opérateur de filtrage. Plusieurs constructions de ce langage (signaux, analyse d’initialisation des flots, machines à états, système d’horloges . . . ) ont par ailleurs été reprises dans SCADE.

let node iter init f x = y where rec y = f x ( init -> pre y)

Figure 1.17 – Un nœud Lucid Synchrone qui itère une fonction f sur un flot de valeurs x

D’autres langages synchrones visent à étendre le modèle de Lustre. Par exemple, le langage Heptagon, développé dans l’équipe PARKAS de l’École Normale Supérieure inclut une représentation optimisée des tableaux de valeurs [GGPP12], et inclut une extension (nommée BZR) qui permet d’intégrer la synthèse de contrôleurs discrets (SCD) à la compilation d’un programme synchrone [DRM11]. Le langage hybride Zélus [BP13], développé par la même équipe, est un langage qui dérive de Lustre et Lucid Synchrone mêlant des notions de temps discret et de temps continu grâce à l’utilisation d’équations différentielles ordinaires.

Dans le cadre de nos travaux, nous estimons que l’approche flots de données adoptée par ces lan-gages synchrones est particulièrement adaptée au matériel et aux applications que nous visons. En effet, le fonctionnement physique du matériel est aisément représenté par des flots : chaque broche d’un mi-crocontrôleur possède à tout moment une valeur (indiquant si elle est parcourue ou non par un courant

électrique), et chaque composant de l’application est alors représentable par un nœud synchrone qui permet de calculer, à chaque instant, des valeurs de courant en sortie à partir des stimuli électriques reçus en entrée par le microcontrôleur. La représentation schématique adoptée par le langage SCADE, proche de la représentation d’un circuit électronique, témoigne par ailleurs bien de cette proximité entre le mo-dèle adopté par un langage comme Lustre et le momo-dèle physique réel. De plus, le momo-dèle de compilation classique de Lustre génère un code peu gourmand en ressources, spécialement adapté aux limitations du matériel que nous considérons.