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