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"
39using namespace firrtl;
41using circt::igraph::InstanceOpInterface;
45using llvm::SmallDenseSet;
52 if (
auto arg = dyn_cast<BlockArgument>(reset)) {
53 auto module = cast<FModuleOp>(arg.getParentRegion()->getParentOp());
54 return {
module.getPortNameAttr(arg.getArgNumber()), module};
56 auto *op = reset.getDefiningOp();
57 return {op->getAttrOfType<StringAttr>(
"name"),
58 op->getParentOfType<FModuleOp>()};
87 std::optional<unsigned> existingPort;
90 ResetDomain() =
default;
93 ResetDomain(Value rootReset)
94 : rootReset(rootReset), resetName(
getResetName(rootReset)),
95 resetType(rootReset.getType()) {}
98 explicit operator bool()
const {
return static_cast<bool>(rootReset); }
102inline bool operator==(
const ResetDomain &a,
const ResetDomain &b) {
103 return (a.isTop == b.isTop && a.resetName == b.resetName &&
104 a.resetType == b.resetType);
106inline bool operator!=(
const ResetDomain &a,
const ResetDomain &b) {
115 auto it = cache.find(type);
116 if (it != cache.end())
118 auto nullBit = [&]() {
120 builder, UIntType::get(builder.getContext(), 1,
true),
125 .
Case<ClockType>([&](
auto type) {
126 return AsClockPrimOp::create(builder, nullBit());
128 .Case<AsyncResetType>([&](
auto type) {
129 return AsAsyncResetPrimOp::create(builder, nullBit());
131 .Case<SIntType, UIntType>([&](
auto type) {
132 return ConstantOp::create(
133 builder, type, APInt::getZero(type.getWidth().value_or(1)));
135 .Case<FEnumType>([&](
auto type) -> Value {
138 if (type.getNumElements() != 0 &&
139 type.getElement(0).value.getValue().isZero()) {
140 const auto &element = type.getElement(0);
142 return FEnumCreateOp::create(builder, type, element.name, value);
144 auto value = ConstantOp::create(builder,
145 UIntType::get(builder.getContext(),
148 APInt::getZero(type.getBitWidth()));
149 return BitCastOp::create(builder, type, value);
151 .Case<BundleType>([&](
auto type) {
152 auto wireOp = WireOp::create(builder, type);
153 for (
unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
154 auto fieldType = type.getElementTypePreservingConst(i);
157 SubfieldOp::create(builder, fieldType, wireOp.getResult(), i);
160 return wireOp.getResult();
162 .Case<FVectorType>([&](
auto type) {
163 auto wireOp = WireOp::create(builder, type);
165 builder, type.getElementTypePreservingConst(), cache);
166 for (
unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
167 auto acc = SubindexOp::create(builder, zero.getType(),
168 wireOp.getResult(), i);
171 return wireOp.getResult();
173 .Case<ResetType, AnalogType>(
174 [&](
auto type) {
return InvalidValueOp::create(builder, type); })
176 llvm_unreachable(
"switch handles all types");
179 cache.insert({type, value});
196 Value reset, Value resetValue) {
200 bool resetValueUsed =
false;
202 for (
auto &use : target.getUses()) {
203 Operation *useOp = use.getOwner();
204 builder.setInsertionPoint(useOp);
205 TypeSwitch<Operation *>(useOp)
208 .Case<ConnectOp, MatchingConnectOp>([&](
auto op) {
209 if (op.getDest() != target)
211 LLVM_DEBUG(llvm::dbgs() <<
" - Insert mux into " << op <<
"\n");
213 MuxPrimOp::create(builder, reset, resetValue, op.getSrc());
214 op.getSrcMutable().assign(muxOp);
215 resetValueUsed =
true;
218 .Case<SubfieldOp>([&](
auto op) {
220 SubfieldOp::create(builder, resetValue, op.getFieldIndexAttr());
222 resetValueUsed =
true;
224 resetSubValue.erase();
227 .Case<SubindexOp>([&](
auto op) {
229 SubindexOp::create(builder, resetValue, op.getIndexAttr());
231 resetValueUsed =
true;
233 resetSubValue.erase();
236 .Case<SubaccessOp>([&](
auto op) {
237 if (op.getInput() != target)
240 SubaccessOp::create(builder, resetValue, op.getIndex());
242 resetValueUsed =
true;
244 resetSubValue.erase();
247 return resetValueUsed;
258enum class ResetKind { Async, Sync };
260static StringRef resetKindToStringRef(
const ResetKind &kind) {
262 case ResetKind::Async:
264 case ResetKind::Sync:
267 llvm_unreachable(
"unhandled reset kind");
272struct MemToRegOfVecConverter {
273 explicit MemToRegOfVecConverter(
bool ignoreReadEnable)
274 : ignoreReadEnable(ignoreReadEnable) {}
276 void runOnModule(FModuleOp mod) {
278 mod.getBodyBlock()->walk([&](MemOp memOp) {
279 LLVM_DEBUG(llvm::dbgs() <<
"\n Memory op:" << memOp);
281 auto firMem = memOp.getSummary();
288 if (firMem.isSeqMem())
291 generateMemory(memOp, firMem);
296 Value addPipelineStages(ImplicitLocOpBuilder &b,
size_t stages, Value clock,
297 Value pipeInput, StringRef name, Value gate = {}) {
302 auto reg = RegOp::create(b, pipeInput.getType(), clock, name).getResult();
304 WhenOp::create(b, gate,
false,
305 [&]() { MatchingConnectOp::create(b, reg, pipeInput); });
307 MatchingConnectOp::create(b, reg, pipeInput);
315 Value getClock(ImplicitLocOpBuilder &builder, Value bundle) {
316 return SubfieldOp::create(builder, bundle,
"clk");
319 Value getAddr(ImplicitLocOpBuilder &builder, Value bundle) {
320 return SubfieldOp::create(builder, bundle,
"addr");
323 Value getWmode(ImplicitLocOpBuilder &builder, Value bundle) {
324 return SubfieldOp::create(builder, bundle,
"wmode");
327 Value getEnable(ImplicitLocOpBuilder &builder, Value bundle) {
328 return SubfieldOp::create(builder, bundle,
"en");
331 Value getMask(ImplicitLocOpBuilder &builder, Value bundle) {
332 auto bType = type_cast<BundleType>(bundle.getType());
333 if (bType.getElement(
"mask"))
334 return SubfieldOp::create(builder, bundle,
"mask");
335 return SubfieldOp::create(builder, bundle,
"wmask");
338 Value getData(ImplicitLocOpBuilder &builder, Value bundle,
339 bool getWdata =
false) {
340 auto bType = type_cast<BundleType>(bundle.getType());
341 if (bType.getElement(
"data"))
342 return SubfieldOp::create(builder, bundle,
"data");
343 if (bType.getElement(
"rdata") && !getWdata)
344 return SubfieldOp::create(builder, bundle,
"rdata");
345 return SubfieldOp::create(builder, bundle,
"wdata");
348 void generateRead(
const FirMemory &firMem, Value clock, Value
addr,
349 Value enable, Value
data, Value regOfVec,
350 ImplicitLocOpBuilder &builder) {
351 if (ignoreReadEnable) {
354 for (
size_t j = 0, e = firMem.
readLatency; j != e; ++j) {
355 auto enLast = enable;
357 enable = addPipelineStages(builder, 1, clock, enable,
"en");
358 addr = addPipelineStages(builder, 1, clock,
addr,
"addr", enLast);
364 addPipelineStages(builder, firMem.
readLatency, clock, enable,
"en");
370 Value
rdata = SubaccessOp::create(builder, regOfVec,
addr);
371 if (!ignoreReadEnable) {
373 MatchingConnectOp::create(
374 builder,
data, InvalidValueOp::create(builder,
data.getType()));
376 WhenOp::create(builder, enable,
false, [&]() {
377 MatchingConnectOp::create(builder,
data,
rdata);
381 MatchingConnectOp::create(builder,
data,
rdata);
385 void generateWrite(
const FirMemory &firMem, Value clock, Value
addr,
386 Value enable, Value maskBits, Value wdataIn,
387 Value regOfVec, ImplicitLocOpBuilder &builder) {
392 addr = addPipelineStages(builder, numStages, clock,
addr,
"addr");
393 enable = addPipelineStages(builder, numStages, clock, enable,
"en");
394 wdataIn = addPipelineStages(builder, numStages, clock, wdataIn,
"wdata");
395 maskBits = addPipelineStages(builder, numStages, clock, maskBits,
"wmask");
404 SmallVector<std::tuple<Value, Value, Value>, 8> loweredRegDataMaskFields;
419 if (!getFields(
rdata, wdataIn, maskBits, loweredRegDataMaskFields,
421 wdataIn.getDefiningOp()->emitOpError(
422 "Cannot convert memory to bank of registers");
426 WhenOp::create(builder, enable,
false, [&]() {
428 for (
auto regDataMask : loweredRegDataMaskFields) {
429 auto regField = std::get<0>(regDataMask);
430 auto dataField = std::get<1>(regDataMask);
431 auto maskField = std::get<2>(regDataMask);
433 WhenOp::create(builder, maskField,
false, [&]() {
434 MatchingConnectOp::create(builder, regField, dataField);
440 void generateReadWrite(
const FirMemory &firMem, Value clock, Value
addr,
441 Value enable, Value maskBits, Value wdataIn,
442 Value rdataOut, Value
wmode, Value regOfVec,
443 ImplicitLocOpBuilder &builder) {
448 addr = addPipelineStages(builder, numStages, clock,
addr,
"addr");
449 enable = addPipelineStages(builder, numStages, clock, enable,
"en");
450 wdataIn = addPipelineStages(builder, numStages, clock, wdataIn,
"wdata");
451 maskBits = addPipelineStages(builder, numStages, clock, maskBits,
"wmask");
454 Value
rdata = SubaccessOp::create(builder, regOfVec,
addr);
456 SmallVector<std::tuple<Value, Value, Value>, 8> loweredRegDataMaskFields;
457 if (!getFields(
rdata, wdataIn, maskBits, loweredRegDataMaskFields,
459 wdataIn.getDefiningOp()->emitOpError(
460 "Cannot convert memory to bank of registers");
464 MatchingConnectOp::create(
465 builder, rdataOut, InvalidValueOp::create(builder, rdataOut.getType()));
467 WhenOp::create(builder, enable,
false, [&]() {
470 builder,
wmode,
true,
474 for (
auto regDataMask : loweredRegDataMaskFields) {
475 auto regField = std::get<0>(regDataMask);
476 auto dataField = std::get<1>(regDataMask);
477 auto maskField = std::get<2>(regDataMask);
480 builder, maskField,
false, [&]() {
481 MatchingConnectOp::create(builder, regField, dataField);
486 [&]() { MatchingConnectOp::create(builder, rdataOut,
rdata); });
497 bool getFields(Value reg, Value input, Value
mask,
498 SmallVectorImpl<std::tuple<Value, Value, Value>> &results,
499 ImplicitLocOpBuilder &builder) {
503 if (
auto bundle = type_dyn_cast<BundleType>(inType)) {
504 if (
auto mBundle = type_dyn_cast<BundleType>(maskType))
505 return mBundle.getNumElements() == bundle.getNumElements();
506 }
else if (
auto vec = type_dyn_cast<FVectorType>(inType)) {
507 if (
auto mVec = type_dyn_cast<FVectorType>(maskType))
508 return mVec.getNumElements() == vec.getNumElements();
514 std::function<bool(Value, Value, Value)> flatAccess =
515 [&](Value
reg, Value input, Value
mask) ->
bool {
516 FIRRTLType inType = type_cast<FIRRTLType>(input.getType());
517 if (!isValidMask(inType, type_cast<FIRRTLType>(
mask.getType()))) {
518 input.getDefiningOp()->emitOpError(
"Mask type is not valid");
522 .
Case<BundleType>([&](BundleType bundle) {
523 for (
size_t i = 0, e = bundle.getNumElements(); i != e; ++i) {
524 auto regField = SubfieldOp::create(builder, reg, i);
525 auto inputField = SubfieldOp::create(builder, input, i);
526 auto maskField = SubfieldOp::create(builder,
mask, i);
527 if (!flatAccess(regField, inputField, maskField))
532 .Case<FVectorType>([&](
auto vector) {
533 for (
size_t i = 0, e = vector.getNumElements(); i != e; ++i) {
534 auto regField = SubindexOp::create(builder, reg, i);
535 auto inputField = SubindexOp::create(builder, input, i);
536 auto maskField = SubindexOp::create(builder,
mask, i);
537 if (!flatAccess(regField, inputField, maskField))
542 .Case<IntType>([&](
auto iType) {
543 results.push_back({
reg, input,
mask});
544 return iType.getWidth().has_value();
546 .Default([&](
auto) {
return false; });
548 if (flatAccess(reg, input,
mask))
554 void generateMemory(MemOp memOp,
FirMemory &firMem) {
555 ImplicitLocOpBuilder builder(memOp.getLoc(), memOp);
556 auto dataType = memOp.getDataType();
558 auto innerSym = memOp.getInnerSym();
559 SmallVector<Value> debugPorts;
562 for (
size_t index = 0, rend = memOp.getNumResults(); index < rend;
564 auto result = memOp.getResult(index);
565 if (type_isa<RefType>(result.getType())) {
566 debugPorts.push_back(result);
571 auto wire = WireOp::create(
572 builder, result.getType(),
573 (memOp.getName() +
"_" + memOp.getPortName(index)).str(),
574 memOp.getNameKind());
575 result.replaceAllUsesWith(wire.getResult());
576 result = wire.getResult();
578 auto adr = getAddr(builder, result);
579 auto enb = getEnable(builder, result);
580 auto clk = getClock(builder, result);
581 auto dta = getData(builder, result);
586 RegOp::create(builder, FVectorType::get(dataType, firMem.
depth),
587 clk, memOp.getNameAttr());
590 if (!memOp.getAnnotationsAttr().empty())
591 regOfVec.setAnnotationsAttr(memOp.getAnnotationsAttr());
593 regOfVec.setInnerSymAttr(memOp.getInnerSymAttr());
595 auto portKind = memOp.getPortKind(index);
596 if (portKind == MemOp::PortKind::Read) {
597 generateRead(firMem,
clk, adr, enb, dta, regOfVec.getResult(), builder);
598 }
else if (portKind == MemOp::PortKind::Write) {
599 auto mask = getMask(builder, result);
600 generateWrite(firMem,
clk, adr, enb,
mask, dta, regOfVec.getResult(),
603 auto wmode = getWmode(builder, result);
604 auto wDta = getData(builder, result,
true);
605 auto mask = getMask(builder, result);
606 generateReadWrite(firMem,
clk, adr, enb,
mask, wDta, dta,
wmode,
607 regOfVec.getResult(), builder);
614 for (
auto r : debugPorts)
615 r.replaceAllUsesWith(RefSendOp::create(builder, regOfVec.getResult()));
618 bool ignoreReadEnable =
false;
619 unsigned numConverted = 0;
624 unsigned &numConverted) {
625 MemToRegOfVecConverter converter(ignoreReadEnable);
626 converter.runOnModule(mod);
627 numConverted += converter.numConverted;
631struct FullResetRunner {
634 InstanceInfo &instanceInfo,
bool convertAsyncDomainMems)
635 : circuit(circuit), instanceGraph(&ig),
636 instancePathCache(&instancePathCache), instanceInfo(&instanceInfo),
637 convertAsyncDomainMems(convertAsyncDomainMems) {}
644 LogicalResult collectAnnos();
650 FailureOr<std::optional<Value>> collectAnnos(FModuleOp module);
652 LogicalResult buildDomains();
653 void buildDomains(FModuleOp module,
const InstancePath &instPath,
655 unsigned indent = 0);
657 void convertMemsInAsyncDomains();
659 LogicalResult determineImpl();
660 LogicalResult determineImpl(FModuleOp module, ResetDomain &domain);
662 LogicalResult implementFullReset();
663 LogicalResult implementFullReset(FModuleOp module, ResetDomain &domain);
664 LogicalResult implementFullReset(Operation *op, FModuleOp module,
668 LogicalResult implementFullReset(FInstanceLike inst, StringAttr moduleName,
676 DenseMap<Operation *, Value> annotatedResets;
691 bool convertAsyncDomainMems =
false;
695LogicalResult FullResetRunner::run() {
696 if (failed(collectAnnos()))
698 if (failed(buildDomains()))
700 if (convertAsyncDomainMems)
701 convertMemsInAsyncDomains();
702 if (failed(determineImpl()))
704 if (failed(implementFullReset()))
709void FullResetRunner::convertMemsInAsyncDomains() {
710 SmallVector<FModuleOp> asyncDomainMods;
711 for (
auto &[mod, entries] : domains) {
714 auto &domain = entries.back().first;
715 if (!domain.rootReset)
717 if (!type_isa<AsyncResetType>(domain.resetType))
719 if (!instanceInfo->anyInstanceInEffectiveDesign(mod))
721 asyncDomainMods.push_back(mod);
723 if (asyncDomainMods.empty())
727 llvm::dbgs() <<
"\n";
728 debugHeader(
"Convert comb mems in async full-reset domains") <<
"\n\n";
729 for (
auto mod : asyncDomainMods)
733 mlir::parallelForEach(
734 circuit.getContext(), asyncDomainMods, [&](FModuleOp mod) {
735 unsigned converted = 0;
736 runCombMemsToRegOfVec(mod, false, converted);
742 bool convertAsyncDomainMems) {
744 return FullResetRunner(circuit, ig, instancePathCache, instanceInfo,
745 convertAsyncDomainMems)
753LogicalResult FullResetRunner::collectAnnos() {
755 llvm::dbgs() <<
"\n";
758 SmallVector<std::pair<FModuleOp, std::optional<Value>>> results;
759 for (
auto module : circuit.getOps<FModuleOp>())
760 results.push_back({module, {}});
762 if (failed(mlir::failableParallelForEach(
763 circuit.getContext(), results, [&](
auto &moduleAndResult) {
764 auto result = collectAnnos(moduleAndResult.first);
767 moduleAndResult.second = *result;
772 for (
auto [module, reset] : results)
773 if (reset.has_value())
774 annotatedResets.insert({module, *reset});
778FailureOr<std::optional<Value>>
779FullResetRunner::collectAnnos(FModuleOp module) {
780 bool anyFailed =
false;
787 if (anno.
isClass(excludeFromFullResetAnnoClass)) {
789 conflictingAnnos.insert({anno, module.getLoc()});
792 if (anno.
isClass(fullResetAnnoClass)) {
794 module.emitError("''FullResetAnnotation' cannot target module; must
"
795 "target port or wire/node instead
");
803 // Consume any reset annotations on module ports.
805 // Helper for checking annotations and determining the reset
806 auto checkAnnotations = [&](Annotation anno, Value arg) {
807 if (anno.isClass(fullResetAnnoClass)) {
808 ResetKind expectedResetKind;
809 if (auto rt = anno.getMember<StringAttr>("resetType
")) {
811 expectedResetKind = ResetKind::Sync;
812 } else if (rt == "async
") {
813 expectedResetKind = ResetKind::Async;
815 mlir::emitError(arg.getLoc(),
816 "'FullResetAnnotation' requires resetType ==
'sync' "
817 "|
'async', but got resetType ==
")
823 mlir::emitError(arg.getLoc(),
824 "'FullResetAnnotation' requires resetType ==
"
825 "'sync' |
'async', but got no resetType
");
829 // Check that the type is well-formed
830 bool isAsync = expectedResetKind == ResetKind::Async;
831 bool validUint = false;
832 if (auto uintT = dyn_cast<UIntType>(arg.getType()))
833 validUint = uintT.getWidth() == 1;
834 if ((isAsync && !isa<AsyncResetType>(arg.getType())) ||
835 (!isAsync && !validUint)) {
836 auto kind = resetKindToStringRef(expectedResetKind);
837 mlir::emitError(arg.getLoc(),
838 "'FullResetAnnotation' with resetType ==
'")
839 << kind << "' must target
" << kind << " reset, but targets
"
846 conflictingAnnos.insert({anno, reset.getLoc()});
850 if (anno.isClass(excludeFromFullResetAnnoClass)) {
852 mlir::emitError(arg.getLoc(),
853 "'ExcludeFromFullResetAnnotation' cannot
"
854 "target port/wire/node; must target
module instead");
862 Value arg =
module.getArgument(argNum);
863 return checkAnnotations(anno, arg);
869 module.getBody().walk([&](Operation *op) {
871 if (!isa<WireOp, NodeOp>(op)) {
872 if (AnnotationSet::hasAnnotation(op, fullResetAnnoClass,
873 excludeFromFullResetAnnoClass)) {
876 "reset annotations must target module, port, or wire/node");
884 auto arg = op->getResult(0);
885 return checkAnnotations(anno, arg);
894 if (!ignore && !reset) {
895 LLVM_DEBUG(llvm::dbgs()
896 <<
"No reset annotation for " << module.getName() <<
"\n");
897 return std::optional<Value>();
901 if (conflictingAnnos.size() > 1) {
902 auto diag =
module.emitError("multiple reset annotations on module '")
903 << module.getName() << "'";
904 for (
auto &annoAndLoc : conflictingAnnos)
905 diag.attachNote(annoAndLoc.second)
906 <<
"conflicting " << annoAndLoc.first.getClassAttr() <<
":";
912 llvm::dbgs() <<
"Annotated reset for " <<
module.getName() << ": ";
914 llvm::dbgs() <<
"no domain\n";
915 else if (
auto arg = dyn_cast<BlockArgument>(reset))
916 llvm::dbgs() <<
"port " <<
module.getPortName(arg.getArgNumber()) << "\n";
918 llvm::dbgs() <<
"wire "
919 << reset.getDefiningOp()->getAttrOfType<StringAttr>(
"name")
925 return std::optional<Value>(reset);
937LogicalResult FullResetRunner::buildDomains() {
939 llvm::dbgs() <<
"\n";
944 auto &instGraph = *instanceGraph;
950 dyn_cast_or_null<FModuleOp>(node.
getModule().getOperation()))
951 buildDomains(module,
InstancePath{}, Value{}, instGraph);
955 bool anyFailed =
false;
956 for (
auto &it : domains) {
957 auto module = cast<FModuleOp>(it.first);
958 auto &domainConflicts = it.second;
959 if (domainConflicts.size() <= 1)
963 SmallDenseSet<Value> printedDomainResets;
964 auto diag =
module.emitError("module '")
966 << "' instantiated in different reset domains";
967 for (
auto &it : domainConflicts) {
968 ResetDomain &domain = it.first;
969 const auto &path = it.second;
970 auto inst = path.leaf();
971 auto loc = path.empty() ?
module.getLoc() : inst.getLoc();
972 auto ¬e = diag.attachNote(loc);
976 note <<
"root instance";
978 note <<
"instance '";
981 [&](InstanceOpInterface inst) { note << inst.getInstanceName(); },
982 [&]() { note <<
"/"; });
988 if (domain.rootReset) {
990 note <<
" reset domain rooted at '" << nameAndModule.first.getValue()
991 <<
"' of module '" << nameAndModule.second.getName() <<
"'";
994 if (printedDomainResets.insert(domain.rootReset).second) {
995 diag.attachNote(domain.rootReset.getLoc())
996 <<
"reset domain '" << nameAndModule.first.getValue()
997 <<
"' of module '" << nameAndModule.second.getName()
998 <<
"' declared here:";
1001 note <<
" no reset domain";
1004 return failure(anyFailed);
1007void FullResetRunner::buildDomains(FModuleOp module,
1012 llvm::dbgs().indent(indent * 2) <<
"Visiting ";
1013 if (instPath.
empty())
1014 llvm::dbgs() <<
"$root";
1016 llvm::dbgs() << instPath.
leaf().getInstanceName();
1017 llvm::dbgs() <<
" (" <<
module.getName() << ")\n";
1022 auto it = annotatedResets.find(module);
1023 if (it != annotatedResets.end()) {
1026 if (
auto localReset = it->second)
1027 domain = ResetDomain(localReset);
1028 domain.isTop =
true;
1029 }
else if (parentReset) {
1031 domain = ResetDomain(parentReset);
1038 auto &entries = domains[module];
1039 if (domain.rootReset)
1040 if (llvm::all_of(entries,
1041 [&](
const auto &entry) {
return entry.first != domain; }))
1042 entries.push_back({domain, instPath});
1045 for (
auto *record : *instGraph[module]) {
1046 auto submodule = dyn_cast<FModuleOp>(*record->getTarget()->getModule());
1050 instancePathCache->appendInstance(instPath, record->getInstance());
1051 buildDomains(submodule, childPath, domain.rootReset, instGraph, indent + 1);
1056LogicalResult FullResetRunner::determineImpl() {
1057 auto anyFailed =
false;
1059 llvm::dbgs() <<
"\n";
1060 debugHeader(
"Determine implementation") <<
"\n\n";
1062 for (
auto &it : domains) {
1063 auto module = cast<FModuleOp>(it.first);
1064 auto &entries = it.second;
1066 if (entries.empty())
1068 auto &domain = entries.back().first;
1069 if (failed(determineImpl(module, domain)))
1072 return failure(anyFailed);
1090LogicalResult FullResetRunner::determineImpl(FModuleOp module,
1091 ResetDomain &domain) {
1095 LLVM_DEBUG(llvm::dbgs() <<
"Planning reset for " << module.getName() <<
"\n");
1100 LLVM_DEBUG(llvm::dbgs()
1101 <<
"- Rooting at local value " << domain.resetName <<
"\n");
1102 domain.localReset = domain.rootReset;
1103 if (
auto blockArg = dyn_cast<BlockArgument>(domain.rootReset))
1104 domain.existingPort = blockArg.getArgNumber();
1110 auto neededName = domain.resetName;
1111 auto neededType = domain.resetType;
1112 LLVM_DEBUG(llvm::dbgs() <<
"- Looking for existing port " << neededName
1114 auto portNames =
module.getPortNames();
1115 auto *portIt = llvm::find(portNames, neededName);
1118 if (portIt == portNames.end()) {
1119 LLVM_DEBUG(llvm::dbgs() <<
"- Creating new port " << neededName <<
"\n");
1120 domain.resetName = neededName;
1124 LLVM_DEBUG(llvm::dbgs() <<
"- Reusing existing port " << neededName <<
"\n");
1127 auto portNo = std::distance(portNames.begin(), portIt);
1128 auto portType =
module.getPortType(portNo);
1129 if (portType != neededType) {
1130 auto diag = emitError(module.getPortLocation(portNo),
"module '")
1131 <<
module.getName() << "' is in reset domain requiring port '"
1132 << domain.resetName.getValue() << "' to have type "
1133 << domain.resetType << ", but has type " << portType;
1134 diag.attachNote(domain.rootReset.getLoc()) <<
"reset domain rooted here";
1139 domain.existingPort = portNo;
1140 domain.localReset =
module.getArgument(portNo);
1149LogicalResult FullResetRunner::implementFullReset() {
1151 llvm::dbgs() <<
"\n";
1154 for (
auto &it : domains) {
1155 auto module = cast<FModuleOp>(it.first);
1156 auto &entries = it.second;
1160 if (!entries.empty())
1161 domain = entries.back().first;
1162 if (failed(implementFullReset(module, domain)))
1173LogicalResult FullResetRunner::implementFullReset(FModuleOp module,
1174 ResetDomain &domain) {
1179 SmallVector<FInstanceLike> instances;
1180 module.walk([&](FInstanceLike instOp) { instances.push_back(instOp); });
1182 if (!instances.empty())
1183 llvm::dbgs() <<
"Tie off instances in " << module.getName() <<
"\n";
1185 for (
auto instOp : instances)
1186 if (failed(implementFullReset(instOp, module, Value())))
1191 LLVM_DEBUG(llvm::dbgs() <<
"Implementing full reset for " << module.getName()
1195 auto *
context =
module.getContext();
1197 annotations.addAnnotations(DictionaryAttr::get(
1199 StringAttr::get(
context, fullResetAnnoClass))));
1200 annotations.applyToOperation(module);
1203 auto actualReset = domain.localReset;
1204 if (!domain.localReset) {
1205 PortInfo portInfo{domain.resetName,
1209 domain.rootReset.getLoc()};
1210 module.insertPorts({{0, portInfo}});
1211 actualReset =
module.getArgument(0);
1212 LLVM_DEBUG(llvm::dbgs() <<
"- Inserted port " << domain.resetName <<
"\n");
1216 llvm::dbgs() <<
"- Using ";
1217 if (
auto blockArg = dyn_cast<BlockArgument>(actualReset))
1218 llvm::dbgs() <<
"port #" << blockArg.getArgNumber() <<
" ";
1220 llvm::dbgs() <<
"wire/node ";
1226 SmallVector<Operation *> opsToUpdate;
1227 module.walk([&](Operation *op) {
1228 if (isa<FInstanceLike, RegOp, RegResetOp>(op))
1229 opsToUpdate.push_back(op);
1236 if (!isa<BlockArgument>(actualReset)) {
1237 mlir::DominanceInfo dom(module);
1242 auto *resetOp = actualReset.getDefiningOp();
1243 if (!opsToUpdate.empty() && !dom.dominates(resetOp, opsToUpdate[0])) {
1244 LLVM_DEBUG(llvm::dbgs()
1245 <<
"- Reset doesn't dominate all uses, needs to be moved\n");
1249 auto nodeOp = dyn_cast<NodeOp>(resetOp);
1250 if (nodeOp && !dom.dominates(nodeOp.getInput(), opsToUpdate[0])) {
1251 LLVM_DEBUG(llvm::dbgs()
1252 <<
"- Promoting node to wire for move: " << nodeOp <<
"\n");
1253 auto builder = ImplicitLocOpBuilder::atBlockBegin(nodeOp.getLoc(),
1254 nodeOp->getBlock());
1255 auto wireOp = WireOp::create(
1256 builder, nodeOp.getResult().getType(), nodeOp.getNameAttr(),
1257 nodeOp.getNameKindAttr(), nodeOp.getAnnotationsAttr(),
1258 nodeOp.getInnerSymAttr(), nodeOp.getForceableAttr());
1260 nodeOp->replaceAllUsesWith(wireOp);
1261 nodeOp->removeAttr(nodeOp.getInnerSymAttrName());
1265 nodeOp.setNameKind(NameKindEnum::DroppableName);
1266 nodeOp.setAnnotationsAttr(ArrayAttr::get(builder.getContext(), {}));
1267 builder.setInsertionPointAfter(nodeOp);
1268 emitConnect(builder, wireOp.getResult(), nodeOp.getResult());
1270 actualReset = wireOp.getResult();
1271 domain.localReset = wireOp.getResult();
1276 Block *targetBlock = dom.findNearestCommonDominator(
1277 resetOp->getBlock(), opsToUpdate[0]->getBlock());
1279 if (targetBlock != resetOp->getBlock())
1280 llvm::dbgs() <<
"- Needs to be moved to different block\n";
1289 auto getParentInBlock = [](Operation *op,
Block *block) {
1290 while (op && op->getBlock() != block)
1291 op = op->getParentOp();
1294 auto *resetOpInTarget = getParentInBlock(resetOp, targetBlock);
1295 auto *firstOpInTarget = getParentInBlock(opsToUpdate[0], targetBlock);
1301 if (resetOpInTarget->isBeforeInBlock(firstOpInTarget))
1302 resetOp->moveBefore(resetOpInTarget);
1304 resetOp->moveBefore(firstOpInTarget);
1309 for (
auto *op : opsToUpdate)
1310 if (failed(implementFullReset(op, module, actualReset)))
1318LogicalResult FullResetRunner::implementFullReset(FInstanceLike inst,
1319 StringAttr moduleName,
1320 Value actualReset) {
1324 auto *node = instanceGraph->lookup(moduleName);
1325 auto refModule = dyn_cast<FModuleOp>(*node->
getModule());
1328 auto *domainIt = domains.find(refModule);
1329 if (domainIt == domains.end() || domainIt->second.empty())
1331 auto &domain = domainIt->second.back().first;
1332 assert(domain &&
"null domains should not be listed");
1334 ImplicitLocOpBuilder builder(inst.getLoc(), inst);
1336 LLVM_DEBUG(llvm::dbgs() << (actualReset ?
"- Update " :
"- Tie-off ")
1342 if (!domain.localReset) {
1343 LLVM_DEBUG(llvm::dbgs() <<
" - Adding new result as reset\n");
1344 auto newInstOp = inst.cloneWithInsertedPortsAndReplaceUses(
1346 {domain.resetName, domain.resetType, Direction::In}}});
1347 instReset = newInstOp->getResult(0);
1348 instanceGraph->replaceInstance(inst, newInstOp);
1351 }
else if (domain.existingPort.has_value()) {
1352 auto idx = *domain.existingPort;
1353 instReset = inst->getResult(idx);
1354 LLVM_DEBUG(llvm::dbgs() <<
" - Using result #" << idx <<
" as reset\n");
1363 builder.setInsertionPointAfter(inst);
1370 LLVM_DEBUG(llvm::dbgs() <<
" - Tying off reset to constant 0\n");
1371 if (type_isa<AsyncResetType>(domain.resetType))
1372 actualReset = SpecialConstantOp::create(builder, domain.resetType,
false);
1374 actualReset = ConstantOp::create(
1375 builder, UIntType::get(builder.getContext(), 1), APInt(1, 0));
1379 assert(instReset && actualReset);
1387LogicalResult FullResetRunner::implementFullReset(Operation *op,
1389 Value actualReset) {
1390 ImplicitLocOpBuilder builder(op->getLoc(), op);
1393 if (
auto instOp = dyn_cast<FInstanceLike>(op))
1394 return implementFullReset(
1395 instOp, cast<StringAttr>(instOp.getReferencedModuleNamesAttr()[0]),
1403 if (
auto regOp = dyn_cast<RegOp>(op)) {
1404 LLVM_DEBUG(llvm::dbgs() <<
"- Adding full reset to " << regOp <<
"\n");
1406 auto newRegOp = RegResetOp::create(
1407 builder, regOp.getResult().getType(), regOp.getClockVal(), actualReset,
1408 zero, regOp.getNameAttr(), regOp.getNameKindAttr(),
1409 regOp.getAnnotations(), regOp.getInnerSymAttr(),
1410 regOp.getForceableAttr(), regOp.getInitialAttr());
1411 regOp.getResult().replaceAllUsesWith(newRegOp.getResult());
1412 if (regOp.getForceable())
1413 regOp.getRef().replaceAllUsesWith(newRegOp.getRef());
1419 if (
auto regOp = dyn_cast<RegResetOp>(op)) {
1422 if (type_isa<AsyncResetType>(regOp.getResetSignal().getType()) ||
1423 type_isa<UIntType>(actualReset.getType())) {
1424 LLVM_DEBUG(llvm::dbgs() <<
"- Skipping (has reset) " << regOp <<
"\n");
1427 if (failed(regOp.verifyInvariants()))
1431 LLVM_DEBUG(llvm::dbgs() <<
"- Updating reset of " << regOp <<
"\n");
1433 auto reset = regOp.getResetSignal();
1434 auto value = regOp.getResetValue();
1440 builder.setInsertionPointAfterValue(regOp.getResult());
1441 auto mux = MuxPrimOp::create(builder, reset, value, regOp.getResult());
1445 builder.setInsertionPoint(regOp);
1447 regOp.getResetSignalMutable().assign(actualReset);
1448 regOp.getResetValueMutable().assign(zero);
1455 :
public circt::firrtl::impl::FullResetBase<FullResetPass> {
1456 using FullResetBase::FullResetBase;
1458 void runOnOperation()
override {
1459 auto &ig = getAnalysis<InstanceGraph>();
1460 auto &instanceInfo = getAnalysis<InstanceInfo>();
1461 if (failed(
runFullReset(getOperation(), ig, instanceInfo,
1462 convertAsyncDomainMems)))
1463 return signalPassFailure();
1464 markAnalysesPreserved<InstanceGraph, InstanceInfo>();
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
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.
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
static bool removePortAnnotations(Operation *module, llvm::function_ref< bool(unsigned, Annotation)> predicate)
Remove all port annotations from a module or extmodule for which predicate returns true.
This class provides a read-only projection of an annotation.
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.
HW-specific instance graph with a virtual entry node linking to all publicly visible modules.
This is a Node in the InstanceGraph.
bool noUses()
Return true if there are no more instances of this module.
auto getModule()
Get the module that this node is tracking.
An instance path composed of a series of instances.
InstanceOpInterface leaf() const
std::string getInstanceName(mlir::func::CallOp callOp)
A helper function to get the instance name.
mlir::TypedValue< FIRRTLBaseType > FIRRTLBaseValue
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)
LogicalResult runFullReset(CircuitOp circuit, InstanceGraph &ig, InstanceInfo &instanceInfo, bool convertAsyncDomainMems=false)
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
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)
This holds the name and type that describes the module's ports.
A data structure that caches and provides paths to module instances in the IR.