CIRCT  19.0.0git
MaterializeFSM.cpp
Go to the documentation of this file.
1 //===- MaterializeCalyxToFSM.cpp - FSM Materialization 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 FSM materialization pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "../PassDetail.h"
17 #include "circt/Support/LLVM.h"
18 #include "mlir/IR/BuiltinTypes.h"
19 #include "mlir/IR/OperationSupport.h"
20 #include "llvm/ADT/STLExtras.h"
21 
22 using namespace circt;
23 using namespace calyx;
24 using namespace mlir;
25 using namespace fsm;
26 
27 namespace {
28 
29 struct MaterializeCalyxToFSMPass
30  : public MaterializeCalyxToFSMBase<MaterializeCalyxToFSMPass> {
31  void runOnOperation() override;
32 
33  /// Assigns the 'fsm.output' operation of the provided 'state' to enabled the
34  /// set of provided groups. If 'topLevelDone' is set, also asserts the
35  /// top-level done signal.
36  void assignStateOutputOperands(OpBuilder &b, StateOp stateOp,
37  bool topLevelDone = false) {
38  SmallVector<Value> outputOperands;
39  auto &enabledGroups = stateEnables[stateOp];
40  for (StringAttr group : referencedGroups)
41  outputOperands.push_back(
42  getOrCreateConstant(b, APInt(1, enabledGroups.contains(group))));
43 
44  assert(outputOperands.size() == machineOp.getNumArguments() - 1 &&
45  "Expected exactly one value for each uniquely referenced group in "
46  "this machine");
47  outputOperands.push_back(getOrCreateConstant(b, APInt(1, topLevelDone)));
48  stateOp.ensureOutput(b);
49  auto outputOp = stateOp.getOutputOp();
50  outputOp->setOperands(outputOperands);
51  }
52 
53  /// Extends every `fsm.return` guard in the transitions of this state to also
54  /// include the provided set of 'doneGuards'. 'doneGuards' is passed by value
55  /// to allow the caller to provide additional done guards apart from group
56  /// enable-generated guards.
57  void assignStateTransitionGuard(OpBuilder &b, StateOp stateOp,
58  SmallVector<Value> doneGuards = {}) {
59  auto &enabledGroups = stateEnables[stateOp];
60  for (auto groupIt : llvm::enumerate(referencedGroups))
61  if (enabledGroups.contains(groupIt.value()))
62  doneGuards.push_back(machineOp.getArgument(groupIt.index()));
63 
64  for (auto transition :
65  stateOp.getTransitions().getOps<fsm::TransitionOp>()) {
66 
67  if (!transition.hasGuard() && doneGuards.empty())
68  continue;
69  transition.ensureGuard(b);
70  auto guardOp = transition.getGuardReturn();
71  llvm::SmallVector<Value> guards;
72  llvm::append_range(guards, doneGuards);
73  if (guardOp.getNumOperands() != 0)
74  guards.push_back(guardOp.getOperand());
75 
76  if (guards.empty())
77  continue;
78 
79  b.setInsertionPoint(guardOp);
80  Value guardConjunction;
81  if (guards.size() == 1)
82  guardConjunction = guards.front();
83  else
84  guardConjunction =
85  b.create<comb::AndOp>(transition.getLoc(), guards, false);
86  guardOp.setOperand(guardConjunction);
87  }
88  }
89 
90  Value getOrCreateConstant(OpBuilder &b, APInt value) {
91  auto it = constants.find(value);
92  if (it != constants.end())
93  return it->second;
94 
95  OpBuilder::InsertionGuard g(b);
96  b.setInsertionPointToStart(&machineOp.getBody().front());
97  auto constantOp = b.create<hw::ConstantOp>(machineOp.getLoc(), value);
98  constants[value] = constantOp;
99  return constantOp;
100  }
101 
102  /// Maintain a set of all groups referenced within this fsm.machine.
103  /// Use a SetVector to ensure a deterministic ordering - strong assumptions
104  /// are placed on the order of referenced groups wrt. the top-level I/O
105  /// created for the group done/go signals.
106  SetVector<StringAttr> referencedGroups;
107 
108  /// Maintain a relation between states and the groups which they enable.
109  DenseMap<fsm::StateOp, DenseSet<StringAttr>> stateEnables;
110 
111  /// A handle to the machine under transformation.
112  MachineOp machineOp;
113 
114  /// Constant cache.
115  DenseMap<APInt, Value> constants;
116 
117  OpBuilder *b;
118 
119  FSMStateNode *entryState;
120  FSMStateNode *exitState;
121 
122  // Walks the machine and gathers the set of referenced groups and SSA values.
123  void walkMachine();
124 
125  // Creates the top-level group go/done I/O for the machine.
126  void materializeGroupIO();
127 
128  // Add attributes to the machine op to indicate which in/out ports are
129  // associated with group activations and which are additional inputs to the
130  // FSM.
131  void assignAttributes();
132 };
133 
134 } // end anonymous namespace
135 
136 void MaterializeCalyxToFSMPass::walkMachine() {
137  // Walk the states of the machine and gather the relation between states and
138  // the groups which they enable as well as the set of all enabled states.
139  for (auto stateOp : machineOp.getOps<fsm::StateOp>()) {
140  for (auto enableOp : llvm::make_early_inc_range(
141  stateOp.getOutput().getOps<calyx::EnableOp>())) {
142  auto groupName = enableOp.getGroupNameAttr().getAttr();
143  stateEnables[stateOp].insert(groupName);
144  referencedGroups.insert(groupName);
145  // Erase the enable op now that we've recorded the information.
146  enableOp.erase();
147  }
148  }
149 }
150 
151 void MaterializeCalyxToFSMPass::materializeGroupIO() {
152  // Materialize the top-level I/O ports of the fsm.machine. We add an in- and
153  // output for every unique group referenced within the machine, as well as an
154  // additional in- and output to represent the top-level "go" input and "done"
155  // output ports.
156  SmallVector<Type> ioTypes = SmallVector<Type>(
157  referencedGroups.size() + /*top-level go/done*/ 1, b->getI1Type());
158  size_t nGroups = ioTypes.size() - 1;
159  machineOp.setType(b->getFunctionType(ioTypes, ioTypes));
160  assert(machineOp.getBody().getNumArguments() == 0 &&
161  "expected no inputs to the FSM");
162  machineOp.getBody().addArguments(
163  ioTypes, SmallVector<Location, 4>(ioTypes.size(), b->getUnknownLoc()));
164 
165  // Build output assignments and transition guards in every state. We here
166  // assume that the ordering of states in referencedGroups is fixed and
167  // deterministic, since it is used as an analogue for port I/O ordering.
168  for (auto stateOp : machineOp.getOps<fsm::StateOp>()) {
169  assignStateOutputOperands(*b, stateOp,
170  /*topLevelDone=*/false);
171  assignStateTransitionGuard(*b, stateOp);
172  }
173 
174  // Assign top-level go guard in the transition state.
175  size_t topLevelGoIdx = nGroups;
176  assignStateTransitionGuard(*b, entryState->getState(),
177  {machineOp.getArgument(topLevelGoIdx)});
178 
179  // Assign top-level done in the exit state.
180  assignStateOutputOperands(*b, exitState->getState(),
181  /*topLevelDone=*/true);
182 }
183 
184 void MaterializeCalyxToFSMPass::assignAttributes() {
185  // sGroupDoneInputs is a mapping from group name to the index of the
186  // corresponding done input port.
187  llvm::SmallVector<NamedAttribute> groupDoneInputs;
188  for (size_t i = 0; i < referencedGroups.size(); ++i)
189  groupDoneInputs.push_back({referencedGroups[i], b->getI64IntegerAttr(i)});
190  machineOp->setAttr(calyxToFSM::sGroupDoneInputs,
191  b->getDictionaryAttr(groupDoneInputs));
192 
193  // sGroupGoOutputs is a mapping from group name to the index of the
194  // corresponding go output port.
195  llvm::SmallVector<NamedAttribute> groupGoOutputs;
196  for (size_t i = 0; i < referencedGroups.size(); ++i)
197  groupGoOutputs.push_back({referencedGroups[i], b->getI64IntegerAttr(i)});
198  machineOp->setAttr(calyxToFSM::sGroupGoOutputs,
199  b->getDictionaryAttr(groupGoOutputs));
200 
201  // Assign top level go/done attributes
202  machineOp->setAttr(calyxToFSM::sFSMTopLevelGoIndex,
203  b->getI64IntegerAttr(referencedGroups.size()));
204  machineOp->setAttr(calyxToFSM::sFSMTopLevelDoneIndex,
205  b->getI64IntegerAttr(referencedGroups.size()));
206 }
207 
208 void MaterializeCalyxToFSMPass::runOnOperation() {
209  ComponentOp component = getOperation();
210  auto *ctx = &getContext();
211  auto builder = OpBuilder(ctx);
212  b = &builder;
213  auto controlOp = component.getControlOp();
214  machineOp =
215  dyn_cast_or_null<fsm::MachineOp>(controlOp.getBodyBlock()->front());
216  if (!machineOp) {
217  controlOp.emitOpError()
218  << "expected an 'fsm.machine' operation as the top-level operation "
219  "within the control region of this component.";
220  signalPassFailure();
221  return;
222  }
223 
224  // Ensure a well-formed FSM.
225  auto graph = FSMGraph(machineOp);
226  entryState = graph.lookup(b->getStringAttr(calyxToFSM::sEntryStateName));
227  exitState = graph.lookup(b->getStringAttr(calyxToFSM::sExitStateName));
228 
229  if (!(entryState && exitState)) {
230  machineOp.emitOpError()
231  << "Expected an '" << calyxToFSM::sEntryStateName << "' and '"
232  << calyxToFSM::sExitStateName << "' state to be present in the FSM.";
233  signalPassFailure();
234  return;
235  }
236 
237  walkMachine();
238  materializeGroupIO();
239  assignAttributes();
240 }
241 
242 std::unique_ptr<mlir::Pass> circt::createMaterializeCalyxToFSMPass() {
243  return std::make_unique<MaterializeCalyxToFSMPass>();
244 }
assert(baseType &&"element must be base type")
Builder builder
static constexpr std::string_view sGroupDoneInputs
Definition: CalyxToFSM.h:32
static constexpr std::string_view sFSMTopLevelGoIndex
Definition: CalyxToFSM.h:37
static constexpr std::string_view sExitStateName
Definition: CalyxToFSM.h:31
static constexpr std::string_view sFSMTopLevelDoneIndex
Definition: CalyxToFSM.h:39
static constexpr std::string_view sEntryStateName
Definition: CalyxToFSM.h:30
static constexpr std::string_view sGroupGoOutputs
Definition: CalyxToFSM.h:34
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
std::unique_ptr< mlir::Pass > createMaterializeCalyxToFSMPass()
Definition: fsm.py:1