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