CIRCT 24.0.0git
Loading...
Searching...
No Matches
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"
16#include "mlir/Analysis/Liveness.h"
17#include "mlir/Dialect/Arith/IR/Arith.h"
18#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
19#include "mlir/IR/Dominance.h"
20#include "mlir/IR/IRMapping.h"
21#include "mlir/IR/Matchers.h"
22#include "mlir/Transforms/RegionUtils.h"
23#include "llvm/ADT/ScopeExit.h"
24#include "llvm/Support/Debug.h"
25#include "llvm/Support/GenericIteratedDominanceFrontier.h"
26
27// Provide a `llhd-deseq` debug option for some high-level observability, and
28// `llhd-deseq-verbose` for additional prints that trace out concrete values
29// propagated across the IR.
30#define DEBUG_TYPE "llhd-deseq"
31#define VERBOSE_DEBUG(...) DEBUG_WITH_TYPE(DEBUG_TYPE "-verbose", __VA_ARGS__)
32
33namespace circt {
34namespace llhd {
35#define GEN_PASS_DEF_DESEQPASS
36#include "circt/Dialect/LLHD/LLHDPasses.h.inc"
37} // namespace llhd
38} // namespace circt
39
40using namespace mlir;
41using namespace circt;
42using namespace llhd;
43using namespace deseq;
45
46namespace {
47
48/// Trace a block argument back through the CFG to find a unique defining value.
49/// If all predecessor branches pass the same value for this argument, return
50/// that value. Otherwise return the original block argument.
51static Value canonicalizeBlockArg(BlockArgument arg,
52 SmallPtrSetImpl<Block *> &visited) {
53 Block *block = arg.getOwner();
54 if (!visited.insert(block).second)
55 return arg; // Cycle detected, bail out.
56
57 Value candidate;
58 for (auto *pred : block->getPredecessors()) {
59 auto *term = pred->getTerminator();
60 Value passedValue;
61
62 // Handle branch operations.
63 if (auto br = dyn_cast<cf::BranchOp>(term)) {
64 if (br.getDest() == block)
65 passedValue = br.getDestOperands()[arg.getArgNumber()];
66 } else if (auto condBr = dyn_cast<cf::CondBranchOp>(term)) {
67 if (condBr.getTrueDest() == block)
68 passedValue = condBr.getTrueDestOperands()[arg.getArgNumber()];
69 else if (condBr.getFalseDest() == block)
70 passedValue = condBr.getFalseDestOperands()[arg.getArgNumber()];
71 } else if (auto wait = dyn_cast<WaitOp>(term)) {
72 if (wait.getDest() == block)
73 passedValue = wait.getDestOperands()[arg.getArgNumber()];
74 } else {
75 // Unknown terminator, can't trace.
76 return arg;
77 }
78
79 if (!passedValue)
80 return arg;
81
82 // Recursively trace if this is also a block argument.
83 if (auto passedArg = dyn_cast<BlockArgument>(passedValue))
84 passedValue = canonicalizeBlockArg(passedArg, visited);
85
86 // Check if all predecessors pass the same value.
87 if (!candidate)
88 candidate = passedValue;
89 else if (candidate != passedValue)
90 return arg; // Different values from different preds.
91 }
92
93 return candidate ? candidate : arg;
94}
95
96/// Convert a value into a (base, fieldID, bitID, bitWidth) key.
97///
98/// - `fieldID == 0` denotes the whole value.
99/// - `fieldID != 0` denotes a stable subfield of `base`.
100/// - `bitID != 0` denotes an additional bit/slice projection within the
101/// selected subfield (e.g., `comb.extract` from an array element).
102/// - `bitWidth != 0` denotes the width (in bits) of the final extracted slice.
103///
104/// This is used to unify equivalent projections across different SSA values in
105/// the process CFG (e.g., past/present clock bits extracted from an observed
106/// bus).
107static ValueField getValueField(Value value) {
108 if (!value)
109 return {};
110
111 // Struct field.
112 if (auto se = value.getDefiningOp<hw::StructExtractOp>()) {
113 Value base = se.getInput();
114 if (auto arg = dyn_cast<BlockArgument>(base)) {
115 SmallPtrSet<Block *, 4> visited;
116 base = canonicalizeBlockArg(arg, visited);
117 }
118 auto baseVF = getValueField(base);
119
120 auto structType =
121 hw::type_dyn_cast<hw::StructType>(se.getInput().getType());
122 if (!structType)
123 return {value, 0, value};
124
125 uint64_t idx = se.getFieldIndex();
126 uint64_t childID = hw::FieldIdImpl::getFieldID(structType, idx);
127 return {baseVF.value, baseVF.fieldID + childID, value};
128 }
129
130 // Array element with constant index.
131 if (auto ae = value.getDefiningOp<hw::ArrayGetOp>()) {
132 Value base = ae.getInput();
133 Value index = ae.getIndex();
134
135 // Fold `array_get (array_slice ...)` into an access of the original array
136 // if both indices are constant.
137 std::optional<uint64_t> idx;
138 if (auto cst = index.getDefiningOp<hw::ConstantOp>())
139 idx = cst.getValue().getZExtValue();
140
141 if (auto slice = base.getDefiningOp<hw::ArraySliceOp>()) {
142 if (auto sliceIdx = slice.getLowIndex().getDefiningOp<hw::ConstantOp>())
143 if (auto getIdx = index.getDefiningOp<hw::ConstantOp>()) {
144 idx = sliceIdx.getValue().getZExtValue() +
145 getIdx.getValue().getZExtValue();
146 base = slice.getInput();
147 }
148 }
149
150 if (!idx)
151 return {value, 0, value};
152
153 if (auto arg = dyn_cast<BlockArgument>(base)) {
154 SmallPtrSet<Block *, 4> visited;
155 base = canonicalizeBlockArg(arg, visited);
156 }
157 auto baseVF = getValueField(base);
158
159 if (auto arrayType = dyn_cast<hw::ArrayType>(base.getType())) {
160 uint64_t childID = hw::FieldIdImpl::getFieldID(arrayType, *idx);
161 return {baseVF.value, baseVF.fieldID + childID, value};
162 }
163 if (auto arrayType = dyn_cast<hw::UnpackedArrayType>(base.getType())) {
164 uint64_t childID = hw::FieldIdImpl::getFieldID(arrayType, *idx);
165 return {baseVF.value, baseVF.fieldID + childID, value};
166 }
167
168 return {value, 0, value};
169 }
170
171 // Bit slice with static low bit: use lowBit+1 to distinguish from whole.
172 if (auto ext = value.getDefiningOp<comb::ExtractOp>()) {
173 Value base = ext.getInput();
174 if (auto arg = dyn_cast<BlockArgument>(base)) {
175 SmallPtrSet<Block *, 4> visited;
176 base = canonicalizeBlockArg(arg, visited);
177 }
178 auto baseVF = getValueField(base);
179 uint64_t lowBit = static_cast<uint64_t>(ext.getLowBit());
180 auto intType = dyn_cast<IntegerType>(ext.getType());
181 if (!intType)
182 return {value, 0, value};
183 uint64_t bitWidth = intType.getWidth();
184
185 // Integer root: accumulate the low bit into the root `fieldID`.
186 if (baseVF.value.getType().isSignlessInteger()) {
187 uint64_t fieldID = baseVF.fieldID ? baseVF.fieldID + lowBit : lowBit + 1;
188 return {baseVF.value, fieldID, value, 0, bitWidth};
189 }
190
191 // Non-integer root: interpret extracts as bit/slice projections within a
192 // selected aggregate field.
193 if (baseVF.fieldID == 0)
194 return {value, 0, value};
195 uint64_t bitID = baseVF.bitID ? baseVF.bitID + lowBit : lowBit + 1;
196 return {baseVF.value, baseVF.fieldID, value, bitID, bitWidth};
197 }
198
199 // Fallback: whole value.
200 return {value, 0, value};
201}
202
203/// The work horse promoting processes into concrete registers.
204struct Deseq {
205 Deseq(ProcessOp process) : process(process) {}
206 LogicalResult deseq();
207
208 bool analyzeProcess();
209 Value tracePastValue(Value pastValue);
210
211 TruthTable computeBoolean(Value value);
212 ValueTable computeValue(Value value);
213 TruthTable computeBoolean(ValueField value);
214 TruthTable computeBoolean(OpResult value);
215 ValueTable computeValue(OpResult value);
216 TruthTable computeBoolean(BlockArgument value);
217 ValueTable computeValue(BlockArgument arg);
218 TruthTable computeBlockCondition(Block *block);
219 TruthTable computeSuccessorCondition(BlockOperand &operand);
220 TruthTable computeSuccessorBoolean(BlockOperand &operand, unsigned argIdx);
221 ValueTable computeSuccessorValue(BlockOperand &operand, unsigned argIdx);
222
223 bool matchDrives();
224 bool matchDrive(DriveInfo &drive);
225 bool matchDriveClock(DriveInfo &drive,
226 ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable);
227 bool
228 matchDriveClockAndReset(DriveInfo &drive,
229 ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable);
230
231 Value materializeProjection(OpBuilder &builder, Location loc, Value value,
232 SmallDenseMap<Value, Value, 8> &cache);
233
234 LogicalResult implementRegisters();
235 LogicalResult implementRegister(DriveInfo &drive);
236
237 Value specializeValue(Value value, FixedValues fixedValues);
238 ValueRange specializeProcess(FixedValues fixedValues);
239
240 /// The process we are desequentializing.
241 ProcessOp process;
242 /// The single wait op of the process.
243 WaitOp wait;
244 /// The boolean values observed by the wait. These trigger the process and
245 /// may cause the described register to update its value.
246 SmallSetVector<ValueField, 2> triggers;
247 /// The values carried from the past into the present as destination operands
248 /// of the wait op. These values are guaranteed to also be contained in
249 /// `triggers`.
250 SmallVector<Value, 2> pastValues;
251 /// The conditional drive operations fed by this process.
252 SmallVector<DriveInfo> driveInfos;
253 /// Specializations of the process for different trigger values.
254 SmallDenseMap<FixedValues, ValueRange, 2> specializedProcesses;
255 /// A cache of `seq.to_clock` ops.
256 SmallDenseMap<Value, Value, 1> materializedClockCasts;
257 /// A cache of `seq.clock_inv` ops.
258 SmallDenseMap<Value, Value, 1> materializedClockInverters;
259 /// A cache of `comb.xor` ops used as inverters.
260 SmallDenseMap<Value, Value, 1> materializedInverters;
261 /// A map of operations that have been checked to be valid reset values.
262 DenseMap<Operation *, bool> staticOps;
263
264 /// The boolean expression computed for an `i1` value in the IR.
265 DenseMap<ValueField, TruthTable> booleanLattice;
266 /// The value table computed for an SSA value in the IR. This essentially
267 /// lists what values an SSA value assumes under certain conditions.
268 DenseMap<Value, ValueTable> valueLattice;
269 /// The condition under which control flow reaches a block. The block
270 /// immediately following the wait op has this set to true; any further
271 /// conditional branches will refine the condition of successor blocks.
272 DenseMap<Block *, TruthTable> blockConditionLattice;
273 /// The condition under which control flows along a terminator's block operand
274 /// to its destination.
275 DenseMap<BlockOperand *, TruthTable> successorConditionLattice;
276 /// The boolean expression passed from a terminator to its destination as a
277 /// destination block operand.
278 DenseMap<std::pair<BlockOperand *, unsigned>, TruthTable>
279 successorBooleanLattice;
280 /// The value table passed from a terminator to its destination as a
281 /// destination block operand.
282 DenseMap<std::pair<BlockOperand *, unsigned>, ValueTable>
283 successorValueLattice;
284
285private:
286 // Utilities to create boolean truth tables. These make working with truth
287 // tables easier, since the calling code doesn't have to care about how
288 // triggers and unknown value markers are packed into truth table columns.
289 TruthTable getPoisonBoolean() const { return TruthTable::getPoison(); }
290 TruthTable getUnknownBoolean() const {
291 return TruthTable::getTerm(triggers.size() * 2 + 1, 0);
292 }
293 TruthTable getConstBoolean(bool value) const {
294 return TruthTable::getConst(triggers.size() * 2 + 1, value);
295 }
296 TruthTable getPastTrigger(unsigned triggerIndex) const {
297 return TruthTable::getTerm(triggers.size() * 2 + 1, triggerIndex * 2 + 1);
298 }
299 TruthTable getPresentTrigger(unsigned triggerIndex) const {
300 return TruthTable::getTerm(triggers.size() * 2 + 1, triggerIndex * 2 + 2);
301 }
302
303 // Utilities to create value tables. These make working with value tables
304 // easier, since the calling code doesn't have to care about how the truth
305 // tables and value tables are constructed.
306 ValueTable getUnknownValue() const {
307 return ValueTable(getConstBoolean(true), ValueEntry::getUnknown());
308 }
309 ValueTable getPoisonValue() const {
310 return ValueTable(getConstBoolean(true), ValueEntry::getPoison());
311 }
312 ValueTable getKnownValue(Value value) const {
313 return ValueTable(getConstBoolean(true), value);
314 }
315};
316} // namespace
317
318/// Try to lower the process to a set of registers.
319LogicalResult Deseq::deseq() {
320 // Check whether the process meets the basic criteria for being replaced by a
321 // register. This includes having only a single `llhd.wait` op and feeding
322 // only particular kinds of `llhd.drv` and `llhd.assign` ops.
323 if (!analyzeProcess())
324 return success();
325 LLVM_DEBUG({
326 llvm::dbgs() << "Desequentializing " << process.getLoc() << "\n";
327 llvm::dbgs() << "- Feeds " << driveInfos.size() << " conditional drives\n";
328 llvm::dbgs() << "- " << triggers.size() << " potential triggers:\n";
329 for (auto [index, trigger] : llvm::enumerate(triggers)) {
330 llvm::dbgs() << " - ";
331 trigger.getProjected().printAsOperand(llvm::dbgs(), OpPrintingFlags());
332 llvm::dbgs() << ": past " << getPastTrigger(index);
333 llvm::dbgs() << ", present " << getPresentTrigger(index);
334 llvm::dbgs() << "\n";
335 }
336 });
337
338 // For each drive fed by this process determine the exact triggers that cause
339 // them to drive a new value, and ensure that the behavior can be represented
340 // by a register.
341 if (!matchDrives())
342 return success();
343
344 // Make the drives unconditional and capture the conditional behavior as
345 // register operations.
346 if (failed(implementRegisters()))
347 return failure();
348
349 // At this point the process has been replaced with specialized versions of it
350 // for the different triggers and can be removed.
351 process.erase();
352 return success();
353}
354
355//===----------------------------------------------------------------------===//
356// Process Analysis
357//===----------------------------------------------------------------------===//
358
359/// Determine whether we can desequentialize the current process. Also gather
360/// the wait and drive ops that are relevant.
361bool Deseq::analyzeProcess() {
362 // We can only desequentialize processes with no side-effecting ops besides
363 // the `WaitOp` or `HaltOp` terminators.
364 for (auto &block : process.getBody()) {
365 for (auto &op : block) {
366 if (isa<WaitOp, HaltOp>(op))
367 continue;
368 if (!isMemoryEffectFree(&op)) {
369 LLVM_DEBUG({
370 llvm::dbgs() << "Skipping " << process.getLoc()
371 << ": contains side-effecting op ";
372 op.print(llvm::dbgs(), OpPrintingFlags().skipRegions());
373 llvm::dbgs() << "\n";
374 });
375 return false;
376 }
377 }
378 }
379
380 // Find the single wait op.
381 for (auto &block : process.getBody()) {
382 if (auto candidate = dyn_cast<WaitOp>(block.getTerminator())) {
383 if (wait) {
384 LLVM_DEBUG(llvm::dbgs() << "Skipping " << process.getLoc()
385 << ": has multiple waits\n");
386 return false;
387 }
388 wait = candidate;
389 }
390 }
391 if (!wait) {
392 LLVM_DEBUG(llvm::dbgs()
393 << "Skipping " << process.getLoc() << ": has no wait\n");
394 return false;
395 }
396
397 // Ensure that all process results lead to conditional drive operations.
398 SmallPtrSet<Operation *, 8> seenDrives;
399 for (auto &use : process->getUses()) {
400 auto driveOp = dyn_cast<DriveLikeOp>(use.getOwner());
401 if (!driveOp) {
402 LLVM_DEBUG(llvm::dbgs()
403 << "Skipping " << process.getLoc() << ": feeds non-drive "
404 << use.getOwner()->getLoc() << "\n");
405 return false;
406 }
407 // We can only deal with conditional drives.
408 if (!driveOp.getEnable()) {
409 LLVM_DEBUG(llvm::dbgs()
410 << "Skipping " << process.getLoc()
411 << ": feeds unconditional drive " << driveOp << "\n");
412 return false;
413 }
414
415 // We can only deal with the process result being used as drive value or
416 // condition.
417 if (&use != &driveOp.getValueMutable()[0] &&
418 !llvm::is_contained(
419 llvm::make_pointer_range(driveOp.getEnableMutable()), &use)) {
420 LLVM_DEBUG(llvm::dbgs()
421 << "Skipping " << process.getLoc()
422 << ": feeds drive operand that is neither value nor enable: "
423 << driveOp << "\n");
424 return false;
425 }
426
427 if (!seenDrives.insert(driveOp).second)
428 continue;
429
430 driveInfos.push_back(DriveInfo(driveOp));
431 }
432
433 // Collect triggers from observed values. We support either:
434 // 1. Direct i1 observed values (traditional case)
435 // 2. Non-i1 observed values where dest operands are i1 projections (e.g.,
436 // comb.extract) - in this case the projections become the triggers
437 bool hasNonI1Observed = false;
438 for (auto value : wait.getObserved()) {
439 if (!value.getType().isSignlessInteger(1))
440 hasNonI1Observed = true;
441 }
442
443 if (!hasNonI1Observed) {
444 // Traditional case: observed values are i1, use them directly as triggers.
445 for (auto value : wait.getObserved())
446 triggers.insert(getValueField(value));
447 } else {
448 // Projected clock case: find i1 dest operands that are projections of
449 // observed values. These become our triggers.
450 for (auto operand : wait.getDestOperands()) {
451 if (!operand.getType().isSignlessInteger(1))
452 continue;
453 auto vf = getValueField(operand);
454 // Check if this is a projection (fieldID != 0) of an observed value.
455 if (vf.fieldID != 0 && llvm::is_contained(wait.getObserved(), vf.value)) {
456 triggers.insert(vf);
457 }
458 }
459 }
460
461 // We only support 1 or 2 observed values, since we map to registers with a
462 // clock and an optional async reset.
463 if (triggers.empty() || triggers.size() > 2) {
464 LLVM_DEBUG(llvm::dbgs() << "Skipping " << process.getLoc() << ": observes "
465 << triggers.size() << " values\n");
466 return false;
467 }
468
469 // Seed the drive value analysis with the triggers.
470 for (auto [index, trigger] : llvm::enumerate(triggers))
471 booleanLattice.insert({trigger, getPresentTrigger(index)});
472
473 // Process the wait op destination operands, i.e. the values passed from the
474 // past into the present. For projected clocks, the dest operand itself may be
475 // a trigger; otherwise trace back to find the observed value it came from.
476 for (auto [operand, blockArg] :
477 llvm::zip(wait.getDestOperands(), wait.getDest()->getArguments())) {
478 // Check if this dest operand is directly a trigger (projected clock case).
479 auto operandVF = getValueField(operand);
480 const auto *it = llvm::find(triggers, operandVF);
481 if (it != triggers.end()) {
482 unsigned index = std::distance(triggers.begin(), it);
483 pastValues.push_back(it->getProjected());
484 booleanLattice.insert({getValueField(blockArg), getPastTrigger(index)});
485 continue;
486 }
487 // Non-i1 dest operands are only allowed if they are observed values
488 // (for projected clocks, the bus is passed through but not used as
489 // trigger).
490 if (!operand.getType().isSignlessInteger(1)) {
491 if (llvm::is_contained(wait.getObserved(), operand))
492 continue; // Observed bus passed through - OK for projected clocks.
493 LLVM_DEBUG(llvm::dbgs() << "Skipping " << process.getLoc()
494 << ": uses non-i1 past value\n");
495 return false;
496 }
497 // Traditional case: trace back to find the observed value.
498 auto trigger = tracePastValue(operand);
499 if (!trigger)
500 return false;
501 pastValues.push_back(trigger);
502 unsigned index = std::distance(
503 triggers.begin(), llvm::find(triggers, getValueField(trigger)));
504 booleanLattice.insert({getValueField(blockArg), getPastTrigger(index)});
505 }
506
507 return true;
508}
509
510/// Trace a value passed from the past into the present as a destination operand
511/// of the wait op back to a single observed value. Returns a null value if the
512/// value does not trace back to a single, unique observed value.
513Value Deseq::tracePastValue(Value pastValue) {
514 // Use a worklist to look through branches and a few common IR patterns to
515 // find the concrete value used as a destination operand.
516 SmallVector<Value> worklist;
517 SmallPtrSet<Value, 8> seen;
518 worklist.push_back(pastValue);
519 seen.insert(pastValue);
520
521 SmallPtrSet<Block *, 2> predSeen;
522 SmallSetVector<BlockOperand *, 4> predWorklist;
523 SmallPtrSet<Value, 2> distinctValues;
524 while (!worklist.empty()) {
525 auto value = worklist.pop_back_val();
526 auto arg = dyn_cast<BlockArgument>(value);
527
528 // If this is one of the observed values, we're done. Otherwise trace
529 // block arguments backwards to their predecessors.
530 if (const auto *it = llvm::find(triggers, getValueField(value));
531 it != triggers.end()) {
532 distinctValues.insert(it->getProjected());
533 continue;
534 }
535 if (!arg) {
536 distinctValues.insert(value);
537 continue;
538 }
539
540 // Collect the predecessor block operands to process.
541 predSeen.clear();
542 predWorklist.clear();
543 for (auto *predecessor : arg.getOwner()->getPredecessors())
544 if (predSeen.insert(predecessor).second)
545 for (auto &operand : predecessor->getTerminator()->getBlockOperands())
546 if (operand.get() == arg.getOwner())
547 predWorklist.insert(&operand);
548
549 // Handle the predecessors. This essentially is a loop over all block
550 // arguments in terminator ops that branch to arg's block.
551 unsigned argIdx = arg.getArgNumber();
552 for (auto *blockOperand : predWorklist) {
553 auto *op = blockOperand->getOwner();
554 if (auto branchOp = dyn_cast<cf::BranchOp>(op)) {
555 // Handle unconditional branches.
556 auto operand = branchOp.getDestOperands()[argIdx];
557 if (seen.insert(operand).second)
558 worklist.push_back(operand);
559 } else if (auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
560 // Handle conditional branches.
561 unsigned destIdx = blockOperand->getOperandNumber();
562 auto operand = destIdx == 0
563 ? condBranchOp.getTrueDestOperands()[argIdx]
564 : condBranchOp.getFalseDestOperands()[argIdx];
565
566 // Undo the `cond_br a, bb(a), bb(a)` to `cond_br a, bb(1), bb(0)`
567 // canonicalization.
568 if ((matchPattern(operand, m_One()) && destIdx == 0) ||
569 (matchPattern(operand, m_Zero()) && destIdx == 1))
570 operand = condBranchOp.getCondition();
571
572 if (seen.insert(operand).second)
573 worklist.push_back(operand);
574 } else {
575 LLVM_DEBUG(llvm::dbgs() << "Skipping " << process.getLoc()
576 << ": unsupported terminator " << op->getName()
577 << " while tracing past value\n");
578 return Value{};
579 }
580 }
581 }
582
583 // Ensure that we have one distinct value being passed from the past into
584 // the present, and that the value is observed.
585 if (distinctValues.size() != 1) {
586 LLVM_DEBUG(
587 llvm::dbgs()
588 << "Skipping " << process.getLoc()
589 << ": multiple past values passed for the same block argument\n");
590 return Value{};
591 }
592 auto distinctValue = *distinctValues.begin();
593 if (!triggers.contains(getValueField(distinctValue))) {
594 LLVM_DEBUG(llvm::dbgs() << "Skipping " << process.getLoc()
595 << ": unobserved past value\n");
596 return Value{};
597 }
598 return distinctValue;
599}
600
601//===----------------------------------------------------------------------===//
602// Data Flow Analysis
603//===----------------------------------------------------------------------===//
604
605/// Convert a boolean SSA value into a truth table. If the value depends on any
606/// of the process' triggers, that dependency is captured explicitly by the
607/// truth table. Any other SSA values that factor into the value are represented
608/// as an opaque term.
609TruthTable Deseq::computeBoolean(Value value) {
610 return computeBoolean(getValueField(value));
611}
612
613TruthTable Deseq::computeBoolean(ValueField vf) {
614 if (!vf)
615 return getUnknownBoolean();
616
617 // Check the lattice first - this is important for projected clocks where
618 // multiple extractions from the same base/offset are equivalent.
619 if (auto it = booleanLattice.find(vf); it != booleanLattice.end())
620 return it->second;
621
622 if (vf.fieldID != 0) {
623 // A projected field is boolean only if we can see the projection; otherwise
624 // we don't try to reason about it. Treat unknown projections as unknown.
625 if (vf.getProjected().getType().isSignlessInteger(1))
626 return computeBoolean(
627 ValueField{vf.getProjected(), 0, vf.getProjected()});
628 return getUnknownBoolean();
629 }
630
631 Value value = vf.value;
632 assert(value.getType().isSignlessInteger(1));
633
634 // If this value is a result of the process we're analyzing, jump to the
635 // corresponding yield operand of the wait op.
636 if (value.getDefiningOp() == process)
637 return computeBoolean(
638 wait.getYieldOperands()[cast<OpResult>(value).getResultNumber()]);
639
640 // Insert an unknown value to break recursions. This will be overwritten by a
641 // concrete value later.
642 booleanLattice[vf] = getUnknownBoolean();
643
644 // Actually compute the value.
645 TruthTable result =
646 TypeSwitch<Value, TruthTable>(value).Case<OpResult, BlockArgument>(
647 [&](auto value) { return computeBoolean(value); });
648
649 // Memoize the result.
651 llvm::dbgs() << "- Boolean ";
652 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
653 llvm::dbgs() << ": " << result << "\n";
654 });
655 booleanLattice[vf] = result;
656 return result;
657}
658
659/// Determine the different concrete values an SSA value may assume depending on
660/// how control flow reaches the given value. This is used to determine the list
661/// of different values that are driven onto a signal under various conditions.
662ValueTable Deseq::computeValue(Value value) {
663 auto vf = getValueField(value);
664
665 // For now, treat projections as distinct but known values identified by the
666 // projected SSA value.
667 if (vf.fieldID != 0)
668 return getKnownValue(vf.getProjected());
669
670 value = vf.value;
671
672 // If this value is a result of the process we're analyzing, jump to the
673 // corresponding yield operand of the wait op.
674 if (value.getDefiningOp() == process)
675 return computeValue(
676 wait.getYieldOperands()[cast<OpResult>(value).getResultNumber()]);
677
678 // Check if we have already computed this value. Otherwise insert an unknown
679 // value to break recursions. This will be overwritten by a concrete value
680 // later.
681 if (auto it = valueLattice.find(value); it != valueLattice.end())
682 return it->second;
683 valueLattice[value] = getUnknownValue();
684
685 // Actually compute the value.
686 ValueTable result =
687 TypeSwitch<Value, ValueTable>(value).Case<OpResult, BlockArgument>(
688 [&](auto value) { return computeValue(value); });
689
690 // Memoize the result.
692 llvm::dbgs() << "- Value ";
693 value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
694 llvm::dbgs() << ": " << result << "\n";
695 });
696 valueLattice[value] = result;
697 return result;
698}
699
700/// Convert a boolean op result to a truth table.
701TruthTable Deseq::computeBoolean(OpResult value) {
702 assert(value.getType().isSignlessInteger(1));
703 auto *op = value.getOwner();
704
705 // Handle constants.
706 if (auto constOp = dyn_cast<hw::ConstantOp>(op))
707 return getConstBoolean(constOp.getValue().isOne());
708
709 // Handle `comb.or`.
710 if (auto orOp = dyn_cast<comb::OrOp>(op)) {
711 auto result = getConstBoolean(false);
712 for (auto operand : orOp.getInputs()) {
713 result |= computeBoolean(operand);
714 if (result.isTrue())
715 break;
716 }
717 return result;
718 }
719
720 // Handle `comb.and`.
721 if (auto andOp = dyn_cast<comb::AndOp>(op)) {
722 auto result = getConstBoolean(true);
723 for (auto operand : andOp.getInputs()) {
724 result &= computeBoolean(operand);
725 if (result.isFalse())
726 break;
727 }
728 return result;
729 }
730
731 // Handle `comb.xor`.
732 if (auto xorOp = dyn_cast<comb::XorOp>(op)) {
733 auto result = getConstBoolean(false);
734 for (auto operand : xorOp.getInputs())
735 result ^= computeBoolean(operand);
736 return result;
737 }
738
739 // Otherwise check if the operation depends on any of the triggers. If it
740 // does, create a poison value since we don't really know how the trigger
741 // affects this boolean. If it doesn't, create an unknown value.
742 if (llvm::any_of(op->getOperands(), [&](auto operand) {
743 // TODO: This should probably also check non-i1 values to see if they
744 // depend on the triggers. Maybe once we merge boolean and value tables?
745 if (!operand.getType().isSignlessInteger(1))
746 return false;
747 auto result = computeBoolean(operand);
748 return result.isPoison() || (result != getUnknownBoolean() &&
749 !result.isTrue() && !result.isFalse());
750 }))
751 return getPoisonBoolean();
752 return getUnknownBoolean();
753}
754
755/// Determine the different values an op result may assume depending how control
756/// flow reaches the op.
757ValueTable Deseq::computeValue(OpResult value) {
758 auto *op = value.getOwner();
759
760 // Handle `comb.mux` and `arith.select`.
761 if (isa<comb::MuxOp, arith::SelectOp>(op)) {
762 auto condition = computeBoolean(op->getOperand(0));
763 auto trueValue = computeValue(op->getOperand(1));
764 auto falseValue = computeValue(op->getOperand(2));
765 trueValue.addCondition(condition);
766 falseValue.addCondition(~condition);
767 trueValue.merge(std::move(falseValue));
768 return trueValue;
769 }
770
771 // TODO: Reject values that depend on the triggers.
772 return getKnownValue(value);
773}
774
775/// Convert a block argument to a truth table.
776TruthTable Deseq::computeBoolean(BlockArgument arg) {
777 auto *block = arg.getOwner();
778
779 // If this isn't a block in the process, simply return an unknown value.
780 if (block->getParentOp() != process)
781 return getUnknownBoolean();
782
783 // Otherwise iterate over all predecessors and compute the boolean values
784 // being passed to this block argument by each.
785 auto result = getConstBoolean(false);
786 SmallPtrSet<Block *, 4> seen;
787 for (auto *predecessor : block->getPredecessors()) {
788 if (!seen.insert(predecessor).second)
789 continue;
790 for (auto &operand : predecessor->getTerminator()->getBlockOperands()) {
791 if (operand.get() != block)
792 continue;
793 auto value = computeSuccessorBoolean(operand, arg.getArgNumber());
794 if (value.isFalse())
795 continue;
796 auto condition = computeSuccessorCondition(operand);
797 result |= value & condition;
798 if (result.isTrue())
799 break;
800 }
801 if (result.isTrue())
802 break;
803 }
804 return result;
805}
806
807/// Determine the different values a block argument may assume depending how
808/// control flow reaches the block.
809ValueTable Deseq::computeValue(BlockArgument arg) {
810 auto *block = arg.getOwner();
811
812 // If this isn't a block in the process, simply return the value itself.
813 if (block->getParentOp() != process)
814 return getKnownValue(arg);
815
816 // Otherwise iterate over all predecessors and compute the boolean values
817 // being passed to this block argument by each.
818 auto result = ValueTable();
819 SmallPtrSet<Block *, 4> seen;
820 for (auto *predecessor : block->getPredecessors()) {
821 if (!seen.insert(predecessor).second)
822 continue;
823 for (auto &operand : predecessor->getTerminator()->getBlockOperands()) {
824 if (operand.get() != block)
825 continue;
826 auto condition = computeSuccessorCondition(operand);
827 if (condition.isFalse())
828 continue;
829 auto value = computeSuccessorValue(operand, arg.getArgNumber());
830 value.addCondition(condition);
831 result.merge(value);
832 }
833 }
834 return result;
835}
836
837/// Compute the boolean condition under which control flow reaches a block, as a
838/// truth table.
839TruthTable Deseq::computeBlockCondition(Block *block) {
840 // Return a memoized result if one exists. Otherwise insert a default result
841 // as recursion breaker.
842 if (auto it = blockConditionLattice.find(block);
843 it != blockConditionLattice.end())
844 return it->second;
845 blockConditionLattice[block] = getConstBoolean(false);
846
847 // Actually compute the block condition by combining all incoming control flow
848 // conditions.
849 auto result = getConstBoolean(false);
850 SmallPtrSet<Block *, 4> seen;
851 for (auto *predecessor : block->getPredecessors()) {
852 if (!seen.insert(predecessor).second)
853 continue;
854 for (auto &operand : predecessor->getTerminator()->getBlockOperands()) {
855 if (operand.get() != block)
856 continue;
857 result |= computeSuccessorCondition(operand);
858 if (result.isTrue())
859 break;
860 }
861 if (result.isTrue())
862 break;
863 }
864
865 // Memoize the result.
867 llvm::dbgs() << "- Block condition ";
868 block->printAsOperand(llvm::dbgs());
869 llvm::dbgs() << ": " << result << "\n";
870 });
871 blockConditionLattice[block] = result;
872 return result;
873}
874
875/// Compute the condition under which control transfers along a terminator's
876/// block operand to the destination block.
877TruthTable Deseq::computeSuccessorCondition(BlockOperand &blockOperand) {
878 // The wait operation of the process is the origin point of the analysis. We
879 // want to know under which conditions drives happen once the wait resumes.
880 // Therefore the branch from the wait to its destination block is expected to
881 // happen.
882 auto *op = blockOperand.getOwner();
883 if (op == wait)
884 return getConstBoolean(true);
885
886 // Return a memoized result if one exists. Otherwise insert a default result
887 // as recursion breaker.
888 if (auto it = successorConditionLattice.find(&blockOperand);
889 it != successorConditionLattice.end())
890 return it->second;
891 successorConditionLattice[&blockOperand] = getConstBoolean(false);
892
893 // Actually compute the condition under which control flows along the given
894 // block operand.
895 auto destIdx = blockOperand.getOperandNumber();
896 auto blockCondition = computeBlockCondition(op->getBlock());
897 auto result = getUnknownBoolean();
898 if (auto branchOp = dyn_cast<cf::BranchOp>(op)) {
899 result = blockCondition;
900 } else if (auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
901 auto branchCondition = computeBoolean(condBranchOp.getCondition());
902 if (destIdx == 0)
903 result = blockCondition & branchCondition;
904 else
905 result = blockCondition & ~branchCondition;
906 } else {
907 result = getPoisonBoolean();
908 }
909
910 // Memoize the result.
912 llvm::dbgs() << "- Successor condition ";
913 op->getBlock()->printAsOperand(llvm::dbgs());
914 llvm::dbgs() << "#succ" << destIdx << " -> ";
915 blockOperand.get()->printAsOperand(llvm::dbgs());
916 llvm::dbgs() << " = " << result << "\n";
917 });
918 successorConditionLattice[&blockOperand] = result;
919 return result;
920}
921
922/// Compute the boolean value of a destination operand when control transfers
923/// along a terminator's block operand to the destination block.
924TruthTable Deseq::computeSuccessorBoolean(BlockOperand &blockOperand,
925 unsigned argIdx) {
926 // Return a memoized result if one exists. Otherwise insert a default result
927 // as recursion breaker.
928 if (auto it = successorBooleanLattice.find({&blockOperand, argIdx});
929 it != successorBooleanLattice.end())
930 return it->second;
931 successorBooleanLattice[{&blockOperand, argIdx}] = getUnknownBoolean();
932
933 // Actually compute the boolean destination operand for the given destination
934 // block.
935 auto *op = blockOperand.getOwner();
936 auto destIdx = blockOperand.getOperandNumber();
937 auto result = getUnknownBoolean();
938 if (auto branchOp = dyn_cast<cf::BranchOp>(op)) {
939 result = computeBoolean(branchOp.getDestOperands()[argIdx]);
940 } else if (auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
941 if (destIdx == 0)
942 result = computeBoolean(condBranchOp.getTrueDestOperands()[argIdx]);
943 else
944 result = computeBoolean(condBranchOp.getFalseDestOperands()[argIdx]);
945 } else {
946 result = getPoisonBoolean();
947 }
948
949 // Memoize the result.
951 llvm::dbgs() << "- Successor boolean ";
952 op->getBlock()->printAsOperand(llvm::dbgs());
953 llvm::dbgs() << "#succ" << destIdx << " -> ";
954 blockOperand.get()->printAsOperand(llvm::dbgs());
955 llvm::dbgs() << "#arg" << argIdx << " = " << result << "\n";
956 });
957 successorBooleanLattice[{&blockOperand, argIdx}] = result;
958 return result;
959}
960
961/// Determine the different values a destination operand may assume when control
962/// transfers along a terminator's block operand to the destination block,
963/// depending on how control flow reaches the terminator.
964ValueTable Deseq::computeSuccessorValue(BlockOperand &blockOperand,
965 unsigned argIdx) {
966 // Return a memoized result if one exists. Otherwise insert a default result
967 // as recursion breaker.
968 if (auto it = successorValueLattice.find({&blockOperand, argIdx});
969 it != successorValueLattice.end())
970 return it->second;
971 successorValueLattice[{&blockOperand, argIdx}] = getUnknownValue();
972
973 // Actually compute the boolean destination operand for the given destination
974 // block.
975 auto *op = blockOperand.getOwner();
976 auto destIdx = blockOperand.getOperandNumber();
977 auto result = getUnknownValue();
978 if (auto branchOp = dyn_cast<cf::BranchOp>(op)) {
979 result = computeValue(branchOp.getDestOperands()[argIdx]);
980 } else if (auto condBranchOp = dyn_cast<cf::CondBranchOp>(op)) {
981 if (destIdx == 0)
982 result = computeValue(condBranchOp.getTrueDestOperands()[argIdx]);
983 else
984 result = computeValue(condBranchOp.getFalseDestOperands()[argIdx]);
985 } else {
986 result = getPoisonValue();
987 }
988
989 // Memoize the result.
991 llvm::dbgs() << "- Successor value ";
992 op->getBlock()->printAsOperand(llvm::dbgs());
993 llvm::dbgs() << "#succ" << destIdx << " -> ";
994 blockOperand.get()->printAsOperand(llvm::dbgs());
995 llvm::dbgs() << "#arg" << argIdx << " = " << result << "\n";
996 });
997 successorValueLattice[{&blockOperand, argIdx}] = result;
998 return result;
999}
1000
1001//===----------------------------------------------------------------------===//
1002// Drive-to-Register Matching
1003//===----------------------------------------------------------------------===//
1004
1005/// Match the drives fed by the process against concrete implementable register
1006/// behaviors. Returns false if any of the drives cannot be implemented as a
1007/// register.
1008bool Deseq::matchDrives() {
1009 for (auto &drive : driveInfos)
1010 if (!matchDrive(drive))
1011 return false;
1012 return true;
1013}
1014
1015/// For a given drive op, determine if its drive condition and driven value as
1016/// determined by the data flow analysis is implementable by a register op. The
1017/// results are stored in the clock and reset info of the given `DriveInfo`.
1018/// Returns false if the drive cannot be implemented as a register.
1019bool Deseq::matchDrive(DriveInfo &drive) {
1020 LLVM_DEBUG(llvm::dbgs() << "- Analyzing " << drive.op << "\n");
1021
1022 // Determine under which condition the drive is enabled.
1023 auto condition = computeBoolean(drive.op.getEnable());
1024 if (condition.isPoison()) {
1025 LLVM_DEBUG(llvm::dbgs()
1026 << "- Aborting: poison condition on " << drive.op << "\n");
1027 return false;
1028 }
1029
1030 // Determine which value is driven under which conditions.
1031 auto initialValueTable = computeValue(drive.op.getValue());
1032 initialValueTable.addCondition(condition);
1033 LLVM_DEBUG({
1034 llvm::dbgs() << " - Condition: " << condition << "\n";
1035 llvm::dbgs() << " - Value: " << initialValueTable << "\n";
1036 });
1037
1038 // Convert the value table from having DNF conditions to having DNFTerm
1039 // conditions. This effectively spreads OR operations in the conditions across
1040 // multiple table entries.
1041 SmallVector<std::pair<DNFTerm, ValueEntry>> valueTable;
1042 for (auto &[condition, value] : initialValueTable.entries) {
1043 auto dnf = condition.canonicalize();
1044 if (dnf.isPoison() || value.isPoison()) {
1045 LLVM_DEBUG(llvm::dbgs()
1046 << "- Aborting: poison in " << initialValueTable << "\n");
1047 return false;
1048 }
1049 for (auto &orTerm : dnf.orTerms)
1050 valueTable.push_back({orTerm, value});
1051 }
1052
1053 // At this point we should have at most three entries in the value table,
1054 // corresponding to the reset, clock, and clock under reset. Everything else
1055 // we have no chance of representing as a register op.
1056 if (valueTable.size() > 3) {
1057 LLVM_DEBUG(llvm::dbgs() << "- Aborting: value table has "
1058 << valueTable.size() << " distinct conditions\n");
1059 return false;
1060 }
1061
1062 // If we have two triggers, one of them must be the reset.
1063 if (triggers.size() == 2)
1064 return matchDriveClockAndReset(drive, valueTable);
1065
1066 // Otherwise we only have a single trigger, which is the clock.
1067 assert(triggers.size() == 1);
1068 return matchDriveClock(drive, valueTable);
1069}
1070
1071/// Assuming there is one trigger, detect the clock scheme represented by a
1072/// value table and store the results in `drive.clock`.
1073bool Deseq::matchDriveClock(
1074 DriveInfo &drive, ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable) {
1075 // We need exactly one entry in the value table to represent a register
1076 // without reset.
1077 if (valueTable.size() != 1) {
1078 LLVM_DEBUG(llvm::dbgs() << "- Aborting: single trigger value table has "
1079 << valueTable.size() << " entries\n");
1080 return false;
1081 }
1082
1083 // Try the posedge and negedge variants of clocking.
1084 for (unsigned variant = 0; variant < (1 << 1); ++variant) {
1085 bool negClock = (variant >> 0) & 1;
1086
1087 // Assemble the conditions in the value table corresponding to a clock edge
1088 // with and without an additional enable condition. The enable condition is
1089 // represented as an additional unknown AND term. The bit patterns here
1090 // follow from how we assign indices to past and present triggers, and how
1091 // the DNF's even bits represent positive terms and odd bits represent
1092 // inverted terms.
1093 uint32_t clockEdge = (negClock ? 0b1001 : 0b0110) << 2;
1094 auto clockWithoutEnable = DNFTerm{clockEdge};
1095 auto clockWithEnable = DNFTerm{clockEdge | 0b01};
1096
1097 // Check if the single value table entry matches this clock.
1098 if (valueTable[0].first == clockWithEnable)
1099 drive.clock.enable = drive.op.getEnable();
1100 else if (valueTable[0].first != clockWithoutEnable)
1101 continue;
1102
1103 // Populate the clock info and return.
1104 drive.clock.clock = triggers[0].getProjected();
1105 drive.clock.risingEdge = !negClock;
1106 drive.clock.value = drive.op.getValue();
1107 if (!valueTable[0].second.isUnknown())
1108 drive.clock.value = valueTable[0].second.value;
1109
1110 LLVM_DEBUG({
1111 llvm::dbgs() << " - Matched " << (negClock ? "neg" : "pos")
1112 << "edge clock ";
1113 drive.clock.clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1114 llvm::dbgs() << " -> " << valueTable[0].second;
1115 if (drive.clock.enable)
1116 llvm::dbgs() << " (with enable)";
1117 llvm::dbgs() << "\n";
1118 });
1119 return true;
1120 }
1121
1122 // If we arrive here, none of the patterns we tried matched.
1123 LLVM_DEBUG(llvm::dbgs() << "- Aborting: unknown clock scheme\n");
1124 return false;
1125}
1126
1127/// Assuming there are two triggers, detect the clock and reset scheme
1128/// represented by a value table and store the results in `drive.reset` and
1129/// `drive.clock`.
1130bool Deseq::matchDriveClockAndReset(
1131 DriveInfo &drive, ArrayRef<std::pair<DNFTerm, ValueEntry>> valueTable) {
1132 // We need two or three entries in the value table to represent a register
1133 // with reset. A table with two entries means that the clock edge while reset
1134 // is inactive has no drive, which is a hold.
1135 if (valueTable.size() != 2 && valueTable.size() != 3) {
1136 LLVM_DEBUG(llvm::dbgs() << "- Aborting: two trigger value table has "
1137 << valueTable.size() << " entries\n");
1138 return false;
1139 }
1140
1141 // Resets take precedence over the clock, which shows up as `/rst` and
1142 // `/clk&rst` entries in the value table. We simply try all variants until we
1143 // find the one that fits.
1144 for (unsigned variant = 0; variant < (1 << 3); ++variant) {
1145 bool negClock = (variant >> 0) & 1;
1146 bool negReset = (variant >> 1) & 1;
1147 unsigned clockIdx = (variant >> 2) & 1;
1148 unsigned resetIdx = 1 - clockIdx;
1149
1150 // Assemble the conditions in the value table corresponding to a clock edge
1151 // and reset edge, alongside the reset being active and inactive. The bit
1152 // patterns here follow from how we assign indices to past and present
1153 // triggers, and how the DNF's even bits represent positive terms and odd
1154 // bits represent inverted terms.
1155 uint32_t clockEdge = (negClock ? 0b1001 : 0b0110) << (clockIdx * 4 + 2);
1156 uint32_t resetEdge = (negReset ? 0b1001 : 0b0110) << (resetIdx * 4 + 2);
1157 uint32_t resetOn = (negReset ? 0b1000 : 0b0100) << (resetIdx * 4 + 2);
1158 uint32_t resetOff = (negReset ? 0b0100 : 0b1000) << (resetIdx * 4 + 2);
1159
1160 // Combine the above bit masks into conditions for the reset edge, clock
1161 // edge with reset active, and clock edge with reset inactive and optional
1162 // enable condition.
1163 auto reset = DNFTerm{resetEdge};
1164 auto clockWhileReset = DNFTerm{clockEdge | resetOn};
1165 auto clockWithoutEnable = DNFTerm{clockEdge | resetOff};
1166 auto clockWithEnable = DNFTerm{clockEdge | resetOff | 0b01};
1167
1168 // Find the entries corresponding to the above conditions.
1169 const auto *resetIt = llvm::find_if(
1170 valueTable, [&](auto &pair) { return pair.first == reset; });
1171 if (resetIt == valueTable.end())
1172 continue;
1173
1174 const auto *clockWhileResetIt = llvm::find_if(
1175 valueTable, [&](auto &pair) { return pair.first == clockWhileReset; });
1176 if (clockWhileResetIt == valueTable.end())
1177 continue;
1178
1179 const auto *clockIt = llvm::find_if(valueTable, [&](auto &pair) {
1180 return pair.first == clockWithoutEnable || pair.first == clockWithEnable;
1181 });
1182 bool clockHolds = clockIt == valueTable.end();
1183 if (clockHolds && valueTable.size() != 2)
1184 continue;
1185
1186 // Ensure that `/rst` and `/clk&rst` set the register to the same reset
1187 // value. Otherwise the reset doesn't have clear precedence over the
1188 // clock, and we can't turn this drive into a register.
1189 if (clockWhileResetIt->second != resetIt->second ||
1190 resetIt->second.isUnknown()) {
1191 LLVM_DEBUG(llvm::dbgs() << "- Aborting: inconsistent reset value\n");
1192 return false;
1193 }
1194
1195 // Populate the reset and clock info, and return.
1196 drive.reset.reset = triggers[resetIdx].getProjected();
1197 drive.reset.value = resetIt->second.value;
1198 drive.reset.activeHigh = !negReset;
1199
1200 drive.clock.clock = triggers[clockIdx].getProjected();
1201 drive.clock.risingEdge = !negClock;
1202 drive.clock.value = drive.op.getValue();
1203 if (clockHolds) {
1204 drive.clock.enable = drive.op.getEnable();
1205 } else {
1206 if (clockIt->first == clockWithEnable)
1207 drive.clock.enable = drive.op.getEnable();
1208 if (!clockIt->second.isUnknown())
1209 drive.clock.value = clockIt->second.value;
1210 }
1211
1212 LLVM_DEBUG({
1213 llvm::dbgs() << " - Matched " << (negClock ? "neg" : "pos")
1214 << "edge clock ";
1215 drive.clock.clock.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1216 if (clockHolds)
1217 llvm::dbgs() << " -> hold";
1218 else
1219 llvm::dbgs() << " -> " << clockIt->second;
1220 if (drive.clock.enable)
1221 llvm::dbgs() << " (with enable)";
1222 llvm::dbgs() << "\n";
1223 llvm::dbgs() << " - Matched active-" << (negReset ? "low" : "high")
1224 << " reset ";
1225 drive.reset.reset.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1226 llvm::dbgs() << " -> " << resetIt->second << "\n";
1227 });
1228 return true;
1229 }
1230
1231 // If we arrive here, none of the patterns we tried matched.
1232 LLVM_DEBUG(llvm::dbgs() << "- Aborting: unknown reset scheme\n");
1233 return false;
1234}
1235
1236//===----------------------------------------------------------------------===//
1237// Register Implementation
1238//===----------------------------------------------------------------------===//
1239
1240Value Deseq::materializeProjection(OpBuilder &builder, Location loc,
1241 Value value,
1242 SmallDenseMap<Value, Value, 8> &cache) {
1243 if (!value)
1244 return value;
1245
1246 // Only values defined within this process need rematerialization.
1247 auto isInThisProcess = [&](Value v) {
1248 if (auto arg = dyn_cast<BlockArgument>(v)) {
1249 Operation *parentOp = arg.getOwner()->getParentOp();
1250 if (!parentOp)
1251 return false;
1252 return parentOp == process.getOperation() ||
1253 parentOp->getParentOfType<ProcessOp>() == process;
1254 }
1255 if (auto *defOp = v.getDefiningOp())
1256 return defOp->getParentOfType<ProcessOp>() == process;
1257 return false;
1258 };
1259 if (!isInThisProcess(value))
1260 return value;
1261
1262 if (auto it = cache.find(value); it != cache.end())
1263 return it->second;
1264
1265 // If we encounter a block argument, trace it back to a unique defining
1266 // value.
1267 if (auto arg = dyn_cast<BlockArgument>(value)) {
1268 SmallPtrSet<Block *, 4> visited;
1269 Value canon = canonicalizeBlockArg(arg, visited);
1270 if (canon == value)
1271 return value;
1272 auto remat = materializeProjection(builder, loc, canon, cache);
1273 cache.insert({value, remat});
1274 return remat;
1275 }
1276
1277 auto *defOp = value.getDefiningOp();
1278 if (!defOp)
1279 return value;
1280
1281 // Rematerialize common pure projection ops.
1282 if (auto ext = dyn_cast<comb::ExtractOp>(defOp)) {
1283 Value input = materializeProjection(builder, loc, ext.getInput(), cache);
1284 Value remat = comb::ExtractOp::create(builder, loc, ext.getType(), input,
1285 ext.getLowBit());
1286 cache.insert({value, remat});
1287 return remat;
1288 }
1289 if (auto get = dyn_cast<hw::ArrayGetOp>(defOp)) {
1290 Value input = materializeProjection(builder, loc, get.getInput(), cache);
1291 Value index = materializeProjection(builder, loc, get.getIndex(), cache);
1292 Value remat = hw::ArrayGetOp::create(builder, loc, input, index);
1293 cache.insert({value, remat});
1294 return remat;
1295 }
1296 if (auto slice = dyn_cast<hw::ArraySliceOp>(defOp)) {
1297 Value input = materializeProjection(builder, loc, slice.getInput(), cache);
1298 Value lowIndex =
1299 materializeProjection(builder, loc, slice.getLowIndex(), cache);
1300 Value remat = hw::ArraySliceOp::create(builder, loc, slice.getType(), input,
1301 lowIndex);
1302 cache.insert({value, remat});
1303 return remat;
1304 }
1305 if (auto se = dyn_cast<hw::StructExtractOp>(defOp)) {
1306 Value input = materializeProjection(builder, loc, se.getInput(), cache);
1307 Value remat =
1308 hw::StructExtractOp::create(builder, loc, input, se.getFieldNameAttr());
1309 cache.insert({value, remat});
1310 return remat;
1311 }
1312 if (auto cst = dyn_cast<hw::ConstantOp>(defOp)) {
1313 Value remat = hw::ConstantOp::create(
1314 builder, loc, cst.getResult().getType(), cst.getValueAttr());
1315 cache.insert({value, remat});
1316 return remat;
1317 }
1318 if (auto cst = dyn_cast<arith::ConstantOp>(defOp)) {
1319 auto *cloned = builder.clone(*defOp);
1320 Value remat = cloned->getResult(cast<OpResult>(value).getResultNumber());
1321 cache.insert({value, remat});
1322 return remat;
1323 }
1324
1325 // Unknown op: leave as-is.
1326 return value;
1327}
1328
1329/// Attempt to find and set an preset (initializer) value for the newly created
1330/// register. This needs to be a constant value that is either used to
1331/// initialize the signal itself, or is unconditionally driven to the signal
1332/// at the same time the register op begins driving the signal. If such an
1333/// initial drive is found, the constant drive operation(s) are erased.
1334/// Returns failure if mutiple conflicting initial drivers are found, or if
1335/// a non-constant value would be inferred from a signal initializer.
1336static LogicalResult inferRegisterPreset(seq::FirRegOp regOp,
1337 DriveInfo &drive) {
1338 auto sigOp = drive.op.getDriven().getDefiningOp<SignalOp>();
1339 SmallVector<std::pair<DriveLikeOp, IntegerAttr>> constantDrives;
1340
1341 // Check if a drive happens at the same time as the register drive. Immediate
1342 // drives are concurrent with each other, and delayed drives are concurrent if
1343 // they have the same delay.
1344 TimeAttr driveTimeAttr;
1345 if (drive.op.getTime())
1346 matchPattern(drive.op.getTime(), m_Constant(&driveTimeAttr));
1347 auto isConcurrent = [&](DriveLikeOp otherDrive) -> bool {
1348 if (otherDrive.getTime() == drive.op.getTime())
1349 return true;
1350 TimeAttr timeCst;
1351 return driveTimeAttr && otherDrive.getTime() &&
1352 matchPattern(otherDrive.getTime(), m_Constant(&timeCst)) &&
1353 timeCst == driveTimeAttr;
1354 };
1355
1356 // Find other unconditional concurrent constant drivers on the signal.
1357 for (auto &use : drive.op.getDriven().getUses()) {
1358 auto driveUser = dyn_cast<DriveLikeOp>(use.getOwner());
1359 if (!driveUser || driveUser == drive.op ||
1360 use != driveUser.getDrivenMutable()[0])
1361 continue;
1362
1363 bool isSameBlock = driveUser->getBlock() == drive.op->getBlock();
1364 bool isUnconditional = !driveUser.getEnable();
1365 if (!(isSameBlock && isUnconditional && isConcurrent(driveUser)))
1366 continue;
1367
1368 IntegerAttr driveCst;
1369 if (matchPattern(driveUser.getValue(), m_Constant(&driveCst)))
1370 constantDrives.push_back({driveUser, driveCst});
1371 }
1372
1373 IntegerAttr preset;
1374 // Initial drives take precedence over signal initializers as they occur
1375 // later and thus overwrite the initializer.
1376 if (!constantDrives.empty()) {
1377 // One or more initial drivers found. Ensure they drive the same constant.
1378 bool isConflicted = !llvm::all_equal(llvm::map_range(
1379 constantDrives, [](auto drivePair) { return drivePair.second; }));
1380 if (isConflicted)
1381 return drive.op->emitError(
1382 "inferred register has conflicting initial drivers.");
1383 preset = constantDrives.front().second;
1384 // `driveInfos` only contains conditional drives, so erasing unconditional
1385 // drives on-the-fly should be safe.
1386 for (auto [initDriveOp, _] : constantDrives)
1387 initDriveOp->erase();
1388 } else if (sigOp && sigOp.getInit()) {
1389 // An integer register preset can only represent a constant initializer.
1390 // Keep dynamic scalar initializers from silently becoming unconstrained
1391 // register state.
1392 if (!matchPattern(sigOp.getInit(), m_Constant(&preset)))
1393 return sigOp->emitError("cannot lower a nonconstant signal initializer "
1394 "to a register preset");
1395 }
1396
1397 if (preset)
1398 regOp.setPresetAttr(preset);
1399 return success();
1400}
1401
1402/// Make all drives unconditional and implement the conditional behavior with
1403/// register ops.
1404LogicalResult Deseq::implementRegisters() {
1405 bool anyFailed = false;
1406 for (auto &drive : driveInfos)
1407 anyFailed |= failed(implementRegister(drive));
1408 return llvm::success(!anyFailed);
1409}
1410
1411/// Implement the conditional behavior of a drive with a `seq.firreg` op and
1412/// make the drive unconditional. This function pulls the analyzed clock and
1413/// reset from the given `DriveInfo` and creates the necessary ops outside the
1414/// process represent the behavior as a register. It also calls
1415/// `specializeValue` and `specializeProcess` to convert the sequential
1416/// `llhd.process` into a purely combinational `llhd.combinational` that is
1417/// simplified by assuming that the clock edge occurs.
1418LogicalResult Deseq::implementRegister(DriveInfo &drive) {
1419 OpBuilder builder(drive.op);
1420 auto loc = drive.op.getLoc();
1421
1422 // Projected clocks and resets compute the trigger value inside the process,
1423 // but the produced register ops must consume the trigger outside.
1424 SmallDenseMap<Value, Value, 8> rematerialized;
1425
1426 // Materialize the clock as a `!seq.clock` value.
1427 Value clockValue =
1428 materializeProjection(builder, loc, drive.clock.clock, rematerialized);
1429
1430 auto &clockCast = materializedClockCasts[clockValue];
1431 if (!clockCast)
1432 clockCast = seq::ToClockOp::create(builder, loc, clockValue);
1433 auto clock = clockCast;
1434 if (!drive.clock.risingEdge) {
1435 auto &clockInv = materializedClockInverters[clock];
1436 if (!clockInv)
1437 clockInv = seq::ClockInverterOp::create(builder, loc, clock);
1438 clock = clockInv;
1439 }
1440
1441 // Handle the optional reset.
1442 Value reset;
1443 Value resetValue;
1444
1445 if (drive.reset) {
1446 reset =
1447 materializeProjection(builder, loc, drive.reset.reset, rematerialized);
1448 resetValue = drive.reset.value;
1449
1450 // Materialize the reset as an `i1` value. Insert an inverter for negedge
1451 // resets.
1452 if (!drive.reset.activeHigh) {
1453 auto &inv = materializedInverters[reset];
1454 if (!inv) {
1455 auto one = hw::ConstantOp::create(builder, loc, builder.getI1Type(), 1);
1456 inv = comb::XorOp::create(builder, loc, reset, one);
1457 }
1458 reset = inv;
1459 }
1460
1461 // Specialize the process for the reset trigger. If the reset value is
1462 // trivially available outside the process, use it directly. If it is a
1463 // constant, move the constant outside the process.
1464 if (!resetValue.getParentRegion()->isProperAncestor(&process.getBody())) {
1465 if (auto *defOp = resetValue.getDefiningOp();
1466 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1467 defOp->moveBefore(process);
1468 else
1469 resetValue = specializeValue(
1470 drive.op.getValue(),
1471 FixedValues{{drive.clock.clock, !drive.clock.risingEdge,
1472 !drive.clock.risingEdge},
1473 {drive.reset.reset, !drive.reset.activeHigh,
1474 drive.reset.activeHigh}});
1475 }
1476 }
1477
1478 // Determine the enable condition. If we have determined that the register
1479 // is trivially enabled, don't add an enable. If the enable condition is a
1480 // simple boolean value available outside the process, use it directly.
1481 Value enable = drive.clock.enable;
1482 if (enable && !enable.getParentRegion()->isProperAncestor(&process.getBody()))
1483 enable = drive.op.getEnable();
1484
1485 // Determine the value. If the value is trivially available outside the
1486 // process, use it directly. If it is a constant, move the constant outside
1487 // the process.
1488 Value value = drive.clock.value;
1489 if (!value.getParentRegion()->isProperAncestor(&process.getBody())) {
1490 if (auto *defOp = value.getDefiningOp();
1491 defOp && defOp->hasTrait<OpTrait::ConstantLike>())
1492 defOp->moveBefore(process);
1493 else
1494 value = drive.op.getValue();
1495 }
1496
1497 // Specialize the process for the clock trigger, which will produce the
1498 // enable and the value for regular clock edges.
1499 FixedValues fixedValues;
1500 fixedValues.push_back(
1501 {drive.clock.clock, !drive.clock.risingEdge, drive.clock.risingEdge});
1502 if (drive.reset)
1503 fixedValues.push_back(
1504 {drive.reset.reset, !drive.reset.activeHigh, !drive.reset.activeHigh});
1505
1506 value = specializeValue(value, fixedValues);
1507 if (enable)
1508 enable = specializeValue(enable, fixedValues);
1509
1510 // Try to guess a name for the register.
1511 StringAttr name;
1512 if (auto sigOp = drive.op.getDriven().getDefiningOp<llhd::SignalOp>())
1513 name = sigOp.getNameAttr();
1514 else
1515 name = builder.getStringAttr("");
1516
1517 // Create the register op.
1518 auto reg = seq::FirRegOp::create(builder, loc, value, clock, name,
1519 hw::InnerSymAttr{}, /*preset=*/IntegerAttr{},
1520 reset, resetValue,
1521 /*isAsync=*/reset != Value{});
1522
1523 // If the register has an enable, insert a self-mux in front of the register.
1524 // Set the `bin` flag on the mux specifically to make up for a subtle
1525 // difference between a `if (en) q <= d` enable on a register, and a `q <= en
1526 // ? d : q` enable.
1527 if (enable) {
1528 OpBuilder::InsertionGuard guard(builder);
1529 builder.setInsertionPoint(reg);
1530 reg.getNextMutable().assign(comb::MuxOp::create(
1531 builder, loc, enable, reg.getNext(), reg.getResult(), true));
1532 }
1533
1534 // Make the original `llhd.drv` drive the register value unconditionally.
1535 drive.op.getValueMutable().assign(reg);
1536 drive.op.getEnableMutable().clear();
1537
1538 // If the original `llhd.drv` had a delta delay, turn it into an immediate
1539 // `llhd.assign` since the delay behavior is now captured by the register op.
1540 TimeAttr attr;
1541 if (drive.op.getTime() &&
1542 matchPattern(drive.op.getTime(), m_Constant(&attr)) &&
1543 attr.getTime() == 0 && attr.getDelta() == 1) {
1544 auto assignOp =
1545 AssignOp::create(builder, drive.op.getLoc(), drive.op.getDriven(),
1546 drive.op.getValue(), Value{});
1547 drive.op.erase();
1548 drive.op = assignOp;
1549 }
1550
1551 // Attempt to merge an initial value into the register op
1552 if (isa<IntegerType>(reg.getType()))
1553 if (failed(inferRegisterPreset(reg, drive)))
1554 return failure();
1555
1556 return success();
1557}
1558
1559//===----------------------------------------------------------------------===//
1560// Process Specialization
1561//===----------------------------------------------------------------------===//
1562
1563/// Specialize a value by assuming the values listed in `fixedValues` are at a
1564/// constant value in the past and the present. The function is guaranteed to
1565/// replace results of the process with results of a new combinational op. All
1566/// other behavior is purely an optimization; the function may not make use of
1567/// the assignments in `fixedValues` at all.
1568Value Deseq::specializeValue(Value value, FixedValues fixedValues) {
1569 auto result = dyn_cast<OpResult>(value);
1570 if (!result || result.getOwner() != process)
1571 return value;
1572 return specializeProcess(fixedValues)[result.getResultNumber()];
1573}
1574
1575/// Specialize the current process by assuming the values listed in
1576/// `fixedValues` are at a constant value in the past and the present. This
1577/// function creates a new combinational op with a simplified version of the
1578/// process where all uses of the values listed in `fixedValues` are replaced
1579/// with their constant counterpart. Since the clock-dependent behavior of the
1580/// process has been absorbed into a register, the process can be replaced with
1581/// a combinational representation that computes the drive value and drive
1582/// condition under the assumption that the clock edge occurs.
1583ValueRange Deseq::specializeProcess(FixedValues fixedValues) {
1584 if (auto it = specializedProcesses.find(fixedValues);
1585 it != specializedProcesses.end())
1586 return it->second;
1587
1588 LLVM_DEBUG({
1589 llvm::dbgs() << "- Specializing process for:\n";
1590 for (auto fixedValue : fixedValues) {
1591 llvm::dbgs() << " - ";
1592 fixedValue.value.printAsOperand(llvm::dbgs(), OpPrintingFlags());
1593 llvm::dbgs() << ": " << fixedValue.past << " -> " << fixedValue.present
1594 << "\n";
1595 }
1596 });
1597
1598 // Create an `llhd.combinational` op with this process specialized to compute
1599 // the result for the given fixed values. The triggers will be absorbed into
1600 // the register operation that consumes the result of this specialized
1601 // process, such that we can make the process purely combinational.
1602 OpBuilder builder(process);
1603 auto executeOp = CombinationalOp::create(builder, process.getLoc(),
1604 process.getResultTypes());
1605
1606 IRMapping mapping;
1607 SmallVector<std::pair<Block *, Block *>> worklist;
1608
1609 auto scheduleBlock = [&](Block *block) {
1610 if (auto *newBlock = mapping.lookupOrNull(block))
1611 return newBlock;
1612 auto *newBlock = &executeOp.getRegion().emplaceBlock();
1613 for (auto arg : block->getArguments()) {
1614 auto newArg = newBlock->addArgument(arg.getType(), arg.getLoc());
1615 mapping.map(arg, newArg);
1616 }
1617 mapping.map(block, newBlock);
1618 worklist.push_back({block, newBlock});
1619 return newBlock;
1620 };
1621
1622 // Initialize the mapping with constants for the fixed values.
1623 auto &entryBlock = executeOp.getRegion().emplaceBlock();
1624 builder.setInsertionPointToStart(&entryBlock);
1625 auto i1 = builder.getI1Type();
1626 auto trueValue = hw::ConstantOp::create(builder, process.getLoc(), i1, 1);
1627 auto falseValue = hw::ConstantOp::create(builder, process.getLoc(), i1, 0);
1628
1629 SmallDenseMap<Value, std::pair<Value, Value>, 2> materializedFixedValues;
1630 for (auto fixedValue : fixedValues) {
1631 auto present = fixedValue.present ? trueValue : falseValue;
1632 auto past = fixedValue.past ? trueValue : falseValue;
1633 materializedFixedValues.insert({fixedValue.value, {past, present}});
1634 mapping.map(fixedValue.value, present);
1635 }
1636
1637 // Compute the truth table that is true for the given fixed values, and false
1638 // otherwise. We will use that table to quickly evaluate booleans later.
1639 auto fixedTable = getConstBoolean(true);
1640 for (auto [index, trigger] : llvm::enumerate(triggers)) {
1641 for (auto fixedValue : fixedValues) {
1642 if (getValueField(fixedValue.value) != trigger)
1643 continue;
1644 auto past = getPastTrigger(index);
1645 fixedTable &= fixedValue.past ? past : ~past;
1646 auto present = getPresentTrigger(index);
1647 fixedTable &= fixedValue.present ? present : ~present;
1648 break;
1649 }
1650 }
1651
1652 // Clone operations over.
1653 auto cloneBlocks = [&](bool stopAtWait) {
1654 SmallVector<Value> foldedResults;
1655 while (!worklist.empty()) {
1656 auto [oldBlock, newBlock] = worklist.pop_back_val();
1657 builder.setInsertionPointToEnd(newBlock);
1658 for (auto &oldOp : *oldBlock) {
1659 // Convert `llhd.wait` into `llhd.yield`.
1660 if (auto waitOp = dyn_cast<WaitOp>(oldOp)) {
1661 if (stopAtWait)
1662 continue;
1663 SmallVector<Value> operands;
1664 for (auto operand : waitOp.getYieldOperands())
1665 operands.push_back(mapping.lookupOrDefault(operand));
1666 YieldOp::create(builder, waitOp.getLoc(), operands);
1667 continue;
1668 }
1669
1670 // Convert `cf.cond_br` ops into `cf.br` if the condition is constant.
1671 if (auto condBranchOp = dyn_cast<cf::CondBranchOp>(oldOp)) {
1672 SmallVector<Value> operands;
1673 auto condition = mapping.lookupOrDefault(condBranchOp.getCondition());
1674 if (matchPattern(condition, m_NonZero())) {
1675 for (auto operand : condBranchOp.getTrueDestOperands())
1676 operands.push_back(mapping.lookupOrDefault(operand));
1677 cf::BranchOp::create(builder, condBranchOp.getLoc(),
1678 scheduleBlock(condBranchOp.getTrueDest()),
1679 operands);
1680 continue;
1681 }
1682 if (matchPattern(condition, m_Zero())) {
1683 for (auto operand : condBranchOp.getFalseOperands())
1684 operands.push_back(mapping.lookupOrDefault(operand));
1685 cf::BranchOp::create(builder, condBranchOp.getLoc(),
1686 scheduleBlock(condBranchOp.getFalseDest()),
1687 operands);
1688 continue;
1689 }
1690 }
1691
1692 // If our initial data flow analysis has produced a concrete boolean
1693 // value for an `i1`-valued op, see if it evaluates to a constant true
1694 // or false with the given fixed values.
1695 if (oldOp.getNumResults() == 1 &&
1696 oldOp.getResult(0).getType().isSignlessInteger(1)) {
1697 if (auto it = booleanLattice.find(getValueField(oldOp.getResult(0)));
1698 it != booleanLattice.end()) {
1699 if ((it->second & fixedTable).isFalse()) {
1700 mapping.map(oldOp.getResult(0), falseValue);
1701 continue;
1702 }
1703 if ((it->second & fixedTable) == fixedTable) {
1704 mapping.map(oldOp.getResult(0), trueValue);
1705 continue;
1706 }
1707 }
1708 }
1709
1710 // Otherwise clone the operation.
1711 for (auto &blockOperand : oldOp.getBlockOperands())
1712 scheduleBlock(blockOperand.get());
1713 auto *clonedOp = builder.clone(oldOp, mapping);
1714
1715 // And immediately try to fold the cloned operation since the fixed
1716 // values introduce a lot of constants into the IR.
1717 if (succeeded(builder.tryFold(clonedOp, foldedResults)) &&
1718 !foldedResults.empty()) {
1719 for (auto [oldResult, foldedResult] :
1720 llvm::zip(oldOp.getResults(), foldedResults))
1721 mapping.map(oldResult, foldedResult);
1722 clonedOp->erase();
1723 }
1724 foldedResults.clear();
1725 }
1726 }
1727 };
1728
1729 // Start at the entry block of the original process and clone all ops until
1730 // we hit the wait.
1731 worklist.push_back({&process.getBody().front(), &entryBlock});
1732 cloneBlocks(true);
1733 builder.setInsertionPointToEnd(mapping.lookup(wait->getBlock()));
1734
1735 // Remove all blocks from the IR mapping. Some blocks may be reachable from
1736 // the entry block and the wait op, in which case we want to create
1737 // duplicates of those blocks.
1738 for (auto &block : process.getBody())
1739 mapping.erase(&block);
1740
1741 // If the wait op is not the only predecessor of its destination block,
1742 // create a branch op to the block. Otherwise inline the destination block
1743 // into the entry block, which allows the specialization to fold more
1744 // constants.
1745 if (wait.getDest()->hasOneUse()) {
1746 // Map the block arguments of the block after the wait op to the constant
1747 // fixed values.
1748 for (auto [arg, pastValue] :
1749 llvm::zip(wait.getDest()->getArguments(), pastValues))
1750 mapping.map(arg, materializedFixedValues.lookup(pastValue).first);
1751
1752 // Schedule the block after the wait for cloning into the entry block.
1753 mapping.map(wait.getDest(), builder.getBlock());
1754 worklist.push_back({wait.getDest(), builder.getBlock()});
1755 } else {
1756 // Schedule the block after the wait for cloning.
1757 auto *dest = scheduleBlock(wait.getDest());
1758
1759 // From the entry block, branch to the block after the wait with the
1760 // appropriate past values as block arguments.
1761 SmallVector<Value> destOperands;
1762 assert(pastValues.size() == wait.getDestOperands().size());
1763 for (auto pastValue : pastValues)
1764 destOperands.push_back(materializedFixedValues.lookup(pastValue).first);
1765 cf::BranchOp::create(builder, wait.getLoc(), dest, destOperands);
1766 }
1767
1768 // Clone everything after the wait operation.
1769 cloneBlocks(false);
1770
1771 // Don't leave unused constants behind.
1772 if (isOpTriviallyDead(trueValue))
1773 trueValue.erase();
1774 if (isOpTriviallyDead(falseValue))
1775 falseValue.erase();
1776
1777 specializedProcesses.insert({fixedValues, executeOp.getResults()});
1778 return executeOp.getResults();
1779}
1780
1781//===----------------------------------------------------------------------===//
1782// Pass Infrastructure
1783//===----------------------------------------------------------------------===//
1784
1785namespace {
1786struct DeseqPass : public llhd::impl::DeseqPassBase<DeseqPass> {
1787 void runOnOperation() override;
1788};
1789} // namespace
1790
1791void DeseqPass::runOnOperation() {
1792 SmallVector<ProcessOp> processes(getOperation().getOps<ProcessOp>());
1793 for (auto process : processes)
1794 if (failed(Deseq(process).deseq())) {
1795 signalPassFailure();
1796 return;
1797 }
1798}
assert(baseType &&"element must be base type")
static LogicalResult inferRegisterPreset(seq::FirRegOp regOp, DriveInfo &drive)
Attempt to find and set an preset (initializer) value for the newly created register.
Definition Deseq.cpp:1336
#define VERBOSE_DEBUG(...)
Definition Deseq.cpp:31
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
static void cloneBlocks(ArrayRef< Block * > blocks, Region &region, Region::iterator before, IRMapping &mapper)
Clone a list of blocks into a region before the given block.
create(low_bit, result_type, input=None)
Definition comb.py:187
create(array_value, idx)
Definition hw.py:453
create(array_value, low_index, ret_type)
Definition hw.py:469
create(data_type, value)
Definition hw.py:433
create(struct_value, str field_name)
Definition hw.py:571
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:56
uint64_t getFieldID(Type type, uint64_t index)
BaseTy type_dyn_cast(Type type)
Definition HWTypes.h:234
SmallVector< FixedValue, 2 > FixedValues
A list of i1 values that are fixed to a given value.
Definition DeseqUtils.h:296
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:239
bool risingEdge
Whether the clock is sensitive to a rising or falling edge.
Definition DeseqUtils.h:243
Value value
The value the register is set to when the clock is triggered.
Definition DeseqUtils.h:241
Value enable
The optional value acting as an enable.
Definition DeseqUtils.h:245
A drive op and the clock and reset that resulted from trigger analysis.
Definition DeseqUtils.h:256
ClockInfo clock
The clock that triggers a change to the driven value.
Definition DeseqUtils.h:261
DriveLikeOp op
The drive operation.
Definition DeseqUtils.h:258
ResetInfo reset
The optional reset that triggers a change of the driven value to a fixed reset value.
Definition DeseqUtils.h:264
Value value
The value the register is reset to.
Definition DeseqUtils.h:227
Value reset
The value acting as the reset, causing the register to be set to value when triggered.
Definition DeseqUtils.h:225
bool activeHigh
Whether the reset is active when high.
Definition DeseqUtils.h:229
static TruthTable getTerm(unsigned numTerms, unsigned term)
Create a boolean expression consisting of a single term.
Definition DeseqUtils.h:131
static TruthTable getPoison()
Definition DeseqUtils.h:118
static TruthTable getConst(unsigned numTerms, bool value)
Create a boolean expression with a constant true or false value.
Definition DeseqUtils.h:124
static ValueEntry getUnknown()
Definition DeseqUtils.h:189
static ValueEntry getPoison()
Definition DeseqUtils.h:186
Identify a specific subfield (or the whole) of an SSA value using the HW field ID scheme.
Definition DeseqUtils.h:27
Value value
The root SSA value being accessed (e.g. the full struct or array).
Definition DeseqUtils.h:29
uint64_t fieldID
The HW field ID describing which subfield is referenced.
Definition DeseqUtils.h:32
void merge(const ValueTable &other)