CIRCT 22.0.0git
Loading...
Searching...
No Matches
HWParameterizeConstantPorts.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// This pass parametizes constant ports on private modules if every instance
10// has a constant as ports (e.g. hart id). This makes it possible to identify
11// constant ports without inter-module analysis.
12//
13//===----------------------------------------------------------------------===//
14
21#include "mlir/IR/Builders.h"
22#include "llvm/Support/Debug.h"
23
24#define DEBUG_TYPE "hw-parameterize-constant-ports"
25
26namespace circt {
27namespace hw {
28#define GEN_PASS_DEF_HWPARAMETERIZECONSTANTPORTS
29#include "circt/Dialect/HW/Passes.h.inc"
30} // namespace hw
31} // namespace circt
32
33using namespace circt;
34using namespace hw;
35
36/// Helper to extract the attribute value and defining operation from a
37/// constant or param.value op.
38static std::pair<Attribute, Operation *>
39getAttributeAndDefiningOp(InstanceOp inst, unsigned portIndex) {
40 Value value = inst.getInputs()[portIndex];
41 auto *op = value.getDefiningOp();
42 if (!op)
43 return {};
44 if (auto constOp = dyn_cast<hw::ConstantOp>(op))
45 return {constOp.getValueAttr(), op};
46 if (auto paramOp = dyn_cast<hw::ParamValueOp>(op))
47 return {paramOp.getValueAttr(), op};
48 return {};
49}
50
51/// Check if all instances have constant values for a given port.
53 unsigned portIndex) {
54 if (node->noUses())
55 return false;
56
57 for (auto *instRecord : node->uses()) {
58 auto inst = dyn_cast<InstanceOp>(instRecord->getInstance().getOperation());
59 if (!inst || !getAttributeAndDefiningOp(inst, portIndex).first)
60 return false;
61 }
62 return true;
63}
64
65namespace {
66struct HWParameterizeConstantPortsPass
67 : public circt::hw::impl::HWParameterizeConstantPortsBase<
68 HWParameterizeConstantPortsPass> {
69 void runOnOperation() override;
70
71private:
72 void processModule(HWModuleOp module, igraph::InstanceGraphNode *node,
73 hw::InstanceGraph &instanceGraph);
74};
75} // namespace
76
77void HWParameterizeConstantPortsPass::processModule(
79 hw::InstanceGraph &instanceGraph) {
80 // Only process private modules with instances
81 if (!module.isPrivate() || node->noUses())
82 return;
83
84 // Find input ports that are constant across all instances
85 hw::ModulePortInfo portInfo(module.getPortList());
86 SmallVector<hw::PortInfo> inputPorts(portInfo.getInputs());
87 SmallVector<unsigned> portsToParameterize;
88
89 for (auto [idx, port] : llvm::enumerate(inputPorts)) {
90 // Skip non-input ports and ports with symbols (could be forced)
91 if (port.dir != ModulePort::Direction::Input || port.getSym())
92 continue;
93
95 portsToParameterize.push_back(idx);
96 }
97
98 if (portsToParameterize.empty())
99 return;
100
101 LLVM_DEBUG(llvm::dbgs() << "Parameterizing " << portsToParameterize.size()
102 << " ports in module " << module.getModuleName()
103 << "\n");
104
105 OpBuilder builder(module.getContext());
106 builder.setInsertionPointToStart(module.getBodyBlock());
107
108 // Create parameters and replace port uses
109 SmallVector<Attribute> newParameters;
110 Namespace paramNamespace;
111 if (auto existingParams = module.getParameters()) {
112 newParameters.append(existingParams.begin(), existingParams.end());
113 for (auto param : existingParams)
114 paramNamespace.newName(cast<ParamDeclAttr>(param).getName().str());
115 }
116
117 // Map from port index to parameter name
118 DenseMap<unsigned, StringAttr> portToParamName;
119
120 for (unsigned portIdx : portsToParameterize) {
121 auto port = inputPorts[portIdx];
122
123 // Create a parameter name based on the port name
124 auto paramNameAttr =
125 builder.getStringAttr(paramNamespace.newName(port.name.str()));
126 portToParamName[portIdx] = paramNameAttr;
127
128 // Create parameter declaration without default value
129 auto paramDecl = ParamDeclAttr::get(paramNameAttr, port.type);
130 newParameters.push_back(paramDecl);
131
132 // Replace uses of the port argument with a param.value operation
133 auto paramRef = ParamDeclRefAttr::get(paramNameAttr, port.type);
134 auto paramValueOp =
135 ParamValueOp::create(builder, module.getLoc(), port.type, paramRef);
136
137 // Replace all uses of the port argument with the param.value operation
138 module.getBodyBlock()->getArgument(portIdx).replaceAllUsesWith(
139 paramValueOp);
140 }
141
142 // Update module parameters
143 module.setParametersAttr(builder.getArrayAttr(newParameters));
144
145 // Remove the parameterized ports from the module signature
146 module.modifyPorts({}, {}, portsToParameterize, {});
147
148 // Erase block arguments in reverse order to maintain indices
149 for (auto idx : llvm::reverse(portsToParameterize))
150 module.getBodyBlock()->eraseArgument(idx);
151
152 // Build new port names for instances (excluding removed ports)
153 DenseSet<unsigned> portsToRemoveSet(portsToParameterize.begin(),
154 portsToParameterize.end());
155 SmallVector<Attribute> newPortNames;
156 for (auto [idx, port] : llvm::enumerate(portInfo.getInputs()))
157 if (!portsToRemoveSet.count(idx))
158 newPortNames.push_back(port.name);
159
160 ArrayAttr newPortNamesAttr = builder.getArrayAttr(newPortNames);
161
162 // Update all instances of this module
163 for (auto *instRecord : node->uses()) {
164 auto inst = dyn_cast<InstanceOp>(instRecord->getInstance().getOperation());
165 // Skip non-InstanceOp users (e.g., InstanceChoiceOp or other instance-like
166 // operations).
167 if (!inst)
168 continue;
169
170 builder.setInsertionPoint(inst);
171
172 // Collect existing parameters and add new ones for removed ports
173 SmallVector<Attribute> instParams;
174 if (auto existingParams = inst.getParameters())
175 instParams.append(existingParams.begin(), existingParams.end());
176
177 for (unsigned portIdx : portsToParameterize) {
178 auto [paramValueAttr, constOp] = getAttributeAndDefiningOp(inst, portIdx);
179 assert(paramValueAttr && "expected constant or param value");
180 auto paramDecl =
181 ParamDeclAttr::get(builder.getContext(), portToParamName[portIdx],
182 inputPorts[portIdx].type, paramValueAttr);
183 instParams.push_back(paramDecl);
184
185 // Delete the constant or param.value op if it's only used by this
186 // instance.
187 if (constOp->hasOneUse()) {
188 constOp->dropAllUses();
189 constOp->erase();
190 }
191 }
192
193 // Build new input list excluding parameterized ports
194 SmallVector<Value> newInputs;
195 for (auto [idx, input] : llvm::enumerate(inst.getInputs()))
196 if (!portsToRemoveSet.count(idx))
197 newInputs.push_back(input);
198
199 // Create new instance with updated parameters and inputs
200 auto newInst = InstanceOp::create(
201 builder, inst.getLoc(), inst.getResultTypes(),
202 inst.getInstanceNameAttr(), inst.getModuleNameAttr(), newInputs,
203 newPortNamesAttr, inst.getResultNamesAttr(),
204 builder.getArrayAttr(instParams), inst.getInnerSymAttr(),
205 inst.getDoNotPrintAttr());
206
207 // Replace old instance
208 instanceGraph.replaceInstance(inst, newInst);
209 inst.replaceAllUsesWith(newInst.getResults());
210 inst.erase();
211 }
212}
213
214void HWParameterizeConstantPortsPass::runOnOperation() {
215 auto &instanceGraph = getAnalysis<hw::InstanceGraph>();
216
217 // Process all HW modules in inverse post-order (top-down).
218 instanceGraph.walkInversePostOrder([&](igraph::InstanceGraphNode &node) {
219 if (auto module =
220 dyn_cast_or_null<HWModuleOp>(node.getModule().getOperation()))
221 processModule(module, &node, instanceGraph);
222 });
223
224 // The instance graph is updated during the pass and remains valid.
225 markAnalysesPreserved<hw::InstanceGraph>();
226}
assert(baseType &&"element must be base type")
static std::pair< Attribute, Operation * > getAttributeAndDefiningOp(InstanceOp inst, unsigned portIndex)
Helper to extract the attribute value and defining operation from a constant or param....
static bool allInstancesHaveConstantForPort(igraph::InstanceGraphNode *node, unsigned portIndex)
Check if all instances have constant values for a given port.
static Block * getBodyBlock(FModuleLike mod)
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:87
HW-specific instance graph with a virtual entry node linking to all publicly visible modules.
This is a Node in the InstanceGraph.
llvm::iterator_range< UseIterator > uses()
bool noUses()
Return true if there are no more instances of this module.
auto getModule()
Get the module that this node is tracking.
virtual void replaceInstance(InstanceOpInterface inst, InstanceOpInterface newInst)
Replaces an instance of a module with another instance.
decltype(auto) walkInversePostOrder(Fn &&fn)
Perform an inverse-post-order walk across the modules.
type(self)
Definition hw.py:325
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition hw.py:1
This holds a decoded list of input/inout and output ports for a module or instance.