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
167 auto value = context.convertRvalueExpression(stmt.expr);
168 if (!value)
169 return failure();
170
171 // Expressions like calls to void functions return a dummy value that has no
172 // uses. If the returned value is trivially dead, remove it.
173 if (auto *defOp = value.getDefiningOp())
174 if (isOpTriviallyDead(defOp))
175 defOp->erase();
176
177 return success();
178 }
179
180 // Handle variable declarations.
181 LogicalResult visit(const slang::ast::VariableDeclStatement &stmt) {
182 const auto &var = stmt.symbol;
183 auto type = context.convertType(*var.getDeclaredType());
184 if (!type)
185 return failure();
186
187 Value initial;
188 if (const auto *init = var.getInitializer()) {
189 initial = context.convertRvalueExpression(*init, type);
190 if (!initial)
191 return failure();
192 }
193
194 // Collect local temporary variables.
195 auto varOp = moore::VariableOp::create(
196 builder, loc, moore::RefType::get(cast<moore::UnpackedType>(type)),
197 builder.getStringAttr(var.name), initial);
198 context.valueSymbols.insertIntoScope(context.valueSymbols.getCurScope(),
199 &var, varOp);
200 return success();
201 }
202
203 // Handle if statements.
204 LogicalResult visit(const slang::ast::ConditionalStatement &stmt) {
205 // Generate the condition. There may be multiple conditions linked with the
206 // `&&&` operator.
207 Value allConds;
208 for (const auto &condition : stmt.conditions) {
209 if (condition.pattern)
210 return mlir::emitError(loc,
211 "match patterns in if conditions not supported");
212 auto cond = context.convertRvalueExpression(*condition.expr);
213 if (!cond)
214 return failure();
215 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
216 if (allConds)
217 allConds = moore::AndOp::create(builder, loc, allConds, cond);
218 else
219 allConds = cond;
220 }
221 assert(allConds && "slang guarantees at least one condition");
222 allConds = moore::ToBuiltinBoolOp::create(builder, loc, allConds);
223
224 // Create the blocks for the true and false branches, and the exit block.
225 Block &exitBlock = createBlock();
226 Block *falseBlock = stmt.ifFalse ? &createBlock() : nullptr;
227 Block &trueBlock = createBlock();
228 cf::CondBranchOp::create(builder, loc, allConds, &trueBlock,
229 falseBlock ? falseBlock : &exitBlock);
230
231 // Generate the true branch.
232 builder.setInsertionPointToEnd(&trueBlock);
233 if (failed(context.convertStatement(stmt.ifTrue)))
234 return failure();
235 if (!isTerminated())
236 cf::BranchOp::create(builder, loc, &exitBlock);
237
238 // Generate the false branch if present.
239 if (stmt.ifFalse) {
240 builder.setInsertionPointToEnd(falseBlock);
241 if (failed(context.convertStatement(*stmt.ifFalse)))
242 return failure();
243 if (!isTerminated())
244 cf::BranchOp::create(builder, loc, &exitBlock);
245 }
246
247 // If control never reaches the exit block, remove it and mark control flow
248 // as terminated. Otherwise we continue inserting ops in the exit block.
249 if (exitBlock.hasNoPredecessors()) {
250 exitBlock.erase();
251 setTerminated();
252 } else {
253 builder.setInsertionPointToEnd(&exitBlock);
254 }
255 return success();
256 }
257
258 /// Handle case statements.
259 LogicalResult visit(const slang::ast::CaseStatement &caseStmt) {
260 using slang::ast::AttributeSymbol;
261 using slang::ast::CaseStatementCondition;
262 auto caseExpr = context.convertRvalueExpression(caseStmt.expr);
263 if (!caseExpr)
264 return failure();
265
266 // Check each case individually. This currently ignores the `unique`,
267 // `unique0`, and `priority` modifiers which would allow for additional
268 // optimizations.
269 auto &exitBlock = createBlock();
270 Block *lastMatchBlock = nullptr;
271 SmallVector<moore::FVIntegerAttr> itemConsts;
272
273 for (const auto &item : caseStmt.items) {
274 // Create the block that will contain the main body of the expression.
275 // This is where any of the comparisons will branch to if they match.
276 auto &matchBlock = createBlock();
277 lastMatchBlock = &matchBlock;
278
279 // The SV standard requires expressions to be checked in the order
280 // specified by the user, and for the evaluation to stop as soon as the
281 // first matching expression is encountered.
282 for (const auto *expr : item.expressions) {
283 auto value = context.convertRvalueExpression(*expr);
284 if (!value)
285 return failure();
286 auto itemLoc = value.getLoc();
287
288 // Take note if the expression is a constant.
289 auto maybeConst = value;
290 while (isa_and_nonnull<moore::ConversionOp, moore::IntToLogicOp,
291 moore::LogicToIntOp>(maybeConst.getDefiningOp()))
292 maybeConst = maybeConst.getDefiningOp()->getOperand(0);
293 if (auto defOp = maybeConst.getDefiningOp<moore::ConstantOp>())
294 itemConsts.push_back(defOp.getValueAttr());
295
296 // Generate the appropriate equality operator.
297 Value cond;
298 switch (caseStmt.condition) {
299 case CaseStatementCondition::Normal:
300 cond = moore::CaseEqOp::create(builder, itemLoc, caseExpr, value);
301 break;
302 case CaseStatementCondition::WildcardXOrZ:
303 cond = moore::CaseXZEqOp::create(builder, itemLoc, caseExpr, value);
304 break;
305 case CaseStatementCondition::WildcardJustZ:
306 cond = moore::CaseZEqOp::create(builder, itemLoc, caseExpr, value);
307 break;
308 case CaseStatementCondition::Inside:
309 mlir::emitError(loc, "unsupported set membership case statement");
310 return failure();
311 }
312 cond = moore::ToBuiltinBoolOp::create(builder, itemLoc, cond);
313
314 // If the condition matches, branch to the match block. Otherwise
315 // continue checking the next expression in a new block.
316 auto &nextBlock = createBlock();
317 mlir::cf::CondBranchOp::create(builder, itemLoc, cond, &matchBlock,
318 &nextBlock);
319 builder.setInsertionPointToEnd(&nextBlock);
320 }
321
322 // The current block is the fall-through after all conditions have been
323 // checked and nothing matched. Move the match block up before this point
324 // to make the IR easier to read.
325 matchBlock.moveBefore(builder.getInsertionBlock());
326
327 // Generate the code for this item's statement in the match block.
328 OpBuilder::InsertionGuard guard(builder);
329 builder.setInsertionPointToEnd(&matchBlock);
330 if (failed(context.convertStatement(*item.stmt)))
331 return failure();
332 if (!isTerminated()) {
333 auto loc = context.convertLocation(item.stmt->sourceRange);
334 mlir::cf::BranchOp::create(builder, loc, &exitBlock);
335 }
336 }
337
338 const auto caseStmtAttrs = context.compilation.getAttributes(caseStmt);
339 const bool hasFullCaseAttr =
340 llvm::find_if(caseStmtAttrs, [](const AttributeSymbol *attr) {
341 return attr->name == "full_case";
342 }) != caseStmtAttrs.end();
343
344 // Check if the case statement looks exhaustive assuming two-state values.
345 // We use this information to work around a common bug in input Verilog
346 // where a case statement enumerates all possible two-state values of the
347 // case expression, but forgets to deal with cases involving X and Z bits in
348 // the input.
349 //
350 // Once the core dialects start supporting four-state values we may want to
351 // tuck this behind an import option that is on by default, since it does
352 // not preserve semantics.
353 auto twoStateExhaustive = false;
354 if (auto intType = dyn_cast<moore::IntType>(caseExpr.getType());
355 intType && intType.getWidth() < 32 &&
356 itemConsts.size() == (1 << intType.getWidth())) {
357 // Sort the constants by value.
358 llvm::sort(itemConsts, [](auto a, auto b) {
359 return a.getValue().getRawValue().ult(b.getValue().getRawValue());
360 });
361
362 // Ensure that every possible value of the case expression is present. Do
363 // this by starting at 0 and iterating over all sorted items. Each item
364 // must be the previous item + 1. At the end, the addition must exactly
365 // overflow and take us back to zero.
366 auto nextValue = FVInt::getZero(intType.getWidth());
367 for (auto value : itemConsts) {
368 if (value.getValue() != nextValue)
369 break;
370 nextValue += 1;
371 }
372 twoStateExhaustive = nextValue.isZero();
373 }
374
375 // If the case statement is exhaustive assuming two-state values, don't
376 // generate the default case. Instead, branch to the last match block. This
377 // will essentially make the last case item the "default".
378 //
379 // Alternatively, if the case statement has an (* full_case *) attribute
380 // but no default case, it indicates that the developer has intentionally
381 // covered all known possible values. Hence, the last match block is
382 // treated as the implicit "default" case.
383 if ((twoStateExhaustive || (hasFullCaseAttr && !caseStmt.defaultCase)) &&
384 lastMatchBlock &&
385 caseStmt.condition == CaseStatementCondition::Normal) {
386 mlir::cf::BranchOp::create(builder, loc, lastMatchBlock);
387 } else {
388 // Generate the default case if present.
389 if (caseStmt.defaultCase)
390 if (failed(context.convertStatement(*caseStmt.defaultCase)))
391 return failure();
392 if (!isTerminated())
393 mlir::cf::BranchOp::create(builder, loc, &exitBlock);
394 }
395
396 // If control never reaches the exit block, remove it and mark control flow
397 // as terminated. Otherwise we continue inserting ops in the exit block.
398 if (exitBlock.hasNoPredecessors()) {
399 exitBlock.erase();
400 setTerminated();
401 } else {
402 builder.setInsertionPointToEnd(&exitBlock);
403 }
404 return success();
405 }
406
407 // Handle `for` loops.
408 LogicalResult visit(const slang::ast::ForLoopStatement &stmt) {
409 // Generate the initializers.
410 for (auto *initExpr : stmt.initializers)
411 if (!context.convertRvalueExpression(*initExpr))
412 return failure();
413
414 // Create the blocks for the loop condition, body, step, and exit.
415 auto &exitBlock = createBlock();
416 auto &stepBlock = createBlock();
417 auto &bodyBlock = createBlock();
418 auto &checkBlock = createBlock();
419 cf::BranchOp::create(builder, loc, &checkBlock);
420
421 // Push the blocks onto the loop stack such that we can continue and break.
422 context.loopStack.push_back({&stepBlock, &exitBlock});
423 auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); });
424
425 // Generate the loop condition check.
426 builder.setInsertionPointToEnd(&checkBlock);
427 auto cond = context.convertRvalueExpression(*stmt.stopExpr);
428 if (!cond)
429 return failure();
430 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
431 cond = moore::ToBuiltinBoolOp::create(builder, loc, cond);
432 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
433
434 // Generate the loop body.
435 builder.setInsertionPointToEnd(&bodyBlock);
436 if (failed(context.convertStatement(stmt.body)))
437 return failure();
438 if (!isTerminated())
439 cf::BranchOp::create(builder, loc, &stepBlock);
440
441 // Generate the step expressions.
442 builder.setInsertionPointToEnd(&stepBlock);
443 for (auto *stepExpr : stmt.steps)
444 if (!context.convertRvalueExpression(*stepExpr))
445 return failure();
446 if (!isTerminated())
447 cf::BranchOp::create(builder, loc, &checkBlock);
448
449 // If control never reaches the exit block, remove it and mark control flow
450 // as terminated. Otherwise we continue inserting ops in the exit block.
451 if (exitBlock.hasNoPredecessors()) {
452 exitBlock.erase();
453 setTerminated();
454 } else {
455 builder.setInsertionPointToEnd(&exitBlock);
456 }
457 return success();
458 }
459
460 LogicalResult visit(const slang::ast::ForeachLoopStatement &stmt) {
461 for (uint32_t level = 0; level < stmt.loopDims.size(); level++) {
462 if (stmt.loopDims[level].loopVar)
463 return recursiveForeach(stmt, level);
464 }
465 return success();
466 }
467
468 // Handle `repeat` loops.
469 LogicalResult visit(const slang::ast::RepeatLoopStatement &stmt) {
470 auto count = context.convertRvalueExpression(stmt.count);
471 if (!count)
472 return failure();
473
474 // Create the blocks for the loop condition, body, step, and exit.
475 auto &exitBlock = createBlock();
476 auto &stepBlock = createBlock();
477 auto &bodyBlock = createBlock();
478 auto &checkBlock = createBlock();
479 auto currentCount = checkBlock.addArgument(count.getType(), count.getLoc());
480 cf::BranchOp::create(builder, loc, &checkBlock, count);
481
482 // Push the blocks onto the loop stack such that we can continue and break.
483 context.loopStack.push_back({&stepBlock, &exitBlock});
484 auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); });
485
486 // Generate the loop condition check.
487 builder.setInsertionPointToEnd(&checkBlock);
488 auto cond = builder.createOrFold<moore::BoolCastOp>(loc, currentCount);
489 cond = moore::ToBuiltinBoolOp::create(builder, loc, cond);
490 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
491
492 // Generate the loop body.
493 builder.setInsertionPointToEnd(&bodyBlock);
494 if (failed(context.convertStatement(stmt.body)))
495 return failure();
496 if (!isTerminated())
497 cf::BranchOp::create(builder, loc, &stepBlock);
498
499 // Decrement the current count and branch back to the check block.
500 builder.setInsertionPointToEnd(&stepBlock);
501 auto one = moore::ConstantOp::create(
502 builder, count.getLoc(), cast<moore::IntType>(count.getType()), 1);
503 Value nextCount =
504 moore::SubOp::create(builder, count.getLoc(), currentCount, one);
505 cf::BranchOp::create(builder, loc, &checkBlock, nextCount);
506
507 // If control never reaches the exit block, remove it and mark control flow
508 // as terminated. Otherwise we continue inserting ops in the exit block.
509 if (exitBlock.hasNoPredecessors()) {
510 exitBlock.erase();
511 setTerminated();
512 } else {
513 builder.setInsertionPointToEnd(&exitBlock);
514 }
515 return success();
516 }
517
518 // Handle `while` and `do-while` loops.
519 LogicalResult createWhileLoop(const slang::ast::Expression &condExpr,
520 const slang::ast::Statement &bodyStmt,
521 bool atLeastOnce) {
522 // Create the blocks for the loop condition, body, and exit.
523 auto &exitBlock = createBlock();
524 auto &bodyBlock = createBlock();
525 auto &checkBlock = createBlock();
526 cf::BranchOp::create(builder, loc, atLeastOnce ? &bodyBlock : &checkBlock);
527 if (atLeastOnce)
528 bodyBlock.moveBefore(&checkBlock);
529
530 // Push the blocks onto the loop stack such that we can continue and break.
531 context.loopStack.push_back({&checkBlock, &exitBlock});
532 auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); });
533
534 // Generate the loop condition check.
535 builder.setInsertionPointToEnd(&checkBlock);
536 auto cond = context.convertRvalueExpression(condExpr);
537 if (!cond)
538 return failure();
539 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
540 cond = moore::ToBuiltinBoolOp::create(builder, loc, cond);
541 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
542
543 // Generate the loop body.
544 builder.setInsertionPointToEnd(&bodyBlock);
545 if (failed(context.convertStatement(bodyStmt)))
546 return failure();
547 if (!isTerminated())
548 cf::BranchOp::create(builder, loc, &checkBlock);
549
550 // If control never reaches the exit block, remove it and mark control flow
551 // as terminated. Otherwise we continue inserting ops in the exit block.
552 if (exitBlock.hasNoPredecessors()) {
553 exitBlock.erase();
554 setTerminated();
555 } else {
556 builder.setInsertionPointToEnd(&exitBlock);
557 }
558 return success();
559 }
560
561 LogicalResult visit(const slang::ast::WhileLoopStatement &stmt) {
562 return createWhileLoop(stmt.cond, stmt.body, false);
563 }
564
565 LogicalResult visit(const slang::ast::DoWhileLoopStatement &stmt) {
566 return createWhileLoop(stmt.cond, stmt.body, true);
567 }
568
569 // Handle `forever` loops.
570 LogicalResult visit(const slang::ast::ForeverLoopStatement &stmt) {
571 // Create the blocks for the loop body and exit.
572 auto &exitBlock = createBlock();
573 auto &bodyBlock = createBlock();
574 cf::BranchOp::create(builder, loc, &bodyBlock);
575
576 // Push the blocks onto the loop stack such that we can continue and break.
577 context.loopStack.push_back({&bodyBlock, &exitBlock});
578 auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); });
579
580 // Generate the loop body.
581 builder.setInsertionPointToEnd(&bodyBlock);
582 if (failed(context.convertStatement(stmt.body)))
583 return failure();
584 if (!isTerminated())
585 cf::BranchOp::create(builder, loc, &bodyBlock);
586
587 // If control never reaches the exit block, remove it and mark control flow
588 // as terminated. Otherwise we continue inserting ops in the exit block.
589 if (exitBlock.hasNoPredecessors()) {
590 exitBlock.erase();
591 setTerminated();
592 } else {
593 builder.setInsertionPointToEnd(&exitBlock);
594 }
595 return success();
596 }
597
598 // Handle timing control.
599 LogicalResult visit(const slang::ast::TimedStatement &stmt) {
600 return context.convertTimingControl(stmt.timing, stmt.stmt);
601 }
602
603 // Handle return statements.
604 LogicalResult visit(const slang::ast::ReturnStatement &stmt) {
605 if (stmt.expr) {
606 auto expr = context.convertRvalueExpression(*stmt.expr);
607 if (!expr)
608 return failure();
609 mlir::func::ReturnOp::create(builder, loc, expr);
610 } else {
611 mlir::func::ReturnOp::create(builder, loc);
612 }
613 setTerminated();
614 return success();
615 }
616
617 // Handle continue statements.
618 LogicalResult visit(const slang::ast::ContinueStatement &stmt) {
619 if (context.loopStack.empty())
620 return mlir::emitError(loc,
621 "cannot `continue` without a surrounding loop");
622 cf::BranchOp::create(builder, loc, context.loopStack.back().continueBlock);
623 setTerminated();
624 return success();
625 }
626
627 // Handle break statements.
628 LogicalResult visit(const slang::ast::BreakStatement &stmt) {
629 if (context.loopStack.empty())
630 return mlir::emitError(loc, "cannot `break` without a surrounding loop");
631 cf::BranchOp::create(builder, loc, context.loopStack.back().breakBlock);
632 setTerminated();
633 return success();
634 }
635
636 // Handle immediate assertion statements.
637 LogicalResult visit(const slang::ast::ImmediateAssertionStatement &stmt) {
638 auto cond = context.convertRvalueExpression(stmt.cond);
639 cond = context.convertToBool(cond);
640 if (!cond)
641 return failure();
642
643 // Handle assertion statements that don't have an action block.
644 if (stmt.ifTrue && stmt.ifTrue->as_if<slang::ast::EmptyStatement>()) {
645 auto defer = moore::DeferAssert::Immediate;
646 if (stmt.isFinal)
647 defer = moore::DeferAssert::Final;
648 else if (stmt.isDeferred)
649 defer = moore::DeferAssert::Observed;
650
651 switch (stmt.assertionKind) {
652 case slang::ast::AssertionKind::Assert:
653 moore::AssertOp::create(builder, loc, defer, cond, StringAttr{});
654 return success();
655 case slang::ast::AssertionKind::Assume:
656 moore::AssumeOp::create(builder, loc, defer, cond, StringAttr{});
657 return success();
658 case slang::ast::AssertionKind::CoverProperty:
659 moore::CoverOp::create(builder, loc, defer, cond, StringAttr{});
660 return success();
661 default:
662 break;
663 }
664 mlir::emitError(loc) << "unsupported immediate assertion kind: "
665 << slang::ast::toString(stmt.assertionKind);
666 return failure();
667 }
668
669 // Regard assertion statements with an action block as the "if-else".
670 cond = moore::ToBuiltinBoolOp::create(builder, loc, cond);
671
672 // Create the blocks for the true and false branches, and the exit block.
673 Block &exitBlock = createBlock();
674 Block *falseBlock = stmt.ifFalse ? &createBlock() : nullptr;
675 Block &trueBlock = createBlock();
676 cf::CondBranchOp::create(builder, loc, cond, &trueBlock,
677 falseBlock ? falseBlock : &exitBlock);
678
679 // Generate the true branch.
680 builder.setInsertionPointToEnd(&trueBlock);
681 if (stmt.ifTrue && failed(context.convertStatement(*stmt.ifTrue)))
682 return failure();
683 if (!isTerminated())
684 cf::BranchOp::create(builder, loc, &exitBlock);
685
686 if (stmt.ifFalse) {
687 // Generate the false branch if present.
688 builder.setInsertionPointToEnd(falseBlock);
689 if (failed(context.convertStatement(*stmt.ifFalse)))
690 return failure();
691 if (!isTerminated())
692 cf::BranchOp::create(builder, loc, &exitBlock);
693 }
694
695 // If control never reaches the exit block, remove it and mark control flow
696 // as terminated. Otherwise we continue inserting ops in the exit block.
697 if (exitBlock.hasNoPredecessors()) {
698 exitBlock.erase();
699 setTerminated();
700 } else {
701 builder.setInsertionPointToEnd(&exitBlock);
702 }
703 return success();
704 }
705
706 // Handle concurrent assertion statements.
707 LogicalResult visit(const slang::ast::ConcurrentAssertionStatement &stmt) {
708 auto loc = context.convertLocation(stmt.sourceRange);
709 auto property = context.convertAssertionExpression(stmt.propertySpec, loc);
710 if (!property)
711 return failure();
712
713 // Handle assertion statements that don't have an action block.
714 if (stmt.ifTrue && stmt.ifTrue->as_if<slang::ast::EmptyStatement>()) {
715 switch (stmt.assertionKind) {
716 case slang::ast::AssertionKind::Assert:
717 verif::AssertOp::create(builder, loc, property, Value(), StringAttr{});
718 return success();
719 case slang::ast::AssertionKind::Assume:
720 verif::AssumeOp::create(builder, loc, property, Value(), StringAttr{});
721 return success();
722 default:
723 break;
724 }
725 mlir::emitError(loc) << "unsupported concurrent assertion kind: "
726 << slang::ast::toString(stmt.assertionKind);
727 return failure();
728 }
729
730 mlir::emitError(loc)
731 << "concurrent assertion statements with action blocks "
732 "are not supported yet";
733 return failure();
734 }
735
736 /// Handle the subset of system calls that return no result value. Return
737 /// true if the called system task could be handled, false otherwise. Return
738 /// failure if an error occurred.
739 FailureOr<bool>
740 visitSystemCall(const slang::ast::ExpressionStatement &stmt,
741 const slang::ast::CallExpression &expr,
742 const slang::ast::CallExpression::SystemCallInfo &info) {
743 const auto &subroutine = *info.subroutine;
744 auto args = expr.arguments();
745
746 // Simulation Control Tasks
747
748 if (subroutine.name == "$stop") {
749 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
750 moore::StopBIOp::create(builder, loc);
751 return true;
752 }
753
754 if (subroutine.name == "$finish") {
755 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
756 moore::FinishBIOp::create(builder, loc, 0);
757 moore::UnreachableOp::create(builder, loc);
758 setTerminated();
759 return true;
760 }
761
762 if (subroutine.name == "$exit") {
763 // Calls to `$exit` from outside a `program` are ignored. Since we don't
764 // yet support programs, there is nothing to do here.
765 // TODO: Fix this once we support programs.
766 return true;
767 }
768
769 // Display and Write Tasks (`$display[boh]?` or `$write[boh]?`)
770
771 // Check for a `$display` or `$write` prefix.
772 bool isDisplay = false; // display or write
773 bool appendNewline = false; // display
774 StringRef remainingName = subroutine.name;
775 if (remainingName.consume_front("$display")) {
776 isDisplay = true;
777 appendNewline = true;
778 } else if (remainingName.consume_front("$write")) {
779 isDisplay = true;
780 }
781
782 // Check for optional `b`, `o`, or `h` suffix indicating default format.
783 using moore::IntFormat;
784 IntFormat defaultFormat = IntFormat::Decimal;
785 if (isDisplay && !remainingName.empty()) {
786 if (remainingName == "b")
787 defaultFormat = IntFormat::Binary;
788 else if (remainingName == "o")
789 defaultFormat = IntFormat::Octal;
790 else if (remainingName == "h")
791 defaultFormat = IntFormat::HexLower;
792 else
793 isDisplay = false;
794 }
795
796 if (isDisplay) {
797 auto message =
798 context.convertFormatString(args, loc, defaultFormat, appendNewline);
799 if (failed(message))
800 return failure();
801 if (*message == Value{})
802 return true;
803 moore::DisplayBIOp::create(builder, loc, *message);
804 return true;
805 }
806
807 // Severity Tasks
808 using moore::Severity;
809 std::optional<Severity> severity;
810 if (subroutine.name == "$info")
811 severity = Severity::Info;
812 else if (subroutine.name == "$warning")
813 severity = Severity::Warning;
814 else if (subroutine.name == "$error")
815 severity = Severity::Error;
816 else if (subroutine.name == "$fatal")
817 severity = Severity::Fatal;
818
819 if (severity) {
820 // The `$fatal` task has an optional leading verbosity argument.
821 const slang::ast::Expression *verbosityExpr = nullptr;
822 if (severity == Severity::Fatal && args.size() >= 1) {
823 verbosityExpr = args[0];
824 args = args.subspan(1);
825 }
826
827 // Handle the string formatting.
828 auto message = context.convertFormatString(args, loc);
829 if (failed(message))
830 return failure();
831 if (*message == Value{})
832 *message = moore::FormatLiteralOp::create(builder, loc, "");
833
834 moore::SeverityBIOp::create(builder, loc, *severity, *message);
835
836 // Handle the `$fatal` case which behaves like a `$finish`.
837 if (severity == Severity::Fatal) {
838 createFinishMessage(verbosityExpr);
839 moore::FinishBIOp::create(builder, loc, 1);
840 moore::UnreachableOp::create(builder, loc);
841 setTerminated();
842 }
843 return true;
844 }
845
846 // Give up on any other system tasks. These will be tried again as an
847 // expression later.
848 return false;
849 }
850
851 /// Create the optional diagnostic message print for finish-like ops.
852 void createFinishMessage(const slang::ast::Expression *verbosityExpr) {
853 unsigned verbosity = 1;
854 if (verbosityExpr) {
855 auto value =
856 context.evaluateConstant(*verbosityExpr).integer().as<unsigned>();
857 assert(value && "Slang guarantees constant verbosity parameter");
858 verbosity = *value;
859 }
860 if (verbosity == 0)
861 return;
862 moore::FinishMessageBIOp::create(builder, loc, verbosity > 1);
863 }
864
865 /// Emit an error for all other statements.
866 template <typename T>
867 LogicalResult visit(T &&stmt) {
868 mlir::emitError(loc, "unsupported statement: ")
869 << slang::ast::toString(stmt.kind);
870 return mlir::failure();
871 }
872
873 LogicalResult visitInvalid(const slang::ast::Statement &stmt) {
874 mlir::emitError(loc, "invalid statement: ")
875 << slang::ast::toString(stmt.kind);
876 return mlir::failure();
877 }
878};
879} // namespace
880
881LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) {
882 assert(builder.getInsertionBlock());
883 auto loc = convertLocation(stmt.sourceRange);
884 return stmt.visit(StmtVisitor(*this, loc));
885}
886// 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.
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:176
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.