mirror of
https://github.com/verilator/verilator.git
synced 2025-02-02 03:34:05 +00:00
341 lines
14 KiB
C++
341 lines
14 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Merge branches/ternary ?:
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// Copyright 2003-2020 by Wilson Snyder. This program is free software; you
|
|
// can redistribute it and/or modify it under the terms of either the GNU
|
|
// Lesser General Public License Version 3 or the Perl Artistic License
|
|
// Version 2.0.
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
//
|
|
//*************************************************************************
|
|
// V3BranchMerge's Transformations:
|
|
//
|
|
// Look for sequences of assignments with ternary conditional on the right
|
|
// hand side with the same condition:
|
|
// lhs0 = cond ? then0 : else0;
|
|
// lhs1 = cond ? then1 : else1;
|
|
// lhs2 = cond ? then2 : else2;
|
|
//
|
|
// This seems to be a common pattern and can make the C compiler take a
|
|
// long time when compiling it with optimization. For us it's easy and fast
|
|
// to convert this to 'if' statements because we know the pattern is common:
|
|
// if (cond) {
|
|
// lhs0 = then0;
|
|
// lhs1 = then1;
|
|
// lhs2 = then2;
|
|
// } else {
|
|
// lhs0 = else0;
|
|
// lhs1 = else1;
|
|
// lhs2 = else2;
|
|
// }
|
|
//
|
|
// For 1-bit signals, we consider strength reduced forms to be conditionals,
|
|
// but only if we already encountered a true conditional we can merge with.
|
|
// If we did, then act as if:
|
|
// 'lhs = cond & value' is actually 'lhs = cond ? value : 1'd0'
|
|
// 'lhs = cond' is actually 'lhs = cond ? 1'd1 : 1'd0'.
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "config_build.h"
|
|
#include "verilatedos.h"
|
|
|
|
#include "V3Global.h"
|
|
#include "V3MergeCond.h"
|
|
#include "V3Stats.h"
|
|
#include "V3Ast.h"
|
|
|
|
//######################################################################
|
|
|
|
class CheckMergeableVisitor final : public AstNVisitor {
|
|
private:
|
|
// STATE
|
|
bool m_mergeable
|
|
= false; // State tracking whether tree being processed is a mergeable condition
|
|
|
|
// METHODS
|
|
VL_DEBUG_FUNC; // Declare debug()
|
|
|
|
void clearMergeable(const AstNode* nodep, const char* reason) {
|
|
UASSERT_OBJ(m_mergeable, nodep, "Should have short-circuited traversal");
|
|
m_mergeable = false;
|
|
UINFO(9, "Clearing mergeable on " << nodep << " due to " << reason << endl);
|
|
}
|
|
|
|
// VISITORS
|
|
virtual void visit(AstNode* nodep) override {
|
|
if (!m_mergeable) return;
|
|
// Clear if node is impure
|
|
if (!nodep->isPure()) {
|
|
clearMergeable(nodep, "impure");
|
|
return;
|
|
}
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
virtual void visit(AstVarRef* nodep) override {
|
|
if (!m_mergeable) return;
|
|
// Clear if it's an LValue referencing a marked variable
|
|
if (nodep->access().isWriteOrRW() && nodep->varp()->user1()) {
|
|
clearMergeable(nodep, "might modify condition");
|
|
}
|
|
}
|
|
|
|
public:
|
|
CheckMergeableVisitor() = default;
|
|
|
|
// Return false if this node should not be merged at all because:
|
|
// - It contains an impure expression
|
|
// - It contains an LValue referencing the condition
|
|
bool operator()(const AstNodeAssign* node) {
|
|
m_mergeable = true;
|
|
iterateChildrenConst(const_cast<AstNodeAssign*>(node));
|
|
return m_mergeable;
|
|
}
|
|
};
|
|
|
|
class MarkVarsVisitor final : public AstNVisitor {
|
|
private:
|
|
// METHODS
|
|
VL_DEBUG_FUNC; // Declare debug()
|
|
|
|
// VISITORS
|
|
virtual void visit(AstVarRef* nodep) override { nodep->varp()->user1(1); }
|
|
virtual void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
|
|
|
|
public:
|
|
// Remove marks from AstVars (clear user1)
|
|
void clear() { AstNode::user1ClearTree(); }
|
|
|
|
// Mark all AstVars referenced by setting user1
|
|
void mark(AstNode* node) { iterate(node); }
|
|
};
|
|
|
|
class MergeCondVisitor final : public AstNVisitor {
|
|
private:
|
|
// NODE STATE
|
|
// AstVar::user1 -> Flag set for variables referenced by m_mgCondp
|
|
AstUser1InUse m_user1InUse;
|
|
|
|
// STATE
|
|
VDouble0 m_statMerges; // Statistic tracking
|
|
VDouble0 m_statMergedItems; // Statistic tracking
|
|
VDouble0 m_statLongestList; // Statistic tracking
|
|
|
|
AstNode* m_mgFirstp = nullptr; // First node in merged sequence
|
|
AstNode* m_mgCondp = nullptr; // The condition of the first node
|
|
AstNode* m_mgLastp = nullptr; // Last node in merged sequence
|
|
const AstNode* m_mgNextp = nullptr; // Next node in list being examined
|
|
uint32_t m_listLenght = 0; // Length of current list
|
|
|
|
CheckMergeableVisitor m_checkMergeable; // Sub visitor for encapsulation & speed
|
|
MarkVarsVisitor m_markVars; // Sub visitor for encapsulation & speed
|
|
|
|
// METHODS
|
|
VL_DEBUG_FUNC; // Declare debug()
|
|
|
|
// This function extracts the Cond node from the RHS, if there is one and
|
|
// it is in a supported position, which are:
|
|
// - RHS is the Cond
|
|
// - RHS is And(Const, Cond). This And is inserted often by V3Clean.
|
|
AstNodeCond* extractCond(AstNode* rhsp) {
|
|
if (AstNodeCond* const condp = VN_CAST(rhsp, NodeCond)) {
|
|
return condp;
|
|
} else if (AstAnd* const andp = VN_CAST(rhsp, And)) {
|
|
if (AstNodeCond* const condp = VN_CAST(andp->rhsp(), NodeCond)) {
|
|
if (VN_IS(andp->lhsp(), Const)) { return condp; }
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Apply (_ & 1'b1). This is necessary because this pass is after V3Clean,
|
|
// and sometimes we have an AstAnd with a 1-bit condition on one side, but
|
|
// a more than 1-bit value on the other side, so we need to keep only the
|
|
// LSB. Ideally we would only do this iff the node is known not to be 1-bit
|
|
// wide, but working that out here is a bit difficult. As this masking is
|
|
// rarely required (only when trying to merge a "cond & value" with an
|
|
// earlier ternary), we will just always mask it for safety.
|
|
AstNode* maskLsb(AstNode* nodep) {
|
|
AstNode* const maskp = new AstConst(nodep->fileline(), AstConst::BitTrue());
|
|
return new AstAnd(nodep->fileline(), nodep, maskp);
|
|
}
|
|
|
|
// Fold the RHS expression assuming the given condition state. Unlink bits
|
|
// from the RHS which is only used once, and can be reused. What remains
|
|
// of the RHS is expected to be deleted by the caller.
|
|
AstNode* foldAndUnlink(AstNode* rhsp, bool condTrue) {
|
|
if (rhsp->sameTree(m_mgCondp)) {
|
|
return new AstConst(rhsp->fileline(), AstConst::BitTrue{}, condTrue);
|
|
} else if (AstNodeCond* const condp = extractCond(rhsp)) {
|
|
AstNode* const resp
|
|
= condTrue ? condp->expr1p()->unlinkFrBack() : condp->expr2p()->unlinkFrBack();
|
|
if (condp == rhsp) { return resp; }
|
|
if (AstAnd* const andp = VN_CAST(rhsp, And)) {
|
|
UASSERT_OBJ(andp->rhsp() == condp, rhsp, "Should not try to fold this");
|
|
return new AstAnd(andp->fileline(), andp->lhsp()->cloneTree(false), resp);
|
|
}
|
|
} else if (AstAnd* const andp = VN_CAST(rhsp, And)) {
|
|
if (andp->lhsp()->sameTree(m_mgCondp)) {
|
|
return condTrue ? maskLsb(andp->rhsp()->unlinkFrBack())
|
|
: new AstConst(rhsp->fileline(), AstConst::BitFalse());
|
|
} else {
|
|
UASSERT_OBJ(andp->rhsp()->sameTree(m_mgCondp), rhsp,
|
|
"AstAnd doesn't hold condition expression");
|
|
return condTrue ? maskLsb(andp->lhsp()->unlinkFrBack())
|
|
: new AstConst(rhsp->fileline(), AstConst::BitFalse());
|
|
}
|
|
}
|
|
rhsp->v3fatalSrc("Don't know how to fold expression");
|
|
}
|
|
|
|
void mergeEnd() {
|
|
UASSERT(m_mgFirstp, "mergeEnd without list");
|
|
// Merge if list is longer than one node
|
|
if (m_mgFirstp != m_mgLastp) {
|
|
UINFO(6, "MergeCond - First: " << m_mgFirstp << " Last: " << m_mgLastp << endl);
|
|
++m_statMerges;
|
|
if (m_listLenght > m_statLongestList) m_statLongestList = m_listLenght;
|
|
|
|
// Create equivalent 'if' statement and insert it before the first node
|
|
AstIf* const ifp
|
|
= new AstIf(m_mgCondp->fileline(), m_mgCondp->unlinkFrBack(), nullptr, nullptr);
|
|
m_mgFirstp->replaceWith(ifp);
|
|
ifp->addNextHere(m_mgFirstp);
|
|
// Unzip the list and insert under branches
|
|
AstNode* nextp = m_mgFirstp;
|
|
do {
|
|
// Grab next pointer and unlink
|
|
AstNode* const currp = nextp;
|
|
nextp = currp != m_mgLastp ? currp->nextp() : nullptr;
|
|
currp->unlinkFrBack();
|
|
// Skip over comments
|
|
if (VN_IS(currp, Comment)) {
|
|
VL_DO_DANGLING(currp->deleteTree(), currp);
|
|
continue;
|
|
}
|
|
// Count
|
|
++m_statMergedItems;
|
|
// Unlink RHS and clone to get the 2 assignments (reusing currp)
|
|
AstNodeAssign* const thenp = VN_CAST(currp, NodeAssign);
|
|
AstNode* const rhsp = thenp->rhsp()->unlinkFrBack();
|
|
AstNodeAssign* const elsep = thenp->cloneTree(false);
|
|
// Construct the new RHSs and add to branches
|
|
thenp->rhsp(foldAndUnlink(rhsp, true));
|
|
elsep->rhsp(foldAndUnlink(rhsp, false));
|
|
ifp->addIfsp(thenp);
|
|
ifp->addElsesp(elsep);
|
|
// Cleanup
|
|
VL_DO_DANGLING(rhsp->deleteTree(), rhsp);
|
|
} while (nextp);
|
|
}
|
|
// Reset state
|
|
m_mgFirstp = nullptr;
|
|
m_mgCondp = nullptr;
|
|
m_mgLastp = nullptr;
|
|
m_mgNextp = nullptr;
|
|
m_markVars.clear();
|
|
}
|
|
|
|
void addToList(AstNode* nodep, AstNode* condp) {
|
|
// Set up head of new list if node is first in list
|
|
if (!m_mgFirstp) {
|
|
UASSERT_OBJ(condp, nodep, "Cannot start new list without condition");
|
|
m_mgFirstp = nodep;
|
|
m_mgCondp = condp;
|
|
m_listLenght = 0;
|
|
m_markVars.mark(condp);
|
|
}
|
|
// Add node
|
|
++m_listLenght;
|
|
// Track end of list
|
|
m_mgLastp = nodep;
|
|
// Set up expected next node in list. Skip over any comments, (inserted
|
|
// by V3Order before always blocks)
|
|
m_mgNextp = nodep->nextp();
|
|
while (m_mgNextp && VN_IS(m_mgNextp, Comment)) { m_mgNextp = m_mgNextp->nextp(); }
|
|
// If last under parent, done with current list
|
|
if (!m_mgNextp) mergeEnd();
|
|
}
|
|
|
|
// VISITORS
|
|
virtual void visit(AstNodeAssign* nodep) override {
|
|
AstNode* const rhsp = nodep->rhsp();
|
|
if (AstNodeCond* const condp = extractCond(rhsp)) {
|
|
if (!m_checkMergeable(nodep)) {
|
|
// Node not mergeable.
|
|
// If no current list, then this node is just special, move on.
|
|
if (!m_mgFirstp) return;
|
|
// Otherwise finish current list
|
|
mergeEnd();
|
|
// Finishing the list might make the node mergeable again, e.g.
|
|
// if the reason we could not merge was due to the condition
|
|
// being assigned, so check again and stop only if still no.
|
|
if (!m_checkMergeable(nodep)) return;
|
|
}
|
|
if (m_mgFirstp && (m_mgNextp != nodep || !condp->condp()->sameTree(m_mgCondp))) {
|
|
// Node in different list, or has different condition.
|
|
// Finish current list, addToList will start a new one.
|
|
mergeEnd();
|
|
}
|
|
// Add current node
|
|
addToList(nodep, condp->condp());
|
|
} else if (m_mgFirstp) {
|
|
// RHS is not a conditional, but we already started a list.
|
|
// If it's a 1-bit signal, and a mergeable assignment, try reduced forms
|
|
if (m_mgNextp == nodep && rhsp->widthMin() == 1 && m_checkMergeable(nodep)) {
|
|
// Is it a 'lhs = cond & value' or 'lhs = value & cond'?
|
|
if (AstAnd* const andp = VN_CAST(rhsp, And)) {
|
|
if (andp->lhsp()->sameTree(m_mgCondp) || andp->rhsp()->sameTree(m_mgCondp)) {
|
|
addToList(nodep, nullptr);
|
|
return;
|
|
}
|
|
}
|
|
// Is it simply 'lhs = cond'?
|
|
if (rhsp->sameTree(m_mgCondp)) {
|
|
addToList(nodep, nullptr);
|
|
return;
|
|
}
|
|
}
|
|
// Not added to list, so we are done with the current list
|
|
mergeEnd();
|
|
}
|
|
}
|
|
virtual void visit(AstComment*) override {} // Skip over comments
|
|
// For speed, only iterate what is necessary.
|
|
virtual void visit(AstNetlist* nodep) override { iterateAndNextNull(nodep->modulesp()); }
|
|
virtual void visit(AstNodeModule* nodep) override { iterateAndNextNull(nodep->stmtsp()); }
|
|
virtual void visit(AstCFunc* nodep) override {
|
|
iterateChildren(nodep);
|
|
// Close list, if there is one at the end of the function
|
|
if (m_mgFirstp) mergeEnd();
|
|
}
|
|
virtual void visit(AstNodeStmt* nodep) override { iterateChildren(nodep); }
|
|
virtual void visit(AstNode* nodep) override {}
|
|
|
|
public:
|
|
// CONSTRUCTORS
|
|
explicit MergeCondVisitor(AstNetlist* nodep) {
|
|
m_markVars.clear();
|
|
iterate(nodep);
|
|
}
|
|
virtual ~MergeCondVisitor() override {
|
|
V3Stats::addStat("Optimizations, MergeCond merges", m_statMerges);
|
|
V3Stats::addStat("Optimizations, MergeCond merged items", m_statMergedItems);
|
|
V3Stats::addStat("Optimizations, MergeCond longest merge", m_statLongestList);
|
|
}
|
|
};
|
|
|
|
//######################################################################
|
|
// MergeConditionals class functions
|
|
|
|
void V3MergeCond::mergeAll(AstNetlist* nodep) {
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
|
{ MergeCondVisitor visitor(nodep); }
|
|
V3Global::dumpCheckGlobalTree("merge_cond", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 6);
|
|
}
|