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