CIRCT 21.0.0git
Loading...
Searching...
No Matches
LowerContracts.cpp
Go to the documentation of this file.
1//===- LowerContracts.cpp - Formal Preparations -----------------*- C++ -*-===//
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//
9// Lower contracts into verif.formal tests.
10//
11//===----------------------------------------------------------------------===//
12
13#include <queue>
14
17#include "mlir/Analysis/TopologicalSortUtils.h"
18#include "mlir/IR/IRMapping.h"
19
20using namespace circt;
21
22namespace circt {
23namespace verif {
24#define GEN_PASS_DEF_LOWERCONTRACTSPASS
25#include "circt/Dialect/Verif/Passes.h.inc"
26} // namespace verif
27} // namespace circt
28
29using namespace mlir;
30using namespace verif;
31using namespace hw;
32
33namespace {
34struct LowerContractsPass
35 : verif::impl::LowerContractsPassBase<LowerContractsPass> {
36 void runOnOperation() override;
37};
38
39Operation *replaceContractOp(OpBuilder &builder, RequireLike op,
40 IRMapping &mapping, bool assumeContract) {
41 StringAttr labelAttr = op.getLabelAttr();
42
43 Value enableValue;
44 if (auto enable = op.getEnable())
45 enable = mapping.lookup(enable);
46
47 auto loc = op.getLoc();
48 auto property = mapping.lookup(op.getProperty());
49
50 if ((isa<EnsureOp>(op) && !assumeContract) ||
51 (isa<RequireOp>(op) && assumeContract))
52 return builder.create<AssertOp>(loc, property, enableValue, labelAttr);
53 if ((isa<EnsureOp>(op) && assumeContract) ||
54 (isa<RequireOp>(op) && !assumeContract))
55 return builder.create<AssumeOp>(loc, property, enableValue, labelAttr);
56 return nullptr;
57}
58
59LogicalResult cloneOp(OpBuilder &builder, Operation *opToClone,
60 IRMapping &mapping, bool assumeContract) {
61 Operation *clonedOp;
62 // Replace ensure/require ops, otherwise clone
63 if (auto requireLike = dyn_cast<RequireLike>(*opToClone)) {
64 clonedOp = replaceContractOp(builder, requireLike, mapping, assumeContract);
65 if (!clonedOp) {
66 return failure();
67 }
68 } else {
69 clonedOp = builder.clone(*opToClone, mapping);
70 }
71
72 // Add mappings for results
73 for (auto [x, y] :
74 llvm::zip(opToClone->getResults(), clonedOp->getResults())) {
75 mapping.map(x, y);
76 }
77 return llvm::success();
78}
79
80void assumeContractHolds(OpBuilder &builder, IRMapping &mapping,
81 ContractOp contract,
82 std::queue<Operation *> &workList) {
83 // Assume it holds by creating symbolic values for the results and inlining
84 // the contract ops into the worklist, by default the clone logic replaces
85 // contract ops with the assume variant
86 for (auto result : contract.getResults()) {
87 auto sym =
88 builder.create<SymbolicValueOp>(result.getLoc(), result.getType());
89 mapping.map(result, sym);
90 }
91 auto &contractOps = contract.getBody().front().getOperations();
92 for (auto it = contractOps.rbegin(); it != contractOps.rend(); ++it) {
93 workList.push(&*it);
94 }
95}
96
97void buildOpsToClone(OpBuilder &builder, IRMapping &mapping, Operation *op,
98 std::queue<Operation *> &workList,
99 Operation *parent = nullptr) {
100 for (auto operand : op->getOperands()) {
101 if (mapping.contains(operand))
102 continue;
103 // Don't need to clone if defining op is within parent op
104 if (parent && parent->isAncestor(operand.getParentBlock()->getParentOp()))
105 continue;
106 if (auto *definingOp = operand.getDefiningOp()) {
107 workList.push(definingOp);
108 } else {
109 // Create symbolic values for arguments
110 auto sym = builder.create<verif::SymbolicValueOp>(operand.getLoc(),
111 operand.getType());
112 mapping.map(operand, sym);
113 }
114 }
115}
116
117LogicalResult cloneFanIn(OpBuilder &builder, Operation *contractToClone,
118 IRMapping &mapping) {
119 DenseSet<Operation *> seen;
120 SmallVector<Operation *> opsToClone;
121 std::queue<Operation *> workList;
122 workList.push(contractToClone);
123
124 while (!workList.empty()) {
125 auto *currentOp = workList.front();
126 workList.pop();
127
128 if (seen.contains(currentOp))
129 continue;
130
131 seen.insert(currentOp);
132
133 auto contract = dyn_cast<ContractOp>(*currentOp);
134 // If it's not the contract being cloned, assume it holds
135 if (contract && (currentOp != contractToClone)) {
136 assumeContractHolds(builder, mapping, contract, workList);
137 // If a contract is assumed, it's nested ops are inlined into the worklist
138 // so no need to walk the body or clone the op
139 continue;
140 }
141
142 // Clone fan in for operands
143 buildOpsToClone(builder, mapping, currentOp, workList);
144 // Clone fan in for nested ops
145 currentOp->walk([&](Operation *nestedOp) {
146 buildOpsToClone(builder, mapping, nestedOp, workList, currentOp);
147 });
148
149 // Source contract is cloned externally
150 if (currentOp != contractToClone)
151 opsToClone.push_back(currentOp);
152 }
153
154 // Sort ops so mapping is available for all operands when cloning an op
155 computeTopologicalSorting(opsToClone);
156
157 for (auto *op : opsToClone) {
158 if (failed(cloneOp(builder, op, mapping, true)))
159 return failure();
160 }
161 return success();
162}
163
164LogicalResult inlineContract(ContractOp &contract, OpBuilder &builder,
165 bool assumeContract) {
166 IRMapping mapping;
167 if (!assumeContract) {
168 // Inlining into formal op, need to clone fan in cone for contract
169 if (failed(cloneFanIn(builder, contract, mapping)))
170 return failure();
171 }
172
173 if (assumeContract) {
174 // Create symbolic values for results
175 for (auto result : contract.getResults()) {
176 auto sym =
177 builder.create<SymbolicValueOp>(result.getLoc(), result.getType());
178 mapping.map(result, sym);
179 }
180 } else {
181 // Map results by looking up the input mappings
182 for (auto [result, input] :
183 llvm::zip(contract.getResults(), contract.getInputs())) {
184 mapping.map(result, mapping.lookup(input));
185 }
186 }
187
188 // Clone body of the contract
189 for (auto &op : contract.getBody().front().getOperations()) {
190 if (failed(cloneOp(builder, &op, mapping, assumeContract)))
191 return failure();
192 }
193
194 if (assumeContract) {
195 // Inlining into hw module, need to update the results using the mapping
196 for (auto result : contract.getResults())
197 result.replaceAllUsesWith(mapping.lookup(result));
198 }
199 return success();
200}
201
202LogicalResult runOnHWModule(HWModuleOp hwModule, ModuleOp mlirModule) {
203 OpBuilder mlirModuleBuilder(mlirModule);
204 mlirModuleBuilder.setInsertionPointAfter(hwModule);
205 OpBuilder hwModuleBuilder(hwModule);
206
207 // Collect contract ops
208 SmallVector<ContractOp> contracts;
209 hwModule.walk([&](ContractOp op) { contracts.push_back(op); });
210
211 for (unsigned i = 0; i < contracts.size(); i++) {
212 auto contract = contracts[i];
213
214 // Create verif.formal op
215 auto name = mlirModuleBuilder.getStringAttr(
216 hwModule.getNameAttr().getValue() + "_CheckContract_" + Twine(i));
217 auto formalOp = mlirModuleBuilder.create<verif::FormalOp>(
218 contract.getLoc(), name, mlirModuleBuilder.getDictionaryAttr({}));
219
220 // Fill in verif.formal body
221 OpBuilder formalBuilder(formalOp);
222 formalBuilder.createBlock(&formalOp.getBody());
223
224 if (failed(inlineContract(contract, formalBuilder, false)))
225 return failure();
226 }
227
228 for (auto contract : contracts) {
229 // Inline contract into hwModule
230 hwModuleBuilder.setInsertionPointAfter(contract);
231 if (failed(inlineContract(contract, hwModuleBuilder, true)))
232 return failure();
233 contract.erase();
234 }
235
236 return success();
237}
238} // namespace
239
240void LowerContractsPass::runOnOperation() {
241 auto mlirModule = getOperation();
242 for (auto module : mlirModule.getOps<HWModuleOp>())
243 if (failed(runOnHWModule(module, mlirModule)))
244 signalPassFailure();
245}
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition hw.py:1
Definition verif.py:1