CIRCT  20.0.0git
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 
25 namespace circt {
26 #define GEN_PASS_DEF_CONVERTVERIFTOSMT
27 #include "circt/Conversion/Passes.h.inc"
28 } // namespace circt
29 
30 using namespace mlir;
31 using namespace circt;
32 using namespace hw;
33 
34 //===----------------------------------------------------------------------===//
35 // Conversion patterns
36 //===----------------------------------------------------------------------===//
37 
38 namespace {
39 /// Lower a verif::AssertOp operation with an i1 operand to a smt::AssertOp,
40 /// negated to check for unsatisfiability.
41 struct VerifAssertOpConversion : OpConversionPattern<verif::AssertOp> {
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
57 struct VerifAssumeOpConversion : OpConversionPattern<verif::AssumeOp> {
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.
76 struct LogicEquivalenceCheckingOpConversion
77  : OpConversionPattern<verif::LogicEquivalenceCheckingOp> {
78  using OpConversionPattern<
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
174 struct VerifBoundedModelCheckingOpConversion
175  : OpConversionPattern<verif::BoundedModelCheckingOp> {
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  auto newClock = loopVals[clockIndex];
371  auto oldClockLow = builder.create<smt::BVNotOp>(loc, oldClock);
372  auto isPosedgeBV =
373  builder.create<smt::BVAndOp>(loc, oldClockLow, newClock);
374  // Convert posedge bv<1> to bool
375  auto trueBV = builder.create<smt::BVConstantOp>(loc, 1, 1);
376  auto isPosedge =
377  builder.create<smt::EqOp>(loc, isPosedgeBV, trueBV);
378  auto regStates =
379  iterArgs.take_front(circuitFuncOp.getNumArguments())
380  .take_back(numRegs);
381  auto regInputs = circuitCallOuts.take_back(numRegs);
382  SmallVector<Value> nextRegStates;
383  for (auto [regState, regInput] : llvm::zip(regStates, regInputs)) {
384  // Create an ITE to calculate the next reg state
385  // TODO: we create a lot of ITEs here that will slow things down -
386  // these could be avoided by making init/loop regions concrete
387  nextRegStates.push_back(builder.create<smt::IteOp>(
388  loc, isPosedge, regInput, regState));
389  }
390  newDecls.append(nextRegStates);
391  }
392 
393  // Add the rest of the loop state args
394  for (; loopIndex < loopVals.size(); ++loopIndex)
395  newDecls.push_back(loopVals[loopIndex]);
396 
397  newDecls.push_back(violated);
398 
399  builder.create<scf::YieldOp>(loc, newDecls);
400  });
401 
402  Value res = rewriter.create<arith::XOrIOp>(loc, forOp->getResults().back(),
403  constTrue);
404  rewriter.create<smt::YieldOp>(loc, res);
405  rewriter.replaceOp(op, solver.getResults());
406  return success();
407  }
408 
409  Namespace &names;
410 };
411 
412 } // namespace
413 
414 //===----------------------------------------------------------------------===//
415 // Convert Verif to SMT pass
416 //===----------------------------------------------------------------------===//
417 
418 namespace {
419 struct ConvertVerifToSMTPass
420  : public circt::impl::ConvertVerifToSMTBase<ConvertVerifToSMTPass> {
421  void runOnOperation() override;
422 };
423 } // namespace
424 
425 void circt::populateVerifToSMTConversionPatterns(TypeConverter &converter,
426  RewritePatternSet &patterns,
427  Namespace &names) {
428  patterns.add<VerifAssertOpConversion, VerifAssumeOpConversion,
429  LogicEquivalenceCheckingOpConversion>(converter,
430  patterns.getContext());
431  patterns.add<VerifBoundedModelCheckingOpConversion>(
432  converter, patterns.getContext(), names);
433 }
434 
435 void ConvertVerifToSMTPass::runOnOperation() {
436  ConversionTarget target(getContext());
437  target.addIllegalDialect<verif::VerifDialect>();
438  target.addLegalDialect<smt::SMTDialect, arith::ArithDialect, scf::SCFDialect,
439  func::FuncDialect>();
440  target.addLegalOp<UnrealizedConversionCastOp>();
441 
442  // Check BMC ops contain only one assertion (done outside pattern to avoid
443  // issues with whether assertions are/aren't lowered yet)
444  SymbolTable symbolTable(getOperation());
445  WalkResult assertionCheck = getOperation().walk(
446  [&](Operation *op) { // Check there is exactly one assertion and clock
447  if (auto bmcOp = dyn_cast<verif::BoundedModelCheckingOp>(op)) {
448  // We also currently don't support initial values on registers that
449  // don't have integer inputs.
450  auto regTypes = TypeRange(bmcOp.getCircuit().getArgumentTypes())
451  .take_back(bmcOp.getNumRegs());
452  for (auto [regType, initVal] :
453  llvm::zip(regTypes, bmcOp.getInitialValues())) {
454  if (!isa<IntegerType>(regType) && !isa<UnitAttr>(initVal)) {
455  op->emitError("initial values are currently only supported for "
456  "registers with integer types");
457  signalPassFailure();
458  return WalkResult::interrupt();
459  }
460  }
461  // Check only one clock is present in the circuit inputs
462  auto numClockArgs = 0;
463  for (auto argType : bmcOp.getCircuit().getArgumentTypes())
464  if (isa<seq::ClockType>(argType))
465  numClockArgs++;
466  // TODO: this can be removed once we have a way to associate reg
467  // ins/outs with clocks
468  if (numClockArgs > 1) {
469  op->emitError(
470  "only modules with one or zero clocks are currently supported");
471  return WalkResult::interrupt();
472  }
473  SmallVector<mlir::Operation *> worklist;
474  int numAssertions = 0;
475  op->walk([&](Operation *curOp) {
476  if (isa<verif::AssertOp>(curOp))
477  numAssertions++;
478  if (auto inst = dyn_cast<InstanceOp>(curOp))
479  worklist.push_back(symbolTable.lookup(inst.getModuleName()));
480  });
481  // TODO: probably negligible compared to actual model checking time
482  // but cacheing the assertion count of modules would speed this up
483  while (!worklist.empty()) {
484  auto *module = worklist.pop_back_val();
485  module->walk([&](Operation *curOp) {
486  if (isa<verif::AssertOp>(curOp))
487  numAssertions++;
488  if (auto inst = dyn_cast<InstanceOp>(curOp))
489  worklist.push_back(symbolTable.lookup(inst.getModuleName()));
490  });
491  if (numAssertions > 1)
492  break;
493  }
494  if (numAssertions > 1) {
495  op->emitError(
496  "bounded model checking problems with multiple assertions are "
497  "not yet "
498  "correctly handled - instead, you can assert the "
499  "conjunction of your assertions");
500  return WalkResult::interrupt();
501  }
502  }
503  return WalkResult::advance();
504  });
505  if (assertionCheck.wasInterrupted())
506  return signalPassFailure();
507  RewritePatternSet patterns(&getContext());
508  TypeConverter converter;
509  populateHWToSMTTypeConverter(converter);
510 
511  SymbolCache symCache;
512  symCache.addDefinitions(getOperation());
513  Namespace names;
514  names.add(symCache);
515 
517 
518  if (failed(mlir::applyPartialConversion(getOperation(), target,
519  std::move(patterns))))
520  return signalPassFailure();
521 }
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
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
void populateVerifToSMTConversionPatterns(TypeConverter &converter, RewritePatternSet &patterns, Namespace &names)
Get the Verif to SMT conversion patterns.
Definition: VerifToSMT.cpp:425
void populateHWToSMTTypeConverter(TypeConverter &converter)
Get the HW to SMT type conversions.
Definition: HWToSMT.cpp:181
Definition: hw.py:1