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