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