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);
121 hw::type_dyn_cast<hw::StructType>(se.getInput().getType());
123 return {value, 0, value};
125 uint64_t idx = se.getFieldIndex();
127 return {baseVF.value, baseVF.fieldID + childID, value};
132 Value base = ae.getInput();
133 Value index = ae.getIndex();
137 std::optional<uint64_t> idx;
139 idx = cst.getValue().getZExtValue();
142 if (
auto sliceIdx = slice.getLowIndex().getDefiningOp<
hw::ConstantOp>())
144 idx = sliceIdx.getValue().getZExtValue() +
145 getIdx.getValue().getZExtValue();
146 base = slice.getInput();
151 return {value, 0, value};
153 if (
auto arg = dyn_cast<BlockArgument>(base)) {
154 SmallPtrSet<Block *, 4> visited;
155 base = canonicalizeBlockArg(arg, visited);
157 auto baseVF = getValueField(base);
159 if (
auto arrayType = dyn_cast<hw::ArrayType>(base.getType())) {
161 return {baseVF.value, baseVF.fieldID + childID, value};
163 if (
auto arrayType = dyn_cast<hw::UnpackedArrayType>(base.getType())) {
165 return {baseVF.value, baseVF.fieldID + childID, value};
168 return {value, 0, value};
173 Value base = ext.getInput();
174 if (
auto arg = dyn_cast<BlockArgument>(base)) {
175 SmallPtrSet<Block *, 4> visited;
176 base = canonicalizeBlockArg(arg, visited);
178 auto baseVF = getValueField(base);
179 uint64_t lowBit =
static_cast<uint64_t
>(ext.getLowBit());
180 auto intType = dyn_cast<IntegerType>(ext.getType());
182 return {value, 0, value};
183 uint64_t bitWidth = intType.getWidth();
186 if (baseVF.value.getType().isSignlessInteger()) {
187 uint64_t fieldID = baseVF.fieldID ? baseVF.fieldID + lowBit : lowBit + 1;
188 return {baseVF.value, fieldID, value, 0, bitWidth};
193 if (baseVF.fieldID == 0)
194 return {value, 0, value};
195 uint64_t bitID = baseVF.bitID ? baseVF.bitID + lowBit : lowBit + 1;
196 return {baseVF.value, baseVF.fieldID, value, bitID, bitWidth};
200 return {value, 0, value};
205 Deseq(ProcessOp process) : process(process) {}
208 bool analyzeProcess();
209 Value tracePastValue(Value pastValue);
216 TruthTable computeBoolean(BlockArgument value);
218 TruthTable computeBlockCondition(Block *block);
219 TruthTable computeSuccessorCondition(BlockOperand &operand);
220 TruthTable computeSuccessorBoolean(BlockOperand &operand,
unsigned argIdx);
221 ValueTable computeSuccessorValue(BlockOperand &operand,
unsigned argIdx);
226 ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable);
228 matchDriveClockAndReset(
DriveInfo &drive,
229 ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable);
231 Value materializeProjection(OpBuilder &builder, Location loc, Value value,
234 void implementRegisters();
235 void implementRegister(
DriveInfo &drive);
237 Value specializeValue(Value value,
FixedValues fixedValues);
238 ValueRange specializeProcess(
FixedValues fixedValues);
250 SmallVector<Value, 2> pastValues;
252 SmallVector<DriveInfo> driveInfos;
262 ConstantTimeOp epsilonDelay;
264 DenseMap<Operation *, bool> staticOps;
267 DenseMap<ValueField, TruthTable> booleanLattice;
270 DenseMap<Value, ValueTable> valueLattice;
274 DenseMap<Block *, TruthTable> blockConditionLattice;
277 DenseMap<BlockOperand *, TruthTable> successorConditionLattice;
280 DenseMap<std::pair<BlockOperand *, unsigned>,
TruthTable>
281 successorBooleanLattice;
284 DenseMap<std::pair<BlockOperand *, unsigned>,
ValueTable>
285 successorValueLattice;
295 TruthTable getConstBoolean(
bool value)
const {
298 TruthTable getPastTrigger(
unsigned triggerIndex)
const {
301 TruthTable getPresentTrigger(
unsigned triggerIndex)
const {
315 return ValueTable(getConstBoolean(
true), value);
325 if (!analyzeProcess())
328 llvm::dbgs() <<
"Desequentializing " << process.getLoc() <<
"\n";
329 llvm::dbgs() <<
"- Feeds " << driveInfos.size() <<
" conditional drives\n";
330 llvm::dbgs() <<
"- " << triggers.size() <<
" potential triggers:\n";
331 for (
auto [index, trigger] :
llvm::enumerate(triggers)) {
332 llvm::dbgs() <<
" - ";
333 trigger.getProjected().printAsOperand(llvm::dbgs(), OpPrintingFlags());
334 llvm::dbgs() <<
": past " << getPastTrigger(index);
335 llvm::dbgs() <<
", present " << getPresentTrigger(index);
336 llvm::dbgs() <<
"\n";
348 implementRegisters();
361bool Deseq::analyzeProcess() {
364 for (
auto &block : process.getBody()) {
365 for (
auto &op : block) {
366 if (isa<WaitOp, HaltOp>(op))
368 if (!isMemoryEffectFree(&op)) {
370 llvm::dbgs() <<
"Skipping " << process.getLoc()
371 <<
": contains side-effecting op ";
372 op.print(llvm::dbgs(), OpPrintingFlags().skipRegions());
373 llvm::dbgs() <<
"\n";
381 for (
auto &block : process.getBody()) {
382 if (
auto candidate = dyn_cast<WaitOp>(block.getTerminator())) {
384 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
385 <<
": has multiple waits\n");
392 LLVM_DEBUG(llvm::dbgs()
393 <<
"Skipping " << process.getLoc() <<
": has no wait\n");
398 SmallPtrSet<Operation *, 8> seenDrives;
399 for (
auto &use : process->getUses()) {
400 auto driveOp = dyn_cast<DriveOp>(use.getOwner());
402 LLVM_DEBUG(llvm::dbgs()
403 <<
"Skipping " << process.getLoc() <<
": feeds non-drive "
404 << use.getOwner()->getLoc() <<
"\n");
408 if (!driveOp.getEnable()) {
409 LLVM_DEBUG(llvm::dbgs()
410 <<
"Skipping " << process.getLoc()
411 <<
": feeds unconditional drive " << driveOp <<
"\n");
418 if (use.getOperandNumber() != 1 && use.getOperandNumber() != 3) {
419 LLVM_DEBUG(llvm::dbgs()
420 <<
"Skipping " << process.getLoc()
421 <<
": feeds drive operand that is neither value nor enable: "
426 if (!seenDrives.insert(driveOp).second)
429 driveInfos.push_back(
DriveInfo(driveOp));
436 bool hasNonI1Observed =
false;
437 for (
auto value : wait.getObserved()) {
438 if (!value.getType().isSignlessInteger(1))
439 hasNonI1Observed =
true;
442 if (!hasNonI1Observed) {
444 for (
auto value : wait.getObserved())
445 triggers.insert(getValueField(value));
449 for (
auto operand : wait.getDestOperands()) {
450 if (!operand.getType().isSignlessInteger(1))
452 auto vf = getValueField(operand);
454 if (vf.fieldID != 0 && llvm::is_contained(wait.getObserved(), vf.value)) {
462 if (triggers.empty() || triggers.size() > 2) {
463 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc() <<
": observes "
464 << triggers.size() <<
" values\n");
469 for (
auto [index, trigger] :
llvm::enumerate(triggers))
470 booleanLattice.insert({trigger, getPresentTrigger(index)});
475 for (
auto [operand, blockArg] :
476 llvm::zip(wait.getDestOperands(), wait.getDest()->getArguments())) {
478 auto operandVF = getValueField(operand);
479 auto it = llvm::find(triggers, operandVF);
480 if (it != triggers.end()) {
481 unsigned index = std::distance(triggers.begin(), it);
482 pastValues.push_back(it->getProjected());
483 booleanLattice.insert({getValueField(blockArg), getPastTrigger(index)});
489 if (!operand.getType().isSignlessInteger(1)) {
490 if (llvm::is_contained(wait.getObserved(), operand))
492 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
493 <<
": uses non-i1 past value\n");
497 auto trigger = tracePastValue(operand);
500 pastValues.push_back(trigger);
501 unsigned index = std::distance(
502 triggers.begin(), llvm::find(triggers, getValueField(trigger)));
503 booleanLattice.insert({getValueField(blockArg), getPastTrigger(index)});
512Value Deseq::tracePastValue(Value pastValue) {
515 SmallVector<Value> worklist;
516 SmallPtrSet<Value, 8> seen;
517 worklist.push_back(pastValue);
518 seen.insert(pastValue);
520 SmallPtrSet<Block *, 2> predSeen;
522 SmallPtrSet<Value, 2> distinctValues;
523 while (!worklist.empty()) {
524 auto value = worklist.pop_back_val();
525 auto arg = dyn_cast<BlockArgument>(value);
529 if (
auto it = llvm::find(triggers, getValueField(value));
530 it != triggers.end()) {
531 distinctValues.insert(it->getProjected());
535 distinctValues.insert(value);
541 predWorklist.clear();
542 for (
auto *predecessor : arg.getOwner()->getPredecessors())
543 if (predSeen.insert(predecessor).second)
544 for (auto &operand : predecessor->getTerminator()->getBlockOperands())
545 if (operand.
get() == arg.getOwner())
546 predWorklist.insert(&operand);
550 unsigned argIdx = arg.getArgNumber();
551 for (
auto *blockOperand : predWorklist) {
552 auto *op = blockOperand->getOwner();
553 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
555 auto operand = branchOp.getDestOperands()[argIdx];
556 if (seen.insert(operand).second)
557 worklist.push_back(operand);
558 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
560 unsigned destIdx = blockOperand->getOperandNumber();
561 auto operand = destIdx == 0
562 ? condBranchOp.getTrueDestOperands()[argIdx]
563 : condBranchOp.getFalseDestOperands()[argIdx];
567 if ((matchPattern(operand, m_One()) && destIdx == 0) ||
568 (matchPattern(operand, m_Zero()) && destIdx == 1))
569 operand = condBranchOp.getCondition();
571 if (seen.insert(operand).second)
572 worklist.push_back(operand);
574 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
575 <<
": unsupported terminator " << op->getName()
576 <<
" while tracing past value\n");
584 if (distinctValues.size() != 1) {
587 <<
"Skipping " << process.getLoc()
588 <<
": multiple past values passed for the same block argument\n");
591 auto distinctValue = *distinctValues.begin();
592 if (!triggers.contains(getValueField(distinctValue))) {
593 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
594 <<
": unobserved past value\n");
597 return distinctValue;
608TruthTable Deseq::computeBoolean(Value value) {
609 return computeBoolean(getValueField(value));
614 return getUnknownBoolean();
618 if (
auto it = booleanLattice.find(vf); it != booleanLattice.end())
625 return computeBoolean(
627 return getUnknownBoolean();
630 Value value = vf.
value;
631 assert(value.getType().isSignlessInteger(1));
635 if (value.getDefiningOp() == process)
636 return computeBoolean(
637 wait.getYieldOperands()[cast<OpResult>(value).getResultNumber()]);
641 booleanLattice[vf] = getUnknownBoolean();
645 TypeSwitch<Value, TruthTable>(value).Case<OpResult, BlockArgument>(
646 [&](
auto value) {
return computeBoolean(value); });
650 llvm::dbgs() <<
"- Boolean ";
651 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
652 llvm::dbgs() <<
": " << result <<
"\n";
654 booleanLattice[vf] = result;
662 auto vf = getValueField(value);
673 if (value.getDefiningOp() == process)
675 wait.getYieldOperands()[cast<OpResult>(value).getResultNumber()]);
680 if (
auto it = valueLattice.find(value); it != valueLattice.end())
682 valueLattice[value] = getUnknownValue();
686 TypeSwitch<Value, ValueTable>(value).Case<OpResult, BlockArgument>(
687 [&](
auto value) {
return computeValue(value); });
691 llvm::dbgs() <<
"- Value ";
692 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
693 llvm::dbgs() <<
": " << result <<
"\n";
695 valueLattice[value] = result;
700TruthTable Deseq::computeBoolean(OpResult value) {
701 assert(value.getType().isSignlessInteger(1));
702 auto *op = value.getOwner();
705 if (
auto constOp = dyn_cast<hw::ConstantOp>(op))
706 return getConstBoolean(constOp.getValue().isOne());
709 if (
auto orOp = dyn_cast<comb::OrOp>(op)) {
710 auto result = getConstBoolean(
false);
711 for (
auto operand : orOp.getInputs()) {
712 result |= computeBoolean(operand);
720 if (
auto andOp = dyn_cast<comb::AndOp>(op)) {
721 auto result = getConstBoolean(
true);
722 for (
auto operand : andOp.getInputs()) {
723 result &= computeBoolean(operand);
724 if (result.isFalse())
731 if (
auto xorOp = dyn_cast<comb::XorOp>(op)) {
732 auto result = getConstBoolean(
false);
733 for (
auto operand : xorOp.getInputs())
734 result ^= computeBoolean(operand);
741 if (llvm::any_of(op->getOperands(), [&](
auto operand) {
744 if (!operand.getType().isSignlessInteger(1))
746 auto result = computeBoolean(operand);
747 return result.isPoison() || (result != getUnknownBoolean() &&
748 !result.isTrue() && !result.isFalse());
750 return getPoisonBoolean();
751 return getUnknownBoolean();
756ValueTable Deseq::computeValue(OpResult value) {
757 auto *op = value.getOwner();
760 if (isa<comb::MuxOp, arith::SelectOp>(op)) {
761 auto condition = computeBoolean(op->getOperand(0));
762 auto trueValue = computeValue(op->getOperand(1));
763 auto falseValue = computeValue(op->getOperand(2));
764 trueValue.addCondition(condition);
765 falseValue.addCondition(~condition);
766 trueValue.merge(std::move(falseValue));
771 return getKnownValue(value);
775TruthTable Deseq::computeBoolean(BlockArgument arg) {
776 auto *block = arg.getOwner();
779 if (block->getParentOp() != process)
780 return getUnknownBoolean();
784 auto result = getConstBoolean(
false);
785 SmallPtrSet<Block *, 4> seen;
786 for (
auto *predecessor : block->getPredecessors()) {
787 if (!seen.insert(predecessor).second)
789 for (
auto &operand : predecessor->getTerminator()->getBlockOperands()) {
790 if (operand.get() != block)
792 auto value = computeSuccessorBoolean(operand, arg.getArgNumber());
795 auto condition = computeSuccessorCondition(operand);
796 result |= value & condition;
808ValueTable Deseq::computeValue(BlockArgument arg) {
809 auto *block = arg.getOwner();
812 if (block->getParentOp() != process)
813 return getKnownValue(arg);
818 SmallPtrSet<Block *, 4> seen;
819 for (
auto *predecessor : block->getPredecessors()) {
820 if (!seen.insert(predecessor).second)
822 for (
auto &operand : predecessor->getTerminator()->getBlockOperands()) {
823 if (operand.get() != block)
825 auto condition = computeSuccessorCondition(operand);
826 if (condition.isFalse())
828 auto value = computeSuccessorValue(operand, arg.getArgNumber());
829 value.addCondition(condition);
838TruthTable Deseq::computeBlockCondition(Block *block) {
841 if (
auto it = blockConditionLattice.find(block);
842 it != blockConditionLattice.end())
844 blockConditionLattice[block] = getConstBoolean(
false);
848 auto result = getConstBoolean(
false);
849 SmallPtrSet<Block *, 4> seen;
850 for (
auto *predecessor : block->getPredecessors()) {
851 if (!seen.insert(predecessor).second)
853 for (
auto &operand : predecessor->getTerminator()->getBlockOperands()) {
854 if (operand.get() != block)
856 result |= computeSuccessorCondition(operand);
866 llvm::dbgs() <<
"- Block condition ";
867 block->printAsOperand(llvm::dbgs());
868 llvm::dbgs() <<
": " << result <<
"\n";
870 blockConditionLattice[block] = result;
876TruthTable Deseq::computeSuccessorCondition(BlockOperand &blockOperand) {
881 auto *op = blockOperand.getOwner();
883 return getConstBoolean(
true);
887 if (
auto it = successorConditionLattice.find(&blockOperand);
888 it != successorConditionLattice.end())
890 successorConditionLattice[&blockOperand] = getConstBoolean(
false);
894 auto destIdx = blockOperand.getOperandNumber();
895 auto blockCondition = computeBlockCondition(op->getBlock());
896 auto result = getUnknownBoolean();
897 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
898 result = blockCondition;
899 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
900 auto branchCondition = computeBoolean(condBranchOp.getCondition());
902 result = blockCondition & branchCondition;
904 result = blockCondition & ~branchCondition;
906 result = getPoisonBoolean();
911 llvm::dbgs() <<
"- Successor condition ";
912 op->getBlock()->printAsOperand(llvm::dbgs());
913 llvm::dbgs() <<
"#succ" << destIdx <<
" -> ";
914 blockOperand.get()->printAsOperand(llvm::dbgs());
915 llvm::dbgs() <<
" = " << result <<
"\n";
917 successorConditionLattice[&blockOperand] = result;
923TruthTable Deseq::computeSuccessorBoolean(BlockOperand &blockOperand,
927 if (
auto it = successorBooleanLattice.find({&blockOperand, argIdx});
928 it != successorBooleanLattice.end())
930 successorBooleanLattice[{&blockOperand, argIdx}] = getUnknownBoolean();
934 auto *op = blockOperand.getOwner();
935 auto destIdx = blockOperand.getOperandNumber();
936 auto result = getUnknownBoolean();
937 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
938 result = computeBoolean(branchOp.getDestOperands()[argIdx]);
939 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
941 result = computeBoolean(condBranchOp.getTrueDestOperands()[argIdx]);
943 result = computeBoolean(condBranchOp.getFalseDestOperands()[argIdx]);
945 result = getPoisonBoolean();
950 llvm::dbgs() <<
"- Successor boolean ";
951 op->getBlock()->printAsOperand(llvm::dbgs());
952 llvm::dbgs() <<
"#succ" << destIdx <<
" -> ";
953 blockOperand.get()->printAsOperand(llvm::dbgs());
954 llvm::dbgs() <<
"#arg" << argIdx <<
" = " << result <<
"\n";
956 successorBooleanLattice[{&blockOperand, argIdx}] = result;
963ValueTable Deseq::computeSuccessorValue(BlockOperand &blockOperand,
967 if (
auto it = successorValueLattice.find({&blockOperand, argIdx});
968 it != successorValueLattice.end())
970 successorValueLattice[{&blockOperand, argIdx}] = getUnknownValue();
974 auto *op = blockOperand.getOwner();
975 auto destIdx = blockOperand.getOperandNumber();
976 auto result = getUnknownValue();
977 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
978 result = computeValue(branchOp.getDestOperands()[argIdx]);
979 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
981 result = computeValue(condBranchOp.getTrueDestOperands()[argIdx]);
983 result = computeValue(condBranchOp.getFalseDestOperands()[argIdx]);
985 result = getPoisonValue();
990 llvm::dbgs() <<
"- Successor value ";
991 op->getBlock()->printAsOperand(llvm::dbgs());
992 llvm::dbgs() <<
"#succ" << destIdx <<
" -> ";
993 blockOperand.get()->printAsOperand(llvm::dbgs());
994 llvm::dbgs() <<
"#arg" << argIdx <<
" = " << result <<
"\n";
996 successorValueLattice[{&blockOperand, argIdx}] = result;
1007bool Deseq::matchDrives() {
1008 for (
auto &drive : driveInfos)
1009 if (!matchDrive(drive))
1018bool Deseq::matchDrive(
DriveInfo &drive) {
1019 LLVM_DEBUG(llvm::dbgs() <<
"- Analyzing " << drive.
op <<
"\n");
1022 auto condition = computeBoolean(drive.
op.getEnable());
1023 if (condition.isPoison()) {
1024 LLVM_DEBUG(llvm::dbgs()
1025 <<
"- Aborting: poison condition on " << drive.
op <<
"\n");
1030 auto initialValueTable = computeValue(drive.
op.getValue());
1031 initialValueTable.addCondition(condition);
1033 llvm::dbgs() <<
" - Condition: " << condition <<
"\n";
1034 llvm::dbgs() <<
" - Value: " << initialValueTable <<
"\n";
1040 SmallVector<std::pair<DNFTerm, ValueEntry>> valueTable;
1041 for (
auto &[condition, value] : initialValueTable.entries) {
1042 auto dnf = condition.canonicalize();
1043 if (dnf.isPoison() || value.isPoison()) {
1044 LLVM_DEBUG(llvm::dbgs()
1045 <<
"- Aborting: poison in " << initialValueTable <<
"\n");
1048 for (
auto &orTerm : dnf.orTerms)
1049 valueTable.push_back({orTerm, value});
1055 if (valueTable.size() > 3) {
1056 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: value table has "
1057 << valueTable.size() <<
" distinct conditions\n");
1062 if (triggers.size() == 2)
1063 return matchDriveClockAndReset(drive, valueTable);
1066 assert(triggers.size() == 1);
1067 return matchDriveClock(drive, valueTable);
1072bool Deseq::matchDriveClock(
1073 DriveInfo &drive, ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable) {
1076 if (valueTable.size() != 1) {
1077 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: single trigger value table has "
1078 << valueTable.size() <<
" entries\n");
1083 for (
unsigned variant = 0; variant < (1 << 1); ++variant) {
1084 bool negClock = (variant >> 0) & 1;
1092 uint32_t clockEdge = (negClock ? 0b1001 : 0b0110) << 2;
1093 auto clockWithoutEnable =
DNFTerm{clockEdge};
1094 auto clockWithEnable =
DNFTerm{clockEdge | 0b01};
1097 if (valueTable[0].first == clockWithEnable)
1099 else if (valueTable[0].first != clockWithoutEnable)
1103 drive.
clock.
clock = triggers[0].getProjected();
1106 if (!valueTable[0].second.isUnknown())
1107 drive.
clock.
value = valueTable[0].second.value;
1110 llvm::dbgs() <<
" - Matched " << (negClock ?
"neg" :
"pos")
1112 drive.
clock.
clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1113 llvm::dbgs() <<
" -> " << valueTable[0].second;
1115 llvm::dbgs() <<
" (with enable)";
1116 llvm::dbgs() <<
"\n";
1122 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: unknown clock scheme\n");
1129bool Deseq::matchDriveClockAndReset(
1130 DriveInfo &drive, ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable) {
1134 if (valueTable.size() != 2 && valueTable.size() != 3) {
1135 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: two trigger value table has "
1136 << valueTable.size() <<
" entries\n");
1143 for (
unsigned variant = 0; variant < (1 << 3); ++variant) {
1144 bool negClock = (variant >> 0) & 1;
1145 bool negReset = (variant >> 1) & 1;
1146 unsigned clockIdx = (variant >> 2) & 1;
1147 unsigned resetIdx = 1 - clockIdx;
1154 uint32_t clockEdge = (negClock ? 0b1001 : 0b0110) << (clockIdx * 4 + 2);
1155 uint32_t resetEdge = (negReset ? 0b1001 : 0b0110) << (resetIdx * 4 + 2);
1156 uint32_t resetOn = (negReset ? 0b1000 : 0b0100) << (resetIdx * 4 + 2);
1157 uint32_t resetOff = (negReset ? 0b0100 : 0b1000) << (resetIdx * 4 + 2);
1162 auto reset =
DNFTerm{resetEdge};
1163 auto clockWhileReset =
DNFTerm{clockEdge | resetOn};
1164 auto clockWithoutEnable =
DNFTerm{clockEdge | resetOff};
1165 auto clockWithEnable =
DNFTerm{clockEdge | resetOff | 0b01};
1168 auto resetIt = llvm::find_if(
1169 valueTable, [&](
auto &pair) {
return pair.first == reset; });
1170 if (resetIt == valueTable.end())
1173 auto clockWhileResetIt = llvm::find_if(
1174 valueTable, [&](
auto &pair) {
return pair.first == clockWhileReset; });
1175 if (clockWhileResetIt == valueTable.end())
1178 auto clockIt = llvm::find_if(valueTable, [&](
auto &pair) {
1179 return pair.first == clockWithoutEnable || pair.first == clockWithEnable;
1181 bool clockHolds = clockIt == valueTable.end();
1182 if (clockHolds && valueTable.size() != 2)
1188 if (clockWhileResetIt->second != resetIt->second ||
1189 resetIt->second.isUnknown()) {
1190 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: inconsistent reset value\n");
1195 drive.
reset.
reset = triggers[resetIdx].getProjected();
1199 drive.
clock.
clock = triggers[clockIdx].getProjected();
1205 if (clockIt->first == clockWithEnable)
1207 if (!clockIt->second.isUnknown())
1212 llvm::dbgs() <<
" - Matched " << (negClock ?
"neg" :
"pos")
1214 drive.
clock.
clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1216 llvm::dbgs() <<
" -> hold";
1218 llvm::dbgs() <<
" -> " << clockIt->second;
1220 llvm::dbgs() <<
" (with enable)";
1221 llvm::dbgs() <<
"\n";
1222 llvm::dbgs() <<
" - Matched active-" << (negReset ?
"low" :
"high")
1224 drive.
reset.
reset.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1225 llvm::dbgs() <<
" -> " << resetIt->second <<
"\n";
1231 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: unknown reset scheme\n");
1239Value Deseq::materializeProjection(OpBuilder &builder, Location loc,
1246 auto isInThisProcess = [&](Value v) {
1247 if (
auto arg = dyn_cast<BlockArgument>(v)) {
1248 Operation *parentOp = arg.getOwner()->getParentOp();
1251 return parentOp == process.getOperation() ||
1252 parentOp->getParentOfType<ProcessOp>() == process;
1254 if (
auto *defOp = v.getDefiningOp())
1255 return defOp->getParentOfType<ProcessOp>() == process;
1258 if (!isInThisProcess(value))
1261 if (
auto it = cache.find(value); it != cache.end())
1266 if (
auto arg = dyn_cast<BlockArgument>(value)) {
1267 SmallPtrSet<Block *, 4> visited;
1268 Value canon = canonicalizeBlockArg(arg, visited);
1271 auto remat = materializeProjection(builder, loc, canon, cache);
1272 cache.insert({value, remat});
1276 auto *defOp = value.getDefiningOp();
1281 if (
auto ext = dyn_cast<comb::ExtractOp>(defOp)) {
1282 Value input = materializeProjection(builder, loc, ext.getInput(), cache);
1285 cache.insert({value, remat});
1288 if (
auto get = dyn_cast<hw::ArrayGetOp>(defOp)) {
1289 Value input = materializeProjection(builder, loc,
get.getInput(), cache);
1290 Value index = materializeProjection(builder, loc,
get.getIndex(), cache);
1292 cache.insert({value, remat});
1295 if (
auto slice = dyn_cast<hw::ArraySliceOp>(defOp)) {
1296 Value input = materializeProjection(builder, loc, slice.getInput(), cache);
1298 materializeProjection(builder, loc, slice.getLowIndex(), cache);
1301 cache.insert({value, remat});
1304 if (
auto se = dyn_cast<hw::StructExtractOp>(defOp)) {
1305 Value input = materializeProjection(builder, loc, se.getInput(), cache);
1308 cache.insert({value, remat});
1311 if (
auto cst = dyn_cast<hw::ConstantOp>(defOp)) {
1313 builder, loc, cst.getResult().getType(), cst.getValueAttr());
1314 cache.insert({value, remat});
1317 if (
auto cst = dyn_cast<arith::ConstantOp>(defOp)) {
1318 auto *cloned = builder.clone(*defOp);
1319 Value remat = cloned->getResult(cast<OpResult>(value).getResultNumber());
1320 cache.insert({value, remat});
1330void Deseq::implementRegisters() {
1331 for (
auto &drive : driveInfos)
1332 implementRegister(drive);
1342void Deseq::implementRegister(
DriveInfo &drive) {
1343 OpBuilder builder(drive.
op);
1344 auto loc = drive.
op.getLoc();
1352 materializeProjection(builder, loc, drive.
clock.
clock, rematerialized);
1354 auto &clockCast = materializedClockCasts[clockValue];
1356 clockCast = seq::ToClockOp::create(builder, loc, clockValue);
1357 auto clock = clockCast;
1359 auto &clockInv = materializedClockInverters[clock];
1361 clockInv = seq::ClockInverterOp::create(builder, loc, clock);
1371 materializeProjection(builder, loc, drive.
reset.
reset, rematerialized);
1377 auto &inv = materializedInverters[reset];
1380 inv = comb::XorOp::create(builder, loc, reset, one);
1388 if (!resetValue.getParentRegion()->isProperAncestor(&process.getBody())) {
1389 if (
auto *defOp = resetValue.getDefiningOp();
1390 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1391 defOp->moveBefore(process);
1393 resetValue = specializeValue(
1394 drive.
op.getValue(),
1395 FixedValues{{drive.clock.clock, !drive.clock.risingEdge,
1396 !drive.clock.risingEdge},
1397 {drive.reset.reset, !drive.reset.activeHigh,
1398 drive.reset.activeHigh}});
1406 if (enable && !enable.getParentRegion()->isProperAncestor(&process.getBody()))
1407 enable = drive.
op.getEnable();
1413 if (!value.getParentRegion()->isProperAncestor(&process.getBody())) {
1414 if (
auto *defOp = value.getDefiningOp();
1415 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1416 defOp->moveBefore(process);
1418 value = drive.
op.getValue();
1424 fixedValues.push_back(
1427 fixedValues.push_back(
1430 value = specializeValue(value, fixedValues);
1432 enable = specializeValue(enable, fixedValues);
1436 if (
auto sigOp = drive.
op.getSignal().getDefiningOp<llhd::SignalOp>())
1437 name = sigOp.getNameAttr();
1439 name = builder.getStringAttr(
"");
1442 auto reg = seq::FirRegOp::create(builder, loc, value, clock, name,
1444 IntegerAttr{}, reset, resetValue,
1452 OpBuilder::InsertionGuard guard(builder);
1453 builder.setInsertionPoint(reg);
1454 reg.getNextMutable().assign(comb::MuxOp::create(
1455 builder, loc, enable,
reg.getNext(),
reg.getResult(),
true));
1459 drive.
op.getValueMutable().assign(reg);
1460 drive.
op.getEnableMutable().clear();
1465 if (matchPattern(drive.
op.getTime(), m_Constant(&attr)) &&
1466 attr.getTime() == 0 && attr.getDelta() == 1 && attr.getEpsilon() == 0) {
1469 ConstantTimeOp::create(builder, process.getLoc(), 0,
"ns", 0, 1);
1470 drive.
op.getTimeMutable().assign(epsilonDelay);
1483Value Deseq::specializeValue(Value value,
FixedValues fixedValues) {
1484 auto result = dyn_cast<OpResult>(value);
1485 if (!result || result.getOwner() != process)
1487 return specializeProcess(fixedValues)[result.getResultNumber()];
1498ValueRange Deseq::specializeProcess(
FixedValues fixedValues) {
1499 if (
auto it = specializedProcesses.find(fixedValues);
1500 it != specializedProcesses.end())
1504 llvm::dbgs() <<
"- Specializing process for:\n";
1505 for (
auto fixedValue : fixedValues) {
1506 llvm::dbgs() <<
" - ";
1507 fixedValue.value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1508 llvm::dbgs() <<
": " << fixedValue.past <<
" -> " << fixedValue.present
1517 OpBuilder builder(process);
1518 auto executeOp = CombinationalOp::create(builder, process.getLoc(),
1519 process.getResultTypes());
1522 SmallVector<std::pair<Block *, Block *>> worklist;
1524 auto scheduleBlock = [&](
Block *block) {
1525 if (
auto *newBlock = mapping.lookupOrNull(block))
1527 auto *newBlock = &executeOp.getRegion().emplaceBlock();
1528 for (
auto arg : block->getArguments()) {
1529 auto newArg = newBlock->addArgument(arg.getType(), arg.getLoc());
1530 mapping.map(arg, newArg);
1532 mapping.map(block, newBlock);
1533 worklist.push_back({block, newBlock});
1538 auto &entryBlock = executeOp.getRegion().emplaceBlock();
1539 builder.setInsertionPointToStart(&entryBlock);
1540 auto i1 = builder.getI1Type();
1545 for (
auto fixedValue : fixedValues) {
1546 auto present = fixedValue.present ? trueValue : falseValue;
1547 auto past = fixedValue.past ? trueValue : falseValue;
1548 materializedFixedValues.insert({fixedValue.value, {past, present}});
1549 mapping.map(fixedValue.value, present);
1554 auto fixedTable = getConstBoolean(
true);
1555 for (
auto [index, trigger] :
llvm::enumerate(triggers)) {
1556 for (
auto fixedValue : fixedValues) {
1557 if (getValueField(fixedValue.value) != trigger)
1559 auto past = getPastTrigger(index);
1560 fixedTable &= fixedValue.past ? past : ~past;
1561 auto present = getPresentTrigger(index);
1562 fixedTable &= fixedValue.present ? present : ~present;
1569 SmallVector<Value> foldedResults;
1570 while (!worklist.empty()) {
1571 auto [oldBlock, newBlock] = worklist.pop_back_val();
1572 builder.setInsertionPointToEnd(newBlock);
1573 for (
auto &oldOp : *oldBlock) {
1575 if (
auto waitOp = dyn_cast<WaitOp>(oldOp)) {
1578 SmallVector<Value> operands;
1579 for (
auto operand : waitOp.getYieldOperands())
1580 operands.push_back(mapping.lookupOrDefault(operand));
1581 YieldOp::create(builder, waitOp.getLoc(), operands);
1586 if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(oldOp)) {
1587 SmallVector<Value> operands;
1588 auto condition = mapping.lookupOrDefault(condBranchOp.getCondition());
1589 if (matchPattern(condition, m_NonZero())) {
1590 for (
auto operand : condBranchOp.getTrueDestOperands())
1591 operands.push_back(mapping.lookupOrDefault(operand));
1592 cf::BranchOp::create(builder, condBranchOp.getLoc(),
1593 scheduleBlock(condBranchOp.getTrueDest()),
1597 if (matchPattern(condition, m_Zero())) {
1598 for (
auto operand : condBranchOp.getFalseOperands())
1599 operands.push_back(mapping.lookupOrDefault(operand));
1600 cf::BranchOp::create(builder, condBranchOp.getLoc(),
1601 scheduleBlock(condBranchOp.getFalseDest()),
1610 if (oldOp.getNumResults() == 1 &&
1611 oldOp.getResult(0).getType().isSignlessInteger(1)) {
1612 if (
auto it = booleanLattice.find(getValueField(oldOp.getResult(0)));
1613 it != booleanLattice.end()) {
1614 if ((it->second & fixedTable).isFalse()) {
1615 mapping.map(oldOp.getResult(0), falseValue);
1618 if ((it->second & fixedTable) == fixedTable) {
1619 mapping.map(oldOp.getResult(0), trueValue);
1626 for (
auto &blockOperand : oldOp.getBlockOperands())
1627 scheduleBlock(blockOperand.
get());
1628 auto *clonedOp = builder.clone(oldOp, mapping);
1632 if (succeeded(builder.tryFold(clonedOp, foldedResults)) &&
1633 !foldedResults.empty()) {
1634 for (
auto [oldResult, foldedResult] :
1635 llvm::zip(oldOp.getResults(), foldedResults))
1636 mapping.map(oldResult, foldedResult);
1639 foldedResults.clear();
1646 worklist.push_back({&process.getBody().front(), &entryBlock});
1648 builder.setInsertionPointToEnd(mapping.lookup(wait->getBlock()));
1653 for (
auto &block : process.getBody())
1654 mapping.erase(&block);
1660 if (wait.getDest()->hasOneUse()) {
1663 for (
auto [arg, pastValue] :
1664 llvm::zip(wait.getDest()->getArguments(), pastValues))
1665 mapping.map(arg, materializedFixedValues.lookup(pastValue).first);
1668 mapping.map(wait.getDest(), builder.getBlock());
1669 worklist.push_back({wait.getDest(), builder.getBlock()});
1672 auto *dest = scheduleBlock(wait.getDest());
1676 SmallVector<Value> destOperands;
1677 assert(pastValues.size() == wait.getDestOperands().size());
1678 for (
auto pastValue : pastValues)
1679 destOperands.push_back(materializedFixedValues.lookup(pastValue).first);
1680 cf::BranchOp::create(builder, wait.getLoc(), dest, destOperands);
1687 if (isOpTriviallyDead(trueValue))
1689 if (isOpTriviallyDead(falseValue))
1692 specializedProcesses.insert({fixedValues, executeOp.getResults()});
1693 return executeOp.getResults();
1701struct DeseqPass :
public llhd::impl::DeseqPassBase<DeseqPass> {
1702 void runOnOperation()
override;
1706void DeseqPass::runOnOperation() {
1707 SmallVector<ProcessOp> processes(getOperation().getOps<ProcessOp>());
1708 for (
auto process : processes)
1709 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)