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