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