Skip to content

Commit 73fae5b

Browse files
committed
[LTL] Add InferLTLClocks pass to propagate clock from ltl.clock to ltl.delay
After FIRRTLToHW lowering, ltl.delay operations have placeholder clocks because FIRRTL's delay intrinsic doesn't carry clock information. Three approaches were considered: A) Delayed lowering at ClockIntrinsicOp - complex, breaks lowering pattern B) Two-pass scanning - extra overhead, complex use-def tracking C) Post-processing pass - minimal changes, independent, testable Chose C for minimal disruption and incremental implementation. The new pass walks ltl.clock ops, finds ltl.delay ops with placeholder clocks in their input chains, and replaces them with properly clocked versions.
1 parent bf1f0b2 commit 73fae5b

File tree

8 files changed

+466
-0
lines changed

8 files changed

+466
-0
lines changed

include/circt/Dialect/LTL/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@ add_dependencies(circt-headers CIRCTLTLEnumsIncGen)
1111
mlir_tablegen(LTLFolds.cpp.inc -gen-rewriters)
1212
add_public_tablegen_target(CIRCTLTLFoldsIncGen)
1313
add_dependencies(circt-headers CIRCTLTLFoldsIncGen)
14+
15+
set(LLVM_TARGET_DEFINITIONS LTLPasses.td)
16+
mlir_tablegen(LTLPasses.h.inc -gen-pass-decls)
17+
add_public_tablegen_target(CIRCTLTLTransformsIncGen)
18+
add_dependencies(circt-headers CIRCTLTLTransformsIncGen)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//===- LTLPasses.h - LTL pass entry points ----------------------*- 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 header file defines prototypes that expose pass constructors.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef CIRCT_DIALECT_LTL_LTLPASSES_H
14+
#define CIRCT_DIALECT_LTL_LTLPASSES_H
15+
16+
#include "mlir/Pass/Pass.h"
17+
#include "mlir/Pass/PassRegistry.h"
18+
19+
namespace circt {
20+
namespace ltl {
21+
22+
#define GEN_PASS_DECL
23+
#include "circt/Dialect/LTL/LTLPasses.h.inc"
24+
25+
/// Generate the code for registering passes.
26+
#define GEN_PASS_REGISTRATION
27+
#include "circt/Dialect/LTL/LTLPasses.h.inc"
28+
29+
} // namespace ltl
30+
} // namespace circt
31+
32+
#endif // CIRCT_DIALECT_LTL_LTLPASSES_H
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//===-- Passes.td - LTL pass definition file ---------------*- tablegen -*-===//
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 file defines the passes that work on the LTL dialect.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef CIRCT_DIALECT_LTL_PASSES_TD
14+
#define CIRCT_DIALECT_LTL_PASSES_TD
15+
16+
include "mlir/Pass/PassBase.td"
17+
18+
def InferLTLClocks : Pass<"ltl-infer-clocks", "hw::HWModuleOp"> {
19+
let summary =
20+
"Infer clock for ltl.delay operations from surrounding ltl.clock ops";
21+
let description = [{
22+
This pass propagates clock information from `ltl.clock` operations down
23+
to `ltl.delay` operations in their input chains. After FIRRTLToHW lowering,
24+
`ltl.delay` operations may have placeholder clocks (constant true) because
25+
the FIRRTL delay intrinsic doesn't carry explicit clock information.
26+
27+
This pass walks through each `ltl.clock` operation, finds all `ltl.delay`
28+
operations in its input chain that have placeholder clocks, and replaces
29+
them with correctly clocked versions using the clock from the enclosing
30+
`ltl.clock` operation.
31+
32+
Example transformation:
33+
```mlir
34+
// Before:
35+
%true = hw.constant true
36+
%d = ltl.delay %true, posedge, %a, 1, 0 : i1
37+
%p = ltl.clock %d, posedge %clk : !ltl.sequence
38+
39+
// After:
40+
%d = ltl.delay %clk, posedge, %a, 1, 0 : i1
41+
%p = ltl.clock %d, posedge %clk : !ltl.sequence
42+
```
43+
}];
44+
let dependentDialects = ["hw::HWDialect", "ltl::LTLDialect"];
45+
}
46+
47+
#endif // CIRCT_DIALECT_LTL_PASSES_TD

include/circt/InitAllPasses.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "circt/Dialect/Handshake/HandshakePasses.h"
3030
#include "circt/Dialect/Kanagawa/KanagawaPasses.h"
3131
#include "circt/Dialect/LLHD/Transforms/LLHDPasses.h"
32+
#include "circt/Dialect/LTL/LTLPasses.h"
3233
#include "circt/Dialect/MSFT/MSFTPasses.h"
3334
#include "circt/Dialect/Moore/MoorePasses.h"
3435
#include "circt/Dialect/OM/OMPasses.h"
@@ -76,6 +77,7 @@ inline void registerAllPasses() {
7677
hw::registerPasses();
7778
kanagawa::registerPasses();
7879
llhd::registerPasses();
80+
ltl::registerPasses();
7981
moore::registerPasses();
8082
msft::registerPasses();
8183
om::registerPasses();

lib/Dialect/LTL/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ add_circt_dialect_library(CIRCTLTL
1919
MLIRIR
2020
MLIRInferTypeOpInterface
2121
)
22+
23+
add_subdirectory(Transforms)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
add_circt_dialect_library(CIRCTLTLTransforms
2+
InferLTLClocks.cpp
3+
4+
DEPENDS
5+
CIRCTLTLTransformsIncGen
6+
7+
LINK_LIBS PUBLIC
8+
CIRCTLTL
9+
CIRCTHW
10+
MLIRIR
11+
MLIRPass
12+
MLIRTransformUtils
13+
)
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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

Comments
 (0)