Loading [MathJax]/extensions/tex2jax.js
CIRCT 21.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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
14#include "mlir/Conversion/ReconcileUnrealizedCasts/ReconcileUnrealizedCasts.h"
15#include "mlir/Dialect/Arith/IR/Arith.h"
16#include "mlir/Dialect/Func/IR/FuncOps.h"
17#include "mlir/Dialect/SCF/IR/SCF.h"
18#include "mlir/Dialect/SMT/IR/SMTOps.h"
19#include "mlir/Dialect/SMT/IR/SMTTypes.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 bool risingClocksOnly)
181 : OpConversionPattern(converter, context), names(names),
182 risingClocksOnly(risingClocksOnly) {}
183 LogicalResult
184 matchAndRewrite(verif::BoundedModelCheckingOp op, OpAdaptor adaptor,
185 ConversionPatternRewriter &rewriter) const override {
186 Location loc = op.getLoc();
187 SmallVector<Type> oldLoopInputTy(op.getLoop().getArgumentTypes());
188 SmallVector<Type> oldCircuitInputTy(op.getCircuit().getArgumentTypes());
189 // TODO: the init and loop regions should be able to be concrete instead of
190 // symbolic which is probably preferable - just need to convert back and
191 // forth
192 SmallVector<Type> loopInputTy, circuitInputTy, initOutputTy,
193 circuitOutputTy;
194 if (failed(typeConverter->convertTypes(oldLoopInputTy, loopInputTy)))
195 return failure();
196 if (failed(typeConverter->convertTypes(oldCircuitInputTy, circuitInputTy)))
197 return failure();
198 if (failed(typeConverter->convertTypes(
199 op.getInit().front().back().getOperandTypes(), initOutputTy)))
200 return failure();
201 if (failed(typeConverter->convertTypes(
202 op.getCircuit().front().back().getOperandTypes(), circuitOutputTy)))
203 return failure();
204 if (failed(rewriter.convertRegionTypes(&op.getInit(), *typeConverter)))
205 return failure();
206 if (failed(rewriter.convertRegionTypes(&op.getLoop(), *typeConverter)))
207 return failure();
208 if (failed(rewriter.convertRegionTypes(&op.getCircuit(), *typeConverter)))
209 return failure();
210
211 unsigned numRegs = op.getNumRegs();
212 auto initialValues = op.getInitialValues();
213
214 auto initFuncTy = rewriter.getFunctionType({}, initOutputTy);
215 // Loop and init output types are necessarily the same, so just use init
216 // output types
217 auto loopFuncTy = rewriter.getFunctionType(loopInputTy, initOutputTy);
218 auto circuitFuncTy =
219 rewriter.getFunctionType(circuitInputTy, circuitOutputTy);
220
221 func::FuncOp initFuncOp, loopFuncOp, circuitFuncOp;
222
223 {
224 OpBuilder::InsertionGuard guard(rewriter);
225 rewriter.setInsertionPointToEnd(
226 op->getParentOfType<ModuleOp>().getBody());
227 initFuncOp = rewriter.create<func::FuncOp>(loc, names.newName("bmc_init"),
228 initFuncTy);
229 rewriter.inlineRegionBefore(op.getInit(), initFuncOp.getFunctionBody(),
230 initFuncOp.end());
231 loopFuncOp = rewriter.create<func::FuncOp>(loc, names.newName("bmc_loop"),
232 loopFuncTy);
233 rewriter.inlineRegionBefore(op.getLoop(), loopFuncOp.getFunctionBody(),
234 loopFuncOp.end());
235 circuitFuncOp = rewriter.create<func::FuncOp>(
236 loc, names.newName("bmc_circuit"), circuitFuncTy);
237 rewriter.inlineRegionBefore(op.getCircuit(),
238 circuitFuncOp.getFunctionBody(),
239 circuitFuncOp.end());
240 auto funcOps = {&initFuncOp, &loopFuncOp, &circuitFuncOp};
241 // initOutputTy is the same as loop output types
242 auto outputTys = {initOutputTy, initOutputTy, circuitOutputTy};
243 for (auto [funcOp, outputTy] : llvm::zip(funcOps, outputTys)) {
244 auto operands = funcOp->getBody().front().back().getOperands();
245 rewriter.eraseOp(&funcOp->getFunctionBody().front().back());
246 rewriter.setInsertionPointToEnd(&funcOp->getBody().front());
247 SmallVector<Value> toReturn;
248 for (unsigned i = 0; i < outputTy.size(); ++i)
249 toReturn.push_back(typeConverter->materializeTargetConversion(
250 rewriter, loc, outputTy[i], operands[i]));
251 rewriter.create<func::ReturnOp>(loc, toReturn);
252 }
253 }
254
255 auto solver =
256 rewriter.create<smt::SolverOp>(loc, rewriter.getI1Type(), ValueRange{});
257 rewriter.createBlock(&solver.getBodyRegion());
258
259 // Call init func to get initial clock values
260 ValueRange initVals =
261 rewriter.create<func::CallOp>(loc, initFuncOp)->getResults();
262
263 // Initial push
264 rewriter.create<smt::PushOp>(loc, 1);
265
266 // InputDecls order should be <circuit arguments> <state arguments>
267 // <wasViolated>
268 // Get list of clock indexes in circuit args
269 size_t initIndex = 0;
270 SmallVector<Value> inputDecls;
271 SmallVector<int> clockIndexes;
272 for (auto [curIndex, oldTy, newTy] :
273 llvm::enumerate(oldCircuitInputTy, circuitInputTy)) {
274 if (isa<seq::ClockType>(oldTy)) {
275 inputDecls.push_back(initVals[initIndex++]);
276 clockIndexes.push_back(curIndex);
277 continue;
278 }
279 if (curIndex >= oldCircuitInputTy.size() - numRegs) {
280 auto initVal =
281 initialValues[curIndex - oldCircuitInputTy.size() + numRegs];
282 if (auto initIntAttr = dyn_cast<IntegerAttr>(initVal)) {
283 inputDecls.push_back(rewriter.create<smt::BVConstantOp>(
284 loc, initIntAttr.getValue().getSExtValue(),
285 cast<smt::BitVectorType>(newTy).getWidth()));
286 continue;
287 }
288 }
289 inputDecls.push_back(rewriter.create<smt::DeclareFunOp>(loc, newTy));
290 }
291
292 auto numStateArgs = initVals.size() - initIndex;
293 // Add the rest of the init vals (state args)
294 for (; initIndex < initVals.size(); ++initIndex)
295 inputDecls.push_back(initVals[initIndex]);
296
297 Value lowerBound =
298 rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(0));
299 Value step =
300 rewriter.create<arith::ConstantOp>(loc, rewriter.getI32IntegerAttr(1));
301 Value upperBound =
302 rewriter.create<arith::ConstantOp>(loc, adaptor.getBoundAttr());
303 Value constFalse =
304 rewriter.create<arith::ConstantOp>(loc, rewriter.getBoolAttr(false));
305 Value constTrue =
306 rewriter.create<arith::ConstantOp>(loc, rewriter.getBoolAttr(true));
307 inputDecls.push_back(constFalse); // wasViolated?
308
309 // TODO: swapping to a whileOp here would allow early exit once the property
310 // is violated
311 // Perform model check up to the provided bound
312 auto forOp = rewriter.create<scf::ForOp>(
313 loc, lowerBound, upperBound, step, inputDecls,
314 [&](OpBuilder &builder, Location loc, Value i, ValueRange iterArgs) {
315 // Drop existing assertions
316 builder.create<smt::PopOp>(loc, 1);
317 builder.create<smt::PushOp>(loc, 1);
318
319 // Execute the circuit
320 ValueRange circuitCallOuts =
321 builder
322 .create<func::CallOp>(
323 loc, circuitFuncOp,
324 iterArgs.take_front(circuitFuncOp.getNumArguments()))
325 ->getResults();
326 auto checkOp =
327 rewriter.create<smt::CheckOp>(loc, builder.getI1Type());
328 {
329 OpBuilder::InsertionGuard guard(builder);
330 builder.createBlock(&checkOp.getSatRegion());
331 builder.create<smt::YieldOp>(loc, constTrue);
332 builder.createBlock(&checkOp.getUnknownRegion());
333 builder.create<smt::YieldOp>(loc, constTrue);
334 builder.createBlock(&checkOp.getUnsatRegion());
335 builder.create<smt::YieldOp>(loc, constFalse);
336 }
337
338 Value violated = builder.create<arith::OrIOp>(
339 loc, checkOp.getResult(0), iterArgs.back());
340
341 // Call loop func to update clock & state arg values
342 SmallVector<Value> loopCallInputs;
343 // Fetch clock values to feed to loop
344 for (auto index : clockIndexes)
345 loopCallInputs.push_back(iterArgs[index]);
346 // Fetch state args to feed to loop
347 for (auto stateArg : iterArgs.drop_back().take_back(numStateArgs))
348 loopCallInputs.push_back(stateArg);
349 ValueRange loopVals =
350 builder.create<func::CallOp>(loc, loopFuncOp, loopCallInputs)
351 ->getResults();
352
353 size_t loopIndex = 0;
354 // Collect decls to yield at end of iteration
355 SmallVector<Value> newDecls;
356 for (auto [oldTy, newTy] :
357 llvm::zip(TypeRange(oldCircuitInputTy).drop_back(numRegs),
358 TypeRange(circuitInputTy).drop_back(numRegs))) {
359 if (isa<seq::ClockType>(oldTy))
360 newDecls.push_back(loopVals[loopIndex++]);
361 else
362 newDecls.push_back(builder.create<smt::DeclareFunOp>(loc, newTy));
363 }
364
365 // Only update the registers on a clock posedge unless in rising
366 // clocks only mode
367 // TODO: this will also need changing with multiple clocks - currently
368 // it only accounts for the one clock case.
369 if (clockIndexes.size() == 1) {
370 SmallVector<Value> regInputs = circuitCallOuts.take_back(numRegs);
371 if (risingClocksOnly) {
372 // In rising clocks only mode we don't need to worry about whether
373 // there was a posedge
374 newDecls.append(regInputs);
375 } else {
376 auto clockIndex = clockIndexes[0];
377 auto oldClock = iterArgs[clockIndex];
378 // The clock is necessarily the first value returned by the loop
379 // region
380 auto newClock = loopVals[0];
381 auto oldClockLow = builder.create<smt::BVNotOp>(loc, oldClock);
382 auto isPosedgeBV =
383 builder.create<smt::BVAndOp>(loc, oldClockLow, newClock);
384 // Convert posedge bv<1> to bool
385 auto trueBV = builder.create<smt::BVConstantOp>(loc, 1, 1);
386 auto isPosedge =
387 builder.create<smt::EqOp>(loc, isPosedgeBV, trueBV);
388 auto regStates =
389 iterArgs.take_front(circuitFuncOp.getNumArguments())
390 .take_back(numRegs);
391 SmallVector<Value> nextRegStates;
392 for (auto [regState, regInput] :
393 llvm::zip(regStates, regInputs)) {
394 // Create an ITE to calculate the next reg state
395 // TODO: we create a lot of ITEs here that will slow things down
396 // - these could be avoided by making init/loop regions concrete
397 nextRegStates.push_back(builder.create<smt::IteOp>(
398 loc, isPosedge, regInput, regState));
399 }
400 newDecls.append(nextRegStates);
401 }
402 }
403
404 // Add the rest of the loop state args
405 for (; loopIndex < loopVals.size(); ++loopIndex)
406 newDecls.push_back(loopVals[loopIndex]);
407
408 newDecls.push_back(violated);
409
410 builder.create<scf::YieldOp>(loc, newDecls);
411 });
412
413 Value res = rewriter.create<arith::XOrIOp>(loc, forOp->getResults().back(),
414 constTrue);
415 rewriter.create<smt::YieldOp>(loc, res);
416 rewriter.replaceOp(op, solver.getResults());
417 return success();
418 }
419
420 Namespace &names;
421 bool risingClocksOnly;
422};
423
424} // namespace
425
426//===----------------------------------------------------------------------===//
427// Convert Verif to SMT pass
428//===----------------------------------------------------------------------===//
429
430namespace {
431struct ConvertVerifToSMTPass
432 : public circt::impl::ConvertVerifToSMTBase<ConvertVerifToSMTPass> {
433 using Base::Base;
434 void runOnOperation() override;
435};
436} // namespace
437
439 RewritePatternSet &patterns,
440 Namespace &names,
441 bool risingClocksOnly) {
442 patterns.add<VerifAssertOpConversion, VerifAssumeOpConversion,
443 LogicEquivalenceCheckingOpConversion>(converter,
444 patterns.getContext());
445 patterns.add<VerifBoundedModelCheckingOpConversion>(
446 converter, patterns.getContext(), names, risingClocksOnly);
447}
448
449void ConvertVerifToSMTPass::runOnOperation() {
450 ConversionTarget target(getContext());
451 target.addIllegalDialect<verif::VerifDialect>();
452 target.addLegalDialect<smt::SMTDialect, arith::ArithDialect, scf::SCFDialect,
453 func::FuncDialect>();
454 target.addLegalOp<UnrealizedConversionCastOp>();
455
456 // Check BMC ops contain only one assertion (done outside pattern to avoid
457 // issues with whether assertions are/aren't lowered yet)
458 SymbolTable symbolTable(getOperation());
459 WalkResult assertionCheck = getOperation().walk(
460 [&](Operation *op) { // Check there is exactly one assertion and clock
461 if (auto bmcOp = dyn_cast<verif::BoundedModelCheckingOp>(op)) {
462 // We also currently don't support initial values on registers that
463 // don't have integer inputs.
464 auto regTypes = TypeRange(bmcOp.getCircuit().getArgumentTypes())
465 .take_back(bmcOp.getNumRegs());
466 for (auto [regType, initVal] :
467 llvm::zip(regTypes, bmcOp.getInitialValues())) {
468 if (!isa<IntegerType>(regType) && !isa<UnitAttr>(initVal)) {
469 op->emitError("initial values are currently only supported for "
470 "registers with integer types");
471 signalPassFailure();
472 return WalkResult::interrupt();
473 }
474 }
475 // Check only one clock is present in the circuit inputs
476 auto numClockArgs = 0;
477 for (auto argType : bmcOp.getCircuit().getArgumentTypes())
478 if (isa<seq::ClockType>(argType))
479 numClockArgs++;
480 // TODO: this can be removed once we have a way to associate reg
481 // ins/outs with clocks
482 if (numClockArgs > 1) {
483 op->emitError(
484 "only modules with one or zero clocks are currently supported");
485 return WalkResult::interrupt();
486 }
487 SmallVector<mlir::Operation *> worklist;
488 int numAssertions = 0;
489 op->walk([&](Operation *curOp) {
490 if (isa<verif::AssertOp>(curOp))
491 numAssertions++;
492 if (auto inst = dyn_cast<InstanceOp>(curOp))
493 worklist.push_back(symbolTable.lookup(inst.getModuleName()));
494 });
495 // TODO: probably negligible compared to actual model checking time
496 // but cacheing the assertion count of modules would speed this up
497 while (!worklist.empty()) {
498 auto *module = worklist.pop_back_val();
499 module->walk([&](Operation *curOp) {
500 if (isa<verif::AssertOp>(curOp))
501 numAssertions++;
502 if (auto inst = dyn_cast<InstanceOp>(curOp))
503 worklist.push_back(symbolTable.lookup(inst.getModuleName()));
504 });
505 if (numAssertions > 1)
506 break;
507 }
508 if (numAssertions > 1) {
509 op->emitError(
510 "bounded model checking problems with multiple assertions are "
511 "not yet "
512 "correctly handled - instead, you can assert the "
513 "conjunction of your assertions");
514 return WalkResult::interrupt();
515 }
516 }
517 return WalkResult::advance();
518 });
519 if (assertionCheck.wasInterrupted())
520 return signalPassFailure();
521 RewritePatternSet patterns(&getContext());
522 TypeConverter converter;
524
525 SymbolCache symCache;
526 symCache.addDefinitions(getOperation());
527 Namespace names;
528 names.add(symCache);
529
531 risingClocksOnly);
532
533 if (failed(mlir::applyPartialConversion(getOperation(), target,
534 std::move(patterns))))
535 return signalPassFailure();
536}
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)
Get the Verif to SMT conversion patterns.
void populateHWToSMTTypeConverter(TypeConverter &converter)
Get the HW to SMT type conversions.
Definition HWToSMT.cpp:184
Definition hw.py:1
Definition seq.py:1