-
Notifications
You must be signed in to change notification settings - Fork 437
Expand file tree
/
Copy pathLowerToHW.cpp
More file actions
5971 lines (5175 loc) · 226 KB
/
LowerToHW.cpp
File metadata and controls
5971 lines (5175 loc) · 226 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
//===- LowerToHW.cpp - FIRRTL to HW/SV Lowering Pass ----------------------===//
//
// 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 is the main FIRRTL to HW/SV Lowering Pass Implementation.
//
//===----------------------------------------------------------------------===//
#include "circt/Conversion/FIRRTLToHW.h"
#include "circt/Dialect/Comb/CombOps.h"
#include "circt/Dialect/Emit/EmitOps.h"
#include "circt/Dialect/FIRRTL/AnnotationDetails.h"
#include "circt/Dialect/FIRRTL/FIRRTLAnnotations.h"
#include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h"
#include "circt/Dialect/FIRRTL/FIRRTLOps.h"
#include "circt/Dialect/FIRRTL/FIRRTLTypes.h"
#include "circt/Dialect/FIRRTL/FIRRTLUtils.h"
#include "circt/Dialect/FIRRTL/FIRRTLVisitors.h"
#include "circt/Dialect/FIRRTL/NLATable.h"
#include "circt/Dialect/FIRRTL/Namespace.h"
#include "circt/Dialect/HW/HWAttributes.h"
#include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/HW/HWTypes.h"
#include "circt/Dialect/HW/InnerSymbolNamespace.h"
#include "circt/Dialect/LTL/LTLOps.h"
#include "circt/Dialect/SV/SVOps.h"
#include "circt/Dialect/Seq/SeqOps.h"
#include "circt/Dialect/Sim/SimOps.h"
#include "circt/Dialect/Verif/VerifOps.h"
#include "circt/Support/BackedgeBuilder.h"
#include "circt/Support/Namespace.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/ImplicitLocOpBuilder.h"
#include "mlir/IR/Threading.h"
#include "mlir/Pass/Pass.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Mutex.h"
#include "llvm/Support/Path.h"
#define DEBUG_TYPE "lower-to-hw"
namespace circt {
#define GEN_PASS_DEF_LOWERFIRRTLTOHW
#include "circt/Conversion/Passes.h.inc"
} // namespace circt
using namespace circt;
using namespace firrtl;
using circt::comb::ICmpPredicate;
/// Attribute that indicates that the module hierarchy starting at the
/// annotated module should be dumped to a file.
static const char moduleHierarchyFileAttrName[] = "firrtl.moduleHierarchyFile";
/// Return true if the specified type is a sized FIRRTL type (Int or Analog)
/// with zero bits.
static bool isZeroBitFIRRTLType(Type type) {
auto ftype = dyn_cast<FIRRTLBaseType>(type);
return ftype && ftype.getPassiveType().getBitWidthOrSentinel() == 0;
}
// Return a single source value in the operands of the given attach op if
// exists.
static Value getSingleNonInstanceOperand(AttachOp op) {
Value singleSource;
for (auto operand : op.getAttached()) {
if (isZeroBitFIRRTLType(operand.getType()) ||
operand.getDefiningOp<InstanceOp>())
continue;
// If it is used by other than attach op or there is already a source
// value, bail out.
if (!operand.hasOneUse() || singleSource)
return {};
singleSource = operand;
}
return singleSource;
}
/// This verifies that the target operation has been lowered to a legal
/// operation. This checks that the operation recursively has no FIRRTL
/// operations or types.
static LogicalResult verifyOpLegality(Operation *op) {
auto checkTypes = [](Operation *op) -> WalkResult {
// Check that this operation is not a FIRRTL op.
if (isa_and_nonnull<FIRRTLDialect>(op->getDialect()))
return op->emitError("Found unhandled FIRRTL operation '")
<< op->getName() << "'";
// Helper to check a TypeRange for any FIRRTL types.
auto checkTypeRange = [&](TypeRange types) -> LogicalResult {
if (llvm::any_of(types, [](Type type) {
return isa<FIRRTLDialect>(type.getDialect());
}))
return op->emitOpError("found unhandled FIRRTL type");
return success();
};
// Check operand and result types.
if (failed(checkTypeRange(op->getOperandTypes())) ||
failed(checkTypeRange(op->getResultTypes())))
return WalkResult::interrupt();
// Check the block argument types.
for (auto ®ion : op->getRegions())
for (auto &block : region)
if (failed(checkTypeRange(block.getArgumentTypes())))
return WalkResult::interrupt();
// Continue to the next operation.
return WalkResult::advance();
};
if (checkTypes(op).wasInterrupted() || op->walk(checkTypes).wasInterrupted())
return failure();
return success();
}
/// Given two FIRRTL integer types, return the widest one.
static IntType getWidestIntType(Type t1, Type t2) {
auto t1c = type_cast<IntType>(t1), t2c = type_cast<IntType>(t2);
return t2c.getWidth() > t1c.getWidth() ? t2c : t1c;
}
/// Cast a value to a desired target type. This will insert struct casts and
/// unrealized conversion casts as necessary.
static Value castToFIRRTLType(Value val, Type type,
ImplicitLocOpBuilder &builder) {
// Use HWStructCastOp for a bundle type.
if (BundleType bundle = dyn_cast<BundleType>(type))
val = builder.createOrFold<HWStructCastOp>(bundle.getPassiveType(), val);
if (type != val.getType())
val = mlir::UnrealizedConversionCastOp::create(builder, type, val)
.getResult(0);
return val;
}
/// Cast from a FIRRTL type (potentially with a flip) to a standard type.
static Value castFromFIRRTLType(Value val, Type type,
ImplicitLocOpBuilder &builder) {
if (hw::StructType structTy = dyn_cast<hw::StructType>(type)) {
// Strip off Flip type if needed.
val = mlir::UnrealizedConversionCastOp::create(
builder,
type_cast<FIRRTLBaseType>(val.getType()).getPassiveType(), val)
.getResult(0);
val = builder.createOrFold<HWStructCastOp>(type, val);
return val;
}
val =
mlir::UnrealizedConversionCastOp::create(builder, type, val).getResult(0);
return val;
}
static unsigned getBitWidthFromVectorSize(unsigned size) {
return size == 1 ? 1 : llvm::Log2_64_Ceil(size);
}
// Try moving a name from an firrtl expression to a hw expression as a name
// hint. Dont' overwrite an existing name.
static void tryCopyName(Operation *dst, Operation *src) {
if (auto attr = src->getAttrOfType<StringAttr>("name"))
if (!dst->hasAttr("sv.namehint") && !dst->hasAttr("name"))
dst->setAttr("sv.namehint", attr);
}
namespace {
// A helper strutc to hold information about output file descriptor.
class FileDescriptorInfo {
public:
FileDescriptorInfo(StringAttr outputFileName, mlir::ValueRange substitutions)
: outputFileFormat(outputFileName), substitutions(substitutions) {
assert(outputFileName ||
substitutions.empty() &&
"substitutions must be empty when output file name is empty");
}
FileDescriptorInfo() = default;
// Substitution is required if substitution oprends are not empty.
bool isSubstitutionRequired() const { return !substitutions.empty(); }
// If the output file is not specified, the default file descriptor is used.
bool isDefaultFd() const { return !outputFileFormat; }
StringAttr getOutputFileFormat() const { return outputFileFormat; }
mlir::ValueRange getSubstitutions() const { return substitutions; }
private:
// "Verilog" format string for the output file.
StringAttr outputFileFormat = {};
// "FIRRTL" pre-lowered operands.
mlir::ValueRange substitutions;
};
} // namespace
//===----------------------------------------------------------------------===//
// firrtl.module Lowering Pass
//===----------------------------------------------------------------------===//
namespace {
struct FIRRTLModuleLowering;
/// This is state shared across the parallel module lowering logic.
struct CircuitLoweringState {
// Flags indicating whether the circuit uses certain header fragments.
std::atomic<bool> usedPrintf{false};
std::atomic<bool> usedAssertVerboseCond{false};
std::atomic<bool> usedStopCond{false};
std::atomic<bool> usedFileDescriptorLib{false};
std::atomic<bool> usedConfiguration{false};
CircuitLoweringState(CircuitOp circuitOp, bool enableAnnotationWarning,
firrtl::VerificationFlavor verificationFlavor,
InstanceGraph &instanceGraph, NLATable *nlaTable,
const InstanceChoiceMacroTable ¯oTable)
: circuitOp(circuitOp), instanceGraph(instanceGraph),
enableAnnotationWarning(enableAnnotationWarning),
verificationFlavor(verificationFlavor), nlaTable(nlaTable),
macroTable(macroTable) {
auto *context = circuitOp.getContext();
// Get the testbench output directory.
if (auto tbAnno =
AnnotationSet(circuitOp).getAnnotation(testBenchDirAnnoClass)) {
auto dirName = tbAnno.getMember<StringAttr>("dirname");
testBenchDirectory = hw::OutputFileAttr::getAsDirectory(
context, dirName.getValue(), false, true);
}
for (auto &op : *circuitOp.getBodyBlock()) {
if (auto module = dyn_cast<FModuleLike>(op)) {
if (AnnotationSet::removeAnnotations(module, markDUTAnnoClass))
dut = module;
// Pre-allocate the entry for this module.
instanceChoicesByModuleAndCase.try_emplace(module.getModuleNameAttr());
}
}
// Figure out which module is the DUT and TestHarness. If there is no
// module marked as the DUT, the top module is the DUT. If the DUT and the
// test harness are the same, then there is no test harness.
testHarness = instanceGraph.getTopLevelModule();
if (!dut) {
dut = testHarness;
testHarness = nullptr;
} else if (dut == testHarness) {
testHarness = nullptr;
}
// Pre-populate the dutModules member with a list of all modules that are
// determined to be under the DUT.
auto inDUT = [&](igraph::ModuleOpInterface child) {
auto isPhony = [](igraph::InstanceRecord *instRec) {
if (auto inst = instRec->getInstance<InstanceOp>())
return inst.getLowerToBind() || inst.getDoNotPrint();
return false;
};
if (auto parent = dyn_cast<igraph::ModuleOpInterface>(*dut))
return getInstanceGraph().isAncestor(child, parent, isPhony);
return dut == child;
};
circuitOp->walk([&](FModuleLike moduleOp) {
if (inDUT(moduleOp))
dutModules.insert(moduleOp);
});
}
Operation *getNewModule(Operation *oldModule) {
auto it = oldToNewModuleMap.find(oldModule);
return it != oldToNewModuleMap.end() ? it->second : nullptr;
}
Operation *getOldModule(Operation *newModule) {
auto it = newToOldModuleMap.find(newModule);
return it != newToOldModuleMap.end() ? it->second : nullptr;
}
void recordModuleMapping(Operation *oldFMod, Operation *newHWMod) {
oldToNewModuleMap[oldFMod] = newHWMod;
newToOldModuleMap[newHWMod] = oldFMod;
}
// Process remaining annotations and emit warnings on unprocessed annotations
// still remaining in the annoSet.
void processRemainingAnnotations(Operation *op, const AnnotationSet &annoSet);
CircuitOp circuitOp;
// Safely add a BindOp to global mutable state. This will acquire a lock to
// do this safely.
void addBind(sv::BindOp op) {
std::lock_guard<std::mutex> lock(bindsMutex);
binds.push_back(op);
}
/// For a given Type Alias, return the corresponding AliasType. Create and
/// record the AliasType, if it doesn't exist.
hw::TypeAliasType getTypeAlias(Type rawType, BaseTypeAliasType firAliasType,
Location typeLoc) {
auto hwAlias = typeAliases.getTypedecl(firAliasType);
if (hwAlias)
return hwAlias;
assert(!typeAliases.isFrozen() &&
"type aliases cannot be generated after its frozen");
return typeAliases.addTypedecl(rawType, firAliasType, typeLoc);
}
FModuleLike getDut() { return dut; }
FModuleLike getTestHarness() { return testHarness; }
// Return true if this module is the DUT or is instantiated by the DUT.
// Returns false if the module is not instantiated by the DUT or is
// instantiated under a bind. This will accept either an old FIRRTL module or
// a new HW module.
bool isInDUT(igraph::ModuleOpInterface child) {
if (auto hwModule = dyn_cast<hw::HWModuleOp>(child.getOperation()))
child = cast<igraph::ModuleOpInterface>(getOldModule(hwModule));
return dutModules.contains(child);
}
hw::OutputFileAttr getTestBenchDirectory() { return testBenchDirectory; }
// Return true if this module is instantiated by the Test Harness. Returns
// false if the module is not instantiated by the Test Harness or if the Test
// Harness is not known.
bool isInTestHarness(igraph::ModuleOpInterface mod) { return !isInDUT(mod); }
InstanceGraph &getInstanceGraph() { return instanceGraph; }
/// Given a type, return the corresponding lowered type for the HW dialect.
/// A wrapper to the FIRRTLUtils::lowerType, required to ensure safe addition
/// of TypeScopeOp for all the TypeDecls.
Type lowerType(Type type, Location loc) {
return ::lowerType(type, loc,
[&](Type rawType, BaseTypeAliasType firrtlType,
Location typeLoc) -> hw::TypeAliasType {
return getTypeAlias(rawType, firrtlType, typeLoc);
});
}
/// Get the sv.verbatim.source op for a filename, if it exists.
sv::SVVerbatimSourceOp getVerbatimSourceForFile(StringRef fileName) {
llvm::sys::SmartScopedLock<true> lock(verbatimSourcesMutex);
auto it = verbatimSourcesByFileName.find(fileName);
return it != verbatimSourcesByFileName.end() ? it->second : nullptr;
}
/// Register an sv.verbatim.source op containing the SV implementation for
/// some extmodule(s).
void registerVerbatimSource(StringRef fileName,
sv::SVVerbatimSourceOp verbatimOp) {
llvm::sys::SmartScopedLock<true> lock(verbatimSourcesMutex);
verbatimSourcesByFileName[fileName] = verbatimOp;
}
/// Get the emit.file op for a filename, if it exists.
emit::FileOp getEmitFileForFile(StringRef fileName) {
llvm::sys::SmartScopedLock<true> lock(emitFilesMutex);
auto it = emitFilesByFileName.find(fileName);
return it != emitFilesByFileName.end() ? it->second : nullptr;
}
/// Register an emit.file op containing the some verbatim collateral
/// required by some extmodule(s).
void registerEmitFile(StringRef fileName, emit::FileOp fileOp) {
llvm::sys::SmartScopedLock<true> lock(emitFilesMutex);
emitFilesByFileName[fileName] = fileOp;
}
private:
friend struct FIRRTLModuleLowering;
friend struct FIRRTLLowering;
CircuitLoweringState(const CircuitLoweringState &) = delete;
void operator=(const CircuitLoweringState &) = delete;
/// Mapping of FModuleOp to HWModuleOp
DenseMap<Operation *, Operation *> oldToNewModuleMap;
/// Mapping of HWModuleOp to FModuleOp
DenseMap<Operation *, Operation *> newToOldModuleMap;
/// Cache of module symbols. We need to test hirarchy-based properties to
/// lower annotaitons.
InstanceGraph &instanceGraph;
/// The set of old FIRRTL modules that are instantiated under the DUT. This
/// is precomputed as a module being under the DUT may rely on knowledge of
/// properties of the instance and is not suitable for querying in the
/// parallel execution region of this pass when the backing instances may
/// already be erased.
DenseSet<igraph::ModuleOpInterface> dutModules;
// Record the set of remaining annotation classes. This is used to warn only
// once about any annotation class.
StringSet<> pendingAnnotations;
const bool enableAnnotationWarning;
std::mutex annotationPrintingMtx;
const firrtl::VerificationFlavor verificationFlavor;
// Records any sv::BindOps that are found during the course of execution.
// This is unsafe to access directly and should only be used through addBind.
SmallVector<sv::BindOp> binds;
// Control access to binds.
std::mutex bindsMutex;
// The design-under-test (DUT), if it is found. This will be set if a
// "sifive.enterprise.firrtl.MarkDUTAnnotation" exists.
FModuleLike dut;
// If there is a module marked as the DUT and it is not the top level module,
// this will be set.
FModuleLike testHarness;
// If there is a testbench output directory, this will be set.
hw::OutputFileAttr testBenchDirectory;
/// A mapping of instances to their forced instantiation names (if
/// applicable).
DenseMap<std::pair<Attribute, Attribute>, Attribute> instanceForceNames;
/// The set of guard macros to emit declarations for.
SetVector<StringAttr> macroDeclNames;
std::mutex macroDeclMutex;
void addMacroDecl(StringAttr name) {
std::unique_lock<std::mutex> lock(macroDeclMutex);
macroDeclNames.insert(name);
}
/// Information about an instance choice for a specific option case.
struct LoweredInstanceChoice {
StringAttr parentModule;
FlatSymbolRefAttr instanceMacro;
hw::InstanceOp hwInstance;
};
using OptionAndCase = std::pair<StringAttr, StringAttr>;
// Map from moduleName to (optionName, caseName) to list of instance choices.
DenseMap<StringAttr,
DenseMap<OptionAndCase, SmallVector<LoweredInstanceChoice>>>
instanceChoicesByModuleAndCase;
void addInstanceChoiceForCase(StringAttr optionName, StringAttr caseName,
StringAttr parentModule,
FlatSymbolRefAttr instanceMacro,
hw::InstanceOp hwInstance) {
OptionAndCase innerKey{optionName, caseName};
instanceChoicesByModuleAndCase.at(parentModule)[innerKey].push_back(
{parentModule, instanceMacro, hwInstance});
}
/// The list of fragments on which the modules rely. Must be set outside the
/// parallelized module lowering since module type reads access it.
DenseMap<hw::HWModuleOp, SetVector<Attribute>> fragments;
llvm::sys::SmartMutex<true> fragmentsMutex;
void addFragment(hw::HWModuleOp module, StringRef fragment) {
llvm::sys::SmartScopedLock<true> lock(fragmentsMutex);
fragments[module].insert(
FlatSymbolRefAttr::get(circuitOp.getContext(), fragment));
}
/// Cached nla table analysis.
NLATable *nlaTable = nullptr;
/// FIRRTL::BaseTypeAliasType is lowered to hw::TypeAliasType, which requires
/// TypedeclOp inside a single global TypeScopeOp. This structure
/// maintains a map of FIRRTL alias types to HW alias type, which is populated
/// in the sequential phase and accessed during the read-only phase when its
/// frozen.
/// This structure ensures that
/// all TypeAliases are lowered as a prepass, before lowering all the modules
/// in parallel. Lowering of TypeAliases must be done sequentially to ensure
/// deteministic TypeDecls inside the global TypeScopeOp.
struct RecordTypeAlias {
RecordTypeAlias(CircuitOp c) : circuitOp(c) {}
hw::TypeAliasType getTypedecl(BaseTypeAliasType firAlias) const {
auto iter = firrtlTypeToAliasTypeMap.find(firAlias);
if (iter != firrtlTypeToAliasTypeMap.end())
return iter->second;
return {};
}
bool isFrozen() { return frozen; }
void freeze() { frozen = true; }
hw::TypeAliasType addTypedecl(Type rawType, BaseTypeAliasType firAlias,
Location typeLoc) {
assert(!frozen && "Record already frozen, cannot be updated");
if (!typeScope) {
auto b = ImplicitLocOpBuilder::atBlockBegin(
circuitOp.getLoc(),
&circuitOp->getParentRegion()->getBlocks().back());
typeScope = hw::TypeScopeOp::create(
b, b.getStringAttr(circuitOp.getName() + "__TYPESCOPE_"));
typeScope.getBodyRegion().push_back(new Block());
}
auto typeName = firAlias.getName();
// Get a unique typedecl name.
// The bundleName can conflict with other symbols, but must be unique
// within the TypeScopeOp.
typeName =
StringAttr::get(typeName.getContext(),
typeDeclNamespace.newName(typeName.getValue()));
auto typeScopeBuilder =
ImplicitLocOpBuilder::atBlockEnd(typeLoc, typeScope.getBodyBlock());
auto typeDecl = hw::TypedeclOp::create(typeScopeBuilder, typeLoc,
typeName, rawType, nullptr);
auto hwAlias = hw::TypeAliasType::get(
SymbolRefAttr::get(typeScope.getSymNameAttr(),
{FlatSymbolRefAttr::get(typeDecl)}),
rawType);
auto insert = firrtlTypeToAliasTypeMap.try_emplace(firAlias, hwAlias);
assert(insert.second && "Entry already exists, insert failed");
return insert.first->second;
}
private:
bool frozen = false;
/// Global typescope for all the typedecls in this module.
hw::TypeScopeOp typeScope;
/// Map of FIRRTL type to the lowered AliasType.
DenseMap<Type, hw::TypeAliasType> firrtlTypeToAliasTypeMap;
/// Set to keep track of unique typedecl names.
Namespace typeDeclNamespace;
CircuitOp circuitOp;
};
RecordTypeAlias typeAliases = RecordTypeAlias(circuitOp);
// sv.verbatim.sources for primary sources for verbatim extmodules
llvm::StringMap<sv::SVVerbatimSourceOp> verbatimSourcesByFileName;
llvm::sys::SmartMutex<true> verbatimSourcesMutex;
// emit.files for additional sources for verbatim extmodules
llvm::StringMap<emit::FileOp> emitFilesByFileName;
llvm::sys::SmartMutex<true> emitFilesMutex;
// Instance choice macro table for looking up option case macros
const InstanceChoiceMacroTable ¯oTable;
};
void CircuitLoweringState::processRemainingAnnotations(
Operation *op, const AnnotationSet &annoSet) {
if (!enableAnnotationWarning || annoSet.empty())
return;
std::lock_guard<std::mutex> lock(annotationPrintingMtx);
for (auto a : annoSet) {
auto inserted = pendingAnnotations.insert(a.getClass());
if (!inserted.second)
continue;
// The following annotations are okay to be silently dropped at this point.
// This can occur for example if an annotation marks something in the IR as
// not to be processed by a pass, but that pass hasn't run anyway.
if (a.isClass(
// If the class is `circt.nonlocal`, it's not really an annotation,
// but part of a path specifier for another annotation which is
// non-local. We can ignore these path specifiers since there will
// be a warning produced for the real annotation.
"circt.nonlocal",
// The following are either consumed by a pass running before
// LowerToHW, or they have no effect if the pass doesn't run at all.
// If the accompanying pass runs on the HW dialect, then LowerToHW
// should have consumed and processed these into an attribute on the
// output.
noDedupAnnoClass,
// The following are inspected (but not consumed) by FIRRTL/GCT
// passes that have all run by now. Since no one is responsible for
// consuming these, they will linger around and can be ignored.
markDUTAnnoClass, metadataDirAnnoClass, testBenchDirAnnoClass,
// This annotation is used to mark which external modules are
// imported blackboxes from the BlackBoxReader pass.
blackBoxAnnoClass,
// This annotation is used by several GrandCentral passes.
extractGrandCentralAnnoClass,
// The following will be handled while lowering the verification
// ops.
extractAssertionsAnnoClass, extractAssumptionsAnnoClass,
extractCoverageAnnoClass,
// The following will be handled after lowering FModule ops, since
// they are still needed on the circuit until after lowering
// FModules.
moduleHierarchyAnnoClass, testHarnessHierarchyAnnoClass,
blackBoxTargetDirAnnoClass))
continue;
mlir::emitWarning(op->getLoc(), "unprocessed annotation:'" + a.getClass() +
"' still remaining after LowerToHW");
}
}
} // end anonymous namespace
namespace {
struct FIRRTLModuleLowering
: public circt::impl::LowerFIRRTLToHWBase<FIRRTLModuleLowering> {
void runOnOperation() override;
void setEnableAnnotationWarning() { enableAnnotationWarning = true; }
using LowerFIRRTLToHWBase<FIRRTLModuleLowering>::verificationFlavor;
private:
void lowerFileHeader(CircuitOp op, CircuitLoweringState &loweringState);
void emitInstanceChoiceIncludes(mlir::ModuleOp circuit,
CircuitLoweringState &loweringState);
static void emitInstanceChoiceIncludeFile(
OpBuilder &builder, ModuleOp circuit, StringAttr publicModuleName,
StringAttr optionName, StringAttr caseName,
ArrayRef<CircuitLoweringState::LoweredInstanceChoice> instances,
Namespace &circuitNamespace, const InstanceChoiceMacroTable ¯oTable);
LogicalResult lowerPorts(ArrayRef<PortInfo> firrtlPorts,
SmallVectorImpl<hw::PortInfo> &ports,
Operation *moduleOp, StringRef moduleName,
CircuitLoweringState &loweringState);
bool handleForceNameAnnos(FModuleLike oldModule, AnnotationSet &annos,
CircuitLoweringState &loweringState);
hw::HWModuleOp lowerModule(FModuleOp oldModule, Block *topLevelModule,
CircuitLoweringState &loweringState);
sv::SVVerbatimSourceOp
getVerbatimSourceForExtModule(FExtModuleOp oldModule, Block *topLevelModule,
CircuitLoweringState &loweringState);
hw::HWModuleLike lowerExtModule(FExtModuleOp oldModule, Block *topLevelModule,
CircuitLoweringState &loweringState);
sv::SVVerbatimModuleOp
lowerVerbatimExtModule(FExtModuleOp oldModule, Block *topLevelModule,
CircuitLoweringState &loweringState);
hw::HWModuleExternOp lowerMemModule(FMemModuleOp oldModule,
Block *topLevelModule,
CircuitLoweringState &loweringState);
LogicalResult
lowerModulePortsAndMoveBody(FModuleOp oldModule, hw::HWModuleOp newModule,
CircuitLoweringState &loweringState);
LogicalResult lowerModuleBody(hw::HWModuleOp module,
CircuitLoweringState &loweringState);
LogicalResult lowerFormalBody(verif::FormalOp formalOp,
CircuitLoweringState &loweringState);
LogicalResult lowerSimulationBody(verif::SimulationOp simulationOp,
CircuitLoweringState &loweringState);
LogicalResult lowerFileBody(emit::FileOp op);
LogicalResult lowerBody(Operation *op, CircuitLoweringState &loweringState);
};
} // end anonymous namespace
/// This is the pass constructor.
std::unique_ptr<mlir::Pass> circt::createLowerFIRRTLToHWPass(
bool enableAnnotationWarning,
firrtl::VerificationFlavor verificationFlavor) {
auto pass = std::make_unique<FIRRTLModuleLowering>();
if (enableAnnotationWarning)
pass->setEnableAnnotationWarning();
pass->verificationFlavor = verificationFlavor;
return pass;
}
/// Run on the firrtl.circuit operation, lowering any firrtl.module operations
/// it contains.
void FIRRTLModuleLowering::runOnOperation() {
// We run on the top level modules in the IR blob. Start by finding the
// firrtl.circuit within it. If there is none, then there is nothing to do.
auto *topLevelModule = getOperation().getBody();
// Find the single firrtl.circuit in the module.
CircuitOp circuit;
for (auto &op : *topLevelModule) {
if ((circuit = dyn_cast<CircuitOp>(&op)))
break;
}
if (!circuit)
return;
auto *circuitBody = circuit.getBodyBlock();
// Keep track of the mapping from old to new modules. The result may be null
// if lowering failed.
CircuitLoweringState state(circuit, enableAnnotationWarning,
verificationFlavor, getAnalysis<InstanceGraph>(),
&getAnalysis<NLATable>(),
getAnalysis<InstanceChoiceMacroTable>());
SmallVector<Operation *, 32> opsToProcess;
AnnotationSet circuitAnno(circuit);
state.processRemainingAnnotations(circuit, circuitAnno);
// Iterate through each operation in the circuit body, transforming any
// FModule's we come across. If any module fails to lower, return early.
for (auto &op : make_early_inc_range(circuitBody->getOperations())) {
auto result =
TypeSwitch<Operation *, LogicalResult>(&op)
.Case<FModuleOp>([&](auto module) {
auto loweredMod = lowerModule(module, topLevelModule, state);
if (!loweredMod)
return failure();
state.recordModuleMapping(&op, loweredMod);
opsToProcess.push_back(loweredMod);
// Lower all the alias types.
module.walk([&](Operation *op) {
for (auto res : op->getResults()) {
if (auto aliasType =
type_dyn_cast<BaseTypeAliasType>(res.getType()))
state.lowerType(aliasType, op->getLoc());
}
});
return lowerModulePortsAndMoveBody(module, loweredMod, state);
})
.Case<FExtModuleOp>([&](auto extModule) {
auto loweredMod =
lowerExtModule(extModule, topLevelModule, state);
if (!loweredMod)
return failure();
state.recordModuleMapping(&op, loweredMod);
return success();
})
.Case<FMemModuleOp>([&](auto memModule) {
auto loweredMod =
lowerMemModule(memModule, topLevelModule, state);
if (!loweredMod)
return failure();
state.recordModuleMapping(&op, loweredMod);
return success();
})
.Case<FormalOp>([&](auto oldOp) {
auto builder = OpBuilder::atBlockEnd(topLevelModule);
auto newOp = verif::FormalOp::create(builder, oldOp.getLoc(),
oldOp.getNameAttr(),
oldOp.getParametersAttr());
newOp.getBody().emplaceBlock();
state.recordModuleMapping(oldOp, newOp);
opsToProcess.push_back(newOp);
return success();
})
.Case<SimulationOp>([&](auto oldOp) {
auto loc = oldOp.getLoc();
auto builder = OpBuilder::atBlockEnd(topLevelModule);
auto newOp = verif::SimulationOp::create(
builder, loc, oldOp.getNameAttr(), oldOp.getParametersAttr());
auto &body = newOp.getRegion().emplaceBlock();
body.addArgument(seq::ClockType::get(builder.getContext()), loc);
body.addArgument(builder.getI1Type(), loc);
state.recordModuleMapping(oldOp, newOp);
opsToProcess.push_back(newOp);
return success();
})
.Case<emit::FileOp>([&](auto fileOp) {
fileOp->moveBefore(topLevelModule, topLevelModule->end());
opsToProcess.push_back(fileOp);
return success();
})
.Case<OptionOp, OptionCaseOp>([&](auto) {
// Option operations are removed after lowering instance choices.
return success();
})
.Default([&](Operation *op) {
// We don't know what this op is. If it has no illegal FIRRTL
// types, we can forward the operation. Otherwise, we emit an
// error and drop the operation from the circuit.
if (succeeded(verifyOpLegality(op)))
op->moveBefore(topLevelModule, topLevelModule->end());
else
return failure();
return success();
});
if (failed(result))
return signalPassFailure();
}
// Ensure no more TypeDecl can be added to the global TypeScope.
state.typeAliases.freeze();
// Handle the creation of the module hierarchy metadata.
// Collect the two sets of hierarchy files from the circuit. Some of them will
// be rooted at the test harness, the others will be rooted at the DUT.
SmallVector<Attribute> dutHierarchyFiles;
SmallVector<Attribute> testHarnessHierarchyFiles;
circuitAnno.removeAnnotations([&](Annotation annotation) {
if (annotation.isClass(moduleHierarchyAnnoClass)) {
auto file = hw::OutputFileAttr::getFromFilename(
&getContext(),
annotation.getMember<StringAttr>("filename").getValue(),
/*excludeFromFileList=*/true);
dutHierarchyFiles.push_back(file);
return true;
}
if (annotation.isClass(testHarnessHierarchyAnnoClass)) {
auto file = hw::OutputFileAttr::getFromFilename(
&getContext(),
annotation.getMember<StringAttr>("filename").getValue(),
/*excludeFromFileList=*/true);
// If there is no testHarness, we print the hiearchy for this file
// starting at the DUT.
if (state.getTestHarness())
testHarnessHierarchyFiles.push_back(file);
else
dutHierarchyFiles.push_back(file);
return true;
}
return false;
});
// Attach the lowered form of these annotations.
if (!dutHierarchyFiles.empty())
state.getNewModule(state.getDut())
->setAttr(moduleHierarchyFileAttrName,
ArrayAttr::get(&getContext(), dutHierarchyFiles));
if (!testHarnessHierarchyFiles.empty())
state.getNewModule(state.getTestHarness())
->setAttr(moduleHierarchyFileAttrName,
ArrayAttr::get(&getContext(), testHarnessHierarchyFiles));
// Lower all module and formal op bodies.
auto result =
mlir::failableParallelForEach(&getContext(), opsToProcess, [&](auto op) {
return lowerBody(op, state);
});
if (failed(result))
return signalPassFailure();
// Move binds from inside modules to outside modules.
for (auto bind : state.binds) {
bind->moveBefore(bind->getParentOfType<hw::HWModuleOp>());
}
// Fix up fragment attributes.
for (auto &[module, fragments] : state.fragments)
module->setAttr(emit::getFragmentsAttrName(),
ArrayAttr::get(&getContext(), fragments.getArrayRef()));
// Finally delete all the old modules.
for (auto oldNew : state.oldToNewModuleMap)
oldNew.first->erase();
if (!state.macroDeclNames.empty()) {
ImplicitLocOpBuilder b(UnknownLoc::get(&getContext()), circuit);
for (auto name : state.macroDeclNames) {
sv::MacroDeclOp::create(b, name);
}
}
// Emit all the macros and preprocessor gunk at the start of the file.
lowerFileHeader(circuit, state);
// Emit global include files for instance choice options.
// Make sure to call after `lowerFileHeader` so that symbols generated for
// instance choices don't conflict with the macros defined in the header.
emitInstanceChoiceIncludes(getOperation(), state);
// Now that the modules are moved over, remove the Circuit.
circuit.erase();
}
/// Emit the file header that defines a bunch of macros.
void FIRRTLModuleLowering::lowerFileHeader(CircuitOp op,
CircuitLoweringState &state) {
// Intentionally pass an UnknownLoc here so we don't get line number
// comments on the output of this boilerplate in generated Verilog.
ImplicitLocOpBuilder b(UnknownLoc::get(&getContext()), op);
// Helper function to emit a "#ifdef guard" with a `define in the then and
// optionally in the else branch.
auto emitGuardedDefine = [&](StringRef guard, StringRef defName,
StringRef defineTrue = "",
StringRef defineFalse = StringRef()) {
if (!defineFalse.data()) {
assert(defineTrue.data() && "didn't define anything");
sv::IfDefOp::create(
b, guard, [&]() { sv::MacroDefOp::create(b, defName, defineTrue); });
} else {
sv::IfDefOp::create(
b, guard,
[&]() {
if (defineTrue.data())
sv::MacroDefOp::create(b, defName, defineTrue);
},
[&]() { sv::MacroDefOp::create(b, defName, defineFalse); });
}
};
// Helper function to emit #ifndef guard.
auto emitGuard = [&](const char *guard, llvm::function_ref<void(void)> body) {
sv::IfDefOp::create(
b, guard, [] {}, body);
};
if (state.usedFileDescriptorLib) {
// Define a type for the file descriptor getter.
SmallVector<hw::ModulePort> ports;
// Input port for filename
hw::ModulePort namePort;
namePort.name = b.getStringAttr("name");
namePort.type = hw::StringType::get(b.getContext());
namePort.dir = hw::ModulePort::Direction::Input;
ports.push_back(namePort);
// Output port for file descriptor
hw::ModulePort fdPort;
fdPort.name = b.getStringAttr("fd");
fdPort.type = b.getIntegerType(32);
fdPort.dir = hw::ModulePort::Direction::Output;
ports.push_back(fdPort);
// Create module type with the ports
auto moduleType = hw::ModuleType::get(b.getContext(), ports);
SmallVector<NamedAttribute> perArgumentsAttr;
perArgumentsAttr.push_back(
{sv::FuncOp::getExplicitlyReturnedAttrName(), b.getUnitAttr()});
SmallVector<Attribute> argumentAttr = {
DictionaryAttr::get(b.getContext(), {}),
DictionaryAttr::get(b.getContext(), perArgumentsAttr)};
// Create the function declaration
auto func = sv::FuncOp::create(
b, /*sym_name=*/
"__circt_lib_logging::FileDescriptor::get", moduleType,
/*perArgumentAttrs=*/
b.getArrayAttr(
{b.getDictionaryAttr({}), b.getDictionaryAttr(perArgumentsAttr)}),
/*inputLocs=*/
ArrayAttr(),
/*resultLocs=*/
ArrayAttr(),
/*verilogName=*/
b.getStringAttr("__circt_lib_logging::FileDescriptor::get"));
func.setPrivate();
sv::MacroDeclOp::create(b, "__CIRCT_LIB_LOGGING");
// Create the fragment containing the FileDescriptor class.
emit::FragmentOp::create(b, "CIRCT_LIB_LOGGING_FRAGMENT", [&] {
emitGuard("SYNTHESIS", [&]() {
emitGuard("__CIRCT_LIB_LOGGING", [&]() {
sv::VerbatimOp::create(b, R"(// CIRCT Logging Library
package __circt_lib_logging;
class FileDescriptor;
static int global_id [string];
static function int get(string name);
if (global_id.exists(name) == 32'h0) begin
global_id[name] = $fopen(name, "w");
if (global_id[name] == 32'h0)
$error("Failed to open file %s", name);
end
return global_id[name];
endfunction
endclass
endpackage
)");
sv::MacroDefOp::create(b, "__CIRCT_LIB_LOGGING", "");
});
});
});
}
if (state.usedPrintf) {
sv::MacroDeclOp::create(b, "PRINTF_COND");
sv::MacroDeclOp::create(b, "PRINTF_COND_");
emit::FragmentOp::create(b, "PRINTF_COND_FRAGMENT", [&] {
sv::VerbatimOp::create(
b, "\n// Users can define 'PRINTF_COND' to add an extra gate to "
"prints.");
emitGuard("PRINTF_COND_", [&]() {
emitGuardedDefine("PRINTF_COND", "PRINTF_COND_", "(`PRINTF_COND)", "1");
});
});
}
if (state.usedAssertVerboseCond) {