CIRCT 23.0.0git
Loading...
Searching...
No Matches
LowerWordToBits.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 pass implements bit-blasting for logic synthesis operations.
10// It converts multi-bit operations (AIG, combinatorial) into equivalent
11// single-bit operations, enabling more efficient synthesis and optimization.
12//
13//===----------------------------------------------------------------------===//
14
19#include "circt/Support/LLVM.h"
20#include "mlir/IR/Operation.h"
21#include "mlir/IR/Value.h"
22#include "mlir/Pass/Pass.h"
23#include "llvm/ADT/APInt.h"
24#include "llvm/ADT/DenseMap.h"
25#include "llvm/ADT/SmallVector.h"
26#include "llvm/ADT/TypeSwitch.h"
27#include "llvm/Support/KnownBits.h"
28#include "llvm/Support/LogicalResult.h"
29#include <array>
30
31#define DEBUG_TYPE "synth-lower-word-to-bits"
32
33namespace circt {
34namespace synth {
35#define GEN_PASS_DEF_LOWERWORDTOBITS
36#include "circt/Dialect/Synth/Transforms/SynthPasses.h.inc"
37} // namespace synth
38} // namespace circt
39
40using namespace circt;
41using namespace synth;
42
43//===----------------------------------------------------------------------===//
44// Utilities
45//===----------------------------------------------------------------------===//
46
47/// Check if an operation should be lowered to bit-level operations.
48static bool shouldLowerOperation(Operation *op) {
49 // Lower all Synth boolean ops through the interface instead of maintaining a
50 // per-op allowlist here.
51 return isa<ChoiceOp, BooleanLogicOpInterface, comb::AndOp, comb::OrOp,
53}
54
55namespace {
56
57//===----------------------------------------------------------------------===//
58// BitBlaster - Bit-level lowering implementation
59//===----------------------------------------------------------------------===//
60
61/// The BitBlaster class implements the core bit-blasting algorithm.
62/// It manages the lowering of multi-bit operations to single-bit operations
63/// while maintaining correctness and optimizing for constant propagation.
64class BitBlaster {
65public:
66 explicit BitBlaster(Operation *topOp) : topOp(topOp) {}
67
68 /// Run the bit-blasting algorithm on the module.
69 LogicalResult run();
70
71 //===--------------------------------------------------------------------===//
72 // Statistics
73 //===--------------------------------------------------------------------===//
74
75 /// Number of bits that were lowered from multi-bit to single-bit operations
76 size_t numLoweredBits = 0;
77
78 /// Number of constant bits that were identified and optimized
79 size_t numLoweredConstants = 0;
80
81 /// Number of operations that were lowered
82 size_t numLoweredOps = 0;
83
84private:
85 //===--------------------------------------------------------------------===//
86 // Core Lowering Methods
87 //===--------------------------------------------------------------------===//
88
89 /// Lower a multi-bit value to individual bits.
90 /// This is the main entry point for bit-blasting a value.
91 ArrayRef<Value> lowerValueToBits(Value value);
92 ArrayRef<Value> lowerBooleanLogicOperation(BooleanLogicOpInterface op);
93 template <typename OpTy>
94 ArrayRef<Value> lowerCombLogicOperations(OpTy op);
95 ArrayRef<Value> lowerCombMux(comb::MuxOp op);
96 ArrayRef<Value>
97 lowerOp(Operation *op,
98 llvm::function_ref<Value(OpBuilder &builder, ValueRange)> createOp);
99
100 /// Extract a specific bit from a value.
101 /// Handles various IR constructs that can represent bit extraction.
102 Value extractBit(Value value, size_t index);
103
104 /// Compute and cache known bits for a value.
105 /// Uses operation-specific logic to determine which bits are constants.
106 const llvm::KnownBits &computeKnownBits(Value value);
107
108 /// Get or create a boolean constant (0 or 1).
109 /// Constants are cached by block to avoid duplication.
110 Value getBoolConstant(bool value, Block *block);
111
112 //===--------------------------------------------------------------------===//
113 // Helper Methods
114 //===--------------------------------------------------------------------===//
115
116 /// Insert lowered bits into the cache.
117 ArrayRef<Value> insertBits(Value value, SmallVector<Value> bits) {
118 auto it = loweredValues.insert({value, std::move(bits)});
119 assert(it.second && "value already inserted");
120 return it.first->second;
121 }
122
123 /// Insert computed known bits into the cache.
124 const llvm::KnownBits &insertKnownBits(Value value, llvm::KnownBits bits) {
125 auto it = knownBits.insert({value, std::move(bits)});
126 return it.first->second;
127 }
128
129 /// Cache for lowered values (multi-bit -> vector of single-bit values)
131
132 /// Cache for computed known bits information
134
135 /// Cached boolean constants by block (false at index 0, true at index 1)
136 llvm::DenseMap<Block *, std::array<Value, 2>> constantsByBlock;
137
138 /// Reference to the top-level operation being processed
139 Operation *topOp;
140};
141
142} // namespace
143
144//===----------------------------------------------------------------------===//
145// BitBlaster Implementation
146//===----------------------------------------------------------------------===//
147
148const llvm::KnownBits &BitBlaster::computeKnownBits(Value value) {
149 // Check cache first
150 auto *it = knownBits.find(value);
151 if (it != knownBits.end())
152 return it->second;
153
154 auto width = hw::getBitWidth(value.getType());
155 auto *op = value.getDefiningOp();
156
157 // For block arguments, return unknown bits
158 if (!op)
159 return insertKnownBits(value, llvm::KnownBits(width));
160
161 llvm::KnownBits result(width);
162 if (auto logicOp = dyn_cast<BooleanLogicOpInterface>(op))
163 result =
164 logicOp.computeKnownBits([&](unsigned i) -> const llvm::KnownBits & {
165 return computeKnownBits(logicOp.getInput(i));
166 });
167 else if (auto choice = dyn_cast<ChoiceOp>(op)) {
168 result = computeKnownBits(choice.getInputs().front());
169 for (auto input : choice.getInputs().drop_front()) {
170 auto known = computeKnownBits(input);
171 result.One |= known.One;
172 result.Zero |= known.Zero;
173 }
174 } else {
175 // For other operations, use the standard known bits computation
176 // TODO: This is not optimal as it has a depth limit and does not check
177 // cached results.
178 result = comb::computeKnownBits(value);
179 }
180
181 return insertKnownBits(value, std::move(result));
182}
183
184Value BitBlaster::extractBit(Value value, size_t index) {
185 if (hw::getBitWidth(value.getType()) <= 1)
186 return value;
187
188 auto *op = value.getDefiningOp();
189
190 // If the value is a block argument, extract the bit.
191 if (!op)
192 return lowerValueToBits(value)[index];
193
194 return TypeSwitch<Operation *, Value>(op)
195 .Case<comb::ConcatOp>([&](comb::ConcatOp op) {
196 for (auto operand : llvm::reverse(op.getOperands())) {
197 auto width = hw::getBitWidth(operand.getType());
198 assert(width >= 0 && "operand has zero width");
199 if (index < static_cast<size_t>(width))
200 return extractBit(operand, index);
201 index -= static_cast<size_t>(width);
202 }
203 llvm_unreachable("index out of bounds");
204 })
205 .Case<comb::ExtractOp>([&](comb::ExtractOp ext) {
206 return extractBit(ext.getInput(),
207 static_cast<size_t>(ext.getLowBit()) + index);
208 })
209 .Case<comb::ReplicateOp>([&](comb::ReplicateOp op) {
210 return extractBit(op.getInput(),
211 index % static_cast<size_t>(hw::getBitWidth(
212 op.getOperand().getType())));
213 })
214 .Case<hw::ConstantOp>([&](hw::ConstantOp op) {
215 auto value = op.getValue();
216 return getBoolConstant(value[index], op->getBlock());
217 })
218 .Default([&](auto op) { return lowerValueToBits(value)[index]; });
219}
220
221ArrayRef<Value> BitBlaster::lowerValueToBits(Value value) {
222 auto *it = loweredValues.find(value);
223 if (it != loweredValues.end())
224 return it->second;
225
226 auto width = hw::getBitWidth(value.getType());
227 if (width <= 1)
228 return insertBits(value, {value});
229
230 auto *op = value.getDefiningOp();
231 if (!op) {
232 SmallVector<Value> results;
233 OpBuilder builder(value.getContext());
234 builder.setInsertionPointAfterValue(value);
235 comb::extractBits(builder, value, results);
236 return insertBits(value, std::move(results));
237 }
238
239 return TypeSwitch<Operation *, ArrayRef<Value>>(op)
240 .Case<ChoiceOp>([&](ChoiceOp op) {
241 auto createOp = [&](OpBuilder &builder, ValueRange operands) {
242 return builder.createOrFold<ChoiceOp>(
243 op.getLoc(), operands[0].getType(), operands);
244 };
245 return lowerOp(op, createOp);
246 })
247 .Case<BooleanLogicOpInterface>(
248 [&](auto op) { return lowerBooleanLogicOperation(op); })
249 .Case<comb::AndOp, comb::OrOp, comb::XorOp>(
250 [&](auto op) { return lowerCombLogicOperations(op); })
251 .Case<comb::MuxOp>([&](comb::MuxOp op) { return lowerCombMux(op); })
252 .Default([&](auto op) {
253 OpBuilder builder(value.getContext());
254 builder.setInsertionPoint(op);
255 SmallVector<Value> results;
256 comb::extractBits(builder, value, results);
257
258 return insertBits(value, std::move(results));
259 });
260}
261
262LogicalResult BitBlaster::run() {
263 // Topologically sort operations in graph regions so that walk visits them in
264 // the topological order.
266 topOp, [](Value value, Operation *op) -> bool {
267 // Otherthan target ops, all other ops are always ready.
268 return !(shouldLowerOperation(op) ||
269 isa<comb::ExtractOp, comb::ReplicateOp, comb::ConcatOp,
270 comb::ReplicateOp>(op));
271 }))) {
272 // If we failed to topologically sort operations we cannot proceed.
273 return mlir::emitError(topOp->getLoc(), "there is a combinational cycle");
274 }
275
276 // Lower target operations
277 topOp->walk([&](Operation *op) {
278 // If the block is in a graph region, topologically sort it first.
279 if (shouldLowerOperation(op))
280 (void)lowerValueToBits(op->getResult(0));
281 });
282
283 // Replace operations with concatenated results if needed
284 for (auto &[value, results] :
285 llvm::make_early_inc_range(llvm::reverse(loweredValues))) {
286 if (hw::getBitWidth(value.getType()) <= 1)
287 continue;
288
289 auto *op = value.getDefiningOp();
290 if (!op)
291 continue;
292
293 if (value.use_empty()) {
294 op->erase();
295 continue;
296 }
297
298 // If a target operation still has an use (e.g. connected to output or
299 // instance), replace the value with the concatenated result.
300 if (shouldLowerOperation(op)) {
301 OpBuilder builder(op);
302 std::reverse(results.begin(), results.end());
303 auto concat = comb::ConcatOp::create(builder, value.getLoc(), results);
304 value.replaceAllUsesWith(concat);
305 op->erase();
306 }
307 }
308
309 return success();
310}
311
312Value BitBlaster::getBoolConstant(bool value, Block *block) {
313 auto &blockConstants = constantsByBlock[block];
314 if (!blockConstants[value]) {
315 auto builder = OpBuilder::atBlockBegin(block);
316 blockConstants[value] = hw::ConstantOp::create(
317 builder, builder.getUnknownLoc(), builder.getI1Type(), value);
318 }
319 return blockConstants[value];
320}
321
322ArrayRef<Value>
323BitBlaster::lowerBooleanLogicOperation(BooleanLogicOpInterface op) {
324 auto createOp = [&](OpBuilder &builder, ValueRange operands) {
325 return op.cloneWithSameInversion(builder, operands);
326 };
327 return lowerOp(op.getOperation(), createOp);
328}
329
330template <typename OpTy>
331ArrayRef<Value> BitBlaster::lowerCombLogicOperations(OpTy op) {
332 auto createOp = [&](OpBuilder &builder, ValueRange operands) {
333 return builder.createOrFold<OpTy>(op.getLoc(), operands,
334 op.getTwoStateAttr());
335 };
336 return lowerOp(op, createOp);
337}
338
339ArrayRef<Value> BitBlaster::lowerCombMux(comb::MuxOp op) {
340 auto createOp = [&](OpBuilder &builder, ValueRange operands) {
341 return builder.createOrFold<comb::MuxOp>(op.getLoc(), operands[0],
342 operands[1], operands[2],
343 op.getTwoStateAttr());
344 };
345 return lowerOp(op, createOp);
346}
347
348ArrayRef<Value> BitBlaster::lowerOp(
349 Operation *op,
350 llvm::function_ref<Value(OpBuilder &builder, ValueRange)> createOp) {
351 auto value = op->getResult(0);
352 OpBuilder builder(op);
353 auto width = hw::getBitWidth(value.getType());
354 assert(width > 1 && "expected multi-bit operation");
355
356 auto known = computeKnownBits(value);
357 APInt knownMask = known.Zero | known.One;
358
359 // Update statistics
360 numLoweredConstants += knownMask.popcount();
361 numLoweredBits += width;
362 ++numLoweredOps;
363
364 SmallVector<Value> results;
365 results.reserve(width);
366
367 for (int64_t i = 0; i < width; ++i) {
368 SmallVector<Value> operands;
369 operands.reserve(op->getNumOperands());
370 if (knownMask[i]) {
371 // Use known constant value
372 results.push_back(getBoolConstant(known.One[i], op->getBlock()));
373 continue;
374 }
375
376 // Extract the i-th bit from each operand
377 for (auto operand : op->getOperands())
378 operands.push_back(extractBit(operand, i));
379
380 // Create the single-bit operation
381 auto result = createOp(builder, operands);
382 results.push_back(result);
383
384 // Add name hint if present
385 if (auto name = op->getAttrOfType<StringAttr>("sv.namehint")) {
386 auto newName = StringAttr::get(
387 op->getContext(), name.getValue() + "[" + std::to_string(i) + "]");
388 if (auto *loweredOp = result.getDefiningOp())
389 loweredOp->setAttr("sv.namehint", newName);
390 }
391 }
392
393 assert(results.size() == static_cast<size_t>(width));
394 return insertBits(value, std::move(results));
395}
396
397//===----------------------------------------------------------------------===//
398// Pass Implementation
399//===----------------------------------------------------------------------===//
400
401namespace {
402struct LowerWordToBitsPass
403 : public impl::LowerWordToBitsBase<LowerWordToBitsPass> {
404 void runOnOperation() override;
405};
406} // namespace
407
408void LowerWordToBitsPass::runOnOperation() {
409 BitBlaster driver(getOperation());
410 if (failed(driver.run()))
411 return signalPassFailure();
412
413 // Update statistics
414 numLoweredBits += driver.numLoweredBits;
415 numLoweredConstants += driver.numLoweredConstants;
416 numLoweredOps += driver.numLoweredOps;
417}
assert(baseType &&"element must be base type")
static KnownBits computeKnownBits(Value v, unsigned depth)
Given an integer SSA value, check to see if we know anything about the result of the computation.
static bool shouldLowerOperation(Operation *op)
Check if an operation should be lowered to bit-level operations.
create(data_type, value)
Definition hw.py:433
LogicalResult topologicallySortGraphRegionBlocks(mlir::Operation *op, llvm::function_ref< bool(mlir::Value, mlir::Operation *)> isOperandReady)
This function performs a topological sort on the operations within each block of graph regions in the...
Definition SynthOps.cpp:577
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