Loading [MathJax]/extensions/tex2jax.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 "DeseqUtils.h"
14#include "mlir/Analysis/Liveness.h"
15#include "mlir/Dialect/Arith/IR/Arith.h"
16#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
17#include "mlir/IR/Dominance.h"
18#include "mlir/IR/IRMapping.h"
19#include "mlir/IR/Matchers.h"
20#include "mlir/Transforms/RegionUtils.h"
21#include "llvm/ADT/ScopeExit.h"
22#include "llvm/Support/Debug.h"
23#include "llvm/Support/GenericIteratedDominanceFrontier.h"
24
25// Provide a `llhd-deseq` debug option for some high-level observability, and
26// `llhd-deseq-verbose` for additional prints that trace out concrete values
27// propagated across the IR.
28#define DEBUG_TYPE "llhd-deseq"
29#define VERBOSE_DEBUG(...) DEBUG_WITH_TYPE(DEBUG_TYPE "-verbose", __VA_ARGS__)
30
31namespace circt {
32namespace llhd {
33#define GEN_PASS_DEF_DESEQPASS
34#include "circt/Dialect/LLHD/Transforms/LLHDPasses.h.inc"
35} // namespace llhd
36} // namespace circt
37
38using namespace mlir;
39using namespace circt;
40using namespace llhd;
41using namespace deseq;
42using llvm::SmallSetVector;
43
44namespace {
45/// The work horse promoting processes into concrete registers.
46struct Deseq {
47 Deseq(ProcessOp process) : process(process) {}
48 void deseq();
49
50 bool analyzeProcess();
51 Value tracePastValue(Value pastValue);
52
53 TruthTable computeBoolean(Value value);
54 ValueTable computeValue(Value value);
55 TruthTable computeBoolean(OpResult value);
56 ValueTable computeValue(OpResult value);
57 TruthTable computeBoolean(BlockArgument value);
58 ValueTable computeValue(BlockArgument arg);
59 TruthTable computeBlockCondition(Block *block);
60 TruthTable computeSuccessorCondition(BlockOperand &operand);
61 TruthTable computeSuccessorBoolean(BlockOperand &operand, unsigned argIdx);
62 ValueTable computeSuccessorValue(BlockOperand &operand, unsigned argIdx);
63
64 bool matchDrives();
65 bool matchDrive(DriveInfo &drive);
66 bool matchDriveClock(DriveInfo &drive,
67 ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable);
68 bool
69 matchDriveClockAndReset(DriveInfo &drive,
70 ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable);
71
72 void implementRegisters();
73 void implementRegister(DriveInfo &drive);
74
75 Value specializeValue(Value value, FixedValues fixedValues);
76 ValueRange specializeProcess(FixedValues fixedValues);
77
78 /// The process we are desequentializing.
79 ProcessOp process;
80 /// The single wait op of the process.
81 WaitOp wait;
82 /// The boolean values observed by the wait. These trigger the process and
83 /// may cause the described register to update its value.
84 SmallSetVector<Value, 2> triggers;
85 /// The values carried from the past into the present as destination operands
86 /// of the wait op. These values are guaranteed to also be contained in
87 /// `triggers`.
88 SmallVector<Value, 2> pastValues;
89 /// The conditional drive operations fed by this process.
90 SmallVector<DriveInfo> driveInfos;
91 /// Specializations of the process for different trigger values.
93 /// A cache of `seq.to_clock` ops.
94 SmallDenseMap<Value, Value, 1> materializedClockCasts;
95 /// A cache of `seq.clock_inv` ops.
96 SmallDenseMap<Value, Value, 1> materializedClockInverters;
97 /// A cache of `comb.xor` ops used as inverters.
98 SmallDenseMap<Value, Value, 1> materializedInverters;
99 /// An `llhd.constant_time` op created to represent an epsilon delay.
100 ConstantTimeOp epsilonDelay;
101 /// A map of operations that have been checked to be valid reset values.
102 DenseMap<Operation *, bool> staticOps;
103
104 /// The boolean expression computed for an `i1` value in the IR.
105 DenseMap<Value, TruthTable> booleanLattice;
106 /// The value table computed for a value in the IR. This essentially lists
107 /// what values an SSA value assumes under certain conditions.
108 DenseMap<Value, ValueTable> valueLattice;
109 /// The condition under which control flow reaches a block. The block
110 /// immediately following the wait op has this set to true; any further
111 /// conditional branches will refine the condition of successor blocks.
112 DenseMap<Block *, TruthTable> blockConditionLattice;
113 /// The condition under which control flows along a terminator's block operand
114 /// to its destination.
115 DenseMap<BlockOperand *, TruthTable> successorConditionLattice;
116 /// The boolean expression passed from a terminator to its destination as a
117 /// destination block operand.
118 DenseMap<std::pair<BlockOperand *, unsigned>, TruthTable>
119 successorBooleanLattice;
120 /// The value table passed from a terminator to its destination as a
121 /// destination block operand.
122 DenseMap<std::pair<BlockOperand *, unsigned>, ValueTable>
123 successorValueLattice;
124
125private:
126 // Utilities to create boolean truth tables. These make working with truth
127 // tables easier, since the calling code doesn't have to care about how
128 // triggers and unknown value markers are packed into truth table columns.
129 TruthTable getPoisonBoolean() const { return TruthTable::getPoison(); }
130 TruthTable getUnknownBoolean() const {
131 return TruthTable::getTerm(triggers.size() * 2 + 1, 0);
132 }
133 TruthTable getConstBoolean(bool value) const {
134 return TruthTable::getConst(triggers.size() * 2 + 1, value);
135 }
136 TruthTable getPastTrigger(unsigned triggerIndex) const {
137 return TruthTable::getTerm(triggers.size() * 2 + 1, triggerIndex * 2 + 1);
138 }
139 TruthTable getPresentTrigger(unsigned triggerIndex) const {
140 return TruthTable::getTerm(triggers.size() * 2 + 1, triggerIndex * 2 + 2);
141 }
142
143 // Utilities to create value tables. These make working with value tables
144 // easier, since the calling code doesn't have to care about how the truth
145 // tables and value tables are constructed.
146 ValueTable getUnknownValue() const {
147 return ValueTable(getConstBoolean(true), ValueEntry::getUnknown());
148 }
149 ValueTable getPoisonValue() const {
150 return ValueTable(getConstBoolean(true), ValueEntry::getPoison());
151 }
152 ValueTable getKnownValue(Value value) const {
153 return ValueTable(getConstBoolean(true), value);
154 }
155};
156} // namespace
157
158/// Try to lower the process to a set of registers.
159void Deseq::deseq() {
160 // Check whether the process meets the basic criteria for being replaced by a
161 // register. This includes having only a single `llhd.wait` op and feeding
162 // only particular kinds of `llhd.drv` ops.
163 if (!analyzeProcess())
164 return;
165 LLVM_DEBUG({
166 llvm::dbgs() << "Desequentializing " << process.getLoc() << "\n";
167 llvm::dbgs() << "- Feeds " << driveInfos.size() << " conditional drives\n";
168 llvm::dbgs() << "- " << triggers.size() << " potential triggers:\n";
169 for (auto [index, trigger] : llvm::enumerate(triggers)) {
170 llvm::dbgs() << " - ";
171 trigger.printAsOperand(llvm::dbgs(), OpPrintingFlags());
172 llvm::dbgs() << ": past " << getPastTrigger(index);
173 llvm::dbgs() << ", present " << getPresentTrigger(index);
174 llvm::dbgs() << "\n";
175 }
176 });
177
178 // For each drive fed by this process determine the exact triggers that cause
179 // them to drive a new value, and ensure that the behavior can be represented
180 // by a register.
181 if (!matchDrives())
182 return;
183
184 // Make the drives unconditional and capture the conditional behavior as
185 // register operations.
186 implementRegisters();
187
188 // At this point the process has been replaced with specialized versions of it
189 // for the different triggers and can be removed.
190 process.erase();
191}
192
193//===----------------------------------------------------------------------===//
194// Process Analysis
195//===----------------------------------------------------------------------===//
196
197/// Determine whether we can desequentialize the current process. Also gather
198/// the wait and drive ops that are relevant.
199bool Deseq::analyzeProcess() {
200 // We can only desequentialize processes with no side-effecting ops besides
201 // the `WaitOp` or `HaltOp` terminators.
202 for (auto &block : process.getBody()) {
203 for (auto &op : block) {
204 if (isa<WaitOp, HaltOp>(op))
205 continue;
206 if (!isMemoryEffectFree(&op)) {
207 LLVM_DEBUG({
208 llvm::dbgs() << "Skipping " << process.getLoc()
209 << ": contains side-effecting op ";
210 op.print(llvm::dbgs(), OpPrintingFlags().skipRegions());
211 llvm::dbgs() << "\n";
212 });
213 return false;
214 }
215 }
216 }
217
218 // Find the single wait op.
219 for (auto &block : process.getBody()) {
220 if (auto candidate = dyn_cast<WaitOp>(block.getTerminator())) {
221 if (wait) {
222 LLVM_DEBUG(llvm::dbgs() << "Skipping " << process.getLoc()
223 << ": has multiple waits\n");
224 return false;
225 }
226 wait = candidate;
227 }
228 }
229 if (!wait) {
230 LLVM_DEBUG(llvm::dbgs()
231 << "Skipping " << process.getLoc() << ": has no wait\n");
232 return false;
233 }
234
235 // Ensure that all process results lead to conditional drive operations.
236 SmallPtrSet<Operation *, 8> seenDrives;
237 for (auto &use : process->getUses()) {
238 auto driveOp = dyn_cast<DrvOp>(use.getOwner());
239 if (!driveOp) {
240 LLVM_DEBUG(llvm::dbgs()
241 << "Skipping " << process.getLoc() << ": feeds non-drive "
242 << use.getOwner()->getLoc() << "\n");
243 return false;
244 }
245 if (!seenDrives.insert(driveOp).second)
246 continue;
247
248 // We can only deal with conditional drives.
249 if (!driveOp.getEnable()) {
250 LLVM_DEBUG(llvm::dbgs()
251 << "Skipping " << process.getLoc()
252 << ": feeds unconditional drive " << driveOp << "\n");
253 return false;
254 }
255
256 // We can only deal with the process result being used as drive value or
257 // condition.
258 if (use.getOperandNumber() != 1 && use.getOperandNumber() != 2) {
259 LLVM_DEBUG(llvm::dbgs()
260 << "Skipping " << process.getLoc()
261 << ": feeds drive operand that is neither value nor enable: "
262 << driveOp << "\n");
263 return false;
264 }
265
266 driveInfos.push_back(DriveInfo(driveOp));
267 }
268
269 // Ensure the observed values are all booleans.
270 for (auto value : wait.getObserved()) {
271 if (!value.getType().isSignlessInteger(1)) {
272 LLVM_DEBUG(llvm::dbgs() << "Skipping " << process.getLoc()
273 << ": observes non-i1 value\n");
274 return false;
275 }
276 triggers.insert(value);
277 }
278
279 // We only support 1 or 2 observed values, since we map to registers with a
280 // clock and an optional async reset.
281 if (triggers.empty() || triggers.size() > 2) {
282 LLVM_DEBUG(llvm::dbgs() << "Skipping " << process.getLoc() << ": observes "
283 << triggers.size() << " values\n");
284 return false;
285 }
286
287 // Seed the drive value analysis with the observed values.
288 for (auto [index, trigger] : llvm::enumerate(triggers))
289 booleanLattice.insert({trigger, getPresentTrigger(index)});
290
291 // Ensure the wait op destination operands, i.e. the values passed from the
292 // past into the present, are the observed values.
293 for (auto [operand, blockArg] :
294 llvm::zip(wait.getDestOperands(), wait.getDest()->getArguments())) {
295 if (!operand.getType().isSignlessInteger(1)) {
296 LLVM_DEBUG(llvm::dbgs() << "Skipping " << process.getLoc()
297 << ": uses non-i1 past value\n");
298 return false;
299 }
300 auto trigger = tracePastValue(operand);
301 if (!trigger)
302 return false;
303 pastValues.push_back(trigger);
304 unsigned index =
305 std::distance(triggers.begin(), llvm::find(triggers, trigger));
306 booleanLattice.insert({blockArg, getPastTrigger(index)});
307 }
308
309 return true;
310}
311
312/// Trace a value passed from the past into the present as a destination operand
313/// of the wait op back to a single observed value. Returns a null value if the
314/// value does not trace back to a single, unique observed value.
315Value Deseq::tracePastValue(Value pastValue) {
316 // Use a worklist to look through branches and a few common IR patterns to
317 // find the concrete value used as a destination operand.
318 SmallVector<Value> worklist;
319 SmallPtrSet<Value, 8> seen;
320 worklist.push_back(pastValue);
321 seen.insert(pastValue);
322
323 SmallPtrSet<Block *, 2> predSeen;
324 SmallSetVector<BlockOperand *, 4> predWorklist;
325 SmallPtrSet<Value, 2> distinctValues;
326 while (!worklist.empty()) {
327 auto value = worklist.pop_back_val();
328 auto arg = dyn_cast<BlockArgument>(value);
329
330 // If this is one of the observed values, we're done. Otherwise trace
331 // block arguments backwards to their predecessors.
332 if (triggers.contains(value) || !arg) {
333 distinctValues.insert(value);
334 continue;
335 }
336
337 // Collect the predecessor block operands to process.
338 predSeen.clear();
339 predWorklist.clear();
340 for (auto *predecessor : arg.getOwner()->getPredecessors())
341 if (predSeen.insert(predecessor).second)
342 for (auto &operand : predecessor->getTerminator()->getBlockOperands())
343 if (operand.get() == arg.getOwner())
344 predWorklist.insert(&operand);
345
346 // Handle the predecessors. This essentially is a loop over all block
347 // arguments in terminator ops that branch to arg's block.
348 unsigned argIdx = arg.getArgNumber();
349 for (auto *blockOperand : predWorklist) {
350 auto *op = blockOperand->getOwner();
351 if (auto branchOp = dyn_cast<cf::BranchOp>(op)) {
352 // Handle unconditional branches.
353 auto operand = branchOp.getDestOperands()[argIdx];
354 if (seen.insert(operand).second)
355 worklist.push_back(operand);
356 } else if (auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
357 // Handle conditional branches.
358 unsigned destIdx = blockOperand->getOperandNumber();
359 auto operand = destIdx == 0
360 ? condBranchOp.getTrueDestOperands()[argIdx]
361 : condBranchOp.getFalseDestOperands()[argIdx];
362
363 // Undo the `cond_br a, bb(a), bb(a)` to `cond_br a, bb(1), bb(0)`
364 // canonicalization.
365 if ((matchPattern(operand, m_One()) && destIdx == 0) ||
366 (matchPattern(operand, m_Zero()) && destIdx == 1))
367 operand = condBranchOp.getCondition();
368
369 if (seen.insert(operand).second)
370 worklist.push_back(operand);
371 } else {
372 LLVM_DEBUG(llvm::dbgs() << "Skipping " << process.getLoc()
373 << ": unsupported terminator " << op->getName()
374 << " while tracing past value\n");
375 return Value{};
376 }
377 }
378 }
379
380 // Ensure that we have one distinct value being passed from the past into
381 // the present, and that the value is observed.
382 if (distinctValues.size() != 1) {
383 LLVM_DEBUG(
384 llvm::dbgs()
385 << "Skipping " << process.getLoc()
386 << ": multiple past values passed for the same block argument\n");
387 return Value{};
388 }
389 auto distinctValue = *distinctValues.begin();
390 if (!triggers.contains(distinctValue)) {
391 LLVM_DEBUG(llvm::dbgs() << "Skipping " << process.getLoc()
392 << ": unobserved past value\n");
393 return Value{};
394 }
395 return distinctValue;
396}
397
398//===----------------------------------------------------------------------===//
399// Data Flow Analysis
400//===----------------------------------------------------------------------===//
401
402/// Convert a boolean SSA value into a truth table. If the value depends on any
403/// of the process' triggers, that dependency is captured explicitly by the
404/// truth table. Any other SSA values that factor into the value are represented
405/// as an opaque term.
406TruthTable Deseq::computeBoolean(Value value) {
407 assert(value.getType().isSignlessInteger(1));
408
409 // If this value is a result of the process we're analyzing, jump to the
410 // corresponding yield operand of the wait op.
411 if (value.getDefiningOp() == process)
412 return computeBoolean(
413 wait.getYieldOperands()[cast<OpResult>(value).getResultNumber()]);
414
415 // Check if we have already computed this value. Otherwise insert an unknown
416 // value to break recursions. This will be overwritten by a concrete value
417 // later.
418 if (auto it = booleanLattice.find(value); it != booleanLattice.end())
419 return it->second;
420 booleanLattice[value] = getUnknownBoolean();
421
422 // Actually compute the value.
423 TruthTable result =
424 TypeSwitch<Value, TruthTable>(value).Case<OpResult, BlockArgument>(
425 [&](auto value) { return computeBoolean(value); });
426
427 // Memoize the result.
429 llvm::dbgs() << "- Boolean ";
430 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
431 llvm::dbgs() << ": " << result << "\n";
432 });
433 booleanLattice[value] = result;
434 return result;
435}
436
437/// Determine the different concrete values an SSA value may assume depending on
438/// how control flow reaches the given value. This is used to determine the list
439/// of different values that are driven onto a signal under various conditions.
440ValueTable Deseq::computeValue(Value value) {
441 // If this value is a result of the process we're analyzing, jump to the
442 // corresponding yield operand of the wait op.
443 if (value.getDefiningOp() == process)
444 return computeValue(
445 wait.getYieldOperands()[cast<OpResult>(value).getResultNumber()]);
446
447 // Check if we have already computed this value. Otherwise insert an unknown
448 // value to break recursions. This will be overwritten by a concrete value
449 // later.
450 if (auto it = valueLattice.find(value); it != valueLattice.end())
451 return it->second;
452 valueLattice[value] = getUnknownValue();
453
454 // Actually compute the value.
455 ValueTable result =
456 TypeSwitch<Value, ValueTable>(value).Case<OpResult, BlockArgument>(
457 [&](auto value) { return computeValue(value); });
458
459 // Memoize the result.
461 llvm::dbgs() << "- Value ";
462 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
463 llvm::dbgs() << ": " << result << "\n";
464 });
465 valueLattice[value] = result;
466 return result;
467}
468
469/// Convert a boolean op result to a truth table.
470TruthTable Deseq::computeBoolean(OpResult value) {
471 assert(value.getType().isSignlessInteger(1));
472 auto *op = value.getOwner();
473
474 // Handle constants.
475 if (auto constOp = dyn_cast<hw::ConstantOp>(op))
476 return getConstBoolean(constOp.getValue().isOne());
477
478 // Handle `comb.or`.
479 if (auto orOp = dyn_cast<comb::OrOp>(op)) {
480 auto result = getConstBoolean(false);
481 for (auto operand : orOp.getInputs()) {
482 result |= computeBoolean(operand);
483 if (result.isTrue())
484 break;
485 }
486 return result;
487 }
488
489 // Handle `comb.and`.
490 if (auto andOp = dyn_cast<comb::AndOp>(op)) {
491 auto result = getConstBoolean(true);
492 for (auto operand : andOp.getInputs()) {
493 result &= computeBoolean(operand);
494 if (result.isFalse())
495 break;
496 }
497 return result;
498 }
499
500 // Handle `comb.xor`.
501 if (auto xorOp = dyn_cast<comb::XorOp>(op)) {
502 auto result = getConstBoolean(false);
503 for (auto operand : xorOp.getInputs())
504 result ^= computeBoolean(operand);
505 return result;
506 }
507
508 // Otherwise check if the operation depends on any of the triggers. If it
509 // does, create a poison value since we don't really know how the trigger
510 // affects this boolean. If it doesn't, create an unknown value.
511 if (llvm::any_of(op->getOperands(), [&](auto operand) {
512 // TODO: This should probably also check non-i1 values to see if they
513 // depend on the triggers. Maybe once we merge boolean and value tables?
514 if (!operand.getType().isSignlessInteger(1))
515 return false;
516 auto result = computeBoolean(operand);
517 return result.isPoison() || (result != getUnknownBoolean() &&
518 !result.isTrue() && !result.isFalse());
519 }))
520 return getPoisonBoolean();
521 return getUnknownBoolean();
522}
523
524/// Determine the different values an op result may assume depending how control
525/// flow reaches the op.
526ValueTable Deseq::computeValue(OpResult value) {
527 auto *op = value.getOwner();
528
529 // Handle `comb.mux` and `arith.select`.
530 if (isa<comb::MuxOp, arith::SelectOp>(op)) {
531 auto condition = computeBoolean(op->getOperand(0));
532 auto trueValue = computeValue(op->getOperand(1));
533 auto falseValue = computeValue(op->getOperand(2));
534 trueValue.addCondition(condition);
535 falseValue.addCondition(~condition);
536 trueValue.merge(std::move(falseValue));
537 return trueValue;
538 }
539
540 // TODO: Reject values that depend on the triggers.
541 return getKnownValue(value);
542}
543
544/// Convert a block argument to a truth table.
545TruthTable Deseq::computeBoolean(BlockArgument arg) {
546 auto *block = arg.getOwner();
547
548 // If this isn't a block in the process, simply return an unknown value.
549 if (block->getParentOp() != process)
550 return getUnknownBoolean();
551
552 // Otherwise iterate over all predecessors and compute the boolean values
553 // being passed to this block argument by each.
554 auto result = getConstBoolean(false);
555 SmallPtrSet<Block *, 4> seen;
556 for (auto *predecessor : block->getPredecessors()) {
557 if (!seen.insert(predecessor).second)
558 continue;
559 for (auto &operand : predecessor->getTerminator()->getBlockOperands()) {
560 if (operand.get() != block)
561 continue;
562 auto value = computeSuccessorBoolean(operand, arg.getArgNumber());
563 if (value.isFalse())
564 continue;
565 auto condition = computeSuccessorCondition(operand);
566 result |= value & condition;
567 if (result.isTrue())
568 break;
569 }
570 if (result.isTrue())
571 break;
572 }
573 return result;
574}
575
576/// Determine the different values a block argument may assume depending how
577/// control flow reaches the block.
578ValueTable Deseq::computeValue(BlockArgument arg) {
579 auto *block = arg.getOwner();
580
581 // If this isn't a block in the process, simply return the value itself.
582 if (block->getParentOp() != process)
583 return getKnownValue(arg);
584
585 // Otherwise iterate over all predecessors and compute the boolean values
586 // being passed to this block argument by each.
587 auto result = ValueTable();
588 SmallPtrSet<Block *, 4> seen;
589 for (auto *predecessor : block->getPredecessors()) {
590 if (!seen.insert(predecessor).second)
591 continue;
592 for (auto &operand : predecessor->getTerminator()->getBlockOperands()) {
593 if (operand.get() != block)
594 continue;
595 auto condition = computeSuccessorCondition(operand);
596 if (condition.isFalse())
597 continue;
598 auto value = computeSuccessorValue(operand, arg.getArgNumber());
599 value.addCondition(condition);
600 result.merge(value);
601 }
602 }
603 return result;
604}
605
606/// Compute the boolean condition under which control flow reaches a block, as a
607/// truth table.
608TruthTable Deseq::computeBlockCondition(Block *block) {
609 // Return a memoized result if one exists. Otherwise insert a default result
610 // as recursion breaker.
611 if (auto it = blockConditionLattice.find(block);
612 it != blockConditionLattice.end())
613 return it->second;
614 blockConditionLattice[block] = getConstBoolean(false);
615
616 // Actually compute the block condition by combining all incoming control flow
617 // conditions.
618 auto result = getConstBoolean(false);
619 SmallPtrSet<Block *, 4> seen;
620 for (auto *predecessor : block->getPredecessors()) {
621 if (!seen.insert(predecessor).second)
622 continue;
623 for (auto &operand : predecessor->getTerminator()->getBlockOperands()) {
624 if (operand.get() != block)
625 continue;
626 result |= computeSuccessorCondition(operand);
627 if (result.isTrue())
628 break;
629 }
630 if (result.isTrue())
631 break;
632 }
633
634 // Memoize the result.
636 llvm::dbgs() << "- Block condition ";
637 block->printAsOperand(llvm::dbgs());
638 llvm::dbgs() << ": " << result << "\n";
639 });
640 blockConditionLattice[block] = result;
641 return result;
642}
643
644/// Compute the condition under which control transfers along a terminator's
645/// block operand to the destination block.
646TruthTable Deseq::computeSuccessorCondition(BlockOperand &blockOperand) {
647 // The wait operation of the process is the origin point of the analysis. We
648 // want to know under which conditions drives happen once the wait resumes.
649 // Therefore the branch from the wait to its destination block is expected to
650 // happen.
651 auto *op = blockOperand.getOwner();
652 if (op == wait)
653 return getConstBoolean(true);
654
655 // Return a memoized result if one exists. Otherwise insert a default result
656 // as recursion breaker.
657 if (auto it = successorConditionLattice.find(&blockOperand);
658 it != successorConditionLattice.end())
659 return it->second;
660 successorConditionLattice[&blockOperand] = getConstBoolean(false);
661
662 // Actually compute the condition under which control flows along the given
663 // block operand.
664 auto destIdx = blockOperand.getOperandNumber();
665 auto blockCondition = computeBlockCondition(op->getBlock());
666 auto result = getUnknownBoolean();
667 if (auto branchOp = dyn_cast<cf::BranchOp>(op)) {
668 result = blockCondition;
669 } else if (auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
670 auto branchCondition = computeBoolean(condBranchOp.getCondition());
671 if (destIdx == 0)
672 result = blockCondition & branchCondition;
673 else
674 result = blockCondition & ~branchCondition;
675 } else {
676 result = getPoisonBoolean();
677 }
678
679 // Memoize the result.
681 llvm::dbgs() << "- Successor condition ";
682 op->getBlock()->printAsOperand(llvm::dbgs());
683 llvm::dbgs() << "#succ" << destIdx << " -> ";
684 blockOperand.get()->printAsOperand(llvm::dbgs());
685 llvm::dbgs() << " = " << result << "\n";
686 });
687 successorConditionLattice[&blockOperand] = result;
688 return result;
689}
690
691/// Compute the boolean value of a destination operand when control transfers
692/// along a terminator's block operand to the destination block.
693TruthTable Deseq::computeSuccessorBoolean(BlockOperand &blockOperand,
694 unsigned argIdx) {
695 // Return a memoized result if one exists. Otherwise insert a default result
696 // as recursion breaker.
697 if (auto it = successorBooleanLattice.find({&blockOperand, argIdx});
698 it != successorBooleanLattice.end())
699 return it->second;
700 successorBooleanLattice[{&blockOperand, argIdx}] = getUnknownBoolean();
701
702 // Actually compute the boolean destination operand for the given destination
703 // block.
704 auto *op = blockOperand.getOwner();
705 auto destIdx = blockOperand.getOperandNumber();
706 auto result = getUnknownBoolean();
707 if (auto branchOp = dyn_cast<cf::BranchOp>(op)) {
708 result = computeBoolean(branchOp.getDestOperands()[argIdx]);
709 } else if (auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
710 if (destIdx == 0)
711 result = computeBoolean(condBranchOp.getTrueDestOperands()[argIdx]);
712 else
713 result = computeBoolean(condBranchOp.getFalseDestOperands()[argIdx]);
714 } else {
715 result = getPoisonBoolean();
716 }
717
718 // Memoize the result.
720 llvm::dbgs() << "- Successor boolean ";
721 op->getBlock()->printAsOperand(llvm::dbgs());
722 llvm::dbgs() << "#succ" << destIdx << " -> ";
723 blockOperand.get()->printAsOperand(llvm::dbgs());
724 llvm::dbgs() << "#arg" << argIdx << " = " << result << "\n";
725 });
726 successorBooleanLattice[{&blockOperand, argIdx}] = result;
727 return result;
728}
729
730/// Determine the different values a destination operand may assume when control
731/// transfers along a terminator's block operand to the destination block,
732/// depending on how control flow reaches the terminator.
733ValueTable Deseq::computeSuccessorValue(BlockOperand &blockOperand,
734 unsigned argIdx) {
735 // Return a memoized result if one exists. Otherwise insert a default result
736 // as recursion breaker.
737 if (auto it = successorValueLattice.find({&blockOperand, argIdx});
738 it != successorValueLattice.end())
739 return it->second;
740 successorValueLattice[{&blockOperand, argIdx}] = getUnknownValue();
741
742 // Actually compute the boolean destination operand for the given destination
743 // block.
744 auto *op = blockOperand.getOwner();
745 auto destIdx = blockOperand.getOperandNumber();
746 auto result = getUnknownValue();
747 if (auto branchOp = dyn_cast<cf::BranchOp>(op)) {
748 result = computeValue(branchOp.getDestOperands()[argIdx]);
749 } else if (auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
750 if (destIdx == 0)
751 result = computeValue(condBranchOp.getTrueDestOperands()[argIdx]);
752 else
753 result = computeValue(condBranchOp.getFalseDestOperands()[argIdx]);
754 } else {
755 result = getPoisonValue();
756 }
757
758 // Memoize the result.
760 llvm::dbgs() << "- Successor value ";
761 op->getBlock()->printAsOperand(llvm::dbgs());
762 llvm::dbgs() << "#succ" << destIdx << " -> ";
763 blockOperand.get()->printAsOperand(llvm::dbgs());
764 llvm::dbgs() << "#arg" << argIdx << " = " << result << "\n";
765 });
766 successorValueLattice[{&blockOperand, argIdx}] = result;
767 return result;
768}
769
770//===----------------------------------------------------------------------===//
771// Drive-to-Register Matching
772//===----------------------------------------------------------------------===//
773
774/// Match the drives fed by the process against concrete implementable register
775/// behaviors. Returns false if any of the drives cannot be implemented as a
776/// register.
777bool Deseq::matchDrives() {
778 for (auto &drive : driveInfos)
779 if (!matchDrive(drive))
780 return false;
781 return true;
782}
783
784/// For a given drive op, determine if its drive condition and driven value as
785/// determined by the data flow analysis is implementable by a register op. The
786/// results are stored in the clock and reset info of the given `DriveInfo`.
787/// Returns false if the drive cannot be implemented as a register.
788bool Deseq::matchDrive(DriveInfo &drive) {
789 LLVM_DEBUG(llvm::dbgs() << "- Analyzing " << drive.op << "\n");
790
791 // Determine under which condition the drive is enabled.
792 auto condition = computeBoolean(drive.op.getEnable());
793 if (condition.isPoison()) {
794 LLVM_DEBUG(llvm::dbgs()
795 << "- Aborting: poison condition on " << drive.op << "\n");
796 return false;
797 }
798
799 // Determine which value is driven under which conditions.
800 auto initialValueTable = computeValue(drive.op.getValue());
801 initialValueTable.addCondition(condition);
802 LLVM_DEBUG({
803 llvm::dbgs() << " - Condition: " << condition << "\n";
804 llvm::dbgs() << " - Value: " << initialValueTable << "\n";
805 });
806
807 // Convert the value table from having DNF conditions to having DNFTerm
808 // conditions. This effectively spreads OR operations in the conditions across
809 // multiple table entries.
810 SmallVector<std::pair<DNFTerm, ValueEntry>> valueTable;
811 for (auto &[condition, value] : initialValueTable.entries) {
812 auto dnf = condition.canonicalize();
813 if (dnf.isPoison() || value.isPoison()) {
814 LLVM_DEBUG(llvm::dbgs()
815 << "- Aborting: poison in " << initialValueTable << "\n");
816 return false;
817 }
818 for (auto &orTerm : dnf.orTerms)
819 valueTable.push_back({orTerm, value});
820 }
821
822 // At this point we should have at most three entries in the value table,
823 // corresponding to the reset, clock, and clock under reset. Everything else
824 // we have no chance of representing as a register op.
825 if (valueTable.size() > 3) {
826 LLVM_DEBUG(llvm::dbgs() << "- Aborting: value table has "
827 << valueTable.size() << " distinct conditions\n");
828 return false;
829 }
830
831 // If we have two triggers, one of them must be the reset.
832 if (triggers.size() == 2)
833 return matchDriveClockAndReset(drive, valueTable);
834
835 // Otherwise we only have a single trigger, which is the clock.
836 assert(triggers.size() == 1);
837 return matchDriveClock(drive, valueTable);
838}
839
840/// Assuming there is one trigger, detect the clock scheme represented by a
841/// value table and store the results in `drive.clock`.
842bool Deseq::matchDriveClock(
843 DriveInfo &drive, ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable) {
844 // We need exactly one entry in the value table to represent a register
845 // without reset.
846 if (valueTable.size() != 1) {
847 LLVM_DEBUG(llvm::dbgs() << "- Aborting: single trigger value table has "
848 << valueTable.size() << " entries\n");
849 return false;
850 }
851
852 // Try the posedge and negedge variants of clocking.
853 for (unsigned variant = 0; variant < (1 << 1); ++variant) {
854 bool negClock = (variant >> 0) & 1;
855
856 // Assemble the conditions in the value table corresponding to a clock edge
857 // with and without an additional enable condition. The enable condition is
858 // represented as an additional unknown AND term. The bit patterns here
859 // follow from how we assign indices to past and present triggers, and how
860 // the DNF's even bits represent positive terms and odd bits represent
861 // inverted terms.
862 uint32_t clockEdge = (negClock ? 0b1001 : 0b0110) << 2;
863 auto clockWithoutEnable = DNFTerm{clockEdge};
864 auto clockWithEnable = DNFTerm{clockEdge | 0b01};
865
866 // Check if the single value table entry matches this clock.
867 if (valueTable[0].first == clockWithEnable)
868 drive.clock.enable = drive.op.getEnable();
869 else if (valueTable[0].first != clockWithoutEnable)
870 continue;
871
872 // Populate the clock info and return.
873 drive.clock.clock = triggers[0];
874 drive.clock.risingEdge = !negClock;
875 drive.clock.value = drive.op.getValue();
876 if (!valueTable[0].second.isUnknown())
877 drive.clock.value = valueTable[0].second.value;
878
879 LLVM_DEBUG({
880 llvm::dbgs() << " - Matched " << (negClock ? "neg" : "pos")
881 << "edge clock ";
882 drive.clock.clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
883 llvm::dbgs() << " -> " << valueTable[0].second;
884 if (drive.clock.enable)
885 llvm::dbgs() << " (with enable)";
886 llvm::dbgs() << "\n";
887 });
888 return true;
889 }
890
891 // If we arrive here, none of the patterns we tried matched.
892 LLVM_DEBUG(llvm::dbgs() << "- Aborting: unknown clock scheme\n");
893 return false;
894}
895
896/// Assuming there are two triggers, detect the clock and reset scheme
897/// represented by a value table and store the results in `drive.reset` and
898/// `drive.clock`.
899bool Deseq::matchDriveClockAndReset(
900 DriveInfo &drive, ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable) {
901 // We need exactly three entries in the value table to represent a register
902 // with reset.
903 if (valueTable.size() != 3) {
904 LLVM_DEBUG(llvm::dbgs() << "- Aborting: two trigger value table has "
905 << valueTable.size() << " entries\n");
906 return false;
907 }
908
909 // Resets take precedence over the clock, which shows up as `/rst` and
910 // `/clk&rst` entries in the value table. We simply try all variants until we
911 // find the one that fits.
912 for (unsigned variant = 0; variant < (1 << 3); ++variant) {
913 bool negClock = (variant >> 0) & 1;
914 bool negReset = (variant >> 1) & 1;
915 unsigned clockIdx = (variant >> 2) & 1;
916 unsigned resetIdx = 1 - clockIdx;
917
918 // Assemble the conditions in the value table corresponding to a clock edge
919 // and reset edge, alongside the reset being active and inactive. The bit
920 // patterns here follow from how we assign indices to past and present
921 // triggers, and how the DNF's even bits represent positive terms and odd
922 // bits represent inverted terms.
923 uint32_t clockEdge = (negClock ? 0b1001 : 0b0110) << (clockIdx * 4 + 2);
924 uint32_t resetEdge = (negReset ? 0b1001 : 0b0110) << (resetIdx * 4 + 2);
925 uint32_t resetOn = (negReset ? 0b1000 : 0b0100) << (resetIdx * 4 + 2);
926 uint32_t resetOff = (negReset ? 0b0100 : 0b1000) << (resetIdx * 4 + 2);
927
928 // Combine the above bit masks into conditions for the reset edge, clock
929 // edge with reset active, and clock edge with reset inactive and optional
930 // enable condition.
931 auto reset = DNFTerm{resetEdge};
932 auto clockWhileReset = DNFTerm{clockEdge | resetOn};
933 auto clockWithoutEnable = DNFTerm{clockEdge | resetOff};
934 auto clockWithEnable = DNFTerm{clockEdge | resetOff | 0b01};
935
936 // Find the entries corresponding to the above conditions.
937 auto resetIt = llvm::find_if(
938 valueTable, [&](auto &pair) { return pair.first == reset; });
939 if (resetIt == valueTable.end())
940 continue;
941
942 auto clockWhileResetIt = llvm::find_if(
943 valueTable, [&](auto &pair) { return pair.first == clockWhileReset; });
944 if (clockWhileResetIt == valueTable.end())
945 continue;
946
947 auto clockIt = llvm::find_if(valueTable, [&](auto &pair) {
948 return pair.first == clockWithoutEnable || pair.first == clockWithEnable;
949 });
950 if (clockIt == valueTable.end())
951 continue;
952
953 // Ensure that `/rst` and `/clk&rst` set the register to the same reset
954 // value. Otherwise the reset doesn't have clear precedence over the
955 // clock, and we can't turn this drive into a register.
956 if (clockWhileResetIt->second != resetIt->second ||
957 resetIt->second.isUnknown()) {
958 LLVM_DEBUG(llvm::dbgs() << "- Aborting: inconsistent reset value\n");
959 return false;
960 }
961
962 // Populate the reset and clock info, and return.
963 drive.reset.reset = triggers[resetIdx];
964 drive.reset.value = resetIt->second.value;
965 drive.reset.activeHigh = !negReset;
966
967 drive.clock.clock = triggers[clockIdx];
968 drive.clock.risingEdge = !negClock;
969 if (clockIt->first == clockWithEnable)
970 drive.clock.enable = drive.op.getEnable();
971 drive.clock.value = drive.op.getValue();
972 if (!clockIt->second.isUnknown())
973 drive.clock.value = clockIt->second.value;
974
975 LLVM_DEBUG({
976 llvm::dbgs() << " - Matched " << (negClock ? "neg" : "pos")
977 << "edge clock ";
978 drive.clock.clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
979 llvm::dbgs() << " -> " << clockIt->second;
980 if (drive.clock.enable)
981 llvm::dbgs() << " (with enable)";
982 llvm::dbgs() << "\n";
983 llvm::dbgs() << " - Matched active-" << (negReset ? "low" : "high")
984 << " reset ";
985 drive.reset.reset.printAsOperand(llvm::dbgs(), OpPrintingFlags());
986 llvm::dbgs() << " -> " << resetIt->second << "\n";
987 });
988 return true;
989 }
990
991 // If we arrive here, none of the patterns we tried matched.
992 LLVM_DEBUG(llvm::dbgs() << "- Aborting: unknown reset scheme\n");
993 return false;
994}
995
996//===----------------------------------------------------------------------===//
997// Register Implementation
998//===----------------------------------------------------------------------===//
999
1000/// Make all drives unconditional and implement the conditional behavior with
1001/// register ops.
1002void Deseq::implementRegisters() {
1003 for (auto &drive : driveInfos)
1004 implementRegister(drive);
1005}
1006
1007/// Implement the conditional behavior of a drive with a `seq.compreg` or
1008/// `seq.compreg.ce` op, and make the drive unconditional. This function pulls
1009/// the analyzed clock and reset from the given `DriveInfo` and creates the
1010/// necessary ops outside the process represent the behavior as a register. It
1011/// also calls `specializeValue` and `specializeProcess` to convert the
1012/// sequential `llhd.process` into a purely combinational `llhd.combinational`
1013/// that is simplified by assuming that the clock edge occurs.
1014void Deseq::implementRegister(DriveInfo &drive) {
1015 OpBuilder builder(drive.op);
1016 auto loc = drive.op.getLoc();
1017
1018 // Materialize the clock as a `!seq.clock` value. Insert an inverter for
1019 // negedge clocks.
1020 auto &clockCast = materializedClockCasts[drive.clock.clock];
1021 if (!clockCast)
1022 clockCast = builder.create<seq::ToClockOp>(loc, drive.clock.clock);
1023 auto clock = clockCast;
1024 if (!drive.clock.risingEdge) {
1025 auto &clockInv = materializedClockInverters[clock];
1026 if (!clockInv)
1027 clockInv = builder.create<seq::ClockInverterOp>(loc, clock);
1028 clock = clockInv;
1029 }
1030
1031 // Handle the optional reset.
1032 Value reset;
1033 Value resetValue;
1034
1035 if (drive.reset) {
1036 reset = drive.reset.reset;
1037 resetValue = drive.reset.value;
1038
1039 // Materialize the reset as an `i1` value. Insert an inverter for negedge
1040 // resets.
1041 if (!drive.reset.activeHigh) {
1042 auto &inv = materializedInverters[reset];
1043 if (!inv) {
1044 auto one = builder.create<hw::ConstantOp>(loc, builder.getI1Type(), 1);
1045 inv = builder.create<comb::XorOp>(loc, reset, one);
1046 }
1047 reset = inv;
1048 }
1049
1050 // Specialize the process for the reset trigger. If the reset value is
1051 // trivially available outside the process, use it directly. If it is a
1052 // constant, move the constant outside the process.
1053 if (!resetValue.getParentRegion()->isProperAncestor(&process.getBody())) {
1054 if (auto *defOp = resetValue.getDefiningOp();
1055 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1056 defOp->moveBefore(process);
1057 else
1058 resetValue = specializeValue(
1059 drive.op.getValue(),
1060 FixedValues{{drive.clock.clock, !drive.clock.risingEdge,
1061 !drive.clock.risingEdge},
1062 {drive.reset.reset, !drive.reset.activeHigh,
1063 drive.reset.activeHigh}});
1064 }
1065 }
1066
1067 // Determine the enable condition. If we have determined that the register
1068 // is trivially enabled, don't add an enable. If the enable condition is a
1069 // simple boolean value available outside the process, use it directly.
1070 Value enable = drive.clock.enable;
1071 if (enable && !enable.getParentRegion()->isProperAncestor(&process.getBody()))
1072 enable = drive.op.getEnable();
1073
1074 // Determine the value. If the value is trivially available outside the
1075 // process, use it directly. If it is a constant, move the constant outside
1076 // the process.
1077 Value value = drive.clock.value;
1078 if (!value.getParentRegion()->isProperAncestor(&process.getBody())) {
1079 if (auto *defOp = value.getDefiningOp();
1080 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1081 defOp->moveBefore(process);
1082 else
1083 value = drive.op.getValue();
1084 }
1085
1086 // Specialize the process for the clock trigger, which will produce the
1087 // enable and the value for regular clock edges.
1088 FixedValues fixedValues;
1089 fixedValues.push_back(
1090 {drive.clock.clock, !drive.clock.risingEdge, drive.clock.risingEdge});
1091 if (drive.reset)
1092 fixedValues.push_back(
1093 {drive.reset.reset, !drive.reset.activeHigh, !drive.reset.activeHigh});
1094
1095 value = specializeValue(value, fixedValues);
1096 if (enable)
1097 enable = specializeValue(enable, fixedValues);
1098
1099 // Create the register op.
1100 Value reg;
1101 if (enable)
1102 reg = builder.create<seq::CompRegClockEnabledOp>(
1103 loc, value, clock, enable, StringAttr{}, reset, resetValue, Value{},
1104 hw::InnerSymAttr{});
1105 else
1106 reg =
1107 builder.create<seq::CompRegOp>(loc, value, clock, StringAttr{}, reset,
1108 resetValue, Value{}, hw::InnerSymAttr{});
1109
1110 // Make the original `llhd.drv` drive the register value unconditionally.
1111 drive.op.getValueMutable().assign(reg);
1112 drive.op.getEnableMutable().clear();
1113
1114 // If the original `llhd.drv` had a delta delay, turn it into an immediate
1115 // drive since the delay behavior is now capture by the register op.
1116 TimeAttr attr;
1117 if (matchPattern(drive.op.getTime(), m_Constant(&attr)) &&
1118 attr.getTime() == 0 && attr.getDelta() == 1 && attr.getEpsilon() == 0) {
1119 if (!epsilonDelay)
1120 epsilonDelay =
1121 builder.create<ConstantTimeOp>(process.getLoc(), 0, "ns", 0, 1);
1122 drive.op.getTimeMutable().assign(epsilonDelay);
1123 }
1124}
1125
1126//===----------------------------------------------------------------------===//
1127// Process Specialization
1128//===----------------------------------------------------------------------===//
1129
1130/// Specialize a value by assuming the values listed in `fixedValues` are at a
1131/// constant value in the past and the present. The function is guaranteed to
1132/// replace results of the process with results of a new combinational op. All
1133/// other behavior is purely an optimization; the function may not make use of
1134/// the assignments in `fixedValues` at all.
1135Value Deseq::specializeValue(Value value, FixedValues fixedValues) {
1136 auto result = dyn_cast<OpResult>(value);
1137 if (!result || result.getOwner() != process)
1138 return value;
1139 return specializeProcess(fixedValues)[result.getResultNumber()];
1140}
1141
1142/// Specialize the current process by assuming the values listed in
1143/// `fixedValues` are at a constant value in the past and the present. This
1144/// function creates a new combinational op with a simplified version of the
1145/// process where all uses of the values listed in `fixedValues` are replaced
1146/// with their constant counterpart. Since the clock-dependent behavior of the
1147/// process has been absorbed into a register, the process can be replaced with
1148/// a combinational representation that computes the drive value and drive
1149/// condition under the assumption that the clock edge occurs.
1150ValueRange Deseq::specializeProcess(FixedValues fixedValues) {
1151 if (auto it = specializedProcesses.find(fixedValues);
1152 it != specializedProcesses.end())
1153 return it->second;
1154
1155 LLVM_DEBUG({
1156 llvm::dbgs() << "- Specializing process for:\n";
1157 for (auto fixedValue : fixedValues) {
1158 llvm::dbgs() << " - ";
1159 fixedValue.value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1160 llvm::dbgs() << ": " << fixedValue.past << " -> " << fixedValue.present
1161 << "\n";
1162 }
1163 });
1164
1165 // Create an `llhd.combinational` op with this process specialized to compute
1166 // the result for the given fixed values. The triggers will be absorbed into
1167 // the register operation that consumes the result of this specialized
1168 // process, such that we can make the process purely combinational.
1169 OpBuilder builder(process);
1170 auto executeOp = builder.create<CombinationalOp>(process.getLoc(),
1171 process.getResultTypes());
1172
1173 IRMapping mapping;
1174 SmallVector<std::pair<Block *, Block *>> worklist;
1175
1176 auto scheduleBlock = [&](Block *block) {
1177 if (auto *newBlock = mapping.lookupOrNull(block))
1178 return newBlock;
1179 auto *newBlock = &executeOp.getRegion().emplaceBlock();
1180 for (auto arg : block->getArguments()) {
1181 auto newArg = newBlock->addArgument(arg.getType(), arg.getLoc());
1182 mapping.map(arg, newArg);
1183 }
1184 mapping.map(block, newBlock);
1185 worklist.push_back({block, newBlock});
1186 return newBlock;
1187 };
1188
1189 // Initialize the mapping with constants for the fixed values.
1190 auto &entryBlock = executeOp.getRegion().emplaceBlock();
1191 builder.setInsertionPointToStart(&entryBlock);
1192 auto i1 = builder.getI1Type();
1193 auto trueValue = builder.create<hw::ConstantOp>(process.getLoc(), i1, 1);
1194 auto falseValue = builder.create<hw::ConstantOp>(process.getLoc(), i1, 0);
1195
1196 SmallDenseMap<Value, std::pair<Value, Value>, 2> materializedFixedValues;
1197 for (auto fixedValue : fixedValues) {
1198 auto present = fixedValue.present ? trueValue : falseValue;
1199 auto past = fixedValue.past ? trueValue : falseValue;
1200 materializedFixedValues.insert({fixedValue.value, {past, present}});
1201 mapping.map(fixedValue.value, present);
1202 }
1203
1204 // Compute the truth table that is true for the given fixed values, and false
1205 // otherwise. We will use that table to quickly evaluate booleans later.
1206 auto fixedTable = getConstBoolean(true);
1207 for (auto [index, value] : llvm::enumerate(triggers)) {
1208 for (auto fixedValue : fixedValues) {
1209 if (fixedValue.value != value)
1210 continue;
1211 auto past = getPastTrigger(index);
1212 fixedTable &= fixedValue.past ? past : ~past;
1213 auto present = getPresentTrigger(index);
1214 fixedTable &= fixedValue.present ? present : ~present;
1215 break;
1216 }
1217 }
1218
1219 // Clone operations over.
1220 auto cloneBlocks = [&](bool stopAtWait) {
1221 SmallVector<Value> foldedResults;
1222 while (!worklist.empty()) {
1223 auto [oldBlock, newBlock] = worklist.pop_back_val();
1224 builder.setInsertionPointToEnd(newBlock);
1225 for (auto &oldOp : *oldBlock) {
1226 // Convert `llhd.wait` into `llhd.yield`.
1227 if (auto waitOp = dyn_cast<WaitOp>(oldOp)) {
1228 if (stopAtWait)
1229 continue;
1230 SmallVector<Value> operands;
1231 for (auto operand : waitOp.getYieldOperands())
1232 operands.push_back(mapping.lookupOrDefault(operand));
1233 builder.create<YieldOp>(waitOp.getLoc(), operands);
1234 continue;
1235 }
1236
1237 // Convert `cf.cond_br` ops into `cf.br` if the condition is constant.
1238 if (auto condBranchOp = dyn_cast<cf::CondBranchOp>(oldOp)) {
1239 SmallVector<Value> operands;
1240 auto condition = mapping.lookupOrDefault(condBranchOp.getCondition());
1241 if (matchPattern(condition, m_NonZero())) {
1242 for (auto operand : condBranchOp.getTrueDestOperands())
1243 operands.push_back(mapping.lookupOrDefault(operand));
1244 builder.create<cf::BranchOp>(
1245 condBranchOp.getLoc(),
1246 scheduleBlock(condBranchOp.getTrueDest()), operands);
1247 continue;
1248 }
1249 if (matchPattern(condition, m_Zero())) {
1250 for (auto operand : condBranchOp.getFalseOperands())
1251 operands.push_back(mapping.lookupOrDefault(operand));
1252 builder.create<cf::BranchOp>(
1253 condBranchOp.getLoc(),
1254 scheduleBlock(condBranchOp.getFalseDest()), operands);
1255 continue;
1256 }
1257 }
1258
1259 // If our initial data flow analysis has produced a concrete boolean
1260 // value for an `i1`-valued op, see if it evaluates to a constant true
1261 // or false with the given fixed values.
1262 if (oldOp.getNumResults() == 1 &&
1263 oldOp.getResult(0).getType().isSignlessInteger(1)) {
1264 if (auto it = booleanLattice.find(oldOp.getResult(0));
1265 it != booleanLattice.end()) {
1266 if ((it->second & fixedTable).isFalse()) {
1267 mapping.map(oldOp.getResult(0), falseValue);
1268 continue;
1269 }
1270 if ((it->second & fixedTable) == fixedTable) {
1271 mapping.map(oldOp.getResult(0), trueValue);
1272 continue;
1273 }
1274 }
1275 }
1276
1277 // Otherwise clone the operation.
1278 for (auto &blockOperand : oldOp.getBlockOperands())
1279 scheduleBlock(blockOperand.get());
1280 auto *clonedOp = builder.clone(oldOp, mapping);
1281
1282 // And immediately try to fold the cloned operation since the fixed
1283 // values introduce a lot of constants into the IR.
1284 if (succeeded(builder.tryFold(clonedOp, foldedResults)) &&
1285 !foldedResults.empty()) {
1286 for (auto [oldResult, foldedResult] :
1287 llvm::zip(oldOp.getResults(), foldedResults))
1288 mapping.map(oldResult, foldedResult);
1289 clonedOp->erase();
1290 }
1291 foldedResults.clear();
1292 }
1293 }
1294 };
1295
1296 // Start at the entry block of the original process and clone all ops until
1297 // we hit the wait.
1298 worklist.push_back({&process.getBody().front(), &entryBlock});
1299 cloneBlocks(true);
1300 builder.setInsertionPointToEnd(mapping.lookup(wait->getBlock()));
1301
1302 // Remove all blocks from the IR mapping. Some blocks may be reachable from
1303 // the entry block and the wait op, in which case we want to create
1304 // duplicates of those blocks.
1305 for (auto &block : process.getBody())
1306 mapping.erase(&block);
1307
1308 // If the wait op is not the only predecessor of its destination block,
1309 // create a branch op to the block. Otherwise inline the destination block
1310 // into the entry block, which allows the specialization to fold more
1311 // constants.
1312 if (wait.getDest()->hasOneUse()) {
1313 // Map the block arguments of the block after the wait op to the constant
1314 // fixed values.
1315 for (auto [arg, pastValue] :
1316 llvm::zip(wait.getDest()->getArguments(), pastValues))
1317 mapping.map(arg, materializedFixedValues.lookup(pastValue).first);
1318
1319 // Schedule the block after the wait for cloning into the entry block.
1320 mapping.map(wait.getDest(), builder.getBlock());
1321 worklist.push_back({wait.getDest(), builder.getBlock()});
1322 } else {
1323 // Schedule the block after the wait for cloning.
1324 auto *dest = scheduleBlock(wait.getDest());
1325
1326 // From the entry block, branch to the block after the wait with the
1327 // appropriate past values as block arguments.
1328 SmallVector<Value> destOperands;
1329 assert(pastValues.size() == wait.getDestOperands().size());
1330 for (auto pastValue : pastValues)
1331 destOperands.push_back(materializedFixedValues.lookup(pastValue).first);
1332 builder.create<cf::BranchOp>(wait.getLoc(), dest, destOperands);
1333 }
1334
1335 // Clone everything after the wait operation.
1336 cloneBlocks(false);
1337
1338 // Don't leave unused constants behind.
1339 if (isOpTriviallyDead(trueValue))
1340 trueValue.erase();
1341 if (isOpTriviallyDead(falseValue))
1342 falseValue.erase();
1343
1344 specializedProcesses.insert({fixedValues, executeOp.getResults()});
1345 return executeOp.getResults();
1346}
1347
1348//===----------------------------------------------------------------------===//
1349// Pass Infrastructure
1350//===----------------------------------------------------------------------===//
1351
1352namespace {
1353struct DeseqPass : public llhd::impl::DeseqPassBase<DeseqPass> {
1354 void runOnOperation() override;
1355};
1356} // namespace
1357
1358void DeseqPass::runOnOperation() {
1359 SmallVector<ProcessOp> processes(getOperation().getOps<ProcessOp>());
1360 for (auto process : processes)
1361 Deseq(process).deseq();
1362}
assert(baseType &&"element must be base type")
#define VERBOSE_DEBUG(...)
Definition Deseq.cpp:29
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
SmallVector< FixedValue, 2 > FixedValues
A list of i1 values that are fixed to a given value.
Definition DeseqUtils.h:256
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition seq.py:21
Value clock
The value acting as the clock, causing the register to be set to a value in valueTable when triggered...
Definition DeseqUtils.h:199
bool risingEdge
Whether the clock is sensitive to a rising or falling edge.
Definition DeseqUtils.h:203
Value value
The value the register is set to when the clock is triggered.
Definition DeseqUtils.h:201
Value enable
The optional value acting as an enable.
Definition DeseqUtils.h:205
A single AND operation within a DNF.
Definition DeseqUtils.h:25
A drive op and the clock and reset that resulted from trigger analysis.
Definition DeseqUtils.h:216
ClockInfo clock
The clock that triggers a change to the driven value.
Definition DeseqUtils.h:221
DrvOp op
The drive operation.
Definition DeseqUtils.h:218
ResetInfo reset
The optional reset that triggers a change of the driven value to a fixed reset value.
Definition DeseqUtils.h:224
Value value
The value the register is reset to.
Definition DeseqUtils.h:187
Value reset
The value acting as the reset, causing the register to be set to value when triggered.
Definition DeseqUtils.h:185
bool activeHigh
Whether the reset is active when high.
Definition DeseqUtils.h:189
A boolean function expressed as a truth table.
Definition DeseqUtils.h:62
static TruthTable getTerm(unsigned numTerms, unsigned term)
Create a boolean expression consisting of a single term.
Definition DeseqUtils.h:91
static TruthTable getPoison()
Definition DeseqUtils.h:78
static TruthTable getConst(unsigned numTerms, bool value)
Create a boolean expression with a constant true or false value.
Definition DeseqUtils.h:84
static ValueEntry getUnknown()
Definition DeseqUtils.h:149
static ValueEntry getPoison()
Definition DeseqUtils.h:146
A table of SSA values and the conditions under which they appear.
Definition DeseqUtils.h:160
void merge(const ValueTable &other)