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.convertToBool(value);
99 value = context.convertToI1(value);
100 }
101 if (!value)
102 return {};
103
104 // Handle repetition
105 // The optional repetition is empty, return the converted expression
106 if (!expr.repetition.has_value()) {
107 return value;
108 }
109
110 // There is a repetition, embed the expression into the kind of given
111 // repetition
112 return createRepetition(loc, expr.repetition.value(), value);
113 }
114
115 Value visit(const slang::ast::SequenceConcatExpr &expr) {
116 // Create a sequence of delayed operations, combined with a concat operation
117 assert(!expr.elements.empty());
118
119 SmallVector<Value> sequenceElements;
120
121 for (const auto &concatElement : expr.elements) {
122 Value sequenceValue =
123 context.convertAssertionExpression(*concatElement.sequence, loc);
124 if (!sequenceValue)
125 return {};
126
127 [[maybe_unused]] Type valueType = sequenceValue.getType();
128 assert(valueType.isInteger(1) || mlir::isa<ltl::SequenceType>(valueType));
129
130 auto [delayMin, delayRange] =
131 convertRangeToAttrs(concatElement.delay.min, concatElement.delay.max);
132 auto delayedSequence = ltl::DelayOp::create(builder, loc, sequenceValue,
133 delayMin, delayRange);
134 sequenceElements.push_back(delayedSequence);
135 }
136
137 return builder.createOrFold<ltl::ConcatOp>(loc, sequenceElements);
138 }
139
140 Value visit(const slang::ast::UnaryAssertionExpr &expr) {
141 auto value = context.convertAssertionExpression(expr.expr, loc);
142 if (!value)
143 return {};
144 using slang::ast::UnaryAssertionOperator;
145 switch (expr.op) {
146 case UnaryAssertionOperator::Not:
147 return ltl::NotOp::create(builder, loc, value);
148 case UnaryAssertionOperator::SEventually:
149 if (expr.range.has_value()) {
150 mlir::emitError(loc, "Strong eventually with range not supported");
151 return {};
152 } else {
153 return ltl::EventuallyOp::create(builder, loc, value);
154 }
155 case UnaryAssertionOperator::Always: {
156 std::pair<mlir::IntegerAttr, mlir::IntegerAttr> attr = {
157 builder.getI64IntegerAttr(0), mlir::IntegerAttr{}};
158 if (expr.range.has_value()) {
159 attr =
160 convertRangeToAttrs(expr.range.value().min, expr.range.value().max);
161 }
162 return ltl::RepeatOp::create(builder, loc, value, attr.first,
163 attr.second);
164 }
165 case UnaryAssertionOperator::NextTime: {
166 auto minRepetitions = builder.getI64IntegerAttr(1);
167 if (expr.range.has_value()) {
168 minRepetitions = builder.getI64IntegerAttr(expr.range.value().min);
169 }
170 return ltl::DelayOp::create(builder, loc, value, minRepetitions,
171 builder.getI64IntegerAttr(0));
172 }
173 case UnaryAssertionOperator::Eventually:
174 case UnaryAssertionOperator::SNextTime:
175 case UnaryAssertionOperator::SAlways:
176 mlir::emitError(loc, "unsupported unary operator: ")
177 << slang::ast::toString(expr.op);
178 return {};
179 }
180 llvm_unreachable("All enum values handled in switch");
181 }
182
183 Value visit(const slang::ast::BinaryAssertionExpr &expr) {
184 auto lhs = context.convertAssertionExpression(expr.left, loc);
185 auto rhs = context.convertAssertionExpression(expr.right, loc);
186 if (!lhs || !rhs)
187 return {};
188 SmallVector<Value, 2> operands = {lhs, rhs};
189 using slang::ast::BinaryAssertionOperator;
190 switch (expr.op) {
191 case BinaryAssertionOperator::And:
192 return ltl::AndOp::create(builder, loc, operands);
193 case BinaryAssertionOperator::Or:
194 return ltl::OrOp::create(builder, loc, operands);
195 case BinaryAssertionOperator::Intersect:
196 return ltl::IntersectOp::create(builder, loc, operands);
197 case BinaryAssertionOperator::Throughout: {
198 auto lhsRepeat = ltl::RepeatOp::create(
199 builder, loc, lhs, builder.getI64IntegerAttr(0), mlir::IntegerAttr{});
200 return ltl::IntersectOp::create(builder, loc,
201 SmallVector<Value, 2>{lhsRepeat, rhs});
202 }
203 case BinaryAssertionOperator::Within: {
204 auto constOne =
205 hw::ConstantOp::create(builder, loc, builder.getI1Type(), 1);
206 auto oneRepeat = ltl::RepeatOp::create(builder, loc, constOne,
207 builder.getI64IntegerAttr(0),
208 mlir::IntegerAttr{});
209 auto repeatDelay = ltl::DelayOp::create(builder, loc, oneRepeat,
210 builder.getI64IntegerAttr(1),
211 builder.getI64IntegerAttr(0));
212 auto lhsDelay =
213 ltl::DelayOp::create(builder, loc, lhs, builder.getI64IntegerAttr(1),
214 builder.getI64IntegerAttr(0));
215 auto combined = ltl::ConcatOp::create(
216 builder, loc, SmallVector<Value, 3>{repeatDelay, lhsDelay, constOne});
217 return ltl::IntersectOp::create(builder, loc,
218 SmallVector<Value, 2>{combined, rhs});
219 }
220 case BinaryAssertionOperator::Iff: {
221 auto ored = ltl::OrOp::create(builder, loc, operands);
222 auto notOred = ltl::NotOp::create(builder, loc, ored);
223 auto anded = ltl::AndOp::create(builder, loc, operands);
224 return ltl::OrOp::create(builder, loc,
225 SmallVector<Value, 2>{notOred, anded});
226 }
227 case BinaryAssertionOperator::Until:
228 return ltl::UntilOp::create(builder, loc, operands);
229 case BinaryAssertionOperator::UntilWith: {
230 auto untilOp = ltl::UntilOp::create(builder, loc, operands);
231 auto andOp = ltl::AndOp::create(builder, loc, operands);
232 auto notUntil = ltl::NotOp::create(builder, loc, untilOp);
233 return ltl::OrOp::create(builder, loc,
234 SmallVector<Value, 2>{notUntil, andOp});
235 }
236 case BinaryAssertionOperator::Implies: {
237 auto notLhs = ltl::NotOp::create(builder, loc, lhs);
238 return ltl::OrOp::create(builder, loc,
239 SmallVector<Value, 2>{notLhs, rhs});
240 }
241 case BinaryAssertionOperator::OverlappedImplication:
242 return ltl::ImplicationOp::create(builder, loc, operands);
243 case BinaryAssertionOperator::NonOverlappedImplication: {
244 auto constOne =
245 hw::ConstantOp::create(builder, loc, builder.getI1Type(), 1);
246 auto lhsDelay =
247 ltl::DelayOp::create(builder, loc, lhs, builder.getI64IntegerAttr(1),
248 builder.getI64IntegerAttr(0));
249 auto antecedent = ltl::ConcatOp::create(
250 builder, loc, SmallVector<Value, 2>{lhsDelay, constOne});
251 return ltl::ImplicationOp::create(builder, loc,
252 SmallVector<Value, 2>{antecedent, rhs});
253 }
254 case BinaryAssertionOperator::OverlappedFollowedBy: {
255 auto notRhs = ltl::NotOp::create(builder, loc, rhs);
256 auto implication = ltl::ImplicationOp::create(
257 builder, loc, SmallVector<Value, 2>{lhs, notRhs});
258 return ltl::NotOp::create(builder, loc, implication);
259 }
260 case BinaryAssertionOperator::NonOverlappedFollowedBy: {
261 auto constOne =
262 hw::ConstantOp::create(builder, loc, builder.getI1Type(), 1);
263 auto notRhs = ltl::NotOp::create(builder, loc, rhs);
264 auto lhsDelay =
265 ltl::DelayOp::create(builder, loc, lhs, builder.getI64IntegerAttr(1),
266 builder.getI64IntegerAttr(0));
267 auto antecedent = ltl::ConcatOp::create(
268 builder, loc, SmallVector<Value, 2>{lhsDelay, constOne});
269 auto implication = ltl::ImplicationOp::create(
270 builder, loc, SmallVector<Value, 2>{antecedent, notRhs});
271 return ltl::NotOp::create(builder, loc, implication);
272 }
273 case BinaryAssertionOperator::SUntil:
274 case BinaryAssertionOperator::SUntilWith:
275 mlir::emitError(loc, "unsupported binary operator: ")
276 << slang::ast::toString(expr.op);
277 return {};
278 }
279 llvm_unreachable("All enum values handled in switch");
280 }
281
282 Value visit(const slang::ast::ClockingAssertionExpr &expr) {
283 auto assertionExpr = context.convertAssertionExpression(expr.expr, loc);
284 if (!assertionExpr)
285 return {};
286 return context.convertLTLTimingControl(expr.clocking, assertionExpr);
287 }
288
289 /// Emit an error for all other expressions.
290 template <typename T>
291 Value visit(T &&node) {
292 mlir::emitError(loc, "unsupported expression: ")
293 << slang::ast::toString(node.kind);
294 return {};
295 }
296
297 Value visitInvalid(const slang::ast::AssertionExpr &expr) {
298 mlir::emitError(loc, "invalid expression");
299 return {};
300 }
301};
302} // namespace
303
304FailureOr<Value> Context::convertSampledValueCallArity1(
305 const slang::ast::SystemSubroutine &subroutine, Location loc, Value value,
306 Type originalType, Value clockVal) {
307 using ksn = slang::parsing::KnownSystemName;
308 auto nameId = subroutine.knownNameId;
309
310 // Helper to cast a builtin integer result back to Moore integer types.
311 auto castToMoore = [&](Value v) -> Value {
312 if (auto ty = dyn_cast<moore::IntType>(originalType)) {
313 v = moore::FromBuiltinIntOp::create(builder, loc, v);
314 if (ty.getDomain() == Domain::FourValued)
315 v = moore::IntToLogicOp::create(builder, loc, v);
316 }
317 return v;
318 };
319
320 // Helper to cast a builtin integer result to a two-valued Moore integer type.
321 auto castToTwoValued = [&](Value v) -> Value {
322 return moore::FromBuiltinIntOp::create(builder, loc, v);
323 };
324
325 switch (nameId) {
326 case ksn::Sampled:
327 return castToMoore(ltl::SampledOp::create(builder, loc, value));
328
329 // Translate $fell to ¬x[0] ∧ x[-1]
330 case ksn::Fell: {
331 auto past =
332 ltl::PastOp::create(builder, loc, value, 1, clockVal).getResult();
333 return castToTwoValued(comb::ICmpOp::create(
334 builder, loc, comb::ICmpPredicate::ugt, past, value, false));
335 }
336
337 // Translate $rose to x[0] ∧ ¬x[-1]
338 case ksn::Rose: {
339 auto past =
340 ltl::PastOp::create(builder, loc, value, 1, clockVal).getResult();
341 return castToTwoValued(comb::ICmpOp::create(
342 builder, loc, comb::ICmpPredicate::ult, past, value, false));
343 }
344
345 // Translate $changed to x[0] ≠ x[-1]
346 case ksn::Changed: {
347 auto past =
348 ltl::PastOp::create(builder, loc, value, 1, clockVal).getResult();
349 return castToTwoValued(comb::ICmpOp::create(
350 builder, loc, comb::ICmpPredicate::ne, past, value, false));
351 }
352
353 // Translate $stable to x[0] = x[-1]
354 case ksn::Stable: {
355 auto past =
356 ltl::PastOp::create(builder, loc, value, 1, clockVal).getResult();
357 return castToTwoValued(comb::ICmpOp::create(
358 builder, loc, comb::ICmpPredicate::eq, past, value, false));
359 }
360
361 case ksn::Past:
362 return castToMoore(ltl::PastOp::create(builder, loc, value, 1, clockVal));
363
364 default:
365 return Value{};
366 }
367}
368
370 const slang::ast::CallExpression &expr,
371 const slang::ast::CallExpression::SystemCallInfo &info, Location loc) {
372
373 const slang::ast::TimingControl *clock = nullptr;
374 auto clockIt = sampledValueCallClocks.find(&expr);
375 if (clockIt != sampledValueCallClocks.end())
376 clock = clockIt->second;
377
378 // Non-procedural expressions don't have a clock registered in
379 // sampledValueCallClocks but we can get it from their scope's default clock
380 if (!clock && !info.scope->isProceduralContext())
381 if (auto defClk =
382 info.scope->getCompilation().getDefaultClocking(*info.scope))
383 clock = &defClk->as<slang::ast::ClockingBlockSymbol>().getEvent();
384
385 if (!clock) {
386 mlir::emitError(loc) << "could not determine clocking event for `"
387 << expr.getSubroutineName() << "`";
388 return {};
389 }
390
391 Value clockVal;
392 const slang::ast::SignalEventControl *signal = nullptr;
393 if (clock->kind == slang::ast::TimingControlKind::SignalEvent) {
394 signal = &clock->as<slang::ast::SignalEventControl>();
395 } else if (clock->kind == slang::ast::TimingControlKind::EventList) {
396 mlir::emitError(loc, "sampled value functions with multiple event "
397 "triggers are not supported");
398 return {};
399 } else {
400 llvm_unreachable("unexpected clock kind for assertion");
401 }
402
403 if (signal->edge != slang::ast::EdgeKind::PosEdge) {
404 mlir::emitError(
405 loc, "sampled value functions are only supported with posedge clocks");
406 return {};
407 }
408 clockVal = convertRvalueExpression(signal->expr);
409 if (clockVal)
410 clockVal = convertToI1(clockVal);
411 if (!clockVal)
412 return {};
413
414 const auto &subroutine = *info.subroutine;
415 auto args = expr.arguments();
416
417 FailureOr<Value> result;
418 Value value;
419 Value intVal;
420 Type originalType;
421 moore::IntType valTy;
422
423 switch (args.size()) {
424 case (1):
425 value = this->convertRvalueExpression(*args[0]);
426 originalType = value.getType();
427 valTy = dyn_cast<moore::IntType>(value.getType());
428 if (!valTy) {
429 if (!isa<moore::PackedType>(value.getType())) {
430 mlir::emitError(loc)
431 << "expected integer argument for `" << subroutine.name << "`";
432 return {};
433 }
434 value = materializePackedToSBVConversion(value, loc, /*fallible=*/false);
435 if (!value)
436 return {};
437 valTy = dyn_cast<moore::IntType>(value.getType());
438 if (!valTy) {
439 mlir::emitError(loc)
440 << "expected integer argument for `" << subroutine.name << "`";
441 return {};
442 }
443 }
444
445 // If the value is four-valued, we need to map it to two-valued before we
446 // cast it to a builtin int
447 if (valTy.getDomain() == Domain::FourValued) {
448 value = builder.createOrFold<moore::LogicToIntOp>(loc, value);
449 }
450 intVal = builder.createOrFold<moore::ToBuiltinIntOp>(loc, value);
451 if (!intVal)
452 return {};
453 result = this->convertSampledValueCallArity1(subroutine, loc, intVal,
454 originalType, clockVal);
455 break;
456
457 default:
458 break;
459 }
460
461 if (failed(result))
462 return {};
463 if (*result) {
464 auto expectedTy = convertType(*expr.type);
465 return materializeConversion(expectedTy, *result, expr.type->isSigned(),
466 loc);
467 }
468
469 mlir::emitError(loc) << "unsupported system call `" << subroutine.name << "`";
470 return {};
471}
472
473Value Context::convertAssertionExpression(const slang::ast::AssertionExpr &expr,
474 Location loc) {
475 AssertionExprVisitor visitor{*this, loc};
476 return expr.visit(visitor);
477}
478// NOLINTEND(misc-no-recursion)
479
480/// Helper function to convert a value to an i1 value.
481Value Context::convertToI1(Value value) {
482 if (!value)
483 return {};
484 auto loc = value.getLoc();
485 auto type = dyn_cast<moore::IntType>(value.getType());
486 if (!type || type.getBitSize() != 1) {
487 mlir::emitError(loc, "expected a 1-bit integer");
488 return {};
489 }
490 if (type.getDomain() == Domain::FourValued) {
491 value = moore::LogicToIntOp::create(builder, loc, value);
492 }
493 return moore::ToBuiltinIntOp::create(builder, loc, value);
494}
495
496namespace {
497struct AssertionClockVisitor
498 : slang::ast::ASTVisitor<AssertionClockVisitor,
499 slang::ast::VisitFlags::AllGood> {
501 const slang::analysis::AnalyzedAssertion &assertion;
502 const slang::ast::TimingControl *currentClock = nullptr;
503
504 AssertionClockVisitor(Context &context,
505 const slang::analysis::AnalyzedAssertion &assertion)
506 : context(context), assertion(assertion) {}
507
508 void handle(const slang::ast::CallExpression &node) {
509 if (currentClock)
510 context.sampledValueCallClocks[&node] = currentClock;
511 visitDefault(node);
512 }
513
514 template <typename T>
515 std::enable_if_t<std::is_base_of_v<slang::ast::AssertionExpr, T>>
516 handle(const T &node) {
517 auto *prevClock = currentClock;
518 if (auto *clk = assertion.getClock(node))
519 currentClock = clk;
520 visitDefault(node);
521 currentClock = prevClock;
522 }
523};
524} // namespace
525
527 compilation.freeze();
528 slang::analysis::AnalysisManager am;
529 am.addListener([this](const slang::analysis::AnalyzedAssertion &assertion) {
530 AssertionClockVisitor visitor{*this, assertion};
531 assertion.getRoot().visit(visitor);
532 });
533 // AnalyzedProcedure captures both procedures and continuous assignments
534 am.addListener([this](const slang::analysis::AnalyzedProcedure &procedure) {
535 if (const auto clock = procedure.getInferredClock())
536 for (const auto *call : procedure.getCallExpressions())
537 sampledValueCallClocks[call] = clock;
538 });
539 am.analyze(compilation);
540 compilation.unfreeze();
541}
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.
void populateSampledValueClocks()
Generates a map from sampled value system calls to clocks using Slang's analysis.
Value convertLTLTimingControl(const slang::ast::TimingControl &ctrl, const Value &seqOrPro)
slang::ast::Compilation & compilation
OpBuilder builder
The builder used to create IR operations.
Value convertSampledValueCallExpression(const slang::ast::CallExpression &expr, const slang::ast::CallExpression::SystemCallInfo &info, Location loc)
Type convertType(const slang::ast::Type &type, LocationAttr loc={})
Convert a slang type into an MLIR type.
Definition Types.cpp:224
Value convertToBool(Value value)
Helper function to convert a value to its "truthy" boolean value.
Value materializePackedToSBVConversion(Value value, Location loc, bool fallible)
Helper function to convert a PackedType value to its simple bit vector representation,...
Value convertRvalueExpression(const slang::ast::Expression &expr, Type requiredType={})
DenseMap< const slang::ast::CallExpression *, const slang::ast::TimingControl * > sampledValueCallClocks
Maps sampled value system calls to their corresponding clocks.
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.
Value convertAssertionExpression(const slang::ast::AssertionExpr &expr, Location loc)
FailureOr< Value > convertSampledValueCallArity1(const slang::ast::SystemSubroutine &subroutine, Location loc, Value value, Type originalType, Value clockVal)
Convert sampled value system function calls with a single argument.
Location convertLocation(slang::SourceLocation loc)
Convert a slang SourceLocation into an MLIR Location.