CIRCT  20.0.0git
CompileControl.cpp
Go to the documentation of this file.
1 //===- CompileControl.cpp - Compile Control 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 Compile Control pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
16 #include "circt/Support/LLVM.h"
17 #include "mlir/IR/BuiltinTypes.h"
18 #include "mlir/IR/OperationSupport.h"
19 #include "mlir/IR/PatternMatch.h"
20 #include "llvm/ADT/TypeSwitch.h"
21 
22 namespace circt {
23 namespace calyx {
24 #define GEN_PASS_DEF_COMPILECONTROL
25 #include "circt/Dialect/Calyx/CalyxPasses.h.inc"
26 } // namespace calyx
27 } // namespace circt
28 
29 using namespace circt;
30 using namespace calyx;
31 using namespace mlir;
32 
33 /// Given some number of states, returns the necessary bit width
34 /// TODO(Calyx): Probably a better built-in operation?
35 static size_t getNecessaryBitWidth(size_t numStates) {
36  APInt apNumStates(64, numStates);
37  size_t log2 = apNumStates.ceilLogBase2();
38  return log2 > 1 ? log2 : 1;
39 }
40 
42 public:
43  CompileControlVisitor(AnalysisManager am) : am(am){};
44  void dispatch(Operation *op, ComponentOp component) {
45  TypeSwitch<Operation *>(op)
46  .template Case<SeqOp, EnableOp>(
47  [&](auto opNode) { visit(opNode, component); })
48  .Default([&](auto) {
49  op->emitError() << "Operation '" << op->getName()
50  << "' not supported for control compilation";
51  });
52  }
53 
54 private:
55  void visit(SeqOp seqOp, ComponentOp &component);
56  void visit(EnableOp, ComponentOp &) {
57  // nothing to do
58  }
59 
60  AnalysisManager am;
61 };
62 
63 /// Generates a latency-insensitive FSM to realize a sequential operation.
64 /// This is done by initializing GroupGoOp values for the enabled groups in
65 /// the SeqOp, and then creating a new Seq GroupOp with the given FSM. Each
66 /// step in the FSM is guarded by the done operation of the group currently
67 /// being executed. After the group is complete, the FSM is incremented. This
68 /// SeqOp is then replaced in the control with an Enable statement referring
69 /// to the new Seq GroupOp.
70 void CompileControlVisitor::visit(SeqOp seq, ComponentOp &component) {
71  auto wires = component.getWiresOp();
72  Block *wiresBody = wires.getBodyBlock();
73 
74  auto &seqOps = seq.getBodyBlock()->getOperations();
75  if (!llvm::all_of(seqOps, [](auto &&op) { return isa<EnableOp>(op); })) {
76  seq.emitOpError("should only contain EnableOps in this pass.");
77  return;
78  }
79 
80  // This should be the number of enable statements + 1 since this is the
81  // maximum value the FSM register will reach.
82  size_t fsmBitWidth = getNecessaryBitWidth(seqOps.size() + 1);
83 
84  OpBuilder builder(component->getRegion(0));
85  auto fsmRegister =
86  createRegister(seq.getLoc(), builder, component, fsmBitWidth, "fsm");
87  Value fsmIn = fsmRegister.getIn();
88  Value fsmWriteEn = fsmRegister.getWriteEn();
89  Value fsmOut = fsmRegister.getOut();
90 
91  builder.setInsertionPointToStart(wiresBody);
92  auto oneConstant = createConstant(wires.getLoc(), builder, component, 1, 1);
93 
94  // Create the new compilation group to replace this SeqOp.
95  builder.setInsertionPointToEnd(wiresBody);
96  auto seqGroup =
97  builder.create<GroupOp>(wires->getLoc(), builder.getStringAttr("seq"));
98 
99  // Guarantees a unique SymbolName for the group.
100  auto &symTable = am.getChildAnalysis<SymbolTable>(wires);
101  symTable.insert(seqGroup);
102 
103  size_t fsmIndex = 0;
104  SmallVector<Attribute, 8> compiledGroups;
105  Value fsmNextState;
106  seq.walk([&](EnableOp enable) {
107  StringRef groupName = enable.getGroupName();
108  compiledGroups.push_back(
109  SymbolRefAttr::get(builder.getContext(), groupName));
110  auto groupOp = symTable.lookup<GroupOp>(groupName);
111 
112  builder.setInsertionPoint(groupOp);
113  auto fsmCurrentState = createConstant(wires->getLoc(), builder, component,
114  fsmBitWidth, fsmIndex);
115 
116  // TODO(Calyx): Eventually, we should canonicalize the GroupDoneOp's guard
117  // and source.
118  auto guard = groupOp.getDoneOp().getGuard();
119  Value source = groupOp.getDoneOp().getSrc();
120  auto doneOpValue = !guard ? source
121  : builder.create<comb::AndOp>(
122  wires->getLoc(), guard, source, false);
123 
124  // Build the Guard for the `go` signal of the current group being walked.
125  // The group should begin when:
126  // (1) the current step in the fsm is reached, and
127  // (2) the done signal of this group is not high.
128  auto eqCmp =
129  builder.create<comb::ICmpOp>(wires->getLoc(), comb::ICmpPredicate::eq,
130  fsmOut, fsmCurrentState, false);
131  auto notDone = comb::createOrFoldNot(wires->getLoc(), doneOpValue, builder);
132  auto groupGoGuard =
133  builder.create<comb::AndOp>(wires->getLoc(), eqCmp, notDone, false);
134 
135  // Guard for the `in` and `write_en` signal of the fsm register. These are
136  // driven when the group has completed.
137  builder.setInsertionPoint(seqGroup);
138  auto groupDoneGuard =
139  builder.create<comb::AndOp>(wires->getLoc(), eqCmp, doneOpValue, false);
140 
141  // Directly update the GroupGoOp of the current group being walked.
142  auto goOp = groupOp.getGoOp();
143  assert(goOp && "The Go Insertion pass should be run before this.");
144  goOp->setOperands({oneConstant, groupGoGuard});
145 
146  // Add guarded assignments to the fsm register `in` and `write_en` ports.
147  fsmNextState = createConstant(wires->getLoc(), builder, component,
148  fsmBitWidth, fsmIndex + 1);
149  builder.setInsertionPointToEnd(seqGroup.getBodyBlock());
150  builder.create<AssignOp>(wires->getLoc(), fsmIn, fsmNextState,
151  groupDoneGuard);
152  builder.create<AssignOp>(wires->getLoc(), fsmWriteEn, oneConstant,
153  groupDoneGuard);
154  // Increment the fsm index for the next group.
155  ++fsmIndex;
156  });
157 
158  // Build the final guard for the new Seq group's GroupDoneOp. This is
159  // defined by the fsm's final state.
160  builder.setInsertionPoint(seqGroup);
161  auto isFinalState = builder.create<comb::ICmpOp>(
162  wires->getLoc(), comb::ICmpPredicate::eq, fsmOut, fsmNextState, false);
163 
164  // Insert the respective GroupDoneOp.
165  builder.setInsertionPointToEnd(seqGroup.getBodyBlock());
166  builder.create<GroupDoneOp>(seqGroup->getLoc(), oneConstant, isFinalState);
167 
168  // Add continuous wires to reset the `in` and `write_en` ports of the fsm
169  // when the SeqGroup is finished executing.
170  builder.setInsertionPointToEnd(wiresBody);
171  auto zeroConstant =
172  createConstant(wires->getLoc(), builder, component, fsmBitWidth, 0);
173  builder.create<AssignOp>(wires->getLoc(), fsmIn, zeroConstant, isFinalState);
174  builder.create<AssignOp>(wires->getLoc(), fsmWriteEn, oneConstant,
175  isFinalState);
176 
177  // Replace the SeqOp with an EnableOp.
178  builder.setInsertionPoint(seq);
179  builder.create<EnableOp>(
180  seq->getLoc(), seqGroup.getSymName(),
181  ArrayAttr::get(builder.getContext(), compiledGroups));
182 
183  seq->erase();
184 }
185 
186 namespace {
187 
188 struct CompileControlPass
189  : public circt::calyx::impl::CompileControlBase<CompileControlPass> {
190  void runOnOperation() override;
191 };
192 
193 } // end anonymous namespace
194 
195 void CompileControlPass::runOnOperation() {
196  ComponentOp component = getOperation();
197  CompileControlVisitor compileControlVisitor(getAnalysisManager());
198  component.getControlOp().walk(
199  [&](Operation *op) { compileControlVisitor.dispatch(op, component); });
200 
201  // A post-condition of this pass is that all undefined GroupGoOps, created
202  // in the Go Insertion pass, are now defined.
203  component.getWiresOp().walk([&](UndefinedOp op) { op->erase(); });
204 }
205 
206 std::unique_ptr<mlir::Pass> circt::calyx::createCompileControlPass() {
207  return std::make_unique<CompileControlPass>();
208 }
assert(baseType &&"element must be base type")
static size_t getNecessaryBitWidth(size_t numStates)
Given some number of states, returns the necessary bit width TODO(Calyx): Probably a better built-in ...
void visit(EnableOp, ComponentOp &)
CompileControlVisitor(AnalysisManager am)
void visit(SeqOp seqOp, ComponentOp &component)
Generates a latency-insensitive FSM to realize a sequential operation.
void dispatch(Operation *op, ComponentOp component)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
std::unique_ptr< mlir::Pass > createCompileControlPass()
hw::ConstantOp createConstant(Location loc, OpBuilder &builder, ComponentOp component, size_t width, size_t value)
A helper function to create constants in the HW dialect.
calyx::RegisterOp createRegister(Location loc, OpBuilder &builder, ComponentOp component, size_t width, Twine prefix)
Creates a RegisterOp, with input and output port bit widths defined by width.
Value createOrFoldNot(Location loc, Value value, OpBuilder &builder, bool twoState=false)
Create a `‘Not’' gate on a value.
Definition: CombOps.cpp:48
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
Definition: seq.py:1