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