CIRCT 23.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:
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., other instance-like operations).
166 if (!inst)
167 continue;
168
169 builder.setInsertionPoint(inst);
170
171 // Collect existing parameters and add new ones for removed ports
172 SmallVector<Attribute> instParams;
173 if (auto existingParams = inst.getParameters())
174 instParams.append(existingParams.begin(), existingParams.end());
175
176 for (unsigned portIdx : portsToParameterize) {
177 auto [paramValueAttr, constOp] = getAttributeAndDefiningOp(inst, portIdx);
178 assert(paramValueAttr && "expected constant or param value");
179 auto paramDecl =
180 ParamDeclAttr::get(builder.getContext(), portToParamName[portIdx],
181 inputPorts[portIdx].type, paramValueAttr);
182 instParams.push_back(paramDecl);
183
184 // Delete the constant or param.value op if it's only used by this
185 // instance.
186 if (constOp->hasOneUse()) {
187 constOp->dropAllUses();
188 constOp->erase();
189 }
190 }
191
192 // Build new input list excluding parameterized ports
193 SmallVector<Value> newInputs;
194 for (auto [idx, input] : llvm::enumerate(inst.getInputs()))
195 if (!portsToRemoveSet.count(idx))
196 newInputs.push_back(input);
197
198 // Create new instance with updated parameters and inputs
199 auto newInst = InstanceOp::create(
200 builder, inst.getLoc(), inst.getResultTypes(),
201 inst.getInstanceNameAttr(), inst.getModuleNameAttr(), newInputs,
202 newPortNamesAttr, inst.getResultNamesAttr(),
203 builder.getArrayAttr(instParams), inst.getInnerSymAttr(),
204 inst.getDoNotPrintAttr());
205
206 // Replace old instance
207 instanceGraph.replaceInstance(inst, newInst);
208 inst.replaceAllUsesWith(newInst.getResults());
209 inst.erase();
210 }
211}
212
213void HWParameterizeConstantPortsPass::runOnOperation() {
214 auto &instanceGraph = getAnalysis<hw::InstanceGraph>();
215
216 // Process all HW modules in inverse post-order (top-down).
217 instanceGraph.walkInversePostOrder([&](igraph::InstanceGraphNode &node) {
218 if (auto module =
219 dyn_cast_or_null<HWModuleOp>(node.getModule().getOperation()))
220 processModule(module, &node, instanceGraph);
221 });
222
223 // The instance graph is updated during the pass and remains valid.
224 markAnalysesPreserved<hw::InstanceGraph>();
225}
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 LogicalResult processModule(const DomainInfo &info, TermAllocator &allocator, DomainTable &table, const ModuleUpdateTable &updateTable, FModuleOp moduleOp)
Populate the domain table by processing the moduleOp.
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.