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