Skip to content

New IC3: compare SAT backends on HWMCC17 - #2066

Draft
kroening wants to merge 27 commits into
diffblue:mainfrom
kroening:dkr-new-ic3-hwmcc17-report
Draft

New IC3: compare SAT backends on HWMCC17#2066
kroening wants to merge 27 commits into
diffblue:mainfrom
kroening:dkr-new-ic3-hwmcc17-report

Conversation

@kroening

@kroening kroening commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

This change adds SAT-backend selection for --new-ic3 and benchmarks the
available backends on the HWMCC 2017 single-track benchmark suite.

Supported backends:

  • ictminisat
  • minisat2
  • cadical

The PR also generalizes the benchmarking script so it can run full benchmark
directories and emit comparable CSV results across multiple solver backends.

User-facing changes

Example:

ebmc --new-ic3 --new-ic3-sat-solver cadical <input>

Experimental setup

  • benchmark suite: HWMCC 2017 single track
  • timeout per run: 60s
  • compiler: clang++
  • build cache: ccache
  • full raw results: benchmarking/hwmcc17_single_new_ic3_sat_backends.csv
  • expected-result metadata: benchmarking/hwmcc17_single_expected.csv
  • full report: benchmarking/hwmcc17_single_new_ic3_sat_solver_report.md

Results

On the 166 benchmarks with known expected outcomes:

  • minisat2: 107 correct solves
  • ictminisat: 105 correct solves
  • cadical: 96 correct solves
  • wrong decisive results: 0
  • unique correct solves:
    • ictminisat: 1
    • minisat2: 3
    • cadical: 1

The report recommends keeping backend selection user-visible and treating
cadical as opt-in. If choosing a default solely from HWMCC 2017 results,
minisat2 is the strongest candidate.

Validation

  • rebuilt with clang++ and ccache
  • make -C src -j4 CXX='ccache clang++' MINISAT2=/local/home/dkr/3hw-cbmc/lib/cbmc/minisat-2.2.1 CADICAL=/local/home/dkr/3hw-cbmc/lib/cbmc/cadical
  • make -C unit -j4 CXX='ccache clang++'
  • full HWMCC 2017 single-track comparison run completed

kroening and others added 26 commits August 9, 2026 07:36
This extracts the code that turns the data in verilog_set_genvarst into a
map into a method.
An implicitly declared net used in a port connection is a one-bit
scalar net (IEEE 1800-2017 6.10). When a wider output port drives such
a net, the value must be truncated to the least-significant bit, as a
narrowing continuous assignment would be. EBMC instead casts the port
value to bool, i.e. computes (port != 0), so the resulting bit is wrong
for any port value whose LSB differs from its zero/non-zero status.
When a port connection ties a wider signal to a one-bit net (e.g. an
implicitly declared net, which per IEEE 1800-2017 6.10 is a one-bit
scalar), the value must be narrowed by taking the least-significant
bit, exactly as a narrowing continuous assignment does (see
assignment_conversion). instantiate_port instead used
typecast_exprt::conditional_cast to bool, which lowers to a (!= 0)
reduction, so the net received the wrong bit whenever the source's LSB
differed from its zero/non-zero status.

Promote the accompanying regression test from KNOWNBUG to CORE.
This adds a call to log_version_and_architecture(...) to both ebmc and
vlindex, to match the behavior of cbmc and hw-cbmc.
Struct literals (assignment patterns) used as parameter values were
rejected because the constant expression check only accepted
expressions with id() == ID_constant. Struct, array and union
expressions have their own IDs (ID_struct, ID_array, ID_union)
but are constant when all their operands are.

This adds a recursive is_constant_rec() check in both
elaborate_constant_expression_check() and verilog_simplifier_rec(),
enabling struct-typed parameters and member access on them (e.g.,
P.x where P is a struct-valued parameter).
defines[identifier] returns a reference to any existing macro entry.
When a macro was redefined, the new parameters and body tokens were
appended to the previous definition instead of replacing it. For a macro
such as FORCE_CONTENTION_ASSERTION_RESET_ACTIVE, defined as 1'b1 several
times in one file, the expansion became "1'b11'b1", which the parser
then rejected.

The entry is now reset before it is populated, so the most recent
definition wins (IEEE 1800-2017 22.5.1).

Resolves the parse error in LogikBench large/nvdlafull and
large/nvdlasmall (both still need library modules that are not part of
the benchmark).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ments

When splitting the actual arguments of a function-like macro on commas,
the preprocessor treated every comma and the first ')' as significant,
regardless of nesting. A single argument containing commas inside
parentheses, brackets or braces -- e.g.
`debug($display("%d %d", a, b);) -- was therefore miscounted, and the
first inner ')' terminated the list prematurely.

Commas and the closing ')' are now only significant at nesting depth
zero (IEEE 1800-2017 22.5.1).

Fixes LogikBench blocks/picorv32.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This bumps to the top of the develop branch.
…in synthesis

When synthesising a blocking assignment, synth_assign tries to simplify the
right-hand side to a constant so the value can be propagated (needed, among
other things, to unroll for/while loops by evaluating their guard each
iteration). It used the plain simplifier, which does not know how to reduce
Verilog-specific constructs such as replication ({n{x}}) to a constant.

As a result a loop whose bit-vector loop variable is initialised with a
replication, e.g.

  for(data_mask = {1'b1, {DW-1{1'b0}}}; data_mask != 0;
      data_mask = data_mask >> 1)

left data_mask non-constant, so synthesis reported "synthesis failed to
evaluate loop guard". Use the Verilog-aware simplifier instead, which lowers
replication to a concatenation before folding. The result is only used when
it is constant, so non-constant right-hand sides are unaffected.

This is the root cause of the loop-guard failures for the LogikBench
blocks/ethmac (rtl/eth_lfsr.v:235) and blocks/lfsr (rtl/lfsr.v:235)
benchmarks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SystemVerilog allows an optional block name after endfunction and
endtask, e.g. "endfunction : is_width_valid" (IEEE 1800-2017 A.9.3).
The grammar previously rejected the colon.

Fixes the first parse error in LogikBench blocks/hmac, blocks/i2c,
blocks/uart and large/aes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
SystemVerilog allows unpacked array dimensions on a parameter or
localparam declarator, e.g. "parameter logic [3:0] p [73] = '{...}"
(IEEE 1800-2017 A.2.4). The grammar previously rejected the '[' after
the parameter name. The declarator's dimensions are now merged with the
declaration type via verilog_declaratort::merged_type().

This addresses the first parse error in LogikBench blocks/spi and
large/cva6. Elaborating an array assignment pattern as a constant
parameter value is a separate gap tracked by the accompanying KNOWNBUG
regression test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adding the optional end label after endfunction/endtask makes the
grammar read one lookahead token before reducing, which shifted the
source location of the whole declaration onto that lookahead token. The
declaration's location is now taken from its declarator, so it points at
the function/task name (an improvement over the previous behaviour, which
pointed at the endfunction/endtask keyword). Updates the
task_name_collision expectation accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
test.pl inverts the pass/fail verdict for KNOWNBUG-tagged tests: the
required-pattern section is meant to describe the aspirational, fixed
behaviour, so that a *mismatch* (the bug still reproducing) is reported
OK, and a match (the bug turns out to be fixed) is flagged so the test
can be promoted to CORE.

unpacked_array_param1.desc had this backwards: its patterns matched the
*current* buggy error message ("expected constant expression, but got
...", EXIT=2) verbatim, which -- given the inversion -- meant the test
would report a hard FAILURE on every single run for as long as the bug
remains unfixed, rather than the intended "known, not yet fixed" OK.
This is what CI's "KNOWNBUG checks" job was catching.

Rewritten to expect the aspirational post-fix output (successful
elaboration, "no properties", matching the convention already used by
e.g. macro_arg_commas1.desc for the same --bound 0/no-properties case).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The small interpreter used to evaluate Verilog functions in constant
contexts (e.g. computing a parameter value) handled blocking assignments,
blocks and for loops, but threw "Don't know how to interpret statement
`if'" on if statements. Add support: evaluate the condition as a constant
expression and interpret the taken branch (handling both if and if/else).

This is the root cause of the elaboration failure for the LogikBench
large/axicrossbar benchmark, whose calcBaseAddrs() constant function
(rtl/axi_crossbar_addr.v) uses if statements.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Module-level 'integer' variables are idiomatically used as loop
counters and combinational scratch. They are elaboration-only: the
synthesis pass never turns them into state (synth_assignments skips
them), yet assignment() still tracked their assignments for driver
and assignment-type conflicts. When such a variable was (blocking-)
assigned in more than one always/initial block -- e.g. a shared 'for'
loop index -- this produced a spurious "conflict with previous
assignment" or "conflicting assignment types (new: clocked, old:
combinational)" error, even though only one block's value is ever
observable.

Exempt variables carrying the Verilog 'integer' type from
assignment-type and member/driver-conflict tracking.

Fixes elaboration of LogikBench basic/crossbar, blocks/viterbi
(signal j) and large/qr (signal k).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A part-select expression is unsigned even when it selects a signed vector
in its entirety (IEEE 1800-2017 11.5.1). When such a full-width part-select
appears on the left-hand side of an assignment (e.g. areg[DW-1:0] <= a),
synth_assignments took a shortcut that assigned the unsigned right-hand
side straight into the signed target symbol. The right- and left-hand
sides then differed in signedness, tripping the "synth_assignments type
consistency" DATA_INVARIANT and aborting (SIGABRT).

We now reinterpret the right-hand side to the type of the source when the
full-width part-select is elided; the bits are identical, so this is a
sign-preserving reinterpret cast that makes the invariant hold.

This is the crash observed on the LogikBench arithmetic/mulreg benchmark.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This adds support for typed assignment patterns.
Add -f option to ebmc and vlindex to read source file names and options
from a command file, as is common in EDA tools. Each line is treated as
a command line option.  Blank lines and // comments are ignored.

Resolves diffblue#1337
When an interface instance is passed to a module port with explicit
modport selection (e.g., sb.receiver), resolve the hierarchical
identifier to the interface instance itself. The modport name is
verified against the interface's modport declarations per IEEE
1800-2017 section 25.5.3.
Add support for virtual interface variable declarations and
assignments per IEEE 1800-2017 section 25.9. The parser now stores
the interface name with a proper IREP ID, the type elaboration
accepts the type, and assignment conversion handles assignments
to virtual interface variables.

Member access through virtual interfaces (vif.data) is left for
future work and tracked as a KNOWNBUG test.
When a module has a port of a parameterized interface type, the port's
interface members are instantiated with default parameters during type
checking. When a differently-parameterized interface instance is connected,
the types may not match.

This fixes the issue in two places:

1. In verilog_synthesis.cpp, synth_module_instance() now updates port
   member types in the symbol table to match the actual bound interface
   before synthesizing the submodule. This ensures the transition system
   invariant constraints have matching types.

2. In ebmc_properties.cpp, a fix_symbol_types() pass updates symbol
   expression types in property expressions to match the symbol table,
   handling cases where the property was type-checked before the type
   update.

The test case verifies that a param_if #(32) interface can be connected
through a port declared as param_if (defaulting to W=8), per IEEE
1800-2017 section 25.8.
Add SAT backend selection for the new IC3 engine, generalize the benchmark runner, and include a full HWMCC 2017 single-track comparison report for ictminisat, minisat2, and cadical.
@kroening
kroening marked this pull request as draft August 9, 2026 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant