• Aucun résultat trouvé

Le langage a pour but de permettre de spécifier des contraintes temps réel avec un niveau d’abs-traction é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 nœud 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 nœud : ce sont des contraintes venant de l’environnement du système.

La période d’une entrée ou d’une sortie d’un nœud peut être spécifiée de la manière suivante :

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

Dans cet exemple, i a pour période 10 et pour phase12, 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 nœuds :

node deadline(i: int rate (10,0)) returns (o: int rate (10,0) due 8)

let

...

tel

x: due dspé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) before 2) returns (o: int)

let

...

tel

x: before dspé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.

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 f0 a pour horloge (n, p) et pour échéance d0 (avec d 6= d0), 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 nœuds 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 commu-nication 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

3. Présentation informelle du langage

date 0 5 10 15 20 25 30 ...

i i0 i1 i2 i3 i4 i5 i6 ...

o i0 i2 i4 i6 ...

FIG. 11 – Sous-échantillonnage périodique

node over_sample(i: int rate (10,0))

returns (o: int rate (5,0))

let

o=i*^2;

tel

e ∗ˆk duplique chaque valeur de e, k fois. Si e a pour horloge α, alors e ∗ˆk a pour horloge α ∗.k. Ce comportement est illustré Fig. 12.

date 0 5 10 15 20 25 30 ...

i i0 i1 i2 i3 ...

o i0 i0 i1 i1 i2 i2 i3 ...

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 i0 i1 i2 i3 ...

o i0 i1 i2 ...

FIG. 13 – Décalage de phase 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

o1=tail(i); o2=tail(o1);

tel

tail(e) é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 i0 i1 i2 i3 i4 i5 i6 ...

o1 i1 i2 i3 i4 i5 i6 ...

o2 i2 i3 i4 i5 i6 ...

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

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 i0 i1 i2 i3 i4 i5 i6 ... v1 i1 i2 i3 i4 i5 i6 ... v2 i2 i3 i4 i5 i6 ... o1 0 i1 i2 i3 i4 i5 i6 ... o2 0 0 i2 i3 i4 i5 i6 ... FIG. 15 – Concaténation de flots

Transitions de rythmes sur des rythmes non spécifiés

Le développeur peut laisser les rythmes des entrées ou sorties d’un nœud non spécifiés, ils seront alors inférés par le calcul d’horloges. Par exemple :

node under_sample(i: int rate(10,0)) returns (o: int)

let

o=i/^2;

tel

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 :

node under_sample(i: int) returns (o: int)

let

o=i/^2;

tel

3. 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 nœud. Le nœud 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 nœud principal), appelée opération capteur, et le wcet de la production de chaque sortie du système (chaque sortie du nœud 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

Documents relatifs