-
Notifications
You must be signed in to change notification settings - Fork 315
/
Copy pathFIRRTLOps.cpp
6186 lines (5320 loc) · 229 KB
/
FIRRTLOps.cpp
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
//===- FIRRTLOps.cpp - Implement the FIRRTL operations --------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implement the FIRRTL ops.
//
//===----------------------------------------------------------------------===//
#include "circt/Dialect/FIRRTL/FIRRTLOps.h"
#include "circt/Dialect/FIRRTL/CHIRRTLDialect.h"
#include "circt/Dialect/FIRRTL/FIRRTLAnnotations.h"
#include "circt/Dialect/FIRRTL/FIRRTLAttributes.h"
#include "circt/Dialect/FIRRTL/FIRRTLInstanceImplementation.h"
#include "circt/Dialect/FIRRTL/FIRRTLTypes.h"
#include "circt/Dialect/FIRRTL/FIRRTLUtils.h"
#include "circt/Dialect/FIRRTL/FIRRTLVisitors.h"
#include "circt/Dialect/HW/HWAttributes.h"
#include "circt/Dialect/HW/HWTypes.h"
#include "circt/Support/CustomDirectiveImpl.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/DialectImplementation.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/IR/SymbolTable.h"
#include "mlir/Interfaces/FunctionImplementation.h"
#include "llvm/ADT/BitVector.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/FormatVariadic.h"
using llvm::SmallDenseSet;
using mlir::RegionRange;
using namespace circt;
using namespace firrtl;
using namespace chirrtl;
//===----------------------------------------------------------------------===//
// Utilities
//===----------------------------------------------------------------------===//
/// Remove elements from the input array corresponding to set bits in
/// `indicesToDrop`, returning the elements not mentioned.
template <typename T>
static SmallVector<T>
removeElementsAtIndices(ArrayRef<T> input,
const llvm::BitVector &indicesToDrop) {
#ifndef NDEBUG
if (!input.empty()) {
int lastIndex = indicesToDrop.find_last();
if (lastIndex >= 0)
assert((size_t)lastIndex < input.size() && "index out of range");
}
#endif
// If the input is empty (which is an optimization we do for certain array
// attributes), simply return an empty vector.
if (input.empty())
return {};
// Copy over the live chunks.
size_t lastCopied = 0;
SmallVector<T> result;
result.reserve(input.size() - indicesToDrop.count());
for (unsigned indexToDrop : indicesToDrop.set_bits()) {
// If we skipped over some valid elements, copy them over.
if (indexToDrop > lastCopied) {
result.append(input.begin() + lastCopied, input.begin() + indexToDrop);
lastCopied = indexToDrop;
}
// Ignore this value so we don't copy it in the next iteration.
++lastCopied;
}
// If there are live elements at the end, copy them over.
if (lastCopied < input.size())
result.append(input.begin() + lastCopied, input.end());
return result;
}
/// Emit an error if optional location is non-null, return null of return type.
template <typename RetTy = FIRRTLType, typename... Args>
static RetTy emitInferRetTypeError(std::optional<Location> loc,
const Twine &message, Args &&...args) {
if (loc)
(mlir::emitError(*loc, message) << ... << std::forward<Args>(args));
return {};
}
bool firrtl::isDuplexValue(Value val) {
// Block arguments are not duplex values.
while (Operation *op = val.getDefiningOp()) {
auto isDuplex =
TypeSwitch<Operation *, std::optional<bool>>(op)
.Case<SubfieldOp, SubindexOp, SubaccessOp>([&val](auto op) {
val = op.getInput();
return std::nullopt;
})
.Case<RegOp, RegResetOp, WireOp>([](auto) { return true; })
.Default([](auto) { return false; });
if (isDuplex)
return *isDuplex;
}
return false;
}
/// Return the kind of port this is given the port type from a 'mem' decl.
static MemOp::PortKind getMemPortKindFromType(FIRRTLType type) {
constexpr unsigned int addr = 1 << 0;
constexpr unsigned int en = 1 << 1;
constexpr unsigned int clk = 1 << 2;
constexpr unsigned int data = 1 << 3;
constexpr unsigned int mask = 1 << 4;
constexpr unsigned int rdata = 1 << 5;
constexpr unsigned int wdata = 1 << 6;
constexpr unsigned int wmask = 1 << 7;
constexpr unsigned int wmode = 1 << 8;
constexpr unsigned int def = 1 << 9;
// Get the kind of port based on the fields of the Bundle.
auto portType = type_dyn_cast<BundleType>(type);
if (!portType)
return MemOp::PortKind::Debug;
unsigned fields = 0;
// Get the kind of port based on the fields of the Bundle.
for (auto elem : portType.getElements()) {
fields |= llvm::StringSwitch<unsigned>(elem.name.getValue())
.Case("addr", addr)
.Case("en", en)
.Case("clk", clk)
.Case("data", data)
.Case("mask", mask)
.Case("rdata", rdata)
.Case("wdata", wdata)
.Case("wmask", wmask)
.Case("wmode", wmode)
.Default(def);
}
if (fields == (addr | en | clk | data))
return MemOp::PortKind::Read;
if (fields == (addr | en | clk | data | mask))
return MemOp::PortKind::Write;
if (fields == (addr | en | clk | wdata | wmask | rdata | wmode))
return MemOp::PortKind::ReadWrite;
return MemOp::PortKind::Debug;
}
Flow firrtl::swapFlow(Flow flow) {
switch (flow) {
case Flow::None:
return Flow::None;
case Flow::Source:
return Flow::Sink;
case Flow::Sink:
return Flow::Source;
case Flow::Duplex:
return Flow::Duplex;
}
}
constexpr const char *toString(Flow flow) {
switch (flow) {
case Flow::None:
return "no flow";
case Flow::Source:
return "source flow";
case Flow::Sink:
return "sink flow";
case Flow::Duplex:
return "duplex flow";
}
}
Flow firrtl::foldFlow(Value val, Flow accumulatedFlow) {
if (auto blockArg = dyn_cast<BlockArgument>(val)) {
auto *op = val.getParentBlock()->getParentOp();
if (auto moduleLike = dyn_cast<FModuleLike>(op)) {
auto direction = moduleLike.getPortDirection(blockArg.getArgNumber());
if (direction == Direction::Out)
return swapFlow(accumulatedFlow);
}
return accumulatedFlow;
}
Operation *op = val.getDefiningOp();
return TypeSwitch<Operation *, Flow>(op)
.Case<SubfieldOp, OpenSubfieldOp>([&](auto op) {
return foldFlow(op.getInput(), op.isFieldFlipped()
? swapFlow(accumulatedFlow)
: accumulatedFlow);
})
.Case<SubindexOp, SubaccessOp, OpenSubindexOp, RefSubOp>(
[&](auto op) { return foldFlow(op.getInput(), accumulatedFlow); })
// Registers, Wires, and behavioral memory ports are always Duplex.
.Case<RegOp, RegResetOp, WireOp, MemoryPortOp>(
[](auto) { return Flow::Duplex; })
.Case<InstanceOp, InstanceChoiceOp>([&](auto inst) {
auto resultNo = cast<OpResult>(val).getResultNumber();
if (inst.getPortDirection(resultNo) == Direction::Out)
return accumulatedFlow;
return swapFlow(accumulatedFlow);
})
.Case<MemOp>([&](auto op) {
// only debug ports with RefType have source flow.
if (type_isa<RefType>(val.getType()))
return Flow::Source;
return swapFlow(accumulatedFlow);
})
.Case<ObjectSubfieldOp>([&](ObjectSubfieldOp op) {
auto input = op.getInput();
auto *inputOp = input.getDefiningOp();
// We are directly accessing a port on a local declaration.
if (auto objectOp = dyn_cast_or_null<ObjectOp>(inputOp)) {
auto classType = input.getType();
auto direction = classType.getElement(op.getIndex()).direction;
if (direction == Direction::In)
return Flow::Sink;
return Flow::Source;
}
// We are accessing a remote object. Input ports on remote objects are
// inaccessible, and thus have Flow::None. Walk backwards through the
// chain of subindexes, to detect if we have indexed through an input
// port. At the end, either we did index through an input port, or the
// entire path was through output ports with source flow.
while (true) {
auto classType = input.getType();
auto direction = classType.getElement(op.getIndex()).direction;
if (direction == Direction::In)
return Flow::None;
op = dyn_cast_or_null<ObjectSubfieldOp>(inputOp);
if (op) {
input = op.getInput();
inputOp = input.getDefiningOp();
continue;
}
return accumulatedFlow;
};
})
// Anything else acts like a universal source.
.Default([&](auto) { return accumulatedFlow; });
}
// TODO: This is doing the same walk as foldFlow. These two functions can be
// combined and return a (flow, kind) product.
DeclKind firrtl::getDeclarationKind(Value val) {
Operation *op = val.getDefiningOp();
if (!op)
return DeclKind::Port;
return TypeSwitch<Operation *, DeclKind>(op)
.Case<InstanceOp>([](auto) { return DeclKind::Instance; })
.Case<SubfieldOp, SubindexOp, SubaccessOp, OpenSubfieldOp, OpenSubindexOp,
RefSubOp>([](auto op) { return getDeclarationKind(op.getInput()); })
.Default([](auto) { return DeclKind::Other; });
}
size_t firrtl::getNumPorts(Operation *op) {
if (auto module = dyn_cast<FModuleLike>(op))
return module.getNumPorts();
return op->getNumResults();
}
/// Check whether an operation has a `DontTouch` annotation, or a symbol that
/// should prevent certain types of canonicalizations.
bool firrtl::hasDontTouch(Operation *op) {
return op->getAttr(hw::InnerSymbolTable::getInnerSymbolAttrName()) ||
AnnotationSet(op).hasDontTouch();
}
/// Check whether a block argument ("port") or the operation defining a value
/// has a `DontTouch` annotation, or a symbol that should prevent certain types
/// of canonicalizations.
bool firrtl::hasDontTouch(Value value) {
if (auto *op = value.getDefiningOp())
return hasDontTouch(op);
auto arg = dyn_cast<BlockArgument>(value);
auto module = cast<FModuleOp>(arg.getOwner()->getParentOp());
return (module.getPortSymbolAttr(arg.getArgNumber())) ||
AnnotationSet::forPort(module, arg.getArgNumber()).hasDontTouch();
}
/// Get a special name to use when printing the entry block arguments of the
/// region contained by an operation in this dialect.
void getAsmBlockArgumentNamesImpl(Operation *op, mlir::Region ®ion,
OpAsmSetValueNameFn setNameFn) {
if (region.empty())
return;
auto *parentOp = op;
auto *block = ®ion.front();
// Check to see if the operation containing the arguments has 'firrtl.name'
// attributes for them. If so, use that as the name.
auto argAttr = parentOp->getAttrOfType<ArrayAttr>("portNames");
// Do not crash on invalid IR.
if (!argAttr || argAttr.size() != block->getNumArguments())
return;
for (size_t i = 0, e = block->getNumArguments(); i != e; ++i) {
auto str = cast<StringAttr>(argAttr[i]).getValue();
if (!str.empty())
setNameFn(block->getArgument(i), str);
}
}
/// A forward declaration for `NameKind` attribute parser.
static ParseResult parseNameKind(OpAsmParser &parser,
firrtl::NameKindEnumAttr &result);
//===----------------------------------------------------------------------===//
// Layer Verification Utilities
//===----------------------------------------------------------------------===//
namespace {
struct CompareSymbolRefAttr {
// True if lhs is lexicographically less than rhs.
bool operator()(SymbolRefAttr lhs, SymbolRefAttr rhs) const {
auto cmp = lhs.getRootReference().compare(rhs.getRootReference());
if (cmp == -1)
return true;
if (cmp == 1)
return false;
auto lhsNested = lhs.getNestedReferences();
auto rhsNested = rhs.getNestedReferences();
auto lhsNestedSize = lhsNested.size();
auto rhsNestedSize = rhsNested.size();
auto e = std::min(lhsNestedSize, rhsNestedSize);
for (unsigned i = 0; i < e; ++i) {
auto cmp = lhsNested[i].getAttr().compare(rhsNested[i].getAttr());
if (cmp == -1)
return true;
if (cmp == 1)
return false;
}
return lhsNestedSize < rhsNestedSize;
}
};
} // namespace
using LayerSet = SmallSet<SymbolRefAttr, 4, CompareSymbolRefAttr>;
/// Get the ambient layers active at the given op.
static LayerSet getAmbientLayersAt(Operation *op) {
// Crawl through the parent ops, accumulating all ambient layers at the given
// operation.
LayerSet result;
for (; op != nullptr; op = op->getParentOp()) {
if (auto module = dyn_cast<FModuleLike>(op)) {
auto layers = module.getLayersAttr().getAsRange<SymbolRefAttr>();
result.insert(layers.begin(), layers.end());
break;
}
if (auto layerblock = dyn_cast<LayerBlockOp>(op)) {
result.insert(layerblock.getLayerName());
continue;
}
}
return result;
}
/// Get the ambient layer requirements at the definition site of the value.
static LayerSet getAmbientLayersFor(Value value) {
return getAmbientLayersAt(getFieldRefFromValue(value).getDefiningOp());
}
/// Get the effective layer requirements for the given value.
/// The effective layers for a value is the union of
/// - the ambient layers for the cannonical storage location.
/// - any explicit layer annotations in the value's type.
static LayerSet getLayersFor(Value value) {
auto result = getAmbientLayersFor(value);
if (auto type = dyn_cast<RefType>(value.getType()))
if (auto layer = type.getLayer())
result.insert(type.getLayer());
return result;
}
/// Check that the source layer is compatible with the destination layer.
/// Either the source and destination are identical, or the source-layer
/// is a parent of the destination. For example `A` is compatible with `A.B.C`,
/// because any definition valid in `A` is also valid in `A.B.C`.
static bool isLayerCompatibleWith(mlir::SymbolRefAttr srcLayer,
mlir::SymbolRefAttr dstLayer) {
// A non-colored probe may be cast to any colored probe.
if (!srcLayer)
return true;
// A colored probe cannot be cast to an uncolored probe.
if (!dstLayer)
return false;
// Return true if the srcLayer is a prefix of the dstLayer.
if (srcLayer.getRootReference() != dstLayer.getRootReference())
return false;
auto srcNames = srcLayer.getNestedReferences();
auto dstNames = dstLayer.getNestedReferences();
if (dstNames.size() < srcNames.size())
return false;
return llvm::all_of(llvm::zip_first(srcNames, dstNames),
[](auto x) { return std::get<0>(x) == std::get<1>(x); });
}
/// Check that the source layer is present in the destination layers.
static bool isLayerCompatibleWith(SymbolRefAttr srcLayer,
const LayerSet &dstLayers) {
// fast path: the required layer is directly listed in the provided layers.
if (dstLayers.contains(srcLayer))
return true;
// Slow path: the required layer is not directly listed in the provided
// layers, but the layer may still be provided by a nested layer.
return any_of(dstLayers, [=](SymbolRefAttr dstLayer) {
return isLayerCompatibleWith(srcLayer, dstLayer);
});
}
/// Check that the source layers are all present in the destination layers.
/// True if all source layers are present in the destination.
/// Outputs the set of source layers that are missing in the destination.
static bool isLayerSetCompatibleWith(const LayerSet &src, const LayerSet &dst,
SmallVectorImpl<SymbolRefAttr> &missing) {
for (auto srcLayer : src)
if (!isLayerCompatibleWith(srcLayer, dst))
missing.push_back(srcLayer);
llvm::sort(missing, CompareSymbolRefAttr());
return missing.empty();
}
//===----------------------------------------------------------------------===//
// CircuitOp
//===----------------------------------------------------------------------===//
void CircuitOp::build(OpBuilder &builder, OperationState &result,
StringAttr name, ArrayAttr annotations) {
// Add an attribute for the name.
result.addAttribute(builder.getStringAttr("name"), name);
if (!annotations)
annotations = builder.getArrayAttr({});
result.addAttribute("annotations", annotations);
// Create a region and a block for the body.
Region *bodyRegion = result.addRegion();
Block *body = new Block();
bodyRegion->push_back(body);
}
static ParseResult parseCircuitOpAttrs(OpAsmParser &parser,
NamedAttrList &resultAttrs) {
auto result = parser.parseOptionalAttrDictWithKeyword(resultAttrs);
if (!resultAttrs.get("annotations"))
resultAttrs.append("annotations", parser.getBuilder().getArrayAttr({}));
return result;
}
static void printCircuitOpAttrs(OpAsmPrinter &p, Operation *op,
DictionaryAttr attr) {
// "name" is always elided.
SmallVector<StringRef> elidedAttrs = {"name"};
// Elide "annotations" if it doesn't exist or if it is empty
auto annotationsAttr = op->getAttrOfType<ArrayAttr>("annotations");
if (annotationsAttr.empty())
elidedAttrs.push_back("annotations");
p.printOptionalAttrDictWithKeyword(op->getAttrs(), elidedAttrs);
}
LogicalResult CircuitOp::verifyRegions() {
StringRef main = getName();
// Check that the circuit has a non-empty name.
if (main.empty()) {
emitOpError("must have a non-empty name");
return failure();
}
mlir::SymbolTable symtbl(getOperation());
if (!symtbl.lookup(main))
return emitOpError().append("Module with same name as circuit not found");
// Store a mapping of defname to either the first external module
// that defines it or, preferentially, the first external module
// that defines it and has no parameters.
llvm::DenseMap<Attribute, FExtModuleOp> defnameMap;
auto verifyExtModule = [&](FExtModuleOp extModule) -> LogicalResult {
if (!extModule)
return success();
auto defname = extModule.getDefnameAttr();
if (!defname)
return success();
// Check that this extmodule's defname does not conflict with
// the symbol name of any module.
if (auto collidingModule = symtbl.lookup<FModuleOp>(defname.getValue()))
return extModule.emitOpError()
.append("attribute 'defname' with value ", defname,
" conflicts with the name of another module in the circuit")
.attachNote(collidingModule.getLoc())
.append("previous module declared here");
// Find an optional extmodule with a defname collision. Update
// the defnameMap if this is the first extmodule with that
// defname or if the current extmodule takes no parameters and
// the collision does. The latter condition improves later
// extmodule verification as checking against a parameterless
// module is stricter.
FExtModuleOp collidingExtModule;
if (auto &value = defnameMap[defname]) {
collidingExtModule = value;
if (!value.getParameters().empty() && extModule.getParameters().empty())
value = extModule;
} else {
value = extModule;
// Go to the next extmodule if no extmodule with the same
// defname was found.
return success();
}
// Check that the number of ports is exactly the same.
SmallVector<PortInfo> ports = extModule.getPorts();
SmallVector<PortInfo> collidingPorts = collidingExtModule.getPorts();
if (ports.size() != collidingPorts.size())
return extModule.emitOpError()
.append("with 'defname' attribute ", defname, " has ", ports.size(),
" ports which is different from a previously defined "
"extmodule with the same 'defname' which has ",
collidingPorts.size(), " ports")
.attachNote(collidingExtModule.getLoc())
.append("previous extmodule definition occurred here");
// Check that ports match for name and type. Since parameters
// *might* affect widths, ignore widths if either module has
// parameters. Note that this allows for misdetections, but
// has zero false positives.
for (auto p : llvm::zip(ports, collidingPorts)) {
StringAttr aName = std::get<0>(p).name, bName = std::get<1>(p).name;
Type aType = std::get<0>(p).type, bType = std::get<1>(p).type;
if (aName != bName)
return extModule.emitOpError()
.append("with 'defname' attribute ", defname,
" has a port with name ", aName,
" which does not match the name of the port in the same "
"position of a previously defined extmodule with the same "
"'defname', expected port to have name ",
bName)
.attachNote(collidingExtModule.getLoc())
.append("previous extmodule definition occurred here");
if (!extModule.getParameters().empty() ||
!collidingExtModule.getParameters().empty()) {
// Compare base types as widthless, others must match.
if (auto base = type_dyn_cast<FIRRTLBaseType>(aType))
aType = base.getWidthlessType();
if (auto base = type_dyn_cast<FIRRTLBaseType>(bType))
bType = base.getWidthlessType();
}
if (aType != bType)
return extModule.emitOpError()
.append("with 'defname' attribute ", defname,
" has a port with name ", aName,
" which has a different type ", aType,
" which does not match the type of the port in the same "
"position of a previously defined extmodule with the same "
"'defname', expected port to have type ",
bType)
.attachNote(collidingExtModule.getLoc())
.append("previous extmodule definition occurred here");
}
return success();
};
for (auto &op : *getBodyBlock()) {
// Verify external modules.
if (auto extModule = dyn_cast<FExtModuleOp>(op)) {
if (verifyExtModule(extModule).failed())
return failure();
}
}
return success();
}
Block *CircuitOp::getBodyBlock() { return &getBody().front(); }
//===----------------------------------------------------------------------===//
// FExtModuleOp and FModuleOp
//===----------------------------------------------------------------------===//
static SmallVector<PortInfo> getPortImpl(FModuleLike module) {
SmallVector<PortInfo> results;
for (unsigned i = 0, e = module.getNumPorts(); i < e; ++i) {
results.push_back({module.getPortNameAttr(i), module.getPortType(i),
module.getPortDirection(i), module.getPortSymbolAttr(i),
module.getPortLocation(i),
AnnotationSet::forPort(module, i)});
}
return results;
}
SmallVector<PortInfo> FModuleOp::getPorts() { return ::getPortImpl(*this); }
SmallVector<PortInfo> FExtModuleOp::getPorts() { return ::getPortImpl(*this); }
SmallVector<PortInfo> FIntModuleOp::getPorts() { return ::getPortImpl(*this); }
SmallVector<PortInfo> FMemModuleOp::getPorts() { return ::getPortImpl(*this); }
static hw::ModulePort::Direction dirFtoH(Direction dir) {
if (dir == Direction::In)
return hw::ModulePort::Direction::Input;
if (dir == Direction::Out)
return hw::ModulePort::Direction::Output;
assert(0 && "invalid direction");
abort();
}
static SmallVector<hw::PortInfo> getPortListImpl(FModuleLike module) {
SmallVector<hw::PortInfo> results;
auto aname = StringAttr::get(module.getContext(),
hw::HWModuleLike::getPortSymbolAttrName());
auto emptyDict = DictionaryAttr::get(module.getContext());
for (unsigned i = 0, e = getNumPorts(module); i < e; ++i) {
auto sym = module.getPortSymbolAttr(i);
results.push_back(
{{module.getPortNameAttr(i), module.getPortType(i),
dirFtoH(module.getPortDirection(i))},
i,
sym ? DictionaryAttr::get(
module.getContext(),
ArrayRef<mlir::NamedAttribute>{NamedAttribute{aname, sym}})
: emptyDict,
module.getPortLocation(i)});
}
return results;
}
SmallVector<::circt::hw::PortInfo> FModuleOp::getPortList() {
return ::getPortListImpl(*this);
}
SmallVector<::circt::hw::PortInfo> FExtModuleOp::getPortList() {
return ::getPortListImpl(*this);
}
SmallVector<::circt::hw::PortInfo> FIntModuleOp::getPortList() {
return ::getPortListImpl(*this);
}
SmallVector<::circt::hw::PortInfo> FMemModuleOp::getPortList() {
return ::getPortListImpl(*this);
}
static hw::PortInfo getPortImpl(FModuleLike module, size_t idx) {
return {{module.getPortNameAttr(idx), module.getPortType(idx),
dirFtoH(module.getPortDirection(idx))},
idx,
DictionaryAttr::get(
module.getContext(),
ArrayRef<mlir::NamedAttribute>{NamedAttribute{
StringAttr::get(module.getContext(),
hw::HWModuleLike::getPortSymbolAttrName()),
module.getPortSymbolAttr(idx)}}),
module.getPortLocation(idx)};
}
::circt::hw::PortInfo FModuleOp::getPort(size_t idx) {
return ::getPortImpl(*this, idx);
}
::circt::hw::PortInfo FExtModuleOp::getPort(size_t idx) {
return ::getPortImpl(*this, idx);
}
::circt::hw::PortInfo FIntModuleOp::getPort(size_t idx) {
return ::getPortImpl(*this, idx);
}
::circt::hw::PortInfo FMemModuleOp::getPort(size_t idx) {
return ::getPortImpl(*this, idx);
}
// Return the port with the specified name.
BlockArgument FModuleOp::getArgument(size_t portNumber) {
return getBodyBlock()->getArgument(portNumber);
}
/// Inserts the given ports. The insertion indices are expected to be in order.
/// Insertion occurs in-order, such that ports with the same insertion index
/// appear in the module in the same order they appeared in the list.
static void insertPorts(FModuleLike op,
ArrayRef<std::pair<unsigned, PortInfo>> ports,
bool supportsInternalPaths = false) {
if (ports.empty())
return;
unsigned oldNumArgs = op.getNumPorts();
unsigned newNumArgs = oldNumArgs + ports.size();
// Add direction markers and names for new ports.
auto existingDirections = op.getPortDirectionsAttr();
ArrayRef<Attribute> existingNames = op.getPortNames();
ArrayRef<Attribute> existingTypes = op.getPortTypes();
ArrayRef<Attribute> existingLocs = op.getPortLocations();
assert(existingDirections.size() == oldNumArgs);
assert(existingNames.size() == oldNumArgs);
assert(existingTypes.size() == oldNumArgs);
assert(existingLocs.size() == oldNumArgs);
SmallVector<Attribute> internalPaths;
auto emptyInternalPath = InternalPathAttr::get(op.getContext());
if (supportsInternalPaths) {
if (auto internalPathsAttr = op->getAttrOfType<ArrayAttr>("internalPaths"))
llvm::append_range(internalPaths, internalPathsAttr);
else
internalPaths.resize(oldNumArgs, emptyInternalPath);
assert(internalPaths.size() == oldNumArgs);
}
SmallVector<bool> newDirections;
SmallVector<Attribute> newNames, newTypes, newAnnos, newSyms, newLocs,
newInternalPaths;
newDirections.reserve(newNumArgs);
newNames.reserve(newNumArgs);
newTypes.reserve(newNumArgs);
newAnnos.reserve(newNumArgs);
newSyms.reserve(newNumArgs);
newLocs.reserve(newNumArgs);
newInternalPaths.reserve(newNumArgs);
auto emptyArray = ArrayAttr::get(op.getContext(), {});
unsigned oldIdx = 0;
auto migrateOldPorts = [&](unsigned untilOldIdx) {
while (oldIdx < oldNumArgs && oldIdx < untilOldIdx) {
newDirections.push_back(existingDirections[oldIdx]);
newNames.push_back(existingNames[oldIdx]);
newTypes.push_back(existingTypes[oldIdx]);
newAnnos.push_back(op.getAnnotationsAttrForPort(oldIdx));
newSyms.push_back(op.getPortSymbolAttr(oldIdx));
newLocs.push_back(existingLocs[oldIdx]);
if (supportsInternalPaths)
newInternalPaths.push_back(internalPaths[oldIdx]);
++oldIdx;
}
};
for (auto pair : llvm::enumerate(ports)) {
auto idx = pair.value().first;
auto &port = pair.value().second;
migrateOldPorts(idx);
newDirections.push_back(direction::unGet(port.direction));
newNames.push_back(port.name);
newTypes.push_back(TypeAttr::get(port.type));
auto annos = port.annotations.getArrayAttr();
newAnnos.push_back(annos ? annos : emptyArray);
newSyms.push_back(port.sym);
newLocs.push_back(port.loc);
if (supportsInternalPaths)
newInternalPaths.push_back(emptyInternalPath);
}
migrateOldPorts(oldNumArgs);
// The lack of *any* port annotations is represented by an empty
// `portAnnotations` array as a shorthand.
if (llvm::all_of(newAnnos, [](Attribute attr) {
return cast<ArrayAttr>(attr).empty();
}))
newAnnos.clear();
// Apply these changed markers.
op->setAttr("portDirections",
direction::packAttribute(op.getContext(), newDirections));
op->setAttr("portNames", ArrayAttr::get(op.getContext(), newNames));
op->setAttr("portTypes", ArrayAttr::get(op.getContext(), newTypes));
op->setAttr("portAnnotations", ArrayAttr::get(op.getContext(), newAnnos));
op.setPortSymbols(newSyms);
op->setAttr("portLocations", ArrayAttr::get(op.getContext(), newLocs));
if (supportsInternalPaths) {
// Drop if all-empty, otherwise set to new array.
auto empty = llvm::all_of(newInternalPaths, [](Attribute attr) {
return !cast<InternalPathAttr>(attr).getPath();
});
if (empty)
op->removeAttr("internalPaths");
else
op->setAttr("internalPaths",
ArrayAttr::get(op.getContext(), newInternalPaths));
}
}
/// Erases the ports that have their corresponding bit set in `portIndices`.
static void erasePorts(FModuleLike op, const llvm::BitVector &portIndices) {
if (portIndices.none())
return;
// Drop the direction markers for dead ports.
ArrayRef<bool> portDirections = op.getPortDirectionsAttr().asArrayRef();
ArrayRef<Attribute> portNames = op.getPortNames();
ArrayRef<Attribute> portTypes = op.getPortTypes();
ArrayRef<Attribute> portAnnos = op.getPortAnnotations();
ArrayRef<Attribute> portSyms = op.getPortSymbols();
ArrayRef<Attribute> portLocs = op.getPortLocations();
auto numPorts = op.getNumPorts();
(void)numPorts;
assert(portDirections.size() == numPorts);
assert(portNames.size() == numPorts);
assert(portAnnos.size() == numPorts || portAnnos.empty());
assert(portTypes.size() == numPorts);
assert(portSyms.size() == numPorts || portSyms.empty());
assert(portLocs.size() == numPorts);
SmallVector<bool> newPortDirections =
removeElementsAtIndices<bool>(portDirections, portIndices);
SmallVector<Attribute> newPortNames, newPortTypes, newPortAnnos, newPortSyms,
newPortLocs;
newPortNames = removeElementsAtIndices(portNames, portIndices);
newPortTypes = removeElementsAtIndices(portTypes, portIndices);
newPortAnnos = removeElementsAtIndices(portAnnos, portIndices);
newPortSyms = removeElementsAtIndices(portSyms, portIndices);
newPortLocs = removeElementsAtIndices(portLocs, portIndices);
op->setAttr("portDirections",
direction::packAttribute(op.getContext(), newPortDirections));
op->setAttr("portNames", ArrayAttr::get(op.getContext(), newPortNames));
op->setAttr("portAnnotations", ArrayAttr::get(op.getContext(), newPortAnnos));
op->setAttr("portTypes", ArrayAttr::get(op.getContext(), newPortTypes));
FModuleLike::fixupPortSymsArray(newPortSyms, op.getContext());
op->setAttr("portSyms", ArrayAttr::get(op.getContext(), newPortSyms));
op->setAttr("portLocations", ArrayAttr::get(op.getContext(), newPortLocs));
}
template <typename T>
static void eraseInternalPaths(T op, const llvm::BitVector &portIndices) {
// Fixup internalPaths array.
auto internalPaths = op.getInternalPaths();
if (!internalPaths)
return;
auto newPaths =
removeElementsAtIndices(internalPaths->getValue(), portIndices);
// Drop if all-empty, otherwise set to new array.
auto empty = llvm::all_of(newPaths, [](Attribute attr) {
return !cast<InternalPathAttr>(attr).getPath();
});
if (empty)
op.removeInternalPathsAttr();
else
op.setInternalPathsAttr(ArrayAttr::get(op.getContext(), newPaths));
}
void FExtModuleOp::erasePorts(const llvm::BitVector &portIndices) {
::erasePorts(cast<FModuleLike>((Operation *)*this), portIndices);
eraseInternalPaths(*this, portIndices);
}
void FIntModuleOp::erasePorts(const llvm::BitVector &portIndices) {
::erasePorts(cast<FModuleLike>((Operation *)*this), portIndices);
eraseInternalPaths(*this, portIndices);
}
void FMemModuleOp::erasePorts(const llvm::BitVector &portIndices) {
::erasePorts(cast<FModuleLike>((Operation *)*this), portIndices);
}
void FModuleOp::erasePorts(const llvm::BitVector &portIndices) {
::erasePorts(cast<FModuleLike>((Operation *)*this), portIndices);
getBodyBlock()->eraseArguments(portIndices);
}
/// Inserts the given ports. The insertion indices are expected to be in order.
/// Insertion occurs in-order, such that ports with the same insertion index
/// appear in the module in the same order they appeared in the list.
void FModuleOp::insertPorts(ArrayRef<std::pair<unsigned, PortInfo>> ports) {
::insertPorts(cast<FModuleLike>((Operation *)*this), ports);
// Insert the block arguments.
auto *body = getBodyBlock();
for (size_t i = 0, e = ports.size(); i < e; ++i) {
// Block arguments are inserted one at a time, so for each argument we
// insert we have to increase the index by 1.
auto &[index, port] = ports[i];
body->insertArgument(index + i, port.type, port.loc);
}
}
void FExtModuleOp::insertPorts(ArrayRef<std::pair<unsigned, PortInfo>> ports) {
::insertPorts(cast<FModuleLike>((Operation *)*this), ports,
/*supportsInternalPaths=*/true);
}
void FIntModuleOp::insertPorts(ArrayRef<std::pair<unsigned, PortInfo>> ports) {
::insertPorts(cast<FModuleLike>((Operation *)*this), ports,
/*supportsInternalPaths=*/true);
}
/// Inserts the given ports. The insertion indices are expected to be in order.
/// Insertion occurs in-order, such that ports with the same insertion index
/// appear in the module in the same order they appeared in the list.
void FMemModuleOp::insertPorts(ArrayRef<std::pair<unsigned, PortInfo>> ports) {
::insertPorts(cast<FModuleLike>((Operation *)*this), ports);
}
static void buildModuleLike(OpBuilder &builder, OperationState &result,
StringAttr name, ArrayRef<PortInfo> ports,
ArrayAttr annotations, ArrayAttr layers,
bool withAnnotations, bool withLayers) {
// Add an attribute for the name.
result.addAttribute(::mlir::SymbolTable::getSymbolAttrName(), name);
// Record the names of the arguments if present.
SmallVector<Direction, 4> portDirections;
SmallVector<Attribute, 4> portNames;
SmallVector<Attribute, 4> portTypes;
SmallVector<Attribute, 4> portAnnotations;
SmallVector<Attribute, 4> portSyms;
SmallVector<Attribute, 4> portLocs;
for (const auto &port : ports) {
portDirections.push_back(port.direction);
portNames.push_back(port.name);
portTypes.push_back(TypeAttr::get(port.type));
portAnnotations.push_back(port.annotations.getArrayAttr());
portSyms.push_back(port.sym);
portLocs.push_back(port.loc);
}
// The lack of *any* port annotations is represented by an empty
// `portAnnotations` array as a shorthand.
if (llvm::all_of(portAnnotations, [](Attribute attr) {
return cast<ArrayAttr>(attr).empty();
}))
portAnnotations.clear();
FModuleLike::fixupPortSymsArray(portSyms, builder.getContext());
// Both attributes are added, even if the module has no ports.
result.addAttribute(
"portDirections",
direction::packAttribute(builder.getContext(), portDirections));
result.addAttribute("portNames", builder.getArrayAttr(portNames));
result.addAttribute("portTypes", builder.getArrayAttr(portTypes));
result.addAttribute("portSyms", builder.getArrayAttr(portSyms));
result.addAttribute("portLocations", builder.getArrayAttr(portLocs));
if (withAnnotations) {
if (!annotations)
annotations = builder.getArrayAttr({});
result.addAttribute("annotations", annotations);
result.addAttribute("portAnnotations",
builder.getArrayAttr(portAnnotations));
}
if (withLayers) {
if (!layers)
layers = builder.getArrayAttr({});
result.addAttribute("layers", layers);
}
result.addRegion();
}
static void buildModule(OpBuilder &builder, OperationState &result,
StringAttr name, ArrayRef<PortInfo> ports,
ArrayAttr annotations, ArrayAttr layers) {
buildModuleLike(builder, result, name, ports, annotations, layers,
/*withAnnotations=*/true, /*withLayers=*/true);
}
static void buildClass(OpBuilder &builder, OperationState &result,
StringAttr name, ArrayRef<PortInfo> ports) {
return buildModuleLike(builder, result, name, ports, {}, {},
/*withAnnotations=*/false,
/*withLayers=*/false);
}
void FModuleOp::build(OpBuilder &builder, OperationState &result,
StringAttr name, ConventionAttr convention,
ArrayRef<PortInfo> ports, ArrayAttr annotations,
ArrayAttr layers) {
buildModule(builder, result, name, ports, annotations, layers);
result.addAttribute("convention", convention);
// Create a region and a block for the body.
auto *bodyRegion = result.regions[0].get();