CIRCT 22.0.0git
Loading...
Searching...
No Matches
AssertionExpr.cpp
Go to the documentation of this file.
1//===- AssertionExpr.cpp - Slang assertion expression conversion ----------===//
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#include "slang/ast/expressions/AssertionExpr.h"
13#include "circt/Support/LLVM.h"
14#include "mlir/IR/BuiltinAttributes.h"
15#include "mlir/Support/LLVM.h"
16#include "slang/ast/SystemSubroutine.h"
17
18#include <optional>
19#include <utility>
20
21using namespace circt;
22using namespace ImportVerilog;
23
24// NOLINTBEGIN(misc-no-recursion)
25namespace {
26struct AssertionExprVisitor {
27 Context &context;
28 Location loc;
29 OpBuilder &builder;
30
31 AssertionExprVisitor(Context &context, Location loc)
32 : context(context), loc(loc), builder(context.builder) {}
33
34 /// Helper to convert a range (min, optional max) to MLIR integer attributes
35 std::pair<mlir::IntegerAttr, mlir::IntegerAttr>
36 convertRangeToAttrs(uint32_t min,
37 std::optional<uint32_t> max = std::nullopt) {
38 auto minAttr = builder.getI64IntegerAttr(min);
39 mlir::IntegerAttr rangeAttr;
40 if (max.has_value()) {
41 rangeAttr = builder.getI64IntegerAttr(max.value() - min);
42 }
43 return {minAttr, rangeAttr};
44 }
45
46 /// Add repetition operation to a sequence
47 Value createRepetition(Location loc,
48 const slang::ast::SequenceRepetition &repetition,
49 Value &inputSequence) {
50 // Extract cycle range
51 auto [minRepetitions, repetitionRange] =
52 convertRangeToAttrs(repetition.range.min, repetition.range.max);
53
54 using slang::ast::SequenceRepetition;
55
56 // Check if repetition range is required
57 if ((repetition.kind == SequenceRepetition::Nonconsecutive ||
58 repetition.kind == SequenceRepetition::GoTo) &&
59 !repetitionRange) {
60 mlir::emitError(loc,
61 repetition.kind == SequenceRepetition::Nonconsecutive
62 ? "Nonconsecutive repetition requires a maximum value"
63 : "GoTo repetition requires a maximum value");
64 return {};
65 }
66
67 switch (repetition.kind) {
68 case SequenceRepetition::Consecutive:
69 return ltl::RepeatOp::create(builder, loc, inputSequence, minRepetitions,
70 repetitionRange);
71 case SequenceRepetition::Nonconsecutive:
72 return ltl::NonConsecutiveRepeatOp::create(
73 builder, loc, inputSequence, minRepetitions, repetitionRange);
74 case SequenceRepetition::GoTo:
75 return ltl::GoToRepeatOp::create(builder, loc, inputSequence,
76 minRepetitions, repetitionRange);
77 }
78 llvm_unreachable("All enum values handled in switch");
79 }
80
81 Value visit(const slang::ast::SimpleAssertionExpr &expr) {
82 // Handle expression
83 auto value = context.convertRvalueExpression(expr.expr);
84 if (!value)
85 return {};
86 auto loc = context.convertLocation(expr.expr.sourceRange);
87 auto valueType = value.getType();
88 // For assertion instances the value is already the expected type, convert
89 // boolean value
90 if (!mlir::isa<ltl::SequenceType, ltl::PropertyType>(valueType)) {
91 value = context.convertToI1(value);
92 }
93 if (!value)
94 return {};
95
96 // Handle repetition
97 // The optional repetition is empty, return the converted expression
98 if (!expr.repetition.has_value()) {
99 return value;
100 }
101
102 // There is a repetition, embed the expression into the kind of given
103 // repetition
104 return createRepetition(loc, expr.repetition.value(), value);
105 }
106
107 Value visit(const slang::ast::SequenceConcatExpr &expr) {
108 // Create a sequence of delayed operations, combined with a concat operation
109 assert(!expr.elements.empty());
110
111 SmallVector<Value> sequenceElements;
112
113 for (const auto &concatElement : expr.elements) {
114 Value sequenceValue =
115 context.convertAssertionExpression(*concatElement.sequence, loc);
116 if (!sequenceValue)
117 return {};
118
119 [[maybe_unused]] Type valueType = sequenceValue.getType();
120 assert(valueType.isInteger(1) || mlir::isa<ltl::SequenceType>(valueType));
121
122 auto [delayMin, delayRange] =
123 convertRangeToAttrs(concatElement.delay.min, concatElement.delay.max);
124 auto delayedSequence = ltl::DelayOp::create(builder, loc, sequenceValue,
125 delayMin, delayRange);
126 sequenceElements.push_back(delayedSequence);
127 }
128
129 return builder.createOrFold<ltl::ConcatOp>(loc, sequenceElements);
130 }
131
132 Value visit(const slang::ast::UnaryAssertionExpr &expr) {
133 auto value = context.convertAssertionExpression(expr.expr, loc);
134 if (!value)
135 return {};
136 using slang::ast::UnaryAssertionOperator;
137 switch (expr.op) {
138 case UnaryAssertionOperator::Not:
139 return ltl::NotOp::create(builder, loc, value);
140 case UnaryAssertionOperator::SEventually:
141 if (expr.range.has_value()) {
142 mlir::emitError(loc, "Strong eventually with range not supported");
143 return {};
144 } else {
145 return ltl::EventuallyOp::create(builder, loc, value);
146 }
147 case UnaryAssertionOperator::Always: {
148 std::pair<mlir::IntegerAttr, mlir::IntegerAttr> attr = {
149 builder.getI64IntegerAttr(0), mlir::IntegerAttr{}};
150 if (expr.range.has_value()) {
151 attr =
152 convertRangeToAttrs(expr.range.value().min, expr.range.value().max);
153 }
154 return ltl::RepeatOp::create(builder, loc, value, attr.first,
155 attr.second);
156 }
157 case UnaryAssertionOperator::NextTime: {
158 auto minRepetitions = builder.getI64IntegerAttr(1);
159 if (expr.range.has_value()) {
160 minRepetitions = builder.getI64IntegerAttr(expr.range.value().min);
161 }
162 return ltl::DelayOp::create(builder, loc, value, minRepetitions,
163 builder.getI64IntegerAttr(0));
164 }
165 case UnaryAssertionOperator::Eventually:
166 case UnaryAssertionOperator::SNextTime:
167 case UnaryAssertionOperator::SAlways:
168 mlir::emitError(loc, "unsupported unary operator: ")
169 << slang::ast::toString(expr.op);
170 return {};
171 }
172 llvm_unreachable("All enum values handled in switch");
173 }
174
175 Value visit(const slang::ast::BinaryAssertionExpr &expr) {
176 auto lhs = context.convertAssertionExpression(expr.left, loc);
177 auto rhs = context.convertAssertionExpression(expr.right, loc);
178 if (!lhs || !rhs)
179 return {};
180 SmallVector<Value, 2> operands = {lhs, rhs};
181 using slang::ast::BinaryAssertionOperator;
182 switch (expr.op) {
183 case BinaryAssertionOperator::And:
184 return ltl::AndOp::create(builder, loc, operands);
185 case BinaryAssertionOperator::Or:
186 return ltl::OrOp::create(builder, loc, operands);
187 case BinaryAssertionOperator::Intersect:
188 return ltl::IntersectOp::create(builder, loc, operands);
189 case BinaryAssertionOperator::Throughout: {
190 auto lhsRepeat = ltl::RepeatOp::create(
191 builder, loc, lhs, builder.getI64IntegerAttr(0), mlir::IntegerAttr{});
192 return ltl::IntersectOp::create(builder, loc,
193 SmallVector<Value, 2>{lhsRepeat, rhs});
194 }
195 case BinaryAssertionOperator::Within: {
196 auto constOne =
197 hw::ConstantOp::create(builder, loc, builder.getI1Type(), 1);
198 auto oneRepeat = ltl::RepeatOp::create(builder, loc, constOne,
199 builder.getI64IntegerAttr(0),
200 mlir::IntegerAttr{});
201 auto repeatDelay = ltl::DelayOp::create(builder, loc, oneRepeat,
202 builder.getI64IntegerAttr(1),
203 builder.getI64IntegerAttr(0));
204 auto lhsDelay =
205 ltl::DelayOp::create(builder, loc, lhs, builder.getI64IntegerAttr(1),
206 builder.getI64IntegerAttr(0));
207 auto combined = ltl::ConcatOp::create(
208 builder, loc, SmallVector<Value, 3>{repeatDelay, lhsDelay, constOne});
209 return ltl::IntersectOp::create(builder, loc,
210 SmallVector<Value, 2>{combined, rhs});
211 }
212 case BinaryAssertionOperator::Iff: {
213 auto ored = ltl::OrOp::create(builder, loc, operands);
214 auto notOred = ltl::NotOp::create(builder, loc, ored);
215 auto anded = ltl::AndOp::create(builder, loc, operands);
216 return ltl::OrOp::create(builder, loc,
217 SmallVector<Value, 2>{notOred, anded});
218 }
219 case BinaryAssertionOperator::Until:
220 return ltl::UntilOp::create(builder, loc, operands);
221 case BinaryAssertionOperator::UntilWith: {
222 auto untilOp = ltl::UntilOp::create(builder, loc, operands);
223 auto andOp = ltl::AndOp::create(builder, loc, operands);
224 auto notUntil = ltl::NotOp::create(builder, loc, untilOp);
225 return ltl::OrOp::create(builder, loc,
226 SmallVector<Value, 2>{notUntil, andOp});
227 }
228 case BinaryAssertionOperator::Implies: {
229 auto notLhs = ltl::NotOp::create(builder, loc, lhs);
230 return ltl::OrOp::create(builder, loc,
231 SmallVector<Value, 2>{notLhs, rhs});
232 }
233 case BinaryAssertionOperator::OverlappedImplication:
234 return ltl::ImplicationOp::create(builder, loc, operands);
235 case BinaryAssertionOperator::NonOverlappedImplication: {
236 auto constOne =
237 hw::ConstantOp::create(builder, loc, builder.getI1Type(), 1);
238 auto lhsDelay =
239 ltl::DelayOp::create(builder, loc, lhs, builder.getI64IntegerAttr(1),
240 builder.getI64IntegerAttr(0));
241 auto antecedent = ltl::ConcatOp::create(
242 builder, loc, SmallVector<Value, 2>{lhsDelay, constOne});
243 return ltl::ImplicationOp::create(builder, loc,
244 SmallVector<Value, 2>{antecedent, rhs});
245 }
246 case BinaryAssertionOperator::OverlappedFollowedBy: {
247 auto notRhs = ltl::NotOp::create(builder, loc, rhs);
248 auto implication = ltl::ImplicationOp::create(
249 builder, loc, SmallVector<Value, 2>{lhs, notRhs});
250 return ltl::NotOp::create(builder, loc, implication);
251 }
252 case BinaryAssertionOperator::NonOverlappedFollowedBy: {
253 auto constOne =
254 hw::ConstantOp::create(builder, loc, builder.getI1Type(), 1);
255 auto notRhs = ltl::NotOp::create(builder, loc, rhs);
256 auto lhsDelay =
257 ltl::DelayOp::create(builder, loc, lhs, builder.getI64IntegerAttr(1),
258 builder.getI64IntegerAttr(0));
259 auto antecedent = ltl::ConcatOp::create(
260 builder, loc, SmallVector<Value, 2>{lhsDelay, constOne});
261 auto implication = ltl::ImplicationOp::create(
262 builder, loc, SmallVector<Value, 2>{antecedent, notRhs});
263 return ltl::NotOp::create(builder, loc, implication);
264 }
265 case BinaryAssertionOperator::SUntil:
266 case BinaryAssertionOperator::SUntilWith:
267 mlir::emitError(loc, "unsupported binary operator: ")
268 << slang::ast::toString(expr.op);
269 return {};
270 }
271 llvm_unreachable("All enum values handled in switch");
272 }
273
274 Value visit(const slang::ast::ClockingAssertionExpr &expr) {
275 auto assertionExpr = context.convertAssertionExpression(expr.expr, loc);
276 if (!assertionExpr)
277 return {};
278 return context.convertLTLTimingControl(expr.clocking, assertionExpr);
279 }
280
281 /// Emit an error for all other expressions.
282 template <typename T>
283 Value visit(T &&node) {
284 mlir::emitError(loc, "unsupported expression: ")
285 << slang::ast::toString(node.kind);
286 return {};
287 }
288
289 Value visitInvalid(const slang::ast::AssertionExpr &expr) {
290 mlir::emitError(loc, "invalid expression");
291 return {};
292 }
293};
294} // namespace
295
296FailureOr<Value> Context::convertAssertionSystemCallArity1(
297 const slang::ast::SystemSubroutine &subroutine, Location loc, Value value) {
298
299 auto systemCallRes =
300 llvm::StringSwitch<std::function<FailureOr<Value>()>>(subroutine.name)
301 // Translate $fell to ¬x[0] ∧ x[-1]
302 .Case("$fell",
303 [&]() -> Value {
304 auto current = value;
305 auto past =
306 ltl::PastOp::create(builder, loc, value, 1).getResult();
307 auto notCurrent =
308 ltl::NotOp::create(builder, loc, current).getResult();
309 auto pastAndNotCurrent =
310 ltl::AndOp::create(builder, loc, {notCurrent, past})
311 .getResult();
312 return pastAndNotCurrent;
313 })
314 // Translate $rose to x[0] ∧ ¬x[-1]
315 .Case("$rose",
316 [&]() -> Value {
317 auto past =
318 ltl::PastOp::create(builder, loc, value, 1).getResult();
319 auto notPast =
320 ltl::NotOp::create(builder, loc, past).getResult();
321 auto current = value;
322 auto notPastAndCurrent =
323 ltl::AndOp::create(builder, loc, {current, notPast})
324 .getResult();
325 return notPastAndCurrent;
326 })
327 // Translate $stable to ( x[0] ∧ x[-1] ) ⋁ ( ¬x[0] ∧ ¬x[-1] )
328 .Case("$stable",
329 [&]() -> Value {
330 auto past =
331 ltl::PastOp::create(builder, loc, value, 1).getResult();
332 auto notPast =
333 ltl::NotOp::create(builder, loc, past).getResult();
334 auto current = value;
335 auto notCurrent =
336 ltl::NotOp::create(builder, loc, value).getResult();
337 auto pastAndCurrent =
338 ltl::AndOp::create(builder, loc, {current, past})
339 .getResult();
340 auto notPastAndNotCurrent =
341 ltl::AndOp::create(builder, loc, {notCurrent, notPast})
342 .getResult();
343 auto stable =
344 ltl::OrOp::create(builder, loc,
345 {pastAndCurrent, notPastAndNotCurrent})
346 .getResult();
347 return stable;
348 })
349 .Default([&]() -> Value { return {}; });
350 return systemCallRes();
351}
352
354 const slang::ast::CallExpression &expr,
355 const slang::ast::CallExpression::SystemCallInfo &info, Location loc) {
356
357 const auto &subroutine = *info.subroutine;
358 auto args = expr.arguments();
359
360 FailureOr<Value> result;
361 Value value;
362 Value boolVal;
363
364 switch (args.size()) {
365 case (1):
366 value = this->convertRvalueExpression(*args[0]);
367 boolVal = builder.createOrFold<moore::ToBuiltinBoolOp>(loc, value);
368 if (!boolVal)
369 return {};
370 result = this->convertAssertionSystemCallArity1(subroutine, loc, boolVal);
371 break;
372
373 default:
374 break;
375 }
376
377 if (failed(result))
378 return {};
379 if (*result)
380 return *result;
381
382 mlir::emitError(loc) << "unsupported system call `" << subroutine.name << "`";
383 return {};
384}
385
386Value Context::convertAssertionExpression(const slang::ast::AssertionExpr &expr,
387 Location loc) {
388 AssertionExprVisitor visitor{*this, loc};
389 return expr.visit(visitor);
390}
391// NOLINTEND(misc-no-recursion)
392
393/// Helper function to convert a value to an i1 value.
394Value Context::convertToI1(Value value) {
395 if (!value)
396 return {};
397 auto type = dyn_cast<moore::IntType>(value.getType());
398 if (!type || type.getBitSize() != 1) {
399 mlir::emitError(value.getLoc(), "expected a 1-bit integer");
400 return {};
401 }
402
403 return moore::ToBuiltinBoolOp::create(builder, value.getLoc(), value);
404}
assert(baseType &&"element must be base type")
create(data_type, value)
Definition hw.py:433
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
A helper class to facilitate the conversion from a Slang AST to MLIR operations.
Value convertToI1(Value value)
Helper function to convert a value to a MLIR I1 value.
Value convertLTLTimingControl(const slang::ast::TimingControl &ctrl, const Value &seqOrPro)
OpBuilder builder
The builder used to create IR operations.
Value convertAssertionCallExpression(const slang::ast::CallExpression &expr, const slang::ast::CallExpression::SystemCallInfo &info, Location loc)
Value convertRvalueExpression(const slang::ast::Expression &expr, Type requiredType={})
FailureOr< Value > convertAssertionSystemCallArity1(const slang::ast::SystemSubroutine &subroutine, Location loc, Value value)
Convert system function calls within properties and assertion with a single argument.
Value convertAssertionExpression(const slang::ast::AssertionExpr &expr, Location loc)
Location convertLocation(slang::SourceLocation loc)
Convert a slang SourceLocation into an MLIR Location.