CIRCT 22.0.0git
Loading...
Searching...
No Matches
HoistSignals.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
11#include "mlir/Analysis/Liveness.h"
12#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
13#include "mlir/Dialect/SCF/IR/SCF.h"
14#include "mlir/IR/Matchers.h"
15#include "llvm/Support/Debug.h"
16
17#define DEBUG_TYPE "llhd-hoist-signals"
18
19namespace circt {
20namespace llhd {
21#define GEN_PASS_DEF_HOISTSIGNALSPASS
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 llvm::PointerUnion;
30using llvm::SmallSetVector;
31
32//===----------------------------------------------------------------------===//
33// Probe Hoisting
34//===----------------------------------------------------------------------===//
35
36namespace {
37/// The struct performing the hoisting of probes in a single region.
38struct ProbeHoister {
39 ProbeHoister(Region &region) : region(region) {}
40 void hoist();
41
42 void findValuesLiveAcrossWait(Liveness &liveness);
43 void hoistProbes();
44
45 /// The region we are hoisting ops out of.
46 Region &region;
47
48 /// The set of values that are alive across wait ops themselves, or that have
49 /// transitive users that are live across wait ops.
50 DenseSet<Value> liveAcrossWait;
51
52 /// A lookup table of probes we have already hoisted, for deduplication.
53 DenseMap<Value, ProbeOp> hoistedProbes;
54};
55} // namespace
56
57void ProbeHoister::hoist() {
58 Liveness liveness(region.getParentOp());
59 findValuesLiveAcrossWait(liveness);
60 hoistProbes();
61}
62
63/// Find all values in the region that are alive across `llhd.wait` operations,
64/// or that have transitive uses that are alive across waits. We can only hoist
65/// probes that do not feed data flow graphs that are alive across such wait
66/// ops. Since control flow edges in `cf.br` and `cf.cond_br` ops are
67/// side-effect free, we have no guarantee that moving a probe out of a process
68/// could potentially cause other ops to become eligible for a move out of the
69/// process. Therefore, if such ops are moved outside of the process, they are
70/// effectively moved across the waits and thus sample their operands at
71/// different points in time. Only values that are explicitly carried across
72/// `llhd.wait`, where the LLHD dialect has control over the control flow
73/// semantics, may have probes in their fan-in cone hoisted out.
74void ProbeHoister::findValuesLiveAcrossWait(Liveness &liveness) {
75 // First find all values that are live across `llhd.wait` operations. We are
76 // only interested in values defined in the current region.
77 SmallVector<Value> worklist;
78 for (auto &block : region)
79 if (isa<WaitOp>(block.getTerminator()))
80 for (auto value : liveness.getLiveOut(&block))
81 if (value.getParentRegion() == &region)
82 if (liveAcrossWait.insert(value).second)
83 worklist.push_back(value);
84
85 // Propagate liveness information along the use-def chain and across control
86 // flow. This will allow us to check `liveAcrossWait` to know if a value
87 // escapes across a wait along its use-def chain that isn't an explicit
88 // successor operand of the wait op.
89 while (!worklist.empty()) {
90 auto value = worklist.pop_back_val();
91 if (auto *defOp = value.getDefiningOp()) {
92 for (auto operand : defOp->getOperands())
93 if (operand.getParentRegion() == &region)
94 if (liveAcrossWait.insert(operand).second)
95 worklist.push_back(operand);
96 } else {
97 auto blockArg = cast<BlockArgument>(value);
98 for (auto &use : blockArg.getOwner()->getUses()) {
99 auto branch = dyn_cast<BranchOpInterface>(use.getOwner());
100 if (!branch)
101 continue;
102 auto operand = branch.getSuccessorOperands(
103 use.getOperandNumber())[blockArg.getArgNumber()];
104 if (operand.getParentRegion() == &region)
105 if (liveAcrossWait.insert(operand).second)
106 worklist.push_back(operand);
107 }
108 }
109 }
110
111 LLVM_DEBUG(llvm::dbgs() << "Found " << liveAcrossWait.size()
112 << " values live across wait\n");
113}
114
115/// Hoist any probes at the beginning of resuming blocks out of the process if
116/// their values do not leak across wait ops. Resuming blocks are blocks where
117/// all predecessors are `llhd.wait` ops, and the entry block. Only waits
118/// without any side-effecting op in between themselves and the beginning of the
119/// block can be hoisted.
120void ProbeHoister::hoistProbes() {
121 auto findExistingProbe = [&](Value signal) {
122 for (auto *user : signal.getUsers())
123 if (auto probeOp = dyn_cast<ProbeOp>(user))
124 if (probeOp->getParentRegion()->isProperAncestor(&region))
125 return probeOp;
126 return ProbeOp{};
127 };
128
129 for (auto &block : region) {
130 // We can only hoist probes in blocks where all predecessors have wait
131 // terminators.
132 if (!llvm::all_of(block.getPredecessors(), [](auto *predecessor) {
133 return isa<WaitOp>(predecessor->getTerminator());
134 }))
135 continue;
136
137 for (auto &op : llvm::make_early_inc_range(block)) {
138 auto probeOp = dyn_cast<ProbeOp>(op);
139
140 // We can only hoist probes that have no side-effecting ops between
141 // themselves and the beginning of a block. If we see a side-effecting op,
142 // give up on this block.
143 if (!probeOp) {
144 if (isMemoryEffectFree(&op))
145 continue;
146 else
147 break;
148 }
149
150 // Only hoist probes that don't leak across wait ops.
151 if (liveAcrossWait.contains(probeOp)) {
152 LLVM_DEBUG(llvm::dbgs()
153 << "- Skipping (live across wait) " << probeOp << "\n");
154 continue;
155 }
156
157 // We can only hoist probes of signals that are declared outside the
158 // process.
159 if (!probeOp.getSignal().getParentRegion()->isProperAncestor(&region)) {
160 LLVM_DEBUG(llvm::dbgs()
161 << "- Skipping (local signal) " << probeOp << "\n");
162 continue;
163 }
164
165 // Move the probe out of the process, trying to reuse any previous probe
166 // that we've already hoisted.
167 auto &hoistedOp = hoistedProbes[probeOp.getSignal()];
168 if (hoistedOp) {
169 LLVM_DEBUG(llvm::dbgs() << "- Replacing " << probeOp << "\n");
170 probeOp.replaceAllUsesWith(hoistedOp.getResult());
171 probeOp.erase();
172 } else {
173 LLVM_DEBUG(llvm::dbgs() << "- Hoisting " << probeOp << "\n");
174 if (auto existingOp = findExistingProbe(probeOp.getSignal())) {
175 probeOp.replaceAllUsesWith(existingOp.getResult());
176 probeOp.erase();
177 hoistedOp = existingOp;
178 } else {
179 if (auto *defOp = probeOp.getSignal().getDefiningOp())
180 probeOp->moveAfter(defOp);
181 else
182 probeOp->moveBefore(region.getParentOp());
183 hoistedOp = probeOp;
184 }
185 }
186 }
187 }
188}
189
190//===----------------------------------------------------------------------===//
191// Drive Operand Tracking
192//===----------------------------------------------------------------------===//
193
194namespace {
195/// An operand value on a drive operation. Can represent constant `IntegerAttr`
196/// or `TimeAttr` operands, regular `Value` operands, a typed dont-care value,
197/// or a null value.
198struct DriveValue {
199 typedef PointerUnion<Value, IntegerAttr, TimeAttr, Type> Data;
200 Data data;
201
202 // Create a null `DriveValue`.
203 DriveValue() : data(Value{}) {}
204
205 // Create a don't care `DriveValue`.
206 static DriveValue dontCare(Type type) { return DriveValue(type); }
207
208 /// Create a `DriveValue` from a non-null constant `IntegerAttr`.
209 DriveValue(IntegerAttr attr) : data(attr) {}
210
211 /// Create a `DriveValue` from a non-null constant `TimeAttr`.
212 DriveValue(TimeAttr attr) : data(attr) {}
213
214 // Create a `DriveValue` from a non-null `Value`. If the value is defined by a
215 // constant-like op, stores the constant attribute instead.
216 DriveValue(Value value) : data(value) {
217 Attribute attr;
218 if (auto *defOp = value.getDefiningOp())
219 if (m_Constant(&attr).match(defOp))
220 TypeSwitch<Attribute>(attr).Case<IntegerAttr, TimeAttr>(
221 [&](auto attr) { data = attr; });
222 }
223
224 bool operator==(const DriveValue &other) const { return data == other.data; }
225 bool operator!=(const DriveValue &other) const { return data != other.data; }
226
227 bool isDontCare() const { return isa<Type>(data); }
228 explicit operator bool() const { return bool(data); }
229
230 Type getType() const {
231 return TypeSwitch<Data, Type>(data)
232 .Case<Value, IntegerAttr, TimeAttr>([](auto x) { return x.getType(); })
233 .Case<Type>([](auto type) { return type; });
234 }
235
236private:
237 explicit DriveValue(Data data) : data(data) {}
238};
239
240/// The operands of an `llhd.drv`, represented as `DriveValue`s.
241struct DriveOperands {
242 DriveValue value;
243 DriveValue delay;
244 DriveValue enable;
245};
246
247/// A set of drives to a single slot. Tracks the drive operations, the operands
248/// of the drive before each terminator, and which operands have a uniform
249/// value.
250struct DriveSet {
251 /// The drive operations covered by the information in this struct.
252 SmallPtrSet<Operation *, 2> ops;
253 /// The drive operands at each terminator.
255 /// The drive operands that are uniform across all terminators, or null if
256 /// non-uniform.
257 DriveOperands uniform;
258};
259} // namespace
260
261static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
262 const DriveValue &dv) {
263 if (!dv.data)
264 os << "null";
265 else
266 TypeSwitch<DriveValue::Data>(dv.data)
267 .Case<Value, IntegerAttr, TimeAttr>([&](auto x) { os << x; })
268 .Case<Type>([&](auto) { os << "dont-care"; });
269 return os;
270}
271
272//===----------------------------------------------------------------------===//
273// Drive Hoisting
274//===----------------------------------------------------------------------===//
275
276namespace {
277/// The struct performing the hoisting of drives in a process.
278struct DriveHoister {
279 DriveHoister(ProcessOp processOp) : processOp(processOp) {}
280 void hoist();
281
282 void findHoistableSlots();
283 void collectDriveSets();
284 void finalizeDriveSets();
285 void hoistDrives();
286
287 /// The process we are hoisting drives out of.
288 ProcessOp processOp;
289
290 /// The slots for which we are trying to hoist drives. Mostly `llhd.sig` ops
291 /// in practice. This establishes a deterministic order for slots, such that
292 /// everything else in the pass can operate using unordered maps and sets.
293 SmallSetVector<Value, 8> slots;
294 SmallVector<Operation *> suspendOps;
296};
297} // namespace
298
299void DriveHoister::hoist() {
300 findHoistableSlots();
301 collectDriveSets();
302 finalizeDriveSets();
303 hoistDrives();
304}
305
306/// Identify any slots driven under the current region which are candidates for
307/// hoisting. This checks if the slots escape or alias in any way which we
308/// cannot reason about.
309void DriveHoister::findHoistableSlots() {
310 SmallPtrSet<Value, 8> seenSlots;
311 processOp.walk([&](DriveOp op) {
312 auto slot = op.getSignal();
313 if (!seenSlots.insert(slot).second)
314 return;
315
316 // We can only hoist drives to slots declared by a `llhd.sig` op outside the
317 // current region.
318 if (!slot.getDefiningOp<llhd::SignalOp>())
319 return;
320 if (!slot.getParentRegion()->isProperAncestor(&processOp.getBody()))
321 return;
322
323 // Ensure the slot is not used in any way we cannot reason about.
324 if (!llvm::all_of(slot.getUsers(), [&](auto *user) {
325 // Ignore uses outside of the region.
326 if (!processOp.getBody().isAncestor(user->getParentRegion()))
327 return true;
328 return isa<ProbeOp, DriveOp>(user);
329 }))
330 return;
331
332 slots.insert(slot);
333 });
334 LLVM_DEBUG(llvm::dbgs() << "Found " << slots.size()
335 << " slots for drive hoisting\n");
336}
337
338/// Collect the operands of all hoistable drives into a per-slot drive set.
339/// After this function returns, the `driveSets` contains a drive set for each
340/// slot that has at least one hoistable drive. Each drive set lists the drive
341/// operands for each suspending terminator. If a slot is not driven before a
342/// terminator, the drive set will not contain an entry for that terminator.
343/// Also populates `suspendOps` with all `llhd.wait` and `llhd.halt` ops.
344void DriveHoister::collectDriveSets() {
345 SmallPtrSet<Value, 8> unhoistableSlots;
347
348 auto trueAttr = BoolAttr::get(processOp.getContext(), true);
349 for (auto &block : processOp.getBody()) {
350 // We can only hoist drives before wait or halt terminators.
351 auto *terminator = block.getTerminator();
352 if (!isa<WaitOp, HaltOp>(terminator))
353 continue;
354 suspendOps.push_back(terminator);
355
356 bool beyondSideEffect = false;
357 laterDrives.clear();
358
359 for (auto &op : llvm::make_early_inc_range(
360 llvm::reverse(block.without_terminator()))) {
361 auto driveOp = dyn_cast<DriveOp>(op);
362
363 // We can only hoist drives that have no side-effecting ops between
364 // themselves and the terminator of the block. If we see a side-effecting
365 // op, give up on this block.
366 if (!driveOp) {
367 if (!isMemoryEffectFree(&op))
368 beyondSideEffect = true;
369 continue;
370 }
371
372 // Check if we can hoist drives to this signal.
373 if (!slots.contains(driveOp.getSignal()) ||
374 unhoistableSlots.contains(driveOp.getSignal())) {
375 LLVM_DEBUG(llvm::dbgs()
376 << "- Skipping (slot unhoistable): " << driveOp << "\n");
377 continue;
378 }
379
380 // If this drive is beyond a side-effecting op, mark the slot as
381 // unhoistable.
382 if (beyondSideEffect) {
383 LLVM_DEBUG(llvm::dbgs()
384 << "- Aborting slot (drive across side-effect): " << driveOp
385 << "\n");
386 unhoistableSlots.insert(driveOp.getSignal());
387 continue;
388 }
389
390 // Handle the case where we've seen a later drive to this slot.
391 auto &laterDrive = laterDrives[driveOp.getSignal()];
392 if (laterDrive) {
393 // If there is a later drive with the same delay and enable condition,
394 // we can simply ignore this drive.
395 if (laterDrive.getTime() == driveOp.getTime() &&
396 laterDrive.getEnable() == driveOp.getEnable()) {
397 LLVM_DEBUG(llvm::dbgs()
398 << "- Skipping (driven later): " << driveOp << "\n");
399 continue;
400 }
401
402 // Otherwise mark the slot as unhoistable since we cannot merge multiple
403 // drives with different delays or enable conditions into a single
404 // drive.
405 LLVM_DEBUG(llvm::dbgs()
406 << "- Aborting slot (multiple drives): " << driveOp << "\n");
407 unhoistableSlots.insert(driveOp.getSignal());
408 continue;
409 }
410 laterDrive = driveOp;
411
412 // Add the operands of this drive to the drive set for the driven slot.
413 auto operands = DriveOperands{
414 driveOp.getValue(),
415 driveOp.getTime(),
416 driveOp.getEnable() ? DriveValue(driveOp.getEnable())
417 : DriveValue(trueAttr),
418 };
419 auto &driveSet = driveSets[driveOp.getSignal()];
420 driveSet.ops.insert(driveOp);
421 driveSet.operands.insert({terminator, operands});
422 }
423 }
424
425 // Remove slots we've found to be unhoistable.
426 slots.remove_if([&](auto slot) {
427 if (unhoistableSlots.contains(slot)) {
428 driveSets.erase(slot);
429 return true;
430 }
431 return false;
432 });
433}
434
435/// Make sure all drive sets specify a drive value for each terminator. If a
436/// terminator is missing, add a drive with its enable set to false. Also
437/// determine which values are uniform and available outside the process, such
438/// that we don't create unnecessary process results.
439void DriveHoister::finalizeDriveSets() {
440 auto falseAttr = BoolAttr::get(processOp.getContext(), false);
441 for (auto &[slot, driveSet] : driveSets) {
442 // Insert drives with enable set to false for any terminators that are
443 // missing. This ensures that the drive set contains information for every
444 // terminator.
445 for (auto *suspendOp : suspendOps) {
446 auto operands = DriveOperands{
447 DriveValue::dontCare(cast<RefType>(slot.getType()).getNestedType()),
448 DriveValue::dontCare(TimeType::get(processOp.getContext())),
449 DriveValue(falseAttr),
450 };
451 driveSet.operands.insert({suspendOp, operands});
452 }
453
454 // Determine which drive operands have a uniform value across all
455 // terminators. A null `DriveValue` indicates that there is no uniform
456 // value.
457 auto unify = [](DriveValue &accumulator, DriveValue other) {
458 if (other.isDontCare())
459 return;
460 if (accumulator == other)
461 return;
462 accumulator = accumulator.isDontCare() ? other : DriveValue();
463 };
464 assert(!driveSet.operands.empty());
465 driveSet.uniform = driveSet.operands.begin()->second;
466 for (auto [terminator, otherOperands] : driveSet.operands) {
467 unify(driveSet.uniform.value, otherOperands.value);
468 unify(driveSet.uniform.delay, otherOperands.delay);
469 unify(driveSet.uniform.enable, otherOperands.enable);
470 }
471
472 // Discard uniform non-constant values. We cannot directly use SSA values
473 // defined outside the process in the extracted drive, since those values
474 // may change at different times than the current process executes and
475 // updates its results.
476 auto clearIfNonConst = [&](DriveValue &driveValue) {
477 if (isa_and_nonnull<Value>(driveValue.data))
478 driveValue = DriveValue();
479 };
480 clearIfNonConst(driveSet.uniform.value);
481 clearIfNonConst(driveSet.uniform.delay);
482 clearIfNonConst(driveSet.uniform.enable);
483 }
484
485 LLVM_DEBUG({
486 for (auto slot : slots) {
487 const auto &driveSet = driveSets[slot];
488 llvm::dbgs() << "Drives to " << slot << "\n";
489 if (driveSet.uniform.value)
490 llvm::dbgs() << "- Uniform value: " << driveSet.uniform.value << "\n";
491 if (driveSet.uniform.delay)
492 llvm::dbgs() << "- Uniform delay: " << driveSet.uniform.delay << "\n";
493 if (driveSet.uniform.enable)
494 llvm::dbgs() << "- Uniform enable: " << driveSet.uniform.enable << "\n";
495 for (auto *suspendOp : suspendOps) {
496 auto operands = driveSet.operands.lookup(suspendOp);
497 llvm::dbgs() << "- At " << *suspendOp << "\n";
498 if (!driveSet.uniform.value)
499 llvm::dbgs() << " - Value: " << operands.value << "\n";
500 if (!driveSet.uniform.delay)
501 llvm::dbgs() << " - Delay: " << operands.delay << "\n";
502 if (!driveSet.uniform.enable)
503 llvm::dbgs() << " - Enable: " << operands.enable << "\n";
504 }
505 }
506 });
507}
508
509/// Hoist drive operations out of the process. This function adds yield operands
510/// to carry the operands of the hoisted drives out of the process, and adds
511/// corresponding process results. It then creates replacement drives outside of
512/// the process and uses the new result values for the drive operands.
513void DriveHoister::hoistDrives() {
514 if (driveSets.empty())
515 return;
516 LLVM_DEBUG(llvm::dbgs() << "Hoisting drives of " << driveSets.size()
517 << " slots\n");
518
519 // A builder to construct constant values outside the region.
520 OpBuilder builder(processOp);
521 SmallDenseMap<Attribute, Value> materializedConstants;
522
523 auto materialize = [&](DriveValue driveValue) -> Value {
524 OpBuilder builder(processOp);
525 return TypeSwitch<DriveValue::Data, Value>(driveValue.data)
526 .Case<Value>([](auto value) { return value; })
527 .Case<IntegerAttr>([&](auto attr) {
528 auto &slot = materializedConstants[attr];
529 if (!slot)
530 slot = hw::ConstantOp::create(builder, processOp.getLoc(), attr);
531 return slot;
532 })
533 .Case<TimeAttr>([&](auto attr) {
534 auto &slot = materializedConstants[attr];
535 if (!slot)
536 slot = ConstantTimeOp::create(builder, processOp.getLoc(), attr);
537 return slot;
538 })
539 .Case<Type>([&](auto type) {
540 // TODO: This should probably create something like a `llhd.dontcare`.
541 if (isa<TimeType>(type)) {
542 auto attr = TimeAttr::get(builder.getContext(), 0, "ns", 0, 0);
543 auto &slot = materializedConstants[attr];
544 if (!slot)
545 slot = ConstantTimeOp::create(builder, processOp.getLoc(), attr);
546 return slot;
547 }
548 auto numBits = hw::getBitWidth(type);
549 assert(numBits >= 0);
550 Value value = hw::ConstantOp::create(
551 builder, processOp.getLoc(), builder.getIntegerType(numBits), 0);
552 if (value.getType() != type)
553 value =
554 hw::BitcastOp::create(builder, processOp.getLoc(), type, value);
555 return value;
556 });
557 };
558
559 // Add the non-uniform drive operands as yield operands of any `llhd.wait` and
560 // `llhd.halt` terminators.
561 for (auto *suspendOp : suspendOps) {
562 LLVM_DEBUG(llvm::dbgs()
563 << "- Adding yield operands to " << *suspendOp << "\n");
564 MutableOperandRange yieldOperands =
565 TypeSwitch<Operation *, MutableOperandRange>(suspendOp)
566 .Case<WaitOp, HaltOp>(
567 [](auto op) { return op.getYieldOperandsMutable(); });
568
569 auto addYieldOperand = [&](DriveValue uniform, DriveValue nonUniform) {
570 if (!uniform)
571 yieldOperands.append(materialize(nonUniform));
572 };
573
574 for (auto slot : slots) {
575 auto &driveSet = driveSets[slot];
576 auto operands = driveSet.operands.lookup(suspendOp);
577 addYieldOperand(driveSet.uniform.value, operands.value);
578 addYieldOperand(driveSet.uniform.delay, operands.delay);
579 addYieldOperand(driveSet.uniform.enable, operands.enable);
580 }
581 }
582
583 // Add process results corresponding to the added yield operands.
584 SmallVector<Type> resultTypes(processOp->getResultTypes());
585 auto oldNumResults = resultTypes.size();
586 auto addResultType = [&](DriveValue uniform, DriveValue nonUniform) {
587 if (!uniform)
588 resultTypes.push_back(nonUniform.getType());
589 };
590 for (auto slot : slots) {
591 auto &driveSet = driveSets[slot];
592 auto operands = driveSet.operands.begin()->second;
593 addResultType(driveSet.uniform.value, operands.value);
594 addResultType(driveSet.uniform.delay, operands.delay);
595 addResultType(driveSet.uniform.enable, operands.enable);
596 }
597 auto newProcessOp =
598 ProcessOp::create(builder, processOp.getLoc(), resultTypes,
599 processOp->getOperands(), processOp->getAttrs());
600 newProcessOp.getBody().takeBody(processOp.getBody());
601 processOp.replaceAllUsesWith(
602 newProcessOp->getResults().slice(0, oldNumResults));
603 processOp.erase();
604 processOp = newProcessOp;
605
606 // Hoist the actual drive operations. We either materialize uniform values
607 // directly, since they are guaranteed to be able outside the process at this
608 // point, or use the new process results.
609 builder.setInsertionPointAfter(processOp);
610 auto newResultIdx = oldNumResults;
611
612 auto useResultValue = [&](DriveValue uniform) {
613 if (!uniform)
614 return processOp.getResult(newResultIdx++);
615 return materialize(uniform);
616 };
617
618 auto removeIfUnused = [](Value value) {
619 if (value)
620 if (auto *defOp = value.getDefiningOp())
621 if (defOp && isOpTriviallyDead(defOp))
622 defOp->erase();
623 };
624
625 auto trueAttr = builder.getBoolAttr(true);
626 for (auto slot : slots) {
627 auto &driveSet = driveSets[slot];
628
629 // Create the new drive outside of the process.
630 auto value = useResultValue(driveSet.uniform.value);
631 auto delay = useResultValue(driveSet.uniform.delay);
632 auto enable = driveSet.uniform.enable != DriveValue(trueAttr)
633 ? useResultValue(driveSet.uniform.enable)
634 : Value{};
635 [[maybe_unused]] auto newDrive =
636 DriveOp::create(builder, slot.getLoc(), slot, value, delay, enable);
637 LLVM_DEBUG(llvm::dbgs() << "- Add " << newDrive << "\n");
638
639 // Remove the old drives inside of the process.
640 for (auto *oldOp : driveSet.ops) {
641 auto oldDrive = cast<DriveOp>(oldOp);
642 LLVM_DEBUG(llvm::dbgs() << "- Remove " << oldDrive << "\n");
643 auto delay = oldDrive.getTime();
644 auto enable = oldDrive.getEnable();
645 oldDrive.erase();
646 removeIfUnused(delay);
647 removeIfUnused(enable);
648 }
649 driveSet.ops.clear();
650 }
651 assert(newResultIdx == processOp->getNumResults());
652}
653
654//===----------------------------------------------------------------------===//
655// Pass Infrastructure
656//===----------------------------------------------------------------------===//
657
658namespace {
659struct HoistSignalsPass
660 : public llhd::impl::HoistSignalsPassBase<HoistSignalsPass> {
661 void runOnOperation() override;
662};
663} // namespace
664
665void HoistSignalsPass::runOnOperation() {
666 SmallVector<Region *> regions;
667 getOperation()->walk([&](Operation *op) {
668 if (isa<ProcessOp, FinalOp, CombinationalOp, scf::IfOp>(op))
669 for (auto &region : op->getRegions())
670 if (!region.empty())
671 regions.push_back(&region);
672 });
673 for (auto *region : regions) {
674 ProbeHoister(*region).hoist();
675 if (auto processOp = dyn_cast<ProcessOp>(region->getParentOp()))
676 DriveHoister(processOp).hoist();
677 }
678}
assert(baseType &&"element must be base type")
static LogicalResult processOp(const DomainInfo &info, TermAllocator &allocator, DomainTable &table, const ModuleUpdateTable &updateTable, InstanceOp op)
static LogicalResult unify(Term *lhs, Term *rhs)
static InstancePath empty
create(data_type, value)
Definition hw.py:441
create(data_type, value)
Definition hw.py:433
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
static bool operator==(const ModulePort &a, const ModulePort &b)
Definition HWTypes.h:35
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
bool operator!=(uint64_t a, const FVInt &b)
Definition FVInt.h:685