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