CIRCT 22.0.0git
Loading...
Searching...
No Matches
Structure.cpp
Go to the documentation of this file.
1//===- Structure.cpp - Slang hierarchy 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/Compilation.h"
11#include "slang/ast/symbols/ClassSymbols.h"
12#include "llvm/ADT/ScopeExit.h"
13
14using namespace circt;
15using namespace ImportVerilog;
16
17//===----------------------------------------------------------------------===//
18// Utilities
19//===----------------------------------------------------------------------===//
20
21static void guessNamespacePrefix(const slang::ast::Symbol &symbol,
22 SmallString<64> &prefix) {
23 if (symbol.kind != slang::ast::SymbolKind::Package)
24 return;
25 guessNamespacePrefix(symbol.getParentScope()->asSymbol(), prefix);
26 if (!symbol.name.empty()) {
27 prefix += symbol.name;
28 prefix += "::";
29 }
30}
31
32//===----------------------------------------------------------------------===//
33// Base Visitor
34//===----------------------------------------------------------------------===//
35
36namespace {
37/// Base visitor which ignores AST nodes that are handled by Slang's name
38/// resolution and type checking.
39struct BaseVisitor {
40 Context &context;
41 Location loc;
42 OpBuilder &builder;
43
44 BaseVisitor(Context &context, Location loc)
45 : context(context), loc(loc), builder(context.builder) {}
46
47 // Skip semicolons.
48 LogicalResult visit(const slang::ast::EmptyMemberSymbol &) {
49 return success();
50 }
51
52 // Skip members that are implicitly imported from some other scope for the
53 // sake of name resolution, such as enum variant names.
54 LogicalResult visit(const slang::ast::TransparentMemberSymbol &) {
55 return success();
56 }
57
58 // Handle classes without parameters or specialized generic classes
59 LogicalResult visit(const slang::ast::ClassType &classdecl) {
60 return context.convertClassDeclaration(classdecl);
61 }
62
63 // GenericClassDefSymbol represents parameterized (template) classes, which
64 // per IEEE 1800-2023 ยง8.25 are abstract and not instantiable. Slang models
65 // concrete specializations as ClassType, so we skip GenericClassDefSymbol
66 // entirely.
67 LogicalResult visit(const slang::ast::GenericClassDefSymbol &) {
68 return success();
69 }
70
71 // Skip typedefs.
72 LogicalResult visit(const slang::ast::TypeAliasType &) { return success(); }
73 LogicalResult visit(const slang::ast::ForwardingTypedefSymbol &) {
74 return success();
75 }
76
77 // Skip imports. The AST already has its names resolved.
78 LogicalResult visit(const slang::ast::ExplicitImportSymbol &) {
79 return success();
80 }
81 LogicalResult visit(const slang::ast::WildcardImportSymbol &) {
82 return success();
83 }
84
85 // Skip type parameters. The Slang AST is already monomorphized.
86 LogicalResult visit(const slang::ast::TypeParameterSymbol &) {
87 return success();
88 }
89
90 // Skip elaboration system tasks. These are reported directly by Slang.
91 LogicalResult visit(const slang::ast::ElabSystemTaskSymbol &) {
92 return success();
93 }
94
95 // Handle parameters.
96 LogicalResult visit(const slang::ast::ParameterSymbol &param) {
97 visitParameter(param);
98 return success();
99 }
100
101 LogicalResult visit(const slang::ast::SpecparamSymbol &param) {
102 visitParameter(param);
103 return success();
104 }
105
106 template <class Node>
107 void visitParameter(const Node &param) {
108 // If debug info is enabled, try to materialize the parameter's constant
109 // value on a best-effort basis and create a `dbg.variable` to track the
110 // value.
111 if (!context.options.debugInfo)
112 return;
113 auto value =
114 context.materializeConstant(param.getValue(), param.getType(), loc);
115 if (!value)
116 return;
117 if (builder.getInsertionBlock()->getParentOp() == context.intoModuleOp)
118 context.orderedRootOps.insert({param.location, value.getDefiningOp()});
119
120 // Prefix the parameter name with the surrounding namespace to create
121 // somewhat sane names in the IR.
122 SmallString<64> paramName;
123 guessNamespacePrefix(param.getParentScope()->asSymbol(), paramName);
124 paramName += param.name;
125
126 debug::VariableOp::create(builder, loc, builder.getStringAttr(paramName),
127 value, Value{});
128 }
129};
130} // namespace
131
132//===----------------------------------------------------------------------===//
133// Top-Level Item Conversion
134//===----------------------------------------------------------------------===//
135
136namespace {
137struct RootVisitor : public BaseVisitor {
138 using BaseVisitor::BaseVisitor;
139 using BaseVisitor::visit;
140
141 // Handle packages.
142 LogicalResult visit(const slang::ast::PackageSymbol &package) {
143 return context.convertPackage(package);
144 }
145
146 // Handle functions and tasks.
147 LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
148 return context.convertFunction(subroutine);
149 }
150
151 // Handle global variables.
152 LogicalResult visit(const slang::ast::VariableSymbol &var) {
153 return context.convertGlobalVariable(var);
154 }
155
156 // Emit an error for all other members.
157 template <typename T>
158 LogicalResult visit(T &&node) {
159 mlir::emitError(loc, "unsupported construct: ")
160 << slang::ast::toString(node.kind);
161 return failure();
162 }
163};
164} // namespace
165
166//===----------------------------------------------------------------------===//
167// Package Conversion
168//===----------------------------------------------------------------------===//
169
170namespace {
171struct PackageVisitor : public BaseVisitor {
172 using BaseVisitor::BaseVisitor;
173 using BaseVisitor::visit;
174
175 // Handle functions and tasks.
176 LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
177 return context.convertFunction(subroutine);
178 }
179
180 // Handle global variables.
181 LogicalResult visit(const slang::ast::VariableSymbol &var) {
182 return context.convertGlobalVariable(var);
183 }
184
185 /// Emit an error for all other members.
186 template <typename T>
187 LogicalResult visit(T &&node) {
188 mlir::emitError(loc, "unsupported package member: ")
189 << slang::ast::toString(node.kind);
190 return failure();
191 }
192};
193} // namespace
194
195//===----------------------------------------------------------------------===//
196// Module Conversion
197//===----------------------------------------------------------------------===//
198
199static moore::ProcedureKind
200convertProcedureKind(slang::ast::ProceduralBlockKind kind) {
201 switch (kind) {
202 case slang::ast::ProceduralBlockKind::Always:
203 return moore::ProcedureKind::Always;
204 case slang::ast::ProceduralBlockKind::AlwaysComb:
205 return moore::ProcedureKind::AlwaysComb;
206 case slang::ast::ProceduralBlockKind::AlwaysLatch:
207 return moore::ProcedureKind::AlwaysLatch;
208 case slang::ast::ProceduralBlockKind::AlwaysFF:
209 return moore::ProcedureKind::AlwaysFF;
210 case slang::ast::ProceduralBlockKind::Initial:
211 return moore::ProcedureKind::Initial;
212 case slang::ast::ProceduralBlockKind::Final:
213 return moore::ProcedureKind::Final;
214 }
215 llvm_unreachable("all procedure kinds handled");
216}
217
218static moore::NetKind convertNetKind(slang::ast::NetType::NetKind kind) {
219 switch (kind) {
220 case slang::ast::NetType::Supply0:
221 return moore::NetKind::Supply0;
222 case slang::ast::NetType::Supply1:
223 return moore::NetKind::Supply1;
224 case slang::ast::NetType::Tri:
225 return moore::NetKind::Tri;
226 case slang::ast::NetType::TriAnd:
227 return moore::NetKind::TriAnd;
228 case slang::ast::NetType::TriOr:
229 return moore::NetKind::TriOr;
230 case slang::ast::NetType::TriReg:
231 return moore::NetKind::TriReg;
232 case slang::ast::NetType::Tri0:
233 return moore::NetKind::Tri0;
234 case slang::ast::NetType::Tri1:
235 return moore::NetKind::Tri1;
236 case slang::ast::NetType::UWire:
237 return moore::NetKind::UWire;
238 case slang::ast::NetType::Wire:
239 return moore::NetKind::Wire;
240 case slang::ast::NetType::WAnd:
241 return moore::NetKind::WAnd;
242 case slang::ast::NetType::WOr:
243 return moore::NetKind::WOr;
244 case slang::ast::NetType::Interconnect:
245 return moore::NetKind::Interconnect;
246 case slang::ast::NetType::UserDefined:
247 return moore::NetKind::UserDefined;
248 case slang::ast::NetType::Unknown:
249 return moore::NetKind::Unknown;
250 }
251 llvm_unreachable("all net kinds handled");
252}
253
254namespace {
255struct ModuleVisitor : public BaseVisitor {
256 using BaseVisitor::visit;
257
258 // A prefix of block names such as `foo.bar.` to put in front of variable and
259 // instance names.
260 StringRef blockNamePrefix;
261
262 ModuleVisitor(Context &context, Location loc, StringRef blockNamePrefix = "")
263 : BaseVisitor(context, loc), blockNamePrefix(blockNamePrefix) {}
264
265 // Skip ports which are already handled by the module itself.
266 LogicalResult visit(const slang::ast::PortSymbol &) { return success(); }
267 LogicalResult visit(const slang::ast::MultiPortSymbol &) { return success(); }
268
269 // Skip genvars.
270 LogicalResult visit(const slang::ast::GenvarSymbol &genvarNode) {
271 return success();
272 }
273
274 // Skip defparams which have been handled by slang.
275 LogicalResult visit(const slang::ast::DefParamSymbol &) { return success(); }
276
277 // Ignore type parameters. These have already been handled by Slang's type
278 // checking.
279 LogicalResult visit(const slang::ast::TypeParameterSymbol &) {
280 return success();
281 }
282
283 // Handle instances.
284 LogicalResult visit(const slang::ast::InstanceSymbol &instNode) {
285 using slang::ast::ArgumentDirection;
286 using slang::ast::AssignmentExpression;
287 using slang::ast::MultiPortSymbol;
288 using slang::ast::PortSymbol;
289
290 auto *moduleLowering = context.convertModuleHeader(&instNode.body);
291 if (!moduleLowering)
292 return failure();
293 auto module = moduleLowering->op;
294 auto moduleType = module.getModuleType();
295
296 // Set visibility attribute for instantiated module.
297 SymbolTable::setSymbolVisibility(module, SymbolTable::Visibility::Private);
298
299 // Prepare the values that are involved in port connections. This creates
300 // rvalues for input ports and appropriate lvalues for output, inout, and
301 // ref ports. We also separate multi-ports into the individual underlying
302 // ports with their corresponding connection.
304 portValues.reserve(moduleType.getNumPorts());
305
306 for (const auto *con : instNode.getPortConnections()) {
307 const auto *expr = con->getExpression();
308
309 // Handle unconnected behavior. The expression is null if it have no
310 // connection for the port.
311 if (!expr) {
312 auto *port = con->port.as_if<PortSymbol>();
313 if (auto *existingPort =
314 moduleLowering->portsBySyntaxNode.lookup(port->getSyntax()))
315 port = existingPort;
316
317 switch (port->direction) {
318 case ArgumentDirection::In: {
319 auto refType = moore::RefType::get(
320 cast<moore::UnpackedType>(context.convertType(port->getType())));
321
322 if (const auto *net =
323 port->internalSymbol->as_if<slang::ast::NetSymbol>()) {
324 auto netOp = moore::NetOp::create(
325 builder, loc, refType,
326 StringAttr::get(builder.getContext(), net->name),
327 convertNetKind(net->netType.netKind), nullptr);
328 auto readOp = moore::ReadOp::create(builder, loc, netOp);
329 portValues.insert({port, readOp});
330 } else if (const auto *var =
331 port->internalSymbol
332 ->as_if<slang::ast::VariableSymbol>()) {
333 auto varOp = moore::VariableOp::create(
334 builder, loc, refType,
335 StringAttr::get(builder.getContext(), var->name), nullptr);
336 auto readOp = moore::ReadOp::create(builder, loc, varOp);
337 portValues.insert({port, readOp});
338 } else {
339 return mlir::emitError(loc)
340 << "unsupported internal symbol for unconnected port `"
341 << port->name << "`";
342 }
343 continue;
344 }
345
346 // No need to express unconnected behavior for output port, skip to the
347 // next iteration of the loop.
348 case ArgumentDirection::Out:
349 continue;
350
351 // TODO: Mark Inout port as unsupported and it will be supported later.
352 default:
353 return mlir::emitError(loc)
354 << "unsupported port `" << port->name << "` ("
355 << slang::ast::toString(port->kind) << ")";
356 }
357 }
358
359 // Unpack the `<expr> = EmptyArgument` pattern emitted by Slang for
360 // output and inout ports.
361 if (const auto *assign = expr->as_if<AssignmentExpression>())
362 expr = &assign->left();
363
364 // Regular ports lower the connected expression to an lvalue or rvalue and
365 // either attach it to the instance as an operand (for input, inout, and
366 // ref ports), or assign an instance output to it (for output ports).
367 if (auto *port = con->port.as_if<PortSymbol>()) {
368 // Convert as rvalue for inputs, lvalue for all others.
369 auto value = (port->direction == ArgumentDirection::In)
370 ? context.convertRvalueExpression(*expr)
371 : context.convertLvalueExpression(*expr);
372 if (!value)
373 return failure();
374 if (auto *existingPort =
375 moduleLowering->portsBySyntaxNode.lookup(con->port.getSyntax()))
376 port = existingPort;
377 portValues.insert({port, value});
378 continue;
379 }
380
381 // Multi-ports lower the connected expression to an lvalue and then slice
382 // it up into multiple sub-values, one for each of the ports in the
383 // multi-port.
384 if (const auto *multiPort = con->port.as_if<MultiPortSymbol>()) {
385 // Convert as lvalue.
386 auto value = context.convertLvalueExpression(*expr);
387 if (!value)
388 return failure();
389 unsigned offset = 0;
390 for (const auto *port : llvm::reverse(multiPort->ports)) {
391 if (auto *existingPort = moduleLowering->portsBySyntaxNode.lookup(
392 con->port.getSyntax()))
393 port = existingPort;
394 unsigned width = port->getType().getBitWidth();
395 auto sliceType = context.convertType(port->getType());
396 if (!sliceType)
397 return failure();
398 Value slice = moore::ExtractRefOp::create(
399 builder, loc,
400 moore::RefType::get(cast<moore::UnpackedType>(sliceType)), value,
401 offset);
402 // Create the "ReadOp" for input ports.
403 if (port->direction == ArgumentDirection::In)
404 slice = moore::ReadOp::create(builder, loc, slice);
405 portValues.insert({port, slice});
406 offset += width;
407 }
408 continue;
409 }
410
411 mlir::emitError(loc) << "unsupported instance port `" << con->port.name
412 << "` (" << slang::ast::toString(con->port.kind)
413 << ")";
414 return failure();
415 }
416
417 // Match the module's ports up with the port values determined above.
418 SmallVector<Value> inputValues;
419 SmallVector<Value> outputValues;
420 inputValues.reserve(moduleType.getNumInputs());
421 outputValues.reserve(moduleType.getNumOutputs());
422
423 for (auto &port : moduleLowering->ports) {
424 auto value = portValues.lookup(&port.ast);
425 if (port.ast.direction == ArgumentDirection::Out)
426 outputValues.push_back(value);
427 else
428 inputValues.push_back(value);
429 }
430
431 // Insert conversions for input ports.
432 for (auto [value, type] :
433 llvm::zip(inputValues, moduleType.getInputTypes()))
434 // TODO: This should honor signedness in the conversion.
435 value = context.materializeConversion(type, value, false, value.getLoc());
436
437 // Here we use the hierarchical value recorded in `Context::valueSymbols`.
438 // Then we pass it as the input port with the ref<T> type of the instance.
439 for (const auto &hierPath : context.hierPaths[&instNode.body])
440 if (auto hierValue = context.valueSymbols.lookup(hierPath.valueSym);
441 hierPath.hierName && hierPath.direction == ArgumentDirection::In)
442 inputValues.push_back(hierValue);
443
444 // Create the instance op itself.
445 auto inputNames = builder.getArrayAttr(moduleType.getInputNames());
446 auto outputNames = builder.getArrayAttr(moduleType.getOutputNames());
447 auto inst = moore::InstanceOp::create(
448 builder, loc, moduleType.getOutputTypes(),
449 builder.getStringAttr(Twine(blockNamePrefix) + instNode.name),
450 FlatSymbolRefAttr::get(module.getSymNameAttr()), inputValues,
451 inputNames, outputNames);
452
453 // Record instance's results generated by hierarchical names.
454 for (const auto &hierPath : context.hierPaths[&instNode.body])
455 if (hierPath.idx && hierPath.direction == ArgumentDirection::Out)
456 context.valueSymbols.insert(hierPath.valueSym,
457 inst->getResult(*hierPath.idx));
458
459 // Assign output values from the instance to the connected expression.
460 for (auto [lvalue, output] : llvm::zip(outputValues, inst.getOutputs())) {
461 if (!lvalue)
462 continue;
463 Value rvalue = output;
464 auto dstType = cast<moore::RefType>(lvalue.getType()).getNestedType();
465 // TODO: This should honor signedness in the conversion.
466 rvalue = context.materializeConversion(dstType, rvalue, false, loc);
467 moore::ContinuousAssignOp::create(builder, loc, lvalue, rvalue);
468 }
469
470 return success();
471 }
472
473 // Handle variables.
474 LogicalResult visit(const slang::ast::VariableSymbol &varNode) {
475 auto loweredType = context.convertType(*varNode.getDeclaredType());
476 if (!loweredType)
477 return failure();
478
479 Value initial;
480 if (const auto *init = varNode.getInitializer()) {
481 initial = context.convertRvalueExpression(*init, loweredType);
482 if (!initial)
483 return failure();
484 }
485
486 auto varOp = moore::VariableOp::create(
487 builder, loc,
488 moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
489 builder.getStringAttr(Twine(blockNamePrefix) + varNode.name), initial);
490 context.valueSymbols.insert(&varNode, varOp);
491 return success();
492 }
493
494 // Handle nets.
495 LogicalResult visit(const slang::ast::NetSymbol &netNode) {
496 auto loweredType = context.convertType(*netNode.getDeclaredType());
497 if (!loweredType)
498 return failure();
499
500 Value assignment;
501 if (const auto *init = netNode.getInitializer()) {
502 assignment = context.convertRvalueExpression(*init, loweredType);
503 if (!assignment)
504 return failure();
505 }
506
507 auto netkind = convertNetKind(netNode.netType.netKind);
508 if (netkind == moore::NetKind::Interconnect ||
509 netkind == moore::NetKind::UserDefined ||
510 netkind == moore::NetKind::Unknown)
511 return mlir::emitError(loc, "unsupported net kind `")
512 << netNode.netType.name << "`";
513
514 auto netOp = moore::NetOp::create(
515 builder, loc,
516 moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
517 builder.getStringAttr(Twine(blockNamePrefix) + netNode.name), netkind,
518 assignment);
519 context.valueSymbols.insert(&netNode, netOp);
520 return success();
521 }
522
523 // Handle continuous assignments.
524 LogicalResult visit(const slang::ast::ContinuousAssignSymbol &assignNode) {
525 const auto &expr =
526 assignNode.getAssignment().as<slang::ast::AssignmentExpression>();
527 auto lhs = context.convertLvalueExpression(expr.left());
528 if (!lhs)
529 return failure();
530
531 auto rhs = context.convertRvalueExpression(
532 expr.right(), cast<moore::RefType>(lhs.getType()).getNestedType());
533 if (!rhs)
534 return failure();
535
536 // Handle delayed assignments.
537 if (auto *timingCtrl = assignNode.getDelay()) {
538 auto *ctrl = timingCtrl->as_if<slang::ast::DelayControl>();
539 assert(ctrl && "slang guarantees this to be a simple delay");
540 auto delay = context.convertRvalueExpression(
541 ctrl->expr, moore::TimeType::get(builder.getContext()));
542 if (!delay)
543 return failure();
544 moore::DelayedContinuousAssignOp::create(builder, loc, lhs, rhs, delay);
545 return success();
546 }
547
548 // Otherwise this is a regular assignment.
549 moore::ContinuousAssignOp::create(builder, loc, lhs, rhs);
550 return success();
551 }
552
553 // Handle procedures.
554 LogicalResult convertProcedure(moore::ProcedureKind kind,
555 const slang::ast::Statement &body) {
556 auto procOp = moore::ProcedureOp::create(builder, loc, kind);
557 OpBuilder::InsertionGuard guard(builder);
558 builder.setInsertionPointToEnd(&procOp.getBody().emplaceBlock());
559 Context::ValueSymbolScope scope(context.valueSymbols);
560 if (failed(context.convertStatement(body)))
561 return failure();
562 if (builder.getBlock())
563 moore::ReturnOp::create(builder, loc);
564 return success();
565 }
566
567 LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) {
568 // Detect `always @(*) <stmt>` and convert to `always_comb <stmt>` if
569 // requested by the user.
570 if (context.options.lowerAlwaysAtStarAsComb) {
571 auto *stmt = procNode.getBody().as_if<slang::ast::TimedStatement>();
572 if (procNode.procedureKind == slang::ast::ProceduralBlockKind::Always &&
573 stmt &&
574 stmt->timing.kind == slang::ast::TimingControlKind::ImplicitEvent)
575 return convertProcedure(moore::ProcedureKind::AlwaysComb, stmt->stmt);
576 }
577
578 return convertProcedure(convertProcedureKind(procNode.procedureKind),
579 procNode.getBody());
580 }
581
582 // Handle generate block.
583 LogicalResult visit(const slang::ast::GenerateBlockSymbol &genNode) {
584 // Ignore uninstantiated blocks.
585 if (genNode.isUninstantiated)
586 return success();
587
588 // If the block has a name, add it to the list of block name prefices.
589 SmallString<64> prefix = blockNamePrefix;
590 if (!genNode.name.empty() ||
591 genNode.getParentScope()->asSymbol().kind !=
592 slang::ast::SymbolKind::GenerateBlockArray) {
593 prefix += genNode.getExternalName();
594 prefix += '.';
595 }
596
597 // Visit each member of the generate block.
598 for (auto &member : genNode.members())
599 if (failed(member.visit(ModuleVisitor(context, loc, prefix))))
600 return failure();
601 return success();
602 }
603
604 // Handle generate block array.
605 LogicalResult visit(const slang::ast::GenerateBlockArraySymbol &genArrNode) {
606 // If the block has a name, add it to the list of block name prefices and
607 // prepare to append the array index and a `.` in each iteration.
608 SmallString<64> prefix = blockNamePrefix;
609 prefix += genArrNode.getExternalName();
610 prefix += '_';
611 auto prefixBaseLen = prefix.size();
612
613 // Visit each iteration entry of the generate block.
614 for (const auto *entry : genArrNode.entries) {
615 // Append the index to the prefix.
616 prefix.resize(prefixBaseLen);
617 if (entry->arrayIndex)
618 prefix += entry->arrayIndex->toString();
619 else
620 Twine(entry->constructIndex).toVector(prefix);
621 prefix += '.';
622
623 // Visit this iteration entry.
624 if (failed(entry->asSymbol().visit(ModuleVisitor(context, loc, prefix))))
625 return failure();
626 }
627 return success();
628 }
629
630 // Ignore statement block symbols. These get generated by Slang for blocks
631 // with variables and other declarations. For example, having an initial
632 // procedure with a variable declaration, such as `initial begin int x;
633 // end`, will create the procedure with a block and variable declaration as
634 // expected, but will also create a `StatementBlockSymbol` with just the
635 // variable layout _next to_ the initial procedure.
636 LogicalResult visit(const slang::ast::StatementBlockSymbol &) {
637 return success();
638 }
639
640 // Ignore sequence declarations. The declarations are already evaluated by
641 // Slang and are part of an AssertionInstance.
642 LogicalResult visit(const slang::ast::SequenceSymbol &seqNode) {
643 return success();
644 }
645
646 // Ignore property declarations. The declarations are already evaluated by
647 // Slang and are part of an AssertionInstance.
648 LogicalResult visit(const slang::ast::PropertySymbol &propNode) {
649 return success();
650 }
651
652 // Handle functions and tasks.
653 LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
654 return context.convertFunction(subroutine);
655 }
656
657 /// Emit an error for all other members.
658 template <typename T>
659 LogicalResult visit(T &&node) {
660 mlir::emitError(loc, "unsupported module member: ")
661 << slang::ast::toString(node.kind);
662 return failure();
663 }
664};
665} // namespace
666
667//===----------------------------------------------------------------------===//
668// Structure and Hierarchy Conversion
669//===----------------------------------------------------------------------===//
670
671/// Convert an entire Slang compilation to MLIR ops. This is the main entry
672/// point for the conversion.
673LogicalResult Context::convertCompilation() {
674 const auto &root = compilation.getRoot();
675
676 // Keep track of the local time scale. `getTimeScale` automatically looks
677 // through parent scopes to find the time scale effective locally.
678 auto prevTimeScale = timeScale;
679 timeScale = root.getTimeScale().value_or(slang::TimeScale());
680 auto timeScaleGuard =
681 llvm::make_scope_exit([&] { timeScale = prevTimeScale; });
682
683 // First only to visit the whole AST to collect the hierarchical names without
684 // any operation creating.
685 for (auto *inst : root.topInstances)
686 if (failed(traverseInstanceBody(inst->body)))
687 return failure();
688
689 // Visit all top-level declarations in all compilation units. This does not
690 // include instantiable constructs like modules, interfaces, and programs,
691 // which are listed separately as top instances.
692 for (auto *unit : root.compilationUnits) {
693 for (const auto &member : unit->members()) {
694 auto loc = convertLocation(member.location);
695 if (failed(member.visit(RootVisitor(*this, loc))))
696 return failure();
697 }
698 }
699
700 // Prime the root definition worklist by adding all the top-level modules.
701 SmallVector<const slang::ast::InstanceSymbol *> topInstances;
702 for (auto *inst : root.topInstances)
703 if (!convertModuleHeader(&inst->body))
704 return failure();
705
706 // Convert all the root module definitions.
707 while (!moduleWorklist.empty()) {
708 auto *module = moduleWorklist.front();
709 moduleWorklist.pop();
710 if (failed(convertModuleBody(module)))
711 return failure();
712 }
713
714 // Convert the initializers of global variables.
715 for (auto *var : globalVariableWorklist) {
716 auto varOp = globalVariables.at(var);
717 auto &block = varOp.getInitRegion().emplaceBlock();
718 OpBuilder::InsertionGuard guard(builder);
719 builder.setInsertionPointToEnd(&block);
720 auto value =
721 convertRvalueExpression(*var->getInitializer(), varOp.getType());
722 if (!value)
723 return failure();
724 moore::YieldOp::create(builder, varOp.getLoc(), value);
725 }
727
728 return success();
729}
730
731/// Convert a module and its ports to an empty module op in the IR. Also adds
732/// the op to the worklist of module bodies to be lowered. This acts like a
733/// module "declaration", allowing instances to already refer to a module even
734/// before its body has been lowered.
736Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) {
737 using slang::ast::ArgumentDirection;
738 using slang::ast::MultiPortSymbol;
739 using slang::ast::ParameterSymbol;
740 using slang::ast::PortSymbol;
741 using slang::ast::TypeParameterSymbol;
742
743 // Keep track of the local time scale. `getTimeScale` automatically looks
744 // through parent scopes to find the time scale effective locally.
745 auto prevTimeScale = timeScale;
746 timeScale = module->getTimeScale().value_or(slang::TimeScale());
747 auto timeScaleGuard =
748 llvm::make_scope_exit([&] { timeScale = prevTimeScale; });
749
750 auto parameters = module->getParameters();
751 bool hasModuleSame = false;
752 // If there is already exist a module that has the same name with this
753 // module ,has the same parent scope and has the same parameters we can
754 // define this module is a duplicate module
755 for (auto const &existingModule : modules) {
756 if (module->getDeclaringDefinition() ==
757 existingModule.getFirst()->getDeclaringDefinition()) {
758 auto moduleParameters = existingModule.getFirst()->getParameters();
759 hasModuleSame = true;
760 for (auto it1 = parameters.begin(), it2 = moduleParameters.begin();
761 it1 != parameters.end() && it2 != moduleParameters.end();
762 it1++, it2++) {
763 // Parameters size different
764 if (it1 == parameters.end() || it2 == moduleParameters.end()) {
765 hasModuleSame = false;
766 break;
767 }
768 const auto *para1 = (*it1)->symbol.as_if<ParameterSymbol>();
769 const auto *para2 = (*it2)->symbol.as_if<ParameterSymbol>();
770 // Parameters kind different
771 if ((para1 == nullptr) ^ (para2 == nullptr)) {
772 hasModuleSame = false;
773 break;
774 }
775 // Compare ParameterSymbol
776 if (para1 != nullptr) {
777 hasModuleSame = para1->getValue() == para2->getValue();
778 }
779 // Compare TypeParameterSymbol
780 if (para1 == nullptr) {
781 auto para1Type = convertType(
782 (*it1)->symbol.as<TypeParameterSymbol>().getTypeAlias());
783 auto para2Type = convertType(
784 (*it2)->symbol.as<TypeParameterSymbol>().getTypeAlias());
785 hasModuleSame = para1Type == para2Type;
786 }
787 if (!hasModuleSame)
788 break;
789 }
790 if (hasModuleSame) {
791 module = existingModule.first;
792 break;
793 }
794 }
795 }
796
797 auto &slot = modules[module];
798 if (slot)
799 return slot.get();
800 slot = std::make_unique<ModuleLowering>();
801 auto &lowering = *slot;
802
803 auto loc = convertLocation(module->location);
804 OpBuilder::InsertionGuard g(builder);
805
806 // We only support modules for now. Extension to interfaces and programs
807 // should be trivial though, since they are essentially the same thing with
808 // only minor differences in semantics.
809 if (module->getDefinition().definitionKind !=
810 slang::ast::DefinitionKind::Module) {
811 mlir::emitError(loc) << "unsupported definition: "
812 << module->getDefinition().getKindString();
813 return {};
814 }
815
816 // Handle the port list.
817 auto block = std::make_unique<Block>();
818 SmallVector<hw::ModulePort> modulePorts;
819
820 // It's used to tag where a hierarchical name is on the port list.
821 unsigned int outputIdx = 0, inputIdx = 0;
822 for (auto *symbol : module->getPortList()) {
823 auto handlePort = [&](const PortSymbol &port) {
824 auto portLoc = convertLocation(port.location);
825 auto type = convertType(port.getType());
826 if (!type)
827 return failure();
828 auto portName = builder.getStringAttr(port.name);
829 BlockArgument arg;
830 if (port.direction == ArgumentDirection::Out) {
831 modulePorts.push_back({portName, type, hw::ModulePort::Output});
832 outputIdx++;
833 } else {
834 // Only the ref type wrapper exists for the time being, the net type
835 // wrapper for inout may be introduced later if necessary.
836 if (port.direction != ArgumentDirection::In)
837 type = moore::RefType::get(cast<moore::UnpackedType>(type));
838 modulePorts.push_back({portName, type, hw::ModulePort::Input});
839 arg = block->addArgument(type, portLoc);
840 inputIdx++;
841 }
842 lowering.ports.push_back({port, portLoc, arg});
843 return success();
844 };
845
846 if (const auto *port = symbol->as_if<PortSymbol>()) {
847 if (failed(handlePort(*port)))
848 return {};
849 } else if (const auto *multiPort = symbol->as_if<MultiPortSymbol>()) {
850 for (auto *port : multiPort->ports)
851 if (failed(handlePort(*port)))
852 return {};
853 } else {
854 mlir::emitError(convertLocation(symbol->location))
855 << "unsupported module port `" << symbol->name << "` ("
856 << slang::ast::toString(symbol->kind) << ")";
857 return {};
858 }
859 }
860
861 // Mapping hierarchical names into the module's ports.
862 for (auto &hierPath : hierPaths[module]) {
863 auto hierType = convertType(hierPath.valueSym->getType());
864 if (!hierType)
865 return {};
866
867 if (auto hierName = hierPath.hierName) {
868 // The type of all hierarchical names are marked as the "RefType".
869 hierType = moore::RefType::get(cast<moore::UnpackedType>(hierType));
870 if (hierPath.direction == ArgumentDirection::Out) {
871 hierPath.idx = outputIdx++;
872 modulePorts.push_back({hierName, hierType, hw::ModulePort::Output});
873 } else {
874 hierPath.idx = inputIdx++;
875 modulePorts.push_back({hierName, hierType, hw::ModulePort::Input});
876 auto hierLoc = convertLocation(hierPath.valueSym->location);
877 block->addArgument(hierType, hierLoc);
878 }
879 }
880 }
881 auto moduleType = hw::ModuleType::get(getContext(), modulePorts);
882
883 // Pick an insertion point for this module according to the source file
884 // location.
885 auto it = orderedRootOps.upper_bound(module->location);
886 if (it == orderedRootOps.end())
887 builder.setInsertionPointToEnd(intoModuleOp.getBody());
888 else
889 builder.setInsertionPoint(it->second);
890
891 // Create an empty module that corresponds to this module.
892 auto moduleOp =
893 moore::SVModuleOp::create(builder, loc, module->name, moduleType);
894 orderedRootOps.insert(it, {module->location, moduleOp});
895 moduleOp.getBodyRegion().push_back(block.release());
896 lowering.op = moduleOp;
897
898 // Add the module to the symbol table of the MLIR module, which uniquifies its
899 // name as we'd expect.
900 symbolTable.insert(moduleOp);
901
902 // Schedule the body to be lowered.
903 moduleWorklist.push(module);
904
905 // Map duplicate port by Syntax
906 for (const auto &port : lowering.ports)
907 lowering.portsBySyntaxNode.insert({port.ast.getSyntax(), &port.ast});
908
909 return &lowering;
910}
911
912/// Convert a module's body to the corresponding IR ops. The module op must have
913/// already been created earlier through a `convertModuleHeader` call.
914LogicalResult
915Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
916 auto &lowering = *modules[module];
917 OpBuilder::InsertionGuard g(builder);
918 builder.setInsertionPointToEnd(lowering.op.getBody());
919
921
922 // Keep track of the local time scale. `getTimeScale` automatically looks
923 // through parent scopes to find the time scale effective locally.
924 auto prevTimeScale = timeScale;
925 timeScale = module->getTimeScale().value_or(slang::TimeScale());
926 auto timeScaleGuard =
927 llvm::make_scope_exit([&] { timeScale = prevTimeScale; });
928
929 // Collect downward hierarchical names. Such as,
930 // module SubA; int x = Top.y; endmodule. The "Top" module is the parent of
931 // the "SubA", so "Top.y" is the downward hierarchical name.
932 for (auto &hierPath : hierPaths[module])
933 if (hierPath.direction == slang::ast::ArgumentDirection::In && hierPath.idx)
934 valueSymbols.insert(hierPath.valueSym,
935 lowering.op.getBody()->getArgument(*hierPath.idx));
936
937 // Convert the body of the module.
938 for (auto &member : module->members()) {
939 auto loc = convertLocation(member.location);
940 if (failed(member.visit(ModuleVisitor(*this, loc))))
941 return failure();
942 }
943
944 // Create additional ops to drive input port values onto the corresponding
945 // internal variables and nets, and to collect output port values for the
946 // terminator.
947 SmallVector<Value> outputs;
948 for (auto &port : lowering.ports) {
949 Value value;
950 if (auto *expr = port.ast.getInternalExpr()) {
951 value = convertLvalueExpression(*expr);
952 } else if (port.ast.internalSymbol) {
953 if (const auto *sym =
954 port.ast.internalSymbol->as_if<slang::ast::ValueSymbol>())
955 value = valueSymbols.lookup(sym);
956 }
957 if (!value)
958 return mlir::emitError(port.loc, "unsupported port: `")
959 << port.ast.name
960 << "` does not map to an internal symbol or expression";
961
962 // Collect output port values to be returned in the terminator.
963 if (port.ast.direction == slang::ast::ArgumentDirection::Out) {
964 if (isa<moore::RefType>(value.getType()))
965 value = moore::ReadOp::create(builder, value.getLoc(), value);
966 outputs.push_back(value);
967 continue;
968 }
969
970 // Assign the value coming in through the port to the internal net or symbol
971 // of that port.
972 Value portArg = port.arg;
973 if (port.ast.direction != slang::ast::ArgumentDirection::In)
974 portArg = moore::ReadOp::create(builder, port.loc, port.arg);
975 moore::ContinuousAssignOp::create(builder, port.loc, value, portArg);
976 }
977
978 // Ensure the number of operands of this module's terminator and the number of
979 // its(the current module) output ports remain consistent.
980 for (auto &hierPath : hierPaths[module])
981 if (auto hierValue = valueSymbols.lookup(hierPath.valueSym))
982 if (hierPath.direction == slang::ast::ArgumentDirection::Out)
983 outputs.push_back(hierValue);
984
985 moore::OutputOp::create(builder, lowering.op.getLoc(), outputs);
986 return success();
987}
988
989/// Convert a package and its contents.
990LogicalResult
991Context::convertPackage(const slang::ast::PackageSymbol &package) {
992 // Keep track of the local time scale. `getTimeScale` automatically looks
993 // through parent scopes to find the time scale effective locally.
994 auto prevTimeScale = timeScale;
995 timeScale = package.getTimeScale().value_or(slang::TimeScale());
996 auto timeScaleGuard =
997 llvm::make_scope_exit([&] { timeScale = prevTimeScale; });
998
999 OpBuilder::InsertionGuard g(builder);
1000 builder.setInsertionPointToEnd(intoModuleOp.getBody());
1002 for (auto &member : package.members()) {
1003 auto loc = convertLocation(member.location);
1004 if (failed(member.visit(PackageVisitor(*this, loc))))
1005 return failure();
1006 }
1007 return success();
1008}
1009
1010/// Convert a function and its arguments to a function declaration in the IR.
1011/// This does not convert the function body.
1013Context::declareFunction(const slang::ast::SubroutineSymbol &subroutine) {
1014 // Check if there already is a declaration for this function.
1015 auto &lowering = functions[&subroutine];
1016 if (lowering) {
1017 if (!lowering->op)
1018 return {};
1019 return lowering.get();
1020 }
1021
1022 if (!subroutine.thisVar) {
1023
1024 SmallString<64> name;
1025 guessNamespacePrefix(subroutine.getParentScope()->asSymbol(), name);
1026 name += subroutine.name;
1027
1028 SmallVector<Type, 1> noThis = {};
1029 return declareCallableImpl(subroutine, name, noThis);
1030 }
1031
1032 auto loc = convertLocation(subroutine.location);
1033
1034 // Extract 'this' type and ensure it's a class.
1035 const slang::ast::Type &thisTy = subroutine.thisVar->getType();
1036 moore::ClassDeclOp ownerDecl;
1037
1038 if (auto *classTy = thisTy.as_if<slang::ast::ClassType>()) {
1039 auto &ownerLowering = classes[classTy];
1040 ownerDecl = ownerLowering->op;
1041 } else {
1042 mlir::emitError(loc) << "expected 'this' to be a class type, got "
1043 << thisTy.toString();
1044 return {};
1045 }
1046
1047 // Build qualified name: @"Pkg::Class"::subroutine
1048 SmallString<64> qualName;
1049 qualName += ownerDecl.getSymName(); // already qualified
1050 qualName += "::";
1051 qualName += subroutine.name;
1052
1053 // %this : class<@C>
1054 SmallVector<Type, 1> extraParams;
1055 {
1056 auto classSym = mlir::FlatSymbolRefAttr::get(ownerDecl.getSymNameAttr());
1057 auto handleTy = moore::ClassHandleType::get(getContext(), classSym);
1058 extraParams.push_back(handleTy);
1059 }
1060
1061 auto *fLowering = declareCallableImpl(subroutine, qualName, extraParams);
1062 return fLowering;
1063}
1064
1065/// Helper function to generate the function signature from a SubroutineSymbol
1066/// and optional extra arguments (used for %this argument)
1067static FunctionType
1069 const slang::ast::SubroutineSymbol &subroutine,
1070 llvm::SmallVectorImpl<Type> &extraParams) {
1071 using slang::ast::ArgumentDirection;
1072
1073 SmallVector<Type> inputTypes;
1074 inputTypes.append(extraParams.begin(), extraParams.end());
1075 SmallVector<Type, 1> outputTypes;
1076
1077 for (const auto *arg : subroutine.getArguments()) {
1078 auto type = context.convertType(arg->getType());
1079 if (!type)
1080 return {};
1081 if (arg->direction == ArgumentDirection::In) {
1082 inputTypes.push_back(type);
1083 } else {
1084 inputTypes.push_back(
1085 moore::RefType::get(cast<moore::UnpackedType>(type)));
1086 }
1087 }
1088
1089 if (!subroutine.getReturnType().isVoid()) {
1090 auto type = context.convertType(subroutine.getReturnType());
1091 if (!type)
1092 return {};
1093 outputTypes.push_back(type);
1094 }
1095
1096 auto funcType =
1097 FunctionType::get(context.getContext(), inputTypes, outputTypes);
1098
1099 // Create a function declaration.
1100 return funcType;
1101}
1102
1103/// Convert a function and its arguments to a function declaration in the IR.
1104/// This does not convert the function body.
1106Context::declareCallableImpl(const slang::ast::SubroutineSymbol &subroutine,
1107 mlir::StringRef qualifiedName,
1108 llvm::SmallVectorImpl<Type> &extraParams) {
1109 auto loc = convertLocation(subroutine.location);
1110 std::unique_ptr<FunctionLowering> lowering =
1111 std::make_unique<FunctionLowering>();
1112
1113 // Pick an insertion point for this function according to the source file
1114 // location.
1115 OpBuilder::InsertionGuard g(builder);
1116 auto it = orderedRootOps.upper_bound(subroutine.location);
1117 if (it == orderedRootOps.end())
1118 builder.setInsertionPointToEnd(intoModuleOp.getBody());
1119 else
1120 builder.setInsertionPoint(it->second);
1121
1122 auto funcTy = getFunctionSignature(*this, subroutine, extraParams);
1123 auto funcOp = mlir::func::FuncOp::create(builder, loc, qualifiedName, funcTy);
1124
1125 SymbolTable::setSymbolVisibility(funcOp, SymbolTable::Visibility::Private);
1126 orderedRootOps.insert(it, {subroutine.location, funcOp});
1127 lowering->op = funcOp;
1128
1129 // Add the function to the symbol table of the MLIR module, which uniquifies
1130 // its name.
1131 symbolTable.insert(funcOp);
1132 functions[&subroutine] = std::move(lowering);
1133
1134 return functions[&subroutine].get();
1135}
1136
1137/// Special case handling for recursive functions with captures;
1138/// this function fixes the in-body call of the recursive function with
1139/// the captured arguments.
1140static LogicalResult rewriteCallSitesToPassCaptures(mlir::func::FuncOp callee,
1141 ArrayRef<Value> captures) {
1142 if (captures.empty())
1143 return success();
1144
1145 mlir::ModuleOp module = callee->getParentOfType<mlir::ModuleOp>();
1146 if (!module)
1147 return callee.emitError("expected callee to be nested under ModuleOp");
1148
1149 auto usesOpt = mlir::SymbolTable::getSymbolUses(callee, module);
1150 if (!usesOpt)
1151 return callee.emitError("failed to compute symbol uses");
1152
1153 // Snapshot the relevant users before we mutate IR.
1154 SmallVector<mlir::func::CallOp, 8> callSites;
1155 callSites.reserve(std::distance(usesOpt->begin(), usesOpt->end()));
1156 for (const mlir::SymbolTable::SymbolUse &use : *usesOpt) {
1157 if (auto call = llvm::dyn_cast<mlir::func::CallOp>(use.getUser()))
1158 callSites.push_back(call);
1159 }
1160 if (callSites.empty())
1161 return success();
1162
1163 Block &entry = callee.getBody().front();
1164 const unsigned numCaps = captures.size();
1165 const unsigned numEntryArgs = entry.getNumArguments();
1166 if (numEntryArgs < numCaps)
1167 return callee.emitError("entry block has fewer args than captures");
1168 const unsigned capArgStart = numEntryArgs - numCaps;
1169
1170 // Current (finalized) function type.
1171 auto fTy = callee.getFunctionType();
1172
1173 for (auto call : callSites) {
1174 SmallVector<Value> newOperands(call.getArgOperands().begin(),
1175 call.getArgOperands().end());
1176
1177 const bool inSameFunc = callee->isProperAncestor(call);
1178 if (inSameFunc) {
1179 // Append the functionโ€™s *capture block arguments* in order.
1180 for (unsigned i = 0; i < numCaps; ++i)
1181 newOperands.push_back(entry.getArgument(capArgStart + i));
1182 } else {
1183 // External call site: pass the captured SSA values.
1184 newOperands.append(captures.begin(), captures.end());
1185 }
1186
1187 OpBuilder b(call);
1188 auto flatRef = mlir::FlatSymbolRefAttr::get(callee);
1189 auto newCall = mlir::func::CallOp::create(
1190 b, call.getLoc(), fTy.getResults(), flatRef, newOperands);
1191 call->replaceAllUsesWith(newCall.getOperation());
1192 call->erase();
1193 }
1194
1195 return success();
1196}
1197
1198/// Convert a function.
1199LogicalResult
1200Context::convertFunction(const slang::ast::SubroutineSymbol &subroutine) {
1201 // Keep track of the local time scale. `getTimeScale` automatically looks
1202 // through parent scopes to find the time scale effective locally.
1203 auto prevTimeScale = timeScale;
1204 timeScale = subroutine.getTimeScale().value_or(slang::TimeScale());
1205 auto timeScaleGuard =
1206 llvm::make_scope_exit([&] { timeScale = prevTimeScale; });
1207
1208 // First get or create the function declaration.
1209 auto *lowering = declareFunction(subroutine);
1210 if (!lowering)
1211 return failure();
1212
1213 // If function already has been finalized, or is already being converted
1214 // (recursive/re-entrant calls) stop here.
1215 if (lowering->capturesFinalized || lowering->isConverting)
1216 return success();
1217
1218 const bool isMethod = (subroutine.thisVar != nullptr);
1219
1221
1222 // Create a function body block and populate it with block arguments.
1223 SmallVector<moore::VariableOp> argVariables;
1224 auto &block = lowering->op.getBody().emplaceBlock();
1225
1226 // If this is a class method, the first input is %this :
1227 // !moore.class<@C>
1228 if (isMethod) {
1229 auto thisLoc = convertLocation(subroutine.location);
1230 auto thisType = lowering->op.getFunctionType().getInput(0);
1231 auto thisArg = block.addArgument(thisType, thisLoc);
1232
1233 // Bind `this` so NamedValue/MemberAccess can find it.
1234 valueSymbols.insert(subroutine.thisVar, thisArg);
1235 }
1236
1237 // Add user-defined block arguments
1238 auto inputs = lowering->op.getFunctionType().getInputs();
1239 auto astArgs = subroutine.getArguments();
1240 auto valInputs = llvm::ArrayRef<Type>(inputs).drop_front(isMethod ? 1 : 0);
1241
1242 for (auto [astArg, type] : llvm::zip(astArgs, valInputs)) {
1243 auto loc = convertLocation(astArg->location);
1244 auto blockArg = block.addArgument(type, loc);
1245
1246 if (isa<moore::RefType>(type)) {
1247 valueSymbols.insert(astArg, blockArg);
1248 } else {
1249 OpBuilder::InsertionGuard g(builder);
1250 builder.setInsertionPointToEnd(&block);
1251
1252 auto shadowArg = moore::VariableOp::create(
1253 builder, loc, moore::RefType::get(cast<moore::UnpackedType>(type)),
1254 StringAttr{}, blockArg);
1255 valueSymbols.insert(astArg, shadowArg);
1256 argVariables.push_back(shadowArg);
1257 }
1258 }
1259
1260 // Convert the body of the function.
1261 OpBuilder::InsertionGuard g(builder);
1262 builder.setInsertionPointToEnd(&block);
1263
1264 Value returnVar;
1265 if (subroutine.returnValVar) {
1266 auto type = convertType(*subroutine.returnValVar->getDeclaredType());
1267 if (!type)
1268 return failure();
1269 returnVar = moore::VariableOp::create(
1270 builder, lowering->op.getLoc(),
1271 moore::RefType::get(cast<moore::UnpackedType>(type)), StringAttr{},
1272 Value{});
1273 valueSymbols.insert(subroutine.returnValVar, returnVar);
1274 }
1275
1276 // Save previous callbacks
1277 auto prevRCb = rvalueReadCallback;
1278 auto prevWCb = variableAssignCallback;
1279 auto prevRCbGuard = llvm::make_scope_exit([&] {
1280 rvalueReadCallback = prevRCb;
1281 variableAssignCallback = prevWCb;
1282 });
1283
1284 // Capture this function's captured context directly
1285 rvalueReadCallback = [lowering, prevRCb](moore::ReadOp rop) {
1286 mlir::Value ref = rop.getInput();
1287
1288 // Don't capture anything that's not a reference
1289 mlir::Type ty = ref.getType();
1290 if (!ty || !(isa<moore::RefType>(ty)))
1291 return;
1292
1293 // Don't capture anything that's a local reference
1294 mlir::Region *defReg = ref.getParentRegion();
1295 if (defReg && lowering->op.getBody().isAncestor(defReg))
1296 return;
1297
1298 // If we've already recorded this capture, skip.
1299 if (lowering->captureIndex.count(ref))
1300 return;
1301
1302 // Only capture refs defined outside this functionโ€™s region
1303 auto [it, inserted] =
1304 lowering->captureIndex.try_emplace(ref, lowering->captures.size());
1305 if (inserted) {
1306 lowering->captures.push_back(ref);
1307 // Propagate over outer scope
1308 if (prevRCb)
1309 prevRCb(rop); // chain previous callback
1310 }
1311 };
1312 // Capture this function's captured context directly
1313 variableAssignCallback = [lowering, prevWCb](mlir::Operation *op) {
1314 mlir::Value dstRef =
1315 llvm::TypeSwitch<mlir::Operation *, mlir::Value>(op)
1316 .Case<moore::BlockingAssignOp, moore::NonBlockingAssignOp,
1317 moore::DelayedNonBlockingAssignOp>(
1318 [](auto op) { return op.getDst(); })
1319 .Default([](auto) -> mlir::Value { return {}; });
1320
1321 // Don't capture anything that's not a reference
1322 mlir::Type ty = dstRef.getType();
1323 if (!ty || !(isa<moore::RefType>(ty)))
1324 return;
1325
1326 // Don't capture anything that's a local reference
1327 mlir::Region *defReg = dstRef.getParentRegion();
1328 if (defReg && lowering->op.getBody().isAncestor(defReg))
1329 return;
1330
1331 // If we've already recorded this capture, skip.
1332 if (lowering->captureIndex.count(dstRef))
1333 return;
1334
1335 // Only capture refs defined outside this functionโ€™s region
1336 auto [it, inserted] =
1337 lowering->captureIndex.try_emplace(dstRef, lowering->captures.size());
1338 if (inserted) {
1339 lowering->captures.push_back(dstRef);
1340 // Propagate over outer scope
1341 if (prevWCb)
1342 prevWCb(op); // chain previous callback
1343 }
1344 };
1345
1346 auto savedThis = currentThisRef;
1347 currentThisRef = valueSymbols.lookup(subroutine.thisVar);
1348 auto restoreThis = llvm::make_scope_exit([&] { currentThisRef = savedThis; });
1349
1350 lowering->isConverting = true;
1351 auto convertingGuard =
1352 llvm::make_scope_exit([&] { lowering->isConverting = false; });
1353
1354 if (failed(convertStatement(subroutine.getBody())))
1355 return failure();
1356
1357 // Plumb captures into the function as extra block arguments
1358 if (failed(finalizeFunctionBodyCaptures(*lowering)))
1359 return failure();
1360
1361 // For the special case of recursive functions, fix the call sites within the
1362 // body
1363 if (failed(rewriteCallSitesToPassCaptures(lowering->op, lowering->captures)))
1364 return failure();
1365
1366 // If there was no explicit return statement provided by the user, insert a
1367 // default one.
1368 if (builder.getBlock()) {
1369 if (returnVar && !subroutine.getReturnType().isVoid()) {
1370 Value read =
1371 moore::ReadOp::create(builder, returnVar.getLoc(), returnVar);
1372 mlir::func::ReturnOp::create(builder, lowering->op.getLoc(), read);
1373 } else {
1374 mlir::func::ReturnOp::create(builder, lowering->op.getLoc(),
1375 ValueRange{});
1376 }
1377 }
1378 if (returnVar && returnVar.use_empty())
1379 returnVar.getDefiningOp()->erase();
1380
1381 for (auto var : argVariables) {
1382 if (llvm::all_of(var->getUsers(),
1383 [](auto *user) { return isa<moore::ReadOp>(user); })) {
1384 for (auto *user : llvm::make_early_inc_range(var->getUsers())) {
1385 user->getResult(0).replaceAllUsesWith(var.getInitial());
1386 user->erase();
1387 }
1388 var->erase();
1389 }
1390 }
1391
1392 lowering->capturesFinalized = true;
1393 return success();
1394}
1395
1396LogicalResult
1398 if (lowering.captures.empty())
1399 return success();
1400
1401 MLIRContext *ctx = getContext();
1402
1403 // Build new input type list: existing inputs + capture ref types.
1404 SmallVector<Type> newInputs(lowering.op.getFunctionType().getInputs().begin(),
1405 lowering.op.getFunctionType().getInputs().end());
1406
1407 for (Value cap : lowering.captures) {
1408 // Expect captures to be refs.
1409 Type capTy = cap.getType();
1410 if (!isa<moore::RefType>(capTy)) {
1411 return lowering.op.emitError(
1412 "expected captured value to be a ref-like type");
1413 }
1414 newInputs.push_back(capTy);
1415 }
1416
1417 // Results unchanged.
1418 auto newFuncTy = FunctionType::get(
1419 ctx, newInputs, lowering.op.getFunctionType().getResults());
1420 lowering.op.setFunctionType(newFuncTy);
1421
1422 // Add the new block arguments to the entry block.
1423 Block &entry = lowering.op.getBody().front();
1424 SmallVector<Value> capArgs;
1425 capArgs.reserve(lowering.captures.size());
1426 for (Type t :
1427 llvm::ArrayRef<Type>(newInputs).take_back(lowering.captures.size())) {
1428 capArgs.push_back(entry.addArgument(t, lowering.op.getLoc()));
1429 }
1430
1431 // Replace uses of each captured Value *inside the function body* with the new
1432 // arg. Keep uses outside untouched (e.g., in callers).
1433 for (auto [cap, idx] : lowering.captureIndex) {
1434 Value arg = capArgs[idx];
1435 cap.replaceUsesWithIf(arg, [&](OpOperand &use) {
1436 return lowering.op->isProperAncestor(use.getOwner());
1437 });
1438 }
1439
1440 return success();
1441}
1442
1443namespace {
1444
1445/// Construct a fully qualified class name containing the instance hierarchy
1446/// and the class name formatted as H1::H2::@C
1447mlir::StringAttr fullyQualifiedClassName(Context &ctx,
1448 const slang::ast::Type &ty) {
1449 SmallString<64> name;
1450 SmallVector<llvm::StringRef, 8> parts;
1451
1452 const slang::ast::Scope *scope = ty.getParentScope();
1453 while (scope) {
1454 const auto &sym = scope->asSymbol();
1455 switch (sym.kind) {
1456 case slang::ast::SymbolKind::Root:
1457 scope = nullptr; // stop at $root
1458 continue;
1459 case slang::ast::SymbolKind::InstanceBody:
1460 case slang::ast::SymbolKind::Instance:
1461 case slang::ast::SymbolKind::Package:
1462 case slang::ast::SymbolKind::ClassType:
1463 if (!sym.name.empty())
1464 parts.push_back(sym.name); // keep packages + outer classes
1465 break;
1466 default:
1467 break;
1468 }
1469 scope = sym.getParentScope();
1470 }
1471
1472 for (auto p : llvm::reverse(parts)) {
1473 name += p;
1474 name += "::";
1475 }
1476 name += ty.name; // classโ€™s own name
1477 return mlir::StringAttr::get(ctx.getContext(), name);
1478}
1479
1480/// Helper function to construct the classes fully qualified base class name
1481/// and the name of all implemented interface classes
1482std::pair<mlir::SymbolRefAttr, mlir::ArrayAttr>
1483buildBaseAndImplementsAttrs(Context &context,
1484 const slang::ast::ClassType &cls) {
1485 mlir::MLIRContext *ctx = context.getContext();
1486
1487 // Base class (if any)
1488 mlir::SymbolRefAttr base;
1489 if (const auto *b = cls.getBaseClass())
1490 base = mlir::SymbolRefAttr::get(fullyQualifiedClassName(context, *b));
1491
1492 // Implemented interfaces (if any)
1493 SmallVector<mlir::Attribute> impls;
1494 if (auto ifaces = cls.getDeclaredInterfaces(); !ifaces.empty()) {
1495 impls.reserve(ifaces.size());
1496 for (const auto *iface : ifaces)
1497 impls.push_back(mlir::FlatSymbolRefAttr::get(
1498 fullyQualifiedClassName(context, *iface)));
1499 }
1500
1501 mlir::ArrayAttr implArr =
1502 impls.empty() ? mlir::ArrayAttr() : mlir::ArrayAttr::get(ctx, impls);
1503
1504 return {base, implArr};
1505}
1506
1507/// Visit a slang::ast::ClassType and populate the body of an existing
1508/// moore::ClassDeclOp with field/method decls.
1509struct ClassDeclVisitor {
1510 Context &context;
1511 OpBuilder &builder;
1512 ClassLowering &classLowering;
1513
1514 ClassDeclVisitor(Context &ctx, ClassLowering &lowering)
1515 : context(ctx), builder(ctx.builder), classLowering(lowering) {}
1516
1517 LogicalResult run(const slang::ast::ClassType &classAST) {
1518 if (!classLowering.op.getBody().empty())
1519 return success();
1520
1521 OpBuilder::InsertionGuard ig(builder);
1522
1523 Block *body = &classLowering.op.getBody().emplaceBlock();
1524 builder.setInsertionPointToEnd(body);
1525
1526 for (const auto &mem : classAST.members())
1527 if (failed(mem.visit(*this)))
1528 return failure();
1529
1530 return success();
1531 }
1532
1533 // Properties: ClassPropertySymbol
1534 LogicalResult visit(const slang::ast::ClassPropertySymbol &prop) {
1535 auto loc = convertLocation(prop.location);
1536 auto ty = context.convertType(prop.getType());
1537 if (!ty)
1538 return failure();
1539
1540 moore::ClassPropertyDeclOp::create(builder, loc, prop.name, ty);
1541 return success();
1542 }
1543
1544 // Parameters in specialized classes hold no further information; slang
1545 // already elaborates them in all relevant places.
1546 LogicalResult visit(const slang::ast::ParameterSymbol &) { return success(); }
1547
1548 // Parameters in specialized classes hold no further information; slang
1549 // already elaborates them in all relevant places.
1550 LogicalResult visit(const slang::ast::TypeParameterSymbol &) {
1551 return success();
1552 }
1553
1554 // Type aliases in specialized classes hold no further information; slang
1555 // already elaborates them in all relevant places.
1556 LogicalResult visit(const slang::ast::TypeAliasType &) { return success(); }
1557
1558 // Fully-fledged functions - SubroutineSymbol
1559 LogicalResult visit(const slang::ast::SubroutineSymbol &fn) {
1560 if (fn.flags & slang::ast::MethodFlags::BuiltIn) {
1561 static bool remarkEmitted = false;
1562 if (remarkEmitted)
1563 return success();
1564
1565 mlir::emitRemark(classLowering.op.getLoc())
1566 << "Class builtin functions (needed for randomization, constraints, "
1567 "and covergroups) are not yet supported and will be dropped "
1568 "during lowering.";
1569 remarkEmitted = true;
1570 return success();
1571 }
1572
1573 const mlir::UnitAttr isVirtual =
1574 (fn.flags & slang::ast::MethodFlags::Virtual)
1575 ? UnitAttr::get(context.getContext())
1576 : nullptr;
1577
1578 auto loc = convertLocation(fn.location);
1579 // Pure virtual functions regulate inheritance rules during parsing.
1580 // They don't emit any code, so we don't need to convert them, we only need
1581 // to register them for the purpose of stable VTable construction.
1582 if (fn.flags & slang::ast::MethodFlags::Pure) {
1583 // Add an extra %this argument.
1584 SmallVector<Type, 1> extraParams;
1585 auto classSym =
1586 mlir::FlatSymbolRefAttr::get(classLowering.op.getSymNameAttr());
1587 auto handleTy =
1588 moore::ClassHandleType::get(context.getContext(), classSym);
1589 extraParams.push_back(handleTy);
1590
1591 auto funcTy = getFunctionSignature(context, fn, extraParams);
1592 moore::ClassMethodDeclOp::create(builder, loc, fn.name, funcTy, nullptr);
1593 return success();
1594 }
1595
1596 auto *lowering = context.declareFunction(fn);
1597 if (!lowering)
1598 return failure();
1599
1600 if (failed(context.convertFunction(fn)))
1601 return failure();
1602
1603 if (!lowering->capturesFinalized)
1604 return failure();
1605
1606 // We only emit methoddecls for virtual methods.
1607 if (!isVirtual)
1608 return success();
1609
1610 // Grab the finalized function type from the lowered func.op.
1611 FunctionType fnTy = lowering->op.getFunctionType();
1612 // Emit the method decl into the class body, preserving source order.
1613 moore::ClassMethodDeclOp::create(builder, loc, fn.name, fnTy,
1614 SymbolRefAttr::get(lowering->op));
1615
1616 return success();
1617 }
1618
1619 // A method prototype corresponds to the forward declaration of a concrete
1620 // method, the forward declaration of a virtual method, or the defintion of an
1621 // interface method meant to be implemented by classes implementing the
1622 // interface class.
1623 // In the first two cases, the best thing to do is to look up the actual
1624 // implementation and translate it when reading the method prototype, so we
1625 // can insert the MethodDeclOp in the correct order in the ClassDeclOp.
1626 // The latter case requires support for virtual interface methods, which is
1627 // currently not implemented. Since forward declarations of non-interface
1628 // methods must be followed by an implementation within the same compilation
1629 // unit, we can simply return a failure if we can't find a unique
1630 // implementation until we implement support for interface methods.
1631 LogicalResult visit(const slang::ast::MethodPrototypeSymbol &fn) {
1632 const auto *externImpl = fn.getSubroutine();
1633 // We needn't convert a forward declaration without a unique implementation.
1634 if (!externImpl) {
1635 mlir::emitError(convertLocation(fn.location))
1636 << "Didn't find an implementation matching the forward declaration "
1637 "of "
1638 << fn.name;
1639 return failure();
1640 }
1641 return visit(*externImpl);
1642 }
1643
1644 // Nested class definition, skip
1645 LogicalResult visit(const slang::ast::GenericClassDefSymbol &) {
1646 return success();
1647 }
1648
1649 // Nested class definition, convert
1650 LogicalResult visit(const slang::ast::ClassType &cls) {
1651 return context.convertClassDeclaration(cls);
1652 }
1653
1654 // Transparent members: ignore (inherited names pulled in by slang)
1655 LogicalResult visit(const slang::ast::TransparentMemberSymbol &) {
1656 return success();
1657 }
1658
1659 // Empty members: ignore
1660 LogicalResult visit(const slang::ast::EmptyMemberSymbol &) {
1661 return success();
1662 }
1663
1664 // Emit an error for all other members.
1665 template <typename T>
1666 LogicalResult visit(T &&node) {
1667 Location loc = UnknownLoc::get(context.getContext());
1668 if constexpr (requires { node.location; })
1669 loc = convertLocation(node.location);
1670 mlir::emitError(loc) << "unsupported construct in ClassType members: "
1671 << slang::ast::toString(node.kind);
1672 return failure();
1673 }
1674
1675private:
1676 Location convertLocation(const slang::SourceLocation &sloc) {
1677 return context.convertLocation(sloc);
1678 }
1679};
1680} // namespace
1681
1682ClassLowering *Context::declareClass(const slang::ast::ClassType &cls) {
1683 // Check if there already is a declaration for this class.
1684 auto &lowering = classes[&cls];
1685 if (lowering) {
1686 if (!lowering->op)
1687 return {};
1688 return lowering.get();
1689 }
1690
1691 lowering = std::make_unique<ClassLowering>();
1692 auto loc = convertLocation(cls.location);
1693
1694 // Pick an insertion point for this function according to the source file
1695 // location.
1696 OpBuilder::InsertionGuard g(builder);
1697 auto it = orderedRootOps.upper_bound(cls.location);
1698 if (it == orderedRootOps.end())
1699 builder.setInsertionPointToEnd(intoModuleOp.getBody());
1700 else
1701 builder.setInsertionPoint(it->second);
1702
1703 auto symName = fullyQualifiedClassName(*this, cls);
1704
1705 // Force build of base here.
1706 if (const auto *maybeBaseClass = cls.getBaseClass())
1707 if (const auto *baseClass = maybeBaseClass->as_if<slang::ast::ClassType>())
1708 if (!classes.contains(baseClass) &&
1709 failed(convertClassDeclaration(*baseClass))) {
1710 mlir::emitError(loc) << "Failed to convert base class "
1711 << baseClass->name << " of class " << cls.name;
1712 return {};
1713 }
1714
1715 auto [base, impls] = buildBaseAndImplementsAttrs(*this, cls);
1716 auto classDeclOp =
1717 moore::ClassDeclOp::create(builder, loc, symName, base, impls);
1718
1719 SymbolTable::setSymbolVisibility(classDeclOp,
1720 SymbolTable::Visibility::Public);
1721 orderedRootOps.insert(it, {cls.location, classDeclOp});
1722 lowering->op = classDeclOp;
1723
1724 symbolTable.insert(classDeclOp);
1725 return lowering.get();
1726}
1727
1728LogicalResult
1729Context::convertClassDeclaration(const slang::ast::ClassType &classdecl) {
1730
1731 // Keep track of local time scale.
1732 auto prevTimeScale = timeScale;
1733 timeScale = classdecl.getTimeScale().value_or(slang::TimeScale());
1734 auto timeScaleGuard =
1735 llvm::make_scope_exit([&] { timeScale = prevTimeScale; });
1736
1737 // Check if there already is a declaration for this class.
1738 if (classes.contains(&classdecl))
1739 return success();
1740
1741 auto *lowering = declareClass(classdecl);
1742 if (failed(ClassDeclVisitor(*this, *lowering).run(classdecl)))
1743 return failure();
1744
1745 return success();
1746}
1747
1748/// Convert a variable to a `moore.global_variable` operation.
1749LogicalResult
1750Context::convertGlobalVariable(const slang::ast::VariableSymbol &var) {
1751 auto loc = convertLocation(var.location);
1752
1753 // Pick an insertion point for this variable according to the source file
1754 // location.
1755 OpBuilder::InsertionGuard g(builder);
1756 auto it = orderedRootOps.upper_bound(var.location);
1757 if (it == orderedRootOps.end())
1758 builder.setInsertionPointToEnd(intoModuleOp.getBody());
1759 else
1760 builder.setInsertionPoint(it->second);
1761
1762 // Prefix the variable name with the surrounding namespace to create somewhat
1763 // sane names in the IR.
1764 SmallString<64> symName;
1765 guessNamespacePrefix(var.getParentScope()->asSymbol(), symName);
1766 symName += var.name;
1767
1768 // Determine the type of the variable.
1769 auto type = convertType(var.getType());
1770 if (!type)
1771 return failure();
1772
1773 // Create the variable op itself.
1774 auto varOp = moore::GlobalVariableOp::create(builder, loc, symName,
1775 cast<moore::UnpackedType>(type));
1776 orderedRootOps.insert({var.location, varOp});
1777 globalVariables.insert({&var, varOp});
1778
1779 // If the variable has an initializer expression, remember it for later such
1780 // that we can convert the initializers once we have seen all global
1781 // variables.
1782 if (var.getInitializer())
1783 globalVariableWorklist.push_back(&var);
1784
1785 return success();
1786}
assert(baseType &&"element must be base type")
static FIRRTLBaseType convertType(FIRRTLBaseType type)
Returns null type if no conversion is needed.
Definition DropConst.cpp:32
static Location convertLocation(MLIRContext *context, const slang::SourceManager &sourceManager, slang::SourceLocation loc)
Convert a slang SourceLocation to an MLIR Location.
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:216
static moore::ProcedureKind convertProcedureKind(slang::ast::ProceduralBlockKind kind)
static FunctionType getFunctionSignature(Context &context, const slang::ast::SubroutineSymbol &subroutine, llvm::SmallVectorImpl< Type > &extraParams)
Helper function to generate the function signature from a SubroutineSymbol and optional extra argumen...
static void guessNamespacePrefix(const slang::ast::Symbol &symbol, SmallString< 64 > &prefix)
Definition Structure.cpp:21
static LogicalResult rewriteCallSitesToPassCaptures(mlir::func::FuncOp callee, ArrayRef< Value > captures)
Special case handling for recursive functions with captures; this function fixes the in-body call of ...
static moore::NetKind convertNetKind(slang::ast::NetType::NetKind kind)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:55
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
Definition codegen.py:121
bool lowerAlwaysAtStarAsComb
Interpret always @(*) as always_comb.
bool debugInfo
Generate debug information in the form of debug dialect ops in the IR.
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.
FunctionLowering * declareCallableImpl(const slang::ast::SubroutineSymbol &subroutine, mlir::StringRef qualifiedName, llvm::SmallVectorImpl< Type > &extraParams)
Helper function to extract the commonalities in lowering of functions and methods.
ModuleLowering * convertModuleHeader(const slang::ast::InstanceBodySymbol *module)
Convert a module and its ports to an empty module op in the IR.
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.
LogicalResult convertModuleBody(const slang::ast::InstanceBodySymbol *module)
Convert a module's body to the corresponding IR ops.
LogicalResult convertClassDeclaration(const slang::ast::ClassType &classdecl)
DenseMap< const slang::ast::ValueSymbol *, moore::GlobalVariableOp > globalVariables
A table of defined global variables that may be referred to by name in expressions.
slang::ast::Compilation & compilation
OpBuilder builder
The builder used to create IR operations.
std::queue< const slang::ast::InstanceBodySymbol * > moduleWorklist
A list of modules for which the header has been created, but the body has not been converted yet.
LogicalResult convertGlobalVariable(const slang::ast::VariableSymbol &var)
Convert a variable to a moore.global_variable operation.
DenseMap< const slang::ast::ClassType *, std::unique_ptr< ClassLowering > > classes
Classes that have already been converted.
std::function< void(moore::ReadOp)> rvalueReadCallback
A listener called for every variable or net being read.
std::map< slang::SourceLocation, Operation * > orderedRootOps
The top-level operations ordered by their Slang source location.
Type convertType(const slang::ast::Type &type, LocationAttr loc={})
Convert a slang type into an MLIR type.
Definition Types.cpp:193
DenseMap< const slang::ast::SubroutineSymbol *, std::unique_ptr< FunctionLowering > > functions
Functions that have already been converted.
slang::TimeScale timeScale
The time scale currently in effect.
LogicalResult finalizeFunctionBodyCaptures(FunctionLowering &lowering)
ClassLowering * declareClass(const slang::ast::ClassType &cls)
std::function< void(mlir::Operation *)> variableAssignCallback
A listener called for every variable or net being assigned.
const ImportVerilogOptions & options
Value convertRvalueExpression(const slang::ast::Expression &expr, Type requiredType={})
LogicalResult traverseInstanceBody(const slang::ast::Symbol &symbol)
Value currentThisRef
Variable to track the value of the current function's implicit this reference.
SymbolTable symbolTable
A symbol table of the MLIR module we are emitting into.
DenseMap< const slang::ast::InstanceBodySymbol *, SmallVector< HierPathInfo > > hierPaths
Collect all hierarchical names used for the per module/instance.
FunctionLowering * declareFunction(const slang::ast::SubroutineSymbol &subroutine)
Convert a function and its arguments to a function declaration in the IR.
LogicalResult convertPackage(const slang::ast::PackageSymbol &package)
Convert a package and its contents.
MLIRContext * getContext()
Return the MLIR context.
LogicalResult convertStatement(const slang::ast::Statement &stmt)
SmallVector< const slang::ast::ValueSymbol * > globalVariableWorklist
A list of global variables that still need their initializers to be converted.
DenseMap< const slang::ast::InstanceBodySymbol *, std::unique_ptr< ModuleLowering > > modules
How we have lowered modules to MLIR.
Location convertLocation(slang::SourceLocation loc)
Convert a slang SourceLocation into an MLIR Location.
llvm::DenseMap< Value, unsigned > captureIndex