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