CIRCT 23.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 "slang/ast/symbols/MemberSymbols.h"
13#include "slang/syntax/AllSyntax.h"
14#include "slang/syntax/SyntaxVisitor.h"
15#include "llvm/ADT/STLFunctionalExtras.h"
16#include "llvm/ADT/ScopeExit.h"
17
18using namespace circt;
19using namespace ImportVerilog;
20
21static constexpr StringLiteral dpiExportAttrName = "circt.dpi.export";
22
23//===----------------------------------------------------------------------===//
24// Utilities
25//===----------------------------------------------------------------------===//
26
27/// Record `export "DPI-C"` directives in the given scope so that callable
28/// declarations can be tagged with the exported C name. Slang resolves the
29/// directives during elaboration but does not expose them on the subroutine
30/// symbols themselves, so walk the scope's syntax to recover them.
32 const slang::ast::Scope &scope,
33 const slang::syntax::SyntaxNode *syntax) {
34 if (!syntax)
35 return;
36
37 auto visitor = slang::syntax::makeSyntaxVisitor(
38 [&](auto &visitor, const slang::syntax::DPIExportSyntax &exportSyntax) {
39 auto svName = exportSyntax.name.valueText();
40 if (svName.empty())
41 return;
42
43 const auto *symbol = scope.find(svName);
44 const auto *subroutine =
45 symbol ? symbol->as_if<slang::ast::SubroutineSymbol>() : nullptr;
46 if (!subroutine)
47 return;
48
49 auto cName = exportSyntax.c_identifier.valueText();
50 if (cName.empty())
51 cName = svName;
52 context.dpiExportCNames[subroutine] = std::string(cName);
53 },
54 [](auto &visitor, const slang::syntax::SyntaxNode &node) {
55 visitor.visitDefault(node);
56 });
57 syntax->visit(visitor);
58}
59
60static void guessNamespacePrefix(const slang::ast::Symbol &symbol,
61 SmallString<64> &prefix) {
62 if (symbol.kind != slang::ast::SymbolKind::Package)
63 return;
64 guessNamespacePrefix(symbol.getParentScope()->asSymbol(), prefix);
65 if (!symbol.name.empty()) {
66 prefix += symbol.name;
67 prefix += "::";
68 }
69}
70
71//===----------------------------------------------------------------------===//
72// Base Visitor
73//===----------------------------------------------------------------------===//
74
75namespace {
76/// Base visitor which ignores AST nodes that are handled by Slang's name
77/// resolution and type checking.
78struct BaseVisitor {
79 Context &context;
80 Location loc;
81 OpBuilder &builder;
82
83 BaseVisitor(Context &context, Location loc)
84 : context(context), loc(loc), builder(context.builder) {}
85
86 // Skip semicolons.
87 LogicalResult visit(const slang::ast::EmptyMemberSymbol &) {
88 return success();
89 }
90
91 // Skip members that are implicitly imported from some other scope for the
92 // sake of name resolution, such as enum variant names.
93 LogicalResult visit(const slang::ast::TransparentMemberSymbol &) {
94 return success();
95 }
96
97 // Handle classes without parameters or specialized generic classes
98 LogicalResult visit(const slang::ast::ClassType &classdecl) {
99 if (failed(context.buildClassProperties(classdecl)))
100 return failure();
101 return context.materializeClassMethods(classdecl);
102 }
103
104 // GenericClassDefSymbol represents parameterized (template) classes, which
105 // per IEEE 1800-2023 §8.25 are abstract and not instantiable. Slang models
106 // concrete specializations as ClassType, so we skip GenericClassDefSymbol
107 // entirely.
108 LogicalResult visit(const slang::ast::GenericClassDefSymbol &) {
109 return success();
110 }
111
112 // Skip typedefs.
113 LogicalResult visit(const slang::ast::TypeAliasType &) { return success(); }
114 LogicalResult visit(const slang::ast::ForwardingTypedefSymbol &) {
115 return success();
116 }
117
118 // Skip imports. The AST already has its names resolved.
119 LogicalResult visit(const slang::ast::ExplicitImportSymbol &) {
120 return success();
121 }
122 LogicalResult visit(const slang::ast::WildcardImportSymbol &) {
123 return success();
124 }
125
126 // Skip type parameters. The Slang AST is already monomorphized.
127 LogicalResult visit(const slang::ast::TypeParameterSymbol &) {
128 return success();
129 }
130
131 // Skip elaboration system tasks. These are reported directly by Slang.
132 LogicalResult visit(const slang::ast::ElabSystemTaskSymbol &) {
133 return success();
134 }
135
136 // Handle parameters.
137 LogicalResult visit(const slang::ast::ParameterSymbol &param) {
138 visitParameter(param);
139 return success();
140 }
141
142 LogicalResult visit(const slang::ast::SpecparamSymbol &param) {
143 visitParameter(param);
144 return success();
145 }
146
147 template <class Node>
148 void visitParameter(const Node &param) {
149 // If debug info is enabled, try to materialize the parameter's constant
150 // value on a best-effort basis and create a `dbg.variable` to track the
151 // value.
152 if (!context.options.debugInfo)
153 return;
154 auto value =
155 context.materializeConstant(param.getValue(), param.getType(), loc);
156 if (!value)
157 return;
158 if (builder.getInsertionBlock()->getParentOp() == context.intoModuleOp) {
159 auto key = LocationKey::get(param.location, context.sourceManager);
160 context.orderedRootOps.insert({key, value.getDefiningOp()});
161 }
162
163 // Prefix the parameter name with the surrounding namespace to create
164 // somewhat sane names in the IR.
165 SmallString<64> paramName;
166 guessNamespacePrefix(param.getParentScope()->asSymbol(), paramName);
167 paramName += param.name;
168
169 debug::VariableOp::create(builder, loc, builder.getStringAttr(paramName),
170 value, Value{});
171 }
172};
173} // namespace
174
175//===----------------------------------------------------------------------===//
176// Top-Level Item Conversion
177//===----------------------------------------------------------------------===//
178
179namespace {
180struct RootVisitor : public BaseVisitor {
181 using BaseVisitor::BaseVisitor;
182 using BaseVisitor::visit;
183
184 // Handle packages.
185 LogicalResult visit(const slang::ast::PackageSymbol &package) {
186 return context.convertPackage(package);
187 }
188
189 // Handle functions and tasks.
190 LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
191 if (!context.declareFunction(subroutine))
192 return failure();
193 return success();
194 }
195
196 // Handle global variables.
197 LogicalResult visit(const slang::ast::VariableSymbol &var) {
198 return context.convertGlobalVariable(var);
199 }
200
201 // Emit an error for all other members.
202 template <typename T>
203 LogicalResult visit(T &&node) {
204 mlir::emitError(loc, "unsupported construct: ")
205 << slang::ast::toString(node.kind);
206 return failure();
207 }
208};
209} // namespace
210
211//===----------------------------------------------------------------------===//
212// Package Conversion
213//===----------------------------------------------------------------------===//
214
215namespace {
216struct PackageVisitor : public BaseVisitor {
217 using BaseVisitor::BaseVisitor;
218 using BaseVisitor::visit;
219
220 // Handle functions and tasks.
221 LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
222 if (!context.declareFunction(subroutine))
223 return failure();
224 return success();
225 }
226
227 // Handle global variables.
228 LogicalResult visit(const slang::ast::VariableSymbol &var) {
229 return context.convertGlobalVariable(var);
230 }
231
232 /// Emit an error for all other members.
233 template <typename T>
234 LogicalResult visit(T &&node) {
235 mlir::emitError(loc, "unsupported package member: ")
236 << slang::ast::toString(node.kind);
237 return failure();
238 }
239};
240} // namespace
241
242//===----------------------------------------------------------------------===//
243// Module Conversion
244//===----------------------------------------------------------------------===//
245
246static moore::ProcedureKind
247convertProcedureKind(slang::ast::ProceduralBlockKind kind) {
248 switch (kind) {
249 case slang::ast::ProceduralBlockKind::Always:
250 return moore::ProcedureKind::Always;
251 case slang::ast::ProceduralBlockKind::AlwaysComb:
252 return moore::ProcedureKind::AlwaysComb;
253 case slang::ast::ProceduralBlockKind::AlwaysLatch:
254 return moore::ProcedureKind::AlwaysLatch;
255 case slang::ast::ProceduralBlockKind::AlwaysFF:
256 return moore::ProcedureKind::AlwaysFF;
257 case slang::ast::ProceduralBlockKind::Initial:
258 return moore::ProcedureKind::Initial;
259 case slang::ast::ProceduralBlockKind::Final:
260 return moore::ProcedureKind::Final;
261 }
262 llvm_unreachable("all procedure kinds handled");
263}
264
265static moore::NetKind convertNetKind(slang::ast::NetType::NetKind kind) {
266 switch (kind) {
267 case slang::ast::NetType::Supply0:
268 return moore::NetKind::Supply0;
269 case slang::ast::NetType::Supply1:
270 return moore::NetKind::Supply1;
271 case slang::ast::NetType::Tri:
272 return moore::NetKind::Tri;
273 case slang::ast::NetType::TriAnd:
274 return moore::NetKind::TriAnd;
275 case slang::ast::NetType::TriOr:
276 return moore::NetKind::TriOr;
277 case slang::ast::NetType::TriReg:
278 return moore::NetKind::TriReg;
279 case slang::ast::NetType::Tri0:
280 return moore::NetKind::Tri0;
281 case slang::ast::NetType::Tri1:
282 return moore::NetKind::Tri1;
283 case slang::ast::NetType::UWire:
284 return moore::NetKind::UWire;
285 case slang::ast::NetType::Wire:
286 return moore::NetKind::Wire;
287 case slang::ast::NetType::WAnd:
288 return moore::NetKind::WAnd;
289 case slang::ast::NetType::WOr:
290 return moore::NetKind::WOr;
291 case slang::ast::NetType::Interconnect:
292 return moore::NetKind::Interconnect;
293 case slang::ast::NetType::UserDefined:
294 return moore::NetKind::UserDefined;
295 case slang::ast::NetType::Unknown:
296 return moore::NetKind::Unknown;
297 }
298 llvm_unreachable("all net kinds handled");
299}
300
301namespace {
302struct ModuleVisitor : public BaseVisitor {
303 using BaseVisitor::visit;
304
305 // A prefix of block names such as `foo.bar.` to put in front of variable and
306 // instance names.
307 StringRef blockNamePrefix;
308
309 ModuleVisitor(Context &context, Location loc, StringRef blockNamePrefix = "")
310 : BaseVisitor(context, loc), blockNamePrefix(blockNamePrefix) {}
311
312 // Skip ports which are already handled by the module itself.
313 LogicalResult visit(const slang::ast::PortSymbol &) { return success(); }
314 LogicalResult visit(const slang::ast::MultiPortSymbol &) { return success(); }
315 LogicalResult visit(const slang::ast::InterfacePortSymbol &) {
316 return success();
317 }
318
319 // Skip genvars.
320 LogicalResult visit(const slang::ast::GenvarSymbol &genvarNode) {
321 return success();
322 }
323
324 // Skip defparams which have been handled by slang.
325 LogicalResult visit(const slang::ast::DefParamSymbol &) { return success(); }
326
327 // Ignore type parameters. These have already been handled by Slang's type
328 // checking.
329 LogicalResult visit(const slang::ast::TypeParameterSymbol &) {
330 return success();
331 }
332
333 // Expand an interface instance into individual variable/net ops
334 // in the enclosing module. Each signal declared in the interface body becomes
335 // a separate op, named with the instance name as a prefix.
336 LogicalResult
337 expandInterfaceInstance(const slang::ast::InstanceSymbol &instNode) {
338 auto prefix = (Twine(blockNamePrefix) + instNode.name + "_").str();
339 auto lowering = std::make_unique<InterfaceLowering>();
340 Context::ValueSymbolScope scope(context.valueSymbols);
341
342 auto recordMember = [&](const slang::ast::Symbol &sym,
343 Value value) -> void {
344 lowering->expandedMembers[&sym] = value;
345 auto nameAttr = builder.getStringAttr(sym.name);
346 lowering->expandedMembersByName[nameAttr] = value;
347 if (auto *valueSym = sym.as_if<slang::ast::ValueSymbol>())
348 context.valueSymbols.insert(valueSym, value);
349 };
350
351 for (const auto &member : instNode.body.members()) {
352 // Error on nested interface instances.
353 if (const auto *nestedInst = member.as_if<slang::ast::InstanceSymbol>()) {
354 if (nestedInst->body.getDefinition().definitionKind ==
355 slang::ast::DefinitionKind::Interface)
356 return mlir::emitError(loc)
357 << "nested interface instances are not supported: `"
358 << nestedInst->name << "` inside `" << instNode.name << "`";
359 }
360 // Expand variables.
361 if (const auto *var = member.as_if<slang::ast::VariableSymbol>()) {
362 auto loweredType = context.convertType(*var->getDeclaredType());
363 if (!loweredType)
364 return failure();
365 auto varOp = moore::VariableOp::create(
366 builder, loc,
367 moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
368 builder.getStringAttr(Twine(prefix) + StringRef(var->name)),
369 Value());
370 recordMember(*var, varOp);
371 continue;
372 }
373 // Expand nets
374 if (const auto *net = member.as_if<slang::ast::NetSymbol>()) {
375 auto loweredType = context.convertType(*net->getDeclaredType());
376 if (!loweredType)
377 return failure();
378 auto netKind = convertNetKind(net->netType.netKind);
379 if (netKind == moore::NetKind::Interconnect ||
380 netKind == moore::NetKind::UserDefined ||
381 netKind == moore::NetKind::Unknown)
382 return mlir::emitError(loc, "unsupported net kind `")
383 << net->netType.name << "`";
384 auto netOp = moore::NetOp::create(
385 builder, loc,
386 moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
387 builder.getStringAttr(Twine(prefix) + StringRef(net->name)),
388 netKind, Value());
389 recordMember(*net, netOp);
390 continue;
391 }
392 // Silently skip other members (modports, parameters , etc.)
393 }
394
395 // Record interface ports by mapping them to their connected expressions.
396 // This is required for virtual interface usage (e.g. `vif.clk`) and for
397 // modports that reference interface ports.
398 for (const auto *con : instNode.getPortConnections()) {
399 const auto *expr = con->getExpression();
400 const auto *port = con->port.as_if<slang::ast::PortSymbol>();
401 if (!port)
402 continue;
403 if (!expr) {
404 // Leave unconnected interface ports unresolved for now.
405 continue;
406 }
407
408 Value lvalue = context.convertLvalueExpression(*expr);
409 if (!lvalue)
410 return failure();
411
412 recordMember(*port, lvalue);
413 if (port->internalSymbol) {
414 recordMember(*port->internalSymbol, lvalue);
415 }
416 }
417
418 // Lower executable interface body members now that all interface signals
419 // and port bindings are available in the scoped `valueSymbols` table.
420 for (const auto &member : instNode.body.members()) {
421 switch (member.kind) {
422 case slang::ast::SymbolKind::ContinuousAssign:
423 case slang::ast::SymbolKind::ProceduralBlock:
424 case slang::ast::SymbolKind::StatementBlock:
425 break;
426 default:
427 continue;
428 }
429 auto memberLoc = context.convertLocation(member.location);
430 if (failed(member.visit(ModuleVisitor(context, memberLoc, prefix))))
431 return failure();
432 if (failed(context.flushPendingMonitors()))
433 return failure();
434 }
435
436 context.interfaceInstanceStorage.push_back(std::move(lowering));
437 context.interfaceInstances.insert(
438 &instNode, context.interfaceInstanceStorage.back().get());
439 return success();
440 }
441
442 // Handle instances.
443 LogicalResult visit(const slang::ast::InstanceSymbol &instNode) {
444 using slang::ast::ArgumentDirection;
445 using slang::ast::AssignmentExpression;
446 using slang::ast::MultiPortSymbol;
447 using slang::ast::PortSymbol;
448
449 if (context.predeclaredInstances.contains(&instNode))
450 return success();
451
452 // Always operate on the canonical instance body if there is one.
453 // This means any symbols we record will be the symbols from the
454 // canonical body, which will match up with the symbols encountered
455 // by analyses which visit the canonical bodies.
456 const slang::ast::InstanceBodySymbol *body = getCanonicalBody(instNode);
457
458 // Interface instances are expanded inline into individual variable/net ops
459 // rather than creating a moore.instance op.
460 auto defKind = body->getDefinition().definitionKind;
461 if (defKind == slang::ast::DefinitionKind::Interface) {
462 if (context.interfaceInstances.lookup(&instNode))
463 return success();
464 return expandInterfaceInstance(instNode);
465 }
466
467 auto *moduleLowering = context.convertModuleHeader(body);
468 if (!moduleLowering)
469 return failure();
470 auto module = moduleLowering->op;
471 auto moduleType = module.getModuleType();
472
473 // Set visibility attribute for instantiated module.
474 SymbolTable::setSymbolVisibility(module, SymbolTable::Visibility::Private);
475
476 // Prepare the values that are involved in port connections. This creates
477 // rvalues for input ports and appropriate lvalues for output, inout, and
478 // ref ports. We also separate multi-ports into the individual underlying
479 // ports with their corresponding connection.
481 portValues.reserve(moduleType.getNumPorts());
482
483 // Map each InterfacePortSymbol to the connected interface instance.
484 SmallDenseMap<const slang::ast::InterfacePortSymbol *,
485 const slang::ast::InstanceSymbol *>
486 ifaceConnMap;
487
488 for (const auto *con : instNode.getPortConnections()) {
489 const auto *expr = con->getExpression();
490
491 // Handle unconnected behavior. The expression is null if it have no
492 // connection for the port.
493 if (!expr) {
494 auto *port = con->port.as_if<PortSymbol>();
495 if (auto *existingPort =
496 moduleLowering->portsBySyntaxNode.lookup(port->getSyntax()))
497 port = existingPort;
498
499 switch (port->direction) {
500 case ArgumentDirection::In: {
501 auto refType = moore::RefType::get(
502 cast<moore::UnpackedType>(context.convertType(port->getType())));
503
504 if (const auto *net =
505 port->internalSymbol->as_if<slang::ast::NetSymbol>()) {
506 auto netOp = moore::NetOp::create(
507 builder, loc, refType,
508 StringAttr::get(builder.getContext(), net->name),
509 convertNetKind(net->netType.netKind), nullptr);
510 auto readOp = moore::ReadOp::create(builder, loc, netOp);
511 portValues.insert({port, readOp});
512 } else if (const auto *var =
513 port->internalSymbol
514 ->as_if<slang::ast::VariableSymbol>()) {
515 auto varOp = moore::VariableOp::create(
516 builder, loc, refType,
517 StringAttr::get(builder.getContext(), var->name), nullptr);
518 auto readOp = moore::ReadOp::create(builder, loc, varOp);
519 portValues.insert({port, readOp});
520 } else {
521 return mlir::emitError(loc)
522 << "unsupported internal symbol for unconnected port `"
523 << port->name << "`";
524 }
525 continue;
526 }
527
528 // No need to express unconnected behavior for output port, skip to the
529 // next iteration of the loop.
530 case ArgumentDirection::Out:
531 continue;
532
533 case ArgumentDirection::InOut:
534 case ArgumentDirection::Ref: {
535 auto refType = moore::RefType::get(
536 cast<moore::UnpackedType>(context.convertType(port->getType())));
537
538 if (const auto *net =
539 port->internalSymbol->as_if<slang::ast::NetSymbol>()) {
540 auto netOp = moore::NetOp::create(
541 builder, loc, refType,
542 StringAttr::get(builder.getContext(), net->name),
543 convertNetKind(net->netType.netKind), nullptr);
544 portValues.insert({port, netOp});
545 } else if (const auto *var =
546 port->internalSymbol
547 ->as_if<slang::ast::VariableSymbol>()) {
548 auto varOp = moore::VariableOp::create(
549 builder, loc, refType,
550 StringAttr::get(builder.getContext(), var->name), nullptr);
551 portValues.insert({port, varOp});
552 } else {
553 return mlir::emitError(loc)
554 << "unsupported internal symbol for unconnected port `"
555 << port->name << "`";
556 }
557 continue;
558 }
559 }
560 }
561
562 // Unpack the `<expr> = EmptyArgument` pattern emitted by Slang for
563 // output and inout ports.
564 if (const auto *assign = expr->as_if<AssignmentExpression>())
565 expr = &assign->left();
566
567 // Regular ports lower the connected expression to an lvalue or rvalue and
568 // either attach it to the instance as an operand (for input, inout, and
569 // ref ports), or assign an instance output to it (for output ports).
570 if (auto *port = con->port.as_if<PortSymbol>()) {
571 // Convert as rvalue for inputs, lvalue for all others.
572 auto value = (port->direction == ArgumentDirection::In)
573 ? context.convertRvalueExpression(*expr)
574 : context.convertLvalueExpression(*expr);
575 if (!value)
576 return failure();
577 if (auto *existingPort =
578 moduleLowering->portsBySyntaxNode.lookup(con->port.getSyntax()))
579 port = existingPort;
580 portValues.insert({port, value});
581 continue;
582 }
583
584 // Multi-ports lower the connected expression to an lvalue and then slice
585 // it up into multiple sub-values, one for each of the ports in the
586 // multi-port.
587 if (const auto *multiPort = con->port.as_if<MultiPortSymbol>()) {
588 // Convert as lvalue.
589 auto value = context.convertLvalueExpression(*expr);
590 if (!value)
591 return failure();
592 unsigned offset = 0;
593 for (const auto *port : llvm::reverse(multiPort->ports)) {
594 if (auto *existingPort = moduleLowering->portsBySyntaxNode.lookup(
595 con->port.getSyntax()))
596 port = existingPort;
597 unsigned width = port->getType().getBitWidth();
598 auto sliceType = context.convertType(port->getType());
599 if (!sliceType)
600 return failure();
601 Value slice = moore::ExtractRefOp::create(
602 builder, loc,
603 moore::RefType::get(cast<moore::UnpackedType>(sliceType)), value,
604 offset);
605 // Create the "ReadOp" for input ports.
606 if (port->direction == ArgumentDirection::In)
607 slice = moore::ReadOp::create(builder, loc, slice);
608 portValues.insert({port, slice});
609 offset += width;
610 }
611 continue;
612 }
613
614 // Interface ports: record the connected interface instance for later
615 // resolution via InterfaceLowering.
616 if (const auto *ifacePort =
617 con->port.as_if<slang::ast::InterfacePortSymbol>()) {
618 auto ifaceConn = con->getIfaceConn();
619 const auto *connInst =
620 ifaceConn.first->as_if<slang::ast::InstanceSymbol>();
621 if (connInst)
622 ifaceConnMap[ifacePort] = connInst;
623 continue;
624 }
625
626 mlir::emitError(loc) << "unsupported instance port `" << con->port.name
627 << "` (" << slang::ast::toString(con->port.kind)
628 << ")";
629 return failure();
630 }
631
632 // Match the module's ports up with the port values determined above.
633 // Values are placed by slot index so regular and expanded
634 // interface-modport ports interleave in declaration order.
635 SmallVector<Value> inputValues(moduleLowering->numExplicitInputs);
636 SmallVector<Value> outputValues(moduleLowering->numExplicitOutputs);
637
638 for (auto &port : moduleLowering->ports) {
639 auto value = portValues.lookup(&port.ast);
640 if (port.ast.direction == ArgumentDirection::Out)
641 outputValues[*port.outputIdx] = value;
642 else
643 inputValues[*port.inputIdx] = value;
644 }
645
646 // Resolve flattened interface port values. For each flattened port,
647 // look up the connected interface instance's InterfaceLowering and
648 // find the body member's expanded SSA value.
649 for (auto &fp : moduleLowering->ifacePorts) {
650 if (!fp.bodySym || !fp.origin)
651 continue;
652 // Find which interface instance is connected to this port.
653 auto it = ifaceConnMap.find(fp.origin);
654 if (it == ifaceConnMap.end()) {
655 mlir::emitError(loc)
656 << "no interface connection for port `" << fp.name << "`";
657 return failure();
658 }
659 const auto *connInst = it->second;
660 // Look up the InterfaceLowering for that instance.
661 auto *ifaceLowering = context.interfaceInstances.lookup(connInst);
662 if (!ifaceLowering) {
663 mlir::emitError(loc)
664 << "interface instance `" << connInst->name << "` was not expanded";
665 return failure();
666 }
667 // Find the expanded SSA value for this body member.
668 auto valIt = ifaceLowering->expandedMembers.find(fp.bodySym);
669 if (valIt == ifaceLowering->expandedMembers.end()) {
670 mlir::emitError(loc)
671 << "unresolved interface port signal `" << fp.name << "`";
672 return failure();
673 }
674 Value val = valIt->second;
675 if (fp.direction == hw::ModulePort::Output) {
676 outputValues[*fp.outputIdx] = val;
677 } else {
678 // For input ports, if the value is a ref (from VariableOp/NetOp),
679 // read it to get the rvalue, unless the port itself expects a ref.
680 if (isa<moore::RefType>(val.getType()) && !isa<moore::RefType>(fp.type))
681 val = moore::ReadOp::create(builder, loc, val);
682 inputValues[*fp.inputIdx] = val;
683 }
684 }
685
686 // Insert conversions for input ports. Unfilled slots (e.g. unresolved
687 // interface-modport ports) are reported by the null-check loop below.
688 for (auto [value, type] :
689 llvm::zip(inputValues, moduleType.getInputTypes())) {
690 if (!value)
691 continue;
692 // TODO: This should honor signedness in the conversion.
693 value = context.materializeConversion(type, value, false, value.getLoc());
694 if (!value)
695 return mlir::emitError(loc) << "unsupported port";
696 }
697
698 // Here we use the hierarchical value recorded in `Context::valueSymbols`.
699 // Then we pass it as the input port with the ref<T> type of the instance.
700 // Note that `body` is always the canonical instance body here and in the
701 // `hierPaths` keys.
702 for (const auto &hierPath : context.hierPaths[body]) {
703 assert(!hierPath.valueSyms.empty() && "hierPath must have valueSyms");
704 if (!hierPath.hierName || hierPath.direction != ArgumentDirection::In)
705 continue;
706 // Which alias is bound in scope depends on which subtree the reference
707 // was observed in, so try them all; a null value is caught below.
708 Value hierValue;
709 for (auto &alias : hierPath.valueSyms)
710 if ((hierValue = context.valueSymbols.lookup(alias.first)))
711 break;
712 inputValues.push_back(hierValue);
713 }
714
715 // Check that all input values are non-null before creating the instance.
716 for (auto value : inputValues)
717 if (!value)
718 return mlir::emitError(loc) << "unsupported port";
719
720 // Create the instance op itself.
721 auto inputNames = builder.getArrayAttr(moduleType.getInputNames());
722 auto outputNames = builder.getArrayAttr(moduleType.getOutputNames());
723 auto inst = moore::InstanceOp::create(
724 builder, loc, moduleType.getOutputTypes(),
725 builder.getStringAttr(Twine(blockNamePrefix) + instNode.name),
726 FlatSymbolRefAttr::get(module.getSymNameAttr()), inputValues,
727 inputNames, outputNames);
728
729 // An alias belongs to this instance if the body containing its symbol is
730 // nested anywhere under the instance in the elaborated tree.
731 auto aliasReachedThroughInstance =
732 [&](const slang::ast::InstanceBodySymbol *aliasBody) {
733 for (auto *b = aliasBody; b && b->parentInstance;
734 b = b->parentInstance->getParentScope()->getContainingInstance())
735 if (b->parentInstance == &instNode)
736 return true;
737 return false;
738 };
739
740 // Record instance's results generated by hierarchical names.
741 // Store in both valueSymbols (for same-scope lookups) and the persistent
742 // hierValueSymbols map (for cross-scope lookups from other modules).
743 // The hierValueSymbols key is {&instNode, hierName} to ensure
744 // instance-specific resolution (e.g., p1 vs p2 get separate entries).
745 for (const auto &hierPath : context.hierPaths[body])
746 if (hierPath.idx && hierPath.direction == ArgumentDirection::Out) {
747 auto result = inst->getResult(*hierPath.idx);
748 for (auto &alias : hierPath.valueSyms)
749 if (aliasReachedThroughInstance(alias.second))
750 context.valueSymbols.insert(alias.first, result);
751 context.hierValueSymbols[{&instNode, hierPath.hierName}] = result;
752 }
753
754 // Assign output values from the instance to the connected expression.
755 for (auto [lvalue, output] : llvm::zip(outputValues, inst.getOutputs())) {
756 if (!lvalue)
757 continue;
758 Value rvalue = output;
759 auto dstType = cast<moore::RefType>(lvalue.getType()).getNestedType();
760 // TODO: This should honor signedness in the conversion.
761 rvalue = context.materializeConversion(dstType, rvalue, false, loc);
762 moore::ContinuousAssignOp::create(builder, loc, lvalue, rvalue);
763 }
764
765 return success();
766 }
767
768 // Handle variables.
769 LogicalResult visit(const slang::ast::VariableSymbol &varNode) {
770 auto ref = context.valueSymbols.lookup(&varNode);
771 if (!ref)
772 return mlir::emitError(loc)
773 << "internal error: missing predeclared variable `" << varNode.name
774 << "`";
775
776 auto varOp = ref.getDefiningOp<moore::VariableOp>();
777 if (!varOp)
778 return mlir::emitError(loc)
779 << "internal error: predeclared variable `" << varNode.name
780 << "` is not a moore.variable";
781
782 if (const auto *init = varNode.getInitializer()) {
783 auto loweredType = cast<moore::RefType>(ref.getType()).getNestedType();
784 auto initial = context.convertRvalueExpression(*init, loweredType);
785 if (!initial)
786 return failure();
787 varOp.getInitialMutable().assign(initial);
788 }
789
790 return success();
791 }
792
793 // Handle nets.
794 LogicalResult visit(const slang::ast::NetSymbol &netNode) {
795 auto ref = context.valueSymbols.lookup(&netNode);
796 if (!ref)
797 return mlir::emitError(loc) << "internal error: missing predeclared net `"
798 << netNode.name << "`";
799
800 auto netOp = ref.getDefiningOp<moore::NetOp>();
801 if (!netOp)
802 return mlir::emitError(loc) << "internal error: predeclared net `"
803 << netNode.name << "` is not a moore.net";
804
805 if (const auto *init = netNode.getInitializer()) {
806 auto loweredType = cast<moore::RefType>(ref.getType()).getNestedType();
807 auto assignment = context.convertRvalueExpression(*init, loweredType);
808 if (!assignment)
809 return failure();
810 netOp.getAssignmentMutable().assign(assignment);
811 }
812 return success();
813 }
814
815 // Handle continuous assignments.
816 LogicalResult visit(const slang::ast::ContinuousAssignSymbol &assignNode) {
817 const auto &expr =
818 assignNode.getAssignment().as<slang::ast::AssignmentExpression>();
819 auto lhs = context.convertLvalueExpression(expr.left());
820 if (!lhs)
821 return failure();
822
823 auto rhs = context.convertRvalueExpression(
824 expr.right(), cast<moore::RefType>(lhs.getType()).getNestedType());
825 if (!rhs)
826 return failure();
827
828 // Handle delayed assignments.
829 if (auto *timingCtrl = assignNode.getDelay()) {
830 if (auto *ctrl = timingCtrl->as_if<slang::ast::DelayControl>()) {
831 auto delay = context.convertRvalueExpression(
832 ctrl->expr, moore::TimeType::get(builder.getContext()));
833 if (!delay)
834 return failure();
835 moore::DelayedContinuousAssignOp::create(builder, loc, lhs, rhs, delay);
836 return success();
837 }
838 mlir::emitError(loc) << "unsupported delay with rise/fall/turn-off";
839 return failure();
840 }
841
842 // Otherwise this is a regular assignment.
843 moore::ContinuousAssignOp::create(builder, loc, lhs, rhs);
844 return success();
845 }
846
847 // Handle procedures.
848 LogicalResult convertProcedure(moore::ProcedureKind kind,
849 const slang::ast::Statement &body) {
850 if (body.as_if<slang::ast::ConcurrentAssertionStatement>())
851 return context.convertStatement(body);
852 auto procOp = moore::ProcedureOp::create(builder, loc, kind);
853 OpBuilder::InsertionGuard guard(builder);
854 builder.setInsertionPointToEnd(&procOp.getBody().emplaceBlock());
855 Context::ValueSymbolScope scope(context.valueSymbols);
856 Context::VirtualInterfaceMemberScope vifMemberScope(
857 context.virtualIfaceMembers);
858 if (failed(context.convertStatement(body)))
859 return failure();
860 if (builder.getBlock())
861 moore::ReturnOp::create(builder, loc);
862 return success();
863 }
864
865 LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) {
866 // Detect `always @(*) <stmt>` and convert to `always_comb <stmt>` if
867 // requested by the user.
868 if (context.options.lowerAlwaysAtStarAsComb) {
869 auto *stmt = procNode.getBody().as_if<slang::ast::TimedStatement>();
870 if (procNode.procedureKind == slang::ast::ProceduralBlockKind::Always &&
871 stmt &&
872 stmt->timing.kind == slang::ast::TimingControlKind::ImplicitEvent)
873 return convertProcedure(moore::ProcedureKind::AlwaysComb, stmt->stmt);
874 }
875
876 return convertProcedure(convertProcedureKind(procNode.procedureKind),
877 procNode.getBody());
878 }
879
880 // Handle generate block.
881 LogicalResult visit(const slang::ast::GenerateBlockSymbol &genNode) {
882 // Ignore uninstantiated blocks.
883 if (genNode.isUninstantiated)
884 return success();
885
886 // If the block has a name, add it to the list of block name prefices.
887 SmallString<64> prefix = blockNamePrefix;
888 if (!genNode.name.empty() ||
889 genNode.getParentScope()->asSymbol().kind !=
890 slang::ast::SymbolKind::GenerateBlockArray) {
891 prefix += genNode.getExternalName();
892 prefix += '.';
893 }
894
895 // Visit each member of the generate block.
896 for (auto &member : genNode.members())
897 if (failed(member.visit(ModuleVisitor(context, loc, prefix))))
898 return failure();
899 return success();
900 }
901
902 // Handle generate block array.
903 LogicalResult visit(const slang::ast::GenerateBlockArraySymbol &genArrNode) {
904 // If the block has a name, add it to the list of block name prefices and
905 // prepare to append the array index and a `.` in each iteration.
906 SmallString<64> prefix = blockNamePrefix;
907 prefix += genArrNode.getExternalName();
908 prefix += '_';
909 auto prefixBaseLen = prefix.size();
910
911 // Visit each iteration entry of the generate block.
912 for (const auto *entry : genArrNode.entries) {
913 // Append the index to the prefix.
914 prefix.resize(prefixBaseLen);
915 if (entry->arrayIndex)
916 prefix += entry->arrayIndex->toString();
917 else
918 Twine(entry->constructIndex).toVector(prefix);
919 prefix += '.';
920
921 // Visit this iteration entry.
922 if (failed(entry->asSymbol().visit(ModuleVisitor(context, loc, prefix))))
923 return failure();
924 }
925 return success();
926 }
927
928 // Ignore statement block symbols. These get generated by Slang for blocks
929 // with variables and other declarations. For example, having an initial
930 // procedure with a variable declaration, such as `initial begin int x;
931 // end`, will create the procedure with a block and variable declaration as
932 // expected, but will also create a `StatementBlockSymbol` with just the
933 // variable layout _next to_ the initial procedure.
934 LogicalResult visit(const slang::ast::StatementBlockSymbol &) {
935 return success();
936 }
937
938 // Ignore sequence declarations. The declarations are already evaluated by
939 // Slang and are part of an AssertionInstance.
940 LogicalResult visit(const slang::ast::SequenceSymbol &seqNode) {
941 return success();
942 }
943
944 // Ignore property declarations. The declarations are already evaluated by
945 // Slang and are part of an AssertionInstance.
946 LogicalResult visit(const slang::ast::PropertySymbol &propNode) {
947 return success();
948 }
949
950 // Ignore let declarations. Slang expands uses into AssertionInstance
951 // expressions, which are lowered when the use site is imported.
952 LogicalResult visit(const slang::ast::LetDeclSymbol &) { return success(); }
953
954 // Handle functions and tasks.
955 LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
956 if (!context.declareFunction(subroutine))
957 return failure();
958 return success();
959 }
960
961 // Handle primitive instances.
962 LogicalResult visit(const slang::ast::PrimitiveInstanceSymbol &prim) {
963 return context.convertPrimitiveInstance(prim);
964 }
965
966 /// Emit an error for all other members.
967 template <typename T>
968 LogicalResult visit(T &&node) {
969 mlir::emitError(loc, "unsupported module member: ")
970 << slang::ast::toString(node.kind);
971 return failure();
972 }
973};
974
975struct ModulePredeclaration {
976 Context &context;
977 OpBuilder &builder;
978
979 ModulePredeclaration(Context &context)
980 : context(context), builder(context.builder) {}
981
982 LogicalResult declareVariable(const slang::ast::VariableSymbol &varNode,
983 Location loc, StringRef blockNamePrefix) {
984 auto loweredType = context.convertType(*varNode.getDeclaredType());
985 if (!loweredType)
986 return failure();
987
988 auto varOp = moore::VariableOp::create(
989 builder, loc,
990 moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
991 builder.getStringAttr(Twine(blockNamePrefix) + varNode.name), Value{});
992 context.valueSymbols.insert(&varNode, varOp);
993
994 const auto &canonTy = varNode.getType().getCanonicalType();
995 if (const auto *vi = canonTy.as_if<slang::ast::VirtualInterfaceType>())
996 if (failed(context.registerVirtualInterfaceMembers(varNode, *vi, loc)))
997 return failure();
998
999 return success();
1000 }
1001
1002 LogicalResult declareNet(const slang::ast::NetSymbol &netNode, Location loc,
1003 StringRef blockNamePrefix) {
1004 auto loweredType = context.convertType(*netNode.getDeclaredType());
1005 if (!loweredType)
1006 return failure();
1007
1008 auto netkind = convertNetKind(netNode.netType.netKind);
1009 if (netkind == moore::NetKind::Interconnect ||
1010 netkind == moore::NetKind::UserDefined ||
1011 netkind == moore::NetKind::Unknown)
1012 return mlir::emitError(loc, "unsupported net kind `")
1013 << netNode.netType.name << "`";
1014
1015 auto netOp = moore::NetOp::create(
1016 builder, loc,
1017 moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
1018 builder.getStringAttr(Twine(blockNamePrefix) + netNode.name), netkind,
1019 Value{});
1020 context.valueSymbols.insert(&netNode, netOp);
1021 return success();
1022 }
1023
1024 SmallString<64>
1025 getGenerateBlockPrefix(const slang::ast::GenerateBlockSymbol &genNode,
1026 StringRef blockNamePrefix) {
1027 SmallString<64> prefix = blockNamePrefix;
1028 if (!genNode.name.empty() ||
1029 genNode.getParentScope()->asSymbol().kind !=
1030 slang::ast::SymbolKind::GenerateBlockArray) {
1031 prefix += genNode.getExternalName();
1032 prefix += '.';
1033 }
1034 return prefix;
1035 }
1036
1037 LogicalResult
1038 predeclareStorageGenerateBlock(const slang::ast::GenerateBlockSymbol &genNode,
1039 StringRef blockNamePrefix) {
1040 if (genNode.isUninstantiated)
1041 return success();
1042 return predeclareStorageScope(
1043 genNode, getGenerateBlockPrefix(genNode, blockNamePrefix));
1044 }
1045
1046 LogicalResult predeclareInterfaceGenerateBlock(
1047 const slang::ast::GenerateBlockSymbol &genNode,
1048 StringRef blockNamePrefix) {
1049 if (genNode.isUninstantiated)
1050 return success();
1051 return predeclareInterfaceScope(
1052 genNode, getGenerateBlockPrefix(genNode, blockNamePrefix));
1053 }
1054
1055 LogicalResult predeclareModuleInstanceGenerateBlock(
1056 const slang::ast::GenerateBlockSymbol &genNode,
1057 StringRef blockNamePrefix) {
1058 if (genNode.isUninstantiated)
1059 return success();
1060 return predeclareModuleInstanceScope(
1061 genNode, getGenerateBlockPrefix(genNode, blockNamePrefix));
1062 }
1063
1064 LogicalResult predeclareGenerateBlockArray(
1065 const slang::ast::GenerateBlockArraySymbol &genArrNode,
1066 StringRef blockNamePrefix,
1067 llvm::function_ref<LogicalResult(const slang::ast::GenerateBlockSymbol &,
1068 StringRef)>
1069 predeclareBlock) {
1070 SmallString<64> prefix = blockNamePrefix;
1071 prefix += genArrNode.getExternalName();
1072 prefix += '_';
1073 auto prefixBaseLen = prefix.size();
1074
1075 for (const auto *entry : genArrNode.entries) {
1076 prefix.resize(prefixBaseLen);
1077 if (entry->arrayIndex)
1078 prefix += entry->arrayIndex->toString();
1079 else
1080 Twine(entry->constructIndex).toVector(prefix);
1081 prefix += '.';
1082
1083 if (failed(predeclareBlock(*entry, prefix)))
1084 return failure();
1085 }
1086 return success();
1087 }
1088
1089 LogicalResult predeclareStorageMember(const slang::ast::Symbol &member,
1090 StringRef blockNamePrefix) {
1091 auto loc = context.convertLocation(member.location);
1092 if (const auto *varNode = member.as_if<slang::ast::VariableSymbol>())
1093 return declareVariable(*varNode, loc, blockNamePrefix);
1094
1095 if (const auto *netNode = member.as_if<slang::ast::NetSymbol>())
1096 return declareNet(*netNode, loc, blockNamePrefix);
1097
1098 if (const auto *genNode = member.as_if<slang::ast::GenerateBlockSymbol>())
1099 return predeclareStorageGenerateBlock(*genNode, blockNamePrefix);
1100
1101 if (const auto *genArrNode =
1102 member.as_if<slang::ast::GenerateBlockArraySymbol>())
1103 return predeclareGenerateBlockArray(
1104 *genArrNode, blockNamePrefix,
1105 [&](const slang::ast::GenerateBlockSymbol &gen, StringRef prefix) {
1106 return predeclareStorageGenerateBlock(gen, prefix);
1107 });
1108
1109 return success();
1110 }
1111
1112 LogicalResult predeclareInterfaceMember(const slang::ast::Symbol &member,
1113 StringRef blockNamePrefix) {
1114 auto loc = context.convertLocation(member.location);
1115 if (const auto *instNode = member.as_if<slang::ast::InstanceSymbol>()) {
1116 if (instNode->body.getDefinition().definitionKind ==
1117 slang::ast::DefinitionKind::Interface)
1118 return ModuleVisitor(context, loc, blockNamePrefix)
1119 .expandInterfaceInstance(*instNode);
1120 return success();
1121 }
1122
1123 if (const auto *genNode = member.as_if<slang::ast::GenerateBlockSymbol>())
1124 return predeclareInterfaceGenerateBlock(*genNode, blockNamePrefix);
1125
1126 if (const auto *genArrNode =
1127 member.as_if<slang::ast::GenerateBlockArraySymbol>())
1128 return predeclareGenerateBlockArray(
1129 *genArrNode, blockNamePrefix,
1130 [&](const slang::ast::GenerateBlockSymbol &gen, StringRef prefix) {
1131 return predeclareInterfaceGenerateBlock(gen, prefix);
1132 });
1133
1134 return success();
1135 }
1136
1137 LogicalResult predeclareModuleInstanceMember(const slang::ast::Symbol &member,
1138 StringRef blockNamePrefix) {
1139 auto loc = context.convertLocation(member.location);
1140 if (const auto *instNode = member.as_if<slang::ast::InstanceSymbol>()) {
1141 if (instNode->body.getDefinition().definitionKind !=
1142 slang::ast::DefinitionKind::Interface) {
1143 if (failed(
1144 ModuleVisitor(context, loc, blockNamePrefix).visit(*instNode)))
1145 return failure();
1146 context.predeclaredInstances.insert(instNode);
1147 }
1148 return success();
1149 }
1150
1151 if (const auto *genNode = member.as_if<slang::ast::GenerateBlockSymbol>())
1152 return predeclareModuleInstanceGenerateBlock(*genNode, blockNamePrefix);
1153
1154 if (const auto *genArrNode =
1155 member.as_if<slang::ast::GenerateBlockArraySymbol>())
1156 return predeclareGenerateBlockArray(
1157 *genArrNode, blockNamePrefix,
1158 [&](const slang::ast::GenerateBlockSymbol &gen, StringRef prefix) {
1159 return predeclareModuleInstanceGenerateBlock(gen, prefix);
1160 });
1161
1162 return success();
1163 }
1164
1165 LogicalResult predeclareStorageScope(const slang::ast::Scope &scope,
1166 StringRef blockNamePrefix) {
1167 for (auto &member : scope.members())
1168 if (failed(predeclareStorageMember(member, blockNamePrefix)))
1169 return failure();
1170 return success();
1171 }
1172
1173 LogicalResult predeclareInterfaceScope(const slang::ast::Scope &scope,
1174 StringRef blockNamePrefix) {
1175 for (auto &member : scope.members())
1176 if (failed(predeclareInterfaceMember(member, blockNamePrefix)))
1177 return failure();
1178 return success();
1179 }
1180
1181 LogicalResult predeclareModuleInstanceScope(const slang::ast::Scope &scope,
1182 StringRef blockNamePrefix) {
1183 for (auto &member : scope.members())
1184 if (failed(predeclareModuleInstanceMember(member, blockNamePrefix)))
1185 return failure();
1186 return success();
1187 }
1188
1189 LogicalResult predeclareScope(const slang::ast::Scope &scope,
1190 StringRef blockNamePrefix) {
1191 // First create variables and nets for the whole generated scope tree so
1192 // later phases can bind port connections or hierarchical references to
1193 // declarations that appear later in source.
1194 if (failed(predeclareStorageScope(scope, blockNamePrefix)))
1195 return failure();
1196
1197 // Then expand interface instances. Interface expansion may lower
1198 // continuous assignments or procedures from the interface body, so all
1199 // storage symbols must already be available.
1200 if (failed(predeclareInterfaceScope(scope, blockNamePrefix)))
1201 return failure();
1202
1203 // Finally instantiate modules. This makes later hierarchical references
1204 // to instance internals available before earlier procedural blocks lower.
1205 return predeclareModuleInstanceScope(scope, blockNamePrefix);
1206 }
1207};
1208} // namespace
1209
1210//===----------------------------------------------------------------------===//
1211// Structure and Hierarchy Conversion
1212//===----------------------------------------------------------------------===//
1213
1214/// Convert an entire Slang compilation to MLIR ops. This is the main entry
1215/// point for the conversion.
1216LogicalResult Context::convertCompilation() {
1217 const auto &root = compilation.getRoot();
1218
1219 // Keep track of the local time scale. `getTimeScale` automatically looks
1220 // through parent scopes to find the time scale effective locally.
1221 auto prevTimeScale = timeScale;
1222 timeScale = root.getTimeScale().value_or(slang::TimeScale());
1223 llvm::scope_exit timeScaleGuard([&] { timeScale = prevTimeScale; });
1224
1225 // Analyze function captures upfront so that function declarations can be
1226 // created with the correct signature including capture parameters.
1227 SmallVector<AmbiguousHierCapture> ambiguousHierCaptures;
1228 functionCaptures = analyzeFunctionCaptures(root, ambiguousHierCaptures);
1229 for (auto &ambiguous : ambiguousHierCaptures) {
1230 auto d = mlir::emitError(convertLocation(ambiguous.function->location))
1231 << "hierarchical reference to `" << ambiguous.symbol->name
1232 << "` is ambiguous: this function reaches it through more than "
1233 "one instance of the same module, which is not yet supported";
1234 d.attachNote(convertLocation(ambiguous.symbol->location))
1235 << "symbol declared here";
1236 }
1237 if (!ambiguousHierCaptures.empty())
1238 return failure();
1239
1240 // Visit the whole AST to collect the hierarchical names without any operation
1241 // creating.
1242 for (auto *inst : root.topInstances)
1243 traverseInstanceBody(*inst);
1244
1245 // Analyze the compilation to infer clocks for assertion system calls
1246 // using Slang's LRM clock inference.
1248
1249 // Visit all top-level declarations in all compilation units. This does not
1250 // include instantiable constructs like modules, interfaces, and programs,
1251 // which are listed separately as top instances.
1252 for (auto *unit : root.compilationUnits) {
1253 recordDPIExportDirectives(*this, *unit, unit->getSyntax());
1254 for (const auto &member : unit->members()) {
1255 auto loc = convertLocation(member.location);
1256 if (failed(member.visit(RootVisitor(*this, loc))))
1257 return failure();
1258 }
1259 }
1260
1261 // Prime the root definition worklist by adding all the top-level modules.
1262 // Interfaces are not lowered as modules; they are expanded inline at each
1263 // use site, so skip them here.
1264 SmallVector<const slang::ast::InstanceSymbol *> topInstances;
1265 for (auto *inst : root.topInstances) {
1266 const slang::ast::InstanceBodySymbol *body = getCanonicalBody(*inst);
1267 if (body->getDefinition().definitionKind !=
1268 slang::ast::DefinitionKind::Interface)
1269 if (!convertModuleHeader(body))
1270 return failure();
1271 }
1272
1273 // Convert all the root module definitions.
1274 while (!moduleWorklist.empty()) {
1275 auto *module = moduleWorklist.front();
1276 moduleWorklist.pop();
1277 if (failed(convertModuleBody(module)))
1278 return failure();
1279 }
1280
1281 // It's possible that after converting modules, we haven't converted all
1282 // methods yet, especially if they are unused. Do that in this pass.
1283 SmallVector<const slang::ast::ClassType *, 16> classMethodWorklist;
1284 classMethodWorklist.reserve(classes.size());
1285 for (auto &kv : classes)
1286 classMethodWorklist.push_back(kv.first);
1287
1288 for (auto *inst : classMethodWorklist) {
1289 if (failed(materializeClassMethods(*inst)))
1290 return failure();
1291 }
1292
1293 // Define all function bodies. Functions are declared (and pushed onto the
1294 // worklist) during module body conversion and class method materialization.
1295 // Defining a function body may discover additional functions through call
1296 // expressions, which are declared and added to the worklist on the fly.
1297 while (!functionWorklist.empty()) {
1298 auto *fn = functionWorklist.front();
1299 functionWorklist.pop();
1300 if (failed(defineFunction(*fn)))
1301 return failure();
1302 }
1303
1304 // Convert the initializers of global variables.
1305 for (auto *var : globalVariableWorklist) {
1306 auto varOp = globalVariables.at(var);
1307 auto &block = varOp.getInitRegion().emplaceBlock();
1308 OpBuilder::InsertionGuard guard(builder);
1309 builder.setInsertionPointToEnd(&block);
1310 auto value =
1311 convertRvalueExpression(*var->getInitializer(), varOp.getType());
1312 if (!value)
1313 return failure();
1314 moore::YieldOp::create(builder, varOp.getLoc(), value);
1315 }
1316 globalVariableWorklist.clear();
1317
1318 return success();
1319}
1320
1322Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) {
1323 using slang::ast::ArgumentDirection;
1324 using slang::ast::MultiPortSymbol;
1325 using slang::ast::ParameterSymbol;
1326 using slang::ast::PortSymbol;
1327 using slang::ast::TypeParameterSymbol;
1328
1329 // Keep track of the local time scale. `getTimeScale` automatically looks
1330 // through parent scopes to find the time scale effective locally.
1331 auto prevTimeScale = timeScale;
1332 timeScale = module->getTimeScale().value_or(slang::TimeScale());
1333 llvm::scope_exit timeScaleGuard([&] { timeScale = prevTimeScale; });
1334
1335 // `module` is the canonical module body if it exists (i.e. deduplicated by
1336 // slang).
1337 auto &slot = modules[module];
1338 if (slot)
1339 return slot.get();
1340 slot = std::make_unique<ModuleLowering>();
1341 auto &lowering = *slot;
1342
1343 auto loc = convertLocation(module->location);
1344 OpBuilder::InsertionGuard g(builder);
1345
1346 // We only support modules and programs here. Interfaces are handled
1347 // separately by expanding them inline at each use site (see
1348 // expandInterfaceInstance in ModuleVisitor)
1349 auto kind = module->getDefinition().definitionKind;
1350 if (kind != slang::ast::DefinitionKind::Module &&
1351 kind != slang::ast::DefinitionKind::Program) {
1352 mlir::emitError(loc) << "unsupported definition: "
1353 << module->getDefinition().getKindString();
1354 return {};
1355 }
1356
1357 // Handle the port list.
1358 auto block = std::make_unique<Block>();
1359 SmallVector<hw::ModulePort> modulePorts;
1360
1361 // It's used to tag where a hierarchical name is on the port list.
1362 unsigned int outputIdx = 0, inputIdx = 0;
1363 for (auto *symbol : module->getPortList()) {
1364 auto handlePort = [&](const PortSymbol &port) {
1365 auto portLoc = convertLocation(port.location);
1366 auto type = convertType(port.getType());
1367 if (!type)
1368 return failure();
1369 auto portName = builder.getStringAttr(port.name);
1370 BlockArgument arg;
1371 std::optional<unsigned> portOutputIdx;
1372 std::optional<unsigned> portInputIdx;
1373 if (port.direction == ArgumentDirection::Out) {
1374 modulePorts.push_back({portName, type, hw::ModulePort::Output});
1375 portOutputIdx = outputIdx++;
1376 } else {
1377 // Only the ref type wrapper exists for the time being, the net type
1378 // wrapper for inout may be introduced later if necessary.
1379 if (port.direction != ArgumentDirection::In)
1380 type = moore::RefType::get(cast<moore::UnpackedType>(type));
1381 modulePorts.push_back({portName, type, hw::ModulePort::Input});
1382 arg = block->addArgument(type, portLoc);
1383 portInputIdx = inputIdx++;
1384 }
1385 lowering.ports.push_back(
1386 {port, portLoc, arg, portOutputIdx, portInputIdx});
1387 return success();
1388 };
1389
1390 // Lambda to handle interface ports by flattening them into individual
1391 // signal ports. Uses modport directions if a modport is specified,
1392 // otherwise treats all signals as inout (ref type)
1393 auto handleIfacePort = [&](const slang::ast::InterfacePortSymbol
1394 &ifacePort) {
1395 auto portLoc = convertLocation(ifacePort.location);
1396 auto [connSym, modportSym] = ifacePort.getConnection();
1397 const auto *ifaceInst =
1398 connSym ? connSym->as_if<slang::ast::InstanceSymbol>() : nullptr;
1399 auto portPrefix = (Twine(ifacePort.name) + "_").str();
1400
1401 if (modportSym) {
1402 // Modport specified: iterate modport members for signal directions.
1403 for (const auto &member : modportSym->members()) {
1404 const auto *mpp = member.as_if<slang::ast::ModportPortSymbol>();
1405 if (!mpp)
1406 continue;
1407 auto type = convertType(mpp->getType());
1408 if (!type)
1409 return failure();
1410 auto name =
1411 builder.getStringAttr(Twine(portPrefix) + StringRef(mpp->name));
1412 BlockArgument arg;
1414 std::optional<unsigned> ifaceOutputIdx;
1415 std::optional<unsigned> ifaceInputIdx;
1416 if (mpp->direction == ArgumentDirection::Out) {
1418 modulePorts.push_back({name, type, dir});
1419 ifaceOutputIdx = outputIdx++;
1420 } else {
1422 if (mpp->direction != ArgumentDirection::In)
1423 type = moore::RefType::get(cast<moore::UnpackedType>(type));
1424 modulePorts.push_back({name, type, dir});
1425 arg = block->addArgument(type, portLoc);
1426 ifaceInputIdx = inputIdx++;
1427 }
1428 lowering.ifacePorts.push_back(
1429 {name, dir, type, portLoc, arg, &ifacePort, mpp->internalSymbol,
1430 ifaceInst, mpp, ifaceOutputIdx, ifaceInputIdx});
1431 }
1432 } else {
1433 // No modport: iterate interface body for all variables and nets.
1434 // Treat them all as inout (input with ref type).
1435 const auto *instSym = connSym->as_if<slang::ast::InstanceSymbol>();
1436 if (!instSym) {
1437 mlir::emitError(portLoc)
1438 << "unsupported interface port connection for `" << ifacePort.name
1439 << "`";
1440 return failure();
1441 }
1442 for (const auto &member : instSym->body.members()) {
1443 const slang::ast::Type *slangType = nullptr;
1444 const slang::ast::Symbol *bodySym = nullptr;
1445 if (const auto *var = member.as_if<slang::ast::VariableSymbol>()) {
1446 slangType = &var->getType();
1447 bodySym = var;
1448 } else if (const auto *net = member.as_if<slang::ast::NetSymbol>()) {
1449 slangType = &net->getType();
1450 bodySym = net;
1451 } else {
1452 continue;
1453 }
1454 auto type = convertType(*slangType);
1455 if (!type)
1456 return failure();
1457 auto name = builder.getStringAttr(Twine(portPrefix) +
1458 StringRef(bodySym->name));
1459 auto refType = moore::RefType::get(cast<moore::UnpackedType>(type));
1460 modulePorts.push_back({name, refType, hw::ModulePort::Input});
1461 auto arg = block->addArgument(refType, portLoc);
1462 lowering.ifacePorts.push_back(
1463 {name, hw::ModulePort::Input, refType, portLoc, arg, &ifacePort,
1464 bodySym, instSym, nullptr, std::nullopt, inputIdx++});
1465 }
1466 }
1467 return success();
1468 };
1469
1470 if (const auto *port = symbol->as_if<PortSymbol>()) {
1471 if (failed(handlePort(*port)))
1472 return {};
1473 } else if (const auto *multiPort = symbol->as_if<MultiPortSymbol>()) {
1474 for (auto *port : multiPort->ports)
1475 if (failed(handlePort(*port)))
1476 return {};
1477 } else if (const auto *ifacePort =
1478 symbol->as_if<slang::ast::InterfacePortSymbol>()) {
1479 if (failed(handleIfacePort(*ifacePort)))
1480 return {};
1481 } else {
1482 mlir::emitError(convertLocation(symbol->location))
1483 << "unsupported module port `" << symbol->name << "` ("
1484 << slang::ast::toString(symbol->kind) << ")";
1485 return {};
1486 }
1487 }
1488
1489 // Record explicit-port counts before hierarchical-name ports are appended.
1490 lowering.numExplicitOutputs = outputIdx;
1491 lowering.numExplicitInputs = inputIdx;
1492
1493 // Mapping hierarchical names into the module's ports.
1494 for (auto &hierPath : hierPaths[module]) {
1495 assert(!hierPath.valueSyms.empty() && "hierPath must have valueSyms");
1496 auto hierType = convertType(hierPath.valueSyms.front().first->getType());
1497 if (!hierType)
1498 return {};
1499
1500 if (auto hierName = hierPath.hierName) {
1501 // The type of all hierarchical names are marked as the "RefType".
1502 hierType = moore::RefType::get(cast<moore::UnpackedType>(hierType));
1503 if (hierPath.direction == ArgumentDirection::Out) {
1504 hierPath.idx = outputIdx++;
1505 modulePorts.push_back({hierName, hierType, hw::ModulePort::Output});
1506 } else {
1507 hierPath.idx = inputIdx++;
1508 modulePorts.push_back({hierName, hierType, hw::ModulePort::Input});
1509 auto hierLoc =
1510 convertLocation(hierPath.valueSyms.front().first->location);
1511 block->addArgument(hierType, hierLoc);
1512 }
1513 }
1514 }
1515 auto moduleType = hw::ModuleType::get(getContext(), modulePorts);
1516
1517 // Pick an insertion point for this module according to the source file
1518 // location.
1519 auto key = LocationKey::get(module->location, sourceManager);
1520 auto it = orderedRootOps.upper_bound(key);
1521 if (it == orderedRootOps.end())
1522 builder.setInsertionPointToEnd(intoModuleOp.getBody());
1523 else
1524 builder.setInsertionPoint(it->second);
1525
1526 // Create an empty module that corresponds to this module.
1527 auto moduleOp =
1528 moore::SVModuleOp::create(builder, loc, module->name, moduleType);
1529 orderedRootOps.insert(it, {key, moduleOp});
1530 moduleOp.getBodyRegion().push_back(block.release());
1531 lowering.op = moduleOp;
1532
1533 // Add the module to the symbol table of the MLIR module, which uniquifies its
1534 // name as we'd expect.
1535 symbolTable.insert(moduleOp);
1536
1537 // Schedule the body to be lowered.
1538 moduleWorklist.push(module);
1539
1540 // Map duplicate port by Syntax
1541 for (const auto &port : lowering.ports)
1542 lowering.portsBySyntaxNode.insert({port.ast.getSyntax(), &port.ast});
1543
1544 return &lowering;
1545}
1546
1547LogicalResult
1548Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
1549 auto &lowering = *modules[module];
1550 auto prevDefinition = currentDefinition;
1551 currentDefinition = &module->getDefinition();
1552 llvm::scope_exit currentDefinitionGuard(
1553 [&] { currentDefinition = prevDefinition; });
1554 recordDPIExportDirectives(*this, *module, module->getSyntax());
1555
1556 OpBuilder::InsertionGuard g(builder);
1557 builder.setInsertionPointToEnd(lowering.op.getBody());
1558
1562
1563 // Keep track of the local time scale. `getTimeScale` automatically looks
1564 // through parent scopes to find the time scale effective locally.
1565 auto prevTimeScale = timeScale;
1566 timeScale = module->getTimeScale().value_or(slang::TimeScale());
1567 llvm::scope_exit timeScaleGuard([&] { timeScale = prevTimeScale; });
1568
1569 // Collect downward hierarchical names. Such as,
1570 // module SubA; int x = Top.y; endmodule. The "Top" module is the parent of
1571 // the "SubA", so "Top.y" is the downward hierarchical name.
1572 for (auto &hierPath : hierPaths[module])
1573 if (hierPath.direction == slang::ast::ArgumentDirection::In &&
1574 hierPath.idx) {
1575 auto arg = lowering.op.getBody()->getArgument(*hierPath.idx);
1576 for (auto &alias : hierPath.valueSyms)
1577 valueSymbols.insert(alias.first, arg);
1578 }
1579
1580 // Register flattened interface port members before lowering the module body
1581 // so expressions can refer to them. Also build per-port interface instance
1582 // lowerings, which enables materializing virtual interface values from
1583 // interface ports.
1584 DenseMap<const slang::ast::InstanceSymbol *, InterfaceLowering *>
1585 ifacePortLowerings;
1586
1587 auto getIfacePortLowering =
1588 [&](const slang::ast::InstanceSymbol *ifaceInst) -> InterfaceLowering * {
1589 if (!ifaceInst)
1590 return nullptr;
1591 if (auto *existing = interfaceInstances.lookup(ifaceInst))
1592 return existing;
1593 if (auto it = ifacePortLowerings.find(ifaceInst);
1594 it != ifacePortLowerings.end())
1595 return it->second;
1596
1597 auto lowering = std::make_unique<InterfaceLowering>();
1598 InterfaceLowering *ptr = lowering.get();
1599 interfaceInstanceStorage.push_back(std::move(lowering));
1600 interfaceInstances.insert(ifaceInst, ptr);
1601 ifacePortLowerings.try_emplace(ifaceInst, ptr);
1602 return ptr;
1603 };
1604
1605 for (auto &fp : lowering.ifacePorts) {
1606 if (!fp.bodySym)
1607 continue;
1608 auto *valueSym = fp.bodySym->as_if<slang::ast::ValueSymbol>();
1609 if (!valueSym)
1610 continue;
1611
1612 Value portValue;
1613 if (fp.direction == hw::ModulePort::Output) {
1614 // Output interface ports are not referenceable within the module body.
1615 // Create internal variables for them and return their value through the
1616 // module terminator.
1617 portValue = moore::VariableOp::create(
1618 builder, fp.loc,
1619 moore::RefType::get(cast<moore::UnpackedType>(fp.type)), fp.name,
1620 Value());
1621 } else {
1622 portValue = fp.arg;
1623 }
1624 valueSymbols.insert(valueSym, portValue);
1625 // Slang resolves in-body accesses (e.g. `bus.r`) through the
1626 // ModportPortSymbol rather than the interface body's variable. Register
1627 // both so the body-level expression lookup finds this port.
1628 if (fp.modportPortSym)
1629 if (auto *mppSym = fp.modportPortSym->as_if<slang::ast::ValueSymbol>())
1630 if (mppSym != valueSym)
1631 valueSymbols.insert(mppSym, portValue);
1632
1633 if (!fp.ifaceInstance)
1634 continue;
1635 if (Value val = valueSymbols.lookup(valueSym)) {
1636 auto *ifaceLowering = getIfacePortLowering(fp.ifaceInstance);
1637 if (!ifaceLowering)
1638 continue;
1639 ifaceLowering->expandedMembers[fp.bodySym] = val;
1640 ifaceLowering
1641 ->expandedMembersByName[builder.getStringAttr(fp.bodySym->name)] =
1642 val;
1643 }
1644 }
1645
1646 predeclaredInstances.clear();
1647 llvm::scope_exit predeclaredInstancesGuard(
1648 [&] { predeclaredInstances.clear(); });
1649
1650 // Always create module-scope storage, expanded interface members, and
1651 // instance shells before the source-order body walk. Slang rejects
1652 // use-before-declare before ImportVerilog runs unless the option is enabled,
1653 // but once the AST is valid this predeclaration supports both source-order
1654 // and forward references. Declaration initializers are still lowered when the
1655 // body visitor reaches the declaration, so they see the same local context as
1656 // other source-ordered expressions.
1657 if (failed(ModulePredeclaration(*this).predeclareScope(*module, "")))
1658 return failure();
1659
1660 // Convert the body of the module.
1661 for (auto &member : module->members()) {
1662 auto loc = convertLocation(member.location);
1663 if (failed(member.visit(ModuleVisitor(*this, loc))))
1664 return failure();
1665 // Flush any pending monitors after each member. This places the monitor
1666 // procedures immediately after the code that sets them up.
1667 if (failed(flushPendingMonitors()))
1668 return failure();
1669 }
1670
1671 // Create additional ops to drive input port values onto the corresponding
1672 // internal variables and nets, and to collect output port values for the
1673 // terminator. Outputs are placed by slot index so regular and
1674 // interface-modport outputs interleave in declaration order.
1675 SmallVector<Value> outputs(lowering.numExplicitOutputs);
1676 for (auto &port : lowering.ports) {
1677 Value value;
1678 if (auto *expr = port.ast.getInternalExpr()) {
1679 value = convertLvalueExpression(*expr);
1680 } else if (port.ast.internalSymbol) {
1681 if (const auto *sym =
1682 port.ast.internalSymbol->as_if<slang::ast::ValueSymbol>())
1683 value = valueSymbols.lookup(sym);
1684 }
1685 if (!value)
1686 return mlir::emitError(port.loc, "unsupported port: `")
1687 << port.ast.name
1688 << "` does not map to an internal symbol or expression";
1689
1690 // Collect output port values to be returned in the terminator.
1691 if (port.ast.direction == slang::ast::ArgumentDirection::Out) {
1692 if (isa<moore::RefType>(value.getType()))
1693 value = moore::ReadOp::create(builder, value.getLoc(), value);
1694 outputs[*port.outputIdx] = value;
1695 continue;
1696 }
1697
1698 // Assign the value coming in through the port to the internal net or symbol
1699 // of that port.
1700 Value portArg = port.arg;
1701 if (port.ast.direction != slang::ast::ArgumentDirection::In)
1702 portArg = moore::ReadOp::create(builder, port.loc, port.arg);
1703 moore::ContinuousAssignOp::create(builder, port.loc, value, portArg);
1704 }
1705
1706 // Collect output values for flattened interface ports. The internal
1707 // references are set up before lowering the module body.
1708 for (auto &fp : lowering.ifacePorts) {
1709 if (fp.direction != hw::ModulePort::Output)
1710 continue;
1711 auto *valueSym =
1712 fp.bodySym ? fp.bodySym->as_if<slang::ast::ValueSymbol>() : nullptr;
1713 if (!valueSym)
1714 continue;
1715 Value ref = valueSymbols.lookup(valueSym);
1716 if (!ref)
1717 continue;
1718 outputs[*fp.outputIdx] =
1719 moore::ReadOp::create(builder, fp.loc, ref).getResult();
1720 }
1721
1722 // Ensure the number of operands of this module's terminator and the number of
1723 // its(the current module) output ports remain consistent.
1724 for (auto &hierPath : hierPaths[module]) {
1725 assert(!hierPath.valueSyms.empty() && "hierPath must have valueSyms");
1726 if (hierPath.direction != slang::ast::ArgumentDirection::Out)
1727 continue;
1728 // A Symbol lowered in this module body resolves through the scoped table.
1729 Value hierValue;
1730 for (auto &alias : hierPath.valueSyms)
1731 if ((hierValue = valueSymbols.lookup(alias.first)))
1732 break;
1733 // Otherwise the value comes from an inner instance's hierarchical port:
1734 // strip the leading instance name and use the instance-keyed map.
1735 if (!hierValue) {
1736 auto name = hierPath.hierName.getValue();
1737 if (auto dot = name.find("."); dot != llvm::StringRef::npos) {
1738 auto innerName = builder.getStringAttr(name.drop_front(dot + 1));
1739 for (auto &member : module->members())
1740 if (auto *inst = member.as_if<slang::ast::InstanceSymbol>())
1741 if (llvm::StringRef(inst->name.data(), inst->name.size()) ==
1742 name.take_front(dot)) {
1743 hierValue = hierValueSymbols.lookup({inst, innerName});
1744 break;
1745 }
1746 } else if (auto *sym =
1747 module->find(std::string_view(name.data(), name.size()))) {
1748 // A dot-free path names a symbol declared directly in this module.
1749 if (auto *valueSym = sym->as_if<slang::ast::ValueSymbol>())
1750 hierValue = valueSymbols.lookup(valueSym);
1751 }
1752 }
1753 if (!hierValue)
1754 return mlir::emitError(lowering.op.getLoc())
1755 << "unable to resolve hierarchical output `"
1756 << hierPath.hierName.getValue() << "` in module `" << module->name
1757 << "`";
1758 outputs.push_back(hierValue);
1759 }
1760
1761 moore::OutputOp::create(builder, lowering.op.getLoc(), outputs);
1762 return success();
1763}
1764
1765/// Convert a package and its contents.
1766LogicalResult
1767Context::convertPackage(const slang::ast::PackageSymbol &package) {
1768 // Keep track of the local time scale. `getTimeScale` automatically looks
1769 // through parent scopes to find the time scale effective locally.
1770 auto prevTimeScale = timeScale;
1771 timeScale = package.getTimeScale().value_or(slang::TimeScale());
1772 llvm::scope_exit timeScaleGuard([&] { timeScale = prevTimeScale; });
1773
1774 recordDPIExportDirectives(*this, package, package.getSyntax());
1775
1776 OpBuilder::InsertionGuard g(builder);
1777 builder.setInsertionPointToEnd(intoModuleOp.getBody());
1779 for (auto &member : package.members()) {
1780 auto loc = convertLocation(member.location);
1781 if (failed(member.visit(PackageVisitor(*this, loc))))
1782 return failure();
1783 }
1784 return success();
1785}
1786
1787/// Convert a function and its arguments to a function declaration in the IR.
1788/// This does not convert the function body.
1790Context::declareFunction(const slang::ast::SubroutineSymbol &subroutine) {
1791 // Check if there already is a declaration for this function.
1792 auto &lowering = functions[&subroutine];
1793 if (lowering) {
1794 if (!lowering->op.getOperation())
1795 return {};
1796 return lowering.get();
1797 }
1798
1799 if (!subroutine.thisVar) {
1800
1801 SmallString<64> name;
1802 guessNamespacePrefix(subroutine.getParentScope()->asSymbol(), name);
1803 name += subroutine.name;
1804
1805 SmallVector<Type, 1> noThis = {};
1806 return declareCallableImpl(subroutine, name, noThis);
1807 }
1808
1809 auto loc = convertLocation(subroutine.location);
1810
1811 // Extract 'this' type and ensure it's a class.
1812 const slang::ast::Type &thisTy = subroutine.thisVar->getType();
1813 moore::ClassDeclOp ownerDecl;
1814
1815 if (auto *classTy = thisTy.as_if<slang::ast::ClassType>()) {
1816 auto &ownerLowering = classes[classTy];
1817 ownerDecl = ownerLowering->op;
1818 } else {
1819 mlir::emitError(loc) << "expected 'this' to be a class type, got "
1820 << thisTy.toString();
1821 return {};
1822 }
1823
1824 // Build qualified name: @"Pkg::Class"::subroutine
1825 SmallString<64> qualName;
1826 qualName += ownerDecl.getSymName(); // already qualified
1827 qualName += "::";
1828 qualName += subroutine.name;
1829
1830 // %this : class<@C>
1831 SmallVector<Type, 1> extraParams;
1832 {
1833 auto classSym = mlir::FlatSymbolRefAttr::get(ownerDecl.getSymNameAttr());
1834 auto handleTy = moore::ClassHandleType::get(getContext(), classSym);
1835 extraParams.push_back(handleTy);
1836 }
1837
1838 auto *fLowering = declareCallableImpl(subroutine, qualName, extraParams);
1839 return fLowering;
1840}
1841
1842/// Helper function to generate the function signature from a SubroutineSymbol
1843/// and optional extra arguments (used for %this argument)
1844static FunctionType getFunctionSignature(
1845 Context &context, const slang::ast::SubroutineSymbol &subroutine,
1846 ArrayRef<Type> prefixParams, ArrayRef<Type> suffixParams = {}) {
1847 using slang::ast::ArgumentDirection;
1848
1849 SmallVector<Type> inputTypes;
1850 inputTypes.append(prefixParams.begin(), prefixParams.end());
1851 SmallVector<Type, 1> outputTypes;
1852
1853 for (const auto *arg : subroutine.getArguments()) {
1854 auto type = context.convertType(arg->getType());
1855 if (!type)
1856 return {};
1857 if (arg->direction == ArgumentDirection::In) {
1858 inputTypes.push_back(type);
1859 } else {
1860 inputTypes.push_back(
1861 moore::RefType::get(cast<moore::UnpackedType>(type)));
1862 }
1863 }
1864
1865 inputTypes.append(suffixParams.begin(), suffixParams.end());
1866
1867 const auto &returnType = subroutine.getReturnType();
1868 if (!returnType.isVoid()) {
1869 auto type = context.convertType(returnType);
1870 if (!type)
1871 return {};
1872 outputTypes.push_back(type);
1873 }
1874
1875 return FunctionType::get(context.getContext(), inputTypes, outputTypes);
1876}
1877
1878static FailureOr<SmallVector<moore::DPIArgInfo>>
1880 const slang::ast::SubroutineSymbol &subroutine) {
1881 using slang::ast::ArgumentDirection;
1882
1883 SmallVector<moore::DPIArgInfo> args;
1884 args.reserve(subroutine.getArguments().size() +
1885 (!subroutine.getReturnType().isVoid() ? 1 : 0));
1886
1887 for (const auto *arg : subroutine.getArguments()) {
1888 auto type = context.convertType(arg->getType());
1889 if (!type)
1890 return failure();
1891 moore::DPIArgDirection dir;
1892 switch (arg->direction) {
1893 case ArgumentDirection::In:
1894 dir = moore::DPIArgDirection::In;
1895 break;
1896 case ArgumentDirection::Out:
1897 dir = moore::DPIArgDirection::Out;
1898 break;
1899 case ArgumentDirection::InOut:
1900 dir = moore::DPIArgDirection::InOut;
1901 break;
1902 case ArgumentDirection::Ref:
1903 llvm_unreachable("'ref' is not legal for DPI functions");
1904 }
1905 args.push_back(
1906 {StringAttr::get(context.getContext(), arg->name), type, dir});
1907 }
1908
1909 if (!subroutine.getReturnType().isVoid()) {
1910 auto type = context.convertType(subroutine.getReturnType());
1911 if (!type)
1912 return failure();
1913 args.push_back({StringAttr::get(context.getContext(), "return"), type,
1914 moore::DPIArgDirection::Return});
1915 }
1916
1917 return args;
1918}
1919
1920/// Convert a function and its arguments to a function declaration in the IR.
1921/// This does not convert the function body.
1923Context::declareCallableImpl(const slang::ast::SubroutineSymbol &subroutine,
1924 mlir::StringRef qualifiedName,
1925 llvm::SmallVectorImpl<Type> &extraParams) {
1926 auto loc = convertLocation(subroutine.location);
1927 // Pick an insertion point for this function according to the source file
1928 // location.
1929 OpBuilder::InsertionGuard g(builder);
1930 auto locationKey = LocationKey::get(subroutine.location, sourceManager);
1931 auto it = orderedRootOps.upper_bound(locationKey);
1932 if (it == orderedRootOps.end())
1933 builder.setInsertionPointToEnd(intoModuleOp.getBody());
1934 else
1935 builder.setInsertionPoint(it->second);
1936
1937 // Build the capture parameter types. These are appended after the user-
1938 // defined arguments, not in the extraParams prefix, so the function type has
1939 // the layout [this?] [user args] [captures].
1940 SmallVector<Type> captureTypes;
1941 auto capturesIt = functionCaptures.find(&subroutine);
1942 if (capturesIt != functionCaptures.end()) {
1943 for (auto *sym : capturesIt->second) {
1944 auto type = convertType(sym->getType());
1945 if (!type)
1946 return nullptr;
1947 captureTypes.push_back(
1948 moore::RefType::get(cast<moore::UnpackedType>(type)));
1949 }
1950 }
1951
1952 auto funcTy =
1953 getFunctionSignature(*this, subroutine, extraParams, captureTypes);
1954 if (!funcTy)
1955 return nullptr;
1956
1957 std::unique_ptr<FunctionLowering> lowering;
1958 Operation *insertedOp = nullptr;
1959 auto dpiExportIt = dpiExportCNames.find(&subroutine);
1960 bool isDPIExport = dpiExportIt != dpiExportCNames.end();
1961 // DPI-exported subroutines must keep a public symbol tagged with the
1962 // exported C name so later pipeline stages can materialize the export.
1963 auto setVisibilityAndExportAttr = [&](Operation *op) {
1964 if (isDPIExport) {
1965 op->setAttr(dpiExportAttrName,
1966 builder.getStringAttr(dpiExportIt->second));
1967 SymbolTable::setSymbolVisibility(op, SymbolTable::Visibility::Public);
1968 return;
1969 }
1970 SymbolTable::setSymbolVisibility(op, SymbolTable::Visibility::Private);
1971 };
1972 if (!subroutine.thisVar &&
1973 subroutine.flags.has(slang::ast::MethodFlags::DPIImport)) {
1974 // DPI-imported function: create a moore.func.dpi declaration.
1975 auto dpiSig = getDPISignature(*this, subroutine);
1976 if (failed(dpiSig))
1977 return nullptr;
1978
1979 auto dpiOp = moore::DPIFuncOp::create(
1980 builder, loc, StringAttr::get(getContext(), qualifiedName), *dpiSig,
1981 /*argumentLocs=*/ArrayAttr(),
1982 StringAttr::get(getContext(), subroutine.name));
1983 setVisibilityAndExportAttr(dpiOp);
1984 lowering = std::make_unique<FunctionLowering>(dpiOp);
1985 insertedOp = dpiOp;
1986 } else if (subroutine.subroutineKind == slang::ast::SubroutineKind::Task) {
1987 // Create a coroutine for tasks (which can suspend).
1988 auto op = moore::CoroutineOp::create(builder, loc, qualifiedName, funcTy);
1989 setVisibilityAndExportAttr(op);
1990 lowering = std::make_unique<FunctionLowering>(op);
1991 insertedOp = op;
1992 } else {
1993 // Create a function for regular functions (which cannot suspend).
1994 auto funcOp =
1995 mlir::func::FuncOp::create(builder, loc, qualifiedName, funcTy);
1996 setVisibilityAndExportAttr(funcOp);
1997 lowering = std::make_unique<FunctionLowering>(funcOp);
1998 insertedOp = funcOp;
1999 }
2000 orderedRootOps.insert(it, {locationKey, insertedOp});
2001
2002 // Store the captured symbols so call sites can look them up.
2003 if (capturesIt != functionCaptures.end())
2004 lowering->capturedSymbols.assign(capturesIt->second.begin(),
2005 capturesIt->second.end());
2006
2007 // Add the op to the symbol table of the MLIR module, which uniquifies
2008 // its name.
2009 symbolTable.insert(insertedOp);
2010 functions[&subroutine] = std::move(lowering);
2011
2012 // Schedule the body to be defined later.
2013 functionWorklist.push(&subroutine);
2014
2015 return functions[&subroutine].get();
2016}
2017
2018/// Define a function’s body. The function must already have been declared via
2019/// `declareFunction`. This is called from the function worklist after all
2020/// declarations have been created, ensuring that all function prototypes are
2021/// available for calls within the body.
2022LogicalResult
2023Context::defineFunction(const slang::ast::SubroutineSymbol &subroutine) {
2024 auto *lowering = functions.at(&subroutine).get();
2025
2026 // Keep track of the local time scale. `getTimeScale` automatically looks
2027 // through parent scopes to find the time scale effective locally.
2028 auto prevTimeScale = timeScale;
2029 timeScale = subroutine.getTimeScale().value_or(slang::TimeScale());
2030 llvm::scope_exit timeScaleGuard([&] { timeScale = prevTimeScale; });
2031
2032 // DPI-C imported functions are extern declarations with no Verilog body.
2033 // Leave the func.func without a body region so it survives as an external
2034 // symbol and calls to it are not eliminated.
2035 if (subroutine.flags.has(slang::ast::MethodFlags::DPIImport))
2036 return success();
2037
2038 const bool isMethod = (subroutine.thisVar != nullptr);
2039
2042 if (isMethod) {
2043 if (const auto *classTy =
2044 subroutine.thisVar->getType().as_if<slang::ast::ClassType>()) {
2045 for (auto &member : classTy->members()) {
2046 const auto *prop = member.as_if<slang::ast::ClassPropertySymbol>();
2047 if (!prop)
2048 continue;
2049 const auto &propCanon = prop->getType().getCanonicalType();
2050 if (const auto *vi =
2051 propCanon.as_if<slang::ast::VirtualInterfaceType>()) {
2052 auto propLoc = convertLocation(prop->location);
2053 if (failed(registerVirtualInterfaceMembers(*prop, *vi, propLoc)))
2054 return failure();
2055 }
2056 }
2057 }
2058 }
2059
2060 // Create a function body block and populate it with block arguments.
2061 SmallVector<moore::VariableOp> argVariables;
2062 auto &block = lowering->op.getFunctionBody().emplaceBlock();
2063
2064 // If this is a class method, the first input is %this :
2065 // !moore.class<@C>
2066 if (isMethod) {
2067 auto thisLoc = convertLocation(subroutine.location);
2068 auto thisType =
2069 cast<FunctionType>(lowering->op.getFunctionType()).getInput(0);
2070 auto thisArg = block.addArgument(thisType, thisLoc);
2071
2072 // Bind `this` so NamedValue/MemberAccess can find it.
2073 valueSymbols.insert(subroutine.thisVar, thisArg);
2074 }
2075
2076 // Add user-defined block arguments. The function type has the shape
2077 // [this?] [user args] [capture args], so we skip the prefix and suffix.
2078 auto inputs = cast<FunctionType>(lowering->op.getFunctionType()).getInputs();
2079 auto astArgs = subroutine.getArguments();
2080 unsigned prefixCount = isMethod ? 1 : 0;
2081 auto valInputs = llvm::ArrayRef<Type>(inputs)
2082 .drop_front(prefixCount)
2083 .take_front(astArgs.size());
2084
2085 for (auto [astArg, type] : llvm::zip(astArgs, valInputs)) {
2086 auto loc = convertLocation(astArg->location);
2087 auto blockArg = block.addArgument(type, loc);
2088
2089 if (isa<moore::RefType>(type)) {
2090 valueSymbols.insert(astArg, blockArg);
2091 } else {
2092 OpBuilder::InsertionGuard g(builder);
2093 builder.setInsertionPointToEnd(&block);
2094
2095 auto shadowArg = moore::VariableOp::create(
2096 builder, loc, moore::RefType::get(cast<moore::UnpackedType>(type)),
2097 StringAttr{}, blockArg);
2098 valueSymbols.insert(astArg, shadowArg);
2099 argVariables.push_back(shadowArg);
2100 }
2101
2102 const auto &argCanon = astArg->getType().getCanonicalType();
2103 if (const auto *vi = argCanon.as_if<slang::ast::VirtualInterfaceType>())
2104 if (failed(registerVirtualInterfaceMembers(*astArg, *vi, loc)))
2105 return failure();
2106 }
2107
2108 // Convert the body of the function.
2109 OpBuilder::InsertionGuard g(builder);
2110 builder.setInsertionPointToEnd(&block);
2111
2112 Value returnVar;
2113 if (subroutine.returnValVar) {
2114 auto type = convertType(*subroutine.returnValVar->getDeclaredType());
2115 if (!type)
2116 return failure();
2117 returnVar = moore::VariableOp::create(
2118 builder, lowering->op->getLoc(),
2119 moore::RefType::get(cast<moore::UnpackedType>(type)), StringAttr{},
2120 Value{});
2121 valueSymbols.insert(subroutine.returnValVar, returnVar);
2122 }
2123
2124 // Add block arguments for captured variables and bind them in the symbol
2125 // table. The captures were already added to the function type during
2126 // declaration; here we create the corresponding block arguments and map each
2127 // captured AST symbol to its block argument so that references in the body
2128 // resolve to the capture parameter instead of the enclosing scope’s value.
2129 for (auto *sym : lowering->capturedSymbols) {
2130 auto type = convertType(sym->getType());
2131 if (!type)
2132 return failure();
2133 auto refType = moore::RefType::get(cast<moore::UnpackedType>(type));
2134 auto loc = convertLocation(sym->location);
2135 auto blockArg = block.addArgument(refType, loc);
2136 valueSymbols.insert(sym, blockArg);
2137 }
2138
2139 auto savedThis = currentThisRef;
2140 currentThisRef = valueSymbols.lookup(subroutine.thisVar);
2141 llvm::scope_exit restoreThis([&] { currentThisRef = savedThis; });
2142
2143 auto *savedFunctionLowering = currentFunctionLowering;
2144 currentFunctionLowering = lowering;
2145 llvm::scope_exit restoreFunctionLowering(
2146 [&] { currentFunctionLowering = savedFunctionLowering; });
2147
2148 if (failed(convertStatement(subroutine.getBody())))
2149 return failure();
2150
2151 // If there was no explicit return statement provided by the user, insert a
2152 // default one.
2153 if (builder.getBlock()) {
2154 if (isa<moore::CoroutineOp>(lowering->op.getOperation())) {
2155 moore::ReturnOp::create(builder, lowering->op->getLoc());
2156 } else if (returnVar && !subroutine.getReturnType().isVoid()) {
2157 Value read =
2158 moore::ReadOp::create(builder, returnVar.getLoc(), returnVar);
2159 mlir::func::ReturnOp::create(builder, lowering->op->getLoc(), read);
2160 } else {
2161 mlir::func::ReturnOp::create(builder, lowering->op->getLoc(),
2162 ValueRange{});
2163 }
2164 }
2165 if (returnVar && returnVar.use_empty())
2166 returnVar.getDefiningOp()->erase();
2167
2168 for (auto var : argVariables) {
2169 if (llvm::all_of(var->getUsers(),
2170 [](auto *user) { return isa<moore::ReadOp>(user); })) {
2171 for (auto *user : llvm::make_early_inc_range(var->getUsers())) {
2172 user->getResult(0).replaceAllUsesWith(var.getInitial());
2173 user->erase();
2174 }
2175 var->erase();
2176 }
2177 }
2178
2179 return success();
2180}
2181
2182/// Convert a primitive instance.
2184 const slang::ast::PrimitiveInstanceSymbol &prim) {
2185 if (prim.getDriveStrength().first.has_value() ||
2186 prim.getDriveStrength().second.has_value())
2187 return mlir::emitError(convertLocation(prim.location))
2188 << "primitive instances with explicit drive strengths are not "
2189 "supported.";
2190
2191 switch (prim.primitiveType.primitiveKind) {
2192 case slang::ast::PrimitiveSymbol::PrimitiveKind::NInput:
2193 return this->convertNInputPrimitive(prim);
2194 break;
2195 case slang::ast::PrimitiveSymbol::PrimitiveKind::NOutput:
2196 return this->convertNOutputPrimitive(prim);
2197 break;
2198 case slang::ast::PrimitiveSymbol::PrimitiveKind::Fixed:
2199 return this->convertFixedPrimitive(prim);
2200 break;
2201 default:
2202 return mlir::emitError(convertLocation(prim.location))
2203 << "unsupported instance of primitive `" << prim.primitiveType.name
2204 << "`";
2205 }
2206}
2207
2209 const slang::ast::PrimitiveInstanceSymbol &prim) {
2210 auto loc = convertLocation(prim.location);
2211 auto primName = prim.primitiveType.name;
2212
2213 auto portConns = prim.getPortConnections();
2214 assert(portConns.size() >= 2 &&
2215 "n-input primitives should have at least 2 ports");
2216
2217 // Get SSA values corresponding to operands (and unwrap where necessary)
2218 auto &outputConn =
2219 portConns[0]->as<slang::ast::AssignmentExpression>().left();
2220
2221 auto outputVal = this->convertLvalueExpression(outputConn);
2222 if (!outputVal)
2223 return failure();
2224
2225 SmallVector<Value> inputVals;
2226 inputVals.reserve(portConns.size() - 1);
2227 for (const auto *inputConn : portConns.subspan(1, portConns.size() - 1)) {
2228 auto inputVal = convertRvalueExpression(*inputConn);
2229 if (!inputVal)
2230 return failure();
2231 inputVals.push_back(inputVal);
2232 }
2233
2234 Value nextInput = inputVals.front();
2235 auto result =
2236 llvm::StringSwitch<std::function<Value()>>(prim.primitiveType.name)
2237 .Case("and", ([&] {
2238 for (Value inputVal : llvm::drop_begin(inputVals))
2239 nextInput =
2240 moore::AndOp::create(builder, loc, nextInput, inputVal);
2241 return nextInput;
2242 }))
2243 .Case("or", ([&] {
2244 for (Value inputVal : llvm::drop_begin(inputVals))
2245 nextInput =
2246 moore::OrOp::create(builder, loc, nextInput, inputVal);
2247 return nextInput;
2248 }))
2249 .Case("xor", ([&] {
2250 for (Value inputVal : llvm::drop_begin(inputVals))
2251 nextInput =
2252 moore::XorOp::create(builder, loc, nextInput, inputVal);
2253 return nextInput;
2254 }))
2255 .Case("nand", ([&] {
2256 for (Value inputVal : llvm::drop_begin(inputVals))
2257 nextInput =
2258 moore::AndOp::create(builder, loc, nextInput, inputVal);
2259 return moore::NotOp::create(builder, loc, nextInput);
2260 }))
2261 .Case("nor", ([&] {
2262 for (Value inputVal : llvm::drop_begin(inputVals))
2263 nextInput =
2264 moore::OrOp::create(builder, loc, nextInput, inputVal);
2265 return moore::NotOp::create(builder, loc, nextInput);
2266 }))
2267 .Case("xnor", ([&] {
2268 for (Value inputVal : llvm::drop_begin(inputVals))
2269 nextInput =
2270 moore::XorOp::create(builder, loc, nextInput, inputVal);
2271 return moore::NotOp::create(builder, loc, nextInput);
2272 }))
2273 .Default([&] {
2274 mlir::emitError(loc)
2275 << "unsupported primitive `" << primName << "`";
2276 return Value();
2277 })();
2278
2279 if (!result)
2280 return failure();
2281
2282 auto dstType = cast<moore::RefType>(outputVal.getType()).getNestedType();
2283 result = materializeConversion(dstType, result, false, loc);
2284 if (!result)
2285 return failure();
2286
2287 if (prim.getDelay()) {
2288 const slang::ast::Expression *delayExpr;
2289 if (const auto *delay3 =
2290 prim.getDelay()->as_if<slang::ast::Delay3Control>()) {
2291 if (delay3->expr2 || delay3->expr3)
2292 return mlir::emitError(loc) << "only n-input primitives that specify a "
2293 "single delay are currently supported.";
2294 delayExpr = &delay3->expr1;
2295 } else if (const auto *delay =
2296 prim.getDelay()->as_if<slang::ast::DelayControl>()) {
2297 delayExpr = &delay->expr;
2298 } else {
2299 llvm_unreachable("unexpected delay control type in primitive instance");
2300 }
2301 auto delayVal = this->convertRvalueExpression(
2302 *delayExpr, moore::TimeType::get(getContext()));
2303 if (!delayVal)
2304 return failure();
2305 moore::DelayedContinuousAssignOp::create(builder, loc, outputVal, result,
2306 delayVal);
2307 } else {
2308 moore::ContinuousAssignOp::create(builder, loc, outputVal, result);
2309 }
2310
2311 return success();
2312}
2313
2315 const slang::ast::PrimitiveInstanceSymbol &prim) {
2316 auto loc = convertLocation(prim.location);
2317 auto primName = prim.primitiveType.name;
2318
2319 auto portConns = prim.getPortConnections();
2320 assert(portConns.size() >= 2 &&
2321 "n-output primitives should have at least 2 ports");
2322
2323 // Get SSA values corresponding to operands (and unwrap where necessary)
2324 SmallVector<Value> outputVals;
2325 outputVals.reserve(portConns.size() - 1);
2326 for (const auto *outputConn : portConns.subspan(0, portConns.size() - 1)) {
2327 auto &output = outputConn->as<slang::ast::AssignmentExpression>().left();
2328 auto outputVal = this->convertLvalueExpression(output);
2329 if (!outputVal)
2330 return failure();
2331 outputVals.push_back(outputVal);
2332 }
2333
2334 auto inputVal = this->convertRvalueExpression(*portConns.back());
2335 if (!inputVal)
2336 return failure();
2337
2338 auto result =
2339 llvm::StringSwitch<std::function<Value()>>(prim.primitiveType.name)
2340 .Case("not",
2341 ([&] { return moore::NotOp::create(builder, loc, inputVal); }))
2342 .Case("buf", ([&] {
2343 return moore::BoolCastOp::create(builder, loc, inputVal);
2344 }))
2345 .Default([&] {
2346 mlir::emitError(loc)
2347 << "unsupported primitive `" << primName << "`";
2348 return Value();
2349 })();
2350
2351 if (!result)
2352 return failure();
2353
2354 Value delayVal;
2355 if (prim.getDelay()) {
2356 const slang::ast::Expression *delayExpr;
2357 if (const auto *delay3 =
2358 prim.getDelay()->as_if<slang::ast::Delay3Control>()) {
2359 if (delay3->expr2 || delay3->expr3)
2360 return mlir::emitError(loc)
2361 << "only n-output primitives that specify a "
2362 "single delay are currently supported.";
2363 delayExpr = &delay3->expr1;
2364 } else if (const auto *delay =
2365 prim.getDelay()->as_if<slang::ast::DelayControl>()) {
2366 delayExpr = &delay->expr;
2367 } else {
2368 llvm_unreachable("unexpected delay control type in primitive instance");
2369 }
2370 delayVal = this->convertRvalueExpression(
2371 *delayExpr, moore::TimeType::get(getContext()));
2372 if (!delayVal)
2373 return failure();
2374 }
2375
2376 for (auto outputVal : outputVals) {
2377 auto dstType = cast<moore::RefType>(outputVal.getType()).getNestedType();
2378 Value converted = materializeConversion(dstType, result, false, loc);
2379 if (!converted)
2380 return failure();
2381 if (delayVal) {
2382 moore::DelayedContinuousAssignOp::create(builder, loc, outputVal,
2383 converted, delayVal);
2384 } else {
2385 moore::ContinuousAssignOp::create(builder, loc, outputVal, converted);
2386 }
2387 }
2388 return success();
2389}
2390
2392 const slang::ast::PrimitiveInstanceSymbol &prim) {
2393 auto primName = prim.primitiveType.name;
2394 auto loc = convertLocation(prim.location);
2395
2396 // Fixed primitives cover a few different cases, so dispatch those separately
2397
2398 if (primName == "pullup" || primName == "pulldown")
2399 return convertPullGatePrimitive(prim);
2400
2401 // Remaining fixed primitives still need handling
2402 mlir::emitError(loc) << "unsupported primitive `" << primName << "`";
2403 return failure();
2404}
2405
2407 const slang::ast::PrimitiveInstanceSymbol &prim) {
2408 assert((prim.primitiveType.name == "pullup" ||
2409 prim.primitiveType.name == "pulldown") &&
2410 "expected pullup or pulldown primitive");
2411 // Slang should catch this
2412 assert(!prim.getDelay() &&
2413 "SystemVerilog does not allow pull gate primitives with delays");
2414 auto loc = convertLocation(prim.location);
2415 auto primName = prim.primitiveType.name;
2416
2417 auto portConns = prim.getPortConnections();
2418 // Slang should ensure this for us
2419 assert(portConns.size() == 1 &&
2420 "pullup/pulldown primitives should have exactly one port");
2421
2422 Value portVal = this->convertLvalueExpression(
2423 portConns.front()->as<slang::ast::AssignmentExpression>().left());
2424
2425 auto dstType = cast<moore::RefType>(portVal.getType()).getNestedType();
2426 auto dstTypeWidth = dstType.getBitSize();
2427 // This should be caught elsewhere
2428 assert(dstTypeWidth &&
2429 "expected fixed-width type for pullup/pulldown primitive");
2430 auto constVal = primName == "pullup" ? -1 : 0;
2431 auto c = moore::ConstantOp::create(
2432 builder, loc,
2433 moore::IntType::getInt(this->getContext(), dstTypeWidth.value()),
2434 constVal);
2435
2436 Value converted = materializeConversion(dstType, c, false, loc);
2437 if (!converted)
2438 return failure();
2439 moore::ContinuousAssignOp::create(builder, loc, portVal, converted);
2440 return success();
2441}
2442
2443namespace {
2444
2445/// Construct a fully qualified class name containing the instance hierarchy
2446/// and the class name formatted as H1::H2::@C
2447mlir::StringAttr fullyQualifiedClassName(Context &ctx,
2448 const slang::ast::Type &ty) {
2449 SmallString<64> name;
2450 SmallVector<llvm::StringRef, 8> parts;
2451
2452 const slang::ast::Scope *scope = ty.getParentScope();
2453 while (scope) {
2454 const auto &sym = scope->asSymbol();
2455 switch (sym.kind) {
2456 case slang::ast::SymbolKind::Root:
2457 scope = nullptr; // stop at $root
2458 continue;
2459 case slang::ast::SymbolKind::InstanceBody:
2460 case slang::ast::SymbolKind::Instance:
2461 case slang::ast::SymbolKind::Package:
2462 case slang::ast::SymbolKind::ClassType:
2463 if (!sym.name.empty())
2464 parts.push_back(sym.name); // keep packages + outer classes
2465 break;
2466 default:
2467 break;
2468 }
2469 scope = sym.getParentScope();
2470 }
2471
2472 for (auto p : llvm::reverse(parts)) {
2473 name += p;
2474 name += "::";
2475 }
2476 name += ty.name; // class’s own name
2477 return mlir::StringAttr::get(ctx.getContext(), name);
2478}
2479
2480/// Helper function to construct the classes fully qualified base class name
2481/// and the name of all implemented interface classes
2482std::pair<mlir::SymbolRefAttr, mlir::ArrayAttr>
2483buildBaseAndImplementsAttrs(Context &context,
2484 const slang::ast::ClassType &cls) {
2485 mlir::MLIRContext *ctx = context.getContext();
2486
2487 // Base class (if any)
2488 mlir::SymbolRefAttr base;
2489 if (const auto *b = cls.getBaseClass())
2490 base = mlir::SymbolRefAttr::get(fullyQualifiedClassName(context, *b));
2491
2492 // Implemented interfaces (if any)
2493 SmallVector<mlir::Attribute> impls;
2494 if (auto ifaces = cls.getDeclaredInterfaces(); !ifaces.empty()) {
2495 impls.reserve(ifaces.size());
2496 for (const auto *iface : ifaces)
2497 impls.push_back(mlir::FlatSymbolRefAttr::get(
2498 fullyQualifiedClassName(context, *iface)));
2499 }
2500
2501 mlir::ArrayAttr implArr =
2502 impls.empty() ? mlir::ArrayAttr() : mlir::ArrayAttr::get(ctx, impls);
2503
2504 return {base, implArr};
2505}
2506
2507/// Base class for visiting slang::ast::ClassType members.
2508/// Contains common state and utility methods.
2509struct ClassDeclVisitorBase {
2511 OpBuilder &builder;
2512 ClassLowering &classLowering;
2513
2514 ClassDeclVisitorBase(Context &ctx, ClassLowering &lowering)
2515 : context(ctx), builder(ctx.builder), classLowering(lowering) {}
2516
2517protected:
2518 Location convertLocation(const slang::SourceLocation &sloc) {
2519 return context.convertLocation(sloc);
2520 }
2521};
2522
2523/// Visitor for class property declarations.
2524/// Populates the ClassDeclOp body with PropertyDeclOps.
2525struct ClassPropertyVisitor : ClassDeclVisitorBase {
2526 using ClassDeclVisitorBase::ClassDeclVisitorBase;
2527
2528 /// Build the ClassDeclOp body and populate it with property declarations.
2529 LogicalResult run(const slang::ast::ClassType &classAST) {
2530 if (!classLowering.op.getBody().empty())
2531 return success();
2532
2533 OpBuilder::InsertionGuard ig(builder);
2534
2535 Block *body = &classLowering.op.getBody().emplaceBlock();
2536 builder.setInsertionPointToEnd(body);
2537
2538 // Visit only ClassPropertySymbols
2539 for (const auto &mem : classAST.members()) {
2540 if (const auto *prop = mem.as_if<slang::ast::ClassPropertySymbol>()) {
2541 if (failed(prop->visit(*this)))
2542 return failure();
2543 }
2544 }
2545
2546 return success();
2547 }
2548
2549 // Properties: ClassPropertySymbol
2550 LogicalResult visit(const slang::ast::ClassPropertySymbol &prop) {
2551 auto loc = convertLocation(prop.location);
2552 auto ty = context.convertType(prop.getType());
2553 if (!ty)
2554 return failure();
2555
2556 if (prop.lifetime == slang::ast::VariableLifetime::Automatic) {
2557 moore::ClassPropertyDeclOp::create(builder, loc, prop.name, ty);
2558 return success();
2559 }
2560
2561 // Static variables should be accessed like globals, and not emit any
2562 // property declaration. Static variables might get hoisted elsewhere
2563 // so check first whether they have been declared already.
2564
2565 if (!context.globalVariables.lookup(&prop))
2566 return context.convertGlobalVariable(prop);
2567 return success();
2568 }
2569
2570 // Nested class definition, convert
2571 LogicalResult visit(const slang::ast::ClassType &cls) {
2572 return context.buildClassProperties(cls);
2573 }
2574
2575 // Catch-all: ignore everything else during property pass
2576 template <typename T>
2577 LogicalResult visit(T &&) {
2578 return success();
2579 }
2580};
2581
2582/// Visitor for class method declarations.
2583/// Materializes methods and nested class definitions.
2584struct ClassMethodVisitor : ClassDeclVisitorBase {
2585 using ClassDeclVisitorBase::ClassDeclVisitorBase;
2586
2587 /// Materialize class methods. The body must already exist from property pass.
2588 LogicalResult run(const slang::ast::ClassType &classAST) {
2589 if (classLowering.methodsFinalized)
2590 return success();
2591
2592 if (classLowering.op.getBody().empty())
2593 return failure();
2594
2595 OpBuilder::InsertionGuard ig(builder);
2596 builder.setInsertionPointToEnd(&classLowering.op.getBody().front());
2597
2598 // Visit everything except ClassPropertySymbols
2599 for (const auto &mem : classAST.members()) {
2600 if (failed(mem.visit(*this)))
2601 return failure();
2602 }
2603
2604 classLowering.methodsFinalized = true;
2605 return success();
2606 }
2607
2608 // Skip properties during method pass
2609 LogicalResult visit(const slang::ast::ClassPropertySymbol &) {
2610 return success();
2611 }
2612
2613 // Parameters in specialized classes hold no further information; slang
2614 // already elaborates them in all relevant places.
2615 LogicalResult visit(const slang::ast::ParameterSymbol &) { return success(); }
2616
2617 // Parameters in specialized classes hold no further information; slang
2618 // already elaborates them in all relevant places.
2619 LogicalResult visit(const slang::ast::TypeParameterSymbol &) {
2620 return success();
2621 }
2622
2623 // Type aliases in specialized classes hold no further information; slang
2624 // already elaborates them in all relevant places.
2625 LogicalResult visit(const slang::ast::TypeAliasType &) { return success(); }
2626
2627 // Nested class definition, skip
2628 LogicalResult visit(const slang::ast::GenericClassDefSymbol &) {
2629 return success();
2630 }
2631
2632 // Transparent members: ignore (inherited names pulled in by slang)
2633 LogicalResult visit(const slang::ast::TransparentMemberSymbol &) {
2634 return success();
2635 }
2636
2637 // Empty members: ignore
2638 LogicalResult visit(const slang::ast::EmptyMemberSymbol &) {
2639 return success();
2640 }
2641
2642 // Fully-fledged functions - SubroutineSymbol
2643 LogicalResult visit(const slang::ast::SubroutineSymbol &fn) {
2644 if (fn.flags & slang::ast::MethodFlags::BuiltIn) {
2645 static bool remarkEmitted = false;
2646 if (remarkEmitted)
2647 return success();
2648
2649 mlir::emitRemark(classLowering.op.getLoc())
2650 << "Class builtin functions (needed for randomization, constraints, "
2651 "and covergroups) are not yet supported and will be dropped "
2652 "during lowering.";
2653 remarkEmitted = true;
2654 return success();
2655 }
2656
2657 const mlir::UnitAttr isVirtual =
2658 (fn.flags & slang::ast::MethodFlags::Virtual)
2659 ? UnitAttr::get(context.getContext())
2660 : nullptr;
2661
2662 auto loc = convertLocation(fn.location);
2663 // Pure virtual functions regulate inheritance rules during parsing.
2664 // They don't emit any code, so we don't need to convert them, we only need
2665 // to register them for the purpose of stable VTable construction.
2666 if (fn.flags & slang::ast::MethodFlags::Pure) {
2667 // Add an extra %this argument.
2668 SmallVector<Type, 1> extraParams;
2669 auto classSym =
2670 mlir::FlatSymbolRefAttr::get(classLowering.op.getSymNameAttr());
2671 auto handleTy =
2672 moore::ClassHandleType::get(context.getContext(), classSym);
2673 extraParams.push_back(handleTy);
2674
2675 auto funcTy = getFunctionSignature(context, fn, extraParams);
2676 if (!funcTy) {
2677 mlir::emitError(loc) << "Invalid function signature for " << fn.name;
2678 return failure();
2679 }
2680
2681 moore::ClassMethodDeclOp::create(builder, loc, fn.name, funcTy, nullptr);
2682 return success();
2683 }
2684
2685 auto *lowering = context.declareFunction(fn);
2686 if (!lowering)
2687 return failure();
2688
2689 // We only emit methoddecls for virtual methods.
2690 if (!isVirtual)
2691 return success();
2692
2693 // Grab the function type from the declaration.
2694 FunctionType fnTy = cast<FunctionType>(lowering->op.getFunctionType());
2695 // Emit the method decl into the class body, preserving source order.
2696 moore::ClassMethodDeclOp::create(
2697 builder, loc, fn.name, fnTy,
2698 SymbolRefAttr::get(lowering->op.getNameAttr()));
2699
2700 return success();
2701 }
2702
2703 // A method prototype corresponds to the forward declaration of a concrete
2704 // method, the forward declaration of a virtual method, or the defintion of an
2705 // interface method meant to be implemented by classes implementing the
2706 // interface class.
2707 // In the first two cases, the best thing to do is to look up the actual
2708 // implementation and translate it when reading the method prototype, so we
2709 // can insert the MethodDeclOp in the correct order in the ClassDeclOp.
2710 // The latter case requires support for virtual interface methods, which is
2711 // currently not implemented. Since forward declarations of non-interface
2712 // methods must be followed by an implementation within the same compilation
2713 // unit, we can simply return a failure if we can't find a unique
2714 // implementation until we implement support for interface methods.
2715 LogicalResult visit(const slang::ast::MethodPrototypeSymbol &fn) {
2716 const auto *externImpl = fn.getSubroutine();
2717 // We needn't convert a forward declaration without a unique implementation.
2718 if (!externImpl) {
2719 mlir::emitError(convertLocation(fn.location))
2720 << "Didn't find an implementation matching the forward declaration "
2721 "of "
2722 << fn.name;
2723 return failure();
2724 }
2725 return visit(*externImpl);
2726 }
2727
2728 // Nested class definition, convert
2729 LogicalResult visit(const slang::ast::ClassType &cls) {
2730 if (failed(context.buildClassProperties(cls)))
2731 return failure();
2732 return context.materializeClassMethods(cls);
2733 }
2734
2735 // Emit an error for all other members.
2736 template <typename T>
2737 LogicalResult visit(T &&node) {
2738 Location loc = UnknownLoc::get(context.getContext());
2739 if constexpr (requires { node.location; })
2740 loc = convertLocation(node.location);
2741 mlir::emitError(loc) << "unsupported construct in ClassType members: "
2742 << slang::ast::toString(node.kind);
2743 return failure();
2744 }
2745};
2746} // namespace
2747
2748ClassLowering *Context::declareClass(const slang::ast::ClassType &cls) {
2749 // Check if there already is a declaration for this class.
2750 auto &lowering = classes[&cls];
2751 if (lowering)
2752 return lowering.get();
2753 lowering = std::make_unique<ClassLowering>();
2754 auto loc = convertLocation(cls.location);
2755
2756 // Pick an insertion point for this function according to the source file
2757 // location.
2758 OpBuilder::InsertionGuard g(builder);
2759 auto locationKey = LocationKey::get(cls.location, sourceManager);
2760 auto it = orderedRootOps.upper_bound(locationKey);
2761 if (it == orderedRootOps.end())
2762 builder.setInsertionPointToEnd(intoModuleOp.getBody());
2763 else
2764 builder.setInsertionPoint(it->second);
2765
2766 auto symName = fullyQualifiedClassName(*this, cls);
2767
2768 auto [base, impls] = buildBaseAndImplementsAttrs(*this, cls);
2769 auto classDeclOp =
2770 moore::ClassDeclOp::create(builder, loc, symName, base, impls);
2771
2772 SymbolTable::setSymbolVisibility(classDeclOp,
2773 SymbolTable::Visibility::Public);
2774 orderedRootOps.insert(it, {locationKey, classDeclOp});
2775 lowering->op = classDeclOp;
2776
2777 symbolTable.insert(classDeclOp);
2778 return lowering.get();
2779}
2780
2781LogicalResult
2782Context::buildClassProperties(const slang::ast::ClassType &classdecl) {
2783 // Keep track of local time scale.
2784 auto prevTimeScale = timeScale;
2785 timeScale = classdecl.getTimeScale().value_or(slang::TimeScale());
2786 llvm::scope_exit timeScaleGuard([&] { timeScale = prevTimeScale; });
2787
2788 // Skip if classdecl is already built
2789 if (classes[&classdecl])
2790 return success();
2791
2792 // Build base class properties first.
2793 if (classdecl.getBaseClass()) {
2794 if (const auto *baseClassDecl =
2795 classdecl.getBaseClass()->as_if<slang::ast::ClassType>()) {
2796 if (failed(buildClassProperties(*baseClassDecl)))
2797 return failure();
2798 }
2799 }
2800
2801 // Declare the class and build the ClassDeclOp with property declarations.
2802 auto *lowering = declareClass(classdecl);
2803 if (!lowering)
2804 return failure();
2805
2806 return ClassPropertyVisitor(*this, *lowering).run(classdecl);
2807}
2808
2809LogicalResult
2810Context::materializeClassMethods(const slang::ast::ClassType &classdecl) {
2811 // Keep track of local time scale.
2812 auto prevTimeScale = timeScale;
2813 timeScale = classdecl.getTimeScale().value_or(slang::TimeScale());
2814 llvm::scope_exit timeScaleGuard([&] { timeScale = prevTimeScale; });
2815
2816 // The class must have been declared already via buildClassProperties.
2817 auto *lowering = classes[&classdecl].get();
2818 if (!lowering)
2819 return failure();
2820
2821 // Materialize base class methods first. This may insert new entries into the
2822 // `classes` map (e.g. for nested classes), so we must not hold an iterator
2823 // or reference into the map across this call.
2824 if (classdecl.getBaseClass()) {
2825 if (const auto *baseClassDecl =
2826 classdecl.getBaseClass()->as_if<slang::ast::ClassType>()) {
2827 if (failed(materializeClassMethods(*baseClassDecl)))
2828 return failure();
2829 }
2830 }
2831
2832 return ClassMethodVisitor(*this, *lowering).run(classdecl);
2833}
2834
2835/// Convert a variable to a `moore.global_variable` operation.
2836LogicalResult
2837Context::convertGlobalVariable(const slang::ast::VariableSymbol &var) {
2838 auto loc = convertLocation(var.location);
2839
2840 // Pick an insertion point for this variable according to the source file
2841 // location.
2842 OpBuilder::InsertionGuard g(builder);
2843 auto locationKey = LocationKey::get(var.location, sourceManager);
2844 auto it = orderedRootOps.upper_bound(locationKey);
2845 if (it == orderedRootOps.end())
2846 builder.setInsertionPointToEnd(intoModuleOp.getBody());
2847 else
2848 builder.setInsertionPoint(it->second);
2849
2850 // Prefix the variable name with the surrounding namespace to create somewhat
2851 // sane names in the IR.
2852 SmallString<64> symName;
2853
2854 // If the variable is a class property, the symbol name needs to be fully
2855 // qualified with the hierarchical class name
2856 if (const auto *classVar = var.as_if<slang::ast::ClassPropertySymbol>()) {
2857 if (const auto *parentScope = classVar->getParentScope()) {
2858 if (const auto *parentClass =
2859 parentScope->asSymbol().as_if<slang::ast::ClassType>())
2860 symName = fullyQualifiedClassName(*this, *parentClass);
2861 else {
2862 mlir::emitError(loc)
2863 << "Could not access parent class of class property "
2864 << classVar->name;
2865 return failure();
2866 }
2867 } else {
2868 mlir::emitError(loc) << "Could not get parent scope of class property "
2869 << classVar->name;
2870 return failure();
2871 }
2872 symName += "::";
2873 symName += var.name;
2874 } else {
2875 guessNamespacePrefix(var.getParentScope()->asSymbol(), symName);
2876 symName += var.name;
2877 }
2878
2879 // Determine the type of the variable.
2880 auto type = convertType(var.getType());
2881 if (!type)
2882 return failure();
2883
2884 // Create the variable op itself.
2885 auto varOp = moore::GlobalVariableOp::create(builder, loc, symName,
2886 cast<moore::UnpackedType>(type));
2887 orderedRootOps.insert({locationKey, varOp});
2888 globalVariables.insert({&var, varOp});
2889
2890 // Add the variable to the symbol table of the MLIR module, which uniquifies
2891 // its name.
2892 symbolTable.insert(varOp);
2893
2894 // If the variable has an initializer expression, remember it for later such
2895 // that we can convert the initializers once we have seen all global
2896 // variables.
2897 if (var.getInitializer())
2898 globalVariableWorklist.push_back(&var);
2899
2900 return success();
2901}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
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 moore::ProcedureKind convertProcedureKind(slang::ast::ProceduralBlockKind kind)
static FailureOr< SmallVector< moore::DPIArgInfo > > getDPISignature(Context &context, const slang::ast::SubroutineSymbol &subroutine)
static void guessNamespacePrefix(const slang::ast::Symbol &symbol, SmallString< 64 > &prefix)
Definition Structure.cpp:60
static constexpr StringLiteral dpiExportAttrName
Definition Structure.cpp:21
static FunctionType getFunctionSignature(Context &context, const slang::ast::SubroutineSymbol &subroutine, ArrayRef< Type > prefixParams, ArrayRef< Type > suffixParams={})
Helper function to generate the function signature from a SubroutineSymbol and optional extra argumen...
static void recordDPIExportDirectives(Context &context, const slang::ast::Scope &scope, const slang::syntax::SyntaxNode *syntax)
Record export "DPI-C" directives in the given scope so that callable declarations can be tagged with ...
Definition Structure.cpp:31
static moore::NetKind convertNetKind(slang::ast::NetType::NetKind kind)
const slang::ast::InstanceBodySymbol * getCanonicalBody(const slang::ast::InstanceSymbol &inst)
Get the slang canonical body for the given instance, if there is one.
CaptureMap analyzeFunctionCaptures(const slang::ast::RootSymbol &root, SmallVectorImpl< AmbiguousHierCapture > &ambiguous)
Analyze the AST rooted at root to determine which variables each function captures: symbols reference...
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:56
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int run(Type[Generator] generator=CppGenerator, List[str] cmdline_args=sys.argv)
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.
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.
std::queue< const slang::ast::SubroutineSymbol * > functionWorklist
A list of functions for which the declaration has been created, but the body has not been defined yet...
Value convertLvalueExpression(const slang::ast::Expression &expr)
LogicalResult registerVirtualInterfaceMembers(const slang::ast::ValueSymbol &base, const slang::ast::VirtualInterfaceType &type, Location loc)
Register the interface members of a virtual interface base symbol for use in later expression convers...
Definition Types.cpp:474
Value materializeConstant(const slang::ConstantValue &constant, const slang::ast::Type &type, Location loc)
Helper function to materialize a ConstantValue as an SSA value.
const slang::ast::DefinitionSymbol * currentDefinition
The definition symbol of the module body currently being converted.
LogicalResult convertModuleBody(const slang::ast::InstanceBodySymbol *module)
Convert a module's body to the corresponding IR ops.
LogicalResult materializeClassMethods(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
LogicalResult flushPendingMonitors()
Process any pending $monitor calls and generate the monitoring procedures at module level.
LogicalResult convertNInputPrimitive(const slang::ast::PrimitiveInstanceSymbol &prim)
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.
DenseSet< const slang::ast::InstanceSymbol * > predeclaredInstances
Module instances already emitted by the predeclaration pass.
CaptureMap functionCaptures
Pre-computed capture analysis: maps each function to the set of non-local, non-global variables it ca...
DenseMap< const slang::ast::ClassType *, std::unique_ptr< ClassLowering > > classes
Classes that have already been converted.
Type convertType(const slang::ast::Type &type, LocationAttr loc={})
Convert a slang type into an MLIR type.
Definition Types.cpp:224
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.
ClassLowering * declareClass(const slang::ast::ClassType &cls)
VirtualInterfaceMembers::ScopeTy VirtualInterfaceMemberScope
LogicalResult convertFixedPrimitive(const slang::ast::PrimitiveInstanceSymbol &prim)
DenseMap< const slang::ast::SubroutineSymbol *, std::string > dpiExportCNames
DPI-C export directives keyed by the SystemVerilog subroutine they expose.
const ImportVerilogOptions & options
Value convertRvalueExpression(const slang::ast::Expression &expr, Type requiredType={})
SmallVector< std::unique_ptr< InterfaceLowering > > interfaceInstanceStorage
Owning storage for InterfaceLowering objects because ScopedHashTable stores values by copy.
VirtualInterfaceMembers virtualIfaceMembers
Value currentThisRef
Variable to track the value of the current function's implicit this reference.
const slang::SourceManager & sourceManager
Value materializeConversion(Type type, Value value, bool isSigned, Location loc, bool fallible=false)
Helper function to insert the necessary operations to cast a value from one type to another.
void traverseInstanceBody(const slang::ast::InstanceSymbol &symbol)
void populateAssertionClocks()
Generates a map from assertions to clocks using Slang's analysis.
std::map< LocationKey, Operation * > orderedRootOps
The top-level operations ordered by their Slang source location.
FunctionLowering * currentFunctionLowering
The function currently being converted, if any.
InterfaceInstances::ScopeTy InterfaceInstanceScope
LogicalResult convertPrimitiveInstance(const slang::ast::PrimitiveInstanceSymbol &prim)
Convert a primitive instance.
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 convertNOutputPrimitive(const slang::ast::PrimitiveInstanceSymbol &prim)
LogicalResult buildClassProperties(const slang::ast::ClassType &classdecl)
LogicalResult convertPackage(const slang::ast::PackageSymbol &package)
Convert a package and its contents.
MLIRContext * getContext()
Return the MLIR context.
LogicalResult defineFunction(const slang::ast::SubroutineSymbol &subroutine)
Define a function’s body.
LogicalResult convertPullGatePrimitive(const slang::ast::PrimitiveInstanceSymbol &prim)
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.
Lowering information for an expanded interface instance.
static LocationKey get(const slang::SourceLocation &loc, const slang::SourceManager &mgr)