22#include "mlir/IR/Threading.h"
23#include "mlir/Pass/Pass.h"
24#include "llvm/ADT/DenseSet.h"
25#include "llvm/Support/Debug.h"
26#include "llvm/Support/LogicalResult.h"
29#define DEBUG_TYPE "firrtl-full-reset"
33#define GEN_PASS_DEF_FULLRESET
34#include "circt/Dialect/FIRRTL/Passes.h.inc"
41using circt::igraph::InstanceOpInterface;
44using llvm::SmallDenseSet;
51 if (
auto arg = dyn_cast<BlockArgument>(reset)) {
52 auto module = cast<FModuleOp>(arg.getParentRegion()->getParentOp());
53 return {
module.getPortNameAttr(arg.getArgNumber()), module};
55 auto *op = reset.getDefiningOp();
56 return {op->getAttrOfType<StringAttr>(
"name"),
57 op->getParentOfType<FModuleOp>()};
86 std::optional<unsigned> existingPort;
89 ResetDomain() =
default;
92 ResetDomain(Value rootReset)
93 : rootReset(rootReset), resetName(
getResetName(rootReset)),
94 resetType(rootReset.getType()) {}
97 explicit operator bool()
const {
return static_cast<bool>(rootReset); }
101inline bool operator==(
const ResetDomain &a,
const ResetDomain &b) {
102 return (a.isTop == b.isTop && a.resetName == b.resetName &&
103 a.resetType == b.resetType);
105inline bool operator!=(
const ResetDomain &a,
const ResetDomain &b) {
114 auto it = cache.find(type);
115 if (it != cache.end())
117 auto nullBit = [&]() {
119 builder, UIntType::get(builder.getContext(), 1,
true),
124 .
Case<ClockType>([&](
auto type) {
125 return AsClockPrimOp::create(builder, nullBit());
127 .Case<AsyncResetType>([&](
auto type) {
128 return AsAsyncResetPrimOp::create(builder, nullBit());
130 .Case<SIntType, UIntType>([&](
auto type) {
131 return ConstantOp::create(
132 builder, type, APInt::getZero(type.getWidth().value_or(1)));
134 .Case<FEnumType>([&](
auto type) -> Value {
137 if (type.getNumElements() != 0 &&
138 type.getElement(0).value.getValue().isZero()) {
139 const auto &element = type.getElement(0);
141 return FEnumCreateOp::create(builder, type, element.name, value);
143 auto value = ConstantOp::create(builder,
144 UIntType::get(builder.getContext(),
147 APInt::getZero(type.getBitWidth()));
148 return BitCastOp::create(builder, type, value);
150 .Case<BundleType>([&](
auto type) {
151 auto wireOp = WireOp::create(builder, type);
152 for (
unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
153 auto fieldType = type.getElementTypePreservingConst(i);
156 SubfieldOp::create(builder, fieldType, wireOp.getResult(), i);
159 return wireOp.getResult();
161 .Case<FVectorType>([&](
auto type) {
162 auto wireOp = WireOp::create(builder, type);
164 builder, type.getElementTypePreservingConst(), cache);
165 for (
unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
166 auto acc = SubindexOp::create(builder, zero.getType(),
167 wireOp.getResult(), i);
170 return wireOp.getResult();
172 .Case<ResetType, AnalogType>(
173 [&](
auto type) {
return InvalidValueOp::create(builder, type); })
175 llvm_unreachable(
"switch handles all types");
178 cache.insert({type, value});
195 Value reset, Value resetValue) {
199 bool resetValueUsed =
false;
201 for (
auto &use : target.getUses()) {
202 Operation *useOp = use.getOwner();
203 builder.setInsertionPoint(useOp);
204 TypeSwitch<Operation *>(useOp)
207 .Case<ConnectOp, MatchingConnectOp>([&](
auto op) {
208 if (op.getDest() != target)
210 LLVM_DEBUG(llvm::dbgs() <<
" - Insert mux into " << op <<
"\n");
212 MuxPrimOp::create(builder, reset, resetValue, op.getSrc());
213 op.getSrcMutable().assign(muxOp);
214 resetValueUsed =
true;
217 .Case<SubfieldOp>([&](
auto op) {
219 SubfieldOp::create(builder, resetValue, op.getFieldIndexAttr());
221 resetValueUsed =
true;
223 resetSubValue.erase();
226 .Case<SubindexOp>([&](
auto op) {
228 SubindexOp::create(builder, resetValue, op.getIndexAttr());
230 resetValueUsed =
true;
232 resetSubValue.erase();
235 .Case<SubaccessOp>([&](
auto op) {
236 if (op.getInput() != target)
239 SubaccessOp::create(builder, resetValue, op.getIndex());
241 resetValueUsed =
true;
243 resetSubValue.erase();
246 return resetValueUsed;
257enum class ResetKind { Async, Sync };
259static StringRef resetKindToStringRef(
const ResetKind &kind) {
261 case ResetKind::Async:
263 case ResetKind::Sync:
266 llvm_unreachable(
"unhandled reset kind");
271struct MemToRegOfVecConverter {
272 explicit MemToRegOfVecConverter(
bool ignoreReadEnable)
273 : ignoreReadEnable(ignoreReadEnable) {}
275 void runOnModule(FModuleOp mod) {
277 mod.getBodyBlock()->walk([&](MemOp memOp) {
278 LLVM_DEBUG(llvm::dbgs() <<
"\n Memory op:" << memOp);
280 auto firMem = memOp.getSummary();
287 if (firMem.isSeqMem())
290 generateMemory(memOp, firMem);
295 Value addPipelineStages(ImplicitLocOpBuilder &b,
size_t stages, Value clock,
296 Value pipeInput, StringRef name, Value gate = {}) {
301 auto reg = RegOp::create(b, pipeInput.getType(), clock, name).getResult();
303 WhenOp::create(b, gate,
false,
304 [&]() { MatchingConnectOp::create(b, reg, pipeInput); });
306 MatchingConnectOp::create(b, reg, pipeInput);
314 Value getClock(ImplicitLocOpBuilder &builder, Value bundle) {
315 return SubfieldOp::create(builder, bundle,
"clk");
318 Value getAddr(ImplicitLocOpBuilder &builder, Value bundle) {
319 return SubfieldOp::create(builder, bundle,
"addr");
322 Value getWmode(ImplicitLocOpBuilder &builder, Value bundle) {
323 return SubfieldOp::create(builder, bundle,
"wmode");
326 Value getEnable(ImplicitLocOpBuilder &builder, Value bundle) {
327 return SubfieldOp::create(builder, bundle,
"en");
330 Value getMask(ImplicitLocOpBuilder &builder, Value bundle) {
332 if (bType.getElement(
"mask"))
333 return SubfieldOp::create(builder, bundle,
"mask");
334 return SubfieldOp::create(builder, bundle,
"wmask");
337 Value getData(ImplicitLocOpBuilder &builder, Value bundle,
338 bool getWdata =
false) {
340 if (bType.getElement(
"data"))
341 return SubfieldOp::create(builder, bundle,
"data");
342 if (bType.getElement(
"rdata") && !getWdata)
343 return SubfieldOp::create(builder, bundle,
"rdata");
344 return SubfieldOp::create(builder, bundle,
"wdata");
347 void generateRead(
const FirMemory &firMem, Value clock, Value
addr,
348 Value enable, Value
data, Value regOfVec,
349 ImplicitLocOpBuilder &builder) {
350 if (ignoreReadEnable) {
353 for (
size_t j = 0, e = firMem.
readLatency; j != e; ++j) {
354 auto enLast = enable;
356 enable = addPipelineStages(builder, 1, clock, enable,
"en");
357 addr = addPipelineStages(builder, 1, clock,
addr,
"addr", enLast);
363 addPipelineStages(builder, firMem.
readLatency, clock, enable,
"en");
369 Value
rdata = SubaccessOp::create(builder, regOfVec,
addr);
370 if (!ignoreReadEnable) {
372 MatchingConnectOp::create(
373 builder,
data, InvalidValueOp::create(builder,
data.getType()));
375 WhenOp::create(builder, enable,
false, [&]() {
376 MatchingConnectOp::create(builder,
data,
rdata);
380 MatchingConnectOp::create(builder,
data,
rdata);
384 void generateWrite(
const FirMemory &firMem, Value clock, Value
addr,
385 Value enable, Value maskBits, Value wdataIn,
386 Value regOfVec, ImplicitLocOpBuilder &builder) {
391 addr = addPipelineStages(builder, numStages, clock,
addr,
"addr");
392 enable = addPipelineStages(builder, numStages, clock, enable,
"en");
393 wdataIn = addPipelineStages(builder, numStages, clock, wdataIn,
"wdata");
394 maskBits = addPipelineStages(builder, numStages, clock, maskBits,
"wmask");
403 SmallVector<std::tuple<Value, Value, Value>, 8> loweredRegDataMaskFields;
418 if (!getFields(
rdata, wdataIn, maskBits, loweredRegDataMaskFields,
420 wdataIn.getDefiningOp()->emitOpError(
421 "Cannot convert memory to bank of registers");
425 WhenOp::create(builder, enable,
false, [&]() {
427 for (
auto regDataMask : loweredRegDataMaskFields) {
428 auto regField = std::get<0>(regDataMask);
429 auto dataField = std::get<1>(regDataMask);
430 auto maskField = std::get<2>(regDataMask);
432 WhenOp::create(builder, maskField,
false, [&]() {
433 MatchingConnectOp::create(builder, regField, dataField);
439 void generateReadWrite(
const FirMemory &firMem, Value clock, Value
addr,
440 Value enable, Value maskBits, Value wdataIn,
441 Value rdataOut, Value
wmode, Value regOfVec,
442 ImplicitLocOpBuilder &builder) {
447 addr = addPipelineStages(builder, numStages, clock,
addr,
"addr");
448 enable = addPipelineStages(builder, numStages, clock, enable,
"en");
449 wdataIn = addPipelineStages(builder, numStages, clock, wdataIn,
"wdata");
450 maskBits = addPipelineStages(builder, numStages, clock, maskBits,
"wmask");
453 Value
rdata = SubaccessOp::create(builder, regOfVec,
addr);
455 SmallVector<std::tuple<Value, Value, Value>, 8> loweredRegDataMaskFields;
456 if (!getFields(
rdata, wdataIn, maskBits, loweredRegDataMaskFields,
458 wdataIn.getDefiningOp()->emitOpError(
459 "Cannot convert memory to bank of registers");
463 MatchingConnectOp::create(
464 builder, rdataOut, InvalidValueOp::create(builder, rdataOut.getType()));
466 WhenOp::create(builder, enable,
false, [&]() {
469 builder,
wmode,
true,
473 for (
auto regDataMask : loweredRegDataMaskFields) {
474 auto regField = std::get<0>(regDataMask);
475 auto dataField = std::get<1>(regDataMask);
476 auto maskField = std::get<2>(regDataMask);
479 builder, maskField,
false, [&]() {
480 MatchingConnectOp::create(builder, regField, dataField);
485 [&]() { MatchingConnectOp::create(builder, rdataOut,
rdata); });
496 bool getFields(Value reg, Value input, Value
mask,
497 SmallVectorImpl<std::tuple<Value, Value, Value>> &results,
498 ImplicitLocOpBuilder &builder) {
501 auto isValidMask = [&](FIRRTLType inType, FIRRTLType maskType) ->
bool {
504 return mBundle.getNumElements() == bundle.getNumElements();
507 return mVec.getNumElements() == vec.getNumElements();
513 std::function<bool(Value, Value, Value)> flatAccess =
514 [&](Value
reg, Value input, Value
mask) ->
bool {
517 input.getDefiningOp()->emitOpError(
"Mask type is not valid");
520 return FIRRTLTypeSwitch<FIRRTLType, bool>(inType)
521 .Case<BundleType>([&](BundleType bundle) {
522 for (
size_t i = 0, e = bundle.getNumElements(); i != e; ++i) {
523 auto regField = SubfieldOp::create(builder, reg, i);
524 auto inputField = SubfieldOp::create(builder, input, i);
525 auto maskField = SubfieldOp::create(builder,
mask, i);
526 if (!flatAccess(regField, inputField, maskField))
531 .Case<FVectorType>([&](
auto vector) {
532 for (
size_t i = 0, e = vector.getNumElements(); i != e; ++i) {
533 auto regField = SubindexOp::create(builder, reg, i);
534 auto inputField = SubindexOp::create(builder, input, i);
535 auto maskField = SubindexOp::create(builder,
mask, i);
536 if (!flatAccess(regField, inputField, maskField))
541 .Case<IntType>([&](
auto iType) {
542 results.push_back({
reg, input,
mask});
543 return iType.getWidth().has_value();
545 .Default([&](
auto) {
return false; });
547 if (flatAccess(reg, input,
mask))
553 void generateMemory(MemOp memOp, FirMemory &firMem) {
554 ImplicitLocOpBuilder builder(memOp.getLoc(), memOp);
555 auto dataType = memOp.getDataType();
557 auto innerSym = memOp.getInnerSym();
558 SmallVector<Value> debugPorts;
561 for (
size_t index = 0, rend = memOp.getNumResults(); index < rend;
563 auto result = memOp.getResult(index);
565 debugPorts.push_back(result);
570 auto wire = WireOp::create(
571 builder, result.getType(),
572 (memOp.getName() +
"_" + memOp.getPortName(index)).str(),
573 memOp.getNameKind());
574 result.replaceAllUsesWith(wire.getResult());
575 result = wire.getResult();
577 auto adr = getAddr(builder, result);
578 auto enb = getEnable(builder, result);
579 auto clk = getClock(builder, result);
580 auto dta = getData(builder, result);
585 RegOp::create(builder, FVectorType::get(dataType, firMem.
depth),
586 clk, memOp.getNameAttr());
589 if (!memOp.getAnnotationsAttr().empty())
590 regOfVec.setAnnotationsAttr(memOp.getAnnotationsAttr());
592 regOfVec.setInnerSymAttr(memOp.getInnerSymAttr());
594 auto portKind = memOp.getPortKind(index);
595 if (portKind == MemOp::PortKind::Read) {
596 generateRead(firMem,
clk, adr, enb, dta, regOfVec.getResult(), builder);
597 }
else if (portKind == MemOp::PortKind::Write) {
598 auto mask = getMask(builder, result);
599 generateWrite(firMem,
clk, adr, enb,
mask, dta, regOfVec.getResult(),
602 auto wmode = getWmode(builder, result);
603 auto wDta = getData(builder, result,
true);
604 auto mask = getMask(builder, result);
605 generateReadWrite(firMem,
clk, adr, enb,
mask, wDta, dta,
wmode,
606 regOfVec.getResult(), builder);
613 for (
auto r : debugPorts)
614 r.replaceAllUsesWith(RefSendOp::create(builder, regOfVec.getResult()));
617 bool ignoreReadEnable =
false;
618 unsigned numConverted = 0;
623 unsigned &numConverted) {
624 MemToRegOfVecConverter converter(ignoreReadEnable);
625 converter.runOnModule(mod);
626 numConverted += converter.numConverted;
630struct FullResetRunner {
633 InstanceInfo &instanceInfo,
bool convertAsyncDomainMems)
634 : circuit(circuit), instanceGraph(&ig),
635 instancePathCache(&instancePathCache), instanceInfo(&instanceInfo),
636 convertAsyncDomainMems(convertAsyncDomainMems) {}
643 LogicalResult collectAnnos();
649 FailureOr<std::optional<Value>> collectAnnos(FModuleOp module);
651 LogicalResult buildDomains();
652 void buildDomains(FModuleOp module,
const InstancePath &instPath,
654 unsigned indent = 0);
656 void convertMemsInAsyncDomains();
658 LogicalResult determineImpl();
659 LogicalResult determineImpl(FModuleOp module, ResetDomain &domain);
661 LogicalResult implementFullReset();
662 LogicalResult implementFullReset(FModuleOp module, ResetDomain &domain);
663 LogicalResult implementFullReset(Operation *op, FModuleOp module,
667 LogicalResult implementFullReset(FInstanceLike inst, StringAttr moduleName,
675 DenseMap<Operation *, Value> annotatedResets;
690 bool convertAsyncDomainMems =
false;
694LogicalResult FullResetRunner::run() {
695 if (failed(collectAnnos()))
697 if (failed(buildDomains()))
699 if (convertAsyncDomainMems)
700 convertMemsInAsyncDomains();
701 if (failed(determineImpl()))
703 if (failed(implementFullReset()))
708void FullResetRunner::convertMemsInAsyncDomains() {
709 SmallVector<FModuleOp> asyncDomainMods;
710 for (
auto &[mod, entries] : domains) {
713 auto &domain = entries.back().first;
714 if (!domain.rootReset)
720 asyncDomainMods.push_back(mod);
722 if (asyncDomainMods.empty())
726 llvm::dbgs() <<
"\n";
727 debugHeader(
"Convert comb mems in async full-reset domains") <<
"\n\n";
728 for (
auto mod : asyncDomainMods)
729 llvm::dbgs() <<
"- " << mod.getName() <<
"\n";
732 mlir::parallelForEach(
733 circuit.getContext(), asyncDomainMods, [&](FModuleOp mod) {
734 unsigned converted = 0;
735 runCombMemsToRegOfVec(mod, false, converted);
741 bool convertAsyncDomainMems) {
743 return FullResetRunner(circuit, ig, instancePathCache, instanceInfo,
744 convertAsyncDomainMems)
752LogicalResult FullResetRunner::collectAnnos() {
754 llvm::dbgs() <<
"\n";
757 SmallVector<std::pair<FModuleOp, std::optional<Value>>> results;
758 for (
auto module : circuit.getOps<FModuleOp>())
759 results.push_back({module, {}});
761 if (failed(mlir::failableParallelForEach(
762 circuit.getContext(), results, [&](
auto &moduleAndResult) {
763 auto result = collectAnnos(moduleAndResult.first);
766 moduleAndResult.second = *result;
771 for (
auto [module, reset] : results)
772 if (reset.has_value())
773 annotatedResets.insert({module, *reset});
777FailureOr<std::optional<Value>>
778FullResetRunner::collectAnnos(FModuleOp module) {
779 bool anyFailed =
false;
780 SmallSetVector<std::pair<Annotation, Location>, 4> conflictingAnnos;
786 if (anno.
isClass(excludeFromFullResetAnnoClass)) {
788 conflictingAnnos.insert({anno, module.getLoc()});
791 if (anno.
isClass(fullResetAnnoClass)) {
793 module.emitError("''FullResetAnnotation' cannot target module; must
"
794 "target port or wire/node instead
");
802 // Consume any reset annotations on module ports.
804 // Helper for checking annotations and determining the reset
805 auto checkAnnotations = [&](Annotation anno, Value arg) {
806 if (anno.isClass(fullResetAnnoClass)) {
807 ResetKind expectedResetKind;
808 if (auto rt = anno.getMember<StringAttr>("resetType
")) {
810 expectedResetKind = ResetKind::Sync;
811 } else if (rt == "async
") {
812 expectedResetKind = ResetKind::Async;
814 mlir::emitError(arg.getLoc(),
815 "'FullResetAnnotation' requires resetType ==
'sync' "
816 "|
'async', but got resetType ==
")
822 mlir::emitError(arg.getLoc(),
823 "'FullResetAnnotation' requires resetType ==
"
824 "'sync' |
'async', but got no resetType
");
828 // Check that the type is well-formed
829 bool isAsync = expectedResetKind == ResetKind::Async;
830 bool validUint = false;
831 if (auto uintT = dyn_cast<UIntType>(arg.getType()))
832 validUint = uintT.getWidth() == 1;
833 if ((isAsync && !isa<AsyncResetType>(arg.getType())) ||
834 (!isAsync && !validUint)) {
835 auto kind = resetKindToStringRef(expectedResetKind);
836 mlir::emitError(arg.getLoc(),
837 "'FullResetAnnotation' with resetType ==
'")
838 << kind << "' must target
" << kind << " reset, but targets
"
845 conflictingAnnos.insert({anno, reset.getLoc()});
849 if (anno.isClass(excludeFromFullResetAnnoClass)) {
851 mlir::emitError(arg.getLoc(),
852 "'ExcludeFromFullResetAnnotation' cannot
"
853 "target port/wire/node; must target module instead
");
859 AnnotationSet::removePortAnnotations(module,
860 [&](unsigned argNum, Annotation anno) {
861 Value arg = module.getArgument(argNum);
862 return checkAnnotations(anno, arg);
867 // Consume any reset annotations on wires in the module body.
868 module.getBody().walk([&](Operation *op) {
869 // Reset annotations must target wire/node ops.
870 if (!isa<WireOp, NodeOp>(op)) {
871 if (AnnotationSet::hasAnnotation(op, fullResetAnnoClass,
872 excludeFromFullResetAnnoClass)) {
875 "reset annotations must target module, port, or wire/node
");
880 // At this point we know that we have a WireOp/NodeOp. Process the reset
882 AnnotationSet::removeAnnotations(op, [&](Annotation anno) {
883 auto arg = op->getResult(0);
884 return checkAnnotations(anno, arg);
890 // If we have found no annotations, there is nothing to do. We just leave
891 // this module unannotated, which will cause it to inherit a reset domain
892 // from its instantiation sites.
893 if (!ignore && !reset) {
894 LLVM_DEBUG(llvm::dbgs()
895 << "No reset annotation
for " << module.getName() << "\n
");
896 return std::optional<Value>();
899 // If we have found multiple annotations, emit an error and abort.
900 if (conflictingAnnos.size() > 1) {
901 auto diag = module.emitError("multiple reset annotations on module
'")
902 << module.getName() << "'";
903 for (auto &annoAndLoc : conflictingAnnos)
904 diag.attachNote(annoAndLoc.second)
905 << "conflicting
" << annoAndLoc.first.getClassAttr() << ":
";
909 // Dump some information in debug builds.
911 llvm::dbgs() << "Annotated reset
for " << module.getName() << ":
";
913 llvm::dbgs() << "no domain\n
";
914 else if (auto arg = dyn_cast<BlockArgument>(reset))
915 llvm::dbgs() << "port
" << module.getPortName(arg.getArgNumber()) << "\n
";
917 llvm::dbgs() << "wire
"
918 << reset.getDefiningOp()->getAttrOfType<StringAttr>("name
")
922 // Store the annotated reset for this module.
923 assert(ignore || reset);
924 return std::optional<Value>(reset);
927//===----------------------------------------------------------------------===//
928// Domain Construction
929//===----------------------------------------------------------------------===//
936LogicalResult FullResetRunner::buildDomains() {
938 llvm::dbgs() << "\n
";
939 debugHeader("Build full reset domains
") << "\n\n
";
942 // Gather the domains.
943 auto &instGraph = *instanceGraph;
944 // Walk all top-level modules.
945 instGraph.walkPostOrder([&](igraph::InstanceGraphNode &node) {
949 dyn_cast_or_null<FModuleOp>(node.getModule().getOperation()))
950 buildDomains(module, InstancePath{}, Value{}, instGraph);
953 // Report any domain conflicts among the modules.
954 bool anyFailed = false;
955 for (auto &it : domains) {
956 auto module = cast<FModuleOp>(it.first);
957 auto &domainConflicts = it.second;
958 if (domainConflicts.size() <= 1)
962 SmallDenseSet<Value> printedDomainResets;
963 auto diag = module.emitError("module
'")
965 << "' instantiated in different reset domains
";
966 for (auto &it : domainConflicts) {
967 ResetDomain &domain = it.first;
968 const auto &path = it.second;
969 auto inst = path.leaf();
970 auto loc = path.empty() ? module.getLoc() : inst.getLoc();
971 auto ¬e = diag.attachNote(loc);
973 // Describe the instance itself.
975 note << "root instance
";
977 note << "instance
'";
980 [&](InstanceOpInterface inst) { note << inst.getInstanceName(); },
981 [&]() { note << "/"; });
985 // Describe the reset domain the instance is in.
987 if (domain.rootReset) {
988 auto nameAndModule = getResetNameAndModule(domain.rootReset);
989 note << " reset domain rooted at
'" << nameAndModule.first.getValue()
990 << "' of module
'" << nameAndModule.second.getName() << "'";
992 // Show where the domain reset is declared (once per reset).
993 if (printedDomainResets.insert(domain.rootReset).second) {
994 diag.attachNote(domain.rootReset.getLoc())
995 << "reset domain
'" << nameAndModule.first.getValue()
996 << "' of module
'" << nameAndModule.second.getName()
997 << "' declared here:
";
1000 note << " no reset domain
";
1003 return failure(anyFailed);
1006void FullResetRunner::buildDomains(FModuleOp module,
1007 const InstancePath &instPath,
1008 Value parentReset, InstanceGraph &instGraph,
1011 llvm::dbgs().indent(indent * 2) << "Visiting
";
1012 if (instPath.empty())
1013 llvm::dbgs() << "$root
";
1015 llvm::dbgs() << instPath.leaf().getInstanceName();
1016 llvm::dbgs() << " (
" << module.getName() << ")\n
";
1019 // Assemble the domain for this module.
1021 auto it = annotatedResets.find(module);
1022 if (it != annotatedResets.end()) {
1023 // If there is an actual reset, use it for our domain. Otherwise, our
1024 // module is explicitly marked to have no domain.
1025 if (auto localReset = it->second)
1026 domain = ResetDomain(localReset);
1027 domain.isTop = true;
1028 } else if (parentReset) {
1029 // Otherwise, we default to using the reset domain of our parent.
1030 domain = ResetDomain(parentReset);
1033 // Associate the domain with this module. Only record non-null reset domains;
1034 // the `domains[module]` entry is created regardless, so modules in no-domain
1035 // contexts will have an empty entries list. If the module already has an
1036 // entry for this domain, don't add a duplicate.
1037 auto &entries = domains[module];
1038 if (domain.rootReset)
1039 if (llvm::all_of(entries,
1040 [&](const auto &entry) { return entry.first != domain; }))
1041 entries.push_back({domain, instPath});
1043 // Traverse the child instances.
1044 for (auto *record : *instGraph[module]) {
1045 auto submodule = dyn_cast<FModuleOp>(*record->getTarget()->getModule());
1049 instancePathCache->appendInstance(instPath, record->getInstance());
1050 buildDomains(submodule, childPath, domain.rootReset, instGraph, indent + 1);
1055LogicalResult FullResetRunner::determineImpl() {
1056 auto anyFailed = false;
1058 llvm::dbgs() << "\n
";
1059 debugHeader("Determine implementation
") << "\n\n
";
1061 for (auto &it : domains) {
1062 auto module = cast<FModuleOp>(it.first);
1063 auto &entries = it.second;
1064 // Skip modules with no reset domain (empty entries).
1065 if (entries.empty())
1067 auto &domain = entries.back().first;
1068 if (failed(determineImpl(module, domain)))
1071 return failure(anyFailed);
1089LogicalResult FullResetRunner::determineImpl(FModuleOp module,
1090 ResetDomain &domain) {
1091 // Nothing to do if the module needs no reset.
1094 LLVM_DEBUG(llvm::dbgs() << "Planning reset
for " << module.getName() << "\n
");
1096 // If this is the root of a reset domain, we don't need to add any ports
1097 // and can just simply reuse the existing values.
1099 LLVM_DEBUG(llvm::dbgs()
1100 << "- Rooting at local value
" << domain.resetName << "\n
");
1101 domain.localReset = domain.rootReset;
1102 if (auto blockArg = dyn_cast<BlockArgument>(domain.rootReset))
1103 domain.existingPort = blockArg.getArgNumber();
1107 // Otherwise, check if a port with this name and type already exists and
1108 // reuse that where possible.
1109 auto neededName = domain.resetName;
1110 auto neededType = domain.resetType;
1111 LLVM_DEBUG(llvm::dbgs() << "- Looking
for existing port
" << neededName
1113 auto portNames = module.getPortNames();
1114 auto *portIt = llvm::find(portNames, neededName);
1116 // If this port does not yet exist, record that we need to create it.
1117 if (portIt == portNames.end()) {
1118 LLVM_DEBUG(llvm::dbgs() << "- Creating
new port
" << neededName << "\n
");
1119 domain.resetName = neededName;
1123 LLVM_DEBUG(llvm::dbgs() << "- Reusing existing port
" << neededName << "\n
");
1125 // If this port has the wrong type, then error out.
1126 auto portNo = std::distance(portNames.begin(), portIt);
1127 auto portType = module.getPortType(portNo);
1128 if (portType != neededType) {
1129 auto diag = emitError(module.getPortLocation(portNo), "module
'")
1130 << module.getName() << "' is in reset domain requiring port
'"
1131 << domain.resetName.getValue() << "' to have type
"
1132 << domain.resetType << ", but has type
" << portType;
1133 diag.attachNote(domain.rootReset.getLoc()) << "reset domain rooted here
";
1137 // We have a pre-existing port which we should use.
1138 domain.existingPort = portNo;
1139 domain.localReset = module.getArgument(portNo);
1143//===----------------------------------------------------------------------===//
1144// Full Reset Implementation
1145//===----------------------------------------------------------------------===//
1148LogicalResult FullResetRunner::implementFullReset() {
1150 llvm::dbgs() << "\n
";
1151 debugHeader("Implement full resets
") << "\n\n
";
1153 for (auto &it : domains) {
1154 auto module = cast<FModuleOp>(it.first);
1155 auto &entries = it.second;
1156 // For modules with a real domain, use that domain. For no-domain modules,
1157 // use a default empty domain but still process for tie-off.
1159 if (!entries.empty())
1160 domain = entries.back().first;
1161 if (failed(implementFullReset(module, domain)))
1172LogicalResult FullResetRunner::implementFullReset(FModuleOp module,
1173 ResetDomain &domain) {
1174 // For modules in no-domain contexts, we skip local transformations (adding
1175 // reset ports, converting registers) but still process instances to tie off
1176 // reset ports of children that have a real reset domain.
1178 SmallVector<FInstanceLike> instances;
1179 module.walk([&](FInstanceLike instOp) { instances.push_back(instOp); });
1181 if (!instances.empty())
1182 llvm::dbgs() << "Tie off instances in
" << module.getName() << "\n
";
1184 for (auto instOp : instances)
1185 if (failed(implementFullReset(instOp, module, Value())))
1190 LLVM_DEBUG(llvm::dbgs() << "Implementing full reset
for " << module.getName()
1193 // Add an annotation indicating that this module belongs to a reset domain.
1194 auto *context = module.getContext();
1195 AnnotationSet annotations(module);
1196 annotations.addAnnotations(DictionaryAttr::get(
1197 context, NamedAttribute(StringAttr::get(context, "class"),
1198 StringAttr::get(context, fullResetAnnoClass))));
1199 annotations.applyToOperation(module);
1201 // If needed, add a reset port to the module.
1202 auto actualReset = domain.localReset;
1203 if (!domain.localReset) {
1204 PortInfo portInfo{domain.resetName,
1208 domain.rootReset.getLoc()};
1209 module.insertPorts({{0, portInfo}});
1210 actualReset = module.getArgument(0);
1211 LLVM_DEBUG(llvm::dbgs() << "- Inserted port
" << domain.resetName << "\n
");
1215 llvm::dbgs() << "- Using
";
1216 if (auto blockArg = dyn_cast<BlockArgument>(actualReset))
1217 llvm::dbgs() << "port #
" << blockArg.getArgNumber() << " ";
1219 llvm::dbgs() << "wire/node
";
1220 llvm::dbgs() << getResetName(actualReset) << "\n
";
1223 // Gather a list of operations in the module that need to be updated with
1225 SmallVector<Operation *> opsToUpdate;
1226 module.walk([&](Operation *op) {
1227 if (isa<FInstanceLike, RegOp, RegResetOp>(op))
1228 opsToUpdate.push_back(op);
1231 // If the reset is a local wire or node, move it upwards such that it
1232 // dominates all the operations that it will need to attach to. In the case
1233 // of a node this might not be easily possible, so we just spill into a wire
1235 if (!isa<BlockArgument>(actualReset)) {
1236 mlir::DominanceInfo dom(module);
1237 // The first op in `opsToUpdate` is the top-most op in the module, since
1238 // the ops and blocks are traversed in a depth-first, top-to-bottom order
1239 // in `walk`. So we can simply check if the local reset declaration is
1240 // before the first op to find out if we need to move anything.
1241 auto *resetOp = actualReset.getDefiningOp();
1242 if (!opsToUpdate.empty() && !dom.dominates(resetOp, opsToUpdate[0])) {
1243 LLVM_DEBUG(llvm::dbgs()
1244 << "- Reset doesn
't dominate all uses, needs to be moved\n");
1246 // If the node can't be moved because its input doesn
't dominate the
1247 // target location, convert it to a wire.
1248 auto nodeOp = dyn_cast<NodeOp>(resetOp);
1249 if (nodeOp && !dom.dominates(nodeOp.getInput(), opsToUpdate[0])) {
1250 LLVM_DEBUG(llvm::dbgs()
1251 << "- Promoting node to wire for move: " << nodeOp << "\n");
1252 auto builder = ImplicitLocOpBuilder::atBlockBegin(nodeOp.getLoc(),
1253 nodeOp->getBlock());
1254 auto wireOp = WireOp::create(
1255 builder, nodeOp.getResult().getType(), nodeOp.getNameAttr(),
1256 nodeOp.getNameKindAttr(), nodeOp.getAnnotationsAttr(),
1257 nodeOp.getInnerSymAttr(), nodeOp.getForceableAttr());
1258 // Don't
delete the node, since it might be in use in worklists.
1259 nodeOp->replaceAllUsesWith(wireOp);
1260 nodeOp->removeAttr(nodeOp.getInnerSymAttrName());
1264 nodeOp.setNameKind(NameKindEnum::DroppableName);
1265 nodeOp.setAnnotationsAttr(ArrayAttr::get(builder.getContext(), {}));
1266 builder.setInsertionPointAfter(nodeOp);
1267 emitConnect(builder, wireOp.getResult(), nodeOp.getResult());
1269 actualReset = wireOp.getResult();
1270 domain.localReset = wireOp.getResult();
1275 Block *targetBlock = dom.findNearestCommonDominator(
1276 resetOp->getBlock(), opsToUpdate[0]->getBlock());
1278 if (targetBlock != resetOp->getBlock())
1279 llvm::dbgs() <<
"- Needs to be moved to different block\n";
1288 auto getParentInBlock = [](Operation *op,
Block *block) {
1289 while (op && op->getBlock() != block)
1290 op = op->getParentOp();
1293 auto *resetOpInTarget = getParentInBlock(resetOp, targetBlock);
1294 auto *firstOpInTarget = getParentInBlock(opsToUpdate[0], targetBlock);
1300 if (resetOpInTarget->isBeforeInBlock(firstOpInTarget))
1301 resetOp->moveBefore(resetOpInTarget);
1303 resetOp->moveBefore(firstOpInTarget);
1308 for (
auto *op : opsToUpdate)
1309 if (failed(implementFullReset(op, module, actualReset)))
1317LogicalResult FullResetRunner::implementFullReset(FInstanceLike inst,
1318 StringAttr moduleName,
1319 Value actualReset) {
1323 auto *node = instanceGraph->
lookup(moduleName);
1324 auto refModule = dyn_cast<FModuleOp>(*node->getModule());
1327 auto *domainIt = domains.find(refModule);
1328 if (domainIt == domains.end() || domainIt->second.empty())
1330 auto &domain = domainIt->second.back().first;
1331 assert(domain &&
"null domains should not be listed");
1333 ImplicitLocOpBuilder builder(inst.getLoc(), inst);
1335 LLVM_DEBUG(llvm::dbgs() << (actualReset ?
"- Update " :
"- Tie-off ")
1336 << inst->getName() <<
" '" << inst.getInstanceName()
1341 if (!domain.localReset) {
1342 LLVM_DEBUG(llvm::dbgs() <<
" - Adding new result as reset\n");
1343 auto newInstOp = inst.cloneWithInsertedPortsAndReplaceUses(
1345 {domain.resetName, domain.resetType, Direction::In}}});
1346 instReset = newInstOp->getResult(0);
1350 }
else if (domain.existingPort.has_value()) {
1351 auto idx = *domain.existingPort;
1352 instReset = inst->getResult(idx);
1353 LLVM_DEBUG(llvm::dbgs() <<
" - Using result #" << idx <<
" as reset\n");
1362 builder.setInsertionPointAfter(inst);
1369 LLVM_DEBUG(llvm::dbgs() <<
" - Tying off reset to constant 0\n");
1371 actualReset = SpecialConstantOp::create(builder, domain.resetType,
false);
1373 actualReset = ConstantOp::create(
1374 builder, UIntType::get(builder.getContext(), 1), APInt(1, 0));
1378 assert(instReset && actualReset);
1386LogicalResult FullResetRunner::implementFullReset(Operation *op,
1388 Value actualReset) {
1389 ImplicitLocOpBuilder builder(op->getLoc(), op);
1392 if (
auto instOp = dyn_cast<FInstanceLike>(op))
1393 return implementFullReset(
1394 instOp, cast<StringAttr>(instOp.getReferencedModuleNamesAttr()[0]),
1402 if (
auto regOp = dyn_cast<RegOp>(op)) {
1403 LLVM_DEBUG(llvm::dbgs() <<
"- Adding full reset to " << regOp <<
"\n");
1405 auto newRegOp = RegResetOp::create(
1406 builder, regOp.getResult().getType(), regOp.getClockVal(), actualReset,
1407 zero, regOp.getNameAttr(), regOp.getNameKindAttr(),
1408 regOp.getAnnotations(), regOp.getInnerSymAttr(),
1409 regOp.getForceableAttr(), regOp.getInitialAttr());
1410 regOp.getResult().replaceAllUsesWith(newRegOp.getResult());
1411 if (regOp.getForceable())
1412 regOp.getRef().replaceAllUsesWith(newRegOp.getRef());
1418 if (
auto regOp = dyn_cast<RegResetOp>(op)) {
1423 LLVM_DEBUG(llvm::dbgs() <<
"- Skipping (has reset) " << regOp <<
"\n");
1426 if (failed(regOp.verifyInvariants()))
1430 LLVM_DEBUG(llvm::dbgs() <<
"- Updating reset of " << regOp <<
"\n");
1432 auto reset = regOp.getResetSignal();
1433 auto value = regOp.getResetValue();
1439 builder.setInsertionPointAfterValue(regOp.getResult());
1440 auto mux = MuxPrimOp::create(builder, reset, value, regOp.getResult());
1444 builder.setInsertionPoint(regOp);
1446 regOp.getResetSignalMutable().assign(actualReset);
1447 regOp.getResetValueMutable().assign(zero);
1454 :
public circt::firrtl::impl::FullResetBase<FullResetPass> {
1455 using FullResetBase::FullResetBase;
1457 void runOnOperation()
override {
1458 auto &ig = getAnalysis<InstanceGraph>();
1459 auto &instanceInfo = getAnalysis<InstanceInfo>();
1460 if (failed(
runFullReset(getOperation(), ig, instanceInfo,
1461 convertAsyncDomainMems)))
1462 return signalPassFailure();
1463 markAnalysesPreserved<InstanceGraph, InstanceInfo>();
assert(baseType &&"element must be base type")
static Value createZeroValue(ImplicitLocOpBuilder &builder, FIRRTLBaseType type, SmallDenseMap< FIRRTLBaseType, Value > &cache)
Construct a zero value of the given type using the given builder.
static StringAttr getResetName(Value reset)
Return the name of a reset.
static bool insertResetMux(ImplicitLocOpBuilder &builder, Value target, Value reset, Value resetValue)
Helper function that inserts reset multiplexer into all ConnectOps with the given target.
static std::pair< StringAttr, FModuleOp > getResetNameAndModule(Value reset)
Return the name and parent module of a reset.
false return failure()
Parse an optional keyword or string and set instance into 'result'.
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
FIRRTLBaseType getConstType(bool isConst) const
Return a 'const' or non-'const' version of this type.
This class implements the same functionality as TypeSwitch except that it uses firrtl::type_dyn_cast ...
FIRRTLTypeSwitch< T, ResultT > & Case(CallableT &&caseFn)
Add a case on the given type.
This graph tracks modules and where they are instantiated.
bool anyInstanceInEffectiveDesign(igraph::ModuleOpInterface op)
Return true if any instance of this module is within (or transitively within) the effective design.
HW-specific instance graph with a virtual entry node linking to all publicly visible modules.
virtual void replaceInstance(InstanceOpInterface inst, InstanceOpInterface newInst)
Replaces an instance of a module with another instance.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
An instance path composed of a series of instances.
BaseTy type_cast(Type type)
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs, bool warnOnTruncation=false)
Emit a connect between two values.
void runCombMemsToRegOfVec(FModuleOp mod, bool ignoreReadEnable, unsigned &numConverted)
mlir::TypedValue< FIRRTLBaseType > FIRRTLBaseValue
igraph::InstancePathCache InstancePathCache
BaseTy type_dyn_cast(Type type)
LogicalResult runFullReset(CircuitOp circuit, InstanceGraph &ig, InstanceInfo &instanceInfo, bool convertAsyncDomainMems=false)
static bool operator==(const ModulePort &a, const ModulePort &b)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
llvm::raw_ostream & debugHeader(const llvm::Twine &str, unsigned width=80)
Write a "header"-like string to the debug stream with a certain width.
bool operator!=(uint64_t a, const FVInt &b)
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
A data structure that caches and provides paths to module instances in the IR.