CIRCT 23.0.0git
Loading...
Searching...
No Matches
ResourceUsageAnalysis.cpp
Go to the documentation of this file.
1//===- ResourceUsageAnalysis.cpp - Resource Usage Analysis ------*- 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 file implements the resource usage analysis for the Synth dialect.
10// The analysis computes resource utilization including and-inverter gates,
11// DFF bits, and LUTs across module hierarchies.
12//
13//===----------------------------------------------------------------------===//
14
22#include "circt/Support/LLVM.h"
23#include "mlir/IR/BuiltinOps.h"
24#include "mlir/Pass/AnalysisManager.h"
25#include "mlir/Support/FileUtilities.h"
26#include "llvm/ADT/ScopeExit.h"
27#include "llvm/Support/JSON.h"
28#include "llvm/Support/ToolOutputFile.h"
29
30namespace circt {
31namespace synth {
32#define GEN_PASS_DEF_PRINTRESOURCEUSAGEANALYSIS
33#include "circt/Dialect/Synth/Transforms/SynthPasses.h.inc"
34} // namespace synth
35} // namespace circt
36
37using namespace circt;
38using namespace synth;
39
40//===----------------------------------------------------------------------===//
41// ResourceUsageAnalysis Implementation
42//===----------------------------------------------------------------------===//
43
44/// Get resource count for an operation if it's a tracked resource type.
45/// Returns (operation name, count) pair, or std::nullopt if not tracked.
46/// The operation name may include additional information (e.g., LUT input
47/// count).
48static std::optional<std::pair<std::string, uint64_t>>
49getResourceCount(Operation *op) {
50 if (op->getNumResults() != 1 || !op->getResult(0).getType().isInteger())
51 return std::nullopt;
52 return TypeSwitch<Operation *,
53 std::optional<std::pair<std::string, uint64_t>>>(op)
54 // Variadic logic operations (AND, OR, XOR, AIG).
55 // Gate count = (num_inputs - 1) * bitwidth
56 .Case<synth::aig::AndInverterOp, comb::AndOp, comb::OrOp, comb::XorOp>(
57 [](auto logicOp) {
58 return std::make_pair(
59 logicOp->getName().getStringRef(),
60 (logicOp.getNumOperands() - 1) *
61 logicOp.getType().getIntOrFloatBitWidth());
62 })
63 // Majority-inverter graph (MIG) - include input count in the name.
64 // Gate count = (num_inputs / 2) * bitwidth
65 // Each MIG gate consumes 3 inputs and produces 1 output, so a variadic
66 // MIG operation with N inputs requires N/2 gates (rounded down).
67 .Case<synth::mig::MajorityInverterOp>([](auto logicOp) {
68 uint64_t count = logicOp.getType().getIntOrFloatBitWidth();
69 // Concatenate input count to the operation name.
70 std::string name = (Twine(logicOp->getName().getStringRef()) + "_" +
71 Twine(logicOp.getNumOperands()))
72 .str();
73 return std::make_pair(std::move(name), count);
74 })
75 // Truth tables (LUTs) - include input count in the name.
76 .Case<comb::TruthTableOp>([](auto op) {
77 uint64_t count = op.getType().getIntOrFloatBitWidth();
78 // Concatenate LUT input number to the operation name.
79 std::string name = (Twine(op->getName().getStringRef()) + "_" +
80 Twine(op.getNumOperands()))
81 .str();
82 return std::make_pair(std::move(name), count);
83 })
84 // Sequential elements.
85 // Count = bitwidth
86 .Case<seq::CompRegOp, seq::FirRegOp>([](auto op) {
87 uint64_t count = op.getType().getIntOrFloatBitWidth();
88 return std::make_pair(op->getName().getStringRef().str(), count);
89 })
90 .Default([](Operation *) { return std::nullopt; });
91}
92
93ResourceUsageAnalysis::ResourceUsageAnalysis(Operation *moduleOp,
94 mlir::AnalysisManager &am)
95 : instanceGraph(&am.getAnalysis<igraph::InstanceGraph>()) {}
96
99 // Check cache first.
100 auto it = designUsageCache.find(moduleName);
101 if (it != designUsageCache.end())
102 return it->second.get();
103
104 // Lookup module in instance graph.
105 auto *node = instanceGraph->lookup(moduleName);
106 if (!node)
107 return nullptr;
108
109 auto module = dyn_cast_or_null<igraph::ModuleOpInterface>(node->getModule());
110 if (!module)
111 return nullptr;
112
113 return getResourceUsage(module);
114}
115
117ResourceUsageAnalysis::getResourceUsage(igraph::ModuleOpInterface module) {
118 // Check cache first.
119 auto cacheIt = designUsageCache.find(module.getModuleNameAttr());
120 if (cacheIt != designUsageCache.end())
121 return cacheIt->second.get();
122
123 auto *node = instanceGraph->lookup(module.getModuleNameAttr());
124
125 // Count local resources by walking all operations in the module.
126 llvm::StringMap<uint64_t> counts;
127 uint64_t unknownOpCount = 0;
128 module->walk([&](Operation *op) {
129 if (auto resource = getResourceCount(op))
130 counts[resource->first] += resource->second;
131 else if (op->getNumResults() > 0 && !isa<hw::HWInstanceLike>(op) &&
132 !op->hasTrait<mlir::OpTrait::ConstantLike>()) {
133 // Track operations that has one result and is not a constant.
134 unknownOpCount++;
135 }
136 });
137
138 // Add unknown operation count if any were found.
139 if (unknownOpCount > 0)
140 counts["<unknown>"] = unknownOpCount;
141
142 // Initialize module usage with local counts.
143 // Total will be updated as we process child instances.
144 ResourceUsage local(std::move(counts));
145 auto moduleUsage = std::make_unique<ModuleResourceUsage>(
146 module.getModuleNameAttr(), local, local);
147
148 // Recursively process child module instances.
149 for (auto *child : *node) {
150 auto *targetNode = child->getTarget();
151 auto childModule =
152 dyn_cast_or_null<igraph::ModuleOpInterface>(targetNode->getModule());
153 if (!childModule)
154 continue;
155
156 auto *instanceOp = child->getInstance().getOperation();
157 // Skip instances with no results or marked as "doNotPrint".
158 if (instanceOp->getNumResults() == 0 ||
159 instanceOp->hasAttrOfType<UnitAttr>("doNotPrint"))
160 continue;
161
162 // Recursively compute child usage and accumulate into total.
163 auto *childUsage = getResourceUsage(childModule);
164 moduleUsage->total += childUsage->total;
165 moduleUsage->instances.emplace_back(
166 childModule.getModuleNameAttr(),
167 child->getInstance().getInstanceNameAttr(), childUsage);
168 }
169
170 // Insert into cache and return.
171 auto [it, success] = designUsageCache.try_emplace(module.getModuleNameAttr(),
172 std::move(moduleUsage));
173 assert(success && "module already exists in cache");
174
175 return it->second.get();
176}
177
178//===----------------------------------------------------------------------===//
179// JSON Serialization
180//===----------------------------------------------------------------------===//
181
182/// Convert ResourceUsage to JSON object.
183static llvm::json::Object
184getModuleResourceUsageJSON(const ResourceUsageAnalysis::ResourceUsage &usage) {
185 llvm::json::Object obj;
186 for (const auto &count : usage.getCounts())
187 obj[count.getKey()] = count.second;
188 return obj;
189}
190
191/// Convert ModuleResourceUsage to JSON object with full hierarchy.
192/// This creates fully-elaborated information including all child instances.
193static llvm::json::Object getModuleResourceUsageJSON(
194 const ResourceUsageAnalysis::ModuleResourceUsage &usage) {
195 llvm::json::Object obj;
196 obj["moduleName"] = usage.moduleName.getValue();
197 obj["local"] = getModuleResourceUsageJSON(usage.getLocal());
198 obj["total"] = getModuleResourceUsageJSON(usage.getTotal());
199
200 // Serialize child instances recursively.
201 SmallVector<llvm::json::Value> instances;
202 for (const auto &instance : usage.instances) {
203 llvm::json::Object child;
204 child["instanceName"] = instance.instanceName.getValue();
205 child["moduleName"] = instance.moduleName.getValue();
206 child["usage"] = getModuleResourceUsageJSON(*instance.usage);
207 instances.push_back(std::move(child));
208 }
209 obj["instances"] = llvm::json::Array(instances);
210
211 return obj;
212}
213
215 raw_ostream &os) const {
216 os << getModuleResourceUsageJSON(*this);
217}
218
219namespace {
220struct PrintResourceUsageAnalysisPass
221 : public impl::PrintResourceUsageAnalysisBase<
222 PrintResourceUsageAnalysisPass> {
223 using PrintResourceUsageAnalysisBase::PrintResourceUsageAnalysisBase;
224
225 void runOnOperation() override;
226
227 /// Determine which modules to analyze based on options.
228 LogicalResult getTopModules(igraph::InstanceGraph *instanceGraph,
229 SmallVectorImpl<igraph::ModuleOpInterface> &tops);
230
231 /// Print analysis result for a single top module.
232 LogicalResult printAnalysisResult(ResourceUsageAnalysis &analysis,
233 igraph::ModuleOpInterface top,
234 llvm::raw_ostream *os,
235 llvm::json::OStream *jsonOS);
236};
237} // namespace
238
239LogicalResult PrintResourceUsageAnalysisPass::getTopModules(
241 SmallVectorImpl<igraph::ModuleOpInterface> &tops) {
242 auto mod = getOperation();
243
244 if (topModuleName.getValue().empty()) {
245 // Automatically infer top modules from instance graph.
246 auto topLevelNodes = instanceGraph->getInferredTopLevelNodes();
247 if (failed(topLevelNodes))
248 return mod.emitError()
249 << "failed to infer top-level modules from instance graph";
250
251 // Collect all ModuleOpInterface instances from top-level nodes.
252 for (auto *node : *topLevelNodes) {
253 if (auto module = dyn_cast<igraph::ModuleOpInterface>(node->getModule()))
254 tops.push_back(module);
255 }
256
257 if (tops.empty())
258 return mod.emitError() << "no top-level modules found in instance graph";
259 } else {
260 // Use user-specified top module name.
261 auto *node = instanceGraph->lookup(
262 mlir::StringAttr::get(mod.getContext(), topModuleName.getValue()));
263 if (!node)
264 return mod.emitError()
265 << "top module '" << topModuleName.getValue() << "' not found";
266
267 auto top = dyn_cast_or_null<igraph::ModuleOpInterface>(node->getModule());
268 if (!top)
269 return mod.emitError() << "module '" << topModuleName.getValue()
270 << "' is not a ModuleOpInterface";
271 tops.push_back(top);
272 }
273
274 return success();
275}
276
277LogicalResult PrintResourceUsageAnalysisPass::printAnalysisResult(
278 ResourceUsageAnalysis &analysis, igraph::ModuleOpInterface top,
279 llvm::raw_ostream *os, llvm::json::OStream *jsonOS) {
280 auto *usage = analysis.getResourceUsage(top);
281 if (!usage)
282 return failure();
283
284 if (jsonOS) {
285 usage->emitJSON(jsonOS->rawValueBegin());
286 jsonOS->rawValueEnd();
287 } else if (os) {
288 auto &stream = *os;
289 stream << "Resource Usage Analysis for module: "
290 << usage->moduleName.getValue() << "\n";
291 stream << "========================================\n";
292 stream << "Total:\n";
293
294 // Sort resource counts by name for consistent output.
295 SmallVector<std::pair<StringRef, uint64_t>> sortedCounts;
296 for (const auto &count : usage->getTotal().getCounts())
297 sortedCounts.emplace_back(count.getKey(), count.second);
298 llvm::sort(sortedCounts,
299 [](const auto &a, const auto &b) { return a.first < b.first; });
300
301 // Find the maximum name length for aligned formatting.
302 size_t maxNameLen = 0;
303 for (const auto &[name, count] : sortedCounts)
304 maxNameLen = std::max(maxNameLen, name.size());
305
306 // Print with aligned columns.
307 for (const auto &[name, count] : sortedCounts)
308 stream << " " << name << ": "
309 << std::string(maxNameLen - name.size(), ' ') << count << "\n";
310 stream << "\n";
311 }
312
313 return success();
314}
315
316void PrintResourceUsageAnalysisPass::runOnOperation() {
317 auto &resourceUsage = getAnalysis<ResourceUsageAnalysis>();
318 auto *instanceGraph = resourceUsage.getInstanceGraph();
319
320 // Determine which modules to analyze.
321 SmallVector<igraph::ModuleOpInterface> tops;
322 if (failed(getTopModules(instanceGraph, tops)))
323 return signalPassFailure();
324
325 // Open output file.
326 std::string error;
327 auto file = mlir::openOutputFile(outputFile.getValue(), &error);
328 if (!file) {
329 llvm::errs() << error;
330 return signalPassFailure();
331 }
332
333 auto &os = file->os();
334 std::unique_ptr<llvm::json::OStream> jsonOS;
335 if (emitJSON.getValue()) {
336 jsonOS = std::make_unique<llvm::json::OStream>(os);
337 jsonOS->arrayBegin();
338 }
339
340 // Ensure JSON array is properly closed on exit.
341 auto closeJson = llvm::scope_exit([&]() {
342 if (jsonOS)
343 jsonOS->arrayEnd();
344 });
345
346 // Print resource usage for each top module.
347 for (auto top : tops) {
348 if (failed(printAnalysisResult(resourceUsage, top, jsonOS ? nullptr : &os,
349 jsonOS.get())))
350 return signalPassFailure();
351 }
352
353 file->keep();
354 markAllAnalysesPreserved();
355}
assert(baseType &&"element must be base type")
static std::optional< std::pair< std::string, uint64_t > > getResourceCount(Operation *op)
Get resource count for an operation if it's a tracked resource type.
static llvm::json::Object getModuleResourceUsageJSON(const ResourceUsageAnalysis::ResourceUsage &usage)
Convert ResourceUsage to JSON object.
HW-specific instance graph with a virtual entry node linking to all publicly visible modules.
This graph tracks modules and where they are instantiated.
FailureOr< llvm::ArrayRef< InstanceGraphNode * > > getInferredTopLevelNodes()
Get the nodes corresponding to the inferred top-level modules of a circuit.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
Analysis that computes resource usage for Synth dialect operations.
DenseMap< StringAttr, std::unique_ptr< ModuleResourceUsage > > designUsageCache
Cache of computed resource usage per module.
ModuleResourceUsage * getResourceUsage(igraph::ModuleOpInterface module)
Get resource usage for a module.
igraph::InstanceGraph * instanceGraph
Instance graph for module hierarchy traversal.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:55
void error(Twine message)
Definition LSPUtils.cpp:16
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition synth.py:1
Resource usage for a single module, including local and total counts.