Introduzione

Un linguaggio reattivo permette di definire programmi che reagiscono ad un ambiente. Ciò vuole dire che reagiscono a stimoli (eventi) provenienti dall'ambiente producendo altri stimoli. Una classe importante di applicazioni sono le cosidette applicazioni in tempo reale. Un'applicazione si dice in tempo reale quando c'è una necessità per l'applicazione di rispondere velocemente all'ambiente.

Esterel è un esempio di linguaggio reattivo. Le caratteristiche di Esterel sono le seguenti:

Esterel usa i segnali per comunicare con l'ambiente. I segnali provenienti dall'ambiente saranno segnali di input, mentre i segnali di reazione saranno segnali di output.

Istante

La nozione principale in Esterel è la nozione d'istante. Un instante è determinato dal periodo di tempo fra la ricezione dei segnali di input e l'emissione dei segnali di output. Esterel è un linguaggio sincrono. Ciò vuole dire che un instante è istantaneo. Questo non è possibile nell'implementazione di Esterel, ma si può considerare una buona approssimazione se il tempo di reazione del programma Esterel è sufficientemente rapido.

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

Emissione

Sintassi

emit <NAME>

Semantica

L'istruzione emit emette un segnale e termina.

Esempi

emit S1
emit STOP
emit READY

Sequenza

Sintassi

<STATEMENT> ; <STATEMENT>

Semantica

L'istruzione ; permette di eseguire in sequenza due istruzioni: se la prima istruzione termina, si passa alla seconda.

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.

Esempio

emit S1;
emit S2

Questo programma reagisce al primo istante emettendo contemporaneamente S1 e S2 e termina.

Niente

Sintassi

nothing

Semantica

L'istruzione nothing non fa niente e termina subito.

Esempio

emit S1;
nothing; 
emit S2

Halt

Sintassi

halt

Semantica

L'istruzione halt ferma l'esecuzione. L'istruzione halt non termina mai.

Esempio

emit S1;
halt;
emit S2

Il programma reagisce al primo istante emettendo S1 dopo si ferma per sempre.

Pausa

Sintassi

pause

Semantica

L'istruzione pause non fa niente, si ferma per un instante e termina.

Esempio

emit S1;
pause;
emit S2

Il programma reagisce al primo istante emettendo S1 dopo si ferma. Nel secondo istante, emette S2 e termina.

Parallelo

Sintassi

<STATEMENT> || <STATEMENT>

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).

Semantica

L'istruzione P || Q esegue in parallelo le istruzioni P e Q. Un parallelo termina solo se entrambe le istruzioni terminano.

Esempi

[
     emit S1
  || 
     emit S2;
     pause;
     emit S3
];
emit S4
Al primo istante il programma emette S1 e S2.
Al secondo istante, emette S3 e S4 e termina.
[
     emit S1;
     halt
  || 
     emit S2;
     pause;
     emit S3
];
emit S4
Al primo istante il programma emette S1 e S2.
Al secondo istante, emette S3.
Poiché la prima istruzione non finisce, il programma non finisce mai e non reagisce piú. In particolare non emetterà mai S4.

Presenza

Sintassi

present <NAME> then <STATEMENT> else <STATEMENT> end present

Variante

present <NAME> then <STATEMENT> end present

present <NAME> else <STATEMENT> end present

present <NAME> then <STATEMENT> end

present <NAME> else <STATEMENT> end

Semantica

L'istruzione present verifica la presenza di un segnale. Se tale segnale è presente, allora viene eseguita la parte then altrimenti si esegue la parte else. L'istruzione finisce quando la parte then o rispettivamente la parte else termina.

Esempi

present S1 then emit S2 else emit S3 end;
emit S4
Al 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 S4
Al primo istante se il segnale S1 è presente, emette S2 e S4 e termina. Se il segnale non è presente, emette S4 e termina.

Interruzione

Sintassi

abort <STATEMENT> when <NAME>

Semantica

L'istruzione abort esegue il suo corpo. Tale esecuzione è interrotta subito quando compare il segnale d'interruzione.
L'istruzione termina quando il suo corpo termina o quando compare il segnale d'interruzione.

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.

Esempi

abort
  emit S1;
  pause;
  emit S2
when S3;
emit S4
Al primo istante viene emesso il segnale S1, indipendentemente dalla presenza o meno del segnale S3: l'attesa su S3 inizierà solo all'istante successivo.
Al secondo istante, se il segnale S3 non è presente, il programma emette S2 e S4 e termina. Se il segnale è presente, il programma emette solo S4 e termina.
abort
  emit S1;
  halt;
when S2;
emit S3
Al primo istante viene emesso il segnale S1, indipendentemente dalla presenza o meno del segnale S3: l'attesa su S3 inizierà solo all'istante successivo.
Negli istanti successivi, il programma reagirà solo se il segnale S2 è presente. In tal caso, il programma emette S3 e termina.
abort
  abort
    emit S1;
    halt;
  when S2;
  emit S3;
when S4;
emit S5
Al primo istante viene emesso il segnale S1, indipendentemente dalla presenza o meno dei segnali S2 e S4.
Negli istanti successivi, il programma reagirà solo se il segnale S2 o il segnale S4 è presente. In tal caso, se il segnale S4 è presente, il programma emette S5 e termina. Se il segnale S4 non è presente ma è presente il segnale S2, allora il programma emette S3 e S5 e termina.

Interruzione immediata

Sintassi

abort <STATEMENT> when immediate <NAME>

Semantica

L'istruzione esegue il suo corpo. Tale esecuzione è interrotta subito quando compare il segnale d'interruzione.
L'istruzione termina quando il suo corpo termina o quando compare il segnale d'interruzione.

L'attesa sul segnale di interruzione è attivata subito.

Esempio

abort
  emit S1;
  pause;
  emit S2
when immediate S3;
emit S4
Al 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.
Se il segnale S3 non era presente al primo istante, al secondo istante se il segnale S3 non è presente, il programma emette S2 e S4 e termina. Se il segnale S3 è presente al secondo istante, il programma emette solamente S4 e termina.

Interruzione debole

Sintassi

weak abort <STATEMENT> when <NAME>

Semantica

L'istruzione abort esegue il suo corpo. Tale esecuzione è interrotta solo alla fine della sua reazione quando compare il segnale d'interruzione.
L'istruzione termina quando il suo corpo termina o quando compare il segnale d'interruzione.

Esempio

weak abort
  emit S1;
  pause;
  emit S2
when S3;
emit S4
Al primo istante viene emesso il segnale S1 indipendentemente dalla presenza o meno di S3: l'attesa di S3 non è attivata.
Se il segnale S3 non era presente al primo istante, al secondo istante il programma emette S2 e S4 e termina indipendentemente dalla presenza o meno di S3.

Interruzione debole immediata

Sintassi

weak abort <STATEMENT> when immediate <NAME>

Semantica

L'istruzione abort esegue il suo corpo. Tale esecuzione è interrotta solo alla fine della sua reazione quando compare il segnale d'interruzione.
L'istruzione termina quando il suo corpo termina o quando compare il segnale d'interruzione.

L'attesa sul segnale di interruzione si fa subito.

Esempio

weak abort
  emit S1;
  pause;
  emit S2
when immediate S3;
emit S4
Al 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.
Se il segnale S3 non era presente al primo istante, al secondo istante il programma emette S2 e S4 e termina indipendentemente dalla presenza o meno di S3.

Interruzione catturata

Sintassi

abort
    <STATEMENT>
when immediate <NAME>
    do <STATEMENT>
end abort

Semantica

L'istruzione abort esegue il suo corpo. Tale esecuzione è interrotta subito quando compare il segnale d'interruzione. In tal caso e solo in tal caso viene eseguito il corpo del do.
L'istruzione termina quando il suo corpo termina o quando compare il segnale d'interruzione ed il corpo del do termina.

Un'interruzione è catturata solo se il corpo dell'istruzione abort non termina nell'istante.

Esempio

abort
  emit S1;
  pause;
  emit S2
when S3
  do emit S4
end abort;
emit S5
Al primo istante viene emesso il segnale S1, indipendentemente dalla presenza o meno del segnale S3: l'attesa su S3 inizierà solo all'istante successivo.
Al secondo istante, se il segnale S3 non è presente, il programma emette S2 e S5 e termina. Se il segnale è presente, il programma emette solo S4 e S5 e termina.
abort
  pause;
when S
  do emit S1
end abort
Il segnale S1 non sarà mai emesso.

Ciclo

Sintassi

loop
    <STATEMENT>
end loop

Semantica

L'istruzione loop esegue il suo corpo ciclicamente. Non termina mai.

Il corpo di un loop non può terminare subito: sarebbe un ciclo infinito istantaneo.

Esempi

loop
  emit S1;
  pause;
end loop
Il programma emette il segnale S1 ad ogni istante.
loop
  emit S1;
  pause;
  pause;
end loop
Il programma emette il segnale S1 ogni due istanti.
loop
  emit S1
end loop
Il programma è un ciclo infinito ed è rifiutato dal compilatore Esterel.

Attesa

Sintassi

await <NAME>

Semantica

L'istruzione await permette di aspettare l'occorrenza di un segnale.

L'attesa comincia all'istante successivo.

await S
è equivalente a
abort
  halt
when S

Attesa immediata

Sintassi

await immediate <NAME>

Semantica

L'istruzione await permette di aspettare l'occorrenza di un segnale.

L'attesa comincia subito.

await immediate S
è equivalente a
abort
  halt
when immediate S

Emissione continua

Sintassi

sustain <NAME>

Semantica

L'istruzione sustain emette un segnale ad ogni istante. L'istruzione non termina mai.
sustain S
è equivalente a
loop 
  emit S;
  pause;
end loop

Interruzione bloccante

Sintassi

do
  <STATEMENT>
upto <NAME>

Semantica

L'istruzione do upto permette di eseguire il corpo in attesa di un'interruzione. L'istruzione finisce solo quando compare l'interruzione.
do 
  P
upto S
è equivalente a
abort
  P;
  halt
when S

Ciclo iterativo

Sintassi

loop
  <STATEMENT>
each <NAME>

Semantica

L'istruzione loop each permette di eseguire il corpo ciclicamente. Tuttavia, si inizia un nuovo ciclo solo se compare il segnale di ciclo.
loop
  P
each S
è equivalente a
loop
  abort
    P;
    halt
  when S
end loop

Iterazione

Sintassi

every <NAME> do
  <STATEMENT>
end every

Semantica

L'istruzione every permette di eseguire il corpo ad ogni occorrenza di un segnale. L'attesa comincia all'istante successivo.
every S do
  P
end every
è equivalente a
await S;
loop
  P
each S

Iterazione immediata

Sintassi

every immediate <NAME> do
  <STATEMENT>
end every

Semantica

L'istruzione every permette di eseguire il corpo ad ogni occorrenza di un segnale. L'attesa è immediata.
every immediate S do
  P
end every
è equivalente a
await immediate S;
loop
  P
each S

Ripetizione

Invece dell'occorrenza di un segnale, si può aspettare un numero fisso di occorrenze:
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

Eccezioni

Sintassi

Per definire una eccezione:

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

Semantica

L'istruzione trap permette di definire una eccezione locale. Quando dentro il corpo viene sollevata l'eccezione, il corpo finisce la sua reazione e termina. Se l'eccezione non è catturata, l'istruzione termina. Se l'eccezione è catturata con un handle, il corpo di handle è eseguito immediatamente.

Quando sono sollevate piú eccezioni, ha precedenza l'eccezione che è stata dichiarata per prima.

Esempi

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

Segnale locale

Sintassi

signal <NAME> in
    <STATEMENT>
end signal

Semantica

L'istruzione dichiara un segnale locale. L'istruzione termina quando il suo corpo termina.

Esempi

loop
  signal S in
    present S then emit O end present
    pause;
    emit S
  end signal
end loop
Il 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

Variabile locale

Sintassi

var <NAME> : <NAME> in
    <STATEMENT>
end var

Semantica

L'istruzione dichiara una variabile locale. L'istruzione termina quando il suo corpo termina. Il tipo della variabile può essere un tipo di base: boolean, integer, float, double, o un tipo astratto.

Esempi

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

Assegnazione

Sintassi

<NAME> := <EXPRESSION>

Semantica

Assegna un valore ad una variabile. Il valore è definito tramite le funzioni di base (+, *, AND ...) o funzioni astratte.

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 := 1
Non è permesso: alla fine dell'istruzione parallela non si sa se il valore di X è 0 oppure 1.

Esempi

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

Segnale con valore

Sintassi

emit <NAME>(<EXPRESSION>)

Semantica

Un segnale può avere un valore (integer, bool, ...). Per sapere il valore di un segnale si usa l'espressione:
?<NAME>
che dà il valore del segnale l'ultima volta che il segnale è stato emesso incluso l'istante presente.

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).

Modulo

La struttura di base di Esterel è il modulo. Il modulo permette di definire un programma reattivo che può essere riutilizzato successivamente.

Sintassi

module <NAME>:

<DECLARATION>

<STATEMENT>

end module

Dichiarazioni di segnali

Nelle dichiarazioni di un modulo, bisogna dichiarare i segnali. Tale segnali possono essere dei segnali ricevuti (INPUT), emessi (OUTPUT), o dei segnali emessi e ricevuti (INPUTOUTPUT).

Sintassi

input <NAME>, ..., <NAME>;

output <NAME>, ..., <NAME>;

inputoutput <NAME>, ..., <NAME>;

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.

Esempi

Ogni volta che viene emesso il segnale I, il programma seguente emette un segnale Count, il cui valore è il numero di volte che il segnale I è stato emesso:
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 module
Il 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

Dichiarazioni di relazioni

In Esterel si può esprimere nelle dichiarazioni la relazione fra i segnali d'input. Tale relazione permette di escludere comportamenti che non sono possibili ed aiutano il compilatore a generare codice più efficiente. Ci sono due tipi di relazione, la relazione d'esclusione (#) e la relazione d'implicazione (=>).

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.

Sintassi

relation <NAME> # ... # <NAME>;

relation <NAME> => <NAME>;

Esempio

Si può scrivere un programma che calcola la velocità usando un segnale Centimeter che è emesso ogni volta che viene percorso un centimetro, ed un segnale Second che viene emesso ogni volta che è passato un secondo. La velocità è indicata dal segnale Speed, il cui valore è un numero intero che rappresenta il numero di centimetri al secondo.
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

Tipi e funzioni astratti

In Esterel i tipi di base predefiniti sono: boolean, integer, float, double. Su tali tipi si possono usare le funzioni usuali: +, -, *, AND, OR.

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>

Esempio

L'esempio precedente che calcola una velocità può essere reso piú astratto usando un tipo astratto T:
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

Riutilizzazione

Una volta che un modulo è stato definito, si può riutilizzarlo usando il commando run:
run <NAME>

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.

Esempio

Riutilizzando il modulo generico che calcola la velocità, si può ottenere il modulo iniziale che manipola i numeri interi facendo:
run GENERIC_SPEED [
  type integer/T;
  constant 0/Initial, 1/Increment;
  function +/Add;
  signal Centimeter/A, Second/B;
 ]

Causalità

Il principio di Esterel è che la reazione di un programma è istantanea e deterministica. Tale principio implica che per dare un senso alla reazione bisogna assicurarsi che il programma si stabilizza e che questa stabilizzazione è unica.

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 
end
Non 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
end
Per 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.

Esempi

Cronometro

L'obiettivo è quello di scrivere il controllore di un cronometro. Si comincia con un cronometro base per poi successivamente rendere il suo comportamento piú complesso.

Cronometro base

Un cronometro base ha solo un pulsante START_STOP che permette alternativamente di lanciare e fermare il tempo. Il controllore si basa su un segnale HS (1/100 di secondi) per calcolare il suo tempo TIME. Il comportamento di tale cronometro può essere descritto come segue:
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

Ri-inizializzazione

Al cronometro precedente si può aggiungere un pulsante RESET che rimette il tempo a zero:
module STOPWATCH_1:
input START_STOP, HS, RESET;
output TIME: integer;
loop
  run BASIC_STOPWATCH
each RESET
end module

Tempo intermedio

Un altro pulsante che si può aggiungere è quello che permette di avere il tempo intermedio LAP. Premere una seconda volta il pulsante permette di riavviare il tempo reale. Tale comportamento si implementa in Esterel come un filtro che lascia passare o no il tempo calcolato dal cronometro precedente:
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.

Interprete

Un vero cronometro ha solo un pulsante BUTTON_2 per la ri-inizializzazione e il tempo intermedio. Se il cronometro sta avanzando, premere sul pulsante è interpretato come la richiesta di un tempo intermedio LAP. Lo stesso vale se il cronometro mostra un tempo intermedio. L'unica situazione dove premere il pulsante è interpretato come un RESET è quando il cronometro è fermo e non sta mostrando un tempo intermedio.

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 module
Tale 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 module
La 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

Cronometro completo

Per avere il cronometro completo si mette semplicemente in parallelo il modulo STOPWATCH_2 e l'interprete.
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

Gioco di riflesso

L'obiettivo è quello di scrivere il controllore di una macchina che permette ad un giocatore di valutare il suo tempo di reazione.

Specifica

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

In tali casi, le lampadine RED e GAME_OVER sono accese ed il gioco è finito.

Il giocatore è avvertito con la campanella RING_BELL quando

Inserire una moneta COIN in un qualsiasi momento fa cominciare un nuovo gioco.

Interfacce

L'interfaccia del modulo che gestisce il 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);

Tempo medio

Per calcolare il tempo medio, abbiamo un modulo che reagisce ad ogni nuova misura (UPDATE_VALUE) emettendo la nuova media (AVERAGE_VALUE):
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

Aspettare READY

Per cominciare una misura, bisogna aspettare che il giocatore prema sul pulsante READY ma non si può aspettare piú di TIME_LIMIT millisecondi, altrimenti c'è un ERROR. Infine, bisogna avvertire quando il giocatore sbaglia premendo STOP invece di READY:
abort 
  abort
    every STOP do emit RING_BELL end
  when READY
when TIME_LIMIT MS do
  exit ERROR
end abort

Accendere la lampadina GO

Dopo che il giocatore ha premuto READY, la macchina aspetta un po' e accende la lampadina GO. Prima del GO, il giocatore non può premere altrimenti c'è un errore. Se il giocatore preme il READY, è avvertito del suo sbaglio con la campanella RING_BELL:
abort 
  abort
    every READY do emit RING_BELL end
  when RANDOM() MS;
  emit GO_ON
when STOP do
  exit ERROR
end abort

Aspettare STOP

Dopo che la lampadina GO è stata accesa, la macchina calcola il tempo che il giocatore impiega a premere il pulsante STOP. Non si può aspettare piú di TIME_LIMIT millisecondi e se il giocatore preme READY invece di STOP è avvertito del suo sbaglio con la campanella RING_BELL:
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

Il gioco completo

Adesso si può dare il gioco completo:
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

Esercizi

G1*. Scrivere un programma che gestisce l'apertura di una porta controllata da un lettore di carta con codice.
Inizialmente il programma aspetta che sia inserita una carta (INSERT_CARD).
Dopo l'inserzione, il codice della carta è digitato cifra dopo cifra (DIGIT) da sinistra a destra.
Il codice è validato premendo il pulsante ENTER.
Una volta validato il codice, il programma richiede la verifica del codice (CHECK_CODE) ad un programma esterno.
Il segnale CHECK_CODE ha per valore un numero intero uguale al codice che è stato digitato cifra dopo cifra.
Quando il codice è stato verificato (VALID_CODE), il programma apre la porta (OPEN_DOOR), restituisce la carta (RETURN_CARD) e aspetta di nuovo una carta.
In caso di errore quando si digita il codice, si può premere il pulsante RESET e digitare di nuovo il codice da capo.
Una volta inserita una carta, si può riottenerla (RETURN_CARD) in ogni momento premendo il pulsante QUIT. Una volta restituita la carta, il programma ne aspetta un'altra.
Quando dopo una richiesta di verifica del codice (CHECK_CODE), la verifica è negativa (INVALID_CODE), si può riprovare a digitare il codice.
Dopo tre tentativi falliti consecutivi di verifica del codice (INVALID_CODE), il programma conserva la carta (KEEP_CARD) e ne aspetta un'altra.
Una carta non può rimanere dentro il lettore per piú di un minuto (60 SECOND). Se ciò accade, il programma restituisce la carta e ne aspetta un'altra.

G2*. Scrivere un programma che gestisce il telecomando di un televisore. Il telecomando ha due modi:

Ogni volta che è premuto il pulsante ON_OFF, il telecomando emette un segnale TURN_ON_OFF per accendere o spengere il televisore e si mette in modo televisione. In modo televisione: Si passa da un modo all'altro premendo il pulsante TXT_ON_OFF.

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).


Laurent Théry
Last modified: Tue Mar 16 02:54:56 MET 2004