CIRCT 23.0.0git
Loading...
Searching...
No Matches
IMConstProp.cpp
Go to the documentation of this file.
1//===- IMConstProp.cpp - Intermodule ConstProp and DCE ----------*- 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 implements SCCP:
10// https://www.cs.wustl.edu/~cytron/531Pages/f11/Resources/Papers/cprop.pdf
11//
12//===----------------------------------------------------------------------===//
13
20#include "circt/Support/APInt.h"
21#include "mlir/IR/Iterators.h"
22#include "mlir/IR/Threading.h"
23#include "mlir/Pass/Pass.h"
24#include "llvm/ADT/APSInt.h"
25#include "llvm/ADT/TinyPtrVector.h"
26#include "llvm/Support/Debug.h"
27#include "llvm/Support/ScopedPrinter.h"
28
29namespace circt {
30namespace firrtl {
31#define GEN_PASS_DEF_IMCONSTPROP
32#include "circt/Dialect/FIRRTL/Passes.h.inc"
33} // namespace firrtl
34} // namespace circt
35
36using namespace circt;
37using namespace firrtl;
38
39#define DEBUG_TYPE "IMCP"
40
41/// Return true if this is a wire or register.
42static bool isWireOrReg(Operation *op) {
43 return isa<WireOp, RegResetOp, RegOp>(op);
44}
45
46/// Return true if this is an aggregate indexer.
47static bool isAggregate(Operation *op) {
48 return isa<SubindexOp, SubaccessOp, SubfieldOp, OpenSubfieldOp,
49 OpenSubindexOp, RefSubOp>(op);
50}
51
52// Return true if this forwards input to output.
53// Implies has appropriate visit method that propagates changes.
54static bool isNodeLike(Operation *op) {
55 return isa<NodeOp, RefResolveOp, RefSendOp>(op);
56}
57
58/// Return true if this is a wire or register we're allowed to delete.
59static bool isDeletableWireOrRegOrNode(Operation *op) {
60 if (!isWireOrReg(op) && !isa<NodeOp>(op))
61 return false;
62
63 // Always allow deleting wires of probe-type.
64 if (type_isa<RefType>(op->getResult(0).getType()))
65 return true;
66
67 // Otherwise, don't delete if has anything keeping it around or unknown.
68 return AnnotationSet(op).empty() && !hasDontTouch(op) &&
69 hasDroppableName(op) && !cast<Forceable>(op).isForceable();
70}
71
72//===----------------------------------------------------------------------===//
73// Pass Infrastructure
74//===----------------------------------------------------------------------===//
75
76namespace {
77/// This class represents a single lattice value. A lattive value corresponds to
78/// the various different states that a value in the SCCP dataflow analysis can
79/// take. See 'Kind' below for more details on the different states a value can
80/// take.
81class LatticeValue {
82 enum Kind {
83 /// A value with a yet-to-be-determined value. This state may be changed to
84 /// anything, it hasn't been processed by IMConstProp.
85 Unknown,
86
87 /// A value that is known to be a constant. This state may be changed to
88 /// overdefined.
89 Constant,
90
91 /// A value that cannot statically be determined to be a constant. This
92 /// state cannot be changed.
93 Overdefined
94 };
95
96public:
97 /// Initialize a lattice value with "Unknown".
98 /*implicit*/ LatticeValue() : valueAndTag(nullptr, Kind::Unknown) {}
99 /// Initialize a lattice value with a constant.
100 /*implicit*/ LatticeValue(IntegerAttr attr)
101 : valueAndTag(attr, Kind::Constant) {}
102 /*implicit*/ LatticeValue(StringAttr attr)
103 : valueAndTag(attr, Kind::Constant) {}
104
105 static LatticeValue getOverdefined() {
106 LatticeValue result;
107 result.markOverdefined();
108 return result;
109 }
110
111 bool isUnknown() const { return valueAndTag.getInt() == Kind::Unknown; }
112 bool isConstant() const { return valueAndTag.getInt() == Kind::Constant; }
113 bool isOverdefined() const {
114 return valueAndTag.getInt() == Kind::Overdefined;
115 }
116
117 /// Mark the lattice value as overdefined.
118 void markOverdefined() {
119 valueAndTag.setPointerAndInt(nullptr, Kind::Overdefined);
120 }
121
122 /// Mark the lattice value as constant.
123 void markConstant(IntegerAttr value) {
124 valueAndTag.setPointerAndInt(value, Kind::Constant);
125 }
126
127 /// If this lattice is constant or invalid value, return the attribute.
128 /// Returns nullptr otherwise.
129 Attribute getValue() const { return valueAndTag.getPointer(); }
130
131 /// If this is in the constant state, return the attribute.
132 Attribute getConstant() const {
134 return getValue();
135 }
136
137 /// Merge in the value of the 'rhs' lattice into this one. Returns true if the
138 /// lattice value changed.
139 bool mergeIn(LatticeValue rhs) {
140 // If we are already overdefined, or rhs is unknown, there is nothing to do.
141 if (isOverdefined() || rhs.isUnknown())
142 return false;
143
144 // If we are unknown, just take the value of rhs.
145 if (isUnknown()) {
146 valueAndTag = rhs.valueAndTag;
147 return true;
148 }
149
150 // Otherwise, if this value doesn't match rhs go straight to overdefined.
151 // This happens when we merge "3" and "4" from two different instance sites
152 // for example.
153 if (valueAndTag != rhs.valueAndTag) {
154 markOverdefined();
155 return true;
156 }
157 return false;
158 }
159
160 bool operator==(const LatticeValue &other) const {
161 return valueAndTag == other.valueAndTag;
162 }
163 bool operator!=(const LatticeValue &other) const {
164 return valueAndTag != other.valueAndTag;
165 }
166
167private:
168 /// The attribute value if this is a constant and the tag for the element
169 /// kind. The attribute is an IntegerAttr (or BoolAttr) or StringAttr.
170 llvm::PointerIntPair<Attribute, 2, Kind> valueAndTag;
171};
172} // end anonymous namespace
173
174LLVM_ATTRIBUTE_USED
175static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
176 const LatticeValue &lattice) {
177 if (lattice.isUnknown())
178 return os << "<Unknown>";
179 if (lattice.isOverdefined())
180 return os << "<Overdefined>";
181 return os << "<" << lattice.getConstant() << ">";
182}
183
184namespace {
185struct IMConstPropPass
186 : public circt::firrtl::impl::IMConstPropBase<IMConstPropPass> {
187
188 void runOnOperation() override;
189 void rewriteModuleBody(FModuleOp module);
190
191 /// Returns true if the given block is executable.
192 bool isBlockExecutable(Block *block) const {
193 return executableBlocks.count(block);
194 }
195
196 bool isOverdefined(FieldRef value) const {
197 auto it = latticeValues.find(value);
198 return it != latticeValues.end() && it->second.isOverdefined();
199 }
200
201 // Mark the given value as overdefined. If the value is an aggregate,
202 // we mark all ground elements as overdefined.
203 void markOverdefined(Value value) {
204 FieldRef fieldRef = getOrCacheFieldRefFromValue(value);
205 auto firrtlType = type_dyn_cast<FIRRTLType>(value.getType());
206 if (!firrtlType || type_isa<PropertyType, DomainType>(firrtlType)) {
207 markOverdefined(fieldRef);
208 return;
209 }
210
211 walkGroundTypes(firrtlType, [&](uint64_t fieldID, auto, auto) {
212 markOverdefined(fieldRef.getSubField(fieldID));
213 });
214 }
215
216 /// Mark the given value as overdefined. This means that we cannot refine a
217 /// specific constant for this value.
218 void markOverdefined(FieldRef value) {
219 auto &entry = latticeValues[value];
220 if (!entry.isOverdefined()) {
221 LLVM_DEBUG({
222 logger.getOStream()
223 << "Setting overdefined : (" << getFieldName(value).first << ")\n";
224 });
225 entry.markOverdefined();
226 changedLatticeValueWorklist.push_back(value);
227 }
228 }
229
230 /// Merge information from the 'from' lattice value into value. If it
231 /// changes, then users of the value are added to the worklist for
232 /// revisitation.
233 void mergeLatticeValue(FieldRef value, LatticeValue &valueEntry,
234 LatticeValue source) {
235 if (valueEntry.mergeIn(source)) {
236 LLVM_DEBUG({
237 logger.getOStream()
238 << "Changed to " << valueEntry << " : (" << value << ")\n";
239 });
240 changedLatticeValueWorklist.push_back(value);
241 }
242 }
243
244 void mergeLatticeValue(FieldRef value, LatticeValue source) {
245 // Don't even do a map lookup if from has no info in it.
246 if (source.isUnknown())
247 return;
248 mergeLatticeValue(value, latticeValues[value], source);
249 }
250
251 void mergeLatticeValue(FieldRef result, FieldRef from) {
252 // If 'from' hasn't been computed yet, then it is unknown, don't do
253 // anything.
254 auto it = latticeValues.find(from);
255 if (it == latticeValues.end())
256 return;
257 mergeLatticeValue(result, it->second);
258 }
259
260 void mergeLatticeValue(Value result, Value from) {
261 FieldRef fieldRefFrom = getOrCacheFieldRefFromValue(from);
262 FieldRef fieldRefResult = getOrCacheFieldRefFromValue(result);
263 if (!type_isa<FIRRTLType>(result.getType()))
264 return mergeLatticeValue(fieldRefResult, fieldRefFrom);
265 // Special-handle PropertyType's, walkGroundType's doesn't support.
266 if (type_isa<PropertyType, DomainType>(result.getType()))
267 return mergeLatticeValue(fieldRefResult, fieldRefFrom);
268 walkGroundTypes(type_cast<FIRRTLType>(result.getType()),
269 [&](uint64_t fieldID, auto, auto) {
270 mergeLatticeValue(fieldRefResult.getSubField(fieldID),
271 fieldRefFrom.getSubField(fieldID));
272 });
273 }
274
275 /// setLatticeValue - This is used when a new LatticeValue is computed for
276 /// the result of the specified value that replaces any previous knowledge,
277 /// e.g. because a fold() function on an op returned a new thing. This should
278 /// not be used on operations that have multiple contributors to it, e.g.
279 /// wires or ports.
280 void setLatticeValue(FieldRef value, LatticeValue source) {
281 // Don't even do a map lookup if from has no info in it.
282 if (source.isUnknown())
283 return;
284
285 // If we've changed this value then revisit all the users.
286 auto &valueEntry = latticeValues[value];
287 if (valueEntry != source) {
288 changedLatticeValueWorklist.push_back(value);
289 valueEntry = source;
290 }
291 }
292
293 // This function returns a field ref of the given value. This function caches
294 // the result to avoid extra IR traversal if the value is an aggregate
295 // element.
296 FieldRef getOrCacheFieldRefFromValue(Value value) {
297 if (!value.getDefiningOp() || !isAggregate(value.getDefiningOp()))
298 return FieldRef(value, 0);
299 auto &fieldRef = valueToFieldRef[value];
300 if (fieldRef)
301 return fieldRef;
302 return fieldRef = getFieldRefFromValue(value);
303 }
304
305 /// Return the lattice value for the specified SSA value, extended to the
306 /// width of the specified destType. If allowTruncation is true, then this
307 /// allows truncating the lattice value to the specified type.
308 LatticeValue getExtendedLatticeValue(FieldRef value, FIRRTLType destType,
309 bool allowTruncation = false);
310
311 /// Mark the given block as executable.
312 void markBlockExecutable(Block *block);
313 void markWireOp(WireOp wireOrReg);
314 void markMemOp(MemOp mem);
315 void markDPICallIntrinsicOp(DPICallIntrinsicOp dpi);
316
317 void markInvalidValueOp(InvalidValueOp invalid);
318 void markAggregateConstantOp(AggregateConstantOp constant);
319 void markInstanceOp(InstanceOp instance);
320 void markInstanceChoiceOp(InstanceChoiceOp instance);
321 void markObjectOp(ObjectOp object);
322 template <typename OpTy>
323 void markConstantValueOp(OpTy op);
324
325 void visitConnectLike(FConnectLike connect, FieldRef changedFieldRef);
326 void visitRefSend(RefSendOp send, FieldRef changedFieldRef);
327 void visitRefResolve(RefResolveOp resolve, FieldRef changedFieldRef);
328 void mergeOnlyChangedLatticeValue(Value dest, Value src,
329 FieldRef changedFieldRef);
330 void visitNode(NodeOp node, FieldRef changedFieldRef);
331 void visitOperation(Operation *op, FieldRef changedFieldRef);
332
333private:
334 /// This is the current instance graph for the Circuit.
335 InstanceGraph *instanceGraph = nullptr;
336
337 /// This keeps track of the current state of each tracked value.
338 DenseMap<FieldRef, LatticeValue> latticeValues;
339
340 /// The set of blocks that are known to execute, or are intrinsically live.
341 SmallPtrSet<Block *, 16> executableBlocks;
342
343 /// A worklist of values whose LatticeValue recently changed, indicating the
344 /// users need to be reprocessed.
345 SmallVector<FieldRef, 64> changedLatticeValueWorklist;
346
347 // A map to give operations to be reprocessed.
348 DenseMap<FieldRef, llvm::TinyPtrVector<Operation *>> fieldRefToUsers;
349
350 // A map to cache results of getFieldRefFromValue since it's costly traverse
351 // the IR.
352 llvm::DenseMap<Value, FieldRef> valueToFieldRef;
353
354 /// This keeps track of users the instance results that correspond to output
355 /// ports.
356 DenseMap<BlockArgument, llvm::TinyPtrVector<Value>>
357 resultPortToInstanceResultMapping;
358
359#ifndef NDEBUG
360 /// A logger used to emit information during the application process.
361 llvm::ScopedPrinter logger{llvm::dbgs()};
362#endif
363};
364} // end anonymous namespace
365
366// TODO: handle annotations: [[OptimizableExtModuleAnnotation]]
367void IMConstPropPass::runOnOperation() {
368 auto circuit = getOperation();
369 LLVM_DEBUG(
370 { logger.startLine() << "IMConstProp : " << circuit.getName() << "\n"; });
371
372 instanceGraph = &getAnalysis<InstanceGraph>();
373
374 // Mark input ports as overdefined where appropriate.
375 for (auto &op : circuit.getOps()) {
376 // Inputs of public modules are overdefined.
377 if (auto module = dyn_cast<FModuleOp>(op)) {
378 if (module.isPublic()) {
379 markBlockExecutable(module.getBodyBlock());
380 for (auto port : module.getBodyBlock()->getArguments())
381 markOverdefined(port);
382 }
383 continue;
384 }
385
386 // Otherwise we check whether the top-level operation contains any
387 // references to modules. Symbol uses in NLAs are ignored.
388 if (isa<hw::HierPathOp>(op))
389 continue;
390
391 // Inputs of modules referenced by unknown operations are overdefined, since
392 // we don't know how those operations affect the input port values. This
393 // handles things like `firrtl.formal`, which may may assign symbolic values
394 // to input ports of a private module.
395 auto symbolUses = SymbolTable::getSymbolUses(&op);
396 if (!symbolUses)
397 continue;
398 for (const auto &use : *symbolUses) {
399 if (auto symRef = dyn_cast<FlatSymbolRefAttr>(use.getSymbolRef())) {
400 if (auto *igNode = instanceGraph->lookupOrNull(symRef.getAttr())) {
401 if (auto module = dyn_cast<FModuleOp>(*igNode->getModule())) {
402 LLVM_DEBUG(llvm::dbgs()
403 << "Unknown use of " << module.getModuleNameAttr()
404 << " in " << op.getName()
405 << ", marking inputs as overdefined\n");
406 markBlockExecutable(module.getBodyBlock());
407 for (auto port : module.getBodyBlock()->getArguments())
408 markOverdefined(port);
409 }
410 }
411 }
412 }
413 }
414
415 // If a value changed lattice state then reprocess any of its users.
416 while (!changedLatticeValueWorklist.empty()) {
417 FieldRef changedFieldRef = changedLatticeValueWorklist.pop_back_val();
418 for (Operation *user : fieldRefToUsers[changedFieldRef]) {
419 if (isBlockExecutable(user->getBlock()))
420 visitOperation(user, changedFieldRef);
421 }
422 }
423
424 // Rewrite any constants in the modules.
425 mlir::parallelForEach(circuit.getContext(),
426 circuit.getBodyBlock()->getOps<FModuleOp>(),
427 [&](auto op) { rewriteModuleBody(op); });
428
429 // Clean up our state for next time.
430 instanceGraph = nullptr;
431 latticeValues.clear();
432 executableBlocks.clear();
433 assert(changedLatticeValueWorklist.empty());
434 fieldRefToUsers.clear();
435 valueToFieldRef.clear();
436 resultPortToInstanceResultMapping.clear();
437}
438
439/// Return the lattice value for the specified SSA value, extended to the width
440/// of the specified destType. If allowTruncation is true, then this allows
441/// truncating the lattice value to the specified type.
442LatticeValue IMConstPropPass::getExtendedLatticeValue(FieldRef value,
443 FIRRTLType destType,
444 bool allowTruncation) {
445 // If 'value' hasn't been computed yet, then it is unknown.
446 auto it = latticeValues.find(value);
447 if (it == latticeValues.end())
448 return LatticeValue();
449
450 auto result = it->second;
451 // Unknown/overdefined stay whatever they are.
452 if (result.isUnknown() || result.isOverdefined())
453 return result;
454
455 // No extOrTrunc for property types. Return what we have.
456 if (isa<PropertyType, DomainType>(destType))
457 return result;
458
459 auto constant = result.getConstant();
460
461 // If not property, only support integers.
462 auto intAttr = dyn_cast<IntegerAttr>(constant);
463 assert(intAttr && "unsupported lattice attribute kind");
464 if (!intAttr)
465 return result;
466
467 // No extOrTrunc necessary for bools.
468 if (auto boolAttr = dyn_cast<BoolAttr>(intAttr))
469 return result;
470
471 // Non-base (or non-ref) types are overdefined.
472 auto baseType = getBaseType(destType);
473 if (!baseType)
474 return LatticeValue::getOverdefined();
475
476 // If destType is wider than the source constant type, extend it.
477 auto resultConstant = intAttr.getAPSInt();
478 auto destWidth = baseType.getBitWidthOrSentinel();
479 if (destWidth == -1) // We don't support unknown width FIRRTL.
480 return LatticeValue::getOverdefined();
481 if (resultConstant.getBitWidth() == (unsigned)destWidth)
482 return result; // Already the right width, we're done.
483
484 // Otherwise, extend the constant using the signedness of the source.
485 resultConstant = extOrTruncZeroWidth(resultConstant, destWidth);
486 return LatticeValue(IntegerAttr::get(destType.getContext(), resultConstant));
487}
488
489// NOLINTBEGIN(misc-no-recursion)
490/// Mark a block executable if it isn't already. This does an initial scan of
491/// the block, processing nullary operations like wires, instances, and
492/// constants that only get processed once.
493void IMConstPropPass::markBlockExecutable(Block *block) {
494 if (!executableBlocks.insert(block).second)
495 return; // Already executable.
496
497 // Mark block arguments, which are module ports, with don't touch as
498 // overdefined.
499 for (auto ba : block->getArguments())
500 if (hasDontTouch(ba))
501 markOverdefined(ba);
502
503 for (auto &op : *block) {
504 // Handle each of the special operations in the firrtl dialect.
505 TypeSwitch<Operation *>(&op)
506 .Case<RegOp, RegResetOp>(
507 [&](auto reg) { markOverdefined(op.getResult(0)); })
508 .Case<WireOp>([&](auto wire) { markWireOp(wire); })
509 .Case<ConstantOp, SpecialConstantOp, StringConstantOp,
510 FIntegerConstantOp, BoolConstantOp>(
511 [&](auto constOp) { markConstantValueOp(constOp); })
512 .Case<AggregateConstantOp>(
513 [&](auto aggConstOp) { markAggregateConstantOp(aggConstOp); })
514 .Case<InvalidValueOp>(
515 [&](auto invalid) { markInvalidValueOp(invalid); })
516 .Case<InstanceOp>([&](auto instance) { markInstanceOp(instance); })
517 .Case<InstanceChoiceOp>(
518 [&](auto instance) { markInstanceChoiceOp(instance); })
519 .Case<ObjectOp>([&](auto obj) { markObjectOp(obj); })
520 .Case<MemOp>([&](auto mem) { markMemOp(mem); })
521 .Case<LayerBlockOp>(
522 [&](auto layer) { markBlockExecutable(layer.getBody(0)); })
523 .Case<DPICallIntrinsicOp>(
524 [&](auto dpi) { markDPICallIntrinsicOp(dpi); })
525 .Default([&](auto _) {
526 if (isa<mlir::UnrealizedConversionCastOp, VerbatimExprOp,
527 VerbatimWireOp, SubaccessOp>(op) ||
528 op.getNumOperands() == 0) {
529 // Mark operations whose results cannot be tracked as overdefined.
530 // Mark unhandled operations with no operand as well since otherwise
531 // they will remain unknown states until the end.
532 for (auto result : op.getResults())
533 markOverdefined(result);
534 } else if (
535 // Operations that are handled when propagating values, or chasing
536 // indexing.
537 !isAggregate(&op) && !isNodeLike(&op) && op.getNumResults() > 0) {
538 // If an unknown operation has an aggregate operand, mark results as
539 // overdefined since we cannot track the dataflow. Similarly if the
540 // operations create aggregate values, we mark them overdefined.
541
542 // TODO: We should handle aggregate operations such as
543 // vector_create, bundle_create or vector operations.
544
545 bool hasAggregateOperand =
546 llvm::any_of(op.getOperandTypes(), [](Type type) {
547 return type_isa<FVectorType, BundleType>(type);
548 });
549
550 for (auto result : op.getResults())
551 if (hasAggregateOperand ||
552 type_isa<FVectorType, BundleType>(result.getType()))
553 markOverdefined(result);
554 }
555 });
556
557 // This tracks a dependency from field refs to operations which need
558 // to be added to worklist when lattice values change.
559 if (!isAggregate(&op)) {
560 for (auto operand : op.getOperands()) {
561 auto fieldRef = getOrCacheFieldRefFromValue(operand);
562 auto firrtlType = type_dyn_cast<FIRRTLType>(operand.getType());
563 if (!firrtlType)
564 continue;
565 // Special-handle PropertyType's, walkGroundTypes doesn't support.
566 if (type_isa<PropertyType, DomainType>(firrtlType)) {
567 fieldRefToUsers[fieldRef].push_back(&op);
568 continue;
569 }
570 walkGroundTypes(firrtlType, [&](uint64_t fieldID, auto type, auto) {
571 fieldRefToUsers[fieldRef.getSubField(fieldID)].push_back(&op);
572 });
573 }
574 }
575 }
576}
577// NOLINTEND(misc-no-recursion)
578
579void IMConstPropPass::markWireOp(WireOp wire) {
580 auto type = type_dyn_cast<FIRRTLType>(wire.getResult().getType());
581 if (!type || hasDontTouch(wire.getResult()) || wire.isForceable()) {
582 for (auto result : wire.getResults())
583 markOverdefined(result);
584 return;
585 }
586
587 // Otherwise, this starts out as unknown and is upgraded by connects.
588}
589
590void IMConstPropPass::markMemOp(MemOp mem) {
591 for (auto result : mem.getResults())
592 markOverdefined(result);
593}
594
595void IMConstPropPass::markDPICallIntrinsicOp(DPICallIntrinsicOp dpi) {
596 if (auto result = dpi.getResult())
597 markOverdefined(result);
598}
599
600template <typename OpTy>
601void IMConstPropPass::markConstantValueOp(OpTy op) {
602 mergeLatticeValue(getOrCacheFieldRefFromValue(op),
603 LatticeValue(op.getValueAttr()));
604}
605
606void IMConstPropPass::markAggregateConstantOp(AggregateConstantOp constant) {
607 walkGroundTypes(constant.getType(), [&](uint64_t fieldID, auto, auto) {
608 mergeLatticeValue(FieldRef(constant, fieldID),
609 LatticeValue(cast<IntegerAttr>(
610 constant.getAttributeFromFieldID(fieldID))));
611 });
612}
613
614void IMConstPropPass::markInvalidValueOp(InvalidValueOp invalid) {
615 markOverdefined(invalid.getResult());
616}
617
618/// Instances have no operands, so they are visited exactly once when their
619/// enclosing block is marked live. This sets up the def-use edges for ports.
620void IMConstPropPass::markInstanceOp(InstanceOp instance) {
621 // Get the module being reference or a null pointer if this is an extmodule.
622 Operation *op = instance.getReferencedModule(*instanceGraph);
623
624 // If this is an extmodule, just remember that any results and inouts are
625 // overdefined.
626 if (!isa<FModuleOp>(op)) {
627 auto module = dyn_cast<FModuleLike>(op);
628 for (size_t resultNo = 0, e = instance.getNumResults(); resultNo != e;
629 ++resultNo) {
630 auto portVal = instance.getResult(resultNo);
631 // If this is an input to the extmodule, we can ignore it.
632 if (module.getPortDirection(resultNo) == Direction::In)
633 continue;
634
635 // Otherwise this is a result from it or an inout, mark it as overdefined.
636 markOverdefined(portVal);
637 }
638 return;
639 }
640
641 // Otherwise this is a defined module.
642 auto fModule = cast<FModuleOp>(op);
643 markBlockExecutable(fModule.getBodyBlock());
644
645 // Ok, it is a normal internal module reference. Populate
646 // resultPortToInstanceResultMapping, and forward any already-computed values.
647 for (size_t resultNo = 0, e = instance.getNumResults(); resultNo != e;
648 ++resultNo) {
649 auto instancePortVal = instance.getResult(resultNo);
650 // If this is an input to the instance, it will
651 // get handled when any connects to it are processed.
652 if (fModule.getPortDirection(resultNo) == Direction::In)
653 continue;
654
655 // Otherwise we have a result from the instance. We need to forward results
656 // from the body to this instance result's SSA value, so remember it.
657 BlockArgument modulePortVal = fModule.getArgument(resultNo);
658
659 resultPortToInstanceResultMapping[modulePortVal].push_back(instancePortVal);
660
661 // If there is already a value known for modulePortVal make sure to forward
662 // it here.
663 mergeLatticeValue(instancePortVal, modulePortVal);
664 }
665}
666
667void IMConstPropPass::markObjectOp(ObjectOp obj) {
668 // Mark overdefined for now, not supported.
669 markOverdefined(obj);
670}
671
672void IMConstPropPass::markInstanceChoiceOp(InstanceChoiceOp instance) {
673 // Mark all results as overdefined.
674 // TODO: Handle instance choice ops by merging lattice values of all possible
675 // choices.
676 for (auto result : instance.getResults())
677 markOverdefined(result);
678
679 // Mark all referenced modules as executable and mark all ports as
680 // overdefined.
681 for (auto moduleName : instance.getModuleNamesAttr()) {
682 Operation *op =
683 instanceGraph->lookup(cast<FlatSymbolRefAttr>(moduleName).getAttr())
684 ->getModule();
685
686 if (auto fModule = dyn_cast<FModuleOp>(op)) {
687 markBlockExecutable(fModule.getBodyBlock());
688 // Mark all ports overdefined.
689 for (auto port : fModule.getBodyBlock()->getArguments())
690 markOverdefined(port);
691 }
692 }
693}
694
695static std::optional<uint64_t>
696getFieldIDOffset(FieldRef changedFieldRef, Type connectionType,
697 FieldRef connectedValueFieldRef) {
698 assert(!type_isa<RefType>(connectionType));
699 if (changedFieldRef.getValue() != connectedValueFieldRef.getValue())
700 return {};
701 if (changedFieldRef.getFieldID() >= connectedValueFieldRef.getFieldID() &&
702 changedFieldRef.getFieldID() <=
703 hw::FieldIdImpl::getMaxFieldID(connectionType) +
704 connectedValueFieldRef.getFieldID())
705 return changedFieldRef.getFieldID() - connectedValueFieldRef.getFieldID();
706 return {};
707}
708
709void IMConstPropPass::mergeOnlyChangedLatticeValue(Value dest, Value src,
710 FieldRef changedFieldRef) {
711
712 // Operate on inner type for refs.
713 auto destType = dest.getType();
714 if (auto refType = type_dyn_cast<RefType>(destType))
715 destType = refType.getType();
716
717 if (!isa<FIRRTLType>(destType)) {
718 // If the dest is not FIRRTL type, conservatively mark
719 // all of them overdefined.
720 markOverdefined(src);
721 return markOverdefined(dest);
722 }
723
724 auto fieldRefSrc = getOrCacheFieldRefFromValue(src);
725 auto fieldRefDest = getOrCacheFieldRefFromValue(dest);
726
727 // If a changed field ref is included the source value, find an offset in the
728 // connection.
729 if (auto srcOffset = getFieldIDOffset(changedFieldRef, destType, fieldRefSrc))
730 mergeLatticeValue(fieldRefDest.getSubField(*srcOffset),
731 fieldRefSrc.getSubField(*srcOffset));
732
733 // If a changed field ref is included the dest value, find an offset in the
734 // connection.
735 if (auto destOffset =
736 getFieldIDOffset(changedFieldRef, destType, fieldRefDest))
737 mergeLatticeValue(fieldRefDest.getSubField(*destOffset),
738 fieldRefSrc.getSubField(*destOffset));
739}
740
741void IMConstPropPass::visitConnectLike(FConnectLike connect,
742 FieldRef changedFieldRef) {
743 // Operate on inner type for refs.
744 auto destType = connect.getDest().getType();
745 if (auto refType = type_dyn_cast<RefType>(destType))
746 destType = refType.getType();
747
748 // Mark foreign types as overdefined.
749 if (!isa<FIRRTLType>(destType)) {
750 markOverdefined(connect.getSrc());
751 return markOverdefined(connect.getDest());
752 }
753
754 auto fieldRefSrc = getOrCacheFieldRefFromValue(connect.getSrc());
755 auto fieldRefDest = getOrCacheFieldRefFromValue(connect.getDest());
756 if (auto subaccess = fieldRefDest.getValue().getDefiningOp<SubaccessOp>()) {
757 // If the destination is subaccess, we give up to precisely track
758 // lattice values and mark entire aggregate as overdefined. This code
759 // should be dead unless we stop lowering of subaccess in LowerTypes.
760 Value parent = subaccess.getInput();
761 while (parent.getDefiningOp() &&
762 parent.getDefiningOp()->getNumOperands() > 0)
763 parent = parent.getDefiningOp()->getOperand(0);
764 return markOverdefined(parent);
765 }
766
767 auto propagateElementLattice = [&](uint64_t fieldID, FIRRTLType destType) {
768 auto fieldRefDestConnected = fieldRefDest.getSubField(fieldID);
769 assert(!firrtl::type_isa<FIRRTLBaseType>(destType) ||
770 firrtl::type_cast<FIRRTLBaseType>(destType).isGround());
771
772 // Handle implicit extensions.
773 auto srcValue =
774 getExtendedLatticeValue(fieldRefSrc.getSubField(fieldID), destType);
775 if (srcValue.isUnknown())
776 return;
777
778 // Driving result ports propagates the value to each instance using the
779 // module.
780 if (auto blockArg = dyn_cast<BlockArgument>(fieldRefDest.getValue())) {
781 for (auto userOfResultPort : resultPortToInstanceResultMapping[blockArg])
782 mergeLatticeValue(
783 FieldRef(userOfResultPort, fieldRefDestConnected.getFieldID()),
784 srcValue);
785 // Output ports are wire-like and may have users.
786 return mergeLatticeValue(fieldRefDestConnected, srcValue);
787 }
788
789 auto dest = cast<mlir::OpResult>(fieldRefDest.getValue());
790
791 // For wires and registers, we drive the value of the wire itself, which
792 // automatically propagates to users.
793 if (isWireOrReg(dest.getOwner()))
794 return mergeLatticeValue(fieldRefDestConnected, srcValue);
795
796 // Driving an instance argument port drives the corresponding argument
797 // of the referenced module.
798 if (auto instance = dest.getDefiningOp<InstanceOp>()) {
799 // Update the dest, when its an instance op.
800 mergeLatticeValue(fieldRefDestConnected, srcValue);
801 auto mod = instance.getReferencedModule<FModuleOp>(*instanceGraph);
802 if (!mod)
803 return;
804
805 BlockArgument modulePortVal = mod.getArgument(dest.getResultNumber());
806
807 return mergeLatticeValue(
808 FieldRef(modulePortVal, fieldRefDestConnected.getFieldID()),
809 srcValue);
810 }
811
812 // Skip unsupported ops that are already marked as overdefined.
813 if (isa_and_nonnull<InstanceChoiceOp, MemOp, ObjectSubfieldOp>(
814 dest.getDefiningOp()))
815 return;
816
817 connect.emitError("connectlike operation unhandled by IMConstProp")
818 .attachNote(connect.getDest().getLoc())
819 << "connect destination is here";
820 };
821
822 if (auto srcOffset = getFieldIDOffset(changedFieldRef, destType, fieldRefSrc))
823 propagateElementLattice(
824 *srcOffset,
825 firrtl::type_cast<FIRRTLType>(
826 hw::FieldIdImpl::getFinalTypeByFieldID(destType, *srcOffset)));
827
828 if (auto relativeDest =
829 getFieldIDOffset(changedFieldRef, destType, fieldRefDest))
830 propagateElementLattice(
831 *relativeDest,
832 firrtl::type_cast<FIRRTLType>(
833 hw::FieldIdImpl::getFinalTypeByFieldID(destType, *relativeDest)));
834}
835
836void IMConstPropPass::visitRefSend(RefSendOp send, FieldRef changedFieldRef) {
837 // Send connects the base value (source) to the result (dest).
838 return mergeOnlyChangedLatticeValue(send.getResult(), send.getBase(),
839 changedFieldRef);
840}
841
842void IMConstPropPass::visitRefResolve(RefResolveOp resolve,
843 FieldRef changedFieldRef) {
844 // Resolve connects the ref value (source) to result (dest).
845 // If writes are ever supported, this will need to work differently!
846 return mergeOnlyChangedLatticeValue(resolve.getResult(), resolve.getRef(),
847 changedFieldRef);
848}
849
850void IMConstPropPass::visitNode(NodeOp node, FieldRef changedFieldRef) {
851 if (hasDontTouch(node.getResult()) || node.isForceable()) {
852 for (auto result : node.getResults())
853 markOverdefined(result);
854 return;
855 }
856
857 return mergeOnlyChangedLatticeValue(node.getResult(), node.getInput(),
858 changedFieldRef);
859}
860
861/// This method is invoked when an operand of the specified op changes its
862/// lattice value state and when the block containing the operation is first
863/// noticed as being alive.
864///
865/// This should update the lattice value state for any result values.
866///
867void IMConstPropPass::visitOperation(Operation *op, FieldRef changedField) {
868 // If this is a operation with special handling, handle it specially.
869 if (auto connectLikeOp = dyn_cast<FConnectLike>(op))
870 return visitConnectLike(connectLikeOp, changedField);
871 if (auto sendOp = dyn_cast<RefSendOp>(op))
872 return visitRefSend(sendOp, changedField);
873 if (auto resolveOp = dyn_cast<RefResolveOp>(op))
874 return visitRefResolve(resolveOp, changedField);
875 if (auto nodeOp = dyn_cast<NodeOp>(op))
876 return visitNode(nodeOp, changedField);
877
878 // The clock operand of regop changing doesn't change its result value. All
879 // other registers are over-defined. Aggregate operations also doesn't change
880 // its result value.
881 if (isa<RegOp, RegResetOp>(op) || isAggregate(op))
882 return;
883 // TODO: Handle 'when' operations.
884
885 // If all of the results of this operation are already overdefined (or if
886 // there are no results) then bail out early: we've converged.
887 auto isOverdefinedFn = [&](Value value) {
888 return isOverdefined(getOrCacheFieldRefFromValue(value));
889 };
890 if (llvm::all_of(op->getResults(), isOverdefinedFn))
891 return;
892
893 // To prevent regressions, mark values as overdefined when they are defined
894 // by operations with a large number of operands.
895 if (op->getNumOperands() > 128) {
896 for (auto value : op->getResults())
897 markOverdefined(value);
898 return;
899 }
900
901 // Collect all of the constant operands feeding into this operation. If any
902 // are not ready to be resolved, bail out and wait for them to resolve.
903 SmallVector<Attribute, 8> operandConstants;
904 operandConstants.reserve(op->getNumOperands());
905 bool hasUnknown = false;
906 for (Value operand : op->getOperands()) {
907
908 auto &operandLattice = latticeValues[getOrCacheFieldRefFromValue(operand)];
909
910 // If the operand is an unknown value, then we generally don't want to
911 // process it - we want to wait until the value is resolved to by the SCCP
912 // algorithm.
913 if (operandLattice.isUnknown())
914 hasUnknown = true;
915
916 // Otherwise, it must be constant, invalid, or overdefined. Translate them
917 // into attributes that the fold hook can look at.
918 if (operandLattice.isConstant())
919 operandConstants.push_back(operandLattice.getValue());
920 else
921 operandConstants.push_back({});
922 }
923
924 // Simulate the result of folding this operation to a constant. If folding
925 // fails mark the results as overdefined.
926 SmallVector<OpFoldResult, 8> foldResults;
927 foldResults.reserve(op->getNumResults());
928 if (failed(op->fold(operandConstants, foldResults))) {
929 LLVM_DEBUG({
930 logger.startLine() << "Folding Failed operation : '" << op->getName()
931 << "\n";
932 op->dump();
933 });
934 // If we had unknown arguments, hold off on overdefining
935 if (!hasUnknown)
936 for (auto value : op->getResults())
937 markOverdefined(value);
938 return;
939 }
940
941 LLVM_DEBUG({
942 logger.getOStream() << "\n";
943 logger.startLine() << "Folding operation : '" << op->getName() << "\n";
944 op->dump();
945 logger.getOStream() << "( ";
946 for (auto cst : operandConstants)
947 if (!cst)
948 logger.getOStream() << "{} ";
949 else
950 logger.getOStream() << cst << " ";
951 logger.unindent();
952 logger.getOStream() << ") -> { ";
953 logger.indent();
954 for (auto &r : foldResults) {
955 logger.getOStream() << r << " ";
956 }
957 logger.unindent();
958 logger.getOStream() << "}\n";
959 });
960
961 // If the folding was in-place, keep going. This is surprising, but since
962 // only folder that will do in-place updates is the commutative folder, we
963 // aren't going to stop. We don't update the results, since they didn't
964 // change, the op just got shuffled around.
965 if (foldResults.empty())
966 return visitOperation(op, changedField);
967
968 // Merge the fold results into the lattice for this operation.
969 assert(foldResults.size() == op->getNumResults() && "invalid result size");
970 for (unsigned i = 0, e = foldResults.size(); i != e; ++i) {
971 // Merge in the result of the fold, either a constant or a value.
972 LatticeValue resultLattice;
973 OpFoldResult foldResult = foldResults[i];
974 if (Attribute foldAttr = dyn_cast<Attribute>(foldResult)) {
975 if (auto intAttr = dyn_cast<IntegerAttr>(foldAttr))
976 resultLattice = LatticeValue(intAttr);
977 else if (auto strAttr = dyn_cast<StringAttr>(foldAttr))
978 resultLattice = LatticeValue(strAttr);
979 else // Treat unsupported constants as overdefined.
980 resultLattice = LatticeValue::getOverdefined();
981 } else { // Folding to an operand results in its value.
982 resultLattice =
983 latticeValues[getOrCacheFieldRefFromValue(cast<Value>(foldResult))];
984 }
985
986 mergeLatticeValue(getOrCacheFieldRefFromValue(op->getResult(i)),
987 resultLattice);
988 }
989}
990
991void IMConstPropPass::rewriteModuleBody(FModuleOp module) {
992 auto *body = module.getBodyBlock();
993 // If a module is unreachable, just ignore it.
994 if (!executableBlocks.count(body))
995 return;
996
997 auto builder = OpBuilder::atBlockBegin(body);
998
999 // Separate the constants we insert from the instructions we are folding and
1000 // processing. Leave these as-is until we're done.
1001 auto cursor = firrtl::ConstantOp::create(builder, module.getLoc(), APSInt(1));
1002 builder.setInsertionPoint(cursor);
1003
1004 // Unique constants per <Const,Type> pair, inserted at entry
1005 DenseMap<std::pair<Attribute, Type>, Operation *> constPool;
1006
1007 std::function<Value(Attribute, Type, Location)> getConst =
1008 [&](Attribute constantValue, Type type, Location loc) -> Value {
1009 auto constIt = constPool.find({constantValue, type});
1010 if (constIt != constPool.end()) {
1011 auto *cst = constIt->second;
1012 // Add location to the constant
1013 cst->setLoc(builder.getFusedLoc({cst->getLoc(), loc}));
1014 return cst->getResult(0);
1015 }
1016 OpBuilder::InsertionGuard x(builder);
1017 builder.setInsertionPoint(cursor);
1018
1019 // Materialize reftype "constants" by materializing the constant
1020 // and probing it.
1021 Operation *cst;
1022 if (auto refType = type_dyn_cast<RefType>(type)) {
1023 assert(!type_cast<RefType>(type).getForceable() &&
1024 "Attempting to materialize rwprobe of constant, shouldn't happen");
1025 auto inner = getConst(constantValue, refType.getType(), loc);
1026 assert(inner);
1027 cst = RefSendOp::create(builder, loc, inner);
1028 } else
1029 cst = module->getDialect()->materializeConstant(builder, constantValue,
1030 type, loc);
1031 assert(cst && "all FIRRTL constants can be materialized");
1032 constPool.insert({{constantValue, type}, cst});
1033 return cst->getResult(0);
1034 };
1035
1036 // If the lattice value for the specified value is a constant update it and
1037 // return true. Otherwise return false.
1038 auto replaceValueIfPossible = [&](Value value) -> bool {
1039 // Lambda to replace all uses of this value a replacement, unless this is
1040 // the destination of a connect. We leave connects alone to avoid upsetting
1041 // flow, i.e., to avoid trying to connect to a constant.
1042 auto replaceIfNotConnect = [&value](Value replacement) {
1043 value.replaceUsesWithIf(replacement, [](OpOperand &operand) {
1044 return !isa<FConnectLike>(operand.getOwner()) ||
1045 operand.getOperandNumber() != 0;
1046 });
1047 };
1048
1049 // TODO: Replace entire aggregate.
1050 auto it = latticeValues.find(getFieldRefFromValue(value));
1051 if (it == latticeValues.end() || it->second.isOverdefined() ||
1052 it->second.isUnknown())
1053 return false;
1054
1055 // Cannot materialize constants for certain types.
1056 // TODO: Let materializeConstant tell us what it supports instead of this.
1057 // Presently it asserts on unsupported combinations, so check this here.
1058 if (!type_isa<FIRRTLBaseType, RefType, FIntegerType, StringType, BoolType>(
1059 value.getType()))
1060 return false;
1061
1062 auto cstValue =
1063 getConst(it->second.getValue(), value.getType(), value.getLoc());
1064
1065 replaceIfNotConnect(cstValue);
1066 return true;
1067 };
1068
1069 // Constant propagate any ports that are always constant.
1070 for (auto &port : body->getArguments())
1071 replaceValueIfPossible(port);
1072
1073 // Walk the IR bottom-up when folding. We often fold entire chains of
1074 // operations into constants, which make the intermediate nodes dead. Going
1075 // bottom up eliminates the users of the intermediate ops, allowing us to
1076 // aggressively delete them.
1077 //
1078 // TODO: Handle WhenOps correctly.
1079 bool aboveCursor = false;
1080 module.walk<mlir::WalkOrder::PostOrder, mlir::ReverseIterator>(
1081 [&](Operation *op) {
1082 auto dropIfDead = [&](Operation *op, const Twine &debugPrefix) {
1083 if (op->use_empty() &&
1084 (wouldOpBeTriviallyDead(op) || isDeletableWireOrRegOrNode(op))) {
1085 LLVM_DEBUG(
1086 { logger.getOStream() << debugPrefix << " : " << *op << "\n"; });
1087 ++numErasedOp;
1088 op->erase();
1089 return true;
1090 }
1091 return false;
1092 };
1093
1094 if (aboveCursor) {
1095 // Drop dead constants we materialized.
1096 dropIfDead(op, "Trivially dead materialized constant");
1097 return WalkResult::advance();
1098 }
1099 // Stop once hit the generated constants.
1100 if (op == cursor) {
1101 cursor.erase();
1102 aboveCursor = true;
1103 return WalkResult::advance();
1104 }
1105
1106 // Connects to values that we found to be constant can be dropped.
1107 if (auto connect = dyn_cast<FConnectLike>(op)) {
1108 if (auto *destOp = connect.getDest().getDefiningOp()) {
1109 auto fieldRef = getOrCacheFieldRefFromValue(connect.getDest());
1110 // Don't remove a field-level connection even if the src value is
1111 // constant. If other elements of the aggregate value are not
1112 // constant, the aggregate value cannot be replaced. We can forward
1113 // the constant to its users, so IMDCE (or SV/HW canonicalizer)
1114 // should remove the aggregate if entire aggregate is dead.
1115 auto type = type_dyn_cast<FIRRTLType>(connect.getDest().getType());
1116 if (!type)
1117 return WalkResult::advance();
1118 auto baseType = type_dyn_cast<FIRRTLBaseType>(type);
1119 if (baseType && !baseType.isGround())
1120 return WalkResult::advance();
1121 if (isDeletableWireOrRegOrNode(destOp) &&
1122 !isOverdefined(fieldRef)) {
1123 connect.erase();
1124 ++numErasedOp;
1125 }
1126 }
1127 return WalkResult::advance();
1128 }
1129
1130 // We only fold single-result ops and instances in practice, because
1131 // they are the expressions.
1132 if (op->getNumResults() != 1 && !isa<InstanceOp>(op))
1133 return WalkResult::advance();
1134
1135 // If this operation is already dead, then go ahead and remove it.
1136 if (dropIfDead(op, "Trivially dead"))
1137 return WalkResult::advance();
1138
1139 // Don't "fold" constants (into equivalent), also because they
1140 // may have name hints we'd like to preserve.
1141 if (op->hasTrait<mlir::OpTrait::ConstantLike>())
1142 return WalkResult::advance();
1143
1144 // If the op had any constants folded, replace them.
1145 builder.setInsertionPoint(op);
1146 bool foldedAny = false;
1147 for (auto result : op->getResults())
1148 foldedAny |= replaceValueIfPossible(result);
1149
1150 if (foldedAny)
1151 ++numFoldedOp;
1152
1153 // If the operation folded to a constant then we can probably nuke it.
1154 if (foldedAny && dropIfDead(op, "Made dead"))
1155 return WalkResult::advance();
1156
1157 return WalkResult::advance();
1158 });
1159}
assert(baseType &&"element must be base type")
static mlir::Operation * resolve(Context &context, mlir::SymbolRefAttr sym)
static std::optional< APSInt > getConstant(Attribute operand)
Determine the value of a constant operand for the sake of constant folding.
static bool isNodeLike(Operation *op)
static bool isWireOrReg(Operation *op)
Return true if this is a wire or register.
static bool isAggregate(Operation *op)
Return true if this is an aggregate indexer.
static std::optional< uint64_t > getFieldIDOffset(FieldRef changedFieldRef, Type connectionType, FieldRef connectedValueFieldRef)
static bool isDeletableWireOrRegOrNode(Operation *op)
Return true if this is a wire or register we're allowed to delete.
static unsigned getFieldID(BundleType type, unsigned index)
static Block * getBodyBlock(FModuleLike mod)
This class represents a reference to a specific field or element of an aggregate value.
Definition FieldRef.h:28
FieldRef getSubField(unsigned subFieldID) const
Get a reference to a subfield.
Definition FieldRef.h:62
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:59
Value getValue() const
Get the Value which created this location.
Definition FieldRef.h:37
Location getLoc() const
Get the location associated with the value of this field ref.
Definition FieldRef.h:67
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
This graph tracks modules and where they are instantiated.
connect(destination, source)
Definition support.py:39
FIRRTLBaseType getBaseType(Type type)
If it is a base type, return it as is.
FieldRef getFieldRefFromValue(Value value, bool lookThroughCasts=false)
Get the FieldRef from a value.
void walkGroundTypes(FIRRTLType firrtlType, llvm::function_ref< void(uint64_t, FIRRTLBaseType, bool)> fn)
Walk leaf ground types in the firrtlType and apply the function fn.
bool isConstant(Operation *op)
Return true if the specified operation has a constant value.
bool hasDontTouch(Value value)
Check whether a block argument ("port") or the operation defining a value has a DontTouch annotation,...
llvm::raw_ostream & operator<<(llvm::raw_ostream &os, const InstanceInfo::LatticeValue &value)
bool hasDroppableName(Operation *op)
Return true if the name is droppable.
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
bool type_isa(Type type)
::mlir::Type getFinalTypeByFieldID(Type type, uint64_t fieldID)
static bool operator==(const ModulePort &a, const ModulePort &b)
Definition HWTypes.h:35
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
APSInt extOrTruncZeroWidth(APSInt value, unsigned width)
A safe version of APSInt::extOrTrunc that will NOT assert on zero-width signed APSInts.
Definition APInt.cpp:22
bool operator!=(uint64_t a, const FVInt &b)
Definition FVInt.h:685
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition seq.py:21