CIRCT 24.0.0git
Loading...
Searching...
No Matches
LTLToCore.cpp
Go to the documentation of this file.
1//===- LTLToCore.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//
9// Converts LTL and Verif operations to Core operations
10//
11//===----------------------------------------------------------------------===//
12
25#include "mlir/Dialect/Func/IR/FuncOps.h"
26#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
27#include "mlir/Dialect/SCF/IR/SCF.h"
28#include "mlir/IR/BuiltinTypes.h"
29#include "mlir/Pass/Pass.h"
30#include "mlir/Transforms/DialectConversion.h"
31#include "llvm/Support/LogicalResult.h"
32
33namespace circt {
34#define GEN_PASS_DEF_LOWERLTLTOCORE
35#include "circt/Conversion/Passes.h.inc"
36} // namespace circt
37
38using namespace mlir;
39using namespace circt;
40using namespace hw;
41
42//===----------------------------------------------------------------------===//
43// Conversion patterns
44//===----------------------------------------------------------------------===//
45
46namespace {
47struct HasBeenResetOpConversion : OpConversionPattern<verif::HasBeenResetOp> {
48 using OpConversionPattern<verif::HasBeenResetOp>::OpConversionPattern;
49
50 // HasBeenReset generates a 1 bit register that is set to one once the reset
51 // has been raised and lowered at at least once.
52 LogicalResult
53 matchAndRewrite(verif::HasBeenResetOp op, OpAdaptor adaptor,
54 ConversionPatternRewriter &rewriter) const override {
55 auto i1 = rewriter.getI1Type();
56 // Generate the constant used to set the register value
57 Value constZero = seq::createConstantInitialValue(
58 rewriter, op->getLoc(), rewriter.getIntegerAttr(i1, 0));
59
60 // Generate the constant used to negate the reset value
61 Value constOne = hw::ConstantOp::create(rewriter, op.getLoc(), i1, 1);
62
63 // Create a backedge for the register to be used in the OrOp
64 circt::BackedgeBuilder bb(rewriter, op.getLoc());
65 circt::Backedge reg = bb.get(rewriter.getI1Type());
66
67 // Generate an or between the reset and the register's value to store
68 // whether or not the reset has been active at least once
69 Value orReset =
70 comb::OrOp::create(rewriter, op.getLoc(), adaptor.getReset(), reg);
71
72 // This register should not be reset, so we give it dummy reset and resetval
73 // operands to fit the build signature
74 Value reset, resetval;
75
76 // Finally generate the register to set the backedge
78 rewriter, op.getLoc(), orReset,
79 rewriter.createOrFold<seq::ToClockOp>(op.getLoc(), adaptor.getClock()),
80 rewriter.getStringAttr("hbr"), reset, resetval, constZero,
81 InnerSymAttr{} // inner_sym
82 ));
83
84 // We also need to consider the case where we are currently in a reset cycle
85 // in which case our hbr register should be down-
86 // Practically this means converting it to (and hbr (not reset))
87 Value notReset = comb::XorOp::create(rewriter, op.getLoc(),
88 adaptor.getReset(), constOne);
89 rewriter.replaceOpWithNewOp<comb::AndOp>(op, reg, notReset);
90
91 return success();
92 }
93};
94
95struct LTLImplicationConversion
96 : public OpConversionPattern<ltl::ImplicationOp> {
97 using OpConversionPattern<ltl::ImplicationOp>::OpConversionPattern;
98
99 LogicalResult
100 matchAndRewrite(ltl::ImplicationOp op, OpAdaptor adaptor,
101 ConversionPatternRewriter &rewriter) const override {
102 // Can only lower boolean implications to comb ops
103 if (!isa<IntegerType>(op.getAntecedent().getType()) ||
104 !isa<IntegerType>(op.getConsequent().getType()))
105 return failure();
106 /// A -> B = !A || B
107 auto loc = op.getLoc();
108 auto notA = comb::createOrFoldNot(rewriter, loc, adaptor.getAntecedent());
109 auto orOp =
110 comb::OrOp::create(rewriter, loc, notA, adaptor.getConsequent());
111 rewriter.replaceOp(op, orOp);
112 return success();
113 }
114};
115
116struct LTLNotConversion : public OpConversionPattern<ltl::NotOp> {
118
119 LogicalResult
120 matchAndRewrite(ltl::NotOp op, OpAdaptor adaptor,
121 ConversionPatternRewriter &rewriter) const override {
122 // Can only lower boolean nots to comb ops
123 if (!isa<IntegerType>(op.getInput().getType()))
124 return failure();
125 auto loc = op.getLoc();
126 auto inverted = comb::createOrFoldNot(rewriter, loc, adaptor.getInput());
127 rewriter.replaceOp(op, inverted);
128 return success();
129 }
130};
131
132struct LTLAndOpConversion : public OpConversionPattern<ltl::AndOp> {
134
135 LogicalResult
136 matchAndRewrite(ltl::AndOp op, OpAdaptor adaptor,
137 ConversionPatternRewriter &rewriter) const override {
138 // Can only lower boolean ands to comb ops
139 if (!isa<IntegerType>(op->getOperandTypes()[0]) ||
140 !isa<IntegerType>(op->getOperandTypes()[1]))
141 return failure();
142 auto loc = op.getLoc();
143 // Explicit twoState value to disambiguate builders
144 auto andOp =
145 comb::AndOp::create(rewriter, loc, adaptor.getOperands(), false);
146 rewriter.replaceOp(op, andOp);
147 return success();
148 }
149};
150
151struct LTLOrOpConversion : public OpConversionPattern<ltl::OrOp> {
153
154 LogicalResult
155 matchAndRewrite(ltl::OrOp op, OpAdaptor adaptor,
156 ConversionPatternRewriter &rewriter) const override {
157 // Can only lower boolean ors to comb ops
158 if (!isa<IntegerType>(op->getOperandTypes()[0]) ||
159 !isa<IntegerType>(op->getOperandTypes()[1]))
160 return failure();
161 auto loc = op.getLoc();
162 // Explicit twoState value to disambiguate builders
163 auto orOp = comb::OrOp::create(rewriter, loc, adaptor.getOperands(), false);
164 rewriter.replaceOp(op, orOp);
165 return success();
166 }
167};
168
169struct LTLIntersectOpConversion : public OpConversionPattern<ltl::IntersectOp> {
170 using OpConversionPattern<ltl::IntersectOp>::OpConversionPattern;
171
172 LogicalResult
173 matchAndRewrite(ltl::IntersectOp op, OpAdaptor adaptor,
174 ConversionPatternRewriter &rewriter) const override {
175 // Can only lower boolean intersects to comb ops; booleans are
176 // instantaneous matches, so intersection is conjunction.
177 if (!isa<IntegerType>(op->getOperandTypes()[0]) ||
178 !isa<IntegerType>(op->getOperandTypes()[1]))
179 return failure();
180 auto loc = op.getLoc();
181 // Explicit twoState value to disambiguate builders
182 auto andOp =
183 comb::AndOp::create(rewriter, loc, adaptor.getOperands(), false);
184 rewriter.replaceOp(op, andOp);
185 return success();
186 }
187};
188
189struct LTLPastOpConversion : public OpConversionPattern<ltl::PastOp> {
191
192 LogicalResult
193 matchAndRewrite(ltl::PastOp op, OpAdaptor adaptor,
194 ConversionPatternRewriter &rewriter) const override {
195 Value clock =
196 seq::ToClockOp::create(rewriter, op.getLoc(), adaptor.getClk());
197 Value cur = adaptor.getInput();
198 Value ce =
199 hw::ConstantOp::create(rewriter, op.getLoc(), rewriter.getI1Type(), 1);
200 auto shiftreg =
201 seq::ShiftRegOp::create(rewriter, op.getLoc(), op.getDelayAttr(), cur,
202 clock, ce, {}, {}, {}, {}, {});
203 rewriter.replaceOp(op, shiftreg);
204 return success();
205 }
206};
207
208// Sample `input` on the requested clock edge.
209static Value createRegister(Value input, Value clock, ltl::ClockEdge edge,
210 bool initialValue, OpBuilder &builder,
211 Operation *contextOp) {
212 assert(edge != ltl::ClockEdge::Both && "both-edge clock not supported");
213 auto clockSignal = clock;
214 if (edge == ltl::ClockEdge::Neg)
215 clockSignal =
216 comb::createOrFoldNot(builder, contextOp->getLoc(), clockSignal);
217 auto seqClock =
218 builder.createOrFold<seq::ToClockOp>(contextOp->getLoc(), clockSignal);
219
220 auto loc = contextOp->getLoc();
221 auto initial = seq::createConstantInitialValue(
222 builder, loc, builder.getIntegerAttr(builder.getI1Type(), initialValue));
223 return seq::CompRegOp::create(builder, loc, input, seqClock,
224 /*reset=*/Value{},
225 /*rstValue=*/Value{}, initial)
226 .getResult();
227}
228
229static void lowerTemporalLTLToCore(hw::HWModuleOp module) {
230 SmallVector<Operation *> assertionsAndAssumptions;
231 module->walk([&](Operation *op) {
232 if (isa<verif::AssertOp, verif::AssumeOp>(op))
233 assertionsAndAssumptions.push_back(op);
234 });
235
236 for (auto *op : assertionsAndAssumptions) {
237 Value property = op->getOperand(0);
238 // Only lower a clocked atom when it is the direct property of an assertion
239 // or assumption. The startup `dontCare` guard is property-level and cannot
240 // be composed correctly through nested LTL operations.
241 auto atom = property.getDefiningOp<ltl::ClockedAtomOp>();
242 if (!atom || atom.getEdge() == ltl::ClockEdge::Both)
243 continue;
244
245 OpBuilder builder(op);
246 auto sampled =
247 createRegister(atom.getInput(), atom.getClock(), atom.getEdge(),
248 /*initialValue=*/false, builder, atom);
249
250 auto constFalse =
251 hw::ConstantOp::create(builder, op->getLoc(), builder.getI1Type(), 0);
252 // `dontCare` is true while `sampled` only contains its initial value. The
253 // first real clock edge clears it and makes the sampled value observable.
254 auto dontCare = createRegister(constFalse, atom.getClock(), atom.getEdge(),
255 /*initialValue=*/true, builder, op);
256 auto guardedProperty =
257 comb::OrOp::create(builder, op->getLoc(), dontCare, sampled,
258 /*twoState=*/false);
259 op->setOperand(0, guardedProperty);
260 }
261}
262
263} // namespace
264
265//===----------------------------------------------------------------------===//
266// Lower LTL To Core pass
267//===----------------------------------------------------------------------===//
268
269namespace {
270struct LowerLTLToCorePass
271 : public circt::impl::LowerLTLToCoreBase<LowerLTLToCorePass> {
272 LowerLTLToCorePass() = default;
273 void runOnOperation() override;
274};
275} // namespace
276
277// Simply applies the conversion patterns defined above
278void LowerLTLToCorePass::runOnOperation() {
279 lowerTemporalLTLToCore(getOperation());
280
281 // Preserve operations that require an LTL-aware downstream backend.
282 ConversionTarget target(getContext());
283 target.addLegalDialect<hw::HWDialect>();
284 target.addLegalDialect<comb::CombDialect>();
285 target.addLegalDialect<sv::SVDialect>();
286 target.addLegalDialect<seq::SeqDialect>();
287 target.addLegalDialect<ltl::LTLDialect>();
288 target.addLegalDialect<verif::VerifDialect>();
289 target.addIllegalOp<verif::HasBeenResetOp>();
290 target.addIllegalOp<ltl::PastOp>();
291
292 auto isLegal = [](Operation *op) {
293 auto hasNonAssertUsers = std::any_of(
294 op->getUsers().begin(), op->getUsers().end(), [](Operation *user) {
295 return !isa<verif::AssertOp, verif::ClockedAssertOp>(user);
296 });
297 auto hasIntegerResultTypes =
298 std::all_of(op->getResultTypes().begin(), op->getResultTypes().end(),
299 [](Type type) { return isa<IntegerType>(type); });
300 // If there are users other than asserts, we can't map it to comb (unless
301 // the return type is already integer anyway)
302 if (hasNonAssertUsers && !hasIntegerResultTypes)
303 return true;
304
305 // Otherwise illegal if operands are i1
306 return std::any_of(
307 op->getOperands().begin(), op->getOperands().end(),
308 [](Value operand) { return !isa<IntegerType>(operand.getType()); });
309 };
310 target.addDynamicallyLegalOp<ltl::ImplicationOp>(isLegal);
311 target.addDynamicallyLegalOp<ltl::NotOp>(isLegal);
312 target.addDynamicallyLegalOp<ltl::AndOp>(isLegal);
313 target.addDynamicallyLegalOp<ltl::OrOp>(isLegal);
314 target.addDynamicallyLegalOp<ltl::IntersectOp>(isLegal);
315
316 // Create type converters, mostly just to convert an ltl property to a bool
317 mlir::TypeConverter converter;
318
319 // Convert the ltl property type to a built-in type
320 converter.addConversion([](IntegerType type) { return type; });
321 converter.addConversion([](ltl::PropertyType type) {
322 return IntegerType::get(type.getContext(), 1);
323 });
324 converter.addConversion([](ltl::SequenceType type) {
325 return IntegerType::get(type.getContext(), 1);
326 });
327
328 // Basic materializations
329 converter.addTargetMaterialization(
330 [&](mlir::OpBuilder &builder, mlir::Type resultType,
331 mlir::ValueRange inputs, mlir::Location loc) -> mlir::Value {
332 if (inputs.size() != 1)
333 return Value();
334 return UnrealizedConversionCastOp::create(builder, loc, resultType,
335 inputs[0])
336 ->getResult(0);
337 });
338
339 converter.addSourceMaterialization(
340 [&](mlir::OpBuilder &builder, mlir::Type resultType,
341 mlir::ValueRange inputs, mlir::Location loc) -> mlir::Value {
342 if (inputs.size() != 1)
343 return Value();
344 return UnrealizedConversionCastOp::create(builder, loc, resultType,
345 inputs[0])
346 ->getResult(0);
347 });
348
349 // Create the operation rewrite patters
350 RewritePatternSet patterns(&getContext());
351 patterns.add<HasBeenResetOpConversion, LTLImplicationConversion,
352 LTLNotConversion, LTLAndOpConversion, LTLOrOpConversion,
353 LTLIntersectOpConversion, LTLPastOpConversion>(
354 converter, patterns.getContext());
355 // Apply the conversions
356 if (failed(
357 applyPartialConversion(getOperation(), target, std::move(patterns))))
358 return signalPassFailure();
359
360 // Clean up remaining unrealized casts by changing assert argument types
361 getOperation().walk([&](Operation *op) {
362 if (!isa<verif::AssertOp, verif::ClockedAssertOp>(op))
363 return;
364 Value prop = op->getOperand(0);
365 if (auto cast = prop.getDefiningOp<UnrealizedConversionCastOp>()) {
366 // Make sure that the cast is from an i1, not something random that was
367 // in the input
368 if (auto intType = dyn_cast<IntegerType>(cast.getOperandTypes()[0]);
369 intType && intType.getWidth() == 1)
370 op->setOperand(0, cast.getInputs()[0]);
371 }
372 });
373}
374
375// Basic default constructor
376std::unique_ptr<mlir::Pass> circt::createLowerLTLToCorePass() {
377 return std::make_unique<LowerLTLToCorePass>();
378}
assert(baseType &&"element must be base type")
Instantiate one of these and use it to build typed backedges.
Backedge get(mlir::Type resultType, mlir::LocationAttr optionalLoc={})
Create a typed backedge.
Backedge is a wrapper class around a Value.
create(data_type, value)
Definition hw.py:433
create(cls, result_type, reset=None, reset_value=None, name=None, sym_name=None, **kwargs)
Definition seq.py:157
calyx::RegisterOp createRegister(Location loc, OpBuilder &builder, ComponentOp component, size_t width, Twine prefix)
Creates a RegisterOp, with input and output port bit widths defined by width.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< mlir::Pass > createLowerLTLToCorePass()
Definition hw.py:1
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition seq.py:21