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