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