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