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