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