Bounded Model Check
Bounded Model Check (BMC) is a formal verification technique in which a system model and a property are translated into a formula whose satisfiability corresponds to the existence of a counterexample within a bounded number of execution steps. Unlike unbounded model checking, which seeks to prove properties for all possible executions, BMC limits its search to executions of length up to a chosen bound, known as the depth or k.
General approach
In its classical formulation, BMC unrolls the transition relation of the system for k steps and asks a SAT solver whether the resulting formula, conjoined with the negated property, is satisfiable. A satisfying assignment corresponds to a concrete counterexample trace of length at most k; if the formula is unsatisfiable, no counterexample exists within that bound (but the property may still be violated at greater depths).
BMC can also be formulated in terms of Quantified Boolean Formulae (QBF). QBF-based encodings can yield exponentially more succinct formulae than the unrolled transition-relation approach, at the cost of requiring an efficient QBF decision procedure. Industrial benchmarks have been used to compare SAT-based, general-purpose-QBF, and special-purpose QBF approaches in BMC.
Beyond hardware, BMC has been applied to software models expressed in first-order relational modelling languages such as Alloy, where it can be combined with temporal logic to specify and verify reactive behaviour.
Use in riscv-formal
The riscv-formal framework uses Bounded Model Checking as one of its two primary solver modes. The verification mode is selected in the [options] section of the genchecks.py configuration file:
| Option | Value | Description |
|---|---|---|
mode |
bmc |
Bounded Model Check (default). |
mode |
prove |
Unbounded verification. |
For most riscv-formal checks, a [depth] section specifies the parameters of the BMC run. The interpretation of the depth values depends on the check:
- Two values (e.g.
csrw,csr_ill,csrc_*,cover,bus_imem,causal*,faults): the first value is the number of cycles to hold reset for, and the second value is the maximum execution depth of the BMC. - Three values (e.g.
liveness,uniqueness): the first value is the reset-cycles count, the second is the trigger depth at which an instruction is required to be retired, and the third is the execution depth of the BMC span.
Checks typically run as BMC
The following riscv-formal checks are commonly configured as bounded model checks. The choice between BMC and unbounded verification depends on factors such as the strength of safety properties present in the core, the overall complexity of the design, and the verification requirements of the application.
- Instruction and CSR checks —
csrw(CSR read/write instruction behaviour),csr_ill(illegal CSR access), and thecsrc_*consistency checks (csrc_const,csrc_zero,csrc_hpm,csrc_any). - Causality checks —
causal,causal_mem, andcausal_io, which verify that out-of-order retirement preserves causality for registers, memory, and I/O memory respectively. The I/O memory region is selected via theRISCV_FORMAL_IOADDR(addr)macro. - Liveness check — Ensures the core never freezes, by requiring that an instruction retired at a configurable trigger depth is followed by a later-retired instruction within the BMC span. Bounded fairness constraints may need to be added to the design for this check to succeed.
- Uniqueness check — Ensures that no two instructions with the same
rvfi_orderare retired. - Faults check — Verifies that dynamically occurring memory faults are handled correctly, using the
RISCV_FORMAL_MEM_FAULTmacro and thervfi_mem_fault*signals. - Cover check — A BMC that uses
cover()SystemVerilog statements to collect data about the bounds required to reach certain states, helping to set bounds for the other bounded model checks. It can also be used to produce witness traces, for instance to examine the conditions under which a specific CSR bit goes high. - Standard bus checks — E.g. the
bus_imeminstruction-bus memory check, which adds a single-word, read-only, unconstrained memory abstraction.
Role of the Cover check
The cover check has a special role in the riscv-formal workflow. Because BMC results are only valid up to the configured depth, the user must pick bounds large enough to expose interesting behaviour. The cover check is run as a BMC whose purpose is to discover the depths at which various RVFI events and event sequences become reachable. These discovered bounds can then be transferred to the other checks so that their BMCs are not only correct but also meaningful.
See also
- riscv-formal — A verification framework that uses Bounded Model Checking (among other techniques) for ISA-compliance checking of RISC-V cores.