10#include "mlir/Dialect/SCF/IR/SCF.h"
11#include "mlir/Dialect/Utils/StaticValueUtils.h"
12#include "mlir/IR/Matchers.h"
13#include "mlir/Interfaces/LoopLikeInterface.h"
14#include "mlir/Interfaces/SideEffectInterfaces.h"
15#include "llvm/ADT/STLExtras.h"
16#include "llvm/Support/Debug.h"
21#define DEBUG_TYPE "aie-channel-turns"
29constexpr size_t maxPeriodEvents = 4096;
31constexpr int maxPeriods = 256;
35 std::string &reason) {
36 assert(!ends.empty() &&
"turns between no endpoints");
37 ObjectFifoCoreEndpointOp first = ends.front();
38 Value tile = first.getTile();
39 if (llvm::any_of(ends, [&](ObjectFifoCoreEndpointOp end) {
40 return end.drains() != first.drains() || end.getTile() != tile;
42 reason =
"the endpoints are not all one core's, one way";
46 for (Operation *user : tile.getUsers())
47 if (
auto candidate = dyn_cast<CoreOp>(user))
50 reason =
"no core works through them";
53 SmallVector<StringAttr> names;
54 for (ObjectFifoCoreEndpointOp end : ends)
55 names.push_back(end.getSymNameAttr());
56 return read(core, names, first.drains(), reason);
60 bool coreDrains, std::string &reason) {
61 this->coreDrains = coreDrains;
63 DenseMap<StringAttr, unsigned> index;
64 for (
auto [i, name] : llvm::enumerate(names))
68 coreDrains ? ObjectFifoPort::Consume : ObjectFifoPort::Produce;
69 auto endOf = [&](Operation *op) -> std::optional<unsigned> {
70 FlatSymbolRefAttr name;
71 std::optional<ObjectFifoPort> used;
72 if (
auto acquire = dyn_cast<ObjectFifoAcquireOp>(op)) {
73 name = acquire.getObjFifoNameAttr();
74 used = acquire.getPort();
75 }
else if (
auto release = dyn_cast<ObjectFifoReleaseOp>(op)) {
76 name = release.getObjFifoNameAttr();
77 used = release.getPort();
79 if (!name || (used && *used != port))
81 auto it = index.find(name.getAttr());
82 if (it == index.end())
87 SmallVector<Operation *> uses;
88 core.walk([&](Operation *op) {
93 reason =
"the core never acquires them";
97 auto loopsAround = [&](Operation *op) {
98 SmallVector<Operation *> loops;
99 for (Operation *parent = op->getParentOp(); parent != core;
100 parent = parent->getParentOp())
101 if (isa<LoopLikeOpInterface>(parent))
102 loops.push_back(parent);
105 SmallVector<Operation *> candidates;
106 for (Operation *candidate : loopsAround(uses.front()))
107 if (llvm::all_of(uses, [&](Operation *use) {
108 return candidate->isProperAncestor(use);
110 candidates.push_back(candidate);
111 if (candidates.empty()) {
112 reason =
"no loop repeats the core's use of them";
117 DenseMap<Value, Attribute> counters;
118 std::function<Attribute(Value)> evaluate = [&](Value value) -> Attribute {
119 Attribute attr = counters.lookup(value);
120 Operation *def = value.getDefiningOp();
121 if (attr || !def || matchPattern(value, m_Constant(&attr)))
123 if (!isPure(def) || def->getNumResults() != 1)
125 SmallVector<Attribute> operands;
126 for (Value operand : def->getOperands())
127 if (!operands.emplace_back(evaluate(operand)))
129 SmallVector<OpFoldResult> folded;
130 if (failed(def->fold(operands, folded)) || folded.size() != 1)
132 if (
auto result = dyn_cast<Attribute>(folded.front()))
134 return evaluate(cast<Value>(folded.front()));
137 std::function<LogicalResult(Block &)> unroll = [&](Block &block) {
138 for (Operation &op : block) {
139 if (std::optional<unsigned> end = endOf(&op)) {
140 if (
auto acquire = dyn_cast<ObjectFifoAcquireOp>(op))
141 period.push_back({*end, acquire.acqNumber(),
true});
144 {*end, cast<ObjectFifoReleaseOp>(op).relNumber(),
false});
145 if (period.size() > maxPeriodEvents) {
146 reason =
"the core's period uses them too many times";
152 uses, [&](Operation *use) {
return op.isProperAncestor(use); }))
154 if (
auto ifOp = dyn_cast<scf::IfOp>(op)) {
156 dyn_cast_or_null<BoolAttr>(evaluate(ifOp.getCondition()))) {
158 taken.getValue() ? ifOp.getThenRegion() : ifOp.getElseRegion())
159 if (failed(unroll(branch)))
164 auto forOp = dyn_cast<scf::ForOp>(op);
165 std::optional<APInt> trips =
166 forOp ? forOp.getStaticTripCount() : std::nullopt;
168 reason =
"the core uses them under control flow that depends on data";
171 if (trips->getSExtValue() >
static_cast<int64_t
>(maxPeriodEvents)) {
172 reason =
"the core's period uses them too many times";
175 std::optional<int64_t> lower = getConstantIntValue(forOp.getLowerBound());
176 std::optional<int64_t> step = getConstantIntValue(forOp.getStep());
177 for (int64_t trip = 0; trip < trips->getSExtValue(); trip++) {
179 counters[forOp.getInductionVar()] = IntegerAttr::get(
180 forOp.getInductionVar().getType(), *lower + trip * *step);
181 if (failed(unroll(*forOp.getBody())))
189 Operation *loop =
nullptr;
190 for (Operation *candidate : candidates) {
193 if (llvm::all_of(cast<LoopLikeOpInterface>(candidate).getLoopRegions(),
194 [&](Region *region) {
195 return llvm::all_of(*region, [&](Block &block) {
196 return succeeded(unroll(block));
207 llvm::dbgs() <<
"period of " << names.size() <<
" ends in "
208 << loop->getName() <<
" at " << loop->getLoc() <<
": "
209 << period.size() <<
" events:";
210 for (
const Event &event : period)
211 llvm::dbgs() <<
" " << (
event.acquire ?
"a" :
"r") << event.end <<
"x"
213 llvm::dbgs() <<
"\n";
220 SmallVector<int64_t> held(ends, 0);
221 std::map<SmallVector<int64_t>,
int> seen;
222 for (
int round = 0; round < maxPeriods; round++) {
224 SmallVector<int64_t> start = held;
225 for (
const Event &event : period) {
226 int64_t &h = held[
event.end];
229 order.append(std::max<int64_t>(0, event.count - h), event.end);
230 h = std::max(h, event.count);
232 if (event.count > h) {
233 reason =
"the core releases objects it does not hold";
238 order.append(event.count, event.end);
241 if (!seen.emplace(start, round).second) {
243 reason =
"the core's period moves none of their objects";
249 reason =
"the core never settles into a steady order";
254 assert(depths.size() == ends &&
"one depth per endpoint");
258 SmallVector<int64_t> counts(ends, 0);
259 for (
unsigned end : order)
261 for (
size_t length = 1; length < order.size(); length++) {
262 if (order.size() % length)
264 size_t repeats = order.size() / length;
265 if (llvm::any_of(counts, [&](int64_t c) {
return c % repeats; }))
267 std::set<SmallVector<unsigned>> tried;
273 SmallVector<int64_t> runCounts(ends, 0);
274 for (
size_t i = 0; i < length; i++)
275 runCounts[order[i]]++;
276 for (
size_t start = 0; start + length <= order.size(); start++) {
278 runCounts[order[start - 1]]--;
279 runCounts[order[start + length - 1]]++;
281 bool proportional =
true;
282 for (
size_t i = 0; i < ends; i++)
284 runCounts[i] *
static_cast<int64_t
>(repeats) == counts[i];
287 SmallVector<unsigned> run(order.begin() + start,
288 order.begin() + start + length);
289 if (!tried.insert(run).second || !
keepsUp(run, depths))
299 ArrayRef<int64_t> depths)
const {
302 SmallVector<int64_t> forCore(ends, 0), forChannel(ends, 0), held(ends, 0);
303 for (
size_t i = 0; i < ends; i++)
304 (coreDrains ? forChannel : forCore)[i] = depths[i];
305 size_t turn = 0,
event = 0;
306 std::map<SmallVector<int64_t>,
int> seen;
307 for (
int round = 0; round < maxPeriods * 4;) {
309 while (forChannel[order[turn]] > 0) {
310 forChannel[order[turn]]--;
311 forCore[order[turn]]++;
312 turn = (turn + 1) % order.size();
316 const Event &e = period[event];
318 int64_t take = std::max<int64_t>(0, e.count - held[e.end]);
319 if (forCore[e.end] < take)
321 forCore[e.end] -= take;
322 held[e.end] = std::max(held[e.end], e.count);
324 held[e.end] -= e.count;
325 forChannel[e.end] += e.count;
328 if (++event == period.size()) {
331 SmallVector<int64_t> state{
static_cast<int64_t
>(turn)};
332 llvm::append_range(state, forCore);
333 llvm::append_range(state, forChannel);
334 llvm::append_range(state, held);
335 if (!seen.emplace(state, round).second)
mlir::LogicalResult read(llvm::ArrayRef< ObjectFifoCoreEndpointOp > ends, std::string &reason)
Reads the order off the program of the core working through ends, all draining (the channel fills the...
bool keepsUp(llvm::ArrayRef< int64_t > depths)
Whether a channel moving objects in turns() order forever, through pools of depths,...