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