CIRCT 20.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
16#include "mlir/Conversion/ReconcileUnrealizedCasts/ReconcileUnrealizedCasts.h"
17#include "mlir/Dialect/Arith/IR/Arith.h"
18#include "mlir/Dialect/Func/IR/FuncOps.h"
19#include "mlir/Dialect/SCF/IR/SCF.h"
20#include "mlir/IR/ValueRange.h"
21#include "mlir/Pass/Pass.h"
22#include "mlir/Transforms/DialectConversion.h"
23#include "llvm/ADT/SmallVector.h"
24
25namespace circt {
26#define GEN_PASS_DEF_CONVERTVERIFTOSMT
27#include "circt/Conversion/Passes.h.inc"
28} // namespace circt
29
30using namespace mlir;
31using namespace circt;
32using namespace hw;
33
34//===----------------------------------------------------------------------===//
35// Conversion patterns
36//===----------------------------------------------------------------------===//
37
38namespace {
39/// Lower a verif::AssertOp operation with an i1 operand to a smt::AssertOp,
40/// negated to check for unsatisfiability.
41struct VerifAssertOpConversion : OpConversionPattern<verif::AssertOp> {
42 using OpConversionPattern<verif::AssertOp>::OpConversionPattern;
43
44 LogicalResult
45 matchAndRewrite(verif::AssertOp op, OpAdaptor adaptor,
46 ConversionPatternRewriter &rewriter) const override {
47 Value cond = typeConverter->materializeTargetConversion(
48 rewriter, op.getLoc(), smt::BoolType::get(getContext()),
49 adaptor.getProperty());
50 Value notCond = rewriter.create<smt::NotOp>(op.getLoc(), cond);
51 rewriter.replaceOpWithNewOp<smt::AssertOp>(op, notCond);
52 return success();
53 }
54};
55
56/// Lower a verif::AssumeOp operation with an i1 operand to a smt::AssertOp
57struct VerifAssumeOpConversion : OpConversionPattern<verif::AssumeOp> {
58 using OpConversionPattern<verif::AssumeOp>::OpConversionPattern;
59
60 LogicalResult
61 matchAndRewrite(verif::AssumeOp op, OpAdaptor adaptor,
62 ConversionPatternRewriter &rewriter) const override {
63 Value cond = typeConverter->materializeTargetConversion(
64 rewriter, op.getLoc(), smt::BoolType::get(getContext()),
65 adaptor.getProperty());
66 rewriter.replaceOpWithNewOp<smt::AssertOp>(op, cond);
67 return success();
68 }
69};
70
71/// Lower a verif::LecOp operation to a miter circuit encoded in SMT.
72/// More information on miter circuits can be found, e.g., in this paper:
73/// Brand, D., 1993, November. Verification of large synthesized designs. In
74/// Proceedings of 1993 International Conference on Computer Aided Design
75/// (ICCAD) (pp. 534-537). IEEE.
76struct LogicEquivalenceCheckingOpConversion
77 : OpConversionPattern<verif::LogicEquivalenceCheckingOp> {
79 verif::LogicEquivalenceCheckingOp>::OpConversionPattern;
80
81 LogicalResult
82 matchAndRewrite(verif::LogicEquivalenceCheckingOp op, OpAdaptor adaptor,
83 ConversionPatternRewriter &rewriter) const override {
84 Location loc = op.getLoc();
85 auto *firstOutputs = adaptor.getFirstCircuit().front().getTerminator();
86 auto *secondOutputs = adaptor.getSecondCircuit().front().getTerminator();
87
88 if (firstOutputs->getNumOperands() == 0) {
89 // Trivially equivalent
90 Value trueVal =
91 rewriter.create<arith::ConstantOp>(loc, rewriter.getBoolAttr(true));
92 rewriter.replaceOp(op, trueVal);
93 return success();
94 }
95
96 smt::SolverOp solver =
97 rewriter.create<smt::SolverOp>(loc, rewriter.getI1Type(), ValueRange{});
98 rewriter.createBlock(&solver.getBodyRegion());
99
100 // First, convert the block arguments of the miter bodies.
101 if (failed(rewriter.convertRegionTypes(&adaptor.getFirstCircuit(),
102 *typeConverter)))
103 return failure();
104 if (failed(rewriter.convertRegionTypes(&adaptor.getSecondCircuit(),
105 *typeConverter)))
106 return failure();
107
108 // Second, create the symbolic values we replace the block arguments with
109 SmallVector<Value> inputs;
110 for (auto arg : adaptor.getFirstCircuit().getArguments())
111 inputs.push_back(rewriter.create<smt::DeclareFunOp>(loc, arg.getType()));
112
113 // Third, inline the blocks
114 // Note: the argument value replacement does not happen immediately, but
115 // only after all the operations are already legalized.
116 // Also, it has to be ensured that the original argument type and the type
117 // of the value with which is is to be replaced match. The value is looked
118 // up (transitively) in the replacement map at the time the replacement
119 // pattern is committed.
120 rewriter.mergeBlocks(&adaptor.getFirstCircuit().front(), solver.getBody(),
121 inputs);
122 rewriter.mergeBlocks(&adaptor.getSecondCircuit().front(), solver.getBody(),
123 inputs);
124 rewriter.setInsertionPointToEnd(solver.getBody());
125
126 // Fourth, convert the yielded values back to the source type system (since
127 // the operations of the inlined blocks will be converted by other patterns
128 // later on and we should make sure the IR is well-typed after each pattern
129 // application), and build the 'assert'.
130 SmallVector<Value> outputsDifferent;
131 for (auto [out1, out2] :
132 llvm::zip(firstOutputs->getOperands(), secondOutputs->getOperands())) {
133 Value o1 = typeConverter->materializeTargetConversion(
134 rewriter, loc, typeConverter->convertType(out1.getType()), out1);
135 Value o2 = typeConverter->materializeTargetConversion(
136 rewriter, loc, typeConverter->convertType(out1.getType()), out2);
137 outputsDifferent.emplace_back(
138 rewriter.create<smt::DistinctOp>(loc, o1, o2));
139 }
140
141 rewriter.eraseOp(firstOutputs);
142 rewriter.eraseOp(secondOutputs);
143
144 Value toAssert;
145 if (outputsDifferent.size() == 1)
146 toAssert = outputsDifferent[0];
147 else
148 toAssert = rewriter.create<smt::OrOp>(loc, outputsDifferent);
149
150 rewriter.create<smt::AssertOp>(loc, toAssert);
151
152 // Fifth, check for satisfiablility and report the result back.
153 Value falseVal =
154 rewriter.create<arith::ConstantOp>(loc, rewriter.getBoolAttr(false));
155 Value trueVal =
156 rewriter.create<arith::ConstantOp>(loc, rewriter.getBoolAttr(true));
157 auto checkOp = rewriter.create<smt::CheckOp>(loc, rewriter.getI1Type());
158 rewriter.createBlock(&checkOp.getSatRegion());
159 rewriter.create<smt::YieldOp>(loc, falseVal);
160 rewriter.createBlock(&checkOp.getUnknownRegion());
161 rewriter.create<smt::YieldOp>(loc, falseVal);
162 rewriter.createBlock(&checkOp.getUnsatRegion());
163 rewriter.create<smt::YieldOp>(loc, trueVal);
164 rewriter.setInsertionPointAfter(checkOp);
165 rewriter.create<smt::YieldOp>(loc, checkOp->getResults());
166
167 rewriter.replaceOp(op, solver->getResults());
168 return success();
169 }
170};
171
172/// Lower a verif::BMCOp operation to an MLIR program that performs the bounded
173/// model check
174struct VerifBoundedModelCheckingOpConversion
175 : OpConversionPattern<verif::BoundedModelCheckingOp> {
176 using OpConversionPattern<verif::BoundedModelCheckingOp>::OpConversionPattern;
177
178 VerifBoundedModelCheckingOpConversion(TypeConverter &converter,
179 MLIRContext *context, Namespace &names)
180 : OpConversionPattern(converter, context), names(names) {}
181
182 LogicalResult
183 matchAndRewrite(verif::BoundedModelCheckingOp op, OpAdaptor adaptor,
184 ConversionPatternRewriter &rewriter) const override {
185 Location loc = op.getLoc();
186 SmallVector<Type> oldLoopInputTy(op.getLoop().getArgumentTypes());
187 SmallVector<Type> oldCircuitInputTy(op.getCircuit().getArgumentTypes());
188 // TODO: the init and loop regions should be able to be concrete instead of
189 // symbolic which is probably preferable - just need to convert back and
190 // forth
191 SmallVector<Type> loopInputTy, circuitInputTy, initOutputTy,
192 circuitOutputTy;
193 if (failed(typeConverter->convertTypes(oldLoopInputTy, loopInputTy)))
194 return failure();
195 if (failed(typeConverter->convertTypes(oldCircuitInputTy, circuitInputTy)))
196 return failure();
197 if (failed(typeConverter->convertTypes(
198 op.getInit().front().back().getOperandTypes(), initOutputTy)))
199 return failure();
200 if (failed(typeConverter->convertTypes(
201 op.getCircuit().front().back().getOperandTypes(), circuitOutputTy)))
202 return failure();
203 if (failed(rewriter.convertRegionTypes(&op.getInit(), *typeConverter)))
204 return failure();
205 if (failed(rewriter.convertRegionTypes(&op.getLoop(), *typeConverter)))
206 return failure();
207 if (failed(rewriter.convertRegionTypes(&op.getCircuit(), *typeConverter)))
208 return failure();
209
210 unsigned numRegs = op.getNumRegs();
211 auto initialValues = op.getInitialValues();
212
213 auto initFuncTy = rewriter.getFunctionType({}, initOutputTy);
214 // Loop and init output types are necessarily the same, so just use init
215 // output types
216 auto loopFuncTy = rewriter.getFunctionType(loopInputTy, initOutputTy);
217 auto circuitFuncTy =
218 rewriter.getFunctionType(circuitInputTy, circuitOutputTy);
219
220 func::FuncOp initFuncOp, loopFuncOp, circuitFuncOp;
221
222 {
223 OpBuilder::InsertionGuard guard(rewriter);
224 rewriter.setInsertionPointToEnd(
225 op->getParentOfType<ModuleOp>().getBody());
226 initFuncOp = rewriter.create<func::FuncOp>(loc, names.newName("bmc_init"),
227 initFuncTy);
228 rewriter.inlineRegionBefore(op.getInit(), initFuncOp.getFunctionBody(),
229 initFuncOp.end());
230 loopFuncOp = rewriter.create<func::FuncOp>(loc, names.newName("bmc_loop"),
231 loopFuncTy);
232 rewriter.inlineRegionBefore(op.getLoop(), loopFuncOp.getFunctionBody(),
233 loopFuncOp.end());
234 circuitFuncOp = rewriter.create<func::FuncOp>(
235 loc, names.newName("bmc_circuit"), circuitFuncTy);
236 rewriter.inlineRegionBefore(op.getCircuit(),
237 circuitFuncOp.getFunctionBody(),
238 circuitFuncOp.end());
239 auto funcOps = {&initFuncOp, &loopFuncOp, &circuitFuncOp};
240 // initOutputTy is the same as loop output types
241 auto outputTys = {initOutputTy, initOutputTy, circuitOutputTy};
242 for (auto [funcOp, outputTy] : llvm::zip(funcOps, outputTys)) {
243 auto operands = funcOp->getBody().front().back().getOperands();
244 rewriter.eraseOp(&funcOp->getFunctionBody().front().back());
245 rewriter.setInsertionPointToEnd(&funcOp->getBody().front());
246 SmallVector<Value> toReturn;
247 for (unsigned i = 0; i < outputTy.size(); ++i)
248 toReturn.push_back(typeConverter->materializeTargetConversion(
249 rewriter, loc, outputTy[i], operands[i]));
250 rewriter.create<func::ReturnOp>(loc, toReturn);
251 }
252 }
253
254 auto solver =
255 rewriter.create<smt::SolverOp>(loc, rewriter.getI1Type(), ValueRange{});
256 rewriter.createBlock(&solver.getBodyRegion());
257
258 // Call init func to get initial clock values
259 ValueRange initVals =
260 rewriter.create<func::CallOp>(loc, initFuncOp)->getResults();
261
262 // Initial push
263 rewriter.create<smt::PushOp>(loc, 1);
264
265 // InputDecls order should be <circuit arguments> <state arguments>
266 // <wasViolated>
267 // Get list of clock indexes in circuit args
268 size_t initIndex = 0;
269 SmallVector<Value> inputDecls;
270 SmallVector<int> clockIndexes;
271 for (auto [curIndex, oldTy, newTy] :
272 llvm::enumerate(oldCircuitInputTy, circuitInputTy)) {
273 if (isa<seq::ClockType>(oldTy)) {
274 inputDecls.push_back(initVals[initIndex++]);
275 clockIndexes.push_back(curIndex);
276 continue;
277 }
278 if (curIndex >= oldCircuitInputTy.size() - numRegs) {
279 auto initVal =
280 initialValues[curIndex - oldCircuitInputTy.size() + numRegs];
281 if (auto initIntAttr = dyn_cast<IntegerAttr>(initVal)) {
282 inputDecls.push_back(rewriter.create<smt::BVConstantOp>(
283 loc, initIntAttr.getValue().getSExtValue(),
284 cast<smt::BitVectorType>(newTy).getWidth()));
285 continue;
286 }
287 }
288 inputDecls.push_back(rewriter.create<smt::DeclareFunOp>(loc, newTy));
289 }
290
291 auto numStateArgs = initVals.size() - initIndex;
292 // Add the rest of the init vals (state args)
293 for (; initIndex < initVals.size(); ++initIndex)
294 inputDecls.push_back(initVals[initIndex]);
295
296 Value lowerBound =
297 rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(0));
298 Value step =
299 rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(1));
300 Value upperBound =
301 rewriter.create<arith::ConstantOp>(loc, adaptor.getBoundAttr());
302 Value constFalse =
303 rewriter.create<arith::ConstantOp>(loc, rewriter.getBoolAttr(false));
304 Value constTrue =
305 rewriter.create<arith::ConstantOp>(loc, rewriter.getBoolAttr(true));
306 inputDecls.push_back(constFalse); // wasViolated?
307
308 // TODO: swapping to a whileOp here would allow early exit once the property
309 // is violated
310 // Perform model check up to the provided bound
311 auto forOp = rewriter.create<scf::ForOp>(
312 loc, lowerBound, upperBound, step, inputDecls,
313 [&](OpBuilder &builder, Location loc, Value i, ValueRange iterArgs) {
314 // Drop existing assertions
315 builder.create<smt::PopOp>(loc, 1);
316 builder.create<smt::PushOp>(loc, 1);
317
318 // Execute the circuit
319 ValueRange circuitCallOuts =
320 builder
321 .create<func::CallOp>(
322 loc, circuitFuncOp,
323 iterArgs.take_front(circuitFuncOp.getNumArguments()))
324 ->getResults();
325 auto checkOp =
326 rewriter.create<smt::CheckOp>(loc, builder.getI1Type());
327 {
328 OpBuilder::InsertionGuard guard(builder);
329 builder.createBlock(&checkOp.getSatRegion());
330 builder.create<smt::YieldOp>(loc, constTrue);
331 builder.createBlock(&checkOp.getUnknownRegion());
332 builder.create<smt::YieldOp>(loc, constTrue);
333 builder.createBlock(&checkOp.getUnsatRegion());
334 builder.create<smt::YieldOp>(loc, constFalse);
335 }
336
337 Value violated = builder.create<arith::OrIOp>(
338 loc, checkOp.getResult(0), iterArgs.back());
339
340 // Call loop func to update clock & state arg values
341 SmallVector<Value> loopCallInputs;
342 // Fetch clock values to feed to loop
343 for (auto index : clockIndexes)
344 loopCallInputs.push_back(iterArgs[index]);
345 // Fetch state args to feed to loop
346 for (auto stateArg : iterArgs.drop_back().take_back(numStateArgs))
347 loopCallInputs.push_back(stateArg);
348 ValueRange loopVals =
349 builder.create<func::CallOp>(loc, loopFuncOp, loopCallInputs)
350 ->getResults();
351
352 size_t loopIndex = 0;
353 // Collect decls to yield at end of iteration
354 SmallVector<Value> newDecls;
355 for (auto [oldTy, newTy] :
356 llvm::zip(TypeRange(oldCircuitInputTy).drop_back(numRegs),
357 TypeRange(circuitInputTy).drop_back(numRegs))) {
358 if (isa<seq::ClockType>(oldTy))
359 newDecls.push_back(loopVals[loopIndex++]);
360 else
361 newDecls.push_back(builder.create<smt::DeclareFunOp>(loc, newTy));
362 }
363
364 // Only update the registers on a clock posedge
365 // TODO: this will also need changing with multiple clocks - currently
366 // it only accounts for the one clock case.
367 if (clockIndexes.size() == 1) {
368 auto clockIndex = clockIndexes[0];
369 auto oldClock = iterArgs[clockIndex];
370 // The clock is necessarily the first value returned by the loop
371 // region
372 auto newClock = loopVals[0];
373 auto oldClockLow = builder.create<smt::BVNotOp>(loc, oldClock);
374 auto isPosedgeBV =
375 builder.create<smt::BVAndOp>(loc, oldClockLow, newClock);
376 // Convert posedge bv<1> to bool
377 auto trueBV = builder.create<smt::BVConstantOp>(loc, 1, 1);
378 auto isPosedge =
379 builder.create<smt::EqOp>(loc, isPosedgeBV, trueBV);
380 auto regStates =
381 iterArgs.take_front(circuitFuncOp.getNumArguments())
382 .take_back(numRegs);
383 auto regInputs = circuitCallOuts.take_back(numRegs);
384 SmallVector<Value> nextRegStates;
385 for (auto [regState, regInput] : llvm::zip(regStates, regInputs)) {
386 // Create an ITE to calculate the next reg state
387 // TODO: we create a lot of ITEs here that will slow things down -
388 // these could be avoided by making init/loop regions concrete
389 nextRegStates.push_back(builder.create<smt::IteOp>(
390 loc, isPosedge, regInput, regState));
391 }
392 newDecls.append(nextRegStates);
393 }
394
395 // Add the rest of the loop state args
396 for (; loopIndex < loopVals.size(); ++loopIndex)
397 newDecls.push_back(loopVals[loopIndex]);
398
399 newDecls.push_back(violated);
400
401 builder.create<scf::YieldOp>(loc, newDecls);
402 });
403
404 Value res = rewriter.create<arith::XOrIOp>(loc, forOp->getResults().back(),
405 constTrue);
406 rewriter.create<smt::YieldOp>(loc, res);
407 rewriter.replaceOp(op, solver.getResults());
408 return success();
409 }
410
411 Namespace &names;
412};
413
414} // namespace
415
416//===----------------------------------------------------------------------===//
417// Convert Verif to SMT pass
418//===----------------------------------------------------------------------===//
419
420namespace {
421struct ConvertVerifToSMTPass
422 : public circt::impl::ConvertVerifToSMTBase<ConvertVerifToSMTPass> {
423 void runOnOperation() override;
424};
425} // namespace
426
428 RewritePatternSet &patterns,
429 Namespace &names) {
430 patterns.add<VerifAssertOpConversion, VerifAssumeOpConversion,
431 LogicEquivalenceCheckingOpConversion>(converter,
432 patterns.getContext());
433 patterns.add<VerifBoundedModelCheckingOpConversion>(
434 converter, patterns.getContext(), names);
435}
436
437void ConvertVerifToSMTPass::runOnOperation() {
438 ConversionTarget target(getContext());
439 target.addIllegalDialect<verif::VerifDialect>();
440 target.addLegalDialect<smt::SMTDialect, arith::ArithDialect, scf::SCFDialect,
441 func::FuncDialect>();
442 target.addLegalOp<UnrealizedConversionCastOp>();
443
444 // Check BMC ops contain only one assertion (done outside pattern to avoid
445 // issues with whether assertions are/aren't lowered yet)
446 SymbolTable symbolTable(getOperation());
447 WalkResult assertionCheck = getOperation().walk(
448 [&](Operation *op) { // Check there is exactly one assertion and clock
449 if (auto bmcOp = dyn_cast<verif::BoundedModelCheckingOp>(op)) {
450 // We also currently don't support initial values on registers that
451 // don't have integer inputs.
452 auto regTypes = TypeRange(bmcOp.getCircuit().getArgumentTypes())
453 .take_back(bmcOp.getNumRegs());
454 for (auto [regType, initVal] :
455 llvm::zip(regTypes, bmcOp.getInitialValues())) {
456 if (!isa<IntegerType>(regType) && !isa<UnitAttr>(initVal)) {
457 op->emitError("initial values are currently only supported for "
458 "registers with integer types");
459 signalPassFailure();
460 return WalkResult::interrupt();
461 }
462 }
463 // Check only one clock is present in the circuit inputs
464 auto numClockArgs = 0;
465 for (auto argType : bmcOp.getCircuit().getArgumentTypes())
466 if (isa<seq::ClockType>(argType))
467 numClockArgs++;
468 // TODO: this can be removed once we have a way to associate reg
469 // ins/outs with clocks
470 if (numClockArgs > 1) {
471 op->emitError(
472 "only modules with one or zero clocks are currently supported");
473 return WalkResult::interrupt();
474 }
475 SmallVector<mlir::Operation *> worklist;
476 int numAssertions = 0;
477 op->walk([&](Operation *curOp) {
478 if (isa<verif::AssertOp>(curOp))
479 numAssertions++;
480 if (auto inst = dyn_cast<InstanceOp>(curOp))
481 worklist.push_back(symbolTable.lookup(inst.getModuleName()));
482 });
483 // TODO: probably negligible compared to actual model checking time
484 // but cacheing the assertion count of modules would speed this up
485 while (!worklist.empty()) {
486 auto *module = worklist.pop_back_val();
487 module->walk([&](Operation *curOp) {
488 if (isa<verif::AssertOp>(curOp))
489 numAssertions++;
490 if (auto inst = dyn_cast<InstanceOp>(curOp))
491 worklist.push_back(symbolTable.lookup(inst.getModuleName()));
492 });
493 if (numAssertions > 1)
494 break;
495 }
496 if (numAssertions > 1) {
497 op->emitError(
498 "bounded model checking problems with multiple assertions are "
499 "not yet "
500 "correctly handled - instead, you can assert the "
501 "conjunction of your assertions");
502 return WalkResult::interrupt();
503 }
504 }
505 return WalkResult::advance();
506 });
507 if (assertionCheck.wasInterrupted())
508 return signalPassFailure();
509 RewritePatternSet patterns(&getContext());
510 TypeConverter converter;
512
513 SymbolCache symCache;
514 symCache.addDefinitions(getOperation());
515 Namespace names;
516 names.add(symCache);
517
519
520 if (failed(mlir::applyPartialConversion(getOperation(), target,
521 std::move(patterns))))
522 return signalPassFailure();
523}
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)
Get the Verif to SMT conversion patterns.
void populateHWToSMTTypeConverter(TypeConverter &converter)
Get the HW to SMT type conversions.
Definition HWToSMT.cpp:181
Definition hw.py:1
Definition seq.py:1
Definition smt.py:1