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