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