[PrettifyVerilog] Sink multiple user expression to NCA in region tree.

This generalizes the previous patch to sink expressions with multiple
users to the deepest common region between all the users.  In the case
of FIRRTL, this sinks a shocking amount of test-only code into
"ifndef SYNTHESIS" blocks, eliminating wires at the top level.

However, because these expressions have multiple uses, they don't get
emitted inline - they get temporaries declared at their local scope,
usually as `automatic logic` values.  These never get their initializer
emitted inline (see Issue #1567), but sinking these is still the right
thing to do IMO.
This commit is contained in:
Chris Lattner 2021-08-18 15:03:48 -07:00
parent 0aaac6e36d
commit 92f78c0cc8
2 changed files with 57 additions and 2 deletions

View File

@ -151,6 +151,17 @@ bool PrettifyVerilogPass::prettifyUnaryOperator(Operation *op) {
return true;
}
// Return the depth of the specified block in the region tree, stopping at
// 'topBlock'.
static unsigned getBlockDepth(Block *block, Block *topBlock) {
unsigned result = 0;
while (block != topBlock) {
block = block->getParentOp()->getBlock();
++result;
}
return result;
}
/// This method is called on expressions to see if we can sink them down the
/// region tree. This is a good thing to do to reduce scope of the expression.
void PrettifyVerilogPass::sinkExpression(Operation *op) {
@ -168,6 +179,49 @@ void PrettifyVerilogPass::sinkExpression(Operation *op) {
}
return;
}
// Find the nearest common ancestor of all the users.
auto userIt = op->user_begin();
Block *ncaBlock = userIt->getBlock();
++userIt;
unsigned ncaBlockDepth = getBlockDepth(ncaBlock, curOpBlock);
if (ncaBlockDepth == 0)
return; // Have a user in the current block.
for (auto e = op->user_end(); userIt != e; ++userIt) {
auto *userBlock = userIt->getBlock();
if (userBlock == curOpBlock)
return; // Op has a user in it own block, can't sink it.
if (userBlock == ncaBlock)
continue;
// Get the region depth of the user block so we can march up the region tree
// to a common ancestor.
unsigned userBlockDepth = getBlockDepth(userBlock, curOpBlock);
while (userBlock != ncaBlock) {
if (ncaBlockDepth < userBlockDepth) {
userBlock = userBlock->getParentOp()->getBlock();
--userBlockDepth;
} else if (userBlockDepth < ncaBlockDepth) {
ncaBlock = ncaBlock->getParentOp()->getBlock();
--ncaBlockDepth;
} else {
userBlock = userBlock->getParentOp()->getBlock();
--userBlockDepth;
ncaBlock = ncaBlock->getParentOp()->getBlock();
--ncaBlockDepth;
}
}
if (ncaBlockDepth == 0)
return; // Have a user in the current block.
}
// Ok, we found a common ancestor between all the users that is deeper than
// the current op. Sink it into the start of that block.
assert(ncaBlock != curOpBlock && "should have bailed out earlier");
op->moveBefore(&ncaBlock->front());
anythingChanged = true;
}
void PrettifyVerilogPass::processPostOrder(Block &body) {

View File

@ -109,12 +109,13 @@ hw.module @sink_expression(%clock: i1, %a: i1, %a2: i1, %a3: i1, %a4: i1) {
// This or is used in one place.
%0 = comb.or %a2, %a3 : i1
// This and/xor chain is used in two. Both should be sunk.
// CHECK: [[AND:%.*]] = comb.and %a2, %a3 : i1
// CHECK: [[XOR:%.*]] = comb.xor [[AND]], %a4 : i1
%1 = comb.and %a2, %a3 : i1
%2 = comb.xor %1, %a4 : i1
// CHECK: sv.always
sv.always posedge %clock {
// CHECK: [[AND:%.*]] = comb.and %a2, %a3 : i1
// CHECK: [[XOR:%.*]] = comb.xor [[AND]], %a4 : i1
// CHECK: sv.ifdef.procedural
sv.ifdef.procedural "SOMETHING" {
// CHECK: [[OR:%.*]] = comb.or %a2, %a3 : i1