Skip to content

Commit de48cfe

Browse files
committed
Deduplicate mulitple moore.procedure ops that with identical wait_event regions.
This is a workaround to support verilog modules with multiple always_ff regions Example rewrite ``` moore.module @bug(...) { moore.procedure always_ff { moore.wait_event { %3 = moore.read %wr_clk_0 : <l1> moore.detect_event posedge %3 : l1 } ... // A } moore.procedure always_ff { moore.wait_event { %3 = moore.read %wr_clk_0 : <l1> moore.detect_event posedge %3 : l1 } ... // B } ~> moore.module @bug(...) { moore.procedure always_ff { moore.wait_event { %3 = moore.read %wr_clk_0 : <l1> moore.detect_event posedge %3 : l1 } ... // A ... // B } ... ```
1 parent afc8084 commit de48cfe

File tree

6 files changed

+298
-1
lines changed

6 files changed

+298
-1
lines changed

include/circt/Dialect/Moore/MoorePasses.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ namespace moore {
2424

2525
std::unique_ptr<mlir::Pass> createSimplifyProceduresPass();
2626
std::unique_ptr<mlir::Pass> createLowerConcatRefPass();
27+
std::unique_ptr<mlir::Pass> createMergeProceduresPass();
2728

2829
/// Generate the code for registering passes.
2930
#define GEN_PASS_REGISTRATION

include/circt/Dialect/Moore/MoorePasses.td

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,53 @@ def LowerConcatRef : Pass<"moore-lower-concatref", "moore::SVModuleOp"> {
3939
let constructor = "circt::moore::createLowerConcatRefPass()";
4040
}
4141

42+
def MergeProcedures : Pass<"moore-merge-procedures", "moore::SVModuleOp"> {
43+
let summary = "Merge multiple always_ff procedures";
44+
let description = [{
45+
Deduplicate mulitple moore.procedure ops that with identical wait_event
46+
regions. This is a workaround to support verilog modules with multiple
47+
always_ff regions. See b/449162932.
48+
49+
Example:
50+
```
51+
module bug (
52+
input logic wr_clk,
53+
input logic wr_data,
54+
output logic [1:0] mem
55+
);
56+
always_ff @(posedge (wr_clk)) begin
57+
mem[0] <= wr_data;
58+
end
59+
always_ff @(posedge (wr_clk)) begin
60+
mem[1] <= wr_data;
61+
end
62+
endmodule
63+
```
64+
65+
produces
66+
67+
```
68+
moore.module @bug(in %wr_clk : !moore.l1, in %wr_data : !moore.l1, out mem : !moore.l2) {
69+
moore.procedure always_ff {
70+
moore.wait_event {
71+
%3 = moore.read %wr_clk_0 : <l1>
72+
moore.detect_event posedge %3 : l1
73+
}
74+
...
75+
}
76+
moore.procedure always_ff {
77+
moore.wait_event {
78+
%3 = moore.read %wr_clk_0 : <l1>
79+
moore.detect_event posedge %3 : l1
80+
}
81+
...
82+
moore.return
83+
}
84+
...
85+
}
86+
```
87+
}];
88+
let constructor = "circt::moore::createMergeProceduresPass()";
89+
}
90+
4291
#endif // CIRCT_DIALECT_MOORE_MOOREPASSES_TD

lib/Dialect/Moore/Transforms/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
add_circt_dialect_library(CIRCTMooreTransforms
22
LowerConcatRef.cpp
33
SimplifyProcedures.cpp
4+
MergeProcedures.cpp
45

56

67
DEPENDS
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#include <string>
2+
3+
#include "circt/Dialect/Moore/MooreOps.h"
4+
#include "circt/Dialect/Moore/MoorePasses.h"
5+
#include "circt/Support/LLVM.h"
6+
#include "llvm/Support/Debug.h"
7+
8+
#define DEBUG_TYPE "merge-procedures"
9+
10+
namespace circt {
11+
namespace moore {
12+
#define GEN_PASS_DEF_MERGEPROCEDURES
13+
#include "circt/Dialect/Moore/MoorePasses.h.inc"
14+
} // namespace moore
15+
} // namespace circt
16+
17+
using namespace circt;
18+
using namespace moore;
19+
using mlir::IRRewriter;
20+
21+
namespace {
22+
23+
struct PrintMatchFailure : mlir::RewriterBase::Listener {
24+
void notifyMatchFailure(
25+
Location loc, function_ref<void(Diagnostic&)> reasonCallback) override {
26+
Diagnostic diag(loc, mlir::DiagnosticSeverity::Remark);
27+
reasonCallback(diag);
28+
}
29+
};
30+
31+
struct MergeProceduresPass
32+
: public circt::moore::impl::MergeProceduresBase<MergeProceduresPass> {
33+
void runOnOperation() override;
34+
LogicalResult TryMerge(IRRewriter& rewriter,
35+
ArrayRef<ProcedureOp> procedures);
36+
};
37+
38+
} // namespace
39+
40+
std::unique_ptr<mlir::Pass> circt::moore::createMergeProceduresPass() {
41+
return std::make_unique<MergeProceduresPass>();
42+
}
43+
44+
LogicalResult MergeProceduresPass::TryMerge(IRRewriter& rewriter,
45+
ArrayRef<ProcedureOp> procedures) {
46+
if (procedures.size() <= 1) return success();
47+
48+
// Only merge always/always_ff procedures if their wait_events are identical.
49+
SmallVector<WaitEventOp> wait_events;
50+
wait_events.reserve(procedures.size());
51+
for (ProcedureOp proc : procedures) {
52+
auto proc_wait_events = proc.getOps<WaitEventOp>();
53+
if (!hasSingleElement(proc_wait_events)) {
54+
return rewriter.notifyMatchFailure(proc, "expected a single wait event");
55+
}
56+
wait_events.push_back(*proc_wait_events.begin());
57+
}
58+
59+
// We only merge procedures with a single block only. Otherwise it needs more
60+
// work to maintain correct control flow.
61+
for (ProcedureOp proc : procedures) {
62+
if (proc.getBody().getBlocks().size() != 1) {
63+
return rewriter.notifyMatchFailure(
64+
proc, "can only merge procedures with a single block");
65+
}
66+
}
67+
68+
// Compare wait event ops by string serialization.
69+
WaitEventOp first_wait_event = wait_events[0];
70+
auto is_equivalent = [&](Value lhs, Value rhs) -> LogicalResult {
71+
// lhs refers to 'first_wait_event'.
72+
auto producer = lhs.getDefiningOp();
73+
// If it's a block argument, check for value equivalence.
74+
if (!producer) return success(lhs == rhs);
75+
// If the value was produced within the wait event, it's equivalent.
76+
if (first_wait_event->isProperAncestor(producer)) return success();
77+
// If the value was produced outside of the wait event, check for value
78+
// equivalence.
79+
return success(lhs == rhs);
80+
};
81+
82+
// Check if wait_events are equivalent.
83+
for (WaitEventOp wait_event : wait_events) {
84+
if (!mlir::OperationEquivalence::isEquivalentTo(
85+
first_wait_event, wait_event,
86+
/*checkEquivalent=*/is_equivalent,
87+
/*markEquivalent=*/nullptr,
88+
mlir::OperationEquivalence::Flags::IgnoreLocations)) {
89+
return rewriter.notifyMatchFailure(wait_event, "wait event mismatch");
90+
}
91+
}
92+
93+
// Remove all but the first wait_event.
94+
for (WaitEventOp wait_event : llvm::ArrayRef(wait_events).drop_front()) {
95+
wait_event.erase();
96+
}
97+
98+
// Merge all procedures into the first one.
99+
ProcedureOp first_proc = procedures[0];
100+
Block* dst_proc_block = &first_proc.getBody().getBlocks().front();
101+
for (ProcedureOp proc : llvm::ArrayRef(procedures).drop_front()) {
102+
Block* src_proc_block = &proc.getBody().getBlocks().front();
103+
src_proc_block->getTerminator()->erase();
104+
rewriter.inlineBlockBefore(src_proc_block, dst_proc_block,
105+
// insert before block terminator.
106+
std::prev(dst_proc_block->end()));
107+
// Delete all but the first procedure.
108+
proc.erase();
109+
}
110+
111+
return success();
112+
}
113+
114+
void MergeProceduresPass::runOnOperation() {
115+
/*
116+
Sample rewrite
117+
118+
moore.module @bug(...) {
119+
moore.procedure always_ff {
120+
moore.wait_event {
121+
%3 = moore.read %wr_clk_0 : <l1>
122+
moore.detect_event posedge %3 : l1
123+
}
124+
... // A
125+
}
126+
moore.procedure always_ff {
127+
moore.wait_event {
128+
%3 = moore.read %wr_clk_0 : <l1>
129+
moore.detect_event posedge %3 : l1
130+
}
131+
... // B
132+
}
133+
134+
~>
135+
136+
moore.module @bug(...) {
137+
moore.procedure always_ff {
138+
moore.wait_event {
139+
%3 = moore.read %wr_clk_0 : <l1>
140+
moore.detect_event posedge %3 : l1
141+
}
142+
... // A
143+
... // B
144+
}
145+
...
146+
*/
147+
148+
// Collect all moore.procedures and group by their kind. We try to merge
149+
// procedures of each kind individually, so we merge multiple always_ff
150+
// procedures, but we don't merge always_ff and always together.
151+
llvm::DenseMap<moore::ProcedureKind, SmallVector<ProcedureOp>> procedures;
152+
getOperation().walk(
153+
[&](ProcedureOp proc) { procedures[proc.getKind()].push_back(proc); });
154+
155+
IRRewriter rewriter(&getContext());
156+
PrintMatchFailure listener;
157+
LLVM_DEBUG(rewriter.setListener(&listener););
158+
159+
// Try to merge procedures of each kind. Failing to merge is not an error -
160+
// instead the error may later surface when lowering arc to llvm.
161+
for (moore::ProcedureKind kind :
162+
{moore::ProcedureKind::AlwaysFF, moore::ProcedureKind::Always}) {
163+
if (failed(TryMerge(rewriter, procedures[kind]))) {
164+
getOperation().emitWarning("could not merge procedures of kind ")
165+
<< static_cast<uint32_t>(kind);
166+
}
167+
}
168+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// RUN: circt-opt %s -moore-merge-procedures -split-input-file -verify-diagnostics | FileCheck %s
2+
module {
3+
// CHECK-LABEL: module @merge_procedures
4+
moore.module @merge_procedures(in %wr_clk : !moore.l1, in %wr_data1 : !moore.l1, in %wr_data2 : !moore.l1, out mem : !moore.l2) {
5+
// CHECK-NEXT: moore.variable name "wr_clk"
6+
%wr_clk_0 = moore.variable name "wr_clk" : <l1>
7+
// CHECK-NEXT: moore.variable name "wr_data1"
8+
%wr_data1_1 = moore.variable name "wr_data1" : <l1>
9+
// CHECK-NEXT: moore.variable name "wr_data2"
10+
%wr_data2_2 = moore.variable name "wr_data2" : <l1>
11+
// CHECK-NEXT: moore.variable
12+
%mem = moore.variable : <l2>
13+
// CHECK-NEXT: moore.procedure always_ff
14+
moore.procedure always_ff {
15+
moore.wait_event {
16+
%3 = moore.read %wr_clk_0 : <l1>
17+
moore.detect_event posedge %3 : l1
18+
}
19+
%1 = moore.extract_ref %mem from 0 : <l2> -> <l1>
20+
%2 = moore.read %wr_data1_1 : <l1>
21+
moore.nonblocking_assign %1, %2 : l1
22+
moore.return
23+
}
24+
moore.procedure always_ff {
25+
moore.wait_event {
26+
%3 = moore.read %wr_clk_0 : <l1>
27+
moore.detect_event posedge %3 : l1
28+
}
29+
%1 = moore.extract_ref %mem from 1 : <l2> -> <l1>
30+
%2 = moore.read %wr_data2_2 : <l1>
31+
moore.nonblocking_assign %1, %2 : l1
32+
moore.return
33+
}
34+
moore.assign %wr_clk_0, %wr_clk : l1
35+
moore.assign %wr_data1_1, %wr_data1 : l1
36+
moore.assign %wr_data2_2, %wr_data2 : l1
37+
%0 = moore.read %mem : <l2>
38+
moore.output %0 : !moore.l2
39+
}
40+
// CHECK-NOT: moore.procedure
41+
}
42+
43+
// -----
44+
45+
46+
module {
47+
// Non-identical wait events should not be merged.
48+
// CHECK-LABEL: module @procedures_with_non_identical_wait_events
49+
// @expected-warning @+1 {{could not merge procedures}}
50+
moore.module @procedures_with_non_identical_wait_events() {
51+
%wr_clk_0 = moore.variable name "wr_clk_1" : <l1>
52+
%wr_clk_1 = moore.variable name "wr_clk_2" : <l1>
53+
// CHECK: moore.procedure always_ff
54+
// CHECK: moore.procedure always_ff
55+
moore.procedure always_ff {
56+
moore.wait_event {
57+
%3 = moore.read %wr_clk_0 : <l1>
58+
moore.detect_event posedge %3 : l1
59+
}
60+
moore.return
61+
}
62+
moore.procedure always_ff {
63+
moore.wait_event {
64+
%3 = moore.read %wr_clk_1 : <l1>
65+
moore.detect_event posedge %3 : l1
66+
}
67+
moore.return
68+
}
69+
moore.output
70+
}
71+
// CHECK-NOT: moore.procedure
72+
}

tools/circt-verilog/circt-verilog.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,13 @@ static CLOptions opts;
286286
//===----------------------------------------------------------------------===//
287287

288288
/// Optimize and simplify the Moore dialect IR.
289-
static void populateMooreTransforms(PassManager &pm) {
289+
static void populateMooreTransforms(PassManager& pm) {
290+
{
291+
// Perform module-specific transformations.
292+
auto& modulePM = pm.nest<moore::SVModuleOp>();
293+
modulePM.addPass(moore::createMergeProceduresPass());
294+
}
295+
290296
{
291297
// Perform an initial cleanup and preprocessing across all
292298
// modules/functions.

0 commit comments

Comments
 (0)