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 // The `$cast` system call is handled by `Context::convertSystemCall` in the
979 // `Expressions.cpp` file. Skip it is order to avoid visiting the
980 // `EmptyArgument` node.
981 if (nameId == ksn::Cast) {
982 return false;
983 }
984
985 // Simulation Control Tasks
986
987 if (nameId == ksn::Stop) {
988 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
989 moore::StopBIOp::create(builder, loc);
990 return true;
991 }
992
993 if (nameId == ksn::Finish) {
994 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
995 moore::FinishBIOp::create(builder, loc, 0);
996 moore::UnreachableOp::create(builder, loc);
997 setTerminated();
998 return true;
999 }
1000
1001 if (nameId == ksn::Exit) {
1002 // Calls to `$exit` from outside a `program` are ignored. Since we don't
1003 // yet support programs, there is nothing to do here.
1004 // TODO: Fix this once we support programs.
1005 return true;
1006 }
1007
1008 // Display and Write Tasks (`$display[boh]?` or `$write[boh]?`)
1009
1010 using moore::IntFormat;
1011 bool isDisplay = false;
1012 bool appendNewline = false;
1013 IntFormat defaultFormat = IntFormat::Decimal;
1014 switch (nameId) {
1015 case ksn::Display:
1016 isDisplay = true;
1017 appendNewline = true;
1018 break;
1019 case ksn::DisplayB:
1020 isDisplay = true;
1021 appendNewline = true;
1022 defaultFormat = IntFormat::Binary;
1023 break;
1024 case ksn::DisplayO:
1025 isDisplay = true;
1026 appendNewline = true;
1027 defaultFormat = IntFormat::Octal;
1028 break;
1029 case ksn::DisplayH:
1030 isDisplay = true;
1031 appendNewline = true;
1032 defaultFormat = IntFormat::HexLower;
1033 break;
1034 case ksn::Write:
1035 isDisplay = true;
1036 break;
1037 case ksn::WriteB:
1038 isDisplay = true;
1039 defaultFormat = IntFormat::Binary;
1040 break;
1041 case ksn::WriteO:
1042 isDisplay = true;
1043 defaultFormat = IntFormat::Octal;
1044 break;
1045 case ksn::WriteH:
1046 isDisplay = true;
1047 defaultFormat = IntFormat::HexLower;
1048 break;
1049 default:
1050 break;
1051 }
1052
1053 if (isDisplay) {
1054 auto message =
1055 context.convertFormatString(args, loc, defaultFormat, appendNewline);
1056 if (failed(message))
1057 return failure();
1058 if (*message == Value{})
1059 return true;
1060 moore::DisplayBIOp::create(builder, loc, *message);
1061 return true;
1062 }
1063
1064 // Severity Tasks
1065 using moore::Severity;
1066 std::optional<Severity> severity;
1067 if (nameId == ksn::Info)
1068 severity = Severity::Info;
1069 else if (nameId == ksn::Warning)
1070 severity = Severity::Warning;
1071 else if (nameId == ksn::Error)
1072 severity = Severity::Error;
1073 else if (nameId == ksn::Fatal)
1074 severity = Severity::Fatal;
1075
1076 if (severity) {
1077 // The `$fatal` task has an optional leading verbosity argument.
1078 const slang::ast::Expression *verbosityExpr = nullptr;
1079 if (severity == Severity::Fatal && args.size() >= 1) {
1080 verbosityExpr = args[0];
1081 args = args.subspan(1);
1082 }
1083
1084 FailureOr<Value> maybeMessage = getDisplayMessage(args);
1085 if (failed(maybeMessage))
1086 return failure();
1087 auto message = maybeMessage.value();
1088
1089 if (message == Value{})
1090 message = moore::FormatLiteralOp::create(builder, loc, "");
1091 moore::SeverityBIOp::create(builder, loc, *severity, message);
1092
1093 // Handle the `$fatal` case which behaves like a `$finish`.
1094 if (severity == Severity::Fatal) {
1095 createFinishMessage(verbosityExpr);
1096 moore::FinishBIOp::create(builder, loc, 1);
1097 moore::UnreachableOp::create(builder, loc);
1098 setTerminated();
1099 }
1100 return true;
1101 }
1102
1103 // File I/O Tasks
1104
1105 if (nameId == ksn::FClose) {
1106 assert(args.size() == 1 && "$fclose takes 1 argument");
1107 auto fd = context.convertRvalueExpression(
1108 *args[0], moore::IntType::getInt(builder.getContext(), 32));
1109 if (!fd)
1110 return failure();
1111 moore::FCloseBIOp::create(builder, loc, fd);
1112 return true;
1113 }
1114
1115 // Queue Tasks
1116
1117 if (args.size() >= 1 && args[0]->type->isQueue()) {
1118 auto queue = context.convertLvalueExpression(*args[0]);
1119
1120 // `delete` has two functions: If there is an index passed, then it
1121 // deletes that specific element, otherwise, it clears the entire queue.
1122 if (nameId == ksn::Delete) {
1123 if (args.size() == 1) {
1124 moore::QueueClearOp::create(builder, loc, queue);
1125 return true;
1126 }
1127 if (args.size() == 2) {
1128 auto index = context.convertRvalueExpression(*args[1]);
1129 moore::QueueDeleteOp::create(builder, loc, queue, index);
1130 return true;
1131 }
1132 } else if (nameId == ksn::Insert && args.size() == 3) {
1133 auto index = context.convertRvalueExpression(*args[1]);
1134 auto item = context.convertRvalueExpression(*args[2]);
1135
1136 moore::QueueInsertOp::create(builder, loc, queue, index, item);
1137 return true;
1138 } else if (nameId == ksn::PushBack && args.size() == 2) {
1139 auto item = context.convertRvalueExpression(*args[1]);
1140 moore::QueuePushBackOp::create(builder, loc, queue, item);
1141 return true;
1142 } else if (nameId == ksn::PushFront && args.size() == 2) {
1143 auto item = context.convertRvalueExpression(*args[1]);
1144 moore::QueuePushFrontOp::create(builder, loc, queue, item);
1145 return true;
1146 }
1147
1148 return false;
1149 }
1150
1151 // Associative array tasks
1152 if (args.size() >= 1 && args[0]->type->isAssociativeArray()) {
1153 auto assocArray = context.convertLvalueExpression(*args[0]);
1154
1155 // `delete` has two functions: If there is an index passed, then it
1156 // deletes that specific element, otherwise, it clears the entire
1157 // associative array.
1158 if (nameId == ksn::Delete) {
1159 if (args.size() == 1) {
1160 moore::AssocArrayClearOp::create(builder, loc, assocArray);
1161 return true;
1162 }
1163 if (args.size() == 2) {
1164 auto index = context.convertRvalueExpression(*args[1]);
1165 moore::AssocArrayDeleteOp::create(builder, loc, assocArray, index);
1166 return true;
1167 }
1168 }
1169 }
1170
1171 // Monitor enable/disable tasks (`$monitoron`, `$monitoroff`)
1172 if (nameId == ksn::MonitorOn || nameId == ksn::MonitorOff) {
1173 context.ensureMonitorGlobals();
1174 bool enable = (nameId == ksn::MonitorOn);
1175 auto enabledRef = moore::GetGlobalVariableOp::create(
1176 context.builder, loc, context.monitorEnabledGlobal);
1177 auto value = moore::ConstantOp::create(context.builder, loc,
1178 moore::Domain::TwoValued, enable);
1179 moore::BlockingAssignOp::create(context.builder, loc, enabledRef, value);
1180 return true;
1181 }
1182
1183 // Monitor tasks (`$monitor[boh]?`)
1184 if (nameId == ksn::Monitor || nameId == ksn::MonitorB ||
1185 nameId == ksn::MonitorO || nameId == ksn::MonitorH) {
1186 context.ensureMonitorGlobals();
1187
1188 // Allocate a unique ID for this monitor.
1189 unsigned myId = context.nextMonitorId++;
1190
1191 // Emit code to activate this monitor by setting the active_id global.
1192 auto i32Type = moore::IntType::getInt(context.getContext(), 32);
1193 auto idConst =
1194 moore::ConstantOp::create(context.builder, loc, i32Type, myId);
1195 auto activeRef = moore::GetGlobalVariableOp::create(
1196 context.builder, loc, context.monitorActiveIdGlobal);
1197 moore::BlockingAssignOp::create(context.builder, loc, activeRef, idConst);
1198
1199 // Queue this monitor for processing at module level.
1200 context.pendingMonitors.push_back({myId, loc, &expr});
1201
1202 return true;
1203 }
1204
1205 // Give up on any other system tasks. These will be tried again as an
1206 // expression later.
1207 return false;
1208 }
1209
1210 /// Create the optional diagnostic message print for finish-like ops.
1211 void createFinishMessage(const slang::ast::Expression *verbosityExpr) {
1212 unsigned verbosity = 1;
1213 if (verbosityExpr) {
1214 auto value =
1215 context.evaluateConstant(*verbosityExpr).integer().as<unsigned>();
1216 assert(value && "Slang guarantees constant verbosity parameter");
1217 verbosity = *value;
1218 }
1219 if (verbosity == 0)
1220 return;
1221 moore::FinishMessageBIOp::create(builder, loc, verbosity > 1);
1222 }
1223
1224 // Handle event trigger statements.
1225 LogicalResult visit(const slang::ast::EventTriggerStatement &stmt) {
1226 if (stmt.timing) {
1227 mlir::emitError(loc) << "unsupported delayed event trigger";
1228 return failure();
1229 }
1230
1231 // Events are lowered to `i1` signals. Get an lvalue ref to the signal such
1232 // that we can assign to it.
1233 auto target = context.convertLvalueExpression(stmt.target);
1234 if (!target)
1235 return failure();
1236
1237 // Read and invert the current value of the signal. Writing this inverted
1238 // value to the signal is our event signaling mechanism.
1239 Value inverted = moore::ReadOp::create(builder, loc, target);
1240 inverted = moore::NotOp::create(builder, loc, inverted);
1241
1242 if (stmt.isNonBlocking)
1243 moore::NonBlockingAssignOp::create(builder, loc, target, inverted);
1244 else
1245 moore::BlockingAssignOp::create(builder, loc, target, inverted);
1246 return success();
1247 }
1248
1249 // Handle `wait` statements
1250 LogicalResult visit(const slang::ast::WaitStatement &stmt) {
1251 auto waitOp = moore::WaitLevelOp::create(builder, loc);
1252 {
1253 OpBuilder::InsertionGuard guard(builder);
1254 builder.setInsertionPointToStart(&waitOp.getBody().emplaceBlock());
1255 auto cond = context.convertRvalueExpression(stmt.cond);
1256 if (!cond)
1257 return failure();
1258 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
1259 moore::DetectLevelOp::create(builder, loc, cond);
1260 }
1261 // Handle optional post-wait operation as if it were a separate statement
1262 if (failed(context.convertStatement(stmt.stmt)))
1263 return failure();
1264
1265 return success();
1266 }
1267
1268 LogicalResult visit(const slang::ast::WaitForkStatement &stmt) {
1269 moore::WaitForkOp::create(builder, loc);
1270 return success();
1271 }
1272
1273 /// Emit an error for all other statements.
1274 template <typename T>
1275 LogicalResult visit(T &&stmt) {
1276 mlir::emitError(loc, "unsupported statement: ")
1277 << slang::ast::toString(stmt.kind);
1278 return mlir::failure();
1279 }
1280
1281 LogicalResult visitInvalid(const slang::ast::Statement &stmt) {
1282 mlir::emitError(loc, "invalid statement: ")
1283 << slang::ast::toString(stmt.kind);
1284 return mlir::failure();
1285 }
1286};
1287} // namespace
1288
1289LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) {
1290 assert(builder.getInsertionBlock());
1291 auto loc = convertLocation(stmt.sourceRange);
1292 return stmt.visit(StmtVisitor(*this, loc));
1293}
1294// NOLINTEND(misc-no-recursion)
1295
1296//===----------------------------------------------------------------------===//
1297// Monitor support
1298//===----------------------------------------------------------------------===//
1299
1301 // If globals already exist, nothing to do.
1303 return;
1304
1305 // Save current builder position and insert at the start of the module.
1306 OpBuilder::InsertionGuard guard(builder);
1307 builder.setInsertionPointToStart(intoModuleOp.getBody());
1308
1309 auto loc = intoModuleOp.getLoc();
1310 auto i32Type = moore::IntType::getInt(getContext(), 32);
1311 auto i1Type = moore::IntType::getInt(getContext(), 1);
1312
1313 // Create "active_id" global variable. Index 0 indicates no monitor
1314 // is active.
1315 monitorActiveIdGlobal = moore::GlobalVariableOp::create(
1316 builder, loc, "__monitor_active_id", i32Type);
1317 {
1318 OpBuilder::InsertionGuard initGuard(builder);
1319 builder.setInsertionPointToStart(
1320 &monitorActiveIdGlobal.getInitRegion().emplaceBlock());
1321 auto zero = moore::ConstantOp::create(builder, loc, i32Type, 0);
1322 moore::YieldOp::create(builder, loc, zero);
1323 }
1325
1326 // Create "enabled" global variable.
1327 monitorEnabledGlobal = moore::GlobalVariableOp::create(
1328 builder, loc, "__monitor_enabled", i1Type);
1329 {
1330 OpBuilder::InsertionGuard initGuard(builder);
1331 builder.setInsertionPointToStart(
1332 &monitorEnabledGlobal.getInitRegion().emplaceBlock());
1333 auto trueVal =
1334 moore::ConstantOp::create(builder, loc, moore::Domain::TwoValued, true);
1335 moore::YieldOp::create(builder, loc, trueVal);
1336 }
1338}
1339
1341 using ksn = slang::parsing::KnownSystemName;
1342 for (auto &pending : pendingMonitors) {
1343 auto &call = *pending.call;
1344 auto loc = pending.loc;
1345
1346 // Extract the SystemCallInfo from the call's subroutine variant.
1347 auto &info =
1348 std::get<slang::ast::CallExpression::SystemCallInfo>(call.subroutine);
1349 auto nameId = info.subroutine->knownNameId;
1350
1351 // Determine the default format based on the system call name.
1352 auto defaultFormat = moore::IntFormat::Decimal;
1353 switch (nameId) {
1354 case ksn::MonitorB:
1355 defaultFormat = moore::IntFormat::Binary;
1356 break;
1357 case ksn::MonitorO:
1358 defaultFormat = moore::IntFormat::Octal;
1359 break;
1360 case ksn::MonitorH:
1361 defaultFormat = moore::IntFormat::HexLower;
1362 break;
1363 default:
1364 break;
1365 }
1366
1367 // Create an always_comb procedure for this monitor. This will implement the
1368 // semantics of printing an updated message whenever one of the input
1369 // signals changes.
1370 auto alwaysProc = moore::ProcedureOp::create(
1371 builder, loc, moore::ProcedureKind::AlwaysComb);
1372 OpBuilder::InsertionGuard guard(builder);
1373 builder.setInsertionPointToStart(&alwaysProc.getBody().emplaceBlock());
1374
1375 // Convert the format string and arguments.
1376 auto message = convertFormatString(call.arguments(), loc, defaultFormat,
1377 /*appendNewline=*/true);
1378 if (failed(message))
1379 return failure();
1380
1381 // Check if this monitor is active and enabled.
1382 auto i32Type = moore::IntType::getInt(getContext(), 32);
1383 auto myId = moore::ConstantOp::create(builder, loc, i32Type, pending.id);
1384 Value isActive =
1385 moore::GetGlobalVariableOp::create(builder, loc, monitorActiveIdGlobal);
1386 isActive = moore::ReadOp::create(builder, loc, isActive);
1387 isActive = moore::EqOp::create(builder, loc, isActive, myId);
1388
1389 Value enabled =
1390 moore::GetGlobalVariableOp::create(builder, loc, monitorEnabledGlobal);
1391 enabled = moore::ReadOp::create(builder, loc, enabled);
1392 enabled = moore::AndOp::create(builder, loc, isActive, enabled);
1393 enabled = moore::ToBuiltinIntOp::create(builder, loc, enabled);
1394
1395 // Branch to a print or skip block based on whether the monitor is enabled
1396 // or not.
1397 auto &printBlock = alwaysProc.getBody().emplaceBlock();
1398 auto &skipBlock = alwaysProc.getBody().emplaceBlock();
1399 cf::CondBranchOp::create(builder, loc, enabled, &printBlock, &skipBlock);
1400
1401 // Display the formatted message if one was created, and the monitor is
1402 // enabled.
1403 builder.setInsertionPointToStart(&printBlock);
1404 if (*message)
1405 moore::DisplayBIOp::create(builder, loc, *message);
1406 moore::ReturnOp::create(builder, loc);
1407
1408 // Otherwise just return.
1409 builder.setInsertionPointToStart(&skipBlock);
1410 moore::ReturnOp::create(builder, loc);
1411 }
1412
1413 pendingMonitors.clear();
1414 return success();
1415}
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.