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