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