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