Loading [MathJax]/extensions/tex2jax.js
CIRCT 21.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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
12using namespace circt;
13using namespace ImportVerilog;
14
15//===----------------------------------------------------------------------===//
16// Utilities
17//===----------------------------------------------------------------------===//
18
19static void guessNamespacePrefix(const slang::ast::Symbol &symbol,
20 SmallString<64> &prefix) {
21 if (symbol.kind != slang::ast::SymbolKind::Package)
22 return;
23 guessNamespacePrefix(symbol.getParentScope()->asSymbol(), prefix);
24 if (!symbol.name.empty()) {
25 prefix += symbol.name;
26 prefix += "::";
27 }
28}
29
30//===----------------------------------------------------------------------===//
31// Base Visitor
32//===----------------------------------------------------------------------===//
33
34namespace {
35/// Base visitor which ignores AST nodes that are handled by Slang's name
36/// resolution and type checking.
37struct BaseVisitor {
38 Context &context;
39 Location loc;
40 OpBuilder &builder;
41
42 BaseVisitor(Context &context, Location loc)
43 : context(context), loc(loc), builder(context.builder) {}
44
45 // Skip semicolons.
46 LogicalResult visit(const slang::ast::EmptyMemberSymbol &) {
47 return success();
48 }
49
50 // Skip members that are implicitly imported from some other scope for the
51 // sake of name resolution, such as enum variant names.
52 LogicalResult visit(const slang::ast::TransparentMemberSymbol &) {
53 return success();
54 }
55
56 // Skip typedefs.
57 LogicalResult visit(const slang::ast::TypeAliasType &) { return success(); }
58 LogicalResult visit(const slang::ast::ForwardingTypedefSymbol &) {
59 return success();
60 }
61
62 // Skip imports. The AST already has its names resolved.
63 LogicalResult visit(const slang::ast::ExplicitImportSymbol &) {
64 return success();
65 }
66 LogicalResult visit(const slang::ast::WildcardImportSymbol &) {
67 return success();
68 }
69
70 // Skip type parameters. The Slang AST is already monomorphized.
71 LogicalResult visit(const slang::ast::TypeParameterSymbol &) {
72 return success();
73 }
74
75 // Skip elaboration system tasks. These are reported directly by Slang.
76 LogicalResult visit(const slang::ast::ElabSystemTaskSymbol &) {
77 return success();
78 }
79
80 // Handle parameters.
81 LogicalResult visit(const slang::ast::ParameterSymbol &param) {
82 visitParameter(param);
83 return success();
84 }
85
86 LogicalResult visit(const slang::ast::SpecparamSymbol &param) {
87 visitParameter(param);
88 return success();
89 }
90
91 template <class Node>
92 void visitParameter(const Node &param) {
93 // If debug info is enabled, try to materialize the parameter's constant
94 // value on a best-effort basis and create a `dbg.variable` to track the
95 // value.
96 if (!context.options.debugInfo)
97 return;
98 auto value =
99 context.materializeConstant(param.getValue(), param.getType(), loc);
100 if (!value)
101 return;
102 if (builder.getInsertionBlock()->getParentOp() == context.intoModuleOp)
103 context.orderedRootOps.insert({param.location, value.getDefiningOp()});
104
105 // Prefix the parameter name with the surrounding namespace to create
106 // somewhat sane names in the IR.
107 SmallString<64> paramName;
108 guessNamespacePrefix(param.getParentScope()->asSymbol(), paramName);
109 paramName += param.name;
110
111 builder.create<debug::VariableOp>(loc, builder.getStringAttr(paramName),
112 value, Value{});
113 }
114};
115} // namespace
116
117//===----------------------------------------------------------------------===//
118// Top-Level Item Conversion
119//===----------------------------------------------------------------------===//
120
121namespace {
122struct RootVisitor : public BaseVisitor {
123 using BaseVisitor::BaseVisitor;
124 using BaseVisitor::visit;
125
126 // Handle packages.
127 LogicalResult visit(const slang::ast::PackageSymbol &package) {
128 return context.convertPackage(package);
129 }
130
131 // Handle functions and tasks.
132 LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
133 return context.convertFunction(subroutine);
134 }
135
136 // Emit an error for all other members.
137 template <typename T>
138 LogicalResult visit(T &&node) {
139 mlir::emitError(loc, "unsupported construct: ")
140 << slang::ast::toString(node.kind);
141 return failure();
142 }
143};
144} // namespace
145
146//===----------------------------------------------------------------------===//
147// Package Conversion
148//===----------------------------------------------------------------------===//
149
150namespace {
151struct PackageVisitor : public BaseVisitor {
152 using BaseVisitor::BaseVisitor;
153 using BaseVisitor::visit;
154
155 // Handle functions and tasks.
156 LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
157 return context.convertFunction(subroutine);
158 }
159
160 /// Emit an error for all other members.
161 template <typename T>
162 LogicalResult visit(T &&node) {
163 mlir::emitError(loc, "unsupported package member: ")
164 << slang::ast::toString(node.kind);
165 return failure();
166 }
167};
168} // namespace
169
170//===----------------------------------------------------------------------===//
171// Module Conversion
172//===----------------------------------------------------------------------===//
173
174static moore::ProcedureKind
175convertProcedureKind(slang::ast::ProceduralBlockKind kind) {
176 switch (kind) {
177 case slang::ast::ProceduralBlockKind::Always:
178 return moore::ProcedureKind::Always;
179 case slang::ast::ProceduralBlockKind::AlwaysComb:
180 return moore::ProcedureKind::AlwaysComb;
181 case slang::ast::ProceduralBlockKind::AlwaysLatch:
182 return moore::ProcedureKind::AlwaysLatch;
183 case slang::ast::ProceduralBlockKind::AlwaysFF:
184 return moore::ProcedureKind::AlwaysFF;
185 case slang::ast::ProceduralBlockKind::Initial:
186 return moore::ProcedureKind::Initial;
187 case slang::ast::ProceduralBlockKind::Final:
188 return moore::ProcedureKind::Final;
189 }
190 llvm_unreachable("all procedure kinds handled");
191}
192
193static moore::NetKind convertNetKind(slang::ast::NetType::NetKind kind) {
194 switch (kind) {
195 case slang::ast::NetType::Supply0:
196 return moore::NetKind::Supply0;
197 case slang::ast::NetType::Supply1:
198 return moore::NetKind::Supply1;
199 case slang::ast::NetType::Tri:
200 return moore::NetKind::Tri;
201 case slang::ast::NetType::TriAnd:
202 return moore::NetKind::TriAnd;
203 case slang::ast::NetType::TriOr:
204 return moore::NetKind::TriOr;
205 case slang::ast::NetType::TriReg:
206 return moore::NetKind::TriReg;
207 case slang::ast::NetType::Tri0:
208 return moore::NetKind::Tri0;
209 case slang::ast::NetType::Tri1:
210 return moore::NetKind::Tri1;
211 case slang::ast::NetType::UWire:
212 return moore::NetKind::UWire;
213 case slang::ast::NetType::Wire:
214 return moore::NetKind::Wire;
215 case slang::ast::NetType::WAnd:
216 return moore::NetKind::WAnd;
217 case slang::ast::NetType::WOr:
218 return moore::NetKind::WOr;
219 case slang::ast::NetType::Interconnect:
220 return moore::NetKind::Interconnect;
221 case slang::ast::NetType::UserDefined:
222 return moore::NetKind::UserDefined;
223 case slang::ast::NetType::Unknown:
224 return moore::NetKind::Unknown;
225 }
226 llvm_unreachable("all net kinds handled");
227}
228
229namespace {
230struct ModuleVisitor : public BaseVisitor {
231 using BaseVisitor::BaseVisitor;
232 using BaseVisitor::visit;
233
234 // Skip ports which are already handled by the module itself.
235 LogicalResult visit(const slang::ast::PortSymbol &) { return success(); }
236 LogicalResult visit(const slang::ast::MultiPortSymbol &) { return success(); }
237
238 // Skip genvars.
239 LogicalResult visit(const slang::ast::GenvarSymbol &genvarNode) {
240 return success();
241 }
242
243 // Skip defparams which have been handled by slang.
244 LogicalResult visit(const slang::ast::DefParamSymbol &) { return success(); }
245
246 // Ignore type parameters. These have already been handled by Slang's type
247 // checking.
248 LogicalResult visit(const slang::ast::TypeParameterSymbol &) {
249 return success();
250 }
251
252 // Handle instances.
253 LogicalResult visit(const slang::ast::InstanceSymbol &instNode) {
254 using slang::ast::ArgumentDirection;
255 using slang::ast::AssignmentExpression;
256 using slang::ast::MultiPortSymbol;
257 using slang::ast::PortSymbol;
258
259 auto *moduleLowering = context.convertModuleHeader(&instNode.body);
260 if (!moduleLowering)
261 return failure();
262 auto module = moduleLowering->op;
263 auto moduleType = module.getModuleType();
264
265 // Set visibility attribute for instantiated module.
266 SymbolTable::setSymbolVisibility(module, SymbolTable::Visibility::Private);
267
268 // Prepare the values that are involved in port connections. This creates
269 // rvalues for input ports and appropriate lvalues for output, inout, and
270 // ref ports. We also separate multi-ports into the individual underlying
271 // ports with their corresponding connection.
273 portValues.reserve(moduleType.getNumPorts());
274
275 for (const auto *con : instNode.getPortConnections()) {
276 const auto *expr = con->getExpression();
277
278 // Handle unconnected behavior. The expression is null if it have no
279 // connection for the port.
280 if (!expr) {
281 auto *port = con->port.as_if<PortSymbol>();
282 if (auto *existingPort =
283 moduleLowering->portsBySyntaxNode.lookup(port->getSyntax()))
284 port = existingPort;
285
286 switch (port->direction) {
287 case ArgumentDirection::In: {
288 auto refType = moore::RefType::get(
289 cast<moore::UnpackedType>(context.convertType(port->getType())));
290
291 if (const auto *net =
292 port->internalSymbol->as_if<slang::ast::NetSymbol>()) {
293 auto netOp = builder.create<moore::NetOp>(
294 loc, refType, StringAttr::get(builder.getContext(), net->name),
295 convertNetKind(net->netType.netKind), nullptr);
296 auto readOp = builder.create<moore::ReadOp>(loc, netOp);
297 portValues.insert({port, readOp});
298 } else if (const auto *var =
299 port->internalSymbol
300 ->as_if<slang::ast::VariableSymbol>()) {
301 auto varOp = builder.create<moore::VariableOp>(
302 loc, refType, StringAttr::get(builder.getContext(), var->name),
303 nullptr);
304 auto readOp = builder.create<moore::ReadOp>(loc, varOp);
305 portValues.insert({port, readOp});
306 } else {
307 return mlir::emitError(loc)
308 << "unsupported internal symbol for unconnected port `"
309 << port->name << "`";
310 }
311 continue;
312 }
313
314 // No need to express unconnected behavior for output port, skip to the
315 // next iteration of the loop.
316 case ArgumentDirection::Out:
317 continue;
318
319 // TODO: Mark Inout port as unsupported and it will be supported later.
320 default:
321 return mlir::emitError(loc)
322 << "unsupported port `" << port->name << "` ("
323 << slang::ast::toString(port->kind) << ")";
324 }
325 }
326
327 // Unpack the `<expr> = EmptyArgument` pattern emitted by Slang for
328 // output and inout ports.
329 if (const auto *assign = expr->as_if<AssignmentExpression>())
330 expr = &assign->left();
331
332 // Regular ports lower the connected expression to an lvalue or rvalue and
333 // either attach it to the instance as an operand (for input, inout, and
334 // ref ports), or assign an instance output to it (for output ports).
335 if (auto *port = con->port.as_if<PortSymbol>()) {
336 // Convert as rvalue for inputs, lvalue for all others.
337 auto value = (port->direction == ArgumentDirection::In)
338 ? context.convertRvalueExpression(*expr)
339 : context.convertLvalueExpression(*expr);
340 if (!value)
341 return failure();
342 if (auto *existingPort =
343 moduleLowering->portsBySyntaxNode.lookup(con->port.getSyntax()))
344 port = existingPort;
345 portValues.insert({port, value});
346 continue;
347 }
348
349 // Multi-ports lower the connected expression to an lvalue and then slice
350 // it up into multiple sub-values, one for each of the ports in the
351 // multi-port.
352 if (const auto *multiPort = con->port.as_if<MultiPortSymbol>()) {
353 // Convert as lvalue.
354 auto value = context.convertLvalueExpression(*expr);
355 if (!value)
356 return failure();
357 unsigned offset = 0;
358 for (const auto *port : llvm::reverse(multiPort->ports)) {
359 if (auto *existingPort = moduleLowering->portsBySyntaxNode.lookup(
360 con->port.getSyntax()))
361 port = existingPort;
362 unsigned width = port->getType().getBitWidth();
363 auto sliceType = context.convertType(port->getType());
364 if (!sliceType)
365 return failure();
366 Value slice = builder.create<moore::ExtractRefOp>(
367 loc, moore::RefType::get(cast<moore::UnpackedType>(sliceType)),
368 value, offset);
369 // Create the "ReadOp" for input ports.
370 if (port->direction == ArgumentDirection::In)
371 slice = builder.create<moore::ReadOp>(loc, slice);
372 portValues.insert({port, slice});
373 offset += width;
374 }
375 continue;
376 }
377
378 mlir::emitError(loc) << "unsupported instance port `" << con->port.name
379 << "` (" << slang::ast::toString(con->port.kind)
380 << ")";
381 return failure();
382 }
383
384 // Match the module's ports up with the port values determined above.
385 SmallVector<Value> inputValues;
386 SmallVector<Value> outputValues;
387 inputValues.reserve(moduleType.getNumInputs());
388 outputValues.reserve(moduleType.getNumOutputs());
389
390 for (auto &port : moduleLowering->ports) {
391 auto value = portValues.lookup(&port.ast);
392 if (port.ast.direction == ArgumentDirection::Out)
393 outputValues.push_back(value);
394 else
395 inputValues.push_back(value);
396 }
397
398 // Insert conversions for input ports.
399 for (auto [value, type] :
400 llvm::zip(inputValues, moduleType.getInputTypes()))
401 if (value.getType() != type)
402 value =
403 builder.create<moore::ConversionOp>(value.getLoc(), type, value);
404
405 // Here we use the hierarchical value recorded in `Context::valueSymbols`.
406 // Then we pass it as the input port with the ref<T> type of the instance.
407 for (const auto &hierPath : context.hierPaths[&instNode.body])
408 if (auto hierValue = context.valueSymbols.lookup(hierPath.valueSym);
409 hierPath.hierName && hierPath.direction == ArgumentDirection::In)
410 inputValues.push_back(hierValue);
411
412 // Create the instance op itself.
413 auto inputNames = builder.getArrayAttr(moduleType.getInputNames());
414 auto outputNames = builder.getArrayAttr(moduleType.getOutputNames());
415 auto inst = builder.create<moore::InstanceOp>(
416 loc, moduleType.getOutputTypes(), builder.getStringAttr(instNode.name),
417 FlatSymbolRefAttr::get(module.getSymNameAttr()), inputValues,
418 inputNames, outputNames);
419
420 // Record instance's results generated by hierarchical names.
421 for (const auto &hierPath : context.hierPaths[&instNode.body])
422 if (hierPath.idx && hierPath.direction == ArgumentDirection::Out)
423 context.valueSymbols.insert(hierPath.valueSym,
424 inst->getResult(*hierPath.idx));
425
426 // Assign output values from the instance to the connected expression.
427 for (auto [lvalue, output] : llvm::zip(outputValues, inst.getOutputs())) {
428 if (!lvalue)
429 continue;
430 Value rvalue = output;
431 auto dstType = cast<moore::RefType>(lvalue.getType()).getNestedType();
432 if (dstType != rvalue.getType())
433 rvalue = builder.create<moore::ConversionOp>(loc, dstType, rvalue);
434 builder.create<moore::ContinuousAssignOp>(loc, lvalue, rvalue);
435 }
436
437 return success();
438 }
439
440 // Handle variables.
441 LogicalResult visit(const slang::ast::VariableSymbol &varNode) {
442 auto loweredType = context.convertType(*varNode.getDeclaredType());
443 if (!loweredType)
444 return failure();
445
446 Value initial;
447 if (const auto *init = varNode.getInitializer()) {
448 initial = context.convertRvalueExpression(*init, loweredType);
449 if (!initial)
450 return failure();
451 }
452
453 auto varOp = builder.create<moore::VariableOp>(
454 loc, moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
455 builder.getStringAttr(varNode.name), initial);
456 context.valueSymbols.insert(&varNode, varOp);
457 return success();
458 }
459
460 // Handle nets.
461 LogicalResult visit(const slang::ast::NetSymbol &netNode) {
462 auto loweredType = context.convertType(*netNode.getDeclaredType());
463 if (!loweredType)
464 return failure();
465
466 Value assignment;
467 if (const auto *init = netNode.getInitializer()) {
468 assignment = context.convertRvalueExpression(*init, loweredType);
469 if (!assignment)
470 return failure();
471 }
472
473 auto netkind = convertNetKind(netNode.netType.netKind);
474 if (netkind == moore::NetKind::Interconnect ||
475 netkind == moore::NetKind::UserDefined ||
476 netkind == moore::NetKind::Unknown)
477 return mlir::emitError(loc, "unsupported net kind `")
478 << netNode.netType.name << "`";
479
480 auto netOp = builder.create<moore::NetOp>(
481 loc, moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
482 builder.getStringAttr(netNode.name), netkind, assignment);
483 context.valueSymbols.insert(&netNode, netOp);
484 return success();
485 }
486
487 // Handle continuous assignments.
488 LogicalResult visit(const slang::ast::ContinuousAssignSymbol &assignNode) {
489 if (const auto *delay = assignNode.getDelay()) {
490 auto loc = context.convertLocation(delay->sourceRange);
491 return mlir::emitError(loc,
492 "delayed continuous assignments not supported");
493 }
494
495 const auto &expr =
496 assignNode.getAssignment().as<slang::ast::AssignmentExpression>();
497 auto lhs = context.convertLvalueExpression(expr.left());
498 if (!lhs)
499 return failure();
500
501 auto rhs = context.convertRvalueExpression(
502 expr.right(), cast<moore::RefType>(lhs.getType()).getNestedType());
503 if (!rhs)
504 return failure();
505
506 builder.create<moore::ContinuousAssignOp>(loc, lhs, rhs);
507 return success();
508 }
509
510 // Handle procedures.
511 LogicalResult convertProcedure(moore::ProcedureKind kind,
512 const slang::ast::Statement &body) {
513 auto procOp = builder.create<moore::ProcedureOp>(loc, kind);
514 OpBuilder::InsertionGuard guard(builder);
515 builder.setInsertionPointToEnd(&procOp.getBody().emplaceBlock());
517 if (failed(context.convertStatement(body)))
518 return failure();
519 if (builder.getBlock())
520 builder.create<moore::ReturnOp>(loc);
521 return success();
522 }
523
524 LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) {
525 // Detect `always @(*) <stmt>` and convert to `always_comb <stmt>` if
526 // requested by the user.
527 if (context.options.lowerAlwaysAtStarAsComb) {
528 auto *stmt = procNode.getBody().as_if<slang::ast::TimedStatement>();
529 if (procNode.procedureKind == slang::ast::ProceduralBlockKind::Always &&
530 stmt &&
531 stmt->timing.kind == slang::ast::TimingControlKind::ImplicitEvent)
532 return convertProcedure(moore::ProcedureKind::AlwaysComb, stmt->stmt);
533 }
534
535 return convertProcedure(convertProcedureKind(procNode.procedureKind),
536 procNode.getBody());
537 }
538
539 // Handle generate block.
540 LogicalResult visit(const slang::ast::GenerateBlockSymbol &genNode) {
541 if (!genNode.isUninstantiated) {
542 for (auto &member : genNode.members()) {
543 if (failed(member.visit(ModuleVisitor(context, loc))))
544 return failure();
545 }
546 }
547 return success();
548 }
549
550 // Handle generate block array.
551 LogicalResult visit(const slang::ast::GenerateBlockArraySymbol &genArrNode) {
552 for (const auto *member : genArrNode.entries) {
553 if (failed(member->asSymbol().visit(ModuleVisitor(context, loc))))
554 return failure();
555 }
556 return success();
557 }
558
559 // Ignore statement block symbols. These get generated by Slang for blocks
560 // with variables and other declarations. For example, having an initial
561 // procedure with a variable declaration, such as `initial begin int x;
562 // end`, will create the procedure with a block and variable declaration as
563 // expected, but will also create a `StatementBlockSymbol` with just the
564 // variable layout _next to_ the initial procedure.
565 LogicalResult visit(const slang::ast::StatementBlockSymbol &) {
566 return success();
567 }
568
569 // Handle functions and tasks.
570 LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
571 return context.convertFunction(subroutine);
572 }
573
574 /// Emit an error for all other members.
575 template <typename T>
576 LogicalResult visit(T &&node) {
577 mlir::emitError(loc, "unsupported module member: ")
578 << slang::ast::toString(node.kind);
579 return failure();
580 }
581};
582} // namespace
583
584//===----------------------------------------------------------------------===//
585// Structure and Hierarchy Conversion
586//===----------------------------------------------------------------------===//
587
588/// Convert an entire Slang compilation to MLIR ops. This is the main entry
589/// point for the conversion.
591 const auto &root = compilation.getRoot();
592
593 // First only to visit the whole AST to collect the hierarchical names without
594 // any operation creating.
595 for (auto *inst : root.topInstances)
596 if (failed(traverseInstanceBody(inst->body)))
597 return failure();
598
599 // Visit all top-level declarations in all compilation units. This does not
600 // include instantiable constructs like modules, interfaces, and programs,
601 // which are listed separately as top instances.
602 for (auto *unit : root.compilationUnits) {
603 for (const auto &member : unit->members()) {
604 auto loc = convertLocation(member.location);
605 if (failed(member.visit(RootVisitor(*this, loc))))
606 return failure();
607 }
608 }
609
610 // Prime the root definition worklist by adding all the top-level modules.
611 SmallVector<const slang::ast::InstanceSymbol *> topInstances;
612 for (auto *inst : root.topInstances)
613 if (!convertModuleHeader(&inst->body))
614 return failure();
615
616 // Convert all the root module definitions.
617 while (!moduleWorklist.empty()) {
618 auto *module = moduleWorklist.front();
619 moduleWorklist.pop();
620 if (failed(convertModuleBody(module)))
621 return failure();
622 }
623
624 return success();
625}
626
627/// Convert a module and its ports to an empty module op in the IR. Also adds
628/// the op to the worklist of module bodies to be lowered. This acts like a
629/// module "declaration", allowing instances to already refer to a module even
630/// before its body has been lowered.
632Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) {
633 using slang::ast::ArgumentDirection;
634 using slang::ast::MultiPortSymbol;
635 using slang::ast::ParameterSymbol;
636 using slang::ast::PortSymbol;
637 using slang::ast::TypeParameterSymbol;
638
639 auto parameters = module->parameters;
640 bool hasModuleSame = false;
641 // If there is already exist a module that has the same name with this
642 // module ,has the same parent scope and has the same parameters we can
643 // define this module is a duplicate module
644 for (auto const &existingModule : modules) {
645 if (module->getDeclaringDefinition() ==
646 existingModule.getFirst()->getDeclaringDefinition()) {
647 auto moduleParameters = existingModule.getFirst()->parameters;
648 hasModuleSame = true;
649 for (auto it1 = parameters.begin(), it2 = moduleParameters.begin();
650 it1 != parameters.end() && it2 != moduleParameters.end();
651 it1++, it2++) {
652 // Parameters size different
653 if (it1 == parameters.end() || it2 == moduleParameters.end()) {
654 hasModuleSame = false;
655 break;
656 }
657 const auto *para1 = (*it1)->symbol.as_if<ParameterSymbol>();
658 const auto *para2 = (*it2)->symbol.as_if<ParameterSymbol>();
659 // Parameters kind different
660 if ((para1 == nullptr) ^ (para2 == nullptr)) {
661 hasModuleSame = false;
662 break;
663 }
664 // Compare ParameterSymbol
665 if (para1 != nullptr) {
666 hasModuleSame = para1->getValue() == para2->getValue();
667 }
668 // Compare TypeParameterSymbol
669 if (para1 == nullptr) {
670 auto para1Type = convertType(
671 (*it1)->symbol.as<TypeParameterSymbol>().getTypeAlias());
672 auto para2Type = convertType(
673 (*it2)->symbol.as<TypeParameterSymbol>().getTypeAlias());
674 hasModuleSame = para1Type == para2Type;
675 }
676 if (!hasModuleSame)
677 break;
678 }
679 if (hasModuleSame) {
680 module = existingModule.first;
681 break;
682 }
683 }
684 }
685
686 auto &slot = modules[module];
687 if (slot)
688 return slot.get();
689 slot = std::make_unique<ModuleLowering>();
690 auto &lowering = *slot;
691
692 auto loc = convertLocation(module->location);
693 OpBuilder::InsertionGuard g(builder);
694
695 // We only support modules for now. Extension to interfaces and programs
696 // should be trivial though, since they are essentially the same thing with
697 // only minor differences in semantics.
698 if (module->getDefinition().definitionKind !=
699 slang::ast::DefinitionKind::Module) {
700 mlir::emitError(loc) << "unsupported definition: "
701 << module->getDefinition().getKindString();
702 return {};
703 }
704
705 // Handle the port list.
706 auto block = std::make_unique<Block>();
707 SmallVector<hw::ModulePort> modulePorts;
708
709 // It's used to tag where a hierarchical name is on the port list.
710 unsigned int outputIdx = 0, inputIdx = 0;
711 for (auto *symbol : module->getPortList()) {
712 auto handlePort = [&](const PortSymbol &port) {
713 auto portLoc = convertLocation(port.location);
714 auto type = convertType(port.getType());
715 if (!type)
716 return failure();
717 auto portName = builder.getStringAttr(port.name);
718 BlockArgument arg;
719 if (port.direction == ArgumentDirection::Out) {
720 modulePorts.push_back({portName, type, hw::ModulePort::Output});
721 outputIdx++;
722 } else {
723 // Only the ref type wrapper exists for the time being, the net type
724 // wrapper for inout may be introduced later if necessary.
725 if (port.direction != ArgumentDirection::In)
726 type = moore::RefType::get(cast<moore::UnpackedType>(type));
727 modulePorts.push_back({portName, type, hw::ModulePort::Input});
728 arg = block->addArgument(type, portLoc);
729 inputIdx++;
730 }
731 lowering.ports.push_back({port, portLoc, arg});
732 return success();
733 };
734
735 if (const auto *port = symbol->as_if<PortSymbol>()) {
736 if (failed(handlePort(*port)))
737 return {};
738 } else if (const auto *multiPort = symbol->as_if<MultiPortSymbol>()) {
739 for (auto *port : multiPort->ports)
740 if (failed(handlePort(*port)))
741 return {};
742 } else {
743 mlir::emitError(convertLocation(symbol->location))
744 << "unsupported module port `" << symbol->name << "` ("
745 << slang::ast::toString(symbol->kind) << ")";
746 return {};
747 }
748 }
749
750 // Mapping hierarchical names into the module's ports.
751 for (auto &hierPath : hierPaths[module]) {
752 auto hierType = convertType(hierPath.valueSym->getType());
753 if (!hierType)
754 return {};
755
756 if (auto hierName = hierPath.hierName) {
757 // The type of all hierarchical names are marked as the "RefType".
758 hierType = moore::RefType::get(cast<moore::UnpackedType>(hierType));
759 if (hierPath.direction == ArgumentDirection::Out) {
760 hierPath.idx = outputIdx++;
761 modulePorts.push_back({hierName, hierType, hw::ModulePort::Output});
762 } else {
763 hierPath.idx = inputIdx++;
764 modulePorts.push_back({hierName, hierType, hw::ModulePort::Input});
765 auto hierLoc = convertLocation(hierPath.valueSym->location);
766 block->addArgument(hierType, hierLoc);
767 }
768 }
769 }
770 auto moduleType = hw::ModuleType::get(getContext(), modulePorts);
771
772 // Pick an insertion point for this module according to the source file
773 // location.
774 auto it = orderedRootOps.upper_bound(module->location);
775 if (it == orderedRootOps.end())
776 builder.setInsertionPointToEnd(intoModuleOp.getBody());
777 else
778 builder.setInsertionPoint(it->second);
779
780 // Create an empty module that corresponds to this module.
781 auto moduleOp =
782 builder.create<moore::SVModuleOp>(loc, module->name, moduleType);
783 orderedRootOps.insert(it, {module->location, moduleOp});
784 moduleOp.getBodyRegion().push_back(block.release());
785 lowering.op = moduleOp;
786
787 // Add the module to the symbol table of the MLIR module, which uniquifies its
788 // name as we'd expect.
789 symbolTable.insert(moduleOp);
790
791 // Schedule the body to be lowered.
792 moduleWorklist.push(module);
793
794 // Map duplicate port by Syntax
795 for (const auto &port : lowering.ports)
796 lowering.portsBySyntaxNode.insert({port.ast.getSyntax(), &port.ast});
797
798 return &lowering;
799}
800
801/// Convert a module's body to the corresponding IR ops. The module op must have
802/// already been created earlier through a `convertModuleHeader` call.
803LogicalResult
804Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
805 auto &lowering = *modules[module];
806 OpBuilder::InsertionGuard g(builder);
807 builder.setInsertionPointToEnd(lowering.op.getBody());
808
810
811 // Collect downward hierarchical names. Such as,
812 // module SubA; int x = Top.y; endmodule. The "Top" module is the parent of
813 // the "SubA", so "Top.y" is the downward hierarchical name.
814 for (auto &hierPath : hierPaths[module])
815 if (hierPath.direction == slang::ast::ArgumentDirection::In && hierPath.idx)
816 valueSymbols.insert(hierPath.valueSym,
817 lowering.op.getBody()->getArgument(*hierPath.idx));
818
819 // Convert the body of the module.
820 for (auto &member : module->members()) {
821 auto loc = convertLocation(member.location);
822 if (failed(member.visit(ModuleVisitor(*this, loc))))
823 return failure();
824 }
825
826 // Create additional ops to drive input port values onto the corresponding
827 // internal variables and nets, and to collect output port values for the
828 // terminator.
829 SmallVector<Value> outputs;
830 for (auto &port : lowering.ports) {
831 Value value;
832 if (auto *expr = port.ast.getInternalExpr()) {
833 value = convertLvalueExpression(*expr);
834 } else if (port.ast.internalSymbol) {
835 if (const auto *sym =
836 port.ast.internalSymbol->as_if<slang::ast::ValueSymbol>())
837 value = valueSymbols.lookup(sym);
838 }
839 if (!value)
840 return mlir::emitError(port.loc, "unsupported port: `")
841 << port.ast.name
842 << "` does not map to an internal symbol or expression";
843
844 // Collect output port values to be returned in the terminator.
845 if (port.ast.direction == slang::ast::ArgumentDirection::Out) {
846 if (isa<moore::RefType>(value.getType()))
847 value = builder.create<moore::ReadOp>(value.getLoc(), value);
848 outputs.push_back(value);
849 continue;
850 }
851
852 // Assign the value coming in through the port to the internal net or symbol
853 // of that port.
854 Value portArg = port.arg;
855 if (port.ast.direction != slang::ast::ArgumentDirection::In)
856 portArg = builder.create<moore::ReadOp>(port.loc, port.arg);
857 builder.create<moore::ContinuousAssignOp>(port.loc, value, portArg);
858 }
859
860 // Ensure the number of operands of this module's terminator and the number of
861 // its(the current module) output ports remain consistent.
862 for (auto &hierPath : hierPaths[module])
863 if (auto hierValue = valueSymbols.lookup(hierPath.valueSym))
864 if (hierPath.direction == slang::ast::ArgumentDirection::Out)
865 outputs.push_back(hierValue);
866
867 builder.create<moore::OutputOp>(lowering.op.getLoc(), outputs);
868 return success();
869}
870
871/// Convert a package and its contents.
872LogicalResult
873Context::convertPackage(const slang::ast::PackageSymbol &package) {
874 OpBuilder::InsertionGuard g(builder);
875 builder.setInsertionPointToEnd(intoModuleOp.getBody());
877 for (auto &member : package.members()) {
878 auto loc = convertLocation(member.location);
879 if (failed(member.visit(PackageVisitor(*this, loc))))
880 return failure();
881 }
882 return success();
883}
884
885/// Convert a function and its arguments to a function declaration in the IR.
886/// This does not convert the function body.
888Context::declareFunction(const slang::ast::SubroutineSymbol &subroutine) {
889 using slang::ast::ArgumentDirection;
890
891 // Check if there already is a declaration for this function.
892 auto &lowering = functions[&subroutine];
893 if (lowering) {
894 if (!lowering->op)
895 return {};
896 return lowering.get();
897 }
898 lowering = std::make_unique<FunctionLowering>();
899 auto loc = convertLocation(subroutine.location);
900
901 // Pick an insertion point for this function according to the source file
902 // location.
903 OpBuilder::InsertionGuard g(builder);
904 auto it = orderedRootOps.upper_bound(subroutine.location);
905 if (it == orderedRootOps.end())
906 builder.setInsertionPointToEnd(intoModuleOp.getBody());
907 else
908 builder.setInsertionPoint(it->second);
909
910 // Class methods are currently not supported.
911 if (subroutine.thisVar) {
912 mlir::emitError(loc) << "unsupported class method";
913 return {};
914 }
915
916 // Determine the function type.
917 SmallVector<Type> inputTypes;
918 SmallVector<Type, 1> outputTypes;
919
920 for (const auto *arg : subroutine.getArguments()) {
921 auto type = cast<moore::UnpackedType>(convertType(arg->getType()));
922 if (!type)
923 return {};
924 if (arg->direction == ArgumentDirection::In) {
925 inputTypes.push_back(type);
926 } else {
927 inputTypes.push_back(moore::RefType::get(type));
928 }
929 }
930
931 if (!subroutine.getReturnType().isVoid()) {
932 auto type = convertType(subroutine.getReturnType());
933 if (!type)
934 return {};
935 outputTypes.push_back(type);
936 }
937
938 auto funcType = FunctionType::get(getContext(), inputTypes, outputTypes);
939
940 // Prefix the function name with the surrounding namespace to create somewhat
941 // sane names in the IR.
942 SmallString<64> funcName;
943 guessNamespacePrefix(subroutine.getParentScope()->asSymbol(), funcName);
944 funcName += subroutine.name;
945
946 // Create a function declaration.
947 auto funcOp = builder.create<mlir::func::FuncOp>(loc, funcName, funcType);
948 SymbolTable::setSymbolVisibility(funcOp, SymbolTable::Visibility::Private);
949 orderedRootOps.insert(it, {subroutine.location, funcOp});
950 lowering->op = funcOp;
951
952 // Add the function to the symbol table of the MLIR module, which uniquifies
953 // its name.
954 symbolTable.insert(funcOp);
955
956 return lowering.get();
957}
958
959/// Convert a function.
960LogicalResult
961Context::convertFunction(const slang::ast::SubroutineSymbol &subroutine) {
962 // First get or create the function declaration.
963 auto *lowering = declareFunction(subroutine);
964 if (!lowering)
965 return failure();
967
968 // Create a function body block and populate it with block arguments.
969 SmallVector<moore::VariableOp> argVariables;
970 auto &block = lowering->op.getBody().emplaceBlock();
971 for (auto [astArg, type] :
972 llvm::zip(subroutine.getArguments(),
973 lowering->op.getFunctionType().getInputs())) {
974 auto loc = convertLocation(astArg->location);
975 auto blockArg = block.addArgument(type, loc);
976
977 if (isa<moore::RefType>(type)) {
978 valueSymbols.insert(astArg, blockArg);
979 } else {
980 // Convert the body of the function.
981 OpBuilder::InsertionGuard g(builder);
982 builder.setInsertionPointToEnd(&block);
983
984 auto shadowArg = builder.create<moore::VariableOp>(
985 loc, moore::RefType::get(cast<moore::UnpackedType>(type)),
986 StringAttr{}, blockArg);
987 valueSymbols.insert(astArg, shadowArg);
988 argVariables.push_back(shadowArg);
989 }
990 }
991
992 // Convert the body of the function.
993 OpBuilder::InsertionGuard g(builder);
994 builder.setInsertionPointToEnd(&block);
995
996 Value returnVar;
997 if (subroutine.returnValVar) {
998 auto type = convertType(*subroutine.returnValVar->getDeclaredType());
999 if (!type)
1000 return failure();
1001 returnVar = builder.create<moore::VariableOp>(
1002 lowering->op.getLoc(),
1003 moore::RefType::get(cast<moore::UnpackedType>(type)), StringAttr{},
1004 Value{});
1005 valueSymbols.insert(subroutine.returnValVar, returnVar);
1006 }
1007
1008 if (failed(convertStatement(subroutine.getBody())))
1009 return failure();
1010
1011 // If there was no explicit return statement provided by the user, insert a
1012 // default one.
1013 if (builder.getBlock()) {
1014 if (returnVar && !subroutine.getReturnType().isVoid()) {
1015 Value read = builder.create<moore::ReadOp>(returnVar.getLoc(), returnVar);
1016 builder.create<mlir::func::ReturnOp>(lowering->op.getLoc(), read);
1017 } else {
1018 builder.create<mlir::func::ReturnOp>(lowering->op.getLoc(), ValueRange{});
1019 }
1020 }
1021 if (returnVar && returnVar.use_empty())
1022 returnVar.getDefiningOp()->erase();
1023
1024 for (auto var : argVariables) {
1025 if (llvm::all_of(var->getUsers(),
1026 [](auto *user) { return isa<moore::ReadOp>(user); })) {
1027 for (auto *user : llvm::make_early_inc_range(var->getUsers())) {
1028 user->getResult(0).replaceAllUsesWith(var.getInitial());
1029 user->erase();
1030 }
1031 var->erase();
1032 }
1033 }
1034 return success();
1035}
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, SmallDenseMap< slang::BufferID, StringRef > &bufferFilePaths, slang::SourceLocation loc)
Convert a slang SourceLocation to an MLIR Location.
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:212
static moore::ProcedureKind convertProcedureKind(slang::ast::ProceduralBlockKind kind)
static void guessNamespacePrefix(const slang::ast::Symbol &symbol, SmallString< 64 > &prefix)
Definition Structure.cpp:19
static moore::NetKind convertNetKind(slang::ast::NetType::NetKind kind)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
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.
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.
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.
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:167
DenseMap< const slang::ast::SubroutineSymbol *, std::unique_ptr< FunctionLowering > > functions
Functions that have already been converted.
const ImportVerilogOptions & options
Value convertRvalueExpression(const slang::ast::Expression &expr, Type requiredType={})
LogicalResult traverseInstanceBody(const slang::ast::Symbol &symbol)
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.
LogicalResult convertCompilation()
Convert hierarchy and structure AST nodes to MLIR ops.
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.