|
| 1 | +//===- InferLTLClocks.cpp - Infer clocks for ltl.delay ops ------*- C++ -*-===// |
| 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 | +// This pass propagates clock information from ltl.clock operations down to |
| 10 | +// ltl.delay operations that have placeholder clocks. |
| 11 | +// |
| 12 | +// When a single delay is used by multiple clock ops with different clocks, |
| 13 | +// this pass creates separate copies of the delay (and its upstream chain) |
| 14 | +// for each clock, ensuring each clock op gets a correctly clocked version. |
| 15 | +// |
| 16 | +//===----------------------------------------------------------------------===// |
| 17 | + |
| 18 | +#include "circt/Dialect/HW/HWOps.h" |
| 19 | +#include "circt/Dialect/LTL/LTLOps.h" |
| 20 | +#include "circt/Dialect/LTL/LTLPasses.h" |
| 21 | +#include "mlir/IR/IRMapping.h" |
| 22 | +#include "mlir/Pass/Pass.h" |
| 23 | +#include "llvm/ADT/DenseMap.h" |
| 24 | +#include "llvm/ADT/DenseSet.h" |
| 25 | +#include "llvm/ADT/SmallVector.h" |
| 26 | + |
| 27 | +namespace circt { |
| 28 | +namespace ltl { |
| 29 | +#define GEN_PASS_DEF_INFERLTLCLOCKS |
| 30 | +#include "circt/Dialect/LTL/LTLPasses.h.inc" |
| 31 | +} // namespace ltl |
| 32 | +} // namespace circt |
| 33 | + |
| 34 | +using namespace circt; |
| 35 | +using namespace ltl; |
| 36 | +using namespace hw; |
| 37 | + |
| 38 | +namespace { |
| 39 | + |
| 40 | +/// Check if a value is a placeholder clock (hw.constant true). |
| 41 | +/// |
| 42 | +/// We use `hw.constant true` as a placeholder because it is semantically |
| 43 | +/// invalid as a real clock: a constant-high signal has no edges (no 0→1 or |
| 44 | +/// 1→0 transitions), so there are no sampling points and "clock cycles" |
| 45 | +/// cannot be defined. No legitimate hardware design would use a constant |
| 46 | +/// as a clock signal for sequential timing. |
| 47 | +static bool isPlaceholderClock(Value clock) { |
| 48 | + if (auto constOp = clock.getDefiningOp<hw::ConstantOp>()) { |
| 49 | + // Check if it's a 1-bit constant with value 1 (true) |
| 50 | + auto value = constOp.getValue(); |
| 51 | + return value.getBitWidth() == 1 && value.isOne(); |
| 52 | + } |
| 53 | + return false; |
| 54 | +} |
| 55 | + |
| 56 | +/// Check if a subtree (rooted at value) contains any placeholder delays. |
| 57 | +static bool hasPlaceholderDelays(Value value, DenseSet<Operation *> &visited) { |
| 58 | + Operation *defOp = value.getDefiningOp(); |
| 59 | + if (!defOp || visited.contains(defOp)) |
| 60 | + return false; |
| 61 | + visited.insert(defOp); |
| 62 | + |
| 63 | + if (auto delayOp = dyn_cast<DelayOp>(defOp)) { |
| 64 | + if (isPlaceholderClock(delayOp.getClock())) |
| 65 | + return true; |
| 66 | + return hasPlaceholderDelays(delayOp.getInput(), visited); |
| 67 | + } |
| 68 | + |
| 69 | + // For clock ops, don't traverse into the clock signal itself |
| 70 | + if (auto clockOp = dyn_cast<ClockOp>(defOp)) { |
| 71 | + return hasPlaceholderDelays(clockOp.getInput(), visited); |
| 72 | + } |
| 73 | + |
| 74 | + for (Value operand : defOp->getOperands()) { |
| 75 | + if (hasPlaceholderDelays(operand, visited)) |
| 76 | + return true; |
| 77 | + } |
| 78 | + return false; |
| 79 | +} |
| 80 | + |
| 81 | +/// Recursively clone/update a value's subtree, replacing placeholder delays |
| 82 | +/// with properly clocked versions. Returns the new value (may be the same |
| 83 | +/// if no changes needed). |
| 84 | +static Value cloneWithClock(Value value, Value clock, ClockEdge edge, |
| 85 | + OpBuilder &builder, IRMapping &mapping, |
| 86 | + DenseSet<Operation *> &visited) { |
| 87 | + // If already mapped, return the mapped value |
| 88 | + if (Value mapped = mapping.lookupOrNull(value)) |
| 89 | + return mapped; |
| 90 | + |
| 91 | + // If it's a block argument or has no defining op, return as-is |
| 92 | + Operation *defOp = value.getDefiningOp(); |
| 93 | + if (!defOp) { |
| 94 | + mapping.map(value, value); |
| 95 | + return value; |
| 96 | + } |
| 97 | + |
| 98 | + // If already visited (cycle detection), return mapped or original |
| 99 | + if (visited.contains(defOp)) { |
| 100 | + if (Value mapped = mapping.lookupOrNull(value)) |
| 101 | + return mapped; |
| 102 | + return value; |
| 103 | + } |
| 104 | + visited.insert(defOp); |
| 105 | + |
| 106 | + // Handle DelayOp specially |
| 107 | + if (auto delayOp = dyn_cast<DelayOp>(defOp)) { |
| 108 | + // First, recursively process the input |
| 109 | + Value newInput = cloneWithClock(delayOp.getInput(), clock, edge, builder, |
| 110 | + mapping, visited); |
| 111 | + |
| 112 | + if (isPlaceholderClock(delayOp.getClock())) { |
| 113 | + // Create a new delay with the correct clock |
| 114 | + builder.setInsertionPointAfter(defOp); |
| 115 | + auto newDelay = DelayOp::create( |
| 116 | + builder, delayOp.getLoc(), clock, |
| 117 | + ClockEdgeAttr::get(builder.getContext(), edge), newInput, |
| 118 | + delayOp.getDelayAttr(), delayOp.getLengthAttr()); |
| 119 | + mapping.map(value, newDelay.getResult()); |
| 120 | + return newDelay.getResult(); |
| 121 | + } else if (newInput != delayOp.getInput()) { |
| 122 | + // Input changed but clock is not placeholder - clone with new input |
| 123 | + builder.setInsertionPointAfter(defOp); |
| 124 | + auto newDelay = DelayOp::create( |
| 125 | + builder, delayOp.getLoc(), delayOp.getClock(), delayOp.getEdgeAttr(), |
| 126 | + newInput, delayOp.getDelayAttr(), delayOp.getLengthAttr()); |
| 127 | + mapping.map(value, newDelay.getResult()); |
| 128 | + return newDelay.getResult(); |
| 129 | + } else { |
| 130 | + // No changes needed |
| 131 | + mapping.map(value, value); |
| 132 | + return value; |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + // Handle ClockOp - don't traverse into clock signal, only input |
| 137 | + if (auto clockOp = dyn_cast<ClockOp>(defOp)) { |
| 138 | + Value newInput = cloneWithClock(clockOp.getInput(), clock, edge, builder, |
| 139 | + mapping, visited); |
| 140 | + if (newInput != clockOp.getInput()) { |
| 141 | + builder.setInsertionPointAfter(defOp); |
| 142 | + auto newClockOp = ClockOp::create(builder, clockOp.getLoc(), newInput, |
| 143 | + clockOp.getEdge(), clockOp.getClock()); |
| 144 | + mapping.map(value, newClockOp.getResult()); |
| 145 | + return newClockOp.getResult(); |
| 146 | + } |
| 147 | + mapping.map(value, value); |
| 148 | + return value; |
| 149 | + } |
| 150 | + |
| 151 | + // For other ops, check if any operand needs updating |
| 152 | + SmallVector<Value> newOperands; |
| 153 | + bool anyChanged = false; |
| 154 | + for (Value operand : defOp->getOperands()) { |
| 155 | + Value newOperand = |
| 156 | + cloneWithClock(operand, clock, edge, builder, mapping, visited); |
| 157 | + newOperands.push_back(newOperand); |
| 158 | + if (newOperand != operand) |
| 159 | + anyChanged = true; |
| 160 | + } |
| 161 | + |
| 162 | + if (!anyChanged) { |
| 163 | + mapping.map(value, value); |
| 164 | + return value; |
| 165 | + } |
| 166 | + |
| 167 | + // Clone the operation with new operands |
| 168 | + builder.setInsertionPointAfter(defOp); |
| 169 | + Operation *newOp = builder.clone(*defOp, mapping); |
| 170 | + // Update operands (clone uses mapping, but let's be explicit) |
| 171 | + for (auto [idx, newOperand] : llvm::enumerate(newOperands)) { |
| 172 | + newOp->setOperand(idx, newOperand); |
| 173 | + } |
| 174 | + |
| 175 | + Value newResult = newOp->getResult(0); |
| 176 | + mapping.map(value, newResult); |
| 177 | + return newResult; |
| 178 | +} |
| 179 | + |
| 180 | +struct InferLTLClocksPass |
| 181 | + : public circt::ltl::impl::InferLTLClocksBase<InferLTLClocksPass> { |
| 182 | + void runOnOperation() override; |
| 183 | +}; |
| 184 | + |
| 185 | +} // namespace |
| 186 | + |
| 187 | +void InferLTLClocksPass::runOnOperation() { |
| 188 | + auto module = getOperation(); |
| 189 | + OpBuilder builder(module.getContext()); |
| 190 | + bool changed = false; |
| 191 | + |
| 192 | + // Collect all clock ops first to avoid iterator invalidation |
| 193 | + SmallVector<ClockOp> clockOps; |
| 194 | + module.walk([&](ClockOp clockOp) { clockOps.push_back(clockOp); }); |
| 195 | + |
| 196 | + for (ClockOp clockOp : clockOps) { |
| 197 | + // Check if this clock op's input subtree has any placeholder delays |
| 198 | + DenseSet<Operation *> checkVisited; |
| 199 | + if (!hasPlaceholderDelays(clockOp.getInput(), checkVisited)) |
| 200 | + continue; |
| 201 | + |
| 202 | + Value clock = clockOp.getClock(); |
| 203 | + ClockEdge edge = clockOp.getEdge(); |
| 204 | + |
| 205 | + // Clone/update the input subtree with the correct clock |
| 206 | + IRMapping mapping; |
| 207 | + DenseSet<Operation *> visited; |
| 208 | + Value newInput = cloneWithClock(clockOp.getInput(), clock, edge, builder, |
| 209 | + mapping, visited); |
| 210 | + |
| 211 | + if (newInput != clockOp.getInput()) { |
| 212 | + clockOp.getInputMutable().assign(newInput); |
| 213 | + changed = true; |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | + // Clean up dead operations (original delays that are no longer used) |
| 218 | + // We need to iterate until fixpoint because deleting an op may make its |
| 219 | + // operands dead. |
| 220 | + if (changed) { |
| 221 | + bool erased = true; |
| 222 | + while (erased) { |
| 223 | + erased = false; |
| 224 | + SmallVector<Operation *> toErase; |
| 225 | + module.walk([&](Operation *op) { |
| 226 | + // Only clean up LTL ops that have no uses |
| 227 | + if (isa<DelayOp, ConcatOp, AndOp, OrOp, IntersectOp, RepeatOp, |
| 228 | + GoToRepeatOp, NonConsecutiveRepeatOp, NotOp, ImplicationOp, |
| 229 | + UntilOp, EventuallyOp>(op)) { |
| 230 | + if (op->use_empty()) |
| 231 | + toErase.push_back(op); |
| 232 | + } |
| 233 | + }); |
| 234 | + for (Operation *op : llvm::reverse(toErase)) { |
| 235 | + op->erase(); |
| 236 | + erased = true; |
| 237 | + } |
| 238 | + } |
| 239 | + } |
| 240 | + |
| 241 | + if (!changed) |
| 242 | + markAllAnalysesPreserved(); |
| 243 | +} |
0 commit comments