@@ -17,7 +17,7 @@ We assume that CBMC is installed on your system. If not so, follow
17
17
<a href="installation-cbmc.shtml">these instructions</a>.</p>
18
18
19
19
<p class="justified">
20
- CBMC can be used to automatically generate test cases following a certain <a
20
+ CBMC can be used to automatically generate test cases that satisfy a certain <a
21
21
href="https://en.wikipedia.org/wiki/Code_coverage">code coverage</a>
22
22
criterion. Common coverage criteria include branch coverage, condition
23
23
coverage and <a
@@ -32,135 +32,168 @@ in a way that affects the outcome of the decision.
32
32
33
33
<p class="justified">
34
34
In the following, we are going to demonstrate how to apply the test suite
35
- generation functionality in CBMC, by means of a case study. Even though it
36
- is simple, the following program (<a href="pid.c">pid.c</a>) is
37
- excerpted from a real-time embedded benchmark <a
35
+ generation functionality in CBMC, by means of a case study. The program
36
+ <a href="pid.c">pid.c</a> is an excerpt from a real-time embedded benchmark <a
38
37
href="https://www.irit.fr/recherches/ARCHI/MARCH/rubrique.php3?id_rubrique=97">PapaBench</a>,
39
- which is an application for autopilot/fly-by-wire and is developed to be
40
- embedded on Unmanned Aerial Vehicles (UAVs). It is adjusted mildly for our
41
- purposes.
38
+ and implements part of a fly-by-wire autopilot for an Unmanned Aerial Vehicle (UAV).
39
+ It is adjusted mildly for our purposes.
42
40
</p>
43
41
44
- <pre><code class="c numbered">01: // CONSTANTS:
45
- 02: #define MAX_CLIMB_SUM_ERR 150
46
- 03: #define MAX_CLIMB 1
47
- 04:
48
- 05: #define CLOCK 16
49
- 06: #define MAX_PPRZ (CLOCK*600)
50
- 07:
51
- 08: #define CLIMB_LEVEL_GAZ 0.31
52
- 09: #define CLIMB_GAZ_OF_CLIMB 0.2
53
- 10: #define CLIMB_PITCH_OF_VZ_PGAIN 0.05
54
- 11: #define CLIMB_PGAIN -0.03
55
- 12: #define CLIMB_IGAIN 0.1
56
- 13:
57
- 14: const float pitch_of_vz_pgain=CLIMB_PITCH_OF_VZ_PGAIN;
58
- 15: const float climb_pgain=CLIMB_PGAIN;
59
- 16: const float climb_igain=CLIMB_IGAIN;
60
- 17: const float nav_pitch=0;
61
- 18:
62
- 19: // OUTPUTS:
63
- 20: float desired_gaz;
64
- 21: float desired_pitch;
65
- 22: float climb_sum_err;
66
- 23:
67
- 24: /** Computes desired_gaz and desired_pitch */
68
- 25: void climb_pid_run (float desired_climb, float estimator_z_dot, float old_climb_sum_err) {
69
- 26: /** Range of inputs */
70
- 27: __CPROVER_assume(desired_climb>=-MAX_CLIMB && desired_climb<=MAX_CLIMB);
71
- 28: __CPROVER_assume(estimator_z_dot>=-MAX_CLIMB && estimator_z_dot<=MAX_CLIMB);
72
- 29: __CPROVER_assume(old_climb_sum_err>=-MAX_CLIMB_SUM_ERR && old_climb_sum_err<=MAX_CLIMB_SUM_ERR);
73
- 30:
74
- 31: float err=estimator_z_dot-desired_climb;
75
- 32:
76
- 33: float fgaz=climb_pgain*(err+climb_igain*old_climb_sum_err)+CLIMB_LEVEL_GAZ+CLIMB_GAZ_OF_CLIMB*desired_climb;
77
- 34:
78
- 35: float pprz=fgaz*MAX_PPRZ;
79
- 36: desired_gaz=((pprz>=0 && pprz<=MAX_PPRZ) ? pprz : (pprz>MAX_PPRZ ? MAX_PPRZ : 0));
80
- 37:
81
- 38: /** pitch offset for climb */
82
- 39: float pitch_of_vz=(desired_climb>0) ? desired_climb*pitch_of_vz_pgain : 0;
83
- 40: desired_pitch=nav_pitch+pitch_of_vz;
84
- 41:
85
- 42: climb_sum_err=err+old_climb_sum_err;
86
- 43: if (climb_sum_err>MAX_CLIMB_SUM_ERR) climb_sum_err=MAX_CLIMB_SUM_ERR;
87
- 44: if (climb_sum_err<-MAX_CLIMB_SUM_ERR) climb_sum_err=-MAX_CLIMB_SUM_ERR;
88
- 45:
89
- 46: }
90
- </code></pre>
91
-
92
42
<p class="justified">
93
- The function <code>climb_pid_run</code> is part of the PID control
94
- procedure. Its input arguments <code>desired_climb</code> and
95
- <code>estimator_z_dot</code> represent respectively the desired and
96
- estimated speed in meters per second in the vertical direction;
97
- <code>old_climb_sum_err</code> is the current accumulated error between
98
- <code>desired_climb</code> and <code>estimator_z_dot</code>. Given inputs,
99
- the function then computes the <code>desired_gaz</code> and
100
- <code>desired_pitch</code> parameters, and updates the accumulated
101
- <code>climb_sum_err</code>.
43
+ The aim of function <code>climb_pid_run</code> is to control the vertical climb of the UAV.
44
+ Details on the theory behind this operation are documented in the <a
45
+ href="https://wiki.paparazziuav.org/wiki/Theory_of_Operation">wiki</a> for the Paparazzi UAV project.
46
+ The behaviour of this simple controller, supposing that the desired speed is 0.5 meters per second,
47
+ is plotted in the Figure below.
102
48
</p>
103
49
104
- <p class="justified">
105
- There are four conditional statements in the function: line 36, line 39, and
106
- line 43 and line 44. Suppose we aim to obtain code coverage
107
- that satisfies the MC/DC criterion. This can be done
108
- using CBMC by calling
109
- </p>
50
+ <center>
51
+ <!--<embed src="pid.pdf" width="400px" height="250px" /> -->
52
+ <img src="pid.png" width="400px" height="250px" alt="The pid controller">
110
53
111
- <pre>
112
- <code>cbmc pid.c --cover mcdc --function climb_pid_run
54
+ </center>
55
+
56
+ <pre><code class="c numbered">01: // CONSTANTS:
57
+ 02: #define MAX_CLIMB_SUM_ERR 100
58
+ 03: #define MAX_CLIMB 1
59
+ 04:
60
+ 05: #define CLOCK 16
61
+ 06: #define MAX_PPRZ (CLOCK*600)
62
+ 07:
63
+ 08: #define CLIMB_LEVEL_GAZ 0.31
64
+ 09: #define CLIMB_GAZ_OF_CLIMB 0.75
65
+ 10: #define CLIMB_PITCH_OF_VZ_PGAIN 0.05
66
+ 11: #define CLIMB_PGAIN -0.03
67
+ 12: #define CLIMB_IGAIN 0.1
68
+ 13:
69
+ 14: const float pitch_of_vz_pgain=CLIMB_PITCH_OF_VZ_PGAIN;
70
+ 15: const float climb_pgain=CLIMB_PGAIN;
71
+ 16: const float climb_igain=CLIMB_IGAIN;
72
+ 17: const float nav_pitch=0;
73
+ 18:
74
+ 19: // 1) the target speed in vertical direction
75
+ 20: float desired_climb;
76
+ 21: // 2) vertical speed of the UAV, estimated by a control function
77
+ 22: float estimator_z_dot;
78
+ 23:
79
+ 24: /** PID funciton OUTPUTS */
80
+ 25: float desired_gaz;
81
+ 26: float desired_pitch;
82
+ 27: // Accumulated error in the system
83
+ 28: float climb_sum_err;
84
+ 29:
85
+ 30: /** Computes desired_gaz and desired_pitch */
86
+ 31: void climb_pid_run()
87
+ 32: {
88
+ 33:
89
+ 34: float err=estimator_z_dot-desired_climb;
90
+ 35:
91
+ 36: float fgaz=climb_pgain*(err+climb_igain*climb_sum_err)+CLIMB_LEVEL_GAZ+CLIMB_GAZ_OF_CLIMB*desired_climb;
92
+ 37:
93
+ 38: float pprz=fgaz*MAX_PPRZ;
94
+ 39: desired_gaz=((pprz>=0 && pprz<=MAX_PPRZ) ? pprz : (pprz>MAX_PPRZ ? MAX_PPRZ : 0));
95
+ 40:
96
+ 41: /** pitch offset for climb */
97
+ 42: float pitch_of_vz=(desired_climb>0) ? desired_climb*pitch_of_vz_pgain : 0;
98
+ 43: desired_pitch=nav_pitch+pitch_of_vz;
99
+ 44:
100
+ 45: climb_sum_err=err+climb_sum_err;
101
+ 46: if (climb_sum_err>MAX_CLIMB_SUM_ERR) climb_sum_err=MAX_CLIMB_SUM_ERR;
102
+ 47: if (climb_sum_err<-MAX_CLIMB_SUM_ERR) climb_sum_err=-MAX_CLIMB_SUM_ERR;
103
+ 48:
104
+ 49: /** Estimates the vertical speed */
105
+ 50: estimator_z_dot=climb_pgain * err + desired_climb;
106
+ 51: }
107
+ 52:
108
+ 53: int main()
109
+ 54: {
110
+ 55: /** Non-deterministic initialisation */
111
+ 56: desired_climb=nondet_float();
112
+ 57: estimator_z_dot=nondet_float();
113
+ 58:
114
+ 59: /** Range of initial values of variables */
115
+ 60: __CPROVER_assume(desired_climb>=-MAX_CLIMB && desired_climb<=MAX_CLIMB);
116
+ 61: __CPROVER_assume(estimator_z_dot>=-MAX_CLIMB && estimator_z_dot<=MAX_CLIMB);
117
+ 62:
118
+ 63: while(1)
119
+ 64: {
120
+ 65: climb_pid_run();
121
+ 66: }
122
+ 67:
123
+ 68: return 0;
124
+ 69: }
113
125
</code></pre>
114
126
115
127
<p class="justified">
116
- The <code>--cover mcdc</code> option chooses the coverage criterion, and <code>--function climb_pid_run</code>
117
- specifies the function to be analyzed.
128
+ In order to test the PID controller, we construct a main control loop,
129
+ which repeatedly invokes the function <code>climb_pid_run</code> (line 65).
130
+ In the beginning of the main function, the values of the desired speed <code>desired_climb</code>
131
+ and the estimated speed <code>estimated_z_dot</code> are initialised non-deterministically.
132
+ Subsequently, the <code>__CPROVER_assume</code> statement in lines 60 and 61 guarantees that
133
+ their values are bounded within a range.
118
134
</p>
119
135
120
136
<p class="justified">
121
- The printed outputs will display the configurations of conditions that have
122
- been covered by CBMC. For each conditional statement, the
123
- configuration for its decision to be <em>true</em>/<em>false</em> will be
124
- covered. To fullfil the requirement of MC/DC, further tests are required.
125
- For example, at line 36, there is
137
+ To demonstrate the automatic test suite generation in CBMC,
138
+ we call the following command
126
139
</p>
127
140
128
- <pre>
129
- <code> pprz>=0 && pprz<=MAX_PPRZ
141
+ <pre><code>cbmc pid.c --cover mcdc --unwind 25 --trace --xml-ui
130
142
</code></pre>
131
143
132
144
<p class="justified">
133
- To satisfy MC/DC on such a decision, three requirements for its conditions (boolean
134
- expressions) are finally generated and reported by CBMC:
145
+ The option <code>--cover mcdc</code> specifies the code coverage criterion.
146
+ There are four conditional statements in the PID function: in line 39, line 42,
147
+ line 46 and line 47.
148
+ To satisfy MC/DC, the test suite has to meet multiple requirements.
149
+ For instance, each conditional statement needs to evaluate to <em>true</em> and <em>false</em>.
150
+ Consider the condition <code>"pprz>=0 && pprz<=MAX_PPRZ"</code> at line 39. CBMC
151
+ instruments three coverage goals to control the respective evaluated results of <code>"pprz>=0"</code> and
152
+ <code>"pprz<=MAX_PPRZ"</code>.
153
+ We list them in below and they satisfy the MC/DC rules.
154
+ Note that <code>MAX_PPRZ</code> is defined as 16 * 600 at line 06 of the program.
135
155
</p>
136
156
137
157
<pre>
138
- <code>C1: !(pprz >= (float)0) && pprz <= (float)(16 * 600)
139
- C2: pprz >= (float)0 && !(pprz <= (float)(16 * 600))
140
- C3: pprz >= (float)0 && pprz <= (float)(16 * 600)
158
+ <code>!(pprz >= (float)0) && pprz <= (float)(16 * 600) id="climb_pid_run.coverage.1"
159
+ pprz >= (float)0 && !(pprz <= (float)(16 * 600)) id="climb_pid_run.coverage.2"
160
+ pprz >= (float)0 && pprz <= (float)(16 * 600) id="climb_pid_run.coverage.3"
141
161
</code></pre>
142
162
143
163
<p class="justified">
144
- CBMC generates the following test suite:
164
+ The "id" of each coverage goal is automatically assigned by CBMC. For every
165
+ coverage goal, a trace (if there exists) of the program execution that
166
+ satisfies such a goal is printed out in XML format, as the parameters
167
+ <code>--trace --xml-ui</code> are given. Multiple coverage goals can share a
168
+ trace, when the corresponding execution of the program satisfies all these
169
+ goals at the same time. Each trace corresponds to a test case.
145
170
</p>
171
+
172
+ <p class="justified">
173
+ For the example above, the following test suites are generated.
146
174
<pre><code>Test suite:
147
- T1: desired_climb=6.250003e-2f, estimator_z_dot=1.000000f, old_climb_sum_err=8.988036e+0f
148
- T2: desired_climb=0.000000f, estimator_z_dot=6.134232e-38f, old_climb_sum_err=-6.232042e-37f
149
- T3: desired_climb=6.750841e-20f, estimator_z_dot=-6.011939e-1f, old_climb_sum_err=1.156406e+2f
150
- T4: desired_climb=-9.999999e-1f, estimator_z_dot=1.000000f, old_climb_sum_err=150.000000f
151
- T5: desired_climb=9.999999e-1f, estimator_z_dot=-1.000000f, old_climb_sum_err=-150.000000f
175
+ T1: desired_climb=-1.000000f, estimator_z_dot=1.000000f
176
+ (goal id: climb_pid_run.coverage.1)
177
+ T2: desired_climb=1.000000f, estimator_z_dot=-1.000000f
178
+ (goal id: climb_pid_run.coverage.2)
179
+ T3: desired_climb=0.000000f, estimator_z_dot=0.000000f
180
+ (goal id: climb_pid_run.coverage.3)
152
181
</code></pre>
182
+ </p>
153
183
154
184
<p class="justified">
155
- The tests T1 and T3 are both tests that cause C3 evaluate to <em>true</em>, T3 and
156
- T4 cover C1, and T5 is for C2. Similar observations can be made for the conditional
157
- statements at line 39, 43 and 44.
185
+ The option <code>--unwind 25</code> unwinds the loop inside the main
186
+ function body 25 times. The number of the unwinding operation is
187
+ adjustable, and an introduction to the use of loop unwinding can be found
188
+ in <a href="cbmc-loops.shtml">Understanding Loop Unwinding</a>.
158
189
</p>
159
190
160
191
<p class="justified">
161
- In addition to <code>--cover mcdc</code>, other coverage criteria
162
- like <code>branch</code>, <code>decision</code>, <code>path</code>
163
- etc. are also available when calling CBMC.
192
+ In this small tutorial, we present the automatic test suite generation
193
+ functionality of CBMC, by applying the MC/DC code coverage criterion to a
194
+ PID controller case study. In addition to <code>--cover mcdc</code>, other
195
+ coverage criteria like <code>branch</code>, <code>decision</code>,
196
+ <code>path</code> etc. are also available when calling CBMC.
164
197
</p>
165
198
166
199
<!--#include virtual="footer.inc" -->
0 commit comments