CIRCT 24.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)
130 llvm::MapVector<Value, SmallVector<Value>> loweredValues;
131
132 /// Cache for computed known bits information
133 llvm::MapVector<Value, llvm::KnownBits> knownBits;
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 SmallVector< T > concat(const SmallVectorImpl< T > &a, const SmallVectorImpl< T > &b)
Returns a new vector containing the concatenation of vectors a and b.
Definition CalyxOps.cpp:555
static bool shouldLowerOperation(Operation *op)
Check if an operation should be lowered to bit-level operations.
return success()
create(data_type, value)
Definition hw.py:433
KnownBits computeKnownBits(Value value, unsigned maxDepth=5)
Compute "known bits" information about the specified value - the set of bits that are guaranteed to a...
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:543
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int run(Type[Generator] generator=CppGenerator, List[str] cmdline_args=sys.argv)
Definition synth.py:1