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