Software systems are often required to satisfy one or more safety properties. With synchronous reactive systems, a safety property is, essentially, one that is true of the system at all instants. When developing such systems in a language like Lustre, a convenient way to specify safety properties is to use synchronous observers.
These are programs that observe the input and output streams of the program S to be verified, and have a Boolean output stream for each safety property specified for S. The observers are defined so that, for each output stream, the output value is true at any point in the stream if, and only if, the corresponding safety property holds for S at that point.
Note that synchronous observers are not limited to safety properties. For instance, they can be used to check more complex properties such as the equivalence of two programs. They are explained nicely in the first 2 sections of [Halb99].
In class we have seen a few examples of synchronous observer programs:
Rising edge and
falling edge examples,
- Three boolean switches with observers
- The traffic light examples
When writing synchronous observers the following in temporal operators are often useful.
- First(X) is a constant stream which has the value that X had initially.
- Sofar(X) is true at any point iff X has been true from the beginning until that point.
- Since(X,Y) is true precisely when X has been true at some point, and Y has been continuously true afterwards
- SinceIncl(X,Y) is true iff X has been true at some point, and Y has been continuously true from then on.
Typical usage patterns are:
- We use "Sofar( Env ) => A" if we want to state that A should be true
if Env has been always true until then. Very often, Env states a condition that we assume
about the environment that is guaranteed never to be broken.
We use "Since( X, Y ) => A" if we want to state that A holds whenever condition Y
has been true since the last time X has been true. In other words, whenever X
happens, and Y is true from that point on, then A should be true too.
- We can define an arbitrary but constant stream a, by saying "a = First( A )", and letting A be an extra input to our observer. This allows us to relate values of two different streams in different points in time. (We use the convention that stream-names starting with a lower-case letter denote constant streams.)
The following additional examples makes use of those operators: Integer switch.
From natural language to propositional logic to Lustre
When formalizing a safety property, the following table may be helpful in translating from natural language.
A and B
A but B
|A ∧ B||A and B|
A if B
A when B
A whenever B
|B ⇒ A||B => A|
|if A, then B
A implies B
A forces B
|A ⇒ B||A => B|
only if A, B
B only if A
|B ⇒ A||B => A|
|A precisely when B
A if and only if B
B ⇔ A
A ⇔ B
B = A
A = B
|either A or B
A or B but not both
|A ⊕ B
("exclusive or", "xor")
|A or B
A unless B
|A ∨ B
|A or B|