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