Skip to content

Commit 2e7cfda

Browse files
authored
[HW] Add HWBypassInnerSymbols pass (#9291)
This pass moves inner symbols from ports to wires, then bypasses wire operations with inner symbols by replacing uses with their inputs while keeping the wire to preserve the symbol. This enables optimizations (like imconstprop or parametization) to cross symbol boundaries while maintaining symbol references. The pass performs two main transformations: 1. Moves inner symbols from module ports to wire operations, allowing optimizations like constant propagation to cross port boundaries. 2. Bypasses wire operations that have inner symbols by replacing all uses of the wire's result with its input operand, while keeping the wire operation itself to preserve the inner symbol reference. Note: This transformation assumes that values associated with inner symbols are not mutated through inner symbols (e.g. force). This assumption may not hold in design verification contexts, but is safe in synthesis.
1 parent 006bc57 commit 2e7cfda

File tree

4 files changed

+156
-0
lines changed

4 files changed

+156
-0
lines changed

include/circt/Dialect/HW/Passes.td

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,26 @@ def HWParameterizeConstantPorts : Pass<"hw-parameterize-constant-ports",
126126
}];
127127
}
128128

129+
def HWBypassInnerSymbols : Pass<"hw-bypass-inner-symbols", "hw::HWModuleOp"> {
130+
let summary = "Pass through values through inner symbols";
131+
let description = [{
132+
This pass moves inner symbols from ports to wires, then bypasses wire
133+
operations with inner symbols by replacing uses with their inputs while
134+
keeping the wire to preserve the symbol. This enables optimizations to
135+
cross symbol boundaries while maintaining symbol references.
136+
137+
Warning: This transformation assumes that values associated with inner
138+
symbols are not mutated through inner symbols (e.g. force). This assumption
139+
may not hold in simulation, but is safe in synthesis. This pass treats
140+
inner symbols differently from the optimization-blocking semantics that
141+
other parts of CIRCT use, so it is opt-in and should only be used when
142+
the above assumptions hold.
143+
}];
144+
145+
let statistics = [
146+
Statistic<"numPortsMoved", "num-ports-moved",
147+
"Number of inner symbols moved from ports to wires">
148+
];
149+
}
150+
129151
#endif // CIRCT_DIALECT_HW_PASSES_TD

lib/Dialect/HW/Transforms/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
add_circt_dialect_library(CIRCTHWTransforms
22
HWAggregateToComb.cpp
3+
HWBypassInnerSymbols.cpp
34
HWParameterizeConstantPorts.cpp
45
HWPrintInstanceGraph.cpp
56
HWSpecialize.cpp
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
// This pass moves inner symbols from ports to wires, then bypasses wire
10+
// operations with inner symbols by replacing uses with their inputs while
11+
// keeping the wire to preserve the symbol. This enables optimizations to
12+
// cross symbol boundaries while maintaining symbol references.
13+
//
14+
// Note: This transformation assumes that values associated with inner
15+
// symbols are not mutated through inner symbols (e.g. force). This assumption
16+
// may not hold in design verification contexts, but is safe in synthesis.
17+
//
18+
//===----------------------------------------------------------------------===//
19+
20+
#include "circt/Dialect/HW/HWOps.h"
21+
#include "circt/Dialect/HW/HWPasses.h"
22+
#include "circt/Dialect/HW/PortImplementation.h"
23+
#include "mlir/IR/PatternMatch.h"
24+
#include "mlir/Transforms/WalkPatternRewriteDriver.h"
25+
26+
namespace circt {
27+
namespace hw {
28+
#define GEN_PASS_DEF_HWBYPASSINNERSYMBOLS
29+
#include "circt/Dialect/HW/Passes.h.inc"
30+
} // namespace hw
31+
} // namespace circt
32+
33+
using namespace circt;
34+
using namespace circt::hw;
35+
36+
namespace {
37+
struct HWBypassInnerSymbolsPass
38+
: public impl::HWBypassInnerSymbolsBase<HWBypassInnerSymbolsPass> {
39+
void runOnOperation() override;
40+
using HWBypassInnerSymbolsBase<
41+
HWBypassInnerSymbolsPass>::HWBypassInnerSymbolsBase;
42+
};
43+
44+
/// Pattern to bypass wire operations with inner symbols.
45+
struct BypassWireWithInnerSym : public OpRewritePattern<WireOp> {
46+
using OpRewritePattern<WireOp>::OpRewritePattern;
47+
48+
LogicalResult matchAndRewrite(WireOp wire,
49+
PatternRewriter &rewriter) const override {
50+
// Only bypass wires that have an inner symbol or use
51+
if (!wire.getInnerSymAttr() || wire.use_empty())
52+
return failure();
53+
54+
// Replace all uses of the wire with its input
55+
rewriter.modifyOpInPlace(wire, [&] {
56+
wire->replaceAllUsesWith(ArrayRef<Value>{wire.getInput()});
57+
});
58+
return success();
59+
}
60+
};
61+
} // namespace
62+
63+
void HWBypassInnerSymbolsPass::runOnOperation() {
64+
auto module = getOperation();
65+
66+
// First move the inner symbol from the port to allow constprop and other
67+
// optimizations to cross the boundary.
68+
hw::ModulePortInfo portList(module.getPortList());
69+
auto *outputOp = module.getBodyBlock()->getTerminator();
70+
OpBuilder builder(&getContext());
71+
bool hasInnerSym = false;
72+
auto moduleType = module.getModuleType();
73+
for (auto [index, port] : llvm::enumerate(portList)) {
74+
auto sym = port.getSym();
75+
if (!sym)
76+
continue;
77+
hasInnerSym = true;
78+
++numPortsMoved;
79+
if (port.isOutput()) {
80+
auto value = outputOp->getOperand(moduleType.getOutputIdForPortId(index));
81+
builder.setInsertionPointAfterValue(value);
82+
auto wire = WireOp::create(builder, value.getLoc(), value);
83+
wire.setInnerSymAttr(sym);
84+
} else {
85+
auto arg = module.getBodyBlock()->getArgument(
86+
moduleType.getInputIdForPortId(index));
87+
builder.setInsertionPointToStart(module.getBodyBlock());
88+
auto wire = WireOp::create(builder, arg.getLoc(), arg);
89+
wire.setInnerSymAttr(sym);
90+
}
91+
}
92+
93+
if (hasInnerSym) {
94+
// Bulk clear all port symbols.
95+
auto innerSymAttr = StringAttr::get(
96+
&getContext(), hw::HWModuleLike::getPortSymbolAttrName());
97+
SmallVector<Attribute, 0> newAttrs(portList.size(), {});
98+
cast<HWModuleLike>(*module).setPortAttrs(innerSymAttr, newAttrs);
99+
}
100+
101+
RewritePatternSet patterns(&getContext());
102+
patterns.add<BypassWireWithInnerSym>(&getContext());
103+
mlir::walkAndApplyPatterns(module, std::move(patterns));
104+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// RUN: circt-opt -hw-bypass-inner-symbols %s | FileCheck %s
2+
3+
// CHECK-LABEL: hw.module @BypassMultipleWires
4+
// CHECK-SAME: in %a : i8, in %b : i8, out out : i8
5+
hw.module @BypassMultipleWires(in %a: i8, in %b: i8, out out: i8) {
6+
// Multiple wires with symbols should all be bypassed
7+
// CHECK: %wire1 = hw.wire %a sym @wire1 : i8
8+
// CHECK: %wire2 = hw.wire %b sym @wire2 : i8
9+
// CHECK: %[[ADD:.+]] = comb.add bin %a, %b
10+
%wire1 = hw.wire %a sym @wire1 : i8
11+
%wire2 = hw.wire %b sym @wire2 : i8
12+
%result = comb.add bin %wire1, %wire2 : i8
13+
14+
// CHECK: hw.output %[[ADD]]
15+
hw.output %result : i8
16+
}
17+
18+
// CHECK-LABEL: hw.module @BypassInputPortWithSym
19+
// CHECK-SAME: in %a : i32, in %b : i32, out out : i32
20+
hw.module @BypassInputPortWithSym(in %a: i32 {hw.exportPort = #hw<innerSym@in>}, in %b: i32, out out: i32 {hw.exportPort = #hw<innerSym@out>}) {
21+
// Port symbols are moved to wires, then wires are bypassed
22+
// CHECK: %[[WIRE_IN:.+]] = hw.wire %a sym @in : i32
23+
// CHECK: %[[ADD:.+]] = comb.add bin %a, %b
24+
// CHECK: %[[WIRE_OUT:.+]] = hw.wire %[[ADD]] sym @out : i32
25+
%result = comb.add bin %a, %b : i32
26+
27+
// CHECK: hw.output %[[ADD]]
28+
hw.output %result : i32
29+
}

0 commit comments

Comments
 (0)