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