CIRCT 24.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 (isa_and_nonnull<moore::IntToLogicOp, moore::LogicToIntOp>(
531 maybeConst.getDefiningOp()))
532 maybeConst = maybeConst.getDefiningOp()->getOperand(0);
533 if (auto defOp = maybeConst.getDefiningOp<moore::ConstantOp>())
534 itemConsts.push_back(defOp.getValueAttr());
535
536 // Generate the appropriate equality operator. A case statement with
537 // real operands uses ordinary equality (`==`) per IEEE 1800 § 11.4.5,
538 // not case-equality; wildcard case kinds on reals are illegal SV.
539 switch (caseStmt.condition) {
540 case CaseStatementCondition::Normal:
541 if (isa<moore::RealType>(caseExpr.getType()))
542 cond = moore::EqRealOp::create(builder, itemLoc, caseExpr, value);
543 else
544 cond = moore::CaseEqOp::create(builder, itemLoc, caseExpr, value);
545 break;
546 case CaseStatementCondition::WildcardXOrZ:
547 cond = moore::CaseXZEqOp::create(builder, itemLoc, caseExpr, value);
548 break;
549 case CaseStatementCondition::WildcardJustZ:
550 cond = moore::CaseZEqOp::create(builder, itemLoc, caseExpr, value);
551 break;
552 case CaseStatementCondition::Inside:
553 llvm_unreachable("Inside condition has been handled already");
554 break;
555 }
556 }
557
558 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
559 ty && ty.getDomain() == Domain::FourValued) {
560 cond = moore::LogicToIntOp::create(builder, loc, cond);
561 }
562 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
563
564 // If the condition matches, branch to the match block. Otherwise
565 // continue checking the next expression in a new block.
566 auto &nextBlock = createBlock();
567 mlir::cf::CondBranchOp::create(builder, itemLoc, cond, &matchBlock,
568 &nextBlock);
569 builder.setInsertionPointToEnd(&nextBlock);
570 }
571
572 // The current block is the fall-through after all conditions have been
573 // checked and nothing matched. Move the match block up before this point
574 // to make the IR easier to read.
575 matchBlock.moveBefore(builder.getInsertionBlock());
576
577 // Generate the code for this item's statement in the match block.
578 OpBuilder::InsertionGuard guard(builder);
579 builder.setInsertionPointToEnd(&matchBlock);
580 if (failed(context.convertStatement(*item.stmt)))
581 return failure();
582 if (!isTerminated()) {
583 auto loc = context.convertLocation(item.stmt->sourceRange);
584 mlir::cf::BranchOp::create(builder, loc, &exitBlock);
585 }
586 }
587
588 const auto caseStmtAttrs = context.compilation.getAttributes(caseStmt);
589 const bool hasFullCaseAttr =
590 llvm::find_if(caseStmtAttrs, [](const AttributeSymbol *attr) {
591 return attr->name == "full_case";
592 }) != caseStmtAttrs.end();
593
594 // Check if the case statement looks exhaustive assuming two-state values.
595 // We use this information to work around a common bug in input Verilog
596 // where a case statement enumerates all possible two-state values of the
597 // case expression, but forgets to deal with cases involving X and Z bits in
598 // the input.
599 //
600 // Once the core dialects start supporting four-state values we may want to
601 // tuck this behind an import option that is on by default, since it does
602 // not preserve semantics.
603 auto twoStateExhaustive = false;
604 if (auto intType = dyn_cast<moore::IntType>(caseExpr.getType());
605 intType && intType.getWidth() < 32 &&
606 itemConsts.size() == (1 << intType.getWidth())) {
607 // Sort the constants by value.
608 llvm::sort(itemConsts, [](auto a, auto b) {
609 return a.getValue().getRawValue().ult(b.getValue().getRawValue());
610 });
611
612 // Ensure that every possible value of the case expression is present. Do
613 // this by starting at 0 and iterating over all sorted items. Each item
614 // must be the previous item + 1. At the end, the addition must exactly
615 // overflow and take us back to zero.
616 auto nextValue = FVInt::getZero(intType.getWidth());
617 for (auto value : itemConsts) {
618 if (value.getValue() != nextValue)
619 break;
620 nextValue += 1;
621 }
622 twoStateExhaustive = nextValue.isZero();
623 }
624
625 // If the case statement is exhaustive assuming two-state values, don't
626 // generate the default case. Instead, branch to the last match block. This
627 // will essentially make the last case item the "default".
628 //
629 // Alternatively, if the case statement has an (* full_case *) attribute
630 // but no default case, it indicates that the developer has intentionally
631 // covered all known possible values. Hence, the last match block is
632 // treated as the implicit "default" case.
633 if ((twoStateExhaustive || (hasFullCaseAttr && !caseStmt.defaultCase)) &&
634 lastMatchBlock &&
635 caseStmt.condition == CaseStatementCondition::Normal) {
636 mlir::cf::BranchOp::create(builder, loc, lastMatchBlock);
637 } else {
638 // Generate the default case if present.
639 if (caseStmt.defaultCase)
640 if (failed(context.convertStatement(*caseStmt.defaultCase)))
641 return failure();
642 if (!isTerminated())
643 mlir::cf::BranchOp::create(builder, loc, &exitBlock);
644 }
645
646 // If control never reaches the exit block, remove it and mark control flow
647 // as terminated. Otherwise we continue inserting ops in the exit block.
648 if (exitBlock.hasNoPredecessors()) {
649 exitBlock.erase();
650 setTerminated();
651 } else {
652 builder.setInsertionPointToEnd(&exitBlock);
653 }
654 return success();
655 }
656
657 // Handle `for` loops.
658 LogicalResult visit(const slang::ast::ForLoopStatement &stmt) {
659 // Generate the initializers.
660 for (auto *initExpr : stmt.initializers)
661 if (!context.convertRvalueExpression(*initExpr))
662 return failure();
663
664 // Create the blocks for the loop condition, body, step, and exit.
665 auto &exitBlock = createBlock();
666 auto &stepBlock = createBlock();
667 auto &bodyBlock = createBlock();
668 auto &checkBlock = createBlock();
669 cf::BranchOp::create(builder, loc, &checkBlock);
670
671 // Push the blocks onto the loop stack such that we can continue and break.
672 context.loopStack.push_back({&stepBlock, &exitBlock});
673 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
674
675 // Generate the loop condition check.
676 builder.setInsertionPointToEnd(&checkBlock);
677 auto cond = context.convertRvalueExpression(*stmt.stopExpr);
678 if (!cond)
679 return failure();
680 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
681 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
682 ty && ty.getDomain() == Domain::FourValued) {
683 cond = moore::LogicToIntOp::create(builder, loc, cond);
684 }
685 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
686 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
687
688 // Generate the loop body.
689 builder.setInsertionPointToEnd(&bodyBlock);
690 if (failed(context.convertStatement(stmt.body)))
691 return failure();
692 if (!isTerminated())
693 cf::BranchOp::create(builder, loc, &stepBlock);
694
695 // Generate the step expressions.
696 builder.setInsertionPointToEnd(&stepBlock);
697 for (auto *stepExpr : stmt.steps)
698 if (!context.convertRvalueExpression(*stepExpr))
699 return failure();
700 if (!isTerminated())
701 cf::BranchOp::create(builder, loc, &checkBlock);
702
703 // If control never reaches the exit block, remove it and mark control flow
704 // as terminated. Otherwise we continue inserting ops in the exit block.
705 if (exitBlock.hasNoPredecessors()) {
706 exitBlock.erase();
707 setTerminated();
708 } else {
709 builder.setInsertionPointToEnd(&exitBlock);
710 }
711 return success();
712 }
713
714 LogicalResult visit(const slang::ast::ForeachLoopStatement &stmt) {
715 for (uint32_t level = 0; level < stmt.loopDims.size(); level++) {
716 if (stmt.loopDims[level].loopVar)
717 return recursiveForeach(stmt, level);
718 }
719 return success();
720 }
721
722 // Handle `repeat` loops.
723 LogicalResult visit(const slang::ast::RepeatLoopStatement &stmt) {
724 auto intType = moore::IntType::getInt(context.getContext(), 32);
725 auto count = context.convertRvalueExpression(stmt.count, intType);
726 if (!count)
727 return failure();
728
729 // Create the blocks for the loop condition, body, step, and exit.
730 auto &exitBlock = createBlock();
731 auto &stepBlock = createBlock();
732 auto &bodyBlock = createBlock();
733 auto &checkBlock = createBlock();
734 auto currentCount = checkBlock.addArgument(count.getType(), count.getLoc());
735 cf::BranchOp::create(builder, loc, &checkBlock, count);
736
737 // Push the blocks onto the loop stack such that we can continue and break.
738 context.loopStack.push_back({&stepBlock, &exitBlock});
739 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
740
741 // Generate the loop condition check.
742 builder.setInsertionPointToEnd(&checkBlock);
743 auto cond = builder.createOrFold<moore::BoolCastOp>(loc, currentCount);
744 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
745 ty && ty.getDomain() == Domain::FourValued) {
746 cond = moore::LogicToIntOp::create(builder, loc, cond);
747 }
748 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
749 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
750
751 // Generate the loop body.
752 builder.setInsertionPointToEnd(&bodyBlock);
753 if (failed(context.convertStatement(stmt.body)))
754 return failure();
755 if (!isTerminated())
756 cf::BranchOp::create(builder, loc, &stepBlock);
757
758 // Decrement the current count and branch back to the check block.
759 builder.setInsertionPointToEnd(&stepBlock);
760 auto one = moore::ConstantOp::create(
761 builder, count.getLoc(), cast<moore::IntType>(count.getType()), 1);
762 Value nextCount =
763 moore::SubOp::create(builder, count.getLoc(), currentCount, one);
764 cf::BranchOp::create(builder, loc, &checkBlock, nextCount);
765
766 // If control never reaches the exit block, remove it and mark control flow
767 // as terminated. Otherwise we continue inserting ops in the exit block.
768 if (exitBlock.hasNoPredecessors()) {
769 exitBlock.erase();
770 setTerminated();
771 } else {
772 builder.setInsertionPointToEnd(&exitBlock);
773 }
774 return success();
775 }
776
777 // Handle `while` and `do-while` loops.
778 LogicalResult createWhileLoop(const slang::ast::Expression &condExpr,
779 const slang::ast::Statement &bodyStmt,
780 bool atLeastOnce) {
781 // Create the blocks for the loop condition, body, and exit.
782 auto &exitBlock = createBlock();
783 auto &bodyBlock = createBlock();
784 auto &checkBlock = createBlock();
785 cf::BranchOp::create(builder, loc, atLeastOnce ? &bodyBlock : &checkBlock);
786 if (atLeastOnce)
787 bodyBlock.moveBefore(&checkBlock);
788
789 // Push the blocks onto the loop stack such that we can continue and break.
790 context.loopStack.push_back({&checkBlock, &exitBlock});
791 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
792
793 // Generate the loop condition check.
794 builder.setInsertionPointToEnd(&checkBlock);
795 auto cond = context.convertRvalueExpression(condExpr);
796 if (!cond)
797 return failure();
798 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
799 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
800 ty && ty.getDomain() == Domain::FourValued) {
801 cond = moore::LogicToIntOp::create(builder, loc, cond);
802 }
803 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
804 cf::CondBranchOp::create(builder, loc, cond, &bodyBlock, &exitBlock);
805
806 // Generate the loop body.
807 builder.setInsertionPointToEnd(&bodyBlock);
808 if (failed(context.convertStatement(bodyStmt)))
809 return failure();
810 if (!isTerminated())
811 cf::BranchOp::create(builder, loc, &checkBlock);
812
813 // If control never reaches the exit block, remove it and mark control flow
814 // as terminated. Otherwise we continue inserting ops in the exit block.
815 if (exitBlock.hasNoPredecessors()) {
816 exitBlock.erase();
817 setTerminated();
818 } else {
819 builder.setInsertionPointToEnd(&exitBlock);
820 }
821 return success();
822 }
823
824 LogicalResult visit(const slang::ast::WhileLoopStatement &stmt) {
825 return createWhileLoop(stmt.cond, stmt.body, false);
826 }
827
828 LogicalResult visit(const slang::ast::DoWhileLoopStatement &stmt) {
829 return createWhileLoop(stmt.cond, stmt.body, true);
830 }
831
832 // Handle `forever` loops.
833 LogicalResult visit(const slang::ast::ForeverLoopStatement &stmt) {
834 // Create the blocks for the loop body and exit.
835 auto &exitBlock = createBlock();
836 auto &bodyBlock = createBlock();
837 cf::BranchOp::create(builder, loc, &bodyBlock);
838
839 // Push the blocks onto the loop stack such that we can continue and break.
840 context.loopStack.push_back({&bodyBlock, &exitBlock});
841 llvm::scope_exit done([&] { context.loopStack.pop_back(); });
842
843 // Generate the loop body.
844 builder.setInsertionPointToEnd(&bodyBlock);
845 if (failed(context.convertStatement(stmt.body)))
846 return failure();
847 if (!isTerminated())
848 cf::BranchOp::create(builder, loc, &bodyBlock);
849
850 // If control never reaches the exit block, remove it and mark control flow
851 // as terminated. Otherwise we continue inserting ops in the exit block.
852 if (exitBlock.hasNoPredecessors()) {
853 exitBlock.erase();
854 setTerminated();
855 } else {
856 builder.setInsertionPointToEnd(&exitBlock);
857 }
858 return success();
859 }
860
861 // Handle timing control.
862 LogicalResult visit(const slang::ast::TimedStatement &stmt) {
863 return context.convertTimingControl(stmt.timing, stmt.stmt);
864 }
865
866 // Handle return statements.
867 LogicalResult visit(const slang::ast::ReturnStatement &stmt) {
868 Operation *parentOp = builder.getInsertionBlock()
869 ? builder.getInsertionBlock()->getParentOp()
870 : nullptr;
871 if (!parentOp)
872 return mlir::emitError(loc) << "return statement is not within an op";
873
874 if (isa<moore::CoroutineOp, moore::ProcedureOp>(parentOp)) {
875 if (stmt.expr)
876 return mlir::emitError(loc)
877 << "unsupported `return <expr>` in a procedure or task";
878 moore::ReturnOp::create(builder, loc);
879 setTerminated();
880 return success();
881 }
882
883 auto funcOp = dyn_cast<mlir::func::FuncOp>(parentOp);
884 if (!funcOp)
885 return mlir::emitError(loc) << "unsupported return statement context";
886
887 if (stmt.expr) {
888 auto resultTypes = funcOp.getFunctionType().getResults();
889 Type resultType = resultTypes.size() == 1 ? resultTypes[0] : Type();
890 auto expr = context.convertRvalueExpression(*stmt.expr, resultType);
891 if (!expr)
892 return failure();
893 mlir::func::ReturnOp::create(builder, loc, expr);
894 } else {
895 mlir::func::ReturnOp::create(builder, loc);
896 }
897 setTerminated();
898 return success();
899 }
900
901 // Handle continue statements.
902 LogicalResult visit(const slang::ast::ContinueStatement &stmt) {
903 if (context.loopStack.empty())
904 return mlir::emitError(loc,
905 "cannot `continue` without a surrounding loop");
906 cf::BranchOp::create(builder, loc, context.loopStack.back().continueBlock);
907 setTerminated();
908 return success();
909 }
910
911 // Handle break statements.
912 LogicalResult visit(const slang::ast::BreakStatement &stmt) {
913 if (context.loopStack.empty())
914 return mlir::emitError(loc, "cannot `break` without a surrounding loop");
915 cf::BranchOp::create(builder, loc, context.loopStack.back().breakBlock);
916 setTerminated();
917 return success();
918 }
919
920 // Handle immediate assertion statements.
921 LogicalResult visit(const slang::ast::ImmediateAssertionStatement &stmt) {
922 auto cond = context.convertRvalueExpression(stmt.cond);
923 cond = context.convertToBool(cond);
924 if (!cond)
925 return failure();
926
927 // Handle assertion statements that don't have an action block.
928 if (stmt.ifTrue && stmt.ifTrue->as_if<slang::ast::EmptyStatement>()) {
929 auto defer = moore::DeferAssert::Immediate;
930 if (stmt.isFinal)
931 defer = moore::DeferAssert::Final;
932 else if (stmt.isDeferred)
933 defer = moore::DeferAssert::Observed;
934
935 switch (stmt.assertionKind) {
936 case slang::ast::AssertionKind::Assert:
937 moore::AssertOp::create(builder, loc, defer, cond, StringAttr{});
938 return success();
939 case slang::ast::AssertionKind::Assume:
940 moore::AssumeOp::create(builder, loc, defer, cond, StringAttr{});
941 return success();
942 case slang::ast::AssertionKind::CoverProperty:
943 moore::CoverOp::create(builder, loc, defer, cond, StringAttr{});
944 return success();
945 default:
946 break;
947 }
948 mlir::emitError(loc) << "unsupported immediate assertion kind: "
949 << slang::ast::toString(stmt.assertionKind);
950 return failure();
951 }
952
953 // Regard assertion statements with an action block as the "if-else".
954 if (auto ty = dyn_cast<moore::IntType>(cond.getType());
955 ty && ty.getDomain() == Domain::FourValued) {
956 cond = moore::LogicToIntOp::create(builder, loc, cond);
957 }
958 cond = moore::ToBuiltinIntOp::create(builder, loc, cond);
959
960 // Create the blocks for the true and false branches, and the exit block.
961 Block &exitBlock = createBlock();
962 Block *falseBlock = stmt.ifFalse ? &createBlock() : nullptr;
963 Block &trueBlock = createBlock();
964 cf::CondBranchOp::create(builder, loc, cond, &trueBlock,
965 falseBlock ? falseBlock : &exitBlock);
966
967 // Generate the true branch.
968 builder.setInsertionPointToEnd(&trueBlock);
969 if (stmt.ifTrue && failed(context.convertStatement(*stmt.ifTrue)))
970 return failure();
971 if (!isTerminated())
972 cf::BranchOp::create(builder, loc, &exitBlock);
973
974 if (stmt.ifFalse) {
975 // Generate the false branch if present.
976 builder.setInsertionPointToEnd(falseBlock);
977 if (failed(context.convertStatement(*stmt.ifFalse)))
978 return failure();
979 if (!isTerminated())
980 cf::BranchOp::create(builder, loc, &exitBlock);
981 }
982
983 // If control never reaches the exit block, remove it and mark control flow
984 // as terminated. Otherwise we continue inserting ops in the exit block.
985 if (exitBlock.hasNoPredecessors()) {
986 exitBlock.erase();
987 setTerminated();
988 } else {
989 builder.setInsertionPointToEnd(&exitBlock);
990 }
991 return success();
992 }
993
994 // Handle concurrent assertion statements.
995 LogicalResult visit(const slang::ast::ConcurrentAssertionStatement &stmt) {
996 auto loc = context.convertLocation(stmt.sourceRange);
997
998 // Check for a `disable iff` expression:
999 // `disable iff` can only appear at the outermost property that is asserted,
1000 // and can never be nested.
1001 // Hence we only need to detect if the top level assertion expression has
1002 // type DisableIff. (or, if the top level expression is
1003 // ClockingAssertionExpr, check for DisableIff inside that).
1004 Value enable;
1005 Value property;
1006 // Find the outermost propertySpec that isn't ClockingAssertionExpr
1007 const slang::ast::AssertionExpr *propertySpec;
1008 const slang::ast::ClockingAssertionExpr *clocking =
1009 stmt.propertySpec.as_if<slang::ast::ClockingAssertionExpr>();
1010 if (clocking)
1011 propertySpec = &(clocking->expr);
1012 else
1013 propertySpec = &(stmt.propertySpec);
1014
1015 if (auto *disableIff =
1016 propertySpec->as_if<slang::ast::DisableIffAssertionExpr>()) {
1017 // Lower disableIff by negating it and passing as the "enable" operand
1018 // to the verif.assert/verif.assume instructions.
1019 auto disableCond = context.convertRvalueExpression(disableIff->condition);
1020 auto enableCond = moore::NotOp::create(builder, loc, disableCond);
1021
1022 enable = context.convertToI1(enableCond);
1023
1024 // Add back the outer `ClockingAssertionExpr` if there is one.
1025 if (clocking) {
1026 auto clockingExpr = slang::ast::ClockingAssertionExpr(
1027 clocking->clocking, disableIff->expr);
1028 property = context.convertAssertionExpression(clockingExpr, loc);
1029 } else {
1030 property = context.convertAssertionExpression(disableIff->expr, loc);
1031 }
1032 } else {
1033 property = context.convertAssertionExpression(stmt.propertySpec, loc);
1034 }
1035
1036 if (!property)
1037 return failure();
1038
1039 // Handle assertion statements that don't have an action block.
1040 if (!stmt.ifTrue || stmt.ifTrue->as_if<slang::ast::EmptyStatement>()) {
1041 switch (stmt.assertionKind) {
1042 case slang::ast::AssertionKind::Assert:
1043 verif::AssertOp::create(builder, loc, property, enable, StringAttr{});
1044 return success();
1045 case slang::ast::AssertionKind::Assume:
1046 verif::AssumeOp::create(builder, loc, property, enable, StringAttr{});
1047 return success();
1048 default:
1049 break;
1050 }
1051 mlir::emitError(loc) << "unsupported concurrent assertion kind: "
1052 << slang::ast::toString(stmt.assertionKind);
1053 return failure();
1054 }
1055
1056 mlir::emitError(loc)
1057 << "concurrent assertion statements with action blocks "
1058 "are not supported yet";
1059 return failure();
1060 }
1061
1062 // According to 1800-2023 Section 21.2.1 "The display and write tasks":
1063 // >> The $display and $write tasks display their arguments in the same
1064 // >> order as they appear in the argument list. Each argument can be a
1065 // >> string literal or an expression that returns a value.
1066 // According to Section 20.10 "Severity system tasks", the same
1067 // semantics apply to $fatal, $error, $warning, and $info.
1068 // This means we must first check whether the first "string-able"
1069 // argument is a Literal Expression which doesn't represent a fully-formatted
1070 // string, otherwise we convert it to a FormatStringType.
1071 FailureOr<Value>
1072 getDisplayMessage(std::span<const slang::ast::Expression *const> args) {
1073 if (args.size() == 0)
1074 return Value{};
1075
1076 // Handle the string formatting.
1077 // If the second argument is a Literal of some type, we should either
1078 // treat it as a literal-to-be-formatted or a FormatStringType.
1079 // In this check we use a StringLiteral, but slang allows casting between
1080 // any literal expressions (strings, integers, reals, and time at least) so
1081 // this is short-hand for "any value literal"
1082 if (args[0]->as_if<slang::ast::StringLiteral>()) {
1083 return context.convertFormatString(args, loc);
1084 }
1085 // Check if there's only one argument and it's a FormatStringType
1086 if (args.size() == 1) {
1087 return context.convertRvalueExpression(
1088 *args[0], builder.getType<moore::FormatStringType>());
1089 }
1090 // Otherwise this looks invalid. Raise an error.
1091 return emitError(loc) << "Failed to convert Display Message!";
1092 }
1093
1094 /// Convert a `$readmemb`/`$readmemh` system task call into a
1095 /// `moore.builtin.readmem` op. See IEEE 1800-2017 § 21.4.
1096 LogicalResult
1097 convertReadMemTask(std::span<const slang::ast::Expression *const> args,
1098 bool isBinary) {
1099 assert(args.size() >= 2 && args.size() <= 4 &&
1100 "$readmemh/$readmemb takes 2 to 4 arguments");
1101
1102 auto i32Ty = moore::IntType::getInt(builder.getContext(), 32);
1103 auto filename = context.convertRvalueExpression(
1104 *args[0], moore::StringType::get(builder.getContext()));
1105 if (!filename)
1106 return failure();
1107
1108 const auto *destExpr = args[1];
1109
1110 // Slang wraps the memory argument in an assignment to the lvalue;
1111 // unwrap it to get at the memory itslef.
1112 if (const auto *assign =
1113 destExpr->as_if<slang::ast::AssignmentExpression>())
1114 destExpr = &assign->left();
1115
1116 // The memory may use slice syntax on its rightmost specified dimension.
1117 // The slice only narrows the address window of the selected array's highest
1118 // dimension; the destination stays the full array.
1119 Value sliceLeft, sliceRight;
1120 if (const auto *rangeExpr =
1121 destExpr->as_if<slang::ast::RangeSelectExpression>()) {
1122 if (rangeExpr->getSelectionKind() !=
1123 slang::ast::RangeSelectionKind::Simple) {
1124 mlir::emitError(loc)
1125 << "unsupported: indexed part-select on $readmem memory";
1126 return failure();
1127 }
1128
1129 sliceLeft = context.convertRvalueExpression(rangeExpr->left(), i32Ty);
1130 sliceRight = context.convertRvalueExpression(rangeExpr->right(), i32Ty);
1131
1132 if (!sliceLeft || !sliceRight)
1133 return failure();
1134
1135 destExpr = &rangeExpr->value();
1136 }
1137
1138 auto dest = context.convertLvalueExpression(*destExpr);
1139 if (!dest)
1140 return failure();
1141
1142 // Collect the declared low bound and direction of every unpacked dimension
1143 // (outermost first): the Moore array types do not carry them, but the
1144 // row-major file layout (§21.4.3) and the address mapping of the lowering
1145 // depend on them. Queues load with their current size fixed (§21.4.1).
1146 const auto *curTy = &destExpr->type->getCanonicalType();
1147 SmallVector<int64_t> dimLows;
1148 SmallVector<bool> dimDescs;
1149 const slang::ast::Type *elemSvTy = curTy;
1150
1151 if (curTy->isAssociativeArray()) {
1152 mlir::emitError(loc) << "unsupported: $readmem into associative array";
1153 return failure();
1154 }
1155
1156 if (const auto *queueTy = curTy->as_if<slang::ast::QueueType>()) {
1157 dimLows.push_back(0);
1158 dimDescs.push_back(false);
1159 elemSvTy = &queueTy->elementType.getCanonicalType();
1160 } else if (curTy->as_if<slang::ast::DynamicArrayType>()) {
1161 mlir::emitError(loc) << "unsupported: $readmem into dynamic array";
1162 return failure();
1163 } else {
1164 while (const auto *fixedArr =
1165 curTy->as_if<slang::ast::FixedSizeUnpackedArrayType>()) {
1166 dimLows.push_back(fixedArr->range.lower());
1167 dimDescs.push_back(fixedArr->range.isDescending());
1168 curTy = &fixedArr->elementType.getCanonicalType();
1169 }
1170 elemSvTy = curTy;
1171 }
1172
1173 if (dimLows.empty()) {
1174 mlir::emitError(loc) << "$readmem memory must be an unpacked array";
1175 return failure();
1176 }
1177
1178 // The file contains binary or hexadecimal numbers, so elements must be
1179 // packed data.
1180 if (!elemSvTy->isIntegral()) {
1181 mlir::emitError(loc) << "unsupported: $readmem element type "
1182 << elemSvTy->toString();
1183 return failure();
1184 }
1185
1186 // Values outside the enumeration must be rejected during the load. Collect
1187 // the legal values so the lowering can check membership; wider enumerations
1188 // cannot be represented in the attribute.
1189 DenseI64ArrayAttr enumValuesAttr;
1190 if (const auto *enumTy = elemSvTy->as_if<slang::ast::EnumType>()) {
1191 if (enumTy->getBitWidth() > 64) {
1192 mlir::emitError(loc)
1193 << "unsupported: $readmem into enumeration wider than 64 bits";
1194 return failure();
1195 }
1196 SmallVector<int64_t> vals;
1197 for (const auto &ev : enumTy->values()) {
1198 auto v = ev.getValue().integer().as<int64_t>();
1199 if (!v) {
1200 mlir::emitError(loc)
1201 << "unsupported: $readmem enumeration value with unknown bits";
1202 return failure();
1203 }
1204 vals.push_back(*v);
1205 }
1206 enumValuesAttr = builder.getDenseI64ArrayAttr(vals);
1207 }
1208
1209 Value startAddr;
1210 if (args.size() >= 3 &&
1211 args[2]->kind != slang::ast::ExpressionKind::EmptyArgument) {
1212 startAddr = context.convertRvalueExpression(*args[2], i32Ty);
1213 if (!startAddr)
1214 return failure();
1215 }
1216
1217 Value finishAddr;
1218 if (args.size() >= 4 &&
1219 args[3]->kind != slang::ast::ExpressionKind::EmptyArgument) {
1220 finishAddr = context.convertRvalueExpression(*args[3], i32Ty);
1221 if (!finishAddr)
1222 return failure();
1223 }
1224
1225 auto base = isBinary ? moore::MemBase::Binary : moore::MemBase::Hex;
1226 moore::ReadMemBIOp::create(
1227 builder, loc, filename, dest,
1228 moore::MemBaseAttr::get(builder.getContext(), base), startAddr,
1229 finishAddr, sliceLeft, sliceRight,
1230 builder.getDenseI64ArrayAttr(dimLows),
1231 builder.getDenseBoolArrayAttr(dimDescs), enumValuesAttr);
1232 return success();
1233 }
1234
1235 /// Handle the subset of system calls that return no result value. Return
1236 /// true if the called system task could be handled, false otherwise. Return
1237 /// failure if an error occurred.
1238 FailureOr<bool>
1239 visitSystemCall(const slang::ast::ExpressionStatement &stmt,
1240 const slang::ast::CallExpression &expr,
1241 const slang::ast::CallExpression::SystemCallInfo &info) {
1242 using ksn = slang::parsing::KnownSystemName;
1243 const auto &subroutine = *info.subroutine;
1244 auto nameId = subroutine.knownNameId;
1245 auto args = expr.arguments();
1246
1247 // The `$cast` system call is handled by `Context::convertSystemCall` in the
1248 // `Expressions.cpp` file. Skip it is order to avoid visiting the
1249 // `EmptyArgument` node.
1250 if (nameId == ksn::Cast) {
1251 return false;
1252 }
1253
1254 // Simulation Control Tasks
1255
1256 if (nameId == ksn::Stop) {
1257 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
1258 moore::StopBIOp::create(builder, loc);
1259 return true;
1260 }
1261
1262 if (nameId == ksn::Finish) {
1263 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
1264 moore::FinishBIOp::create(builder, loc, 0);
1265 moore::UnreachableOp::create(builder, loc);
1266 setTerminated();
1267 return true;
1268 }
1269
1270 if (nameId == ksn::Exit) {
1271 // Calls to `$exit` from outside a `program` are ignored. Since we don't
1272 // yet support programs, there is nothing to do here.
1273 // TODO: Fix this once we support programs.
1274 return true;
1275 }
1276
1277 // Timescale tasks (`$printtimescale`)
1278
1279 if (nameId == ksn::PrintTimeScale) {
1280 auto message = moore::FormatLiteralOp::create(
1281 builder, loc, buildPrintTimeScaleMessage(context, args));
1282 moore::DisplayBIOp::create(builder, loc, message);
1283 return true;
1284 }
1285
1286 // Display and Write Tasks (`$display[boh]?` or `$write[boh]?` or
1287 // `$fdisplay[boh]?` or `$fwrite[boh]?` or `$swrite[boh]` or `$sformat`)
1288
1289 using moore::IntFormat;
1290 bool isDisplay = false;
1291 bool isFDisplay = false;
1292 bool isSWrite = false;
1293 bool isSFormat = false;
1294 bool appendNewline = false;
1295 IntFormat defaultFormat = IntFormat::Decimal;
1296 switch (nameId) {
1297 case ksn::Display:
1298 isDisplay = true;
1299 appendNewline = true;
1300 break;
1301 case ksn::DisplayB:
1302 isDisplay = true;
1303 appendNewline = true;
1304 defaultFormat = IntFormat::Binary;
1305 break;
1306 case ksn::DisplayO:
1307 isDisplay = true;
1308 appendNewline = true;
1309 defaultFormat = IntFormat::Octal;
1310 break;
1311 case ksn::DisplayH:
1312 isDisplay = true;
1313 appendNewline = true;
1314 defaultFormat = IntFormat::HexLower;
1315 break;
1316 case ksn::Write:
1317 isDisplay = true;
1318 break;
1319 case ksn::WriteB:
1320 isDisplay = true;
1321 defaultFormat = IntFormat::Binary;
1322 break;
1323 case ksn::WriteO:
1324 isDisplay = true;
1325 defaultFormat = IntFormat::Octal;
1326 break;
1327 case ksn::WriteH:
1328 isDisplay = true;
1329 defaultFormat = IntFormat::HexLower;
1330 break;
1331 case ksn::FDisplay:
1332 isFDisplay = true;
1333 appendNewline = true;
1334 break;
1335 case ksn::FDisplayB:
1336 isFDisplay = true;
1337 appendNewline = true;
1338 defaultFormat = IntFormat::Binary;
1339 break;
1340 case ksn::FDisplayO:
1341 isFDisplay = true;
1342 appendNewline = true;
1343 defaultFormat = IntFormat::Octal;
1344 break;
1345 case ksn::FDisplayH:
1346 isFDisplay = true;
1347 appendNewline = true;
1348 defaultFormat = IntFormat::HexLower;
1349 break;
1350 case ksn::FWrite:
1351 isFDisplay = true;
1352 break;
1353 case ksn::FWriteB:
1354 isFDisplay = true;
1355 defaultFormat = IntFormat::Binary;
1356 break;
1357 case ksn::FWriteO:
1358 isFDisplay = true;
1359 defaultFormat = IntFormat::Octal;
1360 break;
1361 case ksn::FWriteH:
1362 isFDisplay = true;
1363 defaultFormat = IntFormat::HexLower;
1364 break;
1365 case ksn::SFormat:
1366 isSFormat = true;
1367 break;
1368 case ksn::SWrite:
1369 isSWrite = true;
1370 break;
1371 case ksn::SWriteB:
1372 isSWrite = true;
1373 defaultFormat = IntFormat::Binary;
1374 break;
1375 case ksn::SWriteO:
1376 isSWrite = true;
1377 defaultFormat = IntFormat::Octal;
1378 break;
1379 case ksn::SWriteH:
1380 isSWrite = true;
1381 defaultFormat = IntFormat::HexLower;
1382 break;
1383 default:
1384 break;
1385 }
1386
1387 if (isDisplay) {
1388 auto message =
1389 context.convertFormatString(args, loc, defaultFormat, appendNewline);
1390 if (failed(message))
1391 return failure();
1392 if (*message == Value{})
1393 return true;
1394 moore::DisplayBIOp::create(builder, loc, *message);
1395 return true;
1396 }
1397
1398 if (isFDisplay) {
1399 assert(!args.empty() && "$fdisplay/$fwrite takes at least 1 argument");
1400
1401 auto fd = context.convertRvalueExpression(
1402 *args[0], moore::IntType::getInt(builder.getContext(), 32));
1403 if (!fd)
1404 return failure();
1405 args = args.subspan(1);
1406
1407 auto message =
1408 context.convertFormatString(args, loc, defaultFormat, appendNewline);
1409 if (failed(message))
1410 return failure();
1411 if (*message == Value{})
1412 return true;
1413 moore::FDisplayBIOp::create(builder, loc, fd, *message);
1414 return true;
1415 }
1416
1417 // According to IEEE 1800-2023 Section 21.3.3 "Formatting data to a
1418 // string" the first argument of $sformat/$swrite is its output; the
1419 // other arguments work like a FormatString.
1420 // In Moore we only support writing to a location if it is a reference;
1421 // However, Section 21.3.3 explains that the output of $sformat/$swrite
1422 // is assigned as if it were cast from a string literal (Section 5.9),
1423 // so this implementation casts the string to the target value.
1424 if (isSWrite || isSFormat) {
1425 if (isSFormat && args.size() < 2)
1426 return emitError(loc) << "$sformat requires at least 2 arguments";
1427 if (isSWrite && args.size() < 1)
1428 return emitError(loc) << "$swrite requires at least 1 argument";
1429
1430 auto fmtValue =
1431 context.convertFormatString(args.subspan(1), loc, defaultFormat,
1432 /*appendNewline=*/false);
1433 if (failed(fmtValue))
1434 return failure();
1435 if (*fmtValue == Value{})
1436 return true;
1437 auto strValue =
1438 moore::FormatStringToStringOp::create(builder, loc, *fmtValue);
1439 auto *lhsExpr = args[0];
1440 if (auto *assignExpr =
1441 lhsExpr->as_if<slang::ast::AssignmentExpression>()) {
1442 auto lhs = context.convertLvalueExpression(assignExpr->left());
1443 if (!lhs)
1444 return failure();
1445 auto convertedValue = context.materializeConversion(
1446 cast<moore::RefType>(lhs.getType()).getNestedType(), strValue,
1447 false, loc);
1448 if (!convertedValue)
1449 return failure();
1450 moore::BlockingAssignOp::create(builder, loc, lhs, convertedValue);
1451 return true;
1452 }
1453 return failure();
1454 }
1455
1456 // Severity Tasks
1457 using moore::Severity;
1458 std::optional<Severity> severity;
1459 if (nameId == ksn::Info)
1460 severity = Severity::Info;
1461 else if (nameId == ksn::Warning)
1462 severity = Severity::Warning;
1463 else if (nameId == ksn::Error)
1464 severity = Severity::Error;
1465 else if (nameId == ksn::Fatal)
1466 severity = Severity::Fatal;
1467
1468 if (severity) {
1469 // The `$fatal` task has an optional leading verbosity argument.
1470 const slang::ast::Expression *verbosityExpr = nullptr;
1471 if (severity == Severity::Fatal && args.size() >= 1) {
1472 verbosityExpr = args[0];
1473 args = args.subspan(1);
1474 }
1475
1476 FailureOr<Value> maybeMessage = getDisplayMessage(args);
1477 if (failed(maybeMessage))
1478 return failure();
1479 auto message = maybeMessage.value();
1480
1481 if (message == Value{})
1482 message = moore::FormatLiteralOp::create(builder, loc, "");
1483 moore::SeverityBIOp::create(builder, loc, *severity, message);
1484
1485 // Handle the `$fatal` case which behaves like a `$finish`.
1486 if (severity == Severity::Fatal) {
1487 createFinishMessage(verbosityExpr);
1488 moore::FinishBIOp::create(builder, loc, 1);
1489 moore::UnreachableOp::create(builder, loc);
1490 setTerminated();
1491 }
1492 return true;
1493 }
1494
1495 // File I/O Tasks
1496
1497 if (nameId == ksn::FClose) {
1498 assert(args.size() == 1 && "$fclose takes 1 argument");
1499 auto fd = context.convertRvalueExpression(
1500 *args[0], moore::IntType::getInt(builder.getContext(), 32));
1501 if (!fd)
1502 return failure();
1503 moore::FCloseBIOp::create(builder, loc, fd);
1504 return true;
1505 }
1506
1507 if (nameId == ksn::FFlush) {
1508 assert(args.size() <= 1 && "$fflush takes at most 1 argument");
1509 Value fd;
1510 if (args.size() == 1) {
1511 fd = context.convertRvalueExpression(
1512 *args[0], moore::IntType::getInt(builder.getContext(), 32));
1513 if (!fd)
1514 return failure();
1515 }
1516 moore::FFlushBIOp::create(builder, loc, fd);
1517 return true;
1518 }
1519
1520 if (nameId == ksn::ReadMemH || nameId == ksn::ReadMemB) {
1521 if (failed(convertReadMemTask(args, nameId == ksn::ReadMemB)))
1522 return failure();
1523 return true;
1524 }
1525
1526 // String Tasks
1527 if (args.size() >= 1 && args[0]->type->isString()) {
1528 auto str = context.convertLvalueExpression(*args[0]);
1529
1530 if (nameId == ksn::Putc) {
1531 // Slang already checks the arity of string tasks.
1532 assert(args.size() == 3 && "`putc` takes 3 arguments");
1533 auto index = context.convertRvalueExpression(*args[1]);
1534 auto character = context.convertRvalueExpression(*args[2]);
1535 moore::StringPutOp::create(builder, loc, str, index, character);
1536 return true;
1537 }
1538
1539 if (nameId == ksn::IToA || nameId == ksn::HexToA ||
1540 nameId == ksn::OctToA || nameId == ksn::BinToA) {
1541 // Slang already checks the arity of string tasks.
1542 assert(args.size() == 2 && "`itoa/hex/oct/bin` takes 2 arguments");
1543 auto integerType = moore::IntType::getLogic(builder.getContext(), 32);
1544 auto input = context.convertRvalueExpression(*args[1], integerType);
1545
1546 switch (nameId) {
1547 case ksn::IToA:
1548 moore::StringItoaOp::create(builder, loc, str, input);
1549 break;
1550 case ksn::HexToA:
1551 moore::StringHextoaOp::create(builder, loc, str, input);
1552 break;
1553 case ksn::OctToA:
1554 moore::StringOcttoaOp::create(builder, loc, str, input);
1555 break;
1556 case ksn::BinToA:
1557 moore::StringBintoaOp::create(builder, loc, str, input);
1558 break;
1559 default:
1560 llvm_unreachable("unexpected ASCII integer to string conversion");
1561 return false;
1562 }
1563 return true;
1564 }
1565
1566 if (nameId == ksn::RealToA) {
1567 // Slang already checks the arity of string tasks.
1568 assert(args.size() == 2 && "`realtoa` takes 2 arguments");
1569 auto realType =
1570 moore::RealType::get(context.getContext(), moore::RealWidth::f64);
1571 auto input = context.convertRvalueExpression(*args[1], realType);
1572 moore::StringRealtoaOp::create(builder, loc, str, input);
1573 return true;
1574 }
1575 return false;
1576 }
1577
1578 // Queue Tasks
1579 if (args.size() >= 1 && args[0]->type->isQueue()) {
1580 auto queue = context.convertLvalueExpression(*args[0]);
1581
1582 // `delete` has two functions: If there is an index passed, then it
1583 // deletes that specific element, otherwise, it clears the entire queue.
1584 if (nameId == ksn::Delete) {
1585 if (args.size() == 1) {
1586 moore::QueueClearOp::create(builder, loc, queue);
1587 return true;
1588 }
1589 if (args.size() == 2) {
1590 auto index = context.convertRvalueExpression(*args[1]);
1591 moore::QueueDeleteOp::create(builder, loc, queue, index);
1592 return true;
1593 }
1594 } else if (nameId == ksn::Insert && args.size() == 3) {
1595 auto index = context.convertRvalueExpression(*args[1]);
1596 auto item = context.convertRvalueExpression(*args[2]);
1597
1598 moore::QueueInsertOp::create(builder, loc, queue, index, item);
1599 return true;
1600 } else if (nameId == ksn::PushBack && args.size() == 2) {
1601 auto item = context.convertRvalueExpression(*args[1]);
1602 moore::QueuePushBackOp::create(builder, loc, queue, item);
1603 return true;
1604 } else if (nameId == ksn::PushFront && args.size() == 2) {
1605 auto item = context.convertRvalueExpression(*args[1]);
1606 moore::QueuePushFrontOp::create(builder, loc, queue, item);
1607 return true;
1608 }
1609
1610 return false;
1611 }
1612
1613 // Associative array tasks
1614 if (args.size() >= 1 && args[0]->type->isAssociativeArray()) {
1615 auto assocArray = context.convertLvalueExpression(*args[0]);
1616
1617 // `delete` has two functions: If there is an index passed, then it
1618 // deletes that specific element, otherwise, it clears the entire
1619 // associative array.
1620 if (nameId == ksn::Delete) {
1621 if (args.size() == 1) {
1622 moore::AssocArrayClearOp::create(builder, loc, assocArray);
1623 return true;
1624 }
1625 if (args.size() == 2) {
1626 auto index = context.convertRvalueExpression(*args[1]);
1627 moore::AssocArrayDeleteOp::create(builder, loc, assocArray, index);
1628 return true;
1629 }
1630 }
1631 }
1632
1633 // Monitor enable/disable tasks (`$monitoron`, `$monitoroff`)
1634 if (nameId == ksn::MonitorOn || nameId == ksn::MonitorOff) {
1635 context.ensureMonitorGlobals();
1636 bool enable = (nameId == ksn::MonitorOn);
1637 auto enabledRef = moore::GetGlobalVariableOp::create(
1638 context.builder, loc, context.monitorEnabledGlobal);
1639 auto value = moore::ConstantOp::create(context.builder, loc,
1640 moore::Domain::TwoValued, enable);
1641 moore::BlockingAssignOp::create(context.builder, loc, enabledRef, value);
1642 return true;
1643 }
1644
1645 // Monitor tasks (`$monitor[boh]?`)
1646 if (nameId == ksn::Monitor || nameId == ksn::MonitorB ||
1647 nameId == ksn::MonitorO || nameId == ksn::MonitorH) {
1648 context.ensureMonitorGlobals();
1649
1650 // Allocate a unique ID for this monitor.
1651 unsigned myId = context.nextMonitorId++;
1652
1653 // Emit code to activate this monitor by setting the active_id global.
1654 auto i32Type = moore::IntType::getInt(context.getContext(), 32);
1655 auto idConst =
1656 moore::ConstantOp::create(context.builder, loc, i32Type, myId);
1657 auto activeRef = moore::GetGlobalVariableOp::create(
1658 context.builder, loc, context.monitorActiveIdGlobal);
1659 moore::BlockingAssignOp::create(context.builder, loc, activeRef, idConst);
1660
1661 // Queue this monitor for processing at module level.
1662 context.pendingMonitors.push_back({myId, loc, &expr});
1663
1664 return true;
1665 }
1666
1667 if (nameId == ksn::TimeFormat) {
1668 context.ensureTimeFormatGlobal();
1669 auto i32Ty = moore::IntType::getInt(context.getContext(), 32);
1670 auto strTy = moore::StringType::get(context.getContext());
1671
1672 if (args.empty()) {
1673 auto defaults = getDefaultTimeFormatValues(context.builder, loc,
1674 context.getContext());
1675 std::array<StringRef, 4> argNames = {"unit", "precision", "suffix",
1676 "min_width"};
1677 for (auto [name, value] : llvm::zip(argNames, defaults)) {
1678 auto base = moore::GetGlobalVariableOp::create(
1679 context.builder, loc, context.timeFormatGlobal);
1680 auto fieldRef = moore::StructExtractRefOp::create(
1681 context.builder, loc,
1682 moore::RefType::get(cast<moore::UnpackedType>(value.getType())),
1683 StringAttr::get(context.getContext(), name), base);
1684 moore::BlockingAssignOp::create(context.builder, loc, fieldRef,
1685 value);
1686 }
1687 return true;
1688 }
1689
1690 std::array<std::pair<StringRef, Type>, 4> argsTypes = {{
1691 {"unit", i32Ty},
1692 {"precision", i32Ty},
1693 {"suffix", strTy},
1694 {"min_width", i32Ty},
1695 }};
1696
1697 for (auto [i, arg] : llvm::enumerate(argsTypes)) {
1698 if (args.size() <= i)
1699 break;
1700 auto value = context.convertRvalueExpression(*args[i], arg.second);
1701 if (!value)
1702 return failure();
1703
1704 auto base = moore::GetGlobalVariableOp::create(
1705 context.builder, loc, context.timeFormatGlobal);
1706 auto fieldRef = moore::StructExtractRefOp::create(
1707 context.builder, loc,
1708 moore::RefType::get(cast<moore::UnpackedType>(arg.second)),
1709 StringAttr::get(context.getContext(), arg.first), base);
1710 moore::BlockingAssignOp::create(context.builder, loc, fieldRef, value);
1711 }
1712 return true;
1713 }
1714
1715 // Give up on any other system tasks. These will be tried again as an
1716 // expression later.
1717 return false;
1718 }
1719
1720 /// Create the optional diagnostic message print for finish-like ops.
1721 void createFinishMessage(const slang::ast::Expression *verbosityExpr) {
1722 unsigned verbosity = 1;
1723 if (verbosityExpr) {
1724 auto value =
1725 context.evaluateConstant(*verbosityExpr).integer().as<unsigned>();
1726 assert(value && "Slang guarantees constant verbosity parameter");
1727 verbosity = *value;
1728 }
1729 if (verbosity == 0)
1730 return;
1731 moore::FinishMessageBIOp::create(builder, loc, verbosity > 1);
1732 }
1733
1734 // Handle event trigger statements.
1735 LogicalResult visit(const slang::ast::EventTriggerStatement &stmt) {
1736 if (stmt.timing) {
1737 mlir::emitError(loc) << "unsupported delayed event trigger";
1738 return failure();
1739 }
1740
1741 // Events are lowered to `i1` signals. Get an lvalue ref to the signal such
1742 // that we can assign to it.
1743 auto target = context.convertLvalueExpression(stmt.target);
1744 if (!target)
1745 return failure();
1746
1747 // Read and invert the current value of the signal. Writing this inverted
1748 // value to the signal is our event signaling mechanism.
1749 Value inverted = moore::ReadOp::create(builder, loc, target);
1750 inverted = moore::NotOp::create(builder, loc, inverted);
1751
1752 if (stmt.isNonBlocking)
1753 moore::NonBlockingAssignOp::create(builder, loc, target, inverted);
1754 else
1755 moore::BlockingAssignOp::create(builder, loc, target, inverted);
1756 return success();
1757 }
1758
1759 // Handle `wait` statements
1760 LogicalResult visit(const slang::ast::WaitStatement &stmt) {
1761 auto waitOp = moore::WaitLevelOp::create(builder, loc);
1762 {
1763 OpBuilder::InsertionGuard guard(builder);
1764 builder.setInsertionPointToStart(&waitOp.getBody().emplaceBlock());
1765 auto cond = context.convertRvalueExpression(stmt.cond);
1766 if (!cond)
1767 return failure();
1768 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
1769 moore::DetectLevelOp::create(builder, loc, cond);
1770 }
1771 // Handle optional post-wait operation as if it were a separate statement
1772 if (failed(context.convertStatement(stmt.stmt)))
1773 return failure();
1774
1775 return success();
1776 }
1777
1778 LogicalResult visit(const slang::ast::WaitForkStatement &stmt) {
1779 moore::WaitForkOp::create(builder, loc);
1780 return success();
1781 }
1782
1783 /// Emit an error for all other statements.
1784 template <typename T>
1785 LogicalResult visit(T &&stmt) {
1786 mlir::emitError(loc, "unsupported statement: ")
1787 << slang::ast::toString(stmt.kind);
1788 return mlir::failure();
1789 }
1790
1791 LogicalResult visitInvalid(const slang::ast::Statement &stmt) {
1792 mlir::emitError(loc, "invalid statement: ")
1793 << slang::ast::toString(stmt.kind);
1794 return mlir::failure();
1795 }
1796};
1797} // namespace
1798
1799LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) {
1800 assert(builder.getInsertionBlock());
1801 auto loc = convertLocation(stmt.sourceRange);
1802 return stmt.visit(StmtVisitor(*this, loc));
1803}
1804// NOLINTEND(misc-no-recursion)
1805
1806//===----------------------------------------------------------------------===//
1807// Monitor support
1808//===----------------------------------------------------------------------===//
1809
1811 // If globals already exist, nothing to do.
1813 return;
1814
1815 // Save current builder position and insert at the start of the module.
1816 OpBuilder::InsertionGuard guard(builder);
1817 builder.setInsertionPointToStart(intoModuleOp.getBody());
1818
1819 auto loc = intoModuleOp.getLoc();
1820 auto i32Type = moore::IntType::getInt(getContext(), 32);
1821 auto i1Type = moore::IntType::getInt(getContext(), 1);
1822
1823 // Create "active_id" global variable. Index 0 indicates no monitor
1824 // is active.
1825 monitorActiveIdGlobal = moore::GlobalVariableOp::create(
1826 builder, loc, "__monitor_active_id", /*sym_visibility=*/{}, i32Type);
1827 {
1828 OpBuilder::InsertionGuard initGuard(builder);
1829 builder.setInsertionPointToStart(
1830 &monitorActiveIdGlobal.getInitRegion().emplaceBlock());
1831 auto zero = moore::ConstantOp::create(builder, loc, i32Type, 0);
1832 moore::YieldOp::create(builder, loc, zero);
1833 }
1835
1836 // Create "enabled" global variable.
1837 monitorEnabledGlobal = moore::GlobalVariableOp::create(
1838 builder, loc, "__monitor_enabled", /*sym_visibility=*/{}, i1Type);
1839 {
1840 OpBuilder::InsertionGuard initGuard(builder);
1841 builder.setInsertionPointToStart(
1842 &monitorEnabledGlobal.getInitRegion().emplaceBlock());
1843 auto trueVal =
1844 moore::ConstantOp::create(builder, loc, moore::Domain::TwoValued, true);
1845 moore::YieldOp::create(builder, loc, trueVal);
1846 }
1848}
1849
1851 using ksn = slang::parsing::KnownSystemName;
1852 for (auto &pending : pendingMonitors) {
1853 auto &call = *pending.call;
1854 auto loc = pending.loc;
1855
1856 // Extract the SystemCallInfo from the call's subroutine variant.
1857 auto &info =
1858 std::get<slang::ast::CallExpression::SystemCallInfo>(call.subroutine);
1859 auto nameId = info.subroutine->knownNameId;
1860
1861 // Determine the default format based on the system call name.
1862 auto defaultFormat = moore::IntFormat::Decimal;
1863 switch (nameId) {
1864 case ksn::MonitorB:
1865 defaultFormat = moore::IntFormat::Binary;
1866 break;
1867 case ksn::MonitorO:
1868 defaultFormat = moore::IntFormat::Octal;
1869 break;
1870 case ksn::MonitorH:
1871 defaultFormat = moore::IntFormat::HexLower;
1872 break;
1873 default:
1874 break;
1875 }
1876
1877 // Create an always_comb procedure for this monitor. This will implement the
1878 // semantics of printing an updated message whenever one of the input
1879 // signals changes.
1880 auto alwaysProc = moore::ProcedureOp::create(
1881 builder, loc, moore::ProcedureKind::AlwaysComb);
1882 OpBuilder::InsertionGuard guard(builder);
1883 builder.setInsertionPointToStart(&alwaysProc.getBody().emplaceBlock());
1884
1885 // Convert the format string and arguments.
1886 auto message = convertFormatString(call.arguments(), loc, defaultFormat,
1887 /*appendNewline=*/true);
1888 if (failed(message))
1889 return failure();
1890
1891 // Check if this monitor is active and enabled.
1892 auto i32Type = moore::IntType::getInt(getContext(), 32);
1893 auto myId = moore::ConstantOp::create(builder, loc, i32Type, pending.id);
1894 Value isActive =
1895 moore::GetGlobalVariableOp::create(builder, loc, monitorActiveIdGlobal);
1896 isActive = moore::ReadOp::create(builder, loc, isActive);
1897 isActive = moore::EqOp::create(builder, loc, isActive, myId);
1898
1899 Value enabled =
1900 moore::GetGlobalVariableOp::create(builder, loc, monitorEnabledGlobal);
1901 enabled = moore::ReadOp::create(builder, loc, enabled);
1902 enabled = moore::AndOp::create(builder, loc, isActive, enabled);
1903 enabled = moore::ToBuiltinIntOp::create(builder, loc, enabled);
1904
1905 // Branch to a print or skip block based on whether the monitor is enabled
1906 // or not.
1907 auto &printBlock = alwaysProc.getBody().emplaceBlock();
1908 auto &skipBlock = alwaysProc.getBody().emplaceBlock();
1909 cf::CondBranchOp::create(builder, loc, enabled, &printBlock, &skipBlock);
1910
1911 // Display the formatted message if one was created, and the monitor is
1912 // enabled.
1913 builder.setInsertionPointToStart(&printBlock);
1914 if (*message)
1915 moore::DisplayBIOp::create(builder, loc, *message);
1916 moore::ReturnOp::create(builder, loc);
1917
1918 // Otherwise just return.
1919 builder.setInsertionPointToStart(&skipBlock);
1920 moore::ReturnOp::create(builder, loc);
1921 }
1922
1923 pendingMonitors.clear();
1924 return success();
1925}
1926
1927//===----------------------------------------------------------------------===//
1928// Time format support
1929//===----------------------------------------------------------------------===//
1930
1932 if (timeFormatGlobal)
1933 return;
1934 OpBuilder::InsertionGuard guard(builder);
1935 builder.setInsertionPointToStart(intoModuleOp.getBody());
1936
1937 auto loc = intoModuleOp.getLoc();
1938 auto i32Ty = moore::IntType::getInt(getContext(), 32);
1939 auto strTy = moore::StringType::get(getContext());
1940
1941 SmallVector<moore::StructLikeMember> members{
1942 {StringAttr::get(getContext(), "unit"), i32Ty},
1943 {StringAttr::get(getContext(), "precision"), i32Ty},
1944 {StringAttr::get(getContext(), "suffix"), strTy},
1945 {StringAttr::get(getContext(), "min_width"), i32Ty},
1946 };
1947 auto structTy = moore::UnpackedStructType::get(getContext(), members);
1948
1949 timeFormatGlobal = moore::GlobalVariableOp::create(
1950 builder, loc, "__timeformat_state", /*sym_visibility=*/{}, structTy);
1951 {
1952 OpBuilder::InsertionGuard initGuard(builder);
1953 builder.setInsertionPointToStart(
1954 &timeFormatGlobal.getInitRegion().emplaceBlock());
1955 auto defaults = getDefaultTimeFormatValues(builder, loc, getContext());
1956 auto init = moore::StructCreateOp::create(builder, loc, structTy,
1957 ValueRange(defaults));
1958 moore::YieldOp::create(builder, loc, init);
1959 }
1961}
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.