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