Loading [MathJax]/extensions/tex2jax.js
CIRCT 22.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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 AssertOp::create(builder, loc, property, enableValue, labelAttr);
53 if ((isa<EnsureOp>(op) && assumeContract) ||
54 (isa<RequireOp>(op) && !assumeContract))
55 return AssumeOp::create(builder, loc, property, enableValue, labelAttr);
56 return nullptr;
57}
58
59FailureOr<Operation *> 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 clonedOp;
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 SymbolicValueOp::create(builder, 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 = verif::SymbolicValueOp::create(builder, 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 SmallVector<Operation *> clonedOps;
122 std::queue<Operation *> workList;
123 workList.push(contractToClone);
124
125 while (!workList.empty()) {
126 auto *currentOp = workList.front();
127 workList.pop();
128
129 if (seen.contains(currentOp))
130 continue;
131
132 seen.insert(currentOp);
133
134 auto contract = dyn_cast<ContractOp>(*currentOp);
135 // If it's not the contract being cloned, assume it holds
136 if (contract && (currentOp != contractToClone)) {
137 assumeContractHolds(builder, mapping, contract, workList);
138 // If a contract is assumed, it's nested ops are inlined into the worklist
139 // so no need to walk the body or clone the op
140 continue;
141 }
142
143 // Clone fan in for operands
144 buildOpsToClone(builder, mapping, currentOp, workList);
145 // Clone fan in for nested ops
146 currentOp->walk([&](Operation *nestedOp) {
147 buildOpsToClone(builder, mapping, nestedOp, workList, currentOp);
148 });
149
150 // Source contract is cloned externally
151 if (currentOp != contractToClone)
152 opsToClone.push_back(currentOp);
153 }
154
155 // Sort ops so mapping is available for all operands when cloning an op
156 computeTopologicalSorting(opsToClone);
157
158 clonedOps.reserve(opsToClone.size());
159 for (auto *op : opsToClone) {
160 if (auto clonedOp = cloneOp(builder, op, mapping, true);
161 succeeded(clonedOp)) {
162 clonedOps.push_back(*clonedOp);
163 } else {
164 return failure();
165 }
166 }
167
168 // Remap self-referencing uses of cloned results in the cloned ops
169 for (auto *clonedOp : clonedOps) {
170 for (unsigned i = 0, e = clonedOp->getNumOperands(); i < e; i++) {
171 auto operand = clonedOp->getOperand(i);
172 if (mapping.contains(operand)) {
173 clonedOp->setOperand(i, mapping.lookup(operand));
174 }
175 }
176 }
177 return success();
178}
179
180LogicalResult inlineContract(ContractOp &contract, OpBuilder &builder,
181 bool assumeContract) {
182 IRMapping mapping;
183 if (!assumeContract) {
184 // Inlining into formal op, need to clone fan in cone for contract
185 if (failed(cloneFanIn(builder, contract, mapping)))
186 return failure();
187 }
188
189 if (assumeContract) {
190 // Create symbolic values for results
191 for (auto result : contract.getResults()) {
192 auto sym =
193 SymbolicValueOp::create(builder, result.getLoc(), result.getType());
194 mapping.map(result, sym);
195 }
196 } else {
197 // Map results by looking up the input mappings
198 for (auto [result, input] :
199 llvm::zip(contract.getResults(), contract.getInputs())) {
200 mapping.map(result, mapping.lookup(input));
201 }
202 }
203
204 // Clone body of the contract
205 for (auto &op : contract.getBody().front().getOperations()) {
206 if (failed(cloneOp(builder, &op, mapping, assumeContract)))
207 return failure();
208 }
209
210 if (assumeContract) {
211 // Inlining into hw module, need to update the results using the mapping
212 for (auto result : contract.getResults())
213 result.replaceAllUsesWith(mapping.lookup(result));
214 }
215 return success();
216}
217
218LogicalResult runOnHWModule(HWModuleOp hwModule, ModuleOp mlirModule) {
219 OpBuilder mlirModuleBuilder(mlirModule);
220 mlirModuleBuilder.setInsertionPointAfter(hwModule);
221 OpBuilder hwModuleBuilder(hwModule);
222
223 // Collect contract ops
224 SmallVector<ContractOp> contracts;
225 hwModule.walk([&](ContractOp op) { contracts.push_back(op); });
226
227 for (unsigned i = 0; i < contracts.size(); i++) {
228 auto contract = contracts[i];
229
230 // Create verif.formal op
231 auto name = mlirModuleBuilder.getStringAttr(
232 hwModule.getNameAttr().getValue() + "_CheckContract_" + Twine(i));
233 auto formalOp =
234 verif::FormalOp::create(mlirModuleBuilder, contract.getLoc(), name,
235 mlirModuleBuilder.getDictionaryAttr({}));
236
237 // Fill in verif.formal body
238 OpBuilder formalBuilder(formalOp);
239 formalBuilder.createBlock(&formalOp.getBody());
240
241 if (failed(inlineContract(contract, formalBuilder, false)))
242 return failure();
243 }
244
245 for (auto contract : contracts) {
246 // Inline contract into hwModule
247 hwModuleBuilder.setInsertionPointAfter(contract);
248 if (failed(inlineContract(contract, hwModuleBuilder, true)))
249 return failure();
250 contract.erase();
251 }
252
253 return success();
254}
255} // namespace
256
257void LowerContractsPass::runOnOperation() {
258 auto mlirModule = getOperation();
259 for (auto module : mlirModule.getOps<HWModuleOp>())
260 if (failed(runOnHWModule(module, mlirModule)))
261 signalPassFailure();
262}
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition hw.py:1
Definition verif.py:1