16#include "mlir/Analysis/Liveness.h"
17#include "mlir/Dialect/Arith/IR/Arith.h"
18#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
19#include "mlir/IR/Dominance.h"
20#include "mlir/IR/IRMapping.h"
21#include "mlir/IR/Matchers.h"
22#include "mlir/Transforms/RegionUtils.h"
23#include "llvm/ADT/ScopeExit.h"
24#include "llvm/Support/Debug.h"
25#include "llvm/Support/GenericIteratedDominanceFrontier.h"
30#define DEBUG_TYPE "llhd-deseq"
31#define VERBOSE_DEBUG(...) DEBUG_WITH_TYPE(DEBUG_TYPE "-verbose", __VA_ARGS__)
35#define GEN_PASS_DEF_DESEQPASS
36#include "circt/Dialect/LLHD/LLHDPasses.h.inc"
44using llvm::SmallSetVector;
51static Value canonicalizeBlockArg(BlockArgument arg,
52 SmallPtrSetImpl<Block *> &visited) {
53 Block *block = arg.getOwner();
54 if (!visited.insert(block).second)
58 for (
auto *pred : block->getPredecessors()) {
59 auto *term = pred->getTerminator();
63 if (
auto br = dyn_cast<cf::BranchOp>(term)) {
64 if (br.getDest() == block)
65 passedValue = br.getDestOperands()[arg.getArgNumber()];
66 }
else if (
auto condBr = dyn_cast<cf::CondBranchOp>(term)) {
67 if (condBr.getTrueDest() == block)
68 passedValue = condBr.getTrueDestOperands()[arg.getArgNumber()];
69 else if (condBr.getFalseDest() == block)
70 passedValue = condBr.getFalseDestOperands()[arg.getArgNumber()];
71 }
else if (
auto wait = dyn_cast<WaitOp>(term)) {
72 if (wait.getDest() == block)
73 passedValue = wait.getDestOperands()[arg.getArgNumber()];
83 if (
auto passedArg = dyn_cast<BlockArgument>(passedValue))
84 passedValue = canonicalizeBlockArg(passedArg, visited);
88 candidate = passedValue;
89 else if (candidate != passedValue)
93 return candidate ? candidate : arg;
113 Value base = se.getInput();
114 if (
auto arg = dyn_cast<BlockArgument>(base)) {
115 SmallPtrSet<Block *, 4> visited;
116 base = canonicalizeBlockArg(arg, visited);
118 auto baseVF = getValueField(base);
120 auto structType = dyn_cast<hw::StructType>(se.getInput().getType());
122 return {value, 0, value};
124 uint64_t idx = se.getFieldIndex();
126 return {baseVF.value, baseVF.fieldID + childID, value};
131 Value base = ae.getInput();
132 Value index = ae.getIndex();
136 std::optional<uint64_t> idx;
138 idx = cst.getValue().getZExtValue();
141 if (
auto sliceIdx = slice.getLowIndex().getDefiningOp<
hw::ConstantOp>())
143 idx = sliceIdx.getValue().getZExtValue() +
144 getIdx.getValue().getZExtValue();
145 base = slice.getInput();
150 return {value, 0, value};
152 if (
auto arg = dyn_cast<BlockArgument>(base)) {
153 SmallPtrSet<Block *, 4> visited;
154 base = canonicalizeBlockArg(arg, visited);
156 auto baseVF = getValueField(base);
158 if (
auto arrayType = dyn_cast<hw::ArrayType>(base.getType())) {
160 return {baseVF.value, baseVF.fieldID + childID, value};
162 if (
auto arrayType = dyn_cast<hw::UnpackedArrayType>(base.getType())) {
164 return {baseVF.value, baseVF.fieldID + childID, value};
167 return {value, 0, value};
172 Value base = ext.getInput();
173 if (
auto arg = dyn_cast<BlockArgument>(base)) {
174 SmallPtrSet<Block *, 4> visited;
175 base = canonicalizeBlockArg(arg, visited);
177 auto baseVF = getValueField(base);
178 uint64_t lowBit =
static_cast<uint64_t
>(ext.getLowBit());
179 auto intType = dyn_cast<IntegerType>(ext.getType());
181 return {value, 0, value};
182 uint64_t bitWidth = intType.getWidth();
185 if (baseVF.value.getType().isSignlessInteger()) {
186 uint64_t fieldID = baseVF.fieldID ? baseVF.fieldID + lowBit : lowBit + 1;
187 return {baseVF.value, fieldID, value, 0, bitWidth};
192 if (baseVF.fieldID == 0)
193 return {value, 0, value};
194 uint64_t bitID = baseVF.bitID ? baseVF.bitID + lowBit : lowBit + 1;
195 return {baseVF.value, baseVF.fieldID, value, bitID, bitWidth};
199 return {value, 0, value};
204 Deseq(ProcessOp process) : process(process) {}
207 bool analyzeProcess();
208 Value tracePastValue(Value pastValue);
215 TruthTable computeBoolean(BlockArgument value);
217 TruthTable computeBlockCondition(Block *block);
218 TruthTable computeSuccessorCondition(BlockOperand &operand);
219 TruthTable computeSuccessorBoolean(BlockOperand &operand,
unsigned argIdx);
220 ValueTable computeSuccessorValue(BlockOperand &operand,
unsigned argIdx);
225 ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable);
227 matchDriveClockAndReset(
DriveInfo &drive,
228 ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable);
230 Value materializeProjection(OpBuilder &builder, Location loc, Value value,
233 void implementRegisters();
234 void implementRegister(
DriveInfo &drive);
236 Value specializeValue(Value value,
FixedValues fixedValues);
237 ValueRange specializeProcess(
FixedValues fixedValues);
245 SmallSetVector<ValueField, 2> triggers;
249 SmallVector<Value, 2> pastValues;
251 SmallVector<DriveInfo> driveInfos;
261 ConstantTimeOp epsilonDelay;
263 DenseMap<Operation *, bool> staticOps;
266 DenseMap<ValueField, TruthTable> booleanLattice;
269 DenseMap<Value, ValueTable> valueLattice;
273 DenseMap<Block *, TruthTable> blockConditionLattice;
276 DenseMap<BlockOperand *, TruthTable> successorConditionLattice;
279 DenseMap<std::pair<BlockOperand *, unsigned>,
TruthTable>
280 successorBooleanLattice;
283 DenseMap<std::pair<BlockOperand *, unsigned>,
ValueTable>
284 successorValueLattice;
294 TruthTable getConstBoolean(
bool value)
const {
297 TruthTable getPastTrigger(
unsigned triggerIndex)
const {
300 TruthTable getPresentTrigger(
unsigned triggerIndex)
const {
314 return ValueTable(getConstBoolean(
true), value);
324 if (!analyzeProcess())
327 llvm::dbgs() <<
"Desequentializing " << process.getLoc() <<
"\n";
328 llvm::dbgs() <<
"- Feeds " << driveInfos.size() <<
" conditional drives\n";
329 llvm::dbgs() <<
"- " << triggers.size() <<
" potential triggers:\n";
330 for (
auto [index, trigger] :
llvm::enumerate(triggers)) {
331 llvm::dbgs() <<
" - ";
332 trigger.getProjected().printAsOperand(llvm::dbgs(), OpPrintingFlags());
333 llvm::dbgs() <<
": past " << getPastTrigger(index);
334 llvm::dbgs() <<
", present " << getPresentTrigger(index);
335 llvm::dbgs() <<
"\n";
347 implementRegisters();
360bool Deseq::analyzeProcess() {
363 for (
auto &block : process.getBody()) {
364 for (
auto &op : block) {
365 if (isa<WaitOp, HaltOp>(op))
367 if (!isMemoryEffectFree(&op)) {
369 llvm::dbgs() <<
"Skipping " << process.getLoc()
370 <<
": contains side-effecting op ";
371 op.print(llvm::dbgs(), OpPrintingFlags().skipRegions());
372 llvm::dbgs() <<
"\n";
380 for (
auto &block : process.getBody()) {
381 if (
auto candidate = dyn_cast<WaitOp>(block.getTerminator())) {
383 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
384 <<
": has multiple waits\n");
391 LLVM_DEBUG(llvm::dbgs()
392 <<
"Skipping " << process.getLoc() <<
": has no wait\n");
397 SmallPtrSet<Operation *, 8> seenDrives;
398 for (
auto &use : process->getUses()) {
399 auto driveOp = dyn_cast<DriveOp>(use.getOwner());
401 LLVM_DEBUG(llvm::dbgs()
402 <<
"Skipping " << process.getLoc() <<
": feeds non-drive "
403 << use.getOwner()->getLoc() <<
"\n");
406 if (!seenDrives.insert(driveOp).second)
410 if (!driveOp.getEnable()) {
411 LLVM_DEBUG(llvm::dbgs()
412 <<
"Skipping " << process.getLoc()
413 <<
": feeds unconditional drive " << driveOp <<
"\n");
419 if (use.getOperandNumber() != 1 && use.getOperandNumber() != 2) {
420 LLVM_DEBUG(llvm::dbgs()
421 <<
"Skipping " << process.getLoc()
422 <<
": feeds drive operand that is neither value nor enable: "
427 driveInfos.push_back(
DriveInfo(driveOp));
434 bool hasNonI1Observed =
false;
435 for (
auto value : wait.getObserved()) {
436 if (!value.getType().isSignlessInteger(1))
437 hasNonI1Observed =
true;
440 if (!hasNonI1Observed) {
442 for (
auto value : wait.getObserved())
443 triggers.insert(getValueField(value));
447 for (
auto operand : wait.getDestOperands()) {
448 if (!operand.getType().isSignlessInteger(1))
450 auto vf = getValueField(operand);
452 if (vf.fieldID != 0 && llvm::is_contained(wait.getObserved(), vf.value)) {
460 if (triggers.empty() || triggers.size() > 2) {
461 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc() <<
": observes "
462 << triggers.size() <<
" values\n");
467 for (
auto [index, trigger] :
llvm::enumerate(triggers))
468 booleanLattice.insert({trigger, getPresentTrigger(index)});
473 for (
auto [operand, blockArg] :
474 llvm::zip(wait.getDestOperands(), wait.getDest()->getArguments())) {
476 auto operandVF = getValueField(operand);
477 auto it = llvm::find(triggers, operandVF);
478 if (it != triggers.end()) {
479 unsigned index = std::distance(triggers.begin(), it);
480 pastValues.push_back(it->getProjected());
481 booleanLattice.insert({getValueField(blockArg), getPastTrigger(index)});
487 if (!operand.getType().isSignlessInteger(1)) {
488 if (llvm::is_contained(wait.getObserved(), operand))
490 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
491 <<
": uses non-i1 past value\n");
495 auto trigger = tracePastValue(operand);
498 pastValues.push_back(trigger);
499 unsigned index = std::distance(
500 triggers.begin(), llvm::find(triggers, getValueField(trigger)));
501 booleanLattice.insert({getValueField(blockArg), getPastTrigger(index)});
510Value Deseq::tracePastValue(Value pastValue) {
513 SmallVector<Value> worklist;
514 SmallPtrSet<Value, 8> seen;
515 worklist.push_back(pastValue);
516 seen.insert(pastValue);
518 SmallPtrSet<Block *, 2> predSeen;
519 SmallSetVector<BlockOperand *, 4> predWorklist;
520 SmallPtrSet<Value, 2> distinctValues;
521 while (!worklist.empty()) {
522 auto value = worklist.pop_back_val();
523 auto arg = dyn_cast<BlockArgument>(value);
527 if (
auto it = llvm::find(triggers, getValueField(value));
528 it != triggers.end()) {
529 distinctValues.insert(it->getProjected());
533 distinctValues.insert(value);
539 predWorklist.clear();
540 for (
auto *predecessor : arg.getOwner()->getPredecessors())
541 if (predSeen.insert(predecessor).second)
542 for (auto &operand : predecessor->getTerminator()->getBlockOperands())
543 if (operand.
get() == arg.getOwner())
544 predWorklist.insert(&operand);
548 unsigned argIdx = arg.getArgNumber();
549 for (
auto *blockOperand : predWorklist) {
550 auto *op = blockOperand->getOwner();
551 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
553 auto operand = branchOp.getDestOperands()[argIdx];
554 if (seen.insert(operand).second)
555 worklist.push_back(operand);
556 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
558 unsigned destIdx = blockOperand->getOperandNumber();
559 auto operand = destIdx == 0
560 ? condBranchOp.getTrueDestOperands()[argIdx]
561 : condBranchOp.getFalseDestOperands()[argIdx];
565 if ((matchPattern(operand, m_One()) && destIdx == 0) ||
566 (matchPattern(operand, m_Zero()) && destIdx == 1))
567 operand = condBranchOp.getCondition();
569 if (seen.insert(operand).second)
570 worklist.push_back(operand);
572 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
573 <<
": unsupported terminator " << op->getName()
574 <<
" while tracing past value\n");
582 if (distinctValues.size() != 1) {
585 <<
"Skipping " << process.getLoc()
586 <<
": multiple past values passed for the same block argument\n");
589 auto distinctValue = *distinctValues.begin();
590 if (!triggers.contains(getValueField(distinctValue))) {
591 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
592 <<
": unobserved past value\n");
595 return distinctValue;
606TruthTable Deseq::computeBoolean(Value value) {
607 return computeBoolean(getValueField(value));
612 return getUnknownBoolean();
616 if (
auto it = booleanLattice.find(vf); it != booleanLattice.end())
623 return computeBoolean(
625 return getUnknownBoolean();
628 Value value = vf.
value;
629 assert(value.getType().isSignlessInteger(1));
633 if (value.getDefiningOp() == process)
634 return computeBoolean(
635 wait.getYieldOperands()[cast<OpResult>(value).getResultNumber()]);
639 booleanLattice[vf] = getUnknownBoolean();
643 TypeSwitch<Value, TruthTable>(value).Case<OpResult, BlockArgument>(
644 [&](
auto value) {
return computeBoolean(value); });
648 llvm::dbgs() <<
"- Boolean ";
649 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
650 llvm::dbgs() <<
": " << result <<
"\n";
652 booleanLattice[vf] = result;
660 auto vf = getValueField(value);
671 if (value.getDefiningOp() == process)
673 wait.getYieldOperands()[cast<OpResult>(value).getResultNumber()]);
678 if (
auto it = valueLattice.find(value); it != valueLattice.end())
680 valueLattice[value] = getUnknownValue();
684 TypeSwitch<Value, ValueTable>(value).Case<OpResult, BlockArgument>(
685 [&](
auto value) {
return computeValue(value); });
689 llvm::dbgs() <<
"- Value ";
690 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
691 llvm::dbgs() <<
": " << result <<
"\n";
693 valueLattice[value] = result;
698TruthTable Deseq::computeBoolean(OpResult value) {
699 assert(value.getType().isSignlessInteger(1));
700 auto *op = value.getOwner();
703 if (
auto constOp = dyn_cast<hw::ConstantOp>(op))
704 return getConstBoolean(constOp.getValue().isOne());
707 if (
auto orOp = dyn_cast<comb::OrOp>(op)) {
708 auto result = getConstBoolean(
false);
709 for (
auto operand : orOp.getInputs()) {
710 result |= computeBoolean(operand);
718 if (
auto andOp = dyn_cast<comb::AndOp>(op)) {
719 auto result = getConstBoolean(
true);
720 for (
auto operand : andOp.getInputs()) {
721 result &= computeBoolean(operand);
722 if (result.isFalse())
729 if (
auto xorOp = dyn_cast<comb::XorOp>(op)) {
730 auto result = getConstBoolean(
false);
731 for (
auto operand : xorOp.getInputs())
732 result ^= computeBoolean(operand);
739 if (llvm::any_of(op->getOperands(), [&](
auto operand) {
742 if (!operand.getType().isSignlessInteger(1))
744 auto result = computeBoolean(operand);
745 return result.isPoison() || (result != getUnknownBoolean() &&
746 !result.isTrue() && !result.isFalse());
748 return getPoisonBoolean();
749 return getUnknownBoolean();
754ValueTable Deseq::computeValue(OpResult value) {
755 auto *op = value.getOwner();
758 if (isa<comb::MuxOp, arith::SelectOp>(op)) {
759 auto condition = computeBoolean(op->getOperand(0));
760 auto trueValue = computeValue(op->getOperand(1));
761 auto falseValue = computeValue(op->getOperand(2));
762 trueValue.addCondition(condition);
763 falseValue.addCondition(~condition);
764 trueValue.merge(std::move(falseValue));
769 return getKnownValue(value);
773TruthTable Deseq::computeBoolean(BlockArgument arg) {
774 auto *block = arg.getOwner();
777 if (block->getParentOp() != process)
778 return getUnknownBoolean();
782 auto result = getConstBoolean(
false);
783 SmallPtrSet<Block *, 4> seen;
784 for (
auto *predecessor : block->getPredecessors()) {
785 if (!seen.insert(predecessor).second)
787 for (
auto &operand : predecessor->getTerminator()->getBlockOperands()) {
788 if (operand.get() != block)
790 auto value = computeSuccessorBoolean(operand, arg.getArgNumber());
793 auto condition = computeSuccessorCondition(operand);
794 result |= value & condition;
806ValueTable Deseq::computeValue(BlockArgument arg) {
807 auto *block = arg.getOwner();
810 if (block->getParentOp() != process)
811 return getKnownValue(arg);
816 SmallPtrSet<Block *, 4> seen;
817 for (
auto *predecessor : block->getPredecessors()) {
818 if (!seen.insert(predecessor).second)
820 for (
auto &operand : predecessor->getTerminator()->getBlockOperands()) {
821 if (operand.get() != block)
823 auto condition = computeSuccessorCondition(operand);
824 if (condition.isFalse())
826 auto value = computeSuccessorValue(operand, arg.getArgNumber());
827 value.addCondition(condition);
836TruthTable Deseq::computeBlockCondition(Block *block) {
839 if (
auto it = blockConditionLattice.find(block);
840 it != blockConditionLattice.end())
842 blockConditionLattice[block] = getConstBoolean(
false);
846 auto result = getConstBoolean(
false);
847 SmallPtrSet<Block *, 4> seen;
848 for (
auto *predecessor : block->getPredecessors()) {
849 if (!seen.insert(predecessor).second)
851 for (
auto &operand : predecessor->getTerminator()->getBlockOperands()) {
852 if (operand.get() != block)
854 result |= computeSuccessorCondition(operand);
864 llvm::dbgs() <<
"- Block condition ";
865 block->printAsOperand(llvm::dbgs());
866 llvm::dbgs() <<
": " << result <<
"\n";
868 blockConditionLattice[block] = result;
874TruthTable Deseq::computeSuccessorCondition(BlockOperand &blockOperand) {
879 auto *op = blockOperand.getOwner();
881 return getConstBoolean(
true);
885 if (
auto it = successorConditionLattice.find(&blockOperand);
886 it != successorConditionLattice.end())
888 successorConditionLattice[&blockOperand] = getConstBoolean(
false);
892 auto destIdx = blockOperand.getOperandNumber();
893 auto blockCondition = computeBlockCondition(op->getBlock());
894 auto result = getUnknownBoolean();
895 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
896 result = blockCondition;
897 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
898 auto branchCondition = computeBoolean(condBranchOp.getCondition());
900 result = blockCondition & branchCondition;
902 result = blockCondition & ~branchCondition;
904 result = getPoisonBoolean();
909 llvm::dbgs() <<
"- Successor condition ";
910 op->getBlock()->printAsOperand(llvm::dbgs());
911 llvm::dbgs() <<
"#succ" << destIdx <<
" -> ";
912 blockOperand.get()->printAsOperand(llvm::dbgs());
913 llvm::dbgs() <<
" = " << result <<
"\n";
915 successorConditionLattice[&blockOperand] = result;
921TruthTable Deseq::computeSuccessorBoolean(BlockOperand &blockOperand,
925 if (
auto it = successorBooleanLattice.find({&blockOperand, argIdx});
926 it != successorBooleanLattice.end())
928 successorBooleanLattice[{&blockOperand, argIdx}] = getUnknownBoolean();
932 auto *op = blockOperand.getOwner();
933 auto destIdx = blockOperand.getOperandNumber();
934 auto result = getUnknownBoolean();
935 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
936 result = computeBoolean(branchOp.getDestOperands()[argIdx]);
937 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
939 result = computeBoolean(condBranchOp.getTrueDestOperands()[argIdx]);
941 result = computeBoolean(condBranchOp.getFalseDestOperands()[argIdx]);
943 result = getPoisonBoolean();
948 llvm::dbgs() <<
"- Successor boolean ";
949 op->getBlock()->printAsOperand(llvm::dbgs());
950 llvm::dbgs() <<
"#succ" << destIdx <<
" -> ";
951 blockOperand.get()->printAsOperand(llvm::dbgs());
952 llvm::dbgs() <<
"#arg" << argIdx <<
" = " << result <<
"\n";
954 successorBooleanLattice[{&blockOperand, argIdx}] = result;
961ValueTable Deseq::computeSuccessorValue(BlockOperand &blockOperand,
965 if (
auto it = successorValueLattice.find({&blockOperand, argIdx});
966 it != successorValueLattice.end())
968 successorValueLattice[{&blockOperand, argIdx}] = getUnknownValue();
972 auto *op = blockOperand.getOwner();
973 auto destIdx = blockOperand.getOperandNumber();
974 auto result = getUnknownValue();
975 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
976 result = computeValue(branchOp.getDestOperands()[argIdx]);
977 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
979 result = computeValue(condBranchOp.getTrueDestOperands()[argIdx]);
981 result = computeValue(condBranchOp.getFalseDestOperands()[argIdx]);
983 result = getPoisonValue();
988 llvm::dbgs() <<
"- Successor value ";
989 op->getBlock()->printAsOperand(llvm::dbgs());
990 llvm::dbgs() <<
"#succ" << destIdx <<
" -> ";
991 blockOperand.get()->printAsOperand(llvm::dbgs());
992 llvm::dbgs() <<
"#arg" << argIdx <<
" = " << result <<
"\n";
994 successorValueLattice[{&blockOperand, argIdx}] = result;
1005bool Deseq::matchDrives() {
1006 for (
auto &drive : driveInfos)
1007 if (!matchDrive(drive))
1016bool Deseq::matchDrive(
DriveInfo &drive) {
1017 LLVM_DEBUG(llvm::dbgs() <<
"- Analyzing " << drive.
op <<
"\n");
1020 auto condition = computeBoolean(drive.
op.getEnable());
1021 if (condition.isPoison()) {
1022 LLVM_DEBUG(llvm::dbgs()
1023 <<
"- Aborting: poison condition on " << drive.
op <<
"\n");
1028 auto initialValueTable = computeValue(drive.
op.getValue());
1029 initialValueTable.addCondition(condition);
1031 llvm::dbgs() <<
" - Condition: " << condition <<
"\n";
1032 llvm::dbgs() <<
" - Value: " << initialValueTable <<
"\n";
1038 SmallVector<std::pair<DNFTerm, ValueEntry>> valueTable;
1039 for (
auto &[condition, value] : initialValueTable.entries) {
1040 auto dnf = condition.canonicalize();
1041 if (dnf.isPoison() || value.isPoison()) {
1042 LLVM_DEBUG(llvm::dbgs()
1043 <<
"- Aborting: poison in " << initialValueTable <<
"\n");
1046 for (
auto &orTerm : dnf.orTerms)
1047 valueTable.push_back({orTerm, value});
1053 if (valueTable.size() > 3) {
1054 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: value table has "
1055 << valueTable.size() <<
" distinct conditions\n");
1060 if (triggers.size() == 2)
1061 return matchDriveClockAndReset(drive, valueTable);
1064 assert(triggers.size() == 1);
1065 return matchDriveClock(drive, valueTable);
1070bool Deseq::matchDriveClock(
1071 DriveInfo &drive, ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable) {
1074 if (valueTable.size() != 1) {
1075 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: single trigger value table has "
1076 << valueTable.size() <<
" entries\n");
1081 for (
unsigned variant = 0; variant < (1 << 1); ++variant) {
1082 bool negClock = (variant >> 0) & 1;
1090 uint32_t clockEdge = (negClock ? 0b1001 : 0b0110) << 2;
1091 auto clockWithoutEnable =
DNFTerm{clockEdge};
1092 auto clockWithEnable =
DNFTerm{clockEdge | 0b01};
1095 if (valueTable[0].first == clockWithEnable)
1097 else if (valueTable[0].first != clockWithoutEnable)
1101 drive.
clock.
clock = triggers[0].getProjected();
1104 if (!valueTable[0].second.isUnknown())
1105 drive.
clock.
value = valueTable[0].second.value;
1108 llvm::dbgs() <<
" - Matched " << (negClock ?
"neg" :
"pos")
1110 drive.
clock.
clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1111 llvm::dbgs() <<
" -> " << valueTable[0].second;
1113 llvm::dbgs() <<
" (with enable)";
1114 llvm::dbgs() <<
"\n";
1120 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: unknown clock scheme\n");
1127bool Deseq::matchDriveClockAndReset(
1128 DriveInfo &drive, ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable) {
1131 if (valueTable.size() != 3) {
1132 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: two trigger value table has "
1133 << valueTable.size() <<
" entries\n");
1140 for (
unsigned variant = 0; variant < (1 << 3); ++variant) {
1141 bool negClock = (variant >> 0) & 1;
1142 bool negReset = (variant >> 1) & 1;
1143 unsigned clockIdx = (variant >> 2) & 1;
1144 unsigned resetIdx = 1 - clockIdx;
1151 uint32_t clockEdge = (negClock ? 0b1001 : 0b0110) << (clockIdx * 4 + 2);
1152 uint32_t resetEdge = (negReset ? 0b1001 : 0b0110) << (resetIdx * 4 + 2);
1153 uint32_t resetOn = (negReset ? 0b1000 : 0b0100) << (resetIdx * 4 + 2);
1154 uint32_t resetOff = (negReset ? 0b0100 : 0b1000) << (resetIdx * 4 + 2);
1159 auto reset =
DNFTerm{resetEdge};
1160 auto clockWhileReset =
DNFTerm{clockEdge | resetOn};
1161 auto clockWithoutEnable =
DNFTerm{clockEdge | resetOff};
1162 auto clockWithEnable =
DNFTerm{clockEdge | resetOff | 0b01};
1165 auto resetIt = llvm::find_if(
1166 valueTable, [&](
auto &pair) {
return pair.first == reset; });
1167 if (resetIt == valueTable.end())
1170 auto clockWhileResetIt = llvm::find_if(
1171 valueTable, [&](
auto &pair) {
return pair.first == clockWhileReset; });
1172 if (clockWhileResetIt == valueTable.end())
1175 auto clockIt = llvm::find_if(valueTable, [&](
auto &pair) {
1176 return pair.first == clockWithoutEnable || pair.first == clockWithEnable;
1178 if (clockIt == valueTable.end())
1184 if (clockWhileResetIt->second != resetIt->second ||
1185 resetIt->second.isUnknown()) {
1186 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: inconsistent reset value\n");
1191 drive.
reset.
reset = triggers[resetIdx].getProjected();
1195 drive.
clock.
clock = triggers[clockIdx].getProjected();
1197 if (clockIt->first == clockWithEnable)
1200 if (!clockIt->second.isUnknown())
1204 llvm::dbgs() <<
" - Matched " << (negClock ?
"neg" :
"pos")
1206 drive.
clock.
clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1207 llvm::dbgs() <<
" -> " << clockIt->second;
1209 llvm::dbgs() <<
" (with enable)";
1210 llvm::dbgs() <<
"\n";
1211 llvm::dbgs() <<
" - Matched active-" << (negReset ?
"low" :
"high")
1213 drive.
reset.
reset.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1214 llvm::dbgs() <<
" -> " << resetIt->second <<
"\n";
1220 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: unknown reset scheme\n");
1228Value Deseq::materializeProjection(OpBuilder &builder, Location loc,
1235 auto isInThisProcess = [&](Value v) {
1236 if (
auto arg = dyn_cast<BlockArgument>(v)) {
1237 Operation *parentOp = arg.getOwner()->getParentOp();
1240 return parentOp == process.getOperation() ||
1241 parentOp->getParentOfType<ProcessOp>() == process;
1243 if (
auto *defOp = v.getDefiningOp())
1244 return defOp->getParentOfType<ProcessOp>() == process;
1247 if (!isInThisProcess(value))
1250 if (
auto it = cache.find(value); it != cache.end())
1255 if (
auto arg = dyn_cast<BlockArgument>(value)) {
1256 SmallPtrSet<Block *, 4> visited;
1257 Value canon = canonicalizeBlockArg(arg, visited);
1260 auto remat = materializeProjection(builder, loc, canon, cache);
1261 cache.insert({value, remat});
1265 auto *defOp = value.getDefiningOp();
1270 if (
auto ext = dyn_cast<comb::ExtractOp>(defOp)) {
1271 Value input = materializeProjection(builder, loc, ext.getInput(), cache);
1274 cache.insert({value, remat});
1277 if (
auto get = dyn_cast<hw::ArrayGetOp>(defOp)) {
1278 Value input = materializeProjection(builder, loc,
get.getInput(), cache);
1279 Value index = materializeProjection(builder, loc,
get.getIndex(), cache);
1281 cache.insert({value, remat});
1284 if (
auto slice = dyn_cast<hw::ArraySliceOp>(defOp)) {
1285 Value input = materializeProjection(builder, loc, slice.getInput(), cache);
1287 materializeProjection(builder, loc, slice.getLowIndex(), cache);
1290 cache.insert({value, remat});
1293 if (
auto se = dyn_cast<hw::StructExtractOp>(defOp)) {
1294 Value input = materializeProjection(builder, loc, se.getInput(), cache);
1297 cache.insert({value, remat});
1300 if (
auto cst = dyn_cast<hw::ConstantOp>(defOp)) {
1302 builder, loc, cst.getResult().getType(), cst.getValueAttr());
1303 cache.insert({value, remat});
1306 if (
auto cst = dyn_cast<arith::ConstantOp>(defOp)) {
1307 auto *cloned = builder.clone(*defOp);
1308 Value remat = cloned->getResult(cast<OpResult>(value).getResultNumber());
1309 cache.insert({value, remat});
1319void Deseq::implementRegisters() {
1320 for (
auto &drive : driveInfos)
1321 implementRegister(drive);
1331void Deseq::implementRegister(
DriveInfo &drive) {
1332 OpBuilder builder(drive.
op);
1333 auto loc = drive.
op.getLoc();
1341 materializeProjection(builder, loc, drive.
clock.
clock, rematerialized);
1343 auto &clockCast = materializedClockCasts[clockValue];
1345 clockCast = seq::ToClockOp::create(builder, loc, clockValue);
1346 auto clock = clockCast;
1348 auto &clockInv = materializedClockInverters[clock];
1350 clockInv = seq::ClockInverterOp::create(builder, loc, clock);
1360 materializeProjection(builder, loc, drive.
reset.
reset, rematerialized);
1366 auto &inv = materializedInverters[reset];
1369 inv = comb::XorOp::create(builder, loc, reset, one);
1377 if (!resetValue.getParentRegion()->isProperAncestor(&process.getBody())) {
1378 if (
auto *defOp = resetValue.getDefiningOp();
1379 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1380 defOp->moveBefore(process);
1382 resetValue = specializeValue(
1383 drive.
op.getValue(),
1384 FixedValues{{drive.clock.clock, !drive.clock.risingEdge,
1385 !drive.clock.risingEdge},
1386 {drive.reset.reset, !drive.reset.activeHigh,
1387 drive.reset.activeHigh}});
1395 if (enable && !enable.getParentRegion()->isProperAncestor(&process.getBody()))
1396 enable = drive.
op.getEnable();
1402 if (!value.getParentRegion()->isProperAncestor(&process.getBody())) {
1403 if (
auto *defOp = value.getDefiningOp();
1404 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1405 defOp->moveBefore(process);
1407 value = drive.
op.getValue();
1413 fixedValues.push_back(
1416 fixedValues.push_back(
1419 value = specializeValue(value, fixedValues);
1421 enable = specializeValue(enable, fixedValues);
1425 if (
auto sigOp = drive.
op.getSignal().getDefiningOp<llhd::SignalOp>())
1426 name = sigOp.getNameAttr();
1428 name = builder.getStringAttr(
"");
1431 auto reg = seq::FirRegOp::create(builder, loc, value, clock, name,
1433 IntegerAttr{}, reset, resetValue,
1441 OpBuilder::InsertionGuard guard(builder);
1442 builder.setInsertionPoint(reg);
1443 reg.getNextMutable().assign(comb::MuxOp::create(
1444 builder, loc, enable,
reg.getNext(),
reg.getResult(),
true));
1448 drive.
op.getValueMutable().assign(reg);
1449 drive.
op.getEnableMutable().clear();
1454 if (matchPattern(drive.
op.getTime(), m_Constant(&attr)) &&
1455 attr.getTime() == 0 && attr.getDelta() == 1 && attr.getEpsilon() == 0) {
1458 ConstantTimeOp::create(builder, process.getLoc(), 0,
"ns", 0, 1);
1459 drive.
op.getTimeMutable().assign(epsilonDelay);
1472Value Deseq::specializeValue(Value value,
FixedValues fixedValues) {
1473 auto result = dyn_cast<OpResult>(value);
1474 if (!result || result.getOwner() != process)
1476 return specializeProcess(fixedValues)[result.getResultNumber()];
1487ValueRange Deseq::specializeProcess(
FixedValues fixedValues) {
1488 if (
auto it = specializedProcesses.find(fixedValues);
1489 it != specializedProcesses.end())
1493 llvm::dbgs() <<
"- Specializing process for:\n";
1494 for (
auto fixedValue : fixedValues) {
1495 llvm::dbgs() <<
" - ";
1496 fixedValue.value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1497 llvm::dbgs() <<
": " << fixedValue.past <<
" -> " << fixedValue.present
1506 OpBuilder builder(process);
1507 auto executeOp = CombinationalOp::create(builder, process.getLoc(),
1508 process.getResultTypes());
1511 SmallVector<std::pair<Block *, Block *>> worklist;
1513 auto scheduleBlock = [&](
Block *block) {
1514 if (
auto *newBlock = mapping.lookupOrNull(block))
1516 auto *newBlock = &executeOp.getRegion().emplaceBlock();
1517 for (
auto arg : block->getArguments()) {
1518 auto newArg = newBlock->addArgument(arg.getType(), arg.getLoc());
1519 mapping.map(arg, newArg);
1521 mapping.map(block, newBlock);
1522 worklist.push_back({block, newBlock});
1527 auto &entryBlock = executeOp.getRegion().emplaceBlock();
1528 builder.setInsertionPointToStart(&entryBlock);
1529 auto i1 = builder.getI1Type();
1534 for (
auto fixedValue : fixedValues) {
1535 auto present = fixedValue.present ? trueValue : falseValue;
1536 auto past = fixedValue.past ? trueValue : falseValue;
1537 materializedFixedValues.insert({fixedValue.value, {past, present}});
1538 mapping.map(fixedValue.value, present);
1543 auto fixedTable = getConstBoolean(
true);
1544 for (
auto [index, trigger] :
llvm::enumerate(triggers)) {
1545 for (
auto fixedValue : fixedValues) {
1546 if (getValueField(fixedValue.value) != trigger)
1548 auto past = getPastTrigger(index);
1549 fixedTable &= fixedValue.past ? past : ~past;
1550 auto present = getPresentTrigger(index);
1551 fixedTable &= fixedValue.present ? present : ~present;
1558 SmallVector<Value> foldedResults;
1559 while (!worklist.empty()) {
1560 auto [oldBlock, newBlock] = worklist.pop_back_val();
1561 builder.setInsertionPointToEnd(newBlock);
1562 for (
auto &oldOp : *oldBlock) {
1564 if (
auto waitOp = dyn_cast<WaitOp>(oldOp)) {
1567 SmallVector<Value> operands;
1568 for (
auto operand : waitOp.getYieldOperands())
1569 operands.push_back(mapping.lookupOrDefault(operand));
1570 YieldOp::create(builder, waitOp.getLoc(), operands);
1575 if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(oldOp)) {
1576 SmallVector<Value> operands;
1577 auto condition = mapping.lookupOrDefault(condBranchOp.getCondition());
1578 if (matchPattern(condition, m_NonZero())) {
1579 for (
auto operand : condBranchOp.getTrueDestOperands())
1580 operands.push_back(mapping.lookupOrDefault(operand));
1581 cf::BranchOp::create(builder, condBranchOp.getLoc(),
1582 scheduleBlock(condBranchOp.getTrueDest()),
1586 if (matchPattern(condition, m_Zero())) {
1587 for (
auto operand : condBranchOp.getFalseOperands())
1588 operands.push_back(mapping.lookupOrDefault(operand));
1589 cf::BranchOp::create(builder, condBranchOp.getLoc(),
1590 scheduleBlock(condBranchOp.getFalseDest()),
1599 if (oldOp.getNumResults() == 1 &&
1600 oldOp.getResult(0).getType().isSignlessInteger(1)) {
1601 if (
auto it = booleanLattice.find(getValueField(oldOp.getResult(0)));
1602 it != booleanLattice.end()) {
1603 if ((it->second & fixedTable).isFalse()) {
1604 mapping.map(oldOp.getResult(0), falseValue);
1607 if ((it->second & fixedTable) == fixedTable) {
1608 mapping.map(oldOp.getResult(0), trueValue);
1615 for (
auto &blockOperand : oldOp.getBlockOperands())
1616 scheduleBlock(blockOperand.
get());
1617 auto *clonedOp = builder.clone(oldOp, mapping);
1621 if (succeeded(builder.tryFold(clonedOp, foldedResults)) &&
1622 !foldedResults.empty()) {
1623 for (
auto [oldResult, foldedResult] :
1624 llvm::zip(oldOp.getResults(), foldedResults))
1625 mapping.map(oldResult, foldedResult);
1628 foldedResults.clear();
1635 worklist.push_back({&process.getBody().front(), &entryBlock});
1637 builder.setInsertionPointToEnd(mapping.lookup(wait->getBlock()));
1642 for (
auto &block : process.getBody())
1643 mapping.erase(&block);
1649 if (wait.getDest()->hasOneUse()) {
1652 for (
auto [arg, pastValue] :
1653 llvm::zip(wait.getDest()->getArguments(), pastValues))
1654 mapping.map(arg, materializedFixedValues.lookup(pastValue).first);
1657 mapping.map(wait.getDest(), builder.getBlock());
1658 worklist.push_back({wait.getDest(), builder.getBlock()});
1661 auto *dest = scheduleBlock(wait.getDest());
1665 SmallVector<Value> destOperands;
1666 assert(pastValues.size() == wait.getDestOperands().size());
1667 for (
auto pastValue : pastValues)
1668 destOperands.push_back(materializedFixedValues.lookup(pastValue).first);
1669 cf::BranchOp::create(builder, wait.getLoc(), dest, destOperands);
1676 if (isOpTriviallyDead(trueValue))
1678 if (isOpTriviallyDead(falseValue))
1681 specializedProcesses.insert({fixedValues, executeOp.getResults()});
1682 return executeOp.getResults();
1690struct DeseqPass :
public llhd::impl::DeseqPassBase<DeseqPass> {
1691 void runOnOperation()
override;
1695void DeseqPass::runOnOperation() {
1696 SmallVector<ProcessOp> processes(getOperation().getOps<ProcessOp>());
1697 for (
auto process : processes)
1698 Deseq(process).deseq();
assert(baseType &&"element must be base type")
#define VERBOSE_DEBUG(...)
static void cloneBlocks(ArrayRef< Block * > blocks, Region ®ion, Region::iterator before, IRMapping &mapper)
Clone a list of blocks into a region before the given block.
create(array_value, low_index, ret_type)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
uint64_t getFieldID(Type type, uint64_t index)
SmallVector< FixedValue, 2 > FixedValues
A list of i1 values that are fixed to a given value.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Value clock
The value acting as the clock, causing the register to be set to a value in valueTable when triggered...
bool risingEdge
Whether the clock is sensitive to a rising or falling edge.
Value value
The value the register is set to when the clock is triggered.
Value enable
The optional value acting as an enable.
A single AND operation within a DNF.
A drive op and the clock and reset that resulted from trigger analysis.
ClockInfo clock
The clock that triggers a change to the driven value.
ResetInfo reset
The optional reset that triggers a change of the driven value to a fixed reset value.
DriveOp op
The drive operation.
Value value
The value the register is reset to.
Value reset
The value acting as the reset, causing the register to be set to value when triggered.
bool activeHigh
Whether the reset is active when high.
A boolean function expressed as a truth table.
static TruthTable getTerm(unsigned numTerms, unsigned term)
Create a boolean expression consisting of a single term.
static TruthTable getPoison()
static TruthTable getConst(unsigned numTerms, bool value)
Create a boolean expression with a constant true or false value.
static ValueEntry getUnknown()
static ValueEntry getPoison()
Identify a specific subfield (or the whole) of an SSA value using the HW field ID scheme.
Value value
The root SSA value being accessed (e.g. the full struct or array).
uint64_t fieldID
The HW field ID describing which subfield is referenced.
Value getProjected() const
A table of SSA values and the conditions under which they appear.
void merge(const ValueTable &other)