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 auto fell = comb::ICmpOp::create(builder, loc,
327 comb::ICmpPredicate::ugt,
328 past, current, false)
329 .getResult();
330 return fell;
331 })
332 // Translate $rose to x[0] ∧ ¬x[-1]
333 .Case("$rose",
334 [&]() -> Value {
335 auto past =
336 ltl::PastOp::create(builder, loc, value, 1, Value{})
337 .getResult();
338 auto current = value;
339 auto rose = comb::ICmpOp::create(builder, loc,
340 comb::ICmpPredicate::ult,
341 past, current, false)
342 .getResult();
343 return rose;
344 })
345 // Translate $changed to ( ¬x[0] ∧ x[-1] ) ⋁ ( x[0] ∧ ¬x[-1] )
346 .Case("$changed",
347 [&]() -> Value {
348 auto past =
349 ltl::PastOp::create(builder, loc, value, 1, Value{})
350 .getResult();
351 auto current = value;
352 auto changed = comb::ICmpOp::create(builder, loc,
353 comb::ICmpPredicate::ne,
354 past, current, false)
355 .getResult();
356 return changed;
357 })
358 // Translate $stable to ( x[0] ∧ x[-1] ) ⋁ ( ¬x[0] ∧ ¬x[-1] )
359 .Case("$stable",
360 [&]() -> Value {
361 auto past =
362 ltl::PastOp::create(builder, loc, value, 1, Value{})
363 .getResult();
364 auto current = value;
365 auto stable = comb::ICmpOp::create(builder, loc,
366 comb::ICmpPredicate::eq,
367 past, current, false)
368 .getResult();
369 return stable;
370 })
371 .Case("$past",
372 [&]() -> Value {
373 Value past =
374 ltl::PastOp::create(builder, loc, value, 1, Value{});
375 // Cast back to Moore integers so Moore ops can use the result
376 // if needed
377 if (auto ty = dyn_cast<moore::IntType>(originalType)) {
378 past = moore::FromBuiltinIntOp::create(builder, loc, past);
379 if (ty.getDomain() == Domain::FourValued)
380 past = moore::IntToLogicOp::create(builder, loc, past);
381 }
382 return past;
383 })
384 .Default([&]() -> Value { return {}; });
385 return systemCallRes();
386}
387
389 const slang::ast::CallExpression &expr,
390 const slang::ast::CallExpression::SystemCallInfo &info, Location loc) {
391
392 const auto &subroutine = *info.subroutine;
393 auto args = expr.arguments();
394
395 FailureOr<Value> result;
396 Value value;
397 Value intVal;
398 Type originalType;
399 moore::IntType valTy;
400
401 switch (args.size()) {
402 case (1):
403 value = this->convertRvalueExpression(*args[0]);
404 originalType = value.getType();
405 valTy = dyn_cast<moore::IntType>(value.getType());
406 if (!valTy) {
407 mlir::emitError(loc) << "expected integer argument for system call `"
408 << subroutine.name << "`";
409 return {};
410 }
411 // If the value is four-valued, we need to map it to two-valued before we
412 // cast it to a builtin int
413 if (valTy.getDomain() == Domain::FourValued) {
414 value = builder.createOrFold<moore::LogicToIntOp>(loc, value);
415 }
416 intVal = builder.createOrFold<moore::ToBuiltinIntOp>(loc, value);
417 if (!intVal)
418 return {};
419 result = this->convertAssertionSystemCallArity1(subroutine, loc, intVal,
420 originalType);
421 break;
422
423 default:
424 break;
425 }
426
427 if (failed(result))
428 return {};
429 if (*result)
430 return *result;
431
432 mlir::emitError(loc) << "unsupported system call `" << subroutine.name << "`";
433 return {};
434}
435
436Value Context::convertAssertionExpression(const slang::ast::AssertionExpr &expr,
437 Location loc) {
438 AssertionExprVisitor visitor{*this, loc};
439 return expr.visit(visitor);
440}
441// NOLINTEND(misc-no-recursion)
442
443/// Helper function to convert a value to an i1 value.
444Value Context::convertToI1(Value value) {
445 if (!value)
446 return {};
447 auto loc = value.getLoc();
448 auto type = dyn_cast<moore::IntType>(value.getType());
449 if (!type || type.getBitSize() != 1) {
450 mlir::emitError(loc, "expected a 1-bit integer");
451 return {};
452 }
453 if (type.getDomain() == Domain::FourValued) {
454 value = moore::LogicToIntOp::create(builder, loc, value);
455 }
456 return moore::ToBuiltinIntOp::create(builder, loc, value);
457}
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.