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 {
138 Context &context;
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 case slang::ast::AssertionKind::CoverProperty:
1049 verif::CoverOp::create(builder, loc, property, enable, StringAttr{});
1050 return success();
1051 default:
1052 break;
1053 }
1054 mlir::emitError(loc) << "unsupported concurrent assertion kind: "
1055 << slang::ast::toString(stmt.assertionKind);
1056 return failure();
1057 }
1058
1059 mlir::emitError(loc)
1060 << "concurrent assertion statements with action blocks "
1061 "are not supported yet";
1062 return failure();
1063 }
1064
1065 // According to 1800-2023 Section 21.2.1 "The display and write tasks":
1066 // >> The $display and $write tasks display their arguments in the same
1067 // >> order as they appear in the argument list. Each argument can be a
1068 // >> string literal or an expression that returns a value.
1069 // According to Section 20.10 "Severity system tasks", the same
1070 // semantics apply to $fatal, $error, $warning, and $info.
1071 // This means we must first check whether the first "string-able"
1072 // argument is a Literal Expression which doesn't represent a fully-formatted
1073 // string, otherwise we convert it to a FormatStringType.
1074 FailureOr<Value>
1075 getDisplayMessage(std::span<const slang::ast::Expression *const> args) {
1076 if (args.size() == 0)
1077 return Value{};
1078
1079 // Handle the string formatting.
1080 // If the second argument is a Literal of some type, we should either
1081 // treat it as a literal-to-be-formatted or a FormatStringType.
1082 // In this check we use a StringLiteral, but slang allows casting between
1083 // any literal expressions (strings, integers, reals, and time at least) so
1084 // this is short-hand for "any value literal"
1085 if (args[0]->as_if<slang::ast::StringLiteral>()) {
1086 return context.convertFormatString(args, loc);
1087 }
1088 // Check if there's only one argument and it's a FormatStringType
1089 if (args.size() == 1) {
1090 return context.convertRvalueExpression(
1091 *args[0], builder.getType<moore::FormatStringType>());
1092 }
1093 // Otherwise this looks invalid. Raise an error.
1094 return emitError(loc) << "Failed to convert Display Message!";
1095 }
1096
1097 /// Convert a `$readmemb`/`$readmemh` system task call into a
1098 /// `moore.builtin.readmem` op. See IEEE 1800-2017 § 21.4.
1099 LogicalResult
1100 convertReadMemTask(std::span<const slang::ast::Expression *const> args,
1101 bool isBinary) {
1102 assert(args.size() >= 2 && args.size() <= 4 &&
1103 "$readmemh/$readmemb takes 2 to 4 arguments");
1104
1105 auto i32Ty = moore::IntType::getInt(builder.getContext(), 32);
1106 auto filename = context.convertRvalueExpression(
1107 *args[0], moore::StringType::get(builder.getContext()));
1108 if (!filename)
1109 return failure();
1110
1111 const auto *destExpr = args[1];
1112
1113 // Slang wraps the memory argument in an assignment to the lvalue;
1114 // unwrap it to get at the memory itslef.
1115 if (const auto *assign =
1116 destExpr->as_if<slang::ast::AssignmentExpression>())
1117 destExpr = &assign->left();
1118
1119 // The memory may use slice syntax on its rightmost specified dimension.
1120 // The slice only narrows the address window of the selected array's highest
1121 // dimension; the destination stays the full array.
1122 Value sliceLeft, sliceRight;
1123 if (const auto *rangeExpr =
1124 destExpr->as_if<slang::ast::RangeSelectExpression>()) {
1125 if (rangeExpr->getSelectionKind() !=
1126 slang::ast::RangeSelectionKind::Simple) {
1127 mlir::emitError(loc)
1128 << "unsupported: indexed part-select on $readmem memory";
1129 return failure();
1130 }
1131
1132 sliceLeft = context.convertRvalueExpression(rangeExpr->left(), i32Ty);
1133 sliceRight = context.convertRvalueExpression(rangeExpr->right(), i32Ty);
1134
1135 if (!sliceLeft || !sliceRight)
1136 return failure();
1137
1138 destExpr = &rangeExpr->value();
1139 }
1140
1141 auto dest = context.convertLvalueExpression(*destExpr);
1142 if (!dest)
1143 return failure();
1144
1145 // Collect the declared low bound and direction of every unpacked dimension
1146 // (outermost first): the Moore array types do not carry them, but the
1147 // row-major file layout (§21.4.3) and the address mapping of the lowering
1148 // depend on them. Queues load with their current size fixed (§21.4.1).
1149 const auto *curTy = &destExpr->type->getCanonicalType();
1150 SmallVector<int64_t> dimLows;
1151 SmallVector<bool> dimDescs;
1152 const slang::ast::Type *elemSvTy = curTy;
1153
1154 if (curTy->isAssociativeArray()) {
1155 mlir::emitError(loc) << "unsupported: $readmem into associative array";
1156 return failure();
1157 }
1158
1159 if (const auto *queueTy = curTy->as_if<slang::ast::QueueType>()) {
1160 dimLows.push_back(0);
1161 dimDescs.push_back(false);
1162 elemSvTy = &queueTy->elementType.getCanonicalType();
1163 } else if (curTy->as_if<slang::ast::DynamicArrayType>()) {
1164 mlir::emitError(loc) << "unsupported: $readmem into dynamic array";
1165 return failure();
1166 } else {
1167 while (const auto *fixedArr =
1168 curTy->as_if<slang::ast::FixedSizeUnpackedArrayType>()) {
1169 dimLows.push_back(fixedArr->range.lower());
1170 dimDescs.push_back(fixedArr->range.isDescending());
1171 curTy = &fixedArr->elementType.getCanonicalType();
1172 }
1173 elemSvTy = curTy;
1174 }
1175
1176 if (dimLows.empty()) {
1177 mlir::emitError(loc) << "$readmem memory must be an unpacked array";
1178 return failure();
1179 }
1180
1181 // The file contains binary or hexadecimal numbers, so elements must be
1182 // packed data.
1183 if (!elemSvTy->isIntegral()) {
1184 mlir::emitError(loc) << "unsupported: $readmem element type "
1185 << elemSvTy->toString();
1186 return failure();
1187 }
1188
1189 // Values outside the enumeration must be rejected during the load. Collect
1190 // the legal values so the lowering can check membership; wider enumerations
1191 // cannot be represented in the attribute.
1192 DenseI64ArrayAttr enumValuesAttr;
1193 if (const auto *enumTy = elemSvTy->as_if<slang::ast::EnumType>()) {
1194 if (enumTy->getBitWidth() > 64) {
1195 mlir::emitError(loc)
1196 << "unsupported: $readmem into enumeration wider than 64 bits";
1197 return failure();
1198 }
1199 SmallVector<int64_t> vals;
1200 for (const auto &ev : enumTy->values()) {
1201 auto v = ev.getValue().integer().as<int64_t>();
1202 if (!v) {
1203 mlir::emitError(loc)
1204 << "unsupported: $readmem enumeration value with unknown bits";
1205 return failure();
1206 }
1207 vals.push_back(*v);
1208 }
1209 enumValuesAttr = builder.getDenseI64ArrayAttr(vals);
1210 }
1211
1212 Value startAddr;
1213 if (args.size() >= 3 &&
1214 args[2]->kind != slang::ast::ExpressionKind::EmptyArgument) {
1215 startAddr = context.convertRvalueExpression(*args[2], i32Ty);
1216 if (!startAddr)
1217 return failure();
1218 }
1219
1220 Value finishAddr;
1221 if (args.size() >= 4 &&
1222 args[3]->kind != slang::ast::ExpressionKind::EmptyArgument) {
1223 finishAddr = context.convertRvalueExpression(*args[3], i32Ty);
1224 if (!finishAddr)
1225 return failure();
1226 }
1227
1228 auto base = isBinary ? moore::MemBase::Binary : moore::MemBase::Hex;
1229 moore::ReadMemBIOp::create(
1230 builder, loc, filename, dest,
1231 moore::MemBaseAttr::get(builder.getContext(), base), startAddr,
1232 finishAddr, sliceLeft, sliceRight,
1233 builder.getDenseI64ArrayAttr(dimLows),
1234 builder.getDenseBoolArrayAttr(dimDescs), enumValuesAttr);
1235 return success();
1236 }
1237
1238 /// Handle the subset of system calls that return no result value. Return
1239 /// true if the called system task could be handled, false otherwise. Return
1240 /// failure if an error occurred.
1241 FailureOr<bool>
1242 visitSystemCall(const slang::ast::ExpressionStatement &stmt,
1243 const slang::ast::CallExpression &expr,
1244 const slang::ast::CallExpression::SystemCallInfo &info) {
1245 using ksn = slang::parsing::KnownSystemName;
1246 const auto &subroutine = *info.subroutine;
1247 auto nameId = subroutine.knownNameId;
1248 auto args = expr.arguments();
1249
1250 // The `$cast` system call is handled by `Context::convertSystemCall` in the
1251 // `Expressions.cpp` file. Skip it is order to avoid visiting the
1252 // `EmptyArgument` node.
1253 if (nameId == ksn::Cast) {
1254 return false;
1255 }
1256
1257 // Simulation Control Tasks
1258
1259 if (nameId == ksn::Stop) {
1260 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
1261 moore::StopBIOp::create(builder, loc);
1262 return true;
1263 }
1264
1265 if (nameId == ksn::Finish) {
1266 createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
1267 moore::FinishBIOp::create(builder, loc, 0);
1268 moore::UnreachableOp::create(builder, loc);
1269 setTerminated();
1270 return true;
1271 }
1272
1273 if (nameId == ksn::Exit) {
1274 // Calls to `$exit` from outside a `program` are ignored. Since we don't
1275 // yet support programs, there is nothing to do here.
1276 // TODO: Fix this once we support programs.
1277 return true;
1278 }
1279
1280 // Timescale tasks (`$printtimescale`)
1281
1282 if (nameId == ksn::PrintTimeScale) {
1283 auto message = moore::FormatLiteralOp::create(
1284 builder, loc, buildPrintTimeScaleMessage(context, args));
1285 moore::DisplayBIOp::create(builder, loc, message);
1286 return true;
1287 }
1288
1289 // Display and Write Tasks (`$display[boh]?` or `$write[boh]?` or
1290 // `$fdisplay[boh]?` or `$fwrite[boh]?` or `$swrite[boh]` or `$sformat`)
1291
1292 using moore::IntFormat;
1293 bool isDisplay = false;
1294 bool isFDisplay = false;
1295 bool isSWrite = false;
1296 bool isSFormat = false;
1297 bool appendNewline = false;
1298 IntFormat defaultFormat = IntFormat::Decimal;
1299 switch (nameId) {
1300 case ksn::Display:
1301 isDisplay = true;
1302 appendNewline = true;
1303 break;
1304 case ksn::DisplayB:
1305 isDisplay = true;
1306 appendNewline = true;
1307 defaultFormat = IntFormat::Binary;
1308 break;
1309 case ksn::DisplayO:
1310 isDisplay = true;
1311 appendNewline = true;
1312 defaultFormat = IntFormat::Octal;
1313 break;
1314 case ksn::DisplayH:
1315 isDisplay = true;
1316 appendNewline = true;
1317 defaultFormat = IntFormat::HexLower;
1318 break;
1319 case ksn::Write:
1320 isDisplay = true;
1321 break;
1322 case ksn::WriteB:
1323 isDisplay = true;
1324 defaultFormat = IntFormat::Binary;
1325 break;
1326 case ksn::WriteO:
1327 isDisplay = true;
1328 defaultFormat = IntFormat::Octal;
1329 break;
1330 case ksn::WriteH:
1331 isDisplay = true;
1332 defaultFormat = IntFormat::HexLower;
1333 break;
1334 case ksn::FDisplay:
1335 isFDisplay = true;
1336 appendNewline = true;
1337 break;
1338 case ksn::FDisplayB:
1339 isFDisplay = true;
1340 appendNewline = true;
1341 defaultFormat = IntFormat::Binary;
1342 break;
1343 case ksn::FDisplayO:
1344 isFDisplay = true;
1345 appendNewline = true;
1346 defaultFormat = IntFormat::Octal;
1347 break;
1348 case ksn::FDisplayH:
1349 isFDisplay = true;
1350 appendNewline = true;
1351 defaultFormat = IntFormat::HexLower;
1352 break;
1353 case ksn::FWrite:
1354 isFDisplay = true;
1355 break;
1356 case ksn::FWriteB:
1357 isFDisplay = true;
1358 defaultFormat = IntFormat::Binary;
1359 break;
1360 case ksn::FWriteO:
1361 isFDisplay = true;
1362 defaultFormat = IntFormat::Octal;
1363 break;
1364 case ksn::FWriteH:
1365 isFDisplay = true;
1366 defaultFormat = IntFormat::HexLower;
1367 break;
1368 case ksn::SFormat:
1369 isSFormat = true;
1370 break;
1371 case ksn::SWrite:
1372 isSWrite = true;
1373 break;
1374 case ksn::SWriteB:
1375 isSWrite = true;
1376 defaultFormat = IntFormat::Binary;
1377 break;
1378 case ksn::SWriteO:
1379 isSWrite = true;
1380 defaultFormat = IntFormat::Octal;
1381 break;
1382 case ksn::SWriteH:
1383 isSWrite = true;
1384 defaultFormat = IntFormat::HexLower;
1385 break;
1386 default:
1387 break;
1388 }
1389
1390 if (isDisplay) {
1391 auto message =
1392 context.convertFormatString(args, loc, defaultFormat, appendNewline);
1393 if (failed(message))
1394 return failure();
1395 if (*message == Value{})
1396 return true;
1397 moore::DisplayBIOp::create(builder, loc, *message);
1398 return true;
1399 }
1400
1401 if (isFDisplay) {
1402 assert(!args.empty() && "$fdisplay/$fwrite takes at least 1 argument");
1403
1404 auto fd = context.convertRvalueExpression(
1405 *args[0], moore::IntType::getInt(builder.getContext(), 32));
1406 if (!fd)
1407 return failure();
1408 args = args.subspan(1);
1409
1410 auto message =
1411 context.convertFormatString(args, loc, defaultFormat, appendNewline);
1412 if (failed(message))
1413 return failure();
1414 if (*message == Value{})
1415 return true;
1416 moore::FDisplayBIOp::create(builder, loc, fd, *message);
1417 return true;
1418 }
1419
1420 // According to IEEE 1800-2023 Section 21.3.3 "Formatting data to a
1421 // string" the first argument of $sformat/$swrite is its output; the
1422 // other arguments work like a FormatString.
1423 // In Moore we only support writing to a location if it is a reference;
1424 // However, Section 21.3.3 explains that the output of $sformat/$swrite
1425 // is assigned as if it were cast from a string literal (Section 5.9),
1426 // so this implementation casts the string to the target value.
1427 if (isSWrite || isSFormat) {
1428 if (isSFormat && args.size() < 2)
1429 return emitError(loc) << "$sformat requires at least 2 arguments";
1430 if (isSWrite && args.size() < 1)
1431 return emitError(loc) << "$swrite requires at least 1 argument";
1432
1433 auto fmtValue =
1434 context.convertFormatString(args.subspan(1), loc, defaultFormat,
1435 /*appendNewline=*/false);
1436 if (failed(fmtValue))
1437 return failure();
1438 if (*fmtValue == Value{})
1439 return true;
1440 auto strValue =
1441 moore::FormatStringToStringOp::create(builder, loc, *fmtValue);
1442 auto *lhsExpr = args[0];
1443 if (auto *assignExpr =
1444 lhsExpr->as_if<slang::ast::AssignmentExpression>()) {
1445 auto lhs = context.convertLvalueExpression(assignExpr->left());
1446 if (!lhs)
1447 return failure();
1448 auto convertedValue = context.materializeConversion(
1449 cast<moore::RefType>(lhs.getType()).getNestedType(), strValue,
1450 false, loc);
1451 if (!convertedValue)
1452 return failure();
1453 moore::BlockingAssignOp::create(builder, loc, lhs, convertedValue);
1454 return true;
1455 }
1456 return failure();
1457 }
1458
1459 // Severity Tasks
1460 using moore::Severity;
1461 std::optional<Severity> severity;
1462 if (nameId == ksn::Info)
1463 severity = Severity::Info;
1464 else if (nameId == ksn::Warning)
1465 severity = Severity::Warning;
1466 else if (nameId == ksn::Error)
1467 severity = Severity::Error;
1468 else if (nameId == ksn::Fatal)
1469 severity = Severity::Fatal;
1470
1471 if (severity) {
1472 // The `$fatal` task has an optional leading verbosity argument.
1473 const slang::ast::Expression *verbosityExpr = nullptr;
1474 if (severity == Severity::Fatal && args.size() >= 1) {
1475 verbosityExpr = args[0];
1476 args = args.subspan(1);
1477 }
1478
1479 FailureOr<Value> maybeMessage = getDisplayMessage(args);
1480 if (failed(maybeMessage))
1481 return failure();
1482 auto message = maybeMessage.value();
1483
1484 if (message == Value{})
1485 message = moore::FormatLiteralOp::create(builder, loc, "");
1486 moore::SeverityBIOp::create(builder, loc, *severity, message);
1487
1488 // Handle the `$fatal` case which behaves like a `$finish`.
1489 if (severity == Severity::Fatal) {
1490 createFinishMessage(verbosityExpr);
1491 moore::FinishBIOp::create(builder, loc, 1);
1492 moore::UnreachableOp::create(builder, loc);
1493 setTerminated();
1494 }
1495 return true;
1496 }
1497
1498 // File I/O Tasks
1499
1500 if (nameId == ksn::FClose) {
1501 assert(args.size() == 1 && "$fclose takes 1 argument");
1502 auto fd = context.convertRvalueExpression(
1503 *args[0], moore::IntType::getInt(builder.getContext(), 32));
1504 if (!fd)
1505 return failure();
1506 moore::FCloseBIOp::create(builder, loc, fd);
1507 return true;
1508 }
1509
1510 if (nameId == ksn::FFlush) {
1511 assert(args.size() <= 1 && "$fflush takes at most 1 argument");
1512 Value fd;
1513 if (args.size() == 1) {
1514 fd = context.convertRvalueExpression(
1515 *args[0], moore::IntType::getInt(builder.getContext(), 32));
1516 if (!fd)
1517 return failure();
1518 }
1519 moore::FFlushBIOp::create(builder, loc, fd);
1520 return true;
1521 }
1522
1523 if (nameId == ksn::ReadMemH || nameId == ksn::ReadMemB) {
1524 if (failed(convertReadMemTask(args, nameId == ksn::ReadMemB)))
1525 return failure();
1526 return true;
1527 }
1528
1529 // String Tasks
1530 if (args.size() >= 1 && args[0]->type->isString()) {
1531 auto str = context.convertLvalueExpression(*args[0]);
1532
1533 if (nameId == ksn::Putc) {
1534 // Slang already checks the arity of string tasks.
1535 assert(args.size() == 3 && "`putc` takes 3 arguments");
1536 auto index = context.convertRvalueExpression(*args[1]);
1537 auto character = context.convertRvalueExpression(*args[2]);
1538 moore::StringPutOp::create(builder, loc, str, index, character);
1539 return true;
1540 }
1541
1542 if (nameId == ksn::IToA || nameId == ksn::HexToA ||
1543 nameId == ksn::OctToA || nameId == ksn::BinToA) {
1544 // Slang already checks the arity of string tasks.
1545 assert(args.size() == 2 && "`itoa/hex/oct/bin` takes 2 arguments");
1546 auto integerType = moore::IntType::getLogic(builder.getContext(), 32);
1547 auto input = context.convertRvalueExpression(*args[1], integerType);
1548
1549 switch (nameId) {
1550 case ksn::IToA:
1551 moore::StringItoaOp::create(builder, loc, str, input);
1552 break;
1553 case ksn::HexToA:
1554 moore::StringHextoaOp::create(builder, loc, str, input);
1555 break;
1556 case ksn::OctToA:
1557 moore::StringOcttoaOp::create(builder, loc, str, input);
1558 break;
1559 case ksn::BinToA:
1560 moore::StringBintoaOp::create(builder, loc, str, input);
1561 break;
1562 default:
1563 llvm_unreachable("unexpected ASCII integer to string conversion");
1564 return false;
1565 }
1566 return true;
1567 }
1568
1569 if (nameId == ksn::RealToA) {
1570 // Slang already checks the arity of string tasks.
1571 assert(args.size() == 2 && "`realtoa` takes 2 arguments");
1572 auto realType =
1573 moore::RealType::get(context.getContext(), moore::RealWidth::f64);
1574 auto input = context.convertRvalueExpression(*args[1], realType);
1575 moore::StringRealtoaOp::create(builder, loc, str, input);
1576 return true;
1577 }
1578 return false;
1579 }
1580
1581 // Queue Tasks
1582 if (args.size() >= 1 && args[0]->type->isQueue()) {
1583 auto queue = context.convertLvalueExpression(*args[0]);
1584
1585 // `delete` has two functions: If there is an index passed, then it
1586 // deletes that specific element, otherwise, it clears the entire queue.
1587 if (nameId == ksn::Delete) {
1588 if (args.size() == 1) {
1589 moore::QueueClearOp::create(builder, loc, queue);
1590 return true;
1591 }
1592 if (args.size() == 2) {
1593 auto index = context.convertRvalueExpression(*args[1]);
1594 moore::QueueDeleteOp::create(builder, loc, queue, index);
1595 return true;
1596 }
1597 } else if (nameId == ksn::Insert && args.size() == 3) {
1598 auto index = context.convertRvalueExpression(*args[1]);
1599 auto item = context.convertRvalueExpression(*args[2]);
1600
1601 moore::QueueInsertOp::create(builder, loc, queue, index, item);
1602 return true;
1603 } else if (nameId == ksn::PushBack && args.size() == 2) {
1604 auto item = context.convertRvalueExpression(*args[1]);
1605 moore::QueuePushBackOp::create(builder, loc, queue, item);
1606 return true;
1607 } else if (nameId == ksn::PushFront && args.size() == 2) {
1608 auto item = context.convertRvalueExpression(*args[1]);
1609 moore::QueuePushFrontOp::create(builder, loc, queue, item);
1610 return true;
1611 }
1612
1613 return false;
1614 }
1615
1616 // Associative array tasks
1617 if (args.size() >= 1 && args[0]->type->isAssociativeArray()) {
1618 auto assocArray = context.convertLvalueExpression(*args[0]);
1619
1620 // `delete` has two functions: If there is an index passed, then it
1621 // deletes that specific element, otherwise, it clears the entire
1622 // associative array.
1623 if (nameId == ksn::Delete) {
1624 if (args.size() == 1) {
1625 moore::AssocArrayClearOp::create(builder, loc, assocArray);
1626 return true;
1627 }
1628 if (args.size() == 2) {
1629 auto index = context.convertRvalueExpression(*args[1]);
1630 moore::AssocArrayDeleteOp::create(builder, loc, assocArray, index);
1631 return true;
1632 }
1633 }
1634 }
1635
1636 // Monitor enable/disable tasks (`$monitoron`, `$monitoroff`)
1637 if (nameId == ksn::MonitorOn || nameId == ksn::MonitorOff) {
1638 context.ensureMonitorGlobals();
1639 bool enable = (nameId == ksn::MonitorOn);
1640 auto enabledRef = moore::GetGlobalVariableOp::create(
1641 context.builder, loc, context.monitorEnabledGlobal);
1642 auto value = moore::ConstantOp::create(context.builder, loc,
1643 moore::Domain::TwoValued, enable);
1644 moore::BlockingAssignOp::create(context.builder, loc, enabledRef, value);
1645 return true;
1646 }
1647
1648 // Monitor tasks (`$monitor[boh]?`)
1649 if (nameId == ksn::Monitor || nameId == ksn::MonitorB ||
1650 nameId == ksn::MonitorO || nameId == ksn::MonitorH) {
1651 context.ensureMonitorGlobals();
1652
1653 // Allocate a unique ID for this monitor.
1654 unsigned myId = context.nextMonitorId++;
1655
1656 // Emit code to activate this monitor by setting the active_id global.
1657 auto i32Type = moore::IntType::getInt(context.getContext(), 32);
1658 auto idConst =
1659 moore::ConstantOp::create(context.builder, loc, i32Type, myId);
1660 auto activeRef = moore::GetGlobalVariableOp::create(
1661 context.builder, loc, context.monitorActiveIdGlobal);
1662 moore::BlockingAssignOp::create(context.builder, loc, activeRef, idConst);
1663
1664 // Queue this monitor for processing at module level.
1665 context.pendingMonitors.push_back({myId, loc, &expr});
1666
1667 return true;
1668 }
1669
1670 if (nameId == ksn::TimeFormat) {
1671 context.ensureTimeFormatGlobal();
1672 auto i32Ty = moore::IntType::getInt(context.getContext(), 32);
1673 auto strTy = moore::StringType::get(context.getContext());
1674
1675 if (args.empty()) {
1676 auto defaults = getDefaultTimeFormatValues(context.builder, loc,
1677 context.getContext());
1678 std::array<StringRef, 4> argNames = {"unit", "precision", "suffix",
1679 "min_width"};
1680 for (auto [name, value] : llvm::zip(argNames, defaults)) {
1681 auto base = moore::GetGlobalVariableOp::create(
1682 context.builder, loc, context.timeFormatGlobal);
1683 auto fieldRef = moore::StructExtractRefOp::create(
1684 context.builder, loc,
1685 moore::RefType::get(cast<moore::UnpackedType>(value.getType())),
1686 StringAttr::get(context.getContext(), name), base);
1687 moore::BlockingAssignOp::create(context.builder, loc, fieldRef,
1688 value);
1689 }
1690 return true;
1691 }
1692
1693 std::array<std::pair<StringRef, Type>, 4> argsTypes = {{
1694 {"unit", i32Ty},
1695 {"precision", i32Ty},
1696 {"suffix", strTy},
1697 {"min_width", i32Ty},
1698 }};
1699
1700 for (auto [i, arg] : llvm::enumerate(argsTypes)) {
1701 if (args.size() <= i)
1702 break;
1703 auto value = context.convertRvalueExpression(*args[i], arg.second);
1704 if (!value)
1705 return failure();
1706
1707 auto base = moore::GetGlobalVariableOp::create(
1708 context.builder, loc, context.timeFormatGlobal);
1709 auto fieldRef = moore::StructExtractRefOp::create(
1710 context.builder, loc,
1711 moore::RefType::get(cast<moore::UnpackedType>(arg.second)),
1712 StringAttr::get(context.getContext(), arg.first), base);
1713 moore::BlockingAssignOp::create(context.builder, loc, fieldRef, value);
1714 }
1715 return true;
1716 }
1717
1718 // Give up on any other system tasks. These will be tried again as an
1719 // expression later.
1720 return false;
1721 }
1722
1723 /// Create the optional diagnostic message print for finish-like ops.
1724 void createFinishMessage(const slang::ast::Expression *verbosityExpr) {
1725 unsigned verbosity = 1;
1726 if (verbosityExpr) {
1727 auto value =
1728 context.evaluateConstant(*verbosityExpr).integer().as<unsigned>();
1729 assert(value && "Slang guarantees constant verbosity parameter");
1730 verbosity = *value;
1731 }
1732 if (verbosity == 0)
1733 return;
1734 moore::FinishMessageBIOp::create(builder, loc, verbosity > 1);
1735 }
1736
1737 // Handle event trigger statements.
1738 LogicalResult visit(const slang::ast::EventTriggerStatement &stmt) {
1739 if (stmt.timing) {
1740 mlir::emitError(loc) << "unsupported delayed event trigger";
1741 return failure();
1742 }
1743
1744 // Events are lowered to `i1` signals. Get an lvalue ref to the signal such
1745 // that we can assign to it.
1746 auto target = context.convertLvalueExpression(stmt.target);
1747 if (!target)
1748 return failure();
1749
1750 // Read and invert the current value of the signal. Writing this inverted
1751 // value to the signal is our event signaling mechanism.
1752 Value inverted = moore::ReadOp::create(builder, loc, target);
1753 inverted = moore::NotOp::create(builder, loc, inverted);
1754
1755 if (stmt.isNonBlocking)
1756 moore::NonBlockingAssignOp::create(builder, loc, target, inverted);
1757 else
1758 moore::BlockingAssignOp::create(builder, loc, target, inverted);
1759 return success();
1760 }
1761
1762 // Handle `wait` statements
1763 LogicalResult visit(const slang::ast::WaitStatement &stmt) {
1764 auto waitOp = moore::WaitLevelOp::create(builder, loc);
1765 {
1766 OpBuilder::InsertionGuard guard(builder);
1767 builder.setInsertionPointToStart(&waitOp.getBody().emplaceBlock());
1768 auto cond = context.convertRvalueExpression(stmt.cond);
1769 if (!cond)
1770 return failure();
1771 cond = builder.createOrFold<moore::BoolCastOp>(loc, cond);
1772 moore::DetectLevelOp::create(builder, loc, cond);
1773 }
1774 // Handle optional post-wait operation as if it were a separate statement
1775 if (failed(context.convertStatement(stmt.stmt)))
1776 return failure();
1777
1778 return success();
1779 }
1780
1781 LogicalResult visit(const slang::ast::WaitForkStatement &stmt) {
1782 moore::WaitForkOp::create(builder, loc);
1783 return success();
1784 }
1785
1786 /// Emit an error for all other statements.
1787 template <typename T>
1788 LogicalResult visit(T &&stmt) {
1789 mlir::emitError(loc, "unsupported statement: ")
1790 << slang::ast::toString(stmt.kind);
1791 return mlir::failure();
1792 }
1793
1794 LogicalResult visitInvalid(const slang::ast::Statement &stmt) {
1795 mlir::emitError(loc, "invalid statement: ")
1796 << slang::ast::toString(stmt.kind);
1797 return mlir::failure();
1798 }
1799};
1800} // namespace
1801
1802LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) {
1803 assert(builder.getInsertionBlock());
1804 auto loc = convertLocation(stmt.sourceRange);
1805 return stmt.visit(StmtVisitor(*this, loc));
1806}
1807// NOLINTEND(misc-no-recursion)
1808
1809//===----------------------------------------------------------------------===//
1810// Monitor support
1811//===----------------------------------------------------------------------===//
1812
1814 // If globals already exist, nothing to do.
1816 return;
1817
1818 // Save current builder position and insert at the start of the module.
1819 OpBuilder::InsertionGuard guard(builder);
1820 builder.setInsertionPointToStart(intoModuleOp.getBody());
1821
1822 auto loc = intoModuleOp.getLoc();
1823 auto i32Type = moore::IntType::getInt(getContext(), 32);
1824 auto i1Type = moore::IntType::getInt(getContext(), 1);
1825
1826 // Create "active_id" global variable. Index 0 indicates no monitor
1827 // is active.
1828 monitorActiveIdGlobal = moore::GlobalVariableOp::create(
1829 builder, loc, "__monitor_active_id", /*sym_visibility=*/{}, i32Type);
1830 {
1831 OpBuilder::InsertionGuard initGuard(builder);
1832 builder.setInsertionPointToStart(
1833 &monitorActiveIdGlobal.getInitRegion().emplaceBlock());
1834 auto zero = moore::ConstantOp::create(builder, loc, i32Type, 0);
1835 moore::YieldOp::create(builder, loc, zero);
1836 }
1838
1839 // Create "enabled" global variable.
1840 monitorEnabledGlobal = moore::GlobalVariableOp::create(
1841 builder, loc, "__monitor_enabled", /*sym_visibility=*/{}, i1Type);
1842 {
1843 OpBuilder::InsertionGuard initGuard(builder);
1844 builder.setInsertionPointToStart(
1845 &monitorEnabledGlobal.getInitRegion().emplaceBlock());
1846 auto trueVal =
1847 moore::ConstantOp::create(builder, loc, moore::Domain::TwoValued, true);
1848 moore::YieldOp::create(builder, loc, trueVal);
1849 }
1851}
1852
1854 using ksn = slang::parsing::KnownSystemName;
1855 for (auto &pending : pendingMonitors) {
1856 auto &call = *pending.call;
1857 auto loc = pending.loc;
1858
1859 // Extract the SystemCallInfo from the call's subroutine variant.
1860 auto &info =
1861 std::get<slang::ast::CallExpression::SystemCallInfo>(call.subroutine);
1862 auto nameId = info.subroutine->knownNameId;
1863
1864 // Determine the default format based on the system call name.
1865 auto defaultFormat = moore::IntFormat::Decimal;
1866 switch (nameId) {
1867 case ksn::MonitorB:
1868 defaultFormat = moore::IntFormat::Binary;
1869 break;
1870 case ksn::MonitorO:
1871 defaultFormat = moore::IntFormat::Octal;
1872 break;
1873 case ksn::MonitorH:
1874 defaultFormat = moore::IntFormat::HexLower;
1875 break;
1876 default:
1877 break;
1878 }
1879
1880 // Create an always_comb procedure for this monitor. This will implement the
1881 // semantics of printing an updated message whenever one of the input
1882 // signals changes.
1883 auto alwaysProc = moore::ProcedureOp::create(
1884 builder, loc, moore::ProcedureKind::AlwaysComb);
1885 OpBuilder::InsertionGuard guard(builder);
1886 builder.setInsertionPointToStart(&alwaysProc.getBody().emplaceBlock());
1887
1888 // Convert the format string and arguments.
1889 auto message = convertFormatString(call.arguments(), loc, defaultFormat,
1890 /*appendNewline=*/true);
1891 if (failed(message))
1892 return failure();
1893
1894 // Check if this monitor is active and enabled.
1895 auto i32Type = moore::IntType::getInt(getContext(), 32);
1896 auto myId = moore::ConstantOp::create(builder, loc, i32Type, pending.id);
1897 Value isActive =
1898 moore::GetGlobalVariableOp::create(builder, loc, monitorActiveIdGlobal);
1899 isActive = moore::ReadOp::create(builder, loc, isActive);
1900 isActive = moore::EqOp::create(builder, loc, isActive, myId);
1901
1902 Value enabled =
1903 moore::GetGlobalVariableOp::create(builder, loc, monitorEnabledGlobal);
1904 enabled = moore::ReadOp::create(builder, loc, enabled);
1905 enabled = moore::AndOp::create(builder, loc, isActive, enabled);
1906 enabled = moore::ToBuiltinIntOp::create(builder, loc, enabled);
1907
1908 // Branch to a print or skip block based on whether the monitor is enabled
1909 // or not.
1910 auto &printBlock = alwaysProc.getBody().emplaceBlock();
1911 auto &skipBlock = alwaysProc.getBody().emplaceBlock();
1912 cf::CondBranchOp::create(builder, loc, enabled, &printBlock, &skipBlock);
1913
1914 // Display the formatted message if one was created, and the monitor is
1915 // enabled.
1916 builder.setInsertionPointToStart(&printBlock);
1917 if (*message)
1918 moore::DisplayBIOp::create(builder, loc, *message);
1919 moore::ReturnOp::create(builder, loc);
1920
1921 // Otherwise just return.
1922 builder.setInsertionPointToStart(&skipBlock);
1923 moore::ReturnOp::create(builder, loc);
1924 }
1925
1926 pendingMonitors.clear();
1927 return success();
1928}
1929
1930//===----------------------------------------------------------------------===//
1931// Time format support
1932//===----------------------------------------------------------------------===//
1933
1935 if (timeFormatGlobal)
1936 return;
1937 OpBuilder::InsertionGuard guard(builder);
1938 builder.setInsertionPointToStart(intoModuleOp.getBody());
1939
1940 auto loc = intoModuleOp.getLoc();
1941 auto i32Ty = moore::IntType::getInt(getContext(), 32);
1942 auto strTy = moore::StringType::get(getContext());
1943
1944 SmallVector<moore::StructLikeMember> members{
1945 {StringAttr::get(getContext(), "unit"), i32Ty},
1946 {StringAttr::get(getContext(), "precision"), i32Ty},
1947 {StringAttr::get(getContext(), "suffix"), strTy},
1948 {StringAttr::get(getContext(), "min_width"), i32Ty},
1949 };
1950 auto structTy = moore::UnpackedStructType::get(getContext(), members);
1951
1952 timeFormatGlobal = moore::GlobalVariableOp::create(
1953 builder, loc, "__timeformat_state", /*sym_visibility=*/{}, structTy);
1954 {
1955 OpBuilder::InsertionGuard initGuard(builder);
1956 builder.setInsertionPointToStart(
1957 &timeFormatGlobal.getInitRegion().emplaceBlock());
1958 auto defaults = getDefaultTimeFormatValues(builder, loc, getContext());
1959 auto init = moore::StructCreateOp::create(builder, loc, structTy,
1960 ValueRange(defaults));
1961 moore::YieldOp::create(builder, loc, init);
1962 }
1964}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
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
@ FourValued
Four-valued types such as logic or integer.
Definition MooreTypes.h:55
@ TwoValued
Two-valued types such as bit or int.
Definition MooreTypes.h:53
@ f64
A 64-bit double-precision floation point number ("double").
Definition MooreTypes.h:63
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.
LogicalResult convertStatement(const slang::ast::Statement &stmt)
Location convertLocation(slang::SourceLocation loc)
Convert a slang SourceLocation into an MLIR Location.