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"
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);
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;
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) {
1132 if (valueTable.size() != 2 && valueTable.size() != 3) {
1133 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: two trigger value table has "
1134 << valueTable.size() <<
" entries\n");
1141 for (
unsigned variant = 0; variant < (1 << 3); ++variant) {
1142 bool negClock = (variant >> 0) & 1;
1143 bool negReset = (variant >> 1) & 1;
1144 unsigned clockIdx = (variant >> 2) & 1;
1145 unsigned resetIdx = 1 - clockIdx;
1152 uint32_t clockEdge = (negClock ? 0b1001 : 0b0110) << (clockIdx * 4 + 2);
1153 uint32_t resetEdge = (negReset ? 0b1001 : 0b0110) << (resetIdx * 4 + 2);
1154 uint32_t resetOn = (negReset ? 0b1000 : 0b0100) << (resetIdx * 4 + 2);
1155 uint32_t resetOff = (negReset ? 0b0100 : 0b1000) << (resetIdx * 4 + 2);
1160 auto reset =
DNFTerm{resetEdge};
1161 auto clockWhileReset =
DNFTerm{clockEdge | resetOn};
1162 auto clockWithoutEnable =
DNFTerm{clockEdge | resetOff};
1163 auto clockWithEnable =
DNFTerm{clockEdge | resetOff | 0b01};
1166 auto resetIt = llvm::find_if(
1167 valueTable, [&](
auto &pair) {
return pair.first == reset; });
1168 if (resetIt == valueTable.end())
1171 auto clockWhileResetIt = llvm::find_if(
1172 valueTable, [&](
auto &pair) {
return pair.first == clockWhileReset; });
1173 if (clockWhileResetIt == valueTable.end())
1176 auto clockIt = llvm::find_if(valueTable, [&](
auto &pair) {
1177 return pair.first == clockWithoutEnable || pair.first == clockWithEnable;
1179 bool clockHolds = clockIt == valueTable.end();
1180 if (clockHolds && valueTable.size() != 2)
1186 if (clockWhileResetIt->second != resetIt->second ||
1187 resetIt->second.isUnknown()) {
1188 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: inconsistent reset value\n");
1193 drive.
reset.
reset = triggers[resetIdx].getProjected();
1197 drive.
clock.
clock = triggers[clockIdx].getProjected();
1203 if (clockIt->first == clockWithEnable)
1205 if (!clockIt->second.isUnknown())
1210 llvm::dbgs() <<
" - Matched " << (negClock ?
"neg" :
"pos")
1212 drive.
clock.
clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1214 llvm::dbgs() <<
" -> hold";
1216 llvm::dbgs() <<
" -> " << clockIt->second;
1218 llvm::dbgs() <<
" (with enable)";
1219 llvm::dbgs() <<
"\n";
1220 llvm::dbgs() <<
" - Matched active-" << (negReset ?
"low" :
"high")
1222 drive.
reset.
reset.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1223 llvm::dbgs() <<
" -> " << resetIt->second <<
"\n";
1229 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: unknown reset scheme\n");
1237Value Deseq::materializeProjection(OpBuilder &builder, Location loc,
1244 auto isInThisProcess = [&](Value v) {
1245 if (
auto arg = dyn_cast<BlockArgument>(v)) {
1246 Operation *parentOp = arg.getOwner()->getParentOp();
1249 return parentOp == process.getOperation() ||
1250 parentOp->getParentOfType<ProcessOp>() == process;
1252 if (
auto *defOp = v.getDefiningOp())
1253 return defOp->getParentOfType<ProcessOp>() == process;
1256 if (!isInThisProcess(value))
1259 if (
auto it = cache.find(value); it != cache.end())
1264 if (
auto arg = dyn_cast<BlockArgument>(value)) {
1265 SmallPtrSet<Block *, 4> visited;
1266 Value canon = canonicalizeBlockArg(arg, visited);
1269 auto remat = materializeProjection(builder, loc, canon, cache);
1270 cache.insert({value, remat});
1274 auto *defOp = value.getDefiningOp();
1279 if (
auto ext = dyn_cast<comb::ExtractOp>(defOp)) {
1280 Value input = materializeProjection(builder, loc, ext.getInput(), cache);
1283 cache.insert({value, remat});
1286 if (
auto get = dyn_cast<hw::ArrayGetOp>(defOp)) {
1287 Value input = materializeProjection(builder, loc,
get.getInput(), cache);
1288 Value index = materializeProjection(builder, loc,
get.getIndex(), cache);
1290 cache.insert({value, remat});
1293 if (
auto slice = dyn_cast<hw::ArraySliceOp>(defOp)) {
1294 Value input = materializeProjection(builder, loc, slice.getInput(), cache);
1296 materializeProjection(builder, loc, slice.getLowIndex(), cache);
1299 cache.insert({value, remat});
1302 if (
auto se = dyn_cast<hw::StructExtractOp>(defOp)) {
1303 Value input = materializeProjection(builder, loc, se.getInput(), cache);
1306 cache.insert({value, remat});
1309 if (
auto cst = dyn_cast<hw::ConstantOp>(defOp)) {
1311 builder, loc, cst.getResult().getType(), cst.getValueAttr());
1312 cache.insert({value, remat});
1315 if (
auto cst = dyn_cast<arith::ConstantOp>(defOp)) {
1316 auto *cloned = builder.clone(*defOp);
1317 Value remat = cloned->getResult(cast<OpResult>(value).getResultNumber());
1318 cache.insert({value, remat});
1328void Deseq::implementRegisters() {
1329 for (
auto &drive : driveInfos)
1330 implementRegister(drive);
1340void Deseq::implementRegister(
DriveInfo &drive) {
1341 OpBuilder builder(drive.
op);
1342 auto loc = drive.
op.getLoc();
1350 materializeProjection(builder, loc, drive.
clock.
clock, rematerialized);
1352 auto &clockCast = materializedClockCasts[clockValue];
1354 clockCast = seq::ToClockOp::create(builder, loc, clockValue);
1355 auto clock = clockCast;
1357 auto &clockInv = materializedClockInverters[clock];
1359 clockInv = seq::ClockInverterOp::create(builder, loc, clock);
1369 materializeProjection(builder, loc, drive.
reset.
reset, rematerialized);
1375 auto &inv = materializedInverters[reset];
1378 inv = comb::XorOp::create(builder, loc, reset, one);
1386 if (!resetValue.getParentRegion()->isProperAncestor(&process.getBody())) {
1387 if (
auto *defOp = resetValue.getDefiningOp();
1388 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1389 defOp->moveBefore(process);
1391 resetValue = specializeValue(
1392 drive.
op.getValue(),
1393 FixedValues{{drive.clock.clock, !drive.clock.risingEdge,
1394 !drive.clock.risingEdge},
1395 {drive.reset.reset, !drive.reset.activeHigh,
1396 drive.reset.activeHigh}});
1404 if (enable && !enable.getParentRegion()->isProperAncestor(&process.getBody()))
1405 enable = drive.
op.getEnable();
1411 if (!value.getParentRegion()->isProperAncestor(&process.getBody())) {
1412 if (
auto *defOp = value.getDefiningOp();
1413 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1414 defOp->moveBefore(process);
1416 value = drive.
op.getValue();
1422 fixedValues.push_back(
1425 fixedValues.push_back(
1428 value = specializeValue(value, fixedValues);
1430 enable = specializeValue(enable, fixedValues);
1434 if (
auto sigOp = drive.
op.getSignal().getDefiningOp<llhd::SignalOp>())
1435 name = sigOp.getNameAttr();
1437 name = builder.getStringAttr(
"");
1440 auto reg = seq::FirRegOp::create(builder, loc, value, clock, name,
1442 IntegerAttr{}, reset, resetValue,
1450 OpBuilder::InsertionGuard guard(builder);
1451 builder.setInsertionPoint(reg);
1452 reg.getNextMutable().assign(comb::MuxOp::create(
1453 builder, loc, enable,
reg.getNext(),
reg.getResult(),
true));
1457 drive.
op.getValueMutable().assign(reg);
1458 drive.
op.getEnableMutable().clear();
1463 if (matchPattern(drive.
op.getTime(), m_Constant(&attr)) &&
1464 attr.getTime() == 0 && attr.getDelta() == 1 && attr.getEpsilon() == 0) {
1467 ConstantTimeOp::create(builder, process.getLoc(), 0,
"ns", 0, 1);
1468 drive.
op.getTimeMutable().assign(epsilonDelay);
1481Value Deseq::specializeValue(Value value,
FixedValues fixedValues) {
1482 auto result = dyn_cast<OpResult>(value);
1483 if (!result || result.getOwner() != process)
1485 return specializeProcess(fixedValues)[result.getResultNumber()];
1496ValueRange Deseq::specializeProcess(
FixedValues fixedValues) {
1497 if (
auto it = specializedProcesses.find(fixedValues);
1498 it != specializedProcesses.end())
1502 llvm::dbgs() <<
"- Specializing process for:\n";
1503 for (
auto fixedValue : fixedValues) {
1504 llvm::dbgs() <<
" - ";
1505 fixedValue.value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1506 llvm::dbgs() <<
": " << fixedValue.past <<
" -> " << fixedValue.present
1515 OpBuilder builder(process);
1516 auto executeOp = CombinationalOp::create(builder, process.getLoc(),
1517 process.getResultTypes());
1520 SmallVector<std::pair<Block *, Block *>> worklist;
1522 auto scheduleBlock = [&](
Block *block) {
1523 if (
auto *newBlock = mapping.lookupOrNull(block))
1525 auto *newBlock = &executeOp.getRegion().emplaceBlock();
1526 for (
auto arg : block->getArguments()) {
1527 auto newArg = newBlock->addArgument(arg.getType(), arg.getLoc());
1528 mapping.map(arg, newArg);
1530 mapping.map(block, newBlock);
1531 worklist.push_back({block, newBlock});
1536 auto &entryBlock = executeOp.getRegion().emplaceBlock();
1537 builder.setInsertionPointToStart(&entryBlock);
1538 auto i1 = builder.getI1Type();
1543 for (
auto fixedValue : fixedValues) {
1544 auto present = fixedValue.present ? trueValue : falseValue;
1545 auto past = fixedValue.past ? trueValue : falseValue;
1546 materializedFixedValues.insert({fixedValue.value, {past, present}});
1547 mapping.map(fixedValue.value, present);
1552 auto fixedTable = getConstBoolean(
true);
1553 for (
auto [index, trigger] :
llvm::enumerate(triggers)) {
1554 for (
auto fixedValue : fixedValues) {
1555 if (getValueField(fixedValue.value) != trigger)
1557 auto past = getPastTrigger(index);
1558 fixedTable &= fixedValue.past ? past : ~past;
1559 auto present = getPresentTrigger(index);
1560 fixedTable &= fixedValue.present ? present : ~present;
1567 SmallVector<Value> foldedResults;
1568 while (!worklist.empty()) {
1569 auto [oldBlock, newBlock] = worklist.pop_back_val();
1570 builder.setInsertionPointToEnd(newBlock);
1571 for (
auto &oldOp : *oldBlock) {
1573 if (
auto waitOp = dyn_cast<WaitOp>(oldOp)) {
1576 SmallVector<Value> operands;
1577 for (
auto operand : waitOp.getYieldOperands())
1578 operands.push_back(mapping.lookupOrDefault(operand));
1579 YieldOp::create(builder, waitOp.getLoc(), operands);
1584 if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(oldOp)) {
1585 SmallVector<Value> operands;
1586 auto condition = mapping.lookupOrDefault(condBranchOp.getCondition());
1587 if (matchPattern(condition, m_NonZero())) {
1588 for (
auto operand : condBranchOp.getTrueDestOperands())
1589 operands.push_back(mapping.lookupOrDefault(operand));
1590 cf::BranchOp::create(builder, condBranchOp.getLoc(),
1591 scheduleBlock(condBranchOp.getTrueDest()),
1595 if (matchPattern(condition, m_Zero())) {
1596 for (
auto operand : condBranchOp.getFalseOperands())
1597 operands.push_back(mapping.lookupOrDefault(operand));
1598 cf::BranchOp::create(builder, condBranchOp.getLoc(),
1599 scheduleBlock(condBranchOp.getFalseDest()),
1608 if (oldOp.getNumResults() == 1 &&
1609 oldOp.getResult(0).getType().isSignlessInteger(1)) {
1610 if (
auto it = booleanLattice.find(getValueField(oldOp.getResult(0)));
1611 it != booleanLattice.end()) {
1612 if ((it->second & fixedTable).isFalse()) {
1613 mapping.map(oldOp.getResult(0), falseValue);
1616 if ((it->second & fixedTable) == fixedTable) {
1617 mapping.map(oldOp.getResult(0), trueValue);
1624 for (
auto &blockOperand : oldOp.getBlockOperands())
1625 scheduleBlock(blockOperand.
get());
1626 auto *clonedOp = builder.clone(oldOp, mapping);
1630 if (succeeded(builder.tryFold(clonedOp, foldedResults)) &&
1631 !foldedResults.empty()) {
1632 for (
auto [oldResult, foldedResult] :
1633 llvm::zip(oldOp.getResults(), foldedResults))
1634 mapping.map(oldResult, foldedResult);
1637 foldedResults.clear();
1644 worklist.push_back({&process.getBody().front(), &entryBlock});
1646 builder.setInsertionPointToEnd(mapping.lookup(wait->getBlock()));
1651 for (
auto &block : process.getBody())
1652 mapping.erase(&block);
1658 if (wait.getDest()->hasOneUse()) {
1661 for (
auto [arg, pastValue] :
1662 llvm::zip(wait.getDest()->getArguments(), pastValues))
1663 mapping.map(arg, materializedFixedValues.lookup(pastValue).first);
1666 mapping.map(wait.getDest(), builder.getBlock());
1667 worklist.push_back({wait.getDest(), builder.getBlock()});
1670 auto *dest = scheduleBlock(wait.getDest());
1674 SmallVector<Value> destOperands;
1675 assert(pastValues.size() == wait.getDestOperands().size());
1676 for (
auto pastValue : pastValues)
1677 destOperands.push_back(materializedFixedValues.lookup(pastValue).first);
1678 cf::BranchOp::create(builder, wait.getLoc(), dest, destOperands);
1685 if (isOpTriviallyDead(trueValue))
1687 if (isOpTriviallyDead(falseValue))
1690 specializedProcesses.insert({fixedValues, executeOp.getResults()});
1691 return executeOp.getResults();
1699struct DeseqPass :
public llhd::impl::DeseqPassBase<DeseqPass> {
1700 void runOnOperation()
override;
1704void DeseqPass::runOnOperation() {
1705 SmallVector<ProcessOp> processes(getOperation().getOps<ProcessOp>());
1706 for (
auto process : processes)
1707 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)