Loading [MathJax]/extensions/tex2jax.js
CIRCT 22.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
AIGERRunner.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 file implements a pass that runs external logic solvers
10// (ABC/Yosys/mockturtle) on AIGER files by exporting the current module to
11// AIGER format, running the solver, and importing the results back.
12//
13//===----------------------------------------------------------------------===//
14
22#include "mlir/IR/Builders.h"
23#include "mlir/IR/PatternMatch.h"
24#include "mlir/Support/FileUtilities.h"
25#include "mlir/Support/LLVM.h"
26#include "mlir/Support/Timing.h"
27#include "mlir/Transforms/InliningUtils.h"
28#include "mlir/Transforms/RegionUtils.h"
29#include "llvm/ADT/MapVector.h"
30#include "llvm/ADT/StringRef.h"
31#include "llvm/Support/FileSystem.h"
32#include "llvm/Support/LogicalResult.h"
33#include "llvm/Support/Path.h"
34#include "llvm/Support/Program.h"
35#include "llvm/Support/SourceMgr.h"
36#include "llvm/Support/ToolOutputFile.h"
37#include "llvm/Support/raw_ostream.h"
38
39using namespace circt;
40using namespace circt::aig;
41
42#define DEBUG_TYPE "aig-runner"
43
44namespace circt {
45namespace aig {
46#define GEN_PASS_DEF_AIGERRUNNER
47#define GEN_PASS_DEF_ABCRUNNER
48#include "circt/Dialect/AIG/AIGPasses.h.inc"
49} // namespace aig
50} // namespace circt
51
52//===----------------------------------------------------------------------===//
53// Converter
54//===----------------------------------------------------------------------===//
55
56namespace {
57class Converter : public circt::aiger::ExportAIGERHandler {
58public:
59 void cleanup(hw::HWModuleOp module);
60 void integrateOptimizedModule(hw::HWModuleOp originalModule,
61 hw::HWModuleOp optimizedModule);
62
63private:
64 Value clock;
65 // Map from operand to AIGER outputs.
66 llvm::MapVector<std::pair<Operation *, size_t>, SmallVector<int>> operandMap;
67
68 // Map from result to AIGER inputs
69 llvm::MapVector<Value, SmallVector<int>> valueMap;
70
71 llvm::SetVector<Operation *> willBeErased;
72
73 bool operandCallback(OpOperand &op, size_t bitPos,
74 size_t outputIndex) override;
75 bool valueCallback(Value value, size_t bitPos, size_t inputIndex) override;
76 void notifyEmitted(Operation *op) override;
77 void notifyClock(Value value) override;
78};
79
80} // namespace
81
82/// Callback invoked during AIGER export for each operand bit.
83/// Maps each bit position of an operand to its corresponding AIGER output
84/// index.
85bool Converter::operandCallback(OpOperand &op, size_t bitPos,
86 size_t outputIndex) {
87 // Create a unique key for this operand (owner operation + operand number)
88 auto operandKey = std::make_pair(op.getOwner(), op.getOperandNumber());
89 assert(op.get().getType().isInteger() && "operand is not an integer");
90
91 // Find or create entry in the operand map
92 auto *mapIterator = operandMap.find(operandKey);
93 if (mapIterator == operandMap.end()) {
94 // Initialize with -1 for all bit positions (indicating unmapped)
95 auto bitWidth = hw::getBitWidth(op.get().getType());
96 mapIterator =
97 operandMap.insert({operandKey, SmallVector<int>(bitWidth, -1)}).first;
98 }
99
100 // Map this specific bit position to the AIGER output index
101 mapIterator->second[bitPos] = outputIndex;
102 return true;
103}
104
105/// Callback invoked during AIGER export for each value bit.
106/// Maps each bit position of a value to its corresponding AIGER input index.
107bool Converter::valueCallback(Value value, size_t bitPos, size_t inputIndex) {
108 assert(value.getType().isInteger() && "value is not an integer");
109
110 // Find or create entry in the value map
111 auto *mapIterator = valueMap.find(value);
112 if (mapIterator == valueMap.end()) {
113 // Initialize with -1 for all bit positions (indicating unmapped)
114 auto bitWidth = hw::getBitWidth(value.getType());
115 mapIterator =
116 valueMap.insert({value, SmallVector<int>(bitWidth, -1)}).first;
117 }
118
119 LLVM_DEBUG(llvm::dbgs() << "Mapping value: " << value << " bitPos: " << bitPos
120 << " inputIndex: " << inputIndex << "\n");
121
122 // Map this specific bit position to the AIGER input index
123 mapIterator->second[bitPos] = inputIndex;
124 return true;
125}
126
127/// Clean up operations marked for erasure during the conversion process.
128/// This method replaces marked operations with unrealized conversion casts
129/// and then runs dead code elimination to remove unused operations.
130void Converter::cleanup(hw::HWModuleOp module) {
131 SetVector<Operation *> operationsToErase;
132
133 // Replace all operations marked for erasure with unrealized conversion casts
134 // This allows DCE to determine if they're actually unused
135 mlir::IRRewriter rewriter(module);
136 while (!willBeErased.empty()) {
137 auto *operationToReplace = willBeErased.pop_back_val();
138 rewriter.setInsertionPoint(operationToReplace);
139
140 // Create an unrealized conversion cast as a placeholder
141 auto conversionCast =
142 rewriter.replaceOpWithNewOp<mlir::UnrealizedConversionCastOp>(
143 operationToReplace, operationToReplace->getResultTypes(),
144 ValueRange{});
145 (void)conversionCast;
146
147#ifdef DEBUG
148 // Mark the cast for verification that it gets eliminated
149 conversionCast->setAttr("aig.runner.must_be_dead", rewriter.getUnitAttr());
150#endif
151 }
152
153 // Run dead code elimination to remove unused operations
154 (void)mlir::runRegionDCE(rewriter, module->getRegions());
155
156#ifdef DEBUG
157 // Verify that all marked operations were actually eliminated
158 module.walk([&](mlir::UnrealizedConversionCastOp castOp) {
159 assert(!castOp->hasAttr("aig.runner.must_be_dead") &&
160 "Operation marked for deletion was not eliminated by DCE");
161 });
162#endif
163}
164
165/// Integrate the optimized module from AIGER import into the original module.
166/// This method handles the complex process of reconnecting multi-bit values
167/// that were decomposed during AIGER export/import.
168void Converter::integrateOptimizedModule(hw::HWModuleOp originalModule,
169 hw::HWModuleOp optimizedModule) {
170 mlir::IRRewriter builder(originalModule->getContext());
171 builder.setInsertionPointToStart(originalModule.getBodyBlock());
172
173 auto *optimizedTerminator = optimizedModule.getBodyBlock()->getTerminator();
174 auto *originalTerminator = originalModule.getBodyBlock()->getTerminator();
175
176 // Reconnect multi-bit operands by concatenating their individual bits
177 for (const auto &[operandKey, bitOutputIndices] : operandMap) {
178 auto [operationOwner, operandIndex] = operandKey;
179 SmallVector<Value> bitsToConcat;
180 builder.setInsertionPoint(operationOwner);
181 bitsToConcat.reserve(bitOutputIndices.size());
182
183 // Collect the optimized values for each bit (in reverse order for concat)
184 for (auto outputIndex : llvm::reverse(bitOutputIndices)) {
185 assert(outputIndex != -1 && "Unmapped output index found");
186 bitsToConcat.push_back(optimizedTerminator->getOperand(outputIndex));
187 }
188
189 // Create single value or concatenate multiple bits
190 if (bitsToConcat.size() == 1) {
191 operationOwner->setOperand(operandIndex, bitsToConcat.front());
192 } else {
193 auto concatenatedValue = builder.createOrFold<comb::ConcatOp>(
194 operationOwner->getLoc(), bitsToConcat);
195 operationOwner->setOperand(operandIndex, concatenatedValue);
196 }
197 }
198
199 // Prepare arguments for the optimized module by extracting bits from values
200 SmallVector<Value> moduleArguments(
201 optimizedModule.getBodyBlock()->getNumArguments());
202 for (const auto &[originalValue, bitInputIndices] : valueMap) {
203 builder.setInsertionPointAfterValue(originalValue);
204
205 // Extract each bit from the multi-bit value
206 for (auto [bitPosition, argumentIndex] : llvm::enumerate(bitInputIndices)) {
207 // TODO: Consider caching extract operations for efficiency
208 auto extractedBit = builder.createOrFold<comb::ExtractOp>(
209 originalValue.getLoc(), originalValue, bitPosition, 1);
210 moduleArguments[argumentIndex] = extractedBit;
211 }
212 }
213
214 // Handle clock signal if present (always the last argument)
215 auto arguments = optimizedModule.getBodyBlock()->getArguments();
216 if (arguments.size() > 0 && isa<seq::ClockType>(arguments.back().getType())) {
217 assert(clock && "Clock signal not found");
218 moduleArguments.back() = clock;
219 }
220
221 // Verify all arguments are properly mapped
222 assert(llvm::all_of(moduleArguments, [](Value v) { return v; }) &&
223 "Some module arguments were not properly mapped");
224
225 // Inline the optimized module into the original module
226 builder.inlineBlockBefore(optimizedModule.getBodyBlock(),
227 originalModule.getBodyBlock(),
228 originalTerminator->getIterator(), moduleArguments);
229 optimizedTerminator->erase();
230}
231
232/// Callback invoked when an operation is emitted during AIGER export.
233/// Marks the operation for potential cleanup since it may become unused
234/// after the optimization process replaces it with optimized equivalents.
235void Converter::notifyEmitted(Operation *op) { willBeErased.insert(op); }
236
237/// Callback to notify about the clock signal during AIGER export.
238/// Stores the clock value for later use when reconnecting the optimized module.
239void Converter::notifyClock(Value value) { clock = value; }
240
241//===----------------------------------------------------------------------===//
242// AIGERRunner
243//===----------------------------------------------------------------------===//
244
245namespace {
246class AIGERRunner {
247public:
248 AIGERRunner(llvm::StringRef solverPath, SmallVector<std::string> solverArgs,
249 bool continueOnFailure)
250 : solverPath(solverPath), solverArgs(std::move(solverArgs)),
251 continueOnFailure(continueOnFailure) {}
252
253 LogicalResult run(hw::HWModuleOp module);
254
255private:
256 // Helper methods
257 LogicalResult runSolver(hw::HWModuleOp module, StringRef inputPath,
258 StringRef outputPath);
259 LogicalResult exportToAIGER(Converter &converter, hw::HWModuleOp module,
260 StringRef outputPath);
261 LogicalResult importFromAIGER(Converter &converter, StringRef inputPath,
262 hw::HWModuleOp module);
263 llvm::StringRef solverPath;
264 SmallVector<std::string> solverArgs;
265 bool continueOnFailure;
266};
267
268} // namespace
269
270LogicalResult AIGERRunner::run(hw::HWModuleOp module) {
271
272 // Create temporary files for AIGER input/output
273 SmallString<128> tempDir;
274 if (auto error =
275 llvm::sys::fs::createUniqueDirectory("aiger-runner", tempDir))
276 return emitError(module.getLoc(), "failed to create temporary directory: ")
277 << error.message();
278
279 SmallString<128> inputPath(tempDir);
280 llvm::sys::path::append(inputPath, "input.aig");
281 SmallString<128> outputPath(tempDir);
282 llvm::sys::path::append(outputPath, "output.aig");
283
284 Converter converter;
285
286 auto reportWarningOrError = [&](const Twine &message) -> LogicalResult {
287 (continueOnFailure ? mlir::emitWarning(module.getLoc())
288 : mlir::emitError(module.getLoc()))
289 << message << " on module " << module.getModuleNameAttr();
290 return success(continueOnFailure);
291 };
292
293 // Export current module to AIGER format
294 if (failed(exportToAIGER(converter, cast<hw::HWModuleOp>(module), inputPath)))
295 return reportWarningOrError("failed to export module to AIGER format");
296
297 // Run the external solver
298 if (failed(runSolver(module, inputPath, outputPath)))
299 return reportWarningOrError("failed to run external solver");
300
301 // Import the results back
302 if (failed(
303 importFromAIGER(converter, outputPath, cast<hw::HWModuleOp>(module))))
304 return reportWarningOrError("failed to import results from AIGER format");
305
306 // If we get here, we succeeded. Clean up temporary files.
307 if (llvm::sys::fs::remove(inputPath))
308 return emitError(module.getLoc(), "failed to remove input file: ")
309 << inputPath.str();
310 if (llvm::sys::fs::remove(outputPath))
311 return emitError(module.getLoc(), "failed to remove output file: ")
312 << outputPath.str();
313 if (llvm::sys::fs::remove(tempDir))
314 return emitError(module.getLoc(), "failed to remove temporary directory: ")
315 << tempDir.str();
316
317 return llvm::success();
318}
319
320/// Execute the external solver (ABC, Yosys, etc.) on the AIGER file.
321/// Replaces placeholder tokens in solver arguments with actual file paths.
322LogicalResult AIGERRunner::runSolver(hw::HWModuleOp module, StringRef inputPath,
323 StringRef outputPath) {
324 // Prepare command line arguments for the solver
325 SmallVector<StringRef> commandArgs;
326 std::vector<std::string> processedSolverArgs;
327
328 // Helper function to replace all occurrences of a substring
329 auto replaceAll = [](std::string str, StringRef from, StringRef to) {
330 size_t pos = 0;
331 while ((pos = str.find(from.str(), pos)) != std::string::npos) {
332 str.replace(pos, from.size(), to.str());
333 pos += to.size();
334 }
335 return str;
336 };
337
338 // Process all solver arguments, replacing placeholders with actual paths
339 for (const auto &solverArg : solverArgs) {
340 std::string processedArg =
341 replaceAll(replaceAll(solverArg, "<inputFile>", inputPath),
342 "<outputFile>", outputPath);
343 processedSolverArgs.push_back(std::move(processedArg));
344 }
345
346 // Find the solver program in the system PATH
347 std::string executionError;
348 auto solverProgram = llvm::sys::findProgramByName(solverPath);
349 if (auto e = solverProgram.getError())
350 return emitError(module.getLoc(), "failed to find solver program: ")
351 << solverPath.str() << ": " << e.message();
352
353 // Build complete command line with program name and arguments
354 commandArgs.push_back(*solverProgram);
355 for (auto &processedArg : processedSolverArgs)
356 commandArgs.push_back(processedArg);
357
358 // Execute the solver
359 int executionResult = llvm::sys::ExecuteAndWait(
360 solverProgram.get(), commandArgs,
361 /*Env=*/std::nullopt, /*Redirects=*/{},
362 /*SecondsToWait=*/0, /*MemoryLimit=*/0, &executionError);
363
364 // Check for execution failure
365 if (executionResult != 0)
366 return emitError(module.getLoc(), "solver execution failed for module ")
367 << module.getModuleNameAttr() << " with error: " << executionError;
368
369 return success();
370}
371
372/// Export the hardware module to AIGER format for external solver processing.
373LogicalResult AIGERRunner::exportToAIGER(Converter &converter,
374 hw::HWModuleOp module,
375 StringRef outputPath) {
376 // Open output file for writing AIGER data
377 auto outputFile = mlir::openOutputFile(outputPath);
378 if (!outputFile)
379 return emitError(module.getLoc(), "failed to open AIGER output file: ")
380 << outputPath.str();
381
382 LLVM_DEBUG(llvm::dbgs() << "Exporting module " << module.getModuleNameAttr()
383 << " to AIGER format\n");
384
386 exportOptions.binaryFormat = true; // Use binary format for efficiency
387 exportOptions.includeSymbolTable = true; // Include names for debugging
388
389 // Perform the actual AIGER export
390 auto exportResult = circt::aiger::exportAIGER(module, outputFile->os(),
391 &exportOptions, &converter);
392
393 // Ensure the file is properly written to disk
394 outputFile->keep();
395 return exportResult;
396}
397
398/// Import the optimized AIGER file back into MLIR format.
399/// Creates a new module from the AIGER data and replaces the original module.
400LogicalResult AIGERRunner::importFromAIGER(Converter &converter,
401 StringRef inputPath,
402 hw::HWModuleOp originalModule) {
403 // Set up source manager for file parsing
404 llvm::SourceMgr sourceMgr;
405 auto inputFile = mlir::openInputFile(inputPath);
406 if (!inputFile)
407 return emitError(originalModule.getLoc(),
408 "failed to open AIGER input file: ")
409 << inputPath.str();
410
411 // Add the file buffer to the source manager
412 sourceMgr.AddNewSourceBuffer(std::move(inputFile), llvm::SMLoc());
413
414 // Create a temporary module to hold the imported AIGER content
415 mlir::TimingScope timingScope;
416 mlir::Block temporaryBlock;
417 mlir::OpBuilder builder(originalModule->getContext());
418 builder.setInsertionPointToStart(&temporaryBlock);
419 auto temporaryModule =
420 mlir::ModuleOp::create(builder, builder.getUnknownLoc());
421
422 // Import the AIGER file into the temporary module
423 if (failed(circt::aiger::importAIGER(sourceMgr, originalModule->getContext(),
424 timingScope, temporaryModule)))
425 return emitError(originalModule.getLoc(),
426 "failed to import optimized AIGER file");
427
428 // Extract the hardware module from the imported content
429 auto optimizedModule =
430 cast<hw::HWModuleOp>(temporaryModule.getBody()->front());
431
432 // Integrate the optimized module into the original module structure
433 converter.integrateOptimizedModule(originalModule, optimizedModule);
434
435 // Clean up any operations that became unused during the replacement
436 converter.cleanup(originalModule);
437
438 return llvm::success();
439}
440
441//===----------------------------------------------------------------------===//
442// AIGERRunnerPass
443//===----------------------------------------------------------------------===//
444
445namespace {
446class AIGERRunnerPass : public impl::AIGERRunnerBase<AIGERRunnerPass> {
447public:
448 using AIGERRunnerBase<AIGERRunnerPass>::AIGERRunnerBase;
449 void runOnOperation() override;
450};
451} // namespace
452
453void AIGERRunnerPass::runOnOperation() {
454 auto module = getOperation();
455
456 // Convert pass options to the format expected by AIGERRunner
457 SmallVector<std::string> solverArgsRef;
458 for (const auto &arg : solverArgs)
459 solverArgsRef.push_back(arg);
460
461 AIGERRunner runner(solverPath, std::move(solverArgsRef), continueOnFailure);
462 if (failed(runner.run(module)))
463 signalPassFailure();
464}
465
466//===----------------------------------------------------------------------===//
467// ABCRunnerPass
468//===----------------------------------------------------------------------===//
469
470namespace {
471class ABCRunnerPass : public impl::ABCRunnerBase<ABCRunnerPass> {
472public:
473 using ABCRunnerBase<ABCRunnerPass>::ABCRunnerBase;
474 void runOnOperation() override;
475};
476} // namespace
477
478/// Run ABC optimization commands on the current hardware module.
479/// Builds a command sequence to read AIGER, run optimizations, and write back.
480void ABCRunnerPass::runOnOperation() {
481 auto module = getOperation();
482
483 SmallVector<std::string> abcArguments;
484
485 // Helper to add ABC commands with quiet flag
486 auto addABCCommand = [&](const std::string &command) {
487 abcArguments.push_back("-q"); // Run ABC in quiet mode
488 abcArguments.push_back(command);
489 };
490
491 // Start with reading the input AIGER file
492 addABCCommand("read <inputFile>");
493
494 // Add all user-specified optimization commands
495 for (const auto &optimizationCmd : abcCommands)
496 addABCCommand(optimizationCmd);
497
498 // Finish by writing the optimized result
499 addABCCommand("write <outputFile>");
500
501 // Execute the ABC optimization sequence
502 AIGERRunner abcRunner(abcPath, std::move(abcArguments), continueOnFailure);
503 if (failed(abcRunner.run(module)))
504 signalPassFailure();
505}
assert(baseType &&"element must be base type")
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:216
Definition aig.py:1
mlir::LogicalResult importAIGER(llvm::SourceMgr &sourceMgr, mlir::MLIRContext *context, mlir::TimingScope &ts, mlir::ModuleOp module, const ImportAIGEROptions *options=nullptr)
Parse an AIGER file and populate the given MLIR module with corresponding AIG dialect operations.
mlir::LogicalResult exportAIGER(hw::HWModuleOp module, llvm::raw_ostream &os, const ExportAIGEROptions *options=nullptr, ExportAIGERHandler *handler=nullptr)
Export an MLIR module containing AIG dialect operations to AIGER format.
void error(Twine message)
Definition LSPUtils.cpp:16
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
Definition codegen.py:121
Handler for AIGER export.
Definition ExportAIGER.h:35
virtual bool valueCallback(Value result, size_t bitPos, size_t inputIndex)
Definition ExportAIGER.h:51
virtual void notifyClock(Value value)
Definition ExportAIGER.h:59
virtual bool operandCallback(mlir::OpOperand &operand, size_t bitPos, size_t outputIndex)
Definition ExportAIGER.h:43
virtual void notifyEmitted(Operation *op)
Definition ExportAIGER.h:56
Options for AIGER export.
Definition ExportAIGER.h:63
bool includeSymbolTable
Whether to include symbol table in the output.
Definition ExportAIGER.h:70
bool binaryFormat
Whether to export in binary format (aig) or ASCII format (aag).
Definition ExportAIGER.h:66