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