28.02.2014 Views

CIS 542 Embedded Systems Programming – Summer 2013 Lecture ...

CIS 542 Embedded Systems Programming – Summer 2013 Lecture ...

CIS 542 Embedded Systems Programming – Summer 2013 Lecture ...

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

Model-Driven Development<br />

<strong>CIS</strong> <strong>542</strong> <strong>Embedded</strong> <strong>Systems</strong> <strong>Programming</strong> <strong>–</strong> <strong>Summer</strong> <strong>2013</strong><br />

<strong>Lecture</strong> #3: Model-Driven Development and Verification<br />

<strong>Embedded</strong> systems typically involve some sort of closed-loop control cycle in which the software (or<br />

“controller”) gets input from the outside world (referred to as the “plant”), decides on some action to<br />

take based on the current state and the input, and then sends some sort of control signal as the output.<br />

The software can be modeled using a state machine that represents each state the software/system can<br />

be in, what causes it to transition to another state, and what is the output when it does so.<br />

The model (state machine) is usually created before the software is written so that any potential<br />

problems can be identified early on: it is well known that, the earlier in the software development life<br />

cycle that problems are detected, the easier/cheaper it is to fix them.<br />

We can use a finite state machine (FSM) to represent the model of our software controller. An FSM is<br />

a graph in which:<br />

• nodes/vertices represent states in the system<br />

• edges represent transitions between states<br />

An extended finite state machine (EFSM) is an FSM that allows for boolean trigger conditions for the<br />

transitions.<br />

As a running example, consider a home security system described as follows:<br />

• when the system is armed, if motion is detected by the sensor, the alarm will sound<br />

• when the alarm is sounding, it can be turned off it the user correctly enters the password, but the<br />

system will still be armed; if the password is entered incorrectly, the alarm will continue to<br />

sound<br />

• the user can disarm the system by entering the password correctly; if the password is entered<br />

incorrectly, the alarm will sound<br />

• once the system is disarmed, the user can choose to arm it<br />

• regardless of whether the system is armed or disarmed, the user can cause the alarm to sound by<br />

using a panic button<br />

We can create an EFSM to model this system like so:


disarmed<br />

disarm &&<br />

input ==<br />

password<br />

arm<br />

armed<br />

panic<br />

disarm &&<br />

input !=<br />

password<br />

panic<br />

motion<br />

alarm<br />

sounding<br />

quiet &&<br />

input == password


The EFSM allows us to verify some desirable properties of this system. For instance:<br />

• it is always possible to sound the alarm (we know this is true because there is a path from each<br />

state to the “alarm sounding” state)<br />

• it is always possible to disarm the system (there are paths from all states to “disarmed” too,<br />

though they require the input to be equal to the password)<br />

These are referred to as liveness properties: desirable things that can always (eventually) happen.<br />

Automata-Based <strong>Programming</strong><br />

How would you implement the above EFSM in code? If you don't think it through, this can easily turn<br />

into an unwieldy mess of if/else statements (just look at the original specification!).<br />

A simpler approach, known as automata-based programming, is to use enums to represent the<br />

different possible states and signals, and then simply use switch statements to first figure out which<br />

state you're in, and then which signal you've received, and then act accordingly.<br />

For instance:<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

10<br />

11<br />

12<br />

13<br />

14<br />

15<br />

16<br />

17<br />

18<br />

19<br />

20<br />

21<br />

22<br />

23<br />

24<br />

25<br />

26<br />

27<br />

28<br />

29<br />

30<br />

31<br />

enum states {disarmed, armed, alarm_sounding} state;<br />

enum signals {arm, disarm, panic, motion, quiet};<br />

void handle_signal (enum signals signal) {<br />

switch (state) {<br />

case disarmed:<br />

switch (signal) {<br />

case arm:<br />

state = armed;<br />

break;<br />

case panic:<br />

state = alarm_sounding;<br />

break;<br />

}<br />

break;<br />

case armed:<br />

switch (signal) {<br />

case panic:<br />

state = alarm_sounding;<br />

break;<br />

case motion:<br />

state = alarm_sounding;<br />

break;<br />

case disarm:<br />

if (password_correct()) state = disarmed;<br />

else state = alarm_sounding;<br />

break;<br />

}


32<br />

33<br />

34<br />

35<br />

36<br />

37<br />

38<br />

39<br />

40<br />

41<br />

42<br />

}<br />

break;<br />

case alarm_sounding:<br />

switch (signal) {<br />

case quiet:<br />

if (password_correct()) state = armed;<br />

break;<br />

}<br />

break;<br />

}<br />

Line 1 enumerates all the possible states we could be in, and defines a global variable called state.<br />

Line 2 enumerates all the possible signals we could receive.<br />

Line 4 is the beginning of the function that would represent our controller. We can imagine that another<br />

function gets information from the outside world (either through a sensor or some input device) and<br />

then calls this function to update the state and to take whatever action is necessary.<br />

Line 6 is the start of a switch statement that runs the entire length of the function. It is used to figure<br />

out what to do for the state we're currently in.<br />

For instance, lines 8-17 handle the case where we're in the “disarmed” state. Now we switch on the<br />

signal we've received (line 9). If the signal is “arm” (line 10), we move to the “armed” state (line 11). If<br />

the signal is “panic” (line 13), we move to the “alarm_sounding” state (line 14). For any other signal,<br />

we do nothing, as shown in the EFSM.<br />

Note that, for the “disarmed” state, the number of cases in its switch statement (lines 9-16) is the same<br />

as the number of edges exiting that state in the EFSM. This makes it easy to check that you've included<br />

all transitions and successfully matched the code to the EFSM.<br />

But how do we know it correctly implements the EFSM as drawn above? That is, how do we know that<br />

the transitions it makes are the right ones?<br />

This question can be answered through software testing: we come up with inputs (signals) and, using<br />

the EFSM as our guide, make sure we're always in the state we expect to be in.<br />

If we think of the EFSM as a graph, obviously there are an infinite number of paths through it (because<br />

of the loops), so we can't test all of them. However, we don't need to: the assumption is that it doesn't<br />

matter how we got to a certain state, all that matters is that, when we're in that state, we make the<br />

correct transition for the signal we receive.<br />

We can now create test cases in which we set the state that we want to be in, call “handle_signal” with<br />

the appropriate signal, and then check which state we transitioned to.<br />

state = armed;


handle_signal(motion);<br />

if (state != alarm_sounding) printf(“Test case failed!\n”);<br />

Note that for m states and n signals, we should have mn test cases: one for each combination. So, to<br />

really cover all the possibilities, we should also check to see what happens when we receive signals that<br />

should not change the state:<br />

state = disarmed;<br />

handle_signal(motion);<br />

if (state != disarmed) printf(“Test case failed!\n”);<br />

Now that we have this simple structure in place, we could add function calls to things like sound_alarm<br />

(e.g. between lines 14 and 15), which would either send output signals or take some other action. For<br />

instance (additional changes in bold):<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

10<br />

11<br />

12<br />

13<br />

14<br />

15<br />

16<br />

17<br />

18<br />

19<br />

20<br />

21<br />

22<br />

23<br />

24<br />

25<br />

26<br />

27<br />

28<br />

29<br />

30<br />

31<br />

32<br />

enum states {disarmed, armed, alarm_sounding} state;<br />

enum signals {arm, disarm, panic, motion, quiet};<br />

void handle_signal (enum signals signal) {<br />

switch (state) {<br />

case disarmed:<br />

switch (signal) {<br />

case arm:<br />

state = armed;<br />

arm_system();<br />

break;<br />

case panic:<br />

state = alarm_sounding;<br />

sound_alarm();<br />

break;<br />

}<br />

break;<br />

case armed:<br />

switch (signal) {<br />

case panic:<br />

state = alarm_sounding;<br />

sound_alarm();<br />

break;<br />

case motion:<br />

state = alarm_sounding;<br />

sound_alarm();<br />

break;<br />

case disarm:<br />

if (password_correct()) {


33<br />

34<br />

35<br />

36<br />

37<br />

38<br />

39<br />

40<br />

41<br />

42<br />

43<br />

44<br />

45<br />

46<br />

47<br />

48<br />

49<br />

50<br />

51<br />

52<br />

53<br />

54<br />

55<br />

}<br />

state = disarmed;<br />

disarm_system();<br />

}<br />

else {<br />

state = alarm_sounding;<br />

sound_alarm();<br />

}<br />

break;<br />

}<br />

break;<br />

case alarm_sounding:<br />

switch (signal) {<br />

case quiet:<br />

if (password_correct()) {<br />

state = armed;<br />

arm_system();<br />

}<br />

break;<br />

}<br />

break;<br />

}<br />

Alternatively, we could make “state = armed” the first statement in the “arm_system” function, but you<br />

could conceivably have a situation in which a function is called in two different states, so you<br />

shouldn't bind the state and the functionality too much.<br />

Testing<br />

Let's look at another example. It's the cruise control system for a car. Here's the specification:<br />

• if the driver chooses to accelerate, the speed increases by 10, unless that would put the speed<br />

over 100, in which case it doesn't change<br />

• if the driver chooses to break, the speed decreases by 10; if that would put the speed below 0,<br />

the speed becomes 0<br />

• if the speed is 0, the car stops


Here's the code for the controller:<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

10<br />

11<br />

12<br />

13<br />

14<br />

15<br />

16<br />

17<br />

18<br />

19<br />

20<br />

21<br />

22<br />

23<br />

24<br />

25<br />

26<br />

27<br />

28<br />

29<br />

30<br />

31<br />

32<br />

33<br />

34<br />

35<br />

36<br />

37<br />

38<br />

39<br />

40<br />

41<br />

enum states {moving, stopped};<br />

enum events {accel, brake};<br />

int control(int speed, enum events event, enum states *state) {<br />

}<br />

int new_speed;<br />

switch (*state) {<br />

case moving:<br />

switch(event) {<br />

case accel:<br />

if (speed


We've already seen how to make sure that it progresses to the right state, but now we also want to<br />

check that it correctly updates the speed.<br />

Obviously, the solution here is testing. Testing is the activity of giving input to the code to see how it<br />

behaves. The goal of testing is not to show that it is correct, but rather to show that there are bugs (or<br />

faults, as we say).<br />

Broadly speaking, there are two ways to create test cases for a piece of code:<br />

• Black-box testing: look at the specification and come up with inputs that exercise as much of<br />

the spec as possible, i.e. try all the different conditions that are described<br />

• White-box testing: look at the code and come up with inputs that exercise as many<br />

statements/paths/branches as possible, i.e. try all the code<br />

Let's start with black-box testing. Consider this part of the spec:<br />

“if the driver chooses to accelerate, the speed increases by 10, unless that would put the speed over<br />

100, in which case it doesn't change”<br />

There are two conditions here that we'd want to check:<br />

1. the driver chooses to accelerate, but that doesn't put the speed over 100<br />

2. the driver chooses to accelerate, and that does put the speed over 100<br />

For each of these, we make the assumption that if it works correctly for some input, it will also work<br />

correctly for “similar” inputs. This is known as equivalence partitioning. For instance, if we decide to<br />

use 20 as the speed for #1, and it works correctly, we probably don't need to also test 21, 22, 30, 40, 80,<br />

etc.<br />

So we can now create some test code like this:<br />

/* this tests accelerating but not going over 100 */<br />

int curr_speed = 20;<br />

enum events event = accel;<br />

enum states curr_state = moving;<br />

int new_speed = control(curr_speed, event, &curr_state);<br />

if (new_speed != 30)<br />

printf(“control returned wrong speed!\n”);<br />

if (curr_state != moving)<br />

printf(“control returned wrong state!\n”);<br />

/* this tests accelerating but trying to go over 100 */<br />

curr_speed = 95;<br />

event = accel;<br />

curr_state = moving;<br />

new_speed = control(curr_speed, event, &curr_state);<br />

if (new_speed != 95)<br />

printf(“control returned wrong speed!\n”);<br />

if (curr_state != moving)<br />

printf(“control returned wrong state!\n”);


Because programmers often make off-by-one errors, we also want to include test cases that what<br />

happens when values are very close to the boundary between different parts of the spec. These are<br />

known as boundary conditions. In this case, it would be if we try to accelerate and the speed becomes<br />

exactly 100.<br />

/* this tests accelerating but and going exactly 100 */<br />

curr_speed = 90;<br />

event = accel;<br />

curr_state = moving;<br />

new_speed = control(curr_speed, event, &curr_state);<br />

if (new_speed != 100)<br />

printf(“control returned wrong speed!\n”);<br />

if (curr_state != moving)<br />

printf(“control returned wrong state!\n”);<br />

What about white-box testing? In this approach to identifying test cases, we pick a particular statement,<br />

path, or branch, and then come up with inputs that cause it to be executed.<br />

For instance, if we wanted to cover the statement (line 20) in which we switch the state to “stopped”,<br />

then we'd have to figure out the inputs that would get us there. Clearly, we need:<br />

• *state == moving, so that we go to line 9 in the outer switch statement<br />

• event == brake, so that we go to line 16 in the inner switch statement<br />

• new_speed


A tool you can use to measure coverage is called “gcov”. To use gcov, run the following:<br />

gcc -fprofile-arcs -ftest-coverage myprog.c -o myprog<br />

./myprog (this creates myprog.gcda/data and myprog.gcno/graph)<br />

gcov myprog.c (this creates myprog.c.gcov)<br />

gcov -b myprog.c will also give branch coverage info<br />

Verification<br />

Testing is nice and easy but, as has been famously pointed out, “testing can only prove the existence of<br />

bugs, not their absence.” This is because, when we test, we pick representative inputs but we don't<br />

choose all possible inputs and/or all possible behaviors.<br />

It would be better to actually prove certain properties of the code. This is known as verification. In the<br />

systems we're interested in here, these are often referred to as safety properties: we want to be able to<br />

prove that there are certain bad things that can never happen.<br />

For instance, in this case we may want to prove that the speed is always less than or equal to 100. This<br />

can be done using a technique called model checking: we want to show that our code adheres to this<br />

“model of correctness”.<br />

Here is a piece of code that would use our control method:<br />

1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

10<br />

11<br />

12<br />

13<br />

14<br />

15<br />

16<br />

17<br />

18<br />

19<br />

drive_car() {<br />

}<br />

int speed;<br />

enum states state;<br />

while (1) { // keep looping and waiting for signal<br />

}<br />

// assume this is our interface with the hardware<br />

int signal = get_signal_from_driver();<br />

enum events event;<br />

if (signal == 0) event = brake;<br />

else if (signal == 1) event = accel;<br />

else continue; // illegal signal<br />

speed = control(speed, event, &state);<br />

To prove that the speed is always less than equal to 100, we can use a tool called BLAST. When using<br />

this tool, we attempt to show that the property can never be violated: that is, that it is impossible for the<br />

speed to go over 100.<br />

Here is an updated version of the code that could be used with BLAST:


1<br />

2<br />

3<br />

4<br />

5<br />

6<br />

7<br />

8<br />

9<br />

10<br />

11<br />

12<br />

13<br />

14<br />

15<br />

16<br />

17<br />

18<br />

19<br />

20<br />

21<br />

22<br />

23<br />

drive_car() {<br />

}<br />

int speed;<br />

enum states state;<br />

while (1) { // keep looping and waiting for signal<br />

}<br />

// assume this is our interface with the hardware<br />

int signal = get_signal_from_driver();<br />

enum events event;<br />

if (signal == 0) event = brake;<br />

else if (signal == 1) event = accel;<br />

else continue; // illegal signal<br />

speed = control(speed, event, &state);<br />

if (speed > 100) {<br />

// oh no!<br />

goto ERROR;<br />

ERROR: break;<br />

}<br />

What's going on here? We are going to try to use BLAST to show that the line labelled “ERROR” (line<br />

21) is unreachable. That would prove that line 18 can never be true, which means that speed can never<br />

be more than 100. We need the goto statement on line 20 because if there is no reference to the label,<br />

the compiler will take it out.<br />

When you run BLAST, it will tell you one of three things:<br />

• the line is reachable: the program is unsafe<br />

• the line is not reachable: the program is safe<br />

• BLAST can't figure out whether it's reachable: so it assumes the program is unsafe<br />

If you use BLAST on this code, you'll see that it says that the program is unsafe! Why is that?<br />

The reason is that we have not initialized speed and state on lines 3 and 4. Because they are stack<br />

variables, they will inherit the values that are there when this function is invoked. And if speed just so<br />

happens to be over than 100, and state just so happens to be 1 (which is “stopped”), and the event is<br />

brake, then we'll end up on line 33 of the control function, in which we return the same speed, which<br />

would be over 100. Oops!<br />

Note that verifying that the speed is never over 100 is not the same as “proving correctness” in this<br />

case. After all, if we just had our control function always return 8, this property would be satisfied.<br />

That's why we need test cases, too!

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!