22#include "mlir/IR/PatternMatch.h"
23#include "llvm/Support/Debug.h"
25#define DEBUG_TYPE "llhd-desequentialization"
29#define GEN_PASS_DEF_DESEQUENTIALIZATION
30#include "circt/Dialect/LLHD/Transforms/LLHDPasses.h.inc"
48 APInt compute(ArrayRef<Value> primitives, ArrayRef<APInt> truthTable,
49 uint64_t width, Value root) {
50 assert(primitives.size() == truthTable.size() &&
"must have same size");
52 for (
auto [p, t] : llvm::zip(primitives, truthTable))
57 DenseSet<Operation *> visited;
58 SmallVector<Value> worklist;
59 worklist.push_back(root);
61 while (!worklist.empty()) {
62 auto curr = worklist.back();
63 if (results.contains(curr)) {
68 auto *defOp = curr.getDefiningOp();
74 if (!visited.contains(defOp)) {
75 visited.insert(defOp);
77 bool addedToWorklist =
78 TypeSwitch<Operation *, bool>(defOp)
80 worklist.append(llvm::to_vector(op->getOperands()));
83 .Case([&](comb::ICmpOp op) {
84 if ((op.getPredicate() == circt::comb::ICmpPredicate::eq ||
85 op.getPredicate() == circt::comb::ICmpPredicate::ne) &&
86 op.getLhs().getType().isSignlessInteger(1)) {
87 worklist.append(llvm::to_vector(op->getOperands()));
93 results[op.getResult()] = op.getValue().getBoolValue()
94 ? APInt::getAllOnes(width)
103 dispatchCombinationalVisitor(defOp);
107 return results[root];
111 auto res = APInt::getAllOnes(width);
112 for (
auto operand : op->getOperands())
113 res &= results[operand];
114 results[op.getResult()] = res;
118 auto res = APInt(width, 0);
119 for (
auto operand : op->getOperands())
120 res |= results[operand];
121 results[op.getResult()] = res;
125 auto res = results[op->getOperands()[0]];
126 for (
auto operand : op->getOperands().drop_front())
127 res ^= results[operand];
128 results[op.getResult()] = res;
131 void visitComb(comb::ICmpOp op) {
132 auto res = results[op.getLhs()];
133 res ^= results[op.getRhs()];
134 if (op.getPredicate() == comb::ICmpPredicate::eq)
135 res ^= APInt::getAllOnes(width);
136 results[op.getResult()] = res;
141 void visitComb(
comb::AddOp op) { visitInvalidComb(op); }
142 void visitComb(
comb::SubOp op) { visitInvalidComb(op); }
143 void visitComb(
comb::MulOp op) { visitInvalidComb(op); }
144 void visitComb(
comb::DivUOp op) { visitInvalidComb(op); }
145 void visitComb(
comb::DivSOp op) { visitInvalidComb(op); }
146 void visitComb(
comb::ModUOp op) { visitInvalidComb(op); }
147 void visitComb(
comb::ModSOp op) { visitInvalidComb(op); }
148 void visitComb(
comb::ShlOp op) { visitInvalidComb(op); }
149 void visitComb(
comb::ShrUOp op) { visitInvalidComb(op); }
150 void visitComb(
comb::ShrSOp op) { visitInvalidComb(op); }
153 void visitComb(comb::ReplicateOp op) { visitInvalidComb(op); }
155 void visitComb(
comb::MuxOp op) { visitInvalidComb(op); }
156 void visitComb(comb::ReverseOp op) { visitInvalidComb(op); }
159 DenseMap<Value, APInt> results;
172 static StringRef stringify(
const Trigger::Kind &kind) {
174 case Trigger::Kind::PosEdge:
176 case Trigger::Kind::NegEdge:
178 case Trigger::Kind::Edge:
181 llvm::llvm_unreachable_internal(
"all cases considered above");
185 SmallVector<Value> clocks;
189 SmallVector<Kind> kinds;
198 return os << Trigger::stringify(kind);
206 DnfAnalyzer(Value value) : root(value) {
207 assert(value.getType().isSignlessInteger(1) &&
208 "only 1-bit signless integers supported");
211 LogicalResult prepareAnalyzer(function_ref<
bool(Value)> sampledInPast,
212 unsigned maxPrimitives) {
213 DenseSet<Value> alreadyAdded;
215 SmallVector<Value> worklist;
216 worklist.push_back(root);
218 while (!worklist.empty()) {
219 Value curr = worklist.pop_back_val();
220 auto *defOp = curr.getDefiningOp();
222 if (!alreadyAdded.contains(curr)) {
223 primitives.push_back(curr);
224 primitiveSampledInPast.push_back(sampledInPast(curr));
225 alreadyAdded.insert(curr);
230 TypeSwitch<Operation *>(defOp)
232 worklist.append(llvm::to_vector(op->getOperands()));
234 .Case([&](comb::ICmpOp op) {
235 if ((op.getPredicate() == circt::comb::ICmpPredicate::eq ||
236 op.getPredicate() == circt::comb::ICmpPredicate::ne) &&
237 op.getLhs().getType().isSignlessInteger(1)) {
238 worklist.append(llvm::to_vector(op->getOperands()));
240 if (!alreadyAdded.contains(curr)) {
241 primitives.push_back(curr);
242 primitiveSampledInPast.push_back(sampledInPast(curr));
243 alreadyAdded.insert(curr);
247 .Case<hw::ConstantOp>([](
auto op) { })
248 .Default([&](
auto op) {
249 if (!alreadyAdded.contains(curr)) {
250 primitives.push_back(curr);
251 primitiveSampledInPast.push_back(sampledInPast(curr));
252 alreadyAdded.insert(curr);
258 for (
auto val : primitives)
259 llvm::dbgs() <<
" - Primitive variable: " << val <<
"\n";
262 if (primitives.size() > maxPrimitives) {
263 LLVM_DEBUG({ llvm::dbgs() <<
" Too many primitives, skipping...\n"; });
267 this->isClock = SmallVector<bool>(primitives.size(),
false);
268 this->dontCare = SmallVector<APInt>(primitives.size(),
269 APInt(1ULL << primitives.size(), 0));
278 computeTriggers(OpBuilder &builder, Location loc,
279 function_ref<
bool(Value, Value)> sampledFromSameSignal,
280 SmallVectorImpl<Trigger> &triggers) {
287 if (failed(computeClockValuePairs(sampledFromSameSignal)))
291 simplifyTruthTable();
294 llvm::dbgs() <<
" - Truth table:\n";
296 for (
auto [t, d] :
llvm::zip(truthTable, dontCare))
297 llvm::dbgs() <<
" " <<
FVInt(std::move(t), std::move(d)) <<
"\n";
299 SmallVector<char> str;
303 for (
unsigned i = 0; i < result.getBitWidth() - str.size(); ++i)
306 llvm::dbgs() << str <<
"\n";
312 materializeTriggerEnables(builder, loc);
315 extractTriggerList(triggers);
318 canonicalizeTriggerList(triggers, builder, loc);
324 FVInt computeEnableKey(
unsigned tableRow) {
326 for (
unsigned k = 0; k < primitives.size(); ++k) {
327 if (dontCare[k][tableRow])
330 if (primitiveSampledInPast[k])
338 key.
setBit(k, truthTable[k][tableRow]);
344 void extractTriggerList(SmallVectorImpl<Trigger> &triggers) {
345 for (uint64_t i = 0, e = 1ULL << primitives.size(); i < e; ++i) {
349 auto key = computeEnableKey(i);
351 for (
auto clk : clockPairs) {
352 if (dontCare[
clk.second][i] && dontCare[
clk.first][i])
355 trigger.clocks.push_back(primitives[
clk.second]);
356 trigger.kinds.push_back(truthTable[
clk.second][i]
357 ? Trigger::Kind::PosEdge
358 : Trigger::Kind::NegEdge);
360 trigger.enable = enableMap[key];
362 if (!trigger.clocks.empty())
363 triggers.push_back(trigger);
367 void materializeTriggerEnables(OpBuilder &builder, Location loc) {
370 for (uint64_t i = 0, e = 1ULL << primitives.size(); i < e; ++i) {
374 auto key = computeEnableKey(i);
376 if (!enableMap.contains(key)) {
377 SmallVector<Value> conjuncts;
378 for (
unsigned k = 0; k < primitives.size(); ++k) {
382 if (primitiveSampledInPast[k])
390 if (truthTable[k][i]) {
391 conjuncts.push_back(primitives[k]);
395 comb::XorOp::create(builder, loc, primitives[k], trueVal));
397 if (!conjuncts.empty())
399 builder.createOrFold<
comb::AndOp>(loc, conjuncts,
false);
404 LogicalResult computeClockValuePairs(
405 function_ref<
bool(Value, Value)> sampledFromSameSignal) {
406 for (
unsigned k = 0; k < primitives.size(); ++k) {
410 for (
unsigned l = k + 1; l < primitives.size(); ++l) {
411 if (sampledFromSameSignal(primitives[k], primitives[l]) &&
412 (primitiveSampledInPast[k] != primitiveSampledInPast[l])) {
413 if (primitiveSampledInPast[k])
414 clockPairs.emplace_back(k, l);
416 clockPairs.emplace_back(l, k);
421 if (primitiveSampledInPast[k] && !isClock[k])
428 void simplifyTruthTable() {
429 uint64_t numEntries = 1 << primitives.size();
435 for (uint64_t i = 0; i < numEntries; ++i) {
439 for (uint64_t k = i + 1; k < numEntries; ++k) {
443 unsigned differenceCount = 0;
444 for (
unsigned l = 0; l < primitives.size(); ++l) {
445 if (truthTable[l][i] != truthTable[l][k])
447 if (differenceCount > 1)
451 if (differenceCount == 1) {
452 for (
unsigned l = 0; l < primitives.size(); ++l) {
453 dontCare[l].setBit(k);
454 if (truthTable[l][i] != truthTable[l][k])
455 dontCare[l].setBit(i);
463 uint64_t numEntries = 1 << primitives.size();
464 for (
auto _ [[maybe_unused]] : primitives)
465 truthTable.push_back(APInt(numEntries, 0));
467 for (uint64_t i = 0; i < numEntries; ++i)
468 for (
unsigned k = 0; k < primitives.size(); ++k)
469 truthTable[k].setBitVal(i, APInt(64, i)[k]);
472 CombInterpreter().compute(primitives, truthTable, numEntries, root);
475 void canonicalizeTriggerList(SmallVectorImpl<Trigger> &triggers,
476 OpBuilder &builder, Location loc) {
477 for (
auto *iter1 = triggers.begin(); iter1 != triggers.end(); ++iter1) {
478 for (
auto *iter2 = iter1 + 1; iter2 != triggers.end(); ++iter2) {
479 if (iter1->clocks == iter2->clocks && iter1->kinds == iter2->kinds) {
481 comb::OrOp::create(builder, loc, iter1->enable, iter2->enable);
482 triggers.erase(iter2--);
492 SmallVector<Value> primitives;
493 SmallVector<bool> isClock;
494 SmallVector<bool> primitiveSampledInPast;
495 SmallVector<APInt> truthTable;
496 SmallVector<APInt> dontCare;
497 SmallVector<std::pair<unsigned, unsigned>> clockPairs;
498 DenseMap<FVInt, Value> enableMap;
502struct DesequentializationPass
503 :
public llhd::impl::DesequentializationBase<DesequentializationPass> {
504 DesequentializationPass()
505 : llhd::impl::DesequentializationBase<DesequentializationPass>() {}
506 DesequentializationPass(
const llhd::DesequentializationOptions &options)
507 : llhd::impl::DesequentializationBase<DesequentializationPass>(options) {
508 maxPrimitives.setValue(options.maxPrimitives);
510 void runOnOperation()
override;
511 void runOnProcess(llhd::ProcessOp procOp)
const;
513 isSupportedSequentialProcess(llhd::ProcessOp procOp,
515 SmallVectorImpl<Value> &observed)
const;
519LogicalResult DesequentializationPass::isSupportedSequentialProcess(
521 SmallVectorImpl<Value> &observed)
const {
530 llvm::dbgs() <<
" Combinational process -> no need to desequentialize\n";
535 if (numTRs > 2 || procOp.getBody().getBlocks().size() != 3) {
537 { llvm::dbgs() <<
" Complex sequential process -> not supported\n"; });
541 bool seenWait =
false;
542 WalkResult result = procOp.walk([&](llhd::WaitOp op) -> WalkResult {
543 LLVM_DEBUG({ llvm::dbgs() <<
" Analyzing Wait Operation:\n"; });
544 for (
auto obs : op.getObserved()) {
545 observed.push_back(obs);
546 LLVM_DEBUG({ llvm::dbgs() <<
" - Observes: " << obs <<
"\n"; });
548 LLVM_DEBUG({ llvm::dbgs() <<
"\n"; });
556 trAnalysis.
getBlockTR(op.getOperation()->getBlock())))
560 return WalkResult::advance();
563 if (result.wasInterrupted() || !seenWait) {
565 { llvm::dbgs() <<
" Complex sequential process -> not supported\n"; });
570 { llvm::dbgs() <<
" Sequential process, attempt lowering...\n"; });
575void DesequentializationPass::runOnProcess(llhd::ProcessOp procOp)
const {
577 std::string line(74,
'-');
578 llvm::dbgs() <<
"\n===" << line <<
"===\n";
579 llvm::dbgs() <<
"=== Process\n";
580 llvm::dbgs() <<
"===" << line <<
"===\n";
586 SmallVector<Value> observed;
587 if (failed(isSupportedSequentialProcess(procOp, trAnalysis, observed)))
590 OpBuilder builder(procOp);
591 WalkResult result = procOp.walk([&](llhd::DrvOp op) {
592 LLVM_DEBUG({ llvm::dbgs() <<
"\n Lowering Drive Operation\n"; });
594 if (!op.getEnable()) {
595 LLVM_DEBUG({ llvm::dbgs() <<
" - No enable condition -> skip\n"; });
596 return WalkResult::advance();
599 Location loc = op.getLoc();
600 builder.setInsertionPoint(op);
601 int presentTR = trAnalysis.
getBlockTR(op.getOperation()->getBlock());
603 auto sampledInPast = [&](Value value) ->
bool {
604 if (isa<BlockArgument>(value))
607 if (!procOp->isAncestor(value.getDefiningOp()))
610 return trAnalysis.
getBlockTR(value.getDefiningOp()->getBlock()) !=
614 LLVM_DEBUG({ llvm::dbgs() <<
" - Analyzing enable condition...\n"; });
616 SmallVector<Trigger> triggers;
617 auto sampledFromSameSignal = [](Value val1, Value val2) ->
bool {
618 if (
auto prb1 = val1.getDefiningOp<llhd::PrbOp>())
619 if (
auto prb2 = val2.getDefiningOp<llhd::PrbOp>())
620 return prb1.getSignal() == prb2.getSignal();
627 DnfAnalyzer analyzer(op.getEnable());
628 if (failed(analyzer.prepareAnalyzer(sampledInPast, maxPrimitives)) ||
629 failed(analyzer.computeTriggers(builder, loc, sampledFromSameSignal,
632 llvm::dbgs() <<
" Unable to compute trigger list for drive condition, "
635 return WalkResult::interrupt();
639 if (triggers.empty())
640 llvm::dbgs() <<
" - no triggers found!\n";
644 for (
auto trigger : triggers) {
645 llvm::dbgs() <<
" - Trigger\n";
646 for (
auto [clk, kind] :
llvm::zip(trigger.clocks, trigger.kinds))
647 llvm::dbgs() <<
" - " << kind <<
" "
648 <<
"clock: " <<
clk <<
"\n";
651 llvm::dbgs() <<
" with enable: " << trigger.enable <<
"\n";
656 if (triggers.size() > 2 || triggers.empty())
657 return WalkResult::interrupt();
660 if (triggers[0].clocks.size() != 1 || triggers[0].clocks.size() != 1)
661 return WalkResult::interrupt();
664 if (triggers[0].kinds[0] == Trigger::Kind::Edge)
665 return WalkResult::interrupt();
667 if (!llvm::any_of(observed, [&](Value val) {
668 return sampledFromSameSignal(val, triggers[0].clocks[0]) &&
669 val.getParentRegion() != procOp.getBody();
671 return WalkResult::interrupt();
673 Value clock = seq::ToClockOp::create(builder, loc, triggers[0].clocks[0]);
674 Value reset, resetValue;
676 if (triggers[0].kinds[0] == Trigger::Kind::NegEdge)
677 clock = seq::ClockInverterOp::create(builder, loc, clock);
679 if (triggers[0].enable)
680 clock = seq::ClockGateOp::create(builder, loc, clock, triggers[0].enable);
682 if (triggers.size() == 2) {
684 if (triggers[1].clocks.size() != 1 || triggers[1].kinds.size() != 1)
685 return WalkResult::interrupt();
688 if (triggers[1].kinds[0] == Trigger::Kind::Edge)
689 return WalkResult::interrupt();
692 if (triggers[1].enable)
693 return WalkResult::interrupt();
695 if (!llvm::any_of(observed, [&](Value val) {
696 return sampledFromSameSignal(val, triggers[1].clocks[0]) &&
697 val.getParentRegion() != procOp.getBody();
699 return WalkResult::interrupt();
701 reset = triggers[1].clocks[0];
702 resetValue = op.getValue();
704 if (triggers[1].kinds[0] == Trigger::Kind::NegEdge) {
707 reset = comb::XorOp::create(builder, loc, reset, trueVal);
716 op.getEnableMutable().clear();
717 op.getValueMutable().assign(regOut);
719 llhd::ConstantTimeOp::create(builder, loc, 0,
"ns", 0, 1);
720 op.getTimeMutable().assign(epsilonTime);
723 { llvm::dbgs() <<
" Lowered Drive Operation successfully!\n\n"; });
725 return WalkResult::advance();
728 if (result.wasInterrupted())
731 IRRewriter rewriter(builder);
732 auto &entryBlock = procOp.getBody().getBlocks().front();
735 for (Block &block : procOp.getBody().getBlocks()) {
736 block.getTerminator()->erase();
738 if (!block.isEntryBlock())
739 entryBlock.getOperations().splice(entryBlock.end(),
740 block.getOperations());
743 rewriter.inlineBlockBefore(&entryBlock, procOp);
746 LLVM_DEBUG({ llvm::dbgs() <<
"Lowered process successfully!\n"; });
749void DesequentializationPass::runOnOperation() {
752 llvm::make_early_inc_range(moduleOp.getOps<llhd::ProcessOp>()))
753 runOnProcess(procOp);
assert(baseType &&"element must be base type")
static FailureOr< BinaryTruthTable > computeTruthTable(mlir::ValueRange values, const OpRange &ops, const llvm::SmallSetVector< mlir::Value, 4 > &inputArgs)
Get the truth table for an op.
Four-valued arbitrary precision integers.
SmallString< 16 > toString(unsigned radix=10, bool uppercase=true) const
Convert an FVInt to a string.
static FVInt getAllX(unsigned numBits)
Construct an FVInt with all bits set to X.
void setBit(unsigned index, Bit bit)
Set the value of an individual bit.
This helps visit Combinational nodes.
create(cls, result_type, reset=None, reset_value=None, name=None, sym_name=None, **kwargs)
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int getBlockTR(Block *) const
unsigned getNumTemporalRegions() const
bool hasSingleExitBlock(int tr) const