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