9#include "slang/ast/expressions/AssertionExpr.h"
18#include "mlir/IR/BuiltinAttributes.h"
19#include "mlir/Support/LLVM.h"
20#include "slang/analysis/AnalysisManager.h"
21#include "slang/analysis/AnalyzedAssertion.h"
22#include "slang/ast/ASTVisitor.h"
23#include "slang/ast/SystemSubroutine.h"
24#include "slang/parsing/KnownSystemName.h"
30using namespace ImportVerilog;
34struct AssertionExprVisitor {
39 AssertionExprVisitor(
Context &context, Location loc)
40 : context(context), loc(loc), builder(context.builder) {}
43 std::pair<mlir::IntegerAttr, mlir::IntegerAttr>
44 convertRangeToAttrs(uint32_t min,
45 std::optional<uint32_t> max = std::nullopt) {
46 auto minAttr = builder.getI64IntegerAttr(min);
47 mlir::IntegerAttr rangeAttr;
48 if (max.has_value()) {
49 rangeAttr = builder.getI64IntegerAttr(max.value() - min);
51 return {minAttr, rangeAttr};
55 Value createRepetition(Location loc,
56 const slang::ast::SequenceRepetition &repetition,
57 Value &inputSequence) {
59 auto [minRepetitions, repetitionRange] =
60 convertRangeToAttrs(repetition.range.min, repetition.range.max);
62 using slang::ast::SequenceRepetition;
65 if ((repetition.kind == SequenceRepetition::Nonconsecutive ||
66 repetition.kind == SequenceRepetition::GoTo) &&
69 repetition.kind == SequenceRepetition::Nonconsecutive
70 ?
"Nonconsecutive repetition requires a maximum value"
71 :
"GoTo repetition requires a maximum value");
75 switch (repetition.kind) {
76 case SequenceRepetition::Consecutive:
77 return ltl::RepeatOp::create(builder, loc, inputSequence, minRepetitions,
79 case SequenceRepetition::Nonconsecutive:
80 return ltl::NonConsecutiveRepeatOp::create(
81 builder, loc, inputSequence, minRepetitions, repetitionRange);
82 case SequenceRepetition::GoTo:
83 return ltl::GoToRepeatOp::create(builder, loc, inputSequence,
84 minRepetitions, repetitionRange);
86 llvm_unreachable(
"All enum values handled in switch");
89 Value visit(
const slang::ast::SimpleAssertionExpr &expr) {
95 auto valueType = value.getType();
98 if (!mlir::isa<ltl::SequenceType, ltl::PropertyType, mlir::IntegerType>(
107 if (!expr.repetition.has_value()) {
113 return createRepetition(loc, expr.repetition.value(), value);
116 Value visit(
const slang::ast::SequenceConcatExpr &expr) {
118 assert(!expr.elements.empty());
120 SmallVector<Value> sequenceElements;
122 for (
const auto &concatElement : expr.elements) {
123 Value sequenceValue =
128 [[maybe_unused]] Type valueType = sequenceValue.getType();
129 assert(valueType.isInteger(1) || mlir::isa<ltl::SequenceType>(valueType));
131 auto [delayMin, delayRange] =
132 convertRangeToAttrs(concatElement.delay.min, concatElement.delay.max);
133 auto delayedSequence = ltl::DelayOp::create(builder, loc, sequenceValue,
134 delayMin, delayRange);
135 sequenceElements.push_back(delayedSequence);
138 return builder.createOrFold<ltl::ConcatOp>(loc, sequenceElements);
141 Value visit(
const slang::ast::UnaryAssertionExpr &expr) {
145 using slang::ast::UnaryAssertionOperator;
147 case UnaryAssertionOperator::Not:
148 return ltl::NotOp::create(builder, loc, value);
149 case UnaryAssertionOperator::SEventually:
150 if (expr.range.has_value()) {
151 mlir::emitError(loc,
"Strong eventually with range not supported");
154 return ltl::EventuallyOp::create(builder, loc, value);
156 case UnaryAssertionOperator::Always: {
157 std::pair<mlir::IntegerAttr, mlir::IntegerAttr> attr = {
158 builder.getI64IntegerAttr(0), mlir::IntegerAttr{}};
159 if (expr.range.has_value()) {
161 convertRangeToAttrs(expr.range.value().min, expr.range.value().max);
163 return ltl::RepeatOp::create(builder, loc, value, attr.first,
166 case UnaryAssertionOperator::NextTime: {
167 auto minRepetitions = builder.getI64IntegerAttr(1);
168 if (expr.range.has_value()) {
169 minRepetitions = builder.getI64IntegerAttr(expr.range.value().min);
171 return ltl::DelayOp::create(builder, loc, value, minRepetitions,
172 builder.getI64IntegerAttr(0));
174 case UnaryAssertionOperator::Eventually:
175 case UnaryAssertionOperator::SNextTime:
176 case UnaryAssertionOperator::SAlways:
177 mlir::emitError(loc,
"unsupported unary operator: ")
178 << slang::ast::toString(expr.op);
181 llvm_unreachable(
"All enum values handled in switch");
184 Value visit(
const slang::ast::BinaryAssertionExpr &expr) {
189 SmallVector<Value, 2> operands = {lhs, rhs};
190 using slang::ast::BinaryAssertionOperator;
192 case BinaryAssertionOperator::And:
193 return ltl::AndOp::create(builder, loc, operands);
194 case BinaryAssertionOperator::Or:
195 return ltl::OrOp::create(builder, loc, operands);
196 case BinaryAssertionOperator::Intersect:
197 return ltl::IntersectOp::create(builder, loc, operands);
198 case BinaryAssertionOperator::Throughout: {
199 auto lhsRepeat = ltl::RepeatOp::create(
200 builder, loc, lhs, builder.getI64IntegerAttr(0), mlir::IntegerAttr{});
201 return ltl::IntersectOp::create(builder, loc,
202 SmallVector<Value, 2>{lhsRepeat, rhs});
204 case BinaryAssertionOperator::Within: {
207 auto oneRepeat = ltl::RepeatOp::create(builder, loc, constOne,
208 builder.getI64IntegerAttr(0),
209 mlir::IntegerAttr{});
210 auto repeatDelay = ltl::DelayOp::create(builder, loc, oneRepeat,
211 builder.getI64IntegerAttr(1),
212 builder.getI64IntegerAttr(0));
214 ltl::DelayOp::create(builder, loc, lhs, builder.getI64IntegerAttr(1),
215 builder.getI64IntegerAttr(0));
216 auto combined = ltl::ConcatOp::create(
217 builder, loc, SmallVector<Value, 3>{repeatDelay, lhsDelay, constOne});
218 return ltl::IntersectOp::create(builder, loc,
219 SmallVector<Value, 2>{combined, rhs});
221 case BinaryAssertionOperator::Iff: {
222 auto ored = ltl::OrOp::create(builder, loc, operands);
223 auto notOred = ltl::NotOp::create(builder, loc, ored);
224 auto anded = ltl::AndOp::create(builder, loc, operands);
225 return ltl::OrOp::create(builder, loc,
226 SmallVector<Value, 2>{notOred, anded});
228 case BinaryAssertionOperator::Until:
229 return ltl::UntilOp::create(builder, loc, operands);
230 case BinaryAssertionOperator::UntilWith: {
231 auto untilOp = ltl::UntilOp::create(builder, loc, operands);
232 auto andOp = ltl::AndOp::create(builder, loc, operands);
233 auto notUntil = ltl::NotOp::create(builder, loc, untilOp);
234 return ltl::OrOp::create(builder, loc,
235 SmallVector<Value, 2>{notUntil, andOp});
237 case BinaryAssertionOperator::Implies: {
238 auto notLhs = ltl::NotOp::create(builder, loc, lhs);
239 return ltl::OrOp::create(builder, loc,
240 SmallVector<Value, 2>{notLhs, rhs});
242 case BinaryAssertionOperator::OverlappedImplication:
243 return ltl::ImplicationOp::create(builder, loc, operands);
244 case BinaryAssertionOperator::NonOverlappedImplication: {
248 ltl::DelayOp::create(builder, loc, lhs, builder.getI64IntegerAttr(1),
249 builder.getI64IntegerAttr(0));
250 auto antecedent = ltl::ConcatOp::create(
251 builder, loc, SmallVector<Value, 2>{lhsDelay, constOne});
252 return ltl::ImplicationOp::create(builder, loc,
253 SmallVector<Value, 2>{antecedent, rhs});
255 case BinaryAssertionOperator::OverlappedFollowedBy: {
256 auto notRhs = ltl::NotOp::create(builder, loc, rhs);
257 auto implication = ltl::ImplicationOp::create(
258 builder, loc, SmallVector<Value, 2>{lhs, notRhs});
259 return ltl::NotOp::create(builder, loc, implication);
261 case BinaryAssertionOperator::NonOverlappedFollowedBy: {
264 auto notRhs = ltl::NotOp::create(builder, loc, rhs);
266 ltl::DelayOp::create(builder, loc, lhs, builder.getI64IntegerAttr(1),
267 builder.getI64IntegerAttr(0));
268 auto antecedent = ltl::ConcatOp::create(
269 builder, loc, SmallVector<Value, 2>{lhsDelay, constOne});
270 auto implication = ltl::ImplicationOp::create(
271 builder, loc, SmallVector<Value, 2>{antecedent, notRhs});
272 return ltl::NotOp::create(builder, loc, implication);
274 case BinaryAssertionOperator::SUntil:
275 case BinaryAssertionOperator::SUntilWith:
276 mlir::emitError(loc,
"unsupported binary operator: ")
277 << slang::ast::toString(expr.op);
280 llvm_unreachable(
"All enum values handled in switch");
283 Value visit(
const slang::ast::ClockingAssertionExpr &expr) {
291 template <
typename T>
292 Value visit(T &&node) {
293 mlir::emitError(loc,
"unsupported expression: ")
294 << slang::ast::toString(node.kind);
298 Value visitInvalid(
const slang::ast::AssertionExpr &expr) {
299 mlir::emitError(loc,
"invalid expression");
305FailureOr<Value> Context::convertAssertionSystemCallArity1(
306 const slang::ast::SystemSubroutine &subroutine, Location loc, Value value,
307 Type originalType, Value clockVal) {
308 using ksn = slang::parsing::KnownSystemName;
309 auto nameId = subroutine.knownNameId;
312 auto castToMoore = [&](Value v) -> Value {
313 if (
auto ty = dyn_cast<moore::IntType>(originalType)) {
314 v = moore::FromBuiltinIntOp::create(
builder, loc, v);
315 if (ty.getDomain() == Domain::FourValued)
316 v = moore::IntToLogicOp::create(
builder, loc, v);
323 return castToMoore(ltl::SampledOp::create(
builder, loc, value));
328 ltl::PastOp::create(
builder, loc, value, 1, clockVal).getResult();
329 return castToMoore(comb::ICmpOp::create(
330 builder, loc, comb::ICmpPredicate::ugt, past, value,
false));
336 ltl::PastOp::create(
builder, loc, value, 1, clockVal).getResult();
337 return castToMoore(comb::ICmpOp::create(
338 builder, loc, comb::ICmpPredicate::ult, past, value,
false));
344 ltl::PastOp::create(
builder, loc, value, 1, clockVal).getResult();
345 return castToMoore(comb::ICmpOp::create(
346 builder, loc, comb::ICmpPredicate::ne, past, value,
false));
352 ltl::PastOp::create(
builder, loc, value, 1, clockVal).getResult();
353 return castToMoore(comb::ICmpOp::create(
354 builder, loc, comb::ICmpPredicate::eq, past, value,
false));
358 return castToMoore(ltl::PastOp::create(
builder, loc, value, 1, clockVal));
362 auto minusOne = comb::SubOp::create(
builder, loc, value, one);
363 auto anded = comb::AndOp::create(
builder, loc, value, minusOne);
365 return castToMoore(comb::ICmpOp::create(
366 builder, loc, comb::ICmpPredicate::eq, anded, zero,
false));
371 auto minusOne = comb::SubOp::create(
builder, loc, value, one);
372 auto anded = comb::AndOp::create(
builder, loc, value, minusOne);
374 auto isOneHot0 = comb::ICmpOp::create(
builder, loc, comb::ICmpPredicate::eq,
376 auto isNotZero = comb::ICmpOp::create(
builder, loc, comb::ICmpPredicate::ne,
378 auto result = comb::AndOp::create(
builder, loc, isOneHot0, isNotZero);
379 return castToMoore(result);
387static Value
getIsUnknown(OpBuilder &builder, Location loc, Value value,
388 moore::IntType valTy, MLIRContext *ctx) {
389 Value bitVal = value;
390 if (valTy.getWidth() > 1) {
391 auto mooreI1Type = moore::IntType::get(ctx, 1, valTy.getDomain());
392 bitVal = moore::ReduceXorOp::create(builder, loc, mooreI1Type, value);
397 auto xConst = moore::ConstantOp::create(builder, loc, xType, xValue);
399 return moore::CaseEqOp::create(builder, loc, bitVal, xConst).getResult();
403 const slang::ast::CallExpression &expr,
404 const slang::ast::CallExpression::SystemCallInfo &info, Location loc) {
406 const slang::ast::TimingControl *clock =
nullptr;
409 clock = clockIt->second;
413 const slang::ast::SignalEventControl *signal =
nullptr;
414 if (clock->kind == slang::ast::TimingControlKind::SignalEvent) {
415 signal = &clock->as<slang::ast::SignalEventControl>();
416 }
else if (clock->kind == slang::ast::TimingControlKind::EventList) {
417 mlir::emitError(loc,
"sampled value functions with multiple event "
418 "triggers are not supported");
421 llvm_unreachable(
"unexpected clock kind for assertion");
424 if (signal->edge != slang::ast::EdgeKind::PosEdge) {
427 "sampled value functions are only supported with posedge clocks");
437 const auto &subroutine = *info.subroutine;
438 auto args = expr.arguments();
440 FailureOr<Value> result;
444 moore::IntType valTy;
446 switch (args.size()) {
449 originalType = value.getType();
450 valTy = dyn_cast<moore::IntType>(value.getType());
452 mlir::emitError(loc) <<
"expected integer argument for `"
453 << subroutine.name <<
"`";
460 if (subroutine.knownNameId == slang::parsing::KnownSystemName::IsUnknown) {
465 if ((subroutine.knownNameId == slang::parsing::KnownSystemName::OneHot ||
466 subroutine.knownNameId == slang::parsing::KnownSystemName::OneHot0) &&
467 (valTy.getDomain() == Domain::FourValued)) {
473 Value isUnknownMoore =
476 builder.createOrFold<moore::ToBuiltinIntOp>(loc, isUnknownMoore);
478 Value coerced =
builder.createOrFold<moore::LogicToIntOp>(loc, value);
480 builder.createOrFold<moore::ToBuiltinIntOp>(loc, coerced);
485 subroutine, loc, state2IntVal, originalType, clockVal);
486 if (failed(systemResult) || !*systemResult)
488 Value onehot2state = *systemResult;
491 Value onehot2stateBuiltin =
convertToI1(onehot2state);
494 Value resultBuiltin = comb::MuxOp::create(
495 builder, loc, isUnknown, zeroBuiltin, onehot2stateBuiltin);
498 moore::FromBuiltinIntOp::create(
builder, loc, resultBuiltin);
499 return moore::IntToLogicOp::create(
builder, loc, finalResult);
504 if (valTy.getDomain() == Domain::FourValued) {
505 value =
builder.createOrFold<moore::LogicToIntOp>(loc, value);
507 intVal =
builder.createOrFold<moore::ToBuiltinIntOp>(loc, value);
511 originalType, clockVal);
523 mlir::emitError(loc) <<
"unsupported system call `" << subroutine.name <<
"`";
529 AssertionExprVisitor visitor{*
this, loc};
530 return expr.visit(visitor);
538 auto loc = value.getLoc();
539 auto type = dyn_cast<moore::IntType>(value.getType());
540 if (!type || type.getBitSize() != 1) {
541 mlir::emitError(loc,
"expected a 1-bit integer");
544 if (type.getDomain() == Domain::FourValued) {
545 value = moore::LogicToIntOp::create(
builder, loc, value);
547 return moore::ToBuiltinIntOp::create(
builder, loc, value);
551struct AssertionClockVisitor
552 : slang::ast::ASTVisitor<AssertionClockVisitor, true, true> {
554 const slang::analysis::AnalyzedAssertion &assertion;
555 const slang::ast::TimingControl *currentClock =
nullptr;
558 const slang::analysis::AnalyzedAssertion &assertion)
561 void handle(
const slang::ast::CallExpression &node) {
563 context.assertionCallClocks[&node] = currentClock;
567 template <
typename T>
568 std::enable_if_t<std::is_base_of_v<slang::ast::AssertionExpr, T>>
569 handle(
const T &node) {
570 auto *prevClock = currentClock;
571 if (
auto *clk = assertion.getClock(node))
574 currentClock = prevClock;
581 slang::analysis::AnalysisManager am;
582 am.addListener([
this](
const slang::analysis::AnalyzedAssertion &assertion) {
583 AssertionClockVisitor visitor{*
this, assertion};
584 assertion.getRoot().visit(visitor);
static Value getIsUnknown(OpBuilder &builder, Location loc, Value value, moore::IntType valTy, MLIRContext *ctx)
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static FVInt getAllX(unsigned numBits)
Construct an FVInt with all bits set to X.
@ FourValued
Four-valued types such as logic or integer.
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.
Value convertRvalueExpression(const slang::ast::Expression &expr, Type requiredType={})
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)
MLIRContext * getContext()
Return the MLIR context.
Location convertLocation(slang::SourceLocation loc)
Convert a slang SourceLocation into an MLIR Location.