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