CIRCT 23.0.0git
Loading...
Searching...
No Matches
Expressions.cpp
Go to the documentation of this file.
1//===- Expressions.cpp - Slang expression 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
13#include "circt/Support/FVInt.h"
14#include "mlir/IR/Operation.h"
15#include "mlir/IR/Value.h"
16#include "slang/ast/EvalContext.h"
17#include "slang/ast/SystemSubroutine.h"
18#include "slang/ast/types/AllTypes.h"
19#include "slang/syntax/AllSyntax.h"
20#include "llvm/ADT/ScopeExit.h"
21#include "llvm/ADT/StringExtras.h"
22#include "llvm/Support/SaveAndRestore.h"
23
24using namespace circt;
25using namespace ImportVerilog;
26using moore::Domain;
27
28/// Convert a Slang `SVInt` to a CIRCT `FVInt`.
29static FVInt convertSVIntToFVInt(const slang::SVInt &svint) {
30 if (svint.hasUnknown()) {
31 unsigned numWords = svint.getNumWords() / 2;
32 auto value = ArrayRef<uint64_t>(svint.getRawPtr(), numWords);
33 auto unknown = ArrayRef<uint64_t>(svint.getRawPtr() + numWords, numWords);
34 return FVInt(APInt(svint.getBitWidth(), value),
35 APInt(svint.getBitWidth(), unknown));
36 }
37 auto value = ArrayRef<uint64_t>(svint.getRawPtr(), svint.getNumWords());
38 return FVInt(APInt(svint.getBitWidth(), value));
39}
40
41/// Check if a Moore integer value contains any unknown (x/z) bits.
42/// Returns a Moore i1 result: 1 if any bit is unknown, 0 otherwise.
43static Value getIsUnknown(OpBuilder &builder, Location loc, Value value,
44 moore::IntType valTy, MLIRContext *ctx) {
45 Value bitVal = value;
46 if (valTy.getWidth() > 1) {
47 auto mooreI1Type = moore::IntType::get(ctx, 1, valTy.getDomain());
48 bitVal = moore::ReduceXorOp::create(builder, loc, mooreI1Type, value);
49 }
50 auto xType = moore::IntType::get(ctx, 1, moore::Domain::FourValued);
51 auto xConst =
52 moore::ConstantOp::create(builder, loc, xType, FVInt::getAllX(1));
53 return moore::CaseEqOp::create(builder, loc, bitVal, xConst).getResult();
54}
55
56/// Coerce a Moore integer value to a builtin integer, handling four-valued
57/// inputs by first mapping x/z to 0 via LogicToIntOp.
58static Value coerceToBuiltinInt(OpBuilder &builder, Location loc, Value value,
59 moore::IntType valTy) {
60 if (valTy.getDomain() == moore::Domain::FourValued)
61 value = builder.createOrFold<moore::LogicToIntOp>(loc, value);
62 return builder.createOrFold<moore::ToBuiltinIntOp>(loc, value);
63}
64
65Value ImportVerilog::getSelectIndex(Context &context, Location loc, Value index,
66 const slang::ConstantRange &range) {
67 auto &builder = context.builder;
68 auto indexType = cast<moore::UnpackedType>(index.getType());
69
70 // Compute offset first so we know if it is negative.
71 auto lo = range.lower();
72 auto hi = range.upper();
73 auto offset = range.isDescending() ? lo : hi;
74
75 // If any bound is negative we need a signed index type.
76 const bool needSigned = (lo < 0) || (hi < 0);
77
78 // Magnitude over full range, not just the chosen offset.
79 const uint64_t maxAbs = std::max<uint64_t>(std::abs(lo), std::abs(hi));
80
81 // Bits needed from the range:
82 // - unsigned: ceil(log2(maxAbs + 1)) (ensure at least 1)
83 // - signed: ceil(log2(maxAbs)) + 1 sign bit (ensure at least 2 when neg)
84 unsigned want = needSigned
85 ? (llvm::Log2_64_Ceil(std::max<uint64_t>(1, maxAbs)) + 1)
86 : std::max<unsigned>(1, llvm::Log2_64_Ceil(maxAbs + 1));
87
88 // Keep at least as wide as the incoming index.
89 const unsigned bw = std::max<unsigned>(want, indexType.getBitSize().value());
90
91 auto intType =
92 moore::IntType::get(index.getContext(), bw, indexType.getDomain());
93 index = context.materializeConversion(intType, index, needSigned, loc);
94
95 if (offset == 0) {
96 if (range.isDescending())
97 return index;
98 else
99 return moore::NegOp::create(builder, loc, index);
100 }
101
102 auto offsetConst =
103 moore::ConstantOp::create(builder, loc, intType, offset, needSigned);
104 if (range.isDescending())
105 return moore::SubOp::create(builder, loc, index, offsetConst);
106 else
107 return moore::SubOp::create(builder, loc, offsetConst, index);
108}
109
110/// Get the currently active timescale as an integer number of femtoseconds.
112 static_assert(int(slang::TimeUnit::Seconds) == 0);
113 static_assert(int(slang::TimeUnit::Milliseconds) == 1);
114 static_assert(int(slang::TimeUnit::Microseconds) == 2);
115 static_assert(int(slang::TimeUnit::Nanoseconds) == 3);
116 static_assert(int(slang::TimeUnit::Picoseconds) == 4);
117 static_assert(int(slang::TimeUnit::Femtoseconds) == 5);
118
119 static_assert(int(slang::TimeScaleMagnitude::One) == 1);
120 static_assert(int(slang::TimeScaleMagnitude::Ten) == 10);
121 static_assert(int(slang::TimeScaleMagnitude::Hundred) == 100);
122
123 auto exp = static_cast<unsigned>(context.timeScale.base.unit);
124 assert(exp <= 5);
125 exp = 5 - exp;
126 auto scale = static_cast<uint64_t>(context.timeScale.base.magnitude);
127 while (exp-- > 0)
128 scale *= 1000;
129 return scale;
130}
131
132/// Resolve a hierarchical value that refers to a member of an expanded
133/// interface instance.
135 Context &context, const slang::ast::HierarchicalValueExpression &expr) {
136 auto nameAttr = context.builder.getStringAttr(expr.symbol.name);
137 for (const auto &element : expr.ref.path) {
138 auto *inst = element.symbol->as_if<slang::ast::InstanceSymbol>();
139 if (!inst)
140 continue;
141 auto *lowering = context.interfaceInstances.lookup(inst);
142 if (!lowering)
143 continue;
144 if (auto it = lowering->expandedMembers.find(&expr.symbol);
145 it != lowering->expandedMembers.end())
146 return it->second;
147 if (auto it = lowering->expandedMembersByName.find(nameAttr);
148 it != lowering->expandedMembersByName.end())
149 return it->second;
150 }
151 return {};
152}
153
155 const slang::ast::ClassPropertySymbol &expr) {
156 auto loc = context.convertLocation(expr.location);
157 auto builder = context.builder;
158 auto type = context.convertType(expr.getType());
159 auto fieldTy = cast<moore::UnpackedType>(type);
160 auto fieldRefTy = moore::RefType::get(fieldTy);
161
162 if (expr.lifetime == slang::ast::VariableLifetime::Static) {
163
164 // Variable may or may not have been hoisted already. Hoist if not.
165 if (!context.globalVariables.lookup(&expr)) {
166 if (failed(context.convertGlobalVariable(expr))) {
167 return {};
168 }
169 }
170 // Try the static variable after it has been hoisted.
171 if (auto globalOp = context.globalVariables.lookup(&expr))
172 return moore::GetGlobalVariableOp::create(builder, loc, globalOp);
173
174 mlir::emitError(loc) << "Failed to access static member variable "
175 << expr.name << " as a global variable";
176 return {};
177 }
178
179 // Get the scope's implicit this variable
180 mlir::Value instRef = context.getImplicitThisRef();
181 if (!instRef) {
182 mlir::emitError(loc) << "class property '" << expr.name
183 << "' referenced without an implicit 'this'";
184 return {};
185 }
186
187 auto fieldSym = mlir::FlatSymbolRefAttr::get(builder.getContext(), expr.name);
188
189 moore::ClassHandleType classTy =
190 cast<moore::ClassHandleType>(instRef.getType());
191
192 auto targetClassHandle =
193 context.getAncestorClassWithProperty(classTy, expr.name, loc);
194 if (!targetClassHandle)
195 return {};
196
197 auto upcastRef = context.materializeConversion(targetClassHandle, instRef,
198 false, instRef.getLoc());
199 if (!upcastRef)
200 return {};
201
202 Value fieldRef = moore::ClassPropertyRefOp::create(builder, loc, fieldRefTy,
203 upcastRef, fieldSym);
204 return fieldRef;
205}
206
207namespace {
208/// A visitor handling expressions that can be lowered as lvalue and rvalue.
209struct ExprVisitor {
210 Context &context;
211 Location loc;
212 OpBuilder &builder;
213 bool isLvalue;
214
215 ExprVisitor(Context &context, Location loc, bool isLvalue)
216 : context(context), loc(loc), builder(context.builder),
217 isLvalue(isLvalue) {}
218
219 /// Convert an expression either as an lvalue or rvalue, depending on whether
220 /// this is an lvalue or rvalue visitor. This is useful for projections such
221 /// as `a[i]`, where you want `a` as an lvalue if you want `a[i]` as an
222 /// lvalue, or `a` as an rvalue if you want `a[i]` as an rvalue.
223 Value convertLvalueOrRvalueExpression(const slang::ast::Expression &expr) {
224 if (isLvalue)
225 return context.convertLvalueExpression(expr);
226 return context.convertRvalueExpression(expr);
227 }
228
229 /// Materialize the rvalue of a symbol, regardless of whether it is backed by
230 /// a local reference, global variable, or class property.
231 Value materializeSymbolRvalue(const slang::ast::ValueSymbol &sym) {
232 if (auto value = context.valueSymbols.lookup(&sym)) {
233 if (isa<moore::RefType>(value.getType())) {
234 auto readOp = moore::ReadOp::create(builder, loc, value);
235 if (context.rvalueReadCallback)
236 context.rvalueReadCallback(readOp);
237 return readOp.getResult();
238 }
239 return value;
240 }
241
242 if (auto globalOp = context.globalVariables.lookup(&sym)) {
243 auto ref = moore::GetGlobalVariableOp::create(builder, loc, globalOp);
244 auto readOp = moore::ReadOp::create(builder, loc, ref);
245 if (context.rvalueReadCallback)
246 context.rvalueReadCallback(readOp);
247 return readOp.getResult();
248 }
249
250 if (auto *const property = sym.as_if<slang::ast::ClassPropertySymbol>()) {
251 auto fieldRef = visitClassProperty(context, *property);
252 auto readOp = moore::ReadOp::create(builder, loc, fieldRef);
253 if (context.rvalueReadCallback)
254 context.rvalueReadCallback(readOp);
255 return readOp.getResult();
256 }
257
258 return {};
259 }
260
261 Value visit(const slang::ast::NewArrayExpression &expr) {
262 Type type = context.convertType(*expr.type);
263
264 // TODO: Handle 'initExpr' if it exists
265
266 if (expr.initExpr()) {
267 mlir::emitError(loc)
268 << "unsupported expression: array `new` with initializer\n";
269 return {};
270 }
271
272 auto initialSize = context.convertRvalueExpression(
273 expr.sizeExpr(), context.convertType(*expr.sizeExpr().type));
274 if (!initialSize)
275 return {};
276
277 return moore::OpenUArrayCreateOp::create(builder, loc, type, initialSize);
278 }
279
280 /// Handle single bit selections.
281 Value visit(const slang::ast::ElementSelectExpression &expr) {
282 auto type = context.convertType(*expr.type);
283 auto value = convertLvalueOrRvalueExpression(expr.value());
284 if (!type || !value)
285 return {};
286
287 // We only support indexing into a few select types for now.
288 auto derefType = value.getType();
289 if (isLvalue)
290 derefType = cast<moore::RefType>(derefType).getNestedType();
291
292 if (!isa<moore::IntType, moore::ArrayType, moore::UnpackedArrayType,
293 moore::QueueType, moore::AssocArrayType, moore::StringType,
294 moore::OpenUnpackedArrayType>(derefType)) {
295 mlir::emitError(loc) << "unsupported expression: element select into "
296 << expr.value().type->toString() << "\n";
297 return {};
298 }
299
300 // Associative Arrays are a special case so handle them separately.
301 if (isa<moore::AssocArrayType>(derefType)) {
302 auto assocArray = cast<moore::AssocArrayType>(derefType);
303 auto expectedIndexType = assocArray.getIndexType();
304 auto givenIndex = context.convertRvalueExpression(expr.selector());
305
306 if (!givenIndex)
307 return {};
308
309 if (givenIndex.getType() != expectedIndexType) {
310 mlir::emitError(loc)
311 << "Incorrect index type: expected index type of "
312 << expectedIndexType << " but was given " << givenIndex.getType();
313 }
314
315 if (isLvalue)
316 return moore::AssocArrayExtractRefOp::create(
317 builder, loc, moore::RefType::get(cast<moore::UnpackedType>(type)),
318 value, givenIndex);
319
320 return moore::AssocArrayExtractOp::create(builder, loc, type, value,
321 givenIndex);
322 }
323
324 // Handle string indexing.
325 if (isa<moore::StringType>(derefType)) {
326 if (isLvalue) {
327 mlir::emitError(loc) << "string index assignment not supported";
328 return {};
329 }
330
331 // Convert the index to an rvalue with the required type (TwoValuedI32).
332 auto i32Type = moore::IntType::getInt(builder.getContext(), 32);
333 auto index = context.convertRvalueExpression(expr.selector(), i32Type);
334 if (!index)
335 return {};
336
337 // Create the StringGetOp operation.
338 return moore::StringGetOp::create(builder, loc, value, index);
339 }
340
341 auto resultType =
342 isLvalue ? moore::RefType::get(cast<moore::UnpackedType>(type)) : type;
343 auto range = expr.value().type->getFixedRange();
344 if (auto *constValue = expr.selector().getConstant();
345 constValue && constValue->isInteger()) {
346 assert(!constValue->hasUnknown());
347 assert(constValue->size() <= 32);
348
349 auto lowBit = constValue->integer().as<uint32_t>().value();
350 if (isLvalue)
351 return llvm::TypeSwitch<Type, Value>(derefType)
352 .Case<moore::QueueType>([&](moore::QueueType) {
353 mlir::emitError(loc)
354 << "Unexpected LValue extract on Queue Type!";
355 return Value();
356 })
357 .Default([&](Type) {
358 return moore::ExtractRefOp::create(builder, loc, resultType,
359 value,
360 range.translateIndex(lowBit));
361 });
362 else
363 return llvm::TypeSwitch<Type, Value>(derefType)
364 .Case<moore::QueueType>([&](moore::QueueType) {
365 mlir::emitError(loc)
366 << "Unexpected RValue extract on Queue Type!";
367 return Value();
368 })
369 .Default([&](Type) {
370 return moore::ExtractOp::create(builder, loc, resultType, value,
371 range.translateIndex(lowBit));
372 });
373 }
374
375 // Save the queue which is being indexed: this allows us to handle the `$`
376 // operator, which evaluates to the last valid index in the queue.
377 Value savedQueue = context.currentQueue;
378 llvm::scope_exit restoreQueue([&] { context.currentQueue = savedQueue; });
379 if (isa<moore::QueueType>(derefType)) {
380 // For QueueSizeBIOp, we need a byvalue queue, so if the queue is an
381 // lvalue (because we're assigning to it), we need to dereference it
382 if (isa<moore::RefType>(value.getType())) {
383 context.currentQueue = moore::ReadOp::create(builder, loc, value);
384 } else {
385 context.currentQueue = value;
386 }
387 }
388 auto lowBit = context.convertRvalueExpression(expr.selector());
389
390 if (!lowBit)
391 return {};
392 lowBit = getSelectIndex(context, loc, lowBit, range);
393 if (isLvalue)
394 return llvm::TypeSwitch<Type, Value>(derefType)
395 .Case<moore::QueueType>([&](moore::QueueType) {
396 return moore::DynQueueRefElementOp::create(builder, loc, resultType,
397 value, lowBit);
398 })
399 .Default([&](Type) {
400 return moore::DynExtractRefOp::create(builder, loc, resultType,
401 value, lowBit);
402 });
403
404 else
405 return llvm::TypeSwitch<Type, Value>(derefType)
406 .Case<moore::QueueType>([&](moore::QueueType) {
407 return moore::DynQueueExtractOp::create(builder, loc, resultType,
408 value, lowBit, lowBit);
409 })
410 .Default([&](Type) {
411 return moore::DynExtractOp::create(builder, loc, resultType, value,
412 lowBit);
413 });
414 }
415
416 /// Handle null assignments to variables.
417 /// Compare with IEEE 1800-2023 Table 6-7 - Default variable initial values
418 Value visit(const slang::ast::NullLiteral &expr) {
419 auto type = context.convertType(*expr.type);
420 if (isa<moore::ClassHandleType, moore::ChandleType, moore::EventType,
421 moore::NullType>(type))
422 return moore::NullOp::create(builder, loc);
423 mlir::emitError(loc) << "No null value definition found for value of type "
424 << type;
425 return {};
426 }
427
428 /// Handle range bit selections.
429 Value visit(const slang::ast::RangeSelectExpression &expr) {
430 auto type = context.convertType(*expr.type);
431 auto value = convertLvalueOrRvalueExpression(expr.value());
432 if (!type || !value)
433 return {};
434
435 auto derefType = value.getType();
436 if (isLvalue)
437 derefType = cast<moore::RefType>(derefType).getNestedType();
438
439 if (isa<moore::QueueType>(derefType)) {
440 return handleQueueRangeSelectExpressions(expr, type, value);
441 }
442 return handleArrayRangeSelectExpressions(expr, type, value);
443 }
444
445 // Handles range selections into queues, in which neither bound needs to be
446 // constant
447 Value handleQueueRangeSelectExpressions(
448 const slang::ast::RangeSelectExpression &expr, Type type, Value value) {
449 Value savedQueue = context.currentQueue;
450 llvm::scope_exit restoreQueue([&] { context.currentQueue = savedQueue; });
451 context.currentQueue = value;
452
453 auto lowerIdx = context.convertRvalueExpression(expr.left());
454 auto upperIdx = context.convertRvalueExpression(expr.right());
455 auto resultType =
456 isLvalue ? moore::RefType::get(cast<moore::UnpackedType>(type)) : type;
457
458 if (isLvalue) {
459 mlir::emitError(loc) << "queue lvalue range selections are not supported";
460 return {};
461 }
462 return moore::DynQueueExtractOp::create(builder, loc, resultType, value,
463 lowerIdx, upperIdx);
464 }
465
466 // Handles range selections into arrays, which currently require a constant
467 // upper bound
468 Value handleArrayRangeSelectExpressions(
469 const slang::ast::RangeSelectExpression &expr, Type type, Value value) {
470 std::optional<int32_t> constLeft;
471 std::optional<int32_t> constRight;
472 if (auto *constant = expr.left().getConstant())
473 constLeft = constant->integer().as<int32_t>();
474 if (auto *constant = expr.right().getConstant())
475 constRight = constant->integer().as<int32_t>();
476
477 // We currently require the right-hand-side of the range to be constant.
478 // This catches things like `[42:$]` which we don't support at the moment.
479 if (!constRight) {
480 mlir::emitError(loc)
481 << "unsupported expression: range select with non-constant bounds";
482 return {};
483 }
484
485 // We need to determine the right bound of the range. This is the address of
486 // the least significant bit of the underlying bit storage, which is the
487 // offset we want to pass to the extract op.
488 //
489 // The arrays [6:2] and [2:6] both have 5 bits worth of underlying storage.
490 // The left and right bound of the range only determine the addressing
491 // scheme of the storage bits:
492 //
493 // Storage bits: 4 3 2 1 0 <-- extract op works on storage bits
494 // [6:2] indices: 6 5 4 3 2 ("little endian" in Slang terms)
495 // [2:6] indices: 2 3 4 5 6 ("big endian" in Slang terms)
496 //
497 // Before we can extract, we need to map the range select left and right
498 // bounds from these indices to actual bit positions in the storage.
499
500 Value offsetDyn;
501 int32_t offsetConst = 0;
502 auto range = expr.value().type->getFixedRange();
503
504 using slang::ast::RangeSelectionKind;
505 if (expr.getSelectionKind() == RangeSelectionKind::Simple) {
506 // For a constant range [a:b], we want the offset of the lowest storage
507 // bit from which we are starting the extract. For a range [5:3] this is
508 // bit index 3; for a range [3:5] this is bit index 5. Both of these are
509 // later translated map to bit offset 1 (see bit indices above).
510 assert(constRight && "constness checked in slang");
511 offsetConst = *constRight;
512 } else {
513 // For an indexed range [a+:b] or [a-:b], determining the lowest storage
514 // bit is a bit more complicated. We start out with the base index `a`.
515 // This is the lower *index* of the range, but not the lower *storage bit
516 // position*.
517 //
518 // The range [a+:b] expands to [a+b-1:a] for a [6:2] range, or [a:a+b-1]
519 // for a [2:6] range. The range [a-:b] expands to [a:a-b+1] for a [6:2]
520 // range, or [a-b+1:a] for a [2:6] range.
521 if (constLeft) {
522 offsetConst = *constLeft;
523 } else {
524 offsetDyn = context.convertRvalueExpression(expr.left());
525 if (!offsetDyn)
526 return {};
527 }
528
529 // For a [a-:b] select on [2:6] and a [a+:b] select on [6:2], the range
530 // expands to [a-b+1:a] and [a+b-1:a]. In this case, the right bound which
531 // corresponds to the lower *storage bit offset*, is just `a` and there's
532 // no further tweaking to do.
533 int32_t offsetAdd = 0;
534
535 // For a [a-:b] select on [6:2], the range expands to [a:a-b+1]. We
536 // therefore have to take the `a` from above and adjust it by `-b+1` to
537 // arrive at the right bound.
538 if (expr.getSelectionKind() == RangeSelectionKind::IndexedDown &&
539 range.isDescending()) {
540 assert(constRight && "constness checked in slang");
541 offsetAdd = 1 - *constRight;
542 }
543
544 // For a [a+:b] select on [2:6], the range expands to [a:a+b-1]. We
545 // therefore have to take the `a` from above and adjust it by `+b-1` to
546 // arrive at the right bound.
547 if (expr.getSelectionKind() == RangeSelectionKind::IndexedUp &&
548 !range.isDescending()) {
549 assert(constRight && "constness checked in slang");
550 offsetAdd = *constRight - 1;
551 }
552
553 // Adjust the offset such that it matches the right bound of the range.
554 if (offsetAdd != 0) {
555 if (offsetDyn)
556 offsetDyn = moore::AddOp::create(
557 builder, loc, offsetDyn,
558 moore::ConstantOp::create(
559 builder, loc, cast<moore::IntType>(offsetDyn.getType()),
560 offsetAdd,
561 /*isSigned=*/offsetAdd < 0));
562 else
563 offsetConst += offsetAdd;
564 }
565 }
566
567 // Create a dynamic or constant extract. Use `getSelectIndex` and
568 // `ConstantRange::translateIndex` to map from the bit indices provided by
569 // the user to the actual storage bit position. Since `offset*` corresponds
570 // to the right bound of the range, which provides the index of the least
571 // significant selected storage bit, we get the bit offset at which we want
572 // to start extracting.
573 auto resultType =
574 isLvalue ? moore::RefType::get(cast<moore::UnpackedType>(type)) : type;
575
576 if (offsetDyn) {
577 offsetDyn = getSelectIndex(context, loc, offsetDyn, range);
578 if (isLvalue) {
579 return moore::DynExtractRefOp::create(builder, loc, resultType, value,
580 offsetDyn);
581 } else {
582 return moore::DynExtractOp::create(builder, loc, resultType, value,
583 offsetDyn);
584 }
585 } else {
586 offsetConst = range.translateIndex(offsetConst);
587 if (isLvalue) {
588 return moore::ExtractRefOp::create(builder, loc, resultType, value,
589 offsetConst);
590 } else {
591 return moore::ExtractOp::create(builder, loc, resultType, value,
592 offsetConst);
593 }
594 }
595 }
596
597 /// Handle concatenations.
598 Value visit(const slang::ast::ConcatenationExpression &expr) {
599 SmallVector<Value> operands;
600 if (expr.type->isString()) {
601 for (auto *operand : expr.operands()) {
602 assert(!isLvalue && "checked by Slang");
603 auto value = convertLvalueOrRvalueExpression(*operand);
604 if (!value)
605 return {};
606 value = context.materializeConversion(
607 moore::StringType::get(context.getContext()), value, false,
608 value.getLoc());
609 if (!value)
610 return {};
611 operands.push_back(value);
612 }
613 return moore::StringConcatOp::create(builder, loc, operands);
614 }
615 if (expr.type->isQueue()) {
616 return handleQueueConcat(expr);
617 }
618
619 if (expr.type->isUnpackedArray()) {
620 assert(!isLvalue && "checked by Slang");
621 auto loweredType = context.convertType(*expr.type, loc);
622 if (!loweredType)
623 return {};
624
626 if (auto arrayType = dyn_cast<moore::UnpackedArrayType>(loweredType))
627 elementType = arrayType.getElementType();
628 else if (auto openType =
629 dyn_cast<moore::OpenUnpackedArrayType>(loweredType))
630 elementType = openType.getElementType();
631 else
632 return {};
633
634 SmallVector<Value> operands;
635 for (auto *operand : expr.operands()) {
636 if (operand->type->isVoid())
637 continue;
638 auto value = context.convertRvalueExpression(*operand, elementType);
639 if (!value)
640 return {};
641 operands.push_back(value);
642 }
643
644 auto arrayType = moore::UnpackedArrayType::get(
645 context.getContext(), operands.size(), elementType);
646 return moore::ArrayCreateOp::create(builder, loc, arrayType, operands);
647 }
648
649 for (auto *operand : expr.operands()) {
650 // Handle empty replications like `{0{...}}` which may occur within
651 // concatenations. Slang assigns them a `void` type which we can check for
652 // here.
653 if (operand->type->isVoid())
654 continue;
655 auto value = convertLvalueOrRvalueExpression(*operand);
656 if (!value)
657 return {};
658 if (!isLvalue)
659 value = context.convertToSimpleBitVector(value);
660 if (!value)
661 return {};
662 operands.push_back(value);
663 }
664 if (isLvalue)
665 return moore::ConcatRefOp::create(builder, loc, operands);
666 else
667 return moore::ConcatOp::create(builder, loc, operands);
668 }
669
670 // Handles a `ConcatenationExpression` which produces a queue as a result.
671 // Intuitively, queue concatenations are the same as unpacked array
672 // concatenations. However, because queues may vary in size, we can't
673 // just convert each argument to a simple bit vector.
674 Value handleQueueConcat(const slang::ast::ConcatenationExpression &expr) {
675 SmallVector<Value> operands;
676
677 auto queueType =
678 cast<moore::QueueType>(context.convertType(*expr.type, loc));
679 auto elementType = queueType.getElementType();
680
681 // Strategy:
682 // QueueConcatOp only takes queues, so other types must be converted to
683 // queues.
684 // - Unpacked arrays have a conversion to queues via
685 // `QueueFromUnpackedArrayOp`.
686 // - For individual elements, we create a new queue for each contiguous
687 // sequence of elements, and add this to the QueueConcatOp.
688
689 // The current contiguous sequence of individual elements.
690 Value contigElements;
691
692 for (auto *operand : expr.operands()) {
693 bool isSingleElement =
694 context.convertType(*operand->type, loc) == elementType;
695
696 // If the subsequent operand is not a single element, add the current
697 // sequence of contiguous elements to the QueueConcatOp
698 if (!isSingleElement && contigElements) {
699 operands.push_back(moore::ReadOp::create(builder, loc, contigElements));
700 contigElements = {};
701 }
702
703 assert(!isLvalue && "checked by Slang");
704 auto value = convertLvalueOrRvalueExpression(*operand);
705 if (!value)
706 return {};
707
708 // If value is an element of the queue, create an empty queue and add
709 // that element.
710 if (value.getType() == elementType) {
711 auto queueRefType =
712 moore::RefType::get(context.getContext(), queueType);
713
714 if (!contigElements) {
715 contigElements =
716 moore::VariableOp::create(builder, loc, queueRefType, {}, {});
717 }
718 moore::QueuePushBackOp::create(builder, loc, contigElements, value);
719 continue;
720 }
721
722 // Otherwise, the value should be directly convertible to a queue type.
723 // If the type is a queue type with the same element type, skip this step,
724 // since we don't need to cast things like queue<T, 10> to queue<T, 0>,
725 // - QueueConcatOp doesn't mind the queue bounds.
726 if (!(isa<moore::QueueType>(value.getType()) &&
727 cast<moore::QueueType>(value.getType()).getElementType() ==
728 elementType)) {
729 value = context.materializeConversion(queueType, value, false,
730 value.getLoc());
731 }
732
733 operands.push_back(value);
734 }
735
736 if (contigElements) {
737 operands.push_back(moore::ReadOp::create(builder, loc, contigElements));
738 }
739
740 return moore::QueueConcatOp::create(builder, loc, queueType, operands);
741 }
742
743 /// Handle member accesses.
744 Value visit(const slang::ast::MemberAccessExpression &expr) {
745 auto type = context.convertType(*expr.type);
746 if (!type)
747 return {};
748
749 auto *valueType = expr.value().type.get();
750 auto memberName = builder.getStringAttr(expr.member.name);
751
752 // Handle virtual interfaces. We represent virtual interface handles as a
753 // Moore struct containing references to interface members. Member access
754 // returns the stored reference directly (for lvalues) or reads it (for
755 // rvalues).
756 if (valueType->isVirtualInterface()) {
757 auto memberType = dyn_cast<moore::UnpackedType>(type);
758 if (!memberType) {
759 mlir::emitError(loc)
760 << "unsupported virtual interface member type: " << type;
761 return {};
762 }
763 auto resultRefType = moore::RefType::get(memberType);
764
765 // Always use the rvalue of the base handle to avoid creating
766 // ref<ref<T>> for lvalue member access.
767 Value base = context.convertRvalueExpression(expr.value());
768 if (!base)
769 return {};
770
771 auto memberRef = moore::StructExtractOp::create(
772 builder, loc, resultRefType, memberName, base);
773 if (isLvalue)
774 return memberRef;
775 return moore::ReadOp::create(builder, loc, memberRef);
776 }
777
778 // Handle structs.
779 if (valueType->isStruct()) {
780 auto resultType =
781 isLvalue ? moore::RefType::get(cast<moore::UnpackedType>(type))
782 : type;
783 auto value = convertLvalueOrRvalueExpression(expr.value());
784 if (!value)
785 return {};
786
787 if (isLvalue)
788 return moore::StructExtractRefOp::create(builder, loc, resultType,
789 memberName, value);
790 return moore::StructExtractOp::create(builder, loc, resultType,
791 memberName, value);
792 }
793
794 // Handle unions.
795 if (valueType->isPackedUnion() || valueType->isUnpackedUnion()) {
796 auto resultType =
797 isLvalue ? moore::RefType::get(cast<moore::UnpackedType>(type))
798 : type;
799 auto value = convertLvalueOrRvalueExpression(expr.value());
800 if (!value)
801 return {};
802
803 if (isLvalue)
804 return moore::UnionExtractRefOp::create(builder, loc, resultType,
805 memberName, value);
806 return moore::UnionExtractOp::create(builder, loc, type, memberName,
807 value);
808 }
809
810 // Handle classes.
811 if (valueType->isClass()) {
812 auto valTy = context.convertType(*valueType);
813 if (!valTy)
814 return {};
815 auto targetTy = cast<moore::ClassHandleType>(valTy);
816
817 // `MemberAccessExpression`s may refer to either variables that may or may
818 // not be compile time constants, or to class parameters which are always
819 // elaboration-time constant.
820 //
821 // We distinguish these cases, and materialize a runtime member access
822 // for variables, but force constant conversion for parameter accesses.
823 //
824 // Also see this discussion:
825 // https://github.com/MikePopoloski/slang/issues/1641
826
827 if (expr.member.kind != slang::ast::SymbolKind::Parameter) {
828
829 // We need to pick the closest ancestor that declares a property with
830 // the relevant name. System Verilog explicitly enforces lexical
831 // shadowing, as shown in IEEE 1800-2023 Section 8.14 "Overridden
832 // members".
833 moore::ClassHandleType upcastTargetTy =
834 context.getAncestorClassWithProperty(targetTy, expr.member.name,
835 loc);
836 if (!upcastTargetTy)
837 return {};
838
839 // Convert the class handle to the required target type for property
840 // shadowing purposes.
841 Value baseVal =
842 context.convertRvalueExpression(expr.value(), upcastTargetTy);
843 if (!baseVal)
844 return {};
845
846 // @field and result type !moore.ref<T>.
847 auto fieldSym = mlir::FlatSymbolRefAttr::get(builder.getContext(),
848 expr.member.name);
849 auto fieldRefTy = moore::RefType::get(cast<moore::UnpackedType>(type));
850
851 // Produce a ref to the class property from the (possibly upcast)
852 // handle.
853 Value fieldRef = moore::ClassPropertyRefOp::create(
854 builder, loc, fieldRefTy, baseVal, fieldSym);
855
856 // If we need an RValue, read the reference, otherwise return
857 return isLvalue ? fieldRef
858 : moore::ReadOp::create(builder, loc, fieldRef);
859 }
860
861 slang::ConstantValue constVal;
862 if (auto param = expr.member.as_if<slang::ast::ParameterSymbol>()) {
863 constVal = param->getValue();
864 if (auto value = context.materializeConstant(constVal, *expr.type, loc))
865 return value;
866 }
867
868 mlir::emitError(loc) << "Parameter " << expr.member.name
869 << " has no constant value";
870 return {};
871 }
872
873 mlir::emitError(loc, "expression of type ")
874 << valueType->toString() << " has no member fields";
875 return {};
876 }
877};
878} // namespace
879
880//===----------------------------------------------------------------------===//
881// Rvalue Conversion
882//===----------------------------------------------------------------------===//
883
884// NOLINTBEGIN(misc-no-recursion)
885namespace {
886struct RvalueExprVisitor : public ExprVisitor {
887 RvalueExprVisitor(Context &context, Location loc)
888 : ExprVisitor(context, loc, /*isLvalue=*/false) {}
889 using ExprVisitor::visit;
890
891 // Handle references to the left-hand side of a parent assignment.
892 Value visit(const slang::ast::LValueReferenceExpression &expr) {
893 assert(!context.lvalueStack.empty() && "parent assignments push lvalue");
894 auto lvalue = context.lvalueStack.back();
895 return moore::ReadOp::create(builder, loc, lvalue);
896 }
897
898 // Handle named values, such as references to declared variables.
899 Value visit(const slang::ast::NamedValueExpression &expr) {
900 // Handle local variables.
901 if (auto value = context.valueSymbols.lookup(&expr.symbol)) {
902 if (isa<moore::RefType>(value.getType())) {
903 auto readOp = moore::ReadOp::create(builder, loc, value);
904 if (context.rvalueReadCallback)
905 context.rvalueReadCallback(readOp);
906 value = readOp.getResult();
907 }
908 return value;
909 }
910
911 // Handle global variables.
912 if (auto globalOp = context.globalVariables.lookup(&expr.symbol)) {
913 auto value = moore::GetGlobalVariableOp::create(builder, loc, globalOp);
914 return moore::ReadOp::create(builder, loc, value);
915 }
916
917 // We're reading a class property.
918 if (auto *const property =
919 expr.symbol.as_if<slang::ast::ClassPropertySymbol>()) {
920 auto fieldRef = visitClassProperty(context, *property);
921 return moore::ReadOp::create(builder, loc, fieldRef).getResult();
922 }
923
924 // Slang may resolve `vif.member` accesses (with `vif` being a virtual
925 // interface handle) directly to a NamedValueExpression for `member`.
926 // Reconstruct the virtual interface access by consulting the mapping
927 // populated at declaration sites.
928 if (auto access = context.virtualIfaceMembers.lookup(&expr.symbol);
929 access.base) {
930 auto type = context.convertType(*expr.type);
931 if (!type)
932 return {};
933 auto memberType = dyn_cast<moore::UnpackedType>(type);
934 if (!memberType) {
935 mlir::emitError(loc)
936 << "unsupported virtual interface member type: " << type;
937 return {};
938 }
939
940 Value base = materializeSymbolRvalue(*access.base);
941 if (!base) {
942 auto d = mlir::emitError(loc, "unknown name `")
943 << access.base->name << "`";
944 d.attachNote(context.convertLocation(access.base->location))
945 << "no rvalue generated for virtual interface base";
946 return {};
947 }
948
949 auto fieldName = access.fieldName
950 ? access.fieldName
951 : builder.getStringAttr(expr.symbol.name);
952 auto memberRefType = moore::RefType::get(memberType);
953 auto memberRef = moore::StructExtractOp::create(
954 builder, loc, memberRefType, fieldName, base);
955 auto readOp = moore::ReadOp::create(builder, loc, memberRef);
956 if (context.rvalueReadCallback)
957 context.rvalueReadCallback(readOp);
958 return readOp.getResult();
959 }
960
961 // Try to materialize constant values directly.
962 auto constant = context.evaluateConstant(expr);
963 if (auto value = context.materializeConstant(constant, *expr.type, loc))
964 return value;
965
966 // Otherwise some other part of ImportVerilog should have added an MLIR
967 // value for this expression's symbol to the `context.valueSymbols` table.
968 auto d = mlir::emitError(loc, "unknown name `") << expr.symbol.name << "`";
969 d.attachNote(context.convertLocation(expr.symbol.location))
970 << "no rvalue generated for " << slang::ast::toString(expr.symbol.kind);
971 return {};
972 }
973
974 // Handle hierarchical values, such as `x = Top.sub.var`.
975 Value visit(const slang::ast::HierarchicalValueExpression &expr) {
976 auto hierLoc = context.convertLocation(expr.symbol.location);
977
978 // Canonicalize self-references (e.g., SubD.z inside SubD) to local
979 // variable lookups. When the hierarchical path's first instance body
980 // is the same module that declares the target symbol, the reference
981 // is intra-module and should resolve to the local variable directly.
982 if (!expr.ref.path.empty()) {
983 if (auto *inst = expr.ref.path.front()
984 .symbol->as_if<slang::ast::InstanceSymbol>()) {
985 auto *symbolBody =
986 expr.symbol.getParentScope()->getContainingInstance();
987 if (&inst->body == symbolBody ||
988 (symbolBody && inst->body.getDeclaringDefinition() ==
989 symbolBody->getDeclaringDefinition())) {
990 if (auto value = context.valueSymbols.lookup(&expr.symbol)) {
991 if (isa<moore::RefType>(value.getType())) {
992 auto readOp = moore::ReadOp::create(builder, hierLoc, value);
993 if (context.rvalueReadCallback)
994 context.rvalueReadCallback(readOp);
995 value = readOp.getResult();
996 }
997 return value;
998 }
999 }
1000 }
1001 }
1002
1003 // Inside a function body, a captured symbol must resolve to the capture
1004 // argument to respect region isolation.
1005 if (auto value = context.resolveCapturedValue(expr.symbol)) {
1006 if (isa<moore::RefType>(value.getType())) {
1007 auto readOp = moore::ReadOp::create(builder, hierLoc, value);
1008 if (context.rvalueReadCallback)
1009 context.rvalueReadCallback(readOp);
1010 value = readOp.getResult();
1011 }
1012 return value;
1013 }
1014
1015 // For cross-instance hierarchical references, prefer the isntance-aware
1016 // hierValueSymbols lookup. Sibling instances elaborate distinct symbol
1017 // objects for the same logical variable, and this map keeps p1 vs p2
1018 // resolutions separate where the scoped table could conflate them.
1019 if (auto key = context.buildHierValueKey(expr)) {
1020 if (auto it = context.hierValueSymbols.find(*key);
1021 it != context.hierValueSymbols.end()) {
1022 auto value = it->second;
1023 if (isa<moore::RefType>(value.getType())) {
1024 auto readOp = moore::ReadOp::create(builder, hierLoc, value);
1025 if (context.rvalueReadCallback)
1026 context.rvalueReadCallback(readOp);
1027 value = readOp.getResult();
1028 }
1029 return value;
1030 }
1031 }
1032
1033 // Fall back to scoped symbol table (same-scope lookups, self-refs).
1034 if (auto value = context.valueSymbols.lookup(&expr.symbol)) {
1035 if (isa<moore::RefType>(value.getType())) {
1036 auto readOp = moore::ReadOp::create(builder, hierLoc, value);
1037 if (context.rvalueReadCallback)
1038 context.rvalueReadCallback(readOp);
1039 value = readOp.getResult();
1040 }
1041 return value;
1042 }
1043
1044 if (auto value = lookupExpandedInterfaceMember(context, expr)) {
1045 if (isa<moore::RefType>(value.getType())) {
1046 auto readOp = moore::ReadOp::create(builder, hierLoc, value);
1047 if (context.rvalueReadCallback)
1048 context.rvalueReadCallback(readOp);
1049 return readOp.getResult();
1050 }
1051 return value;
1052 }
1053
1054 /// Materialize compile-time constants directly from the symbol: the
1055 /// generic evaluateConstant refuses hierarchical references unless slang's
1056 /// AllowHierarchicalConst flag is set, which CIRCT does not use.
1057 slang::ConstantValue constant;
1058 switch (expr.symbol.kind) {
1059 case slang::ast::SymbolKind::Parameter:
1060 constant = expr.symbol.as<slang::ast::ParameterSymbol>().getValue(
1061 expr.sourceRange);
1062 break;
1063 case slang::ast::SymbolKind::Specparam:
1064 constant = expr.symbol.as<slang::ast::SpecparamSymbol>().getValue(
1065 expr.sourceRange);
1066 break;
1067 case slang::ast::SymbolKind::EnumValue:
1068 constant = expr.symbol.as<slang::ast::EnumValueSymbol>().getValue(
1069 expr.sourceRange);
1070 break;
1071 default:
1072 constant = context.evaluateConstant(expr);
1073 break;
1074 }
1075 if (auto value = context.materializeConstant(constant, *expr.type, loc))
1076 return value;
1077
1078 // Emit an error for those hierarchical values not recorded in the
1079 // `valueSymbols`.
1080 auto d = mlir::emitError(loc, "unknown hierarchical name `")
1081 << expr.symbol.name << "`";
1082 d.attachNote(hierLoc) << "no rvalue generated for "
1083 << slang::ast::toString(expr.symbol.kind);
1084 return {};
1085 }
1086
1087 // Handle arbitrary symbol references. Slang uses this expression to represent
1088 // "real" interface instances in virtual interface assignments.
1089 Value visit(const slang::ast::ArbitrarySymbolExpression &expr) {
1090 const auto &canonTy = expr.type->getCanonicalType();
1091 if (const auto *vi = canonTy.as_if<slang::ast::VirtualInterfaceType>()) {
1092 auto value = context.materializeVirtualInterfaceValue(*vi, loc);
1093 if (failed(value))
1094 return {};
1095 return *value;
1096 }
1097
1098 mlir::emitError(loc) << "unsupported arbitrary symbol expression of type "
1099 << expr.type->toString();
1100 return {};
1101 }
1102
1103 // Handle type conversions (explicit and implicit).
1104 Value visit(const slang::ast::ConversionExpression &expr) {
1105 auto type = context.convertType(*expr.type);
1106 if (!type)
1107 return {};
1108 return context.convertRvalueExpression(expr.operand(), type);
1109 }
1110
1111 // Handle blocking and non-blocking assignments.
1112 Value visit(const slang::ast::AssignmentExpression &expr) {
1113 auto lhs = context.convertLvalueExpression(expr.left());
1114 if (!lhs)
1115 return {};
1116
1117 // Determine the right-hand side value of the assignment.
1118 context.lvalueStack.push_back(lhs);
1119 auto rhs = context.convertRvalueExpression(
1120 expr.right(), cast<moore::RefType>(lhs.getType()).getNestedType());
1121 context.lvalueStack.pop_back();
1122 if (!rhs)
1123 return {};
1124
1125 // If this is a blocking assignment, we can insert the delay/wait ops of the
1126 // optional timing control directly in between computing the RHS and
1127 // executing the assignment.
1128 if (!expr.isNonBlocking()) {
1129 if (expr.timingControl)
1130 if (failed(context.convertTimingControl(*expr.timingControl)))
1131 return {};
1132 auto assignOp = moore::BlockingAssignOp::create(builder, loc, lhs, rhs);
1133 if (context.variableAssignCallback)
1134 context.variableAssignCallback(assignOp);
1135 return rhs;
1136 }
1137
1138 // For non-blocking assignments, we only support time delays for now.
1139 if (expr.timingControl) {
1140 // Handle regular time delays.
1141 if (auto *ctrl = expr.timingControl->as_if<slang::ast::DelayControl>()) {
1142 auto delay = context.convertRvalueExpression(
1143 ctrl->expr, moore::TimeType::get(builder.getContext()));
1144 if (!delay)
1145 return {};
1146 auto assignOp = moore::DelayedNonBlockingAssignOp::create(
1147 builder, loc, lhs, rhs, delay);
1148 if (context.variableAssignCallback)
1149 context.variableAssignCallback(assignOp);
1150 return rhs;
1151 }
1152
1153 // All other timing controls are not supported.
1154 auto loc = context.convertLocation(expr.timingControl->sourceRange);
1155 mlir::emitError(loc)
1156 << "unsupported non-blocking assignment timing control: "
1157 << slang::ast::toString(expr.timingControl->kind);
1158 return {};
1159 }
1160 auto assignOp = moore::NonBlockingAssignOp::create(builder, loc, lhs, rhs);
1161 if (context.variableAssignCallback)
1162 context.variableAssignCallback(assignOp);
1163 return rhs;
1164 }
1165
1166 // Helper function to convert an argument to a simple bit vector type, pass it
1167 // to a reduction op, and optionally invert the result.
1168 template <class ConcreteOp>
1169 Value createReduction(Value arg, bool invert) {
1170 arg = context.convertToSimpleBitVector(arg);
1171 if (!arg)
1172 return {};
1173 Value result = ConcreteOp::create(builder, loc, arg);
1174 if (invert)
1175 result = moore::NotOp::create(builder, loc, result);
1176 return result;
1177 }
1178
1179 // Helper function to create pre and post increments and decrements.
1180 Value createIncrement(Value arg, bool isInc, bool isPost) {
1181 auto preValue = moore::ReadOp::create(builder, loc, arg);
1182 Value postValue;
1183 // Catch the special case where a signed 1 bit value (i1) is incremented,
1184 // as +1 can not be expressed as a signed 1 bit value. For any 1-bit number
1185 // negating is equivalent to incrementing.
1186 if (moore::isIntType(preValue.getType(), 1)) {
1187 postValue = moore::NotOp::create(builder, loc, preValue).getResult();
1188 } else {
1189
1190 auto one = moore::ConstantOp::create(
1191 builder, loc, cast<moore::IntType>(preValue.getType()), 1);
1192 postValue =
1193 isInc ? moore::AddOp::create(builder, loc, preValue, one).getResult()
1194 : moore::SubOp::create(builder, loc, preValue, one).getResult();
1195 auto assignOp =
1196 moore::BlockingAssignOp::create(builder, loc, arg, postValue);
1197 if (context.variableAssignCallback)
1198 context.variableAssignCallback(assignOp);
1199 }
1200
1201 if (isPost)
1202 return preValue;
1203 return postValue;
1204 }
1205
1206 // Helper function to create pre and post increments and decrements.
1207 Value createRealIncrement(Value arg, bool isInc, bool isPost) {
1208 Value preValue = moore::ReadOp::create(builder, loc, arg);
1209 Value postValue;
1210
1211 bool isTime = isa<moore::TimeType>(preValue.getType());
1212 if (isTime)
1213 preValue = context.materializeConversion(
1214 moore::RealType::get(context.getContext(), moore::RealWidth::f64),
1215 preValue, false, loc);
1216
1217 moore::RealType realTy =
1218 llvm::dyn_cast<moore::RealType>(preValue.getType());
1219 if (!realTy)
1220 return {};
1221
1222 FloatAttr oneAttr;
1223 if (realTy.getWidth() == moore::RealWidth::f32) {
1224 oneAttr = builder.getFloatAttr(builder.getF32Type(), 1.0);
1225 } else if (realTy.getWidth() == moore::RealWidth::f64) {
1226 auto oneVal = isTime ? getTimeScaleInFemtoseconds(context) : 1.0;
1227 oneAttr = builder.getFloatAttr(builder.getF64Type(), oneVal);
1228 } else {
1229 mlir::emitError(loc) << "cannot construct increment for " << realTy;
1230 return {};
1231 }
1232 auto one = moore::ConstantRealOp::create(builder, loc, oneAttr);
1233
1234 postValue =
1235 isInc
1236 ? moore::AddRealOp::create(builder, loc, preValue, one).getResult()
1237 : moore::SubRealOp::create(builder, loc, preValue, one).getResult();
1238
1239 if (isTime)
1240 postValue = context.materializeConversion(
1241 moore::TimeType::get(context.getContext()), postValue, false, loc);
1242
1243 auto assignOp =
1244 moore::BlockingAssignOp::create(builder, loc, arg, postValue);
1245
1246 if (context.variableAssignCallback)
1247 context.variableAssignCallback(assignOp);
1248
1249 if (isPost)
1250 return preValue;
1251 return postValue;
1252 }
1253
1254 Value visitRealUOp(const slang::ast::UnaryExpression &expr) {
1255 Type opFTy = context.convertType(*expr.operand().type);
1256
1257 using slang::ast::UnaryOperator;
1258 Value arg;
1259 if (expr.op == UnaryOperator::Preincrement ||
1260 expr.op == UnaryOperator::Predecrement ||
1261 expr.op == UnaryOperator::Postincrement ||
1262 expr.op == UnaryOperator::Postdecrement)
1263 arg = context.convertLvalueExpression(expr.operand());
1264 else
1265 arg = context.convertRvalueExpression(expr.operand(), opFTy);
1266 if (!arg)
1267 return {};
1268
1269 // Only covers expressions in 'else' branch above.
1270 if (isa<moore::TimeType>(arg.getType()))
1271 arg = context.materializeConversion(
1272 moore::RealType::get(context.getContext(), moore::RealWidth::f64),
1273 arg, false, loc);
1274
1275 switch (expr.op) {
1276 // `+a` is simply `a`
1277 case UnaryOperator::Plus:
1278 return arg;
1279 case UnaryOperator::Minus:
1280 return moore::NegRealOp::create(builder, loc, arg);
1281
1282 case UnaryOperator::Preincrement:
1283 return createRealIncrement(arg, true, false);
1284 case UnaryOperator::Predecrement:
1285 return createRealIncrement(arg, false, false);
1286 case UnaryOperator::Postincrement:
1287 return createRealIncrement(arg, true, true);
1288 case UnaryOperator::Postdecrement:
1289 return createRealIncrement(arg, false, true);
1290
1291 case UnaryOperator::LogicalNot:
1292 arg = context.convertToBool(arg);
1293 if (!arg)
1294 return {};
1295 return moore::NotOp::create(builder, loc, arg);
1296
1297 default:
1298 mlir::emitError(loc) << "Unary operator " << slang::ast::toString(expr.op)
1299 << " not supported with real values!\n";
1300 return {};
1301 }
1302 }
1303
1304 // Handle unary operators.
1305 Value visit(const slang::ast::UnaryExpression &expr) {
1306 // First check whether we need real or integral BOps
1307 const auto *floatType =
1308 expr.operand().type->as_if<slang::ast::FloatingType>();
1309 // If op is real-typed, treat as real BOp.
1310 if (floatType)
1311 return visitRealUOp(expr);
1312
1313 using slang::ast::UnaryOperator;
1314 Value arg;
1315 if (expr.op == UnaryOperator::Preincrement ||
1316 expr.op == UnaryOperator::Predecrement ||
1317 expr.op == UnaryOperator::Postincrement ||
1318 expr.op == UnaryOperator::Postdecrement)
1319 arg = context.convertLvalueExpression(expr.operand());
1320 else
1321 arg = context.convertRvalueExpression(expr.operand());
1322 if (!arg)
1323 return {};
1324
1325 switch (expr.op) {
1326 // `+a` is simply `a`, but converted to a simple bit vector type since
1327 // this is technically an arithmetic operation.
1328 case UnaryOperator::Plus:
1329 return context.convertToSimpleBitVector(arg);
1330
1331 case UnaryOperator::Minus:
1332 arg = context.convertToSimpleBitVector(arg);
1333 if (!arg)
1334 return {};
1335 return moore::NegOp::create(builder, loc, arg);
1336
1337 case UnaryOperator::BitwiseNot:
1338 arg = context.convertToSimpleBitVector(arg);
1339 if (!arg)
1340 return {};
1341 return moore::NotOp::create(builder, loc, arg);
1342
1343 case UnaryOperator::BitwiseAnd:
1344 return createReduction<moore::ReduceAndOp>(arg, false);
1345 case UnaryOperator::BitwiseOr:
1346 return createReduction<moore::ReduceOrOp>(arg, false);
1347 case UnaryOperator::BitwiseXor:
1348 return createReduction<moore::ReduceXorOp>(arg, false);
1349 case UnaryOperator::BitwiseNand:
1350 return createReduction<moore::ReduceAndOp>(arg, true);
1351 case UnaryOperator::BitwiseNor:
1352 return createReduction<moore::ReduceOrOp>(arg, true);
1353 case UnaryOperator::BitwiseXnor:
1354 return createReduction<moore::ReduceXorOp>(arg, true);
1355
1356 case UnaryOperator::LogicalNot:
1357 arg = context.convertToBool(arg);
1358 if (!arg)
1359 return {};
1360 return moore::NotOp::create(builder, loc, arg);
1361
1362 case UnaryOperator::Preincrement:
1363 return createIncrement(arg, true, false);
1364 case UnaryOperator::Predecrement:
1365 return createIncrement(arg, false, false);
1366 case UnaryOperator::Postincrement:
1367 return createIncrement(arg, true, true);
1368 case UnaryOperator::Postdecrement:
1369 return createIncrement(arg, false, true);
1370 }
1371
1372 mlir::emitError(loc, "unsupported unary operator");
1373 return {};
1374 }
1375
1376 /// Handles logical operators (§11.4.7), assuming lhs/rhs are rvalues already.
1377 Value buildLogicalBOp(slang::ast::BinaryOperator op, Value lhs, Value rhs,
1378 std::optional<Domain> domain = std::nullopt) {
1379 using slang::ast::BinaryOperator;
1380 // TODO: These should short-circuit; RHS should be in a separate block.
1381
1382 if (domain) {
1383 lhs = context.convertToBool(lhs, domain.value());
1384 rhs = context.convertToBool(rhs, domain.value());
1385 } else {
1386 lhs = context.convertToBool(lhs);
1387 rhs = context.convertToBool(rhs);
1388 }
1389
1390 if (!lhs || !rhs)
1391 return {};
1392
1393 switch (op) {
1394 case BinaryOperator::LogicalAnd:
1395 return moore::AndOp::create(builder, loc, lhs, rhs);
1396
1397 case BinaryOperator::LogicalOr:
1398 return moore::OrOp::create(builder, loc, lhs, rhs);
1399
1400 case BinaryOperator::LogicalImplication: {
1401 // (lhs -> rhs) == (!lhs || rhs)
1402 auto notLHS = moore::NotOp::create(builder, loc, lhs);
1403 return moore::OrOp::create(builder, loc, notLHS, rhs);
1404 }
1405
1406 case BinaryOperator::LogicalEquivalence: {
1407 // (lhs <-> rhs) == (lhs && rhs) || (!lhs && !rhs)
1408 auto notLHS = moore::NotOp::create(builder, loc, lhs);
1409 auto notRHS = moore::NotOp::create(builder, loc, rhs);
1410 auto both = moore::AndOp::create(builder, loc, lhs, rhs);
1411 auto notBoth = moore::AndOp::create(builder, loc, notLHS, notRHS);
1412 return moore::OrOp::create(builder, loc, both, notBoth);
1413 }
1414
1415 default:
1416 llvm_unreachable("not a logical BinaryOperator");
1417 }
1418 }
1419
1420 Value visitHandleBOp(const slang::ast::BinaryExpression &expr) {
1421 // Convert operands to the chosen target type.
1422 auto lhs = context.convertRvalueExpression(expr.left());
1423 if (!lhs)
1424 return {};
1425 auto rhs = context.convertRvalueExpression(expr.right());
1426 if (!rhs)
1427 return {};
1428
1429 using slang::ast::BinaryOperator;
1430 switch (expr.op) {
1431
1432 case BinaryOperator::Equality:
1433 return moore::HandleEqOp::create(builder, loc, lhs, rhs);
1434 case BinaryOperator::Inequality:
1435 return moore::HandleNeOp::create(builder, loc, lhs, rhs);
1436 case BinaryOperator::CaseEquality:
1437 return moore::HandleCaseEqOp::create(builder, loc, lhs, rhs);
1438 case BinaryOperator::CaseInequality:
1439 return moore::HandleCaseNeOp::create(builder, loc, lhs, rhs);
1440
1441 default:
1442 mlir::emitError(loc)
1443 << "Binary operator " << slang::ast::toString(expr.op)
1444 << " not supported with class handle valued operands!\n";
1445 return {};
1446 }
1447 }
1448
1449 Value visitRealBOp(const slang::ast::BinaryExpression &expr) {
1450 // Convert operands to the chosen target type.
1451 auto lhs = context.convertRvalueExpression(expr.left());
1452 if (!lhs)
1453 return {};
1454 auto rhs = context.convertRvalueExpression(expr.right());
1455 if (!rhs)
1456 return {};
1457
1458 if (isa<moore::TimeType>(lhs.getType()) ||
1459 isa<moore::TimeType>(rhs.getType())) {
1460 lhs = context.materializeConversion(
1461 moore::RealType::get(context.getContext(), moore::RealWidth::f64),
1462 lhs, false, loc);
1463 rhs = context.materializeConversion(
1464 moore::RealType::get(context.getContext(), moore::RealWidth::f64),
1465 rhs, false, loc);
1466 }
1467
1468 using slang::ast::BinaryOperator;
1469 switch (expr.op) {
1470 case BinaryOperator::Add:
1471 return moore::AddRealOp::create(builder, loc, lhs, rhs);
1472 case BinaryOperator::Subtract:
1473 return moore::SubRealOp::create(builder, loc, lhs, rhs);
1474 case BinaryOperator::Multiply:
1475 return moore::MulRealOp::create(builder, loc, lhs, rhs);
1476 case BinaryOperator::Divide:
1477 return moore::DivRealOp::create(builder, loc, lhs, rhs);
1478 case BinaryOperator::Power:
1479 return moore::PowRealOp::create(builder, loc, lhs, rhs);
1480
1481 case BinaryOperator::Equality:
1482 return moore::EqRealOp::create(builder, loc, lhs, rhs);
1483 case BinaryOperator::Inequality:
1484 return moore::NeRealOp::create(builder, loc, lhs, rhs);
1485
1486 case BinaryOperator::GreaterThan:
1487 return moore::FgtOp::create(builder, loc, lhs, rhs);
1488 case BinaryOperator::LessThan:
1489 return moore::FltOp::create(builder, loc, lhs, rhs);
1490 case BinaryOperator::GreaterThanEqual:
1491 return moore::FgeOp::create(builder, loc, lhs, rhs);
1492 case BinaryOperator::LessThanEqual:
1493 return moore::FleOp::create(builder, loc, lhs, rhs);
1494
1495 case BinaryOperator::LogicalAnd:
1496 case BinaryOperator::LogicalOr:
1497 case BinaryOperator::LogicalImplication:
1498 case BinaryOperator::LogicalEquivalence:
1499 return buildLogicalBOp(expr.op, lhs, rhs);
1500
1501 default:
1502 mlir::emitError(loc) << "Binary operator "
1503 << slang::ast::toString(expr.op)
1504 << " not supported with real valued operands!\n";
1505 return {};
1506 }
1507 }
1508
1509 // Helper function to convert two arguments to a simple bit vector type and
1510 // pass them into a binary op.
1511 template <class ConcreteOp>
1512 Value createBinary(Value lhs, Value rhs) {
1513 lhs = context.convertToSimpleBitVector(lhs);
1514 if (!lhs)
1515 return {};
1516 rhs = context.convertToSimpleBitVector(rhs);
1517 if (!rhs)
1518 return {};
1519 return ConcreteOp::create(builder, loc, lhs, rhs);
1520 }
1521
1522 // Handle binary operators.
1523 Value visit(const slang::ast::BinaryExpression &expr) {
1524 if (expr.left().kind == slang::ast::ExpressionKind::TypeReference &&
1525 expr.right().kind == slang::ast::ExpressionKind::TypeReference) {
1526 auto &lhsType =
1527 expr.left().as<slang::ast::TypeReferenceExpression>().targetType;
1528 auto &rhsType =
1529 expr.right().as<slang::ast::TypeReferenceExpression>().targetType;
1530 bool value = lhsType.isMatching(rhsType);
1531
1532 using slang::ast::BinaryOperator;
1533 switch (expr.op) {
1534 case BinaryOperator::Equality:
1535 case BinaryOperator::CaseEquality:
1536 break;
1537 case BinaryOperator::Inequality:
1538 case BinaryOperator::CaseInequality:
1539 value = !value;
1540 break;
1541 default:
1542 mlir::emitError(loc, "unsupported type reference binary operator");
1543 return {};
1544 }
1545
1546 auto type = moore::IntType::get(context.getContext(), /*width=*/1,
1547 moore::Domain::TwoValued);
1548 return moore::ConstantOp::create(builder, loc, type, value,
1549 /*isSigned=*/false);
1550 }
1551
1552 // First check whether we need real or integral BOps
1553 const auto *rhsFloatType =
1554 expr.right().type->as_if<slang::ast::FloatingType>();
1555 const auto *lhsFloatType =
1556 expr.left().type->as_if<slang::ast::FloatingType>();
1557
1558 // If either arg is real-typed, treat as real BOp.
1559 if (rhsFloatType || lhsFloatType)
1560 return visitRealBOp(expr);
1561
1562 // Check whether we are comparing against a Class Handle or CHandle
1563 const auto rhsIsClass = expr.right().type->isClass();
1564 const auto lhsIsClass = expr.left().type->isClass();
1565 const auto rhsIsChandle = expr.right().type->isCHandle();
1566 const auto lhsIsChandle = expr.left().type->isCHandle();
1567 // If either arg is class handle-typed, treat as class handle BOp.
1568 if (rhsIsClass || lhsIsClass || rhsIsChandle || lhsIsChandle)
1569 return visitHandleBOp(expr);
1570
1571 auto lhs = context.convertRvalueExpression(expr.left());
1572 if (!lhs)
1573 return {};
1574 auto rhs = context.convertRvalueExpression(expr.right());
1575 if (!rhs)
1576 return {};
1577
1578 // Determine the domain of the result.
1579 Domain domain = Domain::TwoValued;
1580 if (expr.type->isFourState() || expr.left().type->isFourState() ||
1581 expr.right().type->isFourState())
1582 domain = Domain::FourValued;
1583
1584 using slang::ast::BinaryOperator;
1585 switch (expr.op) {
1586 case BinaryOperator::Add:
1587 return createBinary<moore::AddOp>(lhs, rhs);
1588 case BinaryOperator::Subtract:
1589 return createBinary<moore::SubOp>(lhs, rhs);
1590 case BinaryOperator::Multiply:
1591 return createBinary<moore::MulOp>(lhs, rhs);
1592 case BinaryOperator::Divide:
1593 if (expr.type->isSigned())
1594 return createBinary<moore::DivSOp>(lhs, rhs);
1595 else
1596 return createBinary<moore::DivUOp>(lhs, rhs);
1597 case BinaryOperator::Mod:
1598 if (expr.type->isSigned())
1599 return createBinary<moore::ModSOp>(lhs, rhs);
1600 else
1601 return createBinary<moore::ModUOp>(lhs, rhs);
1602 case BinaryOperator::Power: {
1603 // Slang casts the LHS and result of the `**` operator to a four-valued
1604 // type, since the operator can return X even for two-valued inputs. To
1605 // maintain uniform types across operands and results, cast the RHS to
1606 // that four-valued type as well.
1607 auto rhsCast = context.materializeConversion(
1608 lhs.getType(), rhs, expr.right().type->isSigned(), rhs.getLoc());
1609 if (expr.type->isSigned())
1610 return createBinary<moore::PowSOp>(lhs, rhsCast);
1611 else
1612 return createBinary<moore::PowUOp>(lhs, rhsCast);
1613 }
1614
1615 case BinaryOperator::BinaryAnd:
1616 return createBinary<moore::AndOp>(lhs, rhs);
1617 case BinaryOperator::BinaryOr:
1618 return createBinary<moore::OrOp>(lhs, rhs);
1619 case BinaryOperator::BinaryXor:
1620 return createBinary<moore::XorOp>(lhs, rhs);
1621 case BinaryOperator::BinaryXnor: {
1622 auto result = createBinary<moore::XorOp>(lhs, rhs);
1623 if (!result)
1624 return {};
1625 return moore::NotOp::create(builder, loc, result);
1626 }
1627
1628 case BinaryOperator::Equality:
1629 if (isa<moore::UnpackedArrayType>(lhs.getType()))
1630 return moore::UArrayCmpOp::create(
1631 builder, loc, moore::UArrayCmpPredicate::eq, lhs, rhs);
1632 else if (isa<moore::StringType>(lhs.getType()))
1633 return moore::StringCmpOp::create(
1634 builder, loc, moore::StringCmpPredicate::eq, lhs, rhs);
1635 else if (isa<moore::QueueType>(lhs.getType()))
1636 return moore::QueueCmpOp::create(
1637 builder, loc, moore::UArrayCmpPredicate::eq, lhs, rhs);
1638 else
1639 return createBinary<moore::EqOp>(lhs, rhs);
1640 case BinaryOperator::Inequality:
1641 if (isa<moore::UnpackedArrayType>(lhs.getType()))
1642 return moore::UArrayCmpOp::create(
1643 builder, loc, moore::UArrayCmpPredicate::ne, lhs, rhs);
1644 else if (isa<moore::StringType>(lhs.getType()))
1645 return moore::StringCmpOp::create(
1646 builder, loc, moore::StringCmpPredicate::ne, lhs, rhs);
1647 else if (isa<moore::QueueType>(lhs.getType()))
1648 return moore::QueueCmpOp::create(
1649 builder, loc, moore::UArrayCmpPredicate::ne, lhs, rhs);
1650 else
1651 return createBinary<moore::NeOp>(lhs, rhs);
1652 case BinaryOperator::CaseEquality:
1653 return createBinary<moore::CaseEqOp>(lhs, rhs);
1654 case BinaryOperator::CaseInequality:
1655 return createBinary<moore::CaseNeOp>(lhs, rhs);
1656 case BinaryOperator::WildcardEquality:
1657 return createBinary<moore::WildcardEqOp>(lhs, rhs);
1658 case BinaryOperator::WildcardInequality:
1659 return createBinary<moore::WildcardNeOp>(lhs, rhs);
1660
1661 case BinaryOperator::GreaterThanEqual:
1662 if (expr.left().type->isSigned())
1663 return createBinary<moore::SgeOp>(lhs, rhs);
1664 else if (isa<moore::StringType>(lhs.getType()))
1665 return moore::StringCmpOp::create(
1666 builder, loc, moore::StringCmpPredicate::ge, lhs, rhs);
1667 else
1668 return createBinary<moore::UgeOp>(lhs, rhs);
1669 case BinaryOperator::GreaterThan:
1670 if (expr.left().type->isSigned())
1671 return createBinary<moore::SgtOp>(lhs, rhs);
1672 else if (isa<moore::StringType>(lhs.getType()))
1673 return moore::StringCmpOp::create(
1674 builder, loc, moore::StringCmpPredicate::gt, lhs, rhs);
1675 else
1676 return createBinary<moore::UgtOp>(lhs, rhs);
1677 case BinaryOperator::LessThanEqual:
1678 if (expr.left().type->isSigned())
1679 return createBinary<moore::SleOp>(lhs, rhs);
1680 else if (isa<moore::StringType>(lhs.getType()))
1681 return moore::StringCmpOp::create(
1682 builder, loc, moore::StringCmpPredicate::le, lhs, rhs);
1683 else
1684 return createBinary<moore::UleOp>(lhs, rhs);
1685 case BinaryOperator::LessThan:
1686 if (expr.left().type->isSigned())
1687 return createBinary<moore::SltOp>(lhs, rhs);
1688 else if (isa<moore::StringType>(lhs.getType()))
1689 return moore::StringCmpOp::create(
1690 builder, loc, moore::StringCmpPredicate::lt, lhs, rhs);
1691 else
1692 return createBinary<moore::UltOp>(lhs, rhs);
1693
1694 case BinaryOperator::LogicalAnd:
1695 case BinaryOperator::LogicalOr:
1696 case BinaryOperator::LogicalImplication:
1697 case BinaryOperator::LogicalEquivalence:
1698 return buildLogicalBOp(expr.op, lhs, rhs, domain);
1699
1700 case BinaryOperator::LogicalShiftLeft:
1701 return createBinary<moore::ShlOp>(lhs, rhs);
1702 case BinaryOperator::LogicalShiftRight:
1703 return createBinary<moore::ShrOp>(lhs, rhs);
1704 case BinaryOperator::ArithmeticShiftLeft:
1705 return createBinary<moore::ShlOp>(lhs, rhs);
1706 case BinaryOperator::ArithmeticShiftRight: {
1707 // The `>>>` operator is an arithmetic right shift if the LHS operand is
1708 // signed, or a logical right shift if the operand is unsigned.
1709 lhs = context.convertToSimpleBitVector(lhs);
1710 rhs = context.convertToSimpleBitVector(rhs);
1711 if (!lhs || !rhs)
1712 return {};
1713 if (expr.type->isSigned())
1714 return moore::AShrOp::create(builder, loc, lhs, rhs);
1715 return moore::ShrOp::create(builder, loc, lhs, rhs);
1716 }
1717 }
1718
1719 mlir::emitError(loc, "unsupported binary operator");
1720 return {};
1721 }
1722
1723 // Handle `'0`, `'1`, `'x`, and `'z` literals.
1724 Value visit(const slang::ast::UnbasedUnsizedIntegerLiteral &expr) {
1725 return context.materializeSVInt(expr.getValue(), *expr.type, loc);
1726 }
1727
1728 // Handle integer literals.
1729 Value visit(const slang::ast::IntegerLiteral &expr) {
1730 return context.materializeSVInt(expr.getValue(), *expr.type, loc);
1731 }
1732
1733 // Handle time literals.
1734 Value visit(const slang::ast::TimeLiteral &expr) {
1735 // The time literal is expressed in the current time scale. Determine the
1736 // conversion factor to convert the literal from the current time scale into
1737 // femtoseconds, and round the scaled value to femtoseconds.
1738 double scale = getTimeScaleInFemtoseconds(context);
1739 double value = std::round(expr.getValue() * scale);
1740 assert(value >= 0.0);
1741
1742 // Check that the value does not exceed what we can represent in the IR.
1743 // Casting the maximum uint64 value to double changes its value from
1744 // 18446744073709551615 to 18446744073709551616, which makes the comparison
1745 // overestimate the largest number we can represent. To avoid this, round
1746 // the maximum value down to the closest number that only has the front 53
1747 // bits set. This matches the mantissa of a double, plus the implicit
1748 // leading 1, ensuring that we can accurately represent the limit.
1749 static constexpr uint64_t limit =
1750 (std::numeric_limits<uint64_t>::max() >> 11) << 11;
1751 if (value > limit) {
1752 mlir::emitError(loc) << "time value is larger than " << limit << " fs";
1753 return {};
1754 }
1755
1756 return moore::ConstantTimeOp::create(builder, loc,
1757 static_cast<uint64_t>(value));
1758 }
1759
1760 // Handle replications.
1761 Value visit(const slang::ast::ReplicationExpression &expr) {
1762 auto type = context.convertType(*expr.type);
1763 auto value = context.convertRvalueExpression(expr.concat());
1764 if (!value)
1765 return {};
1766 return moore::ReplicateOp::create(builder, loc, type, value);
1767 }
1768
1769 // Handle set membership operator.
1770 Value visit(const slang::ast::InsideExpression &expr) {
1771 auto lhs = context.convertToSimpleBitVector(
1772 context.convertRvalueExpression(expr.left()));
1773 if (!lhs)
1774 return {};
1775
1776 // All conditions for determining whether it is inside.
1777 SmallVector<Value> conditions;
1778
1779 // Traverse open range list.
1780 for (const auto *listExpr : expr.rangeList()) {
1781 auto cond = context.convertInsideCheck(lhs, loc, *listExpr);
1782 if (!cond)
1783 return {};
1784
1785 conditions.push_back(cond);
1786 }
1787
1788 // Calculate the final result by `or` op.
1789 auto result = conditions.back();
1790 conditions.pop_back();
1791 while (!conditions.empty()) {
1792 result = moore::OrOp::create(builder, loc, conditions.back(), result);
1793 conditions.pop_back();
1794 }
1795 return result;
1796 }
1797
1798 // Handle conditional operator `?:`.
1799 Value visit(const slang::ast::ConditionalExpression &expr) {
1800 auto type = context.convertType(*expr.type);
1801
1802 // Handle condition.
1803 if (expr.conditions.size() > 1) {
1804 mlir::emitError(loc)
1805 << "unsupported conditional expression with more than one condition";
1806 return {};
1807 }
1808 const auto &cond = expr.conditions[0];
1809 if (cond.pattern) {
1810 mlir::emitError(loc) << "unsupported conditional expression with pattern";
1811 return {};
1812 }
1813 auto value =
1814 context.convertToBool(context.convertRvalueExpression(*cond.expr));
1815 if (!value)
1816 return {};
1817 auto conditionalOp =
1818 moore::ConditionalOp::create(builder, loc, type, value);
1819
1820 // Create blocks for true region and false region.
1821 auto &trueBlock = conditionalOp.getTrueRegion().emplaceBlock();
1822 auto &falseBlock = conditionalOp.getFalseRegion().emplaceBlock();
1823
1824 OpBuilder::InsertionGuard g(builder);
1825
1826 // Handle left expression.
1827 builder.setInsertionPointToStart(&trueBlock);
1828 auto trueValue = context.convertRvalueExpression(expr.left(), type);
1829 if (!trueValue)
1830 return {};
1831 moore::YieldOp::create(builder, loc, trueValue);
1832
1833 // Handle right expression.
1834 builder.setInsertionPointToStart(&falseBlock);
1835 auto falseValue = context.convertRvalueExpression(expr.right(), type);
1836 if (!falseValue)
1837 return {};
1838 moore::YieldOp::create(builder, loc, falseValue);
1839
1840 return conditionalOp.getResult();
1841 }
1842
1843 /// Handle calls.
1844 Value visit(const slang::ast::CallExpression &expr) {
1845 // Try to materialize constant values directly.
1846 auto constant = context.evaluateConstant(expr);
1847 if (auto value = context.materializeConstant(constant, *expr.type, loc))
1848 return value;
1849
1850 return std::visit(
1851 [&](auto &subroutine) { return visitCall(expr, subroutine); },
1852 expr.subroutine);
1853 }
1854
1855 /// Get both the actual `this` argument of a method call and the required
1856 /// class type.
1857 std::pair<Value, moore::ClassHandleType>
1858 getMethodReceiverTypeHandle(const slang::ast::CallExpression &expr) {
1859
1860 moore::ClassHandleType handleTy;
1861 Value thisRef;
1862
1863 // Qualified call: t.m(...), extract from thisClass.
1864 if (const slang::ast::Expression *recvExpr = expr.thisClass()) {
1865 thisRef = context.convertRvalueExpression(*recvExpr);
1866 if (!thisRef)
1867 return {};
1868 } else {
1869 // Unqualified call inside a method body: try using implicit %this.
1870 thisRef = context.getImplicitThisRef();
1871 if (!thisRef) {
1872 mlir::emitError(loc) << "method '" << expr.getSubroutineName()
1873 << "' called without an object";
1874 return {};
1875 }
1876 }
1877 handleTy = cast<moore::ClassHandleType>(thisRef.getType());
1878 return {thisRef, handleTy};
1879 }
1880
1881 /// Build a method call including implicit this argument.
1882 mlir::CallOpInterface
1883 buildMethodCall(const slang::ast::SubroutineSymbol *subroutine,
1884 FunctionLowering *lowering,
1885 moore::ClassHandleType actualHandleTy, Value actualThisRef,
1886 SmallVector<Value> &arguments,
1887 SmallVector<Type> &resultTypes) {
1888
1889 // Get the expected receiver type from the lowered method
1890 auto funcTy = cast<FunctionType>(lowering->op.getFunctionType());
1891 auto expected0 = funcTy.getInput(0);
1892 auto expectedHdlTy = cast<moore::ClassHandleType>(expected0);
1893
1894 // Upcast the handle as necessary.
1895 auto implicitThisRef = context.materializeConversion(
1896 expectedHdlTy, actualThisRef, false, actualThisRef.getLoc());
1897
1898 // Build an argument list where the this reference is the first argument.
1899 SmallVector<Value> explicitArguments;
1900 explicitArguments.reserve(arguments.size() + 1);
1901 explicitArguments.push_back(implicitThisRef);
1902 explicitArguments.append(arguments.begin(), arguments.end());
1903
1904 // Method call: choose direct vs virtual.
1905 const bool isVirtual =
1906 (subroutine->flags & slang::ast::MethodFlags::Virtual) != 0;
1907
1908 if (!isVirtual) {
1909 auto calleeSym = lowering->op.getNameAttr().getValue();
1910 if (isa<moore::CoroutineOp>(lowering->op.getOperation()))
1911 return moore::CallCoroutineOp::create(builder, loc, resultTypes,
1912 calleeSym, explicitArguments);
1913 return mlir::func::CallOp::create(builder, loc, resultTypes, calleeSym,
1914 explicitArguments);
1915 }
1916
1917 auto funcName = subroutine->name;
1918 auto method = moore::VTableLoadMethodOp::create(
1919 builder, loc, funcTy, actualThisRef,
1920 SymbolRefAttr::get(context.getContext(), funcName));
1921 return mlir::func::CallIndirectOp::create(builder, loc, method,
1922 explicitArguments);
1923 }
1924
1925 /// Handle subroutine calls.
1926 Value visitCall(const slang::ast::CallExpression &expr,
1927 const slang::ast::SubroutineSymbol *subroutine) {
1928
1929 const bool isMethod = (subroutine->thisVar != nullptr);
1930
1931 auto *lowering = context.declareFunction(*subroutine);
1932 if (!lowering)
1933 return {};
1934
1935 if (isa<moore::DPIFuncOp>(lowering->op.getOperation())) {
1936 SmallVector<Value> operands;
1937 SmallVector<Value> resultTargets;
1938
1939 for (auto [callArg, declArg] :
1940 llvm::zip(expr.arguments(), subroutine->getArguments())) {
1941 auto *actual = callArg;
1942 if (const auto *assign =
1943 actual->as_if<slang::ast::AssignmentExpression>())
1944 actual = &assign->left();
1945
1946 auto argType = context.convertType(declArg->getType());
1947 if (!argType)
1948 return {};
1949
1950 switch (declArg->direction) {
1951 case slang::ast::ArgumentDirection::In: {
1952 auto value = context.convertRvalueExpression(*actual, argType);
1953 if (!value)
1954 return {};
1955 operands.push_back(value);
1956 break;
1957 }
1958 case slang::ast::ArgumentDirection::Out: {
1959 auto lvalue = context.convertLvalueExpression(*actual);
1960 if (!lvalue)
1961 return {};
1962 resultTargets.push_back(lvalue);
1963 break;
1964 }
1965 case slang::ast::ArgumentDirection::InOut:
1966 case slang::ast::ArgumentDirection::Ref: {
1967 auto lvalue = context.convertLvalueExpression(*actual);
1968 if (!lvalue)
1969 return {};
1970 auto value = context.convertRvalueExpression(*actual, argType);
1971 if (!value)
1972 return {};
1973 operands.push_back(value);
1974 resultTargets.push_back(lvalue);
1975 break;
1976 }
1977 }
1978 }
1979
1980 SmallVector<Type> resultTypes(
1981 cast<FunctionType>(lowering->op.getFunctionType()).getResults());
1982 auto callOp = moore::FuncDPICallOp::create(
1983 builder, loc, resultTypes,
1984 SymbolRefAttr::get(lowering->op.getNameAttr()), operands);
1985
1986 unsigned resultIndex = 0;
1987 unsigned targetIndex = 0;
1988 for (const auto *declArg : subroutine->getArguments()) {
1989 auto argType = context.convertType(declArg->getType());
1990 if (!argType)
1991 return {};
1992
1993 switch (declArg->direction) {
1994 case slang::ast::ArgumentDirection::Out:
1995 case slang::ast::ArgumentDirection::InOut:
1996 case slang::ast::ArgumentDirection::Ref: {
1997 auto lvalue = resultTargets[targetIndex++];
1998 auto refTy = dyn_cast<moore::RefType>(lvalue.getType());
1999 if (!refTy) {
2000 lowering->op->emitError(
2001 "expected DPI output target to be moore::RefType");
2002 return {};
2003 }
2004 auto converted = context.materializeConversion(
2005 refTy.getNestedType(), callOp->getResult(resultIndex++),
2006 declArg->getType().isSigned(), loc);
2007 if (!converted)
2008 return {};
2009 moore::BlockingAssignOp::create(builder, loc, lvalue, converted);
2010 break;
2011 }
2012 default:
2013 break;
2014 }
2015 }
2016
2017 if (!subroutine->getReturnType().isVoid())
2018 return callOp->getResult(resultIndex);
2019
2020 return mlir::UnrealizedConversionCastOp::create(
2021 builder, loc, moore::VoidType::get(context.getContext()),
2022 ValueRange{})
2023 .getResult(0);
2024 }
2025
2026 // Convert the call arguments. Input arguments are converted to an rvalue.
2027 // All other arguments are converted to lvalues and passed into the function
2028 // by reference.
2029 SmallVector<Value> arguments;
2030 for (auto [callArg, declArg] :
2031 llvm::zip(expr.arguments(), subroutine->getArguments())) {
2032
2033 // Unpack the `<expr> = EmptyArgument` pattern emitted by Slang for output
2034 // and inout arguments.
2035 auto *expr = callArg;
2036 if (const auto *assign = expr->as_if<slang::ast::AssignmentExpression>())
2037 expr = &assign->left();
2038
2039 Value value;
2040 auto type = context.convertType(declArg->getType());
2041 if (declArg->direction == slang::ast::ArgumentDirection::In) {
2042 value = context.convertRvalueExpression(*expr, type);
2043 } else {
2044 Value lvalue = context.convertLvalueExpression(*expr);
2045 auto unpackedType = dyn_cast<moore::UnpackedType>(type);
2046 if (!unpackedType)
2047 return {};
2048 value =
2049 context.materializeConversion(moore::RefType::get(unpackedType),
2050 lvalue, expr->type->isSigned(), loc);
2051 }
2052 if (!value)
2053 return {};
2054 arguments.push_back(value);
2055 }
2056
2057 // Pass captured variables as extra arguments. Each captured AST symbol is
2058 // resolved to an MLIR value through the scoped symbol table, which
2059 // naturally handles transitive captures (the caller’s own capture block
2060 // argument will be found for variables captured from an outer scope).
2061 for (auto *sym : lowering->capturedSymbols) {
2062 Value val = context.valueSymbols.lookup(sym);
2063 if (!val) {
2064 mlir::emitError(loc) << "failed to resolve captured variable `"
2065 << sym->name << "` at call site";
2066 return {};
2067 }
2068 arguments.push_back(val);
2069 }
2070
2071 // Determine result types from the declared/converted func op.
2072 SmallVector<Type> resultTypes(
2073 cast<FunctionType>(lowering->op.getFunctionType()).getResults().begin(),
2074 cast<FunctionType>(lowering->op.getFunctionType()).getResults().end());
2075
2076 mlir::CallOpInterface callOp;
2077 if (isMethod) {
2078 // Class functions -> build func.call / func.indirect_call with implicit
2079 // this argument
2080 auto [thisRef, tyHandle] = getMethodReceiverTypeHandle(expr);
2081 callOp = buildMethodCall(subroutine, lowering, tyHandle, thisRef,
2082 arguments, resultTypes);
2083 } else if (isa<moore::CoroutineOp>(lowering->op.getOperation())) {
2084 // Free task -> moore.call_coroutine
2085 auto coroutine = cast<moore::CoroutineOp>(lowering->op.getOperation());
2086 callOp =
2087 moore::CallCoroutineOp::create(builder, loc, coroutine, arguments);
2088 } else {
2089 // Free function -> func.call
2090 auto funcOp = cast<mlir::func::FuncOp>(lowering->op.getOperation());
2091 callOp = mlir::func::CallOp::create(builder, loc, funcOp, arguments);
2092 }
2093
2094 auto result = resultTypes.size() > 0 ? callOp->getOpResult(0) : Value{};
2095 // For calls to void functions we need to have a value to return from this
2096 // function. Create a dummy `unrealized_conversion_cast`, which will get
2097 // deleted again later on.
2098 if (resultTypes.size() == 0)
2099 return mlir::UnrealizedConversionCastOp::create(
2100 builder, loc, moore::VoidType::get(context.getContext()),
2101 ValueRange{})
2102 .getResult(0);
2103
2104 return result;
2105 }
2106
2107 /// Handle system calls.
2108 Value visitCall(const slang::ast::CallExpression &expr,
2109 const slang::ast::CallExpression::SystemCallInfo &info) {
2110 using ksn = slang::parsing::KnownSystemName;
2111 const auto &subroutine = *info.subroutine;
2112 auto nameId = subroutine.knownNameId;
2113
2114 // $rose, $fell, $stable, $changed, $past, and $sampled are only valid in
2115 // the context of properties and assertions. Those are treated in the
2116 // LTLDialect; treat them there instead.
2117 switch (nameId) {
2118 case ksn::Rose:
2119 case ksn::Fell:
2120 case ksn::Stable:
2121 case ksn::Changed:
2122 case ksn::Past:
2123 case ksn::Sampled:
2124 return context.convertAssertionCallExpression(expr, info, loc);
2125 default:
2126 break;
2127 }
2128
2129 auto args = expr.arguments();
2130
2131 // $sformatf() and $sformat look like system tasks, but we handle string
2132 // formatting differently from expression evaluation, so handle them
2133 // separately.
2134 // According to IEEE 1800-2023 Section 21.3.3 "Formatting data to a
2135 // string" $sformatf works just like the string formatting but returns
2136 // a StringType.
2137 if (nameId == ksn::SFormatF) {
2138 // Create the FormatString
2139 auto fmtValue = context.convertFormatString(
2140 expr.arguments(), loc, moore::IntFormat::Decimal, false);
2141 if (failed(fmtValue))
2142 return {};
2143 return fmtValue.value();
2144 }
2145
2146 // Convert the system call using unified dispatch
2147 auto result = context.convertSystemCall(subroutine, loc, args);
2148 if (!result)
2149 return {};
2150
2151 auto ty = context.convertType(*expr.type);
2152 // Bit vector builtins ($countones, $isunknown, $onehot, $onehot0) return
2153 // inherently unsigned results that must be zero-extended, even though
2154 // Slang's declared return type may be signed int.
2155 bool isSigned = expr.type->isSigned();
2156 if (nameId == ksn::CountOnes || nameId == ksn::IsUnknown ||
2157 nameId == ksn::OneHot || nameId == ksn::OneHot0)
2158 isSigned = false;
2159 return context.materializeConversion(ty, result, isSigned, loc);
2160 }
2161
2162 /// Handle string literals.
2163 Value visit(const slang::ast::StringLiteral &expr) {
2164 auto type = context.convertType(*expr.type);
2165 return moore::ConstantStringOp::create(builder, loc, type, expr.getValue());
2166 }
2167
2168 /// Handle real literals.
2169 Value visit(const slang::ast::RealLiteral &expr) {
2170 auto fTy = mlir::Float64Type::get(context.getContext());
2171 auto attr = mlir::FloatAttr::get(fTy, expr.getValue());
2172 return moore::ConstantRealOp::create(builder, loc, attr).getResult();
2173 }
2174
2175 /// Helper function to convert RValues at creation of a new Struct, Array or
2176 /// Int.
2177 FailureOr<SmallVector<Value>>
2178 convertElements(const slang::ast::AssignmentPatternExpressionBase &expr,
2179 std::variant<Type, ArrayRef<Type>> expectedTypes,
2180 unsigned replCount) {
2181 const auto &elts = expr.elements();
2182 const size_t elementCount = elts.size();
2183
2184 // Inspect the variant.
2185 const bool hasBroadcast =
2186 std::holds_alternative<Type>(expectedTypes) &&
2187 static_cast<bool>(std::get<Type>(expectedTypes)); // non-null Type
2188
2189 const bool hasPerElem =
2190 std::holds_alternative<ArrayRef<Type>>(expectedTypes) &&
2191 !std::get<ArrayRef<Type>>(expectedTypes).empty();
2192
2193 // If per-element types are provided, enforce arity.
2194 if (hasPerElem) {
2195 auto types = std::get<ArrayRef<Type>>(expectedTypes);
2196 if (types.size() != elementCount) {
2197 mlir::emitError(loc)
2198 << "assignment pattern arity mismatch: expected " << types.size()
2199 << " elements, got " << elementCount;
2200 return failure();
2201 }
2202 }
2203
2204 SmallVector<Value> converted;
2205 converted.reserve(elementCount * std::max(1u, replCount));
2206
2207 // Convert each element heuristically, no type is expected
2208 if (!hasBroadcast && !hasPerElem) {
2209 // No expected type info.
2210 for (const auto *elementExpr : elts) {
2211 Value v = context.convertRvalueExpression(*elementExpr);
2212 if (!v)
2213 return failure();
2214 converted.push_back(v);
2215 }
2216 } else if (hasBroadcast) {
2217 // Same expected type for all elements.
2218 Type want = std::get<Type>(expectedTypes);
2219 for (const auto *elementExpr : elts) {
2220 Value v = want ? context.convertRvalueExpression(*elementExpr, want)
2221 : context.convertRvalueExpression(*elementExpr);
2222 if (!v)
2223 return failure();
2224 converted.push_back(v);
2225 }
2226 } else { // hasPerElem, individual type is expected for each element
2227 auto types = std::get<ArrayRef<Type>>(expectedTypes);
2228 for (size_t i = 0; i < elementCount; ++i) {
2229 Type want = types[i];
2230 const auto *elementExpr = elts[i];
2231 Value v = want ? context.convertRvalueExpression(*elementExpr, want)
2232 : context.convertRvalueExpression(*elementExpr);
2233 if (!v)
2234 return failure();
2235 converted.push_back(v);
2236 }
2237 }
2238
2239 for (unsigned i = 1; i < replCount; ++i)
2240 converted.append(converted.begin(), converted.begin() + elementCount);
2241
2242 return converted;
2243 }
2244
2245 /// Handle assignment patterns.
2246 Value visitAssignmentPattern(
2247 const slang::ast::AssignmentPatternExpressionBase &expr,
2248 unsigned replCount = 1) {
2249 auto type = context.convertType(*expr.type);
2250 const auto &elts = expr.elements();
2251
2252 // Handle integers.
2253 if (auto intType = dyn_cast<moore::IntType>(type)) {
2254 auto elements = convertElements(expr, {}, replCount);
2255
2256 if (failed(elements))
2257 return {};
2258
2259 assert(intType.getWidth() == elements->size());
2260 std::reverse(elements->begin(), elements->end());
2261 return moore::ConcatOp::create(builder, loc, intType, *elements);
2262 }
2263
2264 // Handle packed structs.
2265 if (auto structType = dyn_cast<moore::StructType>(type)) {
2266 SmallVector<Type> expectedTy;
2267 expectedTy.reserve(structType.getMembers().size());
2268 for (auto member : structType.getMembers())
2269 expectedTy.push_back(member.type);
2270
2271 FailureOr<SmallVector<Value>> elements;
2272 if (expectedTy.size() == elts.size())
2273 elements = convertElements(expr, expectedTy, replCount);
2274 else
2275 elements = convertElements(expr, {}, replCount);
2276
2277 if (failed(elements))
2278 return {};
2279
2280 assert(structType.getMembers().size() == elements->size());
2281 return moore::StructCreateOp::create(builder, loc, structType, *elements);
2282 }
2283
2284 // Handle unpacked structs.
2285 if (auto structType = dyn_cast<moore::UnpackedStructType>(type)) {
2286 SmallVector<Type> expectedTy;
2287 expectedTy.reserve(structType.getMembers().size());
2288 for (auto member : structType.getMembers())
2289 expectedTy.push_back(member.type);
2290
2291 FailureOr<SmallVector<Value>> elements;
2292 if (expectedTy.size() == elts.size())
2293 elements = convertElements(expr, expectedTy, replCount);
2294 else
2295 elements = convertElements(expr, {}, replCount);
2296
2297 if (failed(elements))
2298 return {};
2299
2300 assert(structType.getMembers().size() == elements->size());
2301
2302 return moore::StructCreateOp::create(builder, loc, structType, *elements);
2303 }
2304
2305 // Handle packed arrays.
2306 if (auto arrayType = dyn_cast<moore::ArrayType>(type)) {
2307 auto elements =
2308 convertElements(expr, arrayType.getElementType(), replCount);
2309
2310 if (failed(elements))
2311 return {};
2312
2313 assert(arrayType.getSize() == elements->size());
2314 return moore::ArrayCreateOp::create(builder, loc, arrayType, *elements);
2315 }
2316
2317 // Handle unpacked arrays.
2318 if (auto arrayType = dyn_cast<moore::UnpackedArrayType>(type)) {
2319 auto elements =
2320 convertElements(expr, arrayType.getElementType(), replCount);
2321
2322 if (failed(elements))
2323 return {};
2324
2325 assert(arrayType.getSize() == elements->size());
2326 return moore::ArrayCreateOp::create(builder, loc, arrayType, *elements);
2327 }
2328
2329 // Handle open/dynamic unpacked arrays.
2330 if (auto openType = dyn_cast<moore::OpenUnpackedArrayType>(type)) {
2331 auto elements =
2332 convertElements(expr, openType.getElementType(), replCount);
2333
2334 if (failed(elements))
2335 return {};
2336
2337 auto arrayType = moore::UnpackedArrayType::get(
2338 context.getContext(), elements->size(), openType.getElementType());
2339 return moore::ArrayCreateOp::create(builder, loc, arrayType, *elements);
2340 }
2341
2342 mlir::emitError(loc) << "unsupported assignment pattern with type " << type;
2343 return {};
2344 }
2345
2346 Value visit(const slang::ast::SimpleAssignmentPatternExpression &expr) {
2347 return visitAssignmentPattern(expr);
2348 }
2349
2350 Value visit(const slang::ast::StructuredAssignmentPatternExpression &expr) {
2351 return visitAssignmentPattern(expr);
2352 }
2353
2354 Value visit(const slang::ast::ReplicatedAssignmentPatternExpression &expr) {
2355 auto count =
2356 context.evaluateConstant(expr.count()).integer().as<unsigned>();
2357 assert(count && "Slang guarantees constant non-zero replication count");
2358 return visitAssignmentPattern(expr, *count);
2359 }
2360
2361 Value visit(const slang::ast::StreamingConcatenationExpression &expr) {
2362 SmallVector<Value> operands;
2363 for (auto stream : expr.streams()) {
2364 auto operandLoc = context.convertLocation(stream.operand->sourceRange);
2365 if (!stream.constantWithWidth.has_value() && stream.withExpr) {
2366 mlir::emitError(operandLoc)
2367 << "Moore only support streaming "
2368 "concatenation with fixed size 'with expression'";
2369 return {};
2370 }
2371 Value value;
2372 if (stream.constantWithWidth.has_value()) {
2373 value = context.convertRvalueExpression(*stream.withExpr);
2374 auto type = cast<moore::UnpackedType>(value.getType());
2375 auto intType = moore::IntType::get(
2376 context.getContext(), type.getBitSize().value(), type.getDomain());
2377 // Do not care if it's signed, because we will not do expansion.
2378 value = context.materializeConversion(intType, value, false, loc);
2379 } else {
2380 value = context.convertRvalueExpression(*stream.operand);
2381 }
2382
2383 value = context.convertToSimpleBitVector(value);
2384 if (!value)
2385 return {};
2386 operands.push_back(value);
2387 }
2388 Value value;
2389
2390 if (operands.size() == 1) {
2391 // There must be at least one element, otherwise slang will report an
2392 // error.
2393 value = operands.front();
2394 } else {
2395 value = moore::ConcatOp::create(builder, loc, operands).getResult();
2396 }
2397
2398 if (expr.getSliceSize() == 0) {
2399 return value;
2400 }
2401
2402 auto type = cast<moore::IntType>(value.getType());
2403 SmallVector<Value> slicedOperands;
2404 auto iterMax = type.getWidth() / expr.getSliceSize();
2405 auto remainSize = type.getWidth() % expr.getSliceSize();
2406
2407 for (size_t i = 0; i < iterMax; i++) {
2408 auto extractResultType = moore::IntType::get(
2409 context.getContext(), expr.getSliceSize(), type.getDomain());
2410
2411 auto extracted = moore::ExtractOp::create(builder, loc, extractResultType,
2412 value, i * expr.getSliceSize());
2413 slicedOperands.push_back(extracted);
2414 }
2415 // Handle other wire
2416 if (remainSize) {
2417 auto extractResultType = moore::IntType::get(
2418 context.getContext(), remainSize, type.getDomain());
2419
2420 auto extracted =
2421 moore::ExtractOp::create(builder, loc, extractResultType, value,
2422 iterMax * expr.getSliceSize());
2423 slicedOperands.push_back(extracted);
2424 }
2425
2426 return moore::ConcatOp::create(builder, loc, slicedOperands);
2427 }
2428
2429 Value visit(const slang::ast::AssertionInstanceExpression &expr) {
2430 return context.convertAssertionExpression(expr.body, loc);
2431 }
2432
2433 Value visit(const slang::ast::UnboundedLiteral &expr) {
2434 assert(context.getIndexedQueue() &&
2435 "slang checks $ only used within queue index expression");
2436
2437 // Compute queue size and subtract one to get the last element
2438 auto queueSize =
2439 moore::QueueSizeBIOp::create(builder, loc, context.getIndexedQueue());
2440 auto one = moore::ConstantOp::create(builder, loc, queueSize.getType(), 1);
2441 auto lastElement = moore::SubOp::create(builder, loc, queueSize, one);
2442
2443 return lastElement;
2444 }
2445
2446 // A new class expression can stand for one of two things:
2447 // 1) A call to the `new` method (ctor) of a class made outside the scope of
2448 // the class
2449 // 2) A call to the `super.new` method, i.e. the constructor of the base
2450 // class, within the scope of a class, more specifically, within the new
2451 // method override of a class.
2452 // In the first case we should emit an allocation and a call to the ctor if it
2453 // exists (it's optional in System Verilog), in the second case we should emit
2454 // a call to the parent's ctor (System Verilog only has single inheritance, so
2455 // super is always unambiguous), but no allocation, as the child class' new
2456 // invocation already allocated space for both its own and its parent's
2457 // properties.
2458 Value visit(const slang::ast::NewClassExpression &expr) {
2459 auto type = context.convertType(*expr.type);
2460 auto classTy = dyn_cast<moore::ClassHandleType>(type);
2461 Value newObj;
2462
2463 // We are calling new from within a new function, and it's pointing to
2464 // super. Check the implicit this ref to figure out the super class type.
2465 // Do not allocate a new object.
2466 if (!classTy && expr.isSuperClass) {
2467 newObj = context.getImplicitThisRef();
2468 if (!newObj || !newObj.getType() ||
2469 !isa<moore::ClassHandleType>(newObj.getType())) {
2470 mlir::emitError(loc) << "implicit this ref was not set while "
2471 "converting new class function";
2472 return {};
2473 }
2474 auto thisType = cast<moore::ClassHandleType>(newObj.getType());
2475 auto classDecl =
2476 cast<moore::ClassDeclOp>(*context.symbolTable.lookupNearestSymbolFrom(
2477 context.intoModuleOp, thisType.getClassSym()));
2478 auto baseClassSym = classDecl.getBase();
2479 classTy = circt::moore::ClassHandleType::get(context.getContext(),
2480 baseClassSym.value());
2481 } else {
2482 // We are calling from outside a class; allocate space for the object.
2483 newObj = moore::ClassNewOp::create(builder, loc, classTy, {});
2484 }
2485
2486 const auto *constructor = expr.constructorCall();
2487 // If there's no ctor, we are done.
2488 if (!constructor)
2489 return newObj;
2490
2491 if (const auto *callConstructor =
2492 constructor->as_if<slang::ast::CallExpression>())
2493 if (const auto *subroutine =
2494 std::get_if<const slang::ast::SubroutineSymbol *>(
2495 &callConstructor->subroutine)) {
2496 if (!(*subroutine)->thisVar) {
2497 mlir::emitError(loc)
2498 << "unsupported constructor call without `this` argument";
2499 return {};
2500 }
2501 // Pass the newObj as the implicit this argument of the ctor.
2502 llvm::SaveAndRestore saveThis(context.currentThisRef, newObj);
2503 if (!visitCall(*callConstructor, *subroutine))
2504 return {};
2505 return newObj;
2506 }
2507 return {};
2508 }
2509
2510 /// Emit an error for all other expressions.
2511 template <typename T>
2512 Value visit(T &&node) {
2513 mlir::emitError(loc, "unsupported expression: ")
2514 << slang::ast::toString(node.kind);
2515 return {};
2516 }
2517
2518 Value visitInvalid(const slang::ast::Expression &expr) {
2519 mlir::emitError(loc, "invalid expression");
2520 return {};
2521 }
2522};
2523} // namespace
2524
2525//===----------------------------------------------------------------------===//
2526// Lvalue Conversion
2527//===----------------------------------------------------------------------===//
2528
2529namespace {
2530struct LvalueExprVisitor : public ExprVisitor {
2531 LvalueExprVisitor(Context &context, Location loc)
2532 : ExprVisitor(context, loc, /*isLvalue=*/true) {}
2533 using ExprVisitor::visit;
2534
2535 // Handle named values, such as references to declared variables.
2536 Value visit(const slang::ast::NamedValueExpression &expr) {
2537 // Handle local variables.
2538 if (auto value = context.valueSymbols.lookup(&expr.symbol))
2539 return value;
2540
2541 // Handle global variables.
2542 if (auto globalOp = context.globalVariables.lookup(&expr.symbol))
2543 return moore::GetGlobalVariableOp::create(builder, loc, globalOp);
2544
2545 if (auto *const property =
2546 expr.symbol.as_if<slang::ast::ClassPropertySymbol>()) {
2547 return visitClassProperty(context, *property);
2548 }
2549
2550 if (auto access = context.virtualIfaceMembers.lookup(&expr.symbol);
2551 access.base) {
2552 auto type = context.convertType(*expr.type);
2553 if (!type)
2554 return {};
2555 auto memberType = dyn_cast<moore::UnpackedType>(type);
2556 if (!memberType) {
2557 mlir::emitError(loc)
2558 << "unsupported virtual interface member type: " << type;
2559 return {};
2560 }
2561
2562 Value base = materializeSymbolRvalue(*access.base);
2563 if (!base) {
2564 auto d = mlir::emitError(loc, "unknown name `")
2565 << access.base->name << "`";
2566 d.attachNote(context.convertLocation(access.base->location))
2567 << "no rvalue generated for virtual interface base";
2568 return {};
2569 }
2570
2571 auto fieldName = access.fieldName
2572 ? access.fieldName
2573 : builder.getStringAttr(expr.symbol.name);
2574 auto memberRefType = moore::RefType::get(memberType);
2575 return moore::StructExtractOp::create(builder, loc, memberRefType,
2576 fieldName, base);
2577 }
2578
2579 auto d = mlir::emitError(loc, "unknown name `") << expr.symbol.name << "`";
2580 d.attachNote(context.convertLocation(expr.symbol.location))
2581 << "no lvalue generated for " << slang::ast::toString(expr.symbol.kind);
2582 return {};
2583 }
2584
2585 // Handle hierarchical values, such as `Top.sub.var = x`.
2586 Value visit(const slang::ast::HierarchicalValueExpression &expr) {
2587 // Canonicalize self-references (e.g., SubD.w inside SubD) to local
2588 // variable lookups (same rationale as rvalue visitor).
2589 if (!expr.ref.path.empty()) {
2590 if (auto *inst = expr.ref.path.front()
2591 .symbol->as_if<slang::ast::InstanceSymbol>()) {
2592 auto *symbolBody =
2593 expr.symbol.getParentScope()->getContainingInstance();
2594 if (&inst->body == symbolBody ||
2595 (symbolBody && inst->body.getDeclaringDefinition() ==
2596 symbolBody->getDeclaringDefinition())) {
2597 if (auto value = context.valueSymbols.lookup(&expr.symbol))
2598 return value;
2599 }
2600 }
2601 }
2602
2603 // Same capture priority as the rvalue visitor.
2604 if (auto value = context.resolveCapturedValue(expr.symbol))
2605 return value;
2606
2607 // For cross-instance hierarchical references, use the instance-aware
2608 // hierValueSymbols lookup (same priority and rationale as rvalue
2609 // visitor).
2610 if (auto key = context.buildHierValueKey(expr)) {
2611 if (auto it = context.hierValueSymbols.find(*key);
2612 it != context.hierValueSymbols.end())
2613 return it->second;
2614 }
2615
2616 // Fall back to scoped symbol table (same-scope lookups, self-refs).
2617 if (auto value = context.valueSymbols.lookup(&expr.symbol))
2618 return value;
2619
2620 if (auto value = lookupExpandedInterfaceMember(context, expr))
2621 return value;
2622
2623 // Handle global variables.
2624 if (auto globalOp = context.globalVariables.lookup(&expr.symbol))
2625 return moore::GetGlobalVariableOp::create(builder, loc, globalOp);
2626
2627 // Emit an error for those hierarchical values not recorded in the
2628 // `valueSymbols`.
2629 auto d = mlir::emitError(loc, "unknown hierarchical name `")
2630 << expr.symbol.name << "`";
2631 d.attachNote(context.convertLocation(expr.symbol.location))
2632 << "no lvalue generated for " << slang::ast::toString(expr.symbol.kind);
2633 return {};
2634 }
2635
2636 Value visit(const slang::ast::StreamingConcatenationExpression &expr) {
2637 SmallVector<Value> operands;
2638 for (auto stream : expr.streams()) {
2639 auto operandLoc = context.convertLocation(stream.operand->sourceRange);
2640 if (!stream.constantWithWidth.has_value() && stream.withExpr) {
2641 mlir::emitError(operandLoc)
2642 << "Moore only support streaming "
2643 "concatenation with fixed size 'with expression'";
2644 return {};
2645 }
2646 Value value;
2647 if (stream.constantWithWidth.has_value()) {
2648 value = context.convertLvalueExpression(*stream.withExpr);
2649 auto type = cast<moore::UnpackedType>(
2650 cast<moore::RefType>(value.getType()).getNestedType());
2651 auto intType = moore::RefType::get(moore::IntType::get(
2652 context.getContext(), type.getBitSize().value(), type.getDomain()));
2653 // Do not care if it's signed, because we will not do expansion.
2654 value = context.materializeConversion(intType, value, false, loc);
2655 } else {
2656 value = context.convertLvalueExpression(*stream.operand);
2657 }
2658
2659 if (!value)
2660 return {};
2661 operands.push_back(value);
2662 }
2663 Value value;
2664 if (operands.size() == 1) {
2665 // There must be at least one element, otherwise slang will report an
2666 // error.
2667 value = operands.front();
2668 } else {
2669 value = moore::ConcatRefOp::create(builder, loc, operands).getResult();
2670 }
2671
2672 if (expr.getSliceSize() == 0) {
2673 return value;
2674 }
2675
2676 auto type = cast<moore::IntType>(
2677 cast<moore::RefType>(value.getType()).getNestedType());
2678 SmallVector<Value> slicedOperands;
2679 auto widthSum = type.getWidth();
2680 auto domain = type.getDomain();
2681 auto iterMax = widthSum / expr.getSliceSize();
2682 auto remainSize = widthSum % expr.getSliceSize();
2683
2684 for (size_t i = 0; i < iterMax; i++) {
2685 auto extractResultType = moore::RefType::get(moore::IntType::get(
2686 context.getContext(), expr.getSliceSize(), domain));
2687
2688 auto extracted = moore::ExtractRefOp::create(
2689 builder, loc, extractResultType, value, i * expr.getSliceSize());
2690 slicedOperands.push_back(extracted);
2691 }
2692 // Handle other wire
2693 if (remainSize) {
2694 auto extractResultType = moore::RefType::get(
2695 moore::IntType::get(context.getContext(), remainSize, domain));
2696
2697 auto extracted =
2698 moore::ExtractRefOp::create(builder, loc, extractResultType, value,
2699 iterMax * expr.getSliceSize());
2700 slicedOperands.push_back(extracted);
2701 }
2702
2703 return moore::ConcatRefOp::create(builder, loc, slicedOperands);
2704 }
2705
2706 /// Emit an error for all other expressions.
2707 template <typename T>
2708 Value visit(T &&node) {
2709 return context.convertRvalueExpression(node);
2710 }
2711
2712 Value visitInvalid(const slang::ast::Expression &expr) {
2713 mlir::emitError(loc, "invalid expression");
2714 return {};
2715 }
2716};
2717} // namespace
2718
2719//===----------------------------------------------------------------------===//
2720// Hierarchical Name Helpers
2721//===----------------------------------------------------------------------===//
2722
2723Value Context::resolveCapturedValue(const slang::ast::ValueSymbol &sym) {
2725 return {};
2726 if (!llvm::is_contained(currentFunctionLowering->capturedSymbols, &sym))
2727 return {};
2728 return valueSymbols.lookup(&sym);
2729}
2730
2731std::optional<std::pair<const slang::ast::InstanceSymbol *, mlir::StringAttr>>
2733 const slang::ast::HierarchicalValueExpression &expr) {
2734 if (expr.ref.path.empty())
2735 return std::nullopt;
2736
2737 const slang::ast::InstanceSymbol *firstInst = nullptr;
2738 SmallVector<StringRef, 4> names;
2739 for (auto &elem : expr.ref.path) {
2740 if (auto *inst = elem.symbol->as_if<slang::ast::InstanceSymbol>()) {
2741 if (!firstInst) {
2742 firstInst = inst;
2743 } else {
2744 names.push_back(inst->name);
2745 }
2746 }
2747 }
2748 names.push_back(expr.symbol.name);
2749 std::string hierName = llvm::join(names, ".");
2750
2751 if (!firstInst)
2752 return std::nullopt;
2753 return std::make_pair(firstInst, builder.getStringAttr(hierName));
2754}
2755
2756//===----------------------------------------------------------------------===//
2757// Entry Points
2758//===----------------------------------------------------------------------===//
2759
2760Value Context::convertRvalueExpression(const slang::ast::Expression &expr,
2761 Type requiredType) {
2762 auto loc = convertLocation(expr.sourceRange);
2763 auto value = expr.visit(RvalueExprVisitor(*this, loc));
2764 if (value && requiredType)
2765 value =
2766 materializeConversion(requiredType, value, expr.type->isSigned(), loc);
2767 return value;
2768}
2769
2770Value Context::convertLvalueExpression(const slang::ast::Expression &expr) {
2771 auto loc = convertLocation(expr.sourceRange);
2772 return expr.visit(LvalueExprVisitor(*this, loc));
2773}
2774// NOLINTEND(misc-no-recursion)
2775
2776/// Helper function to convert a value to its "truthy" boolean value.
2777Value Context::convertToBool(Value value) {
2778 if (!value)
2779 return {};
2780 if (auto type = dyn_cast_or_null<moore::IntType>(value.getType()))
2781 if (type.getBitSize() == 1)
2782 return value;
2783 if (auto type = dyn_cast_or_null<moore::UnpackedType>(value.getType()))
2784 return moore::BoolCastOp::create(builder, value.getLoc(), value);
2785 mlir::emitError(value.getLoc(), "expression of type ")
2786 << value.getType() << " cannot be cast to a boolean";
2787 return {};
2788}
2789
2790/// Materialize a Slang real literal as a constant op.
2791Value Context::materializeSVReal(const slang::ConstantValue &svreal,
2792 const slang::ast::Type &astType,
2793 Location loc) {
2794 const auto *floatType = astType.as_if<slang::ast::FloatingType>();
2795 assert(floatType);
2796
2797 FloatAttr attr;
2798 if (svreal.isShortReal() &&
2799 floatType->floatKind == slang::ast::FloatingType::ShortReal) {
2800 attr = FloatAttr::get(builder.getF32Type(), svreal.shortReal().v);
2801 } else if (svreal.isReal() &&
2802 floatType->floatKind == slang::ast::FloatingType::Real) {
2803 attr = FloatAttr::get(builder.getF64Type(), svreal.real().v);
2804 } else {
2805 mlir::emitError(loc) << "invalid real constant";
2806 return {};
2807 }
2808
2809 return moore::ConstantRealOp::create(builder, loc, attr);
2810}
2811
2812/// Materialize a Slang string literal as a literal string constant op.
2813Value Context::materializeString(const slang::ConstantValue &stringLiteral,
2814 const slang::ast::Type &astType,
2815 Location loc) {
2816 if (!astType.isString())
2817 return {};
2818 const std::string &str = stringLiteral.str();
2819 auto intTy = moore::IntType::getInt(getContext(),
2820 static_cast<unsigned>(str.size() * 8));
2821 auto immInt =
2822 moore::ConstantStringOp::create(builder, loc, intTy, str).getResult();
2823 return moore::IntToStringOp::create(builder, loc, immInt).getResult();
2824}
2825
2826/// Materialize a Slang integer literal as a constant op.
2827Value Context::materializeSVInt(const slang::SVInt &svint,
2828 const slang::ast::Type &astType, Location loc) {
2829 auto type = convertType(astType);
2830 if (!type)
2831 return {};
2832
2833 bool typeIsFourValued = false;
2834 if (auto unpackedType = dyn_cast<moore::UnpackedType>(type))
2835 typeIsFourValued = unpackedType.getDomain() == moore::Domain::FourValued;
2836
2837 auto fvint = convertSVIntToFVInt(svint);
2838 auto intType = moore::IntType::get(getContext(), fvint.getBitWidth(),
2839 fvint.hasUnknown() || typeIsFourValued
2842 auto result = moore::ConstantOp::create(builder, loc, intType, fvint);
2843 return materializeConversion(type, result, astType.isSigned(), loc);
2844}
2845
2847 const slang::ConstantValue &constant,
2848 const slang::ast::FixedSizeUnpackedArrayType &astType, Location loc) {
2849
2850 auto type = convertType(astType);
2851 if (!type)
2852 return {};
2853
2854 // Handle string array constants.
2855 if (astType.elementType.isString()) {
2856 auto arrayType = dyn_cast<moore::UnpackedArrayType>(type);
2857 if (!arrayType)
2858 return {};
2859
2860 SmallVector<Value> elemVals;
2861 for (const auto &elem : constant.elements()) {
2862 if (!elem.isString())
2863 return {};
2864 auto value = materializeString(elem, astType.elementType, loc);
2865 if (!value)
2866 return {};
2867 elemVals.push_back(value);
2868 }
2869 if (elemVals.size() != arrayType.getSize())
2870 return {};
2871 return moore::ArrayCreateOp::create(builder, loc, arrayType, elemVals);
2872 }
2873
2874 // Check whether underlying type is an integer, if so, get bit width
2875 unsigned bitWidth;
2876 if (astType.elementType.isIntegral())
2877 bitWidth = astType.elementType.getBitWidth();
2878 else
2879 return {};
2880
2881 bool typeIsFourValued = false;
2882
2883 // Check whether the underlying type is four-valued
2884 if (auto unpackedType = dyn_cast<moore::UnpackedType>(type))
2885 typeIsFourValued = unpackedType.getDomain() == moore::Domain::FourValued;
2886 else
2887 return {};
2888
2889 auto domain =
2891
2892 // Construct the integer type this is an unpacked array of; if possible keep
2893 // it two-valued, unless any entry is four-valued or the underlying type is
2894 // four-valued
2895 auto intType = moore::IntType::get(getContext(), bitWidth, domain);
2896 // Construct the full array type from intType
2897 auto arrType = moore::UnpackedArrayType::get(
2898 getContext(), constant.elements().size(), intType);
2899
2900 llvm::SmallVector<mlir::Value> elemVals;
2901 moore::ConstantOp constOp;
2902
2903 mlir::OpBuilder::InsertionGuard guard(builder);
2904
2905 // Add one ConstantOp for every element in the array
2906 for (auto elem : constant.elements()) {
2907 FVInt fvInt = convertSVIntToFVInt(elem.integer());
2908 constOp = moore::ConstantOp::create(builder, loc, intType, fvInt);
2909 elemVals.push_back(constOp.getResult());
2910 }
2911
2912 // Take the result of each ConstantOp and concatenate them into an array (of
2913 // constant values).
2914 auto arrayOp = moore::ArrayCreateOp::create(builder, loc, arrType, elemVals);
2915
2916 return arrayOp.getResult();
2917}
2918
2919Value Context::materializeConstant(const slang::ConstantValue &constant,
2920 const slang::ast::Type &type, Location loc) {
2921
2922 if (auto *arr = type.as_if<slang::ast::FixedSizeUnpackedArrayType>())
2923 return materializeFixedSizeUnpackedArrayType(constant, *arr, loc);
2924 if (constant.isInteger())
2925 return materializeSVInt(constant.integer(), type, loc);
2926 if (constant.isReal() || constant.isShortReal())
2927 return materializeSVReal(constant, type, loc);
2928 if (constant.isString())
2929 return materializeString(constant, type, loc);
2930
2931 return {};
2932}
2933
2934slang::ConstantValue
2935Context::evaluateConstant(const slang::ast::Expression &expr) {
2936 using slang::ast::EvalFlags;
2937 slang::ast::EvalContext evalContext(
2938 slang::ast::ASTContext(compilation.getRoot(),
2939 slang::ast::LookupLocation::max),
2940 EvalFlags::CacheResults | EvalFlags::SpecparamsAllowed);
2941 return expr.eval(evalContext);
2942}
2943
2944/// Helper function to convert a value to its "truthy" boolean value and
2945/// convert it to the given domain.
2946Value Context::convertToBool(Value value, Domain domain) {
2947 value = convertToBool(value);
2948 if (!value)
2949 return {};
2950 auto type = moore::IntType::get(getContext(), 1, domain);
2951 return materializeConversion(type, value, false, value.getLoc());
2952}
2953
2955 if (!value)
2956 return {};
2957 if (isa<moore::IntType>(value.getType()))
2958 return value;
2959
2960 // Some operations in Slang's AST, for example bitwise or `|`, don't cast
2961 // packed struct/array operands to simple bit vectors but directly operate
2962 // on the struct/array. Since the corresponding IR ops operate only on
2963 // simple bit vectors, insert a conversion in this case.
2964 if (auto packed = dyn_cast<moore::PackedType>(value.getType()))
2965 if (auto sbvType = packed.getSimpleBitVector())
2966 return materializeConversion(sbvType, value, false, value.getLoc());
2967
2968 mlir::emitError(value.getLoc()) << "expression of type " << value.getType()
2969 << " cannot be cast to a simple bit vector";
2970 return {};
2971}
2972
2973/// Create the necessary operations to convert from a `PackedType` to the
2974/// corresponding simple bit vector `IntType`. This will apply special handling
2975/// to time values, which requires scaling by the local timescale.
2977 Location loc, bool fallible) {
2978 if (isa<moore::IntType>(value.getType()))
2979 return value;
2980
2981 auto &builder = context.builder;
2982 auto packedType = cast<moore::PackedType>(value.getType());
2983 auto intType = packedType.getSimpleBitVector();
2984 assert(intType);
2985
2986 // If we are converting from a time to an integer, divide the integer by the
2987 // timescale.
2988 if (isa<moore::TimeType>(packedType) &&
2990 value = builder.createOrFold<moore::TimeToLogicOp>(loc, value);
2991 auto scale = moore::ConstantOp::create(builder, loc, intType,
2993 return builder.createOrFold<moore::DivUOp>(loc, value, scale);
2994 }
2995
2996 // If this is an aggregate type, make sure that it does not contain any
2997 // `TimeType` fields. These require special conversion to ensure that the
2998 // local timescale is in effect.
2999 if (packedType.containsTimeType()) {
3000 if (!fallible)
3001 mlir::emitError(loc) << "unsupported conversion: " << packedType
3002 << " cannot be converted to " << intType
3003 << "; contains a time type";
3004 return {};
3005 }
3006
3007 // Otherwise create a simple `PackedToSBVOp` for the conversion.
3008 return builder.createOrFold<moore::PackedToSBVOp>(loc, value);
3009}
3010
3011/// Create the necessary operations to convert from a simple bit vector
3012/// `IntType` to an equivalent `PackedType`. This will apply special handling to
3013/// time values, which requires scaling by the local timescale.
3015 moore::PackedType packedType,
3016 Value value, Location loc,
3017 bool fallible) {
3018 if (value.getType() == packedType)
3019 return value;
3020
3021 auto &builder = context.builder;
3022 auto intType = cast<moore::IntType>(value.getType());
3023 assert(intType && intType == packedType.getSimpleBitVector());
3024
3025 // If we are converting from an integer to a time, multiply the integer by the
3026 // timescale.
3027 if (isa<moore::TimeType>(packedType) &&
3029 auto scale = moore::ConstantOp::create(builder, loc, intType,
3031 value = builder.createOrFold<moore::MulOp>(loc, value, scale);
3032 return builder.createOrFold<moore::LogicToTimeOp>(loc, value);
3033 }
3034
3035 // If this is an aggregate type, make sure that it does not contain any
3036 // `TimeType` fields. These require special conversion to ensure that the
3037 // local timescale is in effect.
3038 if (packedType.containsTimeType()) {
3039 if (!fallible)
3040 mlir::emitError(loc) << "unsupported conversion: " << intType
3041 << " cannot be converted to " << packedType
3042 << "; contains a time type";
3043 return {};
3044 }
3045
3046 // Otherwise create a simple `PackedToSBVOp` for the conversion.
3047 return builder.createOrFold<moore::SBVToPackedOp>(loc, packedType, value);
3048}
3049
3050/// Check whether the actual handle is a subclass of another handle type
3051/// and return a properly upcast version if so.
3052static mlir::Value maybeUpcastHandle(Context &context, mlir::Value actualHandle,
3053 moore::ClassHandleType expectedHandleTy) {
3054 auto loc = actualHandle.getLoc();
3055
3056 auto actualTy = actualHandle.getType();
3057 auto actualHandleTy = dyn_cast<moore::ClassHandleType>(actualTy);
3058 if (!actualHandleTy) {
3059 mlir::emitError(loc) << "expected a !moore.class<...> value, got "
3060 << actualTy;
3061 return {};
3062 }
3063
3064 // Fast path: already the expected handle type.
3065 if (actualHandleTy == expectedHandleTy)
3066 return actualHandle;
3067
3068 if (!context.isClassDerivedFrom(actualHandleTy, expectedHandleTy)) {
3069 mlir::emitError(loc)
3070 << "receiver class " << actualHandleTy.getClassSym()
3071 << " is not the same as, or derived from, expected base class "
3072 << expectedHandleTy.getClassSym().getRootReference();
3073 return {};
3074 }
3075
3076 // Only implicit upcasting is allowed - down casting should never be implicit.
3077 auto casted = moore::ClassUpcastOp::create(context.builder, loc,
3078 expectedHandleTy, actualHandle)
3079 .getResult();
3080 return casted;
3081}
3082
3083Value Context::materializeConversion(Type type, Value value, bool isSigned,
3084 Location loc, bool fallible) {
3085 // Nothing to do if the types are already equal.
3086 if (type == value.getType())
3087 return value;
3088
3089 // Handle packed types which can be converted to a simple bit vector. This
3090 // allows us to perform resizing and domain casting on that bit vector.
3091 auto dstPacked = dyn_cast<moore::PackedType>(type);
3092 auto srcPacked = dyn_cast<moore::PackedType>(value.getType());
3093 auto dstInt = dstPacked ? dstPacked.getSimpleBitVector() : moore::IntType();
3094 auto srcInt = srcPacked ? srcPacked.getSimpleBitVector() : moore::IntType();
3095
3096 if (dstInt && srcInt) {
3097 // Convert the value to a simple bit vector if it isn't one already.
3098 value = materializePackedToSBVConversion(*this, value, loc, fallible);
3099 if (!value)
3100 return {};
3101
3102 // Create truncation or sign/zero extension ops depending on the source and
3103 // destination width.
3104 auto resizedType = moore::IntType::get(
3105 value.getContext(), dstInt.getWidth(), srcPacked.getDomain());
3106 if (dstInt.getWidth() < srcInt.getWidth()) {
3107 value = builder.createOrFold<moore::TruncOp>(loc, resizedType, value);
3108 } else if (dstInt.getWidth() > srcInt.getWidth()) {
3109 if (isSigned)
3110 value = builder.createOrFold<moore::SExtOp>(loc, resizedType, value);
3111 else
3112 value = builder.createOrFold<moore::ZExtOp>(loc, resizedType, value);
3113 }
3114
3115 // Convert the domain if needed.
3116 if (dstInt.getDomain() != srcInt.getDomain()) {
3117 if (dstInt.getDomain() == moore::Domain::TwoValued)
3118 value = builder.createOrFold<moore::LogicToIntOp>(loc, value);
3119 else if (dstInt.getDomain() == moore::Domain::FourValued)
3120 value = builder.createOrFold<moore::IntToLogicOp>(loc, value);
3121 }
3122
3123 // Convert the value from a simple bit vector back to the packed type.
3124 value = materializeSBVToPackedConversion(*this, dstPacked, value, loc,
3125 fallible);
3126 if (!value)
3127 return {};
3128
3129 assert(value.getType() == type);
3130 return value;
3131 }
3132
3133 // Convert from FormatStringType to StringType
3134 if (isa<moore::StringType>(type) &&
3135 isa<moore::FormatStringType>(value.getType())) {
3136 return builder.createOrFold<moore::FormatStringToStringOp>(loc, value);
3137 }
3138
3139 // Convert from StringType to FormatStringType
3140 if (isa<moore::FormatStringType>(type) &&
3141 isa<moore::StringType>(value.getType())) {
3142 return builder.createOrFold<moore::FormatStringOp>(loc, value);
3143 }
3144
3145 // If converting between two queue types of the same element type, then we
3146 // just need to convert the queue bounds.
3147 if (isa<moore::QueueType>(type) && isa<moore::QueueType>(value.getType()) &&
3148 cast<moore::QueueType>(type).getElementType() ==
3149 cast<moore::QueueType>(value.getType()).getElementType())
3150 return builder.createOrFold<moore::QueueResizeOp>(loc, type, value);
3151
3152 // Convert from UnpackedArrayType to QueueType
3153 if (isa<moore::QueueType>(type) &&
3154 isa<moore::UnpackedArrayType>(value.getType())) {
3155 auto queueElType = dyn_cast<moore::QueueType>(type).getElementType();
3156 auto unpackedArrayElType =
3157 dyn_cast<moore::UnpackedArrayType>(value.getType()).getElementType();
3158
3159 if (queueElType == unpackedArrayElType) {
3160 return builder.createOrFold<moore::QueueFromUnpackedArrayOp>(loc, type,
3161 value);
3162 }
3163 }
3164
3165 // Handle Real To Int conversion
3166 if (dstInt && isa<moore::RealType>(value.getType())) {
3167 auto twoValInt = builder.createOrFold<moore::RealToIntOp>(
3168 loc, dstInt.getTwoValued(), value);
3169 return materializeConversion(type, twoValInt, true, loc, fallible);
3170 }
3171
3172 // Handle Int to Real conversion
3173 if (isa<moore::RealType>(type) && isa<moore::IntType>(value.getType())) {
3174 Value twoValInt;
3175 // Check if int needs to be converted to two-valued first
3176 if (dyn_cast<moore::IntType>(value.getType()).getDomain() ==
3178 twoValInt = value;
3179 else
3180 twoValInt = materializeConversion(
3181 dyn_cast<moore::IntType>(value.getType()).getTwoValued(), value, true,
3182 loc);
3183
3184 if (isSigned)
3185 return builder.createOrFold<moore::SIntToRealOp>(loc, type, twoValInt);
3186 return builder.createOrFold<moore::UIntToRealOp>(loc, type, twoValInt);
3187 }
3188
3189 auto getBuiltinFloatType = [&](moore::RealType type) -> Type {
3190 if (type.getWidth() == moore::RealWidth::f32)
3191 return mlir::Float32Type::get(builder.getContext());
3192
3193 return mlir::Float64Type::get(builder.getContext());
3194 };
3195
3196 // Handle f64/f32 to time conversion
3197 if (isa<moore::TimeType>(type) && isa<moore::RealType>(value.getType())) {
3198 auto intType =
3199 moore::IntType::get(builder.getContext(), 64, Domain::TwoValued);
3200 Type floatType =
3201 getBuiltinFloatType(cast<moore::RealType>(value.getType()));
3202 auto scale = moore::ConstantRealOp::create(
3203 builder, loc, value.getType(),
3204 FloatAttr::get(floatType, getTimeScaleInFemtoseconds(*this)));
3205 auto scaled = builder.createOrFold<moore::MulRealOp>(loc, value, scale);
3206 auto asInt = moore::RealToIntOp::create(builder, loc, intType, scaled);
3207 auto asLogic = moore::IntToLogicOp::create(builder, loc, asInt);
3208 return moore::LogicToTimeOp::create(builder, loc, asLogic);
3209 }
3210
3211 // Handle time to f64/f32 conversion
3212 if (isa<moore::RealType>(type) && isa<moore::TimeType>(value.getType())) {
3213 auto asLogic = moore::TimeToLogicOp::create(builder, loc, value);
3214 auto asInt = moore::LogicToIntOp::create(builder, loc, asLogic);
3215 auto asReal = moore::UIntToRealOp::create(builder, loc, type, asInt);
3216 Type floatType = getBuiltinFloatType(cast<moore::RealType>(type));
3217 auto scale = moore::ConstantRealOp::create(
3218 builder, loc, type,
3219 FloatAttr::get(floatType, getTimeScaleInFemtoseconds(*this)));
3220 return moore::DivRealOp::create(builder, loc, asReal, scale);
3221 }
3222
3223 // Handle Int to String
3224 if (isa<moore::StringType>(type)) {
3225 if (auto intType = dyn_cast<moore::IntType>(value.getType())) {
3226 if (intType.getDomain() == moore::Domain::FourValued)
3227 value = moore::LogicToIntOp::create(builder, loc, value);
3228 return moore::IntToStringOp::create(builder, loc, value);
3229 }
3230 }
3231
3232 // Handle String to Int
3233 if (auto intType = dyn_cast<moore::IntType>(type)) {
3234 if (isa<moore::StringType>(value.getType())) {
3235 value = moore::StringToIntOp::create(builder, loc, intType.getTwoValued(),
3236 value);
3237
3238 if (intType.getDomain() == moore::Domain::FourValued)
3239 return moore::IntToLogicOp::create(builder, loc, value);
3240
3241 return value;
3242 }
3243 }
3244
3245 // Handle Int to FormatString
3246 if (isa<moore::FormatStringType>(type)) {
3247 auto asStr = materializeConversion(moore::StringType::get(getContext()),
3248 value, isSigned, loc);
3249 if (!asStr)
3250 return {};
3251 return moore::FormatStringOp::create(builder, loc, asStr, {}, {}, {});
3252 }
3253
3254 if (isa<moore::RealType>(type) && isa<moore::RealType>(value.getType()))
3255 return builder.createOrFold<moore::ConvertRealOp>(loc, type, value);
3256
3257 if (isa<moore::ClassHandleType>(type) &&
3258 isa<moore::ClassHandleType>(value.getType()))
3259 return maybeUpcastHandle(*this, value, cast<moore::ClassHandleType>(type));
3260
3261 // TODO: Handle other conversions with dedicated ops.
3262 if (fallible && value.getType() != type)
3263 return {};
3264 if (value.getType() != type)
3265 value = moore::ConversionOp::create(builder, loc, type, value);
3266 return value;
3267}
3268
3269/// Helper function to convert real math builtin functions that take exactly
3270/// one argument.
3271template <typename OpTy>
3272static Value
3273convertRealMathBI(Context &context, Location loc, StringRef name,
3274 std::span<const slang::ast::Expression *const> args) {
3275 // Slang already checks the arity of real math builtins.
3276 assert(args.size() == 1 && "real math builtin expects 1 argument");
3277 auto value = context.convertRvalueExpression(*args[0]);
3278 if (!value)
3279 return {};
3280 return OpTy::create(context.builder, loc, value);
3281}
3282
3283static LogicalResult
3284emitScanAssignments(Context &context, const Context::ScanStringResult &result,
3285 Location loc) {
3286 auto &builder = context.builder;
3287 auto newBlockAfter = [&](Block *after) -> Block * {
3288 auto block = std::make_unique<Block>();
3289 block->insertAfter(after);
3290 return block.release();
3291 };
3292
3293 for (auto [destExpr, value, matched] : result.assignments) {
3294 auto lhs = context.convertLvalueExpression(*destExpr);
3295 if (!lhs)
3296 return failure();
3297 auto cond = moore::ToBuiltinIntOp::create(builder, loc, matched);
3298
3299 auto *assignBlock = newBlockAfter(builder.getInsertionBlock());
3300 auto *continuedBlock = newBlockAfter(assignBlock);
3301 mlir::cf::CondBranchOp::create(builder, loc, cond, assignBlock,
3302 continuedBlock);
3303
3304 builder.setInsertionPointToEnd(assignBlock);
3305 moore::BlockingAssignOp::create(builder, loc, lhs, value);
3306 mlir::cf::BranchOp::create(builder, loc, continuedBlock);
3307
3308 builder.setInsertionPointToEnd(continuedBlock);
3309 }
3310 return success();
3311}
3312
3314 const slang::ast::SystemSubroutine &subroutine, Location loc,
3315 std::span<const slang::ast::Expression *const> args) {
3316 using ksn = slang::parsing::KnownSystemName;
3317 StringRef name = subroutine.name;
3318 auto nameId = subroutine.knownNameId;
3319 size_t numArgs = args.size();
3320
3321 //===--------------------------------------------------------------------===//
3322 // Random Number System Functions
3323 //===--------------------------------------------------------------------===//
3324
3325 // $urandom, $random, and $urandom_range all map to a single
3326 // moore.builtin.urandom_range primitive with (minval, maxval, seed).
3327 if (nameId == ksn::URandom || nameId == ksn::Random) {
3328 auto i32Ty = moore::IntType::getInt(builder.getContext(), 32);
3329 auto minval = moore::ConstantOp::create(builder, loc, i32Ty, 0);
3330 auto maxval =
3331 moore::ConstantOp::create(builder, loc, i32Ty, APInt::getAllOnes(32));
3332 Value seed;
3333 if (numArgs == 1) {
3334 seed = convertLvalueExpression(*args[0]);
3335 if (!seed)
3336 return {};
3337 }
3338 return moore::UrandomRangeBIOp::create(builder, loc, minval, maxval, seed);
3339 }
3340
3341 if (nameId == ksn::URandomRange) {
3342 auto i32Ty = moore::IntType::getInt(builder.getContext(), 32);
3343 auto maxval = convertRvalueExpression(*args[0]);
3344 if (!maxval)
3345 return {};
3346 Value minval;
3347 if (numArgs >= 2) {
3348 minval = convertRvalueExpression(*args[1]);
3349 if (!minval)
3350 return {};
3351 } else {
3352 minval = moore::ConstantOp::create(builder, loc, i32Ty, 0);
3353 }
3354 return moore::UrandomRangeBIOp::create(builder, loc, minval, maxval,
3355 Value{});
3356 }
3357
3358 //===--------------------------------------------------------------------===//
3359 // Time System Functions
3360 //===--------------------------------------------------------------------===//
3361
3362 if (nameId == ksn::Time || nameId == ksn::STime || nameId == ksn::RealTime) {
3363 // Slang already checks the arity of time functions.
3364 assert(numArgs == 0 && "time functions take no arguments");
3365 return moore::TimeBIOp::create(builder, loc);
3366 }
3367
3368 //===--------------------------------------------------------------------===//
3369 // Math System Functions
3370 //===--------------------------------------------------------------------===//
3371
3372 if (nameId == ksn::Clog2) {
3373 // Slang already checks the arity of `$clog2`.
3374 assert(numArgs == 1 && "`$clog2` takes 1 argument");
3375 auto value = convertRvalueExpression(*args[0]);
3376 if (!value)
3377 return {};
3378 value = convertToSimpleBitVector(value);
3379 if (!value)
3380 return {};
3381 return moore::Clog2BIOp::create(builder, loc, value);
3382 }
3383
3384 //===--------------------------------------------------------------------===//
3385 // Bit Vector System Functions
3386 //===--------------------------------------------------------------------===//
3387
3388 if (nameId == ksn::IsUnknown) {
3389 assert(numArgs == 1 && "`$isunknown` takes 1 argument");
3390 auto value = convertRvalueExpression(*args[0]);
3391 if (!value)
3392 return {};
3393
3394 if (!isa<moore::IntType>(value.getType())) {
3395 if (!isa<moore::PackedType>(value.getType())) {
3396 mlir::emitError(loc) << "expected integer argument for `$isunknown`";
3397 return {};
3398 }
3399 value = materializePackedToSBVConversion(*this, value, loc,
3400 /*fallible=*/false);
3401 if (!value)
3402 return {};
3403 }
3404 auto valTy = dyn_cast<moore::IntType>(value.getType());
3405 return getIsUnknown(builder, loc, value, valTy, getContext());
3406 }
3407
3408 if (nameId == ksn::OneHot0 || nameId == ksn::OneHot) {
3409 assert(numArgs == 1 && "`$onehot`/`$onehot0` takes 1 argument");
3410 auto value = convertRvalueExpression(*args[0]);
3411 if (!value)
3412 return {};
3413 if (!isa<moore::IntType>(value.getType())) {
3414 if (!isa<moore::PackedType>(value.getType())) {
3415 mlir::emitError(loc)
3416 << "expected integer argument for `$onehot`/`$onehot0`";
3417 return {};
3418 }
3419 value = materializePackedToSBVConversion(*this, value, loc,
3420 /*fallible=*/false);
3421 if (!value)
3422 return {};
3423 }
3424 auto valTy = dyn_cast<moore::IntType>(value.getType());
3425 if (!valTy) {
3426 mlir::emitError(loc) << "expected integer argument for `"
3427 << subroutine.name << "`";
3428 return {};
3429 }
3430
3431 // In SystemVerilog, $onehot/$onehot0 return 1'b0 if the expression
3432 // contains any unknown (x/z) bits. Detect and squash if four-valued.
3433 Value isUnknown;
3434 if (valTy.getDomain() == Domain::FourValued) {
3435 Value isUnknownMoore =
3436 getIsUnknown(builder, loc, value, valTy, getContext());
3437 isUnknown =
3438 builder.createOrFold<moore::ToBuiltinIntOp>(loc, isUnknownMoore);
3439 }
3440
3441 // Coerce four-valued input to two-valued for the comb ops.
3442 Value intVal = coerceToBuiltinInt(builder, loc, value, valTy);
3443
3444 // Compute onehot0: (value & (value - 1)) == 0
3445 auto one = hw::ConstantOp::create(builder, loc, intVal.getType(), 1);
3446 auto minusOne = comb::SubOp::create(builder, loc, intVal, one);
3447 auto anded = comb::AndOp::create(builder, loc, intVal, minusOne);
3448 auto zero = hw::ConstantOp::create(builder, loc, intVal.getType(), 0);
3449 Value result = comb::ICmpOp::create(builder, loc, comb::ICmpPredicate::eq,
3450 anded, zero, false);
3451
3452 // For $onehot, additionally require value != 0.
3453 if (nameId == ksn::OneHot) {
3454 auto isNotZero = comb::ICmpOp::create(
3455 builder, loc, comb::ICmpPredicate::ne, intVal, zero, false);
3456 result = comb::AndOp::create(builder, loc, result, isNotZero);
3457 }
3458
3459 // If four-valued, squash to 0 when unknown bits exist.
3460 if (isUnknown) {
3461 Value zeroI1 =
3462 hw::ConstantOp::create(builder, loc, builder.getI1Type(), 0);
3463 result = comb::MuxOp::create(builder, loc, isUnknown, zeroI1, result);
3464 Value resultMoore = moore::FromBuiltinIntOp::create(builder, loc, result);
3465 return moore::IntToLogicOp::create(builder, loc, resultMoore).getResult();
3466 }
3467 return moore::FromBuiltinIntOp::create(builder, loc, result);
3468 }
3469
3470 if (nameId == ksn::CountOnes) {
3471 assert(numArgs == 1 && "`$countones` takes 1 argument");
3472 auto value = convertRvalueExpression(*args[0]);
3473 if (!value)
3474 return {};
3475 if (!isa<moore::IntType>(value.getType())) {
3476 if (!isa<moore::PackedType>(value.getType())) {
3477 mlir::emitError(loc) << "expected integer argument for `$countones`";
3478 return {};
3479 }
3480 value = materializePackedToSBVConversion(*this, value, loc,
3481 /*fallible=*/false);
3482 if (!value)
3483 return {};
3484 }
3485 auto valTy = dyn_cast<moore::IntType>(value.getType());
3486 if (!valTy) {
3487 mlir::emitError(loc) << "expected integer argument for `$countones`";
3488 return {};
3489 }
3490
3491 // Coerce four-valued input to two-valued for the comb ops.
3492 Value intVal = coerceToBuiltinInt(builder, loc, value, valTy);
3493
3494 // Popcount: extract each bit, zero-extend to result width, and sum.
3495 auto builtinIntTy = cast<IntegerType>(intVal.getType());
3496 unsigned width = builtinIntTy.getWidth();
3497 unsigned resultWidth = llvm::Log2_32_Ceil(width + 1);
3498 auto i1Ty = builder.getI1Type();
3499 unsigned padWidth = resultWidth - 1;
3500 auto zeros = hw::ConstantOp::create(builder, loc,
3501 builder.getIntegerType(padWidth), 0);
3502
3503 // Zero-extend the first bit to seed the accumulator.
3504 auto bit0 = comb::ExtractOp::create(builder, loc, i1Ty, intVal, 0);
3505 Value sum = comb::ConcatOp::create(builder, loc, ValueRange{zeros, bit0});
3506
3507 for (unsigned i = 1; i < width; ++i) {
3508 auto bit = comb::ExtractOp::create(builder, loc, i1Ty, intVal, i);
3509 auto extended =
3510 comb::ConcatOp::create(builder, loc, ValueRange{zeros, bit});
3511 sum = comb::AddOp::create(builder, loc, sum, extended);
3512 }
3513
3514 // Wrap back into Moore type (unsigned — CountOnes result is never signed).
3515 return moore::FromBuiltinIntOp::create(builder, loc, sum);
3516 }
3517
3518 // Real math functions (all take 1 real argument)
3519 if (nameId == ksn::Ln)
3520 return convertRealMathBI<moore::LnBIOp>(*this, loc, name, args);
3521 if (nameId == ksn::Log10)
3522 return convertRealMathBI<moore::Log10BIOp>(*this, loc, name, args);
3523 if (nameId == ksn::Exp)
3524 return convertRealMathBI<moore::ExpBIOp>(*this, loc, name, args);
3525 if (nameId == ksn::Sqrt)
3526 return convertRealMathBI<moore::SqrtBIOp>(*this, loc, name, args);
3527 if (nameId == ksn::Floor)
3528 return convertRealMathBI<moore::FloorBIOp>(*this, loc, name, args);
3529 if (nameId == ksn::Ceil)
3530 return convertRealMathBI<moore::CeilBIOp>(*this, loc, name, args);
3531 if (nameId == ksn::Sin)
3532 return convertRealMathBI<moore::SinBIOp>(*this, loc, name, args);
3533 if (nameId == ksn::Cos)
3534 return convertRealMathBI<moore::CosBIOp>(*this, loc, name, args);
3535 if (nameId == ksn::Tan)
3536 return convertRealMathBI<moore::TanBIOp>(*this, loc, name, args);
3537 if (nameId == ksn::Asin)
3538 return convertRealMathBI<moore::AsinBIOp>(*this, loc, name, args);
3539 if (nameId == ksn::Acos)
3540 return convertRealMathBI<moore::AcosBIOp>(*this, loc, name, args);
3541 if (nameId == ksn::Atan)
3542 return convertRealMathBI<moore::AtanBIOp>(*this, loc, name, args);
3543 if (nameId == ksn::Sinh)
3544 return convertRealMathBI<moore::SinhBIOp>(*this, loc, name, args);
3545 if (nameId == ksn::Cosh)
3546 return convertRealMathBI<moore::CoshBIOp>(*this, loc, name, args);
3547 if (nameId == ksn::Tanh)
3548 return convertRealMathBI<moore::TanhBIOp>(*this, loc, name, args);
3549 if (nameId == ksn::Asinh)
3550 return convertRealMathBI<moore::AsinhBIOp>(*this, loc, name, args);
3551 if (nameId == ksn::Acosh)
3552 return convertRealMathBI<moore::AcoshBIOp>(*this, loc, name, args);
3553 if (nameId == ksn::Atanh)
3554 return convertRealMathBI<moore::AtanhBIOp>(*this, loc, name, args);
3555
3556 //===--------------------------------------------------------------------===//
3557 // Type Conversion System Functions
3558 //===--------------------------------------------------------------------===//
3559
3560 if (nameId == ksn::Signed || nameId == ksn::Unsigned) {
3561 // Slang already checks the arity of `$signed`/`$unsigned`.
3562 assert(numArgs == 1 && "`$signed`/`$unsigned` take 1 argument");
3563 // These are just passthroughs in the IR; signedness is carried on the Slang
3564 // AST type which we use to convert the IR.
3565 return convertRvalueExpression(*args[0]);
3566 }
3567
3568 if (nameId == ksn::RealToBits)
3569 return convertRealMathBI<moore::RealtobitsBIOp>(*this, loc, name, args);
3570 if (nameId == ksn::BitsToReal)
3571 return convertRealMathBI<moore::BitstorealBIOp>(*this, loc, name, args);
3572 if (nameId == ksn::ShortrealToBits)
3573 return convertRealMathBI<moore::ShortrealtobitsBIOp>(*this, loc, name,
3574 args);
3575 if (nameId == ksn::BitsToShortreal)
3576 return convertRealMathBI<moore::BitstoshortrealBIOp>(*this, loc, name,
3577 args);
3578
3579 if (nameId == ksn::Cast) {
3580 assert(numArgs == 2 && "`cast` takes 2 arguments");
3581 auto *dstExpr = args[0];
3582 auto dstType = convertType(*dstExpr->type);
3583 if (!dstType)
3584 return {};
3585
3586 if (auto *assign = dstExpr->as_if<slang::ast::AssignmentExpression>())
3587 dstExpr = &assign->left();
3588 auto dst = convertLvalueExpression(*dstExpr);
3589 if (!dst)
3590 return {};
3591
3592 auto src = convertRvalueExpression(*args[1]);
3593 if (!src)
3594 return {};
3595 // Class-typed $cast (upcast/downcast) is intentionally left for follow-up.
3596 if (isa<moore::ClassHandleType>(dstType) ||
3597 isa<moore::ClassHandleType>(src.getType())) {
3598 auto i1Ty = moore::IntType::getInt(builder.getContext(), 1);
3599 return moore::ConstantOp::create(builder, loc, i1Ty, 0,
3600 /*isSigned=*/false);
3601 }
3602 auto converted = materializeConversion(
3603 dstType, src, args[1]->type->isSigned(), loc, /*fallible=*/true);
3604 auto i1Ty = moore::IntType::getInt(builder.getContext(), 1);
3605 if (!converted)
3606 return moore::ConstantOp::create(builder, loc, i1Ty, 0,
3607 /*isSigned=*/false);
3608 moore::BlockingAssignOp::create(builder, loc, dst, converted);
3609 return moore::ConstantOp::create(builder, loc, i1Ty, 1,
3610 /*isSigned=*/false);
3611 }
3612
3613 //===--------------------------------------------------------------------===//
3614 // String Methods
3615 //===--------------------------------------------------------------------===//
3616
3617 if (nameId == ksn::Len) {
3618 // Slang already checks the arity of string methods.
3619 assert(numArgs == 1 && "`len` takes 1 argument");
3620 auto stringType = moore::StringType::get(getContext());
3621 auto value = convertRvalueExpression(*args[0], stringType);
3622 if (!value)
3623 return {};
3624 return moore::StringLenOp::create(builder, loc, value);
3625 }
3626
3627 if (nameId == ksn::Getc) {
3628 // Slang already checks the arity of string methods.
3629 assert(numArgs == 2 && "`getc` takes 2 arguments");
3630 auto stringType = moore::StringType::get(getContext());
3631 auto str = convertRvalueExpression(*args[0], stringType);
3632 auto index = convertRvalueExpression(*args[1]);
3633 if (!str || !index)
3634 return {};
3635 return moore::StringGetOp::create(builder, loc, str, index);
3636 }
3637
3638 if (nameId == ksn::ToUpper) {
3639 // Slang already checks the arity of string methods.
3640 assert(numArgs == 1 && "`toupper` takes 1 argument");
3641 auto stringType = moore::StringType::get(getContext());
3642 auto value = convertRvalueExpression(*args[0], stringType);
3643 if (!value)
3644 return {};
3645 return moore::StringToUpperOp::create(builder, loc, value);
3646 }
3647
3648 if (nameId == ksn::ToLower) {
3649 // Slang already checks the arity of string methods.
3650 assert(numArgs == 1 && "`tolower` takes 1 argument");
3651 auto stringType = moore::StringType::get(getContext());
3652 auto value = convertRvalueExpression(*args[0], stringType);
3653 if (!value)
3654 return {};
3655 return moore::StringToLowerOp::create(builder, loc, value);
3656 }
3657
3658 if (nameId == ksn::Compare || nameId == ksn::ICompare) {
3659 // Slang already checks the arity of string methods.
3660 assert(numArgs == 2);
3661 auto stringType = moore::StringType::get(getContext());
3662 auto lhs = convertRvalueExpression(*args[0], stringType);
3663 auto rhs = convertRvalueExpression(*args[1], stringType);
3664 if (!lhs || !rhs)
3665 return {};
3666 if (nameId == ksn::Compare)
3667 return moore::StringCompareOp::create(builder, loc, lhs, rhs);
3668 return moore::StringICompareOp::create(builder, loc, lhs, rhs);
3669 }
3670
3671 if (nameId == ksn::Substr) {
3672 // Slang already checks the arity of string methods.
3673 assert(numArgs == 3 && "`substr` takes 3 arguments");
3674 auto stringType = moore::StringType::get(getContext());
3675 auto str = convertRvalueExpression(*args[0], stringType);
3676 auto start = convertRvalueExpression(*args[1]);
3677 auto end = convertRvalueExpression(*args[2]);
3678 if (!str || !start || !end)
3679 return {};
3680 return moore::StringSubstrOp::create(builder, loc, str, start, end);
3681 }
3682
3683 if (nameId == ksn::AToI || nameId == ksn::AToHex || nameId == ksn::AToOct ||
3684 nameId == ksn::AToBin) {
3685 // Slang already checks the arity of string methods.
3686 assert(numArgs == 1 && "`atoi/hex/oct/bin` takes 1 argument");
3687 auto stringType = moore::StringType::get(getContext());
3688 auto str = convertRvalueExpression(*args[0], stringType);
3689 if (!str)
3690 return {};
3691 auto integerType = moore::IntType::getLogic(builder.getContext(), 32);
3692 switch (nameId) {
3693 case ksn::AToI:
3694 return moore::StringAtoiOp::create(builder, loc, integerType, str);
3695 case ksn::AToHex:
3696 return moore::StringAtohexOp::create(builder, loc, integerType, str);
3697 case ksn::AToOct:
3698 return moore::StringAtooctOp::create(builder, loc, integerType, str);
3699 case ksn::AToBin:
3700 return moore::StringAtobinOp::create(builder, loc, integerType, str);
3701 default:
3702 llvm_unreachable("unexpected string to integer conversion");
3703 }
3704 }
3705
3706 if (nameId == ksn::AToReal) {
3707 // Slang already checks the arity of string methods.
3708 assert(numArgs == 1 && "`atoreal` takes 1 argument");
3709 auto stringType = moore::StringType::get(getContext());
3710 auto str = convertRvalueExpression(*args[0], stringType);
3711 if (!str)
3712 return {};
3713 auto realType = moore::RealType::get(getContext(), moore::RealWidth::f64);
3714 return moore::StringAtorealOp::create(builder, loc, realType, str);
3715 }
3716
3717 //===--------------------------------------------------------------------===//
3718 // Queue Methods
3719 //===--------------------------------------------------------------------===//
3720
3721 if (nameId == ksn::ArraySize) {
3722 // Slang already checks the arity of `size`.
3723 assert(numArgs == 1 && "`size` takes 1 argument");
3724 if (args[0]->type->isQueue()) {
3725 auto value = convertRvalueExpression(*args[0]);
3726 if (!value)
3727 return {};
3728 return moore::QueueSizeBIOp::create(builder, loc, value);
3729 }
3730 if (args[0]->type->getCanonicalType().kind ==
3731 slang::ast::SymbolKind::DynamicArrayType) {
3732 auto value = convertRvalueExpression(*args[0]);
3733 if (!value)
3734 return {};
3735 return moore::OpenUArraySizeOp::create(builder, loc, value);
3736 }
3737 if (args[0]->type->isAssociativeArray()) {
3738 auto value = convertLvalueExpression(*args[0]);
3739 if (!value)
3740 return {};
3741 return moore::AssocArraySizeOp::create(builder, loc, value);
3742 }
3743 emitError(loc) << "unsupported member function `size` on type `"
3744 << args[0]->type->toString() << "`";
3745 return {};
3746 }
3747
3748 if (nameId == ksn::Delete) {
3749 // Slang already checks the arity of `delete`.
3750 assert(numArgs == 1 && "`delete` takes 1 argument");
3751 if (args[0]->type->getCanonicalType().kind ==
3752 slang::ast::SymbolKind::DynamicArrayType) {
3753 auto value = convertRvalueExpression(*args[0]);
3754 if (!value)
3755 return {};
3756 return moore::OpenUArrayDeleteOp::create(builder, loc, value);
3757 }
3758 emitError(loc) << "unsupported member function `delete` on type `"
3759 << args[0]->type->toString() << "`";
3760 return {};
3761 }
3762
3763 if (nameId == ksn::PopBack) {
3764 // Slang already checks the arity and applicability of `pop_back`.
3765 assert(numArgs == 1 && "`pop_back` takes 1 argument");
3766 assert(args[0]->type->isQueue() && "`pop_back` is only valid on queues");
3767 auto value = convertLvalueExpression(*args[0]);
3768 if (!value)
3769 return {};
3770 return moore::QueuePopBackOp::create(builder, loc, value);
3771 }
3772
3773 if (nameId == ksn::PopFront) {
3774 // Slang already checks the arity and applicability of `pop_front`.
3775 assert(numArgs == 1 && "`pop_front` takes 1 argument");
3776 assert(args[0]->type->isQueue() && "`pop_front` is only valid on queues");
3777 auto value = convertLvalueExpression(*args[0]);
3778 if (!value)
3779 return {};
3780 return moore::QueuePopFrontOp::create(builder, loc, value);
3781 }
3782
3783 //===--------------------------------------------------------------------===//
3784 // Associative Array Methods
3785 //===--------------------------------------------------------------------===//
3786
3787 if (nameId == ksn::Num) {
3788 if (args[0]->type->isAssociativeArray()) {
3789 assert(numArgs == 1 && "`num` takes 1 argument");
3790 auto value = convertLvalueExpression(*args[0]);
3791 if (!value)
3792 return {};
3793 return moore::AssocArraySizeOp::create(builder, loc, value);
3794 }
3795 emitError(loc) << "unsupported system call `" << name << "`";
3796 return {};
3797 }
3798
3799 if (nameId == ksn::Exists) {
3800 // Slang already checks the arity and applicability of `exists`.
3801 assert(numArgs == 2 && "`exists` takes 2 arguments");
3802 assert(args[0]->type->isAssociativeArray() &&
3803 "`exists` is only valid on associative arrays");
3804 auto array = convertLvalueExpression(*args[0]);
3805 auto key = convertRvalueExpression(*args[1]);
3806 if (!array || !key)
3807 return {};
3808 return moore::AssocArrayExistsOp::create(builder, loc, array, key);
3809 }
3810
3811 // Associative array traversal methods (all take 2 arguments: array ref, key
3812 // ref). These names are shared with enum built-in methods (next/prev/first/
3813 // last), which take 1 or 2 arguments. Only handle the associative array case
3814 // here; fall through to the unsupported diagnostic for other types.
3815 if (nameId == ksn::First || nameId == ksn::Last || nameId == ksn::Next ||
3816 nameId == ksn::Prev) {
3817 if (args[0]->type->isAssociativeArray()) {
3818 assert(numArgs == 2 && "traversal methods take 2 arguments");
3819 auto array = convertLvalueExpression(*args[0]);
3820 auto key = convertLvalueExpression(*args[1]);
3821 if (!array || !key)
3822 return {};
3823 if (nameId == ksn::First)
3824 return moore::AssocArrayFirstOp::create(builder, loc, array, key);
3825 if (nameId == ksn::Last)
3826 return moore::AssocArrayLastOp::create(builder, loc, array, key);
3827 if (nameId == ksn::Next)
3828 return moore::AssocArrayNextOp::create(builder, loc, array, key);
3829 if (nameId == ksn::Prev)
3830 return moore::AssocArrayPrevOp::create(builder, loc, array, key);
3831 llvm_unreachable("all traversal cases handled above");
3832 }
3833 emitError(loc) << "unsupported system call `" << name << "`";
3834 return {};
3835 }
3836
3837 //===--------------------------------------------------------------------===//
3838 // File I/O System Functions
3839 //===--------------------------------------------------------------------===//
3840
3841 if (nameId == ksn::FOpen) {
3842 assert(numArgs >= 1 && numArgs <= 2 && "`$fopen` takes 1 or 2 arguments");
3843 auto filename =
3844 convertRvalueExpression(*args[0], moore::StringType::get(getContext()));
3845 if (!filename)
3846 return {};
3847 moore::FOpenModeAttr modeAttr;
3848 if (numArgs == 2) {
3849 auto *strLit = args[1]
3850 ->unwrapImplicitConversions()
3851 .as_if<slang::ast::StringLiteral>();
3852 if (!strLit)
3853 return emitError(loc) << "$fopen mode must be a string literal",
3854 Value{};
3855
3856 auto mode =
3857 llvm::StringSwitch<std::optional<moore::FOpenMode>>(
3858 strLit->getValue())
3859 .Cases({"r", "rb"}, moore::FOpenMode::Read)
3860 .Cases({"w", "wb"}, moore::FOpenMode::Write)
3861 .Cases({"a", "ab"}, moore::FOpenMode::Append)
3862 .Cases({"r+", "r+b", "rb+"}, moore::FOpenMode::ReadUpdate)
3863 .Cases({"w+", "w+b", "wb+"}, moore::FOpenMode::WriteUpdate)
3864 .Cases({"a+", "a+b", "ab+"}, moore::FOpenMode::AppendUpdate)
3865 .Default(std::nullopt);
3866
3867 if (!mode)
3868 return emitError(loc)
3869 << "invalid $fopen mode '" << strLit->getValue() << "'",
3870 Value{};
3871 modeAttr = moore::FOpenModeAttr::get(getContext(), *mode);
3872 }
3873 return moore::FOpenBIOp::create(builder, loc, filename, modeAttr);
3874 }
3875
3876 //===--------------------------------------------------------------------===//
3877 // Command Line Input System Functions
3878 //===--------------------------------------------------------------------===//
3879
3880 if (nameId == ksn::TestPlusArgs) {
3881 // Slang already checks the arity of `$test$plusargs`.
3882 assert(numArgs == 1 && "`$test$plusargs` takes 1 argument");
3883 auto *strLit =
3884 args[0]->unwrapImplicitConversions().as_if<slang::ast::StringLiteral>();
3885 if (!strLit)
3886 return emitError(loc) << "`$test$plusargs` argument must be a string "
3887 "literal",
3888 Value{};
3889 auto foundTy = moore::IntType::getInt(getContext(), 1);
3890 return moore::PlusArgsTestBIOp::create(
3891 builder, loc, foundTy, builder.getStringAttr(strLit->getValue()));
3892 }
3893
3894 if (nameId == ksn::ValuePlusArgs) {
3895 // Slang already checks the arity of `$value$plusargs`. The parsed value is
3896 // written back into the second (lvalue) argument, and the function returns
3897 // whether a matching plusarg was found.
3898 assert(numArgs == 2 && "`$value$plusargs` takes 2 arguments");
3899 auto *strLit =
3900 args[0]->unwrapImplicitConversions().as_if<slang::ast::StringLiteral>();
3901 if (!strLit)
3902 return emitError(loc) << "`$value$plusargs` format must be a string "
3903 "literal",
3904 Value{};
3905 // Slang emits output arguments as a `<lvalue> = EmptyArgument` assignment;
3906 // unpack it to recover the lvalue that receives the parsed value.
3907 const auto *valueArg = args[1];
3908 if (const auto *assign =
3909 valueArg->as_if<slang::ast::AssignmentExpression>())
3910 valueArg = &assign->left();
3911 auto lvalue = convertLvalueExpression(*valueArg);
3912 if (!lvalue)
3913 return {};
3914 auto resultType = cast<moore::RefType>(lvalue.getType()).getNestedType();
3915 auto foundTy = moore::IntType::getInt(getContext(), 1);
3916 auto op = moore::PlusArgsValueBIOp::create(
3917 builder, loc, foundTy, resultType,
3918 builder.getStringAttr(strLit->getValue()));
3919 moore::BlockingAssignOp::create(builder, loc, lvalue, op.getResult());
3920 return op.getFound();
3921 }
3922
3923 if (nameId == ksn::FScanf) {
3924 auto fd = convertRvalueExpression(
3925 *args[0], moore::IntType::getInt(builder.getContext(), 32));
3926 if (!fd)
3927 return {};
3928 auto *fmtLit =
3929 args[1]->unwrapImplicitConversions().as_if<slang::ast::StringLiteral>();
3930 if (!fmtLit)
3931 return (mlir::emitError(loc)
3932 << "$fscanf requires a string literal format string"),
3933 Value{};
3934 auto cursor =
3935 moore::ScanBeginFScanFOp::create(builder, loc, fd).getCursor();
3936 auto result =
3937 convertScanString(fmtLit->getValue(), cursor, args.subspan(2), loc);
3938 if (failed(result))
3939 return {};
3940 if (failed(emitScanAssignments(*this, *result, loc)))
3941 return {};
3942 return moore::ScanEndOp::create(builder, loc, result->finalCursor)
3943 .getCount();
3944 }
3945
3946 if (nameId == ksn::SScanf) {
3947 auto str =
3948 convertRvalueExpression(*args[0], moore::StringType::get(getContext()));
3949 if (!str)
3950 return {};
3951 auto *fmtLit =
3952 args[1]->unwrapImplicitConversions().as_if<slang::ast::StringLiteral>();
3953 if (!fmtLit)
3954 return (mlir::emitError(loc)
3955 << "$sscanf requires a string literal format string"),
3956 Value{};
3957 auto cursor =
3958 moore::ScanBeginSScanFOp::create(builder, loc, str).getCursor();
3959 auto result =
3960 convertScanString(fmtLit->getValue(), cursor, args.subspan(2), loc);
3961 if (failed(result))
3962 return {};
3963 if (failed(emitScanAssignments(*this, *result, loc)))
3964 return {};
3965 return moore::ScanEndOp::create(builder, loc, result->finalCursor)
3966 .getCount();
3967 }
3968
3969 // Unrecognized system call
3970 emitError(loc) << "unsupported system call `" << name << "`";
3971 return {};
3972}
3973
3974// Resolve any (possibly nested) SymbolRefAttr to an op from the root.
3975static mlir::Operation *resolve(Context &context, mlir::SymbolRefAttr sym) {
3976 return context.symbolTable.lookupNearestSymbolFrom(context.intoModuleOp, sym);
3977}
3978
3979bool Context::isClassDerivedFrom(const moore::ClassHandleType &actualTy,
3980 const moore::ClassHandleType &baseTy) {
3981 if (!actualTy || !baseTy)
3982 return false;
3983
3984 mlir::SymbolRefAttr actualSym = actualTy.getClassSym();
3985 mlir::SymbolRefAttr baseSym = baseTy.getClassSym();
3986
3987 if (actualSym == baseSym)
3988 return true;
3989
3990 auto *op = resolve(*this, actualSym);
3991 auto decl = llvm::dyn_cast_or_null<moore::ClassDeclOp>(op);
3992 // Walk up the inheritance chain via ClassDeclOp::$base (SymbolRefAttr).
3993 while (decl) {
3994 mlir::SymbolRefAttr curBase = decl.getBaseAttr();
3995 if (!curBase)
3996 break;
3997 if (curBase == baseSym)
3998 return true;
3999 decl = llvm::dyn_cast_or_null<moore::ClassDeclOp>(resolve(*this, curBase));
4000 }
4001 return false;
4002}
4003
4004moore::ClassHandleType
4005Context::getAncestorClassWithProperty(const moore::ClassHandleType &actualTy,
4006 llvm::StringRef fieldName, Location loc) {
4007 // Start at the actual class symbol.
4008 mlir::SymbolRefAttr classSym = actualTy.getClassSym();
4009
4010 while (classSym) {
4011 // Resolve the class declaration from the root symbol table owner.
4012 auto *op = resolve(*this, classSym);
4013 auto decl = llvm::dyn_cast_or_null<moore::ClassDeclOp>(op);
4014 if (!decl)
4015 break;
4016
4017 // Scan the class body for a property with the requested symbol name.
4018 for (auto &block : decl.getBody()) {
4019 for (auto &opInBlock : block) {
4020 if (auto prop =
4021 llvm::dyn_cast<moore::ClassPropertyDeclOp>(&opInBlock)) {
4022 if (prop.getSymName() == fieldName) {
4023 // Found a declaring ancestor: return its handle type.
4024 return moore::ClassHandleType::get(actualTy.getContext(), classSym);
4025 }
4026 }
4027 }
4028 }
4029
4030 // Not found here—climb to the base class (if any) and continue.
4031 classSym = decl.getBaseAttr(); // may be null; loop ends if so
4032 }
4033
4034 // No ancestor declares that property.
4035 mlir::emitError(loc) << "unknown property `" << fieldName << "`";
4036 return {};
4037}
4038
4039//===--------------------------------------------------------------------===//
4040// Value Range Expression Methods
4041//===--------------------------------------------------------------------===//
4042
4043Value Context::convertInsideCheck(Value insideLhs, Location loc,
4044 const slang::ast::Expression &expr) {
4045 // The value range list on the right-hand side of the inside operator is a
4046 // comma-separated list of expressions or ranges.
4047 if (const auto *valueRange = expr.as_if<slang::ast::ValueRangeExpression>()) {
4048 auto lowBound =
4050 auto highBound =
4052 if (!insideLhs || !lowBound || !highBound)
4053 return {};
4054
4055 Value rangeLhs, rangeRhs;
4056 // Determine if the insideLhs on the left-hand side is inclusively
4057 // within the range.
4058 if (valueRange->left().type->isSigned() ||
4059 insideLhs.getType().isSignedInteger()) {
4060 rangeLhs = moore::SgeOp::create(builder, loc, insideLhs, lowBound);
4061 } else {
4062 rangeLhs = moore::UgeOp::create(builder, loc, insideLhs, lowBound);
4063 }
4064
4065 if (valueRange->right().type->isSigned() ||
4066 insideLhs.getType().isSignedInteger()) {
4067 rangeRhs = moore::SleOp::create(builder, loc, insideLhs, highBound);
4068 } else {
4069 rangeRhs = moore::UleOp::create(builder, loc, insideLhs, highBound);
4070 }
4071
4072 return moore::AndOp::create(builder, loc, rangeLhs, rangeRhs);
4073 }
4074
4075 // Handle expressions.
4076 if (!expr.type->isIntegral()) {
4077 if (expr.type->isUnpackedArray()) {
4078 mlir::emitError(loc,
4079 "unpacked arrays in 'inside' expressions not supported");
4080 return {};
4081 }
4082 mlir::emitError(
4083 loc, "only simple bit vectors supported in 'inside' expressions");
4084 return {};
4085 }
4086
4088 if (!value)
4089 return {};
4090 return moore::WildcardEqOp::create(builder, loc, insideLhs, value);
4091}
assert(baseType &&"element must be base type")
MlirType elementType
Definition CHIRRTL.cpp:29
static std::unique_ptr< Context > context
static Value convertRealMathBI(Context &context, Location loc, StringRef name, std::span< const slang::ast::Expression *const > args)
Helper function to convert real math builtin functions that take exactly one argument.
static mlir::Value maybeUpcastHandle(Context &context, mlir::Value actualHandle, moore::ClassHandleType expectedHandleTy)
Check whether the actual handle is a subclass of another handle type and return a properly upcast ver...
static mlir::Operation * resolve(Context &context, mlir::SymbolRefAttr sym)
static Value lookupExpandedInterfaceMember(Context &context, const slang::ast::HierarchicalValueExpression &expr)
Resolve a hierarchical value that refers to a member of an expanded interface instance.
static Value visitClassProperty(Context &context, const slang::ast::ClassPropertySymbol &expr)
static Value materializeSBVToPackedConversion(Context &context, moore::PackedType packedType, Value value, Location loc, bool fallible)
Create the necessary operations to convert from a simple bit vector IntType to an equivalent PackedTy...
static Value materializePackedToSBVConversion(Context &context, Value value, Location loc, bool fallible)
Create the necessary operations to convert from a PackedType to the corresponding simple bit vector I...
static LogicalResult emitScanAssignments(Context &context, const Context::ScanStringResult &result, Location loc)
static Value getIsUnknown(OpBuilder &builder, Location loc, Value value, moore::IntType valTy, MLIRContext *ctx)
Check if a Moore integer value contains any unknown (x/z) bits.
static uint64_t getTimeScaleInFemtoseconds(Context &context)
Get the currently active timescale as an integer number of femtoseconds.
static Value coerceToBuiltinInt(OpBuilder &builder, Location loc, Value value, moore::IntType valTy)
Coerce a Moore integer value to a builtin integer, handling four-valued inputs by first mapping x/z t...
static FVInt convertSVIntToFVInt(const slang::SVInt &svint)
Convert a Slang SVInt to a CIRCT FVInt.
Four-valued arbitrary precision integers.
Definition FVInt.h:37
static FVInt getAllX(unsigned numBits)
Construct an FVInt with all bits set to X.
Definition FVInt.h:75
A packed SystemVerilog type.
Definition MooreTypes.h:154
bool containsTimeType() const
Check if this is a TimeType, or an aggregate that contains a nested TimeType.
IntType getSimpleBitVector() const
Get the simple bit vector type equivalent to this packed type.
An unpacked SystemVerilog type.
Definition MooreTypes.h:102
create(low_bit, result_type, input=None)
Definition comb.py:187
create(data_type, value)
Definition hw.py:433
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
Domain
The number of values each bit of a type can assume.
Definition MooreTypes.h:50
@ FourValued
Four-valued types such as logic or integer.
@ TwoValued
Two-valued types such as bit or int.
bool isIntType(Type type, unsigned width)
Check if a type is an IntType type of the given width.
@ f32
A standard 32-Bit floating point number ("float")
@ 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.
FailureOr< ScanStringResult > convertScanString(StringRef formatStr, Value initialCursor, std::span< const slang::ast::Expression *const > destinations, Location loc)
Convert a scan format string into a consuming chain of moore.scan.
Value convertLvalueExpression(const slang::ast::Expression &expr)
Value materializeConstant(const slang::ConstantValue &constant, const slang::ast::Type &type, Location loc)
Helper function to materialize a ConstantValue as an SSA value.
slang::ConstantValue evaluateConstant(const slang::ast::Expression &expr)
Evaluate the constant value of an expression.
Value convertInsideCheck(Value insideLhs, Location loc, const slang::ast::Expression &expr)
Convert the inside/set-membership expression.
DenseMap< const slang::ast::ValueSymbol *, moore::GlobalVariableOp > globalVariables
A table of defined global variables that may be referred to by name in expressions.
slang::ast::Compilation & compilation
OpBuilder builder
The builder used to create IR operations.
Value materializeFixedSizeUnpackedArrayType(const slang::ConstantValue &constant, const slang::ast::FixedSizeUnpackedArrayType &astType, Location loc)
Helper function to materialize an unpacked array of SVInts as an SSA value.
std::function< void(moore::ReadOp)> rvalueReadCallback
A listener called for every variable or net being read.
bool isClassDerivedFrom(const moore::ClassHandleType &actualTy, const moore::ClassHandleType &baseTy)
Checks whether one class (actualTy) is derived from another class (baseTy).
Value convertSystemCall(const slang::ast::SystemSubroutine &subroutine, Location loc, std::span< const slang::ast::Expression *const > args)
Convert system function calls.
Type convertType(const slang::ast::Type &type, LocationAttr loc={})
Convert a slang type into an MLIR type.
Definition Types.cpp:224
Value materializeSVInt(const slang::SVInt &svint, const slang::ast::Type &type, Location loc)
Helper function to materialize an SVInt as an SSA value.
Value materializeSVReal(const slang::ConstantValue &svreal, const slang::ast::Type &type, Location loc)
Helper function to materialize a real value as an SSA value.
Value convertToBool(Value value)
Helper function to convert a value to its "truthy" boolean value.
moore::ClassHandleType getAncestorClassWithProperty(const moore::ClassHandleType &actualTy, StringRef fieldName, Location loc)
Tries to find the closest base class of actualTy that carries a property with name fieldName.
Value convertRvalueExpression(const slang::ast::Expression &expr, Type requiredType={})
Value convertToSimpleBitVector(Value value)
Helper function to convert a value to its simple bit vector representation, if it has one.
Value materializeString(const slang::ConstantValue &string, const slang::ast::Type &astType, Location loc)
Helper function to materialize a string as an SSA value.
Value materializeConversion(Type type, Value value, bool isSigned, Location loc, bool fallible=false)
Helper function to insert the necessary operations to cast a value from one type to another.
Value currentQueue
Variable that tracks the queue which we are currently converting the index expression for.
FunctionLowering * currentFunctionLowering
The function currently being converted, if any.
std::optional< std::pair< const slang::ast::InstanceSymbol *, mlir::StringAttr > > buildHierValueKey(const slang::ast::HierarchicalValueExpression &expr)
Build a composite key for hierValueSymbols from a hierarchical value expression.
MLIRContext * getContext()
Return the MLIR context.
Location convertLocation(slang::SourceLocation loc)
Convert a slang SourceLocation into an MLIR Location.
SmallVector< const slang::ast::ValueSymbol *, 4 > capturedSymbols
The AST symbols captured by this function, determined by the capture analysis pre-pass.