CIRCT  20.0.0git
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 
12 using namespace circt;
13 using namespace ImportVerilog;
14 
15 //===----------------------------------------------------------------------===//
16 // Utilities
17 //===----------------------------------------------------------------------===//
18 
19 static 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 
34 namespace {
35 /// Base visitor which ignores AST nodes that are handled by Slang's name
36 /// resolution and type checking.
37 struct 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 
118 namespace {
119 struct 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 
147 namespace {
148 struct 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 
171 static moore::ProcedureKind
172 convertProcedureKind(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 
190 static 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 
226 namespace {
227 struct 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 slang::ast::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>(
294  loc, refType.getNestedType(), netOp);
295  portValues.insert({port, readOp});
296  } else if (const auto *var =
297  port->internalSymbol
298  ->as_if<slang::ast::VariableSymbol>()) {
299  auto varOp = builder.create<moore::VariableOp>(
300  loc, refType, StringAttr::get(builder.getContext(), var->name),
301  nullptr);
302  auto readOp = builder.create<moore::ReadOp>(
303  loc, refType.getNestedType(), varOp);
304  portValues.insert({port, readOp});
305  } else {
306  return mlir::emitError(loc)
307  << "unsupported internal symbol for unconnected port `"
308  << port->name << "`";
309  }
310  continue;
311  }
312 
313  // No need to express unconnected behavior for output port, skip to the
314  // next iteration of the loop.
315  case slang::ast::ArgumentDirection::Out:
316  continue;
317 
318  // TODO: Mark Inout port as unsupported and it will be supported later.
319  default:
320  return mlir::emitError(loc)
321  << "unsupported port `" << port->name << "` ("
322  << slang::ast::toString(port->kind) << ")";
323  }
324  }
325 
326  // Unpack the `<expr> = EmptyArgument` pattern emitted by Slang for
327  // output and inout ports.
328  if (const auto *assign = expr->as_if<AssignmentExpression>())
329  expr = &assign->left();
330 
331  // Regular ports lower the connected expression to an lvalue or rvalue and
332  // either attach it to the instance as an operand (for input, inout, and
333  // ref ports), or assign an instance output to it (for output ports).
334  if (auto *port = con->port.as_if<PortSymbol>()) {
335  // Convert as rvalue for inputs, lvalue for all others.
336  auto value = (port->direction == slang::ast::ArgumentDirection::In)
337  ? context.convertRvalueExpression(*expr)
338  : context.convertLvalueExpression(*expr);
339  if (!value)
340  return failure();
341  if (auto *existingPort =
342  moduleLowering->portsBySyntaxNode.lookup(con->port.getSyntax()))
343  port = existingPort;
344  portValues.insert({port, value});
345  continue;
346  }
347 
348  // Multi-ports lower the connected expression to an lvalue and then slice
349  // it up into multiple sub-values, one for each of the ports in the
350  // multi-port.
351  if (const auto *multiPort = con->port.as_if<MultiPortSymbol>()) {
352  // Convert as lvalue.
353  auto value = context.convertLvalueExpression(*expr);
354  if (!value)
355  return failure();
356  unsigned offset = 0;
357  for (const auto *port : llvm::reverse(multiPort->ports)) {
358  if (auto *existingPort = moduleLowering->portsBySyntaxNode.lookup(
359  con->port.getSyntax()))
360  port = existingPort;
361  unsigned width = port->getType().getBitWidth();
362  auto sliceType = context.convertType(port->getType());
363  if (!sliceType)
364  return failure();
365  Value slice = builder.create<moore::ExtractRefOp>(
366  loc, moore::RefType::get(cast<moore::UnpackedType>(sliceType)),
367  value, offset);
368  // Read to map to rvalue for input ports.
369  if (port->direction == slang::ast::ArgumentDirection::In)
370  slice = builder.create<moore::ReadOp>(loc, sliceType, slice);
371  portValues.insert({port, slice});
372  offset += width;
373  }
374  continue;
375  }
376 
377  mlir::emitError(loc) << "unsupported instance port `" << con->port.name
378  << "` (" << slang::ast::toString(con->port.kind)
379  << ")";
380  return failure();
381  }
382 
383  // Match the module's ports up with the port values determined above.
384  SmallVector<Value> inputValues;
385  SmallVector<Value> outputValues;
386  inputValues.reserve(moduleType.getNumInputs());
387  outputValues.reserve(moduleType.getNumOutputs());
388 
389  for (auto &port : moduleLowering->ports) {
390  auto value = portValues.lookup(&port.ast);
391  if (port.ast.direction == ArgumentDirection::Out)
392  outputValues.push_back(value);
393  else
394  inputValues.push_back(value);
395  }
396 
397  // Insert conversions for input ports.
398  for (auto [value, type] :
399  llvm::zip(inputValues, moduleType.getInputTypes()))
400  if (value.getType() != type)
401  value =
402  builder.create<moore::ConversionOp>(value.getLoc(), type, value);
403 
404  // Create the instance op itself.
405  auto inputNames = builder.getArrayAttr(moduleType.getInputNames());
406  auto outputNames = builder.getArrayAttr(moduleType.getOutputNames());
407  auto inst = builder.create<moore::InstanceOp>(
408  loc, moduleType.getOutputTypes(), builder.getStringAttr(instNode.name),
409  FlatSymbolRefAttr::get(module.getSymNameAttr()), inputValues,
410  inputNames, outputNames);
411 
412  // Assign output values from the instance to the connected expression.
413  for (auto [lvalue, output] : llvm::zip(outputValues, inst.getOutputs())) {
414  if (!lvalue)
415  continue;
416  Value rvalue = output;
417  auto dstType = cast<moore::RefType>(lvalue.getType()).getNestedType();
418  if (dstType != rvalue.getType())
419  rvalue = builder.create<moore::ConversionOp>(loc, dstType, rvalue);
420  builder.create<moore::ContinuousAssignOp>(loc, lvalue, rvalue);
421  }
422 
423  return success();
424  }
425 
426  // Handle variables.
427  LogicalResult visit(const slang::ast::VariableSymbol &varNode) {
428  auto loweredType = context.convertType(*varNode.getDeclaredType());
429  if (!loweredType)
430  return failure();
431 
432  Value initial;
433  if (const auto *init = varNode.getInitializer()) {
434  initial = context.convertRvalueExpression(*init, loweredType);
435  if (!initial)
436  return failure();
437  }
438 
439  auto varOp = builder.create<moore::VariableOp>(
440  loc, moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
441  builder.getStringAttr(varNode.name), initial);
442  context.valueSymbols.insert(&varNode, varOp);
443  return success();
444  }
445 
446  // Handle nets.
447  LogicalResult visit(const slang::ast::NetSymbol &netNode) {
448  auto loweredType = context.convertType(*netNode.getDeclaredType());
449  if (!loweredType)
450  return failure();
451 
452  Value assignment;
453  if (netNode.getInitializer()) {
454  assignment = context.convertRvalueExpression(*netNode.getInitializer(),
455  loweredType);
456  if (!assignment)
457  return failure();
458  }
459 
460  auto netkind = convertNetKind(netNode.netType.netKind);
461  if (netkind == moore::NetKind::Interconnect ||
462  netkind == moore::NetKind::UserDefined ||
463  netkind == moore::NetKind::Unknown)
464  return mlir::emitError(loc, "unsupported net kind `")
465  << netNode.netType.name << "`";
466 
467  auto netOp = builder.create<moore::NetOp>(
468  loc, moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
469  builder.getStringAttr(netNode.name), netkind, assignment);
470  context.valueSymbols.insert(&netNode, netOp);
471  return success();
472  }
473 
474  // Handle continuous assignments.
475  LogicalResult visit(const slang::ast::ContinuousAssignSymbol &assignNode) {
476  if (const auto *delay = assignNode.getDelay()) {
477  auto loc = context.convertLocation(delay->sourceRange);
478  return mlir::emitError(loc,
479  "delayed continuous assignments not supported");
480  }
481 
482  const auto &expr =
483  assignNode.getAssignment().as<slang::ast::AssignmentExpression>();
484  auto lhs = context.convertLvalueExpression(expr.left());
485  if (!lhs)
486  return failure();
487 
488  auto rhs = context.convertRvalueExpression(
489  expr.right(), cast<moore::RefType>(lhs.getType()).getNestedType());
490  if (!rhs)
491  return failure();
492 
493  builder.create<moore::ContinuousAssignOp>(loc, lhs, rhs);
494  return success();
495  }
496 
497  // Handle procedures.
498  LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) {
499  auto procOp = builder.create<moore::ProcedureOp>(
500  loc, convertProcedureKind(procNode.procedureKind));
501  OpBuilder::InsertionGuard guard(builder);
502  builder.setInsertionPointToEnd(&procOp.getBody().emplaceBlock());
504  if (failed(context.convertStatement(procNode.getBody())))
505  return failure();
506  if (builder.getBlock())
507  builder.create<moore::ReturnOp>(loc);
508  return success();
509  }
510 
511  // Handle generate block.
512  LogicalResult visit(const slang::ast::GenerateBlockSymbol &genNode) {
513  if (!genNode.isUninstantiated) {
514  for (auto &member : genNode.members()) {
515  if (failed(member.visit(ModuleVisitor(context, loc))))
516  return failure();
517  }
518  }
519  return success();
520  }
521 
522  // Handle generate block array.
523  LogicalResult visit(const slang::ast::GenerateBlockArraySymbol &genArrNode) {
524  for (const auto *member : genArrNode.entries) {
525  if (failed(member->asSymbol().visit(ModuleVisitor(context, loc))))
526  return failure();
527  }
528  return success();
529  }
530 
531  // Ignore statement block symbols. These get generated by Slang for blocks
532  // with variables and other declarations. For example, having an initial
533  // procedure with a variable declaration, such as `initial begin int x;
534  // end`, will create the procedure with a block and variable declaration as
535  // expected, but will also create a `StatementBlockSymbol` with just the
536  // variable layout _next to_ the initial procedure.
537  LogicalResult visit(const slang::ast::StatementBlockSymbol &) {
538  return success();
539  }
540 
541  // Handle functions and tasks.
542  LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) {
543  return context.convertFunction(subroutine);
544  }
545 
546  /// Emit an error for all other members.
547  template <typename T>
548  LogicalResult visit(T &&node) {
549  mlir::emitError(loc, "unsupported module member: ")
550  << slang::ast::toString(node.kind);
551  return failure();
552  }
553 };
554 } // namespace
555 
556 //===----------------------------------------------------------------------===//
557 // Structure and Hierarchy Conversion
558 //===----------------------------------------------------------------------===//
559 
560 /// Convert an entire Slang compilation to MLIR ops. This is the main entry
561 /// point for the conversion.
563  const auto &root = compilation.getRoot();
564 
565  // Visit all top-level declarations in all compilation units. This does not
566  // include instantiable constructs like modules, interfaces, and programs,
567  // which are listed separately as top instances.
568  for (auto *unit : root.compilationUnits) {
569  for (const auto &member : unit->members()) {
570  auto loc = convertLocation(member.location);
571  if (failed(member.visit(RootVisitor(*this, loc))))
572  return failure();
573  }
574  }
575 
576  // Prime the root definition worklist by adding all the top-level modules.
577  SmallVector<const slang::ast::InstanceSymbol *> topInstances;
578  for (auto *inst : root.topInstances)
579  if (!convertModuleHeader(&inst->body))
580  return failure();
581 
582  // Convert all the root module definitions.
583  while (!moduleWorklist.empty()) {
584  auto *module = moduleWorklist.front();
585  moduleWorklist.pop();
586  if (failed(convertModuleBody(module)))
587  return failure();
588  }
589 
590  return success();
591 }
592 
593 /// Convert a module and its ports to an empty module op in the IR. Also adds
594 /// the op to the worklist of module bodies to be lowered. This acts like a
595 /// module "declaration", allowing instances to already refer to a module even
596 /// before its body has been lowered.
598 Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) {
599  using slang::ast::ArgumentDirection;
600  using slang::ast::MultiPortSymbol;
601  using slang::ast::ParameterSymbol;
602  using slang::ast::PortSymbol;
603  using slang::ast::TypeParameterSymbol;
604 
605  auto parameters = module->parameters;
606  bool hasModuleSame = false;
607  // If there is already exist a module that has the same name with this
608  // module ,has the same parent scope and has the same parameters we can
609  // define this module is a duplicate module
610  for (auto const &existingModule : modules) {
611  if (module->getDeclaringDefinition() ==
612  existingModule.getFirst()->getDeclaringDefinition()) {
613  auto moduleParameters = existingModule.getFirst()->parameters;
614  hasModuleSame = true;
615  for (auto it1 = parameters.begin(), it2 = moduleParameters.begin();
616  it1 != parameters.end() && it2 != moduleParameters.end();
617  it1++, it2++) {
618  // Parameters size different
619  if (it1 == parameters.end() || it2 == moduleParameters.end()) {
620  hasModuleSame = false;
621  break;
622  }
623  const auto *para1 = (*it1)->symbol.as_if<ParameterSymbol>();
624  const auto *para2 = (*it2)->symbol.as_if<ParameterSymbol>();
625  // Parameters kind different
626  if ((para1 == nullptr) ^ (para2 == nullptr)) {
627  hasModuleSame = false;
628  break;
629  }
630  // Compare ParameterSymbol
631  if (para1 != nullptr) {
632  hasModuleSame = para1->getValue() == para2->getValue();
633  }
634  // Compare TypeParameterSymbol
635  if (para1 == nullptr) {
636  auto para1Type = convertType(
637  (*it1)->symbol.as<TypeParameterSymbol>().getTypeAlias());
638  auto para2Type = convertType(
639  (*it2)->symbol.as<TypeParameterSymbol>().getTypeAlias());
640  hasModuleSame = para1Type == para2Type;
641  }
642  if (!hasModuleSame)
643  break;
644  }
645  if (hasModuleSame) {
646  module = existingModule.first;
647  break;
648  }
649  }
650  }
651 
652  auto &slot = modules[module];
653  if (slot)
654  return slot.get();
655  slot = std::make_unique<ModuleLowering>();
656  auto &lowering = *slot;
657 
658  auto loc = convertLocation(module->location);
659  OpBuilder::InsertionGuard g(builder);
660 
661  // We only support modules for now. Extension to interfaces and programs
662  // should be trivial though, since they are essentially the same thing with
663  // only minor differences in semantics.
664  if (module->getDefinition().definitionKind !=
665  slang::ast::DefinitionKind::Module) {
666  mlir::emitError(loc) << "unsupported definition: "
667  << module->getDefinition().getKindString();
668  return {};
669  }
670 
671  // Handle the port list.
672  auto block = std::make_unique<Block>();
673  SmallVector<hw::ModulePort> modulePorts;
674  for (auto *symbol : module->getPortList()) {
675  auto handlePort = [&](const PortSymbol &port) {
676  auto portLoc = convertLocation(port.location);
677  auto type = convertType(port.getType());
678  if (!type)
679  return failure();
680  auto portName = builder.getStringAttr(port.name);
681  BlockArgument arg;
682  if (port.direction == ArgumentDirection::Out) {
683  modulePorts.push_back({portName, type, hw::ModulePort::Output});
684  } else {
685  // Only the ref type wrapper exists for the time being, the net type
686  // wrapper for inout may be introduced later if necessary.
687  if (port.direction != slang::ast::ArgumentDirection::In)
688  type = moore::RefType::get(cast<moore::UnpackedType>(type));
689  modulePorts.push_back({portName, type, hw::ModulePort::Input});
690  arg = block->addArgument(type, portLoc);
691  }
692  lowering.ports.push_back({port, portLoc, arg});
693  return success();
694  };
695 
696  if (const auto *port = symbol->as_if<PortSymbol>()) {
697  if (failed(handlePort(*port)))
698  return {};
699  } else if (const auto *multiPort = symbol->as_if<MultiPortSymbol>()) {
700  for (auto *port : multiPort->ports)
701  if (failed(handlePort(*port)))
702  return {};
703  } else {
704  mlir::emitError(convertLocation(symbol->location))
705  << "unsupported module port `" << symbol->name << "` ("
706  << slang::ast::toString(symbol->kind) << ")";
707  return {};
708  }
709  }
710  auto moduleType = hw::ModuleType::get(getContext(), modulePorts);
711 
712  // Pick an insertion point for this module according to the source file
713  // location.
714  auto it = orderedRootOps.upper_bound(module->location);
715  if (it == orderedRootOps.end())
716  builder.setInsertionPointToEnd(intoModuleOp.getBody());
717  else
718  builder.setInsertionPoint(it->second);
719 
720  // Create an empty module that corresponds to this module.
721  auto moduleOp =
722  builder.create<moore::SVModuleOp>(loc, module->name, moduleType);
723  orderedRootOps.insert(it, {module->location, moduleOp});
724  moduleOp.getBodyRegion().push_back(block.release());
725  lowering.op = moduleOp;
726 
727  // Add the module to the symbol table of the MLIR module, which uniquifies its
728  // name as we'd expect.
729  symbolTable.insert(moduleOp);
730 
731  // Schedule the body to be lowered.
732  moduleWorklist.push(module);
733 
734  // Map duplicate port by Syntax
735  for (const auto &port : lowering.ports)
736  lowering.portsBySyntaxNode.insert({port.ast.getSyntax(), &port.ast});
737 
738  return &lowering;
739 }
740 
741 /// Convert a module's body to the corresponding IR ops. The module op must have
742 /// already been created earlier through a `convertModuleHeader` call.
743 LogicalResult
744 Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
745  auto &lowering = *modules[module];
746  OpBuilder::InsertionGuard g(builder);
747  builder.setInsertionPointToEnd(lowering.op.getBody());
748 
749  // Convert the body of the module.
751  for (auto &member : module->members()) {
752  auto loc = convertLocation(member.location);
753  if (failed(member.visit(ModuleVisitor(*this, loc))))
754  return failure();
755  }
756 
757  // Create additional ops to drive input port values onto the corresponding
758  // internal variables and nets, and to collect output port values for the
759  // terminator.
760  SmallVector<Value> outputs;
761  for (auto &port : lowering.ports) {
762  Value value;
763  if (auto *expr = port.ast.getInternalExpr()) {
764  value = convertLvalueExpression(*expr);
765  } else if (port.ast.internalSymbol) {
766  if (const auto *sym =
767  port.ast.internalSymbol->as_if<slang::ast::ValueSymbol>())
768  value = valueSymbols.lookup(sym);
769  }
770  if (!value)
771  return mlir::emitError(port.loc, "unsupported port: `")
772  << port.ast.name
773  << "` does not map to an internal symbol or expression";
774 
775  // Collect output port values to be returned in the terminator.
776  if (port.ast.direction == slang::ast::ArgumentDirection::Out) {
777  if (isa<moore::RefType>(value.getType()))
778  value = builder.create<moore::ReadOp>(
779  value.getLoc(),
780  cast<moore::RefType>(value.getType()).getNestedType(), value);
781  outputs.push_back(value);
782  continue;
783  }
784 
785  // Assign the value coming in through the port to the internal net or symbol
786  // of that port.
787  Value portArg = port.arg;
788  if (port.ast.direction != slang::ast::ArgumentDirection::In)
789  portArg = builder.create<moore::ReadOp>(
790  port.loc, cast<moore::RefType>(value.getType()).getNestedType(),
791  port.arg);
792  builder.create<moore::ContinuousAssignOp>(port.loc, value, portArg);
793  }
794  builder.create<moore::OutputOp>(lowering.op.getLoc(), outputs);
795 
796  return success();
797 }
798 
799 /// Convert a package and its contents.
800 LogicalResult
801 Context::convertPackage(const slang::ast::PackageSymbol &package) {
802  OpBuilder::InsertionGuard g(builder);
803  builder.setInsertionPointToEnd(intoModuleOp.getBody());
805  for (auto &member : package.members()) {
806  auto loc = convertLocation(member.location);
807  if (failed(member.visit(PackageVisitor(*this, loc))))
808  return failure();
809  }
810  return success();
811 }
812 
813 /// Convert a function and its arguments to a function declaration in the IR.
814 /// This does not convert the function body.
816 Context::declareFunction(const slang::ast::SubroutineSymbol &subroutine) {
817  using slang::ast::ArgumentDirection;
818 
819  // Check if there already is a declaration for this function.
820  auto &lowering = functions[&subroutine];
821  if (lowering) {
822  if (!lowering->op)
823  return {};
824  return lowering.get();
825  }
826  lowering = std::make_unique<FunctionLowering>();
827  auto loc = convertLocation(subroutine.location);
828 
829  // Pick an insertion point for this function according to the source file
830  // location.
831  OpBuilder::InsertionGuard g(builder);
832  auto it = orderedRootOps.upper_bound(subroutine.location);
833  if (it == orderedRootOps.end())
834  builder.setInsertionPointToEnd(intoModuleOp.getBody());
835  else
836  builder.setInsertionPoint(it->second);
837 
838  // Class methods are currently not supported.
839  if (subroutine.thisVar) {
840  mlir::emitError(loc) << "unsupported class method";
841  return {};
842  }
843 
844  // Determine the function type.
845  SmallVector<Type> inputTypes;
846  SmallVector<Type, 1> outputTypes;
847 
848  for (const auto *arg : subroutine.getArguments()) {
849  auto type = cast<moore::UnpackedType>(convertType(arg->getType()));
850  if (!type)
851  return {};
852  if (arg->direction == ArgumentDirection::In) {
853  inputTypes.push_back(type);
854  } else {
855  inputTypes.push_back(moore::RefType::get(type));
856  }
857  }
858 
859  if (!subroutine.getReturnType().isVoid()) {
860  auto type = convertType(subroutine.getReturnType());
861  if (!type)
862  return {};
863  outputTypes.push_back(type);
864  }
865 
866  auto funcType = FunctionType::get(getContext(), inputTypes, outputTypes);
867 
868  // Prefix the function name with the surrounding namespace to create somewhat
869  // sane names in the IR.
870  SmallString<64> funcName;
871  guessNamespacePrefix(subroutine.getParentScope()->asSymbol(), funcName);
872  funcName += subroutine.name;
873 
874  // Create a function declaration.
875  auto funcOp = builder.create<mlir::func::FuncOp>(loc, funcName, funcType);
876  SymbolTable::setSymbolVisibility(funcOp, SymbolTable::Visibility::Private);
877  orderedRootOps.insert(it, {subroutine.location, funcOp});
878  lowering->op = funcOp;
879 
880  // Add the function to the symbol table of the MLIR module, which uniquifies
881  // its name.
882  symbolTable.insert(funcOp);
883 
884  return lowering.get();
885 }
886 
887 /// Convert a function.
888 LogicalResult
889 Context::convertFunction(const slang::ast::SubroutineSymbol &subroutine) {
890  // First get or create the function declaration.
891  auto *lowering = declareFunction(subroutine);
892  if (!lowering)
893  return failure();
895 
896  // Create a function body block and populate it with block arguments.
897  auto &block = lowering->op.getBody().emplaceBlock();
898  for (auto [astArg, type] :
899  llvm::zip(subroutine.getArguments(),
900  lowering->op.getFunctionType().getInputs())) {
901  auto loc = convertLocation(astArg->location);
902  auto blockArg = block.addArgument(type, loc);
903  valueSymbols.insert(astArg, blockArg);
904  }
905 
906  // Convert the body of the function.
907  OpBuilder::InsertionGuard g(builder);
908  builder.setInsertionPointToEnd(&block);
909 
910  Value returnVar;
911  if (subroutine.returnValVar) {
912  auto type = convertType(*subroutine.returnValVar->getDeclaredType());
913  if (!type)
914  return failure();
915  returnVar = builder.create<moore::VariableOp>(
916  lowering->op.getLoc(),
917  moore::RefType::get(cast<moore::UnpackedType>(type)), StringAttr{},
918  Value{});
919  valueSymbols.insert(subroutine.returnValVar, returnVar);
920  }
921 
922  if (failed(convertStatement(subroutine.getBody())))
923  return failure();
924 
925  // If there was no explicit return statement provided by the user, insert a
926  // default one.
927  if (builder.getBlock()) {
928  if (returnVar && !subroutine.getReturnType().isVoid()) {
929  Value read = builder.create<moore::ReadOp>(returnVar.getLoc(), returnVar);
930  builder.create<mlir::func::ReturnOp>(lowering->op.getLoc(), read);
931  } else {
932  builder.create<mlir::func::ReturnOp>(lowering->op.getLoc(), ValueRange{});
933  }
934  }
935  if (returnVar && returnVar.use_empty())
936  returnVar.getDefiningOp()->erase();
937  return success();
938 }
const char * toString(Flow flow)
Definition: FIRRTLOps.cpp:197
int32_t width
Definition: FIRRTL.cpp:36
static moore::ProcedureKind convertProcedureKind(slang::ast::ProceduralBlockKind kind)
Definition: Structure.cpp:172
static void guessNamespacePrefix(const slang::ast::Symbol &symbol, SmallString< 64 > &prefix)
Definition: Structure.cpp:19
static moore::NetKind convertNetKind(slang::ast::NetType::NetKind kind)
Definition: Structure.cpp:190
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
bool debugInfo
Generate debug information in the form of debug dialect ops in the IR.
Definition: ImportVerilog.h:50
A helper class to facilitate the conversion from a Slang AST to MLIR operations.
LogicalResult convertFunction(const slang::ast::SubroutineSymbol &subroutine)
Convert a function.
Definition: Structure.cpp:889
ModuleLowering * convertModuleHeader(const slang::ast::InstanceBodySymbol *module)
Convert a module and its ports to an empty module op in the IR.
Definition: Structure.cpp:598
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.
Definition: Structure.cpp:744
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.
ValueSymbols::ScopeTy ValueSymbolScope
const ImportVerilogOptions & options
Value convertRvalueExpression(const slang::ast::Expression &expr, Type requiredType={})
SymbolTable symbolTable
A symbol table of the MLIR module we are emitting into.
FunctionLowering * declareFunction(const slang::ast::SubroutineSymbol &subroutine)
Convert a function and its arguments to a function declaration in the IR.
Definition: Structure.cpp:816
LogicalResult convertPackage(const slang::ast::PackageSymbol &package)
Convert a package and its contents.
Definition: Structure.cpp:801
LogicalResult convertCompilation()
Convert hierarchy and structure AST nodes to MLIR ops.
Definition: Structure.cpp:562
MLIRContext * getContext()
Return the MLIR context.
LogicalResult convertStatement(const slang::ast::Statement &stmt)
Definition: Statements.cpp:695
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.