Loading [MathJax]/jax/output/HTML-CSS/config.js
CIRCT 21.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
Deseq.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "DNF.h"
14#include "mlir/Analysis/Liveness.h"
15#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
16#include "mlir/Dialect/SCF/IR/SCF.h"
17#include "mlir/IR/Dominance.h"
18#include "mlir/IR/Matchers.h"
19#include "mlir/Transforms/RegionUtils.h"
20#include "llvm/ADT/ScopeExit.h"
21#include "llvm/Support/Debug.h"
22#include "llvm/Support/GenericIteratedDominanceFrontier.h"
23
24#define DEBUG_TYPE "llhd-deseq"
25
26namespace circt {
27namespace llhd {
28#define GEN_PASS_DEF_DESEQPASS
29#include "circt/Dialect/LLHD/Transforms/LLHDPasses.h.inc"
30} // namespace llhd
31} // namespace circt
32
33using namespace mlir;
34using namespace circt;
35using namespace llhd;
36using llvm::SmallMapVector;
37using llvm::SmallSetVector;
38
39namespace circt {
40namespace llhd {
41static llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const DNF &dnf) {
42 dnf.printWithValues(os);
43 return os;
44}
45} // namespace llhd
46} // namespace circt
47
48//===----------------------------------------------------------------------===//
49// Value Table
50//===----------------------------------------------------------------------===//
51
52namespace {
53
54/// An entry in a value table. Associates the value assigned to an SSA value
55/// with a condition under which the association holds.
56struct ValueEntry {
57 DNF condition;
58 Value value;
59
60 int compare(const ValueEntry &other) const {
61 auto order = condition.compare(other.condition);
62 if (order != 0)
63 return order;
64 if (value.getAsOpaquePointer() < other.value.getAsOpaquePointer())
65 return -1;
66 if (value.getAsOpaquePointer() > other.value.getAsOpaquePointer())
67 return 1;
68 return 0;
69 }
70
71 bool operator<(const ValueEntry &other) const { return compare(other) < 0; }
72};
73
74/// A table of values and the conditions under which the values apply. This
75/// struct is used to track the different concrete values an SSA value may have.
76/// Block arguments receive different concrete values from their predecessors,
77/// which this table can track separately given the condition under which
78/// control reaches a block from its predecessors.
79struct ValueTable {
80 /// The entries in the table.
81 SmallVector<ValueEntry, 1> entries;
82
83 /// Create an empty value table.
84 ValueTable() {}
85 /// Create a table with a single value produced under all conditions.
86 explicit ValueTable(Value value) : ValueTable(DNF(true), value) {}
87 /// Create a table with a single value produced under a given condition.
88 ValueTable(DNF condition, Value value) {
89 if (!condition.isFalse())
90 entries.push_back(ValueEntry{condition, value});
91 }
92
93 /// Check whether this table is empty.
94 bool isEmpty() const { return entries.empty(); }
95
96 /// Compare this table to another.
97 int compare(const ValueTable &other) const;
98 bool operator==(const ValueTable &other) const { return compare(other) == 0; }
99 bool operator!=(const ValueTable &other) const { return compare(other) != 0; }
100 bool operator<(const ValueTable &other) const { return compare(other) < 0; }
101 bool operator>(const ValueTable &other) const { return compare(other) > 0; }
102 bool operator>=(const ValueTable &other) const { return compare(other) >= 0; }
103 bool operator<=(const ValueTable &other) const { return compare(other) <= 0; }
104
105 /// Merge the values of another table into this table.
106 void merge(ValueTable &&other);
107 /// Add a condition to all entries in this table. Erases entries from the
108 /// table that become trivially false.
109 void addCondition(const DNF &condition);
110 /// Create a reduced table that only contains values which overlap with a
111 /// given condition.
112 ValueTable filtered(const DNF &condition);
113};
114
115} // namespace
116
117int ValueTable::compare(const ValueTable &other) const {
118 if (entries.size() < other.entries.size())
119 return -1;
120 if (entries.size() > other.entries.size())
121 return 1;
122 for (auto [thisEntry, otherEntry] : llvm::zip(entries, other.entries)) {
123 auto order = thisEntry.compare(otherEntry);
124 if (order != 0)
125 return order;
126 }
127 return 0;
128}
129
130void ValueTable::merge(ValueTable &&other) {
132 for (auto [index, entry] : llvm::enumerate(entries))
133 seenValues[entry.value] = index;
134 for (auto &&entry : other.entries) {
135 if (auto it = seenValues.find(entry.value); it != seenValues.end()) {
136 entries[it->second].condition |= entry.condition;
137 } else {
138 seenValues.insert({entry.value, entries.size()});
139 entries.push_back(entry);
140 }
141 }
142 llvm::sort(entries);
143}
144
145void ValueTable::addCondition(const DNF &condition) {
146 llvm::erase_if(entries, [&](auto &entry) {
147 entry.condition &= condition;
148 return entry.condition.isFalse();
149 });
150}
151
152ValueTable ValueTable::filtered(const DNF &condition) {
153 ValueTable result;
154 for (auto entry : entries) {
155 entry.condition &= condition;
156 if (entry.condition.isFalse())
157 continue;
158
159 // Remove the AND terms that are implied by the condition.
160 for (auto &orTerm : entry.condition.orTerms)
161 llvm::erase_if(orTerm.andTerms, [&](auto andTerm) {
162 return llvm::any_of(condition.orTerms, [&](auto &conditionOrTerm) {
163 return llvm::is_contained(conditionOrTerm.andTerms, andTerm);
164 });
165 });
166
167 result.entries.push_back(std::move(entry));
168 }
169 return result;
170}
171
172static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
173 const ValueTable &table) {
174 SmallMapVector<Value, unsigned, 8> valueIds;
175 auto dumpValue = [&](Value value) {
176 auto id = valueIds.insert({value, valueIds.size()}).first->second;
177 os << "v" << id;
178 };
179
180 // Print the table itself.
181 os << "ValueTable(";
182 llvm::interleaveComma(table.entries, os, [&](auto &entry) {
183 entry.condition.print(os, dumpValue);
184 os << " -> ";
185 Attribute attr;
186 if (auto *defOp = entry.value.getDefiningOp();
187 defOp && defOp->template hasTrait<OpTrait::ConstantLike>() &&
188 m_Constant(&attr).match(defOp))
189 os << attr;
190 else
191 dumpValue(entry.value);
192 });
193
194 // Print the values used.
195 if (!valueIds.empty()) {
196 os << " with ";
197 llvm::interleaveComma(valueIds, os, [&](auto valueAndId) {
198 os << "v" << valueAndId.second << " = ";
199 valueAndId.first.printAsOperand(os, OpPrintingFlags());
200 });
201 }
202 os << ")";
203
204 return os;
205}
206
207//===----------------------------------------------------------------------===//
208// Block Successor Values
209//===----------------------------------------------------------------------===//
210
211namespace {
212
213/// A struct that represents a single control flow edge from a predecessor block
214/// to a one of its successors. It tracks the condition under which control
215/// transfers along this edge, which may be `true` in case of a `cf.br`, or a
216/// concrete branch condition for `cf.cond_br`. Also tracks any DNF expression
217/// and value table associated with each block argument of the successor block.
218struct SuccessorValue {
219 /// The condition under which control transfers to this successor.
220 DNF condition;
221 /// The DNF expression for each successor block argument. Null for any non-i1
222 /// arguments.
223 SmallVector<DNF, 1> booleanArgs;
224 /// The value table for each successor block argument.
225 SmallVector<ValueTable, 1> valueArgs;
226
227 bool operator==(const SuccessorValue &other) const {
228 return condition == other.condition && booleanArgs == other.booleanArgs &&
229 valueArgs == other.valueArgs;
230 }
231
232 bool operator!=(const SuccessorValue &other) const {
233 return !(*this == other);
234 }
235};
236
237/// A list of conditions and block argument values transferred along control
238/// flow edges from a predecessor to each of its successors. Contains an entry
239/// for each successor.
240using SuccessorValues = SmallVector<SuccessorValue, 2>;
241
242} // namespace
243
244//===----------------------------------------------------------------------===//
245// Clock and Reset Analysis
246//===----------------------------------------------------------------------===//
247
248namespace {
249
250/// A single reset extracted from a process during trigger analysis.
251struct ResetInfo {
252 /// The value acting as the reset, causing the register to be set to `value`
253 /// when triggered.
254 Value reset;
255 /// The value the register is reset to.
256 Value value;
257 /// Whether the reset is active when high.
258 bool activeHigh;
259
260 /// Check if this reset info is null.
261 operator bool() const { return bool(reset); }
262};
263
264/// An edge on a trigger.
265enum class Edge { None = 0, Pos, Neg };
266
267/// A single clock extracted from a process during trigger analysis.
268struct ClockInfo {
269 /// The value acting as the clock, causing the register to be set to a value
270 /// in `valueTable` when triggered.
271 Value clock;
272 /// The edge which causes the update. Guaranteed to be `Pos` or `Neg`.
273 Edge edge;
274 /// The table of values that the register will assume when triggered by the
275 /// clock. This may contain a single unconditional value, or may list a
276 /// concrete list of values which are assumed under certain conditions. This
277 /// can be used to infer the use of a register with or without an enable line.
278 ValueTable valueTable;
279
280 /// Check if this clock info is null.
281 operator bool() const { return bool(clock); }
282};
283
284/// A drive op and the clock and reset that resulted from trigger analysis. A
285/// process may describe multiple clock and reset triggers, but since the
286/// registers we lower to only allow a single clock and a single reset, this
287/// struct tracks a single clock and reset, respectively. Processes describing
288/// multiple clocks or resets are skipped.
289struct DriveInfo {
290 /// The drive operation.
291 DrvOp op;
292 /// The clock that triggers a change to the driven value. Guaranteed to be
293 /// non-null.
294 ClockInfo clock;
295 /// The optional reset that triggers a change of the driven value to a fixed
296 /// reset value. Null if no reset was detected.
297 ResetInfo reset;
298
299 DriveInfo() {}
300 explicit DriveInfo(DrvOp op) : op(op) {}
301};
302
303} // namespace
304
305//===----------------------------------------------------------------------===//
306// Value Assignments for Process Specialization
307//===----------------------------------------------------------------------===//
308
309namespace {
310
311/// A single `i1` value that is fixed to a given value in the past and the
312/// present.
313struct FixedValue {
314 /// The IR value being fixed.
315 Value value;
316 /// The assigned value in the past, as transported into the presented via a
317 /// destination operand of a process' wait op.
318 bool past;
319 /// The assigned value in the present.
320 bool present;
321
322 bool operator==(const FixedValue &other) const {
323 return value == other.value && past == other.past &&
324 present == other.present;
325 }
326
327 operator bool() const { return bool(value); }
328};
329
330/// A list of `i1` values that are fixed to a given value. These are used when
331/// specializing a process to compute the value and enable condition for a drive
332/// when a trigger occurs.
333using FixedValues = SmallVector<FixedValue, 2>;
334
335llvm::hash_code hash_value(const FixedValue &arg) {
336 return llvm::hash_combine(arg.value, arg.past, arg.present);
337}
338
339llvm::hash_code hash_value(const FixedValues &arg) {
340 return llvm::hash_combine_range(arg.begin(), arg.end());
341}
342
343} // namespace
344
345// Allow `FixedValue` and `FixedValues` to be used as hash map keys.
346namespace llvm {
347template <>
348struct DenseMapInfo<FixedValue> {
349 static inline FixedValue getEmptyKey() {
350 return FixedValue{DenseMapInfo<Value>::getEmptyKey(), false, false};
351 }
352 static inline FixedValue getTombstoneKey() {
353 return FixedValue{DenseMapInfo<Value>::getTombstoneKey(), false, false};
354 }
355 static unsigned getHashValue(const FixedValue &key) {
356 return hash_value(key);
357 }
358 static bool isEqual(const FixedValue &a, const FixedValue &b) {
359 return a == b;
360 }
361};
362template <>
363struct DenseMapInfo<FixedValues> {
364 static inline FixedValues getEmptyKey() {
366 }
367 static inline FixedValues getTombstoneKey() {
369 }
370 static unsigned getHashValue(const FixedValues &key) {
371 return hash_value(key);
372 }
373 static bool isEqual(const FixedValues &a, const FixedValues &b) {
374 return a == b;
375 }
376};
377} // namespace llvm
378
379//===----------------------------------------------------------------------===//
380// Desequentialization
381//===----------------------------------------------------------------------===//
382
383namespace {
384/// The work horse promoting processes into concrete registers.
385struct Deseq {
386 Deseq(ProcessOp process) : process(process) {}
387 void deseq();
388
389 bool analyzeProcess();
390 bool analyzeTriggers();
391 bool matchDrives();
392 bool matchDrive(DriveInfo &drive);
393 bool isValidResetValue(Value value);
394 void implementRegisters();
395 void implementRegister(DriveInfo &drive);
396 Value specializeValue(Value value, FixedValues fixedValues);
397 ValueRange specializeProcess(FixedValues fixedValues);
398
399 void updateBoolean(Value value, DNF dnf);
400 void updateValue(Value value, ValueTable table);
401 void updateSuccessors(Block *block, SuccessorValues values);
402 void updateCondition(Block *block, DNF condition);
403
404 void markDirty(Operation *op);
405 void markDirty(Block *block);
406
407 void propagate();
408 void propagate(Block *block);
409 void propagate(Operation *op);
410 void propagate(cf::BranchOp op);
411 void propagate(cf::CondBranchOp op);
412 void propagate(WaitOp op);
413 void propagate(comb::OrOp op);
414 void propagate(comb::AndOp op);
415 void propagate(comb::XorOp op);
416 void propagate(comb::MuxOp op);
417
418 /// The process we are desequentializing.
419 ProcessOp process;
420 /// The single wait op of the process.
421 WaitOp wait;
422 /// The boolean values observed by the wait.
423 SmallSetVector<Value, 2> observedBooleans;
424 /// The conditional drive operations fed by this process.
425 SmallVector<DriveInfo> driveInfos;
426 /// The triggers that cause the process to update its results.
427 SmallSetVector<Value, 2> triggers;
428 /// The values carried from the past into the present as destination operands
429 /// of the wait op. These values are guaranteed to also be contained in
430 /// `triggers` and `observedBooleans`.
431 SmallVector<Value, 2> pastValues;
432 /// Specializations of the process for different trigger values.
434 /// An `llhd.constant_time` op created to represent an epsilon delay.
435 ConstantTimeOp epsilonDelay;
436 /// A map of operations that have been checked to be valid reset values.
437 DenseMap<Operation *, bool> staticOps;
438
439 /// A worklist of nodes to be updated during the data flow analysis.
440 SmallSetVector<PointerUnion<Operation *, Block *>, 4> dirtyNodes;
441 /// The DNF expression computed for an `i1` value in the IR.
442 DenseMap<Value, DNF> booleanLattice;
443 /// The value table computed for a value in the IR. This essentially lists
444 /// what values an SSA value assumes under certain conditions.
445 DenseMap<Value, ValueTable> valueLattice;
446 /// The condition under which control flow reaches a block. The block
447 /// immediately following the wait op has this set to true; any further
448 /// conditional branches will refine the condition of successor blocks.
449 DenseMap<Block *, DNF> blockConditionLattice;
450 /// The conditions and values transferred from a block to its successors.
451 DenseMap<Block *, SuccessorValues> successorLattice;
452};
453} // namespace
454
455/// Try to lower the process to a set of registers.
456void Deseq::deseq() {
457 // Check whether the process meets the basic criteria for being replaced by a
458 // register. This includes having only a single `llhd.wait` op and feeding
459 // particular kinds of `llhd.drv` ops.
460 if (!analyzeProcess())
461 return;
462 LLVM_DEBUG({
463 llvm::dbgs() << "Desequentializing " << process.getLoc() << "\n";
464 llvm::dbgs() << "- Feeds " << driveInfos.size() << " conditional drives\n";
465 });
466
467 // Perform a data flow analysis to find SSA values corresponding to a detected
468 // rising or falling edge, and which values are driven under which conditions.
469 propagate();
470
471 // Check which values are used as triggers.
472 if (!analyzeTriggers())
473 return;
474 LLVM_DEBUG({
475 llvm::dbgs() << "- " << triggers.size() << " potential triggers:\n";
476 for (auto trigger : triggers)
477 llvm::dbgs() << " - " << trigger << "\n";
478 });
479
480 // For each drive fed by this process determine the exact triggers that cause
481 // them to drive a new value, and ensure that the behavior can be represented
482 // by a register.
483 if (!matchDrives())
484 return;
485
486 // Make the drives unconditional and capture the conditional behavior as
487 // register operations.
488 implementRegisters();
489
490 // At this point the process has been replaced with specialized versions of it
491 // for the different triggers and can be removed.
492 process.erase();
493}
494
495//===----------------------------------------------------------------------===//
496// Process Analysis
497//===----------------------------------------------------------------------===//
498
499/// Determine whether we can desequentialize the current process. Also gather
500/// the wait and drive ops that are relevant.
501bool Deseq::analyzeProcess() {
502 // We can only desequentialize processes with no side-effecting ops besides
503 // the `WaitOp` or `HaltOp` terminators.
504 for (auto &block : process.getBody()) {
505 for (auto &op : block) {
506 if (isa<WaitOp, HaltOp>(op))
507 continue;
508 if (!isMemoryEffectFree(&op)) {
509 LLVM_DEBUG({
510 llvm::dbgs() << "Skipping " << process.getLoc()
511 << ": contains side-effecting op ";
512 op.print(llvm::dbgs(), OpPrintingFlags().skipRegions());
513 llvm::dbgs() << "\n";
514 });
515 return false;
516 }
517 }
518 }
519
520 // Find the single wait op.
521 for (auto &block : process.getBody()) {
522 if (auto candidate = dyn_cast<WaitOp>(block.getTerminator())) {
523 if (wait) {
524 LLVM_DEBUG(llvm::dbgs() << "Skipping " << process.getLoc()
525 << ": has multiple waits\n");
526 return false;
527 }
528 wait = candidate;
529 }
530 }
531 if (!wait) {
532 LLVM_DEBUG(llvm::dbgs()
533 << "Skipping " << process.getLoc() << ": has no wait\n");
534 return false;
535 }
536 for (auto value : wait.getObserved())
537 if (value.getType().isSignlessInteger(1))
538 observedBooleans.insert(value);
539
540 // Ensure that all process results lead to conditional drive operations.
541 SmallPtrSet<Operation *, 8> seenDrives;
542 for (auto &use : process->getUses()) {
543 auto driveOp = dyn_cast<DrvOp>(use.getOwner());
544 if (!driveOp) {
545 LLVM_DEBUG(llvm::dbgs()
546 << "Skipping " << process.getLoc() << ": feeds non-drive "
547 << use.getOwner()->getLoc() << "\n");
548 return false;
549 }
550 if (!seenDrives.insert(driveOp).second)
551 continue;
552
553 // We can only deal with conditional drives.
554 if (!driveOp.getEnable()) {
555 LLVM_DEBUG(llvm::dbgs()
556 << "Skipping " << process.getLoc()
557 << ": feeds unconditional drive " << driveOp << "\n");
558 return false;
559 }
560
561 // We can only deal with the process result being used as drive value or
562 // condition.
563 if (use.getOperandNumber() != 1 && use.getOperandNumber() != 2) {
564 LLVM_DEBUG(llvm::dbgs()
565 << "Skipping " << process.getLoc()
566 << ": feeds drive operand that is neither value nor enable: "
567 << driveOp << "\n");
568 return false;
569 }
570
571 driveInfos.push_back(DriveInfo(driveOp));
572 }
573
574 return true;
575}
576
577//===----------------------------------------------------------------------===//
578// Data Flow Analysis
579//===----------------------------------------------------------------------===//
580
581/// Add a block to the data flow analysis worklist.
582void Deseq::markDirty(Block *block) { dirtyNodes.insert(block); }
583
584/// Add an operation to the data flow analysis worklist.
585void Deseq::markDirty(Operation *op) {
586 if (op->getParentRegion()->isAncestor(&process.getBody()) ||
587 process.getBody().isAncestor(op->getParentRegion()))
588 dirtyNodes.insert(op);
589}
590
591/// Update the boolean lattice value assigned to a value in the IR, and mark all
592/// dependent nodes dirty.
593void Deseq::updateBoolean(Value value, DNF dnf) {
594 assert(value.getType().isSignlessInteger(1) && "can only trace i1 DNFs");
595 auto &slot = booleanLattice[value];
596 if (slot != dnf) {
597 LLVM_DEBUG({
598 llvm::dbgs() << "- Update ";
599 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
600 llvm::dbgs() << " = " << dnf << "\n";
601 });
602 slot = dnf;
603 for (auto *user : value.getUsers())
604 markDirty(user);
605 }
606}
607
608/// Update the general lattice value assigned to a value in the IR, and mark all
609/// dependent nodes dirty.
610void Deseq::updateValue(Value value, ValueTable table) {
611 auto &slot = valueLattice[value];
612 if (slot != table) {
613 LLVM_DEBUG({
614 llvm::dbgs() << "- Update ";
615 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
616 llvm::dbgs() << " = " << table << "\n";
617 });
618 slot = table;
619 for (auto *user : value.getUsers())
620 markDirty(user);
621 }
622}
623
624/// Update the successor lattice values of a block, and mark all dependent nodes
625/// dirty.
626void Deseq::updateSuccessors(Block *block, SuccessorValues values) {
627 auto &slot = successorLattice[block];
628 for (auto [index, successor] : llvm::enumerate(block->getSuccessors())) {
629 if (!slot.empty() && slot[index] == values[index])
630 continue;
631 markDirty(successor);
632 }
633 slot = values;
634}
635
636/// Update the condition lattice value of a block, and mark all dependent nodes
637/// as dirty.
638void Deseq::updateCondition(Block *block, DNF condition) {
639 auto &slot = blockConditionLattice[block];
640 if (slot != condition) {
641 LLVM_DEBUG({
642 llvm::dbgs() << "- Update ";
643 block->printAsOperand(llvm::dbgs());
644 llvm::dbgs() << " condition = " << condition << "\n";
645 });
646 slot = condition;
647 markDirty(block->getTerminator());
648 }
649}
650
651/// Perform a data flow analysis of the values observed by the process' wait op,
652/// propagated from the past into the present as destination operands of the
653/// wait op, and values yielded as process results. This analysis distills out
654/// drive conditions as canonical DNF expressions and determines under which
655/// conditions given values are produced as process results. This information is
656/// then used to detect if a process expresses a pos/neg edge trigger detection,
657/// among other things.
658void Deseq::propagate() {
659 // Seed the lattice for any value defined outside the process.
660 SmallPtrSet<Operation *, 8> opsAboveSeen;
661 mlir::visitUsedValuesDefinedAbove(
662 process->getRegions(), [&](auto *opOperand) {
663 auto value = opOperand->get();
664 if (auto *defOp = value.getDefiningOp();
665 defOp && !observedBooleans.contains(value)) {
666 if (opsAboveSeen.insert(defOp).second)
667 propagate(defOp);
668 } else {
669 if (value.getType().isSignlessInteger(1))
670 updateBoolean(value, DNF(value));
671 updateValue(value, ValueTable(value));
672 }
673 });
674
675 // Seed the lattice for all operations in the process body.
676 for (auto &block : process.getBody()) {
677 propagate(&block);
678 for (auto &op : block)
679 propagate(&op);
680 }
681
682 // Propagate lattice values.
683 while (!dirtyNodes.empty()) {
684 auto node = dirtyNodes.pop_back_val();
685 if (auto *op = dyn_cast<Operation *>(node))
686 propagate(op);
687 else
688 propagate(cast<Block *>(node));
689 }
690 LLVM_DEBUG(llvm::dbgs() << "- Finished propagation\n");
691}
692
693/// Combine the control flow conditions and block argument values from
694/// predecessor blocks into a unified condition and values for each of the
695/// block's arguments.
696void Deseq::propagate(Block *block) {
697 // Combine all the values coming from our predecessor blocks.
698 const unsigned numArgs = block->getNumArguments();
699 auto condition = DNF(false);
700 SmallVector<DNF> booleanArgs(numArgs, DNF(false));
701 SmallVector<ValueTable> valueArgs(numArgs, ValueTable());
702
703 SmallPtrSet<Block *, 4> seen;
704 for (auto *predecessor : block->getPredecessors()) {
705 if (!seen.insert(predecessor).second)
706 continue;
707 auto &successorValues = successorLattice[predecessor];
708 if (successorValues.empty())
709 continue;
710 auto *terminator = predecessor->getTerminator();
711 for (auto &blockOperand : terminator->getBlockOperands()) {
712 if (blockOperand.get() != block)
713 continue;
714 auto &successorValue = successorValues[blockOperand.getOperandNumber()];
715 condition |= successorValue.condition;
716 for (unsigned argIdx = 0; argIdx < numArgs; ++argIdx) {
717 booleanArgs[argIdx] |=
718 successorValue.booleanArgs[argIdx] & successorValue.condition;
719 auto valueTable = successorValue.valueArgs[argIdx];
720 valueTable.addCondition(successorValue.condition);
721 valueArgs[argIdx].merge(std::move(valueTable));
722 }
723 }
724 }
725
726 // Update the block condition.
727 updateCondition(block, condition);
728
729 // Update the individual arguments.
730 for (auto [arg, booleanArg, valueArg] :
731 llvm::zip(block->getArguments(), booleanArgs, valueArgs)) {
732 if (arg.getType().isSignlessInteger(1))
733 updateBoolean(arg, booleanArg);
734 updateValue(arg, valueArg);
735 }
736}
737
738/// Propagate lattice values across an operation.
739void Deseq::propagate(Operation *op) {
740 // Handle boolean constants.
741 if (op->hasTrait<OpTrait::ConstantLike>() && op->getNumResults() == 1) {
742 APInt intValue;
743 if (op->getResult(0).getType().isSignlessInteger(1) &&
744 m_ConstantInt(&intValue).match(op)) {
745 assert(intValue.getBitWidth() == 1);
746 updateBoolean(op->getResult(0), DNF(intValue.isOne()));
747 }
748 updateValue(op->getResult(0), ValueTable(op->getResult(0)));
749 return;
750 }
751
752 // Handle all other ops.
753 TypeSwitch<Operation *>(op)
754 .Case<cf::BranchOp, cf::CondBranchOp, WaitOp, comb::OrOp, comb::AndOp,
755 comb::XorOp, comb::MuxOp>([&](auto op) {
756 // Handle known ops.
757 propagate(op);
758 })
759 .Default([&](auto) {
760 // All other ops will simply produce their results as opaque values.
761 for (auto result : op->getResults()) {
762 if (result.getType().isSignlessInteger(1))
763 updateBoolean(result, DNF(result));
764 updateValue(result, ValueTable(result));
765 }
766 });
767}
768
769/// Propagate lattice values across an unconditional branch.
770void Deseq::propagate(cf::BranchOp op) {
771 SuccessorValue latticeValue;
772 latticeValue.condition = blockConditionLattice[op->getBlock()];
773 latticeValue.booleanArgs.reserve(op.getDestOperands().size());
774 latticeValue.valueArgs.reserve(op.getDestOperands().size());
775 for (auto operand : op.getDestOperands()) {
776 latticeValue.booleanArgs.push_back(booleanLattice.lookup(operand));
777 latticeValue.valueArgs.push_back(valueLattice.lookup(operand));
778 }
779 updateSuccessors(op->getBlock(), {latticeValue});
780}
781
782/// Propagate lattice values across a conditional branch. This combines the
783/// parent block's condition of control flow reaching it with the branch
784/// condition to determine the condition under which the successor blocks will
785/// be reached.
786void Deseq::propagate(cf::CondBranchOp op) {
787 SuccessorValues latticeValues(2, SuccessorValue());
788 auto blockCondition = blockConditionLattice[op->getBlock()];
789 auto branchCondition = booleanLattice.lookup(op.getCondition());
790
791 // Handle the true branch.
792 auto &trueValue = latticeValues[0];
793 trueValue.condition = blockCondition & branchCondition;
794 trueValue.booleanArgs.reserve(op.getTrueDestOperands().size());
795 trueValue.valueArgs.reserve(op.getTrueDestOperands().size());
796 for (auto operand : op.getTrueDestOperands()) {
797 trueValue.booleanArgs.push_back(booleanLattice.lookup(operand));
798 trueValue.valueArgs.push_back(valueLattice.lookup(operand));
799 }
800
801 // Handle the false branch.
802 auto &falseValue = latticeValues[1];
803 falseValue.condition = blockCondition & ~branchCondition;
804 falseValue.booleanArgs.reserve(op.getFalseDestOperands().size());
805 falseValue.valueArgs.reserve(op.getFalseDestOperands().size());
806 for (auto operand : op.getFalseDestOperands()) {
807 falseValue.booleanArgs.push_back(booleanLattice.lookup(operand));
808 falseValue.valueArgs.push_back(valueLattice.lookup(operand));
809 }
810
811 updateSuccessors(op->getBlock(), latticeValues);
812}
813
814/// Propagate lattice values across a wait op. This will mark values flowing
815/// into the destination block as past values, such that the data flow analysis
816/// can determine if a value is compared against a past version of itself. Also
817/// updates the parent process' result values based on the wait's yield
818/// operands.
819void Deseq::propagate(WaitOp op) {
820 SuccessorValue latticeValue;
821 latticeValue.condition = DNF(true); // execution resumes in the destination
822 latticeValue.booleanArgs.reserve(op.getDestOperands().size());
823 latticeValue.valueArgs.reserve(op.getDestOperands().size());
824 for (auto operand : op.getDestOperands()) {
825 // Destination operands of the wait op are essentially carrying a past value
826 // into the destination block.
827 auto value = booleanLattice.lookup(operand);
828 if (value) {
829 bool nonePastAlready = llvm::all_of(value.orTerms, [](auto &orTerm) {
830 return llvm::all_of(orTerm.andTerms, [](auto &andTerm) {
831 if (andTerm.hasUse(AndTerm::Past) || andTerm.hasUse(AndTerm::NotPast))
832 return false;
833 andTerm.uses <<= 1; // map Id/NotId to Past/NotPast
834 return true;
835 });
836 });
837 if (!nonePastAlready)
838 value = DNF();
839 }
840 latticeValue.booleanArgs.push_back(value);
841 latticeValue.valueArgs.push_back(ValueTable());
842 }
843 updateSuccessors(op->getBlock(), {latticeValue});
844
845 // If this is the single wait in the current process, update the process
846 // results.
847 if (op != wait)
848 return;
849 for (auto [result, operand] :
850 llvm::zip(process->getResults(), op.getYieldOperands())) {
851 if (operand.getType().isSignlessInteger(1))
852 updateBoolean(result, booleanLattice.lookup(operand));
853 updateValue(result, valueLattice.lookup(operand));
854 }
855}
856
857/// Propagate lattice values across a `comb.or` op.
858void Deseq::propagate(comb::OrOp op) {
859 if (op.getType().isSignlessInteger(1)) {
860 auto result = DNF(false);
861 for (auto operand : op.getInputs()) {
862 result |= booleanLattice.lookup(operand);
863 if (result.isTrue())
864 break;
865 }
866 updateBoolean(op, result);
867 }
868 updateValue(op, ValueTable(op.getResult()));
869}
870
871/// Propagate lattice values across a `comb.and` op.
872void Deseq::propagate(comb::AndOp op) {
873 if (op.getType().isSignlessInteger(1)) {
874 auto result = DNF(true);
875 for (auto operand : op.getInputs()) {
876 result &= booleanLattice.lookup(operand);
877 if (result.isFalse())
878 break;
879 }
880 updateBoolean(op, result);
881 }
882 updateValue(op, ValueTable(op.getResult()));
883}
884
885/// Propagate lattice values across a `comb.xor` op.
886void Deseq::propagate(comb::XorOp op) {
887 if (op.getType().isSignlessInteger(1)) {
888 auto result = DNF(false);
889 for (auto operand : op.getInputs())
890 result ^= booleanLattice.lookup(operand);
891 updateBoolean(op, result);
892 }
893 updateValue(op, ValueTable(op.getResult()));
894}
895
896/// Propagate lattice values across a `comb.mux` op.
897void Deseq::propagate(comb::MuxOp op) {
898 auto condition = booleanLattice.lookup(op.getCond());
899 if (op.getType().isSignlessInteger(1)) {
900 auto trueValue = booleanLattice.lookup(op.getTrueValue());
901 auto falseValue = booleanLattice.lookup(op.getFalseValue());
902 updateBoolean(op, condition & trueValue | ~condition & falseValue);
903 }
904 auto trueValue = valueLattice.lookup(op.getTrueValue());
905 auto falseValue = valueLattice.lookup(op.getFalseValue());
906 trueValue.addCondition(condition);
907 falseValue.addCondition(~condition);
908 trueValue.merge(std::move(falseValue));
909 updateValue(op, trueValue);
910}
911
912//===----------------------------------------------------------------------===//
913// Trigger Analysis
914//===----------------------------------------------------------------------===//
915
916/// After propagating values across the lattice, determine which values may be
917/// involved in trigger detection.
918bool Deseq::analyzeTriggers() {
919 for (auto operand : wait.getDestOperands()) {
920 // Only i1 values can be triggers.
921 if (!operand.getType().isSignlessInteger(1)) {
922 LLVM_DEBUG({
923 llvm::dbgs() << "- Aborting: " << operand.getType() << " trigger ";
924 operand.printAsOperand(llvm::dbgs(), OpPrintingFlags());
925 llvm::dbgs() << "\n";
926 });
927 return false;
928 }
929
930 // Data flow analysis must have produced a single value for this past
931 // operand.
932 auto dnf = booleanLattice.lookup(operand);
933 Value value;
934 AndTerm::Use use;
935 if (auto single = dnf.getSingleTerm()) {
936 std::tie(value, use) = *single;
937 } else {
938 LLVM_DEBUG({
939 llvm::dbgs() << "- Aborting: trigger ";
940 operand.printAsOperand(llvm::dbgs(), OpPrintingFlags());
941 llvm::dbgs() << " with multiple values " << dnf << "\n";
942 });
943 return false;
944 }
945 if (use != AndTerm::Id) {
946 LLVM_DEBUG({
947 llvm::dbgs() << "- Aborting: trigger ";
948 operand.printAsOperand(llvm::dbgs(), OpPrintingFlags());
949 llvm::dbgs() << " is inverted: " << dnf << "\n";
950 });
951 return false;
952 }
953
954 // We can only reason about past values defined outside the process.
955 if (process.getBody().isAncestor(value.getParentRegion())) {
956 LLVM_DEBUG({
957 llvm::dbgs() << "- Aborting: trigger ";
958 operand.printAsOperand(llvm::dbgs(), OpPrintingFlags());
959 llvm::dbgs() << " defined inside the process\n";
960 });
961 return false;
962 }
963
964 // We can only reason about past values if they cause the process to resume
965 // when they change.
966 if (!observedBooleans.contains(value)) {
967 LLVM_DEBUG({
968 llvm::dbgs() << "- Aborting: unobserved trigger ";
969 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
970 llvm::dbgs() << "\n";
971 });
972 return false;
973 }
974
975 triggers.insert(value);
976 pastValues.push_back(value);
977 }
978
979 if (triggers.empty()) {
980 LLVM_DEBUG(llvm::dbgs() << "- Aborting: no triggers\n");
981 return false;
982 }
983 return true;
984}
985
986//===----------------------------------------------------------------------===//
987// Drive-to-Register Matching
988//===----------------------------------------------------------------------===//
989
990/// Match the drives fed by the process against concrete implementable register
991/// behaviors. Returns false if any of the drives cannot be implemented as a
992/// register.
993bool Deseq::matchDrives() {
994 for (auto &drive : driveInfos)
995 if (!matchDrive(drive))
996 return false;
997 return true;
998}
999
1000/// For a given drive op, determine if its drive condition and driven value as
1001/// determined by the data flow analysis is implementable by a register op.
1002/// This function first analyzes the drive condition and extracts concrete
1003/// posedge or negedge triggers from it. It then analyzes the value driven under
1004/// each condition and ensures that there is a clear precedence between
1005/// triggers, for example, to disambiguate a clock from an overriding reset.
1006/// This function then distills out a single clock and a single optional reset
1007/// for the drive and stores the information in the given `DriveInfo`. Returns
1008/// false if no single clock and no single optional reset could be extracted.
1009bool Deseq::matchDrive(DriveInfo &drive) {
1010 LLVM_DEBUG(llvm::dbgs() << "- Analyzing " << drive.op << "\n");
1011
1012 // Determine under which condition the drive is enabled.
1013 auto condition = booleanLattice.lookup(drive.op.getEnable());
1014 if (condition.isNull()) {
1015 LLVM_DEBUG(llvm::dbgs()
1016 << "- Aborting: null condition on " << drive.op << "\n");
1017 return false;
1018 }
1019
1020 // Determine which value is driven under which conditions.
1021 auto valueTable = valueLattice.lookup(drive.op.getValue());
1022 valueTable.addCondition(condition);
1023 LLVM_DEBUG({
1024 llvm::dbgs() << " - Condition: " << condition << "\n";
1025 llvm::dbgs() << " - Value: " << valueTable << "\n";
1026 });
1027
1028 // Determine to which edges the drive is sensitive, and ensure that each value
1029 // is only triggered by a single edge.
1030 SmallMapVector<Value, Edge, 2> edges;
1031 for (auto &entry : valueTable.entries) {
1032 for (auto &orTerm : entry.condition.orTerms) {
1033 bool edgeSeen = false;
1034 for (auto &andTerm : orTerm.andTerms) {
1035 // We only care about values sampled in the past to determine an edge.
1036 if (!andTerm.hasAnyUses(AndTerm::PastUses))
1037 continue;
1038
1039 // We only allow a few triggers.
1040 if (!triggers.contains(andTerm.value)) {
1041 LLVM_DEBUG({
1042 llvm::dbgs() << "- Aborting: past sample of ";
1043 andTerm.value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1044 llvm::dbgs() << " which is not a trigger\n";
1045 });
1046 return false;
1047 }
1048
1049 // Determine whether we're sampling a pos or neg edge.
1050 auto edge = Edge::None;
1051 if (andTerm.uses == AndTerm::PosEdgeUses) {
1052 edge = Edge::Pos;
1053 } else if (andTerm.uses == AndTerm::NegEdgeUses) {
1054 edge = Edge::Neg;
1055 } else {
1056 LLVM_DEBUG({
1057 llvm::dbgs() << "- Aborting: sampling of ";
1058 andTerm.value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1059 llvm::dbgs() << " is neither not a pos/neg edge\n";
1060 });
1061 return false;
1062 }
1063
1064 // Registers can only react to a single edge. They cannot check for a
1065 // conjunction of edges.
1066 if (edgeSeen) {
1067 LLVM_DEBUG(llvm::dbgs() << "- Aborting: AND of multiple edges\n");
1068 return false;
1069 }
1070 edgeSeen = true;
1071
1072 // Ensure that we don't sample both a pos and a neg edge.
1073 auto &existingEdge = edges[andTerm.value];
1074 if (existingEdge != Edge::None && existingEdge != edge) {
1075 LLVM_DEBUG({
1076 llvm::dbgs() << "- Aborting: ";
1077 andTerm.value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1078 llvm::dbgs() << " used both as pos and neg edge trigger\n";
1079 });
1080 return false;
1081 }
1082 existingEdge = edge;
1083 }
1084
1085 if (!edgeSeen) {
1086 LLVM_DEBUG(llvm::dbgs() << "- Aborting: triggered by non-edge\n");
1087 return false;
1088 }
1089 }
1090 }
1091 LLVM_DEBUG({
1092 for (auto [value, edge] : edges) {
1093 llvm::dbgs() << " - Triggered by ";
1094 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1095 llvm::dbgs() << (edge == Edge::Pos ? " posedge\n" : " negedge\n");
1096 }
1097 });
1098
1099 // Detect if one of the triggers is a reset. Resets add a particular pattern
1100 // into the table of drive values, which looks like this:
1101 // - pos: /rst | rst&... -> const, !rst&... -> ...
1102 // - neg: \rst | !rst&... -> const, rst&... -> ...
1103 SmallVector<AndTerm, 2> resetEntries;
1104 SmallPtrSet<Value, 2> resetTriggers;
1105 SmallVector<ResetInfo, 2> resetInfos;
1106
1107 if (edges.size() > 1) {
1108 for (auto [value, edge] : edges) {
1109 auto resetEdge =
1110 edge == Edge::Pos ? AndTerm::posEdge(value) : AndTerm::negEdge(value);
1111 auto resetActive = AndTerm(
1112 value, 1 << (edge == Edge::Pos ? AndTerm::Id : AndTerm::NotId));
1113 auto resetInactive = AndTerm(
1114 value, 1 << (edge == Edge::Pos ? AndTerm::NotId : AndTerm::Id));
1115
1116 // Find the single table entry with an edge on the reset.
1117 unsigned singleResetEntry = -1;
1118 for (auto [index, entry] : llvm::enumerate(valueTable.entries)) {
1119 if (!entry.condition.contains(OrTerm(resetEdge)))
1120 continue;
1121 // If we have multiple entries with the reset edge as trigger, abort.
1122 if (singleResetEntry != unsigned(-1)) {
1123 singleResetEntry = -1;
1124 break;
1125 }
1126 singleResetEntry = index;
1127 }
1128 if (singleResetEntry == unsigned(-1))
1129 continue;
1130
1131 // The reset value must be a constant. Unfortunately, we don't fold all
1132 // possible aggregates down to a single constant materializer in the IR.
1133 // To deal with this, we merely limit reset values to be static: as long
1134 // as they are derived through side-effect-free operations that only
1135 // depend on constants, we admit a value as a reset.
1136 //
1137 // Technically also non-constants can be reset values. However, since
1138 // async resets are level sensitive (even though Verilog describes them as
1139 // edge sensitive), the reset value would have to be part of the wait op's
1140 // observed values. We don't check for that.
1141 auto resetValue = valueTable.entries[singleResetEntry].value;
1142 if (!isValidResetValue(resetValue))
1143 continue;
1144
1145 // Ensure that all other entries besides the reset entry contain the
1146 // inactive reset value as an AND term.
1147 auto allGated = llvm::all_of(
1148 llvm::enumerate(valueTable.entries), [&](auto indexAndEntry) {
1149 // Skip the reset entry.
1150 if (indexAndEntry.index() == singleResetEntry)
1151 return true;
1152 // All other entries must contain the inactive reset.
1153 return llvm::all_of(
1154 indexAndEntry.value().condition.orTerms,
1155 [&](auto &orTerm) { return orTerm.contains(resetInactive); });
1156 });
1157 if (!allGated)
1158 continue;
1159
1160 // Keep track of the reset.
1161 resetEntries.push_back(resetEdge);
1162 resetEntries.push_back(resetActive);
1163 resetInfos.push_back({value, resetValue, edge == Edge::Pos});
1164 resetTriggers.insert(value);
1165 }
1166 }
1167
1168 // Remove the resets from the edge triggers.
1169 for (auto &resetInfo : resetInfos)
1170 edges.erase(resetInfo.reset);
1171
1172 // Remove the conditions in the drive value table corresponding to the reset
1173 // being active. This information has been absorbed into `resetInfos`.
1174 llvm::erase_if(valueTable.entries, [&](auto &entry) {
1175 llvm::erase_if(entry.condition.orTerms, [&](auto &orTerm) {
1176 for (auto &andTerm : orTerm.andTerms)
1177 if (llvm::is_contained(resetEntries, andTerm))
1178 return true;
1179 return false;
1180 });
1181 return entry.condition.isFalse();
1182 });
1183
1184 // Assume the resets are inactive by removing them from the remaining AND
1185 // terms in the drive value table.
1186 for (auto &entry : valueTable.entries) {
1187 for (auto &orTerm : entry.condition.orTerms) {
1188 llvm::erase_if(orTerm.andTerms, [&](auto &andTerm) {
1189 return resetTriggers.contains(andTerm.value);
1190 });
1191 }
1192 }
1193
1194 // Group the value table entries by the remaining triggers.
1195 SmallVector<ClockInfo, 2> clockInfos;
1196
1197 for (auto [clock, edge] : edges) {
1198 auto clockEdge =
1199 edge == Edge::Pos ? AndTerm::posEdge(clock) : AndTerm::negEdge(clock);
1200 ValueTable clockValueTable;
1201
1202 for (auto &entry : valueTable.entries) {
1203 auto condition = entry.condition;
1204
1205 // Remove OR terms in the condition that don't mention this clock as
1206 // trigger. Also remove the explicit mention of the clock edge from the
1207 // condition, since this is now implied by the fact that we are grouping
1208 // the driven values by the clock.
1209 llvm::erase_if(condition.orTerms, [&](auto &orTerm) {
1210 bool clockEdgeFound = false;
1211 llvm::erase_if(orTerm.andTerms, [&](auto &andTerm) {
1212 if (andTerm == clockEdge) {
1213 clockEdgeFound = true;
1214 return true;
1215 }
1216 return false;
1217 });
1218 return !clockEdgeFound;
1219 });
1220
1221 // Check if the condition has become trivially true.
1222 if (llvm::any_of(condition.orTerms,
1223 [](auto &orTerm) { return orTerm.isTrue(); }))
1224 condition = DNF(true);
1225
1226 // If the condition is not trivially false, add an entry to the table.
1227 if (!condition.isFalse())
1228 clockValueTable.entries.push_back({condition, entry.value});
1229 }
1230
1231 // Keep track of the clocks.
1232 clockInfos.push_back({clock, edge, std::move(clockValueTable)});
1233 }
1234
1235 // Handle the degenerate case where the clock changes a register to the same
1236 // value as the reset. We detect the clock as a reset in this case, leading to
1237 // zero clocks and two resets. Handle this by promoting one of the resets to a
1238 // clock.
1239 if (resetInfos.size() == 2 && clockInfos.empty()) {
1240 // Guess a sensible clock. Prefer posedge clocks and zero-valued resets.
1241 // Prefer the first trigger in the wait op's observed value list as the
1242 // clock.
1243 unsigned clockIdx = 0;
1244 if (resetInfos[0].activeHigh && !resetInfos[1].activeHigh)
1245 clockIdx = 0;
1246 else if (!resetInfos[0].activeHigh && resetInfos[1].activeHigh)
1247 clockIdx = 1;
1248 else if (matchPattern(resetInfos[1].value, m_Zero()))
1249 clockIdx = 0;
1250 else if (matchPattern(resetInfos[0].value, m_Zero()))
1251 clockIdx = 1;
1252 else if (resetInfos[0].reset == triggers[0])
1253 clockIdx = 0;
1254 else if (resetInfos[1].reset == triggers[0])
1255 clockIdx = 1;
1256
1257 // Move the clock from `resetInfos` over into the `clockInfos` list.
1258 auto &info = resetInfos[clockIdx];
1259 LLVM_DEBUG({
1260 llvm::dbgs() << " - Two resets, no clock: promoting ";
1261 info.reset.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1262 llvm::dbgs() << " to clock\n";
1263 });
1264 clockInfos.push_back({info.reset, info.activeHigh ? Edge::Pos : Edge::Neg,
1265 ValueTable(info.value)});
1266 resetInfos.erase(resetInfos.begin() + clockIdx);
1267 }
1268
1269 // Dump out some debugging information about the detected resets and clocks.
1270 LLVM_DEBUG({
1271 for (auto &resetInfo : resetInfos) {
1272 llvm::dbgs() << " - Reset ";
1273 resetInfo.reset.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1274 llvm::dbgs() << " (active " << (resetInfo.activeHigh ? "high" : "low")
1275 << "): ";
1276 resetInfo.value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1277 llvm::dbgs() << "\n";
1278 }
1279 for (auto &clockInfo : clockInfos) {
1280 llvm::dbgs() << " - Clock ";
1281 clockInfo.clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1282 llvm::dbgs() << " ("
1283 << (clockInfo.edge == Edge::Pos ? "posedge" : "negedge")
1284 << "): " << clockInfo.valueTable << "\n";
1285 }
1286 });
1287
1288 // Ensure that the clock and reset is available outside the process.
1289 for (auto &clockInfo : clockInfos) {
1290 if (clockInfo.clock.getParentRegion()->isProperAncestor(&process.getBody()))
1291 continue;
1292 LLVM_DEBUG({
1293 llvm::dbgs() << "- Aborting: clock ";
1294 clockInfo.clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1295 llvm::dbgs() << " not available outside the process\n";
1296 });
1297 return false;
1298 }
1299 for (auto &resetInfo : resetInfos) {
1300 if (resetInfo.reset.getParentRegion()->isProperAncestor(&process.getBody()))
1301 continue;
1302 LLVM_DEBUG({
1303 llvm::dbgs() << "- Aborting: reset ";
1304 resetInfo.reset.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1305 llvm::dbgs() << " not available outside the process\n";
1306 });
1307 return false;
1308 }
1309
1310 // The registers we can lower to only support a single clock, and a single
1311 // optional reset.
1312 if (clockInfos.size() != 1) {
1313 LLVM_DEBUG(llvm::dbgs()
1314 << "- Aborting: " << clockInfos.size() << " clocks\n");
1315 return false;
1316 }
1317 if (resetInfos.size() > 1) {
1318 LLVM_DEBUG(llvm::dbgs()
1319 << "- Aborting: " << resetInfos.size() << " resets\n");
1320 return false;
1321 }
1322 drive.clock = clockInfos[0];
1323 drive.reset = resetInfos.empty() ? ResetInfo{} : resetInfos[0];
1324
1325 return true;
1326}
1327
1328/// Check if a value is constant or only derived from constant values through
1329/// side-effect-free operations. If it is, the value is guaranteed to never
1330/// change.
1331bool Deseq::isValidResetValue(Value value) {
1332 auto result = dyn_cast<OpResult>(value);
1333 if (!result)
1334 return false;
1335 if (auto it = staticOps.find(result.getOwner()); it != staticOps.end())
1336 return it->second;
1337
1338 struct WorklistItem {
1339 Operation *op;
1340 OperandRange::iterator it;
1341 };
1342 SmallVector<WorklistItem> worklist;
1343 worklist.push_back({result.getOwner(), result.getOwner()->operand_begin()});
1344
1345 while (!worklist.empty()) {
1346 auto item = worklist.pop_back_val();
1347 auto &isStatic = staticOps[item.op];
1348 if (item.it == item.op->operand_begin()) {
1349 if (item.op->hasTrait<OpTrait::ConstantLike>()) {
1350 isStatic = true;
1351 continue;
1352 }
1353 if (!isMemoryEffectFree(item.op))
1354 continue;
1355 }
1356 if (item.it == item.op->operand_end()) {
1357 isStatic = true;
1358 continue;
1359 } else {
1360 auto result = dyn_cast<OpResult>(*item.it);
1361 if (!result)
1362 continue;
1363 auto it = staticOps.find(result.getOwner());
1364 if (it == staticOps.end()) {
1365 worklist.push_back(item);
1366 worklist.push_back(
1367 {result.getOwner(), result.getOwner()->operand_begin()});
1368 } else if (it->second) {
1369 ++item.it;
1370 worklist.push_back(item);
1371 }
1372 }
1373 }
1374
1375 return staticOps.lookup(result.getOwner());
1376}
1377
1378//===----------------------------------------------------------------------===//
1379// Register Implementation
1380//===----------------------------------------------------------------------===//
1381
1382/// Make all drives unconditional and implement the conditional behavior with
1383/// register ops.
1384void Deseq::implementRegisters() {
1385 for (auto &drive : driveInfos)
1386 implementRegister(drive);
1387}
1388
1389/// Implement the conditional behavior of a drive with a `seq.compreg` or
1390/// `seq.compreg.ce` op, and make the drive unconditional. This function pulls
1391/// the analyzed clock and reset from the given `DriveInfo` and creates the
1392/// necessary ops outside the process represent the behavior as a register. It
1393/// also calls `specializeValue` and `specializeProcess` to convert the
1394/// sequential `llhd.process` into a purely combinational `scf.execute_region`
1395/// that is simplified by assuming that the clock edge occurs.
1396void Deseq::implementRegister(DriveInfo &drive) {
1397 OpBuilder builder(drive.op);
1398 auto loc = drive.op.getLoc();
1399
1400 // Materialize the clock as a `!seq.clock` value. Insert an inverter for
1401 // negedge clocks.
1402 Value clock = builder.create<seq::ToClockOp>(loc, drive.clock.clock);
1403 if (drive.clock.edge == Edge::Neg)
1404 clock = builder.create<seq::ClockInverterOp>(loc, clock);
1405
1406 // Handle the optional reset.
1407 Value reset;
1408 Value resetValue;
1409
1410 if (drive.reset) {
1411 reset = drive.reset.reset;
1412 resetValue = drive.reset.value;
1413
1414 // Materialize the reset as an `i1` value. Insert an inverter for negedge
1415 // resets.
1416 if (!drive.reset.activeHigh) {
1417 auto one = builder.create<hw::ConstantOp>(loc, builder.getI1Type(), 1);
1418 reset = builder.create<comb::XorOp>(loc, reset, one);
1419 }
1420
1421 // Specialize the process for the reset trigger. If the reset value is
1422 // trivially available outside the process, use it directly. If it is a
1423 // constant, move the constant outside the process.
1424 if (!resetValue.getParentRegion()->isProperAncestor(&process.getBody())) {
1425 if (auto *defOp = resetValue.getDefiningOp();
1426 defOp && defOp->hasTrait<OpTrait::ConstantLike>()) {
1427 defOp->moveBefore(process);
1428 } else {
1429 resetValue = specializeValue(
1430 drive.op.getValue(),
1431 FixedValues{{drive.clock.clock, drive.clock.edge == Edge::Neg,
1432 drive.clock.edge == Edge::Neg},
1433 {drive.reset.reset, !drive.reset.activeHigh,
1434 drive.reset.activeHigh}});
1435 }
1436 }
1437 }
1438
1439 // Determine the enable condition. If we have determined that the register
1440 // is trivially enabled, don't add an enable. If the enable condition is a
1441 // simple boolean value available outside the process, use it directly.
1442 Value enable = drive.op.getEnable();
1443 if (drive.clock.valueTable.entries.size() == 1) {
1444 if (drive.clock.valueTable.entries[0].condition.isTrue())
1445 enable = {};
1446 else if (auto singleTerm =
1447 drive.clock.valueTable.entries[0].condition.getSingleTerm();
1448 singleTerm && singleTerm->second == AndTerm::Id &&
1449 singleTerm->first.getParentRegion()->isProperAncestor(
1450 &process.getBody())) {
1451 enable = singleTerm->first;
1452 }
1453 }
1454
1455 // Determine the value. If the value is trivially available outside the
1456 // process, use it directly. If it is a constant, move the constant outside
1457 // the process.
1458 Value value = drive.op.getValue();
1459 if (drive.clock.valueTable.entries.size() == 1) {
1460 auto tryValue = drive.clock.valueTable.entries[0].value;
1461 if (tryValue.getParentRegion()->isProperAncestor(&process.getBody())) {
1462 value = tryValue;
1463 } else if (auto *defOp = tryValue.getDefiningOp();
1464 defOp && defOp->hasTrait<OpTrait::ConstantLike>()) {
1465 defOp->moveBefore(process);
1466 value = tryValue;
1467 }
1468 }
1469
1470 // Specialize the process for the clock trigger, which will produce the
1471 // enable and the value for regular clock edges.
1472 FixedValues fixedValues;
1473 fixedValues.push_back({drive.clock.clock, drive.clock.edge == Edge::Neg,
1474 drive.clock.edge == Edge::Pos});
1475 if (drive.reset)
1476 fixedValues.push_back(
1477 {drive.reset.reset, !drive.reset.activeHigh, !drive.reset.activeHigh});
1478
1479 value = specializeValue(value, fixedValues);
1480 if (enable)
1481 enable = specializeValue(enable, fixedValues);
1482
1483 // Create the register op.
1484 Value reg;
1485 if (enable)
1486 reg = builder.create<seq::CompRegClockEnabledOp>(
1487 loc, value, clock, enable, StringAttr{}, reset, resetValue, Value{},
1488 hw::InnerSymAttr{});
1489 else
1490 reg =
1491 builder.create<seq::CompRegOp>(loc, value, clock, StringAttr{}, reset,
1492 resetValue, Value{}, hw::InnerSymAttr{});
1493
1494 // Make the original `llhd.drv` drive the register value unconditionally.
1495 drive.op.getValueMutable().assign(reg);
1496 drive.op.getEnableMutable().clear();
1497
1498 // If the original `llhd.drv` had a delta delay, turn it into an immediate
1499 // drive since the delay behavior is now capture by the register op.
1500 TimeAttr attr;
1501 if (matchPattern(drive.op.getTime(), m_Constant(&attr)) &&
1502 attr.getTime() == 0 && attr.getDelta() == 1 && attr.getEpsilon() == 0) {
1503 if (!epsilonDelay)
1504 epsilonDelay =
1505 builder.create<ConstantTimeOp>(process.getLoc(), 0, "ns", 0, 1);
1506 drive.op.getTimeMutable().assign(epsilonDelay);
1507 }
1508}
1509
1510//===----------------------------------------------------------------------===//
1511// Process Specialization
1512//===----------------------------------------------------------------------===//
1513
1514/// Specialize a value by assuming the values listed in `fixedValues` are at a
1515/// constant value in the past and the present. The function is guaranteed to
1516/// replace results of the process with results of a new combinational
1517/// `scf.execute_region` op. All other behavior is purely an optimization; the
1518/// function may not make use of the assignments in `fixedValues` at all.
1519Value Deseq::specializeValue(Value value, FixedValues fixedValues) {
1520 auto result = dyn_cast<OpResult>(value);
1521 if (!result || result.getOwner() != process)
1522 return value;
1523 return specializeProcess(fixedValues)[result.getResultNumber()];
1524}
1525
1526/// Specialize the current process by assuming the values listed in
1527/// `fixedValues` are at a constant value in the past and the present. This
1528/// function creates a new `scf.execute_region` op with a simplified version
1529/// of the process where all uses of the values listed in `fixedValues` are
1530/// replaced with their constant counterpart. Since the clock-dependent
1531/// behavior of the process has been absorbed into a register, the process can
1532/// be replaced with a combinational representation that computes the drive
1533/// value and drive condition under the assumption that the clock edge occurs.
1534ValueRange Deseq::specializeProcess(FixedValues fixedValues) {
1535 if (auto it = specializedProcesses.find(fixedValues);
1536 it != specializedProcesses.end())
1537 return it->second;
1538
1539 LLVM_DEBUG({
1540 llvm::dbgs() << "- Specializing process for:\n";
1541 for (auto fixedValue : fixedValues) {
1542 llvm::dbgs() << " - ";
1543 fixedValue.value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1544 llvm::dbgs() << ": " << fixedValue.past << " -> " << fixedValue.present
1545 << "\n";
1546 }
1547 });
1548
1549 // Create an `scf.execute_region` with this process specialized to compute
1550 // the result for the given fixed values. The triggers will be absorbed into
1551 // the register operation that consumes the result of this specialized
1552 // process, such that we can make the process purely combinational.
1553 OpBuilder builder(process);
1554 auto executeOp = builder.create<scf::ExecuteRegionOp>(
1555 process.getLoc(), process.getResultTypes());
1556
1557 IRMapping mapping;
1558 SmallVector<std::pair<Block *, Block *>> worklist;
1559
1560 auto scheduleBlock = [&](Block *block) {
1561 if (auto *newBlock = mapping.lookupOrNull(block))
1562 return newBlock;
1563 auto *newBlock = &executeOp.getRegion().emplaceBlock();
1564 for (auto arg : block->getArguments()) {
1565 auto newArg = newBlock->addArgument(arg.getType(), arg.getLoc());
1566 mapping.map(arg, newArg);
1567 }
1568 mapping.map(block, newBlock);
1569 worklist.push_back({block, newBlock});
1570 return newBlock;
1571 };
1572
1573 // Initialize the mapping with constants for the fixed values.
1574 auto &entryBlock = executeOp.getRegion().emplaceBlock();
1575 builder.setInsertionPointToStart(&entryBlock);
1576 auto i1 = builder.getI1Type();
1577 auto trueValue = builder.create<hw::ConstantOp>(process.getLoc(), i1, 1);
1578 auto falseValue = builder.create<hw::ConstantOp>(process.getLoc(), i1, 0);
1579
1580 SmallDenseMap<Value, std::pair<Value, Value>, 2> materializedFixedValues;
1581 for (auto fixedValue : fixedValues) {
1582 auto present = fixedValue.present ? trueValue : falseValue;
1583 auto past = fixedValue.past ? trueValue : falseValue;
1584 materializedFixedValues.insert({fixedValue.value, {past, present}});
1585 mapping.map(fixedValue.value, present);
1586 }
1587
1588 auto evaluateTerm = [&](Value value, bool past) -> std::optional<bool> {
1589 for (auto fixedValue : fixedValues)
1590 if (fixedValue.value == value)
1591 return past ? fixedValue.past : fixedValue.present;
1592 return {};
1593 };
1594
1595 // Clone operations over.
1596 auto cloneBlocks = [&](bool stopAtWait) {
1597 SmallVector<Value> foldedResults;
1598 while (!worklist.empty()) {
1599 auto [oldBlock, newBlock] = worklist.pop_back_val();
1600 builder.setInsertionPointToEnd(newBlock);
1601 for (auto &oldOp : *oldBlock) {
1602 // Convert `llhd.wait` into `scf.yield`.
1603 if (auto waitOp = dyn_cast<WaitOp>(oldOp)) {
1604 if (stopAtWait)
1605 continue;
1606 SmallVector<Value> operands;
1607 for (auto operand : waitOp.getYieldOperands())
1608 operands.push_back(mapping.lookupOrDefault(operand));
1609 builder.create<scf::YieldOp>(waitOp.getLoc(), operands);
1610 continue;
1611 }
1612
1613 // Convert `cf.cond_br` ops into `cf.br` if the condition is constant.
1614 if (auto condBranchOp = dyn_cast<cf::CondBranchOp>(oldOp)) {
1615 SmallVector<Value> operands;
1616 auto condition = mapping.lookupOrDefault(condBranchOp.getCondition());
1617 if (matchPattern(condition, m_NonZero())) {
1618 for (auto operand : condBranchOp.getTrueDestOperands())
1619 operands.push_back(mapping.lookupOrDefault(operand));
1620 builder.create<cf::BranchOp>(
1621 condBranchOp.getLoc(),
1622 scheduleBlock(condBranchOp.getTrueDest()), operands);
1623 continue;
1624 }
1625 if (matchPattern(condition, m_Zero())) {
1626 for (auto operand : condBranchOp.getFalseOperands())
1627 operands.push_back(mapping.lookupOrDefault(operand));
1628 builder.create<cf::BranchOp>(
1629 condBranchOp.getLoc(),
1630 scheduleBlock(condBranchOp.getFalseDest()), operands);
1631 continue;
1632 }
1633 }
1634
1635 // If our initial data flow analysis has produced a concrete DNF for
1636 // an `i1`-valued op, see if the DNF evaluates to a constant true or
1637 // false with the given fixed values.
1638 if (oldOp.getNumResults() == 1 &&
1639 oldOp.getResult(0).getType().isSignlessInteger(1)) {
1640 if (auto dnf = booleanLattice.lookup(oldOp.getResult(0))) {
1641 if (auto result = dnf.evaluate(evaluateTerm)) {
1642 mapping.map(oldOp.getResult(0), *result ? trueValue : falseValue);
1643 continue;
1644 }
1645 }
1646 }
1647
1648 // Otherwise clone the operation.
1649 for (auto &blockOperand : oldOp.getBlockOperands())
1650 scheduleBlock(blockOperand.get());
1651 auto *clonedOp = builder.clone(oldOp, mapping);
1652
1653 // And immediately try to fold the cloned operation since the fixed
1654 // values introduce a lot of constants into the IR.
1655 if (succeeded(builder.tryFold(clonedOp, foldedResults)) &&
1656 !foldedResults.empty()) {
1657 for (auto [oldResult, foldedResult] :
1658 llvm::zip(oldOp.getResults(), foldedResults))
1659 mapping.map(oldResult, foldedResult);
1660 clonedOp->erase();
1661 }
1662 foldedResults.clear();
1663 }
1664 }
1665 };
1666
1667 // Start at the entry block of the original process and clone all ops until
1668 // we hit the wait.
1669 worklist.push_back({&process.getBody().front(), &entryBlock});
1670 cloneBlocks(true);
1671 builder.setInsertionPointToEnd(mapping.lookup(wait->getBlock()));
1672
1673 // Remove all blocks from the IR mapping. Some blocks may be reachable from
1674 // the entry block and the wait op, in which case we want to create
1675 // duplicates of those blocks.
1676 for (auto &block : process.getBody())
1677 mapping.erase(&block);
1678
1679 // If the wait op is not the only predecessor of its destination block,
1680 // create a branch op to the block. Otherwise inline the destination block
1681 // into the entry block, which allows the specialization to fold more
1682 // constants.
1683 if (wait.getDest()->hasOneUse()) {
1684 // Map the block arguments of the block after the wait op to the constant
1685 // fixed values.
1686 for (auto [arg, pastValue] :
1687 llvm::zip(wait.getDest()->getArguments(), pastValues))
1688 mapping.map(arg, materializedFixedValues.lookup(pastValue).first);
1689
1690 // Schedule the block after the wait for cloning into the entry block.
1691 mapping.map(wait.getDest(), builder.getBlock());
1692 worklist.push_back({wait.getDest(), builder.getBlock()});
1693 } else {
1694 // Schedule the block after the wait for cloning.
1695 auto *dest = scheduleBlock(wait.getDest());
1696
1697 // From the entry block, branch to the block after the wait with the
1698 // appropriate past values as block arguments.
1699 SmallVector<Value> destOperands;
1700 assert(pastValues.size() == wait.getDestOperands().size());
1701 for (auto pastValue : pastValues)
1702 destOperands.push_back(materializedFixedValues.lookup(pastValue).first);
1703 builder.create<cf::BranchOp>(wait.getLoc(), dest, destOperands);
1704 }
1705
1706 // Clone everything after the wait operation.
1707 cloneBlocks(false);
1708
1709 // Don't leave unused constants behind.
1710 if (isOpTriviallyDead(trueValue))
1711 trueValue.erase();
1712 if (isOpTriviallyDead(falseValue))
1713 falseValue.erase();
1714
1715 specializedProcesses.insert({fixedValues, executeOp.getResults()});
1716 return executeOp.getResults();
1717}
1718
1719//===----------------------------------------------------------------------===//
1720// Pass Infrastructure
1721//===----------------------------------------------------------------------===//
1722
1723namespace {
1724struct DeseqPass : public llhd::impl::DeseqPassBase<DeseqPass> {
1725 void runOnOperation() override;
1726};
1727} // namespace
1728
1729void DeseqPass::runOnOperation() {
1730 SmallVector<ProcessOp> processes(getOperation().getOps<ProcessOp>());
1731 for (auto process : processes)
1732 Deseq(process).deseq();
1733}
assert(baseType &&"element must be base type")
create(data_type, value)
Definition hw.py:433
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:55
@ None
Don't preserve aggregate at all.
Definition Passes.h:33
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
static bool operator==(const ModulePort &a, const ModulePort &b)
Definition HWTypes.h:35
static llvm::hash_code hash_value(const ModulePort &port)
Definition HWTypes.h:38
static llvm::raw_ostream & operator<<(llvm::raw_ostream &os, const DNF &dnf)
Definition Deseq.cpp:41
void info(Twine message)
Definition LSPUtils.cpp:20
bool operator<(const DictEntry &entry, const DictEntry &other)
Definition RTGTypes.h:25
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
bool operator!=(uint64_t a, const FVInt &b)
Definition FVInt.h:651
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition seq.py:21
An individual term of an AND expression in a DNF.
Definition DNF.h:20
static constexpr Uses PastUses
The set of past uses.
Definition DNF.h:46
Use
An integer indicating one use of the value in this term.
Definition DNF.h:24
@ Id
The plain value x.
Definition DNF.h:26
@ NotId
The inverted value !x.
Definition DNF.h:32
static constexpr Uses PosEdgeUses
The set of uses that indicates a posedge.
Definition DNF.h:40
static AndTerm posEdge(Value value)
Create a term representing a posedge on a value.
Definition DNF.h:62
Value value
The value.
Definition DNF.h:49
static constexpr Uses NegEdgeUses
The set of uses that indicates a negedge.
Definition DNF.h:42
OrTerms orTerms
Definition DNF.h:201
std::optional< bool > evaluate(llvm::function_ref< std::optional< bool >(Value, bool)> evaluateTerm)
Try to evaluate this DNF to a constant true or false value.
Definition DNF.cpp:449
int compare(const DNF &other) const
Compare against another DNF.
Definition DNF.cpp:336
void printWithValues(llvm::raw_ostream &os) const
Print this DNF to os, followed by a list of the concrete values used.
Definition DNF.cpp:495
std::optional< std::pair< Value, AndTerm::Use > > getSingleTerm() const
If this DNF consists of a single term, return it.
Definition DNF.h:233
bool isNull() const
Check whether this DNF is null.
Definition DNF.h:222
bool isFalse() const
Check whether this DNF is trivially false.
Definition DNF.h:229
An individual term of an OR expression in a DNF.
Definition DNF.h:127
static FixedValue getTombstoneKey()
Definition Deseq.cpp:352
static unsigned getHashValue(const FixedValue &key)
Definition Deseq.cpp:355
static FixedValue getEmptyKey()
Definition Deseq.cpp:349
static bool isEqual(const FixedValue &a, const FixedValue &b)
Definition Deseq.cpp:358
static FixedValues getEmptyKey()
Definition Deseq.cpp:364
static unsigned getHashValue(const FixedValues &key)
Definition Deseq.cpp:370
static FixedValues getTombstoneKey()
Definition Deseq.cpp:367
static bool isEqual(const FixedValues &a, const FixedValues &b)
Definition Deseq.cpp:373