CIRCT  20.0.0git
RemoveGroupsFromFSM.cpp
Go to the documentation of this file.
1 //===- RemoveGroupsFromFSM.cpp - Remove Groups Pass -------------*- 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 //
9 // Contains the definitions of the Remove Groups pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
19 #include "circt/Support/LLVM.h"
20 #include "mlir/IR/BuiltinTypes.h"
21 #include "mlir/IR/OperationSupport.h"
22 #include "mlir/Pass/Pass.h"
23 #include "llvm/ADT/STLExtras.h"
24 
25 namespace circt {
26 #define GEN_PASS_DEF_CALYXREMOVEGROUPSFROMFSM
27 #include "circt/Conversion/Passes.h.inc"
28 } // namespace circt
29 
30 using namespace circt;
31 using namespace calyx;
32 using namespace mlir;
33 using namespace fsm;
34 
35 namespace {
36 
37 struct CalyxRemoveGroupsFromFSM
38  : public circt::impl::CalyxRemoveGroupsFromFSMBase<
39  CalyxRemoveGroupsFromFSM> {
40  void runOnOperation() override;
41 
42  // Outlines the `fsm.machine` operation from within the `calyx.control`
43  // operation to the module scope, and instantiates the FSM. By doing so, we
44  // record the association between FSM outputs and group go signals as well as
45  // FSM inputs, which are backedges to the group done signals.
46  LogicalResult outlineMachine();
47 
48  /// Makes several modifications to the operations of a GroupOp:
49  /// 1. Assign the 'done' signal of the component with the done_op of the top
50  /// level control group.
51  /// 2. Append the 'go' signal of the component to guard of each assignment.
52  /// 3. Replace all uses of GroupGoOp with the respective guard, and delete the
53  /// GroupGoOp.
54  /// 4. Remove the GroupDoneOp.
55  LogicalResult modifyGroupOperations();
56 
57  /// Inlines each group in the WiresOp.
58  void inlineGroups();
59 
60  /// A handle to the machine under transformation.
61  MachineOp machineOp;
62 
63  // A handle to the component op under transformation.
64  ComponentOp componentOp;
65 
66  OpBuilder *b;
67  BackedgeBuilder *bb;
68 
69  // A mapping between group names and their 'go' inputs generated by the FSM.
70  DenseMap<StringAttr, Value> groupGoSignals;
71 
72  // A mapping between group names and their 'done' output wires sent to
73  // the FSM.
74  DenseMap<StringAttr, calyx::WireLibOp> groupDoneWires;
75 };
76 
77 } // end anonymous namespace
78 
80  auto loc = componentOp.getLoc();
81  for (auto group : componentOp.getWiresOp().getOps<GroupOp>()) {
82  auto groupGo = group.getGoOp();
83  if (groupGo)
84  return emitError(loc)
85  << "This pass does not need `calyx.group_go` operations.";
86 
87  auto groupDone = group.getDoneOp();
88  if (!groupDone)
89  return emitError(loc) << "Group " << group.getSymName()
90  << " does not have a `calyx.group_done` operation";
91 
92  // Update group assignments to guard with the group go signal.
93  auto fsmGroupGo = groupGoSignals.find(group.getSymNameAttr());
94  assert(fsmGroupGo != groupGoSignals.end() &&
95  "Could not find FSM go signal for group");
96 
97  updateGroupAssignmentGuards(*b, group, fsmGroupGo->second);
98 
99  // Create a calyx wire for the group done signal, and assign it to the
100  // expression of the group_done operation.
101  auto doneWireIt = groupDoneWires.find(group.getSymNameAttr());
102  assert(doneWireIt != groupDoneWires.end() &&
103  "Could not find FSM done backedge for group");
104  auto doneWire = doneWireIt->second;
105 
106  b->setInsertionPointToEnd(componentOp.getWiresOp().getBodyBlock());
107  b->create<calyx::AssignOp>(loc, doneWire.getIn(), groupDone.getSrc(),
108  groupDone.getGuard());
109 
110  groupDone.erase();
111  }
112  return success();
113 }
114 
115 /// Inlines each group in the WiresOp.
117  auto &wiresRegion = componentOp.getWiresOp().getRegion();
118  auto &wireBlocks = wiresRegion.getBlocks();
119  auto lastBlock = wiresRegion.end();
120 
121  // Inline the body of each group as a Block into the WiresOp.
122  wiresRegion.walk([&](GroupOp group) {
123  wireBlocks.splice(lastBlock, group.getRegion().getBlocks());
124  group->erase();
125  });
126 
127  // Merge the operations of each Block into the first block of the WiresOp.
128  auto firstBlock = wireBlocks.begin();
129  for (auto it = firstBlock, e = lastBlock; it != e; ++it) {
130  if (it == firstBlock)
131  continue;
132  firstBlock->getOperations().splice(firstBlock->end(), it->getOperations());
133  }
134 
135  // Erase the (now) empty blocks.
136  while (&wiresRegion.front() != &wiresRegion.back())
137  wiresRegion.back().erase();
138 }
139 
140 LogicalResult CalyxRemoveGroupsFromFSM::outlineMachine() {
141  // Walk all operations within the machine and gather the SSA values which are
142  // referenced in case they are not defined within the machine.
143  // MapVector ensures determinism.
144  llvm::MapVector<Value, SmallVector<Operation *>> referencedValues;
145  machineOp.walk([&](Operation *op) {
146  for (auto &operand : op->getOpOperands()) {
147  if (auto barg = dyn_cast<BlockArgument>(operand.get())) {
148  if (barg.getOwner()->getParentOp() == machineOp)
149  continue;
150 
151  // A block argument defined outside of the machineOp.
152  referencedValues[operand.get()].push_back(op);
153  } else {
154  auto *defOp = operand.get().getDefiningOp();
155  auto machineOpParent = defOp->getParentOfType<MachineOp>();
156  if (machineOpParent && machineOpParent == machineOp)
157  continue;
158 
159  referencedValues[operand.get()].push_back(op);
160  }
161  }
162  });
163 
164  // Add a new input to the machine for each referenced SSA value and replace
165  // all uses of the value with the new input.
166  DenseMap<Value, size_t> ssaInputIndices;
167  auto machineOutputTypes = machineOp.getFunctionType().getResults();
168  auto currentInputs = machineOp.getFunctionType().getInputs();
169  llvm::SmallVector<Type> machineInputTypes(currentInputs);
170 
171  for (auto &[value, users] : referencedValues) {
172  ssaInputIndices[value] = machineOp.getBody().getNumArguments();
173  auto t = value.getType();
174  auto arg = machineOp.getBody().addArgument(t, b->getUnknownLoc());
175  machineInputTypes.push_back(t);
176  for (auto *user : users) {
177  for (auto &operand : user->getOpOperands()) {
178  if (operand.get() == value)
179  operand.set(arg);
180  }
181  }
182  }
183  // Update the machineOp type.
184  machineOp.setType(b->getFunctionType(machineInputTypes, machineOutputTypes));
185 
186  // Move the machine to module scope
187  machineOp->moveBefore(componentOp);
188  size_t nMachineInputs = machineOp.getBody().getNumArguments();
189 
190  // Create an fsm.hwinstance in the Calyx component scope with backedges for
191  // the group done inputs.
192  auto groupDoneInputsAttr =
193  machineOp->getAttrOfType<DictionaryAttr>(calyxToFSM::sGroupDoneInputs);
194  auto groupGoOutputsAttr =
195  machineOp->getAttrOfType<DictionaryAttr>(calyxToFSM::sGroupGoOutputs);
196  if (!groupDoneInputsAttr || !groupGoOutputsAttr)
197  return emitError(machineOp.getLoc())
198  << "MachineOp does not have a " << calyxToFSM::sGroupDoneInputs
199  << " or " << calyxToFSM::sGroupGoOutputs
200  << " attribute. Was --materialize-calyx-to-fsm run before "
201  "this pass?";
202 
203  b->setInsertionPointToStart(&componentOp.getBody().front());
204 
205  // Maintain a mapping between the FSM input index and the SSA value.
206  // We do this to sanity check that all inputs occur in the expected order.
207  DenseMap<size_t, Value> fsmInputMap;
208 
209  // First we inspect the groupDoneInputsAttr map and create backedges.
210  for (auto &namedAttr : groupDoneInputsAttr.getValue()) {
211  auto name = namedAttr.getName();
212  auto idx = cast<IntegerAttr>(namedAttr.getValue());
213  auto inputIdx = cast<IntegerAttr>(idx).getInt();
214  if (fsmInputMap.count(inputIdx))
215  return emitError(machineOp.getLoc())
216  << "MachineOp has duplicate input index " << idx;
217 
218  // Create a wire for the group done input.
219  b->setInsertionPointToStart(&componentOp.getBody().front());
220  auto groupDoneWire = b->create<calyx::WireLibOp>(
221  componentOp.getLoc(), name.str() + "_done", b->getI1Type());
222  fsmInputMap[inputIdx] = groupDoneWire.getOut();
223  groupDoneWires[name] = groupDoneWire;
224  }
225 
226  // Then we inspect the top level go/done attributes.
227  auto topLevelGoAttr =
228  machineOp->getAttrOfType<IntegerAttr>(calyxToFSM::sFSMTopLevelGoIndex);
229  if (!topLevelGoAttr)
230  return emitError(machineOp.getLoc())
231  << "MachineOp does not have a " << calyxToFSM::sFSMTopLevelGoIndex
232  << " attribute.";
233  fsmInputMap[topLevelGoAttr.getInt()] = componentOp.getGoPort();
234 
235  auto topLevelDoneAttr =
236  machineOp->getAttrOfType<IntegerAttr>(calyxToFSM::sFSMTopLevelDoneIndex);
237  if (!topLevelDoneAttr)
238  return emitError(machineOp.getLoc())
239  << "MachineOp does not have a " << calyxToFSM::sFSMTopLevelDoneIndex
240  << " attribute.";
241 
242  // Then we inspect the external SSA values.
243  for (auto [value, idx] : ssaInputIndices) {
244  if (fsmInputMap.count(idx))
245  return emitError(machineOp.getLoc())
246  << "MachineOp has duplicate input index " << idx;
247  fsmInputMap[idx] = value;
248  }
249 
250  if (fsmInputMap.size() != nMachineInputs)
251  return emitError(machineOp.getLoc())
252  << "MachineOp has " << nMachineInputs
253  << " inputs, but only recorded " << fsmInputMap.size()
254  << " inputs. This either means that --materialize-calyx-to-fsm "
255  "failed or that there is a mismatch in the MachineOp attributes.";
256 
257  // Convert the fsmInputMap to a list.
258  llvm::SmallVector<Value> fsmInputs;
259  for (size_t idx = 0; idx < nMachineInputs; ++idx) {
260  auto it = fsmInputMap.find(idx);
261  assert(it != fsmInputMap.end() && "Missing FSM input index");
262  fsmInputs.push_back(it->second);
263  }
264 
265  // Instantiate the FSM.
266  auto clkPort = componentOp.getClkPort();
267  auto clk = b->create<seq::ToClockOp>(clkPort.getLoc(), clkPort);
268  auto fsmInstance = b->create<fsm::HWInstanceOp>(
269  machineOp.getLoc(), machineOutputTypes, b->getStringAttr("controller"),
270  machineOp.getSymNameAttr(), fsmInputs, clk, componentOp.getResetPort());
271 
272  // Record the FSM output group go signals.
273  for (auto namedAttr : groupGoOutputsAttr.getValue()) {
274  auto name = namedAttr.getName();
275  auto idx = cast<IntegerAttr>(namedAttr.getValue()).getInt();
276  groupGoSignals[name] = fsmInstance.getResult(idx);
277  }
278 
279  // Assign FSM top level done to the component done.
280  b->setInsertionPointToEnd(componentOp.getWiresOp().getBodyBlock());
281  b->create<calyx::AssignOp>(machineOp.getLoc(), componentOp.getDonePort(),
282  fsmInstance.getResult(topLevelDoneAttr.getInt()));
283 
284  return success();
285 }
286 
287 void CalyxRemoveGroupsFromFSM::runOnOperation() {
288  componentOp = getOperation();
289  auto *ctx = componentOp.getContext();
290  auto builder = OpBuilder(ctx);
291  builder.setInsertionPointToStart(&componentOp.getBody().front());
292  auto backedgeBuilder = BackedgeBuilder(builder, componentOp.getLoc());
293  b = &builder;
294  bb = &backedgeBuilder;
295 
296  // Locate the FSM machine in the control op..
297  auto machineOps = componentOp.getControlOp().getOps<fsm::MachineOp>();
298  if (std::distance(machineOps.begin(), machineOps.end()) != 1) {
299  emitError(componentOp.getLoc())
300  << "Expected exactly one fsm.MachineOp in the control op";
301  signalPassFailure();
302  return;
303  }
304  machineOp = *machineOps.begin();
305 
306  if (failed(outlineMachine()) || failed(modifyGroupOperations())) {
307  signalPassFailure();
308  return;
309  }
310 
311  inlineGroups();
312 }
313 
314 std::unique_ptr<mlir::Pass> circt::createRemoveGroupsFromFSMPass() {
315  return std::make_unique<CalyxRemoveGroupsFromFSM>();
316 }
assert(baseType &&"element must be base type")
static void modifyGroupOperations(ComponentOp component)
Makes several modifications to the operations of a GroupOp:
void inlineGroups(ComponentOp component)
Inlines each group in the WiresOp.
Instantiate one of these and use it to build typed backedges.
static constexpr std::string_view sGroupDoneInputs
Definition: CalyxToFSM.h:37
static constexpr std::string_view sFSMTopLevelGoIndex
Definition: CalyxToFSM.h:42
static constexpr std::string_view sFSMTopLevelDoneIndex
Definition: CalyxToFSM.h:44
static constexpr std::string_view sGroupGoOutputs
Definition: CalyxToFSM.h:39
static constexpr std::string_view clkPort
Definition: CalyxOps.h:34
static void updateGroupAssignmentGuards(OpBuilder &builder, GroupOp &group, Op &op)
Updates the guard of each assignment within a group with op.
Definition: CalyxHelpers.h:67
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
std::unique_ptr< mlir::Pass > createRemoveGroupsFromFSMPass()
Definition: fsm.py:1