CIRCT  18.0.0git
LowerGroups.cpp
Go to the documentation of this file.
1 //===- LowerGroups.cpp - Lower Optiona Groups to Instances ------*- C++ -*-===//
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 // This pass lowers FIRRTL optional groups to modules and instances.
9 //
10 //===----------------------------------------------------------------------===//
11 
12 #include "PassDetails.h"
16 #include "circt/Dialect/SV/SVOps.h"
17 #include "llvm/Support/Debug.h"
18 #include "llvm/Support/Mutex.h"
19 #include "llvm/Support/RWMutex.h"
20 
21 #define DEBUG_TYPE "firrtl-lower-groups"
22 
23 using namespace circt;
24 using namespace firrtl;
25 
26 class LowerGroupsPass : public LowerGroupsBase<LowerGroupsPass> {
27  /// Safely build a new module with a given namehint. This handles geting a
28  /// lock to modify the top-level circuit.
29  FModuleOp buildNewModule(OpBuilder &builder, Location location,
30  Twine namehint, SmallVectorImpl<PortInfo> &ports);
31 
32  /// Function to process each module.
33  void runOnModule(FModuleOp moduleOp);
34 
35  /// Entry point for the function.
36  void runOnOperation() override;
37 
38  /// Indicates exclusive access to modify the circuitNamespace and the circuit.
39  llvm::sys::SmartMutex<true> *circuitMutex;
40 
41  /// A map of group definition to module name that should be created for it.
42  DenseMap<GroupOp, StringRef> moduleNames;
43 };
44 
45 /// Multi-process safe function to build a module in the circuit and return it.
46 /// The name provided is only a namehint for the module---a unique name will be
47 /// generated if there are conflicts with the namehint in the circuit-level
48 /// namespace.
49 FModuleOp LowerGroupsPass::buildNewModule(OpBuilder &builder, Location location,
50  Twine namehint,
51  SmallVectorImpl<PortInfo> &ports) {
52  llvm::sys::SmartScopedLock<true> instrumentationLock(*circuitMutex);
53  FModuleOp newModule = builder.create<FModuleOp>(
54  location, builder.getStringAttr(namehint),
55  ConventionAttr::get(builder.getContext(), Convention::Internal), ports,
56  ArrayAttr{});
57  SymbolTable::setSymbolVisibility(newModule, SymbolTable::Visibility::Private);
58  return newModule;
59 }
60 
61 /// Process amodule to remove any groups it has.
62 void LowerGroupsPass::runOnModule(FModuleOp moduleOp) {
63  LLVM_DEBUG({
64  llvm::dbgs() << "Module: " << moduleOp.getModuleName() << "\n";
65  llvm::dbgs() << " Examining Groups:\n";
66  });
67 
68  CircuitOp circuitOp = moduleOp->getParentOfType<CircuitOp>();
69  StringRef circuitName = circuitOp.getName();
70 
71  // A map of instance ops to modules that this pass creates. This is used to
72  // check if this was an instance that we created and to do fast module
73  // dereferencing (avoiding a symbol table).
74  DenseMap<InstanceOp, FModuleOp> createdInstances;
75 
76  // Post-order traversal that expands a group into its parent. For each group
77  // found do the following:
78  //
79  // 1. Create and connect one ref-type output port for each value defined in
80  // this group that drives an instance marked lowerToBind and move this
81  // instance outside the group.
82  // 2. Create one input port for each value captured by this group.
83  // 3. Create a new module for this group and move the (mutated) body of this
84  // group to the new module.
85  // 4. Instantiate the new module outside the group and hook it up.
86  // 5. Erase the group.
87  moduleOp.walk<mlir::WalkOrder::PostOrder>([&](Operation *op) {
88  auto group = dyn_cast<GroupOp>(op);
89  if (!group)
90  return WalkResult::advance();
91 
92  // Compute the expanded group name. For group @A::@B::@C, this is "A_B_C".
93  SmallString<32> groupName(group.getGroupName().getRootReference());
94  for (auto ref : group.getGroupName().getNestedReferences()) {
95  groupName.append("_");
96  groupName.append(ref.getValue());
97  }
98  LLVM_DEBUG(llvm::dbgs() << " - Group: " << groupName << "\n");
99 
100  Block *body = group.getBody(0);
101  OpBuilder builder(moduleOp);
102 
103  // Ports that need to be created for the module derived from this group.
104  SmallVector<PortInfo> ports;
105 
106  // Connectsion that need to be made to the instance of the derived module.
107  SmallVector<Value> connectValues;
108 
109  // Create an input port for an operand that is captured from outside.
110  //
111  // TODO: If we allow capturing reference types, this will need to be
112  // updated.
113  auto createInputPort = [&](Value operand, Location loc) {
114  auto portNum = ports.size();
115  auto operandName = getFieldName(FieldRef(operand, 0), true);
116 
117  ports.push_back({builder.getStringAttr("_" + operandName.first),
118  operand.getType(), Direction::In, /*sym=*/{},
119  /*loc=*/loc});
120  // Update the group's body with arguments as we will swap this body into
121  // the module when we create it.
122  body->addArgument(operand.getType(), loc);
123  operand.replaceUsesWithIf(body->getArgument(portNum),
124  [&](OpOperand &operand) {
125  return operand.getOwner()->getBlock() == body;
126  });
127 
128  connectValues.push_back(operand);
129  };
130 
131  // Set the location intelligently. Use the location of the capture if
132  // this is a port created for forwarding from a parent group to a nested
133  // group. Otherwise, use unknown.
134  auto getPortLoc = [&](Value port) -> Location {
135  Location loc = UnknownLoc::get(port.getContext());
136  if (auto *destOp = port.getDefiningOp())
137  if (auto instOp = dyn_cast<InstanceOp>(destOp)) {
138  auto modOpIt = createdInstances.find(instOp);
139  if (modOpIt != createdInstances.end()) {
140  auto portNum = port.cast<OpResult>().getResultNumber();
141  loc = modOpIt->getSecond().getPortLocation(portNum);
142  }
143  }
144  return loc;
145  };
146 
147  // Create an output probe port port and adds the ref.define/ref.send to
148  // drive the port.
149  auto createOutputPort = [&](Value dest, Value src) {
150  auto loc = getPortLoc(dest);
151  auto portNum = ports.size();
152  auto operandName = getFieldName(FieldRef(dest, 0), true);
153 
154  auto refType = RefType::get(
155  type_cast<FIRRTLBaseType>(dest.getType()).getPassiveType(),
156  /*forceable=*/false);
157 
158  ports.push_back({builder.getStringAttr("_" + operandName.first), refType,
159  Direction::Out, /*sym=*/{}, /*loc=*/loc});
160  body->addArgument(refType, loc);
161  connectValues.push_back(dest);
162  OpBuilder::InsertionGuard guard(builder);
163  builder.setInsertionPointAfterValue(src);
164  builder.create<RefDefineOp>(
165  loc, body->getArgument(portNum),
166  builder.create<RefSendOp>(loc, src)->getResult(0));
167  };
168 
169  for (auto &op : llvm::make_early_inc_range(*body)) {
170  // Handle instance ops that were created from nested groups. These ops
171  // need to be moved outside the group to avoid nested binds which are
172  // illegal in the SystemVerilog specification (and checked by FIRRTL
173  // verification).
174  //
175  // For each value defined in this group which drives a port of one of
176  // these instances, create an output reference type port on the
177  // to-be-created module and drive it with the value. Move the instance
178  // outside the group. We will hook it up later once we replace the group
179  // with an instance.
180  if (auto instOp = dyn_cast<InstanceOp>(op)) {
181  // Ignore any instance which this pass did not create from a nested
182  // group. Instances which are not marked lowerToBind do not need to be
183  // split out.
184  if (!createdInstances.contains(instOp))
185  continue;
186  LLVM_DEBUG({
187  llvm::dbgs() << " Found instance created from nested group:\n"
188  << " module: " << instOp.getModuleName() << "\n"
189  << " instance: " << instOp.getName() << "\n";
190  });
191  instOp->moveBefore(group);
192  continue;
193  }
194 
195  if (auto connect = dyn_cast<FConnectLike>(op)) {
196  auto srcInGroup = connect.getSrc().getParentBlock() == body;
197  auto destInGroup = connect.getDest().getParentBlock() == body;
198  if (!srcInGroup && !destInGroup) {
199  connect->moveBefore(group);
200  continue;
201  }
202  // Create an input port.
203  if (!srcInGroup) {
204  createInputPort(connect.getSrc(), op.getLoc());
205  continue;
206  }
207  // Create an output port.
208  if (!destInGroup) {
209  createOutputPort(connect.getDest(), connect.getSrc());
210  connect.erase();
211  continue;
212  }
213  // Source and destination in group. Nothing to do.
214  continue;
215  }
216 
217  // Pattern match the following structure. Move the ref.resolve outside
218  // the group. The strictconnect will be moved outside in the next loop
219  // iteration:
220  // %0 = ...
221  // %1 = ...
222  // firrtl.group {
223  // %2 = ref.resolve %0
224  // firrtl.strictconnect %1, %2
225  // }
226  if (auto refResolve = dyn_cast<RefResolveOp>(op))
227  if (refResolve.getResult().hasOneUse())
228  if (auto connect = dyn_cast<StrictConnectOp>(
229  *refResolve.getResult().getUsers().begin()))
230  if (connect.getDest().getParentBlock() != body) {
231  refResolve->moveBefore(group);
232  continue;
233  }
234 
235  // For any other ops, create input ports for any captured operands.
236  for (auto operand : op.getOperands())
237  if (operand.getParentBlock() != body)
238  createInputPort(operand, op.getLoc());
239  }
240 
241  // Create the new module. This grabs a lock to modify the circuit.
242  FModuleOp newModule = buildNewModule(builder, group.getLoc(),
243  moduleNames.lookup(group), ports);
244  SymbolTable::setSymbolVisibility(newModule,
245  SymbolTable::Visibility::Private);
246  newModule.getBody().takeBody(group.getRegion());
247 
248  LLVM_DEBUG({
249  llvm::dbgs() << " New Module: " << moduleNames.lookup(group) << "\n";
250  llvm::dbgs() << " ports:\n";
251  for (size_t i = 0, e = ports.size(); i != e; ++i) {
252  auto port = ports[i];
253  auto value = connectValues[i];
254  llvm::dbgs() << " - name: " << port.getName() << "\n"
255  << " type: " << port.type << "\n"
256  << " direction: " << port.direction << "\n"
257  << " value: " << value << "\n";
258  }
259  });
260 
261  // Replace the original group with an instance. Hook up the instance.
262  builder.setInsertionPointAfter(group);
263  auto moduleName = newModule.getModuleName();
264  auto instanceOp = builder.create<InstanceOp>(
265  group.getLoc(), /*moduleName=*/newModule,
266  /*name=*/
267  (Twine((char)tolower(moduleName[0])) + moduleName.drop_front()).str(),
268  NameKindEnum::DroppableName,
269  /*annotations=*/ArrayRef<Attribute>{},
270  /*portAnnotations=*/ArrayRef<Attribute>{}, /*lowerToBind=*/true);
271  instanceOp->setAttr("output_file",
272  hw::OutputFileAttr::getFromFilename(
273  builder.getContext(),
274  "groups_" + circuitName + "_" + groupName + ".sv",
275  /*excludeFromFileList=*/true));
276  createdInstances.try_emplace(instanceOp, newModule);
277 
278  // Connect instance ports to values.
279  assert(ports.size() == connectValues.size() &&
280  "the number of instance ports and values to connect to them must be "
281  "equal");
282  for (unsigned portNum = 0, e = newModule.getNumPorts(); portNum < e;
283  ++portNum) {
284  OpBuilder::InsertionGuard guard(builder);
285  builder.setInsertionPointAfterValue(instanceOp.getResult(portNum));
286  if (instanceOp.getPortDirection(portNum) == Direction::In)
287  builder.create<StrictConnectOp>(newModule.getPortLocationAttr(portNum),
288  instanceOp.getResult(portNum),
289  connectValues[portNum]);
290  else
291  builder.create<StrictConnectOp>(
292  getPortLoc(connectValues[portNum]), connectValues[portNum],
293  builder.create<RefResolveOp>(newModule.getPortLocationAttr(portNum),
294  instanceOp.getResult(portNum)));
295  }
296  group.erase();
297 
298  return WalkResult::advance();
299  });
300 }
301 
302 /// Process a circuit to remove all groups in each module and top-level group
303 /// declarations.
305  LLVM_DEBUG(
306  llvm::dbgs() << "==----- Running LowerGroups "
307  "-------------------------------------------------===\n");
308  CircuitOp circuitOp = getOperation();
309 
310  // Initialize members which cannot be initialized automatically.
311  llvm::sys::SmartMutex<true> mutex;
312  circuitMutex = &mutex;
313 
314  // Determine names for all modules that will be created. Do this serially to
315  // avoid non-determinism from creating these in the parallel region.
316  CircuitNamespace ns(circuitOp);
317  circuitOp->walk([&](FModuleOp moduleOp) {
318  moduleOp->walk([&](GroupOp groupOp) {
319  SmallString<32> groupName(groupOp.getGroupName().getRootReference());
320  for (auto ref : groupOp.getGroupName().getNestedReferences()) {
321  groupName.append("_");
322  groupName.append(ref.getValue());
323  }
324  moduleNames.insert(
325  {groupOp, ns.newName(moduleOp.getModuleName() + "_" + groupName)});
326  });
327  });
328 
329  // Early exit if no work to do.
330  if (moduleNames.empty())
331  return markAllAnalysesPreserved();
332 
333  // Lower the groups of each module.
334  SmallVector<FModuleOp> modules(circuitOp.getBodyBlock()->getOps<FModuleOp>());
335  llvm::parallelForEach(modules,
336  [&](FModuleOp moduleOp) { runOnModule(moduleOp); });
337 
338  // Generate the header and footer of each bindings file. The body will be
339  // populated later when binds are exported to Verilog. This produces text
340  // like:
341  //
342  // `include "groups_A.sv"
343  // `include "groups_A_B.sv"
344  // `ifndef groups_A_B_C
345  // `define groups_A_B_C
346  // <body>
347  // `endif // groups_A_B_C
348  //
349  // TODO: This would be better handled without the use of verbatim ops.
350  OpBuilder builder(circuitOp);
351  SmallVector<std::pair<GroupDeclOp, StringAttr>> groupDecls;
352  StringRef circuitName = circuitOp.getName();
353  circuitOp.walk<mlir::WalkOrder::PreOrder>([&](GroupDeclOp groupDeclOp) {
354  auto parentOp = groupDeclOp->getParentOfType<GroupDeclOp>();
355  while (parentOp && parentOp != groupDecls.back().first)
356  groupDecls.pop_back();
357  builder.setInsertionPointToStart(circuitOp.getBodyBlock());
358 
359  // Save the "groups_CIRCUIT_GROUP" string as this is reused a bunch.
360  SmallString<32> prefix("groups_");
361  prefix.append(circuitName);
362  prefix.append("_");
363  for (auto [group, _] : groupDecls) {
364  prefix.append(group.getSymName());
365  prefix.append("_");
366  }
367  prefix.append(groupDeclOp.getSymName());
368 
369  auto outputFileAttr = hw::OutputFileAttr::getFromFilename(
370  builder.getContext(), prefix + ".sv",
371  /*excludeFromFileList=*/true);
372 
373  SmallString<128> includes;
374  for (auto [_, strAttr] : groupDecls) {
375  includes.append(strAttr);
376  includes.append("\n");
377  }
378 
379  // Write header to a verbatim.
380  builder
381  .create<sv::VerbatimOp>(groupDeclOp.getLoc(), includes + "`ifndef " +
382  prefix + "\n" +
383  "`define " + prefix)
384  ->setAttr("output_file", outputFileAttr);
385 
386  // Write footer to a verbatim.
387  builder.setInsertionPointToEnd(circuitOp.getBodyBlock());
388  builder.create<sv::VerbatimOp>(groupDeclOp.getLoc(), "`endif // " + prefix)
389  ->setAttr("output_file", outputFileAttr);
390 
391  if (!groupDeclOp.getBody().getOps<GroupDeclOp>().empty())
392  groupDecls.push_back(
393  {groupDeclOp,
394  builder.getStringAttr("`include \"" + prefix + ".sv\"")});
395  });
396 
397  // All group declarations can now be deleted.
398  for (auto groupDeclOp : llvm::make_early_inc_range(
399  circuitOp.getBodyBlock()->getOps<GroupDeclOp>()))
400  groupDeclOp.erase();
401 }
402 
403 std::unique_ptr<mlir::Pass> circt::firrtl::createLowerGroupsPass() {
404  return std::make_unique<LowerGroupsPass>();
405 }
lowerAnnotationsNoRefTypePorts FirtoolPreserveValuesMode value
Definition: Firtool.cpp:95
assert(baseType &&"element must be base type")
Builder builder
llvm::sys::SmartMutex< true > * circuitMutex
Indicates exclusive access to modify the circuitNamespace and the circuit.
Definition: LowerGroups.cpp:39
void runOnModule(FModuleOp moduleOp)
Function to process each module.
Definition: LowerGroups.cpp:62
void runOnOperation() override
Entry point for the function.
FModuleOp buildNewModule(OpBuilder &builder, Location location, Twine namehint, SmallVectorImpl< PortInfo > &ports)
Safely build a new module with a given namehint.
Definition: LowerGroups.cpp:49
DenseMap< GroupOp, StringRef > moduleNames
A map of group definition to module name that should be created for it.
Definition: LowerGroups.cpp:42
This class represents a reference to a specific field or element of an aggregate value.
Definition: FieldRef.h:28
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition: Namespace.h:63
def connect(destination, source)
Definition: support.py:37
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:53
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
std::unique_ptr< mlir::Pass > createLowerGroupsPass()
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
mlir::raw_indented_ostream & dbgs()
Definition: Utility.h:28
The namespace of a CircuitOp, generally inhabited by modules.
Definition: Namespace.h:24