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