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 if (!sortTopologically(&hwModule.getBodyRegion().front())) {
58 hwModule->emitError("could not resolve cycles in module");
59 return signalPassFailure();
60 }
61
62 if (bound < ignoreAssertionsUntil) {
63 hwModule->emitError(
64 "number of ignored cycles must be less than or equal to bound");
65 return signalPassFailure();
66 }
67
68 // Create necessary function declarations and globals
69 auto *ctx = &getContext();
70 OpBuilder builder(ctx);
71 Location loc = moduleOp->getLoc();
72 builder.setInsertionPointToEnd(moduleOp.getBody());
73 auto ptrTy = LLVM::LLVMPointerType::get(ctx);
74 auto voidTy = LLVM::LLVMVoidType::get(ctx);
75
76 // Lookup or declare printf function.
77 auto printfFunc =
78 LLVM::lookupOrCreateFn(builder, moduleOp, "printf", ptrTy, voidTy, true);
79 if (failed(printfFunc)) {
80 moduleOp->emitError("failed to lookup or create printf");
81 return signalPassFailure();
82 }
83
84 // Replace the top-module with a function performing the BMC
85 auto entryFunc = func::FuncOp::create(builder, loc, topModule,
86 builder.getFunctionType({}, {}));
87 builder.createBlock(&entryFunc.getBody());
88
89 {
90 OpBuilder::InsertionGuard guard(builder);
91 auto *terminator = hwModule.getBody().front().getTerminator();
92 builder.setInsertionPoint(terminator);
93 verif::YieldOp::create(builder, loc, terminator->getOperands());
94 terminator->erase();
95 }
96
97 // Double the bound given to the BMC op unless in rising clocks only mode, as
98 // a clock cycle involves two negations
99 verif::BoundedModelCheckingOp bmcOp;
100 auto numRegs = hwModule->getAttrOfType<IntegerAttr>("num_regs");
101 auto initialValues = hwModule->getAttrOfType<ArrayAttr>("initial_values");
102 if (numRegs && initialValues) {
103 for (auto value : initialValues) {
104 if (!isa<IntegerAttr, UnitAttr>(value)) {
105 hwModule->emitError("initial_values attribute must contain only "
106 "integer or unit attributes");
107 return signalPassFailure();
108 }
109 }
110 bmcOp = verif::BoundedModelCheckingOp::create(
111 builder, loc, risingClocksOnly ? bound : 2 * bound,
112 cast<IntegerAttr>(numRegs).getValue().getZExtValue(), initialValues);
113 // Annotate the op with how many cycles to ignore - again, we may need to
114 // double this to account for rising and falling edges
115 if (ignoreAssertionsUntil)
116 bmcOp->setAttr("ignore_asserts_until",
117 builder.getI32IntegerAttr(
118 risingClocksOnly ? ignoreAssertionsUntil
119 : 2 * ignoreAssertionsUntil));
120 } else {
121 hwModule->emitOpError("no num_regs or initial_values attribute found - "
122 "please run externalize "
123 "registers pass first");
124 return signalPassFailure();
125 }
126
127 // Check that there's only one clock input to the module
128 // TODO: supporting multiple clocks isn't too hard, an interleaving of clock
129 // toggles just needs to be generated
130 bool hasClk = false;
131 for (auto input : hwModule.getInputTypes()) {
132 if (isa<seq::ClockType>(input)) {
133 if (hasClk) {
134 hwModule.emitError("designs with multiple clocks not yet supported");
135 return signalPassFailure();
136 }
137 hasClk = true;
138 }
139 if (auto hwStruct = dyn_cast<hw::StructType>(input)) {
140 for (auto field : hwStruct.getElements()) {
141 if (isa<seq::ClockType>(field.type)) {
142 if (hasClk) {
143 hwModule.emitError(
144 "designs with multiple clocks not yet supported");
145 return signalPassFailure();
146 }
147 hasClk = true;
148 }
149 }
150 }
151 }
152 {
153 OpBuilder::InsertionGuard guard(builder);
154 // Initialize clock to 0 if it exists, otherwise just yield nothing
155 // We initialize to 1 if we're in rising clocks only mode
156 auto *initBlock = builder.createBlock(&bmcOp.getInit());
157 builder.setInsertionPointToStart(initBlock);
158 if (hasClk) {
159 auto initVal = hw::ConstantOp::create(builder, loc, builder.getI1Type(),
160 risingClocksOnly ? 1 : 0);
161 auto toClk = seq::ToClockOp::create(builder, loc, initVal);
162 verif::YieldOp::create(builder, loc, ValueRange{toClk});
163 } else {
164 verif::YieldOp::create(builder, loc, ValueRange{});
165 }
166
167 // Toggle clock in loop region if it exists, otherwise just yield nothing
168 auto *loopBlock = builder.createBlock(&bmcOp.getLoop());
169 builder.setInsertionPointToStart(loopBlock);
170 if (hasClk) {
171 loopBlock->addArgument(seq::ClockType::get(ctx), loc);
172 if (risingClocksOnly) {
173 // In rising clocks only mode we don't need to toggle the clock
174 verif::YieldOp::create(builder, loc,
175 ValueRange{loopBlock->getArgument(0)});
176 } else {
177 auto fromClk =
178 seq::FromClockOp::create(builder, loc, loopBlock->getArgument(0));
179 auto cNeg1 =
180 hw::ConstantOp::create(builder, loc, builder.getI1Type(), -1);
181 auto nClk = comb::XorOp::create(builder, loc, fromClk, cNeg1);
182 auto toClk = seq::ToClockOp::create(builder, loc, nClk);
183 // Only yield clock value
184 verif::YieldOp::create(builder, loc, ValueRange{toClk});
185 }
186 } else {
187 verif::YieldOp::create(builder, loc, ValueRange{});
188 }
189 }
190 bmcOp.getCircuit().takeBody(hwModule.getBody());
191 hwModule->erase();
192
193 // Define global string constants to print on success/failure
194 auto createUniqueStringGlobal = [&](StringRef str) -> FailureOr<Value> {
195 Location loc = moduleOp.getLoc();
196
197 OpBuilder b = OpBuilder::atBlockEnd(moduleOp.getBody());
198 auto arrayTy = LLVM::LLVMArrayType::get(b.getI8Type(), str.size() + 1);
199 auto global = LLVM::GlobalOp::create(
200 b, loc, arrayTy, /*isConstant=*/true, LLVM::linkage::Linkage::Private,
201 "resultString",
202 StringAttr::get(b.getContext(), Twine(str).concat(Twine('\00'))));
203 SymbolTable symTable(moduleOp);
204 if (failed(symTable.renameToUnique(global, {&symTable}))) {
205 return mlir::failure();
206 }
207
208 return success(
209 LLVM::AddressOfOp::create(builder, loc, global)->getResult(0));
210 };
211
212 auto successStrAddr =
213 createUniqueStringGlobal("Bound reached with no violations!\n");
214 auto failureStrAddr =
215 createUniqueStringGlobal("Assertion can be violated!\n");
216
217 if (failed(successStrAddr) || failed(failureStrAddr)) {
218 moduleOp->emitOpError("could not create result message strings");
219 return signalPassFailure();
220 }
221
222 auto formatString =
223 LLVM::SelectOp::create(builder, loc, bmcOp.getResult(),
224 successStrAddr.value(), failureStrAddr.value());
225
226 LLVM::CallOp::create(builder, loc, printfFunc.value(),
227 ValueRange{formatString});
228 func::ReturnOp::create(builder, loc);
229
230 if (insertMainFunc) {
231 builder.setInsertionPointToEnd(getOperation().getBody());
232 Type i32Ty = builder.getI32Type();
233 auto mainFunc = func::FuncOp::create(
234 builder, loc, "main", builder.getFunctionType({i32Ty, ptrTy}, {i32Ty}));
235 builder.createBlock(&mainFunc.getBody(), {}, {i32Ty, ptrTy}, {loc, loc});
236 func::CallOp::create(builder, loc, entryFunc, ValueRange{});
237 // TODO: don't use LLVM here
238 Value constZero = LLVM::ConstantOp::create(builder, loc, i32Ty, 0);
239 func::ReturnOp::create(builder, loc, constZero);
240 }
241}
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