CIRCT 23.0.0git
Loading...
Searching...
No Matches
Statements.cpp
Go to the documentation of this file.
1//===- Statements.cpp - Slang statement 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
10#include "slang/ast/Compilation.h"
11#include "slang/ast/SystemSubroutine.h"
12#include "llvm/ADT/ScopeExit.h"
13
14using namespace mlir;
15using namespace circt;
16using namespace ImportVerilog;
17
18// NOLINTBEGIN(misc-no-recursion)
19namespace {
20struct StmtVisitor {
22 Location loc;
23 OpBuilder &builder;
24
25 StmtVisitor(Context &context, Location loc)
26 : context(context), loc(loc), builder(context.builder) {}
27
28 bool isTerminated() const { return !builder.getInsertionBlock(); }
29 void setTerminated() { builder.clearInsertionPoint(); }
30
31 Block &createBlock() {
32 assert(builder.getInsertionBlock());
33 auto block = std::make_unique<Block>();
34 block->insertAfter(builder.getInsertionBlock());
35 return *block.release();
36 }
37
38 LogicalResult recursiveForeach(const slang::ast::ForeachLoopStatement &stmt,
39 uint32_t level) {
40 // find current dimension we are operate.
41 const auto &loopDim = stmt.loopDims[level];
42 if (!loopDim.range.has_value())
43 return mlir::emitError(loc) << "dynamic loop variable is unsupported";
44 auto &exitBlock = createBlock();
45 auto &stepBlock = createBlock();
46 auto &bodyBlock = createBlock();
47 auto &checkBlock = createBlock();
48
49 // Push the blocks onto the loop stack such that we can continue and break.
50 context.loopStack.push_back({&stepBlock, &exitBlock});
51 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
52
53 const auto &iter = loopDim.loopVar;
54 auto type = context.convertType(*iter->getDeclaredType());
55 if (!type)
56 return failure();
57
58 Value initial = moore::ConstantOp::create(
59 builder, loc, cast<moore::IntType>(type), loopDim.range->lower());
60
61 // Create loop varirable in this dimension
62 Value varOp = moore::VariableOp::create(
63 builder, loc, moore::RefType::get(cast<moore::UnpackedType>(type)),
64 builder.getStringAttr(iter->name), initial);
65 context.valueSymbols.insertIntoScope(context.valueSymbols.getCurScope(),
66 iter, varOp);
67
68 cf::BranchOp::create(builder, loc, &checkBlock);
69 builder.setInsertionPointToEnd(&checkBlock);
70
71 // When the loop variable is greater than the upper bound, goto exit
72 auto upperBound = moore::ConstantOp::create(
73 builder, loc, cast<moore::IntType>(type), loopDim.range->upper());
74
75 auto var = moore::ReadOp::create(builder, loc, varOp);
76 Value cond = moore::SleOp::create(builder, loc, var, upperBound);
77 if (!cond)
78 return failure();
79 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
80 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
81 ty && ty.getDomain() == Domain::FourValued) {
82 cond = moore::LogicToIntOp::create(builder, loc, cond);
83 }
84 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
85 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
86
87 builder.setInsertionPointToEnd(&bodyBlock);
88
89 // find next dimension in this foreach statement, it finded then recuersive
90 // resolve, else perform body statement
91 bool hasNext = false;
92 for (uint32_t nextLevel = level + 1; nextLevel < stmt.loopDims.size();
93 nextLevel++) {
94 if (stmt.loopDims[nextLevel].loopVar) {
95 if (failed(recursiveForeach(stmt, nextLevel)))
96 return failure();
97 hasNext = true;
98 break;
99 }
100 }
101
102 if (!hasNext) {
103 if (failed(context.convertStatement(stmt.body)))
104 return failure();
105 }
106 if (!isTerminated())
107 cf::BranchOp::create(builder, loc, &stepBlock);
108
109 builder.setInsertionPointToEnd(&stepBlock);
110
111 // add one to loop variable
112 var = moore::ReadOp::create(builder, loc, varOp);
113 auto one =
114 moore::ConstantOp::create(builder, loc, cast<moore::IntType>(type), 1);
115 auto postValue = moore::AddOp::create(builder, loc, var, one).getResult();
116 moore::BlockingAssignOp::create(builder, loc, varOp, postValue);
117 cf::BranchOp::create(builder, loc, &checkBlock);
118
119 if (exitBlock.hasNoPredecessors()) {
120 exitBlock.erase();
121 setTerminated();
122 } else {
123 builder.setInsertionPointToEnd(&exitBlock);
124 }
125 return success();
126 }
127
128 // Skip empty statements (stray semicolons).
129 LogicalResult visit(const slang::ast::EmptyStatement &) { return success(); }
130
131 // Convert every statement in a statement list. The Verilog syntax follows a
132 // similar philosophy as C/C++, where things like `if` and `for` accept a
133 // single statement as body. But then a `{...}` block is a valid statement,
134 // which allows for the `if {...}` syntax. In Verilog, things like `final`
135 // accept a single body statement, but that can be a `begin ... end` block,
136 // which in turn has a single body statement, which then commonly is a list of
137 // statements.
138 LogicalResult visit(const slang::ast::StatementList &stmts) {
139 for (auto *stmt : stmts.list) {
140 if (isTerminated()) {
141 auto loc = context.convertLocation(stmt->sourceRange);
142 mlir::emitWarning(loc, "unreachable code");
143 break;
144 }
145 if (failed(context.convertStatement(*stmt)))
146 return failure();
147 }
148 return success();
149 }
150
151 // Inline `begin ... end` blocks into the parent.
152 LogicalResult visit(const slang::ast::BlockStatement &stmt) {
153 return context.convertStatement(stmt.body);
154 }
155
156 // Handle expression statements.
157 LogicalResult visit(const slang::ast::ExpressionStatement &stmt) {
158 // Special handling for calls to system tasks that return no result value.
159 if (const auto *call = stmt.expr.as_if<slang::ast::CallExpression>()) {
160 if (const auto *info =
161 std::get_if<slang::ast::CallExpression::SystemCallInfo>(
162 &call->subroutine)) {
163 auto handled = visitSystemCall(stmt, *call, *info);
164 if (failed(handled))
165 return failure();
166 if (handled == true)
167 return success();
168 }
169
170 // According to IEEE 1800-2023 Section 21.3.3 "Formatting data to a
171 // string" the first argument of $sformat is its output; the other
172 // arguments work like a FormatString.
173 // In Moore we only support writing to a location if it is a reference;
174 // However, Section 21.3.3 explains that the output of $sformat is
175 // assigned as if it were cast from a string literal (Section 5.9),
176 // so this implementation casts the string to the target value.
177 if (!call->getSubroutineName().compare("$sformat")) {
178
179 // Use the first argument as the output location
180 auto *lhsExpr = call->arguments().front();
181 // Format the second and all later arguments as a string
182 auto fmtValue =
183 context.convertFormatString(call->arguments().subspan(1), loc,
184 moore::IntFormat::Decimal, false);
185 if (failed(fmtValue))
186 return failure();
187 // Convert the FormatString to a StringType
188 auto strValue = moore::FormatStringToStringOp::create(builder, loc,
189 fmtValue.value());
190 // The Slang AST produces a `AssignmentExpression` for the first
191 // argument; the RHS of this expression is invalid though
192 // (`EmptyArgument`), so we only use the LHS of the
193 // `AssignmentExpression` and plug in the formatted string for the RHS.
194 if (auto assignExpr =
195 lhsExpr->as_if<slang::ast::AssignmentExpression>()) {
196 auto lhs = context.convertLvalueExpression(assignExpr->left());
197 if (!lhs)
198 return failure();
199
200 auto convertedValue = context.materializeConversion(
201 cast<moore::RefType>(lhs.getType()).getNestedType(), strValue,
202 false, loc);
203 moore::BlockingAssignOp::create(builder, loc, lhs, convertedValue);
204 return success();
205 } else {
206 return failure();
207 }
208 }
209 }
210
211 auto value = context.convertRvalueExpression(stmt.expr);
212 if (!value)
213 return failure();
214
215 // Expressions like calls to void functions return a dummy value that has no
216 // uses. If the returned value is trivially dead, remove it.
217 if (auto *defOp = value.getDefiningOp())
218 if (isOpTriviallyDead(defOp))
219 defOp->erase();
220
221 return success();
222 }
223
224 // Handle variable declarations.
225 LogicalResult visit(const slang::ast::VariableDeclStatement &stmt) {
226 const auto &var = stmt.symbol;
227 auto type = context.convertType(*var.getDeclaredType());
228 if (!type)
229 return failure();
230
231 Value initial;
232 if (const auto *init = var.getInitializer()) {
233 initial = context.convertRvalueExpression(*init, type);
234 if (!initial)
235 return failure();
236 }
237
238 // Collect local temporary variables.
239 auto varOp = moore::VariableOp::create(
240 builder, loc, moore::RefType::get(cast<moore::UnpackedType>(type)),
241 builder.getStringAttr(var.name), initial);
242 context.valueSymbols.insertIntoScope(context.valueSymbols.getCurScope(),
243 &var, varOp);
244 return success();
245 }
246
247 // Handle if statements.
248 LogicalResult visit(const slang::ast::ConditionalStatement &stmt) {
249 // Generate the condition. There may be multiple conditions linked with the
250 // `&&&` operator.
251 Value allConds;
252 for (const auto &condition : stmt.conditions) {
253 if (condition.pattern)
254 return mlir::emitError(loc,
255 "match patterns in if conditions not supported");
256 auto cond = context.convertRvalueExpression(*condition.expr);
257 if (!cond)
258 return failure();
259 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
260 if (allConds)
261 allConds = moore::AndOp::create(builder, loc, allConds, cond);
262 else
263 allConds = cond;
264 }
265 assert(allConds && "slang guarantees at least one condition");
266 if (auto ty = dyn_cast<moore::IntType>(allConds.getType());
267 ty && ty.getDomain() == Domain::FourValued) {
268 allConds = moore::LogicToIntOp::create(builder, loc, allConds);
269 }
270 allConds = moore::ToBuiltinIntOp::create(builder, loc, allConds);
271
272 // Create the blocks for the true and false branches, and the exit block.
273 Block &exitBlock = createBlock();
274 Block *falseBlock = stmt.ifFalse ? &createBlock() : nullptr;
275 Block &trueBlock = createBlock();
276 cf::CondBranchOp::create(builder, loc, allConds, &trueBlock,
277 falseBlock ? falseBlock : &exitBlock);
278
279 // Generate the true branch.
280 builder.setInsertionPointToEnd(&trueBlock);
281 if (failed(context.convertStatement(stmt.ifTrue)))
282 return failure();
283 if (!isTerminated())
284 cf::BranchOp::create(builder, loc, &exitBlock);
285
286 // Generate the false branch if present.
287 if (stmt.ifFalse) {
288 builder.setInsertionPointToEnd(falseBlock);
289 if (failed(context.convertStatement(*stmt.ifFalse)))
290 return failure();
291 if (!isTerminated())
292 cf::BranchOp::create(builder, loc, &exitBlock);
293 }
294
295 // If control never reaches the exit block, remove it and mark control flow
296 // as terminated. Otherwise we continue inserting ops in the exit block.
297 if (exitBlock.hasNoPredecessors()) {
298 exitBlock.erase();
299 setTerminated();
300 } else {
301 builder.setInsertionPointToEnd(&exitBlock);
302 }
303 return success();
304 }
305
306 /// Handle case statements.
307 LogicalResult visit(const slang::ast::CaseStatement &caseStmt) {
308 using slang::ast::AttributeSymbol;
309 using slang::ast::CaseStatementCondition;
310 auto caseExpr = context.convertRvalueExpression(caseStmt.expr);
311 if (!caseExpr)
312 return failure();
313
314 // Check each case individually. This currently ignores the `unique`,
315 // `unique0`, and `priority` modifiers which would allow for additional
316 // optimizations.
317 auto &exitBlock = createBlock();
318 Block *lastMatchBlock = nullptr;
319 SmallVector<moore::FVIntegerAttr> itemConsts;
320
321 for (const auto &item : caseStmt.items) {
322 // Create the block that will contain the main body of the expression.
323 // This is where any of the comparisons will branch to if they match.
324 auto &matchBlock = createBlock();
325 lastMatchBlock = &matchBlock;
326
327 // The SV standard requires expressions to be checked in the order
328 // specified by the user, and for the evaluation to stop as soon as the
329 // first matching expression is encountered.
330 for (const auto *expr : item.expressions) {
331 auto value = context.convertRvalueExpression(*expr);
332 if (!value)
333 return failure();
334 auto itemLoc = value.getLoc();
335
336 // Take note if the expression is a constant.
337 auto maybeConst = value;
338 while (isa_and_nonnull<moore::ConversionOp, moore::IntToLogicOp,
339 moore::LogicToIntOp>(maybeConst.getDefiningOp()))
340 maybeConst = maybeConst.getDefiningOp()->getOperand(0);
341 if (auto defOp = maybeConst.getDefiningOp<moore::ConstantOp>())
342 itemConsts.push_back(defOp.getValueAttr());
343
344 // Generate the appropriate equality operator.
345 Value cond;
346 switch (caseStmt.condition) {
347 case CaseStatementCondition::Normal:
348 cond = moore::CaseEqOp::create(builder, itemLoc, caseExpr, value);
349 break;
350 case CaseStatementCondition::WildcardXOrZ:
351 cond = moore::CaseXZEqOp::create(builder, itemLoc, caseExpr, value);
352 break;
353 case CaseStatementCondition::WildcardJustZ:
354 cond = moore::CaseZEqOp::create(builder, itemLoc, caseExpr, value);
355 break;
356 case CaseStatementCondition::Inside:
357 mlir::emitError(loc, "unsupported set membership case statement");
358 return failure();
359 }
360 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
361 ty && ty.getDomain() == Domain::FourValued) {
362 cond = moore::LogicToIntOp::create(builder, loc, cond);
363 }
364 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
365
366 // If the condition matches, branch to the match block. Otherwise
367 // continue checking the next expression in a new block.
368 auto &nextBlock = createBlock();
369 mlir::cf::CondBranchOp::create(builder, itemLoc, cond, &matchBlock,
370 &nextBlock);
371 builder.setInsertionPointToEnd(&nextBlock);
372 }
373
374 // The current block is the fall-through after all conditions have been
375 // checked and nothing matched. Move the match block up before this point
376 // to make the IR easier to read.
377 matchBlock.moveBefore(builder.getInsertionBlock());
378
379 // Generate the code for this item's statement in the match block.
380 OpBuilder::InsertionGuard guard(builder);
381 builder.setInsertionPointToEnd(&matchBlock);
382 if (failed(context.convertStatement(*item.stmt)))
383 return failure();
384 if (!isTerminated()) {
385 auto loc = context.convertLocation(item.stmt->sourceRange);
386 mlir::cf::BranchOp::create(builder, loc, &exitBlock);
387 }
388 }
389
390 const auto caseStmtAttrs = context.compilation.getAttributes(caseStmt);
391 const bool hasFullCaseAttr =
392 llvm::find_if(caseStmtAttrs, [](const AttributeSymbol *attr) {
393 return attr->name == "full_case";
394 }) != caseStmtAttrs.end();
395
396 // Check if the case statement looks exhaustive assuming two-state values.
397 // We use this information to work around a common bug in input Verilog
398 // where a case statement enumerates all possible two-state values of the
399 // case expression, but forgets to deal with cases involving X and Z bits in
400 // the input.
401 //
402 // Once the core dialects start supporting four-state values we may want to
403 // tuck this behind an import option that is on by default, since it does
404 // not preserve semantics.
405 auto twoStateExhaustive = false;
406 if (auto intType = dyn_cast<moore::IntType>(caseExpr.getType());
407 intType && intType.getWidth() < 32 &&
408 itemConsts.size() == (1 << intType.getWidth())) {
409 // Sort the constants by value.
410 llvm::sort(itemConsts, [](auto a, auto b) {
411 return a.getValue().getRawValue().ult(b.getValue().getRawValue());
412 });
413
414 // Ensure that every possible value of the case expression is present. Do
415 // this by starting at 0 and iterating over all sorted items. Each item
416 // must be the previous item + 1. At the end, the addition must exactly
417 // overflow and take us back to zero.
418 auto nextValue = FVInt::getZero(intType.getWidth());
419 for (auto value : itemConsts) {
420 if (value.getValue() != nextValue)
421 break;
422 nextValue += 1;
423 }
424 twoStateExhaustive = nextValue.isZero();
425 }
426
427 // If the case statement is exhaustive assuming two-state values, don't
428 // generate the default case. Instead, branch to the last match block. This
429 // will essentially make the last case item the "default".
430 //
431 // Alternatively, if the case statement has an (* full_case *) attribute
432 // but no default case, it indicates that the developer has intentionally
433 // covered all known possible values. Hence, the last match block is
434 // treated as the implicit "default" case.
435 if ((twoStateExhaustive || (hasFullCaseAttr && !caseStmt.defaultCase)) &&
436 lastMatchBlock &&
437 caseStmt.condition == CaseStatementCondition::Normal) {
438 mlir::cf::BranchOp::create(builder, loc, lastMatchBlock);
439 } else {
440 // Generate the default case if present.
441 if (caseStmt.defaultCase)
442 if (failed(context.convertStatement(*caseStmt.defaultCase)))
443 return failure();
444 if (!isTerminated())
445 mlir::cf::BranchOp::create(builder, loc, &exitBlock);
446 }
447
448 // If control never reaches the exit block, remove it and mark control flow
449 // as terminated. Otherwise we continue inserting ops in the exit block.
450 if (exitBlock.hasNoPredecessors()) {
451 exitBlock.erase();
452 setTerminated();
453 } else {
454 builder.setInsertionPointToEnd(&exitBlock);
455 }
456 return success();
457 }
458
459 // Handle `for` loops.
460 LogicalResult visit(const slang::ast::ForLoopStatement &stmt) {
461 // Generate the initializers.
462 for (auto *initExpr : stmt.initializers)
463 if (!context.convertRvalueExpression(*initExpr))
464 return failure();
465
466 // Create the blocks for the loop condition, body, step, and exit.
467 auto &exitBlock = createBlock();
468 auto &stepBlock = createBlock();
469 auto &bodyBlock = createBlock();
470 auto &checkBlock = createBlock();
471 cf::BranchOp::create(builder, loc, &checkBlock);
472
473 // Push the blocks onto the loop stack such that we can continue and break.
474 context.loopStack.push_back({&stepBlock, &exitBlock});
475 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
476
477 // Generate the loop condition check.
478 builder.setInsertionPointToEnd(&checkBlock);
479 auto cond = context.convertRvalueExpression(*stmt.stopExpr);
480 if (!cond)
481 return failure();
482 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
483 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
484 ty && ty.getDomain() == Domain::FourValued) {
485 cond = moore::LogicToIntOp::create(builder, loc, cond);
486 }
487 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
488 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
489
490 // Generate the loop body.
491 builder.setInsertionPointToEnd(&bodyBlock);
492 if (failed(context.convertStatement(stmt.body)))
493 return failure();
494 if (!isTerminated())
495 cf::BranchOp::create(builder, loc, &stepBlock);
496
497 // Generate the step expressions.
498 builder.setInsertionPointToEnd(&stepBlock);
499 for (auto *stepExpr : stmt.steps)
500 if (!context.convertRvalueExpression(*stepExpr))
501 return failure();
502 if (!isTerminated())
503 cf::BranchOp::create(builder, loc, &checkBlock);
504
505 // If control never reaches the exit block, remove it and mark control flow
506 // as terminated. Otherwise we continue inserting ops in the exit block.
507 if (exitBlock.hasNoPredecessors()) {
508 exitBlock.erase();
509 setTerminated();
510 } else {
511 builder.setInsertionPointToEnd(&exitBlock);
512 }
513 return success();
514 }
515
516 LogicalResult visit(const slang::ast::ForeachLoopStatement &stmt) {
517 for (uint32_t level = 0; level < stmt.loopDims.size(); level++) {
518 if (stmt.loopDims[level].loopVar)
519 return recursiveForeach(stmt, level);
520 }
521 return success();
522 }
523
524 // Handle `repeat` loops.
525 LogicalResult visit(const slang::ast::RepeatLoopStatement &stmt) {
526 auto count = context.convertRvalueExpression(stmt.count);
527 if (!count)
528 return failure();
529
530 // Create the blocks for the loop condition, body, step, and exit.
531 auto &exitBlock = createBlock();
532 auto &stepBlock = createBlock();
533 auto &bodyBlock = createBlock();
534 auto &checkBlock = createBlock();
535 auto currentCount = checkBlock.addArgument(count.getType(), count.getLoc());
536 cf::BranchOp::create(builder, loc, &checkBlock, count);
537
538 // Push the blocks onto the loop stack such that we can continue and break.
539 context.loopStack.push_back({&stepBlock, &exitBlock});
540 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
541
542 // Generate the loop condition check.
543 builder.setInsertionPointToEnd(&checkBlock);
544 auto cond = builder.createOrFold<moore::BoolCastOp>(loc, currentCount);
545 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
546 ty && ty.getDomain() == Domain::FourValued) {
547 cond = moore::LogicToIntOp::create(builder, loc, cond);
548 }
549 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
550 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
551
552 // Generate the loop body.
553 builder.setInsertionPointToEnd(&bodyBlock);
554 if (failed(context.convertStatement(stmt.body)))
555 return failure();
556 if (!isTerminated())
557 cf::BranchOp::create(builder, loc, &stepBlock);
558
559 // Decrement the current count and branch back to the check block.
560 builder.setInsertionPointToEnd(&stepBlock);
561 auto one = moore::ConstantOp::create(
562 builder, count.getLoc(), cast<moore::IntType>(count.getType()), 1);
563 Value nextCount =
564 moore::SubOp::create(builder, count.getLoc(), currentCount, one);
565 cf::BranchOp::create(builder, loc, &checkBlock, nextCount);
566
567 // If control never reaches the exit block, remove it and mark control flow
568 // as terminated. Otherwise we continue inserting ops in the exit block.
569 if (exitBlock.hasNoPredecessors()) {
570 exitBlock.erase();
571 setTerminated();
572 } else {
573 builder.setInsertionPointToEnd(&exitBlock);
574 }
575 return success();
576 }
577
578 // Handle `while` and `do-while` loops.
579 LogicalResult createWhileLoop(const slang::ast::Expression &condExpr,
580 const slang::ast::Statement &bodyStmt,
581 bool atLeastOnce) {
582 // Create the blocks for the loop condition, body, and exit.
583 auto &exitBlock = createBlock();
584 auto &bodyBlock = createBlock();
585 auto &checkBlock = createBlock();
586 cf::BranchOp::create(builder, loc, atLeastOnce ? &bodyBlock : &checkBlock);
587 if (atLeastOnce)
588 bodyBlock.moveBefore(&checkBlock);
589
590 // Push the blocks onto the loop stack such that we can continue and break.
591 context.loopStack.push_back({&checkBlock, &exitBlock});
592 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
593
594 // Generate the loop condition check.
595 builder.setInsertionPointToEnd(&checkBlock);
596 auto cond = context.convertRvalueExpression(condExpr);
597 if (!cond)
598 return failure();
599 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
600 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
601 ty && ty.getDomain() == Domain::FourValued) {
602 cond = moore::LogicToIntOp::create(builder, loc, cond);
603 }
604 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
605 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
606
607 // Generate the loop body.
608 builder.setInsertionPointToEnd(&bodyBlock);
609 if (failed(context.convertStatement(bodyStmt)))
610 return failure();
611 if (!isTerminated())
612 cf::BranchOp::create(builder, loc, &checkBlock);
613
614 // If control never reaches the exit block, remove it and mark control flow
615 // as terminated. Otherwise we continue inserting ops in the exit block.
616 if (exitBlock.hasNoPredecessors()) {
617 exitBlock.erase();
618 setTerminated();
619 } else {
620 builder.setInsertionPointToEnd(&exitBlock);
621 }
622 return success();
623 }
624
625 LogicalResult visit(const slang::ast::WhileLoopStatement &stmt) {
626 return createWhileLoop(stmt.cond, stmt.body, false);
627 }
628
629 LogicalResult visit(const slang::ast::DoWhileLoopStatement &stmt) {
630 return createWhileLoop(stmt.cond, stmt.body, true);
631 }
632
633 // Handle `forever` loops.
634 LogicalResult visit(const slang::ast::ForeverLoopStatement &stmt) {
635 // Create the blocks for the loop body and exit.
636 auto &exitBlock = createBlock();
637 auto &bodyBlock = createBlock();
638 cf::BranchOp::create(builder, loc, &bodyBlock);
639
640 // Push the blocks onto the loop stack such that we can continue and break.
641 context.loopStack.push_back({&bodyBlock, &exitBlock});
642 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
643
644 // Generate the loop body.
645 builder.setInsertionPointToEnd(&bodyBlock);
646 if (failed(context.convertStatement(stmt.body)))
647 return failure();
648 if (!isTerminated())
649 cf::BranchOp::create(builder, loc, &bodyBlock);
650
651 // If control never reaches the exit block, remove it and mark control flow
652 // as terminated. Otherwise we continue inserting ops in the exit block.
653 if (exitBlock.hasNoPredecessors()) {
654 exitBlock.erase();
655 setTerminated();
656 } else {
657 builder.setInsertionPointToEnd(&exitBlock);
658 }
659 return success();
660 }
661
662 // Handle timing control.
663 LogicalResult visit(const slang::ast::TimedStatement &stmt) {
664 return context.convertTimingControl(stmt.timing, stmt.stmt);
665 }
666
667 // Handle return statements.
668 LogicalResult visit(const slang::ast::ReturnStatement &stmt) {
669 if (stmt.expr) {
670 auto expr = context.convertRvalueExpression(*stmt.expr);
671 if (!expr)
672 return failure();
673 mlir::func::ReturnOp::create(builder, loc, expr);
674 } else {
675 mlir::func::ReturnOp::create(builder, loc);
676 }
677 setTerminated();
678 return success();
679 }
680
681 // Handle continue statements.
682 LogicalResult visit(const slang::ast::ContinueStatement &stmt) {
683 if (context.loopStack.empty())
684 return mlir::emitError(loc,
685 "cannot `continue` without a surrounding loop");
686 cf::BranchOp::create(builder, loc, context.loopStack.back().continueBlock);
687 setTerminated();
688 return success();
689 }
690
691 // Handle break statements.
692 LogicalResult visit(const slang::ast::BreakStatement &stmt) {
693 if (context.loopStack.empty())
694 return mlir::emitError(loc, "cannot `break` without a surrounding loop");
695 cf::BranchOp::create(builder, loc, context.loopStack.back().breakBlock);
696 setTerminated();
697 return success();
698 }
699
700 // Handle immediate assertion statements.
701 LogicalResult visit(const slang::ast::ImmediateAssertionStatement &stmt) {
702 auto cond = context.convertRvalueExpression(stmt.cond);
703 cond = context.convertToBool(cond);
704 if (!cond)
705 return failure();
706
707 // Handle assertion statements that don't have an action block.
708 if (stmt.ifTrue && stmt.ifTrue->as_if<slang::ast::EmptyStatement>()) {
709 auto defer = moore::DeferAssert::Immediate;
710 if (stmt.isFinal)
711 defer = moore::DeferAssert::Final;
712 else if (stmt.isDeferred)
713 defer = moore::DeferAssert::Observed;
714
715 switch (stmt.assertionKind) {
716 case slang::ast::AssertionKind::Assert:
717 moore::AssertOp::create(builder, loc, defer, cond, StringAttr{});
718 return success();
719 case slang::ast::AssertionKind::Assume:
720 moore::AssumeOp::create(builder, loc, defer, cond, StringAttr{});
721 return success();
722 case slang::ast::AssertionKind::CoverProperty:
723 moore::CoverOp::create(builder, loc, defer, cond, StringAttr{});
724 return success();
725 default:
726 break;
727 }
728 mlir::emitError(loc) << "unsupported immediate assertion kind: "
729 << slang::ast::toString(stmt.assertionKind);
730 return failure();
731 }
732
733 // Regard assertion statements with an action block as the "if-else".
734 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
735 ty && ty.getDomain() == Domain::FourValued) {
736 cond = moore::LogicToIntOp::create(builder, loc, cond);
737 }
738 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
739
740 // Create the blocks for the true and false branches, and the exit block.
741 Block &exitBlock = createBlock();
742 Block *falseBlock = stmt.ifFalse ? &createBlock() : nullptr;
743 Block &trueBlock = createBlock();
744 cf::CondBranchOp::create(builder, loc, cond, &trueBlock,
745 falseBlock ? falseBlock : &exitBlock);
746
747 // Generate the true branch.
748 builder.setInsertionPointToEnd(&trueBlock);
749 if (stmt.ifTrue && failed(context.convertStatement(*stmt.ifTrue)))
750 return failure();
751 if (!isTerminated())
752 cf::BranchOp::create(builder, loc, &exitBlock);
753
754 if (stmt.ifFalse) {
755 // Generate the false branch if present.
756 builder.setInsertionPointToEnd(falseBlock);
757 if (failed(context.convertStatement(*stmt.ifFalse)))
758 return failure();
759 if (!isTerminated())
760 cf::BranchOp::create(builder, loc, &exitBlock);
761 }
762
763 // If control never reaches the exit block, remove it and mark control flow
764 // as terminated. Otherwise we continue inserting ops in the exit block.
765 if (exitBlock.hasNoPredecessors()) {
766 exitBlock.erase();
767 setTerminated();
768 } else {
769 builder.setInsertionPointToEnd(&exitBlock);
770 }
771 return success();
772 }
773
774 // Handle concurrent assertion statements.
775 LogicalResult visit(const slang::ast::ConcurrentAssertionStatement &stmt) {
776 auto loc = context.convertLocation(stmt.sourceRange);
777
778 // Check for a `disable iff` expression:
779 // The DisableIff construct can only occcur at the top level of an assertion
780 // and cannot be nested within properties.
781 // Hence we only need to detect if the top level assertion expression
782 // has type DisableIff, negate the `disable` expression, then pass it to
783 // the `enable` parameter of AssertOp/AssumeOp.
784 Value enable;
785 Value property;
786 if (auto *disableIff =
787 stmt.propertySpec.as_if<slang::ast::DisableIffAssertionExpr>()) {
788 auto disableCond = context.convertRvalueExpression(disableIff->condition);
789 auto enableCond = moore::NotOp::create(builder, loc, disableCond);
790
791 enable = context.convertToI1(enableCond);
792 property = context.convertAssertionExpression(disableIff->expr, loc);
793 } else {
794 property = context.convertAssertionExpression(stmt.propertySpec, loc);
795 }
796
797 if (!property)
798 return failure();
799
800 // Handle assertion statements that don't have an action block.
801 if (stmt.ifTrue && stmt.ifTrue->as_if<slang::ast::EmptyStatement>()) {
802 switch (stmt.assertionKind) {
803 case slang::ast::AssertionKind::Assert:
804 verif::AssertOp::create(builder, loc, property, enable, StringAttr{});
805 return success();
806 case slang::ast::AssertionKind::Assume:
807 verif::AssumeOp::create(builder, loc, property, enable, StringAttr{});
808 return success();
809 default:
810 break;
811 }
812 mlir::emitError(loc) << "unsupported concurrent assertion kind: "
813 << slang::ast::toString(stmt.assertionKind);
814 return failure();
815 }
816
817 mlir::emitError(loc)
818 << "concurrent assertion statements with action blocks "
819 "are not supported yet";
820 return failure();
821 }
822
823 // According to 1800-2023 Section 21.2.1 "The display and write tasks":
824 // >> The $display and $write tasks display their arguments in the same
825 // >> order as they appear in the argument list. Each argument can be a
826 // >> string literal or an expression that returns a value.
827 // According to Section 20.10 "Severity system tasks", the same
828 // semantics apply to $fatal, $error, $warning, and $info.
829 // This means we must first check whether the first "string-able"
830 // argument is a Literal Expression which doesn't represent a fully-formatted
831 // string, otherwise we convert it to a FormatStringType.
832 FailureOr<Value>
833 getDisplayMessage(std::span<const slang::ast::Expression *const> args) {
834 if (args.size() == 0)
835 return Value{};
836
837 // Handle the string formatting.
838 // If the second argument is a Literal of some type, we should either
839 // treat it as a literal-to-be-formatted or a FormatStringType.
840 // In this check we use a StringLiteral, but slang allows casting between
841 // any literal expressions (strings, integers, reals, and time at least) so
842 // this is short-hand for "any value literal"
843 if (args[0]->as_if<slang::ast::StringLiteral>()) {
844 return context.convertFormatString(args, loc);
845 }
846 // Check if there's only one argument and it's a FormatStringType
847 if (args.size() == 1) {
848 return context.convertRvalueExpression(
849 *args[0], builder.getType<moore::FormatStringType>());
850 }
851 // Otherwise this looks invalid. Raise an error.
852 return emitError(loc) << "Failed to convert Display Message!";
853 }
854
855 /// Handle the subset of system calls that return no result value. Return
856 /// true if the called system task could be handled, false otherwise. Return
857 /// failure if an error occurred.
858 FailureOr<bool>
859 visitSystemCall(const slang::ast::ExpressionStatement &stmt,
860 const slang::ast::CallExpression &expr,
861 const slang::ast::CallExpression::SystemCallInfo &info) {
862 const auto &subroutine = *info.subroutine;
863 auto args = expr.arguments();
864
865 // Simulation Control Tasks
866
867 if (subroutine.name == "$stop") {
868 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
869 moore::StopBIOp::create(builder, loc);
870 return true;
871 }
872
873 if (subroutine.name == "$finish") {
874 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
875 moore::FinishBIOp::create(builder, loc, 0);
876 moore::UnreachableOp::create(builder, loc);
877 setTerminated();
878 return true;
879 }
880
881 if (subroutine.name == "$exit") {
882 // Calls to `$exit` from outside a `program` are ignored. Since we don't
883 // yet support programs, there is nothing to do here.
884 // TODO: Fix this once we support programs.
885 return true;
886 }
887
888 // Display and Write Tasks (`$display[boh]?` or `$write[boh]?`)
889
890 // Check for a `$display` or `$write` prefix.
891 bool isDisplay = false; // display or write
892 bool appendNewline = false; // display
893 StringRef remainingName = subroutine.name;
894 if (remainingName.consume_front("$display")) {
895 isDisplay = true;
896 appendNewline = true;
897 } else if (remainingName.consume_front("$write")) {
898 isDisplay = true;
899 }
900
901 // Check for optional `b`, `o`, or `h` suffix indicating default format.
902 using moore::IntFormat;
903 IntFormat defaultFormat = IntFormat::Decimal;
904 if (isDisplay && !remainingName.empty()) {
905 if (remainingName == "b")
906 defaultFormat = IntFormat::Binary;
907 else if (remainingName == "o")
908 defaultFormat = IntFormat::Octal;
909 else if (remainingName == "h")
910 defaultFormat = IntFormat::HexLower;
911 else
912 isDisplay = false;
913 }
914
915 if (isDisplay) {
916 auto message =
917 context.convertFormatString(args, loc, defaultFormat, appendNewline);
918 if (failed(message))
919 return failure();
920 if (*message == Value{})
921 return true;
922 moore::DisplayBIOp::create(builder, loc, *message);
923 return true;
924 }
925
926 // Severity Tasks
927 using moore::Severity;
928 std::optional<Severity> severity;
929 if (subroutine.name == "$info")
930 severity = Severity::Info;
931 else if (subroutine.name == "$warning")
932 severity = Severity::Warning;
933 else if (subroutine.name == "$error")
934 severity = Severity::Error;
935 else if (subroutine.name == "$fatal")
936 severity = Severity::Fatal;
937
938 if (severity) {
939 // The `$fatal` task has an optional leading verbosity argument.
940 const slang::ast::Expression *verbosityExpr = nullptr;
941 if (severity == Severity::Fatal && args.size() >= 1) {
942 verbosityExpr = args[0];
943 args = args.subspan(1);
944 }
945
946 FailureOr<Value> maybeMessage = getDisplayMessage(args);
947 if (failed(maybeMessage))
948 return failure();
949 auto message = maybeMessage.value();
950
951 if (message == Value{})
952 message = moore::FormatLiteralOp::create(builder, loc, "");
953 moore::SeverityBIOp::create(builder, loc, *severity, message);
954
955 // Handle the `$fatal` case which behaves like a `$finish`.
956 if (severity == Severity::Fatal) {
957 createFinishMessage(verbosityExpr);
958 moore::FinishBIOp::create(builder, loc, 1);
959 moore::UnreachableOp::create(builder, loc);
960 setTerminated();
961 }
962 return true;
963 }
964
965 // Queue Tasks
966
967 if (args.size() >= 1 && args[0]->type->isQueue()) {
968 auto queue = context.convertLvalueExpression(*args[0]);
969
970 // `delete` has two functions: If there is an index passed, then it
971 // deletes that specific element, otherwise, it clears the entire queue.
972 if (subroutine.name == "delete") {
973 if (args.size() == 1) {
974 moore::QueueClearOp::create(builder, loc, queue);
975 return true;
976 }
977 if (args.size() == 2) {
978 auto index = context.convertRvalueExpression(*args[1]);
979 moore::QueueDeleteOp::create(builder, loc, queue, index);
980 return true;
981 }
982 } else if (subroutine.name == "insert" && args.size() == 3) {
983 auto index = context.convertRvalueExpression(*args[1]);
984 auto item = context.convertRvalueExpression(*args[2]);
985
986 moore::QueueInsertOp::create(builder, loc, queue, index, item);
987 return true;
988 } else if (subroutine.name == "push_back" && args.size() == 2) {
989 auto item = context.convertRvalueExpression(*args[1]);
990 moore::QueuePushBackOp::create(builder, loc, queue, item);
991 return true;
992 } else if (subroutine.name == "push_front" && args.size() == 2) {
993 auto item = context.convertRvalueExpression(*args[1]);
994 moore::QueuePushFrontOp::create(builder, loc, queue, item);
995 return true;
996 }
997
998 return false;
999 }
1000
1001 // Give up on any other system tasks. These will be tried again as an
1002 // expression later.
1003 return false;
1004 }
1005
1006 /// Create the optional diagnostic message print for finish-like ops.
1007 void createFinishMessage(const slang::ast::Expression *verbosityExpr) {
1008 unsigned verbosity = 1;
1009 if (verbosityExpr) {
1010 auto value =
1011 context.evaluateConstant(*verbosityExpr).integer().as<unsigned>();
1012 assert(value && "Slang guarantees constant verbosity parameter");
1013 verbosity = *value;
1014 }
1015 if (verbosity == 0)
1016 return;
1017 moore::FinishMessageBIOp::create(builder, loc, verbosity > 1);
1018 }
1019
1020 // Handle event trigger statements.
1021 LogicalResult visit(const slang::ast::EventTriggerStatement &stmt) {
1022 if (stmt.timing) {
1023 mlir::emitError(loc) << "unsupported delayed event trigger";
1024 return failure();
1025 }
1026
1027 // Events are lowered to `i1` signals. Get an lvalue ref to the signal such
1028 // that we can assign to it.
1029 auto target = context.convertLvalueExpression(stmt.target);
1030 if (!target)
1031 return failure();
1032
1033 // Read and invert the current value of the signal. Writing this inverted
1034 // value to the signal is our event signaling mechanism.
1035 Value inverted = moore::ReadOp::create(builder, loc, target);
1036 inverted = moore::NotOp::create(builder, loc, inverted);
1037
1038 if (stmt.isNonBlocking)
1039 moore::NonBlockingAssignOp::create(builder, loc, target, inverted);
1040 else
1041 moore::BlockingAssignOp::create(builder, loc, target, inverted);
1042 return success();
1043 }
1044
1045 /// Emit an error for all other statements.
1046 template <typename T>
1047 LogicalResult visit(T &&stmt) {
1048 mlir::emitError(loc, "unsupported statement: ")
1049 << slang::ast::toString(stmt.kind);
1050 return mlir::failure();
1051 }
1052
1053 LogicalResult visitInvalid(const slang::ast::Statement &stmt) {
1054 mlir::emitError(loc, "invalid statement: ")
1055 << slang::ast::toString(stmt.kind);
1056 return mlir::failure();
1057 }
1058};
1059} // namespace
1060
1061LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) {
1062 assert(builder.getInsertionBlock());
1063 auto loc = convertLocation(stmt.sourceRange);
1064 return stmt.visit(StmtVisitor(*this, loc));
1065}
1066// NOLINTEND(misc-no-recursion)
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static FVInt getZero(unsigned numBits)
Construct an FVInt with all bits set to 0.
Definition FVInt.h:65
This helps visit TypeOp nodes.
Definition HWVisitors.h:89
void info(Twine message)
Definition LSPUtils.cpp:20
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
A helper class to facilitate the conversion from a Slang AST to MLIR operations.
OpBuilder builder
The builder used to create IR operations.
Location convertLocation(slang::SourceLocation loc)
Convert a slang SourceLocation into an MLIR Location.