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 // Module Member Conversion
17 //===----------------------------------------------------------------------===//
18 
19 static moore::ProcedureKind
20 convertProcedureKind(slang::ast::ProceduralBlockKind kind) {
21  switch (kind) {
22  case slang::ast::ProceduralBlockKind::Always:
23  return moore::ProcedureKind::Always;
24  case slang::ast::ProceduralBlockKind::AlwaysComb:
25  return moore::ProcedureKind::AlwaysComb;
26  case slang::ast::ProceduralBlockKind::AlwaysLatch:
27  return moore::ProcedureKind::AlwaysLatch;
28  case slang::ast::ProceduralBlockKind::AlwaysFF:
29  return moore::ProcedureKind::AlwaysFF;
30  case slang::ast::ProceduralBlockKind::Initial:
31  return moore::ProcedureKind::Initial;
32  case slang::ast::ProceduralBlockKind::Final:
33  return moore::ProcedureKind::Final;
34  }
35  llvm_unreachable("all procedure kinds handled");
36 }
37 
38 static moore::NetKind convertNetKind(slang::ast::NetType::NetKind kind) {
39  switch (kind) {
40  case slang::ast::NetType::Supply0:
41  return moore::NetKind::Supply0;
42  case slang::ast::NetType::Supply1:
43  return moore::NetKind::Supply1;
44  case slang::ast::NetType::Tri:
45  return moore::NetKind::Tri;
46  case slang::ast::NetType::TriAnd:
47  return moore::NetKind::TriAnd;
48  case slang::ast::NetType::TriOr:
49  return moore::NetKind::TriOr;
50  case slang::ast::NetType::TriReg:
51  return moore::NetKind::TriReg;
52  case slang::ast::NetType::Tri0:
53  return moore::NetKind::Tri0;
54  case slang::ast::NetType::Tri1:
55  return moore::NetKind::Tri1;
56  case slang::ast::NetType::UWire:
57  return moore::NetKind::UWire;
58  case slang::ast::NetType::Wire:
59  return moore::NetKind::Wire;
60  case slang::ast::NetType::WAnd:
61  return moore::NetKind::WAnd;
62  case slang::ast::NetType::WOr:
63  return moore::NetKind::WOr;
64  case slang::ast::NetType::Interconnect:
65  return moore::NetKind::Interconnect;
66  case slang::ast::NetType::UserDefined:
67  return moore::NetKind::UserDefined;
68  case slang::ast::NetType::Unknown:
69  return moore::NetKind::Unknown;
70  }
71  llvm_unreachable("all net kinds handled");
72 }
73 
74 namespace {
75 struct MemberVisitor {
76  Context &context;
77  Location loc;
78  OpBuilder &builder;
79 
80  MemberVisitor(Context &context, Location loc)
81  : context(context), loc(loc), builder(context.builder) {}
82 
83  // Skip empty members (stray semicolons).
84  LogicalResult visit(const slang::ast::EmptyMemberSymbol &) {
85  return success();
86  }
87 
88  // Skip members that are implicitly imported from some other scope for the
89  // sake of name resolution, such as enum variant names.
90  LogicalResult visit(const slang::ast::TransparentMemberSymbol &) {
91  return success();
92  }
93 
94  // Skip typedefs.
95  LogicalResult visit(const slang::ast::TypeAliasType &) { return success(); }
96 
97  // Skip ports which are already handled by the module itself.
98  LogicalResult visit(const slang::ast::PortSymbol &) { return success(); }
99  LogicalResult visit(const slang::ast::MultiPortSymbol &) { return success(); }
100 
101  // Handle instances.
102  LogicalResult visit(const slang::ast::InstanceSymbol &instNode) {
103  using slang::ast::ArgumentDirection;
104  using slang::ast::AssignmentExpression;
105  using slang::ast::MultiPortSymbol;
106  using slang::ast::PortSymbol;
107 
108  auto *moduleLowering = context.convertModuleHeader(&instNode.body);
109  if (!moduleLowering)
110  return failure();
111  auto module = moduleLowering->op;
112  auto moduleType = module.getModuleType();
113 
114  // Prepare the values that are involved in port connections. This creates
115  // rvalues for input ports and appropriate lvalues for output, inout, and
116  // ref ports. We also separate multi-ports into the individual underlying
117  // ports with their corresponding connection.
119  portValues.reserve(moduleType.getNumPorts());
120 
121  for (const auto *con : instNode.getPortConnections()) {
122  const auto *expr = con->getExpression();
123  if (!expr)
124  return mlir::emitError(loc)
125  << "unconnected port `" << con->port.name << "` not supported";
126 
127  // Unpack the `<expr> = EmptyArgument` pattern emitted by Slang for
128  // output and inout ports.
129  if (const auto *assign = expr->as_if<AssignmentExpression>())
130  expr = &assign->left();
131 
132  // Regular ports lower the connected expression to an lvalue or rvalue and
133  // either attach it to the instance as an operand (for input, inout, and
134  // ref ports), or assign an instance output to it (for output ports).
135  if (auto *port = con->port.as_if<PortSymbol>()) {
136  // Convert as rvalue for inputs, lvalue for all others.
137  auto value = (port->direction == slang::ast::ArgumentDirection::In)
138  ? context.convertRvalueExpression(*expr)
139  : context.convertLvalueExpression(*expr);
140  if (!value)
141  return failure();
142  portValues.insert({port, value});
143  continue;
144  }
145 
146  // Multi-ports lower the connected expression to an lvalue and then slice
147  // it up into multiple sub-values, one for each of the ports in the
148  // multi-port.
149  if (const auto *multiPort = con->port.as_if<MultiPortSymbol>()) {
150  // Convert as lvalue.
151  auto value = context.convertLvalueExpression(*expr);
152  if (!value)
153  return failure();
154  unsigned offset = 0;
155  auto i32 = moore::IntType::getInt(context.getContext(), 32);
156  for (const auto *port : llvm::reverse(multiPort->ports)) {
157  unsigned width = port->getType().getBitWidth();
158  auto index = builder.create<moore::ConstantOp>(loc, i32, offset);
159  auto sliceType = context.convertType(port->getType());
160  if (!sliceType)
161  return failure();
162  Value slice = builder.create<moore::ExtractRefOp>(
163  loc, moore::RefType::get(cast<moore::UnpackedType>(sliceType)),
164  value, index);
165  // Read to map to rvalue for input ports.
166  if (port->direction == slang::ast::ArgumentDirection::In)
167  slice = builder.create<moore::ReadOp>(loc, sliceType, slice);
168  portValues.insert({port, slice});
169  offset += width;
170  }
171  continue;
172  }
173 
174  mlir::emitError(loc) << "unsupported instance port `" << con->port.name
175  << "` (" << slang::ast::toString(con->port.kind)
176  << ")";
177  return failure();
178  }
179 
180  // Match the module's ports up with the port values determined above.
181  SmallVector<Value> inputValues;
182  SmallVector<Value> outputValues;
183  inputValues.reserve(moduleType.getNumInputs());
184  outputValues.reserve(moduleType.getNumOutputs());
185 
186  for (auto &port : moduleLowering->ports) {
187  auto value = portValues.lookup(&port.ast);
188  assert(value && "no prepared value for port");
189  if (port.ast.direction == ArgumentDirection::Out)
190  outputValues.push_back(value);
191  else
192  inputValues.push_back(value);
193  }
194 
195  // Create the instance op itself.
196  auto inputNames = builder.getArrayAttr(moduleType.getInputNames());
197  auto outputNames = builder.getArrayAttr(moduleType.getOutputNames());
198  auto inst = builder.create<moore::InstanceOp>(
199  loc, moduleType.getOutputTypes(), builder.getStringAttr(instNode.name),
200  FlatSymbolRefAttr::get(module.getSymNameAttr()), inputValues,
201  inputNames, outputNames);
202 
203  // Assign output values from the instance to the connected expression.
204  for (auto [lvalue, output] : llvm::zip(outputValues, inst.getOutputs()))
205  builder.create<moore::ContinuousAssignOp>(loc, lvalue, output);
206 
207  return success();
208  }
209 
210  // Handle variables.
211  LogicalResult visit(const slang::ast::VariableSymbol &varNode) {
212  auto loweredType = context.convertType(*varNode.getDeclaredType());
213  if (!loweredType)
214  return failure();
215 
216  Value initial;
217  if (const auto *init = varNode.getInitializer()) {
218  initial = context.convertRvalueExpression(*init);
219  if (!initial)
220  return failure();
221  }
222 
223  auto varOp = builder.create<moore::VariableOp>(
224  loc, moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
225  builder.getStringAttr(varNode.name), initial);
226  context.valueSymbols.insert(&varNode, varOp);
227  return success();
228  }
229 
230  // Handle nets.
231  LogicalResult visit(const slang::ast::NetSymbol &netNode) {
232  auto loweredType = context.convertType(*netNode.getDeclaredType());
233  if (!loweredType)
234  return failure();
235 
236  Value assignment;
237  if (netNode.getInitializer()) {
238  assignment = context.convertRvalueExpression(*netNode.getInitializer());
239  if (!assignment)
240  return failure();
241  }
242 
243  auto netkind = convertNetKind(netNode.netType.netKind);
244  if (netkind == moore::NetKind::Interconnect ||
245  netkind == moore::NetKind::UserDefined ||
246  netkind == moore::NetKind::Unknown)
247  return mlir::emitError(loc, "unsupported net kind `")
248  << netNode.netType.name << "`";
249 
250  auto netOp = builder.create<moore::NetOp>(
251  loc, moore::RefType::get(cast<moore::UnpackedType>(loweredType)),
252  builder.getStringAttr(netNode.name), netkind, assignment);
253  context.valueSymbols.insert(&netNode, netOp);
254  return success();
255  }
256 
257  // Handle continuous assignments.
258  LogicalResult visit(const slang::ast::ContinuousAssignSymbol &assignNode) {
259  if (const auto *delay = assignNode.getDelay()) {
260  auto loc = context.convertLocation(delay->sourceRange);
261  return mlir::emitError(loc,
262  "delayed continuous assignments not supported");
263  }
264 
265  const auto &expr =
266  assignNode.getAssignment().as<slang::ast::AssignmentExpression>();
267 
268  auto lhs = context.convertLvalueExpression(expr.left());
269  auto rhs = context.convertRvalueExpression(expr.right());
270  if (!lhs || !rhs)
271  return failure();
272 
273  builder.create<moore::ContinuousAssignOp>(loc, lhs, rhs);
274  return success();
275  }
276 
277  // Handle procedures.
278  LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) {
279  auto procOp = builder.create<moore::ProcedureOp>(
280  loc, convertProcedureKind(procNode.procedureKind));
281  procOp.getBodyRegion().emplaceBlock();
282  OpBuilder::InsertionGuard guard(builder);
283  builder.setInsertionPointToEnd(procOp.getBody());
285  return context.convertStatement(procNode.getBody());
286  }
287 
288  // Handle parameters.
289  LogicalResult visit(const slang::ast::ParameterSymbol &paramNode) {
290  auto type = context.convertType(*paramNode.getDeclaredType());
291  if (!type)
292  return failure();
293 
294  const auto *init = paramNode.getInitializer();
295  // skip parameters without value.
296  if (!init)
297  return success();
298  Value initial = context.convertRvalueExpression(*init);
299  if (!initial)
300  return failure();
301 
302  auto namedConstantOp = builder.create<moore::NamedConstantOp>(
303  loc, type, builder.getStringAttr(paramNode.name),
304  paramNode.isLocalParam()
306  moore::NamedConst::LocalParameter)
307  : moore::NamedConstAttr::get(context.getContext(),
308  moore::NamedConst::Parameter),
309  initial);
310  context.valueSymbols.insert(&paramNode, namedConstantOp);
311  return success();
312  }
313 
314  // Handle specparam.
315  LogicalResult visit(const slang::ast::SpecparamSymbol &spNode) {
316  auto type = context.convertType(*spNode.getDeclaredType());
317  if (!type)
318  return failure();
319 
320  const auto *init = spNode.getInitializer();
321  // skip specparam without value.
322  if (!init)
323  return success();
324  Value initial = context.convertRvalueExpression(*init);
325  if (!initial)
326  return failure();
327 
328  auto namedConstantOp = builder.create<moore::NamedConstantOp>(
329  loc, type, builder.getStringAttr(spNode.name),
331  moore::NamedConst::SpecParameter),
332  initial);
333  context.valueSymbols.insert(&spNode, namedConstantOp);
334  return success();
335  }
336 
337  // Ignore statement block symbols. These get generated by Slang for blocks
338  // with variables and other declarations. For example, having an initial
339  // procedure with a variable declaration, such as `initial begin int x;
340  // end`, will create the procedure with a block and variable declaration as
341  // expected, but will also create a `StatementBlockSymbol` with just the
342  // variable layout _next to_ the initial procedure.
343  LogicalResult visit(const slang::ast::StatementBlockSymbol &) {
344  return success();
345  }
346 
347  /// Emit an error for all other members.
348  template <typename T>
349  LogicalResult visit(T &&node) {
350  mlir::emitError(loc, "unsupported construct: ")
351  << slang::ast::toString(node.kind);
352  return failure();
353  }
354 };
355 } // namespace
356 
357 //===----------------------------------------------------------------------===//
358 // Structure and Hierarchy Conversion
359 //===----------------------------------------------------------------------===//
360 
361 /// Convert an entire Slang compilation to MLIR ops. This is the main entry
362 /// point for the conversion.
363 LogicalResult
364 Context::convertCompilation(slang::ast::Compilation &compilation) {
365  const auto &root = compilation.getRoot();
366 
367  // Visit all top-level declarations in all compilation units. This does not
368  // include instantiable constructs like modules, interfaces, and programs,
369  // which are listed separately as top instances.
370  for (auto *unit : root.compilationUnits) {
371  for (const auto &member : unit->members()) {
372  // Error out on all top-level declarations.
373  auto loc = convertLocation(member.location);
374  return mlir::emitError(loc, "unsupported construct: ")
375  << slang::ast::toString(member.kind);
376  }
377  }
378 
379  // Prime the root definition worklist by adding all the top-level modules.
380  SmallVector<const slang::ast::InstanceSymbol *> topInstances;
381  for (auto *inst : root.topInstances)
382  if (!convertModuleHeader(&inst->body))
383  return failure();
384 
385  // Convert all the root module definitions.
386  while (!moduleWorklist.empty()) {
387  auto *module = moduleWorklist.front();
388  moduleWorklist.pop();
389  if (failed(convertModuleBody(module)))
390  return failure();
391  }
392 
393  return success();
394 }
395 
396 /// Convert a module and its ports to an empty module op in the IR. Also adds
397 /// the op to the worklist of module bodies to be lowered. This acts like a
398 /// module "declaration", allowing instances to already refer to a module even
399 /// before its body has been lowered.
401 Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) {
402  using slang::ast::ArgumentDirection;
403  using slang::ast::MultiPortSymbol;
404  using slang::ast::PortSymbol;
405 
406  auto &slot = modules[module];
407  if (slot)
408  return slot.get();
409  slot = std::make_unique<ModuleLowering>();
410  auto &lowering = *slot;
411 
412  auto loc = convertLocation(module->location);
413  OpBuilder::InsertionGuard g(builder);
414 
415  // We only support modules for now. Extension to interfaces and programs
416  // should be trivial though, since they are essentially the same thing with
417  // only minor differences in semantics.
418  if (module->getDefinition().definitionKind !=
419  slang::ast::DefinitionKind::Module) {
420  mlir::emitError(loc) << "unsupported construct: "
421  << module->getDefinition().getKindString();
422  return {};
423  }
424 
425  // Handle the port list.
426  auto block = std::make_unique<Block>();
427  SmallVector<hw::ModulePort> modulePorts;
428  for (auto *symbol : module->getPortList()) {
429  auto handlePort = [&](const PortSymbol &port) {
430  auto portLoc = convertLocation(port.location);
431  auto type = convertType(port.getType());
432  if (!type)
433  return failure();
434  auto portName = builder.getStringAttr(port.name);
435  BlockArgument arg;
436  if (port.direction == ArgumentDirection::Out) {
437  modulePorts.push_back({portName, type, hw::ModulePort::Output});
438  } else {
439  // Only the ref type wrapper exists for the time being, the net type
440  // wrapper for inout may be introduced later if necessary.
441  if (port.direction != slang::ast::ArgumentDirection::In)
442  type = moore::RefType::get(cast<moore::UnpackedType>(type));
443  modulePorts.push_back({portName, type, hw::ModulePort::Input});
444  arg = block->addArgument(type, portLoc);
445  }
446  lowering.ports.push_back({port, portLoc, arg});
447  return success();
448  };
449 
450  if (const auto *port = symbol->as_if<PortSymbol>()) {
451  if (failed(handlePort(*port)))
452  return {};
453  } else if (const auto *multiPort = symbol->as_if<MultiPortSymbol>()) {
454  for (auto *port : multiPort->ports)
455  if (failed(handlePort(*port)))
456  return {};
457  } else {
458  mlir::emitError(convertLocation(symbol->location))
459  << "unsupported module port `" << symbol->name << "` ("
460  << slang::ast::toString(symbol->kind) << ")";
461  return {};
462  }
463  }
464  auto moduleType = hw::ModuleType::get(getContext(), modulePorts);
465 
466  // Pick an insertion point for this module according to the source file
467  // location.
468  auto it = orderedRootOps.lower_bound(module->location);
469  if (it == orderedRootOps.end())
470  builder.setInsertionPointToEnd(intoModuleOp.getBody());
471  else
472  builder.setInsertionPoint(it->second);
473 
474  // Create an empty module that corresponds to this module.
475  auto moduleOp =
476  builder.create<moore::SVModuleOp>(loc, module->name, moduleType);
477  orderedRootOps.insert(it, {module->location, moduleOp});
478  moduleOp.getBodyRegion().push_back(block.release());
479  lowering.op = moduleOp;
480 
481  // Add the module to the symbol table of the MLIR module, which uniquifies its
482  // name as we'd expect.
483  symbolTable.insert(moduleOp);
484 
485  // Schedule the body to be lowered.
486  moduleWorklist.push(module);
487  return &lowering;
488 }
489 
490 /// Convert a module's body to the corresponding IR ops. The module op must have
491 /// already been created earlier through a `convertModuleHeader` call.
492 LogicalResult
493 Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
494  auto &lowering = *modules[module];
495  OpBuilder::InsertionGuard g(builder);
496  builder.setInsertionPointToEnd(lowering.op.getBody());
497 
498  // Convert the body of the module.
500  for (auto &member : module->members()) {
501  auto loc = convertLocation(member.location);
502  if (failed(member.visit(MemberVisitor(*this, loc))))
503  return failure();
504  }
505 
506  // Create additional ops to drive input port values onto the corresponding
507  // internal variables and nets, and to collect output port values for the
508  // terminator.
509  SmallVector<Value> outputs;
510  for (auto &port : lowering.ports) {
511  Value value;
512  if (auto *expr = port.ast.getInternalExpr()) {
513  value = convertLvalueExpression(*expr);
514  } else if (port.ast.internalSymbol) {
515  if (const auto *sym =
516  port.ast.internalSymbol->as_if<slang::ast::ValueSymbol>())
517  value = valueSymbols.lookup(sym);
518  }
519  if (!value)
520  return mlir::emitError(port.loc, "unsupported port: `")
521  << port.ast.name
522  << "` does not map to an internal symbol or expression";
523 
524  // Collect output port values to be returned in the terminator.
525  if (port.ast.direction == slang::ast::ArgumentDirection::Out) {
526  if (isa<moore::RefType>(value.getType()))
527  value = builder.create<moore::ReadOp>(
528  value.getLoc(),
529  cast<moore::RefType>(value.getType()).getNestedType(), value);
530  outputs.push_back(value);
531  continue;
532  }
533 
534  // Assign the value coming in through the port to the internal net or symbol
535  // of that port.
536  Value portArg = port.arg;
537  if (port.ast.direction != slang::ast::ArgumentDirection::In)
538  portArg = builder.create<moore::ReadOp>(
539  port.loc, cast<moore::RefType>(value.getType()).getNestedType(),
540  port.arg);
541  builder.create<moore::ContinuousAssignOp>(port.loc, value, portArg);
542  }
543  builder.create<moore::OutputOp>(lowering.op.getLoc(), outputs);
544 
545  return success();
546 }
assert(baseType &&"element must be base type")
const char * toString(Flow flow)
Definition: FIRRTLOps.cpp:193
int32_t width
Definition: FIRRTL.cpp:36
llvm::SmallVector< StringAttr > outputs
Builder builder
static std::optional< APInt > getInt(Value value)
Helper to convert a value to a constant integer if it is one.
static moore::ProcedureKind convertProcedureKind(slang::ast::ProceduralBlockKind kind)
Definition: Structure.cpp:20
static moore::NetKind convertNetKind(slang::ast::NetType::NetKind kind)
Definition: Structure.cpp:38
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.
ModuleLowering * convertModuleHeader(const slang::ast::InstanceBodySymbol *module)
Convert a module and its ports to an empty module op in the IR.
Definition: Structure.cpp:401
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:493
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.
LogicalResult convertCompilation(slang::ast::Compilation &compilation)
Convert hierarchy and structure AST nodes to MLIR ops.
Definition: Structure.cpp:364
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
ValueSymbols::ScopeTy ValueSymbolScope
Value convertRvalueExpression(const slang::ast::Expression &expr)
SymbolTable symbolTable
A symbol table of the MLIR module we are emitting into.
MLIRContext * getContext()
Return the MLIR context.
LogicalResult convertStatement(const slang::ast::Statement &stmt)
Definition: Statements.cpp:326
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.