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_if<slang::ast::StatementList>();
182 unsigned int threadCount = threadList ? threadList->list.size() : 1;
183
184 auto forkOp = moore::ForkJoinOp::create(builder, loc, kind, threadCount);
185 OpBuilder::InsertionGuard guard(builder);
186
187 // When only a single statement is present, Slang does not create a
188 // `StatementList`.
189 if (!threadList) {
190 auto &tBlock = forkOp->getRegion(0).emplaceBlock();
191 builder.setInsertionPointToStart(&tBlock);
192 if (failed(context.convertStatement(stmt.body)))
193 return failure();
194 moore::CompleteOp::create(builder, loc);
195 return success();
196 }
197
198 int i = 0;
199 for (auto *thread : threadList->list) {
200 auto &tBlock = forkOp->getRegion(i).emplaceBlock();
201 builder.setInsertionPointToStart(&tBlock);
202 // Populate thread operator with thread body and finish with a thread
203 // terminator.
204 if (failed(context.convertStatement(*thread)))
205 return failure();
206 moore::CompleteOp::create(builder, loc);
207 i++;
208 }
209 return success();
210 }
211
212 // Handle expression statements.
213 LogicalResult visit(const slang::ast::ExpressionStatement &stmt) {
214 // Special handling for calls to system tasks that return no result value.
215 if (const auto *call = stmt.expr.as_if<slang::ast::CallExpression>()) {
216 if (const auto *info =
217 std::get_if<slang::ast::CallExpression::SystemCallInfo>(
218 &call->subroutine)) {
219 auto handled = visitSystemCall(stmt, *call, *info);
220 if (failed(handled))
221 return failure();
222 if (handled == true)
223 return success();
224 }
225
226 // According to IEEE 1800-2023 Section 21.3.3 "Formatting data to a
227 // string" the first argument of $sformat/$swrite is its output; the
228 // other arguments work like a FormatString.
229 // In Moore we only support writing to a location if it is a reference;
230 // However, Section 21.3.3 explains that the output of $sformat/$swrite
231 // is assigned as if it were cast from a string literal (Section 5.9),
232 // so this implementation casts the string to the target value.
233 if (!call->getSubroutineName().compare("$sformat") ||
234 !call->getSubroutineName().compare("$swrite")) {
235
236 // Use the first argument as the output location
237 auto *lhsExpr = call->arguments().front();
238 // Format the second and all later arguments as a string
239 auto fmtValue =
240 context.convertFormatString(call->arguments().subspan(1), loc,
241 moore::IntFormat::Decimal, false);
242 if (failed(fmtValue))
243 return failure();
244 // Convert the FormatString to a StringType
245 auto strValue = moore::FormatStringToStringOp::create(builder, loc,
246 fmtValue.value());
247 // The Slang AST produces a `AssignmentExpression` for the first
248 // argument; the RHS of this expression is invalid though
249 // (`EmptyArgument`), so we only use the LHS of the
250 // `AssignmentExpression` and plug in the formatted string for the RHS.
251 if (auto assignExpr =
252 lhsExpr->as_if<slang::ast::AssignmentExpression>()) {
253 auto lhs = context.convertLvalueExpression(assignExpr->left());
254 if (!lhs)
255 return failure();
256
257 auto convertedValue = context.materializeConversion(
258 cast<moore::RefType>(lhs.getType()).getNestedType(), strValue,
259 false, loc);
260 moore::BlockingAssignOp::create(builder, loc, lhs, convertedValue);
261 return success();
262 } else {
263 return failure();
264 }
265 }
266 }
267
268 auto value = context.convertRvalueExpression(stmt.expr);
269 if (!value)
270 return failure();
271
272 // Expressions like calls to void functions return a dummy value that has no
273 // uses. If the returned value is trivially dead, remove it.
274 if (auto *defOp = value.getDefiningOp())
275 if (isOpTriviallyDead(defOp))
276 defOp->erase();
277
278 return success();
279 }
280
281 // Handle variable declarations.
282 LogicalResult visit(const slang::ast::VariableDeclStatement &stmt) {
283 const auto &var = stmt.symbol;
284 auto type = context.convertType(*var.getDeclaredType());
285 if (!type)
286 return failure();
287
288 Value initial;
289 if (const auto *init = var.getInitializer()) {
290 initial = context.convertRvalueExpression(*init, type);
291 if (!initial)
292 return failure();
293 }
294
295 // Collect local temporary variables.
296 auto varOp = moore::VariableOp::create(
297 builder, loc, moore::RefType::get(cast<moore::UnpackedType>(type)),
298 builder.getStringAttr(var.name), initial);
299 context.valueSymbols.insertIntoScope(context.valueSymbols.getCurScope(),
300 &var, varOp);
301 const auto &canonTy = var.getType().getCanonicalType();
302 if (const auto *vi = canonTy.as_if<slang::ast::VirtualInterfaceType>())
303 if (failed(context.registerVirtualInterfaceMembers(var, *vi, loc)))
304 return failure();
305 return success();
306 }
307
308 // Handle if statements.
309 LogicalResult visit(const slang::ast::ConditionalStatement &stmt) {
310 // Generate the condition. There may be multiple conditions linked with the
311 // `&&&` operator.
312 Value allConds;
313 for (const auto &condition : stmt.conditions) {
314 if (condition.pattern)
315 return mlir::emitError(loc,
316 "match patterns in if conditions not supported");
317 auto cond = context.convertRvalueExpression(*condition.expr);
318 if (!cond)
319 return failure();
320 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
321 if (allConds)
322 allConds = moore::AndOp::create(builder, loc, allConds, cond);
323 else
324 allConds = cond;
325 }
326 assert(allConds && "slang guarantees at least one condition");
327 if (auto ty = dyn_cast<moore::IntType>(allConds.getType());
328 ty && ty.getDomain() == Domain::FourValued) {
329 allConds = moore::LogicToIntOp::create(builder, loc, allConds);
330 }
331 allConds = moore::ToBuiltinIntOp::create(builder, loc, allConds);
332
333 // Create the blocks for the true and false branches, and the exit block.
334 Block &exitBlock = createBlock();
335 Block *falseBlock = stmt.ifFalse ? &createBlock() : nullptr;
336 Block &trueBlock = createBlock();
337 cf::CondBranchOp::create(builder, loc, allConds, &trueBlock,
338 falseBlock ? falseBlock : &exitBlock);
339
340 // Generate the true branch.
341 builder.setInsertionPointToEnd(&trueBlock);
342 if (failed(context.convertStatement(stmt.ifTrue)))
343 return failure();
344 if (!isTerminated())
345 cf::BranchOp::create(builder, loc, &exitBlock);
346
347 // Generate the false branch if present.
348 if (stmt.ifFalse) {
349 builder.setInsertionPointToEnd(falseBlock);
350 if (failed(context.convertStatement(*stmt.ifFalse)))
351 return failure();
352 if (!isTerminated())
353 cf::BranchOp::create(builder, loc, &exitBlock);
354 }
355
356 // If control never reaches the exit block, remove it and mark control flow
357 // as terminated. Otherwise we continue inserting ops in the exit block.
358 if (exitBlock.hasNoPredecessors()) {
359 exitBlock.erase();
360 setTerminated();
361 } else {
362 builder.setInsertionPointToEnd(&exitBlock);
363 }
364 return success();
365 }
366
367 /// Handle case statements.
368 LogicalResult visit(const slang::ast::CaseStatement &caseStmt) {
369 using slang::ast::AttributeSymbol;
370 using slang::ast::CaseStatementCondition;
371 auto caseExpr = context.convertRvalueExpression(caseStmt.expr);
372 if (!caseExpr)
373 return failure();
374
375 // Check each case individually. This currently ignores the `unique`,
376 // `unique0`, and `priority` modifiers which would allow for additional
377 // optimizations.
378 auto &exitBlock = createBlock();
379 Block *lastMatchBlock = nullptr;
380 SmallVector<moore::FVIntegerAttr> itemConsts;
381
382 for (const auto &item : caseStmt.items) {
383 // Create the block that will contain the main body of the expression.
384 // This is where any of the comparisons will branch to if they match.
385 auto &matchBlock = createBlock();
386 lastMatchBlock = &matchBlock;
387
388 // The SV standard requires expressions to be checked in the order
389 // specified by the user, and for the evaluation to stop as soon as the
390 // first matching expression is encountered.
391 for (const auto *expr : item.expressions) {
392 auto value = context.convertRvalueExpression(*expr);
393 if (!value)
394 return failure();
395 auto itemLoc = value.getLoc();
396
397 // Take note if the expression is a constant.
398 auto maybeConst = value;
399 while (isa_and_nonnull<moore::ConversionOp, moore::IntToLogicOp,
400 moore::LogicToIntOp>(maybeConst.getDefiningOp()))
401 maybeConst = maybeConst.getDefiningOp()->getOperand(0);
402 if (auto defOp = maybeConst.getDefiningOp<moore::ConstantOp>())
403 itemConsts.push_back(defOp.getValueAttr());
404
405 // Generate the appropriate equality operator.
406 Value cond;
407 switch (caseStmt.condition) {
408 case CaseStatementCondition::Normal:
409 cond = moore::CaseEqOp::create(builder, itemLoc, caseExpr, value);
410 break;
411 case CaseStatementCondition::WildcardXOrZ:
412 cond = moore::CaseXZEqOp::create(builder, itemLoc, caseExpr, value);
413 break;
414 case CaseStatementCondition::WildcardJustZ:
415 cond = moore::CaseZEqOp::create(builder, itemLoc, caseExpr, value);
416 break;
417 case CaseStatementCondition::Inside:
418 mlir::emitError(loc, "unsupported set membership case statement");
419 return failure();
420 }
421 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
422 ty && ty.getDomain() == Domain::FourValued) {
423 cond = moore::LogicToIntOp::create(builder, loc, cond);
424 }
425 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
426
427 // If the condition matches, branch to the match block. Otherwise
428 // continue checking the next expression in a new block.
429 auto &nextBlock = createBlock();
430 mlir::cf::CondBranchOp::create(builder, itemLoc, cond, &matchBlock,
431 &nextBlock);
432 builder.setInsertionPointToEnd(&nextBlock);
433 }
434
435 // The current block is the fall-through after all conditions have been
436 // checked and nothing matched. Move the match block up before this point
437 // to make the IR easier to read.
438 matchBlock.moveBefore(builder.getInsertionBlock());
439
440 // Generate the code for this item's statement in the match block.
441 OpBuilder::InsertionGuard guard(builder);
442 builder.setInsertionPointToEnd(&matchBlock);
443 if (failed(context.convertStatement(*item.stmt)))
444 return failure();
445 if (!isTerminated()) {
446 auto loc = context.convertLocation(item.stmt->sourceRange);
447 mlir::cf::BranchOp::create(builder, loc, &exitBlock);
448 }
449 }
450
451 const auto caseStmtAttrs = context.compilation.getAttributes(caseStmt);
452 const bool hasFullCaseAttr =
453 llvm::find_if(caseStmtAttrs, [](const AttributeSymbol *attr) {
454 return attr->name == "full_case";
455 }) != caseStmtAttrs.end();
456
457 // Check if the case statement looks exhaustive assuming two-state values.
458 // We use this information to work around a common bug in input Verilog
459 // where a case statement enumerates all possible two-state values of the
460 // case expression, but forgets to deal with cases involving X and Z bits in
461 // the input.
462 //
463 // Once the core dialects start supporting four-state values we may want to
464 // tuck this behind an import option that is on by default, since it does
465 // not preserve semantics.
466 auto twoStateExhaustive = false;
467 if (auto intType = dyn_cast<moore::IntType>(caseExpr.getType());
468 intType && intType.getWidth() < 32 &&
469 itemConsts.size() == (1 << intType.getWidth())) {
470 // Sort the constants by value.
471 llvm::sort(itemConsts, [](auto a, auto b) {
472 return a.getValue().getRawValue().ult(b.getValue().getRawValue());
473 });
474
475 // Ensure that every possible value of the case expression is present. Do
476 // this by starting at 0 and iterating over all sorted items. Each item
477 // must be the previous item + 1. At the end, the addition must exactly
478 // overflow and take us back to zero.
479 auto nextValue = FVInt::getZero(intType.getWidth());
480 for (auto value : itemConsts) {
481 if (value.getValue() != nextValue)
482 break;
483 nextValue += 1;
484 }
485 twoStateExhaustive = nextValue.isZero();
486 }
487
488 // If the case statement is exhaustive assuming two-state values, don't
489 // generate the default case. Instead, branch to the last match block. This
490 // will essentially make the last case item the "default".
491 //
492 // Alternatively, if the case statement has an (* full_case *) attribute
493 // but no default case, it indicates that the developer has intentionally
494 // covered all known possible values. Hence, the last match block is
495 // treated as the implicit "default" case.
496 if ((twoStateExhaustive || (hasFullCaseAttr && !caseStmt.defaultCase)) &&
497 lastMatchBlock &&
498 caseStmt.condition == CaseStatementCondition::Normal) {
499 mlir::cf::BranchOp::create(builder, loc, lastMatchBlock);
500 } else {
501 // Generate the default case if present.
502 if (caseStmt.defaultCase)
503 if (failed(context.convertStatement(*caseStmt.defaultCase)))
504 return failure();
505 if (!isTerminated())
506 mlir::cf::BranchOp::create(builder, loc, &exitBlock);
507 }
508
509 // If control never reaches the exit block, remove it and mark control flow
510 // as terminated. Otherwise we continue inserting ops in the exit block.
511 if (exitBlock.hasNoPredecessors()) {
512 exitBlock.erase();
513 setTerminated();
514 } else {
515 builder.setInsertionPointToEnd(&exitBlock);
516 }
517 return success();
518 }
519
520 // Handle `for` loops.
521 LogicalResult visit(const slang::ast::ForLoopStatement &stmt) {
522 // Generate the initializers.
523 for (auto *initExpr : stmt.initializers)
524 if (!context.convertRvalueExpression(*initExpr))
525 return failure();
526
527 // Create the blocks for the loop condition, body, step, and exit.
528 auto &exitBlock = createBlock();
529 auto &stepBlock = createBlock();
530 auto &bodyBlock = createBlock();
531 auto &checkBlock = createBlock();
532 cf::BranchOp::create(builder, loc, &checkBlock);
533
534 // Push the blocks onto the loop stack such that we can continue and break.
535 context.loopStack.push_back({&stepBlock, &exitBlock});
536 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
537
538 // Generate the loop condition check.
539 builder.setInsertionPointToEnd(&checkBlock);
540 auto cond = context.convertRvalueExpression(*stmt.stopExpr);
541 if (!cond)
542 return failure();
543 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
544 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
545 ty && ty.getDomain() == Domain::FourValued) {
546 cond = moore::LogicToIntOp::create(builder, loc, cond);
547 }
548 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
549 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
550
551 // Generate the loop body.
552 builder.setInsertionPointToEnd(&bodyBlock);
553 if (failed(context.convertStatement(stmt.body)))
554 return failure();
555 if (!isTerminated())
556 cf::BranchOp::create(builder, loc, &stepBlock);
557
558 // Generate the step expressions.
559 builder.setInsertionPointToEnd(&stepBlock);
560 for (auto *stepExpr : stmt.steps)
561 if (!context.convertRvalueExpression(*stepExpr))
562 return failure();
563 if (!isTerminated())
564 cf::BranchOp::create(builder, loc, &checkBlock);
565
566 // If control never reaches the exit block, remove it and mark control flow
567 // as terminated. Otherwise we continue inserting ops in the exit block.
568 if (exitBlock.hasNoPredecessors()) {
569 exitBlock.erase();
570 setTerminated();
571 } else {
572 builder.setInsertionPointToEnd(&exitBlock);
573 }
574 return success();
575 }
576
577 LogicalResult visit(const slang::ast::ForeachLoopStatement &stmt) {
578 for (uint32_t level = 0; level < stmt.loopDims.size(); level++) {
579 if (stmt.loopDims[level].loopVar)
580 return recursiveForeach(stmt, level);
581 }
582 return success();
583 }
584
585 // Handle `repeat` loops.
586 LogicalResult visit(const slang::ast::RepeatLoopStatement &stmt) {
587 auto intType = moore::IntType::getInt(context.getContext(), 32);
588 auto count = context.convertRvalueExpression(stmt.count, intType);
589 if (!count)
590 return failure();
591
592 // Create the blocks for the loop condition, body, step, and exit.
593 auto &exitBlock = createBlock();
594 auto &stepBlock = createBlock();
595 auto &bodyBlock = createBlock();
596 auto &checkBlock = createBlock();
597 auto currentCount = checkBlock.addArgument(count.getType(), count.getLoc());
598 cf::BranchOp::create(builder, loc, &checkBlock, count);
599
600 // Push the blocks onto the loop stack such that we can continue and break.
601 context.loopStack.push_back({&stepBlock, &exitBlock});
602 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
603
604 // Generate the loop condition check.
605 builder.setInsertionPointToEnd(&checkBlock);
606 auto cond = builder.createOrFold<moore::BoolCastOp>(loc, currentCount);
607 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
608 ty && ty.getDomain() == Domain::FourValued) {
609 cond = moore::LogicToIntOp::create(builder, loc, cond);
610 }
611 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
612 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
613
614 // Generate the loop body.
615 builder.setInsertionPointToEnd(&bodyBlock);
616 if (failed(context.convertStatement(stmt.body)))
617 return failure();
618 if (!isTerminated())
619 cf::BranchOp::create(builder, loc, &stepBlock);
620
621 // Decrement the current count and branch back to the check block.
622 builder.setInsertionPointToEnd(&stepBlock);
623 auto one = moore::ConstantOp::create(
624 builder, count.getLoc(), cast<moore::IntType>(count.getType()), 1);
625 Value nextCount =
626 moore::SubOp::create(builder, count.getLoc(), currentCount, one);
627 cf::BranchOp::create(builder, loc, &checkBlock, nextCount);
628
629 // If control never reaches the exit block, remove it and mark control flow
630 // as terminated. Otherwise we continue inserting ops in the exit block.
631 if (exitBlock.hasNoPredecessors()) {
632 exitBlock.erase();
633 setTerminated();
634 } else {
635 builder.setInsertionPointToEnd(&exitBlock);
636 }
637 return success();
638 }
639
640 // Handle `while` and `do-while` loops.
641 LogicalResult createWhileLoop(const slang::ast::Expression &condExpr,
642 const slang::ast::Statement &bodyStmt,
643 bool atLeastOnce) {
644 // Create the blocks for the loop condition, body, and exit.
645 auto &exitBlock = createBlock();
646 auto &bodyBlock = createBlock();
647 auto &checkBlock = createBlock();
648 cf::BranchOp::create(builder, loc, atLeastOnce ? &bodyBlock : &checkBlock);
649 if (atLeastOnce)
650 bodyBlock.moveBefore(&checkBlock);
651
652 // Push the blocks onto the loop stack such that we can continue and break.
653 context.loopStack.push_back({&checkBlock, &exitBlock});
654 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
655
656 // Generate the loop condition check.
657 builder.setInsertionPointToEnd(&checkBlock);
658 auto cond = context.convertRvalueExpression(condExpr);
659 if (!cond)
660 return failure();
661 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
662 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
663 ty && ty.getDomain() == Domain::FourValued) {
664 cond = moore::LogicToIntOp::create(builder, loc, cond);
665 }
666 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
667 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
668
669 // Generate the loop body.
670 builder.setInsertionPointToEnd(&bodyBlock);
671 if (failed(context.convertStatement(bodyStmt)))
672 return failure();
673 if (!isTerminated())
674 cf::BranchOp::create(builder, loc, &checkBlock);
675
676 // If control never reaches the exit block, remove it and mark control flow
677 // as terminated. Otherwise we continue inserting ops in the exit block.
678 if (exitBlock.hasNoPredecessors()) {
679 exitBlock.erase();
680 setTerminated();
681 } else {
682 builder.setInsertionPointToEnd(&exitBlock);
683 }
684 return success();
685 }
686
687 LogicalResult visit(const slang::ast::WhileLoopStatement &stmt) {
688 return createWhileLoop(stmt.cond, stmt.body, false);
689 }
690
691 LogicalResult visit(const slang::ast::DoWhileLoopStatement &stmt) {
692 return createWhileLoop(stmt.cond, stmt.body, true);
693 }
694
695 // Handle `forever` loops.
696 LogicalResult visit(const slang::ast::ForeverLoopStatement &stmt) {
697 // Create the blocks for the loop body and exit.
698 auto &exitBlock = createBlock();
699 auto &bodyBlock = createBlock();
700 cf::BranchOp::create(builder, loc, &bodyBlock);
701
702 // Push the blocks onto the loop stack such that we can continue and break.
703 context.loopStack.push_back({&bodyBlock, &exitBlock});
704 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
705
706 // Generate the loop body.
707 builder.setInsertionPointToEnd(&bodyBlock);
708 if (failed(context.convertStatement(stmt.body)))
709 return failure();
710 if (!isTerminated())
711 cf::BranchOp::create(builder, loc, &bodyBlock);
712
713 // If control never reaches the exit block, remove it and mark control flow
714 // as terminated. Otherwise we continue inserting ops in the exit block.
715 if (exitBlock.hasNoPredecessors()) {
716 exitBlock.erase();
717 setTerminated();
718 } else {
719 builder.setInsertionPointToEnd(&exitBlock);
720 }
721 return success();
722 }
723
724 // Handle timing control.
725 LogicalResult visit(const slang::ast::TimedStatement &stmt) {
726 return context.convertTimingControl(stmt.timing, stmt.stmt);
727 }
728
729 // Handle return statements.
730 LogicalResult visit(const slang::ast::ReturnStatement &stmt) {
731 if (stmt.expr) {
732 auto expr = context.convertRvalueExpression(*stmt.expr);
733 if (!expr)
734 return failure();
735 mlir::func::ReturnOp::create(builder, loc, expr);
736 } else {
737 mlir::func::ReturnOp::create(builder, loc);
738 }
739 setTerminated();
740 return success();
741 }
742
743 // Handle continue statements.
744 LogicalResult visit(const slang::ast::ContinueStatement &stmt) {
745 if (context.loopStack.empty())
746 return mlir::emitError(loc,
747 "cannot `continue` without a surrounding loop");
748 cf::BranchOp::create(builder, loc, context.loopStack.back().continueBlock);
749 setTerminated();
750 return success();
751 }
752
753 // Handle break statements.
754 LogicalResult visit(const slang::ast::BreakStatement &stmt) {
755 if (context.loopStack.empty())
756 return mlir::emitError(loc, "cannot `break` without a surrounding loop");
757 cf::BranchOp::create(builder, loc, context.loopStack.back().breakBlock);
758 setTerminated();
759 return success();
760 }
761
762 // Handle immediate assertion statements.
763 LogicalResult visit(const slang::ast::ImmediateAssertionStatement &stmt) {
764 auto cond = context.convertRvalueExpression(stmt.cond);
765 cond = context.convertToBool(cond);
766 if (!cond)
767 return failure();
768
769 // Handle assertion statements that don't have an action block.
770 if (stmt.ifTrue && stmt.ifTrue->as_if<slang::ast::EmptyStatement>()) {
771 auto defer = moore::DeferAssert::Immediate;
772 if (stmt.isFinal)
773 defer = moore::DeferAssert::Final;
774 else if (stmt.isDeferred)
775 defer = moore::DeferAssert::Observed;
776
777 switch (stmt.assertionKind) {
778 case slang::ast::AssertionKind::Assert:
779 moore::AssertOp::create(builder, loc, defer, cond, StringAttr{});
780 return success();
781 case slang::ast::AssertionKind::Assume:
782 moore::AssumeOp::create(builder, loc, defer, cond, StringAttr{});
783 return success();
784 case slang::ast::AssertionKind::CoverProperty:
785 moore::CoverOp::create(builder, loc, defer, cond, StringAttr{});
786 return success();
787 default:
788 break;
789 }
790 mlir::emitError(loc) << "unsupported immediate assertion kind: "
791 << slang::ast::toString(stmt.assertionKind);
792 return failure();
793 }
794
795 // Regard assertion statements with an action block as the "if-else".
796 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
797 ty && ty.getDomain() == Domain::FourValued) {
798 cond = moore::LogicToIntOp::create(builder, loc, cond);
799 }
800 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
801
802 // Create the blocks for the true and false branches, and the exit block.
803 Block &exitBlock = createBlock();
804 Block *falseBlock = stmt.ifFalse ? &createBlock() : nullptr;
805 Block &trueBlock = createBlock();
806 cf::CondBranchOp::create(builder, loc, cond, &trueBlock,
807 falseBlock ? falseBlock : &exitBlock);
808
809 // Generate the true branch.
810 builder.setInsertionPointToEnd(&trueBlock);
811 if (stmt.ifTrue && failed(context.convertStatement(*stmt.ifTrue)))
812 return failure();
813 if (!isTerminated())
814 cf::BranchOp::create(builder, loc, &exitBlock);
815
816 if (stmt.ifFalse) {
817 // Generate the false branch if present.
818 builder.setInsertionPointToEnd(falseBlock);
819 if (failed(context.convertStatement(*stmt.ifFalse)))
820 return failure();
821 if (!isTerminated())
822 cf::BranchOp::create(builder, loc, &exitBlock);
823 }
824
825 // If control never reaches the exit block, remove it and mark control flow
826 // as terminated. Otherwise we continue inserting ops in the exit block.
827 if (exitBlock.hasNoPredecessors()) {
828 exitBlock.erase();
829 setTerminated();
830 } else {
831 builder.setInsertionPointToEnd(&exitBlock);
832 }
833 return success();
834 }
835
836 // Handle concurrent assertion statements.
837 LogicalResult visit(const slang::ast::ConcurrentAssertionStatement &stmt) {
838 auto loc = context.convertLocation(stmt.sourceRange);
839
840 // Check for a `disable iff` expression:
841 // `disable iff` can only appear at the outermost property that is asserted,
842 // and can never be nested.
843 // Hence we only need to detect if the top level assertion expression has
844 // type DisableIff. (or, if the top level expression is
845 // ClockingAssertionExpr, check for DisableIff inside that).
846 Value enable;
847 Value property;
848 // Find the outermost propertySpec that isn't ClockingAssertionExpr
849 const slang::ast::AssertionExpr *propertySpec;
850 const slang::ast::ClockingAssertionExpr *clocking =
851 stmt.propertySpec.as_if<slang::ast::ClockingAssertionExpr>();
852 if (clocking)
853 propertySpec = &(clocking->expr);
854 else
855 propertySpec = &(stmt.propertySpec);
856
857 if (auto *disableIff =
858 propertySpec->as_if<slang::ast::DisableIffAssertionExpr>()) {
859 // Lower disableIff by negating it and passing as the "enable" operand
860 // to the verif.assert/verif.assume instructions.
861 auto disableCond = context.convertRvalueExpression(disableIff->condition);
862 auto enableCond = moore::NotOp::create(builder, loc, disableCond);
863
864 enable = context.convertToI1(enableCond);
865
866 // Add back the outer `ClockingAssertionExpr` if there is one.
867 if (clocking) {
868 auto clockingExpr = slang::ast::ClockingAssertionExpr(
869 clocking->clocking, disableIff->expr);
870 property = context.convertAssertionExpression(clockingExpr, loc);
871 } else {
872 property = context.convertAssertionExpression(disableIff->expr, loc);
873 }
874 } else {
875 property = context.convertAssertionExpression(stmt.propertySpec, loc);
876 }
877
878 if (!property)
879 return failure();
880
881 // Handle assertion statements that don't have an action block.
882 if (stmt.ifTrue && stmt.ifTrue->as_if<slang::ast::EmptyStatement>()) {
883 switch (stmt.assertionKind) {
884 case slang::ast::AssertionKind::Assert:
885 verif::AssertOp::create(builder, loc, property, enable, StringAttr{});
886 return success();
887 case slang::ast::AssertionKind::Assume:
888 verif::AssumeOp::create(builder, loc, property, enable, StringAttr{});
889 return success();
890 default:
891 break;
892 }
893 mlir::emitError(loc) << "unsupported concurrent assertion kind: "
894 << slang::ast::toString(stmt.assertionKind);
895 return failure();
896 }
897
898 mlir::emitError(loc)
899 << "concurrent assertion statements with action blocks "
900 "are not supported yet";
901 return failure();
902 }
903
904 // According to 1800-2023 Section 21.2.1 "The display and write tasks":
905 // >> The $display and $write tasks display their arguments in the same
906 // >> order as they appear in the argument list. Each argument can be a
907 // >> string literal or an expression that returns a value.
908 // According to Section 20.10 "Severity system tasks", the same
909 // semantics apply to $fatal, $error, $warning, and $info.
910 // This means we must first check whether the first "string-able"
911 // argument is a Literal Expression which doesn't represent a fully-formatted
912 // string, otherwise we convert it to a FormatStringType.
913 FailureOr<Value>
914 getDisplayMessage(std::span<const slang::ast::Expression *const> args) {
915 if (args.size() == 0)
916 return Value{};
917
918 // Handle the string formatting.
919 // If the second argument is a Literal of some type, we should either
920 // treat it as a literal-to-be-formatted or a FormatStringType.
921 // In this check we use a StringLiteral, but slang allows casting between
922 // any literal expressions (strings, integers, reals, and time at least) so
923 // this is short-hand for "any value literal"
924 if (args[0]->as_if<slang::ast::StringLiteral>()) {
925 return context.convertFormatString(args, loc);
926 }
927 // Check if there's only one argument and it's a FormatStringType
928 if (args.size() == 1) {
929 return context.convertRvalueExpression(
930 *args[0], builder.getType<moore::FormatStringType>());
931 }
932 // Otherwise this looks invalid. Raise an error.
933 return emitError(loc) << "Failed to convert Display Message!";
934 }
935
936 /// Handle the subset of system calls that return no result value. Return
937 /// true if the called system task could be handled, false otherwise. Return
938 /// failure if an error occurred.
939 FailureOr<bool>
940 visitSystemCall(const slang::ast::ExpressionStatement &stmt,
941 const slang::ast::CallExpression &expr,
942 const slang::ast::CallExpression::SystemCallInfo &info) {
943 using ksn = slang::parsing::KnownSystemName;
944 const auto &subroutine = *info.subroutine;
945 auto nameId = subroutine.knownNameId;
946 auto args = expr.arguments();
947
948 // Simulation Control Tasks
949
950 if (nameId == ksn::Stop) {
951 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
952 moore::StopBIOp::create(builder, loc);
953 return true;
954 }
955
956 if (nameId == ksn::Finish) {
957 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
958 moore::FinishBIOp::create(builder, loc, 0);
959 moore::UnreachableOp::create(builder, loc);
960 setTerminated();
961 return true;
962 }
963
964 if (nameId == ksn::Exit) {
965 // Calls to `$exit` from outside a `program` are ignored. Since we don't
966 // yet support programs, there is nothing to do here.
967 // TODO: Fix this once we support programs.
968 return true;
969 }
970
971 // Display and Write Tasks (`$display[boh]?` or `$write[boh]?`)
972
973 using moore::IntFormat;
974 bool isDisplay = false;
975 bool appendNewline = false;
976 IntFormat defaultFormat = IntFormat::Decimal;
977 switch (nameId) {
978 case ksn::Display:
979 isDisplay = true;
980 appendNewline = true;
981 break;
982 case ksn::DisplayB:
983 isDisplay = true;
984 appendNewline = true;
985 defaultFormat = IntFormat::Binary;
986 break;
987 case ksn::DisplayO:
988 isDisplay = true;
989 appendNewline = true;
990 defaultFormat = IntFormat::Octal;
991 break;
992 case ksn::DisplayH:
993 isDisplay = true;
994 appendNewline = true;
995 defaultFormat = IntFormat::HexLower;
996 break;
997 case ksn::Write:
998 isDisplay = true;
999 break;
1000 case ksn::WriteB:
1001 isDisplay = true;
1002 defaultFormat = IntFormat::Binary;
1003 break;
1004 case ksn::WriteO:
1005 isDisplay = true;
1006 defaultFormat = IntFormat::Octal;
1007 break;
1008 case ksn::WriteH:
1009 isDisplay = true;
1010 defaultFormat = IntFormat::HexLower;
1011 break;
1012 default:
1013 break;
1014 }
1015
1016 if (isDisplay) {
1017 auto message =
1018 context.convertFormatString(args, loc, defaultFormat, appendNewline);
1019 if (failed(message))
1020 return failure();
1021 if (*message == Value{})
1022 return true;
1023 moore::DisplayBIOp::create(builder, loc, *message);
1024 return true;
1025 }
1026
1027 // Severity Tasks
1028 using moore::Severity;
1029 std::optional<Severity> severity;
1030 if (nameId == ksn::Info)
1031 severity = Severity::Info;
1032 else if (nameId == ksn::Warning)
1033 severity = Severity::Warning;
1034 else if (nameId == ksn::Error)
1035 severity = Severity::Error;
1036 else if (nameId == ksn::Fatal)
1037 severity = Severity::Fatal;
1038
1039 if (severity) {
1040 // The `$fatal` task has an optional leading verbosity argument.
1041 const slang::ast::Expression *verbosityExpr = nullptr;
1042 if (severity == Severity::Fatal && args.size() >= 1) {
1043 verbosityExpr = args[0];
1044 args = args.subspan(1);
1045 }
1046
1047 FailureOr<Value> maybeMessage = getDisplayMessage(args);
1048 if (failed(maybeMessage))
1049 return failure();
1050 auto message = maybeMessage.value();
1051
1052 if (message == Value{})
1053 message = moore::FormatLiteralOp::create(builder, loc, "");
1054 moore::SeverityBIOp::create(builder, loc, *severity, message);
1055
1056 // Handle the `$fatal` case which behaves like a `$finish`.
1057 if (severity == Severity::Fatal) {
1058 createFinishMessage(verbosityExpr);
1059 moore::FinishBIOp::create(builder, loc, 1);
1060 moore::UnreachableOp::create(builder, loc);
1061 setTerminated();
1062 }
1063 return true;
1064 }
1065
1066 // Queue Tasks
1067
1068 if (args.size() >= 1 && args[0]->type->isQueue()) {
1069 auto queue = context.convertLvalueExpression(*args[0]);
1070
1071 // `delete` has two functions: If there is an index passed, then it
1072 // deletes that specific element, otherwise, it clears the entire queue.
1073 if (nameId == ksn::Delete) {
1074 if (args.size() == 1) {
1075 moore::QueueClearOp::create(builder, loc, queue);
1076 return true;
1077 }
1078 if (args.size() == 2) {
1079 auto index = context.convertRvalueExpression(*args[1]);
1080 moore::QueueDeleteOp::create(builder, loc, queue, index);
1081 return true;
1082 }
1083 } else if (nameId == ksn::Insert && args.size() == 3) {
1084 auto index = context.convertRvalueExpression(*args[1]);
1085 auto item = context.convertRvalueExpression(*args[2]);
1086
1087 moore::QueueInsertOp::create(builder, loc, queue, index, item);
1088 return true;
1089 } else if (nameId == ksn::PushBack && args.size() == 2) {
1090 auto item = context.convertRvalueExpression(*args[1]);
1091 moore::QueuePushBackOp::create(builder, loc, queue, item);
1092 return true;
1093 } else if (nameId == ksn::PushFront && args.size() == 2) {
1094 auto item = context.convertRvalueExpression(*args[1]);
1095 moore::QueuePushFrontOp::create(builder, loc, queue, item);
1096 return true;
1097 }
1098
1099 return false;
1100 }
1101
1102 // Associative array tasks
1103 if (args.size() >= 1 && args[0]->type->isAssociativeArray()) {
1104 auto assocArray = context.convertLvalueExpression(*args[0]);
1105
1106 // `delete` has two functions: If there is an index passed, then it
1107 // deletes that specific element, otherwise, it clears the entire
1108 // associative array.
1109 if (nameId == ksn::Delete) {
1110 if (args.size() == 1) {
1111 moore::AssocArrayClearOp::create(builder, loc, assocArray);
1112 return true;
1113 }
1114 if (args.size() == 2) {
1115 auto index = context.convertRvalueExpression(*args[1]);
1116 moore::AssocArrayDeleteOp::create(builder, loc, assocArray, index);
1117 return true;
1118 }
1119 }
1120 }
1121
1122 // Monitor enable/disable tasks (`$monitoron`, `$monitoroff`)
1123 if (nameId == ksn::MonitorOn || nameId == ksn::MonitorOff) {
1124 context.ensureMonitorGlobals();
1125 bool enable = (nameId == ksn::MonitorOn);
1126 auto enabledRef = moore::GetGlobalVariableOp::create(
1127 context.builder, loc, context.monitorEnabledGlobal);
1128 auto value = moore::ConstantOp::create(context.builder, loc,
1129 moore::Domain::TwoValued, enable);
1130 moore::BlockingAssignOp::create(context.builder, loc, enabledRef, value);
1131 return true;
1132 }
1133
1134 // Monitor tasks (`$monitor[boh]?`)
1135 if (nameId == ksn::Monitor || nameId == ksn::MonitorB ||
1136 nameId == ksn::MonitorO || nameId == ksn::MonitorH) {
1137 context.ensureMonitorGlobals();
1138
1139 // Allocate a unique ID for this monitor.
1140 unsigned myId = context.nextMonitorId++;
1141
1142 // Emit code to activate this monitor by setting the active_id global.
1143 auto i32Type = moore::IntType::getInt(context.getContext(), 32);
1144 auto idConst =
1145 moore::ConstantOp::create(context.builder, loc, i32Type, myId);
1146 auto activeRef = moore::GetGlobalVariableOp::create(
1147 context.builder, loc, context.monitorActiveIdGlobal);
1148 moore::BlockingAssignOp::create(context.builder, loc, activeRef, idConst);
1149
1150 // Queue this monitor for processing at module level.
1151 context.pendingMonitors.push_back({myId, loc, &expr});
1152
1153 return true;
1154 }
1155
1156 // Give up on any other system tasks. These will be tried again as an
1157 // expression later.
1158 return false;
1159 }
1160
1161 /// Create the optional diagnostic message print for finish-like ops.
1162 void createFinishMessage(const slang::ast::Expression *verbosityExpr) {
1163 unsigned verbosity = 1;
1164 if (verbosityExpr) {
1165 auto value =
1166 context.evaluateConstant(*verbosityExpr).integer().as<unsigned>();
1167 assert(value && "Slang guarantees constant verbosity parameter");
1168 verbosity = *value;
1169 }
1170 if (verbosity == 0)
1171 return;
1172 moore::FinishMessageBIOp::create(builder, loc, verbosity > 1);
1173 }
1174
1175 // Handle event trigger statements.
1176 LogicalResult visit(const slang::ast::EventTriggerStatement &stmt) {
1177 if (stmt.timing) {
1178 mlir::emitError(loc) << "unsupported delayed event trigger";
1179 return failure();
1180 }
1181
1182 // Events are lowered to `i1` signals. Get an lvalue ref to the signal such
1183 // that we can assign to it.
1184 auto target = context.convertLvalueExpression(stmt.target);
1185 if (!target)
1186 return failure();
1187
1188 // Read and invert the current value of the signal. Writing this inverted
1189 // value to the signal is our event signaling mechanism.
1190 Value inverted = moore::ReadOp::create(builder, loc, target);
1191 inverted = moore::NotOp::create(builder, loc, inverted);
1192
1193 if (stmt.isNonBlocking)
1194 moore::NonBlockingAssignOp::create(builder, loc, target, inverted);
1195 else
1196 moore::BlockingAssignOp::create(builder, loc, target, inverted);
1197 return success();
1198 }
1199
1200 // Handle `wait` statements
1201 LogicalResult visit(const slang::ast::WaitStatement &stmt) {
1202 auto waitOp = moore::WaitLevelOp::create(builder, loc);
1203 {
1204 OpBuilder::InsertionGuard guard(builder);
1205 builder.setInsertionPointToStart(&waitOp.getBody().emplaceBlock());
1206 auto cond = context.convertRvalueExpression(stmt.cond);
1207 if (!cond)
1208 return failure();
1209 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
1210 moore::DetectLevelOp::create(builder, loc, cond);
1211 }
1212 // Handle optional post-wait operation as if it were a separate statement
1213 if (failed(context.convertStatement(stmt.stmt)))
1214 return failure();
1215
1216 return success();
1217 }
1218
1219 LogicalResult visit(const slang::ast::WaitForkStatement &stmt) {
1220 moore::WaitForkOp::create(builder, loc);
1221 return success();
1222 }
1223
1224 /// Emit an error for all other statements.
1225 template <typename T>
1226 LogicalResult visit(T &&stmt) {
1227 mlir::emitError(loc, "unsupported statement: ")
1228 << slang::ast::toString(stmt.kind);
1229 return mlir::failure();
1230 }
1231
1232 LogicalResult visitInvalid(const slang::ast::Statement &stmt) {
1233 mlir::emitError(loc, "invalid statement: ")
1234 << slang::ast::toString(stmt.kind);
1235 return mlir::failure();
1236 }
1237};
1238} // namespace
1239
1240LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) {
1241 assert(builder.getInsertionBlock());
1242 auto loc = convertLocation(stmt.sourceRange);
1243 return stmt.visit(StmtVisitor(*this, loc));
1244}
1245// NOLINTEND(misc-no-recursion)
1246
1247//===----------------------------------------------------------------------===//
1248// Monitor support
1249//===----------------------------------------------------------------------===//
1250
1252 // If globals already exist, nothing to do.
1254 return;
1255
1256 // Save current builder position and insert at the start of the module.
1257 OpBuilder::InsertionGuard guard(builder);
1258 builder.setInsertionPointToStart(intoModuleOp.getBody());
1259
1260 auto loc = intoModuleOp.getLoc();
1261 auto i32Type = moore::IntType::getInt(getContext(), 32);
1262 auto i1Type = moore::IntType::getInt(getContext(), 1);
1263
1264 // Create "active_id" global variable. Index 0 indicates no monitor
1265 // is active.
1266 monitorActiveIdGlobal = moore::GlobalVariableOp::create(
1267 builder, loc, "__monitor_active_id", i32Type);
1268 {
1269 OpBuilder::InsertionGuard initGuard(builder);
1270 builder.setInsertionPointToStart(
1271 &monitorActiveIdGlobal.getInitRegion().emplaceBlock());
1272 auto zero = moore::ConstantOp::create(builder, loc, i32Type, 0);
1273 moore::YieldOp::create(builder, loc, zero);
1274 }
1276
1277 // Create "enabled" global variable.
1278 monitorEnabledGlobal = moore::GlobalVariableOp::create(
1279 builder, loc, "__monitor_enabled", i1Type);
1280 {
1281 OpBuilder::InsertionGuard initGuard(builder);
1282 builder.setInsertionPointToStart(
1283 &monitorEnabledGlobal.getInitRegion().emplaceBlock());
1284 auto trueVal =
1285 moore::ConstantOp::create(builder, loc, moore::Domain::TwoValued, true);
1286 moore::YieldOp::create(builder, loc, trueVal);
1287 }
1289}
1290
1292 using ksn = slang::parsing::KnownSystemName;
1293 for (auto &pending : pendingMonitors) {
1294 auto &call = *pending.call;
1295 auto loc = pending.loc;
1296
1297 // Extract the SystemCallInfo from the call's subroutine variant.
1298 auto &info =
1299 std::get<slang::ast::CallExpression::SystemCallInfo>(call.subroutine);
1300 auto nameId = info.subroutine->knownNameId;
1301
1302 // Determine the default format based on the system call name.
1303 auto defaultFormat = moore::IntFormat::Decimal;
1304 switch (nameId) {
1305 case ksn::MonitorB:
1306 defaultFormat = moore::IntFormat::Binary;
1307 break;
1308 case ksn::MonitorO:
1309 defaultFormat = moore::IntFormat::Octal;
1310 break;
1311 case ksn::MonitorH:
1312 defaultFormat = moore::IntFormat::HexLower;
1313 break;
1314 default:
1315 break;
1316 }
1317
1318 // Create an always_comb procedure for this monitor. This will implement the
1319 // semantics of printing an updated message whenever one of the input
1320 // signals changes.
1321 auto alwaysProc = moore::ProcedureOp::create(
1322 builder, loc, moore::ProcedureKind::AlwaysComb);
1323 OpBuilder::InsertionGuard guard(builder);
1324 builder.setInsertionPointToStart(&alwaysProc.getBody().emplaceBlock());
1325
1326 // Convert the format string and arguments.
1327 auto message = convertFormatString(call.arguments(), loc, defaultFormat,
1328 /*appendNewline=*/true);
1329 if (failed(message))
1330 return failure();
1331
1332 // Check if this monitor is active and enabled.
1333 auto i32Type = moore::IntType::getInt(getContext(), 32);
1334 auto myId = moore::ConstantOp::create(builder, loc, i32Type, pending.id);
1335 Value isActive =
1336 moore::GetGlobalVariableOp::create(builder, loc, monitorActiveIdGlobal);
1337 isActive = moore::ReadOp::create(builder, loc, isActive);
1338 isActive = moore::EqOp::create(builder, loc, isActive, myId);
1339
1340 Value enabled =
1341 moore::GetGlobalVariableOp::create(builder, loc, monitorEnabledGlobal);
1342 enabled = moore::ReadOp::create(builder, loc, enabled);
1343 enabled = moore::AndOp::create(builder, loc, isActive, enabled);
1344 enabled = moore::ToBuiltinIntOp::create(builder, loc, enabled);
1345
1346 // Branch to a print or skip block based on whether the monitor is enabled
1347 // or not.
1348 auto &printBlock = alwaysProc.getBody().emplaceBlock();
1349 auto &skipBlock = alwaysProc.getBody().emplaceBlock();
1350 cf::CondBranchOp::create(builder, loc, enabled, &printBlock, &skipBlock);
1351
1352 // Display the formatted message if one was created, and the monitor is
1353 // enabled.
1354 builder.setInsertionPointToStart(&printBlock);
1355 if (*message)
1356 moore::DisplayBIOp::create(builder, loc, *message);
1357 moore::ReturnOp::create(builder, loc);
1358
1359 // Otherwise just return.
1360 builder.setInsertionPointToStart(&skipBlock);
1361 moore::ReturnOp::create(builder, loc);
1362 }
1363
1364 pendingMonitors.clear();
1365 return success();
1366}
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
@ TwoValued
Two-valued types such as bit or int.
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.