Loading [MathJax]/extensions/tex2jax.js
CIRCT 21.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
CombineDrives.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
14#include "mlir/IR/Matchers.h"
15#include "llvm/Support/Debug.h"
16
17#define DEBUG_TYPE "llhd-combine-drives"
18
19namespace circt {
20namespace llhd {
21#define GEN_PASS_DEF_COMBINEDRIVESPASS
22#include "circt/Dialect/LLHD/Transforms/LLHDPasses.h.inc"
23} // namespace llhd
24} // namespace circt
25
26using namespace mlir;
27using namespace circt;
28using namespace llhd;
29using hw::HWModuleOp;
30using llvm::SmallMapVector;
31using llvm::SmallSetVector;
32using llvm::SpecificBumpPtrAllocator;
33
34/// Determine the number of elements in a type. This returns the number of bits
35/// in an integer, the number of elements in an array, or the number of fields
36/// in a struct. Returns zero for everything else.
37static unsigned getLength(Type type) {
38 return TypeSwitch<Type, unsigned>(cast<hw::InOutType>(type).getElementType())
39 .Case<IntegerType>([](auto type) { return type.getWidth(); })
40 .Case<hw::ArrayType>([](auto type) { return type.getNumElements(); })
41 .Case<hw::StructType>([](auto type) { return type.getElements().size(); })
42 .Default([](auto) { return 0; });
43}
44
45//===----------------------------------------------------------------------===//
46// Data Structures
47//===----------------------------------------------------------------------===//
48
49namespace {
50struct Signal;
51
52/// A value representing a slice of a larger aggregate value. Does not track
53/// that larger value directly. Instead this struct only tracks the offset and
54/// length of the slice within that larger value.
55struct ValueSlice {
56 Value value;
57 unsigned offset = 0;
58 unsigned length = 0;
59};
60
61/// A drive assigning a slice of a larger signal. Does not track that larger
62/// signal directly. Instead this struct only tracks the offset and length of
63/// the slice in the signal that is being assigned.
64struct DriveSlice {
65 /// The drive op assigning a value. This may be null if no current drive op
66 /// exists.
67 DrvOp op;
68 /// The value being assigned. Usually this is equal to `op.getValue()`, but
69 /// may hold something like a default signal value if this slice was created
70 /// to fill a gap in drives and no drive op exists.
71 Value value;
72 /// The offset within the larger signal that is being assigned.
73 unsigned offset = 0;
74 /// The number of elements starting at the offset that are being assigned.
75 unsigned length = 0;
76};
77
78/// A slice of a signal. Keeps a pointer to the full `Signal`, alongside the
79/// offset and length of the elements within that signal. Operations like
80/// `llhd.sig.extract` use this struct to track which exact bits of a signal are
81/// being targeted.
82///
83/// Note the difference to `ValueSlice` and `DriveSlice`: this struct *directly*
84/// tracks the signal being sliced, while the other two structs track the result
85/// of the slicing, but not the signal being sliced directly.
86struct SignalSlice {
87 Signal *signal = nullptr;
88 unsigned offset = 0;
89 unsigned length = 0;
90
91 explicit operator bool() const { return signal != nullptr; }
92};
93
94/// A signal that can be sliced and projected into. Arrays and structs track
95/// their elements and fields as separate subsignals. Operations such as
96/// `llhd.sig`, `llhd.sig.array_get`, and `llhd.sig.struct_extract` create new
97/// `Signal`s, since they each represent an independent signal. Operations such
98/// as `llhd.sig.extract` and `llhd.sig.array_slice` *do not* create new
99/// `Signal`s; instead they simply adjust the offset and length of the
100/// `SignalSlice` pointing to an existing signal. This is an important
101/// distinction: operations that descend into subfields of an aggregate create
102/// new `Signal`s corresponding to those subfields, while operations that merely
103/// slice an aggregate into a smaller aggregate do not create new `Signal`s.
104struct Signal {
105 /// The SSA value representing the signal. This is how we first encountered
106 /// this signal in the IR. The goal of the pass is to combine drives to any
107 /// subsignals and slices into a single drive to this value.
108 Value value;
109 /// The parent aggregate signal that contains this signal.
110 Signal *parent = nullptr;
111 /// Index of the field within the parent.
112 unsigned indexInParent = 0;
113 /// The signals corresponding to individual subfields of this signal, if this
114 /// signal is an aggregate.
115 SmallVector<Signal *> subsignals;
116 /// The SSA values representing this signal or slices of it. This likely also
117 /// contains `value`. The slice's `value` field corresponds to the result of
118 /// slicing this signal. The offset and length are referring to elements of
119 /// this signal.
120 SmallVector<ValueSlice> slices;
121 /// The drives that assign a single value to the entire signal. There may be
122 /// multiple drives with different delay and enable operands.
123 SmallVector<DrvOp, 2> completeDrives;
124
125 /// Create a root signal.
126 explicit Signal(Value root) : value(root) {}
127 /// Create a subsignal representing a single field of a parent signal.
128 Signal(Value value, Signal *parent, unsigned indexInParent)
129 : value(value), parent(parent), indexInParent(indexInParent) {}
130};
131
132/// Tracks projections within a module and combines multiple drives to aggregate
133/// fields into single drives of the entire aggregate value.
134struct ModuleContext {
135 ModuleContext(HWModuleOp moduleOp) : moduleOp(moduleOp) {}
136
137 // Utilities to trace the result of a projection op back to the root signal
138 // being projected into.
139 SignalSlice traceProjection(Value value);
140 SignalSlice traceProjectionImpl(Value value);
141 Signal *internSignal(Value root);
142 Signal *internSignal(Value value, Signal *parent, unsigned index);
143
144 // Utilities to aggregate drives to a signal.
145 void aggregateDrives(Signal &signal);
146 void addDefaultDriveSlices(Signal &signal,
147 SmallVectorImpl<DriveSlice> &slices);
148 void aggregateDriveSlices(Signal &signal, Value driveDelay, Value driveEnable,
149 ArrayRef<DriveSlice> slices);
150
151 /// The module within which we are combining drives.
152 HWModuleOp moduleOp;
153 /// The signal slice targeted by each projection op in the module.
154 DenseMap<Value, SignalSlice> projections;
155 /// The root signals that have interesting projections targeting them.
156 SmallVector<Signal *> rootSignals;
157 /// Helper to clean up unused ops.
158 UnusedOpPruner pruner;
159
160private:
161 using SignalKey = std::pair<PointerUnion<Value, Signal *>, unsigned>;
162 SpecificBumpPtrAllocator<Signal> signalAlloc;
163 DenseMap<SignalKey, Signal *> internedSignals;
164};
165} // namespace
166
167/// Print a signal.
168static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
169 const Signal &signal) {
170 if (signal.parent)
171 return os << *signal.parent << "[" << signal.indexInParent << "]";
172 signal.value.printAsOperand(os, OpPrintingFlags().useLocalScope());
173 return os;
174}
175
176/// Print a signal slice.
177static llvm::raw_ostream &operator<<(llvm::raw_ostream &os, SignalSlice slice) {
178 if (!slice)
179 return os << "<null-slice>";
180 return os << *slice.signal << "[" << slice.offset << ".."
181 << (slice.offset + slice.length) << "]";
182}
183
184//===----------------------------------------------------------------------===//
185// Projection Tracing
186//===----------------------------------------------------------------------===//
187
188/// Trace the result of a projection op back to the root signal being projected
189/// into. This returns the slice within the parent signal that the projection
190/// targets.
191SignalSlice ModuleContext::traceProjection(Value value) {
192 // Check if we have already resolved this projection.
193 if (auto it = projections.find(value); it != projections.end())
194 return it->second;
195
196 // Otherwise trace the projection back to the root signal.
197 auto projection = traceProjectionImpl(value);
198 if (projection)
199 projection.signal->slices.push_back(
200 ValueSlice{value, projection.offset, projection.length});
201 projections.insert({value, projection});
202 LLVM_DEBUG(llvm::dbgs() << "- Traced " << value << " to " << projection
203 << "\n");
204 return projection;
205}
206
207/// Uncached version of `traceProjection`.
208SignalSlice ModuleContext::traceProjectionImpl(Value value) {
209 // Handle reprojection operations like `llhd.sig.extract` and
210 // `llhd.sig.array_slice`. These don't descend into a specific subfield of the
211 // input aggregate. Instead, they adjust the offset and length of the slice of
212 // bits or elements targeted by the input aggregate.
213 if (auto op = value.getDefiningOp<SigExtractOp>()) {
214 auto slice = traceProjection(op.getInput());
215 if (!slice)
216 return {};
217 IntegerAttr offsetAttr;
218 if (!matchPattern(op.getLowBit(), m_Constant(&offsetAttr)))
219 return {};
220 slice.offset += offsetAttr.getValue().getZExtValue();
221 slice.length = getLength(value.getType());
222 return slice;
223 }
224
225 if (auto op = value.getDefiningOp<SigArraySliceOp>()) {
226 auto slice = traceProjection(op.getInput());
227 if (!slice)
228 return {};
229 IntegerAttr offsetAttr;
230 if (!matchPattern(op.getLowIndex(), m_Constant(&offsetAttr)))
231 return {};
232 slice.offset += offsetAttr.getValue().getZExtValue();
233 slice.length = getLength(value.getType());
234 return slice;
235 }
236
237 // Handle proper field projections like `llhd.sig.struct_extract` and
238 // `llhd.sig.array_get`. These descend into one specific subfield of the input
239 // aggregate and return a new handle for that specific subsignal.
240 if (auto op = value.getDefiningOp<SigArrayGetOp>()) {
241 auto input = traceProjection(op.getInput());
242 if (!input)
243 return {};
244 IntegerAttr indexAttr;
245 if (!matchPattern(op.getIndex(), m_Constant(&indexAttr)))
246 return {};
247 unsigned offset = input.offset + indexAttr.getValue().getZExtValue();
248 SignalSlice slice;
249 slice.signal = internSignal(value, input.signal, offset);
250 slice.length = getLength(value.getType());
251 return slice;
252 }
253
254 if (auto op = value.getDefiningOp<SigStructExtractOp>()) {
255 auto structType = cast<hw::StructType>(
256 cast<hw::InOutType>(op.getInput().getType()).getElementType());
257 auto input = traceProjection(op.getInput());
258 if (!input)
259 return {};
260 assert(input.offset == 0);
261 assert(input.length == structType.getElements().size());
262 unsigned index = *structType.getFieldIndex(op.getFieldAttr());
263 SignalSlice slice;
264 slice.signal = internSignal(value, input.signal, index);
265 slice.length = getLength(value.getType());
266 return slice;
267 }
268
269 // Otherwise create a root node for this signal.
270 SignalSlice slice;
271 slice.signal = internSignal(value);
272 slice.length = getLength(value.getType());
273 return slice;
274}
275
276/// Return the `Signal` corresponding to the given root value. Create one if it
277/// does not yet exist. This ensures that aliasing projections all collapse to
278/// the same underlying signals.
279Signal *ModuleContext::internSignal(Value root) {
280 auto &slot = internedSignals[{root, 0}];
281 if (!slot) {
282 slot = new (signalAlloc.Allocate()) Signal(root);
283 rootSignals.push_back(slot);
284 }
285 return slot;
286}
287
288/// Return the `Signal` corresponding to the given parent signal and index
289/// within the parent. Create one if it does not yet exist. This ensures that
290/// aliasing projections all collapse to the same underlying signals.
291Signal *ModuleContext::internSignal(Value value, Signal *parent,
292 unsigned index) {
293 auto &slot = internedSignals[{parent, index}];
294 if (!slot) {
295 slot = new (signalAlloc.Allocate()) Signal(value, parent, index);
296 parent->subsignals.push_back(slot);
297 }
298 return slot;
299}
300
301//===----------------------------------------------------------------------===//
302// Drive Aggregation
303//===----------------------------------------------------------------------===//
304
305/// Try to combine separate drives to slices or projections of a signal into one
306/// drive of the entire aggregate value. This only works if the drives target
307/// consecutive and non-overlapping parts of the signal. This recursively
308/// aggregates drives to any subsignals first, and then tries to aggregate
309/// drives for this signal.
310void ModuleContext::aggregateDrives(Signal &signal) {
311 // First try to aggregate drives to our subsignals. This handles signals in a
312 // depth-first manner, first trying to combine drives to leaf fields to be
313 // combined into a single aggregate drive before processing the parent. We
314 // collect the different combinations of delay and enable operands of the
315 // drives as separate vectors of drive slices.
316 SmallMapVector<std::pair<Value, Value>, SmallVector<DriveSlice>, 2> drives;
317 SmallPtrSet<Operation *, 8> knownDrives;
318 auto addDrive = [&](DrvOp op, unsigned offset, unsigned length) {
319 knownDrives.insert(op);
320 drives[{op.getTime(), op.getEnable()}].push_back(
321 DriveSlice{op, op.getValue(), offset, length});
322 };
323 for (auto *subsignal : signal.subsignals) {
324 aggregateDrives(*subsignal);
325
326 // The above call to `aggregateDrives` has populated the signal's
327 // `completeDrives` with the drive ops that assign a full value to the
328 // signal. Use those to seed the drive slices. Each of these drives to a
329 // subsignal assign a single element of the current signal. We indicate the
330 // fact that this is a single scalar element as opposed to a length-1 slice
331 // of the aggregate by setting the drive slice's length field to 0.
332 for (auto driveOp : subsignal->completeDrives)
333 addDrive(driveOp, subsignal->indexInParent, 0);
334 }
335
336 // Gather all drives targeting this signal or slices of it directly.
337 for (auto slice : signal.slices) {
338 for (auto &use : slice.value.getUses()) {
339 auto driveOp = dyn_cast<DrvOp>(use.getOwner());
340 if (driveOp && use.getOperandNumber() == 0 &&
341 driveOp->getBlock() == slice.value.getParentBlock())
342 addDrive(driveOp, slice.offset, slice.length);
343 }
344 }
345
346 // Check if all uses of this signal are probes or drives we are aware of. If
347 // this is true we know that we can drive undriven slices of the signal with
348 // its default value without breaking semantics.
349 SmallSetVector<Value, 8> worklist;
350 worklist.insert(signal.value);
351 bool hasUnknownUses = false;
352 while (!worklist.empty() && !hasUnknownUses) {
353 auto value = worklist.pop_back_val();
354 for (auto *user : value.getUsers()) {
355 if (isa<PrbOp>(user))
356 continue;
357 if (isa<DrvOp>(user) && knownDrives.contains(user))
358 continue;
359 if (isa<SigExtractOp, SigStructExtractOp, SigArrayGetOp, SigArraySliceOp>(
360 user)) {
361 worklist.insert(user->getResult(0));
362 continue;
363 }
364 hasUnknownUses = true;
365 break;
366 }
367 }
368
369 // If the signal has no unknown uses and all drives have the same delay and
370 // condition, we can use the signal's default value to fill in undriven
371 // slices.
372 if (!hasUnknownUses && drives.size() == 1) {
373 auto &slices = drives.begin()->second;
374 addDefaultDriveSlices(signal, slices);
375 }
376
377 // Combine driven values that uniquely cover the entire signal without gaps or
378 // overlaps.
379 for (auto &[key, slices] : drives) {
380 llvm::sort(slices, [](auto &a, auto &b) { return a.offset < b.offset; });
381 aggregateDriveSlices(signal, key.first, key.second, slices);
382 }
383}
384
385/// Fill gaps in a list of drive slices with parts of the signal's default
386/// value. The slices do not have to be sorted. The filler slices are appended
387/// to `slices` directly.
388void ModuleContext::addDefaultDriveSlices(Signal &signal,
389 SmallVectorImpl<DriveSlice> &slices) {
390 auto type = cast<hw::InOutType>(signal.value.getType()).getElementType();
391
392 // Sort the slices such that we can find gaps easily.
393 llvm::sort(slices, [](auto &a, auto &b) { return a.offset < b.offset; });
394
395 // A helper function to add to `gapSlices` to fill in gaps as we encounter
396 // them. Structs require each field to be listed separately, since there are
397 // no struct slices.
398 bool anyOverlaps = false;
399 bool needSeparateFields = isa<hw::StructType>(type);
400 SmallVector<DriveSlice> gapSlices;
401 auto fillGap = [&](unsigned from, unsigned to) {
402 if (from == to)
403 return;
404 if (from > to) {
405 anyOverlaps = true;
406 return;
407 }
408 if (needSeparateFields) {
409 for (auto idx = from; idx < to; ++idx)
410 gapSlices.push_back(DriveSlice{DrvOp{}, Value{}, idx, 0});
411 } else {
412 gapSlices.push_back(DriveSlice{DrvOp{}, Value{}, from, to - from});
413 }
414 };
415
416 // Go through the slices and keep track of the offset at which we expect the
417 // slice to start. If a slice starts beyond that offset, there is a gap which
418 // we can fill with a chunk of the signal's default value.
419 unsigned expectedOffset = 0;
420 for (auto slice : slices) {
421 fillGap(expectedOffset, slice.offset);
422 expectedOffset = slice.offset + std::max<unsigned>(1, slice.length);
423 if (anyOverlaps)
424 return;
425 }
426 fillGap(expectedOffset, getLength(signal.value.getType()));
427
428 // If we have seen any overlapping slices, don't bother filling in gaps
429 // because we'll later give up on combining the drives anyway.
430 if (anyOverlaps || gapSlices.empty())
431 return;
432
433 // Dig up the default value for the signal.
434 //
435 // This is technically only valid if the signal's default value is a constant.
436 // Otherwise its value may have changed between the signal's initialization
437 // and now. But checking for const-ness is tricky because we might use
438 // bitcasts or other aggregate creation ops to build up the constant. We
439 // currently never create non-constant signal values, so this is fine for now.
440 // We'll want to revisit this at a later point, though.
441 //
442 // This currently does not work for nested signals. To support those, we
443 // potentially have to walk up our parent signals to find an actual `llhd.sig`
444 // op, and then descend back down, extracting subfields.
445 auto signalOp = signal.value.getDefiningOp<SignalOp>();
446 if (!signalOp)
447 return;
448 auto defaultValue = signalOp.getInit();
449
450 // Create drives with the default value for the gaps we've filled in.
451 ImplicitLocOpBuilder builder(signal.value.getLoc(),
452 signal.value.getContext());
453 builder.setInsertionPointAfterValue(signal.value);
454
455 for (auto &slice : gapSlices) {
456 LLVM_DEBUG(llvm::dbgs()
457 << "- Filling gap " << signal << "[" << slice.offset << ".."
458 << (slice.offset + slice.length) << "] with initial value\n");
459
460 // Handle integers.
461 if (auto intType = dyn_cast<IntegerType>(type)) {
462 assert(slice.length > 0);
463 slice.value = builder.create<comb::ExtractOp>(
464 builder.getIntegerType(slice.length), defaultValue, slice.offset);
465 continue;
466 }
467
468 // Handle structs.
469 if (auto structType = dyn_cast<hw::StructType>(type)) {
470 assert(slice.length == 0);
471 slice.value = builder.create<hw::StructExtractOp>(
472 defaultValue, structType.getElements()[slice.offset]);
473 continue;
474 }
475
476 // Handle arrays.
477 if (auto arrayType = dyn_cast<hw::ArrayType>(type)) {
478 assert(slice.length > 0);
479 auto offset = builder.create<hw::ConstantOp>(
480 APInt(llvm::Log2_64_Ceil(arrayType.getNumElements()), slice.offset));
481 slice.value = builder.create<hw::ArraySliceOp>(
482 hw::ArrayType::get(arrayType.getElementType(), slice.length),
483 defaultValue, offset);
484 continue;
485 }
486 }
487
488 // Add the gap fillers to the list of slices. These will be resorted later and
489 // will then form a consecutive non-overlapping assignment to the entire
490 // signal.
491 slices.append(gapSlices.begin(), gapSlices.end());
492}
493
494/// Combine multiple drive slices into a single drive of the aggregate value.
495/// The slices must be sorted by offset with the lowest offset first.
496void ModuleContext::aggregateDriveSlices(Signal &signal, Value driveDelay,
497 Value driveEnable,
498 ArrayRef<DriveSlice> slices) {
499 // Check whether the slices are consecutive and non-overlapping.
500 unsigned expectedOffset = 0;
501 for (auto slice : slices) {
502 assert(slice.value && "all slices must have an assigned value");
503 if (slice.offset != expectedOffset) {
504 expectedOffset = -1;
505 break;
506 }
507 // Individual subsignals are represented with length 0, since these describe
508 // an individual field and not a slice of the aggregate (`array<1xi42>` vs.
509 // `i42`). Therefore we have to count length 0 fields as single elements.
510 expectedOffset += std::max<unsigned>(1, slice.length);
511 }
512 if (expectedOffset != getLength(signal.value.getType())) {
513 LLVM_DEBUG(llvm::dbgs()
514 << "- Signal " << signal << " not completely driven\n");
515 return;
516 }
517
518 // If we get here we cover the entire signal. If we already have a single
519 // drive, simply mark that as this signal's single drive. Otherwise we have to
520 // do some actual work.
521 if (slices.size() == 1 && slices[0].length != 0 && slices[0].op) {
522 signal.completeDrives.push_back(slices[0].op);
523 return;
524 }
525 LLVM_DEBUG({
526 llvm::dbgs() << "- Aggregating " << signal << " drives (delay ";
527 driveDelay.printAsOperand(llvm::dbgs(), OpPrintingFlags().useLocalScope());
528 if (driveEnable) {
529 llvm::dbgs() << " if ";
530 driveEnable.printAsOperand(llvm::dbgs(),
531 OpPrintingFlags().useLocalScope());
532 }
533 llvm::dbgs() << ")\n";
534 });
535
536 Value result;
537 auto type = cast<hw::InOutType>(signal.value.getType()).getElementType();
538 ImplicitLocOpBuilder builder(signal.value.getLoc(),
539 signal.value.getContext());
540 builder.setInsertionPointAfterValue(signal.value);
541
542 // Handle integers.
543 if (auto intType = dyn_cast<IntegerType>(type)) {
544 // If there are more than one slices, concatenate them. Integers are pretty
545 // straightforward since there is no dedicated single-bit type. So
546 // everything is just a concatenation.
547 SmallVector<Value> operands;
548 for (auto slice : slices)
549 operands.push_back(slice.value);
550 std::reverse(operands.begin(), operands.end()); // why, just why
551 result = builder.create<comb::ConcatOp>(operands);
552 LLVM_DEBUG(llvm::dbgs() << " - Created " << result << "\n");
553 }
554
555 // Handle structs.
556 if (auto structType = dyn_cast<hw::StructType>(type)) {
557 // Structs are trivial, since there are no struct slices. Everything is an
558 // individual field that we can use directly to create the struct.
559 SmallVector<Value> operands;
560 for (auto slice : slices)
561 operands.push_back(slice.value);
562 result = builder.create<hw::StructCreateOp>(structType, operands);
563 LLVM_DEBUG(llvm::dbgs() << " - Created " << result << "\n");
564 }
565
566 // Handle arrays.
567 if (auto arrayType = dyn_cast<hw::ArrayType>(type)) {
568 // Our slices vector may consist of individual, scalar array elements or
569 // entire slices of the array. In a first step, convert all scalar elements
570 // into array slices.
571 SmallVector<Value> scalars;
572 SmallVector<Value> aggregates;
573 auto flushScalars = [&] {
574 if (scalars.empty())
575 return;
576 std::reverse(scalars.begin(), scalars.end()); // why, just why
577 auto aggregate = builder.create<hw::ArrayCreateOp>(scalars);
578 aggregates.push_back(aggregate);
579 scalars.clear();
580 LLVM_DEBUG(llvm::dbgs() << " - Created " << aggregate << "\n");
581 };
582 for (auto slice : slices) {
583 if (slice.length == 0) {
584 scalars.push_back(slice.value);
585 } else {
586 flushScalars();
587 aggregates.push_back(slice.value);
588 }
589 }
590 flushScalars();
591
592 // If there are more than one aggregate slice of the array, concatenate
593 // them into one single aggregate value.
594 result = aggregates.back();
595 if (aggregates.size() != 1) {
596 std::reverse(aggregates.begin(), aggregates.end()); // why, just why
597 result = builder.create<hw::ArrayConcatOp>(aggregates);
598 LLVM_DEBUG(llvm::dbgs() << " - Created " << result << "\n");
599 }
600 }
601
602 // Create the single drive with the aggregate result.
603 assert(result);
604 auto driveOp =
605 builder.create<DrvOp>(signal.value, result, driveDelay, driveEnable);
606 signal.completeDrives.push_back(driveOp);
607 LLVM_DEBUG(llvm::dbgs() << " - Created " << driveOp << "\n");
608
609 // Mark the old drives as to be deleted.
610 for (auto slice : slices) {
611 if (!slice.op)
612 continue;
613 LLVM_DEBUG(llvm::dbgs() << " - Removed " << slice.op << "\n");
614 pruner.eraseNow(slice.op);
615 }
616}
617
618//===----------------------------------------------------------------------===//
619// Pass Infrastructure
620//===----------------------------------------------------------------------===//
621
622namespace {
623struct CombineDrivesPass
624 : public llhd::impl::CombineDrivesPassBase<CombineDrivesPass> {
625 void runOnOperation() override;
626};
627} // namespace
628
629void CombineDrivesPass::runOnOperation() {
630 LLVM_DEBUG(llvm::dbgs() << "Combining drives in "
631 << getOperation().getModuleNameAttr() << "\n");
632 ModuleContext context(getOperation());
633
634 // Take note of all projection operations.
635 for (auto &op : *context.moduleOp.getBodyBlock())
636 if (isa<SigExtractOp, SigArraySliceOp, SigArrayGetOp, SigStructExtractOp>(
637 &op))
638 context.traceProjection(op.getResult(0));
639
640 // Aggregate drives to these projections.
641 for (auto *signal : context.rootSignals)
642 context.aggregateDrives(*signal);
643
644 // Clean up any ops that have become obsolete.
645 context.pruner.eraseNow();
646}
assert(baseType &&"element must be base type")
static unsigned getLength(Type type)
Determine the number of elements in a type.
static Block * getBodyBlock(FModuleLike mod)
create(data_type, value)
Definition hw.py:433
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Utility that tracks operations that have potentially become unused and allows them to be cleaned up a...