-
Notifications
You must be signed in to change notification settings - Fork 283
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
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: Martin Erhart <[email protected]>
@@ -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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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!
@@ -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. |
There was a problem hiding this comment.
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.
@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! |
There was a problem hiding this 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>(); |
There was a problem hiding this comment.
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>()); |
There was a problem hiding this comment.
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())); | ||
} |
There was a problem hiding this comment.
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) 😉
There was a problem hiding this comment.
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))) { |
There was a problem hiding this comment.
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
llvm::enumerate(llvm::zip(oldCircuitInputTy, circuitInputTy))) { | |
llvm::enumerate(oldCircuitInputTy, circuitInputTy)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤩
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.