CIS 542 Embedded Systems Programming – Summer 2013 Lecture ...
CIS 542 Embedded Systems Programming – Summer 2013 Lecture ...
CIS 542 Embedded Systems Programming – Summer 2013 Lecture ...
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!