CIRCT 20.0.0git
Loading...
Searching...
No Matches
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
25namespace circt {
26#define GEN_PASS_DEF_CALYXREMOVEGROUPSFROMFSM
27#include "circt/Conversion/Passes.h.inc"
28} // namespace circt
29
30using namespace circt;
31using namespace calyx;
32using namespace mlir;
33using namespace fsm;
34
35namespace {
36
37struct 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;
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
79LogicalResult CalyxRemoveGroupsFromFSM::modifyGroupOperations() {
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.
116void CalyxRemoveGroupsFromFSM::inlineGroups() {
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
140LogicalResult 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
287void 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
314std::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.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< mlir::Pass > createRemoveGroupsFromFSMPass()
Definition fsm.py:1