forked from Xilinx/mlir-aie
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAIEDialect.cpp
More file actions
3167 lines (2788 loc) · 112 KB
/
Copy pathAIEDialect.cpp
File metadata and controls
3167 lines (2788 loc) · 112 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//===- AIEDialect.cpp -------------------------------------------*- C++ -*-===//
//
// Copyright (C) 2019-2022 Xilinx, Inc.
// Copyright (C) 2022-2026 Advanced Micro Devices, Inc.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "aie/Dialect/AIE/IR/AIEDialect.h"
#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/IR/DialectImplementation.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/OpDefinition.h"
#include "mlir/IR/SymbolTable.h"
#include "mlir/Interfaces/FoldInterfaces.h"
#include "mlir/Transforms/InliningUtils.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/TypeSwitch.h"
using namespace mlir;
using namespace xilinx::AIE;
// Add TableGen'erated dialect definitions (including constructor)
// We implement the initialize() function further below
#include "aie/Dialect/AIE/IR/AIEDialect.cpp.inc"
#define GET_TYPEDEF_CLASSES
#include "aie/Dialect/AIE/IR/AIETypes.cpp.inc"
namespace {
struct AIEInlinerInterface : DialectInlinerInterface {
using DialectInlinerInterface::DialectInlinerInterface;
// We don't have any special restrictions on what can be inlined into
// destination regions. Always allow it.
bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
IRMapping &valueMapping) const final override {
return true;
}
// Operations in aie dialect are always legal to inline since they are
// pure.
bool isLegalToInline(Operation *op, Region *, bool wouldBeCloned,
IRMapping &) const final override {
return true;
}
// Handle the given inlined terminator by replacing it with a new operation
// as necessary. Required when the inlined region has more than one block.
void handleTerminator(Operation *op, Block *newDest) const final override {}
// Handle the given inlined terminator by replacing it with a new operation
// as necessary. Required when the region has only one block.
void handleTerminator(Operation *op,
ValueRange valuesToRepl) const final override {}
};
struct AIEDialectFoldInterface : DialectFoldInterface {
using DialectFoldInterface::DialectFoldInterface;
/// Registered hook to check if the given region, which is attached to an
/// operation that is *not* isolated from above, should be used when
/// materializing constants.
bool shouldMaterializeInto(Region *region) const final override {
// Materialize constants into the op that "owns" them rather than letting
// them hoist up to the enclosing IsolatedFromAbove aie.device:
// - aie.core bodies are outlined into standalone funcs, so their
// constants must stay local for the func to be self-contained.
// - aie.runtime_sequence bodies carry constant operands (e.g. the scalar
// fields of npu.* ops). Hoisting them to the device body lets CSE merge
// them with a core's identical constants, which would leave the core
// referencing a device-level value that is erased when the core is
// outlined.
// - Make sure SSA values for aie.use_lock operands in
// aie.mem/aie.memtile_dma/aie.shim_dma bodies do not get
// hoisted.
return isa<CoreOp, RuntimeSequenceOp, MemOp, MemTileDMAOp, ShimDMAOp>(
region->getParentOp());
}
};
} // end anonymous namespace
void AIEDialect::initialize() {
addTypes<
#define GET_TYPEDEF_LIST
#include "aie/Dialect/AIE/IR/AIETypes.cpp.inc"
>();
addAttributes<
#define GET_ATTRDEF_LIST
#include "aie/Dialect/AIE/IR/AIEAttrs.cpp.inc"
>();
addOperations<
#define GET_OP_LIST
#include "aie/Dialect/AIE/IR/AIEOps.cpp.inc"
>();
addInterfaces<AIEInlinerInterface, AIEDialectFoldInterface>();
}
// Helper methods to retrieve the encoding associated to a burst length,
// or to find the highest available burst length if the requested one is 0
// (default value).
static std::pair<uint32_t, uint32_t>
getShimBurstLength(const xilinx::AIE::AIETargetModel &tm,
uint32_t burstLength) {
std::vector<std::pair<uint32_t, uint32_t>> bel =
tm.getShimBurstEncodingsAndLengths();
// If we have the default burst length (no burst length was specified),
// use the highest one available on our target model
if (burstLength == 0) {
return *std::max_element(
bel.begin(), bel.end(),
[](auto pair1, auto pair2) { return pair1.second < pair2.second; });
}
// Note that if we are given a burst size, we are checking its existence in
// the pass verification already, so we can safely assume it exists.
return *std::find_if(bel.begin(), bel.end(),
[=](auto p) { return p.second == burstLength; });
}
uint32_t xilinx::AIE::getShimBurstLengthBytes(const AIE::AIETargetModel &tm,
uint32_t burstLength) {
return getShimBurstLength(tm, burstLength).second;
}
uint32_t xilinx::AIE::getShimBurstLengthEncoding(const AIE::AIETargetModel &tm,
uint32_t burstLength) {
return getShimBurstLength(tm, burstLength).first;
}
std::string xilinx::AIE::generateUniqueSymbolName(
mlir::Operation *symbolTableOp, llvm::StringRef prefix, unsigned &counter) {
std::string name;
do {
name = (prefix + llvm::Twine(counter++)).str();
} while (mlir::SymbolTable::lookupSymbolIn(symbolTableOp, name));
return name;
}
LogicalResult
xilinx::AIE::myVerifyOffsetSizeAndStrideOp(OffsetSizeAndStrideOpInterface op) {
std::array<unsigned, 3> maxRanks = op.getArrayAttrMaxRanks();
if (!(op.getMixedOffsets().size() == 1 && maxRanks[0] == 1) && // NOLINT
op.getMixedOffsets().size() != op.getMixedSizes().size())
return op->emitError(
"expected mixed offsets rank to match mixed sizes rank (")
<< op.getMixedOffsets().size() << " vs " << op.getMixedSizes().size()
<< ") so the rank of the result type is well-formed.";
if (failed(verifyListOfOperandsOrIntegers(
op, "offset", maxRanks[0], op.getStaticOffsets(), op.getOffsets())))
return failure();
if (failed(verifyListOfOperandsOrIntegers(
op, "size", maxRanks[1], op.getStaticSizes(), op.getSizes())))
return failure();
if (failed(verifyListOfOperandsOrIntegers(
op, "stride", maxRanks[2], op.getStaticStrides(), op.getStrides())))
return failure();
for (int64_t offset : op.getStaticOffsets())
if (offset < 0 && !ShapedType::isDynamic(offset))
return op->emitError("expected offsets to be non-negative, but got ")
<< offset;
for (int64_t size : op.getStaticSizes())
if (size < 0 && !ShapedType::isDynamic(size))
return op->emitError("expected sizes to be non-negative, but got ")
<< size;
return success();
}
static VC1902TargetModel VC1902model;
static VE2302TargetModel VE2302model;
static VE2802TargetModel VE2802model;
static VirtualizedNPU1TargetModel NPUmodel1col(1);
static VirtualizedNPU1TargetModel NPUmodel2col(2);
static VirtualizedNPU1TargetModel NPUmodel3col(3);
static VirtualizedNPU1TargetModel NPUmodel4col(4);
static NPU2TargetModel NPU2model;
static VirtualizedNPU2TargetModel NPU2model1col(1);
static VirtualizedNPU2TargetModel NPU2model2col(2);
static VirtualizedNPU2TargetModel NPU2model3col(3);
static VirtualizedNPU2TargetModel NPU2model4col(4);
static VirtualizedNPU2TargetModel NPU2model5col(5);
static VirtualizedNPU2TargetModel NPU2model6col(6);
static VirtualizedNPU2TargetModel NPU2model7col(7);
const AIETargetModel &xilinx::AIE::getTargetModel(Operation *op) {
if (auto t = dyn_cast<AIETarget>(op))
return t.getTargetModel();
if (auto t = op->getParentOfType<AIETarget>())
return t.getTargetModel();
// For backward compatibility, return a basic device model compatible with
// the VCK190
return VC1902model;
}
const AIETargetModel &xilinx::AIE::getTargetModel(AIEDevice device) {
switch (device) {
case AIEDevice::xcvc1902:
return VC1902model;
case AIEDevice::xcve2302:
return VE2302model;
case AIEDevice::xcve2802:
return VE2802model;
case AIEDevice::npu1:
return NPUmodel4col;
case AIEDevice::npu1_1col:
return NPUmodel1col;
case AIEDevice::npu1_2col:
return NPUmodel2col;
case AIEDevice::npu1_3col:
return NPUmodel3col;
case AIEDevice::npu2:
return NPU2model;
case AIEDevice::npu2_1col:
return NPU2model1col;
case AIEDevice::npu2_2col:
return NPU2model2col;
case AIEDevice::npu2_3col:
return NPU2model3col;
case AIEDevice::npu2_4col:
return NPU2model4col;
case AIEDevice::npu2_5col:
return NPU2model5col;
case AIEDevice::npu2_6col:
return NPU2model6col;
case AIEDevice::npu2_7col:
return NPU2model7col;
}
return VC1902model;
}
// Walk the operation hierarchy until we find a containing TileElement.
// If no parent is a TileElement, then return null.
static TileElement getParentTileElement(Operation *op) {
auto *parent = op->getParentOp();
while (!llvm::isa_and_nonnull<DeviceOp, ModuleOp>(parent)) {
if (auto element = llvm::dyn_cast<TileElement>(parent))
return element;
parent = parent->getParentOp();
}
return llvm::dyn_cast<TileElement>(parent);
}
// Returns the maximum index described by the input dimensions.
static int64_t getDimsMaxIdx(ArrayRef<BDDimLayoutAttr> dims) {
int64_t maxIdx = 0;
for (BDDimLayoutAttr dim : dims) {
maxIdx += dim.getStride() * (dim.getSize() - 1);
}
return maxIdx;
}
namespace {
struct UsesAreAccessible {
static LogicalResult verifyTrait(Operation *op) {
auto thisElement = cast<TileElement>(op);
// Skip accessibility checks for logical tiles as we cannot tell until tile
// is placed
if (!isa<TileOp>(thisElement.getTile().getDefiningOp()))
return success();
auto thisID = thisElement.getTileID();
auto users = op->getResult(0).getUsers();
const auto &targetModel = getTargetModel(op);
for (auto *user : users) {
// AIE.useLock may be used in a device to set the lock's default value
// Allow in a toplevel module for backward compatibility
if (llvm::isa_and_nonnull<DeviceOp, ModuleOp>(user->getParentOp())) {
continue;
}
// If any parent or the user itself prescribe that accessibility checks be
// skipped, skip the check for that user.
if (user->getParentWithTrait<SkipAccessibilityCheckTrait>() ||
user->hasTrait<SkipAccessibilityCheckTrait>()) {
continue;
}
TileElement element = llvm::dyn_cast<TileElement>(user);
if (!element) {
element = getParentTileElement(user);
}
if (!element) {
// This should probably be caught elsewhere as well.
return op->emitOpError("is accessed outside of a tile")
.attachNote(user->getLoc())
<< "user";
}
auto tileID = element.getTileID();
if (!targetModel.isLegalMemAffinity(tileID.col, tileID.row, thisID.col,
thisID.row)) {
return (op->emitOpError("in Column ")
<< thisID.col << " and Row " << thisID.row
<< " is accessed from an unreachable tile in Column "
<< tileID.col << " and Row " << tileID.row)
.attachNote(user->getLoc())
<< "user";
}
}
return success();
}
};
} // namespace
// Check that the operation only contains terminators in
// TerminatorOpTypes.
template <typename... TerminatorOpTypes>
struct HasSomeTerminator {
static LogicalResult verifyTrait(Operation *op) {
for (auto ®ion : op->getRegions()) {
for (auto &block : region) {
if (!block.empty()) {
if (Operation *operation = &block.back();
!llvm::isa_and_nonnull<TerminatorOpTypes...>(operation))
return operation->emitOpError("is not an allowed terminator")
.attachNote(op->getLoc())
.append("in this context: ");
}
}
}
return success();
}
};
// Check that the given DMA-like op (e.g. MemOp, ShimDMAOp)
// has valid BDs.
template <typename ConcreteType>
LogicalResult HasValidBDs<ConcreteType>::verifyTrait(Operation *op) {
auto element = cast<ConcreteType>(op);
const auto &targetModel = getTargetModel(op);
TileLike tile = element.getTileLike();
if (!tile)
return op->emitOpError("tile must implement TileLike interface");
int bdMax = targetModel.getNumBDs(tile.getTileType());
int bdNum = 0;
for (auto &block : element.getBody()) {
auto bdOps = llvm::to_vector_of<DMABDOp>(block.template getOps<DMABDOp>());
// Skip entry/end block
if (bdOps.empty())
continue;
// Check BD count limit
if (bdNum >= bdMax) {
return (op->emitOpError("has more than ") << bdMax << " blocks")
.attachNote(bdOps.front().getLoc())
.append("no space for this BD");
}
bdNum++;
// Check exactly 1 DMABDOp per BD block
if (bdOps.size() != 1) {
return (op->emitOpError("BD block must have exactly one DMABDOp, found ")
<< bdOps.size())
.attachNote(block.front().getLoc())
.append("in this BD block");
}
// Check at most 2 UseLockOps per BD block (1 acquire, 1 release)
auto useLockOps =
llvm::to_vector_of<UseLockOp>(block.template getOps<UseLockOp>());
int acquireCount = 0;
int releaseCount = 0;
for (auto useLock : useLockOps) {
if (useLock.acquire() || useLock.acquireGE())
acquireCount++;
else if (useLock.release())
releaseCount++;
}
if (acquireCount > 1) {
return (op->emitOpError(
"BD block must have at most one acquire UseLockOp, found ")
<< acquireCount)
.attachNote(block.front().getLoc())
.append("in this BD block");
}
if (releaseCount > 1) {
return (op->emitOpError(
"BD block must have at most one release UseLockOp, found ")
<< releaseCount)
.attachNote(block.front().getLoc())
.append("in this BD block");
}
}
return success();
}
// Check that the given DMA-like op (e.g. MemOp, ShimDMAOp)
// has valid DMA channels.
template <typename ConcreteType>
LogicalResult HasValidDMAChannels<ConcreteType>::verifyTrait(Operation *op) {
DenseSet<DMAChannel> inputChannels;
DenseSet<DMAChannel> outputChannels;
auto element = cast<ConcreteType>(op);
Region &body = element.getBody();
if (body.empty())
return op->emitOpError("should have non-empty body");
for (auto &bodyOp : body.getOps()) {
// check for duplicate DMA channels within the same ShimDMAOp
if (auto dmaStart = dyn_cast<DMAStartOp>(bodyOp)) {
DMAChannel dmaChan = {dmaStart.getChannelDir(),
dmaStart.getChannelIndex()};
// check if number of input and output channels is more than available
// hardware
if (dmaChan.direction == DMAChannelDir::S2MM)
inputChannels.insert(dmaChan);
else
outputChannels.insert(dmaChan);
}
}
TileLike tile = element.getTileLike();
if (!tile)
return op->emitOpError("tile must implement TileLike interface");
if (inputChannels.size() > tile.getNumSourceConnections(WireBundle::DMA))
return op->emitOpError(
"uses more input channels than available on this tile");
if (outputChannels.size() > tile.getNumDestConnections(WireBundle::DMA))
return op->emitOpError(
"uses more output channels than available on this tile");
return success();
}
//===----------------------------------------------------------------------===//
// ObjectFifoCreateOp
//===----------------------------------------------------------------------===//
LogicalResult ObjectFifoCreateOp::verify() {
if (isa<ArrayAttr>(getElemNumber())) {
if (size_t numDepths = dyn_cast<ArrayAttr>(getElemNumber()).size();
numDepths != getConsumerTiles().size() + 1) // +1 for producer depth
return emitOpError("does not have enough depths specified for producer "
"and for each consumer.");
}
// Helper to get tile interface from Value
auto getTileLikeFromValue = [](Value v) -> TileLike {
return llvm::dyn_cast<TileLike>(v.getDefiningOp());
};
TileLike producerTile = getTileLikeFromValue(getProducerTile());
if (!producerTile)
return emitError("producer tile must implement TileLike interface");
// data layout transformations on shim tiles are handled by runtime operations
if (producerTile.isShimTile() && !getDimensionsToStream().empty()) {
return emitError(
"`dimensionsToStream` data layout transformations are not supported "
"on shim tile producers");
}
for (auto consTileVal : getConsumerTiles()) {
TileLike consTile = getTileLikeFromValue(consTileVal);
if (!consTile)
return emitError("consumer tile must implement TileLike interface");
if (consTile.isShimTile() &&
!getDimensionsFromStream(consTileVal).empty()) {
return emitError(
"`dimensionsFromStreamPerConsumer` data layout transformations are "
"not supported on shim tile consumers");
}
}
if (getRepeatCount().has_value()) {
if (producerTile.isShimTile())
return emitError("`repeat_count` unavailable for shim tiles");
}
if (getAieStreamPort().has_value()) {
if (!getAieStream().has_value())
return emitError("`aie_stream` must be defined");
}
if (getAieStream().has_value()) {
if (getConsumerTiles().size() > 1)
return emitError("`aie_stream` can only be used in 1-to-1 object FIFOs");
if (!getAieStreamPort().has_value())
return emitError("`aie_stream_port` must be defined");
if (getAieStream().value() == 0 || getAieStream().value() == 2) {
if (producerTile.isShimTile() || producerTile.isMemTile())
return emitError(
"`aie_stream` is not available for shim and mem tiles");
if (getRepeatCount().has_value())
return emitError("`repeat_count` unavailable on stream end");
if (getInitValues().has_value())
return emitError("`init_values` unavailable on stream end");
if (getIterCount().has_value())
return emitError("`iter_count` unavailable on stream end");
if (!getDimensionsToStream().empty())
return emitError("`dimensionsToStream` data layout transformations are "
"unavailable on stream end");
}
if (getAieStream().value() == 1 || getAieStream().value() == 2) {
TileLike consTile = getTileLikeFromValue(getConsumerTiles()[0]);
if (consTile && (consTile.isShimTile() || consTile.isMemTile()))
return emitError(
"`aie_stream` is not available for shim and mem tiles");
}
if (!getDimensionsFromStreamPerConsumer()[0].empty())
return emitError("`dimensionsFromStreamPerConsumer` data layout "
"transformations are unavailable on stream end");
}
if (getInitValues().has_value()) {
if (producerTile.isShimTile())
return emitError("`init_values` unavailable for shim tiles");
}
if (getInitValues().has_value()) {
if ((int)getInitValues().value().size() != size())
return emitError("`init_values` does not initialize all objects");
}
if (getIterCount().has_value()) {
int iterCount = getIterCount().value();
if (iterCount < 1 || iterCount > 256)
return emitError("`iter_count` must be between 1 and 256");
// Check that either producer or at least one consumer is a MemTile
bool hasMemTile = producerTile.isMemTile();
if (!hasMemTile) {
for (auto consTileVal : getConsumerTiles()) {
TileLike consTile = getTileLikeFromValue(consTileVal);
if (consTile && consTile.isMemTile()) {
hasMemTile = true;
break;
}
}
}
if (!hasMemTile)
return emitError("`iter_count` is currently only supported on MemTiles");
}
if (getConsumerElemType().has_value()) {
auto consType =
llvm::dyn_cast<AIEObjectFifoType>(getConsumerElemType().value());
if (!consType)
return emitError("consumer element type must be an "
"!aie.objectfifo<memref<...>> type");
auto prodType = llvm::cast<AIEObjectFifoType>(getElemType());
auto prodMemref = prodType.getElementType();
auto consMemref = consType.getElementType();
if (prodMemref.getElementType() != consMemref.getElementType())
return emitError("producer and consumer must have the same scalar "
"element type, but got ")
<< prodMemref.getElementType() << " vs "
<< consMemref.getElementType();
int64_t prodSize = prodMemref.getNumElements();
int64_t consSize = consMemref.getNumElements();
if (consSize <= 0)
return emitError("consumer element count must be positive");
if (prodSize % consSize != 0)
return emitError("producer element size (")
<< prodSize << ") must be an integer multiple of consumer "
<< "element size (" << consSize << ")";
}
return success();
}
TileOp ObjectFifoCreateOp::getProducerTileOp() {
return cast<TileOp>(getProducerTile().getDefiningOp());
}
BDDimLayoutArrayAttr
ObjectFifoCreateOp::getDimensionsFromStream(Value consumerTile) {
int dimsIndex = 0;
for (auto cons : getConsumerTiles()) {
if (cons == consumerTile)
break;
else
dimsIndex++;
}
return getDimensionsFromStreamPerConsumer()[dimsIndex];
}
ParseResult xilinx::AIE::parseObjectFifoProducerTile(
OpAsmParser &parser, OpAsmParser::UnresolvedOperand &operand,
BDDimLayoutArrayAttr &dimensions) {
std::vector<BDDimLayoutAttr> emptyDims = {};
if (parser.parseOperand(operand))
return failure();
if (succeeded(parser.parseOptionalKeyword("dimensionsToStream"))) {
if (parser.parseCustomAttributeWithFallback<BDDimLayoutArrayAttr>(
dimensions)) {
return failure();
}
} else {
dimensions =
BDDimLayoutArrayAttr::get(parser.getContext(), ArrayRef(emptyDims));
}
return success();
}
void xilinx::AIE::printObjectFifoProducerTile(OpAsmPrinter &printer,
Operation *op, Value operand,
BDDimLayoutArrayAttr dimensions) {
printer << operand;
if (!dimensions.empty()) {
printer << " dimensionsToStream ";
printer.printStrippedAttrOrType(dimensions);
}
}
ParseResult xilinx::AIE::parseObjectFifoConsumerTiles(
OpAsmParser &parser, SmallVectorImpl<OpAsmParser::UnresolvedOperand> &tiles,
BDDimLayoutArrayArrayAttr &dimensions) {
// parseCommaSeparatedList doesn't handle the missing case for "none",
// so we handle it custom here.
std::vector<BDDimLayoutArrayAttr> tileDims = {};
auto parseOneOperand = [&]() -> ParseResult {
if (parser.parseOperand(tiles.emplace_back(), true)) {
return failure();
}
// By default, create empty dimensions array for each consumer; this way,
// we can be certain to have as many entries in the dimensions array as
// there are customer
BDDimLayoutArrayAttr dimAttr =
BDDimLayoutArrayAttr::get(parser.getContext(), {});
if (succeeded(parser.parseOptionalKeyword("dimensionsFromStream"))) {
// If specified, parse actual data layout transform dimensions
if (parser.parseCustomAttributeWithFallback<BDDimLayoutArrayAttr>(
dimAttr)) {
return failure();
}
}
tileDims.emplace_back(dimAttr);
return success();
};
if (parser.parseCommaSeparatedList(AsmParser::Delimiter::None,
parseOneOperand, " in operand list"))
return failure();
dimensions = BDDimLayoutArrayArrayAttr::get(parser.getContext(), tileDims);
return success();
}
void xilinx::AIE::printObjectFifoConsumerTiles(
OpAsmPrinter &printer, Operation *op, OperandRange tiles,
BDDimLayoutArrayArrayAttr dimsPerTileAttr) {
size_t tileIdx = 0;
for (auto tile : tiles) {
printer << tile;
if (dimsPerTileAttr && tileIdx < dimsPerTileAttr.size() &&
dimsPerTileAttr[tileIdx] && !dimsPerTileAttr[tileIdx].empty()) {
printer << " dimensionsFromStream ";
printer.printStrippedAttrOrType(dimsPerTileAttr[tileIdx]);
}
if (tileIdx < tiles.size() - 1) {
printer << ", ";
}
tileIdx++;
}
}
static void printObjectFifoConsumerElemType(OpAsmPrinter &p,
ObjectFifoCreateOp op,
TypeAttr consumerElemType) {
if (consumerElemType)
p << " -> " << consumerElemType;
}
static ParseResult parseObjectFifoConsumerElemType(OpAsmParser &parser,
TypeAttr &consumerElemType) {
if (failed(parser.parseOptionalArrow()))
return success(); // no consumer type
Type type;
if (parser.parseType(type))
return failure();
consumerElemType = TypeAttr::get(type);
return success();
}
static void printObjectFifoInitValues(OpAsmPrinter &p, ObjectFifoCreateOp op,
Attribute numElem, TypeAttr type,
Attribute initValues) {
if (op.getInitValues()) {
p << "= [";
// `numElem` may be an IntegerAttr (scalar depth) or an ArrayAttr of
// per-endpoint depths; initValues populates the producer side, which
// is the first entry of the ArrayAttr.
int depth;
if (isa<ArrayAttr>(numElem)) {
depth = llvm::dyn_cast<mlir::IntegerAttr>(
llvm::dyn_cast<mlir::ArrayAttr>(numElem)[0])
.getInt();
} else {
depth = llvm::dyn_cast<mlir::IntegerAttr>(numElem).getInt();
}
for (int i = 0; i < depth; i++) {
p.printStrippedAttrOrType(llvm::dyn_cast<mlir::ArrayAttr>(initValues)[i]);
if (i < depth - 1) {
p << ", ";
}
}
p << "]";
}
}
static ParseResult parseObjectFifoInitValues(OpAsmParser &parser,
Attribute numElem, TypeAttr type,
Attribute &initValues) {
int depth;
if (isa<ArrayAttr>(numElem)) {
depth = llvm::dyn_cast<mlir::IntegerAttr>(
llvm::dyn_cast<mlir::ArrayAttr>(numElem)[0])
.getInt();
} else {
depth = llvm::dyn_cast<mlir::IntegerAttr>(numElem).getInt();
}
auto objfifoType = llvm::cast<AIEObjectFifoType>(type.getValue());
auto memrefType = llvm::cast<MemRefType>(objfifoType.getElementType());
if (!memrefType.hasStaticShape())
return parser.emitError(parser.getNameLoc())
<< "type should be static shaped memref, but got " << memrefType;
if (parser.parseOptionalEqual())
return success();
Type tensorType = mlir::memref::getTensorTypeFromMemRefType(memrefType);
if (parser.parseAttribute(initValues, tensorType))
return failure();
for (int i = 0; i < depth; i++) {
auto initialValues = llvm::dyn_cast<mlir::ArrayAttr>(initValues);
if ((int)initialValues.size() != depth)
return parser.emitError(parser.getNameLoc())
<< "initial values should initialize all objects";
if (!llvm::isa<ElementsAttr>(initialValues[i]))
return parser.emitError(parser.getNameLoc())
<< "initial value should be an elements attribute";
}
return success();
}
//===----------------------------------------------------------------------===//
// ObjectFifoAllocateOp
//===----------------------------------------------------------------------===//
LogicalResult ObjectFifoAllocateOp::verify() {
ObjectFifoCreateOp objFifo = getObjectFifo();
if (!objFifo)
return emitError("cannot retrieve associated object FIFO");
if (objFifo.getConsumerTiles().size() != 1)
return emitError("can only be used in 1-to-1 object FIFOs");
if (objFifo.getVia_DMA())
return emitError("cannot allocate a shared memory module to objectfifo "
"with set `via_DMA` attribute");
if (objFifo.getRepeatCount().has_value())
return emitError("cannot allocate a shared memory module to objectfifo "
"with set `repeat_count` attribute");
if (!objFifo.getDimensionsToStream().empty())
return emitError("cannot allocate a shared memory module to objectfifo "
"with set dimensions attribute");
if (objFifo.getAieStream().has_value())
return emitError("cannot allocate a shared memory module to objectfifo "
"using stream port");
return success();
}
TileOp ObjectFifoAllocateOp::getDelegateTileOp() {
return cast<TileOp>(getDelegateTile().getDefiningOp());
}
ObjectFifoCreateOp ObjectFifoAllocateOp::getObjectFifo() {
Operation *parent = getOperation();
while ((parent = parent->getParentOp())) {
if (parent->hasTrait<OpTrait::SymbolTable>()) {
if (auto *st = SymbolTable::lookupSymbolIn(parent, getObjFifoName());
isa_and_nonnull<ObjectFifoCreateOp>(st))
return dyn_cast<ObjectFifoCreateOp>(st);
}
}
return {};
}
//===----------------------------------------------------------------------===//
// ObjectFifoLinkOp
//===----------------------------------------------------------------------===//
LogicalResult ObjectFifoLinkOp::verify() {
if (isJoin() && isDistribute())
return emitError("ObjectFifoLinkOp does not support 'join' and "
"'distribute' at the same time");
auto sharedTile = getOptionalSharedTile();
if (!sharedTile)
return emitError("ObjectFifoLinkOp must have a link point, i.e., a "
"shared tile between objectFifos");
TileLike tile = llvm::dyn_cast<TileLike>(sharedTile.value().getDefiningOp());
if (!tile)
return emitError("shared tile must implement TileLike interface");
if (!tile.isMemTile()) {
if (isJoin() || isDistribute())
return emitError("ObjectFifoLinkOp join and distribute are "
"unavailable on compute or shim tiles");
}
if (isJoin()) {
if (getFifoIns().size() != getSrcOffsets().size())
return emitOpError("number of provided src offsets must be equal "
"to the number of input objectFifos");
if (!getDstOffsets().empty())
return emitOpError("dst offsets should be empty for join");
ObjectFifoCreateOp fifoOut = getOutputObjectFifos()[0];
if (!fifoOut.getDimensionsToStream().empty()) {
int64_t maxIdx = getDimsMaxIdx(fifoOut.getDimensionsToStream());
int64_t minInputBufferSize = -1;
for (auto lenIn : getJoinTransferLengths()) {
if (lenIn <= minInputBufferSize || minInputBufferSize < 0)
minInputBufferSize = lenIn;
}
if (minInputBufferSize <= maxIdx) {
return emitOpError()
<< "specified output stride(s) and size(s) result in out "
"of bounds access in join input, for index "
<< std::to_string(maxIdx) << " in transfer of length "
<< std::to_string(minInputBufferSize) << ".";
}
}
} else if (isDistribute()) {
if (getFifoOuts().size() != getDstOffsets().size())
return emitOpError("number of provided dst offsets must be equal "
"to the number of output objectFifos");
if (!getSrcOffsets().empty())
return emitOpError("src offsets should be empty for distribute");
ObjectFifoCreateOp fifoIn = getInputObjectFifos()[0];
if (!fifoIn.getDimensionsFromStream(sharedTile.value()).empty()) {
int64_t maxIdx =
getDimsMaxIdx(fifoIn.getDimensionsFromStream(sharedTile.value()));
int64_t minOutputBufferSize = -1;
for (auto lenOut : getDistributeTransferLengths()) {
if (lenOut <= minOutputBufferSize || minOutputBufferSize < 0)
minOutputBufferSize = lenOut;
}
if (minOutputBufferSize <= maxIdx) {
return emitOpError()
<< "specified input stride(s) and size(s) result in out "
"of bounds access in distribute output, for index "
<< std::to_string(maxIdx) << " in transfer of length "
<< std::to_string(minOutputBufferSize) << ".";
}
}
std::vector<int> repeat_counts;
for (auto fifoOut : getOutputObjectFifos()) {
if (fifoOut.getRepeatCount().has_value()) {
repeat_counts.push_back(fifoOut.getRepeatCount().value());
} else {
repeat_counts.push_back(0);
}
}
for (auto repeat : repeat_counts)
if (repeat_counts[0] != repeat)
return emitError("repeat counts of output object FIFOs must be equal");
} else {
if (!getSrcOffsets().empty() && !getDstOffsets().empty())
return emitOpError("all offsets should be empty if there is no "
"join or distribute");
}
return success();
}
std::optional<Value> ObjectFifoLinkOp::getOptionalSharedTile() {
if (isJoin()) {
auto fifoOut = getOutputObjectFifos()[0];
for (auto fifoIn : getInputObjectFifos())
if (fifoOut.getProducerTile() != fifoIn.getConsumerTiles()[0])
return {};
return {fifoOut.getProducerTile()};
}
if (isDistribute()) {
auto fifoIn = getInputObjectFifos()[0];
for (auto fifoOut : getOutputObjectFifos())
if (fifoIn.getConsumerTiles()[0] != fifoOut.getProducerTile())
return {};
return {fifoIn.getConsumerTiles()[0]};
}
auto fifoIn = getInputObjectFifos();
if (auto fifoOut = getOutputObjectFifos();
!fifoIn.empty() && !fifoOut.empty())
for (auto consumerIn : fifoIn[0].getConsumerTiles())
if (consumerIn == fifoOut[0].getProducerTile())
return {fifoOut[0].getProducerTile()};
return {};
}
std::vector<ObjectFifoCreateOp> ObjectFifoLinkOp::getInputObjectFifos() {
std::vector<ObjectFifoCreateOp> inputObjFifos;
Operation *parent = getOperation();
while ((parent = parent->getParentOp())) {
if (parent->hasTrait<OpTrait::SymbolTable>()) {
for (auto sym : getFifoIns()) {
auto name = dyn_cast<FlatSymbolRefAttr>(sym);
if (auto *st = SymbolTable::lookupSymbolIn(parent, name);
isa_and_nonnull<ObjectFifoCreateOp>(st))
inputObjFifos.push_back(dyn_cast<ObjectFifoCreateOp>(st));
}
}
}
return inputObjFifos;
}
std::vector<ObjectFifoCreateOp> ObjectFifoLinkOp::getOutputObjectFifos() {
std::vector<ObjectFifoCreateOp> outputObjFifos;
Operation *parent = getOperation();
while ((parent = parent->getParentOp())) {
if (parent->hasTrait<OpTrait::SymbolTable>()) {
for (auto sym : getFifoOuts()) {
auto name = dyn_cast<FlatSymbolRefAttr>(sym);
if (auto *st = mlir::SymbolTable::lookupSymbolIn(parent, name);
isa_and_nonnull<ObjectFifoCreateOp>(st))
outputObjFifos.push_back(dyn_cast<ObjectFifoCreateOp>(st));
}
}
}
return outputObjFifos;
}
std::vector<int> ObjectFifoLinkOp::getJoinTransferLengths() {
std::vector<int> lengths;
if (isJoin()) {
auto fifoOut =
llvm::cast<AIEObjectFifoType>(getOutputObjectFifos()[0].getElemType());
auto elemTypeOut = llvm::cast<MemRefType>(fifoOut.getElementType());
int lenOut = elemTypeOut.getNumElements();
for (size_t i = 0; i < getFifoIns().size(); i++) {
int len = 0;
int offset = *getConstantIntValue(getSrcOffsets()[i]);
if (i == getFifoIns().size() - 1)
len = lenOut - *getConstantIntValue(getSrcOffsets()[i]);
else
len = *getConstantIntValue(getSrcOffsets()[i + 1]) - offset;
lengths.push_back(len);
}
}
return lengths;
}
std::vector<int> ObjectFifoLinkOp::getDistributeTransferLengths() {
std::vector<int> lengths;
if (isDistribute()) {
auto fifoIn =
llvm::cast<AIEObjectFifoType>(getInputObjectFifos()[0].getElemType());
auto elemTypeIn = llvm::cast<MemRefType>(fifoIn.getElementType());
int lenIn = elemTypeIn.getNumElements();
for (size_t i = 0; i < getFifoOuts().size(); i++) {
int offset = *getConstantIntValue(getDstOffsets()[i]);
int len = 0;
if (i == getFifoOuts().size() - 1)
len = lenIn - *getConstantIntValue(getDstOffsets()[i]);
else
len = *getConstantIntValue(getDstOffsets()[i + 1]) - offset;
lengths.push_back(len);
}
}
return lengths;
}
std::optional<int> ObjectFifoLinkOp::getRepeatCount() {
for (auto fifoOut : getOutputObjectFifos())