CIRCT 23.0.0git
Loading...
Searching...
No Matches
VerifToSMT.cpp
Go to the documentation of this file.
1//===- VerifToSMT.cpp -----------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
14#include "mlir/Conversion/ReconcileUnrealizedCasts/ReconcileUnrealizedCasts.h"
15#include "mlir/Dialect/Arith/IR/Arith.h"
16#include "mlir/Dialect/Func/IR/FuncOps.h"
17#include "mlir/Dialect/SCF/IR/SCF.h"
18#include "mlir/Dialect/SMT/IR/SMTOps.h"
19#include "mlir/Dialect/SMT/IR/SMTTypes.h"
20#include "mlir/IR/ValueRange.h"
21#include "mlir/Pass/Pass.h"
22#include "mlir/Transforms/DialectConversion.h"
23#include "llvm/ADT/SmallVector.h"
24
25namespace circt {
26#define GEN_PASS_DEF_CONVERTVERIFTOSMT
27#include "circt/Conversion/Passes.h.inc"
28} // namespace circt
29
30using namespace mlir;
31using namespace circt;
32using namespace hw;
33
34//===----------------------------------------------------------------------===//
35// Conversion patterns
36//===----------------------------------------------------------------------===//
37
38namespace {
39/// Lower a verif::AssertOp operation with an i1 operand to a smt::AssertOp,
40/// negated to check for unsatisfiability.
41struct VerifAssertOpConversion : OpConversionPattern<verif::AssertOp> {
42 using OpConversionPattern<verif::AssertOp>::OpConversionPattern;
43
44 LogicalResult
45 matchAndRewrite(verif::AssertOp op, OpAdaptor adaptor,
46 ConversionPatternRewriter &rewriter) const override {
47 Value cond = typeConverter->materializeTargetConversion(
48 rewriter, op.getLoc(), smt::BoolType::get(getContext()),
49 adaptor.getProperty());
50 Value notCond = smt::NotOp::create(rewriter, op.getLoc(), cond);
51 rewriter.replaceOpWithNewOp<smt::AssertOp>(op, notCond);
52 return success();
53 }
54};
55
56/// Lower a verif::AssumeOp operation with an i1 operand to a smt::AssertOp
57struct VerifAssumeOpConversion : OpConversionPattern<verif::AssumeOp> {
58 using OpConversionPattern<verif::AssumeOp>::OpConversionPattern;
59
60 LogicalResult
61 matchAndRewrite(verif::AssumeOp op, OpAdaptor adaptor,
62 ConversionPatternRewriter &rewriter) const override {
63 Value cond = typeConverter->materializeTargetConversion(
64 rewriter, op.getLoc(), smt::BoolType::get(getContext()),
65 adaptor.getProperty());
66 rewriter.replaceOpWithNewOp<smt::AssertOp>(op, cond);
67 return success();
68 }
69};
70
71template <typename OpTy>
72struct CircuitRelationCheckOpConversion : public OpConversionPattern<OpTy> {
74
75protected:
76 using ConversionPattern::typeConverter;
77 void
78 createOutputsDifferentOps(Operation *firstOutputs, Operation *secondOutputs,
79 Location &loc, ConversionPatternRewriter &rewriter,
80 SmallVectorImpl<Value> &outputsDifferent) const {
81 // Convert the yielded values back to the source type system (since
82 // the operations of the inlined blocks will be converted by other patterns
83 // later on and we should make sure the IR is well-typed after each pattern
84 // application), and compare the output values.
85 for (auto [out1, out2] :
86 llvm::zip(firstOutputs->getOperands(), secondOutputs->getOperands())) {
87 Value o1 = typeConverter->materializeTargetConversion(
88 rewriter, loc, typeConverter->convertType(out1.getType()), out1);
89 Value o2 = typeConverter->materializeTargetConversion(
90 rewriter, loc, typeConverter->convertType(out1.getType()), out2);
91 outputsDifferent.emplace_back(
92 smt::DistinctOp::create(rewriter, loc, o1, o2));
93 }
94 }
95
96 void replaceOpWithSatCheck(OpTy &op, Location &loc,
97 ConversionPatternRewriter &rewriter,
98 smt::SolverOp &solver) const {
99 // If no operation uses the result of this solver, we leave our check
100 // operations empty. If the result is used, we create a check operation with
101 // the result type of the operation and yield the result of the check
102 // operation.
103 if (op.getNumResults() == 0) {
104 auto checkOp = smt::CheckOp::create(rewriter, loc, TypeRange{});
105 rewriter.createBlock(&checkOp.getSatRegion());
106 smt::YieldOp::create(rewriter, loc);
107 rewriter.createBlock(&checkOp.getUnknownRegion());
108 smt::YieldOp::create(rewriter, loc);
109 rewriter.createBlock(&checkOp.getUnsatRegion());
110 smt::YieldOp::create(rewriter, loc);
111 rewriter.setInsertionPointAfter(checkOp);
112 smt::YieldOp::create(rewriter, loc);
113
114 // Erase as operation is replaced by an operator without a return value.
115 rewriter.eraseOp(op);
116 } else {
117 Value falseVal =
118 arith::ConstantOp::create(rewriter, loc, rewriter.getBoolAttr(false));
119 Value trueVal =
120 arith::ConstantOp::create(rewriter, loc, rewriter.getBoolAttr(true));
121 auto checkOp = smt::CheckOp::create(rewriter, loc, rewriter.getI1Type());
122 rewriter.createBlock(&checkOp.getSatRegion());
123 smt::YieldOp::create(rewriter, loc, falseVal);
124 rewriter.createBlock(&checkOp.getUnknownRegion());
125 smt::YieldOp::create(rewriter, loc, falseVal);
126 rewriter.createBlock(&checkOp.getUnsatRegion());
127 smt::YieldOp::create(rewriter, loc, trueVal);
128 rewriter.setInsertionPointAfter(checkOp);
129 smt::YieldOp::create(rewriter, loc, checkOp->getResults());
130
131 rewriter.replaceOp(op, solver->getResults());
132 }
133 }
134};
135
136/// Lower a verif::LecOp operation to a miter circuit encoded in SMT.
137/// More information on miter circuits can be found, e.g., in this paper:
138/// Brand, D., 1993, November. Verification of large synthesized designs. In
139/// Proceedings of 1993 International Conference on Computer Aided Design
140/// (ICCAD) (pp. 534-537). IEEE.
141struct LogicEquivalenceCheckingOpConversion
142 : CircuitRelationCheckOpConversion<verif::LogicEquivalenceCheckingOp> {
143 using CircuitRelationCheckOpConversion<
144 verif::LogicEquivalenceCheckingOp>::CircuitRelationCheckOpConversion;
145
146 LogicalResult
147 matchAndRewrite(verif::LogicEquivalenceCheckingOp op, OpAdaptor adaptor,
148 ConversionPatternRewriter &rewriter) const override {
149 Location loc = op.getLoc();
150 auto *firstOutputs = adaptor.getFirstCircuit().front().getTerminator();
151 auto *secondOutputs = adaptor.getSecondCircuit().front().getTerminator();
152
153 auto hasNoResult = op.getNumResults() == 0;
154
155 if (firstOutputs->getNumOperands() == 0) {
156 // Trivially equivalent
157 if (hasNoResult) {
158 rewriter.eraseOp(op);
159 } else {
160 Value trueVal = arith::ConstantOp::create(rewriter, loc,
161 rewriter.getBoolAttr(true));
162 rewriter.replaceOp(op, trueVal);
163 }
164 return success();
165 }
166
167 // Solver will only return a result when it is used to check the returned
168 // value.
169 smt::SolverOp solver;
170 if (hasNoResult)
171 solver = smt::SolverOp::create(rewriter, loc, TypeRange{}, ValueRange{});
172 else
173 solver = smt::SolverOp::create(rewriter, loc, rewriter.getI1Type(),
174 ValueRange{});
175 rewriter.createBlock(&solver.getBodyRegion());
176
177 // First, convert the block arguments of the miter bodies.
178 if (failed(rewriter.convertRegionTypes(&adaptor.getFirstCircuit(),
179 *typeConverter)))
180 return failure();
181 if (failed(rewriter.convertRegionTypes(&adaptor.getSecondCircuit(),
182 *typeConverter)))
183 return failure();
184
185 // Second, create the symbolic values we replace the block arguments with
186 SmallVector<Value> inputs;
187 for (auto arg : adaptor.getFirstCircuit().getArguments())
188 inputs.push_back(smt::DeclareFunOp::create(rewriter, loc, arg.getType()));
189
190 // Third, inline the blocks
191 // Note: the argument value replacement does not happen immediately, but
192 // only after all the operations are already legalized.
193 // Also, it has to be ensured that the original argument type and the type
194 // of the value with which is is to be replaced match. The value is looked
195 // up (transitively) in the replacement map at the time the replacement
196 // pattern is committed.
197 rewriter.mergeBlocks(&adaptor.getFirstCircuit().front(), solver.getBody(),
198 inputs);
199 rewriter.mergeBlocks(&adaptor.getSecondCircuit().front(), solver.getBody(),
200 inputs);
201 rewriter.setInsertionPointToEnd(solver.getBody());
202
203 // Fourth, build the assertion.
204 SmallVector<Value> outputsDifferent;
205 createOutputsDifferentOps(firstOutputs, secondOutputs, loc, rewriter,
206 outputsDifferent);
207
208 rewriter.eraseOp(firstOutputs);
209 rewriter.eraseOp(secondOutputs);
210
211 Value toAssert;
212 if (outputsDifferent.size() == 1)
213 toAssert = outputsDifferent[0];
214 else
215 toAssert = smt::OrOp::create(rewriter, loc, outputsDifferent);
216
217 smt::AssertOp::create(rewriter, loc, toAssert);
218
219 // Fifth, check for satisfiablility and report the result back.
220 replaceOpWithSatCheck(op, loc, rewriter, solver);
221 return success();
222 }
223};
224
225struct RefinementCheckingOpConversion
226 : CircuitRelationCheckOpConversion<verif::RefinementCheckingOp> {
227 using CircuitRelationCheckOpConversion<
228 verif::RefinementCheckingOp>::CircuitRelationCheckOpConversion;
229
230 LogicalResult
231 matchAndRewrite(verif::RefinementCheckingOp op, OpAdaptor adaptor,
232 ConversionPatternRewriter &rewriter) const override {
233
234 // Find non-deterministic values (free variables) in the source circuit.
235 // For now, only support quantification over 'primitive' types.
236 SmallVector<Value> srcNonDetValues;
237 bool canBind = true;
238 for (auto ndOp : op.getFirstCircuit().getOps<smt::DeclareFunOp>()) {
239 if (!isa<smt::IntType, smt::BoolType, smt::BitVectorType>(
240 ndOp.getType())) {
241 ndOp.emitError("Uninterpreted function of non-primitive type cannot be "
242 "converted.");
243 canBind = false;
244 }
245 srcNonDetValues.push_back(ndOp.getResult());
246 }
247 if (!canBind)
248 return failure();
249
250 if (srcNonDetValues.empty()) {
251 // If there is no non-determinism in the source circuit, the
252 // refinement check becomes an equivalence check, which does not
253 // need quantified expressions.
254 auto eqOp = verif::LogicEquivalenceCheckingOp::create(
255 rewriter, op.getLoc(), op.getNumResults() != 0);
256 rewriter.moveBlockBefore(&op.getFirstCircuit().front(),
257 &eqOp.getFirstCircuit(),
258 eqOp.getFirstCircuit().end());
259 rewriter.moveBlockBefore(&op.getSecondCircuit().front(),
260 &eqOp.getSecondCircuit(),
261 eqOp.getSecondCircuit().end());
262 rewriter.replaceOp(op, eqOp);
263 return success();
264 }
265
266 Location loc = op.getLoc();
267 auto *firstOutputs = adaptor.getFirstCircuit().front().getTerminator();
268 auto *secondOutputs = adaptor.getSecondCircuit().front().getTerminator();
269
270 auto hasNoResult = op.getNumResults() == 0;
271
272 if (firstOutputs->getNumOperands() == 0) {
273 // Trivially equivalent
274 if (hasNoResult) {
275 rewriter.eraseOp(op);
276 } else {
277 Value trueVal = arith::ConstantOp::create(rewriter, loc,
278 rewriter.getBoolAttr(true));
279 rewriter.replaceOp(op, trueVal);
280 }
281 return success();
282 }
283
284 // Solver will only return a result when it is used to check the returned
285 // value.
286 smt::SolverOp solver;
287 if (hasNoResult)
288 solver = smt::SolverOp::create(rewriter, loc, TypeRange{}, ValueRange{});
289 else
290 solver = smt::SolverOp::create(rewriter, loc, rewriter.getI1Type(),
291 ValueRange{});
292 rewriter.createBlock(&solver.getBodyRegion());
293
294 // Convert the block arguments of the miter bodies.
295 if (failed(rewriter.convertRegionTypes(&adaptor.getFirstCircuit(),
296 *typeConverter)))
297 return failure();
298 if (failed(rewriter.convertRegionTypes(&adaptor.getSecondCircuit(),
299 *typeConverter)))
300 return failure();
301
302 // Create the symbolic values we replace the block arguments with
303 SmallVector<Value> inputs;
304 for (auto arg : adaptor.getFirstCircuit().getArguments())
305 inputs.push_back(smt::DeclareFunOp::create(rewriter, loc, arg.getType()));
306
307 // Inline the target circuit. Free variables remain free variables.
308 rewriter.mergeBlocks(&adaptor.getSecondCircuit().front(), solver.getBody(),
309 inputs);
310 rewriter.setInsertionPointToEnd(solver.getBody());
311
312 // Create the universally quantified expression containing the source
313 // circuit. Free variables in the circuit's body become bound variables.
314 auto forallOp = smt::ForallOp::create(
315 rewriter, op.getLoc(), TypeRange(srcNonDetValues),
316 [&](OpBuilder &builder, auto, ValueRange args) -> Value {
317 // Inline the source circuit
318 Block *body = builder.getBlock();
319 rewriter.mergeBlocks(&adaptor.getFirstCircuit().front(), body,
320 inputs);
321
322 // Replace non-deterministic values with the quantifier's bound
323 // variables
324 for (auto [freeVar, boundVar] : llvm::zip(srcNonDetValues, args))
325 rewriter.replaceOp(freeVar.getDefiningOp(), boundVar);
326
327 // Compare the output values
328 rewriter.setInsertionPointToEnd(body);
329 SmallVector<Value> outputsDifferent;
330 createOutputsDifferentOps(firstOutputs, secondOutputs, loc, rewriter,
331 outputsDifferent);
332 if (outputsDifferent.size() == 1)
333 return outputsDifferent[0];
334 else
335 return rewriter.createOrFold<smt::OrOp>(loc, outputsDifferent);
336 });
337
338 rewriter.eraseOp(firstOutputs);
339 rewriter.eraseOp(secondOutputs);
340
341 // Assert the quantified expression
342 rewriter.setInsertionPointAfter(forallOp);
343 smt::AssertOp::create(rewriter, op.getLoc(), forallOp.getResult());
344
345 // Check for satisfiability and report the result back.
346 replaceOpWithSatCheck(op, loc, rewriter, solver);
347 return success();
348 }
349};
350
351/// Lower a verif::BMCOp operation to an MLIR program that performs the bounded
352/// model check
353struct VerifBoundedModelCheckingOpConversion
354 : OpConversionPattern<verif::BoundedModelCheckingOp> {
355 using OpConversionPattern<verif::BoundedModelCheckingOp>::OpConversionPattern;
356
357 VerifBoundedModelCheckingOpConversion(
358 TypeConverter &converter, MLIRContext *context, Namespace &names,
359 bool risingClocksOnly, SmallVectorImpl<Operation *> &propertylessBMCOps)
360 : OpConversionPattern(converter, context), names(names),
361 risingClocksOnly(risingClocksOnly),
362 propertylessBMCOps(propertylessBMCOps) {}
363 LogicalResult
364 matchAndRewrite(verif::BoundedModelCheckingOp op, OpAdaptor adaptor,
365 ConversionPatternRewriter &rewriter) const override {
366 Location loc = op.getLoc();
367
368 if (std::find(propertylessBMCOps.begin(), propertylessBMCOps.end(), op) !=
369 propertylessBMCOps.end()) {
370 // No properties to check, so we don't bother solving, we just return true
371 // (without this we would incorrectly find violations, since the solver
372 // will always return SAT)
373 Value trueVal =
374 arith::ConstantOp::create(rewriter, loc, rewriter.getBoolAttr(true));
375 rewriter.replaceOp(op, trueVal);
376 return success();
377 }
378
379 SmallVector<Type> oldLoopInputTy(op.getLoop().getArgumentTypes());
380 SmallVector<Type> oldCircuitInputTy(op.getCircuit().getArgumentTypes());
381 // TODO: the init and loop regions should be able to be concrete instead of
382 // symbolic which is probably preferable - just need to convert back and
383 // forth
384 SmallVector<Type> loopInputTy, circuitInputTy, initOutputTy,
385 circuitOutputTy;
386 if (failed(typeConverter->convertTypes(oldLoopInputTy, loopInputTy)))
387 return failure();
388 if (failed(typeConverter->convertTypes(oldCircuitInputTy, circuitInputTy)))
389 return failure();
390 if (failed(typeConverter->convertTypes(
391 op.getInit().front().back().getOperandTypes(), initOutputTy)))
392 return failure();
393 if (failed(typeConverter->convertTypes(
394 op.getCircuit().front().back().getOperandTypes(), circuitOutputTy)))
395 return failure();
396 if (failed(rewriter.convertRegionTypes(&op.getInit(), *typeConverter)))
397 return failure();
398 if (failed(rewriter.convertRegionTypes(&op.getLoop(), *typeConverter)))
399 return failure();
400 if (failed(rewriter.convertRegionTypes(&op.getCircuit(), *typeConverter)))
401 return failure();
402
403 unsigned numRegs = op.getNumRegs();
404 auto initialValues = op.getInitialValues();
405
406 auto initFuncTy = rewriter.getFunctionType({}, initOutputTy);
407 // Loop and init output types are necessarily the same, so just use init
408 // output types
409 auto loopFuncTy = rewriter.getFunctionType(loopInputTy, initOutputTy);
410 auto circuitFuncTy =
411 rewriter.getFunctionType(circuitInputTy, circuitOutputTy);
412
413 func::FuncOp initFuncOp, loopFuncOp, circuitFuncOp;
414
415 {
416 OpBuilder::InsertionGuard guard(rewriter);
417 rewriter.setInsertionPointToEnd(
418 op->getParentOfType<ModuleOp>().getBody());
419 initFuncOp = func::FuncOp::create(rewriter, loc,
420 names.newName("bmc_init"), initFuncTy);
421 rewriter.inlineRegionBefore(op.getInit(), initFuncOp.getFunctionBody(),
422 initFuncOp.end());
423 loopFuncOp = func::FuncOp::create(rewriter, loc,
424 names.newName("bmc_loop"), loopFuncTy);
425 rewriter.inlineRegionBefore(op.getLoop(), loopFuncOp.getFunctionBody(),
426 loopFuncOp.end());
427 circuitFuncOp = func::FuncOp::create(
428 rewriter, loc, names.newName("bmc_circuit"), circuitFuncTy);
429 rewriter.inlineRegionBefore(op.getCircuit(),
430 circuitFuncOp.getFunctionBody(),
431 circuitFuncOp.end());
432 auto funcOps = {&initFuncOp, &loopFuncOp, &circuitFuncOp};
433 // initOutputTy is the same as loop output types
434 auto outputTys = {initOutputTy, initOutputTy, circuitOutputTy};
435 for (auto [funcOp, outputTy] : llvm::zip(funcOps, outputTys)) {
436 auto operands = funcOp->getBody().front().back().getOperands();
437 rewriter.eraseOp(&funcOp->getFunctionBody().front().back());
438 rewriter.setInsertionPointToEnd(&funcOp->getBody().front());
439 SmallVector<Value> toReturn;
440 for (unsigned i = 0; i < outputTy.size(); ++i)
441 toReturn.push_back(typeConverter->materializeTargetConversion(
442 rewriter, loc, outputTy[i], operands[i]));
443 func::ReturnOp::create(rewriter, loc, toReturn);
444 }
445 }
446
447 auto solver = smt::SolverOp::create(rewriter, loc, rewriter.getI1Type(),
448 ValueRange{});
449 rewriter.createBlock(&solver.getBodyRegion());
450
451 // Call init func to get initial clock values
452 ValueRange initVals =
453 func::CallOp::create(rewriter, loc, initFuncOp)->getResults();
454
455 // Initial push
456 smt::PushOp::create(rewriter, loc, 1);
457
458 // InputDecls order should be <circuit arguments> <state arguments>
459 // <wasViolated>
460 // Get list of clock indexes in circuit args
461 size_t initIndex = 0;
462 size_t regStartIdx = oldCircuitInputTy.size() - numRegs;
463 SmallVector<Value> inputDecls;
464 SmallVector<int> clockIndexes;
465 for (auto [curIndex, oldTy, newTy] :
466 llvm::enumerate(oldCircuitInputTy, circuitInputTy)) {
467 if (isa<seq::ClockType>(oldTy)) {
468 inputDecls.push_back(initVals[initIndex++]);
469 clockIndexes.push_back(curIndex);
470 continue;
471 }
472 if (curIndex >= regStartIdx) {
473 auto initVal = initialValues[curIndex - regStartIdx];
474 if (auto initIntAttr = dyn_cast<IntegerAttr>(initVal)) {
475 const auto &cstInt = initIntAttr.getValue();
476 assert(cstInt.getBitWidth() ==
477 cast<smt::BitVectorType>(newTy).getWidth() &&
478 "Width mismatch between initial value and target type");
479 inputDecls.push_back(
480 smt::BVConstantOp::create(rewriter, loc, cstInt));
481 continue;
482 }
483 }
484 // Give a meaningful name prefix based on the argument's role.
485 std::string name;
486 if (curIndex >= regStartIdx)
487 name = ("reg_" + Twine(curIndex - regStartIdx)).str();
488 else
489 name = ("input_" + Twine(curIndex)).str();
490 inputDecls.push_back(smt::DeclareFunOp::create(
491 rewriter, loc, newTy, rewriter.getStringAttr(name)));
492 }
493
494 auto numStateArgs = initVals.size() - initIndex;
495 // Add the rest of the init vals (state args)
496 for (; initIndex < initVals.size(); ++initIndex)
497 inputDecls.push_back(initVals[initIndex]);
498
499 Value lowerBound =
500 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
501 Value step =
502 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(1));
503 Value upperBound =
504 arith::ConstantOp::create(rewriter, loc, adaptor.getBoundAttr());
505 Value constFalse =
506 arith::ConstantOp::create(rewriter, loc, rewriter.getBoolAttr(false));
507 Value constTrue =
508 arith::ConstantOp::create(rewriter, loc, rewriter.getBoolAttr(true));
509 inputDecls.push_back(constFalse); // wasViolated?
510
511 // TODO: swapping to a whileOp here would allow early exit once the property
512 // is violated
513 // Perform model check up to the provided bound
514 auto forOp = scf::ForOp::create(
515 rewriter, loc, lowerBound, upperBound, step, inputDecls,
516 [&](OpBuilder &builder, Location loc, Value i, ValueRange iterArgs) {
517 // Drop existing assertions
518 smt::PopOp::create(builder, loc, 1);
519 smt::PushOp::create(builder, loc, 1);
520
521 // Execute the circuit
522 ValueRange circuitCallOuts =
523 func::CallOp::create(
524 builder, loc, circuitFuncOp,
525 iterArgs.take_front(circuitFuncOp.getNumArguments()))
526 ->getResults();
527
528 // If we have a cycle up to which we ignore assertions, we need an
529 // IfOp to track this
530 // First, save the insertion point so we can safely enter the IfOp
531
532 auto insideForPoint = builder.saveInsertionPoint();
533 // We need to still have the yielded result of the op in scope after
534 // we've built the check
535 Value yieldedValue;
536 auto ignoreAssertionsUntil =
537 op->getAttrOfType<IntegerAttr>("ignore_asserts_until");
538 if (ignoreAssertionsUntil) {
539 auto ignoreUntilConstant = arith::ConstantOp::create(
540 builder, loc,
541 rewriter.getI32IntegerAttr(
542 ignoreAssertionsUntil.getValue().getZExtValue()));
543 auto shouldIgnore =
544 arith::CmpIOp::create(builder, loc, arith::CmpIPredicate::ult,
545 i, ignoreUntilConstant);
546 auto ifShouldIgnore = scf::IfOp::create(
547 builder, loc, builder.getI1Type(), shouldIgnore, true);
548 // If we should ignore, yield the existing value
549 builder.setInsertionPointToEnd(
550 &ifShouldIgnore.getThenRegion().front());
551 scf::YieldOp::create(builder, loc, ValueRange(iterArgs.back()));
552 builder.setInsertionPointToEnd(
553 &ifShouldIgnore.getElseRegion().front());
554 yieldedValue = ifShouldIgnore.getResult(0);
555 }
556
557 auto checkOp =
558 smt::CheckOp::create(rewriter, loc, builder.getI1Type());
559 {
560 OpBuilder::InsertionGuard guard(builder);
561 builder.createBlock(&checkOp.getSatRegion());
562 smt::YieldOp::create(builder, loc, constTrue);
563 builder.createBlock(&checkOp.getUnknownRegion());
564 smt::YieldOp::create(builder, loc, constTrue);
565 builder.createBlock(&checkOp.getUnsatRegion());
566 smt::YieldOp::create(builder, loc, constFalse);
567 }
568
569 Value violated = arith::OrIOp::create(
570 builder, loc, checkOp.getResult(0), iterArgs.back());
571
572 // If we've packaged everything in an IfOp, we need to yield the
573 // new violated value
574 if (ignoreAssertionsUntil) {
575 scf::YieldOp::create(builder, loc, violated);
576 // Replace the variable with the yielded value
577 violated = yieldedValue;
578 }
579
580 // If we created an IfOp, make sure we start inserting after it again
581 builder.restoreInsertionPoint(insideForPoint);
582
583 // Call loop func to update clock & state arg values
584 SmallVector<Value> loopCallInputs;
585 // Fetch clock values to feed to loop
586 for (auto index : clockIndexes)
587 loopCallInputs.push_back(iterArgs[index]);
588 // Fetch state args to feed to loop
589 for (auto stateArg : iterArgs.drop_back().take_back(numStateArgs))
590 loopCallInputs.push_back(stateArg);
591 ValueRange loopVals =
592 func::CallOp::create(builder, loc, loopFuncOp, loopCallInputs)
593 ->getResults();
594
595 size_t loopIndex = 0;
596 // Collect decls to yield at end of iteration
597 SmallVector<Value> newDecls;
598 for (auto [inputIdx, oldTy, newTy] :
599 llvm::enumerate(TypeRange(oldCircuitInputTy).drop_back(numRegs),
600 TypeRange(circuitInputTy).drop_back(numRegs))) {
601 if (isa<seq::ClockType>(oldTy)) {
602 newDecls.push_back(loopVals[loopIndex++]);
603 } else {
604 auto name = ("input_" + Twine(inputIdx)).str();
605 newDecls.push_back(smt::DeclareFunOp::create(
606 builder, loc, newTy, builder.getStringAttr(name)));
607 }
608 }
609
610 // Only update the registers on a clock posedge unless in rising
611 // clocks only mode
612 // TODO: this will also need changing with multiple clocks - currently
613 // it only accounts for the one clock case.
614 if (clockIndexes.size() == 1) {
615 SmallVector<Value> regInputs = circuitCallOuts.take_back(numRegs);
616 if (risingClocksOnly) {
617 // In rising clocks only mode we don't need to worry about whether
618 // there was a posedge
619 newDecls.append(regInputs);
620 } else {
621 auto clockIndex = clockIndexes[0];
622 auto oldClock = iterArgs[clockIndex];
623 // The clock is necessarily the first value returned by the loop
624 // region
625 auto newClock = loopVals[0];
626 auto oldClockLow = smt::BVNotOp::create(builder, loc, oldClock);
627 auto isPosedgeBV =
628 smt::BVAndOp::create(builder, loc, oldClockLow, newClock);
629 // Convert posedge bv<1> to bool
630 auto trueBV = smt::BVConstantOp::create(builder, loc, 1, 1);
631 auto isPosedge =
632 smt::EqOp::create(builder, loc, isPosedgeBV, trueBV);
633 auto regStates =
634 iterArgs.take_front(circuitFuncOp.getNumArguments())
635 .take_back(numRegs);
636 SmallVector<Value> nextRegStates;
637 for (auto [regState, regInput] :
638 llvm::zip(regStates, regInputs)) {
639 // Create an ITE to calculate the next reg state
640 // TODO: we create a lot of ITEs here that will slow things down
641 // - these could be avoided by making init/loop regions concrete
642 nextRegStates.push_back(smt::IteOp::create(
643 builder, loc, isPosedge, regInput, regState));
644 }
645 newDecls.append(nextRegStates);
646 }
647 }
648
649 // Add the rest of the loop state args
650 for (; loopIndex < loopVals.size(); ++loopIndex)
651 newDecls.push_back(loopVals[loopIndex]);
652
653 newDecls.push_back(violated);
654
655 scf::YieldOp::create(builder, loc, newDecls);
656 });
657
658 Value res = arith::XOrIOp::create(rewriter, loc, forOp->getResults().back(),
659 constTrue);
660 smt::YieldOp::create(rewriter, loc, res);
661 rewriter.replaceOp(op, solver.getResults());
662 return success();
663 }
664
665 Namespace &names;
666 bool risingClocksOnly;
667 SmallVectorImpl<Operation *> &propertylessBMCOps;
668};
669
670} // namespace
671
672//===----------------------------------------------------------------------===//
673// Convert Verif to SMT pass
674//===----------------------------------------------------------------------===//
675
676namespace {
677struct ConvertVerifToSMTPass
678 : public circt::impl::ConvertVerifToSMTBase<ConvertVerifToSMTPass> {
679 using Base::Base;
680 void runOnOperation() override;
681};
682} // namespace
683
685 TypeConverter &converter, RewritePatternSet &patterns, Namespace &names,
686 bool risingClocksOnly, SmallVectorImpl<Operation *> &propertylessBMCOps) {
687 patterns.add<VerifAssertOpConversion, VerifAssumeOpConversion,
688 LogicEquivalenceCheckingOpConversion,
689 RefinementCheckingOpConversion>(converter,
690 patterns.getContext());
691 patterns.add<VerifBoundedModelCheckingOpConversion>(
692 converter, patterns.getContext(), names, risingClocksOnly,
693 propertylessBMCOps);
694}
695
696void ConvertVerifToSMTPass::runOnOperation() {
697 ConversionTarget target(getContext());
698 target.addIllegalDialect<verif::VerifDialect>();
699 target.addLegalDialect<smt::SMTDialect, arith::ArithDialect, scf::SCFDialect,
700 func::FuncDialect>();
701 target.addLegalOp<UnrealizedConversionCastOp>();
702
703 // Check BMC ops contain only one assertion (done outside pattern to avoid
704 // issues with whether assertions are/aren't lowered yet)
705 SymbolTable symbolTable(getOperation());
706 SmallVector<Operation *> propertylessBMCOps;
707 WalkResult assertionCheck = getOperation().walk(
708 [&](Operation *op) { // Check there is exactly one assertion and clock
709 if (auto bmcOp = dyn_cast<verif::BoundedModelCheckingOp>(op)) {
710 // We also currently don't support initial values on registers that
711 // don't have integer inputs.
712 auto regTypes = TypeRange(bmcOp.getCircuit().getArgumentTypes())
713 .take_back(bmcOp.getNumRegs());
714 for (auto [regType, initVal] :
715 llvm::zip(regTypes, bmcOp.getInitialValues())) {
716 if (!isa<UnitAttr>(initVal)) {
717 if (!isa<IntegerType>(regType)) {
718 op->emitError("initial values are currently only supported for "
719 "registers with integer types");
720 return WalkResult::interrupt();
721 }
722 auto tyAttr = dyn_cast<TypedAttr>(initVal);
723 if (!tyAttr || tyAttr.getType() != regType) {
724 op->emitError("type of initial value does not match type of "
725 "initialized register");
726 return WalkResult::interrupt();
727 }
728 }
729 }
730 // Check only one clock is present in the circuit inputs
731 auto numClockArgs = 0;
732 for (auto argType : bmcOp.getCircuit().getArgumentTypes())
733 if (isa<seq::ClockType>(argType))
734 numClockArgs++;
735 // TODO: this can be removed once we have a way to associate reg
736 // ins/outs with clocks
737 if (numClockArgs > 1) {
738 op->emitError(
739 "only modules with one or zero clocks are currently supported");
740 return WalkResult::interrupt();
741 }
742 SmallVector<mlir::Operation *> worklist;
743 int numAssertions = 0;
744 op->walk([&](Operation *curOp) {
745 if (isa<verif::AssertOp>(curOp))
746 numAssertions++;
747 if (auto inst = dyn_cast<InstanceOp>(curOp))
748 worklist.push_back(symbolTable.lookup(inst.getModuleName()));
749 if (auto func = dyn_cast<func::CallOp>(curOp))
750 worklist.push_back(symbolTable.lookup(func.getCallee()));
751 });
752 // TODO: probably negligible compared to actual model checking time
753 // but cacheing the assertion count of modules would speed this up
754 while (!worklist.empty()) {
755 auto *module = worklist.pop_back_val();
756 module->walk([&](Operation *curOp) {
757 if (isa<verif::AssertOp>(curOp))
758 numAssertions++;
759 if (auto inst = dyn_cast<InstanceOp>(curOp))
760 worklist.push_back(symbolTable.lookup(inst.getModuleName()));
761 if (auto func = dyn_cast<func::CallOp>(curOp))
762 worklist.push_back(symbolTable.lookup(func.getCallee()));
763 });
764 if (numAssertions > 1)
765 break;
766 }
767 if (numAssertions == 0) {
768 op->emitWarning("no property provided to check in module - will "
769 "trivially find no violations.");
770 propertylessBMCOps.push_back(bmcOp);
771 }
772 if (numAssertions > 1) {
773 op->emitError(
774 "bounded model checking problems with multiple assertions are "
775 "not yet "
776 "correctly handled - instead, you can assert the "
777 "conjunction of your assertions");
778 return WalkResult::interrupt();
779 }
780 }
781 return WalkResult::advance();
782 });
783 if (assertionCheck.wasInterrupted())
784 return signalPassFailure();
785 RewritePatternSet patterns(&getContext());
786 TypeConverter converter;
788
789 SymbolCache symCache;
790 symCache.addDefinitions(getOperation());
791 Namespace names;
792 names.add(symCache);
793
795 risingClocksOnly, propertylessBMCOps);
796
797 if (failed(mlir::applyPartialConversion(getOperation(), target,
798 std::move(patterns))))
799 return signalPassFailure();
800}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static LogicalResult checkOp(Operation *op)
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
void add(mlir::ModuleOp module)
Definition Namespace.h:48
void addDefinitions(mlir::Operation *top)
Populate the symbol cache with all symbol-defining operations within the 'top' operation.
Definition SymCache.cpp:23
Default symbol cache implementation; stores associations between names (StringAttr's) to mlir::Operat...
Definition SymCache.h:85
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
void populateVerifToSMTConversionPatterns(TypeConverter &converter, RewritePatternSet &patterns, Namespace &names, bool risingClocksOnly, SmallVectorImpl< Operation * > &propertylessBMCOps)
Get the Verif to SMT conversion patterns.
void populateHWToSMTTypeConverter(TypeConverter &converter)
Get the HW to SMT type conversions.
Definition HWToSMT.cpp:265
Definition hw.py:1
Definition seq.py:1