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