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