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