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