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