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 }
768
769 operands.push_back(value);
770 }
771
772 if (contigElements) {
773 operands.push_back(moore::ReadOp::create(builder, loc, contigElements));
774 }
775
776 return moore::QueueConcatOp::create(builder, loc, queueType, operands);
777 }
778
779 /// Handle member accesses.
780 Value visit(const slang::ast::MemberAccessExpression &expr) {
781 auto type = context.convertType(*expr.type);
782 if (!type)
783 return {};
784
785 auto *valueType = expr.value().type.get();
786 auto memberName = builder.getStringAttr(expr.member.name);
787
788 // Handle virtual interfaces. We represent virtual interface handles as a
789 // Moore struct containing references to interface members. Member access
790 // returns the stored reference directly (for lvalues) or reads it (for
791 // rvalues).
792 if (valueType->isVirtualInterface()) {
793 auto memberType = dyn_cast<moore::UnpackedType>(type);
794 if (!memberType) {
795 mlir::emitError(loc)
796 << "unsupported virtual interface member type: " << type;
797 return {};
798 }
799 auto resultRefType = moore::RefType::get(memberType);
800
801 // Always use the rvalue of the base handle to avoid creating
802 // ref<ref<T>> for lvalue member access.
803 Value base = context.convertRvalueExpression(expr.value());
804 if (!base)
805 return {};
806
807 auto memberRef = moore::StructExtractOp::create(
808 builder, loc, resultRefType, memberName, base);
809 if (isLvalue)
810 return memberRef;
811 return moore::ReadOp::create(builder, loc, memberRef);
812 }
813
814 // Handle structs.
815 if (valueType->isStruct()) {
816 auto resultType =
817 isLvalue ? moore::RefType::get(cast<moore::UnpackedType>(type))
818 : type;
819 auto value = convertLvalueOrRvalueExpression(expr.value());
820 if (!value)
821 return {};
822
823 if (isLvalue)
824 return moore::StructExtractRefOp::create(builder, loc, resultType,
825 memberName, value);
826 return moore::StructExtractOp::create(builder, loc, resultType,
827 memberName, value);
828 }
829
830 // Handle unions.
831 if (valueType->isPackedUnion() || valueType->isUnpackedUnion()) {
832 auto resultType =
833 isLvalue ? moore::RefType::get(cast<moore::UnpackedType>(type))
834 : type;
835 auto value = convertLvalueOrRvalueExpression(expr.value());
836 if (!value)
837 return {};
838
839 if (isLvalue)
840 return moore::UnionExtractRefOp::create(builder, loc, resultType,
841 memberName, value);
842 return moore::UnionExtractOp::create(builder, loc, type, memberName,
843 value);
844 }
845
846 // Handle classes.
847 if (valueType->isClass()) {
848 auto valTy = context.convertType(*valueType);
849 if (!valTy)
850 return {};
851 auto targetTy = cast<moore::ClassHandleType>(valTy);
852
853 // `MemberAccessExpression`s may refer to either variables that may or may
854 // not be compile time constants, or to class parameters which are always
855 // elaboration-time constant.
856 //
857 // We distinguish these cases, and materialize a runtime member access
858 // for variables, but force constant conversion for parameter accesses.
859 //
860 // Also see this discussion:
861 // https://github.com/MikePopoloski/slang/issues/1641
862
863 if (expr.member.kind != slang::ast::SymbolKind::Parameter) {
864
865 // We need to pick the closest ancestor that declares a property with
866 // the relevant name. System Verilog explicitly enforces lexical
867 // shadowing, as shown in IEEE 1800-2023 Section 8.14 "Overridden
868 // members".
869 moore::ClassHandleType upcastTargetTy =
870 context.getAncestorClassWithProperty(targetTy, expr.member.name,
871 loc);
872 if (!upcastTargetTy)
873 return {};
874
875 // Convert the class handle to the required target type for property
876 // shadowing purposes.
877 Value baseVal =
878 context.convertRvalueExpression(expr.value(), upcastTargetTy);
879 if (!baseVal)
880 return {};
881
882 // @field and result type !moore.ref<T>.
883 auto fieldSym = mlir::FlatSymbolRefAttr::get(builder.getContext(),
884 expr.member.name);
885 auto fieldRefTy = moore::RefType::get(cast<moore::UnpackedType>(type));
886
887 // Produce a ref to the class property from the (possibly upcast)
888 // handle.
889 Value fieldRef = moore::ClassPropertyRefOp::create(
890 builder, loc, fieldRefTy, baseVal, fieldSym);
891
892 // If we need an RValue, read the reference, otherwise return
893 return isLvalue ? fieldRef
894 : moore::ReadOp::create(builder, loc, fieldRef);
895 }
896
897 slang::ConstantValue constVal;
898 if (auto param = expr.member.as_if<slang::ast::ParameterSymbol>()) {
899 constVal = param->getValue();
900 if (auto value = context.materializeConstant(constVal, *expr.type, loc))
901 return value;
902 }
903
904 mlir::emitError(loc) << "Parameter " << expr.member.name
905 << " has no constant value";
906 return {};
907 }
908
909 mlir::emitError(loc, "expression of type ")
910 << valueType->toString() << " has no member fields";
911 return {};
912 }
913};
914} // namespace
915
916//===----------------------------------------------------------------------===//
917// Rvalue Conversion
918//===----------------------------------------------------------------------===//
919
920// NOLINTBEGIN(misc-no-recursion)
921namespace {
922struct RvalueExprVisitor : public ExprVisitor {
923 RvalueExprVisitor(Context &context, Location loc)
924 : ExprVisitor(context, loc, /*isLvalue=*/false) {}
925 using ExprVisitor::visit;
926
927 // Handle references to the left-hand side of a parent assignment.
928 Value visit(const slang::ast::LValueReferenceExpression &expr) {
929 assert(!context.lvalueStack.empty() && "parent assignments push lvalue");
930 auto lvalue = context.lvalueStack.back();
931 return moore::ReadOp::create(builder, loc, lvalue);
932 }
933
934 // Handle named values, such as references to declared variables.
935 Value visit(const slang::ast::NamedValueExpression &expr) {
936 // Handle local variables.
937 if (auto value = context.valueSymbols.lookup(&expr.symbol)) {
938 if (isa<moore::RefType>(value.getType())) {
939 auto readOp = moore::ReadOp::create(builder, loc, value);
940 if (context.rvalueReadCallback)
941 context.rvalueReadCallback(readOp);
942 value = readOp.getResult();
943 }
944 return value;
945 }
946
947 // Handle global variables.
948 if (auto globalOp = context.globalVariables.lookup(&expr.symbol)) {
949 auto value = moore::GetGlobalVariableOp::create(builder, loc, globalOp);
950 return moore::ReadOp::create(builder, loc, value);
951 }
952
953 // We're reading a class property.
954 if (auto *const property =
955 expr.symbol.as_if<slang::ast::ClassPropertySymbol>()) {
956 auto fieldRef = visitClassProperty(context, *property);
957 return moore::ReadOp::create(builder, loc, fieldRef).getResult();
958 }
959
960 // Slang may resolve `vif.member` accesses (with `vif` being a virtual
961 // interface handle) directly to a NamedValueExpression for `member`.
962 // Reconstruct the virtual interface access by consulting the mapping
963 // populated at declaration sites.
964 if (auto access = context.virtualIfaceMembers.lookup(&expr.symbol);
965 access.base) {
966 auto type = context.convertType(*expr.type);
967 if (!type)
968 return {};
969 auto memberType = dyn_cast<moore::UnpackedType>(type);
970 if (!memberType) {
971 mlir::emitError(loc)
972 << "unsupported virtual interface member type: " << type;
973 return {};
974 }
975
976 Value base = materializeSymbolRvalue(*access.base);
977 if (!base) {
978 auto d = mlir::emitError(loc, "unknown name `")
979 << access.base->name << "`";
980 d.attachNote(context.convertLocation(access.base->location))
981 << "no rvalue generated for virtual interface base";
982 return {};
983 }
984
985 auto fieldName = access.fieldName
986 ? access.fieldName
987 : builder.getStringAttr(expr.symbol.name);
988 auto memberRefType = moore::RefType::get(memberType);
989 auto memberRef = moore::StructExtractOp::create(
990 builder, loc, memberRefType, fieldName, base);
991 auto readOp = moore::ReadOp::create(builder, loc, memberRef);
992 if (context.rvalueReadCallback)
993 context.rvalueReadCallback(readOp);
994 return readOp.getResult();
995 }
996
997 // Try to materialize constant values directly.
998 auto constant = context.evaluateConstant(expr);
999 if (auto value = context.materializeConstant(constant, *expr.type, loc))
1000 return value;
1001
1002 // Otherwise some other part of ImportVerilog should have added an MLIR
1003 // value for this expression's symbol to the `context.valueSymbols` table.
1004 auto d = mlir::emitError(loc, "unknown name `") << expr.symbol.name << "`";
1005 d.attachNote(context.convertLocation(expr.symbol.location))
1006 << "no rvalue generated for " << slang::ast::toString(expr.symbol.kind);
1007 return {};
1008 }
1009
1010 // Handle hierarchical values, such as `x = Top.sub.var`.
1011 Value visit(const slang::ast::HierarchicalValueExpression &expr) {
1012 auto hierLoc = context.convertLocation(expr.symbol.location);
1013
1014 // Canonicalize self-references (e.g., SubD.z inside SubD) to local
1015 // variable lookups. When the hierarchical path's first instance body
1016 // is the same module that declares the target symbol, the reference
1017 // is intra-module and should resolve to the local variable directly.
1018 if (!expr.ref.path.empty()) {
1019 if (auto *inst = expr.ref.path.front()
1020 .symbol->as_if<slang::ast::InstanceSymbol>()) {
1021 auto *symbolBody =
1022 expr.symbol.getParentScope()->getContainingInstance();
1023 if (&inst->body == symbolBody ||
1024 (symbolBody && inst->body.getDeclaringDefinition() ==
1025 symbolBody->getDeclaringDefinition())) {
1026 if (auto value = context.valueSymbols.lookup(&expr.symbol)) {
1027 if (isa<moore::RefType>(value.getType())) {
1028 auto readOp = moore::ReadOp::create(builder, hierLoc, value);
1029 if (context.rvalueReadCallback)
1030 context.rvalueReadCallback(readOp);
1031 value = readOp.getResult();
1032 }
1033 return value;
1034 }
1035 }
1036 }
1037 }
1038
1039 // Inside a function body, a captured symbol must resolve to the capture
1040 // argument to respect region isolation.
1041 if (auto value = context.resolveCapturedValue(expr.symbol)) {
1042 if (isa<moore::RefType>(value.getType())) {
1043 auto readOp = moore::ReadOp::create(builder, hierLoc, value);
1044 if (context.rvalueReadCallback)
1045 context.rvalueReadCallback(readOp);
1046 value = readOp.getResult();
1047 }
1048 return value;
1049 }
1050
1051 // For cross-instance hierarchical references, prefer the isntance-aware
1052 // hierValueSymbols lookup. Sibling instances elaborate distinct symbol
1053 // objects for the same logical variable, and this map keeps p1 vs p2
1054 // resolutions separate where the scoped table could conflate them.
1055 if (auto key = context.buildHierValueKey(expr)) {
1056 if (auto it = context.hierValueSymbols.find(*key);
1057 it != context.hierValueSymbols.end()) {
1058 auto value = it->second;
1059 if (isa<moore::RefType>(value.getType())) {
1060 auto readOp = moore::ReadOp::create(builder, hierLoc, value);
1061 if (context.rvalueReadCallback)
1062 context.rvalueReadCallback(readOp);
1063 value = readOp.getResult();
1064 }
1065 return value;
1066 }
1067 }
1068
1069 // Fall back to scoped symbol table (same-scope lookups, self-refs).
1070 if (auto value = context.valueSymbols.lookup(&expr.symbol)) {
1071 if (isa<moore::RefType>(value.getType())) {
1072 auto readOp = moore::ReadOp::create(builder, hierLoc, value);
1073 if (context.rvalueReadCallback)
1074 context.rvalueReadCallback(readOp);
1075 value = readOp.getResult();
1076 }
1077 return value;
1078 }
1079
1080 if (auto value = lookupExpandedInterfaceMember(context, expr)) {
1081 if (isa<moore::RefType>(value.getType())) {
1082 auto readOp = moore::ReadOp::create(builder, hierLoc, value);
1083 if (context.rvalueReadCallback)
1084 context.rvalueReadCallback(readOp);
1085 return readOp.getResult();
1086 }
1087 return value;
1088 }
1089
1090 /// Materialize compile-time constants directly from the symbol: the
1091 /// generic evaluateConstant refuses hierarchical references unless slang's
1092 /// AllowHierarchicalConst flag is set, which CIRCT does not use.
1093 slang::ConstantValue constant;
1094 switch (expr.symbol.kind) {
1095 case slang::ast::SymbolKind::Parameter:
1096 constant = expr.symbol.as<slang::ast::ParameterSymbol>().getValue(
1097 expr.sourceRange);
1098 break;
1099 case slang::ast::SymbolKind::Specparam:
1100 constant = expr.symbol.as<slang::ast::SpecparamSymbol>().getValue(
1101 expr.sourceRange);
1102 break;
1103 case slang::ast::SymbolKind::EnumValue:
1104 constant = expr.symbol.as<slang::ast::EnumValueSymbol>().getValue(
1105 expr.sourceRange);
1106 break;
1107 default:
1108 constant = context.evaluateConstant(expr);
1109 break;
1110 }
1111 if (auto value = context.materializeConstant(constant, *expr.type, loc))
1112 return value;
1113
1114 // Emit an error for those hierarchical values not recorded in the
1115 // `valueSymbols`.
1116 auto d = mlir::emitError(loc, "unknown hierarchical name `")
1117 << expr.symbol.name << "`";
1118 d.attachNote(hierLoc) << "no rvalue generated for "
1119 << slang::ast::toString(expr.symbol.kind);
1120 return {};
1121 }
1122
1123 // Handle arbitrary symbol references. Slang uses this expression to represent
1124 // "real" interface instances in virtual interface assignments.
1125 Value visit(const slang::ast::ArbitrarySymbolExpression &expr) {
1126 const auto &canonTy = expr.type->getCanonicalType();
1127 if (const auto *vi = canonTy.as_if<slang::ast::VirtualInterfaceType>()) {
1128 auto value = context.materializeVirtualInterfaceValue(*vi, loc);
1129 if (failed(value))
1130 return {};
1131 return *value;
1132 }
1133
1134 mlir::emitError(loc) << "unsupported arbitrary symbol expression of type "
1135 << expr.type->toString();
1136 return {};
1137 }
1138
1139 // Handle type conversions (explicit and implicit).
1140 Value visit(const slang::ast::ConversionExpression &expr) {
1141 auto type = context.convertType(*expr.type);
1142 if (!type)
1143 return {};
1144 return context.convertRvalueExpression(expr.operand(), type);
1145 }
1146
1147 // Handle blocking and non-blocking assignments.
1148 Value visit(const slang::ast::AssignmentExpression &expr) {
1149 auto lhs = context.convertLvalueExpression(expr.left());
1150 if (!lhs)
1151 return {};
1152
1153 // Determine the right-hand side value of the assignment.
1154 context.lvalueStack.push_back(lhs);
1155 auto rhs = context.convertRvalueExpression(
1156 expr.right(), cast<moore::RefType>(lhs.getType()).getNestedType());
1157 context.lvalueStack.pop_back();
1158 if (!rhs)
1159 return {};
1160
1161 // If this is a blocking assignment, we can insert the delay/wait ops of the
1162 // optional timing control directly in between computing the RHS and
1163 // executing the assignment.
1164 if (!expr.isNonBlocking()) {
1165 if (expr.timingControl)
1166 if (failed(context.convertTimingControl(*expr.timingControl)))
1167 return {};
1168 auto assignOp = moore::BlockingAssignOp::create(builder, loc, lhs, rhs);
1169 if (context.variableAssignCallback)
1170 context.variableAssignCallback(assignOp);
1171 return rhs;
1172 }
1173
1174 // For non-blocking assignments, we only support time delays for now.
1175 if (expr.timingControl) {
1176 // Handle regular time delays.
1177 if (auto *ctrl = expr.timingControl->as_if<slang::ast::DelayControl>()) {
1178 auto delay = context.convertRvalueExpression(
1179 ctrl->expr, moore::TimeType::get(builder.getContext()));
1180 if (!delay)
1181 return {};
1182 auto assignOp = moore::DelayedNonBlockingAssignOp::create(
1183 builder, loc, lhs, rhs, delay);
1184 if (context.variableAssignCallback)
1185 context.variableAssignCallback(assignOp);
1186 return rhs;
1187 }
1188
1189 // All other timing controls are not supported.
1190 auto loc = context.convertLocation(expr.timingControl->sourceRange);
1191 mlir::emitError(loc)
1192 << "unsupported non-blocking assignment timing control: "
1193 << slang::ast::toString(expr.timingControl->kind);
1194 return {};
1195 }
1196 auto assignOp = moore::NonBlockingAssignOp::create(builder, loc, lhs, rhs);
1197 if (context.variableAssignCallback)
1198 context.variableAssignCallback(assignOp);
1199 return rhs;
1200 }
1201
1202 // Helper function to convert an argument to a simple bit vector type, pass it
1203 // to a reduction op, and optionally invert the result.
1204 template <class ConcreteOp>
1205 Value createReduction(Value arg, bool invert) {
1206 arg = context.convertToSimpleBitVector(arg);
1207 if (!arg)
1208 return {};
1209 Value result = ConcreteOp::create(builder, loc, arg);
1210 if (invert)
1211 result = moore::NotOp::create(builder, loc, result);
1212 return result;
1213 }
1214
1215 // Helper function to create pre and post increments and decrements.
1216 Value createIncrement(Value arg, bool isInc, bool isPost) {
1217 auto preValue = moore::ReadOp::create(builder, loc, arg);
1218 Value postValue;
1219 // Catch the special case where a signed 1 bit value (i1) is incremented,
1220 // as +1 can not be expressed as a signed 1 bit value. For any 1-bit number
1221 // negating is equivalent to incrementing.
1222 if (moore::isIntType(preValue.getType(), 1)) {
1223 postValue = moore::NotOp::create(builder, loc, preValue).getResult();
1224 } else {
1225
1226 auto one = moore::ConstantOp::create(
1227 builder, loc, cast<moore::IntType>(preValue.getType()), 1);
1228 postValue =
1229 isInc ? moore::AddOp::create(builder, loc, preValue, one).getResult()
1230 : moore::SubOp::create(builder, loc, preValue, one).getResult();
1231 auto assignOp =
1232 moore::BlockingAssignOp::create(builder, loc, arg, postValue);
1233 if (context.variableAssignCallback)
1234 context.variableAssignCallback(assignOp);
1235 }
1236
1237 if (isPost)
1238 return preValue;
1239 return postValue;
1240 }
1241
1242 // Helper function to create pre and post increments and decrements.
1243 Value createRealIncrement(Value arg, bool isInc, bool isPost) {
1244 Value preValue = moore::ReadOp::create(builder, loc, arg);
1245 Value postValue;
1246
1247 bool isTime = isa<moore::TimeType>(preValue.getType());
1248 if (isTime)
1249 preValue = context.materializeConversion(
1250 moore::RealType::get(context.getContext(), moore::RealWidth::f64),
1251 preValue, false, loc);
1252
1253 moore::RealType realTy =
1254 llvm::dyn_cast<moore::RealType>(preValue.getType());
1255 if (!realTy)
1256 return {};
1257
1258 FloatAttr oneAttr;
1259 if (realTy.getWidth() == moore::RealWidth::f32) {
1260 oneAttr = builder.getFloatAttr(builder.getF32Type(), 1.0);
1261 } else if (realTy.getWidth() == moore::RealWidth::f64) {
1262 auto oneVal = isTime ? getTimeScaleInFemtoseconds(context) : 1.0;
1263 oneAttr = builder.getFloatAttr(builder.getF64Type(), oneVal);
1264 } else {
1265 mlir::emitError(loc) << "cannot construct increment for " << realTy;
1266 return {};
1267 }
1268 auto one = moore::ConstantRealOp::create(builder, loc, oneAttr);
1269
1270 postValue =
1271 isInc
1272 ? moore::AddRealOp::create(builder, loc, preValue, one).getResult()
1273 : moore::SubRealOp::create(builder, loc, preValue, one).getResult();
1274
1275 if (isTime)
1276 postValue = context.materializeConversion(
1277 moore::TimeType::get(context.getContext()), postValue, false, loc);
1278
1279 auto assignOp =
1280 moore::BlockingAssignOp::create(builder, loc, arg, postValue);
1281
1282 if (context.variableAssignCallback)
1283 context.variableAssignCallback(assignOp);
1284
1285 if (isPost)
1286 return preValue;
1287 return postValue;
1288 }
1289
1290 Value visitRealUOp(const slang::ast::UnaryExpression &expr) {
1291 Type opFTy = context.convertType(*expr.operand().type);
1292
1293 using slang::ast::UnaryOperator;
1294 Value arg;
1295 if (expr.op == UnaryOperator::Preincrement ||
1296 expr.op == UnaryOperator::Predecrement ||
1297 expr.op == UnaryOperator::Postincrement ||
1298 expr.op == UnaryOperator::Postdecrement)
1299 arg = context.convertLvalueExpression(expr.operand());
1300 else
1301 arg = context.convertRvalueExpression(expr.operand(), opFTy);
1302 if (!arg)
1303 return {};
1304
1305 // Only covers expressions in 'else' branch above.
1306 if (isa<moore::TimeType>(arg.getType()))
1307 arg = context.materializeConversion(
1308 moore::RealType::get(context.getContext(), moore::RealWidth::f64),
1309 arg, false, loc);
1310
1311 switch (expr.op) {
1312 // `+a` is simply `a`
1313 case UnaryOperator::Plus:
1314 return arg;
1315 case UnaryOperator::Minus:
1316 return moore::NegRealOp::create(builder, loc, arg);
1317
1318 case UnaryOperator::Preincrement:
1319 return createRealIncrement(arg, true, false);
1320 case UnaryOperator::Predecrement:
1321 return createRealIncrement(arg, false, false);
1322 case UnaryOperator::Postincrement:
1323 return createRealIncrement(arg, true, true);
1324 case UnaryOperator::Postdecrement:
1325 return createRealIncrement(arg, false, true);
1326
1327 case UnaryOperator::LogicalNot:
1328 arg = context.convertToBool(arg);
1329 if (!arg)
1330 return {};
1331 return moore::NotOp::create(builder, loc, arg);
1332
1333 default:
1334 mlir::emitError(loc) << "Unary operator " << slang::ast::toString(expr.op)
1335 << " not supported with real values!\n";
1336 return {};
1337 }
1338 }
1339
1340 // Handle unary operators.
1341 Value visit(const slang::ast::UnaryExpression &expr) {
1342 // First check whether we need real or integral BOps
1343 const auto *floatType =
1344 expr.operand().type->as_if<slang::ast::FloatingType>();
1345 // If op is real-typed, treat as real BOp.
1346 if (floatType)
1347 return visitRealUOp(expr);
1348
1349 using slang::ast::UnaryOperator;
1350 Value arg;
1351 if (expr.op == UnaryOperator::Preincrement ||
1352 expr.op == UnaryOperator::Predecrement ||
1353 expr.op == UnaryOperator::Postincrement ||
1354 expr.op == UnaryOperator::Postdecrement)
1355 arg = context.convertLvalueExpression(expr.operand());
1356 else
1357 arg = context.convertRvalueExpression(expr.operand());
1358 if (!arg)
1359 return {};
1360
1361 switch (expr.op) {
1362 // `+a` is simply `a`, but converted to a simple bit vector type since
1363 // this is technically an arithmetic operation.
1364 case UnaryOperator::Plus:
1365 return context.convertToSimpleBitVector(arg);
1366
1367 case UnaryOperator::Minus:
1368 arg = context.convertToSimpleBitVector(arg);
1369 if (!arg)
1370 return {};
1371 return moore::NegOp::create(builder, loc, arg);
1372
1373 case UnaryOperator::BitwiseNot:
1374 arg = context.convertToSimpleBitVector(arg);
1375 if (!arg)
1376 return {};
1377 return moore::NotOp::create(builder, loc, arg);
1378
1379 case UnaryOperator::BitwiseAnd:
1380 return createReduction<moore::ReduceAndOp>(arg, false);
1381 case UnaryOperator::BitwiseOr:
1382 return createReduction<moore::ReduceOrOp>(arg, false);
1383 case UnaryOperator::BitwiseXor:
1384 return createReduction<moore::ReduceXorOp>(arg, false);
1385 case UnaryOperator::BitwiseNand:
1386 return createReduction<moore::ReduceAndOp>(arg, true);
1387 case UnaryOperator::BitwiseNor:
1388 return createReduction<moore::ReduceOrOp>(arg, true);
1389 case UnaryOperator::BitwiseXnor:
1390 return createReduction<moore::ReduceXorOp>(arg, true);
1391
1392 case UnaryOperator::LogicalNot:
1393 arg = context.convertToBool(arg);
1394 if (!arg)
1395 return {};
1396 return moore::NotOp::create(builder, loc, arg);
1397
1398 case UnaryOperator::Preincrement:
1399 return createIncrement(arg, true, false);
1400 case UnaryOperator::Predecrement:
1401 return createIncrement(arg, false, false);
1402 case UnaryOperator::Postincrement:
1403 return createIncrement(arg, true, true);
1404 case UnaryOperator::Postdecrement:
1405 return createIncrement(arg, false, true);
1406 }
1407
1408 mlir::emitError(loc, "unsupported unary operator");
1409 return {};
1410 }
1411
1412 /// Handles logical operators (§11.4.7), assuming lhs/rhs are rvalues already.
1413 Value buildLogicalBOp(slang::ast::BinaryOperator op, Value lhs, Value rhs,
1414 std::optional<Domain> domain = std::nullopt) {
1415 using slang::ast::BinaryOperator;
1416 // TODO: These should short-circuit; RHS should be in a separate block.
1417
1418 if (domain) {
1419 lhs = context.convertToBool(lhs, domain.value());
1420 rhs = context.convertToBool(rhs, domain.value());
1421 } else {
1422 lhs = context.convertToBool(lhs);
1423 rhs = context.convertToBool(rhs);
1424 }
1425
1426 if (!lhs || !rhs)
1427 return {};
1428
1429 switch (op) {
1430 case BinaryOperator::LogicalAnd:
1431 return moore::AndOp::create(builder, loc, lhs, rhs);
1432
1433 case BinaryOperator::LogicalOr:
1434 return moore::OrOp::create(builder, loc, lhs, rhs);
1435
1436 case BinaryOperator::LogicalImplication: {
1437 // (lhs -> rhs) == (!lhs || rhs)
1438 auto notLHS = moore::NotOp::create(builder, loc, lhs);
1439 return moore::OrOp::create(builder, loc, notLHS, rhs);
1440 }
1441
1442 case BinaryOperator::LogicalEquivalence: {
1443 // (lhs <-> rhs) == (lhs && rhs) || (!lhs && !rhs)
1444 auto notLHS = moore::NotOp::create(builder, loc, lhs);
1445 auto notRHS = moore::NotOp::create(builder, loc, rhs);
1446 auto both = moore::AndOp::create(builder, loc, lhs, rhs);
1447 auto notBoth = moore::AndOp::create(builder, loc, notLHS, notRHS);
1448 return moore::OrOp::create(builder, loc, both, notBoth);
1449 }
1450
1451 default:
1452 llvm_unreachable("not a logical BinaryOperator");
1453 }
1454 }
1455
1456 Value visitHandleBOp(const slang::ast::BinaryExpression &expr) {
1457 // Convert operands to the chosen target type.
1458 auto lhs = context.convertRvalueExpression(expr.left());
1459 if (!lhs)
1460 return {};
1461 auto rhs = context.convertRvalueExpression(expr.right());
1462 if (!rhs)
1463 return {};
1464
1465 using slang::ast::BinaryOperator;
1466 switch (expr.op) {
1467
1468 case BinaryOperator::Equality:
1469 return moore::HandleEqOp::create(builder, loc, lhs, rhs);
1470 case BinaryOperator::Inequality:
1471 return moore::HandleNeOp::create(builder, loc, lhs, rhs);
1472 case BinaryOperator::CaseEquality:
1473 return moore::HandleCaseEqOp::create(builder, loc, lhs, rhs);
1474 case BinaryOperator::CaseInequality:
1475 return moore::HandleCaseNeOp::create(builder, loc, lhs, rhs);
1476
1477 default:
1478 mlir::emitError(loc)
1479 << "Binary operator " << slang::ast::toString(expr.op)
1480 << " not supported with class handle valued operands!\n";
1481 return {};
1482 }
1483 }
1484
1485 Value visitRealBOp(const slang::ast::BinaryExpression &expr) {
1486 // Convert operands to the chosen target type.
1487 auto lhs = context.convertRvalueExpression(expr.left());
1488 if (!lhs)
1489 return {};
1490 auto rhs = context.convertRvalueExpression(expr.right());
1491 if (!rhs)
1492 return {};
1493
1494 if (isa<moore::TimeType>(lhs.getType()) ||
1495 isa<moore::TimeType>(rhs.getType())) {
1496 lhs = context.materializeConversion(
1497 moore::RealType::get(context.getContext(), moore::RealWidth::f64),
1498 lhs, false, loc);
1499 rhs = context.materializeConversion(
1500 moore::RealType::get(context.getContext(), moore::RealWidth::f64),
1501 rhs, false, loc);
1502 }
1503
1504 using slang::ast::BinaryOperator;
1505 switch (expr.op) {
1506 case BinaryOperator::Add:
1507 return moore::AddRealOp::create(builder, loc, lhs, rhs);
1508 case BinaryOperator::Subtract:
1509 return moore::SubRealOp::create(builder, loc, lhs, rhs);
1510 case BinaryOperator::Multiply:
1511 return moore::MulRealOp::create(builder, loc, lhs, rhs);
1512 case BinaryOperator::Divide:
1513 return moore::DivRealOp::create(builder, loc, lhs, rhs);
1514 case BinaryOperator::Power:
1515 return moore::PowRealOp::create(builder, loc, lhs, rhs);
1516
1517 case BinaryOperator::Equality:
1518 return moore::EqRealOp::create(builder, loc, lhs, rhs);
1519 case BinaryOperator::Inequality:
1520 return moore::NeRealOp::create(builder, loc, lhs, rhs);
1521
1522 case BinaryOperator::GreaterThan:
1523 return moore::FgtOp::create(builder, loc, lhs, rhs);
1524 case BinaryOperator::LessThan:
1525 return moore::FltOp::create(builder, loc, lhs, rhs);
1526 case BinaryOperator::GreaterThanEqual:
1527 return moore::FgeOp::create(builder, loc, lhs, rhs);
1528 case BinaryOperator::LessThanEqual:
1529 return moore::FleOp::create(builder, loc, lhs, rhs);
1530
1531 case BinaryOperator::LogicalAnd:
1532 case BinaryOperator::LogicalOr:
1533 case BinaryOperator::LogicalImplication:
1534 case BinaryOperator::LogicalEquivalence: {
1535 Domain domain = Domain::TwoValued;
1536 if (expr.left().type->isFourState() || expr.right().type->isFourState())
1537 domain = Domain::FourValued;
1538 return buildLogicalBOp(expr.op, lhs, rhs, domain);
1539 }
1540
1541 default:
1542 mlir::emitError(loc) << "Binary operator "
1543 << slang::ast::toString(expr.op)
1544 << " not supported with real valued operands!\n";
1545 return {};
1546 }
1547 }
1548
1549 // Helper function to convert two arguments to a simple bit vector type and
1550 // pass them into a binary op.
1551 template <class ConcreteOp>
1552 Value createBinary(Value lhs, Value rhs) {
1553 lhs = context.convertToSimpleBitVector(lhs);
1554 if (!lhs)
1555 return {};
1556 rhs = context.convertToSimpleBitVector(rhs);
1557 if (!rhs)
1558 return {};
1559 return ConcreteOp::create(builder, loc, lhs, rhs);
1560 }
1561
1562 // Handle binary operators.
1563 Value visit(const slang::ast::BinaryExpression &expr) {
1564 if (expr.left().kind == slang::ast::ExpressionKind::TypeReference &&
1565 expr.right().kind == slang::ast::ExpressionKind::TypeReference) {
1566 auto &lhsType =
1567 expr.left().as<slang::ast::TypeReferenceExpression>().targetType;
1568 auto &rhsType =
1569 expr.right().as<slang::ast::TypeReferenceExpression>().targetType;
1570 bool value = lhsType.isMatching(rhsType);
1571
1572 using slang::ast::BinaryOperator;
1573 switch (expr.op) {
1574 case BinaryOperator::Equality:
1575 case BinaryOperator::CaseEquality:
1576 break;
1577 case BinaryOperator::Inequality:
1578 case BinaryOperator::CaseInequality:
1579 value = !value;
1580 break;
1581 default:
1582 mlir::emitError(loc, "unsupported type reference binary operator");
1583 return {};
1584 }
1585
1586 auto type = moore::IntType::get(context.getContext(), /*width=*/1,
1587 moore::Domain::TwoValued);
1588 return moore::ConstantOp::create(builder, loc, type, value,
1589 /*isSigned=*/false);
1590 }
1591
1592 // First check whether we need real or integral BOps
1593 const auto *rhsFloatType =
1594 expr.right().type->as_if<slang::ast::FloatingType>();
1595 const auto *lhsFloatType =
1596 expr.left().type->as_if<slang::ast::FloatingType>();
1597
1598 // If either arg is real-typed, treat as real BOp.
1599 if (rhsFloatType || lhsFloatType)
1600 return visitRealBOp(expr);
1601
1602 // Check whether we are comparing against a Class Handle or CHandle
1603 const auto rhsIsClass = expr.right().type->isClass();
1604 const auto lhsIsClass = expr.left().type->isClass();
1605 const auto rhsIsChandle = expr.right().type->isCHandle();
1606 const auto lhsIsChandle = expr.left().type->isCHandle();
1607 // If either arg is class handle-typed, treat as class handle BOp.
1608 if (rhsIsClass || lhsIsClass || rhsIsChandle || lhsIsChandle)
1609 return visitHandleBOp(expr);
1610
1611 auto lhs = context.convertRvalueExpression(expr.left());
1612 if (!lhs)
1613 return {};
1614 auto rhs = context.convertRvalueExpression(expr.right());
1615 if (!rhs)
1616 return {};
1617
1618 // Determine the domain of the result.
1619 Domain domain = Domain::TwoValued;
1620 if (expr.type->isFourState() || expr.left().type->isFourState() ||
1621 expr.right().type->isFourState())
1622 domain = Domain::FourValued;
1623
1624 using slang::ast::BinaryOperator;
1625 switch (expr.op) {
1626 case BinaryOperator::Add:
1627 return createBinary<moore::AddOp>(lhs, rhs);
1628 case BinaryOperator::Subtract:
1629 return createBinary<moore::SubOp>(lhs, rhs);
1630 case BinaryOperator::Multiply:
1631 return createBinary<moore::MulOp>(lhs, rhs);
1632 case BinaryOperator::Divide:
1633 if (expr.type->isSigned())
1634 return createBinary<moore::DivSOp>(lhs, rhs);
1635 else
1636 return createBinary<moore::DivUOp>(lhs, rhs);
1637 case BinaryOperator::Mod:
1638 if (expr.type->isSigned())
1639 return createBinary<moore::ModSOp>(lhs, rhs);
1640 else
1641 return createBinary<moore::ModUOp>(lhs, rhs);
1642 case BinaryOperator::Power: {
1643 // Slang casts the LHS and result of the `**` operator to a four-valued
1644 // type, since the operator can return X even for two-valued inputs. To
1645 // maintain uniform types across operands and results, cast the RHS to
1646 // that four-valued type as well.
1647 auto rhsCast = context.materializeConversion(
1648 lhs.getType(), rhs, expr.right().type->isSigned(), rhs.getLoc());
1649 if (expr.type->isSigned())
1650 return createBinary<moore::PowSOp>(lhs, rhsCast);
1651 else
1652 return createBinary<moore::PowUOp>(lhs, rhsCast);
1653 }
1654
1655 case BinaryOperator::BinaryAnd:
1656 return createBinary<moore::AndOp>(lhs, rhs);
1657 case BinaryOperator::BinaryOr:
1658 return createBinary<moore::OrOp>(lhs, rhs);
1659 case BinaryOperator::BinaryXor:
1660 return createBinary<moore::XorOp>(lhs, rhs);
1661 case BinaryOperator::BinaryXnor: {
1662 auto result = createBinary<moore::XorOp>(lhs, rhs);
1663 if (!result)
1664 return {};
1665 return moore::NotOp::create(builder, loc, result);
1666 }
1667
1668 case BinaryOperator::Equality:
1669 if (isa<moore::UnpackedArrayType>(lhs.getType()))
1670 return moore::UArrayCmpOp::create(
1671 builder, loc, moore::UArrayCmpPredicate::eq, lhs, rhs);
1672 else if (isa<moore::StringType>(lhs.getType()))
1673 return moore::StringCmpOp::create(
1674 builder, loc, moore::StringCmpPredicate::eq, lhs, rhs);
1675 else if (isa<moore::QueueType>(lhs.getType()))
1676 return moore::QueueCmpOp::create(
1677 builder, loc, moore::UArrayCmpPredicate::eq, lhs, rhs);
1678 else
1679 return createBinary<moore::EqOp>(lhs, rhs);
1680 case BinaryOperator::Inequality:
1681 if (isa<moore::UnpackedArrayType>(lhs.getType()))
1682 return moore::UArrayCmpOp::create(
1683 builder, loc, moore::UArrayCmpPredicate::ne, lhs, rhs);
1684 else if (isa<moore::StringType>(lhs.getType()))
1685 return moore::StringCmpOp::create(
1686 builder, loc, moore::StringCmpPredicate::ne, lhs, rhs);
1687 else if (isa<moore::QueueType>(lhs.getType()))
1688 return moore::QueueCmpOp::create(
1689 builder, loc, moore::UArrayCmpPredicate::ne, lhs, rhs);
1690 else
1691 return createBinary<moore::NeOp>(lhs, rhs);
1692 case BinaryOperator::CaseEquality:
1693 return createBinary<moore::CaseEqOp>(lhs, rhs);
1694 case BinaryOperator::CaseInequality:
1695 return createBinary<moore::CaseNeOp>(lhs, rhs);
1696 case BinaryOperator::WildcardEquality:
1697 return createBinary<moore::WildcardEqOp>(lhs, rhs);
1698 case BinaryOperator::WildcardInequality:
1699 return createBinary<moore::WildcardNeOp>(lhs, rhs);
1700
1701 case BinaryOperator::GreaterThanEqual:
1702 if (expr.left().type->isSigned())
1703 return createBinary<moore::SgeOp>(lhs, rhs);
1704 else if (isa<moore::StringType>(lhs.getType()))
1705 return moore::StringCmpOp::create(
1706 builder, loc, moore::StringCmpPredicate::ge, lhs, rhs);
1707 else
1708 return createBinary<moore::UgeOp>(lhs, rhs);
1709 case BinaryOperator::GreaterThan:
1710 if (expr.left().type->isSigned())
1711 return createBinary<moore::SgtOp>(lhs, rhs);
1712 else if (isa<moore::StringType>(lhs.getType()))
1713 return moore::StringCmpOp::create(
1714 builder, loc, moore::StringCmpPredicate::gt, lhs, rhs);
1715 else
1716 return createBinary<moore::UgtOp>(lhs, rhs);
1717 case BinaryOperator::LessThanEqual:
1718 if (expr.left().type->isSigned())
1719 return createBinary<moore::SleOp>(lhs, rhs);
1720 else if (isa<moore::StringType>(lhs.getType()))
1721 return moore::StringCmpOp::create(
1722 builder, loc, moore::StringCmpPredicate::le, lhs, rhs);
1723 else
1724 return createBinary<moore::UleOp>(lhs, rhs);
1725 case BinaryOperator::LessThan:
1726 if (expr.left().type->isSigned())
1727 return createBinary<moore::SltOp>(lhs, rhs);
1728 else if (isa<moore::StringType>(lhs.getType()))
1729 return moore::StringCmpOp::create(
1730 builder, loc, moore::StringCmpPredicate::lt, lhs, rhs);
1731 else
1732 return createBinary<moore::UltOp>(lhs, rhs);
1733
1734 case BinaryOperator::LogicalAnd:
1735 case BinaryOperator::LogicalOr:
1736 case BinaryOperator::LogicalImplication:
1737 case BinaryOperator::LogicalEquivalence:
1738 return buildLogicalBOp(expr.op, lhs, rhs, domain);
1739
1740 case BinaryOperator::LogicalShiftLeft:
1741 return createBinary<moore::ShlOp>(lhs, rhs);
1742 case BinaryOperator::LogicalShiftRight:
1743 return createBinary<moore::ShrOp>(lhs, rhs);
1744 case BinaryOperator::ArithmeticShiftLeft:
1745 return createBinary<moore::ShlOp>(lhs, rhs);
1746 case BinaryOperator::ArithmeticShiftRight: {
1747 // The `>>>` operator is an arithmetic right shift if the LHS operand is
1748 // signed, or a logical right shift if the operand is unsigned.
1749 lhs = context.convertToSimpleBitVector(lhs);
1750 rhs = context.convertToSimpleBitVector(rhs);
1751 if (!lhs || !rhs)
1752 return {};
1753 if (expr.type->isSigned())
1754 return moore::AShrOp::create(builder, loc, lhs, rhs);
1755 return moore::ShrOp::create(builder, loc, lhs, rhs);
1756 }
1757 }
1758
1759 mlir::emitError(loc, "unsupported binary operator");
1760 return {};
1761 }
1762
1763 // Handle `'0`, `'1`, `'x`, and `'z` literals.
1764 Value visit(const slang::ast::UnbasedUnsizedIntegerLiteral &expr) {
1765 return context.materializeSVInt(expr.getValue(), *expr.type, loc);
1766 }
1767
1768 // Handle integer literals.
1769 Value visit(const slang::ast::IntegerLiteral &expr) {
1770 return context.materializeSVInt(expr.getValue(), *expr.type, loc);
1771 }
1772
1773 // Handle time literals.
1774 Value visit(const slang::ast::TimeLiteral &expr) {
1775 // The time literal is expressed in the current time scale. Determine the
1776 // conversion factor to convert the literal from the current time scale into
1777 // femtoseconds, and round the scaled value to femtoseconds.
1778 double scale = getTimeScaleInFemtoseconds(context);
1779 double value = std::round(expr.getValue() * scale);
1780 assert(value >= 0.0);
1781
1782 // Check that the value does not exceed what we can represent in the IR.
1783 // Casting the maximum uint64 value to double changes its value from
1784 // 18446744073709551615 to 18446744073709551616, which makes the comparison
1785 // overestimate the largest number we can represent. To avoid this, round
1786 // the maximum value down to the closest number that only has the front 53
1787 // bits set. This matches the mantissa of a double, plus the implicit
1788 // leading 1, ensuring that we can accurately represent the limit.
1789 static constexpr uint64_t limit =
1790 (std::numeric_limits<uint64_t>::max() >> 11) << 11;
1791 if (value > limit) {
1792 mlir::emitError(loc) << "time value is larger than " << limit << " fs";
1793 return {};
1794 }
1795
1796 return moore::ConstantTimeOp::create(builder, loc,
1797 static_cast<uint64_t>(value));
1798 }
1799
1800 // Handle replications.
1801 Value visit(const slang::ast::ReplicationExpression &expr) {
1802 auto type = context.convertType(*expr.type);
1803 auto value = context.convertRvalueExpression(expr.concat());
1804 if (!value)
1805 return {};
1806 return moore::ReplicateOp::create(builder, loc, type, value);
1807 }
1808
1809 // Handle set membership operator.
1810 Value visit(const slang::ast::InsideExpression &expr) {
1811 auto lhs = context.convertToSimpleBitVector(
1812 context.convertRvalueExpression(expr.left()));
1813 if (!lhs)
1814 return {};
1815
1816 // All conditions for determining whether it is inside.
1817 SmallVector<Value> conditions;
1818
1819 // Traverse open range list.
1820 for (const auto *listExpr : expr.rangeList()) {
1821 auto cond = context.convertInsideCheck(lhs, loc, *listExpr);
1822 if (!cond)
1823 return {};
1824
1825 conditions.push_back(cond);
1826 }
1827
1828 // Calculate the final result by `or` op.
1829 auto result = conditions.back();
1830 conditions.pop_back();
1831 while (!conditions.empty()) {
1832 result = moore::OrOp::create(builder, loc, conditions.back(), result);
1833 conditions.pop_back();
1834 }
1835 return result;
1836 }
1837
1838 // Handle conditional operator `?:`.
1839 Value visit(const slang::ast::ConditionalExpression &expr) {
1840 auto type = context.convertType(*expr.type);
1841
1842 // Handle condition.
1843 if (expr.conditions.size() > 1) {
1844 mlir::emitError(loc)
1845 << "unsupported conditional expression with more than one condition";
1846 return {};
1847 }
1848 const auto &cond = expr.conditions[0];
1849 if (cond.pattern) {
1850 mlir::emitError(loc) << "unsupported conditional expression with pattern";
1851 return {};
1852 }
1853 auto value =
1854 context.convertToBool(context.convertRvalueExpression(*cond.expr));
1855 if (!value)
1856 return {};
1857 auto conditionalOp =
1858 moore::ConditionalOp::create(builder, loc, type, value);
1859
1860 // Create blocks for true region and false region.
1861 auto &trueBlock = conditionalOp.getTrueRegion().emplaceBlock();
1862 auto &falseBlock = conditionalOp.getFalseRegion().emplaceBlock();
1863
1864 OpBuilder::InsertionGuard g(builder);
1865
1866 // Handle left expression.
1867 builder.setInsertionPointToStart(&trueBlock);
1868 auto trueValue = context.convertRvalueExpression(expr.left(), type);
1869 if (!trueValue)
1870 return {};
1871 moore::YieldOp::create(builder, loc, trueValue);
1872
1873 // Handle right expression.
1874 builder.setInsertionPointToStart(&falseBlock);
1875 auto falseValue = context.convertRvalueExpression(expr.right(), type);
1876 if (!falseValue)
1877 return {};
1878 moore::YieldOp::create(builder, loc, falseValue);
1879
1880 return conditionalOp.getResult();
1881 }
1882
1883 /// Handle calls.
1884 Value visit(const slang::ast::CallExpression &expr) {
1885 // Try to materialize constant values directly.
1886 auto constant = context.evaluateConstant(expr);
1887 if (auto value = context.materializeConstant(constant, *expr.type, loc))
1888 return value;
1889
1890 return std::visit(
1891 [&](auto &subroutine) { return visitCall(expr, subroutine); },
1892 expr.subroutine);
1893 }
1894
1895 /// Get both the actual `this` argument of a method call and the required
1896 /// class type.
1897 std::pair<Value, moore::ClassHandleType>
1898 getMethodReceiverTypeHandle(const slang::ast::CallExpression &expr) {
1899
1900 moore::ClassHandleType handleTy;
1901 Value thisRef;
1902
1903 // Qualified call: t.m(...), extract from thisClass.
1904 if (const slang::ast::Expression *recvExpr = expr.thisClass()) {
1905 thisRef = context.convertRvalueExpression(*recvExpr);
1906 if (!thisRef)
1907 return {};
1908 } else {
1909 // Unqualified call inside a method body: try using implicit %this.
1910 thisRef = context.getImplicitThisRef();
1911 if (!thisRef) {
1912 mlir::emitError(loc) << "method '" << expr.getSubroutineName()
1913 << "' called without an object";
1914 return {};
1915 }
1916 }
1917 handleTy = cast<moore::ClassHandleType>(thisRef.getType());
1918 return {thisRef, handleTy};
1919 }
1920
1921 /// Build a method call including implicit this argument.
1922 mlir::CallOpInterface
1923 buildMethodCall(const slang::ast::SubroutineSymbol *subroutine,
1924 FunctionLowering *lowering,
1925 moore::ClassHandleType actualHandleTy, Value actualThisRef,
1926 SmallVector<Value> &arguments,
1927 SmallVector<Type> &resultTypes) {
1928
1929 // Get the expected receiver type from the lowered method
1930 auto funcTy = cast<FunctionType>(lowering->op.getFunctionType());
1931 auto expected0 = funcTy.getInput(0);
1932 auto expectedHdlTy = cast<moore::ClassHandleType>(expected0);
1933
1934 // Upcast the handle as necessary.
1935 auto implicitThisRef = context.materializeConversion(
1936 expectedHdlTy, actualThisRef, false, actualThisRef.getLoc());
1937
1938 // Build an argument list where the this reference is the first argument.
1939 SmallVector<Value> explicitArguments;
1940 explicitArguments.reserve(arguments.size() + 1);
1941 explicitArguments.push_back(implicitThisRef);
1942 explicitArguments.append(arguments.begin(), arguments.end());
1943
1944 // Method call: choose direct vs virtual.
1945 const bool isVirtual =
1946 (subroutine->flags & slang::ast::MethodFlags::Virtual) != 0;
1947
1948 if (!isVirtual) {
1949 auto calleeSym = lowering->op.getNameAttr().getValue();
1950 if (isa<moore::CoroutineOp>(lowering->op.getOperation()))
1951 return moore::CallCoroutineOp::create(builder, loc, resultTypes,
1952 calleeSym, explicitArguments);
1953 return mlir::func::CallOp::create(builder, loc, resultTypes, calleeSym,
1954 explicitArguments);
1955 }
1956
1957 auto funcName = subroutine->name;
1958 auto method = moore::VTableLoadMethodOp::create(
1959 builder, loc, funcTy, actualThisRef,
1960 SymbolRefAttr::get(context.getContext(), funcName));
1961 return mlir::func::CallIndirectOp::create(builder, loc, method,
1962 explicitArguments);
1963 }
1964
1965 /// Handle subroutine calls.
1966 Value visitCall(const slang::ast::CallExpression &expr,
1967 const slang::ast::SubroutineSymbol *subroutine) {
1968
1969 const bool isMethod = (subroutine->thisVar != nullptr);
1970
1971 auto *lowering = context.declareFunction(*subroutine);
1972 if (!lowering)
1973 return {};
1974
1975 if (isa<moore::DPIFuncOp>(lowering->op.getOperation())) {
1976 SmallVector<Value> operands;
1977 SmallVector<Value> resultTargets;
1978
1979 for (auto [callArg, declArg] :
1980 llvm::zip(expr.arguments(), subroutine->getArguments())) {
1981 auto *actual = callArg;
1982 if (const auto *assign =
1983 actual->as_if<slang::ast::AssignmentExpression>())
1984 actual = &assign->left();
1985
1986 auto argType = context.convertType(declArg->getType());
1987 if (!argType)
1988 return {};
1989
1990 switch (declArg->direction) {
1991 case slang::ast::ArgumentDirection::In: {
1992 auto value = context.convertRvalueExpression(*actual, argType);
1993 if (!value)
1994 return {};
1995 operands.push_back(value);
1996 break;
1997 }
1998 case slang::ast::ArgumentDirection::Out: {
1999 auto lvalue = context.convertLvalueExpression(*actual);
2000 if (!lvalue)
2001 return {};
2002 resultTargets.push_back(lvalue);
2003 break;
2004 }
2005 case slang::ast::ArgumentDirection::InOut:
2006 case slang::ast::ArgumentDirection::Ref: {
2007 auto lvalue = context.convertLvalueExpression(*actual);
2008 if (!lvalue)
2009 return {};
2010 auto value = context.convertRvalueExpression(*actual, argType);
2011 if (!value)
2012 return {};
2013 operands.push_back(value);
2014 resultTargets.push_back(lvalue);
2015 break;
2016 }
2017 }
2018 }
2019
2020 SmallVector<Type> resultTypes(
2021 cast<FunctionType>(lowering->op.getFunctionType()).getResults());
2022 auto callOp = moore::FuncDPICallOp::create(
2023 builder, loc, resultTypes,
2024 SymbolRefAttr::get(lowering->op.getNameAttr()), operands);
2025
2026 unsigned resultIndex = 0;
2027 unsigned targetIndex = 0;
2028 for (const auto *declArg : subroutine->getArguments()) {
2029 auto argType = context.convertType(declArg->getType());
2030 if (!argType)
2031 return {};
2032
2033 switch (declArg->direction) {
2034 case slang::ast::ArgumentDirection::Out:
2035 case slang::ast::ArgumentDirection::InOut:
2036 case slang::ast::ArgumentDirection::Ref: {
2037 auto lvalue = resultTargets[targetIndex++];
2038 auto refTy = dyn_cast<moore::RefType>(lvalue.getType());
2039 if (!refTy) {
2040 lowering->op->emitError(
2041 "expected DPI output target to be moore::RefType");
2042 return {};
2043 }
2044 auto converted = context.materializeConversion(
2045 refTy.getNestedType(), callOp->getResult(resultIndex++),
2046 declArg->getType().isSigned(), loc);
2047 if (!converted)
2048 return {};
2049 moore::BlockingAssignOp::create(builder, loc, lvalue, converted);
2050 break;
2051 }
2052 default:
2053 break;
2054 }
2055 }
2056
2057 if (!subroutine->getReturnType().isVoid())
2058 return callOp->getResult(resultIndex);
2059
2060 return mlir::UnrealizedConversionCastOp::create(
2061 builder, loc, moore::VoidType::get(context.getContext()),
2062 ValueRange{})
2063 .getResult(0);
2064 }
2065
2066 // Convert the call arguments. Input arguments are converted to an rvalue.
2067 // All other arguments are converted to lvalues and passed into the function
2068 // by reference.
2069 SmallVector<Value> arguments;
2070 for (auto [callArg, declArg] :
2071 llvm::zip(expr.arguments(), subroutine->getArguments())) {
2072
2073 // Unpack the `<expr> = EmptyArgument` pattern emitted by Slang for output
2074 // and inout arguments.
2075 auto *expr = callArg;
2076 if (const auto *assign = expr->as_if<slang::ast::AssignmentExpression>())
2077 expr = &assign->left();
2078
2079 Value value;
2080 auto type = context.convertType(declArg->getType());
2081 if (declArg->direction == slang::ast::ArgumentDirection::In) {
2082 value = context.convertRvalueExpression(*expr, type);
2083 } else {
2084 Value lvalue = context.convertLvalueExpression(*expr);
2085 auto unpackedType = dyn_cast<moore::UnpackedType>(type);
2086 if (!unpackedType)
2087 return {};
2088 value =
2089 context.materializeConversion(moore::RefType::get(unpackedType),
2090 lvalue, expr->type->isSigned(), loc);
2091 }
2092 if (!value)
2093 return {};
2094 arguments.push_back(value);
2095 }
2096
2097 // Pass captured variables as extra arguments. Each captured AST symbol is
2098 // resolved to an MLIR value through the scoped symbol table, which
2099 // naturally handles transitive captures (the caller’s own capture block
2100 // argument will be found for variables captured from an outer scope).
2101 for (auto *sym : lowering->capturedSymbols) {
2102 Value val = context.valueSymbols.lookup(sym);
2103 if (!val) {
2104 mlir::emitError(loc) << "failed to resolve captured variable `"
2105 << sym->name << "` at call site";
2106 return {};
2107 }
2108 arguments.push_back(val);
2109 }
2110
2111 // Determine result types from the declared/converted func op.
2112 SmallVector<Type> resultTypes(
2113 cast<FunctionType>(lowering->op.getFunctionType()).getResults().begin(),
2114 cast<FunctionType>(lowering->op.getFunctionType()).getResults().end());
2115
2116 mlir::CallOpInterface callOp;
2117 if (isMethod) {
2118 // Class functions -> build func.call / func.indirect_call with implicit
2119 // this argument
2120 auto [thisRef, tyHandle] = getMethodReceiverTypeHandle(expr);
2121 callOp = buildMethodCall(subroutine, lowering, tyHandle, thisRef,
2122 arguments, resultTypes);
2123 } else if (isa<moore::CoroutineOp>(lowering->op.getOperation())) {
2124 // Free task -> moore.call_coroutine
2125 auto coroutine = cast<moore::CoroutineOp>(lowering->op.getOperation());
2126 callOp =
2127 moore::CallCoroutineOp::create(builder, loc, coroutine, arguments);
2128 } else {
2129 // Free function -> func.call
2130 auto funcOp = cast<mlir::func::FuncOp>(lowering->op.getOperation());
2131 callOp = mlir::func::CallOp::create(builder, loc, funcOp, arguments);
2132 }
2133
2134 auto result = resultTypes.size() > 0 ? callOp->getOpResult(0) : Value{};
2135 // For calls to void functions we need to have a value to return from this
2136 // function. Create a dummy `unrealized_conversion_cast`, which will get
2137 // deleted again later on.
2138 if (resultTypes.size() == 0)
2139 return mlir::UnrealizedConversionCastOp::create(
2140 builder, loc, moore::VoidType::get(context.getContext()),
2141 ValueRange{})
2142 .getResult(0);
2143
2144 return result;
2145 }
2146
2147 /// Handle system calls.
2148 Value visitCall(const slang::ast::CallExpression &expr,
2149 const slang::ast::CallExpression::SystemCallInfo &info) {
2150 using ksn = slang::parsing::KnownSystemName;
2151 const auto &subroutine = *info.subroutine;
2152 auto nameId = subroutine.knownNameId;
2153
2154 // $rose, $fell, $stable, $changed, $past, and $sampled are only valid in
2155 // the contexts with clocks. Those are treated in AssertionExpr.
2156 switch (nameId) {
2157 case ksn::Rose:
2158 case ksn::Fell:
2159 case ksn::Stable:
2160 case ksn::Changed:
2161 case ksn::Past:
2162 case ksn::Sampled:
2163 return context.convertSampledValueCallExpression(expr, info, loc);
2164 default:
2165 break;
2166 }
2167
2168 auto args = expr.arguments();
2169
2170 // $sformatf() and $sformat look like system tasks, but we handle string
2171 // formatting differently from expression evaluation, so handle them
2172 // separately.
2173 // According to IEEE 1800-2023 Section 21.3.3 "Formatting data to a
2174 // string" $sformatf works just like the string formatting but returns
2175 // a StringType.
2176 if (nameId == ksn::SFormatF) {
2177 // Create the FormatString
2178 auto fmtValue = context.convertFormatString(
2179 expr.arguments(), loc, moore::IntFormat::Decimal, false);
2180 if (failed(fmtValue))
2181 return {};
2182 return fmtValue.value();
2183 }
2184
2185 // Convert the system call using unified dispatch
2186 auto result = context.convertSystemCall(subroutine, loc, args);
2187 if (!result)
2188 return {};
2189
2190 auto ty = context.convertType(*expr.type);
2191 // Bit vector builtins ($countones, $isunknown, $onehot, $onehot0) return
2192 // inherently unsigned results that must be zero-extended, even though
2193 // Slang's declared return type may be signed int.
2194 bool isSigned = expr.type->isSigned();
2195 if (nameId == ksn::CountOnes || nameId == ksn::IsUnknown ||
2196 nameId == ksn::OneHot || nameId == ksn::OneHot0)
2197 isSigned = false;
2198 return context.materializeConversion(ty, result, isSigned, loc);
2199 }
2200
2201 /// Handle string literals.
2202 Value visit(const slang::ast::StringLiteral &expr) {
2203 auto type = context.convertType(*expr.type);
2204 return moore::ConstantStringOp::create(builder, loc, type, expr.getValue());
2205 }
2206
2207 /// Handle real literals.
2208 Value visit(const slang::ast::RealLiteral &expr) {
2209 auto fTy = mlir::Float64Type::get(context.getContext());
2210 auto attr = mlir::FloatAttr::get(fTy, expr.getValue());
2211 return moore::ConstantRealOp::create(builder, loc, attr).getResult();
2212 }
2213
2214 /// Helper function to convert RValues at creation of a new Struct, Array or
2215 /// Int.
2216 FailureOr<SmallVector<Value>>
2217 convertElements(const slang::ast::AssignmentPatternExpressionBase &expr,
2218 std::variant<Type, ArrayRef<Type>> expectedTypes,
2219 unsigned replCount) {
2220 const auto &elts = expr.elements();
2221 const size_t elementCount = elts.size();
2222
2223 // Inspect the variant.
2224 const bool hasBroadcast =
2225 std::holds_alternative<Type>(expectedTypes) &&
2226 static_cast<bool>(std::get<Type>(expectedTypes)); // non-null Type
2227
2228 const bool hasPerElem =
2229 std::holds_alternative<ArrayRef<Type>>(expectedTypes) &&
2230 !std::get<ArrayRef<Type>>(expectedTypes).empty();
2231
2232 // If per-element types are provided, enforce arity.
2233 if (hasPerElem) {
2234 auto types = std::get<ArrayRef<Type>>(expectedTypes);
2235 if (types.size() != elementCount) {
2236 mlir::emitError(loc)
2237 << "assignment pattern arity mismatch: expected " << types.size()
2238 << " elements, got " << elementCount;
2239 return failure();
2240 }
2241 }
2242
2243 SmallVector<Value> converted;
2244 converted.reserve(elementCount * std::max(1u, replCount));
2245
2246 // Convert each element heuristically, no type is expected
2247 if (!hasBroadcast && !hasPerElem) {
2248 // No expected type info.
2249 for (const auto *elementExpr : elts) {
2250 Value v = context.convertRvalueExpression(*elementExpr);
2251 if (!v)
2252 return failure();
2253 converted.push_back(v);
2254 }
2255 } else if (hasBroadcast) {
2256 // Same expected type for all elements.
2257 Type want = std::get<Type>(expectedTypes);
2258 for (const auto *elementExpr : elts) {
2259 Value v = want ? context.convertRvalueExpression(*elementExpr, want)
2260 : context.convertRvalueExpression(*elementExpr);
2261 if (!v)
2262 return failure();
2263 converted.push_back(v);
2264 }
2265 } else { // hasPerElem, individual type is expected for each element
2266 auto types = std::get<ArrayRef<Type>>(expectedTypes);
2267 for (size_t i = 0; i < elementCount; ++i) {
2268 Type want = types[i];
2269 const auto *elementExpr = elts[i];
2270 Value v = want ? context.convertRvalueExpression(*elementExpr, want)
2271 : context.convertRvalueExpression(*elementExpr);
2272 if (!v)
2273 return failure();
2274 converted.push_back(v);
2275 }
2276 }
2277
2278 for (unsigned i = 1; i < replCount; ++i)
2279 converted.append(converted.begin(), converted.begin() + elementCount);
2280
2281 return converted;
2282 }
2283
2284 /// Handle assignment patterns.
2285 Value visitAssignmentPattern(
2286 const slang::ast::AssignmentPatternExpressionBase &expr,
2287 unsigned replCount = 1) {
2288 auto type = context.convertType(*expr.type);
2289 const auto &elts = expr.elements();
2290
2291 // Handle integers.
2292 if (auto intType = dyn_cast<moore::IntType>(type)) {
2293 auto elements = convertElements(expr, {}, replCount);
2294
2295 if (failed(elements))
2296 return {};
2297
2298 assert(intType.getWidth() == elements->size());
2299 ensureDescendingOrder(*elements, *expr.type);
2300 return moore::ConcatOp::create(builder, loc, intType, *elements);
2301 }
2302
2303 // Handle packed structs.
2304 if (auto structType = dyn_cast<moore::StructType>(type)) {
2305 SmallVector<Type> expectedTy;
2306 expectedTy.reserve(structType.getMembers().size());
2307 for (auto member : structType.getMembers())
2308 expectedTy.push_back(member.type);
2309
2310 FailureOr<SmallVector<Value>> elements;
2311 if (expectedTy.size() == elts.size())
2312 elements = convertElements(expr, expectedTy, replCount);
2313 else
2314 elements = convertElements(expr, {}, replCount);
2315
2316 if (failed(elements))
2317 return {};
2318
2319 assert(structType.getMembers().size() == elements->size());
2320 return moore::StructCreateOp::create(builder, loc, structType, *elements);
2321 }
2322
2323 // Handle unpacked structs.
2324 if (auto structType = dyn_cast<moore::UnpackedStructType>(type)) {
2325 SmallVector<Type> expectedTy;
2326 expectedTy.reserve(structType.getMembers().size());
2327 for (auto member : structType.getMembers())
2328 expectedTy.push_back(member.type);
2329
2330 FailureOr<SmallVector<Value>> elements;
2331 if (expectedTy.size() == elts.size())
2332 elements = convertElements(expr, expectedTy, replCount);
2333 else
2334 elements = convertElements(expr, {}, replCount);
2335
2336 if (failed(elements))
2337 return {};
2338
2339 assert(structType.getMembers().size() == elements->size());
2340
2341 return moore::StructCreateOp::create(builder, loc, structType, *elements);
2342 }
2343
2344 // Handle packed arrays.
2345 if (auto arrayType = dyn_cast<moore::ArrayType>(type)) {
2346 auto elements =
2347 convertElements(expr, arrayType.getElementType(), replCount);
2348
2349 if (failed(elements))
2350 return {};
2351
2352 assert(arrayType.getSize() == elements->size());
2353 ensureDescendingOrder(*elements, *expr.type);
2354 return moore::ArrayCreateOp::create(builder, loc, arrayType, *elements);
2355 }
2356
2357 // Handle unpacked arrays.
2358 if (auto arrayType = dyn_cast<moore::UnpackedArrayType>(type)) {
2359 auto elements =
2360 convertElements(expr, arrayType.getElementType(), replCount);
2361
2362 if (failed(elements))
2363 return {};
2364
2365 assert(arrayType.getSize() == elements->size());
2366 return moore::ArrayCreateOp::create(builder, loc, arrayType, *elements);
2367 }
2368
2369 // Handle open/dynamic unpacked arrays.
2370 if (auto openType = dyn_cast<moore::OpenUnpackedArrayType>(type)) {
2371 auto elements =
2372 convertElements(expr, openType.getElementType(), replCount);
2373
2374 if (failed(elements))
2375 return {};
2376
2377 auto arrayType = moore::UnpackedArrayType::get(
2378 context.getContext(), elements->size(), openType.getElementType());
2379 return moore::ArrayCreateOp::create(builder, loc, arrayType, *elements);
2380 }
2381
2382 mlir::emitError(loc) << "unsupported assignment pattern with type " << type;
2383 return {};
2384 }
2385
2386 Value visit(const slang::ast::SimpleAssignmentPatternExpression &expr) {
2387 return visitAssignmentPattern(expr);
2388 }
2389
2390 Value visit(const slang::ast::StructuredAssignmentPatternExpression &expr) {
2391 return visitAssignmentPattern(expr);
2392 }
2393
2394 Value visit(const slang::ast::ReplicatedAssignmentPatternExpression &expr) {
2395 auto count =
2396 context.evaluateConstant(expr.count()).integer().as<unsigned>();
2397 assert(count && "Slang guarantees constant non-zero replication count");
2398 return visitAssignmentPattern(expr, *count);
2399 }
2400
2401 Value visit(const slang::ast::StreamingConcatenationExpression &expr) {
2402 SmallVector<Value> operands;
2403 for (auto stream : expr.streams()) {
2404 auto operandLoc = context.convertLocation(stream.operand->sourceRange);
2405 if (!stream.constantWithWidth.has_value() && stream.withExpr) {
2406 mlir::emitError(operandLoc)
2407 << "Moore only support streaming "
2408 "concatenation with fixed size 'with expression'";
2409 return {};
2410 }
2411 Value value;
2412 if (stream.constantWithWidth.has_value()) {
2413 value = context.convertRvalueExpression(*stream.withExpr);
2414 auto type = cast<moore::UnpackedType>(value.getType());
2415 auto intType = moore::IntType::get(
2416 context.getContext(), type.getBitSize().value(), type.getDomain());
2417 // Do not care if it's signed, because we will not do expansion.
2418 value = context.materializeConversion(intType, value, false, loc);
2419 } else {
2420 value = context.convertRvalueExpression(*stream.operand);
2421 }
2422
2423 value = context.convertToSimpleBitVector(value);
2424 if (!value)
2425 return {};
2426 operands.push_back(value);
2427 }
2428 Value value;
2429
2430 if (operands.size() == 1) {
2431 // There must be at least one element, otherwise slang will report an
2432 // error.
2433 value = operands.front();
2434 } else {
2435 value = moore::ConcatOp::create(builder, loc, operands).getResult();
2436 }
2437
2438 if (expr.getSliceSize() == 0) {
2439 return value;
2440 }
2441
2442 auto type = cast<moore::IntType>(value.getType());
2443 SmallVector<Value> slicedOperands;
2444 auto iterMax = type.getWidth() / expr.getSliceSize();
2445 auto remainSize = type.getWidth() % expr.getSliceSize();
2446
2447 for (size_t i = 0; i < iterMax; i++) {
2448 auto extractResultType = moore::IntType::get(
2449 context.getContext(), expr.getSliceSize(), type.getDomain());
2450
2451 auto extracted = moore::ExtractOp::create(builder, loc, extractResultType,
2452 value, i * expr.getSliceSize());
2453 slicedOperands.push_back(extracted);
2454 }
2455 // Handle other wire
2456 if (remainSize) {
2457 auto extractResultType = moore::IntType::get(
2458 context.getContext(), remainSize, type.getDomain());
2459
2460 auto extracted =
2461 moore::ExtractOp::create(builder, loc, extractResultType, value,
2462 iterMax * expr.getSliceSize());
2463 slicedOperands.push_back(extracted);
2464 }
2465
2466 return moore::ConcatOp::create(builder, loc, slicedOperands);
2467 }
2468
2469 Value visit(const slang::ast::AssertionInstanceExpression &expr) {
2470 return context.convertAssertionExpression(expr.body, loc);
2471 }
2472
2473 Value visit(const slang::ast::UnboundedLiteral &expr) {
2474 assert(context.getIndexedQueue() &&
2475 "slang checks $ only used within queue index expression");
2476
2477 // Compute queue size and subtract one to get the last element
2478 auto queueSize =
2479 moore::QueueSizeBIOp::create(builder, loc, context.getIndexedQueue());
2480 auto one = moore::ConstantOp::create(builder, loc, queueSize.getType(), 1);
2481 auto lastElement = moore::SubOp::create(builder, loc, queueSize, one);
2482
2483 return lastElement;
2484 }
2485
2486 // A new class expression can stand for one of two things:
2487 // 1) A call to the `new` method (ctor) of a class made outside the scope of
2488 // the class
2489 // 2) A call to the `super.new` method, i.e. the constructor of the base
2490 // class, within the scope of a class, more specifically, within the new
2491 // method override of a class.
2492 // In the first case we should emit an allocation and a call to the ctor if it
2493 // exists (it's optional in System Verilog), in the second case we should emit
2494 // a call to the parent's ctor (System Verilog only has single inheritance, so
2495 // super is always unambiguous), but no allocation, as the child class' new
2496 // invocation already allocated space for both its own and its parent's
2497 // properties.
2498 Value visit(const slang::ast::NewClassExpression &expr) {
2499 auto type = context.convertType(*expr.type);
2500 auto classTy = dyn_cast<moore::ClassHandleType>(type);
2501 Value newObj;
2502
2503 // We are calling new from within a new function, and it's pointing to
2504 // super. Check the implicit this ref to figure out the super class type.
2505 // Do not allocate a new object.
2506 if (!classTy && expr.isSuperClass) {
2507 newObj = context.getImplicitThisRef();
2508 if (!newObj || !newObj.getType() ||
2509 !isa<moore::ClassHandleType>(newObj.getType())) {
2510 mlir::emitError(loc) << "implicit this ref was not set while "
2511 "converting new class function";
2512 return {};
2513 }
2514 auto thisType = cast<moore::ClassHandleType>(newObj.getType());
2515 auto classDecl =
2516 cast<moore::ClassDeclOp>(*context.symbolTable.lookupNearestSymbolFrom(
2517 context.intoModuleOp, thisType.getClassSym()));
2518 auto baseClassSym = classDecl.getBase();
2519 classTy = circt::moore::ClassHandleType::get(context.getContext(),
2520 baseClassSym.value());
2521 } else {
2522 // We are calling from outside a class; allocate space for the object.
2523 newObj = moore::ClassNewOp::create(builder, loc, classTy, {});
2524 }
2525
2526 const auto *constructor = expr.constructorCall();
2527 // If there's no ctor, we are done.
2528 if (!constructor)
2529 return newObj;
2530
2531 if (const auto *callConstructor =
2532 constructor->as_if<slang::ast::CallExpression>())
2533 if (const auto *subroutine =
2534 std::get_if<const slang::ast::SubroutineSymbol *>(
2535 &callConstructor->subroutine)) {
2536 if (!(*subroutine)->thisVar) {
2537 mlir::emitError(loc)
2538 << "unsupported constructor call without `this` argument";
2539 return {};
2540 }
2541 // Pass the newObj as the implicit this argument of the ctor.
2542 llvm::SaveAndRestore saveThis(context.currentThisRef, newObj);
2543 if (!visitCall(*callConstructor, *subroutine))
2544 return {};
2545 return newObj;
2546 }
2547 return {};
2548 }
2549
2550 /// Emit an error for all other expressions.
2551 template <typename T>
2552 Value visit(T &&node) {
2553 mlir::emitError(loc, "unsupported expression: ")
2554 << slang::ast::toString(node.kind);
2555 return {};
2556 }
2557
2558 Value visitInvalid(const slang::ast::Expression &expr) {
2559 mlir::emitError(loc, "invalid expression");
2560 return {};
2561 }
2562};
2563} // namespace
2564
2565//===----------------------------------------------------------------------===//
2566// Lvalue Conversion
2567//===----------------------------------------------------------------------===//
2568
2569namespace {
2570struct LvalueExprVisitor : public ExprVisitor {
2571 LvalueExprVisitor(Context &context, Location loc)
2572 : ExprVisitor(context, loc, /*isLvalue=*/true) {}
2573 using ExprVisitor::visit;
2574
2575 // Handle named values, such as references to declared variables.
2576 Value visit(const slang::ast::NamedValueExpression &expr) {
2577 // Handle local variables.
2578 if (auto value = context.valueSymbols.lookup(&expr.symbol))
2579 return value;
2580
2581 // Handle global variables.
2582 if (auto globalOp = context.globalVariables.lookup(&expr.symbol))
2583 return moore::GetGlobalVariableOp::create(builder, loc, globalOp);
2584
2585 if (auto *const property =
2586 expr.symbol.as_if<slang::ast::ClassPropertySymbol>()) {
2587 return visitClassProperty(context, *property);
2588 }
2589
2590 if (auto access = context.virtualIfaceMembers.lookup(&expr.symbol);
2591 access.base) {
2592 auto type = context.convertType(*expr.type);
2593 if (!type)
2594 return {};
2595 auto memberType = dyn_cast<moore::UnpackedType>(type);
2596 if (!memberType) {
2597 mlir::emitError(loc)
2598 << "unsupported virtual interface member type: " << type;
2599 return {};
2600 }
2601
2602 Value base = materializeSymbolRvalue(*access.base);
2603 if (!base) {
2604 auto d = mlir::emitError(loc, "unknown name `")
2605 << access.base->name << "`";
2606 d.attachNote(context.convertLocation(access.base->location))
2607 << "no rvalue generated for virtual interface base";
2608 return {};
2609 }
2610
2611 auto fieldName = access.fieldName
2612 ? access.fieldName
2613 : builder.getStringAttr(expr.symbol.name);
2614 auto memberRefType = moore::RefType::get(memberType);
2615 return moore::StructExtractOp::create(builder, loc, memberRefType,
2616 fieldName, base);
2617 }
2618
2619 auto d = mlir::emitError(loc, "unknown name `") << expr.symbol.name << "`";
2620 d.attachNote(context.convertLocation(expr.symbol.location))
2621 << "no lvalue generated for " << slang::ast::toString(expr.symbol.kind);
2622 return {};
2623 }
2624
2625 // Handle hierarchical values, such as `Top.sub.var = x`.
2626 Value visit(const slang::ast::HierarchicalValueExpression &expr) {
2627 // Canonicalize self-references (e.g., SubD.w inside SubD) to local
2628 // variable lookups (same rationale as rvalue visitor).
2629 if (!expr.ref.path.empty()) {
2630 if (auto *inst = expr.ref.path.front()
2631 .symbol->as_if<slang::ast::InstanceSymbol>()) {
2632 auto *symbolBody =
2633 expr.symbol.getParentScope()->getContainingInstance();
2634 if (&inst->body == symbolBody ||
2635 (symbolBody && inst->body.getDeclaringDefinition() ==
2636 symbolBody->getDeclaringDefinition())) {
2637 if (auto value = context.valueSymbols.lookup(&expr.symbol))
2638 return value;
2639 }
2640 }
2641 }
2642
2643 // Same capture priority as the rvalue visitor.
2644 if (auto value = context.resolveCapturedValue(expr.symbol))
2645 return value;
2646
2647 // For cross-instance hierarchical references, use the instance-aware
2648 // hierValueSymbols lookup (same priority and rationale as rvalue
2649 // visitor).
2650 if (auto key = context.buildHierValueKey(expr)) {
2651 if (auto it = context.hierValueSymbols.find(*key);
2652 it != context.hierValueSymbols.end())
2653 return it->second;
2654 }
2655
2656 // Fall back to scoped symbol table (same-scope lookups, self-refs).
2657 if (auto value = context.valueSymbols.lookup(&expr.symbol))
2658 return value;
2659
2660 if (auto value = lookupExpandedInterfaceMember(context, expr))
2661 return value;
2662
2663 // Handle global variables.
2664 if (auto globalOp = context.globalVariables.lookup(&expr.symbol))
2665 return moore::GetGlobalVariableOp::create(builder, loc, globalOp);
2666
2667 // Emit an error for those hierarchical values not recorded in the
2668 // `valueSymbols`.
2669 auto d = mlir::emitError(loc, "unknown hierarchical name `")
2670 << expr.symbol.name << "`";
2671 d.attachNote(context.convertLocation(expr.symbol.location))
2672 << "no lvalue generated for " << slang::ast::toString(expr.symbol.kind);
2673 return {};
2674 }
2675
2676 Value visit(const slang::ast::StreamingConcatenationExpression &expr) {
2677 SmallVector<Value> operands;
2678 for (auto stream : expr.streams()) {
2679 auto operandLoc = context.convertLocation(stream.operand->sourceRange);
2680 if (!stream.constantWithWidth.has_value() && stream.withExpr) {
2681 mlir::emitError(operandLoc)
2682 << "Moore only support streaming "
2683 "concatenation with fixed size 'with expression'";
2684 return {};
2685 }
2686 Value value;
2687 if (stream.constantWithWidth.has_value()) {
2688 value = context.convertLvalueExpression(*stream.withExpr);
2689 auto type = cast<moore::UnpackedType>(
2690 cast<moore::RefType>(value.getType()).getNestedType());
2691 auto intType = moore::RefType::get(moore::IntType::get(
2692 context.getContext(), type.getBitSize().value(), type.getDomain()));
2693 // Do not care if it's signed, because we will not do expansion.
2694 value = context.materializeConversion(intType, value, false, loc);
2695 } else {
2696 value = context.convertLvalueExpression(*stream.operand);
2697 }
2698
2699 if (!value)
2700 return {};
2701 operands.push_back(value);
2702 }
2703 Value value;
2704 if (operands.size() == 1) {
2705 // There must be at least one element, otherwise slang will report an
2706 // error.
2707 value = operands.front();
2708 } else {
2709 value = moore::ConcatRefOp::create(builder, loc, operands).getResult();
2710 }
2711
2712 if (expr.getSliceSize() == 0) {
2713 return value;
2714 }
2715
2716 auto type = cast<moore::IntType>(
2717 cast<moore::RefType>(value.getType()).getNestedType());
2718 SmallVector<Value> slicedOperands;
2719 auto widthSum = type.getWidth();
2720 auto domain = type.getDomain();
2721 auto iterMax = widthSum / expr.getSliceSize();
2722 auto remainSize = widthSum % expr.getSliceSize();
2723
2724 for (size_t i = 0; i < iterMax; i++) {
2725 auto extractResultType = moore::RefType::get(moore::IntType::get(
2726 context.getContext(), expr.getSliceSize(), domain));
2727
2728 auto extracted = moore::ExtractRefOp::create(
2729 builder, loc, extractResultType, value, i * expr.getSliceSize());
2730 slicedOperands.push_back(extracted);
2731 }
2732 // Handle other wire
2733 if (remainSize) {
2734 auto extractResultType = moore::RefType::get(
2735 moore::IntType::get(context.getContext(), remainSize, domain));
2736
2737 auto extracted =
2738 moore::ExtractRefOp::create(builder, loc, extractResultType, value,
2739 iterMax * expr.getSliceSize());
2740 slicedOperands.push_back(extracted);
2741 }
2742
2743 return moore::ConcatRefOp::create(builder, loc, slicedOperands);
2744 }
2745
2746 /// Emit an error for all other expressions.
2747 template <typename T>
2748 Value visit(T &&node) {
2749 return context.convertRvalueExpression(node);
2750 }
2751
2752 Value visitInvalid(const slang::ast::Expression &expr) {
2753 mlir::emitError(loc, "invalid expression");
2754 return {};
2755 }
2756};
2757} // namespace
2758
2759//===----------------------------------------------------------------------===//
2760// Hierarchical Name Helpers
2761//===----------------------------------------------------------------------===//
2762
2763Value Context::resolveCapturedValue(const slang::ast::ValueSymbol &sym) {
2765 return {};
2766 if (!llvm::is_contained(currentFunctionLowering->capturedSymbols, &sym))
2767 return {};
2768 return valueSymbols.lookup(&sym);
2769}
2770
2771std::optional<std::pair<const slang::ast::InstanceSymbol *, mlir::StringAttr>>
2773 const slang::ast::HierarchicalValueExpression &expr) {
2774 if (expr.ref.path.empty())
2775 return std::nullopt;
2776
2777 const slang::ast::InstanceSymbol *firstInst = nullptr;
2778 SmallVector<StringRef, 4> names;
2779 for (auto &elem : expr.ref.path) {
2780 if (auto *inst = elem.symbol->as_if<slang::ast::InstanceSymbol>()) {
2781 if (!firstInst) {
2782 firstInst = inst;
2783 } else {
2784 names.push_back(inst->name);
2785 }
2786 }
2787 }
2788 names.push_back(expr.symbol.name);
2789 std::string hierName = llvm::join(names, ".");
2790
2791 if (!firstInst)
2792 return std::nullopt;
2793 return std::make_pair(firstInst, builder.getStringAttr(hierName));
2794}
2795
2796//===----------------------------------------------------------------------===//
2797// Entry Points
2798//===----------------------------------------------------------------------===//
2799
2800Value Context::convertRvalueExpression(const slang::ast::Expression &expr,
2801 Type requiredType) {
2802 auto loc = convertLocation(expr.sourceRange);
2803 auto value = expr.visit(RvalueExprVisitor(*this, loc));
2804 if (value && requiredType)
2805 value =
2806 materializeConversion(requiredType, value, expr.type->isSigned(), loc);
2807 return value;
2808}
2809
2810Value Context::convertLvalueExpression(const slang::ast::Expression &expr) {
2811 auto loc = convertLocation(expr.sourceRange);
2812 return expr.visit(LvalueExprVisitor(*this, loc));
2813}
2814// NOLINTEND(misc-no-recursion)
2815
2816/// Helper function to convert a value to its "truthy" boolean value.
2817Value Context::convertToBool(Value value) {
2818 if (!value)
2819 return {};
2820 if (auto type = dyn_cast_or_null<moore::IntType>(value.getType()))
2821 if (type.getBitSize() == 1)
2822 return value;
2823 if (auto type = dyn_cast_or_null<moore::UnpackedType>(value.getType()))
2824 return moore::BoolCastOp::create(builder, value.getLoc(), value);
2825 mlir::emitError(value.getLoc(), "expression of type ")
2826 << value.getType() << " cannot be cast to a boolean";
2827 return {};
2828}
2829
2830/// Materialize a Slang real literal as a constant op.
2831Value Context::materializeSVReal(const slang::ConstantValue &svreal,
2832 const slang::ast::Type &astType,
2833 Location loc) {
2834 const auto *floatType = astType.as_if<slang::ast::FloatingType>();
2835 assert(floatType);
2836
2837 FloatAttr attr;
2838 if (svreal.isShortReal() &&
2839 floatType->floatKind == slang::ast::FloatingType::ShortReal) {
2840 attr = FloatAttr::get(builder.getF32Type(), svreal.shortReal().v);
2841 } else if (svreal.isReal() &&
2842 floatType->floatKind == slang::ast::FloatingType::Real) {
2843 attr = FloatAttr::get(builder.getF64Type(), svreal.real().v);
2844 } else {
2845 mlir::emitError(loc) << "invalid real constant";
2846 return {};
2847 }
2848
2849 return moore::ConstantRealOp::create(builder, loc, attr);
2850}
2851
2852/// Materialize a Slang string literal as a literal string constant op.
2853Value Context::materializeString(const slang::ConstantValue &stringLiteral,
2854 const slang::ast::Type &astType,
2855 Location loc) {
2856 if (!astType.isString())
2857 return {};
2858 const std::string &str = stringLiteral.str();
2859 auto intTy = moore::IntType::getInt(getContext(),
2860 static_cast<unsigned>(str.size() * 8));
2861 auto immInt =
2862 moore::ConstantStringOp::create(builder, loc, intTy, str).getResult();
2863 return moore::IntToStringOp::create(builder, loc, immInt).getResult();
2864}
2865
2866/// Materialize a Slang integer literal as a constant op.
2867Value Context::materializeSVInt(const slang::SVInt &svint,
2868 const slang::ast::Type &astType, Location loc) {
2869 auto type = convertType(astType);
2870 if (!type)
2871 return {};
2872
2873 bool typeIsFourValued = false;
2874 if (auto unpackedType = dyn_cast<moore::UnpackedType>(type))
2875 typeIsFourValued = unpackedType.getDomain() == moore::Domain::FourValued;
2876
2877 auto fvint = convertSVIntToFVInt(svint);
2878 auto intType = moore::IntType::get(getContext(), fvint.getBitWidth(),
2879 fvint.hasUnknown() || typeIsFourValued
2882 auto result = moore::ConstantOp::create(builder, loc, intType, fvint);
2883 return materializeConversion(type, result, astType.isSigned(), loc);
2884}
2885
2887 const slang::ConstantValue &constant,
2888 const slang::ast::FixedSizeUnpackedArrayType &astType, Location loc) {
2889
2890 auto type = convertType(astType);
2891 if (!type)
2892 return {};
2893
2894 // Handle string array constants.
2895 if (astType.elementType.isString()) {
2896 auto arrayType = dyn_cast<moore::UnpackedArrayType>(type);
2897 if (!arrayType)
2898 return {};
2899
2900 SmallVector<Value> elemVals;
2901 for (const auto &elem : constant.elements()) {
2902 if (!elem.isString())
2903 return {};
2904 auto value = materializeString(elem, astType.elementType, loc);
2905 if (!value)
2906 return {};
2907 elemVals.push_back(value);
2908 }
2909 if (elemVals.size() != arrayType.getSize())
2910 return {};
2911 return moore::ArrayCreateOp::create(builder, loc, arrayType, elemVals);
2912 }
2913
2914 // Check whether underlying type is an integer, if so, get bit width
2915 unsigned bitWidth;
2916 if (astType.elementType.isIntegral())
2917 bitWidth = astType.elementType.getBitWidth();
2918 else
2919 return {};
2920
2921 bool typeIsFourValued = false;
2922
2923 // Check whether the underlying type is four-valued
2924 if (auto unpackedType = dyn_cast<moore::UnpackedType>(type))
2925 typeIsFourValued = unpackedType.getDomain() == moore::Domain::FourValued;
2926 else
2927 return {};
2928
2929 auto domain =
2931
2932 // Construct the integer type this is an unpacked array of; if possible keep
2933 // it two-valued, unless any entry is four-valued or the underlying type is
2934 // four-valued
2935 auto intType = moore::IntType::get(getContext(), bitWidth, domain);
2936 // Construct the full array type from intType
2937 auto arrType = moore::UnpackedArrayType::get(
2938 getContext(), constant.elements().size(), intType);
2939
2940 llvm::SmallVector<mlir::Value> elemVals;
2941 moore::ConstantOp constOp;
2942
2943 mlir::OpBuilder::InsertionGuard guard(builder);
2944
2945 // Add one ConstantOp for every element in the array
2946 for (auto elem : constant.elements()) {
2947 FVInt fvInt = convertSVIntToFVInt(elem.integer());
2948 constOp = moore::ConstantOp::create(builder, loc, intType, fvInt);
2949 elemVals.push_back(constOp.getResult());
2950 }
2951
2952 // Take the result of each ConstantOp and concatenate them into an array (of
2953 // constant values).
2954 auto arrayOp = moore::ArrayCreateOp::create(builder, loc, arrType, elemVals);
2955
2956 return arrayOp.getResult();
2957}
2958
2959Value Context::materializeConstant(const slang::ConstantValue &constant,
2960 const slang::ast::Type &type, Location loc) {
2961
2962 if (auto *arr = type.as_if<slang::ast::FixedSizeUnpackedArrayType>())
2963 return materializeFixedSizeUnpackedArrayType(constant, *arr, loc);
2964 if (constant.isInteger())
2965 return materializeSVInt(constant.integer(), type, loc);
2966 if (constant.isReal() || constant.isShortReal())
2967 return materializeSVReal(constant, type, loc);
2968 if (constant.isString())
2969 return materializeString(constant, type, loc);
2970
2971 return {};
2972}
2973
2974slang::ConstantValue
2975Context::evaluateConstant(const slang::ast::Expression &expr) {
2976 using slang::ast::EvalFlags;
2977 slang::ast::EvalContext evalContext(
2978 slang::ast::ASTContext(compilation.getRoot(),
2979 slang::ast::LookupLocation::max),
2980 EvalFlags::CacheResults | EvalFlags::SpecparamsAllowed);
2981 return expr.eval(evalContext);
2982}
2983
2984/// Helper function to convert a value to its "truthy" boolean value and
2985/// convert it to the given domain.
2986Value Context::convertToBool(Value value, Domain domain) {
2987 value = convertToBool(value);
2988 if (!value)
2989 return {};
2990 auto type = moore::IntType::get(getContext(), 1, domain);
2991 return materializeConversion(type, value, false, value.getLoc());
2992}
2993
2995 if (!value)
2996 return {};
2997 if (isa<moore::IntType>(value.getType()))
2998 return value;
2999
3000 // Some operations in Slang's AST, for example bitwise or `|`, don't cast
3001 // packed struct/array operands to simple bit vectors but directly operate
3002 // on the struct/array. Since the corresponding IR ops operate only on
3003 // simple bit vectors, insert a conversion in this case.
3004 if (auto packed = dyn_cast<moore::PackedType>(value.getType()))
3005 if (auto sbvType = packed.getSimpleBitVector())
3006 return materializeConversion(sbvType, value, false, value.getLoc());
3007
3008 mlir::emitError(value.getLoc()) << "expression of type " << value.getType()
3009 << " cannot be cast to a simple bit vector";
3010 return {};
3011}
3012
3013Value Context::materializePackedToSBVConversion(Value value, Location loc,
3014 bool fallible) {
3015 if (isa<moore::IntType>(value.getType()))
3016 return value;
3017
3018 auto packedType = cast<moore::PackedType>(value.getType());
3019 auto intType = packedType.getSimpleBitVector();
3020 assert(intType);
3021
3022 // If we are converting from a time to an integer, divide the integer by the
3023 // timescale.
3024 if (isa<moore::TimeType>(packedType) &&
3026 value = builder.createOrFold<moore::TimeToLogicOp>(loc, value);
3027 auto scale = moore::ConstantOp::create(builder, loc, intType,
3029 return builder.createOrFold<moore::DivUOp>(loc, value, scale);
3030 }
3031
3032 // If this is an aggregate type, make sure that it does not contain any
3033 // `TimeType` fields. These require special conversion to ensure that the
3034 // local timescale is in effect.
3035 if (packedType.containsTimeType()) {
3036 if (!fallible)
3037 mlir::emitError(loc) << "unsupported conversion: " << packedType
3038 << " cannot be converted to " << intType
3039 << "; contains a time type";
3040 return {};
3041 }
3042
3043 // Otherwise create a simple `PackedToSBVOp` for the conversion.
3044 return builder.createOrFold<moore::PackedToSBVOp>(loc, value);
3045}
3046
3047/// Create the necessary operations to convert from a simple bit vector
3048/// `IntType` to an equivalent `PackedType`. This will apply special handling to
3049/// time values, which requires scaling by the local timescale.
3051 moore::PackedType packedType,
3052 Value value, Location loc,
3053 bool fallible) {
3054 if (value.getType() == packedType)
3055 return value;
3056
3057 auto &builder = context.builder;
3058 auto intType = cast<moore::IntType>(value.getType());
3059 assert(intType && intType == packedType.getSimpleBitVector());
3060
3061 // If we are converting from an integer to a time, multiply the integer by the
3062 // timescale.
3063 if (isa<moore::TimeType>(packedType) &&
3065 auto scale = moore::ConstantOp::create(builder, loc, intType,
3067 value = builder.createOrFold<moore::MulOp>(loc, value, scale);
3068 return builder.createOrFold<moore::LogicToTimeOp>(loc, value);
3069 }
3070
3071 // If this is an aggregate type, make sure that it does not contain any
3072 // `TimeType` fields. These require special conversion to ensure that the
3073 // local timescale is in effect.
3074 if (packedType.containsTimeType()) {
3075 if (!fallible)
3076 mlir::emitError(loc) << "unsupported conversion: " << intType
3077 << " cannot be converted to " << packedType
3078 << "; contains a time type";
3079 return {};
3080 }
3081
3082 // Otherwise create a simple `PackedToSBVOp` for the conversion.
3083 return builder.createOrFold<moore::SBVToPackedOp>(loc, packedType, value);
3084}
3085
3086/// Check whether the actual handle is a subclass of another handle type
3087/// and return a properly upcast version if so.
3088static mlir::Value maybeUpcastHandle(Context &context, mlir::Value actualHandle,
3089 moore::ClassHandleType expectedHandleTy) {
3090 auto loc = actualHandle.getLoc();
3091
3092 auto actualTy = actualHandle.getType();
3093 auto actualHandleTy = dyn_cast<moore::ClassHandleType>(actualTy);
3094 if (!actualHandleTy) {
3095 mlir::emitError(loc) << "expected a !moore.class<...> value, got "
3096 << actualTy;
3097 return {};
3098 }
3099
3100 // Fast path: already the expected handle type.
3101 if (actualHandleTy == expectedHandleTy)
3102 return actualHandle;
3103
3104 if (!context.isClassDerivedFrom(actualHandleTy, expectedHandleTy)) {
3105 mlir::emitError(loc)
3106 << "receiver class " << actualHandleTy.getClassSym()
3107 << " is not the same as, or derived from, expected base class "
3108 << expectedHandleTy.getClassSym().getRootReference();
3109 return {};
3110 }
3111
3112 // Only implicit upcasting is allowed - down casting should never be implicit.
3113 auto casted = moore::ClassUpcastOp::create(context.builder, loc,
3114 expectedHandleTy, actualHandle)
3115 .getResult();
3116 return casted;
3117}
3118
3119Value Context::materializeConversion(Type type, Value value, bool isSigned,
3120 Location loc, bool fallible) {
3121 // Nothing to do if the types are already equal.
3122 if (type == value.getType())
3123 return value;
3124
3125 // Handle packed types which can be converted to a simple bit vector. This
3126 // allows us to perform resizing and domain casting on that bit vector.
3127 auto dstPacked = dyn_cast<moore::PackedType>(type);
3128 auto srcPacked = dyn_cast<moore::PackedType>(value.getType());
3129 auto dstInt = dstPacked ? dstPacked.getSimpleBitVector() : moore::IntType();
3130 auto srcInt = srcPacked ? srcPacked.getSimpleBitVector() : moore::IntType();
3131
3132 if (dstInt && srcInt) {
3133 // Convert the value to a simple bit vector if it isn't one already.
3134 value = materializePackedToSBVConversion(value, loc, fallible);
3135 if (!value)
3136 return {};
3137
3138 // Create truncation or sign/zero extension ops depending on the source and
3139 // destination width.
3140 auto resizedType = moore::IntType::get(
3141 value.getContext(), dstInt.getWidth(), srcPacked.getDomain());
3142 if (dstInt.getWidth() < srcInt.getWidth()) {
3143 value = builder.createOrFold<moore::TruncOp>(loc, resizedType, value);
3144 } else if (dstInt.getWidth() > srcInt.getWidth()) {
3145 if (isSigned)
3146 value = builder.createOrFold<moore::SExtOp>(loc, resizedType, value);
3147 else
3148 value = builder.createOrFold<moore::ZExtOp>(loc, resizedType, value);
3149 }
3150
3151 // Convert the domain if needed.
3152 if (dstInt.getDomain() != srcInt.getDomain()) {
3153 if (dstInt.getDomain() == moore::Domain::TwoValued)
3154 value = builder.createOrFold<moore::LogicToIntOp>(loc, value);
3155 else if (dstInt.getDomain() == moore::Domain::FourValued)
3156 value = builder.createOrFold<moore::IntToLogicOp>(loc, value);
3157 }
3158
3159 // Convert the value from a simple bit vector back to the packed type.
3160 value = materializeSBVToPackedConversion(*this, dstPacked, value, loc,
3161 fallible);
3162 if (!value)
3163 return {};
3164
3165 assert(value.getType() == type);
3166 return value;
3167 }
3168
3169 // Convert from FormatStringType to StringType
3170 if (isa<moore::StringType>(type) &&
3171 isa<moore::FormatStringType>(value.getType())) {
3172 return builder.createOrFold<moore::FormatStringToStringOp>(loc, value);
3173 }
3174
3175 // Convert from StringType to FormatStringType
3176 if (isa<moore::FormatStringType>(type) &&
3177 isa<moore::StringType>(value.getType())) {
3178 return builder.createOrFold<moore::FormatStringOp>(loc, value);
3179 }
3180
3181 // If converting between two queue types of the same element type, then we
3182 // just need to convert the queue bounds.
3183 if (isa<moore::QueueType>(type) && isa<moore::QueueType>(value.getType()) &&
3184 cast<moore::QueueType>(type).getElementType() ==
3185 cast<moore::QueueType>(value.getType()).getElementType())
3186 return builder.createOrFold<moore::QueueResizeOp>(loc, type, value);
3187
3188 // Convert from UnpackedArrayType to QueueType
3189 if (isa<moore::QueueType>(type) &&
3190 isa<moore::UnpackedArrayType>(value.getType())) {
3191 auto queueElType = dyn_cast<moore::QueueType>(type).getElementType();
3192 auto unpackedArrayElType =
3193 dyn_cast<moore::UnpackedArrayType>(value.getType()).getElementType();
3194
3195 if (queueElType == unpackedArrayElType) {
3196 return builder.createOrFold<moore::QueueFromUnpackedArrayOp>(loc, type,
3197 value);
3198 }
3199 }
3200
3201 // Handle Real To Int conversion
3202 if (dstInt && isa<moore::RealType>(value.getType())) {
3203 auto twoValInt = builder.createOrFold<moore::RealToIntOp>(
3204 loc, dstInt.getTwoValued(), value);
3205 return materializeConversion(type, twoValInt, true, loc, fallible);
3206 }
3207
3208 // Handle Int to Real conversion
3209 if (isa<moore::RealType>(type) && isa<moore::IntType>(value.getType())) {
3210 Value twoValInt;
3211 // Check if int needs to be converted to two-valued first
3212 if (dyn_cast<moore::IntType>(value.getType()).getDomain() ==
3214 twoValInt = value;
3215 else
3216 twoValInt = materializeConversion(
3217 dyn_cast<moore::IntType>(value.getType()).getTwoValued(), value, true,
3218 loc);
3219
3220 if (isSigned)
3221 return builder.createOrFold<moore::SIntToRealOp>(loc, type, twoValInt);
3222 return builder.createOrFold<moore::UIntToRealOp>(loc, type, twoValInt);
3223 }
3224
3225 auto getBuiltinFloatType = [&](moore::RealType type) -> Type {
3226 if (type.getWidth() == moore::RealWidth::f32)
3227 return mlir::Float32Type::get(builder.getContext());
3228
3229 return mlir::Float64Type::get(builder.getContext());
3230 };
3231
3232 // Handle f64/f32 to time conversion
3233 if (isa<moore::TimeType>(type) && isa<moore::RealType>(value.getType())) {
3234 auto intType =
3235 moore::IntType::get(builder.getContext(), 64, Domain::TwoValued);
3236 Type floatType =
3237 getBuiltinFloatType(cast<moore::RealType>(value.getType()));
3238 auto scale = moore::ConstantRealOp::create(
3239 builder, loc, value.getType(),
3240 FloatAttr::get(floatType, getTimeScaleInFemtoseconds(*this)));
3241 auto scaled = builder.createOrFold<moore::MulRealOp>(loc, value, scale);
3242 auto asInt = moore::RealToIntOp::create(builder, loc, intType, scaled);
3243 auto asLogic = moore::IntToLogicOp::create(builder, loc, asInt);
3244 return moore::LogicToTimeOp::create(builder, loc, asLogic);
3245 }
3246
3247 // Handle time to f64/f32 conversion
3248 if (isa<moore::RealType>(type) && isa<moore::TimeType>(value.getType())) {
3249 auto asLogic = moore::TimeToLogicOp::create(builder, loc, value);
3250 auto asInt = moore::LogicToIntOp::create(builder, loc, asLogic);
3251 auto asReal = moore::UIntToRealOp::create(builder, loc, type, asInt);
3252 Type floatType = getBuiltinFloatType(cast<moore::RealType>(type));
3253 auto scale = moore::ConstantRealOp::create(
3254 builder, loc, type,
3255 FloatAttr::get(floatType, getTimeScaleInFemtoseconds(*this)));
3256 return moore::DivRealOp::create(builder, loc, asReal, scale);
3257 }
3258
3259 // Handle Int to String
3260 if (isa<moore::StringType>(type)) {
3261 if (auto intType = dyn_cast<moore::IntType>(value.getType())) {
3262 if (intType.getDomain() == moore::Domain::FourValued)
3263 value = moore::LogicToIntOp::create(builder, loc, value);
3264 return moore::IntToStringOp::create(builder, loc, value);
3265 }
3266 }
3267
3268 // Handle String to Int
3269 if (auto intType = dyn_cast<moore::IntType>(type)) {
3270 if (isa<moore::StringType>(value.getType())) {
3271 value = moore::StringToIntOp::create(builder, loc, intType.getTwoValued(),
3272 value);
3273
3274 if (intType.getDomain() == moore::Domain::FourValued)
3275 return moore::IntToLogicOp::create(builder, loc, value);
3276
3277 return value;
3278 }
3279 }
3280
3281 // Handle Int to FormatString
3282 if (isa<moore::FormatStringType>(type)) {
3283 auto asStr = materializeConversion(moore::StringType::get(getContext()),
3284 value, isSigned, loc);
3285 if (!asStr)
3286 return {};
3287 return moore::FormatStringOp::create(builder, loc, asStr, {}, {}, {});
3288 }
3289
3290 if (isa<moore::RealType>(type) && isa<moore::RealType>(value.getType()))
3291 return builder.createOrFold<moore::ConvertRealOp>(loc, type, value);
3292
3293 if (isa<moore::ClassHandleType>(type) &&
3294 isa<moore::ClassHandleType>(value.getType()))
3295 return maybeUpcastHandle(*this, value, cast<moore::ClassHandleType>(type));
3296
3297 // TODO: Handle other conversions with dedicated ops.
3298 if (fallible && value.getType() != type)
3299 return {};
3300 if (value.getType() != type)
3301 value = moore::ConversionOp::create(builder, loc, type, value);
3302 return value;
3303}
3304
3305/// Helper function to convert real math builtin functions that take exactly
3306/// one argument.
3307template <typename OpTy>
3308static Value
3309convertRealMathBI(Context &context, Location loc, StringRef name,
3310 std::span<const slang::ast::Expression *const> args) {
3311 // Slang already checks the arity of real math builtins.
3312 assert(args.size() == 1 && "real math builtin expects 1 argument");
3313 auto value = context.convertRvalueExpression(*args[0]);
3314 if (!value)
3315 return {};
3316 return OpTy::create(context.builder, loc, value);
3317}
3318
3319/// Helper function to convert real math builtin functions that take exactly
3320/// two arguments.
3321template <typename OpTy>
3322static Value
3323convertRealMathTwoBI(Context &context, Location loc, StringRef name,
3324 std::span<const slang::ast::Expression *const> args) {
3325 // Slang already checks the arity of real math builtins.
3326 assert(args.size() == 2 && "real math builtin expects 2 arguments");
3327 auto realType =
3328 moore::RealType::get(context.getContext(), moore::RealWidth::f64);
3329 auto lhs = context.convertRvalueExpression(*args[0], realType);
3330 auto rhs = context.convertRvalueExpression(*args[1], realType);
3331 if (!lhs || !rhs)
3332 return {};
3333 return OpTy::create(context.builder, loc, lhs, rhs);
3334}
3335
3336static LogicalResult
3337emitScanAssignments(Context &context, const Context::ScanStringResult &result,
3338 Location loc) {
3339 auto &builder = context.builder;
3340 auto newBlockAfter = [&](Block *after) -> Block * {
3341 auto block = std::make_unique<Block>();
3342 block->insertAfter(after);
3343 return block.release();
3344 };
3345
3346 for (auto [destExpr, value, matched] : result.assignments) {
3347 auto lhs = context.convertLvalueExpression(*destExpr);
3348 if (!lhs)
3349 return failure();
3350 auto cond = moore::ToBuiltinIntOp::create(builder, loc, matched);
3351
3352 auto *assignBlock = newBlockAfter(builder.getInsertionBlock());
3353 auto *continuedBlock = newBlockAfter(assignBlock);
3354 mlir::cf::CondBranchOp::create(builder, loc, cond, assignBlock,
3355 continuedBlock);
3356
3357 builder.setInsertionPointToEnd(assignBlock);
3358 moore::BlockingAssignOp::create(builder, loc, lhs, value);
3359 mlir::cf::BranchOp::create(builder, loc, continuedBlock);
3360
3361 builder.setInsertionPointToEnd(continuedBlock);
3362 }
3363 return success();
3364}
3365
3367 const slang::ast::SystemSubroutine &subroutine, Location loc,
3368 std::span<const slang::ast::Expression *const> args) {
3369 using ksn = slang::parsing::KnownSystemName;
3370 StringRef name = subroutine.name;
3371 auto nameId = subroutine.knownNameId;
3372 size_t numArgs = args.size();
3373
3374 //===--------------------------------------------------------------------===//
3375 // Random Number System Functions
3376 //===--------------------------------------------------------------------===//
3377
3378 // $urandom, $random, and $urandom_range all map to a single
3379 // moore.builtin.urandom_range primitive with (minval, maxval, seed).
3380 if (nameId == ksn::URandom || nameId == ksn::Random) {
3381 auto i32Ty = moore::IntType::getInt(builder.getContext(), 32);
3382 auto minval = moore::ConstantOp::create(builder, loc, i32Ty, 0);
3383 auto maxval =
3384 moore::ConstantOp::create(builder, loc, i32Ty, APInt::getAllOnes(32));
3385 Value seed;
3386 if (numArgs == 1) {
3387 seed = convertLvalueExpression(*args[0]);
3388 if (!seed)
3389 return {};
3390 }
3391 return moore::UrandomRangeBIOp::create(builder, loc, minval, maxval, seed);
3392 }
3393
3394 if (nameId == ksn::URandomRange) {
3395 auto i32Ty = moore::IntType::getInt(builder.getContext(), 32);
3396 auto maxval = convertRvalueExpression(*args[0]);
3397 if (!maxval)
3398 return {};
3399 Value minval;
3400 if (numArgs >= 2) {
3401 minval = convertRvalueExpression(*args[1]);
3402 if (!minval)
3403 return {};
3404 } else {
3405 minval = moore::ConstantOp::create(builder, loc, i32Ty, 0);
3406 }
3407 return moore::UrandomRangeBIOp::create(builder, loc, minval, maxval,
3408 Value{});
3409 }
3410
3411 //===--------------------------------------------------------------------===//
3412 // Time System Functions
3413 //===--------------------------------------------------------------------===//
3414
3415 if (nameId == ksn::Time || nameId == ksn::STime || nameId == ksn::RealTime) {
3416 // Slang already checks the arity of time functions.
3417 assert(numArgs == 0 && "time functions take no arguments");
3418 return moore::TimeBIOp::create(builder, loc);
3419 }
3420
3421 //===--------------------------------------------------------------------===//
3422 // Math System Functions
3423 //===--------------------------------------------------------------------===//
3424
3425 if (nameId == ksn::Clog2) {
3426 // Slang already checks the arity of `$clog2`.
3427 assert(numArgs == 1 && "`$clog2` takes 1 argument");
3428 auto value = convertRvalueExpression(*args[0]);
3429 if (!value)
3430 return {};
3431 value = convertToSimpleBitVector(value);
3432 if (!value)
3433 return {};
3434 return moore::Clog2BIOp::create(builder, loc, value);
3435 }
3436
3437 //===--------------------------------------------------------------------===//
3438 // Bit Vector System Functions
3439 //===--------------------------------------------------------------------===//
3440
3441 if (nameId == ksn::IsUnknown) {
3442 assert(numArgs == 1 && "`$isunknown` takes 1 argument");
3443 auto value = convertRvalueExpression(*args[0]);
3444 if (!value)
3445 return {};
3446
3447 if (!isa<moore::IntType>(value.getType())) {
3448 if (!isa<moore::PackedType>(value.getType())) {
3449 mlir::emitError(loc) << "expected integer argument for `$isunknown`";
3450 return {};
3451 }
3452 value = materializePackedToSBVConversion(value, loc,
3453 /*fallible=*/false);
3454 if (!value)
3455 return {};
3456 }
3457 auto valTy = dyn_cast<moore::IntType>(value.getType());
3458 return getIsUnknown(builder, loc, value, valTy, getContext());
3459 }
3460
3461 if (nameId == ksn::OneHot0 || nameId == ksn::OneHot) {
3462 assert(numArgs == 1 && "`$onehot`/`$onehot0` takes 1 argument");
3463 auto value = convertRvalueExpression(*args[0]);
3464 if (!value)
3465 return {};
3466 if (!isa<moore::IntType>(value.getType())) {
3467 if (!isa<moore::PackedType>(value.getType())) {
3468 mlir::emitError(loc)
3469 << "expected integer argument for `$onehot`/`$onehot0`";
3470 return {};
3471 }
3472 value = materializePackedToSBVConversion(value, loc,
3473 /*fallible=*/false);
3474 if (!value)
3475 return {};
3476 }
3477 auto valTy = dyn_cast<moore::IntType>(value.getType());
3478 if (!valTy) {
3479 mlir::emitError(loc) << "expected integer argument for `"
3480 << subroutine.name << "`";
3481 return {};
3482 }
3483
3484 // In SystemVerilog, $onehot/$onehot0 return 1'b0 if the expression
3485 // contains any unknown (x/z) bits. Detect and squash if four-valued.
3486 Value isUnknown;
3487 if (valTy.getDomain() == Domain::FourValued) {
3488 Value isUnknownMoore =
3489 getIsUnknown(builder, loc, value, valTy, getContext());
3490 isUnknown =
3491 builder.createOrFold<moore::ToBuiltinIntOp>(loc, isUnknownMoore);
3492 }
3493
3494 // Coerce four-valued input to two-valued for the comb ops.
3495 Value intVal = coerceToBuiltinInt(builder, loc, value, valTy);
3496
3497 // Compute onehot0: (value & (value - 1)) == 0
3498 auto one = hw::ConstantOp::create(builder, loc, intVal.getType(), 1);
3499 auto minusOne = comb::SubOp::create(builder, loc, intVal, one);
3500 auto anded = comb::AndOp::create(builder, loc, intVal, minusOne);
3501 auto zero = hw::ConstantOp::create(builder, loc, intVal.getType(), 0);
3502 Value result = comb::ICmpOp::create(builder, loc, comb::ICmpPredicate::eq,
3503 anded, zero, false);
3504
3505 // For $onehot, additionally require value != 0.
3506 if (nameId == ksn::OneHot) {
3507 auto isNotZero = comb::ICmpOp::create(
3508 builder, loc, comb::ICmpPredicate::ne, intVal, zero, false);
3509 result = comb::AndOp::create(builder, loc, result, isNotZero);
3510 }
3511
3512 // If four-valued, squash to 0 when unknown bits exist.
3513 if (isUnknown) {
3514 Value zeroI1 =
3515 hw::ConstantOp::create(builder, loc, builder.getI1Type(), 0);
3516 result = comb::MuxOp::create(builder, loc, isUnknown, zeroI1, result);
3517 Value resultMoore = moore::FromBuiltinIntOp::create(builder, loc, result);
3518 return moore::IntToLogicOp::create(builder, loc, resultMoore).getResult();
3519 }
3520 return moore::FromBuiltinIntOp::create(builder, loc, result);
3521 }
3522
3523 if (nameId == ksn::CountOnes) {
3524 assert(numArgs == 1 && "`$countones` takes 1 argument");
3525 auto value = convertRvalueExpression(*args[0]);
3526 if (!value)
3527 return {};
3528 if (!isa<moore::IntType>(value.getType())) {
3529 if (!isa<moore::PackedType>(value.getType())) {
3530 mlir::emitError(loc) << "expected integer argument for `$countones`";
3531 return {};
3532 }
3533 value = materializePackedToSBVConversion(value, loc,
3534 /*fallible=*/false);
3535 if (!value)
3536 return {};
3537 }
3538 auto valTy = dyn_cast<moore::IntType>(value.getType());
3539 if (!valTy) {
3540 mlir::emitError(loc) << "expected integer argument for `$countones`";
3541 return {};
3542 }
3543
3544 // Coerce four-valued input to two-valued for the comb ops.
3545 Value intVal = coerceToBuiltinInt(builder, loc, value, valTy);
3546
3547 // Popcount: extract each bit, zero-extend to result width, and sum.
3548 auto builtinIntTy = cast<IntegerType>(intVal.getType());
3549 unsigned width = builtinIntTy.getWidth();
3550 unsigned resultWidth = llvm::Log2_32_Ceil(width + 1);
3551 auto i1Ty = builder.getI1Type();
3552 unsigned padWidth = resultWidth - 1;
3553 auto zeros = hw::ConstantOp::create(builder, loc,
3554 builder.getIntegerType(padWidth), 0);
3555
3556 // Zero-extend the first bit to seed the accumulator.
3557 auto bit0 = comb::ExtractOp::create(builder, loc, i1Ty, intVal, 0);
3558 Value sum = comb::ConcatOp::create(builder, loc, ValueRange{zeros, bit0});
3559
3560 for (unsigned i = 1; i < width; ++i) {
3561 auto bit = comb::ExtractOp::create(builder, loc, i1Ty, intVal, i);
3562 auto extended =
3563 comb::ConcatOp::create(builder, loc, ValueRange{zeros, bit});
3564 sum = comb::AddOp::create(builder, loc, sum, extended);
3565 }
3566
3567 // Wrap back into Moore type (unsigned — CountOnes result is never signed).
3568 return moore::FromBuiltinIntOp::create(builder, loc, sum);
3569 }
3570
3571 // Real math functions (all take 1 real argument)
3572 if (nameId == ksn::Ln)
3573 return convertRealMathBI<moore::LnBIOp>(*this, loc, name, args);
3574 if (nameId == ksn::Log10)
3575 return convertRealMathBI<moore::Log10BIOp>(*this, loc, name, args);
3576 if (nameId == ksn::Exp)
3577 return convertRealMathBI<moore::ExpBIOp>(*this, loc, name, args);
3578 if (nameId == ksn::Sqrt)
3579 return convertRealMathBI<moore::SqrtBIOp>(*this, loc, name, args);
3580 if (nameId == ksn::Floor)
3581 return convertRealMathBI<moore::FloorBIOp>(*this, loc, name, args);
3582 if (nameId == ksn::Ceil)
3583 return convertRealMathBI<moore::CeilBIOp>(*this, loc, name, args);
3584 if (nameId == ksn::Sin)
3585 return convertRealMathBI<moore::SinBIOp>(*this, loc, name, args);
3586 if (nameId == ksn::Cos)
3587 return convertRealMathBI<moore::CosBIOp>(*this, loc, name, args);
3588 if (nameId == ksn::Tan)
3589 return convertRealMathBI<moore::TanBIOp>(*this, loc, name, args);
3590 if (nameId == ksn::Asin)
3591 return convertRealMathBI<moore::AsinBIOp>(*this, loc, name, args);
3592 if (nameId == ksn::Acos)
3593 return convertRealMathBI<moore::AcosBIOp>(*this, loc, name, args);
3594 if (nameId == ksn::Atan)
3595 return convertRealMathBI<moore::AtanBIOp>(*this, loc, name, args);
3596 if (nameId == ksn::Sinh)
3597 return convertRealMathBI<moore::SinhBIOp>(*this, loc, name, args);
3598 if (nameId == ksn::Cosh)
3599 return convertRealMathBI<moore::CoshBIOp>(*this, loc, name, args);
3600 if (nameId == ksn::Tanh)
3601 return convertRealMathBI<moore::TanhBIOp>(*this, loc, name, args);
3602 if (nameId == ksn::Asinh)
3603 return convertRealMathBI<moore::AsinhBIOp>(*this, loc, name, args);
3604 if (nameId == ksn::Acosh)
3605 return convertRealMathBI<moore::AcoshBIOp>(*this, loc, name, args);
3606 if (nameId == ksn::Atanh)
3607 return convertRealMathBI<moore::AtanhBIOp>(*this, loc, name, args);
3608 // Real math functions (all take 2 real arguments)
3609 if (nameId == ksn::Pow)
3610 return convertRealMathTwoBI<moore::PowRealOp>(*this, loc, name, args);
3611 if (nameId == ksn::Atan2)
3612 return convertRealMathTwoBI<moore::Atan2BIOp>(*this, loc, name, args);
3613 if (nameId == ksn::Hypot)
3614 return convertRealMathTwoBI<moore::HypotBIOp>(*this, loc, name, args);
3615
3616 //===--------------------------------------------------------------------===//
3617 // Type Conversion System Functions
3618 //===--------------------------------------------------------------------===//
3619
3620 if (nameId == ksn::Itor) {
3621 assert(numArgs == 1 && "`$itor` takes 1 argument");
3622 auto realType = moore::RealType::get(getContext(), moore::RealWidth::f64);
3623 return convertRvalueExpression(*args[0], realType);
3624 }
3625
3626 if (nameId == ksn::Rtoi) {
3627 assert(numArgs == 1 && "`$rtoi` takes 1 argument");
3628 auto intType = moore::IntType::get(getContext(), 32, Domain::TwoValued);
3629 return convertRvalueExpression(*args[0], intType);
3630 }
3631
3632 if (nameId == ksn::Signed || nameId == ksn::Unsigned) {
3633 // Slang already checks the arity of `$signed`/`$unsigned`.
3634 assert(numArgs == 1 && "`$signed`/`$unsigned` take 1 argument");
3635 // These are just passthroughs in the IR; signedness is carried on the Slang
3636 // AST type which we use to convert the IR.
3637 return convertRvalueExpression(*args[0]);
3638 }
3639
3640 if (nameId == ksn::RealToBits)
3641 return convertRealMathBI<moore::RealtobitsBIOp>(*this, loc, name, args);
3642 if (nameId == ksn::BitsToReal)
3643 return convertRealMathBI<moore::BitstorealBIOp>(*this, loc, name, args);
3644 if (nameId == ksn::ShortrealToBits)
3645 return convertRealMathBI<moore::ShortrealtobitsBIOp>(*this, loc, name,
3646 args);
3647 if (nameId == ksn::BitsToShortreal)
3648 return convertRealMathBI<moore::BitstoshortrealBIOp>(*this, loc, name,
3649 args);
3650
3651 if (nameId == ksn::Cast) {
3652 assert(numArgs == 2 && "`cast` takes 2 arguments");
3653 auto *dstExpr = args[0];
3654 auto dstType = convertType(*dstExpr->type);
3655 if (!dstType)
3656 return {};
3657
3658 if (auto *assign = dstExpr->as_if<slang::ast::AssignmentExpression>())
3659 dstExpr = &assign->left();
3660 auto dst = convertLvalueExpression(*dstExpr);
3661 if (!dst)
3662 return {};
3663
3664 auto src = convertRvalueExpression(*args[1]);
3665 if (!src)
3666 return {};
3667 // Class-typed $cast (upcast/downcast) is intentionally left for follow-up.
3668 if (isa<moore::ClassHandleType>(dstType) ||
3669 isa<moore::ClassHandleType>(src.getType())) {
3670 auto i1Ty = moore::IntType::getInt(builder.getContext(), 1);
3671 return moore::ConstantOp::create(builder, loc, i1Ty, 0,
3672 /*isSigned=*/false);
3673 }
3674 auto converted = materializeConversion(
3675 dstType, src, args[1]->type->isSigned(), loc, /*fallible=*/true);
3676 auto i1Ty = moore::IntType::getInt(builder.getContext(), 1);
3677 if (!converted)
3678 return moore::ConstantOp::create(builder, loc, i1Ty, 0,
3679 /*isSigned=*/false);
3680 moore::BlockingAssignOp::create(builder, loc, dst, converted);
3681 return moore::ConstantOp::create(builder, loc, i1Ty, 1,
3682 /*isSigned=*/false);
3683 }
3684
3685 //===--------------------------------------------------------------------===//
3686 // String Methods
3687 //===--------------------------------------------------------------------===//
3688
3689 if (nameId == ksn::Len) {
3690 // Slang already checks the arity of string methods.
3691 assert(numArgs == 1 && "`len` takes 1 argument");
3692 auto stringType = moore::StringType::get(getContext());
3693 auto value = convertRvalueExpression(*args[0], stringType);
3694 if (!value)
3695 return {};
3696 return moore::StringLenOp::create(builder, loc, value);
3697 }
3698
3699 if (nameId == ksn::Getc) {
3700 // Slang already checks the arity of string methods.
3701 assert(numArgs == 2 && "`getc` takes 2 arguments");
3702 auto stringType = moore::StringType::get(getContext());
3703 auto str = convertRvalueExpression(*args[0], stringType);
3704 auto index = convertRvalueExpression(*args[1]);
3705 if (!str || !index)
3706 return {};
3707 return moore::StringGetOp::create(builder, loc, str, index);
3708 }
3709
3710 if (nameId == ksn::ToUpper) {
3711 // Slang already checks the arity of string methods.
3712 assert(numArgs == 1 && "`toupper` takes 1 argument");
3713 auto stringType = moore::StringType::get(getContext());
3714 auto value = convertRvalueExpression(*args[0], stringType);
3715 if (!value)
3716 return {};
3717 return moore::StringToUpperOp::create(builder, loc, value);
3718 }
3719
3720 if (nameId == ksn::ToLower) {
3721 // Slang already checks the arity of string methods.
3722 assert(numArgs == 1 && "`tolower` takes 1 argument");
3723 auto stringType = moore::StringType::get(getContext());
3724 auto value = convertRvalueExpression(*args[0], stringType);
3725 if (!value)
3726 return {};
3727 return moore::StringToLowerOp::create(builder, loc, value);
3728 }
3729
3730 if (nameId == ksn::Compare || nameId == ksn::ICompare) {
3731 // Slang already checks the arity of string methods.
3732 assert(numArgs == 2);
3733 auto stringType = moore::StringType::get(getContext());
3734 auto lhs = convertRvalueExpression(*args[0], stringType);
3735 auto rhs = convertRvalueExpression(*args[1], stringType);
3736 if (!lhs || !rhs)
3737 return {};
3738 if (nameId == ksn::Compare)
3739 return moore::StringCompareOp::create(builder, loc, lhs, rhs);
3740 return moore::StringICompareOp::create(builder, loc, lhs, rhs);
3741 }
3742
3743 if (nameId == ksn::Substr) {
3744 // Slang already checks the arity of string methods.
3745 assert(numArgs == 3 && "`substr` takes 3 arguments");
3746 auto stringType = moore::StringType::get(getContext());
3747 auto str = convertRvalueExpression(*args[0], stringType);
3748 auto start = convertRvalueExpression(*args[1]);
3749 auto end = convertRvalueExpression(*args[2]);
3750 if (!str || !start || !end)
3751 return {};
3752 return moore::StringSubstrOp::create(builder, loc, str, start, end);
3753 }
3754
3755 if (nameId == ksn::AToI || nameId == ksn::AToHex || nameId == ksn::AToOct ||
3756 nameId == ksn::AToBin) {
3757 // Slang already checks the arity of string methods.
3758 assert(numArgs == 1 && "`atoi/hex/oct/bin` takes 1 argument");
3759 auto stringType = moore::StringType::get(getContext());
3760 auto str = convertRvalueExpression(*args[0], stringType);
3761 if (!str)
3762 return {};
3763 auto integerType = moore::IntType::getLogic(builder.getContext(), 32);
3764 switch (nameId) {
3765 case ksn::AToI:
3766 return moore::StringAtoiOp::create(builder, loc, integerType, str);
3767 case ksn::AToHex:
3768 return moore::StringAtohexOp::create(builder, loc, integerType, str);
3769 case ksn::AToOct:
3770 return moore::StringAtooctOp::create(builder, loc, integerType, str);
3771 case ksn::AToBin:
3772 return moore::StringAtobinOp::create(builder, loc, integerType, str);
3773 default:
3774 llvm_unreachable("unexpected string to integer conversion");
3775 }
3776 }
3777
3778 if (nameId == ksn::AToReal) {
3779 // Slang already checks the arity of string methods.
3780 assert(numArgs == 1 && "`atoreal` takes 1 argument");
3781 auto stringType = moore::StringType::get(getContext());
3782 auto str = convertRvalueExpression(*args[0], stringType);
3783 if (!str)
3784 return {};
3785 auto realType = moore::RealType::get(getContext(), moore::RealWidth::f64);
3786 return moore::StringAtorealOp::create(builder, loc, realType, str);
3787 }
3788
3789 //===--------------------------------------------------------------------===//
3790 // Queue Methods
3791 //===--------------------------------------------------------------------===//
3792
3793 if (nameId == ksn::ArraySize) {
3794 // Slang already checks the arity of `size`.
3795 assert(numArgs == 1 && "`size` takes 1 argument");
3796 if (args[0]->type->isQueue()) {
3797 auto value = convertRvalueExpression(*args[0]);
3798 if (!value)
3799 return {};
3800 return moore::QueueSizeBIOp::create(builder, loc, value);
3801 }
3802 if (args[0]->type->getCanonicalType().kind ==
3803 slang::ast::SymbolKind::DynamicArrayType) {
3804 auto value = convertRvalueExpression(*args[0]);
3805 if (!value)
3806 return {};
3807 return moore::OpenUArraySizeOp::create(builder, loc, value);
3808 }
3809 if (args[0]->type->isAssociativeArray()) {
3810 auto value = convertLvalueExpression(*args[0]);
3811 if (!value)
3812 return {};
3813 return moore::AssocArraySizeOp::create(builder, loc, value);
3814 }
3815 emitError(loc) << "unsupported member function `size` on type `"
3816 << args[0]->type->toString() << "`";
3817 return {};
3818 }
3819
3820 if (nameId == ksn::Delete) {
3821 // Slang already checks the arity of `delete`.
3822 assert(numArgs == 1 && "`delete` takes 1 argument");
3823 if (args[0]->type->getCanonicalType().kind ==
3824 slang::ast::SymbolKind::DynamicArrayType) {
3825 auto value = convertRvalueExpression(*args[0]);
3826 if (!value)
3827 return {};
3828 return moore::OpenUArrayDeleteOp::create(builder, loc, value);
3829 }
3830 emitError(loc) << "unsupported member function `delete` on type `"
3831 << args[0]->type->toString() << "`";
3832 return {};
3833 }
3834
3835 if (nameId == ksn::PopBack) {
3836 // Slang already checks the arity and applicability of `pop_back`.
3837 assert(numArgs == 1 && "`pop_back` takes 1 argument");
3838 assert(args[0]->type->isQueue() && "`pop_back` is only valid on queues");
3839 auto value = convertLvalueExpression(*args[0]);
3840 if (!value)
3841 return {};
3842 return moore::QueuePopBackOp::create(builder, loc, value);
3843 }
3844
3845 if (nameId == ksn::PopFront) {
3846 // Slang already checks the arity and applicability of `pop_front`.
3847 assert(numArgs == 1 && "`pop_front` takes 1 argument");
3848 assert(args[0]->type->isQueue() && "`pop_front` is only valid on queues");
3849 auto value = convertLvalueExpression(*args[0]);
3850 if (!value)
3851 return {};
3852 return moore::QueuePopFrontOp::create(builder, loc, value);
3853 }
3854
3855 //===--------------------------------------------------------------------===//
3856 // Associative Array Methods
3857 //===--------------------------------------------------------------------===//
3858
3859 if (nameId == ksn::Num) {
3860 if (args[0]->type->isAssociativeArray()) {
3861 assert(numArgs == 1 && "`num` takes 1 argument");
3862 auto value = convertLvalueExpression(*args[0]);
3863 if (!value)
3864 return {};
3865 return moore::AssocArraySizeOp::create(builder, loc, value);
3866 }
3867 emitError(loc) << "unsupported system call `" << name << "`";
3868 return {};
3869 }
3870
3871 if (nameId == ksn::Exists) {
3872 // Slang already checks the arity and applicability of `exists`.
3873 assert(numArgs == 2 && "`exists` takes 2 arguments");
3874 assert(args[0]->type->isAssociativeArray() &&
3875 "`exists` is only valid on associative arrays");
3876 auto array = convertLvalueExpression(*args[0]);
3877 auto key = convertRvalueExpression(*args[1]);
3878 if (!array || !key)
3879 return {};
3880 return moore::AssocArrayExistsOp::create(builder, loc, array, key);
3881 }
3882
3883 // Associative array traversal methods (all take 2 arguments: array ref, key
3884 // ref). These names are shared with enum built-in methods (next/prev/first/
3885 // last), which take 1 or 2 arguments. Only handle the associative array case
3886 // here; fall through to the unsupported diagnostic for other types.
3887 if (nameId == ksn::First || nameId == ksn::Last || nameId == ksn::Next ||
3888 nameId == ksn::Prev) {
3889 if (args[0]->type->isAssociativeArray()) {
3890 assert(numArgs == 2 && "traversal methods take 2 arguments");
3891 auto array = convertLvalueExpression(*args[0]);
3892 auto key = convertLvalueExpression(*args[1]);
3893 if (!array || !key)
3894 return {};
3895 if (nameId == ksn::First)
3896 return moore::AssocArrayFirstOp::create(builder, loc, array, key);
3897 if (nameId == ksn::Last)
3898 return moore::AssocArrayLastOp::create(builder, loc, array, key);
3899 if (nameId == ksn::Next)
3900 return moore::AssocArrayNextOp::create(builder, loc, array, key);
3901 if (nameId == ksn::Prev)
3902 return moore::AssocArrayPrevOp::create(builder, loc, array, key);
3903 llvm_unreachable("all traversal cases handled above");
3904 }
3905 emitError(loc) << "unsupported system call `" << name << "`";
3906 return {};
3907 }
3908
3909 //===--------------------------------------------------------------------===//
3910 // File I/O System Functions
3911 //===--------------------------------------------------------------------===//
3912
3913 if (nameId == ksn::FOpen) {
3914 assert(numArgs >= 1 && numArgs <= 2 && "`$fopen` takes 1 or 2 arguments");
3915 auto filename =
3916 convertRvalueExpression(*args[0], moore::StringType::get(getContext()));
3917 if (!filename)
3918 return {};
3919 moore::FOpenModeAttr modeAttr;
3920 if (numArgs == 2) {
3921 auto *strLit = args[1]
3922 ->unwrapImplicitConversions()
3923 .as_if<slang::ast::StringLiteral>();
3924 if (!strLit)
3925 return emitError(loc) << "$fopen mode must be a string literal",
3926 Value{};
3927
3928 auto mode =
3929 llvm::StringSwitch<std::optional<moore::FOpenMode>>(
3930 strLit->getValue())
3931 .Cases({"r", "rb"}, moore::FOpenMode::Read)
3932 .Cases({"w", "wb"}, moore::FOpenMode::Write)
3933 .Cases({"a", "ab"}, moore::FOpenMode::Append)
3934 .Cases({"r+", "r+b", "rb+"}, moore::FOpenMode::ReadUpdate)
3935 .Cases({"w+", "w+b", "wb+"}, moore::FOpenMode::WriteUpdate)
3936 .Cases({"a+", "a+b", "ab+"}, moore::FOpenMode::AppendUpdate)
3937 .Default(std::nullopt);
3938
3939 if (!mode)
3940 return emitError(loc)
3941 << "invalid $fopen mode '" << strLit->getValue() << "'",
3942 Value{};
3943 modeAttr = moore::FOpenModeAttr::get(getContext(), *mode);
3944 }
3945 return moore::FOpenBIOp::create(builder, loc, filename, modeAttr);
3946 }
3947
3948 //===--------------------------------------------------------------------===//
3949 // Command Line Input System Functions
3950 //===--------------------------------------------------------------------===//
3951
3952 if (nameId == ksn::TestPlusArgs) {
3953 // Slang already checks the arity of `$test$plusargs`.
3954 assert(numArgs == 1 && "`$test$plusargs` takes 1 argument");
3955 auto *strLit =
3956 args[0]->unwrapImplicitConversions().as_if<slang::ast::StringLiteral>();
3957 if (!strLit)
3958 return emitError(loc) << "`$test$plusargs` argument must be a string "
3959 "literal",
3960 Value{};
3961 auto foundTy = moore::IntType::getInt(getContext(), 1);
3962 return moore::PlusArgsTestBIOp::create(
3963 builder, loc, foundTy, builder.getStringAttr(strLit->getValue()));
3964 }
3965
3966 if (nameId == ksn::ValuePlusArgs) {
3967 // Slang already checks the arity of `$value$plusargs`. The parsed value is
3968 // written back into the second (lvalue) argument, and the function returns
3969 // whether a matching plusarg was found.
3970 assert(numArgs == 2 && "`$value$plusargs` takes 2 arguments");
3971 auto *strLit =
3972 args[0]->unwrapImplicitConversions().as_if<slang::ast::StringLiteral>();
3973 if (!strLit)
3974 return emitError(loc) << "`$value$plusargs` format must be a string "
3975 "literal",
3976 Value{};
3977 // Slang emits output arguments as a `<lvalue> = EmptyArgument` assignment;
3978 // unpack it to recover the lvalue that receives the parsed value.
3979 const auto *valueArg = args[1];
3980 if (const auto *assign =
3981 valueArg->as_if<slang::ast::AssignmentExpression>())
3982 valueArg = &assign->left();
3983 auto lvalue = convertLvalueExpression(*valueArg);
3984 if (!lvalue)
3985 return {};
3986 auto resultType = cast<moore::RefType>(lvalue.getType()).getNestedType();
3987 auto foundTy = moore::IntType::getInt(getContext(), 1);
3988 auto op = moore::PlusArgsValueBIOp::create(
3989 builder, loc, foundTy, resultType,
3990 builder.getStringAttr(strLit->getValue()));
3991 moore::BlockingAssignOp::create(builder, loc, lvalue, op.getResult());
3992 return op.getFound();
3993 }
3994
3995 if (nameId == ksn::FScanf) {
3996 auto fd = convertRvalueExpression(
3997 *args[0], moore::IntType::getInt(builder.getContext(), 32));
3998 if (!fd)
3999 return {};
4000 auto *fmtLit =
4001 args[1]->unwrapImplicitConversions().as_if<slang::ast::StringLiteral>();
4002 if (!fmtLit)
4003 return (mlir::emitError(loc)
4004 << "$fscanf requires a string literal format string"),
4005 Value{};
4006 auto cursor =
4007 moore::ScanBeginFScanFOp::create(builder, loc, fd).getCursor();
4008 auto result =
4009 convertScanString(fmtLit->getValue(), cursor, args.subspan(2), loc);
4010 if (failed(result))
4011 return {};
4012 if (failed(emitScanAssignments(*this, *result, loc)))
4013 return {};
4014 return moore::ScanEndOp::create(builder, loc, result->finalCursor)
4015 .getCount();
4016 }
4017
4018 if (nameId == ksn::SScanf) {
4019 auto str =
4020 convertRvalueExpression(*args[0], moore::StringType::get(getContext()));
4021 if (!str)
4022 return {};
4023 auto *fmtLit =
4024 args[1]->unwrapImplicitConversions().as_if<slang::ast::StringLiteral>();
4025 if (!fmtLit)
4026 return (mlir::emitError(loc)
4027 << "$sscanf requires a string literal format string"),
4028 Value{};
4029 auto cursor =
4030 moore::ScanBeginSScanFOp::create(builder, loc, str).getCursor();
4031 auto result =
4032 convertScanString(fmtLit->getValue(), cursor, args.subspan(2), loc);
4033 if (failed(result))
4034 return {};
4035 if (failed(emitScanAssignments(*this, *result, loc)))
4036 return {};
4037 return moore::ScanEndOp::create(builder, loc, result->finalCursor)
4038 .getCount();
4039 }
4040
4041 // Unrecognized system call
4042 emitError(loc) << "unsupported system call `" << name << "`";
4043 return {};
4044}
4045
4046// Resolve any (possibly nested) SymbolRefAttr to an op from the root.
4047static mlir::Operation *resolve(Context &context, mlir::SymbolRefAttr sym) {
4048 return context.symbolTable.lookupNearestSymbolFrom(context.intoModuleOp, sym);
4049}
4050
4051bool Context::isClassDerivedFrom(const moore::ClassHandleType &actualTy,
4052 const moore::ClassHandleType &baseTy) {
4053 if (!actualTy || !baseTy)
4054 return false;
4055
4056 mlir::SymbolRefAttr actualSym = actualTy.getClassSym();
4057 mlir::SymbolRefAttr baseSym = baseTy.getClassSym();
4058
4059 if (actualSym == baseSym)
4060 return true;
4061
4062 auto *op = resolve(*this, actualSym);
4063 auto decl = llvm::dyn_cast_or_null<moore::ClassDeclOp>(op);
4064 // Walk up the inheritance chain via ClassDeclOp::$base (SymbolRefAttr).
4065 while (decl) {
4066 mlir::SymbolRefAttr curBase = decl.getBaseAttr();
4067 if (!curBase)
4068 break;
4069 if (curBase == baseSym)
4070 return true;
4071 decl = llvm::dyn_cast_or_null<moore::ClassDeclOp>(resolve(*this, curBase));
4072 }
4073 return false;
4074}
4075
4076moore::ClassHandleType
4077Context::getAncestorClassWithProperty(const moore::ClassHandleType &actualTy,
4078 llvm::StringRef fieldName, Location loc) {
4079 // Start at the actual class symbol.
4080 mlir::SymbolRefAttr classSym = actualTy.getClassSym();
4081
4082 while (classSym) {
4083 // Resolve the class declaration from the root symbol table owner.
4084 auto *op = resolve(*this, classSym);
4085 auto decl = llvm::dyn_cast_or_null<moore::ClassDeclOp>(op);
4086 if (!decl)
4087 break;
4088
4089 // Scan the class body for a property with the requested symbol name.
4090 for (auto &block : decl.getBody()) {
4091 for (auto &opInBlock : block) {
4092 if (auto prop =
4093 llvm::dyn_cast<moore::ClassPropertyDeclOp>(&opInBlock)) {
4094 if (prop.getSymName() == fieldName) {
4095 // Found a declaring ancestor: return its handle type.
4096 return moore::ClassHandleType::get(actualTy.getContext(), classSym);
4097 }
4098 }
4099 }
4100 }
4101
4102 // Not found here—climb to the base class (if any) and continue.
4103 classSym = decl.getBaseAttr(); // may be null; loop ends if so
4104 }
4105
4106 // No ancestor declares that property.
4107 mlir::emitError(loc) << "unknown property `" << fieldName << "`";
4108 return {};
4109}
4110
4111//===--------------------------------------------------------------------===//
4112// Value Range Expression Methods
4113//===--------------------------------------------------------------------===//
4114
4115Value Context::convertInsideCheck(Value insideLhs, Location loc,
4116 const slang::ast::Expression &expr) {
4117 // The value range list on the right-hand side of the inside operator is a
4118 // comma-separated list of expressions or ranges.
4119 if (const auto *valueRange = expr.as_if<slang::ast::ValueRangeExpression>()) {
4120 auto lowBound =
4122 auto highBound =
4124 if (!insideLhs || !lowBound || !highBound)
4125 return {};
4126
4127 Value rangeLhs, rangeRhs;
4128 // Determine if the insideLhs on the left-hand side is inclusively
4129 // within the range.
4130 if (valueRange->left().type->isSigned() ||
4131 insideLhs.getType().isSignedInteger()) {
4132 rangeLhs = moore::SgeOp::create(builder, loc, insideLhs, lowBound);
4133 } else {
4134 rangeLhs = moore::UgeOp::create(builder, loc, insideLhs, lowBound);
4135 }
4136
4137 if (valueRange->right().type->isSigned() ||
4138 insideLhs.getType().isSignedInteger()) {
4139 rangeRhs = moore::SleOp::create(builder, loc, insideLhs, highBound);
4140 } else {
4141 rangeRhs = moore::UleOp::create(builder, loc, insideLhs, highBound);
4142 }
4143
4144 return moore::AndOp::create(builder, loc, rangeLhs, rangeRhs);
4145 }
4146
4147 // Handle expressions.
4148 if (!expr.type->isIntegral()) {
4149 if (expr.type->isUnpackedArray()) {
4150 mlir::emitError(loc,
4151 "unpacked arrays in 'inside' expressions not supported");
4152 return {};
4153 }
4154 mlir::emitError(
4155 loc, "only simple bit vectors supported in 'inside' expressions");
4156 return {};
4157 }
4158
4160 if (!value)
4161 return {};
4162 return moore::WildcardEqOp::create(builder, loc, insideLhs, value);
4163}
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.
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.
Type convertType(const slang::ast::Type &type, LocationAttr loc={})
Convert a slang type into an MLIR type.
Definition Types.cpp:224
Value materializeSVInt(const slang::SVInt &svint, const slang::ast::Type &type, Location loc)
Helper function to materialize an SVInt as an SSA value.
Value materializeSVReal(const slang::ConstantValue &svreal, const slang::ast::Type &type, Location loc)
Helper function to materialize a real value as an SSA value.
Value convertToBool(Value value)
Helper function to convert a value to its "truthy" boolean value.
moore::ClassHandleType getAncestorClassWithProperty(const moore::ClassHandleType &actualTy, StringRef fieldName, Location loc)
Tries to find the closest base class of actualTy that carries a property with name fieldName.
Value 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.
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.
FunctionLowering * currentFunctionLowering
The function currently being converted, if any.
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.