Verification of Futurebus+ Cache Coherence Protocol
Published:
In this post, I share a simple verification for the Futurebus+ cache coherence protocol as part of an assignment for the COSC 6385 course at the University of Houston.
Initial Setup & Steps
Initially clone the repository and go to the code main
directory and execute the following two command to start the docker instances (make sure docker is already installed in your computer and started; check here for installation steps of docker.
docker-compose build
docker-compose run ubuntu bash
Note: the docker implementation and murphi3.1 integration obtained from the following repository.
After that you will get into the bash, follow the steps shown below in the ubuntu bash,
- First go to the directory
Murphi3.1/src
and compile the Murphi usingmake
command (if it not already generated). And optionally make the executable access using the commandchmod +x Murphi3.1/src/mu
, if you in the home directory. - Then go to the directory
verification
and obtain the C file for the Futurebus+ verification using./../Murphi3.1/src/mu futurebus.m
command (if it not already generated). This will generate the C filefuturebus.C
. - Thereafter, compile the generate file using the command
make futurebus
, this eventually generate the executable program. - Finally, run the executable using the following command
./futurebus
OR
you can perform these entire steps by simply go to the verification
directory and run the command sh run.sh
.
Verification using Murphi3.1
Please note that Murphi3.1 come installed with docker image so no need to worry about downloading and setting up.
I structure the code as follows,
- constants - to define constants such as number of processor, number of values
const
processor_count: 3;
value_count: 1;
- define types such as processor states (
ProcState
) using enumeration which represents all the states in Futurebus+ protocol, message types (MessageType
) as enumeration which includes different message used to send between the states either bus or cpu call, finally define the message type (Message
) .
type
Proc : scalarset(processor_count);
Value: scalarset(value_count);
ProcState:
record
state: enum {
FB_I,
FB_EU,
FB_EM,
FB_SU,
FB_PR,
FB_PEMR,
FB_PSU,
FB_PW,
FB_PEMW
};
value: Value;
end;
MessageType: enum {
ReadShared,
ReadModified,
Invalidate
};
Message:
record
mtype: MessageType;
value: Value;
end;
- variables - the global variables represents the system.
var
proc_state: array[Proc] of ProcState;
transaction_flag: boolean;
last_write: Value;
one_flag: boolean;
more_flag: boolean;
send_msg: Message;
- procedures - contains the function used by the verification.
procedure SendMessage(msg: Message; i: Proc);
begin
switch msg.mtype
case ReadShared:
if (proc_state[i].state = FB_EM |
proc_state[i].state = FB_EU) then
proc_state[i].state := FB_SU;
endif;
case ReadModified:
if (proc_state[i].state = FB_SU |
proc_state[i].state = FB_PR |
proc_state[i].state = FB_PSU |
proc_state[i].state = FB_PEMR |
proc_state[i].state = FB_EU) then
proc_state[i].state := FB_I;
endif;
case Invalidate:
proc_state[i].state := FB_I;
endswitch;
proc_state[i].value := msg.value;
if (proc_state[i].state = FB_I) then
proc_state[i].value := undefined;
endif;
end;
procedure UpdateSignals();
begin
for m: Proc do
if proc_state[m].state = FB_PEMR |
proc_state[m].state = FB_PSU |
proc_state[m].state = FB_EU |
proc_state[m].state = FB_EM |
proc_state[m].state = FB_SU then
transaction_flag := true;
endif;
endfor;
for m: Proc do
if proc_state[m].state = FB_PR then
if one_flag = true then
more_flag := true;
endif;
if one_flag = false & transaction_flag = false then
one_flag := true;
endif;
endif;
endfor;
end;
- ruleset - define the set of rules that can be used to model the systems.
ruleset i: Proc do
alias p: proc_state[i] do
ruleset v: Value do
rule "Write data"
(p.state = FB_SU | p.state = FB_EU | p.state = FB_EM)
==>
p.state := FB_EM;
p.value := v;
last_write := v;
UpdateSignals();
for k:Proc do
if i != k then
send_msg.mtype := Invalidate;
send_msg.value := undefined;
SendMessage(send_msg, k);
endif;
endfor;
endrule;
rule "Trying to write data"
(p.state = FB_EM | p.state = FB_PEMW)
==>
p.state := FB_PEMW;
p.value := v;
UpdateSignals();
endrule;
rule "Invalidate, on DACKemw"
(p.state = FB_PEMW)
==>
p.state := FB_I;
p.value := undefined;
UpdateSignals();
endrule;
rule "Write data, on DACK"
(p.state = FB_PW)
==>
p.state := FB_EM;
p.value := v;
last_write := v;
UpdateSignals();
for k: Proc do
if i != k then
send_msg.mtype := ReadModified;
send_msg.value := v;
SendMessage(send_msg, k);
endif;
endfor;
endrule;
rule "Write data, on DACKemw"
(p.state = FB_PW)
==>
p.state := FB_EM;
p.value := v;
last_write := v;
UpdateSignals();
for k: Proc do
if i != k then
send_msg.mtype := ReadModified;
send_msg.value := v;
SendMessage(send_msg, k);
endif;
endfor;
endrule;
rule "Trying to write data"
(p.state = FB_I)
==>
p.state := FB_PW;
UpdateSignals();
endrule;
rule "Trying to write data (pending)"
(p.state = FB_PW)
==>
p.state := FB_PW;
endrule;
endruleset;
rule "Trying to read data"
(p.state = FB_I)
==>
p.state := FB_PR;
UpdateSignals();
endrule;
rule "Trying to read data (pending)"
(p.state = FB_PR)
==>
p.state := FB_PR;
endrule;
rule "Moved to shared state from EMR on DACKem"
(p.state = FB_PEMR)
==>
p.state := FB_SU;
p.value := last_write;
UpdateSignals();
for k: Proc do
if i != k then
send_msg.mtype := ReadShared;
send_msg.value := last_write;
SendMessage(send_msg, k);
endif;
endfor;
endrule;
rule "Read shared data"
(p.state = FB_SU)
==>
p.state := FB_SU;
p.value := last_write;
UpdateSignals();
endrule;
rule "Read data from EM"
(p.state = FB_EM | p.state = FB_PEMR)
==>
p.state := FB_PEMR;
UpdateSignals();
endrule;
rule "Go to Pending Shared state"
(p.state = FB_EU | p.state = FB_SU | p.state = FB_PSU)
==>
p.state := FB_PSU;
UpdateSignals();
endrule;
rule "Go to Shared state on DACK"
(p.state = FB_PSU)
==>
p.state := FB_SU;
p.value := last_write;
UpdateSignals();
for k: Proc do
if i != k then
send_msg.mtype := ReadShared;
send_msg.value := last_write;
SendMessage(send_msg, k);
endif;
endfor;
endrule;
rule "Go to Shared state on DACK"
(p.state = FB_PR & transaction_flag)
==>
p.state := FB_SU;
p.value := last_write;
UpdateSignals();
for k: Proc do
if i != k then
send_msg.mtype := ReadShared;
send_msg.value := last_write;
SendMessage(send_msg, k);
endif;
endfor;
endrule;
rule "Go to Shared state on DACK"
(p.state = FB_PR & transaction_flag = false & more_flag = true)
==>
p.state := FB_SU;
p.value := last_write;
UpdateSignals();
for k: Proc do
if i != k then
send_msg.mtype := ReadShared;
send_msg.value := last_write;
SendMessage(send_msg, k);
endif;
endfor;
endrule;
rule "Go to Shared state on DACKem"
(p.state = FB_PR & transaction_flag = false & more_flag = true)
==>
p.state := FB_SU;
p.value := last_write;
UpdateSignals();
for k: Proc do
if i != k then
send_msg.mtype := ReadShared;
send_msg.value := last_write;
SendMessage(send_msg, k);
endif;
endfor;
endrule;
rule "Go to Exclusive state on DACK"
(p.state = FB_PR & transaction_flag = false & one_flag = true)
==>
p.state := FB_EU;
p.value := last_write;
UpdateSignals();
for k: Proc do
if i != k then
send_msg.mtype := ReadShared;
send_msg.value := last_write;
SendMessage(send_msg, k);
endif;
endfor;
endrule;
rule "Read data from Exclusive states"
(p.state = FB_EM | p.state = FB_EU)
==>
p.value := last_write;
UpdateSignals();
endrule;
endalias;
endruleset;
- start-state - define the start state of the system.
startstate
for i: Proc do
proc_state[i].state := FB_I;
endfor;
transaction_flag := false;
one_flag := false;
more_flag := false;
undefine last_write;
undefine send_msg;
endstartstate;
- invariants - define the cases which determine the correct states of the system, or checking the validity of the system.
Invariant "only one processor in EM state"
ForAll p1 : Proc Do
ForAll p2 : Proc Do
proc_state[p1].state = FB_EM & proc_state[p2].state = FB_EM
->
p1 = p2
End
End;
Invariant "only one processor in EU state"
ForAll p1 : Proc Do
ForAll p2 : Proc Do
proc_state[p1].state = FB_EU & proc_state[p2].state = FB_EU
->
p1 = p2
End
End;
invariant "values in valid state match last write"
ForAll n : Proc Do
proc_state[n].state = FB_SU | proc_state[n].state = FB_EM | proc_state[n].state = FB_EU
->
proc_state[n].value = last_write
End;
invariant "value is undefined while invalid"
ForAll n : Proc Do
proc_state[n].state = FB_I
->
IsUndefined(proc_state[n].value)
End;
Output of verification of Futurebus+ cache coherence protocol, this provides a brief summary whether any errors or issues found and other statistics.
Protocol: futurebus
Algorithm:
Verification by breadth first search.
with symmetry algorithm 3 -- Heuristic Small Memory Normalization
with permutation trial limit 10.
Memory usage:
* The size of each state is 96 bits (rounded up to 12 bytes).
* The memory allocated for the hash table and state queue is
8 Mbytes.
With two words of overhead per state, the maximum size of
the state space is 487811 statesn the above code, I structure code in the following structure,
\begin{itemize}
\item constants - to define constants such as number of processor, number of values
\item type - define types such as processor states using enumeration which represents all the states in Futurebus+ proctcol, message types as enumeration which includes different message used to send between the states either bus or cpu call, finally the message type.
* Use option "-k" or "-m" to increase this, if necessary.
* Capacity in queue for breadth-first search: 48781 states.
* Change the constant gPercentActiveStates in mu_prolog.inc
to increase this, if necessary.
Warning: No trace will not be printed in the case of protocol errors!
Check the options if you want to have error traces.
Result:
Invariant "only one processor in EM state" failed.
State Space Explored:
56 states, 184 rules fired in 0.10s.
Analysis of State Space:
There are rules that are never fired.
If you are running with symmetry, this may be why. Otherwise,
please run this program with "-pr" for the rules information.
Next I provides the output of error traces of Futurebus+ traces of cache coherence protocol using the command suffix -tv
. This provides the the reasons why the error happen (i.e two processors go to the exclusive states).
Protocol: futurebus
Algorithm:
Verification by breadth first search.
with symmetry algorithm 3 -- Heuristic Small Memory Normalization
with permutation trial limit 10.
Memory usage:
* The size of each state is 96 bits (rounded up to 12 bytes).
* The memory allocated for the hash table and state queue is
8 Mbytes.
With two words of overhead per state, the maximum size of
the state space is 487811 states \item variables - the global variables represents the system.
* Use option "-k" or "-m" to increase this, if necessary.
* Capacity in queue for breadth-first search: 48781 states.
* Change the constant gPercentActiveStates in mu_prolog.inc
to increase this, if necessary.
The following is the error trace for the error:
Invariant "only one processor in EM state" failed.
Startstate Startstate 0 fired.
proc_state[Proc_1].state:FB_I
proc_state[Proc_1].value:Undefined
proc_state[Proc_2].state:FB_I
proc_state[Proc_2].value:Undefined
proc_state[Proc_3].state:FB_I
proc_state[Proc_3].value:Undefined
transaction_flag:false
last_write:Undefined
one_flag:false
more_flag:false
send_msg.mtype:Undefined
send_msg.value:Undefined
Rule Trying to write data, v:Value_1, i:Proc_1 fired.
proc_state[Proc_1].state:FB_PW
Rule Trying to write data, v:Value_1, i:Proc_2 fired.
proc_state[Proc_2].state:FB_PW
Rule Write data, on DACKemw, v:Value_1, i:Proc_1 fired.
proc_state[Proc_1].state:FB_EM
proc_state[Proc_1].value:Value_1
proc_state[Proc_2].value:Value_1
transaction_flag:true
last_write:Value_1
send_msg.mtype:ReadModified
send_msg.value:Value_1
Rule Write data, on DACKemw, v:Value_1, i:Proc_2 fired.
The last state of the trace (in full) is:
proc_state[Proc_1].state:FB_EM
proc_state[Proc_1].value:Value_1
proc_state[Proc_2].state:FB_EM
proc_state[Proc_2].value:Value_1
proc_state[Proc_3].state:FB_I
proc_state[Proc_3].value:Undefined
transaction_flag:true
last_write:Value_1
one_flag:false
more_flag:false
send_msg.mtype:ReadModified
send_msg.value:Value_1
End of the error trace.
Result:
Invariant "only one processor in EM state" failed.
State Space Explored:
56 states, 184 rules fired in 0.10s.
Analysis of State Space:
There are rules that are never fired.
If you are running with symmetry, this may be why. Otherwise,
p \item procedures - contains the function used by the verification.
\item rulease run this program with "-pr" for the rules information.
Finally, to observe what are rule fired, I obtain the output of rule firing of Futurebus+ traces of cache coherence protocol using the command suffix -pr
.
Protocol: futurebus
Algorithm:
Verification by breadth first search.
with symmetry algorithm 3 -- Heuristic Small Memory Normalization
with permutation trial limit 10.
Memory usage:
* The size of each state is 96 bits (rounded up to 12 bytes).
* The memory allocated for the hash table and state queue is
8 Mbytes.
With two words of overhead per state, the maximum size of
the state space is 487811 statet - define the set of rules that can be used to model the systems.
* Use option "-k" or "-m" to increase this, if necessary.
* Capacity in queue for breadth-first search: 48781 states.
* Change the constant gPercentActiveStates in mu_prolog.inc
to increase this, if necessary.
Warning: No trace will not be printed in the case of protocol errors!
Check the options if you want to have error traces.
Result:
Invariant "only one processor in EM state" failed.
State Space Explored:
56 states, 184 rules fired in 0.10s.
Rules Information:
Fired 0 times - Rule "Read data from Exclusive states, i:Proc_1"
Fired 4 times - Rule "Read data from Exclusive states, i:Proc_2"
Fired 4 times - Rule "Read data from Exclusive states, i:Proc_3"
Fired 4 times - Rule "Go to Exclusive state on DACK, i:Proc_1"
Fired 5 times - Rule "Go to Exclusive state on DACK, i:Proc_2"
Fired 3 times - Rule "Go to Exclusive state on DACK, i:Proc_3"
Fired 3 times - Rule "Go to Shared state on DACKem, i:Proc_1"
Fired 4 times - Rule "Go to Shared state on DACKem, i:Proc_2"
Fired 2 times - Rule "Go to Shared state on DACKem, i:Proc_3"
Fired 3 times - Rule "Go to Shared state on DACK, i:Proc_1"
Fired 4 times - Rule "Go to Shared state on DACK, i:Proc_2"
Fired 2 times - Rule "Go to Shared state on DACK, i:Proc_3"
Fired 0 times - Rule "Go to Shared state on DACK, i:Proc_1"
Fired 0 times - Rule "Go to Shared state on DACK, i:Proc_2"
Fired 2 times - Rule "Go to Shared state on DACK, i:Proc_3"
Fired 0 times - Rule "Go to Shared state on DACK, i:Proc_1"
Fired 0 times - Rule "Go to Shared state on DACK, i:Proc_2"
Fired 1 times - Rule "Go to Shared state on DACK, i:Proc_3"
Fired 0 times - Rule "Go to Pending Shared state, i:Proc_1"
Fired 5 times - Rule "Go to Pending Shared state, i:Proc_2"
Fired 2 times - Rule "Go to Pending Shared state, i:Proc_3"
Fired 0 times - Rule "Read data from EM, i:Proc_1"
Fired 1 times - Rule "Read data from EM, i:Proc_2"
Fired 3 times - Rule "Read data from EM, i:Proc_3"
Fired 0 times - Rule "Read shared data, i:Proc_1"
Fired 2 times - Rule "Read shared data, i:Proc_2"
Fired 0 times - Rule "Read shared data, i:Proc_3"
Fired 0 times - Rule "Moved to shared state from EMR on DACKem, i:Proc_1"
Fired 0 times - Rule "Moved to shared state from EMR on DACKem, i:Proc_2"
Fired 0 times - Rule "Moved to shared state from EMR on DACKem, i:Proc_3"
Fired 4 times - Rule "Trying to read data (pending), i:Proc_1"
Fired 5 times - Rule "Trying to read data (pending), i:Proc_2"
Fired 5 times - Rule "Trying to read data (pending), i:Proc_3"
Fired 18 times - Rule "Trying to read data, i:Proc_1"
Fired 8 times - Rule "Trying to read data, i:Proc_2"
Fired 1 times - Rule "Trying to read data, i:Proc_3"
Fired 1 times - Rule "Trying to write data (pending), v:Value_1, i:Proc_1"
Fired 4 times - Rule "Trying to write data (pending), v:Value_1, i:Proc_2"
Fired 12 times - Rule "Trying to write data (pending), v:Value_1, i:Proc_3"
Fired 18 times - Rule "Trying to write data, v:Value_1, i:Proc_1"
Fired 8 times - Rule "Trying to write data, v:Value_1, i:Proc_2"
Fired 1 times - Rule "Trying to write data, v:Value_1, i:Proc_3"
Fired 1 times - Rule "Write data, on DACKemw, v:Value_1, i:Proc_1"
Fired 4 times - Rule "Write data, on DACKemw, v:Value_1, i:Proc_2"
Fired 12 times - Rule "Write data, on DACKemw, v:Value_1, i:Proc_3"
Fired 1 times - Rule "Write data, on DACK, v:Value_1, i:Proc_1"
Fired 4 times - Rule "Write data, on DACK, v:Value_1, i:Proc_2"
Fired 11 times - Rule "Write data, on DACK, v:Value_1, i:Proc_3"
Fired 0 times - Rule "Invalidate, on DACKemw, v:Value_1, i:Proc_1"
Fired 0 times - Rule "Invalidate, on DACKemw, v:Value_1, i:Proc_2"
Fired 0 times - Rule "Invalidate, on DACKemw, v:Value_1, i:Proc_3"
Fired 0 times - Rule "Trying to write data, v:Value_1, i:Proc_1"
Fired 0 times - Rule "Trying to write data, v:Value_1, i:Proc_2"
Fired 3 times - Rule "Trying to write data, v:Value_1, i:Proc_3"
Fired 0 times - Rule "Write data, v:Value_1, i:Proc_1"
Fired 5 times - Rule "Write data, v:Value_1, i:Proc_2"
Fired 4 times - Rule "Write data, v:Value_1, i:Proc_3"
In the next post, I will write about how to apply a simple fix for Futurebus+ protocol to avoid multiple processers get into exclusive state.