CIRCT 22.0.0git
Loading...
Searching...
No Matches
LowerToBMC.cpp
Go to the documentation of this file.
1//===- LowerToBMC.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 "circt/Support/LLVM.h"
18#include "mlir/Analysis/TopologicalSortUtils.h"
19#include "mlir/Dialect/Func/IR/FuncOps.h"
20#include "mlir/Dialect/LLVMIR/FunctionCallUtils.h"
21#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
22#include "mlir/IR/Builders.h"
23#include "mlir/IR/Location.h"
24#include "mlir/IR/SymbolTable.h"
25#include "llvm/Support/LogicalResult.h"
26
27using namespace mlir;
28using namespace circt;
29using namespace hw;
30
31namespace circt {
32#define GEN_PASS_DEF_LOWERTOBMC
33#include "circt/Tools/circt-bmc/Passes.h.inc"
34} // namespace circt
35
36//===----------------------------------------------------------------------===//
37// Convert Lower To BMC pass
38//===----------------------------------------------------------------------===//
39
40namespace {
41struct LowerToBMCPass : public circt::impl::LowerToBMCBase<LowerToBMCPass> {
42 using LowerToBMCBase::LowerToBMCBase;
43 void runOnOperation() override;
44};
45} // namespace
46
47void LowerToBMCPass::runOnOperation() {
48 Namespace names;
49 // Fetch the 'hw.module' operation to model check.
50 auto moduleOp = getOperation();
51 auto hwModule = moduleOp.lookupSymbol<hw::HWModuleOp>(topModule);
52 if (!hwModule) {
53 moduleOp.emitError("hw.module named '") << topModule << "' not found";
54 return signalPassFailure();
55 }
56
57 // TODO: Check whether instances contain properties to check
58 bool noProperties = false;
59 if (hwModule.getOps<verif::AssertOp>().empty() &&
60 hwModule.getOps<hw::InstanceOp>().empty()) {
61 hwModule.emitWarning("no property provided to check in module - will "
62 "trivially find no assertions.");
63 noProperties = true;
64 }
65
66 if (!sortTopologically(&hwModule.getBodyRegion().front())) {
67 hwModule->emitError("could not resolve cycles in module");
68 return signalPassFailure();
69 }
70
71 if (bound < ignoreAssertionsUntil) {
72 hwModule->emitError(
73 "number of ignored cycles must be less than or equal to bound");
74 return signalPassFailure();
75 }
76
77 // Create necessary function declarations and globals
78 auto *ctx = &getContext();
79 OpBuilder builder(ctx);
80 Location loc = moduleOp->getLoc();
81 builder.setInsertionPointToEnd(moduleOp.getBody());
82 auto ptrTy = LLVM::LLVMPointerType::get(ctx);
83 auto voidTy = LLVM::LLVMVoidType::get(ctx);
84
85 // Lookup or declare printf function.
86 auto printfFunc =
87 LLVM::lookupOrCreateFn(builder, moduleOp, "printf", ptrTy, voidTy, true);
88 if (failed(printfFunc)) {
89 moduleOp->emitError("failed to lookup or create printf");
90 return signalPassFailure();
91 }
92
93 // Replace the top-module with a function performing the BMC
94 auto entryFunc = func::FuncOp::create(builder, loc, topModule,
95 builder.getFunctionType({}, {}));
96 builder.createBlock(&entryFunc.getBody());
97
98 {
99 OpBuilder::InsertionGuard guard(builder);
100 auto *terminator = hwModule.getBody().front().getTerminator();
101 builder.setInsertionPoint(terminator);
102 verif::YieldOp::create(builder, loc, terminator->getOperands());
103 terminator->erase();
104 }
105
106 // Double the bound given to the BMC op unless in rising clocks only mode, as
107 // a clock cycle involves two negations
108 verif::BoundedModelCheckingOp bmcOp;
109 auto numRegs = hwModule->getAttrOfType<IntegerAttr>("num_regs");
110 auto initialValues = hwModule->getAttrOfType<ArrayAttr>("initial_values");
111 if (numRegs && initialValues) {
112 for (auto value : initialValues) {
113 if (!isa<IntegerAttr, UnitAttr>(value)) {
114 hwModule->emitError("initial_values attribute must contain only "
115 "integer or unit attributes");
116 return signalPassFailure();
117 }
118 }
119 bmcOp = verif::BoundedModelCheckingOp::create(
120 builder, loc, risingClocksOnly ? bound : 2 * bound,
121 cast<IntegerAttr>(numRegs).getValue().getZExtValue(), initialValues);
122 // Annotate the op with how many cycles to ignore - again, we may need to
123 // double this to account for rising and falling edges
124 if (ignoreAssertionsUntil)
125 bmcOp->setAttr("ignore_asserts_until",
126 builder.getI32IntegerAttr(
127 risingClocksOnly ? ignoreAssertionsUntil
128 : 2 * ignoreAssertionsUntil));
129 } else {
130 hwModule->emitOpError("no num_regs or initial_values attribute found - "
131 "please run externalize "
132 "registers pass first");
133 return signalPassFailure();
134 }
135
136 // Check that there's only one clock input to the module
137 // TODO: supporting multiple clocks isn't too hard, an interleaving of clock
138 // toggles just needs to be generated
139 bool hasClk = false;
140 for (auto input : hwModule.getInputTypes()) {
141 if (isa<seq::ClockType>(input)) {
142 if (hasClk) {
143 hwModule.emitError("designs with multiple clocks not yet supported");
144 return signalPassFailure();
145 }
146 hasClk = true;
147 }
148 if (auto hwStruct = dyn_cast<hw::StructType>(input)) {
149 for (auto field : hwStruct.getElements()) {
150 if (isa<seq::ClockType>(field.type)) {
151 if (hasClk) {
152 hwModule.emitError(
153 "designs with multiple clocks not yet supported");
154 return signalPassFailure();
155 }
156 hasClk = true;
157 }
158 }
159 }
160 }
161 {
162 OpBuilder::InsertionGuard guard(builder);
163 // Initialize clock to 0 if it exists, otherwise just yield nothing
164 // We initialize to 1 if we're in rising clocks only mode
165 auto *initBlock = builder.createBlock(&bmcOp.getInit());
166 builder.setInsertionPointToStart(initBlock);
167 if (hasClk) {
168 auto initVal = hw::ConstantOp::create(builder, loc, builder.getI1Type(),
169 risingClocksOnly ? 1 : 0);
170 auto toClk = seq::ToClockOp::create(builder, loc, initVal);
171 verif::YieldOp::create(builder, loc, ValueRange{toClk});
172 } else {
173 verif::YieldOp::create(builder, loc, ValueRange{});
174 }
175
176 // Toggle clock in loop region if it exists, otherwise just yield nothing
177 auto *loopBlock = builder.createBlock(&bmcOp.getLoop());
178 builder.setInsertionPointToStart(loopBlock);
179 if (hasClk) {
180 loopBlock->addArgument(seq::ClockType::get(ctx), loc);
181 if (risingClocksOnly) {
182 // In rising clocks only mode we don't need to toggle the clock
183 verif::YieldOp::create(builder, loc,
184 ValueRange{loopBlock->getArgument(0)});
185 } else {
186 auto fromClk =
187 seq::FromClockOp::create(builder, loc, loopBlock->getArgument(0));
188 auto cNeg1 =
189 hw::ConstantOp::create(builder, loc, builder.getI1Type(), -1);
190 auto nClk = comb::XorOp::create(builder, loc, fromClk, cNeg1);
191 auto toClk = seq::ToClockOp::create(builder, loc, nClk);
192 // Only yield clock value
193 verif::YieldOp::create(builder, loc, ValueRange{toClk});
194 }
195 } else {
196 verif::YieldOp::create(builder, loc, ValueRange{});
197 }
198 }
199 bmcOp.getCircuit().takeBody(hwModule.getBody());
200 hwModule->erase();
201
202 // Define global string constants to print on success/failure
203 auto createUniqueStringGlobal = [&](StringRef str) -> FailureOr<Value> {
204 Location loc = moduleOp.getLoc();
205
206 OpBuilder b = OpBuilder::atBlockEnd(moduleOp.getBody());
207 auto arrayTy = LLVM::LLVMArrayType::get(b.getI8Type(), str.size() + 1);
208 auto global = LLVM::GlobalOp::create(
209 b, loc, arrayTy, /*isConstant=*/true, LLVM::linkage::Linkage::Private,
210 "resultString",
211 StringAttr::get(b.getContext(), Twine(str).concat(Twine('\00'))));
212 SymbolTable symTable(moduleOp);
213 if (failed(symTable.renameToUnique(global, {&symTable}))) {
214 return mlir::failure();
215 }
216
217 return success(
218 LLVM::AddressOfOp::create(builder, loc, global)->getResult(0));
219 };
220
221 Value formatString;
222 if (noProperties) {
223 auto messageString = createUniqueStringGlobal(
224 "No properties provided - trivially no violations.\n");
225 if (failed(messageString)) {
226 moduleOp->emitOpError("could not create result message string");
227 return signalPassFailure();
228 }
229 formatString = messageString.value();
230 } else {
231 auto successStrAddr =
232 createUniqueStringGlobal("Bound reached with no violations!\n");
233 auto failureStrAddr =
234 createUniqueStringGlobal("Assertion can be violated!\n");
235
236 if (failed(successStrAddr) || failed(failureStrAddr)) {
237 moduleOp->emitOpError("could not create result message strings");
238 return signalPassFailure();
239 }
240
241 formatString =
242 LLVM::SelectOp::create(builder, loc, bmcOp.getResult(),
243 successStrAddr.value(), failureStrAddr.value());
244 }
245
246 LLVM::CallOp::create(builder, loc, printfFunc.value(),
247 ValueRange{formatString});
248 func::ReturnOp::create(builder, loc);
249
250 if (insertMainFunc) {
251 builder.setInsertionPointToEnd(getOperation().getBody());
252 Type i32Ty = builder.getI32Type();
253 auto mainFunc = func::FuncOp::create(
254 builder, loc, "main", builder.getFunctionType({i32Ty, ptrTy}, {i32Ty}));
255 builder.createBlock(&mainFunc.getBody(), {}, {i32Ty, ptrTy}, {loc, loc});
256 func::CallOp::create(builder, loc, entryFunc, ValueRange{});
257 // TODO: don't use LLVM here
258 Value constZero = LLVM::ConstantOp::create(builder, loc, i32Ty, 0);
259 func::ReturnOp::create(builder, loc, constZero);
260 }
261}
static SmallVector< T > concat(const SmallVectorImpl< T > &a, const SmallVectorImpl< T > &b)
Returns a new vector containing the concatenation of vectors a and b.
Definition CalyxOps.cpp:540
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
create(data_type, value)
Definition hw.py:433
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition hw.py:1