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