Skip to content

Commit

Permalink
Split up assignments to wides with Concat on the RHS
Browse files Browse the repository at this point in the history
Add a new pass to split up (recursively):

foo = {l, r};

into the following, with the right indices, iff the concatenation
straddles a wide word boundary.

foo[_:_] = r;
foo[_:_] = l;

This eliminates more wide temporaries.

Another 23% speedup on VeeR EH2 high_perf. Also brings the predicted
stack size from 8M to 40k.
  • Loading branch information
gezalore committed Nov 10, 2024
1 parent b741105 commit a06c7bf
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 1 deletion.
4 changes: 4 additions & 0 deletions docs/guide/exe_verilator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,10 @@ Summary:

.. option:: -fno-expand

.. option:: -fno-func-split-cat

Do not split wide assignments with concatenation on the RHS.

.. option:: -fno-gate

.. option:: -fno-inline
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ set(HEADERS
V3Force.h
V3Fork.h
V3FunctionTraits.h
V3Funcopt.h
V3Gate.h
V3Global.h
V3Graph.h
Expand Down Expand Up @@ -255,6 +256,7 @@ set(COMMON_SOURCES
V3FileLine.cpp
V3Force.cpp
V3Fork.cpp
V3Funcopt.cpp
V3Gate.cpp
V3Global.cpp
V3Graph.cpp
Expand Down
1 change: 1 addition & 0 deletions src/Makefile_obj.in
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ RAW_OBJS_PCH_ASTMT = \
V3EmitCPch.o \
V3EmitV.o \
V3File.o \
V3Funcopt.o \
V3Global.o \
V3Hasher.o \
V3Number.o \
Expand Down
171 changes: 171 additions & 0 deletions src/V3Funcopt.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Generic optimizations on a per function basis
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2024 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
//
//*************************************************************************
//
// - Split assignments to wide locations with Concat on the RHS
// at word boundaries:
// foo = {l, r};
// becomes (recursively):
// foo[_:_] = r;
// foo[_:_] = l;
//
//*************************************************************************

#include "V3PchAstMT.h"

#include "V3Funcopt.h"

#include "V3Global.h"
#include "V3Stats.h"
#include "V3ThreadPool.h"

VL_DEFINE_DEBUG_FUNCTIONS;

class FuncoptVisitor final : public VNVisitor {
// NODE STATE
// AstNodeAssign::user() -> bool. Already checked, safe to split. Omit expensive check.

// STATE
AstCFunc* const m_funcp; // The function being processes

VDouble0 m_concatSplits; // Number of splits in assignments with Concat on RHS

static bool rhsReadsLhs(AstNodeAssign* nodep) {
// It is expected that the number of vars written on the LHS is very small (should be 1).
std::vector<AstVar*> writtenVarps;
nodep->lhsp()->foreach([&](const AstVarRef* refp) {
if (refp->access().isWriteOrRW()) writtenVarps.emplace_back(refp->varp());
});
if (writtenVarps.size() == 1) {
// Common case
return nodep->rhsp()->exists([&](const AstVarRef* refp) { //
return refp->varp() == writtenVarps[0];
});
} else {
// Generic case - TODO: should this ever be hit at this point?
return nodep->rhsp()->exists([&](const AstVarRef* refp) {
for (AstVar* const writtenVarp : writtenVarps) {
if (refp->varp() == writtenVarp) return true;
}
return false;
});
}
}

// METHODS
// Split wide assignments with a wide concatenation on the RHS.
// Returns true if 'nodep' was deleted
bool splitConcat(AstNodeAssign* nodep) {
// Only care about concatenations on the right
AstConcat* const rhsp = VN_CAST(nodep->rhsp(), Concat);
if (!rhsp) return false;
// Will need the LHS
AstNodeExpr* lhsp = nodep->lhsp();
UASSERT_OBJ(lhsp->width() == rhsp->width(), nodep, "Inconsistent assignment");
// Only consider pure assignments. Nodes inserted below are safe.
if (!nodep->user1() && (!lhsp->isPure() || !rhsp->isPure())) return false;
// Check for a Sel on the LHS if present, and skip over it
uint32_t lsb = 0;
if (AstSel* const selp = VN_CAST(lhsp, Sel)) {
if (AstConst* const lsbp = VN_CAST(selp->lsbp(), Const)) {
lhsp = selp->fromp();
lsb = lsbp->toUInt();
} else {
// Don't optimize if it's a variable select
return false;
}
}
// No need to split assignments targeting storage smaller than a machine register
if (lhsp->width() <= VL_QUADSIZE) return false;

// If it's a concat straddling a word boundary, try to split it.
// The next visit on the new nodes will split it recursively.
// Otherwise, keep the original assignment.
const int lsbWord = lsb / VL_EDATASIZE;
const int msbWord = (lsb + rhsp->width() - 1) / VL_EDATASIZE;
if (lsbWord == msbWord) return false;

// If the RHS reads the LHS, we can't actually do this. Nodes inserted below are safe.
if (!nodep->user1() && rhsReadsLhs(nodep)) return false;

// Ok, actually split it now
++m_concatSplits;
// The 2 parts and their offsets
AstNodeExpr* const rrp = rhsp->rhsp()->unlinkFrBack();
AstNodeExpr* const rlp = rhsp->lhsp()->unlinkFrBack();
const int rLsb = lsb;
const int lLsb = lsb + rrp->width();
// Insert the 2 assignment right after the original. They will be visited next.
AstAssign* const arp = new AstAssign{
nodep->fileline(),
new AstSel{lhsp->fileline(), lhsp->cloneTreePure(false), rLsb, rrp->width()}, rrp};
AstAssign* const alp = new AstAssign{
nodep->fileline(),
new AstSel{lhsp->fileline(), lhsp->unlinkFrBack(), lLsb, rlp->width()}, rlp};
nodep->addNextHere(arp);
arp->addNextHere(alp);
// Safe to split these.
arp->user1(true);
alp->user1(true);
// Nuke what is left
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return true;
}

// VISIT
void visit(AstNodeAssign* nodep) override {
// TODO: Only thing remaining inside functions should be AstAssign (that is, an actual
// assignment statemant), but we stil use AstAssignW, AstAssignDly, and all, fix.
if (v3Global.opt.fFuncSplitCat()) {
if (splitConcat(nodep)) return; // Must return here, in case more code is added below
}
}

void visit(AstNodeExpr*) override {} // No need to descend further (Ignore AstExprStmt...)

void visit(AstNode* nodep) override { iterateChildren(nodep); }

// CONSTRUCTORS
explicit FuncoptVisitor(AstCFunc* funcp)
: m_funcp{funcp} {
iterateChildren(funcp);
}
~FuncoptVisitor() override {
V3Stats::addStatSum("Optimizations, Funcopt concat splits", m_concatSplits);
}

public:
static void apply(AstCFunc* funcp) { FuncoptVisitor{funcp}; }
};

//######################################################################

void V3Funcopt::funcoptAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ": " << endl);
{
const VNUser1InUse user1InUse;
V3ThreadScope threadScope;
for (AstNodeModule *modp = nodep->modulesp(), *nextModp; modp; modp = nextModp) {
nextModp = VN_AS(modp->nextp(), NodeModule);
for (AstNode *nodep = modp->stmtsp(), *nextNodep; nodep; nodep = nextNodep) {
nextNodep = nodep->nextp();
if (AstCFunc* const cfuncp = VN_CAST(nodep, CFunc)) {
threadScope.enqueue([cfuncp]() { FuncoptVisitor::apply(cfuncp); });
}
}
}
}
V3Global::dumpCheckGlobalTree("funcopt", 0, dumpTreeEitherLevel() >= 3);
}
32 changes: 32 additions & 0 deletions src/V3Funcopt.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Generic optimizations on a per function basis
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2024 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
//
//*************************************************************************

#ifndef VERILATOR_V3FUNCOPT_H_
#define VERILATOR_V3FUNCOPT_H_

#include "config_build.h"
#include "verilatedos.h"

class AstNetlist;

//============================================================================

class V3Funcopt final {
public:
static void funcoptAll(AstNetlist* nodep);
};

#endif // Guard
3 changes: 2 additions & 1 deletion src/V3Options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1076,7 +1076,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
const V3OptionParser::AppendHelper DECL_OPTION{parser};
V3OPTION_PARSER_DECL_TAGS;

const auto callStrSetter = [this](void (V3Options::*cbStr)(const string&)) {
const auto callStrSetter = [this](void (V3Options::* cbStr)(const string&)) {
return [this, cbStr](const string& v) { (this->*cbStr)(v); };
};
// Usage
Expand Down Expand Up @@ -1303,6 +1303,7 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc,
DECL_OPTION("-fdead-assigns", FOnOff, &m_fDeadAssigns);
DECL_OPTION("-fdead-cells", FOnOff, &m_fDeadCells);
DECL_OPTION("-fexpand", FOnOff, &m_fExpand);
DECL_OPTION("-ffunc-split-cat", FOnOff, &m_fFuncSplitCat);
DECL_OPTION("-fgate", FOnOff, &m_fGate);
DECL_OPTION("-finline", FOnOff, &m_fInline);
DECL_OPTION("-flife", FOnOff, &m_fLife);
Expand Down
3 changes: 3 additions & 0 deletions src/V3Options.h
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ class V3Options final {
bool m_fDeadAssigns; // main switch: -fno-dead-assigns: remove dead assigns
bool m_fDeadCells; // main switch: -fno-dead-cells: remove dead cells
bool m_fExpand; // main switch: -fno-expand: expansion of C macros
bool m_fFuncSplitCat = true; // main switch: -fno-func-split-cat: expansion of C macros
bool m_fGate; // main switch: -fno-gate: gate wire elimination
bool m_fInline; // main switch: -fno-inline: module inlining
bool m_fLife; // main switch: -fno-life: variable lifetime
Expand Down Expand Up @@ -674,6 +675,8 @@ class V3Options final {
bool fDeadAssigns() const { return m_fDeadAssigns; }
bool fDeadCells() const { return m_fDeadCells; }
bool fExpand() const { return m_fExpand; }
bool fFuncSplitCat() const { return m_fFuncSplitCat; }
bool fFunc() const { return fFuncSplitCat(); }
bool fGate() const { return m_fGate; }
bool fInline() const { return m_fInline; }
bool fLife() const { return m_fLife; }
Expand Down
4 changes: 4 additions & 0 deletions src/Verilator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#include "V3File.h"
#include "V3Force.h"
#include "V3Fork.h"
#include "V3Funcopt.h"
#include "V3Gate.h"
#include "V3Global.h"
#include "V3Graph.h"
Expand Down Expand Up @@ -497,6 +498,9 @@ static void process() {
// --GENERATION------------------

if (!v3Global.opt.serializeOnly()) {
// Generic optimizations on a per-function basis
if (v3Global.opt.fFunc()) V3Funcopt::funcoptAll(v3Global.rootp());

// Remove unused vars
V3Const::constifyAll(v3Global.rootp());
V3Dead::deadifyAll(v3Global.rootp());
Expand Down
3 changes: 3 additions & 0 deletions test_regress/t/t_dfg_balance_cats.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@
r' Optimizations, DFG pre inline BalanceTrees, concat trees balanced\s+(\d+)', 0)
test.file_grep(test.stats,
r' Optimizations, DFG post inline BalanceTrees, concat trees balanced\s+(\d+)', 1)
test.file_grep(test.stats, r'Optimizations, DFG pre inline Dfg2Ast, result equations\s+(\d+)', 1)
test.file_grep(test.stats, r'Optimizations, DFG post inline Dfg2Ast, result equations\s+(\d+)', 1)
test.file_grep(test.stats, r'Optimizations, Funcopt concat splits\s+(\d+)', 62)

test.passes()
26 changes: 26 additions & 0 deletions test_regress/t/t_dfg_balance_cats_nofunc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 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

import vltest_bootstrap

test.scenarios('vlt')

test.top_filename = "t/t_dfg_balance_cats.v"

test.compile(verilator_flags2=["--stats", "-fno-func-split-cat"])

test.file_grep(test.stats,
r' Optimizations, DFG pre inline BalanceTrees, concat trees balanced\s+(\d+)', 0)
test.file_grep(test.stats,
r' Optimizations, DFG post inline BalanceTrees, concat trees balanced\s+(\d+)', 1)
test.file_grep(test.stats, r'Optimizations, DFG pre inline Dfg2Ast, result equations\s+(\d+)', 1)
test.file_grep(test.stats, r'Optimizations, DFG post inline Dfg2Ast, result equations\s+(\d+)', 1)
test.file_grep_not(test.stats, r'Optimizations, Funcopt concat splits')

test.passes()

0 comments on commit a06c7bf

Please sign in to comment.