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