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