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