CIRCT 23.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 if (failed(runFullReset(getOperation(), *instanceGraph,
308 getAnalysis<InstanceInfo>())))
309 return signalPassFailure();
310
311 // Require that no Abstract Resets exist on ports in the design.
312 if (failed(verifyNoAbstractReset()))
313 return signalPassFailure();
314}
315
316ResetSignal InferResetsPass::guessRoot(ResetNetwork net) {
317 ResetDrives &drives = getResetDrives(net);
318 ResetSignal bestSignal = *net.begin();
319 unsigned bestNumDrives = -1;
320
321 for (auto signal : net) {
322 // Don't consider `invalidvalue` for reporting as a root.
323 if (isa_and_nonnull<InvalidValueOp>(
324 signal.field.getValue().getDefiningOp()))
325 continue;
326
327 // Count the number of times this particular signal in the reset network is
328 // assigned to.
329 unsigned numDrives = 0;
330 for (auto &drive : drives)
331 if (drive.dst == signal)
332 ++numDrives;
333
334 // Keep track of the signal with the lowest number of assigns. These tend to
335 // be the signals further up the reset tree. This will usually resolve to
336 // the root of the reset tree far up in the design hierarchy.
337 if (numDrives < bestNumDrives) {
338 bestNumDrives = numDrives;
339 bestSignal = signal;
340 }
341 }
342 return bestSignal;
343}
344
345//===----------------------------------------------------------------------===//
346// Custom Field IDs
347//===----------------------------------------------------------------------===//
348
349// The following functions implement custom field IDs specifically for the use
350// in reset inference. They look much more like tracking fields on types than
351// individual values. For example, vectors don't carry separate IDs for each of
352// their elements. Instead they have one set of IDs for the entire vector, since
353// the element type is uniform across all elements.
354
355static unsigned getMaxFieldID(FIRRTLBaseType type) {
357 .Case<BundleType>([](auto type) {
358 unsigned id = 0;
359 for (auto e : type.getElements())
360 id += getMaxFieldID(e.type) + 1;
361 return id;
362 })
363 .Case<FVectorType>(
364 [](auto type) { return getMaxFieldID(type.getElementType()) + 1; })
365 .Default([](auto) { return 0; });
366}
367
368static unsigned getFieldID(BundleType type, unsigned index) {
369 assert(index < type.getNumElements());
370 unsigned id = 1;
371 for (unsigned i = 0; i < index; ++i)
372 id += getMaxFieldID(type.getElementType(i)) + 1;
373 return id;
374}
375
376static unsigned getFieldID(FVectorType type) { return 1; }
377
378static unsigned getIndexForFieldID(BundleType type, unsigned fieldID) {
379 assert(type.getNumElements() && "Bundle must have >0 fields");
380 --fieldID;
381 for (const auto &e : llvm::enumerate(type.getElements())) {
382 auto numSubfields = getMaxFieldID(e.value().type) + 1;
383 if (fieldID < numSubfields)
384 return e.index();
385 fieldID -= numSubfields;
386 }
387 assert(false && "field id outside bundle");
388 return 0;
389}
390
391// If a field is pointing to a child of a zero-length vector, it is useless.
392static bool isUselessVec(FIRRTLBaseType oldType, unsigned fieldID) {
393 if (oldType.isGround()) {
394 assert(fieldID == 0);
395 return false;
396 }
397
398 // If this is a bundle type, recurse.
399 if (auto bundleType = type_dyn_cast<BundleType>(oldType)) {
400 unsigned index = getIndexForFieldID(bundleType, fieldID);
401 return isUselessVec(bundleType.getElementType(index),
402 fieldID - getFieldID(bundleType, index));
403 }
404
405 // If this is a vector type, check if it is zero length. Anything in a
406 // zero-length vector is useless.
407 if (auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
408 if (vectorType.getNumElements() == 0)
409 return true;
410 return isUselessVec(vectorType.getElementType(),
411 fieldID - getFieldID(vectorType));
412 }
413
414 return false;
415}
416
417// If a field is pointing to a child of a zero-length vector, it is useless.
418static bool isUselessVec(FieldRef field) {
419 return isUselessVec(
420 getBaseType(type_cast<FIRRTLType>(field.getValue().getType())),
421 field.getFieldID());
422}
423
424static bool getDeclName(Value value, SmallString<32> &string) {
425 if (auto arg = dyn_cast<BlockArgument>(value)) {
426 auto module = cast<FModuleOp>(arg.getOwner()->getParentOp());
427 string += module.getPortName(arg.getArgNumber());
428 return true;
429 }
430
431 auto *op = value.getDefiningOp();
432 return TypeSwitch<Operation *, bool>(op)
433 .Case<InstanceOp, InstanceChoiceOp, MemOp>([&](auto op) {
434 string += op.getName();
435 string += ".";
436 string += op.getPortName(cast<OpResult>(value).getResultNumber());
437 return true;
438 })
439 .Case<WireOp, NodeOp, RegOp, RegResetOp>([&](auto op) {
440 string += op.getName();
441 return true;
442 })
443 .Default([](auto) { return false; });
444}
445
446static bool getFieldName(const FieldRef &fieldRef, SmallString<32> &string) {
447 SmallString<64> name;
448 auto value = fieldRef.getValue();
449 if (!getDeclName(value, string))
450 return false;
451
452 auto type = value.getType();
453 auto localID = fieldRef.getFieldID();
454 while (localID) {
455 if (auto bundleType = type_dyn_cast<BundleType>(type)) {
456 auto index = getIndexForFieldID(bundleType, localID);
457 // Add the current field string, and recurse into a subfield.
458 auto &element = bundleType.getElements()[index];
459 if (!string.empty())
460 string += ".";
461 string += element.name.getValue();
462 // Recurse in to the element type.
463 type = element.type;
464 localID = localID - getFieldID(bundleType, index);
465 } else if (auto vecType = type_dyn_cast<FVectorType>(type)) {
466 string += "[]";
467 // Recurse in to the element type.
468 type = vecType.getElementType();
469 localID = localID - getFieldID(vecType);
470 } else {
471 // If we reach here, the field ref is pointing inside some aggregate type
472 // that isn't a bundle or a vector. If the type is a ground type, then the
473 // localID should be 0 at this point, and we should have broken from the
474 // loop.
475 llvm_unreachable("unsupported type");
476 }
477 }
478 return true;
479}
480
481//===----------------------------------------------------------------------===//
482// Reset Tracing
483//===----------------------------------------------------------------------===//
484
485/// Check whether a type contains a `ResetType`.
486static bool typeContainsReset(Type type) {
487 return TypeSwitch<Type, bool>(type)
488 .Case<FIRRTLType>([](auto type) {
489 return type.getRecursiveTypeProperties().hasUninferredReset;
490 })
491 .Default([](auto) { return false; });
492}
493
494/// Iterate over a circuit and follow all signals with `ResetType`, aggregating
495/// them into reset nets. After this function returns, the `resetMap` is
496/// populated with the reset networks in the circuit, alongside information on
497/// drivers and their types that contribute to the reset.
498void InferResetsPass::traceResets(CircuitOp circuit) {
499 LLVM_DEBUG({
500 llvm::dbgs() << "\n";
501 debugHeader("Tracing uninferred resets") << "\n\n";
502 });
503
504 SmallVector<std::pair<FModuleOp, SmallVector<Operation *>>> moduleToOps;
505
506 for (auto module : circuit.getOps<FModuleOp>())
507 moduleToOps.push_back({module, {}});
508
509 hw::InnerRefNamespace irn{getAnalysis<SymbolTable>(),
510 getAnalysis<hw::InnerSymbolTableCollection>()};
511
512 mlir::parallelForEach(circuit.getContext(), moduleToOps, [](auto &e) {
513 e.first.walk([&](Operation *op) {
514 // We are only interested in operations which are related to abstract
515 // reset.
516 if (llvm::any_of(
517 op->getResultTypes(),
518 [](mlir::Type type) { return typeContainsReset(type); }) ||
519 llvm::any_of(op->getOperandTypes(), typeContainsReset))
520 e.second.push_back(op);
521 });
522 });
523
524 for (auto &[_, ops] : moduleToOps)
525 for (auto *op : ops) {
526 TypeSwitch<Operation *>(op)
527 .Case<FConnectLike>([&](auto op) {
528 traceResets(op.getDest(), op.getSrc(), op.getLoc());
529 })
530 .Case<FInstanceLike>([&](auto op) { traceResets(op); })
531 .Case<RefSendOp>([&](auto op) {
532 // Trace using base types.
533 traceResets(op.getType().getType(), op.getResult(), 0,
534 op.getBase().getType().getPassiveType(), op.getBase(),
535 0, op.getLoc());
536 })
537 .Case<RefResolveOp>([&](auto op) {
538 // Trace using base types.
539 traceResets(op.getType(), op.getResult(), 0,
540 op.getRef().getType().getType(), op.getRef(), 0,
541 op.getLoc());
542 })
543 .Case<Forceable>([&](Forceable op) {
544 if (auto node = dyn_cast<NodeOp>(op.getOperation()))
545 traceResets(node.getResult(), node.getInput(), node.getLoc());
546 // Trace reset into rwprobe. Avoid invalid IR.
547 if (op.isForceable())
548 traceResets(op.getDataType(), op.getData(), 0, op.getDataType(),
549 op.getDataRef(), 0, op.getLoc());
550 })
551 .Case<RWProbeOp>([&](RWProbeOp op) {
552 auto ist = irn.lookup(op.getTarget());
553 assert(ist);
554 auto ref = getFieldRefForTarget(ist);
555 auto baseType = op.getType().getType();
556 traceResets(baseType, op.getResult(), 0, baseType.getPassiveType(),
557 ref.getValue(), ref.getFieldID(), op.getLoc());
558 })
559 .Case<UninferredResetCastOp, ConstCastOp, RefCastOp,
560 UnsafeDomainCastOp>([&](auto op) {
561 traceResets(op.getResult(), op.getInput(), op.getLoc());
562 })
563 .Case<InvalidValueOp>([&](auto op) {
564 // Uniquify `InvalidValueOp`s that are contributing to multiple
565 // reset networks. These are tricky to handle because passes
566 // like CSE will generally ensure that there is only a single
567 // `InvalidValueOp` per type. However, a `reset` invalid value
568 // may be connected to two reset networks that end up being
569 // inferred as `asyncreset` and `uint<1>`. In that case, we need
570 // a distinct `InvalidValueOp` for each reset network in order
571 // to assign it the correct type.
572 auto type = op.getType();
573 if (!typeContainsReset(type) || op->hasOneUse() || op->use_empty())
574 return;
575 LLVM_DEBUG(llvm::dbgs() << "Uniquify " << op << "\n");
576 ImplicitLocOpBuilder builder(op->getLoc(), op);
577 for (auto &use :
578 llvm::make_early_inc_range(llvm::drop_begin(op->getUses()))) {
579 // - `make_early_inc_range` since `getUses()` is invalidated
580 // upon
581 // `use.set(...)`.
582 // - `drop_begin` such that the first use can keep the
583 // original op.
584 auto newOp = InvalidValueOp::create(builder, type);
585 use.set(newOp);
586 }
587 })
588
589 .Case<SubfieldOp>([&](auto op) {
590 // Associate the input bundle's resets with the output field's
591 // resets.
592 BundleType bundleType = op.getInput().getType();
593 auto index = op.getFieldIndex();
594 traceResets(op.getType(), op.getResult(), 0,
595 bundleType.getElements()[index].type, op.getInput(),
596 getFieldID(bundleType, index), op.getLoc());
597 })
598
599 .Case<SubindexOp, SubaccessOp>([&](auto op) {
600 // Associate the input vector's resets with the output field's
601 // resets.
602 //
603 // This collapses all elements in vectors into one shared
604 // element which will ensure that reset inference provides a
605 // uniform result for all elements.
606 //
607 // CAVEAT: This may infer reset networks that are too big, since
608 // unrelated resets in the same vector end up looking as if they
609 // were connected. However for the sake of type inference, this
610 // is indistinguishable from them having to share the same type
611 // (namely the vector element type).
612 FVectorType vectorType = op.getInput().getType();
613 traceResets(op.getType(), op.getResult(), 0,
614 vectorType.getElementType(), op.getInput(),
615 getFieldID(vectorType), op.getLoc());
616 })
617
618 .Case<RefSubOp>([&](RefSubOp op) {
619 // Trace through ref.sub.
620 auto aggType = op.getInput().getType().getType();
621 uint64_t fieldID = TypeSwitch<FIRRTLBaseType, uint64_t>(aggType)
622 .Case<FVectorType>([](auto type) {
623 return getFieldID(type);
624 })
625 .Case<BundleType>([&](auto type) {
626 return getFieldID(type, op.getIndex());
627 });
628 traceResets(op.getType(), op.getResult(), 0,
629 op.getResult().getType(), op.getInput(), fieldID,
630 op.getLoc());
631 });
632 }
633}
634
635/// Trace reset signals through an instance or instance choice. This essentially
636/// associates the instance's port values with the target module's port values.
637void InferResetsPass::traceResets(FInstanceLike inst) {
638 LLVM_DEBUG(llvm::dbgs() << "Visiting instance " << inst.getInstanceName()
639 << "\n");
640 auto moduleNames = inst.getReferencedModuleNamesAttr();
641 for (auto moduleName : moduleNames.getAsRange<StringAttr>()) {
642 auto *node = instanceGraph->lookup(moduleName);
643 auto module = dyn_cast<FModuleOp>(*node->getModule());
644 if (!module)
645 return;
646
647 // Establish a connection between the instance ports and module ports.
648 for (const auto &it : llvm::enumerate(inst->getResults())) {
649 Value dstPort = module.getArgument(it.index());
650 Value srcPort = it.value();
651 if (module.getPortDirection(it.index()) == Direction::Out)
652 std::swap(dstPort, srcPort);
653 traceResets(dstPort, srcPort, it.value().getLoc());
654 }
655 }
656}
657
658/// Analyze a connect of one (possibly aggregate) value to another.
659/// Each drive involving a `ResetType` is recorded.
660void InferResetsPass::traceResets(Value dst, Value src, Location loc) {
661 // Analyze the actual connection.
662 traceResets(dst.getType(), dst, 0, src.getType(), src, 0, loc);
663}
664
665/// Analyze a connect of one (possibly aggregate) value to another.
666/// Each drive involving a `ResetType` is recorded.
667void InferResetsPass::traceResets(Type dstType, Value dst, unsigned dstID,
668 Type srcType, Value src, unsigned srcID,
669 Location loc) {
670 if (auto dstBundle = type_dyn_cast<BundleType>(dstType)) {
671 auto srcBundle = type_cast<BundleType>(srcType);
672 for (unsigned dstIdx = 0, e = dstBundle.getNumElements(); dstIdx < e;
673 ++dstIdx) {
674 auto dstField = dstBundle.getElements()[dstIdx].name;
675 auto srcIdx = srcBundle.getElementIndex(dstField);
676 if (!srcIdx)
677 continue;
678 auto &dstElt = dstBundle.getElements()[dstIdx];
679 auto &srcElt = srcBundle.getElements()[*srcIdx];
680 if (dstElt.isFlip) {
681 traceResets(srcElt.type, src, srcID + getFieldID(srcBundle, *srcIdx),
682 dstElt.type, dst, dstID + getFieldID(dstBundle, dstIdx),
683 loc);
684 } else {
685 traceResets(dstElt.type, dst, dstID + getFieldID(dstBundle, dstIdx),
686 srcElt.type, src, srcID + getFieldID(srcBundle, *srcIdx),
687 loc);
688 }
689 }
690 return;
691 }
692
693 if (auto dstVector = type_dyn_cast<FVectorType>(dstType)) {
694 auto srcVector = type_cast<FVectorType>(srcType);
695 auto srcElType = srcVector.getElementType();
696 auto dstElType = dstVector.getElementType();
697 // Collapse all elements into one shared element. See comment in traceResets
698 // above for some context. Note that we are directly passing on the field ID
699 // of the vector itself as a stand-in for its element type. This is not
700 // really what `FieldRef` is designed to do, but tends to work since all the
701 // places that need to reason about the resulting weird IDs are inside this
702 // file. Normally you would pick a specific index from the vector, which
703 // would also move the field ID forward by some amount. However, we can't
704 // distinguish individual elements for the sake of type inference *and* we
705 // have to support zero-length vectors for which the only available ID is
706 // the vector itself. Therefore we always just pick the vector itself for
707 // the field ID and make sure in `updateType` that we handle vectors
708 // accordingly.
709 traceResets(dstElType, dst, dstID + getFieldID(dstVector), srcElType, src,
710 srcID + getFieldID(srcVector), loc);
711 return;
712 }
713
714 // Handle connecting ref's. Other uses trace using base type.
715 if (auto dstRef = type_dyn_cast<RefType>(dstType)) {
716 auto srcRef = type_cast<RefType>(srcType);
717 return traceResets(dstRef.getType(), dst, dstID, srcRef.getType(), src,
718 srcID, loc);
719 }
720
721 // Handle reset connections.
722 auto dstBase = type_dyn_cast<FIRRTLBaseType>(dstType);
723 auto srcBase = type_dyn_cast<FIRRTLBaseType>(srcType);
724 if (!dstBase || !srcBase)
725 return;
726 if (!type_isa<ResetType>(dstBase) && !type_isa<ResetType>(srcBase))
727 return;
728
729 FieldRef dstField(dst, dstID);
730 FieldRef srcField(src, srcID);
731 LLVM_DEBUG(llvm::dbgs() << "Visiting driver '" << dstField << "' = '"
732 << srcField << "' (" << dstType << " = " << srcType
733 << ")\n");
734
735 // Determine the leaders for the dst and src reset networks before we make
736 // the connection. This will allow us to later detect if dst got merged
737 // into src, or src into dst.
738 ResetSignal dstLeader =
739 *resetClasses.findLeader(resetClasses.insert({dstField, dstBase}));
740 ResetSignal srcLeader =
741 *resetClasses.findLeader(resetClasses.insert({srcField, srcBase}));
742
743 // Unify the two reset networks.
744 ResetSignal unionLeader = *resetClasses.unionSets(dstLeader, srcLeader);
745 assert(unionLeader == dstLeader || unionLeader == srcLeader);
746
747 // If dst got merged into src, append dst's drives to src's, or vice
748 // versa. Also, remove dst's or src's entry in resetDrives, because they
749 // will never come up as a leader again.
750 if (dstLeader != srcLeader) {
751 auto &unionDrives = resetDrives[unionLeader]; // needed before finds
752 auto mergedDrivesIt =
753 resetDrives.find(unionLeader == dstLeader ? srcLeader : dstLeader);
754 if (mergedDrivesIt != resetDrives.end()) {
755 unionDrives.append(mergedDrivesIt->second);
756 resetDrives.erase(mergedDrivesIt);
757 }
758 }
759
760 // Keep note of this drive so we can point the user at the right location
761 // in case something goes wrong.
762 resetDrives[unionLeader].push_back(
763 {{dstField, dstBase}, {srcField, srcBase}, loc});
764}
765
766//===----------------------------------------------------------------------===//
767// Reset Inference
768//===----------------------------------------------------------------------===//
769
770LogicalResult InferResetsPass::inferAndUpdateResets() {
771 LLVM_DEBUG({
772 llvm::dbgs() << "\n";
773 debugHeader("Infer reset types") << "\n\n";
774 });
775 for (const auto &it : resetClasses) {
776 if (!it->isLeader())
777 continue;
778 ResetNetwork net = resetClasses.members(*it);
779
780 // Infer whether this should be a sync or async reset.
781 auto kind = inferReset(net);
782 if (failed(kind))
783 return failure();
784
785 // Update the types in the IR to match the inferred kind.
786 if (failed(updateReset(net, *kind)))
787 return failure();
788 }
789 return success();
790}
791
792FailureOr<ResetKind> InferResetsPass::inferReset(ResetNetwork net) {
793 LLVM_DEBUG(llvm::dbgs() << "Inferring reset network with "
794 << std::distance(net.begin(), net.end())
795 << " nodes\n");
796
797 // Go through the nodes and track the involved types.
798 unsigned asyncDrives = 0;
799 unsigned syncDrives = 0;
800 unsigned invalidDrives = 0;
801 for (ResetSignal signal : net) {
802 // Keep track of whether this signal contributes a vote for async or sync.
803 if (type_isa<AsyncResetType>(signal.type))
804 ++asyncDrives;
805 else if (type_isa<UIntType>(signal.type))
806 ++syncDrives;
807 else if (isUselessVec(signal.field) ||
808 isa_and_nonnull<InvalidValueOp>(
809 signal.field.getValue().getDefiningOp()))
810 ++invalidDrives;
811 }
812 LLVM_DEBUG(llvm::dbgs() << "- Found " << asyncDrives << " async, "
813 << syncDrives << " sync, " << invalidDrives
814 << " invalid drives\n");
815
816 // Handle the case where we have no votes for either kind.
817 if (asyncDrives == 0 && syncDrives == 0 && invalidDrives == 0) {
818 ResetSignal root = guessRoot(net);
819 auto diag = mlir::emitError(root.field.getValue().getLoc())
820 << "reset network never driven with concrete type";
821 for (ResetSignal signal : net)
822 diag.attachNote(signal.field.getLoc()) << "here: ";
823 return failure();
824 }
825
826 // Handle the case where we have votes for both kinds.
827 if (asyncDrives > 0 && syncDrives > 0) {
828 ResetSignal root = guessRoot(net);
829 bool majorityAsync = asyncDrives >= syncDrives;
830 auto diag = mlir::emitError(root.field.getValue().getLoc())
831 << "reset network";
832 SmallString<32> fieldName;
833 if (getFieldName(root.field, fieldName))
834 diag << " \"" << fieldName << "\"";
835 diag << " simultaneously connected to async and sync resets";
836 diag.attachNote(root.field.getValue().getLoc())
837 << "majority of connections to this reset are "
838 << (majorityAsync ? "async" : "sync");
839 for (auto &drive : getResetDrives(net)) {
840 if ((type_isa<AsyncResetType>(drive.dst.type) && !majorityAsync) ||
841 (type_isa<AsyncResetType>(drive.src.type) && !majorityAsync) ||
842 (type_isa<UIntType>(drive.dst.type) && majorityAsync) ||
843 (type_isa<UIntType>(drive.src.type) && majorityAsync))
844 diag.attachNote(drive.loc)
845 << (type_isa<AsyncResetType>(drive.src.type) ? "async" : "sync")
846 << " drive here:";
847 }
848 return failure();
849 }
850
851 // At this point we know that the type of the reset is unambiguous. If there
852 // are any votes for async, we make the reset async. Otherwise we make it
853 // sync.
854 auto kind = (asyncDrives ? ResetKind::Async : ResetKind::Sync);
855 LLVM_DEBUG(llvm::dbgs() << "- Inferred as " << kind << "\n");
856 return kind;
857}
858
859//===----------------------------------------------------------------------===//
860// Reset Updating
861//===----------------------------------------------------------------------===//
862
863LogicalResult InferResetsPass::updateReset(ResetNetwork net, ResetKind kind) {
864 LLVM_DEBUG(llvm::dbgs() << "Updating reset network with "
865 << std::distance(net.begin(), net.end())
866 << " nodes to " << kind << "\n");
867
868 // Determine the final type the reset should have.
869 FIRRTLBaseType resetType;
870 if (kind == ResetKind::Async)
871 resetType = AsyncResetType::get(&getContext());
872 else
873 resetType = UIntType::get(&getContext(), 1);
874
875 // Update all those values in the network that cannot be inferred from
876 // operands. If we change the type of a module port (i.e. BlockArgument), add
877 // the module to a module worklist since we need to update its function type.
879 SmallDenseSet<Operation *> moduleWorklist;
880 SmallDenseSet<std::pair<Operation *, Operation *>> extmoduleWorklist;
881 for (auto signal : net) {
882 Value value = signal.field.getValue();
883 if (!isa<BlockArgument>(value) &&
884 !isa_and_nonnull<WireOp, RegOp, RegResetOp, FInstanceLike,
885 InvalidValueOp, ConstCastOp, RefCastOp,
886 UninferredResetCastOp, RWProbeOp, AsResetPrimOp>(
887 value.getDefiningOp()))
888 continue;
889 if (updateReset(signal.field, resetType)) {
890 for (auto *user : value.getUsers())
891 worklist.insert(user);
892 if (auto blockArg = dyn_cast<BlockArgument>(value)) {
893 moduleWorklist.insert(blockArg.getOwner()->getParentOp());
894 continue;
895 }
896
897 TypeSwitch<Operation *>(value.getDefiningOp())
898 .Case<FInstanceLike>([&](FInstanceLike op) {
899 for (auto moduleName : op.getReferencedModuleNamesAttr()) {
900 auto *node = instanceGraph->lookup(cast<StringAttr>(moduleName));
901 if (auto refModule = dyn_cast<FExtModuleOp>(*node->getModule()))
902 extmoduleWorklist.insert({refModule, op.getOperation()});
903 }
904 })
905 .Case<UninferredResetCastOp>([&](auto op) {
906 op.replaceAllUsesWith(op.getInput());
907 op.erase();
908 })
909 .Case<AsResetPrimOp>([&](auto op) {
910 // Remove `asReset` casts for sync resets, or replace them with an
911 // `asAsyncReset` cast for async resets.
912 Value result = op.getInput();
913 if (type_isa<AsyncResetType>(resetType)) {
914 ImplicitLocOpBuilder builder(op.getLoc(), op);
915 result = AsAsyncResetPrimOp::create(builder, op.getInput());
916 }
917 op.replaceAllUsesWith(result);
918 op.erase();
919 });
920 }
921 }
922
923 // Process the worklist of operations that have their type changed, pushing
924 // types down the SSA dataflow graph. This is important because we change the
925 // reset types in aggregates, and then need all the subindex, subfield, and
926 // subaccess operations to be updated as appropriate.
927 while (!worklist.empty()) {
928 auto *wop = worklist.pop_back_val();
929 SmallVector<Type, 2> types;
930 if (auto op = dyn_cast<InferTypeOpInterface>(wop)) {
931 // Determine the new result types.
932 SmallVector<Type, 2> types;
933 if (failed(op.inferReturnTypes(op->getContext(), op->getLoc(),
934 op->getOperands(), op->getAttrDictionary(),
935 op->getPropertiesStorage(),
936 op->getRegions(), types)))
937 return failure();
938
939 // Update the results and add the changed ones to the
940 // worklist.
941 for (auto it : llvm::zip(op->getResults(), types)) {
942 auto newType = std::get<1>(it);
943 if (std::get<0>(it).getType() == newType)
944 continue;
945 std::get<0>(it).setType(newType);
946 for (auto *user : std::get<0>(it).getUsers())
947 worklist.insert(user);
948 }
949 LLVM_DEBUG(llvm::dbgs() << "- Inferred " << *op << "\n");
950 } else if (auto uop = dyn_cast<UninferredResetCastOp>(wop)) {
951 for (auto *user : uop.getResult().getUsers())
952 worklist.insert(user);
953 uop.replaceAllUsesWith(uop.getInput());
954 LLVM_DEBUG(llvm::dbgs() << "- Inferred " << uop << "\n");
955 uop.erase();
956 }
957 }
958
959 // Update module types based on the type of the block arguments.
960 for (auto *op : moduleWorklist) {
961 auto module = dyn_cast<FModuleOp>(op);
962 if (!module)
963 continue;
964
965 SmallVector<Attribute> argTypes;
966 argTypes.reserve(module.getNumPorts());
967 for (auto arg : module.getArguments())
968 argTypes.push_back(TypeAttr::get(arg.getType()));
969
970 module.setPortTypesAttr(ArrayAttr::get(op->getContext(), argTypes));
971 LLVM_DEBUG(llvm::dbgs()
972 << "- Updated type of module '" << module.getName() << "'\n");
973 }
974
975 // Update extmodule types based on their instantiation.
976 for (auto [mod, instOp] : extmoduleWorklist) {
977 auto module = cast<FExtModuleOp>(mod);
978
979 SmallVector<Attribute> types;
980 for (auto type : instOp->getResultTypes())
981 types.push_back(TypeAttr::get(type));
982
983 module.setPortTypesAttr(ArrayAttr::get(module->getContext(), types));
984 LLVM_DEBUG(llvm::dbgs()
985 << "- Updated type of extmodule '" << module.getName() << "'\n");
986 }
987
988 return success();
989}
990
991/// Update the type of a single field within a type.
992static FIRRTLBaseType updateType(FIRRTLBaseType oldType, unsigned fieldID,
993 FIRRTLBaseType fieldType) {
994 // If this is a ground type, simply replace it, preserving constness.
995 if (oldType.isGround()) {
996 assert(fieldID == 0);
997 return fieldType.getConstType(oldType.isConst());
998 }
999
1000 // If this is a bundle type, update the corresponding field.
1001 if (auto bundleType = type_dyn_cast<BundleType>(oldType)) {
1002 unsigned index = getIndexForFieldID(bundleType, fieldID);
1003 SmallVector<BundleType::BundleElement> fields(bundleType.begin(),
1004 bundleType.end());
1005 fields[index].type = updateType(
1006 fields[index].type, fieldID - getFieldID(bundleType, index), fieldType);
1007 return BundleType::get(oldType.getContext(), fields, bundleType.isConst());
1008 }
1009
1010 // If this is a vector type, update the element type.
1011 if (auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
1012 auto newType = updateType(vectorType.getElementType(),
1013 fieldID - getFieldID(vectorType), fieldType);
1014 return FVectorType::get(newType, vectorType.getNumElements(),
1015 vectorType.isConst());
1016 }
1017
1018 llvm_unreachable("unknown aggregate type");
1019 return oldType;
1020}
1021
1022/// Update the reset type of a specific field.
1023bool InferResetsPass::updateReset(FieldRef field, FIRRTLBaseType resetType) {
1024 // Compute the updated type.
1025 auto oldType = type_cast<FIRRTLType>(field.getValue().getType());
1026 FIRRTLType newType = mapBaseType(oldType, [&](auto base) {
1027 return updateType(base, field.getFieldID(), resetType);
1028 });
1029
1030 // Update the type if necessary.
1031 if (oldType == newType)
1032 return false;
1033 LLVM_DEBUG(llvm::dbgs() << "- Updating '" << field << "' from " << oldType
1034 << " to " << newType << "\n");
1035 field.getValue().setType(newType);
1036 return true;
1037}
1038
1039LogicalResult InferResetsPass::verifyNoAbstractReset() {
1040 bool hasAbstractResetPorts = false;
1041 for (FModuleLike module :
1042 getOperation().getBodyBlock()->getOps<FModuleLike>()) {
1043 for (PortInfo port : module.getPorts()) {
1044 if (getBaseOfType<ResetType>(port.type)) {
1045 auto diag = emitError(port.loc)
1046 << "a port \"" << port.getName()
1047 << "\" with abstract reset type was unable to be "
1048 "inferred by InferResets (is this a top-level port?)";
1049 diag.attachNote(module->getLoc())
1050 << "the module with this uninferred reset port was defined here";
1051 hasAbstractResetPorts = true;
1052 }
1053 }
1054 }
1055
1056 if (hasAbstractResetPorts)
1057 return failure();
1058 return success();
1059}
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.
LogicalResult runFullReset(CircuitOp circuit, InstanceGraph &ig, InstanceInfo &instanceInfo, bool convertAsyncDomainMems=false)
static bool operator==(const ModulePort &a, const ModulePort &b)
Definition HWTypes.h:36
static llvm::hash_code hash_value(const ModulePort &port)
Definition HWTypes.h:39
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)