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