Esterel è un esempio di linguaggio reattivo. Le caratteristiche di Esterel sono le seguenti:
Usando questo paradigma di istante istantaneo, si può vedere Esterel come un linguaggio che ha una macchina d'esecuzione infinitamente rapida: il programma reagisce immediatamente agli stimoli.
L'esecuzione di un programma Esterel sarà la seguente. Il programma inizia con uno stato iniziale. Dopo riceverà degli stimoli (un insieme di segnali di input) e reagirà in un istante (istantaneamente) per produrre altri stimoli (un insieme di segnali di output). Alla fine dell'istante avrà un altro stato che gli permetterà di reagire ad altri stimoli, etc...
Il comportamento di un programma può essere rappresentato da una linea con intervalli. Ogni intervallo rappresenta il tempo fra due reazioni. Sopra si possono mettere i segnali di input all'inizio della reazione. Sotto si possono mettere i segnali di output prodotti dalla reazione. Per esempio:
Il programma al primo istante ha ricevuto S1 e ha reagito emettendo il segnale S5. Al secondo istante, non c'è stato segnale di input ed il programma non ha emesso segnali. Al terzo istante, sul segnale S3, il programma ha reagito emettendo due segnali S5 e S6. Al quarto istante nessun input ma il programma ha emesso S6, etc...
La definizione degli istanti, i.e. la decisione di quanto si chiede
al programma Esterel, è a discrezione della persona che
integra il programma Esterel al suo ambiente. Un esempio di integrazione
è per esempio quello di chiamare il programma ogni tot secondi mettendo
come segnali di input tutti gli stimoli che si sono prodotti durante questo
periodo. È importante notare che questa è una decisione che
viene presa al di fuori del linguaggio Esterel.
Dentro Esterel l'unica cosa che importa è quella di definire la reazione
del programma ad un insieme di segnali.
Istruzioni
emit S1
emit STOP
emit READY
La sequenza termina quando l'ultima istruzione termina.
L'esecuzione è istantanea.
Se per caso la prima istruzione termina subito, allora la seconda istruzione
è eseguita nello stesso instante.
emit S1; emit S2
Questo programma reagisce al primo istante emettendo contemporaneamente S1 e S2 e termina.
emit S1; nothing; emit S2
emit S1; halt; emit S2
Il programma reagisce al primo istante emettendo S1 dopo si ferma per sempre.
emit S1; pause; emit S2
Il programma reagisce al primo istante emettendo S1 dopo si ferma. Nel secondo istante, emette S2 e termina.
In Esterel la sequenza ha precedenza
sul parallelo:
P ; Q || R
si legge come [ P ; Q ] || R
e non P ; [Q || R]
(le parentesi [] sono le parentesi di Esterel).
[ emit S1 || emit S2; pause; emit S3 ]; emit S4Al primo istante il programma emette S1 e S2.
[ emit S1; halt || emit S2; pause; emit S3 ]; emit S4Al primo istante il programma emette S1 e S2.
present <NAME> then <STATEMENT> end present
present <NAME> else <STATEMENT> end present
present <NAME> then <STATEMENT> end
present <NAME> else <STATEMENT> end
present S1 then emit S2 else emit S3 end; emit S4Al primo istante se il segnale S1 è presente, emette S2 e S4 e termina. Se il segnale non è presente emette S3 e S4 e termina.
present S1 then emit S2 end; emit S4Al primo istante se il segnale S1 è presente, emette S2 e S4 e termina. Se il segnale non è presente, emette S4 e termina.
L'attesa sul segnale di interruzione
si fa non subito ma all'istante successivo a quello in cui l'interruzione
è stata attivata.
Quando ci sono piú di una
interruzione nello stesso istante, ha precedenza l'interruzione che è
stata dichiarata per prima.
abort emit S1; pause; emit S2 when S3; emit S4Al primo istante viene emesso il segnale S1, indipendentemente dalla presenza o meno del segnale S3: l'attesa su S3 inizierà solo all'istante successivo.
abort emit S1; halt; when S2; emit S3Al primo istante viene emesso il segnale S1, indipendentemente dalla presenza o meno del segnale S3: l'attesa su S3 inizierà solo all'istante successivo.
abort abort emit S1; halt; when S2; emit S3; when S4; emit S5Al primo istante viene emesso il segnale S1, indipendentemente dalla presenza o meno dei segnali S2 e S4.
L'attesa sul segnale di interruzione è attivata subito.
abort emit S1; pause; emit S2 when immediate S3; emit S4Al primo istante viene emesso il segnale S1 solo se il segnale S3 non è presente. Se il segnale S3 è presente, il programma emette S4 e termina.
weak abort emit S1; pause; emit S2 when S3; emit S4Al primo istante viene emesso il segnale S1 indipendentemente dalla presenza o meno di S3: l'attesa di S3 non è attivata.
L'attesa sul segnale di interruzione si fa subito.
weak abort emit S1; pause; emit S2 when immediate S3; emit S4Al primo istante, se il segnale S3 è presente, il programma emette S1 e S4 e termina. Se il segnale S3 non è presente, il programma emette S1.
Un'interruzione è catturata
solo se il corpo dell'istruzione abort non termina nell'istante.
abort emit S1; pause; emit S2 when S3 do emit S4 end abort; emit S5Al primo istante viene emesso il segnale S1, indipendentemente dalla presenza o meno del segnale S3: l'attesa su S3 inizierà solo all'istante successivo.
abort pause; when S do emit S1 end abortIl segnale S1 non sarà mai emesso.
Il corpo di un loop
non può terminare subito: sarebbe un ciclo infinito istantaneo.
loop emit S1; pause; end loopIl programma emette il segnale S1 ad ogni istante.
loop emit S1; pause; pause; end loopIl programma emette il segnale S1 ogni due istanti.
loop emit S1 end loopIl programma è un ciclo infinito ed è rifiutato dal compilatore Esterel.
L'attesa comincia all'istante
successivo.
await Sè equivalente a
abort halt when S
L'attesa comincia subito.
await immediate Sè equivalente a
abort halt when immediate S
sustain Sè equivalente a
loop emit S; pause; end loop
do P upto Sè equivalente a
abort P; halt when S
loop P each Sè equivalente a
loop abort P; halt when S end loop
every S do P end everyè equivalente a
await S; loop P each S
every immediate S do P end everyè equivalente a
await immediate S; loop P each S
await 2 Sè equivalente a
await S; await S
abort P when 2 Sè equivalente a
abort P when T1; emit T2; || weak abort await 2 S; emit T when T2
loop do P each 2 Sè equivalente a
loop abort P; halt when 2 S end loop
every 2 S do P end everyè equivalente a
await 2 S; loop P each 2 S
repeat 2 times do P end repeatè equivalente a
P; P
trap <NAME> in
<STATEMENT>
end trap
Per sollevare una eccezione:
exit <NAME>
Per catturare una eccezione:
trap <NAME> in
<STATEMENT>
handle <NAME> do
<STATEMENT>
end trap
Quando sono sollevate piú eccezioni, ha precedenza l'eccezione che è stata dichiarata per prima.
weak abort P when Sè equivalente a
trap T in P; exit T || await S; exit T end trap
weak abort P when S do Q end abortè equivalente a
trap Terminate in trap WeakAbort in P; exit Terminate || await S; exit WeakAbort handle WeakAbort do Q end trap end trap
loop signal S in present S then emit O end present pause; emit S end signal end loopIl segnale O non sarà mai emesso. Al primo istante, il segnale S non è presente, il programma si ferma. Al secondo istante, viene emesso il segnale locale S, l'istruzione signal termina. Si entra in un nuovo ciclo. L'istruzione signal è eseguita di nuovo, crea un nuovo segnale locale S che dunque non è presente.
signal S in loop present S then emit O end present pause; emit S end loop end Sè equivalente a:
pause; sustain S
var X : double in P end var
var X : integer, Y: integer in P end varè equivalente a:
var X : integer in var Y: integer in P end var end var
Il tipo della variabile può essere un tipo di base: boolean, integer, float, double, o un tipo astratto.
Il linguaggio Esterel è deterministico. Dunque non si possono fare assegnazioni parallele sulla stessa variabile in uno stesso istante:
X := 0 || X := 1Non è permesso: alla fine dell'istruzione parallela non si sa se il valore di X è 0 oppure 1.
var X : integer in X := 0 || pause; X := 1 end varè permesso. Ci sono due assegnazioni ma non accadono nello stesso istante.
var X : integer := 1 in P end varè equivalente a:
var X : integer in X := 1; P end var
Si può anche sapere il valore di un segnale l'ultima volta che è
stato emesso (escluso l'istante presente) con l'espressione:
?pre(<NAME>)
Il comportamento di default di un segnale con valore è che un tale segnale può essere emesso una volta sola per istante. Nell'esempio seguente:
emit S1(1) || emit S1(2) || emit S2(?S1)il segnale S1 è emesso due volte nel primo istante. Per default, tale programma è rifutato dal compilatore Esterel. Per determinare il valore di un segnale emesso più di una volta bisogna dare una funzione (associativa e commutativa) che definisce come si fa per combinare i valori. Nell'esempio precedente, se dichiariamo la funzione + come funzione di combinazione, il programma adesso è valido e il valore di S2 è 3 (=1+2).
Per i segnali con valore, bisogna dare il suo tipo. Si può opzionalmente dare il suo valore iniziale (il valore del suo pre al primo istante) e la funzione che permette di combinare i valori del segnale. La sintassi per i segnali di input è:
input <NAME> : <TYPE>;
input <NAME> := <EXPRESSION> : <TYPE>;
input <NAME> : combine <TYPE> with <FUNCTION>;
input <NAME> := <EXPRESSION> : combine <TYPE> with <FUNCTION>;
Lo stesso vale per i segnali di output e inputoutput.
module COUNT: input I; output Count: integer; var Count := 0 : integer in every I do Count := Count + 1; emit Count(Count); end every end var end moduleIl segnale I è un segnale d'input. Il segnale Count è un segnale d'output a valore intero.
Un programma equivalente ma che usa il costruttore pre è il seguente:
module COUNT: input I; output Count := 0 : integer; every I do emit Count(?pre(Count)+1); end every end var end module
Una relazione d'esclusione indica che due segnali nella relazione non possono essere presenti nello stesso istante.
Una relazione d'implicazione indica che se un segnale è presente, allora un altro segnale è presente.
module SPEED: input Centimeter, Second; relation Centimeter # Second; ouput Speed: integer; loop var Distance := 0 : integer in abort every Centimeter do Distance := Distance + 1; end every when Second do emit Speed(Distance) end abort end var end loop end module
Si può definire un nuovo tipo astratto usando il
comando type:
type <TYPE>
Si può definire una nuova costante astratta usando il
comando constant:
constante <NAME>: <TYPE>
Si può definire una nuova funzione astratta usando il
comando function:
function <NAME>(<TYPE>,...,<TYPE>) : <TYPE>
module GENERIC_SPEED: type T; constant Initial: T, Increment: T; function Add(T,T): T; input A, B; output Speed: T; loop var Count := Initial : T in abort every immediate A do Count := Add(Count,Increment) end every when B do emit Speed(Count) end abort end var end loop end module
Si può anche modificare, cambiare, ridenominare i
tipi, costanti, funzioni e segnali:
run <NAME> [
type <NAME> / <NAME>;
constant <NAME> / <NAME>;
function <NAME> / <NAME>;
signal <NAME> / <NAME>;
]
Nella notazione <NAME> / <NAME> il primo nome è quello nuovo ed il secondo è quello definito nel modulo.
run GENERIC_SPEED [ type integer/T; constant 0/Initial, 1/Increment; function +/Add; signal Centimeter/A, Second/B; ]
Assicurarsi che un programma ha questa proprietà di stabilità per tutte le sue configurazioni è la vera difficoltà della programmazione sincrona. Il problema principale è quello della causalità. L'esempio seguente mostra dove è il problema:
present S then nothing else emit S endNon si può dare una semantica a tale programma. All'inizio dell'istante, il segnale S non è presente. Dunque l'istruzione present sceglie l'alternativa else che richiede di emettere il segnale S. Ciò vuole dire che bisogna cambiare l'alternativa e andare nell'alternativa then. Ma in tal caso ciò significa che il segnale S non è piú emesso. Dunque, bisogna cambiare alternativa: il programma non si stabilizza mai. La non-presenza di S implica la presenza di S. C'è un problema di causalità che è individuato staticamente in fase di compilazione.
Un altro problema è quando il programma può avere piú di un comportamento possibile. Un esempio è il programma seguente:
present S then emit S else nothing endPer tale programma, i comportamenti che consistono nell'emettere S e nel non emettere S sono due comportamenti accettabili. Poiché Esterel è un linguaggio deterministico, il compilatore rifiuta tale programma.
module BASIC_STOPWATCH: input START_STOP, HS; output TIME: integer; var TIME := 0 : integer in loop emit TIME(TIME); await START_STOP; abort every HS do TIME := TIME + 1; emit TIME(TIME); end every when START_STOP end loop end var end module
module STOPWATCH_1: input START_STOP, HS, RESET; output TIME: integer; loop run BASIC_STOPWATCH each RESET end module
module STOPWATCH_2: input START_STOP, HS, RESET, LAP; output TIME: integer; signal INTERNAL_TIME : integer in run STOPWATCH_1[ signal INTERNAL_TIME/TIME] || loop abort abort every INTERNAL_TIME do emit TIME(?INTERNAL_TIME) end when LAP; emit TIME(?INTERNAL_TIME); await LAP; when RESET end loop end signal end module.
Per interpretare il pulsante BUTTON_2, definiamo un modulo che usa due segnali locali STOPWATCH_RUNNING, FROZEN_TIME per indicare se il cronometro sta avanzando e se sta mostrando un tempo intermedio:
module BUTTON_INTERPRETER: input START_STOP, BUTTON_2; output RESET, LAP; signal STOPWATCH_RUNNING, FROZEN_TIME in every BUTTON_2 do present STOPWATCH_RUNNING then emit LAP else present FROZEN_TIME then emit LAP else emit RESET end present end present end every || loop await START_STOP; abort sustain STOPWATCH_RUNNING when START_STOP end || loop await LAP; abort sustain FROZEN_TIME when LAP; end loop end signal end moduleTale codice non va bene perché c'è un problema di causalità fra LAP e FROZEN_TIME.
module BUTTON_INTERPRETER: input START_STOP, BUTTON_2; output RESET, LAP; signal STOPWATCH_RUNNING, FROZEN_TIME in every BUTTON_2 do present STOPWATCH_RUNNING then emit LAP else present FROZEN_TIME then emit LAP else emit RESET end present end present end every || loop await START_STOP; abort sustain STOPWATCH_RUNNING when START_STOP end || loop await LAP; abort sustain FROZEN_TIME when LAP; end loop end signal end moduleLa presenza di FROZEN_TIME fa emettere LAP che a sua volta impedisce l'emissione di FROZEN_TIME. Una soluzione è quella di rendere l'interruzione weak, cosí l'emissione di LAP impedisce l'emissione di FROZEN_TIME solo all'istante successivo:
module BUTTON_INTERPRETER: input START_STOP, BUTTON_2; output RESET, LAP; signal STOPWATCH_RUNNING, FROZEN_TIME in every BUTTON_2 do present STOPWATCH_RUNNING then emit LAP else present FROZEN_TIME then emit LAP else emit RESET end present end present end every || loop await START_STOP; abort sustain STOPWATCH_RUNNING when START_STOP end || loop await LAP; weak abort sustain FROZEN_TIME when LAP; end loop end signal end module
module FULL_STOPWATCH : input START_STOP, HS, BUTTON_2; ouput TIME: integer; relation START_STOP # HS # BUTTON_2; signal RESET, LAP in run STOPWATCH_2 || run BUTTON_INTERPRETER end signal end module
Il giocatore può inserire una moneta (COIN) e premere sui pulsanti READY e STOP.
La macchina ha:
All'inizio, solo la lampadina GAME_OVER è accesa.
Il gioco comincia quando il giocatore mette una moneta. In tal caso la lampadina GAME_OVER si spenge.
Per valutare il tempo di reazione, la macchina prenderà NUMBER misure e farà una media.
Per iniziare una misura, il giocatore dove premere sul pulsante READY.
Dopo, la macchina aspetta un momento aleatorio e accende la lampadina GO. Adesso il giocatore deve premere il pulsante STOP il piú velocemente possibile.
Alla fine di ogni misura, il tempo di reazione appare sullo schermo DISPLAY.
Alla fine delle NUMBER misure, la macchina aspetta PAUSE_LENGTH millisecondi per mettere sullo schermo la media del tempo. Il gioco è finito.
Il gioco può finire in anticipo, se
Il giocatore è avvertito con la campanella RING_BELL quando
Inserire una moneta COIN in un qualsiasi momento fa cominciare un nuovo gioco.
module REFLEX_GAME: constant NUMBER, PAUSE_LENGTH, TIME_LIMIT: integer; function RANDOM(): integer; input MS, COIN, READY, STOP; relation MS # COIN # READY, COIN # STOP, READY # STOP; output GO_ON, GO_OFF, GAME_OVER_ON, GAME_OVER_OFF, RED_ON, RED_OFF, DISPLAY: integer, RING_BELL; emit GO_OFF; emit RED_OFF; emit GAME_OVER_ON; emit DISPLAY(0);
module AVERAGE: input UPDATE_AVERAGE: integer; output AVERAGE_VALUE: integer; var MEASURE_NUMBER := 0, TOTAL_TIME := 0 : integer in every immediate UPDATE_AVERAGE do TOTAL_TIME := TOTAL_TIME + ?UPDATE_AVERAGE; MEASURE_NUMBER := MEASURE_NUMBER + 1; emit AVERAGE_VALUE(TOTAL_TIME/MEASURE_NUMBER); end every end var
abort abort every STOP do emit RING_BELL end when READY when TIME_LIMIT MS do exit ERROR end abort
abort abort every READY do emit RING_BELL end when RANDOM() MS; emit GO_ON when STOP do exit ERROR end abort
abort var TIME := 0 : integer in abort every MS do TIME := TIME+1 end || every READY do emit RING_BELL end when STOP; emit DISPLAY(time); emit UPDATE_AVERAGE(TIME); emit GO_OFF; end var when TIME_LIMIT MS do exit ERROR
module REFLEX_GAME: constant NUMBER, PAUSE_LENGTH, TIME_LIMIT: integer; function RANDOM(): integer; input MS, COIN, READY, STOP; relation MS # COIN # READY, COIN # STOP, READY # STOP; output GO_ON, GO_OFF, GAME_OVER_ON, GAME_OVER_OFF, RED_ON, RED_OFF, DISPLAY: integer, RING_BELL; emit GO_OFF; emit RED_OFF; emit GAME_OVER_ON; emit DISPLAY(0); every COIN do emit GO_OFF; emit RED_OFF; emit GAME_OVER_OFF; emit DISPLAY(0); trap END_GAME in trap ERROR in signal UPDATE_AVERAGE: integer, AVERAGE_VALUE: integer in run AVERAGE || repeat NUMBER times abort abort every STOP do emit RING_BELL end when READY when TIME_LIMIT MS do exit ERROR end abort; abort abort every READY do emit RING_BELL end when RANDOM() MS; emit GO_ON when STOP do exit ERROR end abort; abort var TIME := 0 : integer in abort every MS do TIME := TIME+1 end || every READY do emit RING_BELL end when STOP; emit DISPLAY(time); emit UPDATE_AVERAGE(TIME); emit GO_OFF; end var when TIME_LIMIT MS do exit ERROR end repeat; await PAUSE_LENGTH MS; emit DISPLAY(?AVERAGE_VALUE); exit END_GAME end signal end trap; emit RED_ON; emit GO_OFF end trap; emit GAME_OVER_ON end every
G2*. Scrivere un programma che gestisce il telecomando di un televisore. Il telecomando ha due modi:
G3*.
Scrivere un programma che gestisce un orologio per un gioco
con 2 giocatori.
L'orologio mostra per ciascun giocatore il tempo che gli rimane
per finire la partita (i segnali DISPLAY1 e DISPLAY2).
Il tempo è indicato in millisecondi.
`Per calcolare il tempo che passa, il programma riceve un segnale MS ogni millisecondo.
Quando un giocatore ha giocato la sua mossa, preme il suo pulsante
(BUTTON1 o BUTTON2): si ferma il suo tempo e parte
il tempo del suo avversario.
All'inizio della partita, ogni giocatore ha lo stesso tempo (INIT_TIME) ed i due tempi sono fermi.
La partita è finita (END) quando uno dei due tempi arriva a zero.
In ogni momento, si può ri-inizializzare l'orologio usando il pulsante
RESET.
G4*.
Scrivere un programma che gestisce una macchina per fare delle
foto-tessera.
All'inizio, la macchina
non è attiva, ciò viene indicato da una lampadina rossa (RED).
La macchina viene attivata quando un utente inserisce una moneta (INSERT_COIN).
Il fatto che la macchina sia attiva è indicato da una lampadina verde (GREEN).
Una volta inserita la moneta, l'utente deve scegliere il formato delle
foto (WHICH_FORMAT).
Quando l'utente ha scelto il formato (FORMAT), si può proseguire.
Se l'utente non sceglie entro 60 secondi (SECOND), la macchina
restituisce i soldi (RETURN_COIN) e diventa inattiva (RED).
Una sessione è composta di tre foto validate dall'utente.
Per fare una foto, la macchina aspetta 10 secondi, poi accende una
lampadina per avvertire l'utente di essere pronto (WARNING)
e dopo 2 secondi (SECOND) scatta la foto (CLICK). Dopo
3 secondi (SECOND),
mostra il risultato all'utente (SHOW). Se l'utente è d'accordo
(YES), la foto è validata, altrimenti la foto è rifiutata (NO).
Se l'utente non risponde entro 60 secondi (SECOND) o se l'utente
rifiuta più di 10 foto, la macchina restituisce i soldi
(RETURN_COIN) e diventa inattiva (RED).
Quando sono state validate tre foto, la macchina stampa le foto
(PRINT) e diventa inattiva.
G5*.
Scrivere un programma che gestisce i semafori di un incrocio.
Ci sono quattro semafori (nord, est, sud, ovest) ma il controllo è fatto
considerando i semafori a coppia. Ci sono due coppie: la coppia nord-sud
(NS) e la coppia est-ovest (EW).
Un semaforo è alternativamente verde (GREEN_NS o GREEN_EW),
giallo (ORANGE_NS o ORANGE_EW) e rosso (RED_NS o RED_EW).
Quando una coppia di semafori è verde o gialla l'altra deve essere
necessariamente rossa.
In un ciclo normale, un semaforo resta verde 30 secondi (SECOND)
, giallo 2 secondi e rosso 32 secondi.
Un pedone può premere un pulsante (WALK_NS o WALK_EW).
Questo assicura che se il semaforo è verde non lo resterà per piú
di altri 10 secondi.
G6*.
Scrivere un programma che gestisce l'apertura di una porta
automatica di un parcheggio per auto.
Prima di gestire l'apertura della porta, il programma aspetta
di avere il codice segreto (INIT_CODE).
Inizialmente la porta è chiusa.
A intervalli regolari viene segnalata la presenza di una macchina davanti
la porta (DETECT).
La porta si apre (OPEN) se il conducente della macchina
introduce un codice (CODE) che corrisponde al codice
segreto.
La porta si chiude (CLOSE) se non sono piú individuate
macchine (DETECT) per un periodo di 5 secondi (SECOND).
Ogni volta che viene introdotto un codice errato,
si accende una lampadina (RETRY).
Se dopo 2 minuti che una macchina è stata individuata
la porta non è stata aperta, viene avvertito il centralino (ERROR).