CIRCT 24.0.0git
Loading...
Searching...
No Matches
FullReset.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file defines the FullReset pass.
10//
11//===----------------------------------------------------------------------===//
12
21#include "circt/Support/Debug.h"
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"
27#include <atomic>
28
29#define DEBUG_TYPE "firrtl-full-reset"
30
31namespace circt {
32namespace firrtl {
33#define GEN_PASS_DEF_FULLRESET
34#include "circt/Dialect/FIRRTL/Passes.h.inc"
35} // namespace firrtl
36} // namespace circt
37
38using namespace circt;
39using namespace firrtl;
40
41using circt::igraph::InstanceOpInterface;
44using llvm::SmallDenseSet;
46using mlir::FailureOr;
47
48/// Return the name and parent module of a reset. The reset value must either be
49/// a module port or a wire/node operation.
50static std::pair<StringAttr, FModuleOp> getResetNameAndModule(Value reset) {
51 if (auto arg = dyn_cast<BlockArgument>(reset)) {
52 auto module = cast<FModuleOp>(arg.getParentRegion()->getParentOp());
53 return {module.getPortNameAttr(arg.getArgNumber()), module};
54 }
55 auto *op = reset.getDefiningOp();
56 return {op->getAttrOfType<StringAttr>("name"),
57 op->getParentOfType<FModuleOp>()};
58}
59
60/// Return the name of a reset. The reset value must either be a module port or
61/// a wire/node operation.
62static StringAttr getResetName(Value reset) {
63 return getResetNameAndModule(reset).first;
64}
65
66namespace {
67/// A reset domain.
68struct ResetDomain {
69 /// Whether this is the root of the reset domain.
70 bool isTop = false;
71
72 /// The reset signal for this domain. A null value indicates that this domain
73 /// explicitly has no reset.
74 Value rootReset;
75
76 /// The name of this reset signal.
77 StringAttr resetName;
78 /// The type of this reset signal.
79 Type resetType;
80
81 /// Implementation details for this domain. This will be the module local
82 /// signal for this domain.
83 Value localReset;
84 /// If this module already has a port with the matching name, this holds the
85 /// index of the port.
86 std::optional<unsigned> existingPort;
87
88 /// Create a reset domain without any reset.
89 ResetDomain() = default;
90
91 /// Create a reset domain associated with the root reset.
92 ResetDomain(Value rootReset)
93 : rootReset(rootReset), resetName(getResetName(rootReset)),
94 resetType(rootReset.getType()) {}
95
96 /// Returns true if this is in a reset domain, false if this is not a domain.
97 explicit operator bool() const { return static_cast<bool>(rootReset); }
98};
99} // namespace
100
101inline bool operator==(const ResetDomain &a, const ResetDomain &b) {
102 return (a.isTop == b.isTop && a.resetName == b.resetName &&
103 a.resetType == b.resetType);
104}
105inline bool operator!=(const ResetDomain &a, const ResetDomain &b) {
106 return !(a == b);
107}
108
109/// Construct a zero value of the given type using the given builder.
110static Value createZeroValue(ImplicitLocOpBuilder &builder, FIRRTLBaseType type,
112 // The zero value's type is a const version of `type`.
113 type = type.getConstType(true);
114 auto it = cache.find(type);
115 if (it != cache.end())
116 return it->second;
117 auto nullBit = [&]() {
118 return createZeroValue(
119 builder, UIntType::get(builder.getContext(), 1, /*isConst=*/true),
120 cache);
121 };
122 auto value =
124 .Case<ClockType>([&](auto type) {
125 return AsClockPrimOp::create(builder, nullBit());
126 })
127 .Case<AsyncResetType>([&](auto type) {
128 return AsAsyncResetPrimOp::create(builder, nullBit());
129 })
130 .Case<SIntType, UIntType>([&](auto type) {
131 return ConstantOp::create(
132 builder, type, APInt::getZero(type.getWidth().value_or(1)));
133 })
134 .Case<FEnumType>([&](auto type) -> Value {
135 // There might not be a variant that corresponds to 0, in which case
136 // we have to create a 0 value and bitcast it to the enum.
137 if (type.getNumElements() != 0 &&
138 type.getElement(0).value.getValue().isZero()) {
139 const auto &element = type.getElement(0);
140 auto value = createZeroValue(builder, element.type, cache);
141 return FEnumCreateOp::create(builder, type, element.name, value);
142 }
143 auto value = ConstantOp::create(builder,
144 UIntType::get(builder.getContext(),
145 type.getBitWidth(),
146 /*isConst=*/true),
147 APInt::getZero(type.getBitWidth()));
148 return BitCastOp::create(builder, type, value);
149 })
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);
154 auto zero = createZeroValue(builder, fieldType, cache);
155 auto acc =
156 SubfieldOp::create(builder, fieldType, wireOp.getResult(), i);
157 emitConnect(builder, acc, zero);
158 }
159 return wireOp.getResult();
160 })
161 .Case<FVectorType>([&](auto type) {
162 auto wireOp = WireOp::create(builder, type);
163 auto zero = createZeroValue(
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);
168 emitConnect(builder, acc, zero);
169 }
170 return wireOp.getResult();
171 })
172 .Case<ResetType, AnalogType>(
173 [&](auto type) { return InvalidValueOp::create(builder, type); })
174 .Default([](auto) {
175 llvm_unreachable("switch handles all types");
176 return Value{};
177 });
178 cache.insert({type, value});
179 return value;
180}
181
182/// Construct a null value of the given type using the given builder.
183static Value createZeroValue(ImplicitLocOpBuilder &builder,
184 FIRRTLBaseType type) {
186 return createZeroValue(builder, type, cache);
187}
188
189/// Helper function that inserts reset multiplexer into all `ConnectOp`s
190/// with the given target. Looks through `SubfieldOp`, `SubindexOp`,
191/// and `SubaccessOp`, and inserts multiplexers into connects to
192/// these subaccesses as well. Modifies the insertion location of the builder.
193/// Returns true if the `resetValue` was used in any way, false otherwise.
194static bool insertResetMux(ImplicitLocOpBuilder &builder, Value target,
195 Value reset, Value resetValue) {
196 // Indicates whether the `resetValue` was assigned to in some way. We use this
197 // to erase unused subfield/subindex/subaccess ops on the reset value if they
198 // end up unused.
199 bool resetValueUsed = false;
200
201 for (auto &use : target.getUses()) {
202 Operation *useOp = use.getOwner();
203 builder.setInsertionPoint(useOp);
204 TypeSwitch<Operation *>(useOp)
205 // Insert a mux on the value connected to the target:
206 // connect(dst, src) -> connect(dst, mux(reset, resetValue, src))
207 .Case<ConnectOp, MatchingConnectOp>([&](auto op) {
208 if (op.getDest() != target)
209 return;
210 LLVM_DEBUG(llvm::dbgs() << " - Insert mux into " << op << "\n");
211 auto muxOp =
212 MuxPrimOp::create(builder, reset, resetValue, op.getSrc());
213 op.getSrcMutable().assign(muxOp);
214 resetValueUsed = true;
215 })
216 // Look through subfields.
217 .Case<SubfieldOp>([&](auto op) {
218 auto resetSubValue =
219 SubfieldOp::create(builder, resetValue, op.getFieldIndexAttr());
220 if (insertResetMux(builder, op, reset, resetSubValue))
221 resetValueUsed = true;
222 else
223 resetSubValue.erase();
224 })
225 // Look through subindices.
226 .Case<SubindexOp>([&](auto op) {
227 auto resetSubValue =
228 SubindexOp::create(builder, resetValue, op.getIndexAttr());
229 if (insertResetMux(builder, op, reset, resetSubValue))
230 resetValueUsed = true;
231 else
232 resetSubValue.erase();
233 })
234 // Look through subaccesses.
235 .Case<SubaccessOp>([&](auto op) {
236 if (op.getInput() != target)
237 return;
238 auto resetSubValue =
239 SubaccessOp::create(builder, resetValue, op.getIndex());
240 if (insertResetMux(builder, op, reset, resetSubValue))
241 resetValueUsed = true;
242 else
243 resetSubValue.erase();
244 });
245 }
246 return resetValueUsed;
247}
248
249//===----------------------------------------------------------------------===//
250// Reset Network
251//===----------------------------------------------------------------------===//
252
253/// A reset signal.
254///
255
256namespace {
257enum class ResetKind { Async, Sync };
258
259static StringRef resetKindToStringRef(const ResetKind &kind) {
260 switch (kind) {
261 case ResetKind::Async:
262 return "async";
263 case ResetKind::Sync:
264 return "sync";
265 }
266 llvm_unreachable("unhandled reset kind");
267}
268} // namespace
269
270namespace {
271struct MemToRegOfVecConverter {
272 explicit MemToRegOfVecConverter(bool ignoreReadEnable)
273 : ignoreReadEnable(ignoreReadEnable) {}
274
275 void runOnModule(FModuleOp mod) {
276
277 mod.getBodyBlock()->walk([&](MemOp memOp) {
278 LLVM_DEBUG(llvm::dbgs() << "\n Memory op:" << memOp);
279
280 auto firMem = memOp.getSummary();
281 // Ignore if the memory is a sequential memory, i.e., something that is
282 // supposed to be an SRAM. In either possible eventual lowering by later
283 // passes (blackboxing or lowering to a behavioral model) we don't want to
284 // blow this out here as it both breaks expectations about later passes
285 // that may add asynchronous resets (InferResets) or that expect metadata
286 // on SRAMs to not be split up (LowerClasses).
287 if (firMem.isSeqMem())
288 return;
289
290 generateMemory(memOp, firMem);
291 ++numConverted;
292 memOp.erase();
293 });
294 }
295 Value addPipelineStages(ImplicitLocOpBuilder &b, size_t stages, Value clock,
296 Value pipeInput, StringRef name, Value gate = {}) {
297 if (!stages)
298 return pipeInput;
299
300 while (stages--) {
301 auto reg = RegOp::create(b, pipeInput.getType(), clock, name).getResult();
302 if (gate) {
303 WhenOp::create(b, gate, /*withElseRegion*/ false,
304 [&]() { MatchingConnectOp::create(b, reg, pipeInput); });
305 } else
306 MatchingConnectOp::create(b, reg, pipeInput);
307
308 pipeInput = reg;
309 }
310
311 return pipeInput;
312 }
313
314 Value getClock(ImplicitLocOpBuilder &builder, Value bundle) {
315 return SubfieldOp::create(builder, bundle, "clk");
316 }
317
318 Value getAddr(ImplicitLocOpBuilder &builder, Value bundle) {
319 return SubfieldOp::create(builder, bundle, "addr");
320 }
321
322 Value getWmode(ImplicitLocOpBuilder &builder, Value bundle) {
323 return SubfieldOp::create(builder, bundle, "wmode");
324 }
325
326 Value getEnable(ImplicitLocOpBuilder &builder, Value bundle) {
327 return SubfieldOp::create(builder, bundle, "en");
328 }
329
330 Value getMask(ImplicitLocOpBuilder &builder, Value bundle) {
331 auto bType = type_cast<BundleType>(bundle.getType());
332 if (bType.getElement("mask"))
333 return SubfieldOp::create(builder, bundle, "mask");
334 return SubfieldOp::create(builder, bundle, "wmask");
335 }
336
337 Value getData(ImplicitLocOpBuilder &builder, Value bundle,
338 bool getWdata = false) {
339 auto bType = type_cast<BundleType>(bundle.getType());
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");
345 }
346
347 void generateRead(const FirMemory &firMem, Value clock, Value addr,
348 Value enable, Value data, Value regOfVec,
349 ImplicitLocOpBuilder &builder) {
350 if (ignoreReadEnable) {
351 // If read enable is ignored, then guard the address update with read
352 // enable.
353 for (size_t j = 0, e = firMem.readLatency; j != e; ++j) {
354 auto enLast = enable;
355 if (j < e - 1)
356 enable = addPipelineStages(builder, 1, clock, enable, "en");
357 addr = addPipelineStages(builder, 1, clock, addr, "addr", enLast);
358 }
359 } else {
360 // Add pipeline stages to respect the read latency. One register for each
361 // latency cycle.
362 enable =
363 addPipelineStages(builder, firMem.readLatency, clock, enable, "en");
364 addr =
365 addPipelineStages(builder, firMem.readLatency, clock, addr, "addr");
366 }
367
368 // Read the register[address] into a temporary.
369 Value rdata = SubaccessOp::create(builder, regOfVec, addr);
370 if (!ignoreReadEnable) {
371 // Initialize read data out with invalid.
372 MatchingConnectOp::create(
373 builder, data, InvalidValueOp::create(builder, data.getType()));
374 // If enable is true, then connect the data read from memory register.
375 WhenOp::create(builder, enable, /*withElseRegion*/ false, [&]() {
376 MatchingConnectOp::create(builder, data, rdata);
377 });
378 } else {
379 // Ignore read enable signal.
380 MatchingConnectOp::create(builder, data, rdata);
381 }
382 }
383
384 void generateWrite(const FirMemory &firMem, Value clock, Value addr,
385 Value enable, Value maskBits, Value wdataIn,
386 Value regOfVec, ImplicitLocOpBuilder &builder) {
387
388 auto numStages = firMem.writeLatency - 1;
389 // Add pipeline stages to respect the write latency. Intermediate registers
390 // for each stage.
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");
395 // Create the register access.
396 FIRRTLBaseValue rdata = SubaccessOp::create(builder, regOfVec, addr);
397
398 // The tuple for the access to individual fields of an aggregate data type.
399 // Tuple::<register, data, mask>
400 // The logic:
401 // if (mask)
402 // register = data
403 SmallVector<std::tuple<Value, Value, Value>, 8> loweredRegDataMaskFields;
404
405 // Write to each aggregate data field is guarded by the corresponding mask
406 // field. This means we have to generate read and write access for each
407 // individual field of the aggregate type.
408 // There are two options to handle this,
409 // 1. FlattenMemory: cast the aggregate data into a UInt and generate
410 // appropriate mask logic.
411 // 2. Create access for each individual field of the aggregate type.
412 // Here we implement the option 2 using getFields.
413 // getFields, creates an access to each individual field of the data and
414 // mask, and the corresponding field into the register. It populates
415 // the loweredRegDataMaskFields vector.
416 // This is similar to what happens in LowerTypes.
417 //
418 if (!getFields(rdata, wdataIn, maskBits, loweredRegDataMaskFields,
419 builder)) {
420 wdataIn.getDefiningOp()->emitOpError(
421 "Cannot convert memory to bank of registers");
422 return;
423 }
424 // If enable:
425 WhenOp::create(builder, enable, /*withElseRegion*/ false, [&]() {
426 // For each data field. Only one field if not aggregate.
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);
431 // If mask, then update the register field.
432 WhenOp::create(builder, maskField, /*withElseRegion*/ false, [&]() {
433 MatchingConnectOp::create(builder, regField, dataField);
434 });
435 }
436 });
437 }
438
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) {
443
444 // Add pipeline stages to respect the write latency. Intermediate registers
445 // for each stage. Number of pipeline stages, max of read/write latency.
446 auto numStages = std::max(firMem.readLatency, firMem.writeLatency) - 1;
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");
451
452 // Read the register[address] into a temporary.
453 Value rdata = SubaccessOp::create(builder, regOfVec, addr);
454
455 SmallVector<std::tuple<Value, Value, Value>, 8> loweredRegDataMaskFields;
456 if (!getFields(rdata, wdataIn, maskBits, loweredRegDataMaskFields,
457 builder)) {
458 wdataIn.getDefiningOp()->emitOpError(
459 "Cannot convert memory to bank of registers");
460 return;
461 }
462 // Initialize read data out with invalid.
463 MatchingConnectOp::create(
464 builder, rdataOut, InvalidValueOp::create(builder, rdataOut.getType()));
465 // If enable:
466 WhenOp::create(builder, enable, /*withElseRegion*/ false, [&]() {
467 // If write mode:
468 WhenOp::create(
469 builder, wmode, true,
470 // Write block:
471 [&]() {
472 // For each data field. Only one field if not aggregate.
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);
477 // If mask true, then set the field.
478 WhenOp::create(
479 builder, maskField, /*withElseRegion*/ false, [&]() {
480 MatchingConnectOp::create(builder, regField, dataField);
481 });
482 }
483 },
484 // Read block:
485 [&]() { MatchingConnectOp::create(builder, rdataOut, rdata); });
486 });
487 }
488
489 // Generate individual field accesses for an aggregate type. Return false if
490 // it fails. Which can happen if invalid fields are present of the mask and
491 // input types donot match. The assumption is that, \p reg and \p input have
492 // exactly the same type. And \p mask has the same bundle fields, but each
493 // field is of type UInt<1> So, populate the \p results with each field
494 // access. For example, the first entry should be access to first field of \p
495 // reg, first field of \p input and first field of \p mask.
496 bool getFields(Value reg, Value input, Value mask,
497 SmallVectorImpl<std::tuple<Value, Value, Value>> &results,
498 ImplicitLocOpBuilder &builder) {
499
500 // Check if the number of fields of mask and input type match.
501 auto isValidMask = [&](FIRRTLType inType, FIRRTLType maskType) -> bool {
502 if (auto bundle = type_dyn_cast<BundleType>(inType)) {
503 if (auto mBundle = type_dyn_cast<BundleType>(maskType))
504 return mBundle.getNumElements() == bundle.getNumElements();
505 } else if (auto vec = type_dyn_cast<FVectorType>(inType)) {
506 if (auto mVec = type_dyn_cast<FVectorType>(maskType))
507 return mVec.getNumElements() == vec.getNumElements();
508 } else
509 return true;
510 return false;
511 };
512
513 std::function<bool(Value, Value, Value)> flatAccess =
514 [&](Value reg, Value input, Value mask) -> bool {
515 FIRRTLType inType = type_cast<FIRRTLType>(input.getType());
516 if (!isValidMask(inType, type_cast<FIRRTLType>(mask.getType()))) {
517 input.getDefiningOp()->emitOpError("Mask type is not valid");
518 return false;
519 }
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))
527 return false;
528 }
529 return true;
530 })
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))
537 return false;
538 }
539 return true;
540 })
541 .Case<IntType>([&](auto iType) {
542 results.push_back({reg, input, mask});
543 return iType.getWidth().has_value();
544 })
545 .Default([&](auto) { return false; });
546 };
547 if (flatAccess(reg, input, mask))
548 return true;
549 return false;
550 }
551
552 /// Generate the logic for implementing the memory using Registers.
553 void generateMemory(MemOp memOp, FirMemory &firMem) {
554 ImplicitLocOpBuilder builder(memOp.getLoc(), memOp);
555 auto dataType = memOp.getDataType();
556
557 auto innerSym = memOp.getInnerSym();
558 SmallVector<Value> debugPorts;
559
560 RegOp regOfVec = {};
561 for (size_t index = 0, rend = memOp.getNumResults(); index < rend;
562 ++index) {
563 auto result = memOp.getResult(index);
564 if (type_isa<RefType>(result.getType())) {
565 debugPorts.push_back(result);
566 continue;
567 }
568 // Create a temporary wire to replace the memory port. This makes it
569 // simpler to delete the memOp.
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();
576 // Create an access to all the common subfields.
577 auto adr = getAddr(builder, result);
578 auto enb = getEnable(builder, result);
579 auto clk = getClock(builder, result);
580 auto dta = getData(builder, result);
581 // IF the register is not yet created.
582 if (!regOfVec) {
583 // Create the register corresponding to the memory.
584 regOfVec =
585 RegOp::create(builder, FVectorType::get(dataType, firMem.depth),
586 clk, memOp.getNameAttr());
587
588 // Copy all the memory annotations.
589 if (!memOp.getAnnotationsAttr().empty())
590 regOfVec.setAnnotationsAttr(memOp.getAnnotationsAttr());
591 if (innerSym)
592 regOfVec.setInnerSymAttr(memOp.getInnerSymAttr());
593 }
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(),
600 builder);
601 } else {
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);
607 }
608 }
609 // If a valid register is created, then replace all the debug port users
610 // with a RefType of the register. The RefType is obtained by using a
611 // RefSend on the register.
612 if (regOfVec)
613 for (auto r : debugPorts)
614 r.replaceAllUsesWith(RefSendOp::create(builder, regOfVec.getResult()));
615 }
616
617 bool ignoreReadEnable = false;
618 unsigned numConverted = 0;
619};
620} // end anonymous namespace
621
622void circt::firrtl::runCombMemsToRegOfVec(FModuleOp mod, bool ignoreReadEnable,
623 unsigned &numConverted) {
624 MemToRegOfVecConverter converter(ignoreReadEnable);
625 converter.runOnModule(mod);
626 numConverted += converter.numConverted;
627}
628
629namespace {
630struct FullResetRunner {
631 FullResetRunner(CircuitOp circuit, InstanceGraph &ig,
632 InstancePathCache &instancePathCache,
633 InstanceInfo &instanceInfo, bool convertAsyncDomainMems)
634 : circuit(circuit), instanceGraph(&ig),
635 instancePathCache(&instancePathCache), instanceInfo(&instanceInfo),
636 convertAsyncDomainMems(convertAsyncDomainMems) {}
637
638 LogicalResult run();
639
640 //===--------------------------------------------------------------------===//
641 // Full reset implementation
642
643 LogicalResult collectAnnos();
644 // Collect reset annotations in the module and return a reset signal.
645 // Return `failure()` if there was an error in the annotation processing.
646 // Return `std::nullopt` if there was no reset annotation.
647 // Return `nullptr` if there was `ignore` annotation.
648 // Return a non-null Value if the reset was actually provided.
649 FailureOr<std::optional<Value>> collectAnnos(FModuleOp module);
650
651 LogicalResult buildDomains();
652 void buildDomains(FModuleOp module, const InstancePath &instPath,
653 Value parentReset, InstanceGraph &instGraph,
654 unsigned indent = 0);
655
656 void convertMemsInAsyncDomains();
657
658 LogicalResult determineImpl();
659 LogicalResult determineImpl(FModuleOp module, ResetDomain &domain);
660
661 LogicalResult implementFullReset();
662 LogicalResult implementFullReset(FModuleOp module, ResetDomain &domain);
663 LogicalResult implementFullReset(Operation *op, FModuleOp module,
664 Value actualReset);
665
666 // Helper to implement full reset for instance-like operations
667 LogicalResult implementFullReset(FInstanceLike inst, StringAttr moduleName,
668 Value actualReset);
669
670 CircuitOp circuit;
671
672 /// The annotated reset for a module. A null value indicates that the module
673 /// is explicitly annotated with `ignore`. Otherwise the port/wire/node
674 /// annotated as reset within the module is stored.
675 DenseMap<Operation *, Value> annotatedResets;
676
677 /// The reset domain for a module. In case of conflicting domain membership,
678 /// the vector for a module contains multiple elements.
680 domains;
681
682 /// Cache of modules symbols
683 InstanceGraph *instanceGraph = nullptr;
684
685 /// Cache of instance paths.
686 InstancePathCache *instancePathCache = nullptr;
687
688 InstanceInfo *instanceInfo = nullptr;
689
690 bool convertAsyncDomainMems = false;
691};
692} // namespace
693
694LogicalResult FullResetRunner::run() {
695 if (failed(collectAnnos()))
696 return failure();
697 if (failed(buildDomains()))
698 return failure();
699 if (convertAsyncDomainMems)
700 convertMemsInAsyncDomains();
701 if (failed(determineImpl()))
702 return failure();
703 if (failed(implementFullReset()))
704 return failure();
705 return success();
706}
707
708void FullResetRunner::convertMemsInAsyncDomains() {
709 SmallVector<FModuleOp> asyncDomainMods;
710 for (auto &[mod, entries] : domains) {
711 if (entries.empty())
712 continue;
713 auto &domain = entries.back().first;
714 if (!domain.rootReset)
715 continue;
716 if (!type_isa<AsyncResetType>(domain.resetType))
717 continue;
718 if (!instanceInfo->anyInstanceInEffectiveDesign(mod))
719 continue;
720 asyncDomainMods.push_back(mod);
721 }
722 if (asyncDomainMods.empty())
723 return;
724
725 LLVM_DEBUG({
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";
730 });
731
732 mlir::parallelForEach(
733 circuit.getContext(), asyncDomainMods, [&](FModuleOp mod) {
734 unsigned converted = 0;
735 runCombMemsToRegOfVec(mod, /*ignoreReadEnable=*/false, converted);
736 });
737}
738
739LogicalResult circt::firrtl::runFullReset(CircuitOp circuit, InstanceGraph &ig,
740 InstanceInfo &instanceInfo,
741 bool convertAsyncDomainMems) {
742 InstancePathCache instancePathCache(ig);
743 return FullResetRunner(circuit, ig, instancePathCache, instanceInfo,
744 convertAsyncDomainMems)
745 .run();
746}
747
748//===----------------------------------------------------------------------===//
749// Reset Annotations
750//===----------------------------------------------------------------------===//
751
752LogicalResult FullResetRunner::collectAnnos() {
753 LLVM_DEBUG({
754 llvm::dbgs() << "\n";
755 debugHeader("Gather reset annotations") << "\n\n";
756 });
757 SmallVector<std::pair<FModuleOp, std::optional<Value>>> results;
758 for (auto module : circuit.getOps<FModuleOp>())
759 results.push_back({module, {}});
760 // Collect annotations parallelly.
761 if (failed(mlir::failableParallelForEach(
762 circuit.getContext(), results, [&](auto &moduleAndResult) {
763 auto result = collectAnnos(moduleAndResult.first);
764 if (failed(result))
765 return failure();
766 moduleAndResult.second = *result;
767 return success();
768 })))
769 return failure();
770
771 for (auto [module, reset] : results)
772 if (reset.has_value())
773 annotatedResets.insert({module, *reset});
774 return success();
775}
776
777FailureOr<std::optional<Value>>
778FullResetRunner::collectAnnos(FModuleOp module) {
779 bool anyFailed = false;
780 SmallSetVector<std::pair<Annotation, Location>, 4> conflictingAnnos;
781
782 // Consume a possible "ignore" annotation on the module itself, which
783 // explicitly assigns it no reset domain.
784 bool ignore = false;
785 AnnotationSet::removeAnnotations(module, [&](Annotation anno) {
786 if (anno.isClass(excludeFromFullResetAnnoClass)) {
787 ignore = true;
788 conflictingAnnos.insert({anno, module.getLoc()});
789 return true;
790 }
791 if (anno.isClass(fullResetAnnoClass)) {
792 anyFailed = true;
793 module.emitError("''FullResetAnnotation' cannot target module; must "
794 "target port or wire/node instead");
795 return true;
796 }
797 return false;
798 });
799 if (anyFailed)
800 return failure();
801
802 // Consume any reset annotations on module ports.
803 Value reset;
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")) {
809 if (rt == "sync") {
810 expectedResetKind = ResetKind::Sync;
811 } else if (rt == "async") {
812 expectedResetKind = ResetKind::Async;
813 } else {
814 mlir::emitError(arg.getLoc(),
815 "'FullResetAnnotation' requires resetType == 'sync' "
816 "| 'async', but got resetType == ")
817 << rt;
818 anyFailed = true;
819 return true;
820 }
821 } else {
822 mlir::emitError(arg.getLoc(),
823 "'FullResetAnnotation' requires resetType == "
824 "'sync' | 'async', but got no resetType");
825 anyFailed = true;
826 return true;
827 }
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 "
839 << arg.getType();
840 anyFailed = true;
841 return true;
842 }
843
844 reset = arg;
845 conflictingAnnos.insert({anno, reset.getLoc()});
846
847 return false;
848 }
849 if (anno.isClass(excludeFromFullResetAnnoClass)) {
850 anyFailed = true;
851 mlir::emitError(arg.getLoc(),
852 "'ExcludeFromFullResetAnnotation' cannot "
853 "target port/wire/node; must target module instead");
854 return true;
855 }
856 return false;
857 };
858
859 AnnotationSet::removePortAnnotations(module,
860 [&](unsigned argNum, Annotation anno) {
861 Value arg = module.getArgument(argNum);
862 return checkAnnotations(anno, arg);
863 });
864 if (anyFailed)
865 return failure();
866
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)) {
873 anyFailed = true;
874 op->emitError(
875 "reset annotations must target module, port, or wire/node");
876 }
877 return;
878 }
879
880 // At this point we know that we have a WireOp/NodeOp. Process the reset
881 // annotations.
882 AnnotationSet::removeAnnotations(op, [&](Annotation anno) {
883 auto arg = op->getResult(0);
884 return checkAnnotations(anno, arg);
885 });
886 });
887 if (anyFailed)
888 return failure();
889
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>();
897 }
898
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() << ":";
906 return failure();
907 }
908
909 // Dump some information in debug builds.
910 LLVM_DEBUG({
911 llvm::dbgs() << "Annotated reset for " << module.getName() << ": ";
912 if (ignore)
913 llvm::dbgs() << "no domain\n";
914 else if (auto arg = dyn_cast<BlockArgument>(reset))
915 llvm::dbgs() << "port " << module.getPortName(arg.getArgNumber()) << "\n";
916 else
917 llvm::dbgs() << "wire "
918 << reset.getDefiningOp()->getAttrOfType<StringAttr>("name")
919 << "\n";
920 });
921
922 // Store the annotated reset for this module.
923 assert(ignore || reset);
924 return std::optional<Value>(reset);
925}
926
927//===----------------------------------------------------------------------===//
928// Domain Construction
929//===----------------------------------------------------------------------===//
930
931/// Gather the reset domains present in a circuit. This traverses the instance
932/// hierarchy of the design, making instances either live in a new reset
933/// domain if so annotated, or inherit their parent's domain. This can go
934/// wrong in some cases, mainly when a module is instantiated multiple times
935/// within different reset domains.
936LogicalResult FullResetRunner::buildDomains() {
937 LLVM_DEBUG({
938 llvm::dbgs() << "\n";
939 debugHeader("Build full reset domains") << "\n\n";
940 });
941
942 // Gather the domains.
943 auto &instGraph = *instanceGraph;
944 // Walk all top-level modules.
945 instGraph.walkPostOrder([&](igraph::InstanceGraphNode &node) {
946 if (!node.noUses())
947 return;
948 if (auto module =
949 dyn_cast_or_null<FModuleOp>(node.getModule().getOperation()))
950 buildDomains(module, InstancePath{}, Value{}, instGraph);
951 });
952
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)
959 continue;
960
961 anyFailed = true;
962 SmallDenseSet<Value> printedDomainResets;
963 auto diag = module.emitError("module '")
964 << module.getName()
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 &note = diag.attachNote(loc);
972
973 // Describe the instance itself.
974 if (path.empty())
975 note << "root instance";
976 else {
977 note << "instance '";
978 llvm::interleave(
979 path,
980 [&](InstanceOpInterface inst) { note << inst.getInstanceName(); },
981 [&]() { note << "/"; });
982 note << "'";
983 }
984
985 // Describe the reset domain the instance is in.
986 note << " 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() << "'";
991
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:";
998 }
999 } else
1000 note << " no reset domain";
1001 }
1002 }
1003 return failure(anyFailed);
1004}
1005
1006void FullResetRunner::buildDomains(FModuleOp module,
1007 const InstancePath &instPath,
1008 Value parentReset, InstanceGraph &instGraph,
1009 unsigned indent) {
1010 LLVM_DEBUG({
1011 llvm::dbgs().indent(indent * 2) << "Visiting ";
1012 if (instPath.empty())
1013 llvm::dbgs() << "$root";
1014 else
1015 llvm::dbgs() << instPath.leaf().getInstanceName();
1016 llvm::dbgs() << " (" << module.getName() << ")\n";
1017 });
1018
1019 // Assemble the domain for this module.
1020 ResetDomain domain;
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);
1031 }
1032
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});
1042
1043 // Traverse the child instances.
1044 for (auto *record : *instGraph[module]) {
1045 auto submodule = dyn_cast<FModuleOp>(*record->getTarget()->getModule());
1046 if (!submodule)
1047 continue;
1048 auto childPath =
1049 instancePathCache->appendInstance(instPath, record->getInstance());
1050 buildDomains(submodule, childPath, domain.rootReset, instGraph, indent + 1);
1051 }
1052}
1053
1054/// Determine how the reset for each module shall be implemented.
1055LogicalResult FullResetRunner::determineImpl() {
1056 auto anyFailed = false;
1057 LLVM_DEBUG({
1058 llvm::dbgs() << "\n";
1059 debugHeader("Determine implementation") << "\n\n";
1060 });
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())
1066 continue;
1067 auto &domain = entries.back().first;
1068 if (failed(determineImpl(module, domain)))
1069 anyFailed = true;
1070 }
1071 return failure(anyFailed);
1072}
1073
1074/// Determine how the reset for a module shall be implemented. This function
1075/// fills in the `localReset` and `existingPort` fields of the given reset
1076/// domain.
1077///
1078/// Generally it does the following:
1079/// - If the domain has explicitly no reset ("ignore"), leaves everything
1080/// empty.
1081/// - If the domain is the place where the reset is defined ("top"), fills in
1082/// the existing port/wire/node as reset.
1083/// - If the module already has a port with the reset's name:
1084/// - If the port has the same name and type as the reset domain, reuses that
1085/// port.
1086/// - Otherwise errors out.
1087/// - Otherwise indicates that a port with the reset's name should be created.
1088///
1089LogicalResult FullResetRunner::determineImpl(FModuleOp module,
1090 ResetDomain &domain) {
1091 // Nothing to do if the module needs no reset.
1092 if (!domain)
1093 return success();
1094 LLVM_DEBUG(llvm::dbgs() << "Planning reset for " << module.getName() << "\n");
1095
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.
1098 if (domain.isTop) {
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();
1104 return success();
1105 }
1106
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
1112 << "\n");
1113 auto portNames = module.getPortNames();
1114 auto *portIt = llvm::find(portNames, neededName);
1115
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;
1120 return success();
1121 }
1122
1123 LLVM_DEBUG(llvm::dbgs() << "- Reusing existing port " << neededName << "\n");
1124
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";
1134 return failure();
1135 }
1136
1137 // We have a pre-existing port which we should use.
1138 domain.existingPort = portNo;
1139 domain.localReset = module.getArgument(portNo);
1140 return success();
1141}
1142
1143//===----------------------------------------------------------------------===//
1144// Full Reset Implementation
1145//===----------------------------------------------------------------------===//
1146
1147/// Implement the annotated resets gathered in the pass' `domains` map.
1148LogicalResult FullResetRunner::implementFullReset() {
1149 LLVM_DEBUG({
1150 llvm::dbgs() << "\n";
1151 debugHeader("Implement full resets") << "\n\n";
1152 });
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.
1158 ResetDomain domain;
1159 if (!entries.empty())
1160 domain = entries.back().first;
1161 if (failed(implementFullReset(module, domain)))
1162 return failure();
1163 }
1164 return success();
1165}
1166
1167/// Implement the async resets for a specific module.
1168///
1169/// This will add ports to the module as appropriate, update the register ops
1170/// in the module, and update any instantiated submodules with their
1171/// corresponding reset implementation details.
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.
1177 if (!domain) {
1178 SmallVector<FInstanceLike> instances;
1179 module.walk([&](FInstanceLike instOp) { instances.push_back(instOp); });
1180 LLVM_DEBUG({
1181 if (!instances.empty())
1182 llvm::dbgs() << "Tie off instances in " << module.getName() << "\n";
1183 });
1184 for (auto instOp : instances)
1185 if (failed(implementFullReset(instOp, module, Value())))
1186 return failure();
1187 return success();
1188 }
1189
1190 LLVM_DEBUG(llvm::dbgs() << "Implementing full reset for " << module.getName()
1191 << "\n");
1192
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);
1200
1201 // If needed, add a reset port to the module.
1202 auto actualReset = domain.localReset;
1203 if (!domain.localReset) {
1204 PortInfo portInfo{domain.resetName,
1205 domain.resetType,
1206 Direction::In,
1207 {},
1208 domain.rootReset.getLoc()};
1209 module.insertPorts({{0, portInfo}});
1210 actualReset = module.getArgument(0);
1211 LLVM_DEBUG(llvm::dbgs() << "- Inserted port " << domain.resetName << "\n");
1212 }
1213
1214 LLVM_DEBUG({
1215 llvm::dbgs() << "- Using ";
1216 if (auto blockArg = dyn_cast<BlockArgument>(actualReset))
1217 llvm::dbgs() << "port #" << blockArg.getArgNumber() << " ";
1218 else
1219 llvm::dbgs() << "wire/node ";
1220 llvm::dbgs() << getResetName(actualReset) << "\n";
1221 });
1222
1223 // Gather a list of operations in the module that need to be updated with
1224 // the new reset.
1225 SmallVector<Operation *> opsToUpdate;
1226 module.walk([&](Operation *op) {
1227 if (isa<FInstanceLike, RegOp, RegResetOp>(op))
1228 opsToUpdate.push_back(op);
1229 });
1230
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
1234 // in that case.
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");
1245
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());
1261 nodeOp.setName("");
1262 // Leave forcable alone, since we cannot remove a result. It will be
1263 // cleaned up in canonicalization since it is dead. As will this node.
1264 nodeOp.setNameKind(NameKindEnum::DroppableName);
1265 nodeOp.setAnnotationsAttr(ArrayAttr::get(builder.getContext(), {}));
1266 builder.setInsertionPointAfter(nodeOp);
1267 emitConnect(builder, wireOp.getResult(), nodeOp.getResult());
1268 resetOp = wireOp;
1269 actualReset = wireOp.getResult();
1270 domain.localReset = wireOp.getResult();
1271 }
1272
1273 // Determine the block into which the reset declaration needs to be
1274 // moved.
1275 Block *targetBlock = dom.findNearestCommonDominator(
1276 resetOp->getBlock(), opsToUpdate[0]->getBlock());
1277 LLVM_DEBUG({
1278 if (targetBlock != resetOp->getBlock())
1279 llvm::dbgs() << "- Needs to be moved to different block\n";
1280 });
1281
1282 // At this point we have to figure out in front of which operation in
1283 // the target block the reset declaration has to be moved. The reset
1284 // declaration and the first op it needs to dominate may be buried
1285 // inside blocks of other operations (e.g. `WhenOp`), so we have to look
1286 // through their parent operations until we find the one that lies
1287 // within the target block.
1288 auto getParentInBlock = [](Operation *op, Block *block) {
1289 while (op && op->getBlock() != block)
1290 op = op->getParentOp();
1291 return op;
1292 };
1293 auto *resetOpInTarget = getParentInBlock(resetOp, targetBlock);
1294 auto *firstOpInTarget = getParentInBlock(opsToUpdate[0], targetBlock);
1295
1296 // Move the operation upwards. Since there are situations where the
1297 // reset declaration does not dominate the first use, but the `WhenOp`
1298 // it is nested within actually *does* come before that use, we have to
1299 // consider moving the reset declaration in front of its parent op.
1300 if (resetOpInTarget->isBeforeInBlock(firstOpInTarget))
1301 resetOp->moveBefore(resetOpInTarget);
1302 else
1303 resetOp->moveBefore(firstOpInTarget);
1304 }
1305 }
1306
1307 // Update the operations.
1308 for (auto *op : opsToUpdate)
1309 if (failed(implementFullReset(op, module, actualReset)))
1310 return failure();
1311
1312 return success();
1313}
1314
1315/// Helper to implement full reset for instance-like operations.
1316/// This handles the common logic of adding reset ports and connecting them.
1317LogicalResult FullResetRunner::implementFullReset(FInstanceLike inst,
1318 StringAttr moduleName,
1319 Value actualReset) {
1320 // Lookup the reset domain of the default target module. If there is no
1321 // reset domain associated with that module, as indicated by an empty list
1322 // of domains, simply skip it.
1323 auto *node = instanceGraph->lookup(moduleName);
1324 auto refModule = dyn_cast<FModuleOp>(*node->getModule());
1325 if (!refModule)
1326 return success();
1327 auto *domainIt = domains.find(refModule);
1328 if (domainIt == domains.end() || domainIt->second.empty())
1329 return success();
1330 auto &domain = domainIt->second.back().first;
1331 assert(domain && "null domains should not be listed");
1332
1333 ImplicitLocOpBuilder builder(inst.getLoc(), inst);
1334
1335 LLVM_DEBUG(llvm::dbgs() << (actualReset ? "- Update " : "- Tie-off ")
1336 << inst->getName() << " '" << inst.getInstanceName()
1337 << "'\n");
1338
1339 // If needed, add a reset port to the instance.
1340 Value instReset;
1341 if (!domain.localReset) {
1342 LLVM_DEBUG(llvm::dbgs() << " - Adding new result as reset\n");
1343 auto newInstOp = inst.cloneWithInsertedPortsAndReplaceUses(
1344 {{/*portIndex=*/0,
1345 {domain.resetName, domain.resetType, Direction::In}}});
1346 instReset = newInstOp->getResult(0);
1347 instanceGraph->replaceInstance(inst, newInstOp);
1348 inst->erase();
1349 inst = newInstOp;
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");
1354 }
1355
1356 // If there's no reset port on the instance to connect, we're done. This
1357 // can happen if the instantiated module has a reset domain, but that
1358 // domain is e.g. rooted at an internal wire.
1359 if (!instReset)
1360 return success();
1361
1362 builder.setInsertionPointAfter(inst);
1363
1364 // If the module that contains the instance is not in a reset domain, as
1365 // indicated by actualReset being null, create a tie-off constant which
1366 // effectively turns the no-reset registers that had full resets added back
1367 // into no-reset registers.
1368 if (!actualReset) {
1369 LLVM_DEBUG(llvm::dbgs() << " - Tying off reset to constant 0\n");
1370 if (type_isa<AsyncResetType>(domain.resetType))
1371 actualReset = SpecialConstantOp::create(builder, domain.resetType, false);
1372 else
1373 actualReset = ConstantOp::create(
1374 builder, UIntType::get(builder.getContext(), 1), APInt(1, 0));
1375 }
1376
1377 // Connect the instance's reset to the actual reset or tie-off.
1378 assert(instReset && actualReset);
1379 emitConnect(builder, instReset, actualReset);
1380 return success();
1381}
1382
1383/// Modify an operation in a module to implement an full reset for that
1384/// module. If actualReset is null and op is an `InstanceOp`, creates a tie-off
1385/// constant for added reset ports. If the op is not an instance, aborts.
1386LogicalResult FullResetRunner::implementFullReset(Operation *op,
1387 FModuleOp module,
1388 Value actualReset) {
1389 ImplicitLocOpBuilder builder(op->getLoc(), op);
1390
1391 // Handle instances.
1392 if (auto instOp = dyn_cast<FInstanceLike>(op))
1393 return implementFullReset(
1394 instOp, cast<StringAttr>(instOp.getReferencedModuleNamesAttr()[0]),
1395 actualReset);
1396
1397 // All other ops require an actual reset. We only ever call this function with
1398 // null actualReset to create tie-offs on instance ops.
1399 assert(actualReset);
1400
1401 // Handle reset-less registers.
1402 if (auto regOp = dyn_cast<RegOp>(op)) {
1403 LLVM_DEBUG(llvm::dbgs() << "- Adding full reset to " << regOp << "\n");
1404 auto zero = createZeroValue(builder, regOp.getResult().getType());
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());
1413 regOp->erase();
1414 return success();
1415 }
1416
1417 // Handle registers with reset.
1418 if (auto regOp = dyn_cast<RegResetOp>(op)) {
1419 // If the register already has an async reset or if the type of the added
1420 // reset is sync, leave it alone.
1421 if (type_isa<AsyncResetType>(regOp.getResetSignal().getType()) ||
1422 type_isa<UIntType>(actualReset.getType())) {
1423 LLVM_DEBUG(llvm::dbgs() << "- Skipping (has reset) " << regOp << "\n");
1424 // The following performs the logic of `CheckResets` in the original
1425 // Scala source code.
1426 if (failed(regOp.verifyInvariants()))
1427 return failure();
1428 return success();
1429 }
1430 LLVM_DEBUG(llvm::dbgs() << "- Updating reset of " << regOp << "\n");
1431
1432 auto reset = regOp.getResetSignal();
1433 auto value = regOp.getResetValue();
1434
1435 // If we arrive here, the register has a sync reset and the added reset is
1436 // async. In order to add an async reset, we have to move the sync reset
1437 // into a mux in front of the register.
1438 insertResetMux(builder, regOp.getResult(), reset, value);
1439 builder.setInsertionPointAfterValue(regOp.getResult());
1440 auto mux = MuxPrimOp::create(builder, reset, value, regOp.getResult());
1441 emitConnect(builder, regOp.getResult(), mux);
1442
1443 // Replace the existing reset with the async reset.
1444 builder.setInsertionPoint(regOp);
1445 auto zero = createZeroValue(builder, regOp.getResult().getType());
1446 regOp.getResetSignalMutable().assign(actualReset);
1447 regOp.getResetValueMutable().assign(zero);
1448 }
1449 return success();
1450}
1451
1452namespace {
1453struct FullResetPass
1454 : public circt::firrtl::impl::FullResetBase<FullResetPass> {
1455 using FullResetBase::FullResetBase;
1456
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>();
1464 }
1465};
1466} // namespace
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.
Definition FullReset.cpp:62
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.
Definition FullReset.cpp:50
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
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)
bool type_isa(Type type)
static bool operator==(const ModulePort &a, const ModulePort &b)
Definition HWTypes.h:64
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.
Definition Debug.cpp:17
bool operator!=(uint64_t a, const FVInt &b)
Definition FVInt.h:685
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition seq.py:21
A data structure that caches and provides paths to module instances in the IR.