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