Formal verification is a vast and interesting topic. One of the biggest issues is tools to support SVA. I recently found hw-cbmc
, an open source tool to run SVA or SMV on Verilog. This is a deep dive into hw-cbmc
.
Hello World Link to heading
This is a summary of commands to build hw-cbmc
. This is based on COMPILING.md and Dockerfile.
apt-get install g++ gcc flex bison make git curl patch
git clone https://github.com/diffblue/hw-cbmc
git submodule init; git submodule update
make -C lib/cbmc/src minisat2-download
make -C src
To run hello world, ebmc
is called with --top
and --bound
with 3.
ebmc main.sv --top main --bound 3
module main(input clk, x, y);
reg [1:0] cnt1;
reg z;
initial cnt1=0;
initial z=0;
always @(posedge clk) cnt1=cnt1+1;
always @(posedge clk)
casex (cnt1)
2'b00:;
2'b01:;
2'b1?: z=1;
endcase
p1: assert property (@(posedge clk) (z==0));
endmodule
Engines Link to heading
I asked copilot to explain the methods used in ebmc
and I got the following list. Technically, completeness threshold is not an engine. It is a metric that can be used with heuristics to choose an engine, if not provided on the command line.
- __Bounded Model Checking (BMC)__:
- BMC checks properties within a fixed bound (e.g., a number of steps or timeframes).
- It uses SAT or SMT solvers to find counterexamples within the bound.
- Suitable for finding bugs quickly but does not guarantee correctness beyond the bound.
- __1-Induction__:
- A proof-based method that attempts to prove properties by induction.
- It checks the base case and the inductive step to ensure the property holds for all states.
- Useful for proving correctness but may fail if the inductive step cannot be established.
- __Completeness Threshold__:
- Evaluates properties based on a completeness threshold, which determines the maximum depth or complexity of the system.
- It is a heuristic approach that tries to simplify the verification process.
- May not always succeed in proving or disproving properties.
- __BDD Engine__:
- Uses Binary Decision Diagrams (BDDs) to represent and manipulate boolean functions.
- Efficient for certain types of properties but can suffer from memory issues for large systems.
- __IC3__:
- Incremental Construction of Inductive Clauses (IC3) is a SAT-based method for proving safety properties.
- It incrementally builds inductive invariants to prove properties.
- Effective for safety properties but not applicable to liveness properties.
- __Word-Level BMC__:
- Operates at a higher abstraction level, using word-level constructs (e.g., integers, arrays) directly.
- Typically employs SMT (Satisfiability Modulo Theories) solvers, which can handle complex data types and arithmetic operations.
- Suitable for systems with high-level constructs, as it avoids the overhead of bit-level encoding.
- __Bit-Level BMC__:
- Works at the lowest level of abstraction, representing everything as individual bits.
- Uses SAT (Boolean Satisfiability) solvers, which are optimized for boolean logic.
- Ideal for hardware verification and systems where bit-level precision is critical.
Deep Dive into ebmc Link to heading
Starting with src/ebmc/main.cpp
with ebmc
where it calls ebmc_parse_options
.
int main(int argc, const char **argv)
{
ebmc_parse_optionst ebmc_parse_options(argc, argv);
return ebmc_parse_options.main();
}
Then doit
is called from src/ebmc/ebmc_parse_options.cpp
to register languages.
int ebmc_parse_optionst::doit()
{
if(cmdline.isset("verbosity"))
ui_message_handler.set_verbosity(
unsafe_string2unsigned(cmdline.get_value("verbosity")));
else
ui_message_handler.set_verbosity(messaget::M_STATUS); // default
if(config.set(cmdline))
{
usage_error();
return CPROVER_EXIT_USAGE_ERROR;
}
register_languages();
Then doit
creates the transition system from the Verilog design.
auto transition_system = get_transition_system(cmdline, ui_message_handler);
The next step is extracting the assertion and related properties by calling from_command_line
and property_checker
.
auto properties = ebmc_propertiest::from_command_line(
cmdline, transition_system, ui_message_handler);
auto checker_result = property_checker(
cmdline, transition_system, properties, ui_message_handler);
The next step is calling the engine from src/ebmc/property_checker.cpp
. The engine is picked up from command line options. If none is given, it goes to engine_heuristic
.
if(cmdline.isset("bdd") || cmdline.isset("show-bdds"))
{
return bdd_engine(
cmdline, transition_system, properties, message_handler);
}
else if(cmdline.isset("aig") || cmdline.isset("dimacs"))
{
// bit-level BMC
return bit_level_bmc(
cmdline, transition_system, properties, message_handler);
}
else if(cmdline.isset("k-induction"))
{
return k_induction(
cmdline, transition_system, properties, message_handler);
}
else if(cmdline.isset("ic3"))
{
#ifdef _WIN32
throw ebmc_errort() << "No support for IC3 on Windows";
#else
return ic3_engine(
cmdline, transition_system, properties, message_handler);
#endif
}
else if(cmdline.isset("bound"))
{
// word-level BMC
return word_level_bmc(
cmdline, transition_system, properties, message_handler);
}
else
{
// heuristic engine selection
return engine_heuristic(
cmdline, transition_system, properties, message_handler);
}
Transition System Link to heading
Circling back to get_transition_system
in src/ebmc/transition_system.cpp
, where Verilog (or VHDL) files are parsed and represented in the form of a transition system.
int get_transition_system(
const cmdlinet &cmdline,
message_handlert &message_handler,
transition_systemt &transition_system)
{
messaget message(message_handler);
if(cmdline.isset("preprocess"))
return preprocess(cmdline, message_handler);
//
// parsing
//
language_filest language_files;
if(parse(cmdline, language_files, message_handler))
return 1;
if(cmdline.isset("show-parse"))
{
language_files.show_parse(std::cout, message_handler);
return 0;
}
Properties Link to heading
Once we have the transition system parsed and built, it needs to extract the assertions and properties. from_transition_system
, src/ebmc/ebmc_properties.cpp
, is called to extract properties.
auto properties =
from_transition_system(transition_system, message_handler);
std::string value_as_string = from_expr(ns, symbol.name, symbol.value);
message.debug() << "Property: " << value_as_string << messaget::eom;
properties.properties.push_back(propertyt());
properties.properties.back().number = properties.properties.size() - 1;
properties.properties.back().identifier = symbol.name;
properties.properties.back().name = symbol.display_name();
properties.properties.back().original_expr = symbol.value;
properties.properties.back().location = symbol.location;
properties.properties.back().mode = symbol.mode;
properties.properties.back().description =
id2string(symbol.location.get_comment());