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