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