CIRCT 24.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
23#include "circt/Support/LLVM.h"
24#include "mlir/IR/BuiltinOps.h"
25#include "mlir/Pass/AnalysisManager.h"
26#include "mlir/Support/FileUtilities.h"
27#include "llvm/ADT/ScopeExit.h"
28#include "llvm/Support/JSON.h"
29#include "llvm/Support/ToolOutputFile.h"
30
31namespace circt {
32namespace synth {
33#define GEN_PASS_DEF_PRINTRESOURCEUSAGEANALYSIS
34#include "circt/Dialect/Synth/Transforms/SynthPasses.h.inc"
35} // namespace synth
36} // namespace circt
37
38using namespace circt;
39using namespace synth;
40
41//===----------------------------------------------------------------------===//
42// ResourceUsageAnalysis Implementation
43//===----------------------------------------------------------------------===//
44
45/// Accumulate resource counts for an operation if it's a tracked resource type.
46/// Returns true if the operation was tracked, false otherwise.
47static bool accumulateResourceCounts(Operation *op,
48 llvm::StringMap<uint64_t> &counts) {
49 // Memory declarations do not produce integer values, and memory ports are
50 // already accounted for by their declaration.
51 if (auto memory = dyn_cast<seq::FirMemOp>(op)) {
52 auto type = memory.getMemory().getType();
53 counts[op->getName().getStringRef()] += type.getDepth() * type.getWidth();
54 return true;
55 }
56 if (isa<seq::FirMemReadOp, seq::FirMemWriteOp, seq::FirMemReadWriteOp>(op))
57 return true;
58 if (op->getNumResults() != 1 || !op->getResult(0).getType().isInteger())
59 return false;
60 return TypeSwitch<Operation *, bool>(op)
61 .Case<BooleanLogicOpInterface>([&](auto logicOp) {
62 if (auto areaCost = logicOp.getLogicAreaCost()) {
63 counts[op->getName().getStringRef()] += *areaCost;
64 return true;
65 }
66 return false;
67 })
68 // Variadic comb logic operations.
69 // Gate count = (num_inputs - 1) * bitwidth
70 .Case<comb::AndOp, comb::OrOp, comb::XorOp>([&](auto logicOp) {
71 counts[logicOp->getName().getStringRef()] +=
72 static_cast<uint64_t>(logicOp.getNumOperands() - 1) *
73 logicOp.getType().getIntOrFloatBitWidth();
74 return true;
75 })
76 // Truth tables (LUTs) - count both the total number of truth tables and
77 // the per-input breakdown.
78 .Case<comb::TruthTableOp>([&](auto op) {
79 uint64_t count = op.getType().getIntOrFloatBitWidth();
80 counts[op->getName().getStringRef()] += count;
81 std::string bucket = (Twine(op->getName().getStringRef()) + "_" +
82 Twine(op.getNumOperands()))
83 .str();
84 counts[bucket] += count;
85 return true;
86 })
87 // Sequential elements.
88 // Count = bitwidth
89 .Case<seq::CompRegOp, seq::FirRegOp>([&](auto op) {
90 uint64_t count = op.getType().getIntOrFloatBitWidth();
91 counts[op->getName().getStringRef()] += count;
92 return true;
93 })
94 .Default([](Operation *) { return false; });
95}
96
97ResourceUsageAnalysis::ResourceUsageAnalysis(Operation *moduleOp,
98 mlir::AnalysisManager &am)
99 : instanceGraph(&am.getAnalysis<igraph::InstanceGraph>()) {}
100
103 // Check cache first.
104 auto it = designUsageCache.find(moduleName);
105 if (it != designUsageCache.end())
106 return it->second.get();
107
108 // Lookup module in instance graph.
109 auto *node = instanceGraph->lookup(moduleName);
110 if (!node)
111 return nullptr;
112
113 return getResourceUsage(node->getModule());
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 (accumulateResourceCounts(op, counts))
130 return;
131 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
152 auto childModule = targetNode->getModule();
153
154 auto *instanceOp = child->getInstance().getOperation();
155 // Skip instances with no results or marked as "doNotPrint".
156 if (instanceOp->getNumResults() == 0 ||
157 instanceOp->hasAttrOfType<UnitAttr>("doNotPrint"))
158 continue;
159
160 // Recursively compute child usage and accumulate into total.
161 auto *childUsage = getResourceUsage(childModule);
162 moduleUsage->total += childUsage->total;
163 moduleUsage->instances.emplace_back(
164 childModule.getModuleNameAttr(),
165 child->getInstance().getInstanceNameAttr(), childUsage);
166 }
167
168 // Insert into cache and return.
169 auto [it, success] = designUsageCache.try_emplace(module.getModuleNameAttr(),
170 std::move(moduleUsage));
171 assert(success && "module already exists in cache");
172
173 return it->second.get();
174}
175
176//===----------------------------------------------------------------------===//
177// JSON Serialization
178//===----------------------------------------------------------------------===//
179
180/// Convert ResourceUsage to JSON object.
181static llvm::json::Object
182getModuleResourceUsageJSON(const ResourceUsageAnalysis::ResourceUsage &usage) {
183 llvm::json::Object obj;
184 for (const auto &count : usage.getCounts())
185 obj[count.getKey()] = count.second;
186 return obj;
187}
188
189/// Convert ModuleResourceUsage to JSON object with full hierarchy.
190/// This creates fully-elaborated information including all child instances.
191static llvm::json::Object getModuleResourceUsageJSON(
192 const ResourceUsageAnalysis::ModuleResourceUsage &usage) {
193 llvm::json::Object obj;
194 obj["moduleName"] = usage.moduleName.getValue();
195 obj["local"] = getModuleResourceUsageJSON(usage.getLocal());
196 obj["total"] = getModuleResourceUsageJSON(usage.getTotal());
197
198 // Serialize child instances recursively.
199 SmallVector<llvm::json::Value> instances;
200 for (const auto &instance : usage.instances) {
201 llvm::json::Object child;
202 child["instanceName"] = instance.instanceName.getValue();
203 child["moduleName"] = instance.moduleName.getValue();
204 child["usage"] = getModuleResourceUsageJSON(*instance.usage);
205 instances.push_back(std::move(child));
206 }
207 obj["instances"] = llvm::json::Array(instances);
208
209 return obj;
210}
211
213 raw_ostream &os) const {
214 os << getModuleResourceUsageJSON(*this);
215}
216
217namespace {
218struct PrintResourceUsageAnalysisPass
219 : public impl::PrintResourceUsageAnalysisBase<
220 PrintResourceUsageAnalysisPass> {
221 using PrintResourceUsageAnalysisBase::PrintResourceUsageAnalysisBase;
222
223 void runOnOperation() override;
224
225 /// Determine which modules to analyze based on options.
226 LogicalResult getTopModules(igraph::InstanceGraph *instanceGraph,
227 SmallVectorImpl<igraph::ModuleOpInterface> &tops);
228
229 /// Print analysis result for a single top module.
230 LogicalResult printAnalysisResult(ResourceUsageAnalysis &analysis,
231 igraph::ModuleOpInterface top,
232 llvm::raw_ostream *os,
233 llvm::json::OStream *jsonOS);
234};
235} // namespace
236
237LogicalResult PrintResourceUsageAnalysisPass::getTopModules(
239 SmallVectorImpl<igraph::ModuleOpInterface> &tops) {
240 auto mod = getOperation();
241
242 if (topModuleName.getValue().empty()) {
243 // Automatically infer top modules from instance graph.
244 auto topLevelNodes = instanceGraph->getInferredTopLevelNodes();
245 if (failed(topLevelNodes))
246 return mod.emitError()
247 << "failed to infer top-level modules from instance graph";
248
249 // Collect all ModuleOpInterface instances from top-level nodes.
250 for (auto *node : *topLevelNodes) {
251 if (auto module = node->getModule())
252 tops.push_back(module);
253 }
254
255 if (tops.empty())
256 return mod.emitError() << "no top-level modules found in instance graph";
257 } else {
258 // Use user-specified top module name.
259 auto *node = instanceGraph->lookup(
260 mlir::StringAttr::get(mod.getContext(), topModuleName.getValue()));
261 if (!node)
262 return mod.emitError()
263 << "top module '" << topModuleName.getValue() << "' not found";
264
265 tops.push_back(node->getModule());
266 }
267
268 return success();
269}
270
271LogicalResult PrintResourceUsageAnalysisPass::printAnalysisResult(
272 ResourceUsageAnalysis &analysis, igraph::ModuleOpInterface top,
273 llvm::raw_ostream *os, llvm::json::OStream *jsonOS) {
274 auto *usage = analysis.getResourceUsage(top);
275 if (!usage)
276 return failure();
277
278 if (jsonOS) {
279 usage->emitJSON(jsonOS->rawValueBegin());
280 jsonOS->rawValueEnd();
281 } else if (os) {
282 auto &stream = *os;
283 stream << "Resource Usage Analysis for module: "
284 << usage->moduleName.getValue() << "\n";
285 stream << "========================================\n";
286 stream << "Total:\n";
287
288 // Sort resource counts by name for consistent output.
289 SmallVector<std::pair<StringRef, uint64_t>> sortedCounts;
290 for (const auto &count : usage->getTotal().getCounts())
291 sortedCounts.emplace_back(count.getKey(), count.second);
292 llvm::sort(sortedCounts,
293 [](const auto &a, const auto &b) { return a.first < b.first; });
294
295 // Find the maximum name length for aligned formatting.
296 size_t maxNameLen = 0;
297 for (const auto &[name, count] : sortedCounts)
298 maxNameLen = std::max(maxNameLen, name.size());
299
300 // Print with aligned columns.
301 for (const auto &[name, count] : sortedCounts)
302 stream << " " << name << ": "
303 << std::string(maxNameLen - name.size(), ' ') << count << "\n";
304 stream << "\n";
305 }
306
307 return success();
308}
309
310void PrintResourceUsageAnalysisPass::runOnOperation() {
311 auto &resourceUsage = getAnalysis<ResourceUsageAnalysis>();
312 auto *instanceGraph = resourceUsage.getInstanceGraph();
313
314 // Determine which modules to analyze.
315 SmallVector<igraph::ModuleOpInterface> tops;
316 if (failed(getTopModules(instanceGraph, tops)))
317 return signalPassFailure();
318
319 // Open output file.
320 std::string error;
321 auto file = mlir::openOutputFile(outputFile.getValue(), &error);
322 if (!file) {
323 llvm::errs() << error;
324 return signalPassFailure();
325 }
326
327 auto &os = file->os();
328 std::unique_ptr<llvm::json::OStream> jsonOS;
329 if (emitJSON.getValue()) {
330 jsonOS = std::make_unique<llvm::json::OStream>(os);
331 jsonOS->arrayBegin();
332 }
333
334 // Ensure JSON array is properly closed on exit.
335 auto closeJson = llvm::scope_exit([&]() {
336 if (jsonOS)
337 jsonOS->arrayEnd();
338 });
339
340 // Print resource usage for each top module.
341 for (auto top : tops) {
342 if (failed(printAnalysisResult(resourceUsage, top, jsonOS ? nullptr : &os,
343 jsonOS.get())))
344 return signalPassFailure();
345 }
346
347 file->keep();
348 markAllAnalysesPreserved();
349}
assert(baseType &&"element must be base type")
static llvm::json::Object getModuleResourceUsageJSON(const ResourceUsageAnalysis::ResourceUsage &usage)
Convert ResourceUsage to JSON object.
static bool accumulateResourceCounts(Operation *op, llvm::StringMap< uint64_t > &counts)
Accumulate resource counts for an operation if it's a tracked resource type.
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:56
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.