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 "slang/ast/expressions/MiscExpressions.h"
19#include "slang/ast/symbols/CompilationUnitSymbols.h"
20#include "slang/ast/symbols/InstanceSymbols.h"
21#include "llvm/ADT/ScopeExit.h"
22#include "llvm/Support/raw_ostream.h"
23
24using namespace mlir;
25using namespace circt;
26using namespace ImportVerilog;
27
28/// Build the message printed by the `$printtimescale` system task. If a module
29/// instance or `$unit` is passed as argument, report that scope's time scale;
30/// otherwise report the time scale of the current scope.
31static std::string buildPrintTimeScaleMessage(
32 Context &context, std::span<const slang::ast::Expression *const> args) {
33 auto timeScale = context.timeScale;
34 std::string target;
35
36 if (!args.empty()) {
37 if (auto *expr = args[0]->as_if<slang::ast::ArbitrarySymbolExpression>()) {
38 const auto *symbol = expr->symbol.get();
39 if (auto *instance = symbol->as_if<slang::ast::InstanceSymbol>()) {
40 timeScale = instance->body.getTimeScale().value_or(timeScale);
41 target = instance->getHierarchicalPath();
42 } else if (auto *unit =
43 symbol->as_if<slang::ast::CompilationUnitSymbol>()) {
44 timeScale = unit->getTimeScale().value_or(timeScale);
45 target = "$unit";
46 } else if (symbol->kind == slang::ast::SymbolKind::Root) {
47 target = "$root";
48 }
49 }
50 }
51
52 std::string out;
53 llvm::raw_string_ostream os(out);
54 os << "Time scale";
55 if (!target.empty())
56 os << " of " << target;
57 os << " is " << timeScale.base.toString() << " / "
58 << timeScale.precision.toString() << "\n";
59 return out;
60}
61
62// NOLINTBEGIN(misc-no-recursion)
63namespace {
64struct StmtVisitor {
66 Location loc;
67 OpBuilder &builder;
68
69 StmtVisitor(Context &context, Location loc)
70 : context(context), loc(loc), builder(context.builder) {}
71
72 bool isTerminated() const { return !builder.getInsertionBlock(); }
73 void setTerminated() { builder.clearInsertionPoint(); }
74
75 Block &createBlock() {
76 assert(builder.getInsertionBlock());
77 auto block = std::make_unique<Block>();
78 block->insertAfter(builder.getInsertionBlock());
79 return *block.release();
80 }
81
82 LogicalResult recursiveForeach(const slang::ast::ForeachLoopStatement &stmt,
83 uint32_t level) {
84 // find current dimension we are operate.
85 const auto &loopDim = stmt.loopDims[level];
86 if (!loopDim.range.has_value())
87 return mlir::emitError(loc) << "dynamic loop variable is unsupported";
88 auto &exitBlock = createBlock();
89 auto &stepBlock = createBlock();
90 auto &bodyBlock = createBlock();
91 auto &checkBlock = createBlock();
92
93 // Push the blocks onto the loop stack such that we can continue and break.
94 context.loopStack.push_back({&stepBlock, &exitBlock});
95 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
96
97 const auto &iter = loopDim.loopVar;
98 auto type = context.convertType(*iter->getDeclaredType());
99 if (!type)
100 return failure();
101
102 Value initial = moore::ConstantOp::create(
103 builder, loc, cast<moore::IntType>(type), loopDim.range->lower());
104
105 // Create loop varirable in this dimension
106 Value varOp = moore::VariableOp::create(
107 builder, loc, moore::RefType::get(cast<moore::UnpackedType>(type)),
108 builder.getStringAttr(iter->name), initial);
109 context.valueSymbols.insertIntoScope(context.valueSymbols.getCurScope(),
110 iter, varOp);
111
112 cf::BranchOp::create(builder, loc, &checkBlock);
113 builder.setInsertionPointToEnd(&checkBlock);
114
115 // When the loop variable is greater than the upper bound, goto exit
116 auto upperBound = moore::ConstantOp::create(
117 builder, loc, cast<moore::IntType>(type), loopDim.range->upper());
118
119 auto var = moore::ReadOp::create(builder, loc, varOp);
120 Value cond = moore::SleOp::create(builder, loc, var, upperBound);
121 if (!cond)
122 return failure();
123 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
124 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
125 ty && ty.getDomain() == Domain::FourValued) {
126 cond = moore::LogicToIntOp::create(builder, loc, cond);
127 }
128 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
129 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
130
131 builder.setInsertionPointToEnd(&bodyBlock);
132
133 // find next dimension in this foreach statement, it finded then recuersive
134 // resolve, else perform body statement
135 bool hasNext = false;
136 for (uint32_t nextLevel = level + 1; nextLevel < stmt.loopDims.size();
137 nextLevel++) {
138 if (stmt.loopDims[nextLevel].loopVar) {
139 if (failed(recursiveForeach(stmt, nextLevel)))
140 return failure();
141 hasNext = true;
142 break;
143 }
144 }
145
146 if (!hasNext) {
147 if (failed(context.convertStatement(stmt.body)))
148 return failure();
149 }
150 if (!isTerminated())
151 cf::BranchOp::create(builder, loc, &stepBlock);
152
153 builder.setInsertionPointToEnd(&stepBlock);
154
155 // add one to loop variable
156 var = moore::ReadOp::create(builder, loc, varOp);
157 auto one =
158 moore::ConstantOp::create(builder, loc, cast<moore::IntType>(type), 1);
159 auto postValue = moore::AddOp::create(builder, loc, var, one).getResult();
160 moore::BlockingAssignOp::create(builder, loc, varOp, postValue);
161 cf::BranchOp::create(builder, loc, &checkBlock);
162
163 if (exitBlock.hasNoPredecessors()) {
164 exitBlock.erase();
165 setTerminated();
166 } else {
167 builder.setInsertionPointToEnd(&exitBlock);
168 }
169 return success();
170 }
171
172 // Skip empty statements (stray semicolons).
173 LogicalResult visit(const slang::ast::EmptyStatement &) { return success(); }
174
175 // Convert every statement in a statement list. The Verilog syntax follows a
176 // similar philosophy as C/C++, where things like `if` and `for` accept a
177 // single statement as body. But then a `{...}` block is a valid statement,
178 // which allows for the `if {...}` syntax. In Verilog, things like `final`
179 // accept a single body statement, but that can be a `begin ... end` block,
180 // which in turn has a single body statement, which then commonly is a list of
181 // statements.
182 LogicalResult visit(const slang::ast::StatementList &stmts) {
183 for (auto *stmt : stmts.list) {
184 if (isTerminated()) {
185 auto loc = context.convertLocation(stmt->sourceRange);
186 mlir::emitWarning(loc, "unreachable code");
187 break;
188 }
189 if (failed(context.convertStatement(*stmt)))
190 return failure();
191 }
192 return success();
193 }
194
195 // Process slang BlockStatements. These comprise all standard `begin ... end`
196 // blocks as well as `fork ... join` constructs. Standard blocks can have
197 // their contents extracted directly, however fork-join blocks require special
198 // handling.
199 LogicalResult visit(const slang::ast::BlockStatement &stmt) {
200 moore::JoinKind kind;
201 switch (stmt.blockKind) {
202 case slang::ast::StatementBlockKind::Sequential:
203 // Inline standard `begin ... end` blocks into the parent.
204 return context.convertStatement(stmt.body);
205 case slang::ast::StatementBlockKind::JoinAll:
206 kind = moore::JoinKind::Join;
207 break;
208 case slang::ast::StatementBlockKind::JoinAny:
209 kind = moore::JoinKind::JoinAny;
210 break;
211 case slang::ast::StatementBlockKind::JoinNone:
212 kind = moore::JoinKind::JoinNone;
213 break;
214 }
215
216 // Slang stores all threads of a fork-join block inside a `StatementList`.
217 // This cannot be visited normally due to the need to make each statement a
218 // separate thread so must be converted here.
219 auto *threadList = stmt.body.as_if<slang::ast::StatementList>();
220 unsigned int threadCount = threadList ? threadList->list.size() : 1;
221
222 auto forkOp = moore::ForkJoinOp::create(builder, loc, kind, threadCount);
223 OpBuilder::InsertionGuard guard(builder);
224
225 // When only a single statement is present, Slang does not create a
226 // `StatementList`.
227 if (!threadList) {
228 auto &tBlock = forkOp->getRegion(0).emplaceBlock();
229 builder.setInsertionPointToStart(&tBlock);
230 if (failed(context.convertStatement(stmt.body)))
231 return failure();
232 moore::CompleteOp::create(builder, loc);
233 return success();
234 }
235
236 int i = 0;
237 for (auto *thread : threadList->list) {
238 auto &tBlock = forkOp->getRegion(i).emplaceBlock();
239 builder.setInsertionPointToStart(&tBlock);
240 // Populate thread operator with thread body and finish with a thread
241 // terminator.
242 if (failed(context.convertStatement(*thread)))
243 return failure();
244 moore::CompleteOp::create(builder, loc);
245 i++;
246 }
247 return success();
248 }
249
250 // Handle expression statements.
251 LogicalResult visit(const slang::ast::ExpressionStatement &stmt) {
252 // Special handling for calls to system tasks that return no result value.
253 if (const auto *call = stmt.expr.as_if<slang::ast::CallExpression>()) {
254 if (const auto *info =
255 std::get_if<slang::ast::CallExpression::SystemCallInfo>(
256 &call->subroutine)) {
257 auto handled = visitSystemCall(stmt, *call, *info);
258 if (failed(handled))
259 return failure();
260 if (handled == true)
261 return success();
262 }
263 }
264
265 auto value = context.convertRvalueExpression(stmt.expr);
266 if (!value)
267 return failure();
268
269 // Expressions like calls to void functions return a dummy value that has no
270 // uses. If the returned value is trivially dead, remove it.
271 if (auto *defOp = value.getDefiningOp())
272 if (isOpTriviallyDead(defOp))
273 defOp->erase();
274
275 return success();
276 }
277
278 // Handle variable declarations.
279 LogicalResult visit(const slang::ast::VariableDeclStatement &stmt) {
280 const auto &var = stmt.symbol;
281 auto type = context.convertType(*var.getDeclaredType());
282 if (!type)
283 return failure();
284
285 Value initial;
286 if (const auto *init = var.getInitializer()) {
287 initial = context.convertRvalueExpression(*init, type);
288 if (!initial)
289 return failure();
290 }
291
292 // Collect local temporary variables.
293 auto varOp = moore::VariableOp::create(
294 builder, loc, moore::RefType::get(cast<moore::UnpackedType>(type)),
295 builder.getStringAttr(var.name), initial);
296 context.valueSymbols.insertIntoScope(context.valueSymbols.getCurScope(),
297 &var, varOp);
298 const auto &canonTy = var.getType().getCanonicalType();
299 if (const auto *vi = canonTy.as_if<slang::ast::VirtualInterfaceType>())
300 if (failed(context.registerVirtualInterfaceMembers(var, *vi, loc)))
301 return failure();
302 return success();
303 }
304
305 // Handle if statements.
306 LogicalResult visit(const slang::ast::ConditionalStatement &stmt) {
307 // Generate the condition. There may be multiple conditions linked with the
308 // `&&&` operator.
309 Value allConds;
310 for (const auto &condition : stmt.conditions) {
311 if (condition.pattern)
312 return mlir::emitError(loc,
313 "match patterns in if conditions not supported");
314 auto cond = context.convertRvalueExpression(*condition.expr);
315 if (!cond)
316 return failure();
317 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
318 if (allConds)
319 allConds = moore::AndOp::create(builder, loc, allConds, cond);
320 else
321 allConds = cond;
322 }
323 assert(allConds && "slang guarantees at least one condition");
324 if (auto ty = dyn_cast<moore::IntType>(allConds.getType());
325 ty && ty.getDomain() == Domain::FourValued) {
326 allConds = moore::LogicToIntOp::create(builder, loc, allConds);
327 }
328 allConds = moore::ToBuiltinIntOp::create(builder, loc, allConds);
329
330 // Create the blocks for the true and false branches, and the exit block.
331 Block &exitBlock = createBlock();
332 Block *falseBlock = stmt.ifFalse ? &createBlock() : nullptr;
333 Block &trueBlock = createBlock();
334 cf::CondBranchOp::create(builder, loc, allConds, &trueBlock,
335 falseBlock ? falseBlock : &exitBlock);
336
337 // Generate the true branch.
338 builder.setInsertionPointToEnd(&trueBlock);
339 if (failed(context.convertStatement(stmt.ifTrue)))
340 return failure();
341 if (!isTerminated())
342 cf::BranchOp::create(builder, loc, &exitBlock);
343
344 // Generate the false branch if present.
345 if (stmt.ifFalse) {
346 builder.setInsertionPointToEnd(falseBlock);
347 if (failed(context.convertStatement(*stmt.ifFalse)))
348 return failure();
349 if (!isTerminated())
350 cf::BranchOp::create(builder, loc, &exitBlock);
351 }
352
353 // If control never reaches the exit block, remove it and mark control flow
354 // as terminated. Otherwise we continue inserting ops in the exit block.
355 if (exitBlock.hasNoPredecessors()) {
356 exitBlock.erase();
357 setTerminated();
358 } else {
359 builder.setInsertionPointToEnd(&exitBlock);
360 }
361 return success();
362 }
363
364 /// Handle case statements.
365 LogicalResult visit(const slang::ast::CaseStatement &caseStmt) {
366 using slang::ast::AttributeSymbol;
367 using slang::ast::CaseStatementCondition;
368 if (auto *caseType =
369 caseStmt.expr.as_if<slang::ast::TypeReferenceExpression>()) {
370 if (caseStmt.condition != CaseStatementCondition::Normal)
371 return mlir::emitError(loc,
372 "unsupported type reference case condition");
373
374 const slang::ast::Statement *matchedStmt = nullptr;
375 for (const auto &item : caseStmt.items) {
376 for (const auto *expr : item.expressions) {
377 auto *itemType = expr->as_if<slang::ast::TypeReferenceExpression>();
378 if (!itemType)
379 return mlir::emitError(
380 context.convertLocation(expr->sourceRange),
381 "unsupported non-type item in type reference case statement");
382 if (itemType->targetType.isMatching(caseType->targetType)) {
383 matchedStmt = item.stmt;
384 break;
385 }
386 }
387 if (matchedStmt)
388 break;
389 }
390
391 if (matchedStmt)
392 return context.convertStatement(*matchedStmt);
393 if (caseStmt.defaultCase)
394 return context.convertStatement(*caseStmt.defaultCase);
395 return success();
396 }
397
398 auto caseExpr = context.convertRvalueExpression(caseStmt.expr);
399 if (!caseExpr)
400 return failure();
401
402 // Check each case individually. This currently ignores the `unique`,
403 // `unique0`, and `priority` modifiers which would allow for additional
404 // optimizations.
405 auto &exitBlock = createBlock();
406 Block *lastMatchBlock = nullptr;
407 SmallVector<moore::FVIntegerAttr> itemConsts;
408
409 for (const auto &item : caseStmt.items) {
410 // Create the block that will contain the main body of the expression.
411 // This is where any of the comparisons will branch to if they match.
412 auto &matchBlock = createBlock();
413 lastMatchBlock = &matchBlock;
414
415 // The SV standard requires expressions to be checked in the order
416 // specified by the user, and for the evaluation to stop as soon as the
417 // first matching expression is encountered.
418 for (const auto *expr : item.expressions) {
419 Value cond;
420 auto itemLoc = loc;
421
422 if (caseStmt.condition == CaseStatementCondition::Inside) {
423 // ConvertInsideCheck will check insideLhs whether it is empty or not.
424 cond = context.convertInsideCheck(
425 context.convertToSimpleBitVector(caseExpr), itemLoc, *expr);
426 if (!cond)
427 return failure();
428 } else {
429 auto value = context.convertRvalueExpression(*expr);
430 if (!value)
431 return failure();
432 itemLoc = value.getLoc();
433
434 // Take note if the expression is a constant.
435 auto maybeConst = value;
436 while (
437 isa_and_nonnull<moore::ConversionOp, moore::IntToLogicOp,
438 moore::LogicToIntOp>(maybeConst.getDefiningOp()))
439 maybeConst = maybeConst.getDefiningOp()->getOperand(0);
440 if (auto defOp = maybeConst.getDefiningOp<moore::ConstantOp>())
441 itemConsts.push_back(defOp.getValueAttr());
442
443 // Generate the appropriate equality operator.
444 switch (caseStmt.condition) {
445 case CaseStatementCondition::Normal:
446 cond = moore::CaseEqOp::create(builder, itemLoc, caseExpr, value);
447 break;
448 case CaseStatementCondition::WildcardXOrZ:
449 cond = moore::CaseXZEqOp::create(builder, itemLoc, caseExpr, value);
450 break;
451 case CaseStatementCondition::WildcardJustZ:
452 cond = moore::CaseZEqOp::create(builder, itemLoc, caseExpr, value);
453 break;
454 case CaseStatementCondition::Inside:
455 llvm_unreachable("Inside condition has been handled already");
456 break;
457 }
458 }
459
460 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
461 ty && ty.getDomain() == Domain::FourValued) {
462 cond = moore::LogicToIntOp::create(builder, loc, cond);
463 }
464 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
465
466 // If the condition matches, branch to the match block. Otherwise
467 // continue checking the next expression in a new block.
468 auto &nextBlock = createBlock();
469 mlir::cf::CondBranchOp::create(builder, itemLoc, cond, &matchBlock,
470 &nextBlock);
471 builder.setInsertionPointToEnd(&nextBlock);
472 }
473
474 // The current block is the fall-through after all conditions have been
475 // checked and nothing matched. Move the match block up before this point
476 // to make the IR easier to read.
477 matchBlock.moveBefore(builder.getInsertionBlock());
478
479 // Generate the code for this item's statement in the match block.
480 OpBuilder::InsertionGuard guard(builder);
481 builder.setInsertionPointToEnd(&matchBlock);
482 if (failed(context.convertStatement(*item.stmt)))
483 return failure();
484 if (!isTerminated()) {
485 auto loc = context.convertLocation(item.stmt->sourceRange);
486 mlir::cf::BranchOp::create(builder, loc, &exitBlock);
487 }
488 }
489
490 const auto caseStmtAttrs = context.compilation.getAttributes(caseStmt);
491 const bool hasFullCaseAttr =
492 llvm::find_if(caseStmtAttrs, [](const AttributeSymbol *attr) {
493 return attr->name == "full_case";
494 }) != caseStmtAttrs.end();
495
496 // Check if the case statement looks exhaustive assuming two-state values.
497 // We use this information to work around a common bug in input Verilog
498 // where a case statement enumerates all possible two-state values of the
499 // case expression, but forgets to deal with cases involving X and Z bits in
500 // the input.
501 //
502 // Once the core dialects start supporting four-state values we may want to
503 // tuck this behind an import option that is on by default, since it does
504 // not preserve semantics.
505 auto twoStateExhaustive = false;
506 if (auto intType = dyn_cast<moore::IntType>(caseExpr.getType());
507 intType && intType.getWidth() < 32 &&
508 itemConsts.size() == (1 << intType.getWidth())) {
509 // Sort the constants by value.
510 llvm::sort(itemConsts, [](auto a, auto b) {
511 return a.getValue().getRawValue().ult(b.getValue().getRawValue());
512 });
513
514 // Ensure that every possible value of the case expression is present. Do
515 // this by starting at 0 and iterating over all sorted items. Each item
516 // must be the previous item + 1. At the end, the addition must exactly
517 // overflow and take us back to zero.
518 auto nextValue = FVInt::getZero(intType.getWidth());
519 for (auto value : itemConsts) {
520 if (value.getValue() != nextValue)
521 break;
522 nextValue += 1;
523 }
524 twoStateExhaustive = nextValue.isZero();
525 }
526
527 // If the case statement is exhaustive assuming two-state values, don't
528 // generate the default case. Instead, branch to the last match block. This
529 // will essentially make the last case item the "default".
530 //
531 // Alternatively, if the case statement has an (* full_case *) attribute
532 // but no default case, it indicates that the developer has intentionally
533 // covered all known possible values. Hence, the last match block is
534 // treated as the implicit "default" case.
535 if ((twoStateExhaustive || (hasFullCaseAttr && !caseStmt.defaultCase)) &&
536 lastMatchBlock &&
537 caseStmt.condition == CaseStatementCondition::Normal) {
538 mlir::cf::BranchOp::create(builder, loc, lastMatchBlock);
539 } else {
540 // Generate the default case if present.
541 if (caseStmt.defaultCase)
542 if (failed(context.convertStatement(*caseStmt.defaultCase)))
543 return failure();
544 if (!isTerminated())
545 mlir::cf::BranchOp::create(builder, loc, &exitBlock);
546 }
547
548 // If control never reaches the exit block, remove it and mark control flow
549 // as terminated. Otherwise we continue inserting ops in the exit block.
550 if (exitBlock.hasNoPredecessors()) {
551 exitBlock.erase();
552 setTerminated();
553 } else {
554 builder.setInsertionPointToEnd(&exitBlock);
555 }
556 return success();
557 }
558
559 // Handle `for` loops.
560 LogicalResult visit(const slang::ast::ForLoopStatement &stmt) {
561 // Generate the initializers.
562 for (auto *initExpr : stmt.initializers)
563 if (!context.convertRvalueExpression(*initExpr))
564 return failure();
565
566 // Create the blocks for the loop condition, body, step, and exit.
567 auto &exitBlock = createBlock();
568 auto &stepBlock = createBlock();
569 auto &bodyBlock = createBlock();
570 auto &checkBlock = createBlock();
571 cf::BranchOp::create(builder, loc, &checkBlock);
572
573 // Push the blocks onto the loop stack such that we can continue and break.
574 context.loopStack.push_back({&stepBlock, &exitBlock});
575 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
576
577 // Generate the loop condition check.
578 builder.setInsertionPointToEnd(&checkBlock);
579 auto cond = context.convertRvalueExpression(*stmt.stopExpr);
580 if (!cond)
581 return failure();
582 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
583 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
584 ty && ty.getDomain() == Domain::FourValued) {
585 cond = moore::LogicToIntOp::create(builder, loc, cond);
586 }
587 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
588 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
589
590 // Generate the loop body.
591 builder.setInsertionPointToEnd(&bodyBlock);
592 if (failed(context.convertStatement(stmt.body)))
593 return failure();
594 if (!isTerminated())
595 cf::BranchOp::create(builder, loc, &stepBlock);
596
597 // Generate the step expressions.
598 builder.setInsertionPointToEnd(&stepBlock);
599 for (auto *stepExpr : stmt.steps)
600 if (!context.convertRvalueExpression(*stepExpr))
601 return failure();
602 if (!isTerminated())
603 cf::BranchOp::create(builder, loc, &checkBlock);
604
605 // If control never reaches the exit block, remove it and mark control flow
606 // as terminated. Otherwise we continue inserting ops in the exit block.
607 if (exitBlock.hasNoPredecessors()) {
608 exitBlock.erase();
609 setTerminated();
610 } else {
611 builder.setInsertionPointToEnd(&exitBlock);
612 }
613 return success();
614 }
615
616 LogicalResult visit(const slang::ast::ForeachLoopStatement &stmt) {
617 for (uint32_t level = 0; level < stmt.loopDims.size(); level++) {
618 if (stmt.loopDims[level].loopVar)
619 return recursiveForeach(stmt, level);
620 }
621 return success();
622 }
623
624 // Handle `repeat` loops.
625 LogicalResult visit(const slang::ast::RepeatLoopStatement &stmt) {
626 auto intType = moore::IntType::getInt(context.getContext(), 32);
627 auto count = context.convertRvalueExpression(stmt.count, intType);
628 if (!count)
629 return failure();
630
631 // Create the blocks for the loop condition, body, step, and exit.
632 auto &exitBlock = createBlock();
633 auto &stepBlock = createBlock();
634 auto &bodyBlock = createBlock();
635 auto &checkBlock = createBlock();
636 auto currentCount = checkBlock.addArgument(count.getType(), count.getLoc());
637 cf::BranchOp::create(builder, loc, &checkBlock, count);
638
639 // Push the blocks onto the loop stack such that we can continue and break.
640 context.loopStack.push_back({&stepBlock, &exitBlock});
641 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
642
643 // Generate the loop condition check.
644 builder.setInsertionPointToEnd(&checkBlock);
645 auto cond = builder.createOrFold<moore::BoolCastOp>(loc, currentCount);
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(stmt.body)))
656 return failure();
657 if (!isTerminated())
658 cf::BranchOp::create(builder, loc, &stepBlock);
659
660 // Decrement the current count and branch back to the check block.
661 builder.setInsertionPointToEnd(&stepBlock);
662 auto one = moore::ConstantOp::create(
663 builder, count.getLoc(), cast<moore::IntType>(count.getType()), 1);
664 Value nextCount =
665 moore::SubOp::create(builder, count.getLoc(), currentCount, one);
666 cf::BranchOp::create(builder, loc, &checkBlock, nextCount);
667
668 // If control never reaches the exit block, remove it and mark control flow
669 // as terminated. Otherwise we continue inserting ops in the exit block.
670 if (exitBlock.hasNoPredecessors()) {
671 exitBlock.erase();
672 setTerminated();
673 } else {
674 builder.setInsertionPointToEnd(&exitBlock);
675 }
676 return success();
677 }
678
679 // Handle `while` and `do-while` loops.
680 LogicalResult createWhileLoop(const slang::ast::Expression &condExpr,
681 const slang::ast::Statement &bodyStmt,
682 bool atLeastOnce) {
683 // Create the blocks for the loop condition, body, and exit.
684 auto &exitBlock = createBlock();
685 auto &bodyBlock = createBlock();
686 auto &checkBlock = createBlock();
687 cf::BranchOp::create(builder, loc, atLeastOnce ? &bodyBlock : &checkBlock);
688 if (atLeastOnce)
689 bodyBlock.moveBefore(&checkBlock);
690
691 // Push the blocks onto the loop stack such that we can continue and break.
692 context.loopStack.push_back({&checkBlock, &exitBlock});
693 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
694
695 // Generate the loop condition check.
696 builder.setInsertionPointToEnd(&checkBlock);
697 auto cond = context.convertRvalueExpression(condExpr);
698 if (!cond)
699 return failure();
700 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
701 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
702 ty && ty.getDomain() == Domain::FourValued) {
703 cond = moore::LogicToIntOp::create(builder, loc, cond);
704 }
705 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
706 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
707
708 // Generate the loop body.
709 builder.setInsertionPointToEnd(&bodyBlock);
710 if (failed(context.convertStatement(bodyStmt)))
711 return failure();
712 if (!isTerminated())
713 cf::BranchOp::create(builder, loc, &checkBlock);
714
715 // If control never reaches the exit block, remove it and mark control flow
716 // as terminated. Otherwise we continue inserting ops in the exit block.
717 if (exitBlock.hasNoPredecessors()) {
718 exitBlock.erase();
719 setTerminated();
720 } else {
721 builder.setInsertionPointToEnd(&exitBlock);
722 }
723 return success();
724 }
725
726 LogicalResult visit(const slang::ast::WhileLoopStatement &stmt) {
727 return createWhileLoop(stmt.cond, stmt.body, false);
728 }
729
730 LogicalResult visit(const slang::ast::DoWhileLoopStatement &stmt) {
731 return createWhileLoop(stmt.cond, stmt.body, true);
732 }
733
734 // Handle `forever` loops.
735 LogicalResult visit(const slang::ast::ForeverLoopStatement &stmt) {
736 // Create the blocks for the loop body and exit.
737 auto &exitBlock = createBlock();
738 auto &bodyBlock = createBlock();
739 cf::BranchOp::create(builder, loc, &bodyBlock);
740
741 // Push the blocks onto the loop stack such that we can continue and break.
742 context.loopStack.push_back({&bodyBlock, &exitBlock});
743 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
744
745 // Generate the loop body.
746 builder.setInsertionPointToEnd(&bodyBlock);
747 if (failed(context.convertStatement(stmt.body)))
748 return failure();
749 if (!isTerminated())
750 cf::BranchOp::create(builder, loc, &bodyBlock);
751
752 // If control never reaches the exit block, remove it and mark control flow
753 // as terminated. Otherwise we continue inserting ops in the exit block.
754 if (exitBlock.hasNoPredecessors()) {
755 exitBlock.erase();
756 setTerminated();
757 } else {
758 builder.setInsertionPointToEnd(&exitBlock);
759 }
760 return success();
761 }
762
763 // Handle timing control.
764 LogicalResult visit(const slang::ast::TimedStatement &stmt) {
765 return context.convertTimingControl(stmt.timing, stmt.stmt);
766 }
767
768 // Handle return statements.
769 LogicalResult visit(const slang::ast::ReturnStatement &stmt) {
770 Operation *parentOp = builder.getInsertionBlock()
771 ? builder.getInsertionBlock()->getParentOp()
772 : nullptr;
773 if (!parentOp)
774 return mlir::emitError(loc) << "return statement is not within an op";
775
776 if (isa<moore::CoroutineOp, moore::ProcedureOp>(parentOp)) {
777 if (stmt.expr)
778 return mlir::emitError(loc)
779 << "unsupported `return <expr>` in a procedure or task";
780 moore::ReturnOp::create(builder, loc);
781 setTerminated();
782 return success();
783 }
784
785 if (!isa<mlir::func::FuncOp>(parentOp))
786 return mlir::emitError(loc) << "unsupported return statement context";
787
788 if (stmt.expr) {
789 auto expr = context.convertRvalueExpression(*stmt.expr);
790 if (!expr)
791 return failure();
792 mlir::func::ReturnOp::create(builder, loc, expr);
793 } else {
794 mlir::func::ReturnOp::create(builder, loc);
795 }
796 setTerminated();
797 return success();
798 }
799
800 // Handle continue statements.
801 LogicalResult visit(const slang::ast::ContinueStatement &stmt) {
802 if (context.loopStack.empty())
803 return mlir::emitError(loc,
804 "cannot `continue` without a surrounding loop");
805 cf::BranchOp::create(builder, loc, context.loopStack.back().continueBlock);
806 setTerminated();
807 return success();
808 }
809
810 // Handle break statements.
811 LogicalResult visit(const slang::ast::BreakStatement &stmt) {
812 if (context.loopStack.empty())
813 return mlir::emitError(loc, "cannot `break` without a surrounding loop");
814 cf::BranchOp::create(builder, loc, context.loopStack.back().breakBlock);
815 setTerminated();
816 return success();
817 }
818
819 // Handle immediate assertion statements.
820 LogicalResult visit(const slang::ast::ImmediateAssertionStatement &stmt) {
821 auto cond = context.convertRvalueExpression(stmt.cond);
822 cond = context.convertToBool(cond);
823 if (!cond)
824 return failure();
825
826 // Handle assertion statements that don't have an action block.
827 if (stmt.ifTrue && stmt.ifTrue->as_if<slang::ast::EmptyStatement>()) {
828 auto defer = moore::DeferAssert::Immediate;
829 if (stmt.isFinal)
830 defer = moore::DeferAssert::Final;
831 else if (stmt.isDeferred)
832 defer = moore::DeferAssert::Observed;
833
834 switch (stmt.assertionKind) {
835 case slang::ast::AssertionKind::Assert:
836 moore::AssertOp::create(builder, loc, defer, cond, StringAttr{});
837 return success();
838 case slang::ast::AssertionKind::Assume:
839 moore::AssumeOp::create(builder, loc, defer, cond, StringAttr{});
840 return success();
841 case slang::ast::AssertionKind::CoverProperty:
842 moore::CoverOp::create(builder, loc, defer, cond, StringAttr{});
843 return success();
844 default:
845 break;
846 }
847 mlir::emitError(loc) << "unsupported immediate assertion kind: "
848 << slang::ast::toString(stmt.assertionKind);
849 return failure();
850 }
851
852 // Regard assertion statements with an action block as the "if-else".
853 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
854 ty && ty.getDomain() == Domain::FourValued) {
855 cond = moore::LogicToIntOp::create(builder, loc, cond);
856 }
857 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
858
859 // Create the blocks for the true and false branches, and the exit block.
860 Block &exitBlock = createBlock();
861 Block *falseBlock = stmt.ifFalse ? &createBlock() : nullptr;
862 Block &trueBlock = createBlock();
863 cf::CondBranchOp::create(builder, loc, cond, &trueBlock,
864 falseBlock ? falseBlock : &exitBlock);
865
866 // Generate the true branch.
867 builder.setInsertionPointToEnd(&trueBlock);
868 if (stmt.ifTrue && failed(context.convertStatement(*stmt.ifTrue)))
869 return failure();
870 if (!isTerminated())
871 cf::BranchOp::create(builder, loc, &exitBlock);
872
873 if (stmt.ifFalse) {
874 // Generate the false branch if present.
875 builder.setInsertionPointToEnd(falseBlock);
876 if (failed(context.convertStatement(*stmt.ifFalse)))
877 return failure();
878 if (!isTerminated())
879 cf::BranchOp::create(builder, loc, &exitBlock);
880 }
881
882 // If control never reaches the exit block, remove it and mark control flow
883 // as terminated. Otherwise we continue inserting ops in the exit block.
884 if (exitBlock.hasNoPredecessors()) {
885 exitBlock.erase();
886 setTerminated();
887 } else {
888 builder.setInsertionPointToEnd(&exitBlock);
889 }
890 return success();
891 }
892
893 // Handle concurrent assertion statements.
894 LogicalResult visit(const slang::ast::ConcurrentAssertionStatement &stmt) {
895 auto loc = context.convertLocation(stmt.sourceRange);
896
897 // Check for a `disable iff` expression:
898 // `disable iff` can only appear at the outermost property that is asserted,
899 // and can never be nested.
900 // Hence we only need to detect if the top level assertion expression has
901 // type DisableIff. (or, if the top level expression is
902 // ClockingAssertionExpr, check for DisableIff inside that).
903 Value enable;
904 Value property;
905 // Find the outermost propertySpec that isn't ClockingAssertionExpr
906 const slang::ast::AssertionExpr *propertySpec;
907 const slang::ast::ClockingAssertionExpr *clocking =
908 stmt.propertySpec.as_if<slang::ast::ClockingAssertionExpr>();
909 if (clocking)
910 propertySpec = &(clocking->expr);
911 else
912 propertySpec = &(stmt.propertySpec);
913
914 if (auto *disableIff =
915 propertySpec->as_if<slang::ast::DisableIffAssertionExpr>()) {
916 // Lower disableIff by negating it and passing as the "enable" operand
917 // to the verif.assert/verif.assume instructions.
918 auto disableCond = context.convertRvalueExpression(disableIff->condition);
919 auto enableCond = moore::NotOp::create(builder, loc, disableCond);
920
921 enable = context.convertToI1(enableCond);
922
923 // Add back the outer `ClockingAssertionExpr` if there is one.
924 if (clocking) {
925 auto clockingExpr = slang::ast::ClockingAssertionExpr(
926 clocking->clocking, disableIff->expr);
927 property = context.convertAssertionExpression(clockingExpr, loc);
928 } else {
929 property = context.convertAssertionExpression(disableIff->expr, loc);
930 }
931 } else {
932 property = context.convertAssertionExpression(stmt.propertySpec, loc);
933 }
934
935 if (!property)
936 return failure();
937
938 // Handle assertion statements that don't have an action block.
939 if (!stmt.ifTrue || stmt.ifTrue->as_if<slang::ast::EmptyStatement>()) {
940 switch (stmt.assertionKind) {
941 case slang::ast::AssertionKind::Assert:
942 verif::AssertOp::create(builder, loc, property, enable, StringAttr{});
943 return success();
944 case slang::ast::AssertionKind::Assume:
945 verif::AssumeOp::create(builder, loc, property, enable, StringAttr{});
946 return success();
947 default:
948 break;
949 }
950 mlir::emitError(loc) << "unsupported concurrent assertion kind: "
951 << slang::ast::toString(stmt.assertionKind);
952 return failure();
953 }
954
955 mlir::emitError(loc)
956 << "concurrent assertion statements with action blocks "
957 "are not supported yet";
958 return failure();
959 }
960
961 // According to 1800-2023 Section 21.2.1 "The display and write tasks":
962 // >> The $display and $write tasks display their arguments in the same
963 // >> order as they appear in the argument list. Each argument can be a
964 // >> string literal or an expression that returns a value.
965 // According to Section 20.10 "Severity system tasks", the same
966 // semantics apply to $fatal, $error, $warning, and $info.
967 // This means we must first check whether the first "string-able"
968 // argument is a Literal Expression which doesn't represent a fully-formatted
969 // string, otherwise we convert it to a FormatStringType.
970 FailureOr<Value>
971 getDisplayMessage(std::span<const slang::ast::Expression *const> args) {
972 if (args.size() == 0)
973 return Value{};
974
975 // Handle the string formatting.
976 // If the second argument is a Literal of some type, we should either
977 // treat it as a literal-to-be-formatted or a FormatStringType.
978 // In this check we use a StringLiteral, but slang allows casting between
979 // any literal expressions (strings, integers, reals, and time at least) so
980 // this is short-hand for "any value literal"
981 if (args[0]->as_if<slang::ast::StringLiteral>()) {
982 return context.convertFormatString(args, loc);
983 }
984 // Check if there's only one argument and it's a FormatStringType
985 if (args.size() == 1) {
986 return context.convertRvalueExpression(
987 *args[0], builder.getType<moore::FormatStringType>());
988 }
989 // Otherwise this looks invalid. Raise an error.
990 return emitError(loc) << "Failed to convert Display Message!";
991 }
992
993 /// Handle the subset of system calls that return no result value. Return
994 /// true if the called system task could be handled, false otherwise. Return
995 /// failure if an error occurred.
996 FailureOr<bool>
997 visitSystemCall(const slang::ast::ExpressionStatement &stmt,
998 const slang::ast::CallExpression &expr,
999 const slang::ast::CallExpression::SystemCallInfo &info) {
1000 using ksn = slang::parsing::KnownSystemName;
1001 const auto &subroutine = *info.subroutine;
1002 auto nameId = subroutine.knownNameId;
1003 auto args = expr.arguments();
1004
1005 // The `$cast` system call is handled by `Context::convertSystemCall` in the
1006 // `Expressions.cpp` file. Skip it is order to avoid visiting the
1007 // `EmptyArgument` node.
1008 if (nameId == ksn::Cast) {
1009 return false;
1010 }
1011
1012 // Simulation Control Tasks
1013
1014 if (nameId == ksn::Stop) {
1015 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
1016 moore::StopBIOp::create(builder, loc);
1017 return true;
1018 }
1019
1020 if (nameId == ksn::Finish) {
1021 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
1022 moore::FinishBIOp::create(builder, loc, 0);
1023 moore::UnreachableOp::create(builder, loc);
1024 setTerminated();
1025 return true;
1026 }
1027
1028 if (nameId == ksn::Exit) {
1029 // Calls to `$exit` from outside a `program` are ignored. Since we don't
1030 // yet support programs, there is nothing to do here.
1031 // TODO: Fix this once we support programs.
1032 return true;
1033 }
1034
1035 // Timescale tasks (`$printtimescale`)
1036
1037 if (nameId == ksn::PrintTimeScale) {
1038 auto message = moore::FormatLiteralOp::create(
1039 builder, loc, buildPrintTimeScaleMessage(context, args));
1040 moore::DisplayBIOp::create(builder, loc, message);
1041 return true;
1042 }
1043
1044 // Display and Write Tasks (`$display[boh]?` or `$write[boh]?` or
1045 // `$fdisplay[boh]?` or `$fwrite[boh]?` or `$swrite[boh]` or `$sformat`)
1046
1047 using moore::IntFormat;
1048 bool isDisplay = false;
1049 bool isFDisplay = false;
1050 bool isSWrite = false;
1051 bool isSFormat = false;
1052 bool appendNewline = false;
1053 IntFormat defaultFormat = IntFormat::Decimal;
1054 switch (nameId) {
1055 case ksn::Display:
1056 isDisplay = true;
1057 appendNewline = true;
1058 break;
1059 case ksn::DisplayB:
1060 isDisplay = true;
1061 appendNewline = true;
1062 defaultFormat = IntFormat::Binary;
1063 break;
1064 case ksn::DisplayO:
1065 isDisplay = true;
1066 appendNewline = true;
1067 defaultFormat = IntFormat::Octal;
1068 break;
1069 case ksn::DisplayH:
1070 isDisplay = true;
1071 appendNewline = true;
1072 defaultFormat = IntFormat::HexLower;
1073 break;
1074 case ksn::Write:
1075 isDisplay = true;
1076 break;
1077 case ksn::WriteB:
1078 isDisplay = true;
1079 defaultFormat = IntFormat::Binary;
1080 break;
1081 case ksn::WriteO:
1082 isDisplay = true;
1083 defaultFormat = IntFormat::Octal;
1084 break;
1085 case ksn::WriteH:
1086 isDisplay = true;
1087 defaultFormat = IntFormat::HexLower;
1088 break;
1089 case ksn::FDisplay:
1090 isFDisplay = true;
1091 appendNewline = true;
1092 break;
1093 case ksn::FDisplayB:
1094 isFDisplay = true;
1095 appendNewline = true;
1096 defaultFormat = IntFormat::Binary;
1097 break;
1098 case ksn::FDisplayO:
1099 isFDisplay = true;
1100 appendNewline = true;
1101 defaultFormat = IntFormat::Octal;
1102 break;
1103 case ksn::FDisplayH:
1104 isFDisplay = true;
1105 appendNewline = true;
1106 defaultFormat = IntFormat::HexLower;
1107 break;
1108 case ksn::FWrite:
1109 isFDisplay = true;
1110 break;
1111 case ksn::FWriteB:
1112 isFDisplay = true;
1113 defaultFormat = IntFormat::Binary;
1114 break;
1115 case ksn::FWriteO:
1116 isFDisplay = true;
1117 defaultFormat = IntFormat::Octal;
1118 break;
1119 case ksn::FWriteH:
1120 isFDisplay = true;
1121 defaultFormat = IntFormat::HexLower;
1122 break;
1123 case ksn::SFormat:
1124 isSFormat = true;
1125 break;
1126 case ksn::SWrite:
1127 isSWrite = true;
1128 break;
1129 case ksn::SWriteB:
1130 isSWrite = true;
1131 defaultFormat = IntFormat::Binary;
1132 break;
1133 case ksn::SWriteO:
1134 isSWrite = true;
1135 defaultFormat = IntFormat::Octal;
1136 break;
1137 case ksn::SWriteH:
1138 isSWrite = true;
1139 defaultFormat = IntFormat::HexLower;
1140 break;
1141 default:
1142 break;
1143 }
1144
1145 if (isDisplay) {
1146 auto message =
1147 context.convertFormatString(args, loc, defaultFormat, appendNewline);
1148 if (failed(message))
1149 return failure();
1150 if (*message == Value{})
1151 return true;
1152 moore::DisplayBIOp::create(builder, loc, *message);
1153 return true;
1154 }
1155
1156 if (isFDisplay) {
1157 assert(!args.empty() && "$fdisplay/$fwrite takes at least 1 argument");
1158
1159 auto fd = context.convertRvalueExpression(
1160 *args[0], moore::IntType::getInt(builder.getContext(), 32));
1161 if (!fd)
1162 return failure();
1163 args = args.subspan(1);
1164
1165 auto message =
1166 context.convertFormatString(args, loc, defaultFormat, appendNewline);
1167 if (failed(message))
1168 return failure();
1169 if (*message == Value{})
1170 return true;
1171 moore::FDisplayBIOp::create(builder, loc, fd, *message);
1172 return true;
1173 }
1174
1175 // According to IEEE 1800-2023 Section 21.3.3 "Formatting data to a
1176 // string" the first argument of $sformat/$swrite is its output; the
1177 // other arguments work like a FormatString.
1178 // In Moore we only support writing to a location if it is a reference;
1179 // However, Section 21.3.3 explains that the output of $sformat/$swrite
1180 // is assigned as if it were cast from a string literal (Section 5.9),
1181 // so this implementation casts the string to the target value.
1182 if (isSWrite || isSFormat) {
1183 if (isSFormat && args.size() < 2)
1184 return emitError(loc) << "$sformat requires at least 2 arguments";
1185 if (isSWrite && args.size() < 1)
1186 return emitError(loc) << "$swrite requires at least 1 argument";
1187
1188 auto fmtValue =
1189 context.convertFormatString(args.subspan(1), loc, defaultFormat,
1190 /*appendNewline=*/false);
1191 if (failed(fmtValue))
1192 return failure();
1193 if (*fmtValue == Value{})
1194 return true;
1195 auto strValue =
1196 moore::FormatStringToStringOp::create(builder, loc, *fmtValue);
1197 auto *lhsExpr = args[0];
1198 if (auto *assignExpr =
1199 lhsExpr->as_if<slang::ast::AssignmentExpression>()) {
1200 auto lhs = context.convertLvalueExpression(assignExpr->left());
1201 if (!lhs)
1202 return failure();
1203 auto convertedValue = context.materializeConversion(
1204 cast<moore::RefType>(lhs.getType()).getNestedType(), strValue,
1205 false, loc);
1206 moore::BlockingAssignOp::create(builder, loc, lhs, convertedValue);
1207 return true;
1208 }
1209 return failure();
1210 }
1211
1212 // Severity Tasks
1213 using moore::Severity;
1214 std::optional<Severity> severity;
1215 if (nameId == ksn::Info)
1216 severity = Severity::Info;
1217 else if (nameId == ksn::Warning)
1218 severity = Severity::Warning;
1219 else if (nameId == ksn::Error)
1220 severity = Severity::Error;
1221 else if (nameId == ksn::Fatal)
1222 severity = Severity::Fatal;
1223
1224 if (severity) {
1225 // The `$fatal` task has an optional leading verbosity argument.
1226 const slang::ast::Expression *verbosityExpr = nullptr;
1227 if (severity == Severity::Fatal && args.size() >= 1) {
1228 verbosityExpr = args[0];
1229 args = args.subspan(1);
1230 }
1231
1232 FailureOr<Value> maybeMessage = getDisplayMessage(args);
1233 if (failed(maybeMessage))
1234 return failure();
1235 auto message = maybeMessage.value();
1236
1237 if (message == Value{})
1238 message = moore::FormatLiteralOp::create(builder, loc, "");
1239 moore::SeverityBIOp::create(builder, loc, *severity, message);
1240
1241 // Handle the `$fatal` case which behaves like a `$finish`.
1242 if (severity == Severity::Fatal) {
1243 createFinishMessage(verbosityExpr);
1244 moore::FinishBIOp::create(builder, loc, 1);
1245 moore::UnreachableOp::create(builder, loc);
1246 setTerminated();
1247 }
1248 return true;
1249 }
1250
1251 // File I/O Tasks
1252
1253 if (nameId == ksn::FClose) {
1254 assert(args.size() == 1 && "$fclose takes 1 argument");
1255 auto fd = context.convertRvalueExpression(
1256 *args[0], moore::IntType::getInt(builder.getContext(), 32));
1257 if (!fd)
1258 return failure();
1259 moore::FCloseBIOp::create(builder, loc, fd);
1260 return true;
1261 }
1262
1263 if (nameId == ksn::FFlush) {
1264 assert(args.size() <= 1 && "$fflush takes at most 1 argument");
1265 Value fd;
1266 if (args.size() == 1) {
1267 fd = context.convertRvalueExpression(
1268 *args[0], moore::IntType::getInt(builder.getContext(), 32));
1269 if (!fd)
1270 return failure();
1271 }
1272 moore::FFlushBIOp::create(builder, loc, fd);
1273 return true;
1274 }
1275
1276 // String Tasks
1277 if (args.size() >= 1 && args[0]->type->isString()) {
1278 auto str = context.convertLvalueExpression(*args[0]);
1279
1280 if (nameId == ksn::Putc) {
1281 // Slang already checks the arity of string tasks.
1282 assert(args.size() == 3 && "`putc` takes 3 arguments");
1283 auto index = context.convertRvalueExpression(*args[1]);
1284 auto character = context.convertRvalueExpression(*args[2]);
1285 moore::StringPutOp::create(builder, loc, str, index, character);
1286 return true;
1287 }
1288
1289 if (nameId == ksn::IToA || nameId == ksn::HexToA ||
1290 nameId == ksn::OctToA || nameId == ksn::BinToA) {
1291 // Slang already checks the arity of string tasks.
1292 assert(args.size() == 2 && "`itoa/hex/oct/bin` takes 2 arguments");
1293 auto integerType = moore::IntType::getLogic(builder.getContext(), 32);
1294 auto input = context.convertRvalueExpression(*args[1], integerType);
1295
1296 switch (nameId) {
1297 case ksn::IToA:
1298 moore::StringItoaOp::create(builder, loc, str, input);
1299 break;
1300 case ksn::HexToA:
1301 moore::StringHextoaOp::create(builder, loc, str, input);
1302 break;
1303 case ksn::OctToA:
1304 moore::StringOcttoaOp::create(builder, loc, str, input);
1305 break;
1306 case ksn::BinToA:
1307 moore::StringBintoaOp::create(builder, loc, str, input);
1308 break;
1309 default:
1310 llvm_unreachable("unexpected ASCII integer to string conversion");
1311 return false;
1312 }
1313 return true;
1314 }
1315
1316 if (nameId == ksn::RealToA) {
1317 // Slang already checks the arity of string tasks.
1318 assert(args.size() == 2 && "`realtoa` takes 2 arguments");
1319 auto realType =
1320 moore::RealType::get(context.getContext(), moore::RealWidth::f64);
1321 auto input = context.convertRvalueExpression(*args[1], realType);
1322 moore::StringRealtoaOp::create(builder, loc, str, input);
1323 return true;
1324 }
1325 return false;
1326 }
1327
1328 // Queue Tasks
1329 if (args.size() >= 1 && args[0]->type->isQueue()) {
1330 auto queue = context.convertLvalueExpression(*args[0]);
1331
1332 // `delete` has two functions: If there is an index passed, then it
1333 // deletes that specific element, otherwise, it clears the entire queue.
1334 if (nameId == ksn::Delete) {
1335 if (args.size() == 1) {
1336 moore::QueueClearOp::create(builder, loc, queue);
1337 return true;
1338 }
1339 if (args.size() == 2) {
1340 auto index = context.convertRvalueExpression(*args[1]);
1341 moore::QueueDeleteOp::create(builder, loc, queue, index);
1342 return true;
1343 }
1344 } else if (nameId == ksn::Insert && args.size() == 3) {
1345 auto index = context.convertRvalueExpression(*args[1]);
1346 auto item = context.convertRvalueExpression(*args[2]);
1347
1348 moore::QueueInsertOp::create(builder, loc, queue, index, item);
1349 return true;
1350 } else if (nameId == ksn::PushBack && args.size() == 2) {
1351 auto item = context.convertRvalueExpression(*args[1]);
1352 moore::QueuePushBackOp::create(builder, loc, queue, item);
1353 return true;
1354 } else if (nameId == ksn::PushFront && args.size() == 2) {
1355 auto item = context.convertRvalueExpression(*args[1]);
1356 moore::QueuePushFrontOp::create(builder, loc, queue, item);
1357 return true;
1358 }
1359
1360 return false;
1361 }
1362
1363 // Associative array tasks
1364 if (args.size() >= 1 && args[0]->type->isAssociativeArray()) {
1365 auto assocArray = context.convertLvalueExpression(*args[0]);
1366
1367 // `delete` has two functions: If there is an index passed, then it
1368 // deletes that specific element, otherwise, it clears the entire
1369 // associative array.
1370 if (nameId == ksn::Delete) {
1371 if (args.size() == 1) {
1372 moore::AssocArrayClearOp::create(builder, loc, assocArray);
1373 return true;
1374 }
1375 if (args.size() == 2) {
1376 auto index = context.convertRvalueExpression(*args[1]);
1377 moore::AssocArrayDeleteOp::create(builder, loc, assocArray, index);
1378 return true;
1379 }
1380 }
1381 }
1382
1383 // Monitor enable/disable tasks (`$monitoron`, `$monitoroff`)
1384 if (nameId == ksn::MonitorOn || nameId == ksn::MonitorOff) {
1385 context.ensureMonitorGlobals();
1386 bool enable = (nameId == ksn::MonitorOn);
1387 auto enabledRef = moore::GetGlobalVariableOp::create(
1388 context.builder, loc, context.monitorEnabledGlobal);
1389 auto value = moore::ConstantOp::create(context.builder, loc,
1390 moore::Domain::TwoValued, enable);
1391 moore::BlockingAssignOp::create(context.builder, loc, enabledRef, value);
1392 return true;
1393 }
1394
1395 // Monitor tasks (`$monitor[boh]?`)
1396 if (nameId == ksn::Monitor || nameId == ksn::MonitorB ||
1397 nameId == ksn::MonitorO || nameId == ksn::MonitorH) {
1398 context.ensureMonitorGlobals();
1399
1400 // Allocate a unique ID for this monitor.
1401 unsigned myId = context.nextMonitorId++;
1402
1403 // Emit code to activate this monitor by setting the active_id global.
1404 auto i32Type = moore::IntType::getInt(context.getContext(), 32);
1405 auto idConst =
1406 moore::ConstantOp::create(context.builder, loc, i32Type, myId);
1407 auto activeRef = moore::GetGlobalVariableOp::create(
1408 context.builder, loc, context.monitorActiveIdGlobal);
1409 moore::BlockingAssignOp::create(context.builder, loc, activeRef, idConst);
1410
1411 // Queue this monitor for processing at module level.
1412 context.pendingMonitors.push_back({myId, loc, &expr});
1413
1414 return true;
1415 }
1416
1417 // Give up on any other system tasks. These will be tried again as an
1418 // expression later.
1419 return false;
1420 }
1421
1422 /// Create the optional diagnostic message print for finish-like ops.
1423 void createFinishMessage(const slang::ast::Expression *verbosityExpr) {
1424 unsigned verbosity = 1;
1425 if (verbosityExpr) {
1426 auto value =
1427 context.evaluateConstant(*verbosityExpr).integer().as<unsigned>();
1428 assert(value && "Slang guarantees constant verbosity parameter");
1429 verbosity = *value;
1430 }
1431 if (verbosity == 0)
1432 return;
1433 moore::FinishMessageBIOp::create(builder, loc, verbosity > 1);
1434 }
1435
1436 // Handle event trigger statements.
1437 LogicalResult visit(const slang::ast::EventTriggerStatement &stmt) {
1438 if (stmt.timing) {
1439 mlir::emitError(loc) << "unsupported delayed event trigger";
1440 return failure();
1441 }
1442
1443 // Events are lowered to `i1` signals. Get an lvalue ref to the signal such
1444 // that we can assign to it.
1445 auto target = context.convertLvalueExpression(stmt.target);
1446 if (!target)
1447 return failure();
1448
1449 // Read and invert the current value of the signal. Writing this inverted
1450 // value to the signal is our event signaling mechanism.
1451 Value inverted = moore::ReadOp::create(builder, loc, target);
1452 inverted = moore::NotOp::create(builder, loc, inverted);
1453
1454 if (stmt.isNonBlocking)
1455 moore::NonBlockingAssignOp::create(builder, loc, target, inverted);
1456 else
1457 moore::BlockingAssignOp::create(builder, loc, target, inverted);
1458 return success();
1459 }
1460
1461 // Handle `wait` statements
1462 LogicalResult visit(const slang::ast::WaitStatement &stmt) {
1463 auto waitOp = moore::WaitLevelOp::create(builder, loc);
1464 {
1465 OpBuilder::InsertionGuard guard(builder);
1466 builder.setInsertionPointToStart(&waitOp.getBody().emplaceBlock());
1467 auto cond = context.convertRvalueExpression(stmt.cond);
1468 if (!cond)
1469 return failure();
1470 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
1471 moore::DetectLevelOp::create(builder, loc, cond);
1472 }
1473 // Handle optional post-wait operation as if it were a separate statement
1474 if (failed(context.convertStatement(stmt.stmt)))
1475 return failure();
1476
1477 return success();
1478 }
1479
1480 LogicalResult visit(const slang::ast::WaitForkStatement &stmt) {
1481 moore::WaitForkOp::create(builder, loc);
1482 return success();
1483 }
1484
1485 /// Emit an error for all other statements.
1486 template <typename T>
1487 LogicalResult visit(T &&stmt) {
1488 mlir::emitError(loc, "unsupported statement: ")
1489 << slang::ast::toString(stmt.kind);
1490 return mlir::failure();
1491 }
1492
1493 LogicalResult visitInvalid(const slang::ast::Statement &stmt) {
1494 mlir::emitError(loc, "invalid statement: ")
1495 << slang::ast::toString(stmt.kind);
1496 return mlir::failure();
1497 }
1498};
1499} // namespace
1500
1501LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) {
1502 assert(builder.getInsertionBlock());
1503 auto loc = convertLocation(stmt.sourceRange);
1504 return stmt.visit(StmtVisitor(*this, loc));
1505}
1506// NOLINTEND(misc-no-recursion)
1507
1508//===----------------------------------------------------------------------===//
1509// Monitor support
1510//===----------------------------------------------------------------------===//
1511
1513 // If globals already exist, nothing to do.
1515 return;
1516
1517 // Save current builder position and insert at the start of the module.
1518 OpBuilder::InsertionGuard guard(builder);
1519 builder.setInsertionPointToStart(intoModuleOp.getBody());
1520
1521 auto loc = intoModuleOp.getLoc();
1522 auto i32Type = moore::IntType::getInt(getContext(), 32);
1523 auto i1Type = moore::IntType::getInt(getContext(), 1);
1524
1525 // Create "active_id" global variable. Index 0 indicates no monitor
1526 // is active.
1527 monitorActiveIdGlobal = moore::GlobalVariableOp::create(
1528 builder, loc, "__monitor_active_id", i32Type);
1529 {
1530 OpBuilder::InsertionGuard initGuard(builder);
1531 builder.setInsertionPointToStart(
1532 &monitorActiveIdGlobal.getInitRegion().emplaceBlock());
1533 auto zero = moore::ConstantOp::create(builder, loc, i32Type, 0);
1534 moore::YieldOp::create(builder, loc, zero);
1535 }
1537
1538 // Create "enabled" global variable.
1539 monitorEnabledGlobal = moore::GlobalVariableOp::create(
1540 builder, loc, "__monitor_enabled", i1Type);
1541 {
1542 OpBuilder::InsertionGuard initGuard(builder);
1543 builder.setInsertionPointToStart(
1544 &monitorEnabledGlobal.getInitRegion().emplaceBlock());
1545 auto trueVal =
1546 moore::ConstantOp::create(builder, loc, moore::Domain::TwoValued, true);
1547 moore::YieldOp::create(builder, loc, trueVal);
1548 }
1550}
1551
1553 using ksn = slang::parsing::KnownSystemName;
1554 for (auto &pending : pendingMonitors) {
1555 auto &call = *pending.call;
1556 auto loc = pending.loc;
1557
1558 // Extract the SystemCallInfo from the call's subroutine variant.
1559 auto &info =
1560 std::get<slang::ast::CallExpression::SystemCallInfo>(call.subroutine);
1561 auto nameId = info.subroutine->knownNameId;
1562
1563 // Determine the default format based on the system call name.
1564 auto defaultFormat = moore::IntFormat::Decimal;
1565 switch (nameId) {
1566 case ksn::MonitorB:
1567 defaultFormat = moore::IntFormat::Binary;
1568 break;
1569 case ksn::MonitorO:
1570 defaultFormat = moore::IntFormat::Octal;
1571 break;
1572 case ksn::MonitorH:
1573 defaultFormat = moore::IntFormat::HexLower;
1574 break;
1575 default:
1576 break;
1577 }
1578
1579 // Create an always_comb procedure for this monitor. This will implement the
1580 // semantics of printing an updated message whenever one of the input
1581 // signals changes.
1582 auto alwaysProc = moore::ProcedureOp::create(
1583 builder, loc, moore::ProcedureKind::AlwaysComb);
1584 OpBuilder::InsertionGuard guard(builder);
1585 builder.setInsertionPointToStart(&alwaysProc.getBody().emplaceBlock());
1586
1587 // Convert the format string and arguments.
1588 auto message = convertFormatString(call.arguments(), loc, defaultFormat,
1589 /*appendNewline=*/true);
1590 if (failed(message))
1591 return failure();
1592
1593 // Check if this monitor is active and enabled.
1594 auto i32Type = moore::IntType::getInt(getContext(), 32);
1595 auto myId = moore::ConstantOp::create(builder, loc, i32Type, pending.id);
1596 Value isActive =
1597 moore::GetGlobalVariableOp::create(builder, loc, monitorActiveIdGlobal);
1598 isActive = moore::ReadOp::create(builder, loc, isActive);
1599 isActive = moore::EqOp::create(builder, loc, isActive, myId);
1600
1601 Value enabled =
1602 moore::GetGlobalVariableOp::create(builder, loc, monitorEnabledGlobal);
1603 enabled = moore::ReadOp::create(builder, loc, enabled);
1604 enabled = moore::AndOp::create(builder, loc, isActive, enabled);
1605 enabled = moore::ToBuiltinIntOp::create(builder, loc, enabled);
1606
1607 // Branch to a print or skip block based on whether the monitor is enabled
1608 // or not.
1609 auto &printBlock = alwaysProc.getBody().emplaceBlock();
1610 auto &skipBlock = alwaysProc.getBody().emplaceBlock();
1611 cf::CondBranchOp::create(builder, loc, enabled, &printBlock, &skipBlock);
1612
1613 // Display the formatted message if one was created, and the monitor is
1614 // enabled.
1615 builder.setInsertionPointToStart(&printBlock);
1616 if (*message)
1617 moore::DisplayBIOp::create(builder, loc, *message);
1618 moore::ReturnOp::create(builder, loc);
1619
1620 // Otherwise just return.
1621 builder.setInsertionPointToStart(&skipBlock);
1622 moore::ReturnOp::create(builder, loc);
1623 }
1624
1625 pendingMonitors.clear();
1626 return success();
1627}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static std::string buildPrintTimeScaleMessage(Context &context, std::span< const slang::ast::Expression *const > args)
Build the message printed by the $printtimescale system task.
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
@ TwoValued
Two-valued types such as bit or int.
@ f64
A 64-bit double-precision floation point number ("double")
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< PendingMonitor > pendingMonitors
Pending $monitor calls that need to be converted at module level.
LogicalResult flushPendingMonitors()
Process any pending $monitor calls and generate the monitoring procedures at module level.
OpBuilder builder
The builder used to create IR operations.
void ensureMonitorGlobals()
Ensure that the global variables for $monitor state exist.
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...
moore::GlobalVariableOp monitorActiveIdGlobal
Global variable ops for $monitor state management.
moore::GlobalVariableOp monitorEnabledGlobal
SymbolTable symbolTable
A symbol table of the MLIR module we are emitting into.
MLIRContext * getContext()
Return the MLIR context.
Location convertLocation(slang::SourceLocation loc)
Convert a slang SourceLocation into an MLIR Location.