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");
407 if (!driveOp.getEnable()) {
408 LLVM_DEBUG(llvm::dbgs()
409 <<
"Skipping " << process.getLoc()
410 <<
": feeds unconditional drive " << driveOp <<
"\n");
417 if (use.getOperandNumber() != 1 && use.getOperandNumber() != 3) {
418 LLVM_DEBUG(llvm::dbgs()
419 <<
"Skipping " << process.getLoc()
420 <<
": feeds drive operand that is neither value nor enable: "
425 if (!seenDrives.insert(driveOp).second)
428 driveInfos.push_back(
DriveInfo(driveOp));
435 bool hasNonI1Observed =
false;
436 for (
auto value : wait.getObserved()) {
437 if (!value.getType().isSignlessInteger(1))
438 hasNonI1Observed =
true;
441 if (!hasNonI1Observed) {
443 for (
auto value : wait.getObserved())
444 triggers.insert(getValueField(value));
448 for (
auto operand : wait.getDestOperands()) {
449 if (!operand.getType().isSignlessInteger(1))
451 auto vf = getValueField(operand);
453 if (vf.fieldID != 0 && llvm::is_contained(wait.getObserved(), vf.value)) {
461 if (triggers.empty() || triggers.size() > 2) {
462 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc() <<
": observes "
463 << triggers.size() <<
" values\n");
468 for (
auto [index, trigger] :
llvm::enumerate(triggers))
469 booleanLattice.insert({trigger, getPresentTrigger(index)});
474 for (
auto [operand, blockArg] :
475 llvm::zip(wait.getDestOperands(), wait.getDest()->getArguments())) {
477 auto operandVF = getValueField(operand);
478 auto it = llvm::find(triggers, operandVF);
479 if (it != triggers.end()) {
480 unsigned index = std::distance(triggers.begin(), it);
481 pastValues.push_back(it->getProjected());
482 booleanLattice.insert({getValueField(blockArg), getPastTrigger(index)});
488 if (!operand.getType().isSignlessInteger(1)) {
489 if (llvm::is_contained(wait.getObserved(), operand))
491 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
492 <<
": uses non-i1 past value\n");
496 auto trigger = tracePastValue(operand);
499 pastValues.push_back(trigger);
500 unsigned index = std::distance(
501 triggers.begin(), llvm::find(triggers, getValueField(trigger)));
502 booleanLattice.insert({getValueField(blockArg), getPastTrigger(index)});
511Value Deseq::tracePastValue(Value pastValue) {
514 SmallVector<Value> worklist;
515 SmallPtrSet<Value, 8> seen;
516 worklist.push_back(pastValue);
517 seen.insert(pastValue);
519 SmallPtrSet<Block *, 2> predSeen;
521 SmallPtrSet<Value, 2> distinctValues;
522 while (!worklist.empty()) {
523 auto value = worklist.pop_back_val();
524 auto arg = dyn_cast<BlockArgument>(value);
528 if (
auto it = llvm::find(triggers, getValueField(value));
529 it != triggers.end()) {
530 distinctValues.insert(it->getProjected());
534 distinctValues.insert(value);
540 predWorklist.clear();
541 for (
auto *predecessor : arg.getOwner()->getPredecessors())
542 if (predSeen.insert(predecessor).second)
543 for (auto &operand : predecessor->getTerminator()->getBlockOperands())
544 if (operand.
get() == arg.getOwner())
545 predWorklist.insert(&operand);
549 unsigned argIdx = arg.getArgNumber();
550 for (
auto *blockOperand : predWorklist) {
551 auto *op = blockOperand->getOwner();
552 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
554 auto operand = branchOp.getDestOperands()[argIdx];
555 if (seen.insert(operand).second)
556 worklist.push_back(operand);
557 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
559 unsigned destIdx = blockOperand->getOperandNumber();
560 auto operand = destIdx == 0
561 ? condBranchOp.getTrueDestOperands()[argIdx]
562 : condBranchOp.getFalseDestOperands()[argIdx];
566 if ((matchPattern(operand, m_One()) && destIdx == 0) ||
567 (matchPattern(operand, m_Zero()) && destIdx == 1))
568 operand = condBranchOp.getCondition();
570 if (seen.insert(operand).second)
571 worklist.push_back(operand);
573 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
574 <<
": unsupported terminator " << op->getName()
575 <<
" while tracing past value\n");
583 if (distinctValues.size() != 1) {
586 <<
"Skipping " << process.getLoc()
587 <<
": multiple past values passed for the same block argument\n");
590 auto distinctValue = *distinctValues.begin();
591 if (!triggers.contains(getValueField(distinctValue))) {
592 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
593 <<
": unobserved past value\n");
596 return distinctValue;
607TruthTable Deseq::computeBoolean(Value value) {
608 return computeBoolean(getValueField(value));
613 return getUnknownBoolean();
617 if (
auto it = booleanLattice.find(vf); it != booleanLattice.end())
624 return computeBoolean(
626 return getUnknownBoolean();
629 Value value = vf.
value;
630 assert(value.getType().isSignlessInteger(1));
634 if (value.getDefiningOp() == process)
635 return computeBoolean(
636 wait.getYieldOperands()[cast<OpResult>(value).getResultNumber()]);
640 booleanLattice[vf] = getUnknownBoolean();
644 TypeSwitch<Value, TruthTable>(value).Case<OpResult, BlockArgument>(
645 [&](
auto value) {
return computeBoolean(value); });
649 llvm::dbgs() <<
"- Boolean ";
650 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
651 llvm::dbgs() <<
": " << result <<
"\n";
653 booleanLattice[vf] = result;
661 auto vf = getValueField(value);
672 if (value.getDefiningOp() == process)
674 wait.getYieldOperands()[cast<OpResult>(value).getResultNumber()]);
679 if (
auto it = valueLattice.find(value); it != valueLattice.end())
681 valueLattice[value] = getUnknownValue();
685 TypeSwitch<Value, ValueTable>(value).Case<OpResult, BlockArgument>(
686 [&](
auto value) {
return computeValue(value); });
690 llvm::dbgs() <<
"- Value ";
691 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
692 llvm::dbgs() <<
": " << result <<
"\n";
694 valueLattice[value] = result;
699TruthTable Deseq::computeBoolean(OpResult value) {
700 assert(value.getType().isSignlessInteger(1));
701 auto *op = value.getOwner();
704 if (
auto constOp = dyn_cast<hw::ConstantOp>(op))
705 return getConstBoolean(constOp.getValue().isOne());
708 if (
auto orOp = dyn_cast<comb::OrOp>(op)) {
709 auto result = getConstBoolean(
false);
710 for (
auto operand : orOp.getInputs()) {
711 result |= computeBoolean(operand);
719 if (
auto andOp = dyn_cast<comb::AndOp>(op)) {
720 auto result = getConstBoolean(
true);
721 for (
auto operand : andOp.getInputs()) {
722 result &= computeBoolean(operand);
723 if (result.isFalse())
730 if (
auto xorOp = dyn_cast<comb::XorOp>(op)) {
731 auto result = getConstBoolean(
false);
732 for (
auto operand : xorOp.getInputs())
733 result ^= computeBoolean(operand);
740 if (llvm::any_of(op->getOperands(), [&](
auto operand) {
743 if (!operand.getType().isSignlessInteger(1))
745 auto result = computeBoolean(operand);
746 return result.isPoison() || (result != getUnknownBoolean() &&
747 !result.isTrue() && !result.isFalse());
749 return getPoisonBoolean();
750 return getUnknownBoolean();
755ValueTable Deseq::computeValue(OpResult value) {
756 auto *op = value.getOwner();
759 if (isa<comb::MuxOp, arith::SelectOp>(op)) {
760 auto condition = computeBoolean(op->getOperand(0));
761 auto trueValue = computeValue(op->getOperand(1));
762 auto falseValue = computeValue(op->getOperand(2));
763 trueValue.addCondition(condition);
764 falseValue.addCondition(~condition);
765 trueValue.merge(std::move(falseValue));
770 return getKnownValue(value);
774TruthTable Deseq::computeBoolean(BlockArgument arg) {
775 auto *block = arg.getOwner();
778 if (block->getParentOp() != process)
779 return getUnknownBoolean();
783 auto result = getConstBoolean(
false);
784 SmallPtrSet<Block *, 4> seen;
785 for (
auto *predecessor : block->getPredecessors()) {
786 if (!seen.insert(predecessor).second)
788 for (
auto &operand : predecessor->getTerminator()->getBlockOperands()) {
789 if (operand.get() != block)
791 auto value = computeSuccessorBoolean(operand, arg.getArgNumber());
794 auto condition = computeSuccessorCondition(operand);
795 result |= value & condition;
807ValueTable Deseq::computeValue(BlockArgument arg) {
808 auto *block = arg.getOwner();
811 if (block->getParentOp() != process)
812 return getKnownValue(arg);
817 SmallPtrSet<Block *, 4> seen;
818 for (
auto *predecessor : block->getPredecessors()) {
819 if (!seen.insert(predecessor).second)
821 for (
auto &operand : predecessor->getTerminator()->getBlockOperands()) {
822 if (operand.get() != block)
824 auto condition = computeSuccessorCondition(operand);
825 if (condition.isFalse())
827 auto value = computeSuccessorValue(operand, arg.getArgNumber());
828 value.addCondition(condition);
837TruthTable Deseq::computeBlockCondition(Block *block) {
840 if (
auto it = blockConditionLattice.find(block);
841 it != blockConditionLattice.end())
843 blockConditionLattice[block] = getConstBoolean(
false);
847 auto result = getConstBoolean(
false);
848 SmallPtrSet<Block *, 4> seen;
849 for (
auto *predecessor : block->getPredecessors()) {
850 if (!seen.insert(predecessor).second)
852 for (
auto &operand : predecessor->getTerminator()->getBlockOperands()) {
853 if (operand.get() != block)
855 result |= computeSuccessorCondition(operand);
865 llvm::dbgs() <<
"- Block condition ";
866 block->printAsOperand(llvm::dbgs());
867 llvm::dbgs() <<
": " << result <<
"\n";
869 blockConditionLattice[block] = result;
875TruthTable Deseq::computeSuccessorCondition(BlockOperand &blockOperand) {
880 auto *op = blockOperand.getOwner();
882 return getConstBoolean(
true);
886 if (
auto it = successorConditionLattice.find(&blockOperand);
887 it != successorConditionLattice.end())
889 successorConditionLattice[&blockOperand] = getConstBoolean(
false);
893 auto destIdx = blockOperand.getOperandNumber();
894 auto blockCondition = computeBlockCondition(op->getBlock());
895 auto result = getUnknownBoolean();
896 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
897 result = blockCondition;
898 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
899 auto branchCondition = computeBoolean(condBranchOp.getCondition());
901 result = blockCondition & branchCondition;
903 result = blockCondition & ~branchCondition;
905 result = getPoisonBoolean();
910 llvm::dbgs() <<
"- Successor condition ";
911 op->getBlock()->printAsOperand(llvm::dbgs());
912 llvm::dbgs() <<
"#succ" << destIdx <<
" -> ";
913 blockOperand.get()->printAsOperand(llvm::dbgs());
914 llvm::dbgs() <<
" = " << result <<
"\n";
916 successorConditionLattice[&blockOperand] = result;
922TruthTable Deseq::computeSuccessorBoolean(BlockOperand &blockOperand,
926 if (
auto it = successorBooleanLattice.find({&blockOperand, argIdx});
927 it != successorBooleanLattice.end())
929 successorBooleanLattice[{&blockOperand, argIdx}] = getUnknownBoolean();
933 auto *op = blockOperand.getOwner();
934 auto destIdx = blockOperand.getOperandNumber();
935 auto result = getUnknownBoolean();
936 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
937 result = computeBoolean(branchOp.getDestOperands()[argIdx]);
938 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
940 result = computeBoolean(condBranchOp.getTrueDestOperands()[argIdx]);
942 result = computeBoolean(condBranchOp.getFalseDestOperands()[argIdx]);
944 result = getPoisonBoolean();
949 llvm::dbgs() <<
"- Successor boolean ";
950 op->getBlock()->printAsOperand(llvm::dbgs());
951 llvm::dbgs() <<
"#succ" << destIdx <<
" -> ";
952 blockOperand.get()->printAsOperand(llvm::dbgs());
953 llvm::dbgs() <<
"#arg" << argIdx <<
" = " << result <<
"\n";
955 successorBooleanLattice[{&blockOperand, argIdx}] = result;
962ValueTable Deseq::computeSuccessorValue(BlockOperand &blockOperand,
966 if (
auto it = successorValueLattice.find({&blockOperand, argIdx});
967 it != successorValueLattice.end())
969 successorValueLattice[{&blockOperand, argIdx}] = getUnknownValue();
973 auto *op = blockOperand.getOwner();
974 auto destIdx = blockOperand.getOperandNumber();
975 auto result = getUnknownValue();
976 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
977 result = computeValue(branchOp.getDestOperands()[argIdx]);
978 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
980 result = computeValue(condBranchOp.getTrueDestOperands()[argIdx]);
982 result = computeValue(condBranchOp.getFalseDestOperands()[argIdx]);
984 result = getPoisonValue();
989 llvm::dbgs() <<
"- Successor value ";
990 op->getBlock()->printAsOperand(llvm::dbgs());
991 llvm::dbgs() <<
"#succ" << destIdx <<
" -> ";
992 blockOperand.get()->printAsOperand(llvm::dbgs());
993 llvm::dbgs() <<
"#arg" << argIdx <<
" = " << result <<
"\n";
995 successorValueLattice[{&blockOperand, argIdx}] = result;
1006bool Deseq::matchDrives() {
1007 for (
auto &drive : driveInfos)
1008 if (!matchDrive(drive))
1017bool Deseq::matchDrive(
DriveInfo &drive) {
1018 LLVM_DEBUG(llvm::dbgs() <<
"- Analyzing " << drive.
op <<
"\n");
1021 auto condition = computeBoolean(drive.
op.getEnable());
1022 if (condition.isPoison()) {
1023 LLVM_DEBUG(llvm::dbgs()
1024 <<
"- Aborting: poison condition on " << drive.
op <<
"\n");
1029 auto initialValueTable = computeValue(drive.
op.getValue());
1030 initialValueTable.addCondition(condition);
1032 llvm::dbgs() <<
" - Condition: " << condition <<
"\n";
1033 llvm::dbgs() <<
" - Value: " << initialValueTable <<
"\n";
1039 SmallVector<std::pair<DNFTerm, ValueEntry>> valueTable;
1040 for (
auto &[condition, value] : initialValueTable.entries) {
1041 auto dnf = condition.canonicalize();
1042 if (dnf.isPoison() || value.isPoison()) {
1043 LLVM_DEBUG(llvm::dbgs()
1044 <<
"- Aborting: poison in " << initialValueTable <<
"\n");
1047 for (
auto &orTerm : dnf.orTerms)
1048 valueTable.push_back({orTerm, value});
1054 if (valueTable.size() > 3) {
1055 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: value table has "
1056 << valueTable.size() <<
" distinct conditions\n");
1061 if (triggers.size() == 2)
1062 return matchDriveClockAndReset(drive, valueTable);
1065 assert(triggers.size() == 1);
1066 return matchDriveClock(drive, valueTable);
1071bool Deseq::matchDriveClock(
1072 DriveInfo &drive, ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable) {
1075 if (valueTable.size() != 1) {
1076 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: single trigger value table has "
1077 << valueTable.size() <<
" entries\n");
1082 for (
unsigned variant = 0; variant < (1 << 1); ++variant) {
1083 bool negClock = (variant >> 0) & 1;
1091 uint32_t clockEdge = (negClock ? 0b1001 : 0b0110) << 2;
1092 auto clockWithoutEnable =
DNFTerm{clockEdge};
1093 auto clockWithEnable =
DNFTerm{clockEdge | 0b01};
1096 if (valueTable[0].first == clockWithEnable)
1098 else if (valueTable[0].first != clockWithoutEnable)
1102 drive.
clock.
clock = triggers[0].getProjected();
1105 if (!valueTable[0].second.isUnknown())
1106 drive.
clock.
value = valueTable[0].second.value;
1109 llvm::dbgs() <<
" - Matched " << (negClock ?
"neg" :
"pos")
1111 drive.
clock.
clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1112 llvm::dbgs() <<
" -> " << valueTable[0].second;
1114 llvm::dbgs() <<
" (with enable)";
1115 llvm::dbgs() <<
"\n";
1121 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: unknown clock scheme\n");
1128bool Deseq::matchDriveClockAndReset(
1129 DriveInfo &drive, ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable) {
1133 if (valueTable.size() != 2 && valueTable.size() != 3) {
1134 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: two trigger value table has "
1135 << valueTable.size() <<
" entries\n");
1142 for (
unsigned variant = 0; variant < (1 << 3); ++variant) {
1143 bool negClock = (variant >> 0) & 1;
1144 bool negReset = (variant >> 1) & 1;
1145 unsigned clockIdx = (variant >> 2) & 1;
1146 unsigned resetIdx = 1 - clockIdx;
1153 uint32_t clockEdge = (negClock ? 0b1001 : 0b0110) << (clockIdx * 4 + 2);
1154 uint32_t resetEdge = (negReset ? 0b1001 : 0b0110) << (resetIdx * 4 + 2);
1155 uint32_t resetOn = (negReset ? 0b1000 : 0b0100) << (resetIdx * 4 + 2);
1156 uint32_t resetOff = (negReset ? 0b0100 : 0b1000) << (resetIdx * 4 + 2);
1161 auto reset =
DNFTerm{resetEdge};
1162 auto clockWhileReset =
DNFTerm{clockEdge | resetOn};
1163 auto clockWithoutEnable =
DNFTerm{clockEdge | resetOff};
1164 auto clockWithEnable =
DNFTerm{clockEdge | resetOff | 0b01};
1167 auto resetIt = llvm::find_if(
1168 valueTable, [&](
auto &pair) {
return pair.first == reset; });
1169 if (resetIt == valueTable.end())
1172 auto clockWhileResetIt = llvm::find_if(
1173 valueTable, [&](
auto &pair) {
return pair.first == clockWhileReset; });
1174 if (clockWhileResetIt == valueTable.end())
1177 auto clockIt = llvm::find_if(valueTable, [&](
auto &pair) {
1178 return pair.first == clockWithoutEnable || pair.first == clockWithEnable;
1180 bool clockHolds = clockIt == valueTable.end();
1181 if (clockHolds && valueTable.size() != 2)
1187 if (clockWhileResetIt->second != resetIt->second ||
1188 resetIt->second.isUnknown()) {
1189 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: inconsistent reset value\n");
1194 drive.
reset.
reset = triggers[resetIdx].getProjected();
1198 drive.
clock.
clock = triggers[clockIdx].getProjected();
1204 if (clockIt->first == clockWithEnable)
1206 if (!clockIt->second.isUnknown())
1211 llvm::dbgs() <<
" - Matched " << (negClock ?
"neg" :
"pos")
1213 drive.
clock.
clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1215 llvm::dbgs() <<
" -> hold";
1217 llvm::dbgs() <<
" -> " << clockIt->second;
1219 llvm::dbgs() <<
" (with enable)";
1220 llvm::dbgs() <<
"\n";
1221 llvm::dbgs() <<
" - Matched active-" << (negReset ?
"low" :
"high")
1223 drive.
reset.
reset.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1224 llvm::dbgs() <<
" -> " << resetIt->second <<
"\n";
1230 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: unknown reset scheme\n");
1238Value Deseq::materializeProjection(OpBuilder &builder, Location loc,
1245 auto isInThisProcess = [&](Value v) {
1246 if (
auto arg = dyn_cast<BlockArgument>(v)) {
1247 Operation *parentOp = arg.getOwner()->getParentOp();
1250 return parentOp == process.getOperation() ||
1251 parentOp->getParentOfType<ProcessOp>() == process;
1253 if (
auto *defOp = v.getDefiningOp())
1254 return defOp->getParentOfType<ProcessOp>() == process;
1257 if (!isInThisProcess(value))
1260 if (
auto it = cache.find(value); it != cache.end())
1265 if (
auto arg = dyn_cast<BlockArgument>(value)) {
1266 SmallPtrSet<Block *, 4> visited;
1267 Value canon = canonicalizeBlockArg(arg, visited);
1270 auto remat = materializeProjection(builder, loc, canon, cache);
1271 cache.insert({value, remat});
1275 auto *defOp = value.getDefiningOp();
1280 if (
auto ext = dyn_cast<comb::ExtractOp>(defOp)) {
1281 Value input = materializeProjection(builder, loc, ext.getInput(), cache);
1284 cache.insert({value, remat});
1287 if (
auto get = dyn_cast<hw::ArrayGetOp>(defOp)) {
1288 Value input = materializeProjection(builder, loc,
get.getInput(), cache);
1289 Value index = materializeProjection(builder, loc,
get.getIndex(), cache);
1291 cache.insert({value, remat});
1294 if (
auto slice = dyn_cast<hw::ArraySliceOp>(defOp)) {
1295 Value input = materializeProjection(builder, loc, slice.getInput(), cache);
1297 materializeProjection(builder, loc, slice.getLowIndex(), cache);
1300 cache.insert({value, remat});
1303 if (
auto se = dyn_cast<hw::StructExtractOp>(defOp)) {
1304 Value input = materializeProjection(builder, loc, se.getInput(), cache);
1307 cache.insert({value, remat});
1310 if (
auto cst = dyn_cast<hw::ConstantOp>(defOp)) {
1312 builder, loc, cst.getResult().getType(), cst.getValueAttr());
1313 cache.insert({value, remat});
1316 if (
auto cst = dyn_cast<arith::ConstantOp>(defOp)) {
1317 auto *cloned = builder.clone(*defOp);
1318 Value remat = cloned->getResult(cast<OpResult>(value).getResultNumber());
1319 cache.insert({value, remat});
1329void Deseq::implementRegisters() {
1330 for (
auto &drive : driveInfos)
1331 implementRegister(drive);
1341void Deseq::implementRegister(
DriveInfo &drive) {
1342 OpBuilder builder(drive.
op);
1343 auto loc = drive.
op.getLoc();
1351 materializeProjection(builder, loc, drive.
clock.
clock, rematerialized);
1353 auto &clockCast = materializedClockCasts[clockValue];
1355 clockCast = seq::ToClockOp::create(builder, loc, clockValue);
1356 auto clock = clockCast;
1358 auto &clockInv = materializedClockInverters[clock];
1360 clockInv = seq::ClockInverterOp::create(builder, loc, clock);
1370 materializeProjection(builder, loc, drive.
reset.
reset, rematerialized);
1376 auto &inv = materializedInverters[reset];
1379 inv = comb::XorOp::create(builder, loc, reset, one);
1387 if (!resetValue.getParentRegion()->isProperAncestor(&process.getBody())) {
1388 if (
auto *defOp = resetValue.getDefiningOp();
1389 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1390 defOp->moveBefore(process);
1392 resetValue = specializeValue(
1393 drive.
op.getValue(),
1394 FixedValues{{drive.clock.clock, !drive.clock.risingEdge,
1395 !drive.clock.risingEdge},
1396 {drive.reset.reset, !drive.reset.activeHigh,
1397 drive.reset.activeHigh}});
1405 if (enable && !enable.getParentRegion()->isProperAncestor(&process.getBody()))
1406 enable = drive.
op.getEnable();
1412 if (!value.getParentRegion()->isProperAncestor(&process.getBody())) {
1413 if (
auto *defOp = value.getDefiningOp();
1414 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1415 defOp->moveBefore(process);
1417 value = drive.
op.getValue();
1423 fixedValues.push_back(
1426 fixedValues.push_back(
1429 value = specializeValue(value, fixedValues);
1431 enable = specializeValue(enable, fixedValues);
1435 if (
auto sigOp = drive.
op.getSignal().getDefiningOp<llhd::SignalOp>())
1436 name = sigOp.getNameAttr();
1438 name = builder.getStringAttr(
"");
1441 auto reg = seq::FirRegOp::create(builder, loc, value, clock, name,
1443 IntegerAttr{}, reset, resetValue,
1451 OpBuilder::InsertionGuard guard(builder);
1452 builder.setInsertionPoint(reg);
1453 reg.getNextMutable().assign(comb::MuxOp::create(
1454 builder, loc, enable,
reg.getNext(),
reg.getResult(),
true));
1458 drive.
op.getValueMutable().assign(reg);
1459 drive.
op.getEnableMutable().clear();
1464 if (matchPattern(drive.
op.getTime(), m_Constant(&attr)) &&
1465 attr.getTime() == 0 && attr.getDelta() == 1 && attr.getEpsilon() == 0) {
1468 ConstantTimeOp::create(builder, process.getLoc(), 0,
"ns", 0, 1);
1469 drive.
op.getTimeMutable().assign(epsilonDelay);
1482Value Deseq::specializeValue(Value value,
FixedValues fixedValues) {
1483 auto result = dyn_cast<OpResult>(value);
1484 if (!result || result.getOwner() != process)
1486 return specializeProcess(fixedValues)[result.getResultNumber()];
1497ValueRange Deseq::specializeProcess(
FixedValues fixedValues) {
1498 if (
auto it = specializedProcesses.find(fixedValues);
1499 it != specializedProcesses.end())
1503 llvm::dbgs() <<
"- Specializing process for:\n";
1504 for (
auto fixedValue : fixedValues) {
1505 llvm::dbgs() <<
" - ";
1506 fixedValue.value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1507 llvm::dbgs() <<
": " << fixedValue.past <<
" -> " << fixedValue.present
1516 OpBuilder builder(process);
1517 auto executeOp = CombinationalOp::create(builder, process.getLoc(),
1518 process.getResultTypes());
1521 SmallVector<std::pair<Block *, Block *>> worklist;
1523 auto scheduleBlock = [&](
Block *block) {
1524 if (
auto *newBlock = mapping.lookupOrNull(block))
1526 auto *newBlock = &executeOp.getRegion().emplaceBlock();
1527 for (
auto arg : block->getArguments()) {
1528 auto newArg = newBlock->addArgument(arg.getType(), arg.getLoc());
1529 mapping.map(arg, newArg);
1531 mapping.map(block, newBlock);
1532 worklist.push_back({block, newBlock});
1537 auto &entryBlock = executeOp.getRegion().emplaceBlock();
1538 builder.setInsertionPointToStart(&entryBlock);
1539 auto i1 = builder.getI1Type();
1544 for (
auto fixedValue : fixedValues) {
1545 auto present = fixedValue.present ? trueValue : falseValue;
1546 auto past = fixedValue.past ? trueValue : falseValue;
1547 materializedFixedValues.insert({fixedValue.value, {past, present}});
1548 mapping.map(fixedValue.value, present);
1553 auto fixedTable = getConstBoolean(
true);
1554 for (
auto [index, trigger] :
llvm::enumerate(triggers)) {
1555 for (
auto fixedValue : fixedValues) {
1556 if (getValueField(fixedValue.value) != trigger)
1558 auto past = getPastTrigger(index);
1559 fixedTable &= fixedValue.past ? past : ~past;
1560 auto present = getPresentTrigger(index);
1561 fixedTable &= fixedValue.present ? present : ~present;
1568 SmallVector<Value> foldedResults;
1569 while (!worklist.empty()) {
1570 auto [oldBlock, newBlock] = worklist.pop_back_val();
1571 builder.setInsertionPointToEnd(newBlock);
1572 for (
auto &oldOp : *oldBlock) {
1574 if (
auto waitOp = dyn_cast<WaitOp>(oldOp)) {
1577 SmallVector<Value> operands;
1578 for (
auto operand : waitOp.getYieldOperands())
1579 operands.push_back(mapping.lookupOrDefault(operand));
1580 YieldOp::create(builder, waitOp.getLoc(), operands);
1585 if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(oldOp)) {
1586 SmallVector<Value> operands;
1587 auto condition = mapping.lookupOrDefault(condBranchOp.getCondition());
1588 if (matchPattern(condition, m_NonZero())) {
1589 for (
auto operand : condBranchOp.getTrueDestOperands())
1590 operands.push_back(mapping.lookupOrDefault(operand));
1591 cf::BranchOp::create(builder, condBranchOp.getLoc(),
1592 scheduleBlock(condBranchOp.getTrueDest()),
1596 if (matchPattern(condition, m_Zero())) {
1597 for (
auto operand : condBranchOp.getFalseOperands())
1598 operands.push_back(mapping.lookupOrDefault(operand));
1599 cf::BranchOp::create(builder, condBranchOp.getLoc(),
1600 scheduleBlock(condBranchOp.getFalseDest()),
1609 if (oldOp.getNumResults() == 1 &&
1610 oldOp.getResult(0).getType().isSignlessInteger(1)) {
1611 if (
auto it = booleanLattice.find(getValueField(oldOp.getResult(0)));
1612 it != booleanLattice.end()) {
1613 if ((it->second & fixedTable).isFalse()) {
1614 mapping.map(oldOp.getResult(0), falseValue);
1617 if ((it->second & fixedTable) == fixedTable) {
1618 mapping.map(oldOp.getResult(0), trueValue);
1625 for (
auto &blockOperand : oldOp.getBlockOperands())
1626 scheduleBlock(blockOperand.
get());
1627 auto *clonedOp = builder.clone(oldOp, mapping);
1631 if (succeeded(builder.tryFold(clonedOp, foldedResults)) &&
1632 !foldedResults.empty()) {
1633 for (
auto [oldResult, foldedResult] :
1634 llvm::zip(oldOp.getResults(), foldedResults))
1635 mapping.map(oldResult, foldedResult);
1638 foldedResults.clear();
1645 worklist.push_back({&process.getBody().front(), &entryBlock});
1647 builder.setInsertionPointToEnd(mapping.lookup(wait->getBlock()));
1652 for (
auto &block : process.getBody())
1653 mapping.erase(&block);
1659 if (wait.getDest()->hasOneUse()) {
1662 for (
auto [arg, pastValue] :
1663 llvm::zip(wait.getDest()->getArguments(), pastValues))
1664 mapping.map(arg, materializedFixedValues.lookup(pastValue).first);
1667 mapping.map(wait.getDest(), builder.getBlock());
1668 worklist.push_back({wait.getDest(), builder.getBlock()});
1671 auto *dest = scheduleBlock(wait.getDest());
1675 SmallVector<Value> destOperands;
1676 assert(pastValues.size() == wait.getDestOperands().size());
1677 for (
auto pastValue : pastValues)
1678 destOperands.push_back(materializedFixedValues.lookup(pastValue).first);
1679 cf::BranchOp::create(builder, wait.getLoc(), dest, destOperands);
1686 if (isOpTriviallyDead(trueValue))
1688 if (isOpTriviallyDead(falseValue))
1691 specializedProcesses.insert({fixedValues, executeOp.getResults()});
1692 return executeOp.getResults();
1700struct DeseqPass :
public llhd::impl::DeseqPassBase<DeseqPass> {
1701 void runOnOperation()
override;
1705void DeseqPass::runOnOperation() {
1706 SmallVector<ProcessOp> processes(getOperation().getOps<ProcessOp>());
1707 for (
auto process : processes)
1708 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)