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"
17#define DEBUG_TYPE "llhd-hoist-signals"
21#define GEN_PASS_DEF_HOISTSIGNALSPASS
22#include "circt/Dialect/LLHD/Transforms/LLHDPasses.h.inc"
29using llvm::PointerUnion;
30using llvm::SmallSetVector;
39 ProbeHoister(Region ®ion) : region(region) {}
42 void findValuesLiveAcrossWait(Liveness &liveness);
50 DenseSet<Value> liveAcrossWait;
53 DenseMap<Value, PrbOp> hoistedProbes;
57void ProbeHoister::hoist() {
58 Liveness liveness(region.getParentOp());
59 findValuesLiveAcrossWait(liveness);
74void ProbeHoister::findValuesLiveAcrossWait(Liveness &liveness) {
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() == ®ion)
82 if (liveAcrossWait.insert(value).second)
83 worklist.push_back(value);
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() == ®ion)
94 if (liveAcrossWait.insert(operand).second)
95 worklist.push_back(operand);
97 auto blockArg = cast<BlockArgument>(value);
98 for (
auto &use : blockArg.getOwner()->getUses()) {
99 auto branch = dyn_cast<BranchOpInterface>(use.getOwner());
102 auto operand = branch.getSuccessorOperands(
103 use.getOperandNumber())[blockArg.getArgNumber()];
104 if (operand.getParentRegion() == ®ion)
105 if (liveAcrossWait.insert(operand).second)
106 worklist.push_back(operand);
111 LLVM_DEBUG(llvm::dbgs() <<
"Found " << liveAcrossWait.size()
112 <<
" values live across wait\n");
120void ProbeHoister::hoistProbes() {
121 auto findExistingProbe = [&](Value signal) {
122 for (
auto *user : signal.getUsers())
123 if (auto probeOp = dyn_cast<PrbOp>(user))
124 if (probeOp->getParentRegion()->isProperAncestor(®ion))
129 for (
auto &block : region) {
132 if (!llvm::all_of(block.getPredecessors(), [](
auto *predecessor) {
133 return isa<WaitOp>(predecessor->getTerminator());
137 for (
auto &op :
llvm::make_early_inc_range(block)) {
138 auto probeOp = dyn_cast<PrbOp>(op);
144 if (isMemoryEffectFree(&op))
151 if (liveAcrossWait.contains(probeOp)) {
152 LLVM_DEBUG(llvm::dbgs()
153 <<
"- Skipping (live across wait) " << probeOp <<
"\n");
159 if (!probeOp.getSignal().getParentRegion()->isProperAncestor(®ion)) {
160 LLVM_DEBUG(llvm::dbgs()
161 <<
"- Skipping (local signal) " << probeOp <<
"\n");
167 auto &hoistedOp = hoistedProbes[probeOp.getSignal()];
169 LLVM_DEBUG(llvm::dbgs() <<
"- Replacing " << probeOp <<
"\n");
170 probeOp.replaceAllUsesWith(hoistedOp.getResult());
173 LLVM_DEBUG(llvm::dbgs() <<
"- Hoisting " << probeOp <<
"\n");
174 if (
auto existingOp = findExistingProbe(probeOp.getSignal())) {
175 probeOp.replaceAllUsesWith(existingOp.getResult());
177 hoistedOp = existingOp;
179 if (
auto *defOp = probeOp.getSignal().getDefiningOp())
180 probeOp->moveAfter(defOp);
182 probeOp->moveBefore(region.getParentOp());
199 typedef PointerUnion<Value, IntegerAttr, TimeAttr, Type> Data;
203 DriveValue() :
data(Value{}) {}
206 static DriveValue dontCare(Type type) {
return DriveValue(type); }
209 DriveValue(IntegerAttr attr) :
data(attr) {}
212 DriveValue(TimeAttr attr) :
data(attr) {}
216 DriveValue(Value value) :
data(value) {
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; });
224 bool operator==(
const DriveValue &other)
const {
return data == other.data; }
225 bool operator!=(
const DriveValue &other)
const {
return data != other.data; }
227 bool isDontCare()
const {
return isa<Type>(data); }
228 explicit operator bool()
const {
return bool(data); }
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; });
237 explicit DriveValue(Data data) :
data(
data) {}
241struct DriveOperands {
252 SmallPtrSet<Operation *, 2> ops;
257 DriveOperands uniform;
262 const DriveValue &dv) {
266 TypeSwitch<DriveValue::Data>(dv.data)
267 .Case<Value, IntegerAttr, TimeAttr>([&](
auto x) { os << x; })
268 .Case<Type>([&](
auto) { os <<
"dont-care"; });
279 DriveHoister(ProcessOp processOp) : processOp(processOp) {}
282 void findHoistableSlots();
283 void collectDriveSets();
284 void finalizeDriveSets();
293 SmallSetVector<Value, 8> slots;
294 SmallVector<Operation *> suspendOps;
299void DriveHoister::hoist() {
300 findHoistableSlots();
309void DriveHoister::findHoistableSlots() {
310 SmallPtrSet<Value, 8> seenSlots;
311 processOp.walk([&](DrvOp op) {
312 auto slot = op.getSignal();
313 if (!seenSlots.insert(slot).second)
318 if (!slot.getDefiningOp<llhd::SignalOp>())
320 if (!slot.getParentRegion()->isProperAncestor(&processOp.getBody()))
324 if (!llvm::all_of(slot.getUsers(), [&](
auto *user) {
326 if (!processOp.getBody().isAncestor(user->getParentRegion()))
328 return isa<PrbOp, DrvOp>(user);
334 LLVM_DEBUG(llvm::dbgs() <<
"Found " << slots.size()
335 <<
" slots for drive hoisting\n");
344void DriveHoister::collectDriveSets() {
345 auto trueAttr = BoolAttr::get(processOp.getContext(),
true);
346 for (
auto &block : processOp.getBody()) {
348 auto *terminator = block.getTerminator();
349 if (!isa<WaitOp, HaltOp>(terminator))
351 suspendOps.push_back(terminator);
353 SmallPtrSet<Value, 8> drivenSlots;
354 for (
auto &op :
llvm::make_early_inc_range(
355 llvm::reverse(block.without_terminator()))) {
356 auto driveOp = dyn_cast<DrvOp>(op);
362 if (isMemoryEffectFree(&op))
369 if (!slots.contains(driveOp.getSignal())) {
370 LLVM_DEBUG(llvm::dbgs()
371 <<
"- Skipping (slot unhoistable) " << driveOp <<
"\n");
376 if (!drivenSlots.insert(driveOp.getSignal()).second) {
377 LLVM_DEBUG(llvm::dbgs()
378 <<
"- Skipping (driven later) " << driveOp <<
"\n");
383 auto operands = DriveOperands{
386 driveOp.getEnable() ? DriveValue(driveOp.getEnable())
387 : DriveValue(trueAttr),
389 auto &driveSet = driveSets[driveOp.getSignal()];
390 driveSet.ops.insert(driveOp);
391 driveSet.operands.insert({terminator, operands});
400void DriveHoister::finalizeDriveSets() {
401 auto falseAttr = BoolAttr::get(processOp.getContext(),
false);
402 for (
auto &[slot, driveSet] : driveSets) {
406 for (
auto *suspendOp : suspendOps) {
407 auto operands = DriveOperands{
408 DriveValue::dontCare(
409 cast<hw::InOutType>(slot.getType()).getElementType()),
410 DriveValue::dontCare(TimeType::get(processOp.getContext())),
411 DriveValue(falseAttr),
413 driveSet.operands.insert({suspendOp, operands});
419 auto unify = [](DriveValue &accumulator, DriveValue other) {
420 if (other.isDontCare())
422 if (accumulator == other)
424 accumulator = accumulator.isDontCare() ? other : DriveValue();
426 assert(!driveSet.operands.empty());
427 driveSet.uniform = driveSet.operands.begin()->second;
428 for (
auto [terminator, otherOperands] : driveSet.operands) {
429 unify(driveSet.uniform.value, otherOperands.value);
430 unify(driveSet.uniform.delay, otherOperands.delay);
431 unify(driveSet.uniform.enable, otherOperands.enable);
438 auto clearIfNonConst = [&](DriveValue &driveValue) {
439 if (isa_and_nonnull<Value>(driveValue.data))
440 driveValue = DriveValue();
442 clearIfNonConst(driveSet.uniform.value);
443 clearIfNonConst(driveSet.uniform.delay);
444 clearIfNonConst(driveSet.uniform.enable);
448 for (
auto slot : slots) {
449 const auto &driveSet = driveSets[slot];
450 llvm::dbgs() <<
"Drives to " << slot <<
"\n";
451 if (driveSet.uniform.value)
452 llvm::dbgs() <<
"- Uniform value: " << driveSet.uniform.value <<
"\n";
453 if (driveSet.uniform.delay)
454 llvm::dbgs() <<
"- Uniform delay: " << driveSet.uniform.delay <<
"\n";
455 if (driveSet.uniform.enable)
456 llvm::dbgs() <<
"- Uniform enable: " << driveSet.uniform.enable <<
"\n";
457 for (
auto *suspendOp : suspendOps) {
458 auto operands = driveSet.operands.lookup(suspendOp);
459 llvm::dbgs() <<
"- At " << *suspendOp <<
"\n";
460 if (!driveSet.uniform.value)
461 llvm::dbgs() <<
" - Value: " << operands.value <<
"\n";
462 if (!driveSet.uniform.delay)
463 llvm::dbgs() <<
" - Delay: " << operands.delay <<
"\n";
464 if (!driveSet.uniform.enable)
465 llvm::dbgs() <<
" - Enable: " << operands.enable <<
"\n";
475void DriveHoister::hoistDrives() {
476 if (driveSets.empty())
478 LLVM_DEBUG(llvm::dbgs() <<
"Hoisting drives of " << driveSets.size()
482 OpBuilder builder(processOp);
485 auto materialize = [&](DriveValue driveValue) -> Value {
486 OpBuilder builder(processOp);
487 return TypeSwitch<DriveValue::Data, Value>(driveValue.data)
488 .Case<Value>([](
auto value) {
return value; })
489 .Case<IntegerAttr>([&](
auto attr) {
490 auto &slot = materializedConstants[attr];
495 .Case<TimeAttr>([&](
auto attr) {
496 auto &slot = materializedConstants[attr];
499 builder.create<llhd::ConstantTimeOp>(processOp.getLoc(), attr);
502 .Case<Type>([&](
auto type) {
504 unsigned numBits = hw::getBitWidth(type);
507 processOp.getLoc(), builder.getIntegerType(numBits), 0);
508 if (value.getType() != type)
517 for (
auto *suspendOp : suspendOps) {
518 LLVM_DEBUG(llvm::dbgs()
519 <<
"- Adding yield operands to " << *suspendOp <<
"\n");
520 MutableOperandRange yieldOperands =
521 TypeSwitch<Operation *, MutableOperandRange>(suspendOp)
522 .Case<WaitOp, HaltOp>(
523 [](
auto op) {
return op.getYieldOperandsMutable(); });
525 auto addYieldOperand = [&](DriveValue uniform, DriveValue nonUniform) {
527 yieldOperands.append(materialize(nonUniform));
530 for (
auto slot : slots) {
531 auto &driveSet = driveSets[slot];
532 auto operands = driveSet.operands.lookup(suspendOp);
533 addYieldOperand(driveSet.uniform.value, operands.value);
534 addYieldOperand(driveSet.uniform.delay, operands.delay);
535 addYieldOperand(driveSet.uniform.enable, operands.enable);
540 SmallVector<Type> resultTypes(processOp->getResultTypes());
541 auto oldNumResults = resultTypes.size();
542 auto addResultType = [&](DriveValue uniform, DriveValue nonUniform) {
544 resultTypes.push_back(nonUniform.getType());
546 for (
auto slot : slots) {
547 auto &driveSet = driveSets[slot];
548 auto operands = driveSet.operands.begin()->second;
549 addResultType(driveSet.uniform.value, operands.value);
550 addResultType(driveSet.uniform.delay, operands.delay);
551 addResultType(driveSet.uniform.enable, operands.enable);
553 auto newProcessOp = builder.create<ProcessOp>(processOp.getLoc(), resultTypes,
554 processOp->getOperands(),
555 processOp->getAttrs());
556 newProcessOp.getBody().takeBody(processOp.getBody());
557 processOp.replaceAllUsesWith(
558 newProcessOp->getResults().slice(0, oldNumResults));
560 processOp = newProcessOp;
565 builder.setInsertionPointAfter(processOp);
566 auto newResultIdx = oldNumResults;
568 auto useResultValue = [&](DriveValue uniform) {
570 return processOp.getResult(newResultIdx++);
571 return materialize(uniform);
574 auto removeIfUnused = [](Value value) {
576 if (
auto *defOp = value.getDefiningOp())
577 if (defOp && isOpTriviallyDead(defOp))
581 auto trueAttr = builder.getBoolAttr(
true);
582 for (
auto slot : slots) {
583 auto &driveSet = driveSets[slot];
586 auto value = useResultValue(driveSet.uniform.value);
587 auto delay = useResultValue(driveSet.uniform.delay);
588 auto enable = driveSet.uniform.enable != DriveValue(trueAttr)
589 ? useResultValue(driveSet.uniform.enable)
592 builder.create<DrvOp>(slot.getLoc(), slot, value, delay, enable);
593 LLVM_DEBUG(llvm::dbgs() <<
"- Add " << newDrive <<
"\n");
596 for (
auto *oldOp : driveSet.ops) {
597 auto oldDrive = cast<DrvOp>(oldOp);
598 LLVM_DEBUG(llvm::dbgs() <<
"- Remove " << oldDrive <<
"\n");
599 auto delay = oldDrive.getTime();
600 auto enable = oldDrive.getEnable();
602 removeIfUnused(delay);
603 removeIfUnused(enable);
605 driveSet.ops.clear();
607 assert(newResultIdx == processOp->getNumResults());
615struct HoistSignalsPass
616 :
public llhd::impl::HoistSignalsPassBase<HoistSignalsPass> {
617 void runOnOperation()
override;
621void HoistSignalsPass::runOnOperation() {
622 SmallVector<Region *> regions;
623 getOperation()->walk([&](Operation *op) {
624 if (isa<ProcessOp, FinalOp, CombinationalOp, scf::IfOp>(op))
625 for (
auto ®ion : op->getRegions())
627 regions.push_back(®ion);
629 for (
auto *region : regions) {
630 ProbeHoister(*region).hoist();
631 if (
auto processOp = dyn_cast<ProcessOp>(region->getParentOp()))
632 DriveHoister(processOp).hoist();
assert(baseType &&"element must be base type")
static InstancePath empty
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
static bool operator==(const ModulePort &a, const ModulePort &b)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
bool operator!=(uint64_t a, const FVInt &b)