CIRCT 23.0.0git
Loading...
Searching...
No Matches
PrettifyVerilog.cpp
Go to the documentation of this file.
1//===- PrettifyVerilog.cpp - Transformations to improve Verilog quality ---===//
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 contains elective transformations that improve the quality of
10// SystemVerilog generated by the ExportVerilog library. This pass is not
11// compulsory: things that are required for ExportVerilog to be correct should
12// be included as part of the ExportVerilog pass itself to make sure it is self
13// contained. This allows the ExportVerilog pass to be simpler.
14//
15// PrettifyVerilog is run prior to Verilog emission but must be aware of the
16// options in LoweringOptions. It shouldn't introduce invalid constructs that
17// aren't present in the IR already: this isn't a general "raising" pass.
18//
19//===----------------------------------------------------------------------===//
20
26#include "mlir/IR/ImplicitLocOpBuilder.h"
27#include "mlir/IR/Matchers.h"
28#include "mlir/Pass/Pass.h"
29#include "llvm/ADT/SetVector.h"
30#include "llvm/ADT/TypeSwitch.h"
31
32namespace circt {
33namespace sv {
34#define GEN_PASS_DEF_PRETTIFYVERILOG
35#include "circt/Dialect/SV/SVPasses.h.inc"
36} // namespace sv
37} // namespace circt
38
39using namespace circt;
40//===----------------------------------------------------------------------===//
41// PrettifyVerilogPass
42//===----------------------------------------------------------------------===//
43
44namespace {
45struct PrettifyVerilogPass
46 : public circt::sv::impl::PrettifyVerilogBase<PrettifyVerilogPass> {
47 void runOnOperation() override;
48
49private:
50 void processPostOrder(Block &block);
51 bool prettifyUnaryOperator(Operation *op);
52 void sinkOrCloneOpToUses(Operation *op);
53 void sinkExpression(Operation *op);
54 void useNamedOperands(Operation *op, DenseMap<Value, Operation *> &pipeMap);
55
56 bool splitStructAssignment(OpBuilder &builder, hw::StructType ty, Value dst,
57 Value src);
58 bool splitArrayAssignment(OpBuilder &builder, hw::ArrayType ty, Value dst,
59 Value src);
60 bool splitAssignment(OpBuilder &builder, Value dst, Value src);
61
62 bool anythingChanged;
63 LoweringOptions options;
64
65 llvm::SetVector<Operation *> toDelete;
66};
67} // end anonymous namespace
68
69/// Return true if this is a unary operator that is cheap enough to sink or
70/// duplicate into its users as part of prettification.
71static bool isPrettifiableUnaryOperator(Operation *op) {
72 // NOTE: Don't allow reduction operators (^, &, |) here since they are not
73 // cheap.
74 if (auto xorOp = dyn_cast<comb::XorOp>(op))
75 return xorOp.isBinaryNot();
76
77 return false;
78}
79
80/// Helper to convert a value to a constant integer if it is one.
81static std::optional<APInt> getInt(Value value) {
82 if (auto cst = dyn_cast_or_null<hw::ConstantOp>(value.getDefiningOp()))
83 return cst.getValue();
84 return std::nullopt;
85}
86
87// Checks whether the destination and the source of an assignment are the same.
88// However, the destination is a value with an inout type in SV form, while the
89// source is built using ops from HWAggregates.
90static bool isSelfWrite(Value dst, Value src) {
91 if (dst == src)
92 return true;
93
94 auto *srcOp = src.getDefiningOp();
95 auto *dstOp = dst.getDefiningOp();
96 if (!srcOp || !dstOp)
97 return false;
98
99 return TypeSwitch<Operation *, bool>(srcOp)
100 .Case<hw::StructExtractOp>([&](auto extract) {
101 auto toField = dyn_cast<sv::StructFieldInOutOp>(dstOp);
102 if (!toField)
103 return false;
104 if (toField.getFieldAttr() != extract.getFieldNameAttr())
105 return false;
106 return isSelfWrite(toField.getInput(), extract.getInput());
107 })
108 .Case<hw::ArrayGetOp>([&](auto get) {
109 auto toGet = dyn_cast<sv::ArrayIndexInOutOp>(dstOp);
110 if (!toGet || toGet.getIndex().getType() != get.getIndex().getType())
111 return false;
112 auto toIdx = getInt(toGet.getIndex());
113 auto fromIdx = getInt(get.getIndex());
114 if (!toIdx || !fromIdx || toIdx != fromIdx)
115 return false;
116 return isSelfWrite(toGet.getInput(), get.getInput());
117 })
118 .Case<sv::ReadInOutOp>([&](auto read) { return dst == read.getInput(); })
119 .Default([&](auto srcOp) { return false; });
120}
121
122/// Split an assignment to a structure to writes of individual fields.
123bool PrettifyVerilogPass::splitStructAssignment(OpBuilder &builder,
124 hw::StructType ty, Value dst,
125 Value src) {
126 // Follow a chain of injects to find all fields overwritten and separate
127 // them into a series of field updates instead of a whole-structure write.
128 DenseMap<StringAttr, std::pair<Location, Value>> fields;
129 while (auto inj = dyn_cast_or_null<hw::StructInjectOp>(src.getDefiningOp())) {
130 // Inner injects are overwritten by outer injects.
131 // Insert does not overwrite the store to be lowered.
132 auto field = std::make_pair(inj.getLoc(), inj.getNewValue());
133 fields.try_emplace(inj.getFieldNameAttr(), field);
134 src = inj.getInput();
135 }
136
137 // The origin must be either the object itself (partial update)
138 // or the whole object must be overwritten.
139 if (!isSelfWrite(dst, src) && ty.getElements().size() != fields.size())
140 return false;
141
142 // Emit the field assignments in the order of their definition.
143 for (auto &field : ty.getElements()) {
144 const auto &name = field.name;
145
146 auto it = fields.find(name);
147 if (it == fields.end())
148 continue;
149
150 auto &[loc, value] = it->second;
151 auto ref = sv::StructFieldInOutOp::create(builder, loc, dst, name);
152 if (!splitAssignment(builder, ref, value))
153 sv::PAssignOp::create(builder, loc, ref, value);
154 }
155 return true;
156}
157
158/// Split an assignment to an array element to writes of individual indices.
159bool PrettifyVerilogPass::splitArrayAssignment(OpBuilder &builder,
160 hw::ArrayType ty, Value dst,
161 Value src) {
162 // Follow a chain of concat + slice operations that alter a single element.
163 if (auto op = dyn_cast_or_null<hw::ArrayCreateOp>(src.getDefiningOp())) {
164 // TODO: consider breaking up array assignments into assignments
165 // to individual fields.
166 auto ty = hw::type_cast<hw::ArrayType>(op.getType());
167 if (ty.getNumElements() != 1)
168 return false;
169 APInt zero(std::max(1u, llvm::Log2_64_Ceil(ty.getNumElements())), 0);
170
171 Value value = op.getInputs()[0];
172 auto loc = op.getLoc();
173 auto index = hw::ConstantOp::create(builder, loc, zero);
174
175 auto field = sv::ArrayIndexInOutOp::create(builder, loc, dst, index);
176 if (!splitAssignment(builder, field, value))
177 sv::PAssignOp::create(builder, loc, field, value);
178 return true;
179 }
180
181 // TODO: generalise to ranges and arbitrary concatenations.
182 SmallVector<std::tuple<APInt, Location, Value>> fields;
183 while (auto concat =
184 dyn_cast_or_null<hw::ArrayConcatOp>(src.getDefiningOp())) {
185 auto loc = concat.getLoc();
186
187 // Look for a slice and an element:
188 // concat(slice(a, 0, size - 1), elem)
189 // concat(elem, slice(a, 1, size - 1))
190 if (concat.getNumOperands() == 2) {
191 auto c = concat.getInputs();
192
193 auto lhs = dyn_cast_or_null<hw::ArraySliceOp>(c[1].getDefiningOp());
194 auto rhs = dyn_cast_or_null<hw::ArraySliceOp>(c[0].getDefiningOp());
195 auto midL = dyn_cast_or_null<hw::ArrayCreateOp>(c[1].getDefiningOp());
196 auto midR = dyn_cast_or_null<hw::ArrayCreateOp>(c[0].getDefiningOp());
197
198 auto size =
199 hw::type_cast<hw::ArrayType>(concat.getType()).getNumElements();
200 if (lhs && midR) {
201 auto baseIdx = getInt(lhs.getLowIndex());
202 if (!baseIdx || *baseIdx != 0 || midR.getInputs().size() != 1)
203 break;
204 fields.emplace_back(APInt(baseIdx->getBitWidth(), size - 1), loc,
205 midR.getInputs()[0]);
206 src = lhs.getInput();
207 continue;
208 }
209 if (rhs && midL) {
210 auto baseIdx = getInt(rhs.getLowIndex());
211 if (!baseIdx || *baseIdx != 1 || midL.getInputs().size() != 1)
212 break;
213 src = rhs.getInput();
214 fields.emplace_back(APInt(baseIdx->getBitWidth(), 0), loc,
215 midL.getInputs()[0]);
216 continue;
217 }
218 break;
219 }
220
221 // Look for a pattern overwriting a single element of the array.
222 // concat(slice(a, 0, n - 1), create(get(a, n)), slice(n + 1, size -
223 // n))
224 if (concat.getNumOperands() == 3) {
225 auto c = concat.getInputs();
226 auto rhs = dyn_cast_or_null<hw::ArraySliceOp>(c[0].getDefiningOp());
227 auto mid = dyn_cast_or_null<hw::ArrayCreateOp>(c[1].getDefiningOp());
228 auto lhs = dyn_cast_or_null<hw::ArraySliceOp>(c[2].getDefiningOp());
229 if (!lhs || !mid || !rhs || mid.getInputs().size() != 1)
230 break;
231 auto elem = mid.getInputs()[0];
232 auto arr = lhs.getInput();
233 if (arr != rhs.getInput() || arr.getType() != concat.getType())
234 break;
235
236 auto lhsSize =
237 hw::type_cast<hw::ArrayType>(lhs.getType()).getNumElements();
238 auto lhsIdx = getInt(lhs.getLowIndex());
239 auto rhsIdx = getInt(rhs.getLowIndex());
240 if (!lhsIdx || *lhsIdx != 0)
241 break;
242 if (!rhsIdx || *rhsIdx != lhsSize + 1)
243 break;
244 fields.emplace_back(*rhsIdx - 1, loc, elem);
245 src = arr;
246 continue;
247 }
248 break;
249 }
250
251 if (!isSelfWrite(dst, src))
252 return false;
253
254 // Emit the assignments in the order of the indices.
255 std::stable_sort(fields.begin(), fields.end(), [](auto l, auto r) {
256 return std::get<0>(l).ult(std::get<0>(r));
257 });
258
259 std::optional<APInt> last;
260 for (auto &[i, loc, value] : fields) {
261 if (i == last)
262 continue;
263 auto index = hw::ConstantOp::create(builder, loc, i);
264 auto field = sv::ArrayIndexInOutOp::create(builder, loc, dst, index);
265 if (!splitAssignment(builder, field, value))
266 sv::PAssignOp::create(builder, loc, field, value);
267 last = i;
268 }
269 return true;
270}
271
272/// Instead of emitting a struct_inject to alter fields or concatenation
273/// to adjust array elements, emit a more readable sequence of disjoint
274/// assignments to individual fields and indices. Returns true if
275/// sub-assignments were emitted and the original one can be deleted.
276bool PrettifyVerilogPass::splitAssignment(OpBuilder &builder, Value dst,
277 Value src) {
278 if (isSelfWrite(dst, src))
279 return true;
280
281 if (auto ty = hw::type_dyn_cast<hw::StructType>(src.getType()))
282 return splitStructAssignment(builder, ty, dst, src);
283
284 if (auto ty = hw::type_dyn_cast<hw::ArrayType>(src.getType()))
285 return splitArrayAssignment(builder, ty, dst, src);
286
287 return false;
288}
289
290/// Sink an operation into the same block where it is used. This will clone the
291/// operation so it can be sunk into multiple blocks. If there are no more uses
292/// in the current block, the op will be removed.
293void PrettifyVerilogPass::sinkOrCloneOpToUses(Operation *op) {
294 assert(mlir::isMemoryEffectFree(op) &&
295 "Op with side effects cannot be sunk to its uses.");
296 auto block = op->getBlock();
297 // This maps a block to the block local instance of the op.
298 SmallDenseMap<Block *, Value, 8> blockLocalValues;
299 for (auto &use : llvm::make_early_inc_range(op->getUses())) {
300 // If the current use is in the same block as the operation, there is
301 // nothing to do.
302 auto localBlock = use.getOwner()->getBlock();
303 if (block == localBlock)
304 continue;
305 // Find the block local clone of the operation. If there is not one already,
306 // the op will be cloned in to the block.
307 auto &localValue = blockLocalValues[localBlock];
308 if (!localValue) {
309 // Clone the operation and insert it to the beginning of the block.
310 localValue = OpBuilder::atBlockBegin(localBlock).clone(*op)->getResult(0);
311 }
312 // Replace the current use, removing it from the use list.
313 use.set(localValue);
314 anythingChanged = true;
315 }
316 // If this op is no longer used, drop it.
317 if (op->use_empty()) {
318 toDelete.insert(op);
319 anythingChanged = true;
320 }
321}
322
323/// This is called on unary operators. This returns true if the operator is
324/// moved.
325bool PrettifyVerilogPass::prettifyUnaryOperator(Operation *op) {
326 // If this is a multiple use unary operator, duplicate it and move it into the
327 // block corresponding to the user. This avoids emitting a temporary just for
328 // a unary operator. Instead of:
329 //
330 // tmp1 = ^(thing+thing);
331 // = tmp1 + 42
332 //
333 // we get:
334 //
335 // tmp2 = thing+thing;
336 // = ^tmp2 + 42
337 //
338 // This is particularly helpful when the operand of the unary op has multiple
339 // uses as well.
340 if (op->use_empty() || op->hasOneUse())
341 return false;
342
343 // If this operation has any users that cannot inline the operation, then
344 // don't duplicate any of them.
345 for (auto *user : op->getUsers()) {
346 if (isa<comb::ExtractOp, hw::ArraySliceOp>(user))
347 return false;
348 // TODO: We should use the condition used in ExportVerilog regarding
349 // allowExprInEventControl.
350 if (!options.allowExprInEventControl &&
351 isa<sv::AlwaysFFOp, sv::AlwaysOp, sv::AssertConcurrentOp,
352 sv::AssumeConcurrentOp, sv::CoverConcurrentOp, sv::AssertPropertyOp,
353 sv::AssumePropertyOp, sv::CoverPropertyOp>(user))
354 return false;
355 }
356
357 // Duplicating unary operations can move them across blocks (down the region
358 // tree). Make sure to keep referenced constants local.
359 auto cloneConstantOperandsIfNeeded = [&](Operation *op) {
360 for (auto &operand : op->getOpOperands()) {
361 auto constant = operand.get().getDefiningOp<hw::ConstantOp>();
362 if (!constant)
363 continue;
364
365 // If the constant is in a different block, clone or move it into the
366 // block.
367 if (constant->getBlock() != op->getBlock())
368 operand.set(OpBuilder(op).clone(*constant)->getResult(0));
369 }
370 };
371
372 while (!op->hasOneUse()) {
373 OpOperand &use = *op->use_begin();
374 Operation *user = use.getOwner();
375
376 // Clone the operation and insert before this user.
377 auto *cloned = OpBuilder(user).clone(*op);
378 cloneConstantOperandsIfNeeded(cloned);
379
380 // Update user's operand to the new value.
381 use.set(cloned->getResult(0));
382 }
383
384 // There is exactly one user left, so move this before it.
385 Operation *user = *op->user_begin();
386 op->moveBefore(user);
387 cloneConstantOperandsIfNeeded(op);
388
389 anythingChanged = true;
390 return true;
391}
392
393// Return the depth of the specified block in the region tree, stopping at
394// 'topBlock'.
395static unsigned getBlockDepth(Block *block, Block *topBlock) {
396 unsigned result = 0;
397 while (block != topBlock) {
398 block = block->getParentOp()->getBlock();
399 ++result;
400 }
401 return result;
402}
403
404/// This method is called on expressions to see if we can sink them down the
405/// region tree. This is a good thing to do to reduce scope of the expression.
406///
407/// This is called on expressions that may have side effects, and filters out
408/// the side effecting cases late for efficiency.
409void PrettifyVerilogPass::sinkExpression(Operation *op) {
410 // Ignore expressions with no users.
411 if (op->use_empty())
412 return;
413
414 Block *curOpBlock = op->getBlock();
415
416 // Single-used expressions are the most common and simple to handle.
417 if (op->hasOneUse()) {
418 if (curOpBlock != op->user_begin()->getBlock()) {
419 // Ok, we're about to make a change, ensure that there are no side
420 // effects.
421 if (!mlir::isMemoryEffectFree(op))
422 return;
423
424 op->moveBefore(*op->user_begin());
425 anythingChanged = true;
426 }
427 return;
428 }
429
430 // Find the nearest common ancestor of all the users.
431 auto userIt = op->user_begin();
432 Block *ncaBlock = userIt->getBlock();
433 ++userIt;
434 unsigned ncaBlockDepth = getBlockDepth(ncaBlock, curOpBlock);
435 if (ncaBlockDepth == 0)
436 return; // Have a user in the current block.
437
438 for (auto e = op->user_end(); userIt != e; ++userIt) {
439 auto *userBlock = userIt->getBlock();
440 if (userBlock == curOpBlock)
441 return; // Op has a user in it own block, can't sink it.
442 if (userBlock == ncaBlock)
443 continue;
444
445 // Get the region depth of the user block so we can march up the region tree
446 // to a common ancestor.
447 unsigned userBlockDepth = getBlockDepth(userBlock, curOpBlock);
448 while (userBlock != ncaBlock) {
449 if (ncaBlockDepth < userBlockDepth) {
450 userBlock = userBlock->getParentOp()->getBlock();
451 --userBlockDepth;
452 } else if (userBlockDepth < ncaBlockDepth) {
453 ncaBlock = ncaBlock->getParentOp()->getBlock();
454 --ncaBlockDepth;
455 } else {
456 userBlock = userBlock->getParentOp()->getBlock();
457 --userBlockDepth;
458 ncaBlock = ncaBlock->getParentOp()->getBlock();
459 --ncaBlockDepth;
460 }
461 }
462
463 if (ncaBlockDepth == 0)
464 return; // Have a user in the current block.
465 }
466
467 // Ok, we're about to make a change, ensure that there are no side
468 // effects.
469 if (!mlir::isMemoryEffectFree(op))
470 return;
471
472 // Ok, we found a common ancestor between all the users that is deeper than
473 // the current op. Sink it into the start of that block.
474 assert(ncaBlock != curOpBlock && "should have bailed out earlier");
475 op->moveBefore(&ncaBlock->front());
476 anythingChanged = true;
477}
478
479void PrettifyVerilogPass::processPostOrder(Block &body) {
480 SmallVector<Operation *> instances;
481
482 // Walk the block bottom-up, processing the region tree inside out.
483 for (auto &op :
484 llvm::make_early_inc_range(llvm::reverse(body.getOperations()))) {
485 if (op.getNumRegions()) {
486 for (auto &region : op.getRegions())
487 for (auto &regionBlock : region.getBlocks())
488 processPostOrder(regionBlock);
489 }
490
491 // Simplify assignments involving structures and arrays.
492 if (auto assign = dyn_cast<sv::PAssignOp>(op)) {
493 auto dst = assign.getDest();
494 auto src = assign.getSrc();
495 if (!isSelfWrite(dst, src)) {
496 OpBuilder builder(assign);
497 if (splitAssignment(builder, dst, src)) {
498 anythingChanged = true;
499 toDelete.insert(src.getDefiningOp());
500 toDelete.insert(dst.getDefiningOp());
501 assign.erase();
502 continue;
503 }
504 }
505 }
506
507 // Sink and duplicate unary operators.
508 if (isPrettifiableUnaryOperator(&op) && prettifyUnaryOperator(&op))
509 continue;
510
511 // Sink or duplicate constant ops and invisible "free" ops into the same
512 // block as their use. This will allow the verilog emitter to inline
513 // constant expressions and avoids ReadInOutOp from preventing motion.
514 if (matchPattern(&op, mlir::m_Constant()) ||
515 isa<sv::ReadInOutOp, sv::ArrayIndexInOutOp, sv::StructFieldInOutOp,
516 sv::IndexedPartSelectInOutOp, hw::ParamValueOp>(op)) {
517 sinkOrCloneOpToUses(&op);
518 continue;
519 }
520
521 // Sink normal expressions down the region tree if they aren't used within
522 // their current block. This allows them to be folded into the using
523 // expression inline in the best case, and better scopes the temporary wire
524 // they generate in the worst case. Our overall traversal order is
525 // post-order here which means all users will already be sunk.
526 if (hw::isCombinational(&op) || sv::isExpression(&op)) {
527 sinkExpression(&op);
528 continue;
529 }
530
531 if (isa<hw::InstanceOp>(op))
532 instances.push_back(&op);
533 }
534
535 // If we have any instances, keep their relative order but shift them to the
536 // end of the module. Any outputs will be writing to a wire or an output port
537 // of the enclosing module anyway, and this allows inputs to be inlined into
538 // the operand list as parameters.
539 if (!instances.empty()) {
540 for (Operation *instance : llvm::reverse(instances)) {
541 if (instance != &body.back())
542 instance->moveBefore(&body.back());
543 }
544 }
545}
546
547void PrettifyVerilogPass::runOnOperation() {
548 hw::HWModuleOp thisModule = getOperation();
549 options = LoweringOptions(thisModule->getParentOfType<mlir::ModuleOp>());
550
551 // Keeps track if anything changed during this pass, used to determine if
552 // the analyses were preserved.
553 anythingChanged = false;
554
555 // Walk the operations in post-order, transforming any that are interesting.
556 processPostOrder(*thisModule.getBodyBlock());
557
558 // Erase any dangling operands of simplified operations.
559 while (!toDelete.empty()) {
560 auto *op = toDelete.pop_back_val();
561 if (!op || !isOpTriviallyDead(op))
562 continue;
563
564 for (auto operand : op->getOperands())
565 toDelete.insert(operand.getDefiningOp());
566
567 op->erase();
568 }
569
570 // If we did not change anything in the graph mark all analysis as
571 // preserved.
572 if (!anythingChanged)
573 markAllAnalysesPreserved();
574}
assert(baseType &&"element must be base type")
static bool isPrettifiableUnaryOperator(Operation *op)
Return true if this is a unary operator that is cheap enough to sink or duplicate into its users as p...
static bool isSelfWrite(Value dst, Value src)
static std::optional< APInt > getInt(Value value)
Helper to convert a value to a constant integer if it is one.
static unsigned getBlockDepth(Block *block, Block *topBlock)
create(data_type, value)
Definition hw.py:433
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition sv.py:1
Options which control the emission from CIRCT to Verilog.