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