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