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