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