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
11#include "mlir/IR/Operation.h"
12#include "mlir/IR/Value.h"
13#include "slang/ast/EvalContext.h"
14#include "slang/ast/SystemSubroutine.h"
15#include "slang/ast/types/AllTypes.h"
16#include "slang/syntax/AllSyntax.h"
17#include "llvm/ADT/ScopeExit.h"
18
19using namespace circt;
20using namespace ImportVerilog;
21using moore::Domain;
22
23/// Convert a Slang `SVInt` to a CIRCT `FVInt`.
24static FVInt convertSVIntToFVInt(const slang::SVInt &svint) {
25 if (svint.hasUnknown()) {
26 unsigned numWords = svint.getNumWords() / 2;
27 auto value = ArrayRef<uint64_t>(svint.getRawPtr(), numWords);
28 auto unknown = ArrayRef<uint64_t>(svint.getRawPtr() + numWords, numWords);
29 return FVInt(APInt(svint.getBitWidth(), value),
30 APInt(svint.getBitWidth(), unknown));
31 }
32 auto value = ArrayRef<uint64_t>(svint.getRawPtr(), svint.getNumWords());
33 return FVInt(APInt(svint.getBitWidth(), value));
34}
35
36/// Map an index into an array, with bounds `range`, to a bit offset of the
37/// underlying bit storage. This is a dynamic version of
38/// `slang::ConstantRange::translateIndex`.
39static Value getSelectIndex(Context &context, Location loc, Value index,
40 const slang::ConstantRange &range) {
41 auto &builder = context.builder;
42 auto indexType = cast<moore::UnpackedType>(index.getType());
43
44 // Compute offset first so we know if it is negative.
45 auto lo = range.lower();
46 auto hi = range.upper();
47 auto offset = range.isLittleEndian() ? lo : hi;
48
49 // If any bound is negative we need a signed index type.
50 const bool needSigned = (lo < 0) || (hi < 0);
51
52 // Magnitude over full range, not just the chosen offset.
53 const uint64_t maxAbs = std::max<uint64_t>(std::abs(lo), std::abs(hi));
54
55 // Bits needed from the range:
56 // - unsigned: ceil(log2(maxAbs + 1)) (ensure at least 1)
57 // - signed: ceil(log2(maxAbs)) + 1 sign bit (ensure at least 2 when neg)
58 unsigned want = needSigned
59 ? (llvm::Log2_64_Ceil(std::max<uint64_t>(1, maxAbs)) + 1)
60 : std::max<unsigned>(1, llvm::Log2_64_Ceil(maxAbs + 1));
61
62 // Keep at least as wide as the incoming index.
63 const unsigned bw = std::max<unsigned>(want, indexType.getBitSize().value());
64
65 auto intType =
66 moore::IntType::get(index.getContext(), bw, indexType.getDomain());
67 index = context.materializeConversion(intType, index, needSigned, loc);
68
69 if (offset == 0) {
70 if (range.isLittleEndian())
71 return index;
72 else
73 return moore::NegOp::create(builder, loc, index);
74 }
75
76 auto offsetConst =
77 moore::ConstantOp::create(builder, loc, intType, offset, needSigned);
78 if (range.isLittleEndian())
79 return moore::SubOp::create(builder, loc, index, offsetConst);
80 else
81 return moore::SubOp::create(builder, loc, offsetConst, index);
82}
83
84/// Get the currently active timescale as an integer number of femtoseconds.
86 static_assert(int(slang::TimeUnit::Seconds) == 0);
87 static_assert(int(slang::TimeUnit::Milliseconds) == 1);
88 static_assert(int(slang::TimeUnit::Microseconds) == 2);
89 static_assert(int(slang::TimeUnit::Nanoseconds) == 3);
90 static_assert(int(slang::TimeUnit::Picoseconds) == 4);
91 static_assert(int(slang::TimeUnit::Femtoseconds) == 5);
92
93 static_assert(int(slang::TimeScaleMagnitude::One) == 1);
94 static_assert(int(slang::TimeScaleMagnitude::Ten) == 10);
95 static_assert(int(slang::TimeScaleMagnitude::Hundred) == 100);
96
97 auto exp = static_cast<unsigned>(context.timeScale.base.unit);
98 assert(exp <= 5);
99 exp = 5 - exp;
100 auto scale = static_cast<uint64_t>(context.timeScale.base.magnitude);
101 while (exp-- > 0)
102 scale *= 1000;
103 return scale;
104}
105
107 const slang::ast::ClassPropertySymbol &expr) {
108 auto loc = context.convertLocation(expr.location);
109 auto builder = context.builder;
110 auto type = context.convertType(expr.getType());
111 auto fieldTy = cast<moore::UnpackedType>(type);
112 auto fieldRefTy = moore::RefType::get(fieldTy);
113
114 if (expr.lifetime == slang::ast::VariableLifetime::Static) {
115
116 // Variable may or may not have been hoisted already. Hoist if not.
117 if (!context.globalVariables.lookup(&expr)) {
118 if (failed(context.convertGlobalVariable(expr))) {
119 return {};
120 }
121 }
122 // Try the static variable after it has been hoisted.
123 if (auto globalOp = context.globalVariables.lookup(&expr))
124 return moore::GetGlobalVariableOp::create(builder, loc, globalOp);
125
126 mlir::emitError(loc) << "Failed to access static member variable "
127 << expr.name << " as a global variable";
128 return {};
129 }
130
131 // Get the scope's implicit this variable
132 mlir::Value instRef = context.getImplicitThisRef();
133 if (!instRef) {
134 mlir::emitError(loc) << "class property '" << expr.name
135 << "' referenced without an implicit 'this'";
136 return {};
137 }
138
139 auto fieldSym = mlir::FlatSymbolRefAttr::get(builder.getContext(), expr.name);
140
141 moore::ClassHandleType classTy =
142 cast<moore::ClassHandleType>(instRef.getType());
143
144 auto targetClassHandle =
145 context.getAncestorClassWithProperty(classTy, expr.name, loc);
146 if (!targetClassHandle)
147 return {};
148
149 auto upcastRef = context.materializeConversion(targetClassHandle, instRef,
150 false, instRef.getLoc());
151 if (!upcastRef)
152 return {};
153
154 Value fieldRef = moore::ClassPropertyRefOp::create(builder, loc, fieldRefTy,
155 upcastRef, fieldSym);
156 return fieldRef;
157}
158
159namespace {
160/// A visitor handling expressions that can be lowered as lvalue and rvalue.
161struct ExprVisitor {
162 Context &context;
163 Location loc;
164 OpBuilder &builder;
165 bool isLvalue;
166
167 ExprVisitor(Context &context, Location loc, bool isLvalue)
168 : context(context), loc(loc), builder(context.builder),
169 isLvalue(isLvalue) {}
170
171 /// Convert an expression either as an lvalue or rvalue, depending on whether
172 /// this is an lvalue or rvalue visitor. This is useful for projections such
173 /// as `a[i]`, where you want `a` as an lvalue if you want `a[i]` as an
174 /// lvalue, or `a` as an rvalue if you want `a[i]` as an rvalue.
175 Value convertLvalueOrRvalueExpression(const slang::ast::Expression &expr) {
176 if (isLvalue)
177 return context.convertLvalueExpression(expr);
178 return context.convertRvalueExpression(expr);
179 }
180
181 /// Handle single bit selections.
182 Value visit(const slang::ast::ElementSelectExpression &expr) {
183 auto type = context.convertType(*expr.type);
184 auto value = convertLvalueOrRvalueExpression(expr.value());
185 if (!type || !value)
186 return {};
187
188 // We only support indexing into a few select types for now.
189 auto derefType = value.getType();
190 if (isLvalue)
191 derefType = cast<moore::RefType>(derefType).getNestedType();
192
193 if (!isa<moore::IntType, moore::ArrayType, moore::UnpackedArrayType,
194 moore::QueueType>(derefType)) {
195 mlir::emitError(loc) << "unsupported expression: element select into "
196 << expr.value().type->toString() << "\n";
197 return {};
198 }
199
200 auto resultType =
201 isLvalue ? moore::RefType::get(cast<moore::UnpackedType>(type)) : type;
202 auto range = expr.value().type->getFixedRange();
203 if (auto *constValue = expr.selector().getConstant();
204 constValue && constValue->isInteger()) {
205 assert(!constValue->hasUnknown());
206 assert(constValue->size() <= 32);
207
208 auto lowBit = constValue->integer().as<uint32_t>().value();
209 if (isLvalue)
210 return llvm::TypeSwitch<Type, Value>(derefType)
211 .Case<moore::QueueType>([&](moore::QueueType) {
212 mlir::emitError(loc)
213 << "Unexpected LValue extract on Queue Type!";
214 return Value();
215 })
216 .Default([&](Type) {
217 return moore::ExtractRefOp::create(builder, loc, resultType,
218 value,
219 range.translateIndex(lowBit));
220 });
221 else
222 return llvm::TypeSwitch<Type, Value>(derefType)
223 .Case<moore::QueueType>([&](moore::QueueType) {
224 mlir::emitError(loc)
225 << "Unexpected RValue extract on Queue Type!";
226 return Value();
227 })
228 .Default([&](Type) {
229 return moore::ExtractOp::create(builder, loc, resultType, value,
230 range.translateIndex(lowBit));
231 });
232 }
233
234 auto lowBit = context.convertRvalueExpression(expr.selector());
235 if (!lowBit)
236 return {};
237 lowBit = getSelectIndex(context, loc, lowBit, range);
238 if (isLvalue)
239 return llvm::TypeSwitch<Type, Value>(derefType)
240 .Case<moore::QueueType>([&](moore::QueueType) {
241 return moore::DynQueueExtractRefOp::create(builder, loc, resultType,
242 value, lowBit);
243 })
244 .Default([&](Type) {
245 return moore::DynExtractRefOp::create(builder, loc, resultType,
246 value, lowBit);
247 });
248
249 else
250 return llvm::TypeSwitch<Type, Value>(derefType)
251 .Case<moore::QueueType>([&](moore::QueueType) {
252 return moore::DynQueueExtractOp::create(builder, loc, resultType,
253 value, lowBit);
254 })
255 .Default([&](Type) {
256 return moore::DynExtractOp::create(builder, loc, resultType, value,
257 lowBit);
258 });
259 }
260
261 /// Handle null assignments to variables.
262 /// Compare with IEEE 1800-2023 Table 6-7 - Default variable initial values
263 Value visit(const slang::ast::NullLiteral &expr) {
264 auto type = context.convertType(*expr.type);
265 if (isa<moore::ClassHandleType, moore::ChandleType, moore::EventType,
266 moore::NullType>(type))
267 return moore::NullOp::create(builder, loc);
268 mlir::emitError(loc) << "No null value definition found for value of type "
269 << type;
270 return {};
271 }
272
273 /// Handle range bit selections.
274 Value visit(const slang::ast::RangeSelectExpression &expr) {
275 auto type = context.convertType(*expr.type);
276 auto value = convertLvalueOrRvalueExpression(expr.value());
277 if (!type || !value)
278 return {};
279
280 std::optional<int32_t> constLeft;
281 std::optional<int32_t> constRight;
282 if (auto *constant = expr.left().getConstant())
283 constLeft = constant->integer().as<int32_t>();
284 if (auto *constant = expr.right().getConstant())
285 constRight = constant->integer().as<int32_t>();
286
287 // We currently require the right-hand-side of the range to be constant.
288 // This catches things like `[42:$]` which we don't support at the moment.
289 if (!constRight) {
290 mlir::emitError(loc)
291 << "unsupported expression: range select with non-constant bounds";
292 return {};
293 }
294
295 // We need to determine the right bound of the range. This is the address of
296 // the least significant bit of the underlying bit storage, which is the
297 // offset we want to pass to the extract op.
298 //
299 // The arrays [6:2] and [2:6] both have 5 bits worth of underlying storage.
300 // The left and right bound of the range only determine the addressing
301 // scheme of the storage bits:
302 //
303 // Storage bits: 4 3 2 1 0 <-- extract op works on storage bits
304 // [6:2] indices: 6 5 4 3 2 ("little endian" in Slang terms)
305 // [2:6] indices: 2 3 4 5 6 ("big endian" in Slang terms)
306 //
307 // Before we can extract, we need to map the range select left and right
308 // bounds from these indices to actual bit positions in the storage.
309
310 Value offsetDyn;
311 int32_t offsetConst = 0;
312 auto range = expr.value().type->getFixedRange();
313
314 using slang::ast::RangeSelectionKind;
315 if (expr.getSelectionKind() == RangeSelectionKind::Simple) {
316 // For a constant range [a:b], we want the offset of the lowest storage
317 // bit from which we are starting the extract. For a range [5:3] this is
318 // bit index 3; for a range [3:5] this is bit index 5. Both of these are
319 // later translated map to bit offset 1 (see bit indices above).
320 assert(constRight && "constness checked in slang");
321 offsetConst = *constRight;
322 } else {
323 // For an indexed range [a+:b] or [a-:b], determining the lowest storage
324 // bit is a bit more complicated. We start out with the base index `a`.
325 // This is the lower *index* of the range, but not the lower *storage bit
326 // position*.
327 //
328 // The range [a+:b] expands to [a+b-1:a] for a [6:2] range, or [a:a+b-1]
329 // for a [2:6] range. The range [a-:b] expands to [a:a-b+1] for a [6:2]
330 // range, or [a-b+1:a] for a [2:6] range.
331 if (constLeft) {
332 offsetConst = *constLeft;
333 } else {
334 offsetDyn = context.convertRvalueExpression(expr.left());
335 if (!offsetDyn)
336 return {};
337 }
338
339 // For a [a-:b] select on [2:6] and a [a+:b] select on [6:2], the range
340 // expands to [a-b+1:a] and [a+b-1:a]. In this case, the right bound which
341 // corresponds to the lower *storage bit offset*, is just `a` and there's
342 // no further tweaking to do.
343 int32_t offsetAdd = 0;
344
345 // For a [a-:b] select on [6:2], the range expands to [a:a-b+1]. We
346 // therefore have to take the `a` from above and adjust it by `-b+1` to
347 // arrive at the right bound.
348 if (expr.getSelectionKind() == RangeSelectionKind::IndexedDown &&
349 range.isLittleEndian()) {
350 assert(constRight && "constness checked in slang");
351 offsetAdd = 1 - *constRight;
352 }
353
354 // For a [a+:b] select on [2:6], the range expands to [a:a+b-1]. We
355 // therefore have to take the `a` from above and adjust it by `+b-1` to
356 // arrive at the right bound.
357 if (expr.getSelectionKind() == RangeSelectionKind::IndexedUp &&
358 !range.isLittleEndian()) {
359 assert(constRight && "constness checked in slang");
360 offsetAdd = *constRight - 1;
361 }
362
363 // Adjust the offset such that it matches the right bound of the range.
364 if (offsetAdd != 0) {
365 if (offsetDyn)
366 offsetDyn = moore::AddOp::create(
367 builder, loc, offsetDyn,
368 moore::ConstantOp::create(
369 builder, loc, cast<moore::IntType>(offsetDyn.getType()),
370 offsetAdd,
371 /*isSigned=*/offsetAdd < 0));
372 else
373 offsetConst += offsetAdd;
374 }
375 }
376
377 // Create a dynamic or constant extract. Use `getSelectIndex` and
378 // `ConstantRange::translateIndex` to map from the bit indices provided by
379 // the user to the actual storage bit position. Since `offset*` corresponds
380 // to the right bound of the range, which provides the index of the least
381 // significant selected storage bit, we get the bit offset at which we want
382 // to start extracting.
383 auto resultType =
384 isLvalue ? moore::RefType::get(cast<moore::UnpackedType>(type)) : type;
385
386 if (offsetDyn) {
387 offsetDyn = getSelectIndex(context, loc, offsetDyn, range);
388 if (isLvalue) {
389 return moore::DynExtractRefOp::create(builder, loc, resultType, value,
390 offsetDyn);
391 } else {
392 return moore::DynExtractOp::create(builder, loc, resultType, value,
393 offsetDyn);
394 }
395 } else {
396 offsetConst = range.translateIndex(offsetConst);
397 if (isLvalue) {
398 return moore::ExtractRefOp::create(builder, loc, resultType, value,
399 offsetConst);
400 } else {
401 return moore::ExtractOp::create(builder, loc, resultType, value,
402 offsetConst);
403 }
404 }
405 }
406
407 /// Handle concatenations.
408 Value visit(const slang::ast::ConcatenationExpression &expr) {
409 SmallVector<Value> operands;
410 if (expr.type->isString()) {
411 for (auto *operand : expr.operands()) {
412 assert(!isLvalue && "checked by Slang");
413 auto value = convertLvalueOrRvalueExpression(*operand);
414 if (!value)
415 return {};
416 value = context.materializeConversion(
417 moore::StringType::get(context.getContext()), value, false,
418 value.getLoc());
419 if (!value)
420 return {};
421 operands.push_back(value);
422 }
423 return moore::StringConcatOp::create(builder, loc, operands);
424 }
425 for (auto *operand : expr.operands()) {
426 // Handle empty replications like `{0{...}}` which may occur within
427 // concatenations. Slang assigns them a `void` type which we can check for
428 // here.
429 if (operand->type->isVoid())
430 continue;
431 auto value = convertLvalueOrRvalueExpression(*operand);
432 if (!value)
433 return {};
434 if (!isLvalue)
435 value = context.convertToSimpleBitVector(value);
436 if (!value)
437 return {};
438 operands.push_back(value);
439 }
440 if (isLvalue)
441 return moore::ConcatRefOp::create(builder, loc, operands);
442 else
443 return moore::ConcatOp::create(builder, loc, operands);
444 }
445
446 /// Handle member accesses.
447 Value visit(const slang::ast::MemberAccessExpression &expr) {
448 auto type = context.convertType(*expr.type);
449 if (!type)
450 return {};
451
452 auto *valueType = expr.value().type.get();
453 auto memberName = builder.getStringAttr(expr.member.name);
454
455 // Handle structs.
456 if (valueType->isStruct()) {
457 auto resultType =
458 isLvalue ? moore::RefType::get(cast<moore::UnpackedType>(type))
459 : type;
460 auto value = convertLvalueOrRvalueExpression(expr.value());
461 if (!value)
462 return {};
463
464 if (isLvalue)
465 return moore::StructExtractRefOp::create(builder, loc, resultType,
466 memberName, value);
467 return moore::StructExtractOp::create(builder, loc, resultType,
468 memberName, value);
469 }
470
471 // Handle unions.
472 if (valueType->isPackedUnion() || valueType->isUnpackedUnion()) {
473 auto resultType =
474 isLvalue ? moore::RefType::get(cast<moore::UnpackedType>(type))
475 : type;
476 auto value = convertLvalueOrRvalueExpression(expr.value());
477 if (!value)
478 return {};
479
480 if (isLvalue)
481 return moore::UnionExtractRefOp::create(builder, loc, resultType,
482 memberName, value);
483 return moore::UnionExtractOp::create(builder, loc, type, memberName,
484 value);
485 }
486
487 // Handle classes.
488 if (valueType->isClass()) {
489 auto valTy = context.convertType(*valueType);
490 if (!valTy)
491 return {};
492 auto targetTy = cast<moore::ClassHandleType>(valTy);
493
494 // `MemberAccessExpression`s may refer to either variables that may or may
495 // not be compile time constants, or to class parameters which are always
496 // elaboration-time constant.
497 //
498 // We distinguish these cases, and materialize a runtime member access
499 // for variables, but force constant conversion for parameter accesses.
500 //
501 // Also see this discussion:
502 // https://github.com/MikePopoloski/slang/issues/1641
503
504 if (expr.member.kind != slang::ast::SymbolKind::Parameter) {
505
506 // We need to pick the closest ancestor that declares a property with
507 // the relevant name. System Verilog explicitly enforces lexical
508 // shadowing, as shown in IEEE 1800-2023 Section 8.14 "Overridden
509 // members".
510 moore::ClassHandleType upcastTargetTy =
511 context.getAncestorClassWithProperty(targetTy, expr.member.name,
512 loc);
513 if (!upcastTargetTy)
514 return {};
515
516 // Convert the class handle to the required target type for property
517 // shadowing purposes.
518 Value baseVal =
519 context.convertRvalueExpression(expr.value(), upcastTargetTy);
520 if (!baseVal)
521 return {};
522
523 // @field and result type !moore.ref<T>.
524 auto fieldSym = mlir::FlatSymbolRefAttr::get(builder.getContext(),
525 expr.member.name);
526 auto fieldRefTy = moore::RefType::get(cast<moore::UnpackedType>(type));
527
528 // Produce a ref to the class property from the (possibly upcast)
529 // handle.
530 Value fieldRef = moore::ClassPropertyRefOp::create(
531 builder, loc, fieldRefTy, baseVal, fieldSym);
532
533 // If we need an RValue, read the reference, otherwise return
534 return isLvalue ? fieldRef
535 : moore::ReadOp::create(builder, loc, fieldRef);
536 }
537
538 slang::ConstantValue constVal;
539 if (auto param = expr.member.as_if<slang::ast::ParameterSymbol>()) {
540 constVal = param->getValue();
541 if (auto value = context.materializeConstant(constVal, *expr.type, loc))
542 return value;
543 }
544
545 mlir::emitError(loc) << "Parameter " << expr.member.name
546 << " has no constant value";
547 return {};
548 }
549
550 mlir::emitError(loc, "expression of type ")
551 << valueType->toString() << " has no member fields";
552 return {};
553 }
554};
555} // namespace
556
557//===----------------------------------------------------------------------===//
558// Rvalue Conversion
559//===----------------------------------------------------------------------===//
560
561// NOLINTBEGIN(misc-no-recursion)
562namespace {
563struct RvalueExprVisitor : public ExprVisitor {
564 RvalueExprVisitor(Context &context, Location loc)
565 : ExprVisitor(context, loc, /*isLvalue=*/false) {}
566 using ExprVisitor::visit;
567
568 // Handle references to the left-hand side of a parent assignment.
569 Value visit(const slang::ast::LValueReferenceExpression &expr) {
570 assert(!context.lvalueStack.empty() && "parent assignments push lvalue");
571 auto lvalue = context.lvalueStack.back();
572 return moore::ReadOp::create(builder, loc, lvalue);
573 }
574
575 // Handle named values, such as references to declared variables.
576 Value visit(const slang::ast::NamedValueExpression &expr) {
577 // Handle local variables.
578 if (auto value = context.valueSymbols.lookup(&expr.symbol)) {
579 if (isa<moore::RefType>(value.getType())) {
580 auto readOp = moore::ReadOp::create(builder, loc, value);
581 if (context.rvalueReadCallback)
582 context.rvalueReadCallback(readOp);
583 value = readOp.getResult();
584 }
585 return value;
586 }
587
588 // Handle global variables.
589 if (auto globalOp = context.globalVariables.lookup(&expr.symbol)) {
590 auto value = moore::GetGlobalVariableOp::create(builder, loc, globalOp);
591 return moore::ReadOp::create(builder, loc, value);
592 }
593
594 // We're reading a class property.
595 if (auto *const property =
596 expr.symbol.as_if<slang::ast::ClassPropertySymbol>()) {
597 auto fieldRef = visitClassProperty(context, *property);
598 return moore::ReadOp::create(builder, loc, fieldRef).getResult();
599 }
600
601 // Try to materialize constant values directly.
602 auto constant = context.evaluateConstant(expr);
603 if (auto value = context.materializeConstant(constant, *expr.type, loc))
604 return value;
605
606 // Otherwise some other part of ImportVerilog should have added an MLIR
607 // value for this expression's symbol to the `context.valueSymbols` table.
608 auto d = mlir::emitError(loc, "unknown name `") << expr.symbol.name << "`";
609 d.attachNote(context.convertLocation(expr.symbol.location))
610 << "no rvalue generated for " << slang::ast::toString(expr.symbol.kind);
611 return {};
612 }
613
614 // Handle hierarchical values, such as `x = Top.sub.var`.
615 Value visit(const slang::ast::HierarchicalValueExpression &expr) {
616 auto hierLoc = context.convertLocation(expr.symbol.location);
617 if (auto value = context.valueSymbols.lookup(&expr.symbol)) {
618 if (isa<moore::RefType>(value.getType())) {
619 auto readOp = moore::ReadOp::create(builder, hierLoc, value);
620 if (context.rvalueReadCallback)
621 context.rvalueReadCallback(readOp);
622 value = readOp.getResult();
623 }
624 return value;
625 }
626
627 // Emit an error for those hierarchical values not recorded in the
628 // `valueSymbols`.
629 auto d = mlir::emitError(loc, "unknown hierarchical name `")
630 << expr.symbol.name << "`";
631 d.attachNote(hierLoc) << "no rvalue generated for "
632 << slang::ast::toString(expr.symbol.kind);
633 return {};
634 }
635
636 // Handle type conversions (explicit and implicit).
637 Value visit(const slang::ast::ConversionExpression &expr) {
638 auto type = context.convertType(*expr.type);
639 if (!type)
640 return {};
641 return context.convertRvalueExpression(expr.operand(), type);
642 }
643
644 // Handle blocking and non-blocking assignments.
645 Value visit(const slang::ast::AssignmentExpression &expr) {
646 auto lhs = context.convertLvalueExpression(expr.left());
647 if (!lhs)
648 return {};
649
650 // Determine the right-hand side value of the assignment.
651 context.lvalueStack.push_back(lhs);
652 auto rhs = context.convertRvalueExpression(
653 expr.right(), cast<moore::RefType>(lhs.getType()).getNestedType());
654 context.lvalueStack.pop_back();
655 if (!rhs)
656 return {};
657
658 // If this is a blocking assignment, we can insert the delay/wait ops of the
659 // optional timing control directly in between computing the RHS and
660 // executing the assignment.
661 if (!expr.isNonBlocking()) {
662 if (expr.timingControl)
663 if (failed(context.convertTimingControl(*expr.timingControl)))
664 return {};
665 auto assignOp = moore::BlockingAssignOp::create(builder, loc, lhs, rhs);
666 if (context.variableAssignCallback)
667 context.variableAssignCallback(assignOp);
668 return rhs;
669 }
670
671 // For non-blocking assignments, we only support time delays for now.
672 if (expr.timingControl) {
673 // Handle regular time delays.
674 if (auto *ctrl = expr.timingControl->as_if<slang::ast::DelayControl>()) {
675 auto delay = context.convertRvalueExpression(
676 ctrl->expr, moore::TimeType::get(builder.getContext()));
677 if (!delay)
678 return {};
679 auto assignOp = moore::DelayedNonBlockingAssignOp::create(
680 builder, loc, lhs, rhs, delay);
681 if (context.variableAssignCallback)
682 context.variableAssignCallback(assignOp);
683 return rhs;
684 }
685
686 // All other timing controls are not supported.
687 auto loc = context.convertLocation(expr.timingControl->sourceRange);
688 mlir::emitError(loc)
689 << "unsupported non-blocking assignment timing control: "
690 << slang::ast::toString(expr.timingControl->kind);
691 return {};
692 }
693 auto assignOp = moore::NonBlockingAssignOp::create(builder, loc, lhs, rhs);
694 if (context.variableAssignCallback)
695 context.variableAssignCallback(assignOp);
696 return rhs;
697 }
698
699 // Helper function to convert an argument to a simple bit vector type, pass it
700 // to a reduction op, and optionally invert the result.
701 template <class ConcreteOp>
702 Value createReduction(Value arg, bool invert) {
703 arg = context.convertToSimpleBitVector(arg);
704 if (!arg)
705 return {};
706 Value result = ConcreteOp::create(builder, loc, arg);
707 if (invert)
708 result = moore::NotOp::create(builder, loc, result);
709 return result;
710 }
711
712 // Helper function to create pre and post increments and decrements.
713 Value createIncrement(Value arg, bool isInc, bool isPost) {
714 auto preValue = moore::ReadOp::create(builder, loc, arg);
715 Value postValue;
716 // Catch the special case where a signed 1 bit value (i1) is incremented,
717 // as +1 can not be expressed as a signed 1 bit value. For any 1-bit number
718 // negating is equivalent to incrementing.
719 if (moore::isIntType(preValue.getType(), 1)) {
720 postValue = moore::NotOp::create(builder, loc, preValue).getResult();
721 } else {
722
723 auto one = moore::ConstantOp::create(
724 builder, loc, cast<moore::IntType>(preValue.getType()), 1);
725 postValue =
726 isInc ? moore::AddOp::create(builder, loc, preValue, one).getResult()
727 : moore::SubOp::create(builder, loc, preValue, one).getResult();
728 auto assignOp =
729 moore::BlockingAssignOp::create(builder, loc, arg, postValue);
730 if (context.variableAssignCallback)
731 context.variableAssignCallback(assignOp);
732 }
733
734 if (isPost)
735 return preValue;
736 return postValue;
737 }
738
739 // Helper function to create pre and post increments and decrements.
740 Value createRealIncrement(Value arg, bool isInc, bool isPost) {
741 Value preValue = moore::ReadOp::create(builder, loc, arg);
742 Value postValue;
743
744 bool isTime = isa<moore::TimeType>(preValue.getType());
745 if (isTime)
746 preValue = context.materializeConversion(
747 moore::RealType::get(context.getContext(), moore::RealWidth::f64),
748 preValue, false, loc);
749
750 moore::RealType realTy =
751 llvm::dyn_cast<moore::RealType>(preValue.getType());
752 if (!realTy)
753 return {};
754
755 FloatAttr oneAttr;
756 if (realTy.getWidth() == moore::RealWidth::f32) {
757 oneAttr = builder.getFloatAttr(builder.getF32Type(), 1.0);
758 } else if (realTy.getWidth() == moore::RealWidth::f64) {
759 auto oneVal = isTime ? getTimeScaleInFemtoseconds(context) : 1.0;
760 oneAttr = builder.getFloatAttr(builder.getF64Type(), oneVal);
761 } else {
762 mlir::emitError(loc) << "cannot construct increment for " << realTy;
763 return {};
764 }
765 auto one = moore::ConstantRealOp::create(builder, loc, oneAttr);
766
767 postValue =
768 isInc
769 ? moore::AddRealOp::create(builder, loc, preValue, one).getResult()
770 : moore::SubRealOp::create(builder, loc, preValue, one).getResult();
771
772 if (isTime)
773 postValue = context.materializeConversion(
774 moore::TimeType::get(context.getContext()), postValue, false, loc);
775
776 auto assignOp =
777 moore::BlockingAssignOp::create(builder, loc, arg, postValue);
778
779 if (context.variableAssignCallback)
780 context.variableAssignCallback(assignOp);
781
782 if (isPost)
783 return preValue;
784 return postValue;
785 }
786
787 Value visitRealUOp(const slang::ast::UnaryExpression &expr) {
788 Type opFTy = context.convertType(*expr.operand().type);
789
790 using slang::ast::UnaryOperator;
791 Value arg;
792 if (expr.op == UnaryOperator::Preincrement ||
793 expr.op == UnaryOperator::Predecrement ||
794 expr.op == UnaryOperator::Postincrement ||
795 expr.op == UnaryOperator::Postdecrement)
796 arg = context.convertLvalueExpression(expr.operand());
797 else
798 arg = context.convertRvalueExpression(expr.operand(), opFTy);
799 if (!arg)
800 return {};
801
802 // Only covers expressions in 'else' branch above.
803 if (isa<moore::TimeType>(arg.getType()))
804 arg = context.materializeConversion(
805 moore::RealType::get(context.getContext(), moore::RealWidth::f64),
806 arg, false, loc);
807
808 switch (expr.op) {
809 // `+a` is simply `a`
810 case UnaryOperator::Plus:
811 return arg;
812 case UnaryOperator::Minus:
813 return moore::NegRealOp::create(builder, loc, arg);
814
815 case UnaryOperator::Preincrement:
816 return createRealIncrement(arg, true, false);
817 case UnaryOperator::Predecrement:
818 return createRealIncrement(arg, false, false);
819 case UnaryOperator::Postincrement:
820 return createRealIncrement(arg, true, true);
821 case UnaryOperator::Postdecrement:
822 return createRealIncrement(arg, false, true);
823
824 case UnaryOperator::LogicalNot:
825 arg = context.convertToBool(arg);
826 if (!arg)
827 return {};
828 return moore::NotOp::create(builder, loc, arg);
829
830 default:
831 mlir::emitError(loc) << "Unary operator " << slang::ast::toString(expr.op)
832 << " not supported with real values!\n";
833 return {};
834 }
835 }
836
837 // Handle unary operators.
838 Value visit(const slang::ast::UnaryExpression &expr) {
839 // First check whether we need real or integral BOps
840 const auto *floatType =
841 expr.operand().type->as_if<slang::ast::FloatingType>();
842 // If op is real-typed, treat as real BOp.
843 if (floatType)
844 return visitRealUOp(expr);
845
846 using slang::ast::UnaryOperator;
847 Value arg;
848 if (expr.op == UnaryOperator::Preincrement ||
849 expr.op == UnaryOperator::Predecrement ||
850 expr.op == UnaryOperator::Postincrement ||
851 expr.op == UnaryOperator::Postdecrement)
852 arg = context.convertLvalueExpression(expr.operand());
853 else
854 arg = context.convertRvalueExpression(expr.operand());
855 if (!arg)
856 return {};
857
858 switch (expr.op) {
859 // `+a` is simply `a`, but converted to a simple bit vector type since
860 // this is technically an arithmetic operation.
861 case UnaryOperator::Plus:
862 return context.convertToSimpleBitVector(arg);
863
864 case UnaryOperator::Minus:
865 arg = context.convertToSimpleBitVector(arg);
866 if (!arg)
867 return {};
868 return moore::NegOp::create(builder, loc, arg);
869
870 case UnaryOperator::BitwiseNot:
871 arg = context.convertToSimpleBitVector(arg);
872 if (!arg)
873 return {};
874 return moore::NotOp::create(builder, loc, arg);
875
876 case UnaryOperator::BitwiseAnd:
877 return createReduction<moore::ReduceAndOp>(arg, false);
878 case UnaryOperator::BitwiseOr:
879 return createReduction<moore::ReduceOrOp>(arg, false);
880 case UnaryOperator::BitwiseXor:
881 return createReduction<moore::ReduceXorOp>(arg, false);
882 case UnaryOperator::BitwiseNand:
883 return createReduction<moore::ReduceAndOp>(arg, true);
884 case UnaryOperator::BitwiseNor:
885 return createReduction<moore::ReduceOrOp>(arg, true);
886 case UnaryOperator::BitwiseXnor:
887 return createReduction<moore::ReduceXorOp>(arg, true);
888
889 case UnaryOperator::LogicalNot:
890 arg = context.convertToBool(arg);
891 if (!arg)
892 return {};
893 return moore::NotOp::create(builder, loc, arg);
894
895 case UnaryOperator::Preincrement:
896 return createIncrement(arg, true, false);
897 case UnaryOperator::Predecrement:
898 return createIncrement(arg, false, false);
899 case UnaryOperator::Postincrement:
900 return createIncrement(arg, true, true);
901 case UnaryOperator::Postdecrement:
902 return createIncrement(arg, false, true);
903 }
904
905 mlir::emitError(loc, "unsupported unary operator");
906 return {};
907 }
908
909 /// Handles logical operators (§11.4.7), assuming lhs/rhs are rvalues already.
910 Value buildLogicalBOp(slang::ast::BinaryOperator op, Value lhs, Value rhs,
911 std::optional<Domain> domain = std::nullopt) {
912 using slang::ast::BinaryOperator;
913 // TODO: These should short-circuit; RHS should be in a separate block.
914
915 if (domain) {
916 lhs = context.convertToBool(lhs, domain.value());
917 rhs = context.convertToBool(rhs, domain.value());
918 } else {
919 lhs = context.convertToBool(lhs);
920 rhs = context.convertToBool(rhs);
921 }
922
923 if (!lhs || !rhs)
924 return {};
925
926 switch (op) {
927 case BinaryOperator::LogicalAnd:
928 return moore::AndOp::create(builder, loc, lhs, rhs);
929
930 case BinaryOperator::LogicalOr:
931 return moore::OrOp::create(builder, loc, lhs, rhs);
932
933 case BinaryOperator::LogicalImplication: {
934 // (lhs -> rhs) == (!lhs || rhs)
935 auto notLHS = moore::NotOp::create(builder, loc, lhs);
936 return moore::OrOp::create(builder, loc, notLHS, rhs);
937 }
938
939 case BinaryOperator::LogicalEquivalence: {
940 // (lhs <-> rhs) == (lhs && rhs) || (!lhs && !rhs)
941 auto notLHS = moore::NotOp::create(builder, loc, lhs);
942 auto notRHS = moore::NotOp::create(builder, loc, rhs);
943 auto both = moore::AndOp::create(builder, loc, lhs, rhs);
944 auto notBoth = moore::AndOp::create(builder, loc, notLHS, notRHS);
945 return moore::OrOp::create(builder, loc, both, notBoth);
946 }
947
948 default:
949 llvm_unreachable("not a logical BinaryOperator");
950 }
951 }
952
953 Value visitHandleBOp(const slang::ast::BinaryExpression &expr) {
954 // Convert operands to the chosen target type.
955 auto lhs = context.convertRvalueExpression(expr.left());
956 if (!lhs)
957 return {};
958 auto rhs = context.convertRvalueExpression(expr.right());
959 if (!rhs)
960 return {};
961
962 using slang::ast::BinaryOperator;
963 switch (expr.op) {
964
965 case BinaryOperator::Equality:
966 return moore::HandleEqOp::create(builder, loc, lhs, rhs);
967 case BinaryOperator::Inequality:
968 return moore::HandleNeOp::create(builder, loc, lhs, rhs);
969 case BinaryOperator::CaseEquality:
970 return moore::HandleCaseEqOp::create(builder, loc, lhs, rhs);
971 case BinaryOperator::CaseInequality:
972 return moore::HandleCaseNeOp::create(builder, loc, lhs, rhs);
973
974 default:
975 mlir::emitError(loc)
976 << "Binary operator " << slang::ast::toString(expr.op)
977 << " not supported with class handle valued operands!\n";
978 return {};
979 }
980 }
981
982 Value visitRealBOp(const slang::ast::BinaryExpression &expr) {
983 // Convert operands to the chosen target type.
984 auto lhs = context.convertRvalueExpression(expr.left());
985 if (!lhs)
986 return {};
987 auto rhs = context.convertRvalueExpression(expr.right());
988 if (!rhs)
989 return {};
990
991 if (isa<moore::TimeType>(lhs.getType()) ||
992 isa<moore::TimeType>(rhs.getType())) {
993 lhs = context.materializeConversion(
994 moore::RealType::get(context.getContext(), moore::RealWidth::f64),
995 lhs, false, loc);
996 rhs = context.materializeConversion(
997 moore::RealType::get(context.getContext(), moore::RealWidth::f64),
998 rhs, false, loc);
999 }
1000
1001 using slang::ast::BinaryOperator;
1002 switch (expr.op) {
1003 case BinaryOperator::Add:
1004 return moore::AddRealOp::create(builder, loc, lhs, rhs);
1005 case BinaryOperator::Subtract:
1006 return moore::SubRealOp::create(builder, loc, lhs, rhs);
1007 case BinaryOperator::Multiply:
1008 return moore::MulRealOp::create(builder, loc, lhs, rhs);
1009 case BinaryOperator::Divide:
1010 return moore::DivRealOp::create(builder, loc, lhs, rhs);
1011 case BinaryOperator::Power:
1012 return moore::PowRealOp::create(builder, loc, lhs, rhs);
1013
1014 case BinaryOperator::Equality:
1015 return moore::EqRealOp::create(builder, loc, lhs, rhs);
1016 case BinaryOperator::Inequality:
1017 return moore::NeRealOp::create(builder, loc, lhs, rhs);
1018
1019 case BinaryOperator::GreaterThan:
1020 return moore::FgtOp::create(builder, loc, lhs, rhs);
1021 case BinaryOperator::LessThan:
1022 return moore::FltOp::create(builder, loc, lhs, rhs);
1023 case BinaryOperator::GreaterThanEqual:
1024 return moore::FgeOp::create(builder, loc, lhs, rhs);
1025 case BinaryOperator::LessThanEqual:
1026 return moore::FleOp::create(builder, loc, lhs, rhs);
1027
1028 case BinaryOperator::LogicalAnd:
1029 case BinaryOperator::LogicalOr:
1030 case BinaryOperator::LogicalImplication:
1031 case BinaryOperator::LogicalEquivalence:
1032 return buildLogicalBOp(expr.op, lhs, rhs);
1033
1034 default:
1035 mlir::emitError(loc) << "Binary operator "
1036 << slang::ast::toString(expr.op)
1037 << " not supported with real valued operands!\n";
1038 return {};
1039 }
1040 }
1041
1042 // Helper function to convert two arguments to a simple bit vector type and
1043 // pass them into a binary op.
1044 template <class ConcreteOp>
1045 Value createBinary(Value lhs, Value rhs) {
1046 lhs = context.convertToSimpleBitVector(lhs);
1047 if (!lhs)
1048 return {};
1049 rhs = context.convertToSimpleBitVector(rhs);
1050 if (!rhs)
1051 return {};
1052 return ConcreteOp::create(builder, loc, lhs, rhs);
1053 }
1054
1055 // Handle binary operators.
1056 Value visit(const slang::ast::BinaryExpression &expr) {
1057 // First check whether we need real or integral BOps
1058 const auto *rhsFloatType =
1059 expr.right().type->as_if<slang::ast::FloatingType>();
1060 const auto *lhsFloatType =
1061 expr.left().type->as_if<slang::ast::FloatingType>();
1062
1063 // If either arg is real-typed, treat as real BOp.
1064 if (rhsFloatType || lhsFloatType)
1065 return visitRealBOp(expr);
1066
1067 // Check whether we are comparing against a Class Handle or CHandle
1068 const auto rhsIsClass = expr.right().type->isClass();
1069 const auto lhsIsClass = expr.left().type->isClass();
1070 const auto rhsIsChandle = expr.right().type->isCHandle();
1071 const auto lhsIsChandle = expr.left().type->isCHandle();
1072 // If either arg is class handle-typed, treat as class handle BOp.
1073 if (rhsIsClass || lhsIsClass || rhsIsChandle || lhsIsChandle)
1074 return visitHandleBOp(expr);
1075
1076 auto lhs = context.convertRvalueExpression(expr.left());
1077 if (!lhs)
1078 return {};
1079 auto rhs = context.convertRvalueExpression(expr.right());
1080 if (!rhs)
1081 return {};
1082
1083 // Determine the domain of the result.
1084 Domain domain = Domain::TwoValued;
1085 if (expr.type->isFourState() || expr.left().type->isFourState() ||
1086 expr.right().type->isFourState())
1087 domain = Domain::FourValued;
1088
1089 using slang::ast::BinaryOperator;
1090 switch (expr.op) {
1091 case BinaryOperator::Add:
1092 return createBinary<moore::AddOp>(lhs, rhs);
1093 case BinaryOperator::Subtract:
1094 return createBinary<moore::SubOp>(lhs, rhs);
1095 case BinaryOperator::Multiply:
1096 return createBinary<moore::MulOp>(lhs, rhs);
1097 case BinaryOperator::Divide:
1098 if (expr.type->isSigned())
1099 return createBinary<moore::DivSOp>(lhs, rhs);
1100 else
1101 return createBinary<moore::DivUOp>(lhs, rhs);
1102 case BinaryOperator::Mod:
1103 if (expr.type->isSigned())
1104 return createBinary<moore::ModSOp>(lhs, rhs);
1105 else
1106 return createBinary<moore::ModUOp>(lhs, rhs);
1107 case BinaryOperator::Power: {
1108 // Slang casts the LHS and result of the `**` operator to a four-valued
1109 // type, since the operator can return X even for two-valued inputs. To
1110 // maintain uniform types across operands and results, cast the RHS to
1111 // that four-valued type as well.
1112 auto rhsCast = context.materializeConversion(
1113 lhs.getType(), rhs, expr.right().type->isSigned(), rhs.getLoc());
1114 if (expr.type->isSigned())
1115 return createBinary<moore::PowSOp>(lhs, rhsCast);
1116 else
1117 return createBinary<moore::PowUOp>(lhs, rhsCast);
1118 }
1119
1120 case BinaryOperator::BinaryAnd:
1121 return createBinary<moore::AndOp>(lhs, rhs);
1122 case BinaryOperator::BinaryOr:
1123 return createBinary<moore::OrOp>(lhs, rhs);
1124 case BinaryOperator::BinaryXor:
1125 return createBinary<moore::XorOp>(lhs, rhs);
1126 case BinaryOperator::BinaryXnor: {
1127 auto result = createBinary<moore::XorOp>(lhs, rhs);
1128 if (!result)
1129 return {};
1130 return moore::NotOp::create(builder, loc, result);
1131 }
1132
1133 case BinaryOperator::Equality:
1134 if (isa<moore::UnpackedArrayType>(lhs.getType()))
1135 return moore::UArrayCmpOp::create(
1136 builder, loc, moore::UArrayCmpPredicate::eq, lhs, rhs);
1137 else if (isa<moore::StringType>(lhs.getType()))
1138 return moore::StringCmpOp::create(
1139 builder, loc, moore::StringCmpPredicate::eq, lhs, rhs);
1140 else
1141 return createBinary<moore::EqOp>(lhs, rhs);
1142 case BinaryOperator::Inequality:
1143 if (isa<moore::UnpackedArrayType>(lhs.getType()))
1144 return moore::UArrayCmpOp::create(
1145 builder, loc, moore::UArrayCmpPredicate::ne, lhs, rhs);
1146 else if (isa<moore::StringType>(lhs.getType()))
1147 return moore::StringCmpOp::create(
1148 builder, loc, moore::StringCmpPredicate::ne, lhs, rhs);
1149 else
1150 return createBinary<moore::NeOp>(lhs, rhs);
1151 case BinaryOperator::CaseEquality:
1152 return createBinary<moore::CaseEqOp>(lhs, rhs);
1153 case BinaryOperator::CaseInequality:
1154 return createBinary<moore::CaseNeOp>(lhs, rhs);
1155 case BinaryOperator::WildcardEquality:
1156 return createBinary<moore::WildcardEqOp>(lhs, rhs);
1157 case BinaryOperator::WildcardInequality:
1158 return createBinary<moore::WildcardNeOp>(lhs, rhs);
1159
1160 case BinaryOperator::GreaterThanEqual:
1161 if (expr.left().type->isSigned())
1162 return createBinary<moore::SgeOp>(lhs, rhs);
1163 else if (isa<moore::StringType>(lhs.getType()))
1164 return moore::StringCmpOp::create(
1165 builder, loc, moore::StringCmpPredicate::ge, lhs, rhs);
1166 else
1167 return createBinary<moore::UgeOp>(lhs, rhs);
1168 case BinaryOperator::GreaterThan:
1169 if (expr.left().type->isSigned())
1170 return createBinary<moore::SgtOp>(lhs, rhs);
1171 else if (isa<moore::StringType>(lhs.getType()))
1172 return moore::StringCmpOp::create(
1173 builder, loc, moore::StringCmpPredicate::gt, lhs, rhs);
1174 else
1175 return createBinary<moore::UgtOp>(lhs, rhs);
1176 case BinaryOperator::LessThanEqual:
1177 if (expr.left().type->isSigned())
1178 return createBinary<moore::SleOp>(lhs, rhs);
1179 else if (isa<moore::StringType>(lhs.getType()))
1180 return moore::StringCmpOp::create(
1181 builder, loc, moore::StringCmpPredicate::le, lhs, rhs);
1182 else
1183 return createBinary<moore::UleOp>(lhs, rhs);
1184 case BinaryOperator::LessThan:
1185 if (expr.left().type->isSigned())
1186 return createBinary<moore::SltOp>(lhs, rhs);
1187 else if (isa<moore::StringType>(lhs.getType()))
1188 return moore::StringCmpOp::create(
1189 builder, loc, moore::StringCmpPredicate::lt, lhs, rhs);
1190 else
1191 return createBinary<moore::UltOp>(lhs, rhs);
1192
1193 case BinaryOperator::LogicalAnd:
1194 case BinaryOperator::LogicalOr:
1195 case BinaryOperator::LogicalImplication:
1196 case BinaryOperator::LogicalEquivalence:
1197 return buildLogicalBOp(expr.op, lhs, rhs, domain);
1198
1199 case BinaryOperator::LogicalShiftLeft:
1200 return createBinary<moore::ShlOp>(lhs, rhs);
1201 case BinaryOperator::LogicalShiftRight:
1202 return createBinary<moore::ShrOp>(lhs, rhs);
1203 case BinaryOperator::ArithmeticShiftLeft:
1204 return createBinary<moore::ShlOp>(lhs, rhs);
1205 case BinaryOperator::ArithmeticShiftRight: {
1206 // The `>>>` operator is an arithmetic right shift if the LHS operand is
1207 // signed, or a logical right shift if the operand is unsigned.
1208 lhs = context.convertToSimpleBitVector(lhs);
1209 rhs = context.convertToSimpleBitVector(rhs);
1210 if (!lhs || !rhs)
1211 return {};
1212 if (expr.type->isSigned())
1213 return moore::AShrOp::create(builder, loc, lhs, rhs);
1214 return moore::ShrOp::create(builder, loc, lhs, rhs);
1215 }
1216 }
1217
1218 mlir::emitError(loc, "unsupported binary operator");
1219 return {};
1220 }
1221
1222 // Handle `'0`, `'1`, `'x`, and `'z` literals.
1223 Value visit(const slang::ast::UnbasedUnsizedIntegerLiteral &expr) {
1224 return context.materializeSVInt(expr.getValue(), *expr.type, loc);
1225 }
1226
1227 // Handle integer literals.
1228 Value visit(const slang::ast::IntegerLiteral &expr) {
1229 return context.materializeSVInt(expr.getValue(), *expr.type, loc);
1230 }
1231
1232 // Handle time literals.
1233 Value visit(const slang::ast::TimeLiteral &expr) {
1234 // The time literal is expressed in the current time scale. Determine the
1235 // conversion factor to convert the literal from the current time scale into
1236 // femtoseconds, and round the scaled value to femtoseconds.
1237 double scale = getTimeScaleInFemtoseconds(context);
1238 double value = std::round(expr.getValue() * scale);
1239 assert(value >= 0.0);
1240
1241 // Check that the value does not exceed what we can represent in the IR.
1242 // Casting the maximum uint64 value to double changes its value from
1243 // 18446744073709551615 to 18446744073709551616, which makes the comparison
1244 // overestimate the largest number we can represent. To avoid this, round
1245 // the maximum value down to the closest number that only has the front 53
1246 // bits set. This matches the mantissa of a double, plus the implicit
1247 // leading 1, ensuring that we can accurately represent the limit.
1248 static constexpr uint64_t limit =
1249 (std::numeric_limits<uint64_t>::max() >> 11) << 11;
1250 if (value > limit) {
1251 mlir::emitError(loc) << "time value is larger than " << limit << " fs";
1252 return {};
1253 }
1254
1255 return moore::ConstantTimeOp::create(builder, loc,
1256 static_cast<uint64_t>(value));
1257 }
1258
1259 // Handle replications.
1260 Value visit(const slang::ast::ReplicationExpression &expr) {
1261 auto type = context.convertType(*expr.type);
1262 auto value = context.convertRvalueExpression(expr.concat());
1263 if (!value)
1264 return {};
1265 return moore::ReplicateOp::create(builder, loc, type, value);
1266 }
1267
1268 // Handle set membership operator.
1269 Value visit(const slang::ast::InsideExpression &expr) {
1270 auto lhs = context.convertToSimpleBitVector(
1271 context.convertRvalueExpression(expr.left()));
1272 if (!lhs)
1273 return {};
1274 // All conditions for determining whether it is inside.
1275 SmallVector<Value> conditions;
1276
1277 // Traverse open range list.
1278 for (const auto *listExpr : expr.rangeList()) {
1279 Value cond;
1280 // The open range list on the right-hand side of the inside operator is a
1281 // comma-separated list of expressions or ranges.
1282 if (const auto *openRange =
1283 listExpr->as_if<slang::ast::ValueRangeExpression>()) {
1284 // Handle ranges.
1285 auto lowBound = context.convertToSimpleBitVector(
1286 context.convertRvalueExpression(openRange->left()));
1287 auto highBound = context.convertToSimpleBitVector(
1288 context.convertRvalueExpression(openRange->right()));
1289 if (!lowBound || !highBound)
1290 return {};
1291 Value leftValue, rightValue;
1292 // Determine if the expression on the left-hand side is inclusively
1293 // within the range.
1294 if (openRange->left().type->isSigned() ||
1295 expr.left().type->isSigned()) {
1296 leftValue = moore::SgeOp::create(builder, loc, lhs, lowBound);
1297 } else {
1298 leftValue = moore::UgeOp::create(builder, loc, lhs, lowBound);
1299 }
1300 if (openRange->right().type->isSigned() ||
1301 expr.left().type->isSigned()) {
1302 rightValue = moore::SleOp::create(builder, loc, lhs, highBound);
1303 } else {
1304 rightValue = moore::UleOp::create(builder, loc, lhs, highBound);
1305 }
1306 cond = moore::AndOp::create(builder, loc, leftValue, rightValue);
1307 } else {
1308 // Handle expressions.
1309 if (!listExpr->type->isIntegral()) {
1310 if (listExpr->type->isUnpackedArray()) {
1311 mlir::emitError(
1312 loc, "unpacked arrays in 'inside' expressions not supported");
1313 return {};
1314 }
1315 mlir::emitError(
1316 loc, "only simple bit vectors supported in 'inside' expressions");
1317 return {};
1318 }
1319
1320 auto value = context.convertToSimpleBitVector(
1321 context.convertRvalueExpression(*listExpr));
1322 if (!value)
1323 return {};
1324 cond = moore::WildcardEqOp::create(builder, loc, lhs, value);
1325 }
1326 conditions.push_back(cond);
1327 }
1328
1329 // Calculate the final result by `or` op.
1330 auto result = conditions.back();
1331 conditions.pop_back();
1332 while (!conditions.empty()) {
1333 result = moore::OrOp::create(builder, loc, conditions.back(), result);
1334 conditions.pop_back();
1335 }
1336 return result;
1337 }
1338
1339 // Handle conditional operator `?:`.
1340 Value visit(const slang::ast::ConditionalExpression &expr) {
1341 auto type = context.convertType(*expr.type);
1342
1343 // Handle condition.
1344 if (expr.conditions.size() > 1) {
1345 mlir::emitError(loc)
1346 << "unsupported conditional expression with more than one condition";
1347 return {};
1348 }
1349 const auto &cond = expr.conditions[0];
1350 if (cond.pattern) {
1351 mlir::emitError(loc) << "unsupported conditional expression with pattern";
1352 return {};
1353 }
1354 auto value =
1355 context.convertToBool(context.convertRvalueExpression(*cond.expr));
1356 if (!value)
1357 return {};
1358 auto conditionalOp =
1359 moore::ConditionalOp::create(builder, loc, type, value);
1360
1361 // Create blocks for true region and false region.
1362 auto &trueBlock = conditionalOp.getTrueRegion().emplaceBlock();
1363 auto &falseBlock = conditionalOp.getFalseRegion().emplaceBlock();
1364
1365 OpBuilder::InsertionGuard g(builder);
1366
1367 // Handle left expression.
1368 builder.setInsertionPointToStart(&trueBlock);
1369 auto trueValue = context.convertRvalueExpression(expr.left(), type);
1370 if (!trueValue)
1371 return {};
1372 moore::YieldOp::create(builder, loc, trueValue);
1373
1374 // Handle right expression.
1375 builder.setInsertionPointToStart(&falseBlock);
1376 auto falseValue = context.convertRvalueExpression(expr.right(), type);
1377 if (!falseValue)
1378 return {};
1379 moore::YieldOp::create(builder, loc, falseValue);
1380
1381 return conditionalOp.getResult();
1382 }
1383
1384 /// Handle calls.
1385 Value visit(const slang::ast::CallExpression &expr) {
1386 // Try to materialize constant values directly.
1387 auto constant = context.evaluateConstant(expr);
1388 if (auto value = context.materializeConstant(constant, *expr.type, loc))
1389 return value;
1390
1391 return std::visit(
1392 [&](auto &subroutine) { return visitCall(expr, subroutine); },
1393 expr.subroutine);
1394 }
1395
1396 /// Get both the actual `this` argument of a method call and the required
1397 /// class type.
1398 std::pair<Value, moore::ClassHandleType>
1399 getMethodReceiverTypeHandle(const slang::ast::CallExpression &expr) {
1400
1401 moore::ClassHandleType handleTy;
1402 Value thisRef;
1403
1404 // Qualified call: t.m(...), extract from thisClass.
1405 if (const slang::ast::Expression *recvExpr = expr.thisClass()) {
1406 thisRef = context.convertRvalueExpression(*recvExpr);
1407 if (!thisRef)
1408 return {};
1409 } else {
1410 // Unqualified call inside a method body: try using implicit %this.
1411 thisRef = context.getImplicitThisRef();
1412 if (!thisRef) {
1413 mlir::emitError(loc) << "method '" << expr.getSubroutineName()
1414 << "' called without an object";
1415 return {};
1416 }
1417 }
1418 handleTy = cast<moore::ClassHandleType>(thisRef.getType());
1419 return {thisRef, handleTy};
1420 }
1421
1422 /// Build a method call including implicit this argument.
1423 mlir::CallOpInterface
1424 buildMethodCall(const slang::ast::SubroutineSymbol *subroutine,
1425 FunctionLowering *lowering,
1426 moore::ClassHandleType actualHandleTy, Value actualThisRef,
1427 SmallVector<Value> &arguments,
1428 SmallVector<Type> &resultTypes) {
1429
1430 // Get the expected receiver type from the lowered method
1431 auto funcTy = lowering->op.getFunctionType();
1432 auto expected0 = funcTy.getInput(0);
1433 auto expectedHdlTy = cast<moore::ClassHandleType>(expected0);
1434
1435 // Upcast the handle as necessary.
1436 auto implicitThisRef = context.materializeConversion(
1437 expectedHdlTy, actualThisRef, false, actualThisRef.getLoc());
1438
1439 // Build an argument list where the this reference is the first argument.
1440 SmallVector<Value> explicitArguments;
1441 explicitArguments.reserve(arguments.size() + 1);
1442 explicitArguments.push_back(implicitThisRef);
1443 explicitArguments.append(arguments.begin(), arguments.end());
1444
1445 // Method call: choose direct vs virtual.
1446 const bool isVirtual =
1447 (subroutine->flags & slang::ast::MethodFlags::Virtual) != 0;
1448
1449 if (!isVirtual) {
1450 auto calleeSym = lowering->op.getSymName();
1451 // Direct (non-virtual) call -> moore.class.call
1452 return mlir::func::CallOp::create(builder, loc, resultTypes, calleeSym,
1453 explicitArguments);
1454 }
1455
1456 auto funcName = subroutine->name;
1457 auto method = moore::VTableLoadMethodOp::create(
1458 builder, loc, funcTy, actualThisRef,
1459 SymbolRefAttr::get(context.getContext(), funcName));
1460 return mlir::func::CallIndirectOp::create(builder, loc, method,
1461 explicitArguments);
1462 }
1463
1464 /// Handle subroutine calls.
1465 Value visitCall(const slang::ast::CallExpression &expr,
1466 const slang::ast::SubroutineSymbol *subroutine) {
1467
1468 const bool isMethod = (subroutine->thisVar != nullptr);
1469
1470 auto *lowering = context.declareFunction(*subroutine);
1471 if (!lowering)
1472 return {};
1473 auto convertedFunction = context.convertFunction(*subroutine);
1474 if (failed(convertedFunction))
1475 return {};
1476
1477 // Convert the call arguments. Input arguments are converted to an rvalue.
1478 // All other arguments are converted to lvalues and passed into the function
1479 // by reference.
1480 SmallVector<Value> arguments;
1481 for (auto [callArg, declArg] :
1482 llvm::zip(expr.arguments(), subroutine->getArguments())) {
1483
1484 // Unpack the `<expr> = EmptyArgument` pattern emitted by Slang for output
1485 // and inout arguments.
1486 auto *expr = callArg;
1487 if (const auto *assign = expr->as_if<slang::ast::AssignmentExpression>())
1488 expr = &assign->left();
1489
1490 Value value;
1491 auto type = context.convertType(declArg->getType());
1492 if (declArg->direction == slang::ast::ArgumentDirection::In) {
1493 value = context.convertRvalueExpression(*expr, type);
1494 } else {
1495 Value lvalue = context.convertLvalueExpression(*expr);
1496 auto unpackedType = dyn_cast<moore::UnpackedType>(type);
1497 if (!unpackedType)
1498 return {};
1499 value =
1500 context.materializeConversion(moore::RefType::get(unpackedType),
1501 lvalue, expr->type->isSigned(), loc);
1502 }
1503 if (!value)
1504 return {};
1505 arguments.push_back(value);
1506 }
1507
1508 if (!lowering->isConverting && !lowering->captures.empty()) {
1509 auto materializeCaptureAtCall = [&](Value cap) -> Value {
1510 // Captures are expected to be moore::RefType.
1511 auto refTy = dyn_cast<moore::RefType>(cap.getType());
1512 if (!refTy) {
1513 lowering->op.emitError(
1514 "expected captured value to be moore::RefType");
1515 return {};
1516 }
1517
1518 // Expected case: the capture stems from a variable of any parent
1519 // scope. We need to walk up, since definition might be a couple regions
1520 // up.
1521 Region *capRegion = [&]() -> Region * {
1522 if (auto ba = dyn_cast<BlockArgument>(cap))
1523 return ba.getOwner()->getParent();
1524 if (auto *def = cap.getDefiningOp())
1525 return def->getParentRegion();
1526 return nullptr;
1527 }();
1528
1529 Region *callRegion =
1530 builder.getBlock() ? builder.getBlock()->getParent() : nullptr;
1531
1532 for (Region *r = callRegion; r; r = r->getParentRegion()) {
1533 if (r == capRegion) {
1534 // Safe to use the SSA value directly here.
1535 return cap;
1536 }
1537 }
1538
1539 // Otherwise we can’t legally rematerialize this capture here.
1540 lowering->op.emitError()
1541 << "cannot materialize captured ref at call site; non-symbol "
1542 << "source: "
1543 << (cap.getDefiningOp()
1544 ? cap.getDefiningOp()->getName().getStringRef()
1545 : "<block-arg>");
1546 return {};
1547 };
1548
1549 for (Value cap : lowering->captures) {
1550 Value mat = materializeCaptureAtCall(cap);
1551 if (!mat)
1552 return {};
1553 arguments.push_back(mat);
1554 }
1555 }
1556
1557 // Determine result types from the declared/converted func op.
1558 SmallVector<Type> resultTypes(
1559 lowering->op.getFunctionType().getResults().begin(),
1560 lowering->op.getFunctionType().getResults().end());
1561
1562 mlir::CallOpInterface callOp;
1563 if (isMethod) {
1564 // Class functions -> build func.call / func.indirect_call with implicit
1565 // this argument
1566 auto [thisRef, tyHandle] = getMethodReceiverTypeHandle(expr);
1567 callOp = buildMethodCall(subroutine, lowering, tyHandle, thisRef,
1568 arguments, resultTypes);
1569 } else {
1570 // Free function -> func.call
1571 callOp =
1572 mlir::func::CallOp::create(builder, loc, lowering->op, arguments);
1573 }
1574
1575 auto result = resultTypes.size() > 0 ? callOp->getOpResult(0) : Value{};
1576 // For calls to void functions we need to have a value to return from this
1577 // function. Create a dummy `unrealized_conversion_cast`, which will get
1578 // deleted again later on.
1579 if (resultTypes.size() == 0)
1580 return mlir::UnrealizedConversionCastOp::create(
1581 builder, loc, moore::VoidType::get(context.getContext()),
1582 ValueRange{})
1583 .getResult(0);
1584
1585 return result;
1586 }
1587
1588 /// Handle system calls.
1589 Value visitCall(const slang::ast::CallExpression &expr,
1590 const slang::ast::CallExpression::SystemCallInfo &info) {
1591 const auto &subroutine = *info.subroutine;
1592
1593 // $rose, $fell, $stable, $changed, and $past are only valid in
1594 // the context of properties and assertions. Those are treated in the
1595 // LTLDialect; treat them there instead.
1596 bool isAssertionCall = llvm::StringSwitch<bool>(subroutine.name)
1597 .Cases({"$rose", "$fell", "$stable", "$changed",
1598 "$past", "$sampled"},
1599 true)
1600 .Default(false);
1601
1602 if (isAssertionCall)
1603 return context.convertAssertionCallExpression(expr, info, loc);
1604
1605 auto args = expr.arguments();
1606
1607 FailureOr<Value> result = Value{};
1608 Value value;
1609 Value value2;
1610
1611 // $sformatf() and $sformat look like system tasks, but we handle string
1612 // formatting differently from expression evaluation, so handle them
1613 // separately.
1614 // According to IEEE 1800-2023 Section 21.3.3 "Formatting data to a
1615 // string" $sformatf works just like the string formatting but returns
1616 // a StringType.
1617 if (!subroutine.name.compare("$sformatf")) {
1618 // Create the FormatString
1619 auto fmtValue = context.convertFormatString(
1620 expr.arguments(), loc, moore::IntFormat::Decimal, false);
1621 if (failed(fmtValue))
1622 return {};
1623 return fmtValue.value();
1624 }
1625
1626 // Queue ops take their parameter as a reference
1627 bool isByRefOp = args.size() >= 1 && args[0]->type->isQueue();
1628
1629 // Call the conversion function with the appropriate arity. These return one
1630 // of the following:
1631 //
1632 // - `failure()` if the system call was recognized but some error occurred
1633 // - `Value{}` if the system call was not recognized
1634 // - non-null `Value` result otherwise
1635 switch (args.size()) {
1636 case (0):
1637 result = context.convertSystemCallArity0(subroutine, loc);
1638 break;
1639
1640 case (1):
1641 value = isByRefOp ? context.convertLvalueExpression(*args[0])
1642 : context.convertRvalueExpression(*args[0]);
1643 if (!value)
1644 return {};
1645 result = context.convertSystemCallArity1(subroutine, loc, value);
1646 break;
1647
1648 case (2):
1649 value = isByRefOp ? context.convertLvalueExpression(*args[0])
1650 : context.convertRvalueExpression(*args[0]);
1651 value2 = context.convertRvalueExpression(*args[1]);
1652 if (!value || !value2)
1653 return {};
1654 result = context.convertSystemCallArity2(subroutine, loc, value, value2);
1655 break;
1656
1657 default:
1658 break;
1659 }
1660
1661 // If we have recognized the system call but the conversion has encountered
1662 // and already reported an error, simply return the usual null `Value` to
1663 // indicate failure.
1664 if (failed(result))
1665 return {};
1666
1667 // If we have recognized the system call and got a non-null `Value` result,
1668 // return that.
1669 if (*result) {
1670 auto ty = context.convertType(*expr.type);
1671 return context.materializeConversion(ty, *result, expr.type->isSigned(),
1672 loc);
1673 }
1674
1675 // Otherwise we didn't recognize the system call.
1676 mlir::emitError(loc) << "unsupported system call `" << subroutine.name
1677 << "`";
1678 return {};
1679 }
1680
1681 /// Handle string literals.
1682 Value visit(const slang::ast::StringLiteral &expr) {
1683 auto type = context.convertType(*expr.type);
1684 return moore::ConstantStringOp::create(builder, loc, type, expr.getValue());
1685 }
1686
1687 /// Handle real literals.
1688 Value visit(const slang::ast::RealLiteral &expr) {
1689 auto fTy = mlir::Float64Type::get(context.getContext());
1690 auto attr = mlir::FloatAttr::get(fTy, expr.getValue());
1691 return moore::ConstantRealOp::create(builder, loc, attr).getResult();
1692 }
1693
1694 /// Helper function to convert RValues at creation of a new Struct, Array or
1695 /// Int.
1696 FailureOr<SmallVector<Value>>
1697 convertElements(const slang::ast::AssignmentPatternExpressionBase &expr,
1698 std::variant<Type, ArrayRef<Type>> expectedTypes,
1699 unsigned replCount) {
1700 const auto &elts = expr.elements();
1701 const size_t elementCount = elts.size();
1702
1703 // Inspect the variant.
1704 const bool hasBroadcast =
1705 std::holds_alternative<Type>(expectedTypes) &&
1706 static_cast<bool>(std::get<Type>(expectedTypes)); // non-null Type
1707
1708 const bool hasPerElem =
1709 std::holds_alternative<ArrayRef<Type>>(expectedTypes) &&
1710 !std::get<ArrayRef<Type>>(expectedTypes).empty();
1711
1712 // If per-element types are provided, enforce arity.
1713 if (hasPerElem) {
1714 auto types = std::get<ArrayRef<Type>>(expectedTypes);
1715 if (types.size() != elementCount) {
1716 mlir::emitError(loc)
1717 << "assignment pattern arity mismatch: expected " << types.size()
1718 << " elements, got " << elementCount;
1719 return failure();
1720 }
1721 }
1722
1723 SmallVector<Value> converted;
1724 converted.reserve(elementCount * std::max(1u, replCount));
1725
1726 // Convert each element heuristically, no type is expected
1727 if (!hasBroadcast && !hasPerElem) {
1728 // No expected type info.
1729 for (const auto *elementExpr : elts) {
1730 Value v = context.convertRvalueExpression(*elementExpr);
1731 if (!v)
1732 return failure();
1733 converted.push_back(v);
1734 }
1735 } else if (hasBroadcast) {
1736 // Same expected type for all elements.
1737 Type want = std::get<Type>(expectedTypes);
1738 for (const auto *elementExpr : elts) {
1739 Value v = want ? context.convertRvalueExpression(*elementExpr, want)
1740 : context.convertRvalueExpression(*elementExpr);
1741 if (!v)
1742 return failure();
1743 converted.push_back(v);
1744 }
1745 } else { // hasPerElem, individual type is expected for each element
1746 auto types = std::get<ArrayRef<Type>>(expectedTypes);
1747 for (size_t i = 0; i < elementCount; ++i) {
1748 Type want = types[i];
1749 const auto *elementExpr = elts[i];
1750 Value v = want ? context.convertRvalueExpression(*elementExpr, want)
1751 : context.convertRvalueExpression(*elementExpr);
1752 if (!v)
1753 return failure();
1754 converted.push_back(v);
1755 }
1756 }
1757
1758 for (unsigned i = 1; i < replCount; ++i)
1759 converted.append(converted.begin(), converted.begin() + elementCount);
1760
1761 return converted;
1762 }
1763
1764 /// Handle assignment patterns.
1765 Value visitAssignmentPattern(
1766 const slang::ast::AssignmentPatternExpressionBase &expr,
1767 unsigned replCount = 1) {
1768 auto type = context.convertType(*expr.type);
1769 const auto &elts = expr.elements();
1770
1771 // Handle integers.
1772 if (auto intType = dyn_cast<moore::IntType>(type)) {
1773 auto elements = convertElements(expr, {}, replCount);
1774
1775 if (failed(elements))
1776 return {};
1777
1778 assert(intType.getWidth() == elements->size());
1779 std::reverse(elements->begin(), elements->end());
1780 return moore::ConcatOp::create(builder, loc, intType, *elements);
1781 }
1782
1783 // Handle packed structs.
1784 if (auto structType = dyn_cast<moore::StructType>(type)) {
1785 SmallVector<Type> expectedTy;
1786 expectedTy.reserve(structType.getMembers().size());
1787 for (auto member : structType.getMembers())
1788 expectedTy.push_back(member.type);
1789
1790 FailureOr<SmallVector<Value>> elements;
1791 if (expectedTy.size() == elts.size())
1792 elements = convertElements(expr, expectedTy, replCount);
1793 else
1794 elements = convertElements(expr, {}, replCount);
1795
1796 if (failed(elements))
1797 return {};
1798
1799 assert(structType.getMembers().size() == elements->size());
1800 return moore::StructCreateOp::create(builder, loc, structType, *elements);
1801 }
1802
1803 // Handle unpacked structs.
1804 if (auto structType = dyn_cast<moore::UnpackedStructType>(type)) {
1805 SmallVector<Type> expectedTy;
1806 expectedTy.reserve(structType.getMembers().size());
1807 for (auto member : structType.getMembers())
1808 expectedTy.push_back(member.type);
1809
1810 FailureOr<SmallVector<Value>> elements;
1811 if (expectedTy.size() == elts.size())
1812 elements = convertElements(expr, expectedTy, replCount);
1813 else
1814 elements = convertElements(expr, {}, replCount);
1815
1816 if (failed(elements))
1817 return {};
1818
1819 assert(structType.getMembers().size() == elements->size());
1820
1821 return moore::StructCreateOp::create(builder, loc, structType, *elements);
1822 }
1823
1824 // Handle packed arrays.
1825 if (auto arrayType = dyn_cast<moore::ArrayType>(type)) {
1826 auto elements =
1827 convertElements(expr, arrayType.getElementType(), replCount);
1828
1829 if (failed(elements))
1830 return {};
1831
1832 assert(arrayType.getSize() == elements->size());
1833 return moore::ArrayCreateOp::create(builder, loc, arrayType, *elements);
1834 }
1835
1836 // Handle unpacked arrays.
1837 if (auto arrayType = dyn_cast<moore::UnpackedArrayType>(type)) {
1838 auto elements =
1839 convertElements(expr, arrayType.getElementType(), replCount);
1840
1841 if (failed(elements))
1842 return {};
1843
1844 assert(arrayType.getSize() == elements->size());
1845 return moore::ArrayCreateOp::create(builder, loc, arrayType, *elements);
1846 }
1847
1848 mlir::emitError(loc) << "unsupported assignment pattern with type " << type;
1849 return {};
1850 }
1851
1852 Value visit(const slang::ast::SimpleAssignmentPatternExpression &expr) {
1853 return visitAssignmentPattern(expr);
1854 }
1855
1856 Value visit(const slang::ast::StructuredAssignmentPatternExpression &expr) {
1857 return visitAssignmentPattern(expr);
1858 }
1859
1860 Value visit(const slang::ast::ReplicatedAssignmentPatternExpression &expr) {
1861 auto count =
1862 context.evaluateConstant(expr.count()).integer().as<unsigned>();
1863 assert(count && "Slang guarantees constant non-zero replication count");
1864 return visitAssignmentPattern(expr, *count);
1865 }
1866
1867 Value visit(const slang::ast::StreamingConcatenationExpression &expr) {
1868 SmallVector<Value> operands;
1869 for (auto stream : expr.streams()) {
1870 auto operandLoc = context.convertLocation(stream.operand->sourceRange);
1871 if (!stream.constantWithWidth.has_value() && stream.withExpr) {
1872 mlir::emitError(operandLoc)
1873 << "Moore only support streaming "
1874 "concatenation with fixed size 'with expression'";
1875 return {};
1876 }
1877 Value value;
1878 if (stream.constantWithWidth.has_value()) {
1879 value = context.convertRvalueExpression(*stream.withExpr);
1880 auto type = cast<moore::UnpackedType>(value.getType());
1881 auto intType = moore::IntType::get(
1882 context.getContext(), type.getBitSize().value(), type.getDomain());
1883 // Do not care if it's signed, because we will not do expansion.
1884 value = context.materializeConversion(intType, value, false, loc);
1885 } else {
1886 value = context.convertRvalueExpression(*stream.operand);
1887 }
1888
1889 value = context.convertToSimpleBitVector(value);
1890 if (!value)
1891 return {};
1892 operands.push_back(value);
1893 }
1894 Value value;
1895
1896 if (operands.size() == 1) {
1897 // There must be at least one element, otherwise slang will report an
1898 // error.
1899 value = operands.front();
1900 } else {
1901 value = moore::ConcatOp::create(builder, loc, operands).getResult();
1902 }
1903
1904 if (expr.getSliceSize() == 0) {
1905 return value;
1906 }
1907
1908 auto type = cast<moore::IntType>(value.getType());
1909 SmallVector<Value> slicedOperands;
1910 auto iterMax = type.getWidth() / expr.getSliceSize();
1911 auto remainSize = type.getWidth() % expr.getSliceSize();
1912
1913 for (size_t i = 0; i < iterMax; i++) {
1914 auto extractResultType = moore::IntType::get(
1915 context.getContext(), expr.getSliceSize(), type.getDomain());
1916
1917 auto extracted = moore::ExtractOp::create(builder, loc, extractResultType,
1918 value, i * expr.getSliceSize());
1919 slicedOperands.push_back(extracted);
1920 }
1921 // Handle other wire
1922 if (remainSize) {
1923 auto extractResultType = moore::IntType::get(
1924 context.getContext(), remainSize, type.getDomain());
1925
1926 auto extracted =
1927 moore::ExtractOp::create(builder, loc, extractResultType, value,
1928 iterMax * expr.getSliceSize());
1929 slicedOperands.push_back(extracted);
1930 }
1931
1932 return moore::ConcatOp::create(builder, loc, slicedOperands);
1933 }
1934
1935 Value visit(const slang::ast::AssertionInstanceExpression &expr) {
1936 return context.convertAssertionExpression(expr.body, loc);
1937 }
1938
1939 // A new class expression can stand for one of two things:
1940 // 1) A call to the `new` method (ctor) of a class made outside the scope of
1941 // the class
1942 // 2) A call to the `super.new` method, i.e. the constructor of the base
1943 // class, within the scope of a class, more specifically, within the new
1944 // method override of a class.
1945 // In the first case we should emit an allocation and a call to the ctor if it
1946 // exists (it's optional in System Verilog), in the second case we should emit
1947 // a call to the parent's ctor (System Verilog only has single inheritance, so
1948 // super is always unambiguous), but no allocation, as the child class' new
1949 // invocation already allocated space for both its own and its parent's
1950 // properties.
1951 Value visit(const slang::ast::NewClassExpression &expr) {
1952 auto type = context.convertType(*expr.type);
1953 auto classTy = dyn_cast<moore::ClassHandleType>(type);
1954 Value newObj;
1955
1956 // We are calling new from within a new function, and it's pointing to
1957 // super. Check the implicit this ref to figure out the super class type.
1958 // Do not allocate a new object.
1959 if (!classTy && expr.isSuperClass) {
1960 newObj = context.getImplicitThisRef();
1961 if (!newObj || !newObj.getType() ||
1962 !isa<moore::ClassHandleType>(newObj.getType())) {
1963 mlir::emitError(loc) << "implicit this ref was not set while "
1964 "converting new class function";
1965 return {};
1966 }
1967 auto thisType = cast<moore::ClassHandleType>(newObj.getType());
1968 auto classDecl =
1969 cast<moore::ClassDeclOp>(*context.symbolTable.lookupNearestSymbolFrom(
1970 context.intoModuleOp, thisType.getClassSym()));
1971 auto baseClassSym = classDecl.getBase();
1972 classTy = circt::moore::ClassHandleType::get(context.getContext(),
1973 baseClassSym.value());
1974 } else {
1975 // We are calling from outside a class; allocate space for the object.
1976 newObj = moore::ClassNewOp::create(builder, loc, classTy, {});
1977 }
1978
1979 const auto *constructor = expr.constructorCall();
1980 // If there's no ctor, we are done.
1981 if (!constructor)
1982 return newObj;
1983
1984 if (const auto *callConstructor =
1985 constructor->as_if<slang::ast::CallExpression>())
1986 if (const auto *subroutine =
1987 std::get_if<const slang::ast::SubroutineSymbol *>(
1988 &callConstructor->subroutine)) {
1989 // Bit paranoid, but virtually free checks that new is a class method
1990 // and the subroutine has already been converted.
1991 if (!(*subroutine)->thisVar) {
1992 mlir::emitError(loc) << "Expected subroutine called by new to use an "
1993 "implicit this reference";
1994 return {};
1995 }
1996 if (failed(context.convertFunction(**subroutine)))
1997 return {};
1998 // Pass the newObj as the implicit this argument of the ctor.
1999 auto savedThis = context.currentThisRef;
2000 context.currentThisRef = newObj;
2001 llvm::scope_exit restoreThis(
2002 [&] { context.currentThisRef = savedThis; });
2003 // Emit a call to ctor
2004 if (!visitCall(*callConstructor, *subroutine))
2005 return {};
2006 // Return new handle
2007 return newObj;
2008 }
2009 return {};
2010 }
2011
2012 /// Emit an error for all other expressions.
2013 template <typename T>
2014 Value visit(T &&node) {
2015 mlir::emitError(loc, "unsupported expression: ")
2016 << slang::ast::toString(node.kind);
2017 return {};
2018 }
2019
2020 Value visitInvalid(const slang::ast::Expression &expr) {
2021 mlir::emitError(loc, "invalid expression");
2022 return {};
2023 }
2024};
2025} // namespace
2026
2027//===----------------------------------------------------------------------===//
2028// Lvalue Conversion
2029//===----------------------------------------------------------------------===//
2030
2031namespace {
2032struct LvalueExprVisitor : public ExprVisitor {
2033 LvalueExprVisitor(Context &context, Location loc)
2034 : ExprVisitor(context, loc, /*isLvalue=*/true) {}
2035 using ExprVisitor::visit;
2036
2037 // Handle named values, such as references to declared variables.
2038 Value visit(const slang::ast::NamedValueExpression &expr) {
2039 // Handle local variables.
2040 if (auto value = context.valueSymbols.lookup(&expr.symbol))
2041 return value;
2042
2043 // Handle global variables.
2044 if (auto globalOp = context.globalVariables.lookup(&expr.symbol))
2045 return moore::GetGlobalVariableOp::create(builder, loc, globalOp);
2046
2047 if (auto *const property =
2048 expr.symbol.as_if<slang::ast::ClassPropertySymbol>()) {
2049 return visitClassProperty(context, *property);
2050 }
2051
2052 auto d = mlir::emitError(loc, "unknown name `") << expr.symbol.name << "`";
2053 d.attachNote(context.convertLocation(expr.symbol.location))
2054 << "no lvalue generated for " << slang::ast::toString(expr.symbol.kind);
2055 return {};
2056 }
2057
2058 // Handle hierarchical values, such as `Top.sub.var = x`.
2059 Value visit(const slang::ast::HierarchicalValueExpression &expr) {
2060 // Handle local variables.
2061 if (auto value = context.valueSymbols.lookup(&expr.symbol))
2062 return value;
2063
2064 // Handle global variables.
2065 if (auto globalOp = context.globalVariables.lookup(&expr.symbol))
2066 return moore::GetGlobalVariableOp::create(builder, loc, globalOp);
2067
2068 // Emit an error for those hierarchical values not recorded in the
2069 // `valueSymbols`.
2070 auto d = mlir::emitError(loc, "unknown hierarchical name `")
2071 << expr.symbol.name << "`";
2072 d.attachNote(context.convertLocation(expr.symbol.location))
2073 << "no lvalue generated for " << slang::ast::toString(expr.symbol.kind);
2074 return {};
2075 }
2076
2077 Value visit(const slang::ast::StreamingConcatenationExpression &expr) {
2078 SmallVector<Value> operands;
2079 for (auto stream : expr.streams()) {
2080 auto operandLoc = context.convertLocation(stream.operand->sourceRange);
2081 if (!stream.constantWithWidth.has_value() && stream.withExpr) {
2082 mlir::emitError(operandLoc)
2083 << "Moore only support streaming "
2084 "concatenation with fixed size 'with expression'";
2085 return {};
2086 }
2087 Value value;
2088 if (stream.constantWithWidth.has_value()) {
2089 value = context.convertLvalueExpression(*stream.withExpr);
2090 auto type = cast<moore::UnpackedType>(
2091 cast<moore::RefType>(value.getType()).getNestedType());
2092 auto intType = moore::RefType::get(moore::IntType::get(
2093 context.getContext(), type.getBitSize().value(), type.getDomain()));
2094 // Do not care if it's signed, because we will not do expansion.
2095 value = context.materializeConversion(intType, value, false, loc);
2096 } else {
2097 value = context.convertLvalueExpression(*stream.operand);
2098 }
2099
2100 if (!value)
2101 return {};
2102 operands.push_back(value);
2103 }
2104 Value value;
2105 if (operands.size() == 1) {
2106 // There must be at least one element, otherwise slang will report an
2107 // error.
2108 value = operands.front();
2109 } else {
2110 value = moore::ConcatRefOp::create(builder, loc, operands).getResult();
2111 }
2112
2113 if (expr.getSliceSize() == 0) {
2114 return value;
2115 }
2116
2117 auto type = cast<moore::IntType>(
2118 cast<moore::RefType>(value.getType()).getNestedType());
2119 SmallVector<Value> slicedOperands;
2120 auto widthSum = type.getWidth();
2121 auto domain = type.getDomain();
2122 auto iterMax = widthSum / expr.getSliceSize();
2123 auto remainSize = widthSum % expr.getSliceSize();
2124
2125 for (size_t i = 0; i < iterMax; i++) {
2126 auto extractResultType = moore::RefType::get(moore::IntType::get(
2127 context.getContext(), expr.getSliceSize(), domain));
2128
2129 auto extracted = moore::ExtractRefOp::create(
2130 builder, loc, extractResultType, value, i * expr.getSliceSize());
2131 slicedOperands.push_back(extracted);
2132 }
2133 // Handle other wire
2134 if (remainSize) {
2135 auto extractResultType = moore::RefType::get(
2136 moore::IntType::get(context.getContext(), remainSize, domain));
2137
2138 auto extracted =
2139 moore::ExtractRefOp::create(builder, loc, extractResultType, value,
2140 iterMax * expr.getSliceSize());
2141 slicedOperands.push_back(extracted);
2142 }
2143
2144 return moore::ConcatRefOp::create(builder, loc, slicedOperands);
2145 }
2146
2147 /// Emit an error for all other expressions.
2148 template <typename T>
2149 Value visit(T &&node) {
2150 return context.convertRvalueExpression(node);
2151 }
2152
2153 Value visitInvalid(const slang::ast::Expression &expr) {
2154 mlir::emitError(loc, "invalid expression");
2155 return {};
2156 }
2157};
2158} // namespace
2159
2160//===----------------------------------------------------------------------===//
2161// Entry Points
2162//===----------------------------------------------------------------------===//
2163
2164Value Context::convertRvalueExpression(const slang::ast::Expression &expr,
2165 Type requiredType) {
2166 auto loc = convertLocation(expr.sourceRange);
2167 auto value = expr.visit(RvalueExprVisitor(*this, loc));
2168 if (value && requiredType)
2169 value =
2170 materializeConversion(requiredType, value, expr.type->isSigned(), loc);
2171 return value;
2172}
2173
2174Value Context::convertLvalueExpression(const slang::ast::Expression &expr) {
2175 auto loc = convertLocation(expr.sourceRange);
2176 return expr.visit(LvalueExprVisitor(*this, loc));
2177}
2178// NOLINTEND(misc-no-recursion)
2179
2180/// Helper function to convert a value to its "truthy" boolean value.
2181Value Context::convertToBool(Value value) {
2182 if (!value)
2183 return {};
2184 if (auto type = dyn_cast_or_null<moore::IntType>(value.getType()))
2185 if (type.getBitSize() == 1)
2186 return value;
2187 if (auto type = dyn_cast_or_null<moore::UnpackedType>(value.getType()))
2188 return moore::BoolCastOp::create(builder, value.getLoc(), value);
2189 mlir::emitError(value.getLoc(), "expression of type ")
2190 << value.getType() << " cannot be cast to a boolean";
2191 return {};
2192}
2193
2194/// Materialize a Slang real literal as a constant op.
2195Value Context::materializeSVReal(const slang::ConstantValue &svreal,
2196 const slang::ast::Type &astType,
2197 Location loc) {
2198 const auto *floatType = astType.as_if<slang::ast::FloatingType>();
2199 assert(floatType);
2200
2201 FloatAttr attr;
2202 if (svreal.isShortReal() &&
2203 floatType->floatKind == slang::ast::FloatingType::ShortReal) {
2204 attr = FloatAttr::get(builder.getF32Type(), svreal.shortReal().v);
2205 } else if (svreal.isReal() &&
2206 floatType->floatKind == slang::ast::FloatingType::Real) {
2207 attr = FloatAttr::get(builder.getF64Type(), svreal.real().v);
2208 } else {
2209 mlir::emitError(loc) << "invalid real constant";
2210 return {};
2211 }
2212
2213 return moore::ConstantRealOp::create(builder, loc, attr);
2214}
2215
2216/// Materialize a Slang string literal as a literal string constant op.
2217Value Context::materializeString(const slang::ConstantValue &stringLiteral,
2218 const slang::ast::Type &astType,
2219 Location loc) {
2220 slang::ConstantValue intVal = stringLiteral.convertToInt();
2221 auto effectiveWidth = intVal.getEffectiveWidth();
2222 if (!effectiveWidth)
2223 return {};
2224
2225 auto intTy = moore::IntType::getInt(getContext(), effectiveWidth.value());
2226
2227 if (astType.isString()) {
2228 auto immInt = moore::ConstantStringOp::create(builder, loc, intTy,
2229 stringLiteral.toString())
2230 .getResult();
2231 return moore::IntToStringOp::create(builder, loc, immInt).getResult();
2232 }
2233 return {};
2234}
2235
2236/// Materialize a Slang integer literal as a constant op.
2237Value Context::materializeSVInt(const slang::SVInt &svint,
2238 const slang::ast::Type &astType, Location loc) {
2239 auto type = convertType(astType);
2240 if (!type)
2241 return {};
2242
2243 bool typeIsFourValued = false;
2244 if (auto unpackedType = dyn_cast<moore::UnpackedType>(type))
2245 typeIsFourValued = unpackedType.getDomain() == moore::Domain::FourValued;
2246
2247 auto fvint = convertSVIntToFVInt(svint);
2248 auto intType = moore::IntType::get(getContext(), fvint.getBitWidth(),
2249 fvint.hasUnknown() || typeIsFourValued
2252 auto result = moore::ConstantOp::create(builder, loc, intType, fvint);
2253 return materializeConversion(type, result, astType.isSigned(), loc);
2254}
2255
2257 const slang::ConstantValue &constant,
2258 const slang::ast::FixedSizeUnpackedArrayType &astType, Location loc) {
2259
2260 auto type = convertType(astType);
2261 if (!type)
2262 return {};
2263
2264 // Check whether underlying type is an integer, if so, get bit width
2265 unsigned bitWidth;
2266 if (astType.elementType.isIntegral())
2267 bitWidth = astType.elementType.getBitWidth();
2268 else
2269 return {};
2270
2271 bool typeIsFourValued = false;
2272
2273 // Check whether the underlying type is four-valued
2274 if (auto unpackedType = dyn_cast<moore::UnpackedType>(type))
2275 typeIsFourValued = unpackedType.getDomain() == moore::Domain::FourValued;
2276 else
2277 return {};
2278
2279 auto domain =
2281
2282 // Construct the integer type this is an unpacked array of; if possible keep
2283 // it two-valued, unless any entry is four-valued or the underlying type is
2284 // four-valued
2285 auto intType = moore::IntType::get(getContext(), bitWidth, domain);
2286 // Construct the full array type from intType
2287 auto arrType = moore::UnpackedArrayType::get(
2288 getContext(), constant.elements().size(), intType);
2289
2290 llvm::SmallVector<mlir::Value> elemVals;
2291 moore::ConstantOp constOp;
2292
2293 mlir::OpBuilder::InsertionGuard guard(builder);
2294
2295 // Add one ConstantOp for every element in the array
2296 for (auto elem : constant.elements()) {
2297 FVInt fvInt = convertSVIntToFVInt(elem.integer());
2298 constOp = moore::ConstantOp::create(builder, loc, intType, fvInt);
2299 elemVals.push_back(constOp.getResult());
2300 }
2301
2302 // Take the result of each ConstantOp and concatenate them into an array (of
2303 // constant values).
2304 auto arrayOp = moore::ArrayCreateOp::create(builder, loc, arrType, elemVals);
2305
2306 return arrayOp.getResult();
2307}
2308
2309Value Context::materializeConstant(const slang::ConstantValue &constant,
2310 const slang::ast::Type &type, Location loc) {
2311
2312 if (auto *arr = type.as_if<slang::ast::FixedSizeUnpackedArrayType>())
2313 return materializeFixedSizeUnpackedArrayType(constant, *arr, loc);
2314 if (constant.isInteger())
2315 return materializeSVInt(constant.integer(), type, loc);
2316 if (constant.isReal() || constant.isShortReal())
2317 return materializeSVReal(constant, type, loc);
2318 if (constant.isString())
2319 return materializeString(constant, type, loc);
2320
2321 return {};
2322}
2323
2324slang::ConstantValue
2325Context::evaluateConstant(const slang::ast::Expression &expr) {
2326 using slang::ast::EvalFlags;
2327 slang::ast::EvalContext evalContext(
2328 slang::ast::ASTContext(compilation.getRoot(),
2329 slang::ast::LookupLocation::max),
2330 EvalFlags::CacheResults | EvalFlags::SpecparamsAllowed);
2331 return expr.eval(evalContext);
2332}
2333
2334/// Helper function to convert a value to its "truthy" boolean value and
2335/// convert it to the given domain.
2336Value Context::convertToBool(Value value, Domain domain) {
2337 value = convertToBool(value);
2338 if (!value)
2339 return {};
2340 auto type = moore::IntType::get(getContext(), 1, domain);
2341 return materializeConversion(type, value, false, value.getLoc());
2342}
2343
2345 if (!value)
2346 return {};
2347 if (isa<moore::IntType>(value.getType()))
2348 return value;
2349
2350 // Some operations in Slang's AST, for example bitwise or `|`, don't cast
2351 // packed struct/array operands to simple bit vectors but directly operate
2352 // on the struct/array. Since the corresponding IR ops operate only on
2353 // simple bit vectors, insert a conversion in this case.
2354 if (auto packed = dyn_cast<moore::PackedType>(value.getType()))
2355 if (auto sbvType = packed.getSimpleBitVector())
2356 return materializeConversion(sbvType, value, false, value.getLoc());
2357
2358 mlir::emitError(value.getLoc()) << "expression of type " << value.getType()
2359 << " cannot be cast to a simple bit vector";
2360 return {};
2361}
2362
2363/// Create the necessary operations to convert from a `PackedType` to the
2364/// corresponding simple bit vector `IntType`. This will apply special handling
2365/// to time values, which requires scaling by the local timescale.
2367 Location loc) {
2368 if (isa<moore::IntType>(value.getType()))
2369 return value;
2370
2371 auto &builder = context.builder;
2372 auto packedType = cast<moore::PackedType>(value.getType());
2373 auto intType = packedType.getSimpleBitVector();
2374 assert(intType);
2375
2376 // If we are converting from a time to an integer, divide the integer by the
2377 // timescale.
2378 if (isa<moore::TimeType>(packedType) &&
2380 value = builder.createOrFold<moore::TimeToLogicOp>(loc, value);
2381 auto scale = moore::ConstantOp::create(builder, loc, intType,
2383 return builder.createOrFold<moore::DivUOp>(loc, value, scale);
2384 }
2385
2386 // If this is an aggregate type, make sure that it does not contain any
2387 // `TimeType` fields. These require special conversion to ensure that the
2388 // local timescale is in effect.
2389 if (packedType.containsTimeType()) {
2390 mlir::emitError(loc) << "unsupported conversion: " << packedType
2391 << " cannot be converted to " << intType
2392 << "; contains a time type";
2393 return {};
2394 }
2395
2396 // Otherwise create a simple `PackedToSBVOp` for the conversion.
2397 return builder.createOrFold<moore::PackedToSBVOp>(loc, value);
2398}
2399
2400/// Create the necessary operations to convert from a simple bit vector
2401/// `IntType` to an equivalent `PackedType`. This will apply special handling to
2402/// time values, which requires scaling by the local timescale.
2404 moore::PackedType packedType,
2405 Value value, Location loc) {
2406 if (value.getType() == packedType)
2407 return value;
2408
2409 auto &builder = context.builder;
2410 auto intType = cast<moore::IntType>(value.getType());
2411 assert(intType && intType == packedType.getSimpleBitVector());
2412
2413 // If we are converting from an integer to a time, multiply the integer by the
2414 // timescale.
2415 if (isa<moore::TimeType>(packedType) &&
2417 auto scale = moore::ConstantOp::create(builder, loc, intType,
2419 value = builder.createOrFold<moore::MulOp>(loc, value, scale);
2420 return builder.createOrFold<moore::LogicToTimeOp>(loc, value);
2421 }
2422
2423 // If this is an aggregate type, make sure that it does not contain any
2424 // `TimeType` fields. These require special conversion to ensure that the
2425 // local timescale is in effect.
2426 if (packedType.containsTimeType()) {
2427 mlir::emitError(loc) << "unsupported conversion: " << intType
2428 << " cannot be converted to " << packedType
2429 << "; contains a time type";
2430 return {};
2431 }
2432
2433 // Otherwise create a simple `PackedToSBVOp` for the conversion.
2434 return builder.createOrFold<moore::SBVToPackedOp>(loc, packedType, value);
2435}
2436
2437/// Check whether the actual handle is a subclass of another handle type
2438/// and return a properly upcast version if so.
2439static mlir::Value maybeUpcastHandle(Context &context, mlir::Value actualHandle,
2440 moore::ClassHandleType expectedHandleTy) {
2441 auto loc = actualHandle.getLoc();
2442
2443 auto actualTy = actualHandle.getType();
2444 auto actualHandleTy = dyn_cast<moore::ClassHandleType>(actualTy);
2445 if (!actualHandleTy) {
2446 mlir::emitError(loc) << "expected a !moore.class<...> value, got "
2447 << actualTy;
2448 return {};
2449 }
2450
2451 // Fast path: already the expected handle type.
2452 if (actualHandleTy == expectedHandleTy)
2453 return actualHandle;
2454
2455 if (!context.isClassDerivedFrom(actualHandleTy, expectedHandleTy)) {
2456 mlir::emitError(loc)
2457 << "receiver class " << actualHandleTy.getClassSym()
2458 << " is not the same as, or derived from, expected base class "
2459 << expectedHandleTy.getClassSym().getRootReference();
2460 return {};
2461 }
2462
2463 // Only implicit upcasting is allowed - down casting should never be implicit.
2464 auto casted = moore::ClassUpcastOp::create(context.builder, loc,
2465 expectedHandleTy, actualHandle)
2466 .getResult();
2467 return casted;
2468}
2469
2470Value Context::materializeConversion(Type type, Value value, bool isSigned,
2471 Location loc) {
2472 // Nothing to do if the types are already equal.
2473 if (type == value.getType())
2474 return value;
2475
2476 // Handle packed types which can be converted to a simple bit vector. This
2477 // allows us to perform resizing and domain casting on that bit vector.
2478 auto dstPacked = dyn_cast<moore::PackedType>(type);
2479 auto srcPacked = dyn_cast<moore::PackedType>(value.getType());
2480 auto dstInt = dstPacked ? dstPacked.getSimpleBitVector() : moore::IntType();
2481 auto srcInt = srcPacked ? srcPacked.getSimpleBitVector() : moore::IntType();
2482
2483 if (dstInt && srcInt) {
2484 // Convert the value to a simple bit vector if it isn't one already.
2485 value = materializePackedToSBVConversion(*this, value, loc);
2486 if (!value)
2487 return {};
2488
2489 // Create truncation or sign/zero extension ops depending on the source and
2490 // destination width.
2491 auto resizedType = moore::IntType::get(
2492 value.getContext(), dstInt.getWidth(), srcPacked.getDomain());
2493 if (dstInt.getWidth() < srcInt.getWidth()) {
2494 value = builder.createOrFold<moore::TruncOp>(loc, resizedType, value);
2495 } else if (dstInt.getWidth() > srcInt.getWidth()) {
2496 if (isSigned)
2497 value = builder.createOrFold<moore::SExtOp>(loc, resizedType, value);
2498 else
2499 value = builder.createOrFold<moore::ZExtOp>(loc, resizedType, value);
2500 }
2501
2502 // Convert the domain if needed.
2503 if (dstInt.getDomain() != srcInt.getDomain()) {
2504 if (dstInt.getDomain() == moore::Domain::TwoValued)
2505 value = builder.createOrFold<moore::LogicToIntOp>(loc, value);
2506 else if (dstInt.getDomain() == moore::Domain::FourValued)
2507 value = builder.createOrFold<moore::IntToLogicOp>(loc, value);
2508 }
2509
2510 // Convert the value from a simple bit vector back to the packed type.
2511 value = materializeSBVToPackedConversion(*this, dstPacked, value, loc);
2512 if (!value)
2513 return {};
2514
2515 assert(value.getType() == type);
2516 return value;
2517 }
2518
2519 // Convert from FormatStringType to StringType
2520 if (isa<moore::StringType>(type) &&
2521 isa<moore::FormatStringType>(value.getType())) {
2522 return builder.createOrFold<moore::FormatStringToStringOp>(loc, value);
2523 }
2524
2525 // Convert from StringType to FormatStringType
2526 if (isa<moore::FormatStringType>(type) &&
2527 isa<moore::StringType>(value.getType())) {
2528 return builder.createOrFold<moore::FormatStringOp>(loc, value);
2529 }
2530
2531 // Handle Real To Int conversion
2532 if (isa<moore::IntType>(type) && isa<moore::RealType>(value.getType())) {
2533 auto twoValInt = builder.createOrFold<moore::RealToIntOp>(
2534 loc, dyn_cast<moore::IntType>(type).getTwoValued(), value);
2535
2536 if (dyn_cast<moore::IntType>(type).getDomain() == moore::Domain::FourValued)
2537 return materializePackedToSBVConversion(*this, twoValInt, loc);
2538 return twoValInt;
2539 }
2540
2541 // Handle Int to Real conversion
2542 if (isa<moore::RealType>(type) && isa<moore::IntType>(value.getType())) {
2543 Value twoValInt;
2544 // Check if int needs to be converted to two-valued first
2545 if (dyn_cast<moore::IntType>(value.getType()).getDomain() ==
2547 twoValInt = value;
2548 else
2549 twoValInt = materializeConversion(
2550 dyn_cast<moore::IntType>(value.getType()).getTwoValued(), value, true,
2551 loc);
2552
2553 if (isSigned)
2554 return builder.createOrFold<moore::SIntToRealOp>(loc, type, twoValInt);
2555 return builder.createOrFold<moore::UIntToRealOp>(loc, type, twoValInt);
2556 }
2557
2558 auto getBuiltinFloatType = [&](moore::RealType type) -> Type {
2559 if (type.getWidth() == moore::RealWidth::f32)
2560 return mlir::Float32Type::get(builder.getContext());
2561
2562 return mlir::Float64Type::get(builder.getContext());
2563 };
2564
2565 // Handle f64/f32 to time conversion
2566 if (isa<moore::TimeType>(type) && isa<moore::RealType>(value.getType())) {
2567 auto intType =
2568 moore::IntType::get(builder.getContext(), 64, Domain::TwoValued);
2569 Type floatType =
2570 getBuiltinFloatType(cast<moore::RealType>(value.getType()));
2571 auto scale = moore::ConstantRealOp::create(
2572 builder, loc, value.getType(),
2573 FloatAttr::get(floatType, getTimeScaleInFemtoseconds(*this)));
2574 auto scaled = builder.createOrFold<moore::MulRealOp>(loc, value, scale);
2575 auto asInt = moore::RealToIntOp::create(builder, loc, intType, scaled);
2576 auto asLogic = moore::IntToLogicOp::create(builder, loc, asInt);
2577 return moore::LogicToTimeOp::create(builder, loc, asLogic);
2578 }
2579
2580 // Handle time to f64/f32 conversion
2581 if (isa<moore::RealType>(type) && isa<moore::TimeType>(value.getType())) {
2582 auto asLogic = moore::TimeToLogicOp::create(builder, loc, value);
2583 auto asInt = moore::LogicToIntOp::create(builder, loc, asLogic);
2584 auto asReal = moore::UIntToRealOp::create(builder, loc, type, asInt);
2585 Type floatType = getBuiltinFloatType(cast<moore::RealType>(type));
2586 auto scale = moore::ConstantRealOp::create(
2587 builder, loc, type,
2588 FloatAttr::get(floatType, getTimeScaleInFemtoseconds(*this)));
2589 return moore::DivRealOp::create(builder, loc, asReal, scale);
2590 }
2591
2592 // Handle Int to String
2593 if (isa<moore::StringType>(type)) {
2594 if (auto intType = dyn_cast<moore::IntType>(value.getType())) {
2595 if (intType.getDomain() == moore::Domain::FourValued)
2596 value = moore::LogicToIntOp::create(builder, loc, value);
2597 return moore::IntToStringOp::create(builder, loc, value);
2598 }
2599 }
2600
2601 // Handle String to Int
2602 if (auto intType = dyn_cast<moore::IntType>(type)) {
2603 if (isa<moore::StringType>(value.getType())) {
2604 value = moore::StringToIntOp::create(builder, loc, intType.getTwoValued(),
2605 value);
2606
2607 if (intType.getDomain() == moore::Domain::FourValued)
2608 return moore::IntToLogicOp::create(builder, loc, value);
2609
2610 return value;
2611 }
2612 }
2613
2614 // Handle Int to FormatString
2615 if (isa<moore::FormatStringType>(type)) {
2616 auto asStr = materializeConversion(moore::StringType::get(getContext()),
2617 value, isSigned, loc);
2618 if (!asStr)
2619 return {};
2620 return moore::FormatStringOp::create(builder, loc, asStr, {}, {}, {});
2621 }
2622
2623 if (isa<moore::RealType>(type) && isa<moore::RealType>(value.getType()))
2624 return builder.createOrFold<moore::ConvertRealOp>(loc, type, value);
2625
2626 if (isa<moore::ClassHandleType>(type) &&
2627 isa<moore::ClassHandleType>(value.getType()))
2628 return maybeUpcastHandle(*this, value, cast<moore::ClassHandleType>(type));
2629
2630 // TODO: Handle other conversions with dedicated ops.
2631 if (value.getType() != type)
2632 value = moore::ConversionOp::create(builder, loc, type, value);
2633 return value;
2634}
2635
2636FailureOr<Value>
2637Context::convertSystemCallArity0(const slang::ast::SystemSubroutine &subroutine,
2638 Location loc) {
2639
2640 auto systemCallRes =
2641 llvm::StringSwitch<std::function<FailureOr<Value>()>>(subroutine.name)
2642 .Case("$urandom",
2643 [&]() -> Value {
2644 return moore::UrandomBIOp::create(builder, loc, nullptr);
2645 })
2646 .Case("$random",
2647 [&]() -> Value {
2648 return moore::RandomBIOp::create(builder, loc, nullptr);
2649 })
2650 .Case(
2651 "$time",
2652 [&]() -> Value { return moore::TimeBIOp::create(builder, loc); })
2653 .Case(
2654 "$stime",
2655 [&]() -> Value { return moore::TimeBIOp::create(builder, loc); })
2656 .Case(
2657 "$realtime",
2658 [&]() -> Value { return moore::TimeBIOp::create(builder, loc); })
2659 .Default([&]() -> Value { return {}; });
2660 return systemCallRes();
2661}
2662
2663FailureOr<Value>
2664Context::convertSystemCallArity1(const slang::ast::SystemSubroutine &subroutine,
2665 Location loc, Value value) {
2666 auto systemCallRes =
2667 llvm::StringSwitch<std::function<FailureOr<Value>()>>(subroutine.name)
2668 // Signed and unsigned system functions.
2669 .Case("$signed", [&]() { return value; })
2670 .Case("$unsigned", [&]() { return value; })
2671
2672 // Math functions in SystemVerilog.
2673 .Case("$clog2",
2674 [&]() -> FailureOr<Value> {
2675 value = convertToSimpleBitVector(value);
2676 if (!value)
2677 return failure();
2678 return (Value)moore::Clog2BIOp::create(builder, loc, value);
2679 })
2680 .Case("$ln",
2681 [&]() -> Value {
2682 return moore::LnBIOp::create(builder, loc, value);
2683 })
2684 .Case("$log10",
2685 [&]() -> Value {
2686 return moore::Log10BIOp::create(builder, loc, value);
2687 })
2688 .Case("$sin",
2689 [&]() -> Value {
2690 return moore::SinBIOp::create(builder, loc, value);
2691 })
2692 .Case("$cos",
2693 [&]() -> Value {
2694 return moore::CosBIOp::create(builder, loc, value);
2695 })
2696 .Case("$tan",
2697 [&]() -> Value {
2698 return moore::TanBIOp::create(builder, loc, value);
2699 })
2700 .Case("$exp",
2701 [&]() -> Value {
2702 return moore::ExpBIOp::create(builder, loc, value);
2703 })
2704 .Case("$sqrt",
2705 [&]() -> Value {
2706 return moore::SqrtBIOp::create(builder, loc, value);
2707 })
2708 .Case("$floor",
2709 [&]() -> Value {
2710 return moore::FloorBIOp::create(builder, loc, value);
2711 })
2712 .Case("$ceil",
2713 [&]() -> Value {
2714 return moore::CeilBIOp::create(builder, loc, value);
2715 })
2716 .Case("$asin",
2717 [&]() -> Value {
2718 return moore::AsinBIOp::create(builder, loc, value);
2719 })
2720 .Case("$acos",
2721 [&]() -> Value {
2722 return moore::AcosBIOp::create(builder, loc, value);
2723 })
2724 .Case("$atan",
2725 [&]() -> Value {
2726 return moore::AtanBIOp::create(builder, loc, value);
2727 })
2728 .Case("$sinh",
2729 [&]() -> Value {
2730 return moore::SinhBIOp::create(builder, loc, value);
2731 })
2732 .Case("$cosh",
2733 [&]() -> Value {
2734 return moore::CoshBIOp::create(builder, loc, value);
2735 })
2736 .Case("$tanh",
2737 [&]() -> Value {
2738 return moore::TanhBIOp::create(builder, loc, value);
2739 })
2740 .Case("$asinh",
2741 [&]() -> Value {
2742 return moore::AsinhBIOp::create(builder, loc, value);
2743 })
2744 .Case("$acosh",
2745 [&]() -> Value {
2746 return moore::AcoshBIOp::create(builder, loc, value);
2747 })
2748 .Case("$atanh",
2749 [&]() -> Value {
2750 return moore::AtanhBIOp::create(builder, loc, value);
2751 })
2752 .Case("$urandom",
2753 [&]() -> Value {
2754 return moore::UrandomBIOp::create(builder, loc, value);
2755 })
2756 .Case("$random",
2757 [&]() -> Value {
2758 return moore::RandomBIOp::create(builder, loc, value);
2759 })
2760 .Case("$urandom_range",
2761 [&]() -> Value {
2762 return moore::UrandomrangeBIOp::create(builder, loc, value,
2763 nullptr);
2764 })
2765 .Case("$realtobits",
2766 [&]() -> Value {
2767 return moore::RealtobitsBIOp::create(builder, loc, value);
2768 })
2769 .Case("$bitstoreal",
2770 [&]() -> Value {
2771 return moore::BitstorealBIOp::create(builder, loc, value);
2772 })
2773 .Case("$shortrealtobits",
2774 [&]() -> Value {
2775 return moore::ShortrealtobitsBIOp::create(builder, loc,
2776 value);
2777 })
2778 .Case("$bitstoshortreal",
2779 [&]() -> Value {
2780 return moore::BitstoshortrealBIOp::create(builder, loc,
2781 value);
2782 })
2783 .Case("len",
2784 [&]() -> Value {
2785 if (isa<moore::StringType>(value.getType()))
2786 return moore::StringLenOp::create(builder, loc, value);
2787 return {};
2788 })
2789 .Case("toupper",
2790 [&]() -> Value {
2791 return moore::StringToUpperOp::create(builder, loc, value);
2792 })
2793 .Case(
2794 "size",
2795 [&]() -> Value {
2796 if (isa<moore::RefType>(value.getType()) &&
2797 isa<moore::QueueType>(
2798 cast<moore::RefType>(value.getType()).getNestedType()))
2799 return moore::QueueSizeBIOp::create(builder, loc, value);
2800 return {};
2801 })
2802 .Case("tolower",
2803 [&]() -> Value {
2804 return moore::StringToLowerOp::create(builder, loc, value);
2805 })
2806 .Case(
2807 "pop_back",
2808 [&]() -> Value {
2809 if (isa<moore::RefType>(value.getType()) &&
2810 isa<moore::QueueType>(
2811 cast<moore::RefType>(value.getType()).getNestedType()))
2812 return moore::QueuePopBackOp::create(builder, loc, value);
2813
2814 return {};
2815 })
2816 .Case(
2817 "pop_front",
2818 [&]() -> Value {
2819 if (isa<moore::RefType>(value.getType()) &&
2820 isa<moore::QueueType>(
2821 cast<moore::RefType>(value.getType()).getNestedType()))
2822 return moore::QueuePopFrontOp::create(builder, loc, value);
2823 return {};
2824 })
2825 .Default([&]() -> Value { return {}; });
2826 return systemCallRes();
2827}
2828
2829FailureOr<Value>
2830Context::convertSystemCallArity2(const slang::ast::SystemSubroutine &subroutine,
2831 Location loc, Value value1, Value value2) {
2832 auto systemCallRes =
2833 llvm::StringSwitch<std::function<FailureOr<Value>()>>(subroutine.name)
2834 .Case("getc",
2835 [&]() -> Value {
2836 return moore::StringGetCOp::create(builder, loc, value1,
2837 value2);
2838 })
2839 .Case("$urandom_range",
2840 [&]() -> Value {
2841 return moore::UrandomrangeBIOp::create(builder, loc, value1,
2842 value2);
2843 })
2844 .Default([&]() -> Value { return {}; });
2845 return systemCallRes();
2846}
2847
2848// Resolve any (possibly nested) SymbolRefAttr to an op from the root.
2849static mlir::Operation *resolve(Context &context, mlir::SymbolRefAttr sym) {
2850 return context.symbolTable.lookupNearestSymbolFrom(context.intoModuleOp, sym);
2851}
2852
2853bool Context::isClassDerivedFrom(const moore::ClassHandleType &actualTy,
2854 const moore::ClassHandleType &baseTy) {
2855 if (!actualTy || !baseTy)
2856 return false;
2857
2858 mlir::SymbolRefAttr actualSym = actualTy.getClassSym();
2859 mlir::SymbolRefAttr baseSym = baseTy.getClassSym();
2860
2861 if (actualSym == baseSym)
2862 return true;
2863
2864 auto *op = resolve(*this, actualSym);
2865 auto decl = llvm::dyn_cast_or_null<moore::ClassDeclOp>(op);
2866 // Walk up the inheritance chain via ClassDeclOp::$base (SymbolRefAttr).
2867 while (decl) {
2868 mlir::SymbolRefAttr curBase = decl.getBaseAttr();
2869 if (!curBase)
2870 break;
2871 if (curBase == baseSym)
2872 return true;
2873 decl = llvm::dyn_cast_or_null<moore::ClassDeclOp>(resolve(*this, curBase));
2874 }
2875 return false;
2876}
2877
2878moore::ClassHandleType
2879Context::getAncestorClassWithProperty(const moore::ClassHandleType &actualTy,
2880 llvm::StringRef fieldName, Location loc) {
2881 // Start at the actual class symbol.
2882 mlir::SymbolRefAttr classSym = actualTy.getClassSym();
2883
2884 while (classSym) {
2885 // Resolve the class declaration from the root symbol table owner.
2886 auto *op = resolve(*this, classSym);
2887 auto decl = llvm::dyn_cast_or_null<moore::ClassDeclOp>(op);
2888 if (!decl)
2889 break;
2890
2891 // Scan the class body for a property with the requested symbol name.
2892 for (auto &block : decl.getBody()) {
2893 for (auto &opInBlock : block) {
2894 if (auto prop =
2895 llvm::dyn_cast<moore::ClassPropertyDeclOp>(&opInBlock)) {
2896 if (prop.getSymName() == fieldName) {
2897 // Found a declaring ancestor: return its handle type.
2898 return moore::ClassHandleType::get(actualTy.getContext(), classSym);
2899 }
2900 }
2901 }
2902 }
2903
2904 // Not found here—climb to the base class (if any) and continue.
2905 classSym = decl.getBaseAttr(); // may be null; loop ends if so
2906 }
2907
2908 // No ancestor declares that property.
2909 mlir::emitError(loc) << "unknown property `" << fieldName << "`";
2910 return {};
2911}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static Value materializeSBVToPackedConversion(Context &context, moore::PackedType packedType, Value value, Location loc)
Create the necessary operations to convert from a simple bit vector IntType to an equivalent PackedTy...
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 visitClassProperty(Context &context, const slang::ast::ClassPropertySymbol &expr)
static uint64_t getTimeScaleInFemtoseconds(Context &context)
Get the currently active timescale as an integer number of femtoseconds.
static Value materializePackedToSBVConversion(Context &context, Value value, Location loc)
Create the necessary operations to convert from a PackedType to the corresponding simple bit vector I...
static Value getSelectIndex(Context &context, Location loc, Value index, const slang::ConstantRange &range)
Map an index into an array, with bounds range, to a bit offset of the underlying bit storage.
static FVInt convertSVIntToFVInt(const slang::SVInt &svint)
Convert a Slang SVInt to a CIRCT FVInt.
Four-valued arbitrary precision integers.
Definition FVInt.h:37
A packed SystemVerilog type.
Definition MooreTypes.h:153
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.
void info(Twine message)
Definition LSPUtils.cpp:20
Domain
The number of values each bit of a type can assume.
Definition MooreTypes.h:49
@ 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")
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
A helper class to facilitate the conversion from a Slang AST to MLIR operations.
Value materializeConversion(Type type, Value value, bool isSigned, Location loc)
Helper function to insert the necessary operations to cast a value from one type to another.
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.
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.
bool isClassDerivedFrom(const moore::ClassHandleType &actualTy, const moore::ClassHandleType &baseTy)
Checks whether one class (actualTy) is derived from another class (baseTy).
Type convertType(const slang::ast::Type &type, LocationAttr loc={})
Convert a slang type into an MLIR type.
Definition Types.cpp:206
Value materializeSVInt(const slang::SVInt &svint, const slang::ast::Type &type, Location loc)
Helper function to materialize an SVInt as an SSA value.
Value materializeSVReal(const slang::ConstantValue &svreal, const slang::ast::Type &type, Location loc)
Helper function to materialize a real value as an SSA value.
Value convertToBool(Value value)
Helper function to convert a value to its "truthy" boolean value.
moore::ClassHandleType getAncestorClassWithProperty(const moore::ClassHandleType &actualTy, StringRef fieldName, Location loc)
Tries to find the closest base class of actualTy that carries a property with name fieldName.
Value convertRvalueExpression(const slang::ast::Expression &expr, Type requiredType={})
FailureOr< Value > convertSystemCallArity0(const slang::ast::SystemSubroutine &subroutine, Location loc)
Convert system function calls only have arity-0.
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.
FailureOr< Value > convertSystemCallArity1(const slang::ast::SystemSubroutine &subroutine, Location loc, Value value)
Convert system function calls only have arity-1.
MLIRContext * getContext()
Return the MLIR context.
FailureOr< Value > convertSystemCallArity2(const slang::ast::SystemSubroutine &subroutine, Location loc, Value value1, Value value2)
Convert system function calls with arity-2.
Location convertLocation(slang::SourceLocation loc)
Convert a slang SourceLocation into an MLIR Location.