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