CIRCT 24.0.0git
Loading...
Searching...
No Matches
InferResets.cpp
Go to the documentation of this file.
1//===- InferResets.cpp - Infer resets and add full reset --------*- C++ -*-===//
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 InferResets pass.
10//
11//===----------------------------------------------------------------------===//
12
21#include "circt/Support/Debug.h"
25#include "mlir/IR/Dominance.h"
26#include "mlir/IR/ImplicitLocOpBuilder.h"
27#include "mlir/IR/Threading.h"
28#include "mlir/Pass/Pass.h"
29#include "llvm/ADT/EquivalenceClasses.h"
30#include "llvm/ADT/SetVector.h"
31#include "llvm/ADT/TypeSwitch.h"
32#include "llvm/Support/Debug.h"
33#include "llvm/Support/LogicalResult.h"
34
35#define DEBUG_TYPE "infer-resets"
36
37namespace circt {
38namespace firrtl {
39#define GEN_PASS_DEF_INFERRESETS
40#include "circt/Dialect/FIRRTL/Passes.h.inc"
41} // namespace firrtl
42} // namespace circt
43
44using circt::igraph::InstanceOpInterface;
47using llvm::BumpPtrAllocator;
48using llvm::MapVector;
49using llvm::SmallDenseSet;
51using mlir::FailureOr;
52using mlir::InferTypeOpInterface;
53
54using namespace circt;
55using namespace firrtl;
56
57//===----------------------------------------------------------------------===//
58// Utilities
59//===----------------------------------------------------------------------===//
60
61namespace {
62
63/// This essentially combines the exact `FieldRef` of the signal in question
64/// with a type to be used for error reporting and inferring the reset kind.
65struct ResetSignal {
66 ResetSignal(FieldRef field, FIRRTLBaseType type) : field(field), type(type) {}
67 bool operator<(const ResetSignal &other) const { return field < other.field; }
68 bool operator==(const ResetSignal &other) const {
69 return field == other.field;
70 }
71 bool operator!=(const ResetSignal &other) const { return !(*this == other); }
72
73 FieldRef field;
74 FIRRTLBaseType type;
75};
76
77/// A connection made to or from a reset network.
78///
79/// These drives are tracked for each reset network, and are used for error
80/// reporting to the user.
81struct ResetDrive {
82 /// What's being driven.
83 ResetSignal dst;
84 /// What's driving.
85 ResetSignal src;
86 /// The location to use for diagnostics.
87 Location loc;
88};
89
90/// A list of connections to a reset network.
91using ResetDrives = SmallVector<ResetDrive, 1>;
92
93/// All signals connected together into a reset network.
94using ResetNetwork = llvm::iterator_range<
95 llvm::EquivalenceClasses<ResetSignal>::member_iterator>;
96
97/// Whether a reset is sync or async.
98enum class ResetKind { Async, Sync };
99
100} // namespace
101
102namespace llvm {
103template <>
104struct DenseMapInfo<ResetSignal> {
105 static unsigned getHashValue(const ResetSignal &x) {
106 return circt::hash_value(x.field);
107 }
108 static bool isEqual(const ResetSignal &lhs, const ResetSignal &rhs) {
109 return lhs == rhs;
110 }
111};
112} // namespace llvm
113
114template <typename T>
115static T &operator<<(T &os, const ResetKind &kind) {
116 switch (kind) {
117 case ResetKind::Async:
118 return os << "async";
119 case ResetKind::Sync:
120 return os << "sync";
121 }
122 return os;
123}
124
125//===----------------------------------------------------------------------===//
126// Pass Infrastructure
127//===----------------------------------------------------------------------===//
128
129namespace {
130/// Infer concrete reset types and insert full reset.
131///
132/// This pass replaces `reset` types in the IR with a concrete `asyncreset` or
133/// `uint<1>` depending on how the reset is used, and adds resets to registers
134/// in modules marked with the corresponding `FullResetAnnotation`.
135///
136/// On a high level, the first stage of the pass that deals with reset inference
137/// operates as follows:
138///
139/// 1. Build a global graph of the resets in the design by tracing reset signals
140/// through instances. This uses the `ResetNetwork` utilities and boils down
141/// to finding groups of values in the IR that are part of the same reset
142/// network (i.e., somehow attached together through ports, wires, instances,
143/// and connects). We use LLVM's `EquivalenceClasses` data structure to do
144/// this efficiently.
145///
146/// 2. Infer the type of each reset network found in step 1 by looking at the
147/// type of values connected to the network. This results in the network
148/// being declared a sync (`uint<1>`) or async (`asyncreset`) network. If the
149/// reset is never driven by a concrete type, an error is emitted.
150///
151/// 3. Walk the IR and update the type of wires and ports with the reset types
152/// found in step 2. This will replace all `reset` types in the IR with
153/// a concrete type.
154///
155/// The second stage that deals with the addition of full resets operates as
156/// follows:
157///
158/// 4. Visit every module in the design and determine if it has an explicit
159/// reset annotated. Ports of and wires in the module can have a
160/// `FullResetAnnotation`, which marks that port or wire as the reset for
161/// the module. A module may also carry a `ExcludeFromFullResetAnnotation`,
162/// which marks it as being explicitly not in a reset domain. These
163/// annotations are sparse; it is very much possible that just the top-level
164/// module in the design has a full reset annotation. A module can only
165/// ever carry one of these annotations, which puts it into one of three
166/// categories from a full reset inference perspective:
167///
168/// a. unambiguously marks a port or wire as the module's full reset
169/// b. explicitly marks it as not to have any full resets added
170/// c. inherit reset
171///
172/// 5. For every module in the design, determine the full full reset domain it
173/// is in. Note that this very narrowly deals with the inference of a
174/// "default" full reset, which basically goes through the IR and attaches
175/// all non-reset registers to a default full reset signal. If a module
176/// carries one of the annotations mentioned in (4), the annotated port or
177/// wire is used as its reset domain. Otherwise, it inherits the reset domain
178/// from parent modules. This conceptually involves looking at all the places
179/// where a module is instantiated, and recursively determining the reset
180/// domain at the instantiation site. A module can only ever be in one reset
181/// domain. In case it is inferred to lie in multiple ones, e.g., if it is
182/// instantiated in different reset domains, an error is emitted. If
183/// successful, every module is associated with a reset signal, either one of
184/// its local ports or wires, or a port or wire within one of its parent
185/// modules.
186///
187/// 6. For every module in the design, determine how full resets shall be
188/// implemented. This step handles the following distinct cases:
189///
190/// a. Skip a module because it is marked as having no reset domain.
191/// b. Use a port or wire in the module itself as reset. This is possible
192/// if the module is at the "top" of its reset domain, which means that
193/// it itself carried a reset annotation, and the reset value is either
194/// a port or wire of the module itself.
195/// c. Route a parent module's reset through a module port and use that
196/// port as the reset. This happens if the module is *not* at the "top"
197/// of its reset domain, but rather refers to a value in a parent module
198/// as its reset.
199///
200/// As a result, a module's reset domain is annotated with the existing local
201/// value to reuse (port or wire), the index of an existing port to reuse,
202/// and the name of an additional port to insert into its port list.
203///
204/// 7. For every module in the design, full resets are implemented. This
205/// determines the local value to use as the reset signal and updates the
206/// `reg` and `regreset` operations in the design. If the register already
207/// has an async reset, or if the type of the full reset is sync, the
208/// register's reset is left unchanged. If it has a sync reset and the full
209/// reset is async, the sync reset is moved into a `mux` operation on all
210/// `connect`s to the register (which the Scala code base called the
211/// `RemoveResets` pass). Finally the register is replaced with a `regreset`
212/// operation, with the reset signal determined earlier, and a "zero" value
213/// constructed for the register's type.
214///
215/// Determining the local reset value is trivial if step 6 found a module to
216/// be of case a or b. Case c is the non-trivial one, because it requires
217/// modifying the port list of the module. This is done by first determining
218/// the name of the reset signal in the parent module, which is either the
219/// name of the port or wire declaration. We then look for an existing
220/// port of the same type in the port list and reuse that as reset. If no
221/// port with that name was found, or the existing port is of the wrong type,
222/// a new port is inserted into the port list.
223///
224/// TODO: This logic is *very* brittle and error-prone. It may make sense to
225/// just add an additional port for the inferred reset in any case, with an
226/// optimization to use an existing port if all of the module's
227/// instantiations have that port connected to the desired signal already.
228///
229struct InferResetsPass
230 : public circt::firrtl::impl::InferResetsBase<InferResetsPass> {
231 void runOnOperation() override;
232 void runOnOperationInner();
233
234 // Copy creates a new empty pass (because ResetMap has no copy constructor).
235 using InferResetsBase::InferResetsBase;
236 InferResetsPass(const InferResetsPass &other) : InferResetsBase(other) {}
237
238 //===--------------------------------------------------------------------===//
239 // Reset type inference
240
241 void traceResets(CircuitOp circuit);
242 void traceResets(FInstanceLike inst);
243 void traceResets(Value dst, Value src, Location loc);
244 void traceResets(Value value);
245 void traceResets(Type dstType, Value dst, unsigned dstID, Type srcType,
246 Value src, unsigned srcID, Location loc);
247
248 LogicalResult inferAndUpdateResets();
249 FailureOr<ResetKind> inferReset(ResetNetwork net);
250 LogicalResult updateReset(ResetNetwork net, ResetKind kind);
251 bool updateReset(FieldRef field, FIRRTLBaseType resetType);
252
253 LogicalResult verifyNoAbstractReset();
254
255 //===--------------------------------------------------------------------===//
256 // Utilities
257
258 /// Get the reset network a signal belongs to.
259 ResetNetwork getResetNetwork(ResetSignal signal) {
260 return llvm::make_range(resetClasses.findLeader(signal),
261 resetClasses.member_end());
262 }
263
264 /// Get the drives of a reset network.
265 ResetDrives &getResetDrives(ResetNetwork net) {
266 return resetDrives[*net.begin()];
267 }
268
269 /// Guess the root node of a reset network, such that we have something for
270 /// the user to make sense of.
271 ResetSignal guessRoot(ResetNetwork net);
272 ResetSignal guessRoot(ResetSignal signal) {
273 return guessRoot(getResetNetwork(signal));
274 }
275
276 //===--------------------------------------------------------------------===//
277 // Analysis data
278
279 /// A map of all traced reset networks in the circuit.
280 llvm::EquivalenceClasses<ResetSignal> resetClasses;
281
282 /// A map of all connects to and from a reset.
283 DenseMap<ResetSignal, ResetDrives> resetDrives;
284
285 /// Cache of modules symbols
286 InstanceGraph *instanceGraph = nullptr;
287};
288} // namespace
289
290void InferResetsPass::runOnOperation() {
291 runOnOperationInner();
292 resetClasses = llvm::EquivalenceClasses<ResetSignal>();
293 resetDrives.clear();
294 markAnalysesPreserved<InstanceGraph>();
295}
296
297void InferResetsPass::runOnOperationInner() {
298 instanceGraph = &getAnalysis<InstanceGraph>();
299
300 // Trace the uninferred reset networks throughout the design.
301 traceResets(getOperation());
302
303 // Infer the type of the traced resets and update the IR.
304 if (failed(inferAndUpdateResets()))
305 return signalPassFailure();
306
307 // Require that no Abstract Resets exist on ports in the design.
308 if (failed(verifyNoAbstractReset()))
309 return signalPassFailure();
310}
311
312ResetSignal InferResetsPass::guessRoot(ResetNetwork net) {
313 ResetDrives &drives = getResetDrives(net);
314 ResetSignal bestSignal = *net.begin();
315 unsigned bestNumDrives = -1;
316
317 for (auto signal : net) {
318 // Don't consider `invalidvalue` for reporting as a root.
319 if (isa_and_nonnull<InvalidValueOp>(
320 signal.field.getValue().getDefiningOp()))
321 continue;
322
323 // Count the number of times this particular signal in the reset network is
324 // assigned to.
325 unsigned numDrives = 0;
326 for (auto &drive : drives)
327 if (drive.dst == signal)
328 ++numDrives;
329
330 // Keep track of the signal with the lowest number of assigns. These tend to
331 // be the signals further up the reset tree. This will usually resolve to
332 // the root of the reset tree far up in the design hierarchy.
333 if (numDrives < bestNumDrives) {
334 bestNumDrives = numDrives;
335 bestSignal = signal;
336 }
337 }
338 return bestSignal;
339}
340
341//===----------------------------------------------------------------------===//
342// Custom Field IDs
343//===----------------------------------------------------------------------===//
344
345// The following functions implement custom field IDs specifically for the use
346// in reset inference. They look much more like tracking fields on types than
347// individual values. For example, vectors don't carry separate IDs for each of
348// their elements. Instead they have one set of IDs for the entire vector, since
349// the element type is uniform across all elements.
350
351static unsigned getMaxFieldID(FIRRTLBaseType type) {
353 .Case<BundleType>([](auto type) {
354 unsigned id = 0;
355 for (auto e : type.getElements())
356 id += getMaxFieldID(e.type) + 1;
357 return id;
358 })
359 .Case<FVectorType>(
360 [](auto type) { return getMaxFieldID(type.getElementType()) + 1; })
361 .Default([](auto) { return 0; });
362}
363
364static unsigned getFieldID(BundleType type, unsigned index) {
365 assert(index < type.getNumElements());
366 unsigned id = 1;
367 for (unsigned i = 0; i < index; ++i)
368 id += getMaxFieldID(type.getElementType(i)) + 1;
369 return id;
370}
371
372static unsigned getFieldID(FVectorType type) { return 1; }
373
374static unsigned getIndexForFieldID(BundleType type, unsigned fieldID) {
375 assert(type.getNumElements() && "Bundle must have >0 fields");
376 --fieldID;
377 for (const auto &e : llvm::enumerate(type.getElements())) {
378 auto numSubfields = getMaxFieldID(e.value().type) + 1;
379 if (fieldID < numSubfields)
380 return e.index();
381 fieldID -= numSubfields;
382 }
383 assert(false && "field id outside bundle");
384 return 0;
385}
386
387// If a field is pointing to a child of a zero-length vector, it is useless.
388static bool isUselessVec(FIRRTLBaseType oldType, unsigned fieldID) {
389 if (oldType.isGround()) {
390 assert(fieldID == 0);
391 return false;
392 }
393
394 // If this is a bundle type, recurse.
395 if (auto bundleType = type_dyn_cast<BundleType>(oldType)) {
396 unsigned index = getIndexForFieldID(bundleType, fieldID);
397 return isUselessVec(bundleType.getElementType(index),
398 fieldID - getFieldID(bundleType, index));
399 }
400
401 // If this is a vector type, check if it is zero length. Anything in a
402 // zero-length vector is useless.
403 if (auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
404 if (vectorType.getNumElements() == 0)
405 return true;
406 return isUselessVec(vectorType.getElementType(),
407 fieldID - getFieldID(vectorType));
408 }
409
410 return false;
411}
412
413// If a field is pointing to a child of a zero-length vector, it is useless.
414static bool isUselessVec(FieldRef field) {
415 return isUselessVec(
416 getBaseType(type_cast<FIRRTLType>(field.getValue().getType())),
417 field.getFieldID());
418}
419
420static bool getDeclName(Value value, SmallString<32> &string) {
421 if (auto arg = dyn_cast<BlockArgument>(value)) {
422 auto module = cast<FModuleOp>(arg.getOwner()->getParentOp());
423 string += module.getPortName(arg.getArgNumber());
424 return true;
425 }
426
427 auto *op = value.getDefiningOp();
428 return TypeSwitch<Operation *, bool>(op)
429 .Case<InstanceOp, InstanceChoiceOp, MemOp>([&](auto op) {
430 string += op.getName();
431 string += ".";
432 string += op.getPortName(cast<OpResult>(value).getResultNumber());
433 return true;
434 })
435 .Case<WireOp, NodeOp, RegOp, RegResetOp>([&](auto op) {
436 string += op.getName();
437 return true;
438 })
439 .Default([](auto) { return false; });
440}
441
442static bool getFieldName(const FieldRef &fieldRef, SmallString<32> &string) {
443 SmallString<64> name;
444 auto value = fieldRef.getValue();
445 if (!getDeclName(value, string))
446 return false;
447
448 auto type = value.getType();
449 auto localID = fieldRef.getFieldID();
450 while (localID) {
451 if (auto bundleType = type_dyn_cast<BundleType>(type)) {
452 auto index = getIndexForFieldID(bundleType, localID);
453 // Add the current field string, and recurse into a subfield.
454 auto &element = bundleType.getElements()[index];
455 if (!string.empty())
456 string += ".";
457 string += element.name.getValue();
458 // Recurse in to the element type.
459 type = element.type;
460 localID = localID - getFieldID(bundleType, index);
461 } else if (auto vecType = type_dyn_cast<FVectorType>(type)) {
462 string += "[]";
463 // Recurse in to the element type.
464 type = vecType.getElementType();
465 localID = localID - getFieldID(vecType);
466 } else {
467 // If we reach here, the field ref is pointing inside some aggregate type
468 // that isn't a bundle or a vector. If the type is a ground type, then the
469 // localID should be 0 at this point, and we should have broken from the
470 // loop.
471 llvm_unreachable("unsupported type");
472 }
473 }
474 return true;
475}
476
477//===----------------------------------------------------------------------===//
478// Reset Tracing
479//===----------------------------------------------------------------------===//
480
481/// Check whether a type contains a `ResetType`.
482static bool typeContainsReset(Type type) {
483 return TypeSwitch<Type, bool>(type)
484 .Case<FIRRTLType>([](auto type) {
485 return type.getRecursiveTypeProperties().hasUninferredReset;
486 })
487 .Default([](auto) { return false; });
488}
489
490/// Iterate over a circuit and follow all signals with `ResetType`, aggregating
491/// them into reset nets. After this function returns, the `resetMap` is
492/// populated with the reset networks in the circuit, alongside information on
493/// drivers and their types that contribute to the reset.
494void InferResetsPass::traceResets(CircuitOp circuit) {
495 LLVM_DEBUG({
496 llvm::dbgs() << "\n";
497 debugHeader("Tracing uninferred resets") << "\n\n";
498 });
499
500 SmallVector<std::pair<FModuleOp, SmallVector<Operation *>>> moduleToOps;
501
502 for (auto module : circuit.getOps<FModuleOp>())
503 moduleToOps.push_back({module, {}});
504
505 hw::InnerRefNamespace irn{getAnalysis<SymbolTable>(),
506 getAnalysis<hw::InnerSymbolTableCollection>()};
507
508 mlir::parallelForEach(circuit.getContext(), moduleToOps, [](auto &e) {
509 e.first.walk([&](Operation *op) {
510 // We are only interested in operations which are related to abstract
511 // reset.
512 if (llvm::any_of(
513 op->getResultTypes(),
514 [](mlir::Type type) { return typeContainsReset(type); }) ||
515 llvm::any_of(op->getOperandTypes(), typeContainsReset))
516 e.second.push_back(op);
517 });
518 });
519
520 for (auto &[_, ops] : moduleToOps)
521 for (auto *op : ops) {
522 TypeSwitch<Operation *>(op)
523 .Case<FConnectLike>([&](auto op) {
524 traceResets(op.getDest(), op.getSrc(), op.getLoc());
525 })
526 .Case<FInstanceLike>([&](auto op) { traceResets(op); })
527 .Case<RefSendOp>([&](auto op) {
528 // Trace using base types.
529 traceResets(op.getType().getType(), op.getResult(), 0,
530 op.getBase().getType().getPassiveType(), op.getBase(),
531 0, op.getLoc());
532 })
533 .Case<RefResolveOp>([&](auto op) {
534 // Trace using base types.
535 traceResets(op.getType(), op.getResult(), 0,
536 op.getRef().getType().getType(), op.getRef(), 0,
537 op.getLoc());
538 })
539 .Case<Forceable>([&](Forceable op) {
540 if (auto node = dyn_cast<NodeOp>(op.getOperation()))
541 traceResets(node.getResult(), node.getInput(), node.getLoc());
542 // Trace reset into rwprobe. Avoid invalid IR.
543 if (op.isForceable())
544 traceResets(op.getDataType(), op.getData(), 0, op.getDataType(),
545 op.getDataRef(), 0, op.getLoc());
546 })
547 .Case<RWProbeOp>([&](RWProbeOp op) {
548 auto ist = irn.lookup(op.getTarget());
549 assert(ist);
550 auto ref = getFieldRefForTarget(ist);
551 auto baseType = op.getType().getType();
552 traceResets(baseType, op.getResult(), 0, baseType.getPassiveType(),
553 ref.getValue(), ref.getFieldID(), op.getLoc());
554 })
555 .Case<UninferredResetCastOp, ConstCastOp, RefCastOp,
556 UnsafeDomainCastOp>([&](auto op) {
557 traceResets(op.getResult(), op.getInput(), op.getLoc());
558 })
559 .Case<InvalidValueOp>([&](auto op) {
560 // Uniquify `InvalidValueOp`s that are contributing to multiple
561 // reset networks. These are tricky to handle because passes
562 // like CSE will generally ensure that there is only a single
563 // `InvalidValueOp` per type. However, a `reset` invalid value
564 // may be connected to two reset networks that end up being
565 // inferred as `asyncreset` and `uint<1>`. In that case, we need
566 // a distinct `InvalidValueOp` for each reset network in order
567 // to assign it the correct type.
568 auto type = op.getType();
569 if (!typeContainsReset(type) || op->hasOneUse() || op->use_empty())
570 return;
571 LLVM_DEBUG(llvm::dbgs() << "Uniquify " << op << "\n");
572 ImplicitLocOpBuilder builder(op->getLoc(), op);
573 for (auto &use :
574 llvm::make_early_inc_range(llvm::drop_begin(op->getUses()))) {
575 // - `make_early_inc_range` since `getUses()` is invalidated
576 // upon
577 // `use.set(...)`.
578 // - `drop_begin` such that the first use can keep the
579 // original op.
580 auto newOp = InvalidValueOp::create(builder, type);
581 use.set(newOp);
582 }
583 })
584
585 .Case<SubfieldOp>([&](auto op) {
586 // Associate the input bundle's resets with the output field's
587 // resets.
588 BundleType bundleType = op.getInput().getType();
589 auto index = op.getFieldIndex();
590 traceResets(op.getType(), op.getResult(), 0,
591 bundleType.getElements()[index].type, op.getInput(),
592 getFieldID(bundleType, index), op.getLoc());
593 })
594
595 .Case<SubindexOp, SubaccessOp>([&](auto op) {
596 // Associate the input vector's resets with the output field's
597 // resets.
598 //
599 // This collapses all elements in vectors into one shared
600 // element which will ensure that reset inference provides a
601 // uniform result for all elements.
602 //
603 // CAVEAT: This may infer reset networks that are too big, since
604 // unrelated resets in the same vector end up looking as if they
605 // were connected. However for the sake of type inference, this
606 // is indistinguishable from them having to share the same type
607 // (namely the vector element type).
608 FVectorType vectorType = op.getInput().getType();
609 traceResets(op.getType(), op.getResult(), 0,
610 vectorType.getElementType(), op.getInput(),
611 getFieldID(vectorType), op.getLoc());
612 })
613
614 .Case<RefSubOp>([&](RefSubOp op) {
615 // Trace through ref.sub.
616 auto aggType = op.getInput().getType().getType();
617 uint64_t fieldID = TypeSwitch<FIRRTLBaseType, uint64_t>(aggType)
618 .Case<FVectorType>([](auto type) {
619 return getFieldID(type);
620 })
621 .Case<BundleType>([&](auto type) {
622 return getFieldID(type, op.getIndex());
623 });
624 traceResets(op.getType(), op.getResult(), 0,
625 op.getResult().getType(), op.getInput(), fieldID,
626 op.getLoc());
627 });
628 }
629}
630
631/// Trace reset signals through an instance or instance choice. This essentially
632/// associates the instance's port values with the target module's port values.
633void InferResetsPass::traceResets(FInstanceLike inst) {
634 LLVM_DEBUG(llvm::dbgs() << "Visiting instance " << inst.getInstanceName()
635 << "\n");
636 auto moduleNames = inst.getReferencedModuleNamesAttr();
637 for (auto moduleName : moduleNames.getAsRange<StringAttr>()) {
638 auto *node = instanceGraph->lookup(moduleName);
639 auto module = dyn_cast<FModuleOp>(*node->getModule());
640 if (!module)
641 return;
642
643 // Establish a connection between the instance ports and module ports.
644 for (const auto &it : llvm::enumerate(inst->getResults())) {
645 Value dstPort = module.getArgument(it.index());
646 Value srcPort = it.value();
647 if (module.getPortDirection(it.index()) == Direction::Out)
648 std::swap(dstPort, srcPort);
649 traceResets(dstPort, srcPort, it.value().getLoc());
650 }
651 }
652}
653
654/// Analyze a connect of one (possibly aggregate) value to another.
655/// Each drive involving a `ResetType` is recorded.
656void InferResetsPass::traceResets(Value dst, Value src, Location loc) {
657 // Analyze the actual connection.
658 traceResets(dst.getType(), dst, 0, src.getType(), src, 0, loc);
659}
660
661/// Analyze a connect of one (possibly aggregate) value to another.
662/// Each drive involving a `ResetType` is recorded.
663void InferResetsPass::traceResets(Type dstType, Value dst, unsigned dstID,
664 Type srcType, Value src, unsigned srcID,
665 Location loc) {
666 if (auto dstBundle = type_dyn_cast<BundleType>(dstType)) {
667 auto srcBundle = type_cast<BundleType>(srcType);
668 for (unsigned dstIdx = 0, e = dstBundle.getNumElements(); dstIdx < e;
669 ++dstIdx) {
670 auto dstField = dstBundle.getElements()[dstIdx].name;
671 auto srcIdx = srcBundle.getElementIndex(dstField);
672 if (!srcIdx)
673 continue;
674 auto &dstElt = dstBundle.getElements()[dstIdx];
675 auto &srcElt = srcBundle.getElements()[*srcIdx];
676 if (dstElt.isFlip) {
677 traceResets(srcElt.type, src, srcID + getFieldID(srcBundle, *srcIdx),
678 dstElt.type, dst, dstID + getFieldID(dstBundle, dstIdx),
679 loc);
680 } else {
681 traceResets(dstElt.type, dst, dstID + getFieldID(dstBundle, dstIdx),
682 srcElt.type, src, srcID + getFieldID(srcBundle, *srcIdx),
683 loc);
684 }
685 }
686 return;
687 }
688
689 if (auto dstVector = type_dyn_cast<FVectorType>(dstType)) {
690 auto srcVector = type_cast<FVectorType>(srcType);
691 auto srcElType = srcVector.getElementType();
692 auto dstElType = dstVector.getElementType();
693 // Collapse all elements into one shared element. See comment in traceResets
694 // above for some context. Note that we are directly passing on the field ID
695 // of the vector itself as a stand-in for its element type. This is not
696 // really what `FieldRef` is designed to do, but tends to work since all the
697 // places that need to reason about the resulting weird IDs are inside this
698 // file. Normally you would pick a specific index from the vector, which
699 // would also move the field ID forward by some amount. However, we can't
700 // distinguish individual elements for the sake of type inference *and* we
701 // have to support zero-length vectors for which the only available ID is
702 // the vector itself. Therefore we always just pick the vector itself for
703 // the field ID and make sure in `updateType` that we handle vectors
704 // accordingly.
705 traceResets(dstElType, dst, dstID + getFieldID(dstVector), srcElType, src,
706 srcID + getFieldID(srcVector), loc);
707 return;
708 }
709
710 // Handle connecting ref's. Other uses trace using base type.
711 if (auto dstRef = type_dyn_cast<RefType>(dstType)) {
712 auto srcRef = type_cast<RefType>(srcType);
713 return traceResets(dstRef.getType(), dst, dstID, srcRef.getType(), src,
714 srcID, loc);
715 }
716
717 // Handle reset connections.
718 auto dstBase = type_dyn_cast<FIRRTLBaseType>(dstType);
719 auto srcBase = type_dyn_cast<FIRRTLBaseType>(srcType);
720 if (!dstBase || !srcBase)
721 return;
722 if (!type_isa<ResetType>(dstBase) && !type_isa<ResetType>(srcBase))
723 return;
724
725 FieldRef dstField(dst, dstID);
726 FieldRef srcField(src, srcID);
727 LLVM_DEBUG(llvm::dbgs() << "Visiting driver '" << dstField << "' = '"
728 << srcField << "' (" << dstType << " = " << srcType
729 << ")\n");
730
731 // Determine the leaders for the dst and src reset networks before we make
732 // the connection. This will allow us to later detect if dst got merged
733 // into src, or src into dst.
734 ResetSignal dstLeader =
735 *resetClasses.findLeader(resetClasses.insert({dstField, dstBase}));
736 ResetSignal srcLeader =
737 *resetClasses.findLeader(resetClasses.insert({srcField, srcBase}));
738
739 // Unify the two reset networks.
740 ResetSignal unionLeader = *resetClasses.unionSets(dstLeader, srcLeader);
741 assert(unionLeader == dstLeader || unionLeader == srcLeader);
742
743 // If dst got merged into src, append dst's drives to src's, or vice
744 // versa. Also, remove dst's or src's entry in resetDrives, because they
745 // will never come up as a leader again.
746 if (dstLeader != srcLeader) {
747 auto &unionDrives = resetDrives[unionLeader]; // needed before finds
748 auto mergedDrivesIt =
749 resetDrives.find(unionLeader == dstLeader ? srcLeader : dstLeader);
750 if (mergedDrivesIt != resetDrives.end()) {
751 unionDrives.append(mergedDrivesIt->second);
752 resetDrives.erase(mergedDrivesIt);
753 }
754 }
755
756 // Keep note of this drive so we can point the user at the right location
757 // in case something goes wrong.
758 resetDrives[unionLeader].push_back(
759 {{dstField, dstBase}, {srcField, srcBase}, loc});
760}
761
762//===----------------------------------------------------------------------===//
763// Reset Inference
764//===----------------------------------------------------------------------===//
765
766LogicalResult InferResetsPass::inferAndUpdateResets() {
767 LLVM_DEBUG({
768 llvm::dbgs() << "\n";
769 debugHeader("Infer reset types") << "\n\n";
770 });
771 for (const auto &it : resetClasses) {
772 if (!it->isLeader())
773 continue;
774 ResetNetwork net = resetClasses.members(*it);
775
776 // Infer whether this should be a sync or async reset.
777 auto kind = inferReset(net);
778 if (failed(kind))
779 return failure();
780
781 // Update the types in the IR to match the inferred kind.
782 if (failed(updateReset(net, *kind)))
783 return failure();
784 }
785 return success();
786}
787
788FailureOr<ResetKind> InferResetsPass::inferReset(ResetNetwork net) {
789 LLVM_DEBUG(llvm::dbgs() << "Inferring reset network with "
790 << std::distance(net.begin(), net.end())
791 << " nodes\n");
792
793 // Go through the nodes and track the involved types.
794 unsigned asyncDrives = 0;
795 unsigned syncDrives = 0;
796 unsigned invalidDrives = 0;
797 for (ResetSignal signal : net) {
798 // Keep track of whether this signal contributes a vote for async or sync.
799 if (type_isa<AsyncResetType>(signal.type))
800 ++asyncDrives;
801 else if (type_isa<UIntType>(signal.type))
802 ++syncDrives;
803 else if (isUselessVec(signal.field) ||
804 isa_and_nonnull<InvalidValueOp>(
805 signal.field.getValue().getDefiningOp()))
806 ++invalidDrives;
807 }
808 LLVM_DEBUG(llvm::dbgs() << "- Found " << asyncDrives << " async, "
809 << syncDrives << " sync, " << invalidDrives
810 << " invalid drives\n");
811
812 // Handle the case where we have no votes for either kind.
813 if (asyncDrives == 0 && syncDrives == 0 && invalidDrives == 0) {
814 ResetSignal root = guessRoot(net);
815 auto diag = mlir::emitError(root.field.getValue().getLoc())
816 << "reset network never driven with concrete type";
817 for (ResetSignal signal : net)
818 diag.attachNote(signal.field.getLoc()) << "here: ";
819 return failure();
820 }
821
822 // Handle the case where we have votes for both kinds.
823 if (asyncDrives > 0 && syncDrives > 0) {
824 ResetSignal root = guessRoot(net);
825 bool majorityAsync = asyncDrives >= syncDrives;
826 auto diag = mlir::emitError(root.field.getValue().getLoc())
827 << "reset network";
828 SmallString<32> fieldName;
829 if (getFieldName(root.field, fieldName))
830 diag << " \"" << fieldName << "\"";
831 diag << " simultaneously connected to async and sync resets";
832 diag.attachNote(root.field.getValue().getLoc())
833 << "majority of connections to this reset are "
834 << (majorityAsync ? "async" : "sync");
835 for (auto &drive : getResetDrives(net)) {
836 if ((type_isa<AsyncResetType>(drive.dst.type) && !majorityAsync) ||
837 (type_isa<AsyncResetType>(drive.src.type) && !majorityAsync) ||
838 (type_isa<UIntType>(drive.dst.type) && majorityAsync) ||
839 (type_isa<UIntType>(drive.src.type) && majorityAsync))
840 diag.attachNote(drive.loc)
841 << (type_isa<AsyncResetType>(drive.src.type) ? "async" : "sync")
842 << " drive here:";
843 }
844 return failure();
845 }
846
847 // At this point we know that the type of the reset is unambiguous. If there
848 // are any votes for async, we make the reset async. Otherwise we make it
849 // sync.
850 auto kind = (asyncDrives ? ResetKind::Async : ResetKind::Sync);
851 LLVM_DEBUG(llvm::dbgs() << "- Inferred as " << kind << "\n");
852 return kind;
853}
854
855//===----------------------------------------------------------------------===//
856// Reset Updating
857//===----------------------------------------------------------------------===//
858
859LogicalResult InferResetsPass::updateReset(ResetNetwork net, ResetKind kind) {
860 LLVM_DEBUG(llvm::dbgs() << "Updating reset network with "
861 << std::distance(net.begin(), net.end())
862 << " nodes to " << kind << "\n");
863
864 // Determine the final type the reset should have.
865 FIRRTLBaseType resetType;
866 if (kind == ResetKind::Async)
867 resetType = AsyncResetType::get(&getContext());
868 else
869 resetType = UIntType::get(&getContext(), 1);
870
871 // Update all those values in the network that cannot be inferred from
872 // operands. If we change the type of a module port (i.e. BlockArgument), add
873 // the module to a module worklist since we need to update its function type.
875 SmallDenseSet<Operation *> moduleWorklist;
876 SmallDenseSet<std::pair<Operation *, Operation *>> extmoduleWorklist;
877 for (auto signal : net) {
878 Value value = signal.field.getValue();
879 if (!isa<BlockArgument>(value) &&
880 !isa_and_nonnull<WireOp, RegOp, RegResetOp, FInstanceLike,
881 InvalidValueOp, ConstCastOp, RefCastOp,
882 UninferredResetCastOp, RWProbeOp, AsResetPrimOp>(
883 value.getDefiningOp()))
884 continue;
885 if (updateReset(signal.field, resetType)) {
886 for (auto *user : value.getUsers())
887 worklist.insert(user);
888 if (auto blockArg = dyn_cast<BlockArgument>(value)) {
889 moduleWorklist.insert(blockArg.getOwner()->getParentOp());
890 continue;
891 }
892
893 TypeSwitch<Operation *>(value.getDefiningOp())
894 .Case<FInstanceLike>([&](FInstanceLike op) {
895 for (auto moduleName : op.getReferencedModuleNamesAttr()) {
896 auto *node = instanceGraph->lookup(cast<StringAttr>(moduleName));
897 if (auto refModule = dyn_cast<FExtModuleOp>(*node->getModule()))
898 extmoduleWorklist.insert({refModule, op.getOperation()});
899 }
900 })
901 .Case<UninferredResetCastOp>([&](auto op) {
902 op.replaceAllUsesWith(op.getInput());
903 op.erase();
904 })
905 .Case<AsResetPrimOp>([&](auto op) {
906 // Remove `asReset` casts for sync resets, or replace them with an
907 // `asAsyncReset` cast for async resets.
908 Value result = op.getInput();
909 if (type_isa<AsyncResetType>(resetType)) {
910 ImplicitLocOpBuilder builder(op.getLoc(), op);
911 result = AsAsyncResetPrimOp::create(builder, op.getInput());
912 }
913 op.replaceAllUsesWith(result);
914 op.erase();
915 });
916 }
917 }
918
919 // Process the worklist of operations that have their type changed, pushing
920 // types down the SSA dataflow graph. This is important because we change the
921 // reset types in aggregates, and then need all the subindex, subfield, and
922 // subaccess operations to be updated as appropriate.
923 while (!worklist.empty()) {
924 auto *wop = worklist.pop_back_val();
925 SmallVector<Type, 2> types;
926 if (auto op = dyn_cast<InferTypeOpInterface>(wop)) {
927 // Determine the new result types.
928 SmallVector<Type, 2> types;
929 if (failed(op.inferReturnTypes(op->getContext(), op->getLoc(),
930 op->getOperands(), op->getAttrDictionary(),
931 op->getPropertiesStorage(),
932 op->getRegions(), types)))
933 return failure();
934
935 // Update the results and add the changed ones to the
936 // worklist.
937 for (auto it : llvm::zip(op->getResults(), types)) {
938 auto newType = std::get<1>(it);
939 if (std::get<0>(it).getType() == newType)
940 continue;
941 std::get<0>(it).setType(newType);
942 for (auto *user : std::get<0>(it).getUsers())
943 worklist.insert(user);
944 }
945 LLVM_DEBUG(llvm::dbgs() << "- Inferred " << *op << "\n");
946 } else if (auto uop = dyn_cast<UninferredResetCastOp>(wop)) {
947 for (auto *user : uop.getResult().getUsers())
948 worklist.insert(user);
949 uop.replaceAllUsesWith(uop.getInput());
950 LLVM_DEBUG(llvm::dbgs() << "- Inferred " << uop << "\n");
951 uop.erase();
952 }
953 }
954
955 // Update module types based on the type of the block arguments.
956 for (auto *op : moduleWorklist) {
957 auto module = dyn_cast<FModuleOp>(op);
958 if (!module)
959 continue;
960
961 SmallVector<Attribute> argTypes;
962 argTypes.reserve(module.getNumPorts());
963 for (auto arg : module.getArguments())
964 argTypes.push_back(TypeAttr::get(arg.getType()));
965
966 module.setPortTypesAttr(ArrayAttr::get(op->getContext(), argTypes));
967 LLVM_DEBUG(llvm::dbgs()
968 << "- Updated type of module '" << module.getName() << "'\n");
969 }
970
971 // Update extmodule types based on their instantiation.
972 for (auto [mod, instOp] : extmoduleWorklist) {
973 auto module = cast<FExtModuleOp>(mod);
974
975 SmallVector<Attribute> types;
976 for (auto type : instOp->getResultTypes())
977 types.push_back(TypeAttr::get(type));
978
979 module.setPortTypesAttr(ArrayAttr::get(module->getContext(), types));
980 LLVM_DEBUG(llvm::dbgs()
981 << "- Updated type of extmodule '" << module.getName() << "'\n");
982 }
983
984 return success();
985}
986
987/// Update the type of a single field within a type.
988static FIRRTLBaseType updateType(FIRRTLBaseType oldType, unsigned fieldID,
989 FIRRTLBaseType fieldType) {
990 // If this is a ground type, simply replace it, preserving constness.
991 if (oldType.isGround()) {
992 assert(fieldID == 0);
993 return fieldType.getConstType(oldType.isConst());
994 }
995
996 // If this is a bundle type, update the corresponding field.
997 if (auto bundleType = type_dyn_cast<BundleType>(oldType)) {
998 unsigned index = getIndexForFieldID(bundleType, fieldID);
999 SmallVector<BundleType::BundleElement> fields(bundleType.begin(),
1000 bundleType.end());
1001 fields[index].type = updateType(
1002 fields[index].type, fieldID - getFieldID(bundleType, index), fieldType);
1003 return BundleType::get(oldType.getContext(), fields, bundleType.isConst());
1004 }
1005
1006 // If this is a vector type, update the element type.
1007 if (auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
1008 auto newType = updateType(vectorType.getElementType(),
1009 fieldID - getFieldID(vectorType), fieldType);
1010 return FVectorType::get(newType, vectorType.getNumElements(),
1011 vectorType.isConst());
1012 }
1013
1014 llvm_unreachable("unknown aggregate type");
1015 return oldType;
1016}
1017
1018/// Update the reset type of a specific field.
1019bool InferResetsPass::updateReset(FieldRef field, FIRRTLBaseType resetType) {
1020 // Compute the updated type.
1021 auto oldType = type_cast<FIRRTLType>(field.getValue().getType());
1022 FIRRTLType newType = mapBaseType(oldType, [&](auto base) {
1023 return updateType(base, field.getFieldID(), resetType);
1024 });
1025
1026 // Update the type if necessary.
1027 if (oldType == newType)
1028 return false;
1029 LLVM_DEBUG(llvm::dbgs() << "- Updating '" << field << "' from " << oldType
1030 << " to " << newType << "\n");
1031 field.getValue().setType(newType);
1032 return true;
1033}
1034
1035LogicalResult InferResetsPass::verifyNoAbstractReset() {
1036 bool hasAbstractResetPorts = false;
1037 for (FModuleLike module :
1038 getOperation().getBodyBlock()->getOps<FModuleLike>()) {
1039 for (PortInfo port : module.getPorts()) {
1040 if (getBaseOfType<ResetType>(port.type)) {
1041 auto diag = emitError(port.loc)
1042 << "a port \"" << port.getName()
1043 << "\" with abstract reset type was unable to be "
1044 "inferred by InferResets (is this a top-level port?)";
1045 diag.attachNote(module->getLoc())
1046 << "the module with this uninferred reset port was defined here";
1047 hasAbstractResetPorts = true;
1048 }
1049 }
1050 }
1051
1052 if (hasAbstractResetPorts)
1053 return failure();
1054 return success();
1055}
assert(baseType &&"element must be base type")
static unsigned getFieldID(BundleType type, unsigned index)
static unsigned getIndexForFieldID(BundleType type, unsigned fieldID)
static FIRRTLBaseType updateType(FIRRTLBaseType oldType, unsigned fieldID, FIRRTLBaseType fieldType)
Update the type of a single field within a type.
static bool isUselessVec(FIRRTLBaseType oldType, unsigned fieldID)
static bool typeContainsReset(Type type)
Check whether a type contains a ResetType.
static bool getDeclName(Value value, SmallString< 32 > &string)
static unsigned getMaxFieldID(FIRRTLBaseType type)
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:222
static Block * getBodyBlock(FModuleLike mod)
static InstancePath empty
This class represents a reference to a specific field or element of an aggregate value.
Definition FieldRef.h:28
unsigned getFieldID() const
Get the field ID of this FieldRef, which is a unique identifier mapped to a specific field in a bundl...
Definition FieldRef.h:61
Value getValue() const
Get the Value which created this location.
Definition FieldRef.h:39
FIRRTLBaseType getConstType(bool isConst) const
Return a 'const' or non-'const' version of this type.
bool isConst() const
Returns true if this is a 'const' type that can only hold compile-time constant values.
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.
An instance path composed of a series of instances.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:56
FieldRef getFieldRefForTarget(const hw::InnerSymTarget &ist)
Get FieldRef pointing to the specified inner symbol target, which must be valid.
FIRRTLBaseType getBaseType(Type type)
If it is a base type, return it as is.
FIRRTLType mapBaseType(FIRRTLType type, function_ref< FIRRTLBaseType(FIRRTLBaseType)> fn)
Return a FIRRTLType with its base type component mutated by the given function.
llvm::raw_ostream & operator<<(llvm::raw_ostream &os, const InstanceInfo::LatticeValue &value)
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
static bool operator==(const ModulePort &a, const ModulePort &b)
Definition HWTypes.h:63
static llvm::hash_code hash_value(const ModulePort &port)
Definition HWTypes.h:66
bool operator<(const DictEntry &entry, const DictEntry &other)
Definition RTGTypes.h:25
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
This holds the name and type that describes the module's ports.
This class represents the namespace in which InnerRef's can be resolved.
A data structure that caches and provides paths to module instances in the IR.
static bool isEqual(const ResetSignal &lhs, const ResetSignal &rhs)
static unsigned getHashValue(const ResetSignal &x)