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