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