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);
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) {}
206 LogicalResult deseq();
208 bool analyzeProcess();
209 Value tracePastValue(Value pastValue);
211 TruthTable computeBoolean(Value value);
212 ValueTable computeValue(Value value);
213 TruthTable computeBoolean(ValueField value);
214 TruthTable computeBoolean(OpResult value);
215 ValueTable computeValue(OpResult value);
216 TruthTable computeBoolean(BlockArgument value);
217 ValueTable computeValue(BlockArgument arg);
218 TruthTable computeBlockCondition(Block *block);
219 TruthTable computeSuccessorCondition(BlockOperand &operand);
220 TruthTable computeSuccessorBoolean(BlockOperand &operand,
unsigned argIdx);
221 ValueTable computeSuccessorValue(BlockOperand &operand,
unsigned argIdx);
224 bool matchDrive(DriveInfo &drive);
225 bool matchDriveClock(DriveInfo &drive,
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,
232 SmallDenseMap<Value, Value, 8> &cache);
234 LogicalResult implementRegisters();
235 LogicalResult implementRegister(DriveInfo &drive);
237 Value specializeValue(Value value,
FixedValues fixedValues);
238 ValueRange specializeProcess(
FixedValues fixedValues);
246 SmallSetVector<ValueField, 2> triggers;
250 SmallVector<Value, 2> pastValues;
252 SmallVector<DriveInfo> driveInfos;
254 SmallDenseMap<FixedValues, ValueRange, 2> specializedProcesses;
256 SmallDenseMap<Value, Value, 1> materializedClockCasts;
258 SmallDenseMap<Value, Value, 1> materializedClockInverters;
260 SmallDenseMap<Value, Value, 1> materializedInverters;
262 DenseMap<Operation *, bool> staticOps;
265 DenseMap<ValueField, TruthTable> booleanLattice;
268 DenseMap<Value, ValueTable> valueLattice;
272 DenseMap<Block *, TruthTable> blockConditionLattice;
275 DenseMap<BlockOperand *, TruthTable> successorConditionLattice;
278 DenseMap<std::pair<BlockOperand *, unsigned>, TruthTable>
279 successorBooleanLattice;
282 DenseMap<std::pair<BlockOperand *, unsigned>, ValueTable>
283 successorValueLattice;
290 TruthTable getUnknownBoolean()
const {
293 TruthTable getConstBoolean(
bool value)
const {
296 TruthTable getPastTrigger(
unsigned triggerIndex)
const {
299 TruthTable getPresentTrigger(
unsigned triggerIndex)
const {
306 ValueTable getUnknownValue()
const {
309 ValueTable getPoisonValue()
const {
312 ValueTable getKnownValue(Value value)
const {
313 return ValueTable(getConstBoolean(
true), value);
319LogicalResult Deseq::deseq() {
323 if (!analyzeProcess())
326 llvm::dbgs() <<
"Desequentializing " << process.getLoc() <<
"\n";
327 llvm::dbgs() <<
"- Feeds " << driveInfos.size() <<
" conditional drives\n";
328 llvm::dbgs() <<
"- " << triggers.size() <<
" potential triggers:\n";
329 for (
auto [index, trigger] : llvm::enumerate(triggers)) {
330 llvm::dbgs() <<
" - ";
331 trigger.getProjected().printAsOperand(llvm::dbgs(), OpPrintingFlags());
332 llvm::dbgs() <<
": past " << getPastTrigger(index);
333 llvm::dbgs() <<
", present " << getPresentTrigger(index);
334 llvm::dbgs() <<
"\n";
346 if (failed(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<DriveLikeOp>(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");
417 if (&use != &driveOp.getValueMutable()[0] &&
419 llvm::make_pointer_range(driveOp.getEnableMutable()), &use)) {
420 LLVM_DEBUG(llvm::dbgs()
421 <<
"Skipping " << process.getLoc()
422 <<
": feeds drive operand that is neither value nor enable: "
427 if (!seenDrives.insert(driveOp).second)
430 driveInfos.push_back(DriveInfo(driveOp));
437 bool hasNonI1Observed =
false;
438 for (
auto value : wait.getObserved()) {
439 if (!value.getType().isSignlessInteger(1))
440 hasNonI1Observed =
true;
443 if (!hasNonI1Observed) {
445 for (
auto value : wait.getObserved())
446 triggers.insert(getValueField(value));
450 for (
auto operand : wait.getDestOperands()) {
451 if (!operand.getType().isSignlessInteger(1))
453 auto vf = getValueField(operand);
455 if (vf.fieldID != 0 && llvm::is_contained(wait.getObserved(), vf.value)) {
463 if (triggers.empty() || triggers.size() > 2) {
464 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc() <<
": observes "
465 << triggers.size() <<
" values\n");
470 for (
auto [index, trigger] : llvm::enumerate(triggers))
471 booleanLattice.insert({trigger, getPresentTrigger(index)});
476 for (
auto [operand, blockArg] :
477 llvm::zip(wait.getDestOperands(), wait.getDest()->getArguments())) {
479 auto operandVF = getValueField(operand);
480 const auto *it = llvm::find(triggers, operandVF);
481 if (it != triggers.end()) {
482 unsigned index = std::distance(triggers.begin(), it);
483 pastValues.push_back(it->getProjected());
484 booleanLattice.insert({getValueField(blockArg), getPastTrigger(index)});
490 if (!operand.getType().isSignlessInteger(1)) {
491 if (llvm::is_contained(wait.getObserved(), operand))
493 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
494 <<
": uses non-i1 past value\n");
498 auto trigger = tracePastValue(operand);
501 pastValues.push_back(trigger);
502 unsigned index = std::distance(
503 triggers.begin(), llvm::find(triggers, getValueField(trigger)));
504 booleanLattice.insert({getValueField(blockArg), getPastTrigger(index)});
513Value Deseq::tracePastValue(Value pastValue) {
516 SmallVector<Value> worklist;
517 SmallPtrSet<Value, 8> seen;
518 worklist.push_back(pastValue);
519 seen.insert(pastValue);
521 SmallPtrSet<Block *, 2> predSeen;
522 SmallSetVector<BlockOperand *, 4> predWorklist;
523 SmallPtrSet<Value, 2> distinctValues;
524 while (!worklist.empty()) {
525 auto value = worklist.pop_back_val();
526 auto arg = dyn_cast<BlockArgument>(value);
530 if (
const auto *it = llvm::find(triggers, getValueField(value));
531 it != triggers.end()) {
532 distinctValues.insert(it->getProjected());
536 distinctValues.insert(value);
542 predWorklist.clear();
543 for (
auto *predecessor : arg.getOwner()->getPredecessors())
544 if (predSeen.insert(predecessor).second)
545 for (
auto &operand : predecessor->getTerminator()->getBlockOperands())
546 if (operand.get() == arg.getOwner())
547 predWorklist.insert(&operand);
551 unsigned argIdx = arg.getArgNumber();
552 for (
auto *blockOperand : predWorklist) {
553 auto *op = blockOperand->getOwner();
554 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
556 auto operand = branchOp.getDestOperands()[argIdx];
557 if (seen.insert(operand).second)
558 worklist.push_back(operand);
559 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
561 unsigned destIdx = blockOperand->getOperandNumber();
562 auto operand = destIdx == 0
563 ? condBranchOp.getTrueDestOperands()[argIdx]
564 : condBranchOp.getFalseDestOperands()[argIdx];
568 if ((matchPattern(operand, m_One()) && destIdx == 0) ||
569 (matchPattern(operand, m_Zero()) && destIdx == 1))
570 operand = condBranchOp.getCondition();
572 if (seen.insert(operand).second)
573 worklist.push_back(operand);
575 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
576 <<
": unsupported terminator " << op->getName()
577 <<
" while tracing past value\n");
585 if (distinctValues.size() != 1) {
588 <<
"Skipping " << process.getLoc()
589 <<
": multiple past values passed for the same block argument\n");
592 auto distinctValue = *distinctValues.begin();
593 if (!triggers.contains(getValueField(distinctValue))) {
594 LLVM_DEBUG(llvm::dbgs() <<
"Skipping " << process.getLoc()
595 <<
": unobserved past value\n");
598 return distinctValue;
609TruthTable Deseq::computeBoolean(Value value) {
610 return computeBoolean(getValueField(value));
613TruthTable Deseq::computeBoolean(ValueField vf) {
615 return getUnknownBoolean();
619 if (
auto it = booleanLattice.find(vf); it != booleanLattice.end())
626 return computeBoolean(
628 return getUnknownBoolean();
631 Value value = vf.
value;
632 assert(value.getType().isSignlessInteger(1));
636 if (value.getDefiningOp() == process)
637 return computeBoolean(
638 wait.getYieldOperands()[cast<OpResult>(value).getResultNumber()]);
642 booleanLattice[vf] = getUnknownBoolean();
646 TypeSwitch<Value, TruthTable>(value).Case<OpResult, BlockArgument>(
647 [&](
auto value) {
return computeBoolean(value); });
651 llvm::dbgs() <<
"- Boolean ";
652 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
653 llvm::dbgs() <<
": " << result <<
"\n";
655 booleanLattice[vf] = result;
662ValueTable Deseq::computeValue(Value value) {
663 auto vf = getValueField(value);
674 if (value.getDefiningOp() == process)
676 wait.getYieldOperands()[cast<OpResult>(value).getResultNumber()]);
681 if (
auto it = valueLattice.find(value); it != valueLattice.end())
683 valueLattice[value] = getUnknownValue();
687 TypeSwitch<Value, ValueTable>(value).Case<OpResult, BlockArgument>(
688 [&](
auto value) {
return computeValue(value); });
692 llvm::dbgs() <<
"- Value ";
693 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
694 llvm::dbgs() <<
": " << result <<
"\n";
696 valueLattice[value] = result;
701TruthTable Deseq::computeBoolean(OpResult value) {
702 assert(value.getType().isSignlessInteger(1));
703 auto *op = value.getOwner();
706 if (
auto constOp = dyn_cast<hw::ConstantOp>(op))
707 return getConstBoolean(constOp.getValue().isOne());
710 if (
auto orOp = dyn_cast<comb::OrOp>(op)) {
711 auto result = getConstBoolean(
false);
712 for (
auto operand : orOp.getInputs()) {
713 result |= computeBoolean(operand);
721 if (
auto andOp = dyn_cast<comb::AndOp>(op)) {
722 auto result = getConstBoolean(
true);
723 for (
auto operand : andOp.getInputs()) {
724 result &= computeBoolean(operand);
725 if (result.isFalse())
732 if (
auto xorOp = dyn_cast<comb::XorOp>(op)) {
733 auto result = getConstBoolean(
false);
734 for (
auto operand : xorOp.getInputs())
735 result ^= computeBoolean(operand);
742 if (llvm::any_of(op->getOperands(), [&](
auto operand) {
745 if (!operand.getType().isSignlessInteger(1))
747 auto result = computeBoolean(operand);
748 return result.isPoison() || (result != getUnknownBoolean() &&
749 !result.isTrue() && !result.isFalse());
751 return getPoisonBoolean();
752 return getUnknownBoolean();
757ValueTable Deseq::computeValue(OpResult value) {
758 auto *op = value.getOwner();
761 if (isa<comb::MuxOp, arith::SelectOp>(op)) {
762 auto condition = computeBoolean(op->getOperand(0));
763 auto trueValue = computeValue(op->getOperand(1));
764 auto falseValue = computeValue(op->getOperand(2));
765 trueValue.addCondition(condition);
766 falseValue.addCondition(~condition);
767 trueValue.merge(std::move(falseValue));
772 return getKnownValue(value);
776TruthTable Deseq::computeBoolean(BlockArgument arg) {
777 auto *block = arg.getOwner();
780 if (block->getParentOp() != process)
781 return getUnknownBoolean();
785 auto result = getConstBoolean(
false);
786 SmallPtrSet<Block *, 4> seen;
787 for (
auto *predecessor : block->getPredecessors()) {
788 if (!seen.insert(predecessor).second)
790 for (
auto &operand : predecessor->getTerminator()->getBlockOperands()) {
791 if (operand.get() != block)
793 auto value = computeSuccessorBoolean(operand, arg.getArgNumber());
796 auto condition = computeSuccessorCondition(operand);
797 result |= value & condition;
809ValueTable Deseq::computeValue(BlockArgument arg) {
810 auto *block = arg.getOwner();
813 if (block->getParentOp() != process)
814 return getKnownValue(arg);
818 auto result = ValueTable();
819 SmallPtrSet<Block *, 4> seen;
820 for (
auto *predecessor : block->getPredecessors()) {
821 if (!seen.insert(predecessor).second)
823 for (
auto &operand : predecessor->getTerminator()->getBlockOperands()) {
824 if (operand.get() != block)
826 auto condition = computeSuccessorCondition(operand);
827 if (condition.isFalse())
829 auto value = computeSuccessorValue(operand, arg.getArgNumber());
830 value.addCondition(condition);
839TruthTable Deseq::computeBlockCondition(Block *block) {
842 if (
auto it = blockConditionLattice.find(block);
843 it != blockConditionLattice.end())
845 blockConditionLattice[block] = getConstBoolean(
false);
849 auto result = getConstBoolean(
false);
850 SmallPtrSet<Block *, 4> seen;
851 for (
auto *predecessor : block->getPredecessors()) {
852 if (!seen.insert(predecessor).second)
854 for (
auto &operand : predecessor->getTerminator()->getBlockOperands()) {
855 if (operand.get() != block)
857 result |= computeSuccessorCondition(operand);
867 llvm::dbgs() <<
"- Block condition ";
868 block->printAsOperand(llvm::dbgs());
869 llvm::dbgs() <<
": " << result <<
"\n";
871 blockConditionLattice[block] = result;
877TruthTable Deseq::computeSuccessorCondition(BlockOperand &blockOperand) {
882 auto *op = blockOperand.getOwner();
884 return getConstBoolean(
true);
888 if (
auto it = successorConditionLattice.find(&blockOperand);
889 it != successorConditionLattice.end())
891 successorConditionLattice[&blockOperand] = getConstBoolean(
false);
895 auto destIdx = blockOperand.getOperandNumber();
896 auto blockCondition = computeBlockCondition(op->getBlock());
897 auto result = getUnknownBoolean();
898 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
899 result = blockCondition;
900 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
901 auto branchCondition = computeBoolean(condBranchOp.getCondition());
903 result = blockCondition & branchCondition;
905 result = blockCondition & ~branchCondition;
907 result = getPoisonBoolean();
912 llvm::dbgs() <<
"- Successor condition ";
913 op->getBlock()->printAsOperand(llvm::dbgs());
914 llvm::dbgs() <<
"#succ" << destIdx <<
" -> ";
915 blockOperand.get()->printAsOperand(llvm::dbgs());
916 llvm::dbgs() <<
" = " << result <<
"\n";
918 successorConditionLattice[&blockOperand] = result;
924TruthTable Deseq::computeSuccessorBoolean(BlockOperand &blockOperand,
928 if (
auto it = successorBooleanLattice.find({&blockOperand, argIdx});
929 it != successorBooleanLattice.end())
931 successorBooleanLattice[{&blockOperand, argIdx}] = getUnknownBoolean();
935 auto *op = blockOperand.getOwner();
936 auto destIdx = blockOperand.getOperandNumber();
937 auto result = getUnknownBoolean();
938 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
939 result = computeBoolean(branchOp.getDestOperands()[argIdx]);
940 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
942 result = computeBoolean(condBranchOp.getTrueDestOperands()[argIdx]);
944 result = computeBoolean(condBranchOp.getFalseDestOperands()[argIdx]);
946 result = getPoisonBoolean();
951 llvm::dbgs() <<
"- Successor boolean ";
952 op->getBlock()->printAsOperand(llvm::dbgs());
953 llvm::dbgs() <<
"#succ" << destIdx <<
" -> ";
954 blockOperand.get()->printAsOperand(llvm::dbgs());
955 llvm::dbgs() <<
"#arg" << argIdx <<
" = " << result <<
"\n";
957 successorBooleanLattice[{&blockOperand, argIdx}] = result;
964ValueTable Deseq::computeSuccessorValue(BlockOperand &blockOperand,
968 if (
auto it = successorValueLattice.find({&blockOperand, argIdx});
969 it != successorValueLattice.end())
971 successorValueLattice[{&blockOperand, argIdx}] = getUnknownValue();
975 auto *op = blockOperand.getOwner();
976 auto destIdx = blockOperand.getOperandNumber();
977 auto result = getUnknownValue();
978 if (
auto branchOp = dyn_cast<cf::BranchOp>(op)) {
979 result = computeValue(branchOp.getDestOperands()[argIdx]);
980 }
else if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
982 result = computeValue(condBranchOp.getTrueDestOperands()[argIdx]);
984 result = computeValue(condBranchOp.getFalseDestOperands()[argIdx]);
986 result = getPoisonValue();
991 llvm::dbgs() <<
"- Successor value ";
992 op->getBlock()->printAsOperand(llvm::dbgs());
993 llvm::dbgs() <<
"#succ" << destIdx <<
" -> ";
994 blockOperand.get()->printAsOperand(llvm::dbgs());
995 llvm::dbgs() <<
"#arg" << argIdx <<
" = " << result <<
"\n";
997 successorValueLattice[{&blockOperand, argIdx}] = result;
1008bool Deseq::matchDrives() {
1009 for (
auto &drive : driveInfos)
1010 if (!matchDrive(drive))
1019bool Deseq::matchDrive(DriveInfo &drive) {
1020 LLVM_DEBUG(llvm::dbgs() <<
"- Analyzing " << drive.
op <<
"\n");
1023 auto condition = computeBoolean(drive.
op.getEnable());
1024 if (condition.isPoison()) {
1025 LLVM_DEBUG(llvm::dbgs()
1026 <<
"- Aborting: poison condition on " << drive.
op <<
"\n");
1031 auto initialValueTable = computeValue(drive.
op.getValue());
1032 initialValueTable.addCondition(condition);
1034 llvm::dbgs() <<
" - Condition: " << condition <<
"\n";
1035 llvm::dbgs() <<
" - Value: " << initialValueTable <<
"\n";
1041 SmallVector<std::pair<DNFTerm, ValueEntry>> valueTable;
1042 for (
auto &[condition, value] : initialValueTable.entries) {
1043 auto dnf = condition.canonicalize();
1044 if (dnf.isPoison() || value.isPoison()) {
1045 LLVM_DEBUG(llvm::dbgs()
1046 <<
"- Aborting: poison in " << initialValueTable <<
"\n");
1049 for (
auto &orTerm : dnf.orTerms)
1050 valueTable.push_back({orTerm, value});
1056 if (valueTable.size() > 3) {
1057 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: value table has "
1058 << valueTable.size() <<
" distinct conditions\n");
1063 if (triggers.size() == 2)
1064 return matchDriveClockAndReset(drive, valueTable);
1067 assert(triggers.size() == 1);
1068 return matchDriveClock(drive, valueTable);
1073bool Deseq::matchDriveClock(
1074 DriveInfo &drive, ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable) {
1077 if (valueTable.size() != 1) {
1078 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: single trigger value table has "
1079 << valueTable.size() <<
" entries\n");
1084 for (
unsigned variant = 0; variant < (1 << 1); ++variant) {
1085 bool negClock = (variant >> 0) & 1;
1093 uint32_t clockEdge = (negClock ? 0b1001 : 0b0110) << 2;
1094 auto clockWithoutEnable = DNFTerm{clockEdge};
1095 auto clockWithEnable = DNFTerm{clockEdge | 0b01};
1098 if (valueTable[0].first == clockWithEnable)
1100 else if (valueTable[0].first != clockWithoutEnable)
1104 drive.
clock.
clock = triggers[0].getProjected();
1107 if (!valueTable[0].second.isUnknown())
1108 drive.
clock.
value = valueTable[0].second.value;
1111 llvm::dbgs() <<
" - Matched " << (negClock ?
"neg" :
"pos")
1113 drive.
clock.
clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1114 llvm::dbgs() <<
" -> " << valueTable[0].second;
1116 llvm::dbgs() <<
" (with enable)";
1117 llvm::dbgs() <<
"\n";
1123 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: unknown clock scheme\n");
1130bool Deseq::matchDriveClockAndReset(
1131 DriveInfo &drive, ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable) {
1135 if (valueTable.size() != 2 && valueTable.size() != 3) {
1136 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: two trigger value table has "
1137 << valueTable.size() <<
" entries\n");
1144 for (
unsigned variant = 0; variant < (1 << 3); ++variant) {
1145 bool negClock = (variant >> 0) & 1;
1146 bool negReset = (variant >> 1) & 1;
1147 unsigned clockIdx = (variant >> 2) & 1;
1148 unsigned resetIdx = 1 - clockIdx;
1155 uint32_t clockEdge = (negClock ? 0b1001 : 0b0110) << (clockIdx * 4 + 2);
1156 uint32_t resetEdge = (negReset ? 0b1001 : 0b0110) << (resetIdx * 4 + 2);
1157 uint32_t resetOn = (negReset ? 0b1000 : 0b0100) << (resetIdx * 4 + 2);
1158 uint32_t resetOff = (negReset ? 0b0100 : 0b1000) << (resetIdx * 4 + 2);
1163 auto reset = DNFTerm{resetEdge};
1164 auto clockWhileReset = DNFTerm{clockEdge | resetOn};
1165 auto clockWithoutEnable = DNFTerm{clockEdge | resetOff};
1166 auto clockWithEnable = DNFTerm{clockEdge | resetOff | 0b01};
1169 const auto *resetIt = llvm::find_if(
1170 valueTable, [&](
auto &pair) {
return pair.first == reset; });
1171 if (resetIt == valueTable.end())
1174 const auto *clockWhileResetIt = llvm::find_if(
1175 valueTable, [&](
auto &pair) {
return pair.first == clockWhileReset; });
1176 if (clockWhileResetIt == valueTable.end())
1179 const auto *clockIt = llvm::find_if(valueTable, [&](
auto &pair) {
1180 return pair.first == clockWithoutEnable || pair.first == clockWithEnable;
1182 bool clockHolds = clockIt == valueTable.end();
1183 if (clockHolds && valueTable.size() != 2)
1189 if (clockWhileResetIt->second != resetIt->second ||
1190 resetIt->second.isUnknown()) {
1191 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: inconsistent reset value\n");
1196 drive.
reset.
reset = triggers[resetIdx].getProjected();
1200 drive.
clock.
clock = triggers[clockIdx].getProjected();
1206 if (clockIt->first == clockWithEnable)
1208 if (!clockIt->second.isUnknown())
1213 llvm::dbgs() <<
" - Matched " << (negClock ?
"neg" :
"pos")
1215 drive.
clock.
clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1217 llvm::dbgs() <<
" -> hold";
1219 llvm::dbgs() <<
" -> " << clockIt->second;
1221 llvm::dbgs() <<
" (with enable)";
1222 llvm::dbgs() <<
"\n";
1223 llvm::dbgs() <<
" - Matched active-" << (negReset ?
"low" :
"high")
1225 drive.
reset.
reset.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1226 llvm::dbgs() <<
" -> " << resetIt->second <<
"\n";
1232 LLVM_DEBUG(llvm::dbgs() <<
"- Aborting: unknown reset scheme\n");
1240Value Deseq::materializeProjection(OpBuilder &builder, Location loc,
1242 SmallDenseMap<Value, Value, 8> &cache) {
1247 auto isInThisProcess = [&](Value v) {
1248 if (
auto arg = dyn_cast<BlockArgument>(v)) {
1249 Operation *parentOp = arg.getOwner()->getParentOp();
1252 return parentOp == process.getOperation() ||
1253 parentOp->getParentOfType<ProcessOp>() == process;
1255 if (
auto *defOp = v.getDefiningOp())
1256 return defOp->getParentOfType<ProcessOp>() == process;
1259 if (!isInThisProcess(value))
1262 if (
auto it = cache.find(value); it != cache.end())
1267 if (
auto arg = dyn_cast<BlockArgument>(value)) {
1268 SmallPtrSet<Block *, 4> visited;
1269 Value canon = canonicalizeBlockArg(arg, visited);
1272 auto remat = materializeProjection(builder, loc, canon, cache);
1273 cache.insert({value, remat});
1277 auto *defOp = value.getDefiningOp();
1282 if (
auto ext = dyn_cast<comb::ExtractOp>(defOp)) {
1283 Value input = materializeProjection(builder, loc, ext.getInput(), cache);
1286 cache.insert({value, remat});
1289 if (
auto get = dyn_cast<hw::ArrayGetOp>(defOp)) {
1290 Value input = materializeProjection(builder, loc,
get.getInput(), cache);
1291 Value index = materializeProjection(builder, loc,
get.getIndex(), cache);
1293 cache.insert({value, remat});
1296 if (
auto slice = dyn_cast<hw::ArraySliceOp>(defOp)) {
1297 Value input = materializeProjection(builder, loc, slice.getInput(), cache);
1299 materializeProjection(builder, loc, slice.getLowIndex(), cache);
1302 cache.insert({value, remat});
1305 if (
auto se = dyn_cast<hw::StructExtractOp>(defOp)) {
1306 Value input = materializeProjection(builder, loc, se.getInput(), cache);
1309 cache.insert({value, remat});
1312 if (
auto cst = dyn_cast<hw::ConstantOp>(defOp)) {
1314 builder, loc, cst.getResult().getType(), cst.getValueAttr());
1315 cache.insert({value, remat});
1318 if (
auto cst = dyn_cast<arith::ConstantOp>(defOp)) {
1319 auto *cloned = builder.clone(*defOp);
1320 Value remat = cloned->getResult(cast<OpResult>(value).getResultNumber());
1321 cache.insert({value, remat});
1338 auto sigOp = drive.
op.getDriven().getDefiningOp<SignalOp>();
1339 SmallVector<std::pair<DriveLikeOp, IntegerAttr>> constantDrives;
1344 TimeAttr driveTimeAttr;
1345 if (drive.
op.getTime())
1346 matchPattern(drive.
op.getTime(), m_Constant(&driveTimeAttr));
1347 auto isConcurrent = [&](DriveLikeOp otherDrive) ->
bool {
1348 if (otherDrive.getTime() == drive.
op.getTime())
1351 return driveTimeAttr && otherDrive.getTime() &&
1352 matchPattern(otherDrive.getTime(), m_Constant(&timeCst)) &&
1353 timeCst == driveTimeAttr;
1357 for (
auto &use : drive.
op.getDriven().getUses()) {
1358 auto driveUser = dyn_cast<DriveLikeOp>(use.getOwner());
1359 if (!driveUser || driveUser == drive.
op ||
1360 use != driveUser.getDrivenMutable()[0])
1363 bool isSameBlock = driveUser->getBlock() == drive.
op->getBlock();
1364 bool isUnconditional = !driveUser.getEnable();
1365 if (!(isSameBlock && isUnconditional && isConcurrent(driveUser)))
1368 IntegerAttr driveCst;
1369 if (matchPattern(driveUser.getValue(), m_Constant(&driveCst)))
1370 constantDrives.push_back({driveUser, driveCst});
1376 if (!constantDrives.empty()) {
1378 bool isConflicted = !llvm::all_equal(llvm::map_range(
1379 constantDrives, [](
auto drivePair) {
return drivePair.second; }));
1381 return drive.
op->emitError(
1382 "inferred register has conflicting initial drivers.");
1383 preset = constantDrives.front().second;
1386 for (
auto [initDriveOp, _] : constantDrives)
1387 initDriveOp->erase();
1388 }
else if (sigOp && sigOp.getInit()) {
1392 if (!matchPattern(sigOp.getInit(), m_Constant(&preset)))
1393 return sigOp->emitError(
"cannot lower a nonconstant signal initializer "
1394 "to a register preset");
1398 regOp.setPresetAttr(preset);
1404LogicalResult Deseq::implementRegisters() {
1405 bool anyFailed =
false;
1406 for (
auto &drive : driveInfos)
1407 anyFailed |= failed(implementRegister(drive));
1408 return llvm::success(!anyFailed);
1418LogicalResult Deseq::implementRegister(DriveInfo &drive) {
1419 OpBuilder builder(drive.
op);
1420 auto loc = drive.
op.getLoc();
1424 SmallDenseMap<Value, Value, 8> rematerialized;
1428 materializeProjection(builder, loc, drive.
clock.
clock, rematerialized);
1430 auto &clockCast = materializedClockCasts[clockValue];
1432 clockCast = seq::ToClockOp::create(builder, loc, clockValue);
1433 auto clock = clockCast;
1435 auto &clockInv = materializedClockInverters[clock];
1437 clockInv = seq::ClockInverterOp::create(builder, loc, clock);
1447 materializeProjection(builder, loc, drive.
reset.
reset, rematerialized);
1453 auto &inv = materializedInverters[reset];
1456 inv = comb::XorOp::create(builder, loc, reset, one);
1464 if (!resetValue.getParentRegion()->isProperAncestor(&process.getBody())) {
1465 if (
auto *defOp = resetValue.getDefiningOp();
1466 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1467 defOp->moveBefore(process);
1469 resetValue = specializeValue(
1470 drive.
op.getValue(),
1471 FixedValues{{drive.clock.clock, !drive.clock.risingEdge,
1472 !drive.clock.risingEdge},
1473 {drive.reset.reset, !drive.reset.activeHigh,
1474 drive.reset.activeHigh}});
1482 if (enable && !enable.getParentRegion()->isProperAncestor(&process.getBody()))
1483 enable = drive.
op.getEnable();
1489 if (!value.getParentRegion()->isProperAncestor(&process.getBody())) {
1490 if (
auto *defOp = value.getDefiningOp();
1491 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1492 defOp->moveBefore(process);
1494 value = drive.
op.getValue();
1500 fixedValues.push_back(
1503 fixedValues.push_back(
1506 value = specializeValue(value, fixedValues);
1508 enable = specializeValue(enable, fixedValues);
1512 if (
auto sigOp = drive.
op.getDriven().getDefiningOp<llhd::SignalOp>())
1513 name = sigOp.getNameAttr();
1515 name = builder.getStringAttr(
"");
1518 auto reg = seq::FirRegOp::create(builder, loc, value, clock, name,
1519 hw::InnerSymAttr{}, IntegerAttr{},
1528 OpBuilder::InsertionGuard guard(builder);
1529 builder.setInsertionPoint(reg);
1530 reg.getNextMutable().assign(comb::MuxOp::create(
1531 builder, loc, enable,
reg.getNext(),
reg.getResult(),
true));
1535 drive.
op.getValueMutable().assign(reg);
1536 drive.
op.getEnableMutable().clear();
1541 if (drive.
op.getTime() &&
1542 matchPattern(drive.
op.getTime(), m_Constant(&attr)) &&
1543 attr.getTime() == 0 && attr.getDelta() == 1) {
1545 AssignOp::create(builder, drive.
op.getLoc(), drive.
op.getDriven(),
1546 drive.
op.getValue(), Value{});
1548 drive.
op = assignOp;
1552 if (isa<IntegerType>(
reg.getType()))
1568Value Deseq::specializeValue(Value value,
FixedValues fixedValues) {
1569 auto result = dyn_cast<OpResult>(value);
1570 if (!result || result.getOwner() != process)
1572 return specializeProcess(fixedValues)[result.getResultNumber()];
1583ValueRange Deseq::specializeProcess(
FixedValues fixedValues) {
1584 if (
auto it = specializedProcesses.find(fixedValues);
1585 it != specializedProcesses.end())
1589 llvm::dbgs() <<
"- Specializing process for:\n";
1590 for (
auto fixedValue : fixedValues) {
1591 llvm::dbgs() <<
" - ";
1592 fixedValue.value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1593 llvm::dbgs() <<
": " << fixedValue.past <<
" -> " << fixedValue.present
1602 OpBuilder builder(process);
1603 auto executeOp = CombinationalOp::create(builder, process.getLoc(),
1604 process.getResultTypes());
1607 SmallVector<std::pair<Block *, Block *>> worklist;
1609 auto scheduleBlock = [&](
Block *block) {
1610 if (
auto *newBlock = mapping.lookupOrNull(block))
1612 auto *newBlock = &executeOp.getRegion().emplaceBlock();
1613 for (
auto arg : block->getArguments()) {
1614 auto newArg = newBlock->addArgument(arg.getType(), arg.getLoc());
1615 mapping.map(arg, newArg);
1617 mapping.map(block, newBlock);
1618 worklist.push_back({block, newBlock});
1623 auto &entryBlock = executeOp.getRegion().emplaceBlock();
1624 builder.setInsertionPointToStart(&entryBlock);
1625 auto i1 = builder.getI1Type();
1629 SmallDenseMap<Value, std::pair<Value, Value>, 2> materializedFixedValues;
1630 for (
auto fixedValue : fixedValues) {
1631 auto present = fixedValue.present ? trueValue : falseValue;
1632 auto past = fixedValue.past ? trueValue : falseValue;
1633 materializedFixedValues.insert({fixedValue.value, {past, present}});
1634 mapping.map(fixedValue.value, present);
1639 auto fixedTable = getConstBoolean(
true);
1640 for (
auto [index, trigger] : llvm::enumerate(triggers)) {
1641 for (
auto fixedValue : fixedValues) {
1642 if (getValueField(fixedValue.value) != trigger)
1644 auto past = getPastTrigger(index);
1645 fixedTable &= fixedValue.past ? past : ~past;
1646 auto present = getPresentTrigger(index);
1647 fixedTable &= fixedValue.present ? present : ~present;
1654 SmallVector<Value> foldedResults;
1655 while (!worklist.empty()) {
1656 auto [oldBlock, newBlock] = worklist.pop_back_val();
1657 builder.setInsertionPointToEnd(newBlock);
1658 for (
auto &oldOp : *oldBlock) {
1660 if (
auto waitOp = dyn_cast<WaitOp>(oldOp)) {
1663 SmallVector<Value> operands;
1664 for (
auto operand : waitOp.getYieldOperands())
1665 operands.push_back(mapping.lookupOrDefault(operand));
1666 YieldOp::create(builder, waitOp.getLoc(), operands);
1671 if (
auto condBranchOp = dyn_cast<cf::CondBranchOp>(oldOp)) {
1672 SmallVector<Value> operands;
1673 auto condition = mapping.lookupOrDefault(condBranchOp.getCondition());
1674 if (matchPattern(condition, m_NonZero())) {
1675 for (
auto operand : condBranchOp.getTrueDestOperands())
1676 operands.push_back(mapping.lookupOrDefault(operand));
1677 cf::BranchOp::create(builder, condBranchOp.getLoc(),
1678 scheduleBlock(condBranchOp.getTrueDest()),
1682 if (matchPattern(condition, m_Zero())) {
1683 for (
auto operand : condBranchOp.getFalseOperands())
1684 operands.push_back(mapping.lookupOrDefault(operand));
1685 cf::BranchOp::create(builder, condBranchOp.getLoc(),
1686 scheduleBlock(condBranchOp.getFalseDest()),
1695 if (oldOp.getNumResults() == 1 &&
1696 oldOp.getResult(0).getType().isSignlessInteger(1)) {
1697 if (
auto it = booleanLattice.find(getValueField(oldOp.getResult(0)));
1698 it != booleanLattice.end()) {
1699 if ((it->second & fixedTable).isFalse()) {
1700 mapping.map(oldOp.getResult(0), falseValue);
1703 if ((it->second & fixedTable) == fixedTable) {
1704 mapping.map(oldOp.getResult(0), trueValue);
1711 for (
auto &blockOperand : oldOp.getBlockOperands())
1712 scheduleBlock(blockOperand.get());
1713 auto *clonedOp = builder.clone(oldOp, mapping);
1717 if (succeeded(builder.tryFold(clonedOp, foldedResults)) &&
1718 !foldedResults.empty()) {
1719 for (
auto [oldResult, foldedResult] :
1720 llvm::zip(oldOp.getResults(), foldedResults))
1721 mapping.map(oldResult, foldedResult);
1724 foldedResults.clear();
1731 worklist.push_back({&process.getBody().front(), &entryBlock});
1733 builder.setInsertionPointToEnd(mapping.lookup(wait->getBlock()));
1738 for (
auto &block : process.getBody())
1739 mapping.erase(&block);
1745 if (wait.getDest()->hasOneUse()) {
1748 for (
auto [arg, pastValue] :
1749 llvm::zip(wait.getDest()->getArguments(), pastValues))
1750 mapping.map(arg, materializedFixedValues.lookup(pastValue).first);
1753 mapping.map(wait.getDest(), builder.getBlock());
1754 worklist.push_back({wait.getDest(), builder.getBlock()});
1757 auto *dest = scheduleBlock(wait.getDest());
1761 SmallVector<Value> destOperands;
1762 assert(pastValues.size() == wait.getDestOperands().size());
1763 for (
auto pastValue : pastValues)
1764 destOperands.push_back(materializedFixedValues.lookup(pastValue).first);
1765 cf::BranchOp::create(builder, wait.getLoc(), dest, destOperands);
1772 if (isOpTriviallyDead(trueValue))
1774 if (isOpTriviallyDead(falseValue))
1777 specializedProcesses.insert({fixedValues, executeOp.getResults()});
1778 return executeOp.getResults();
1786struct DeseqPass :
public llhd::impl::DeseqPassBase<DeseqPass> {
1787 void runOnOperation()
override;
1791void DeseqPass::runOnOperation() {
1792 SmallVector<ProcessOp> processes(getOperation().getOps<ProcessOp>());
1793 for (
auto process : processes)
1794 if (failed(Deseq(process).deseq())) {
1795 signalPassFailure();
assert(baseType &&"element must be base type")
static LogicalResult inferRegisterPreset(seq::FirRegOp regOp, DriveInfo &drive)
Attempt to find and set an preset (initializer) value for the newly created register.
#define VERBOSE_DEBUG(...)
false return failure()
Parse an optional keyword or string and set instance into 'result'.
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)
BaseTy type_dyn_cast(Type type)
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 drive op and the clock and reset that resulted from trigger analysis.
ClockInfo clock
The clock that triggers a change to the driven value.
DriveLikeOp op
The drive operation.
ResetInfo reset
The optional reset that triggers a change of the driven value to a fixed reset value.
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.
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
void merge(const ValueTable &other)