Bounded Model Checking (BMC)
Bounded Model Checking (BMC) is a useful technique for bounded verification and bug finding.
(Note: the unroll
command is a now deprecated command which performs bounded model checking for non-LTL properties only. bmc
performs bounded model checking for both LTL and non-LTL properties)
The Fibonacci example (introduced in A first look at UCLID5) is reproduced below but now with the bmc
command, which has the same semantics as the unroll
command.
module main {
var a, b : integer;
init {
a = 0; b = 1;
}
next {
a’, b’ = b, a + b;
}
// This time the invariant will be checked by BMC
// instead of an inductiveness check.
invariant a_le_b: a <= b;
control {
// The bmc command
bmc (3);
check;
print_results;
}
}
The bmc
command unrolls the next
block for 3 steps, and generates an SMT query for each step which checks whether the system can violate the property at that step.
Checking Embedded Specifications
To demonstrate checking embedded specifications, we now present a variant of the above Fibonacci example.
module main {
// System description.
var a, b : integer;
const flag : boolean;
procedure set_init()
modifies a, b;
{
havoc a;
havoc b;
// embedded assumptions.
assume (a <= b);
assume (a >= 0 && b >= 0);
assume (flag);
}
init {
call set_init();
}
next {
a', b' = b, a + b;
if (flag) {
assert (a' <= b');
} else {
assert (false);
}
}
// Proof script.
control {
unroll (3);
check;
print_results;
}
}
We have introduced the constant flag
on line 4.
const flag : boolean;
A constant holds a symbolic value that does not change during computation. The initial value of the constant is assigned non-deterministically and can be controlled using assumptions. A second difference with between this and the first example is on lines 12–14, 23 and 25.
Line 12-14:
assume (a <= b);
assume (a >= 0 && b >= 0);
assume (flag);
Line 23:
assert (a' <= b');
Line 25:
assert (false);
Instead of using a module-level assumption declarations as in the first example, we have three embedded assumptions in the set init procedure on lines 12–14, and two embedded assertions in the next block on lines 23 and 25.
A module-level assumption is assumed to hold for the solver at every step of execution,
while an embedded assumption is assumed “instantaneously” at the location of the statement.
In particular, the assumptions on lines 12–14 tells the solver
to assume that \\( a \leq b \\)
, \\( a \geq 0 \\)
and \\( b \leq 0 \\)
at the end of the set_init
procedure.
Notice that we are not assigning specific values to a and b, instead we are asking Uclid5 to consider potential values of a
and b
such that a ≤ b
, a ≥ 0
and b ≥ 0
.
Similarly the assertions on lines 23 and 25 are evaluated at that specific location in the
code. In particular the assertion on line 23 is only checked when flag
is true
,
while the assertion one line 25 is checked when flag
is false
.
Since flag
is always true
in our model, the assertion on line 25 will never fire.
In contrast, note that a module-level assertion would be evaluated after the init block
and after each execution of the next block.
Note that we use unroll
here again because bmc
currently ignores embedded specifications.
This will be fixed in a future release.
Checking LTL Specifications
We demonstrate checking LTL Specifications using BMC with a UCLID5 model of an intersection with two traffic lights.
The full UCLID5 model:
module main
{
type light_t = enum { red, yellow, green };
var step1, step2 : integer;
var light1, light2 : light_t;
init {
light1, step1 = red, 2;
light2, step2 = green, 1;
}
next {
call (light1', step1') = next_light(light1, step1);
call (light2', step2') = next_light(light2, step2);
}
procedure next_light(light : light_t, step : integer)
returns (lightP : light_t, stepP: integer)
{
if (step == 0) {
case
(light == green) : {
lightP = yellow;
stepP = step;
}
(light == yellow) : {
lightP = red;
stepP = 2;
}
(light == red) : {
lightP = green;
stepP = 1;
}
esac
} else {
lightP = light;
stepP = step - 1;
}
}
property[LTL] always_one_red: G(light1 == red || light2 == red);
// property[LTL] eventually_green:
// G(F(light1 == green)) && G(F(light2 == green));
control {
v = bmc(10);
check;
print_results;
v.print_cex(light1, step1, light2, step2);
}
}
Lines 3–39 define the functionality of the traffic light; this part of the model should be familiar.
type light_t = enum { red, yellow, green };
var step1, step2 : integer;
var light1, light2 : light_t;
init {
light1, step1 = red, 2;
light2, step2 = green, 1;
}
next {
call (light1', step1') = next_light(light1, step1);
call (light2', step2') = next_light(light2, step2);
}
procedure next_light(light : light_t, step : integer)
returns (lightP : light_t, stepP: integer)
{
if (step == 0) {
case
(light == green) : {
lightP = yellow;
stepP = step;
}
(light == yellow) : {
lightP = red;
stepP = 2;
}
(light == red) : {
lightP = green;
stepP = 1;
}
esac
} else {
lightP = light;
stepP = step - 1;
}
}
The current state of the lights are stored in the variables light1
and light2
,
and these switch from red
to green
to yellow
and back to red
.
The variables step1
and step2
can be thought of timers,
and ensure that each light stays red for three transitions,
green for two transitions and stays yellow for a single transition.
The LTL properties are on lines 41-43.
property[LTL] always_one_red: G(light1 == red || light2 == red);
// property[LTL] eventually_green:
// G(F(light1 == green)) && G(F(light2 == green));
The property always_one_red
specifies a safety property which states
that at least one of the two lights must be red
in every particular cycle.
The notation G(φ)
refers to the LTL globally operator,
while the notation F(φ)
refers to the LTL eventually (future) operator.
Other supported operators include next-time: X(φ)
, (strong-)until: U(φ1, φ2)
and weak-until: W(φ1, φ2)
.
always_one_red
is a safety property.
The property eventually_green
is an example of liveness property,
and specifies that both lights become green
infinitely often.
The command for bounded verification of LTL properties is the bmc
command.
This is invoked on line 46.
v = bmc(10);
Note that, currently, a UCLID5 model can only have one LTL property at a time. This will be fixed in a future release.