CIRCT 20.0.0git
Loading...
Searching...
No Matches
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
22namespace circt {
23namespace calyx {
24#define GEN_PASS_DEF_COMPILECONTROL
25#include "circt/Dialect/Calyx/CalyxPasses.h.inc"
26} // namespace calyx
27} // namespace circt
28
29using namespace circt;
30using namespace calyx;
31using namespace mlir;
32
33/// Given some number of states, returns the necessary bit width
34/// TODO(Calyx): Probably a better built-in operation?
35static 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
42public:
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
54private:
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.
70void 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
186namespace {
187
188struct CompileControlPass
189 : public circt::calyx::impl::CompileControlBase<CompileControlPass> {
190 void runOnOperation() override;
191};
192
193} // end anonymous namespace
194
195void 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
206std::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)
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.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition seq.py:1