Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add verif.bmc VerifToSMT lowering #7603

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

TaoBi22
Copy link
Contributor

@TaoBi22 TaoBi22 commented Sep 16, 2024

This PR adds a pattern to lower the verif.bmc op into the program to actually perform the check (from the splitting up of #7259 - only the actual tool left after this!), similarly to what circt-lec currently does.

@@ -30,7 +35,8 @@ using namespace hw;
//===----------------------------------------------------------------------===//

namespace {
/// Lower a verif::AssertOp operation with an i1 operand to a smt::AssertOp.
/// Lower a verif::AssertOp operation with an i1 operand to a smt::AssertOp,
/// negated to check for unsatisfiability.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently verif.assert is lowered directly to smt.assert, whereas we want it negated in this context to check for unsatisfiability. I'm not aware of any verif dialect use cases that mean this negation is undesirable, but let me know if any exist

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the drive-by comment, I haven't looked into this properly. But won't this change inevitably lead to effectively the same problem as in #5358 ? I.e., any verif.assert(true) will now become smt.assert(false), thus rendering the model unsatisfiable regardless of all other (failing) assertions?

Copy link
Member

@dobios dobios Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I ran into a similar issue in btor and opted simply the prune those assertions as they're trivially true, maybe the same should be done here -- or maybe we should just do this in a fold for the verif.assert and verif.assume ops themselves?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing such trivial assertions is nice, but I'd probably not do it in a canonicalizer because it makes it harder to keep track of a list of passed assertions to report back to the engineer, etc.
Also, just pruning these trivial ones doesn't make the model checker sound.

Given

assert P1
assert P2
assert P3
// equivalent to
assert P1 & P2 & P3

we currently emit

assert !P1
assert !P2
assert !P3
// equivalent to
assert !(P1 || P2 || P3)

but we want

assert !(P1 & P2 & P3)

Not sure how other open-source or commercial model checkers deal with that. I see two options

  • add assertion by assertion "interactively" by checking satisfiability after each added assertion (bad performance-wise unless the solver is really good in caching things, which I don't know enough about)
  • Combine all assertions into a single one (returning good counter-examples may need some post processing/running additional checks)

I like the approach here of keeping the assert lowering simple. Maybe we can add a separate pass earlier in the pipeline to restructure the circuit for one of the two approaches later on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks everyone for taking a look, and thanks for catching that @fzi-hielscher!

This is a great point - thanks for the suggestions @maerhart, those sound good to me! I guess the easy interrim fix is to add on an error in this lowering (or a canonicalizer to the BMC op, though to me this seems more of a point in the lowering than the op itself) that requires the BMC op to have only one assertion - that shouldn't really introduce any compromises in functionality as what would have previously been individual assertions can all just be ANDed, but means that we won't unsoundly produce an unsat when there's actually an assertion that can be violated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think checking for a single assert in the pass that inserts the verif.bmc operation and emitting an error or at least warning would be a reasonable solution for now. Should be quite easy using an InstanceGraph.

Copy link
Member

@maerhart maerhart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really cool, thanks a lot!

lib/Conversion/VerifToSMT/CMakeLists.txt Show resolved Hide resolved
lib/Conversion/VerifToSMT/VerifToSMT.cpp Outdated Show resolved Hide resolved
lib/Conversion/VerifToSMT/VerifToSMT.cpp Outdated Show resolved Hide resolved
@@ -30,7 +35,8 @@ using namespace hw;
//===----------------------------------------------------------------------===//

namespace {
/// Lower a verif::AssertOp operation with an i1 operand to a smt::AssertOp.
/// Lower a verif::AssertOp operation with an i1 operand to a smt::AssertOp,
/// negated to check for unsatisfiability.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think checking for a single assert in the pass that inserts the verif.bmc operation and emitting an error or at least warning would be a reasonable solution for now. Should be quite easy using an InstanceGraph.

@TaoBi22
Copy link
Contributor Author

TaoBi22 commented Sep 17, 2024

@maerhart I think that should be all comments addressed - if you could take a quick scan over the added assertion count checker that would be great!

Copy link
Member

@maerhart maerhart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

matchAndRewrite(verif::BoundedModelCheckingOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
// Check there is exactly one assertion
auto topAssertions = op.getCircuit().getOps<verif::AssertOp>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to do a walk here, because getOps doesn't visit ops in nested regions (also for the other 2 getOps uses below). On the other hand, we're not really using nested regions at the HW level, so should also be fine as is.

auto topAssertions = op.getCircuit().getOps<verif::AssertOp>();
int numAssertions =
std::distance(topAssertions.begin(), topAssertions.end());
SymbolTable symbolTable(op->getParentOfType<ModuleOp>());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually this shouldn't be recomputed for each pattern invocation. It might also be unsafe to iterate of the entire module while the rewriting is in progress.
I'd just instantiate it in runOnOperation and pass it to the pattern to be safe.

SmallVector<mlir::Operation *> worklist;
for (auto instance : op.getCircuit().getOps<InstanceOp>()) {
worklist.push_back(symbolTable.lookup(instance.getModuleName()));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we typically omit curly braces here (and also the two other cases further down) 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦‍♀️ thanks!

SmallVector<Value> inputDecls;
SmallVector<int> clockIndexes;
for (auto [curIndex, types] :
llvm::enumerate(llvm::zip(oldCircuitInputTy, circuitInputTy))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Enumerate can zip at the same time

Suggested change
llvm::enumerate(llvm::zip(oldCircuitInputTy, circuitInputTy))) {
llvm::enumerate(oldCircuitInputTy, circuitInputTy)) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤩

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants