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