mirror of
https://github.com/verilator/verilator.git
synced 2025-01-01 04:07:34 +00:00
312 lines
12 KiB
C++
312 lines
12 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Variable ordering
|
|
//
|
|
// 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
|
|
//
|
|
//*************************************************************************
|
|
// V3VariableOrder's Transformations:
|
|
//
|
|
// Each module:
|
|
// Order module variables
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "V3PchAstMT.h"
|
|
|
|
#include "V3VariableOrder.h"
|
|
|
|
#include "V3AstUserAllocator.h"
|
|
#include "V3EmitCBase.h"
|
|
#include "V3ExecGraph.h"
|
|
#include "V3TSP.h"
|
|
#include "V3ThreadPool.h"
|
|
|
|
#include <vector>
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
using MTaskIdVec = std::vector<bool>; // Used as a bit-set indexed by MTask ID
|
|
using MTaskAffinityMap = std::unordered_map<const AstVar*, MTaskIdVec>;
|
|
|
|
// Trace through code reachable form an MTask and annotate referenced variabels
|
|
class GatherMTaskAffinity final : VNVisitorConst {
|
|
// NODE STATE
|
|
// AstCFunc::user1() // bool: Already traced this function
|
|
// AstVar::user1() // bool: Already traced this variable
|
|
const VNUser1InUse m_user1InUse;
|
|
|
|
// STATE
|
|
MTaskAffinityMap& m_results; // The result map being built;
|
|
const uint32_t m_id; // Id of mtask being analysed
|
|
const size_t m_usedIds = ExecMTask::numUsedIds(); // Value of max id + 1
|
|
|
|
// CONSTRUCTOR
|
|
GatherMTaskAffinity(const ExecMTask* mTaskp, MTaskAffinityMap& results)
|
|
: m_results{results}
|
|
, m_id{mTaskp->id()} {
|
|
iterateChildrenConst(mTaskp->bodyp());
|
|
}
|
|
~GatherMTaskAffinity() = default;
|
|
VL_UNMOVABLE(GatherMTaskAffinity);
|
|
|
|
// VISIT
|
|
void visit(AstNodeVarRef* nodep) override {
|
|
// Cheaper than relying on emplace().second
|
|
if (nodep->user1SetOnce()) return;
|
|
AstVar* const varp = nodep->varp();
|
|
// Ignore TriggerVec. They are big and read-only in the MTask bodies
|
|
AstBasicDType* const basicp = varp->dtypep()->basicp();
|
|
if (basicp && basicp->isTriggerVec()) return;
|
|
// Set affinity bit
|
|
MTaskIdVec& affinity = m_results
|
|
.emplace(std::piecewise_construct, //
|
|
std::forward_as_tuple(varp), //
|
|
std::forward_as_tuple(m_usedIds))
|
|
.first->second;
|
|
affinity[m_id] = true;
|
|
}
|
|
|
|
void visit(AstCFunc* nodep) override {
|
|
if (nodep->user1SetOnce()) return; // Prevent repeat traversals/recursion
|
|
iterateChildrenConst(nodep);
|
|
}
|
|
|
|
void visit(AstNodeCCall* nodep) override {
|
|
iterateChildrenConst(nodep); // Arguments
|
|
iterateConst(nodep->funcp()); // Callee
|
|
}
|
|
|
|
void visit(AstNode* nodep) override { iterateChildrenConst(nodep); }
|
|
|
|
public:
|
|
static void apply(const ExecMTask* mTaskp, MTaskAffinityMap& results) {
|
|
GatherMTaskAffinity{mTaskp, results};
|
|
}
|
|
};
|
|
|
|
//######################################################################
|
|
// Establish mtask variable sort order in mtasks mode
|
|
|
|
class VarTspSorter final : public V3TSP::TspStateBase {
|
|
// MEMBERS
|
|
const MTaskIdVec& m_mTaskIds; // Mtask we're ordering
|
|
static uint32_t s_serialNext; // Unique ID to establish serial order
|
|
const uint32_t m_serial = ++s_serialNext; // Serial ordering
|
|
public:
|
|
// CONSTRUCTORS
|
|
explicit VarTspSorter(const MTaskIdVec& mTaskIds)
|
|
: m_mTaskIds{mTaskIds} {
|
|
UASSERT(mTaskIds.size() == ExecMTask::numUsedIds(), "Wrong size for MTask ID vector");
|
|
}
|
|
~VarTspSorter() override = default;
|
|
// METHODS
|
|
bool operator<(const TspStateBase& other) const override {
|
|
return operator<(static_cast<const VarTspSorter&>(other));
|
|
}
|
|
bool operator<(const VarTspSorter& other) const { return m_serial < other.m_serial; }
|
|
const MTaskIdVec& mTaskIds() const { return m_mTaskIds; }
|
|
int cost(const TspStateBase* otherp) const override VL_MT_SAFE {
|
|
return cost(static_cast<const VarTspSorter*>(otherp));
|
|
}
|
|
int cost(const VarTspSorter* otherp) const VL_MT_SAFE {
|
|
// Compute the number of MTasks not shared (Hamming distance)
|
|
int cost = 0;
|
|
const size_t size = ExecMTask::numUsedIds();
|
|
for (size_t i = 0; i < size; ++i) cost += m_mTaskIds.at(i) ^ otherp->m_mTaskIds.at(i);
|
|
return cost;
|
|
}
|
|
};
|
|
|
|
uint32_t VarTspSorter::s_serialNext = 0;
|
|
|
|
struct VarAttributes final {
|
|
uint8_t stratum; // Roughly equivalent to alignment requirement, to avoid padding
|
|
bool anonOk; // Can be emitted as part of anonymous structure
|
|
};
|
|
class VariableOrder final {
|
|
std::unordered_map<const AstVar*, VarAttributes> m_attributes;
|
|
|
|
const MTaskAffinityMap& m_mTaskAffinity;
|
|
std::vector<AstVar*>& m_varps;
|
|
|
|
VariableOrder(AstNodeModule* modp, const MTaskAffinityMap& mTaskAffinity,
|
|
std::vector<AstVar*>& varps)
|
|
: m_mTaskAffinity{mTaskAffinity}
|
|
, m_varps{varps} {
|
|
orderModuleVars(modp);
|
|
}
|
|
~VariableOrder() = default;
|
|
VL_UNCOPYABLE(VariableOrder);
|
|
|
|
//######################################################################
|
|
|
|
// Simple sort
|
|
void simpleSortVars(std::vector<AstVar*>& varps) {
|
|
stable_sort(varps.begin(), varps.end(),
|
|
[this](const AstVar* ap, const AstVar* bp) -> bool {
|
|
if (ap->isStatic() != bp->isStatic()) { // Non-statics before statics
|
|
return bp->isStatic();
|
|
}
|
|
UASSERT(m_attributes.find(ap) != m_attributes.end()
|
|
&& m_attributes.find(bp) != m_attributes.end(),
|
|
"m_attributes should be populated for each AstVar");
|
|
const auto& attrA = m_attributes.at(ap);
|
|
const auto& attrB = m_attributes.at(bp);
|
|
if (attrA.anonOk != attrB.anonOk) { // Anons before non-anons
|
|
return attrA.anonOk;
|
|
}
|
|
return attrA.stratum < attrB.stratum; // Finally sort by stratum
|
|
});
|
|
}
|
|
|
|
// Sort by MTask-affinity first, then the same as simpleSortVars
|
|
void tspSortVars(std::vector<AstVar*>& varps) {
|
|
// Map from "MTask affinity" -> "variable list"
|
|
std::map<const MTaskIdVec, std::vector<AstVar*>> m2v;
|
|
const MTaskIdVec emptyVec(ExecMTask::numUsedIds(), false);
|
|
for (AstVar* const varp : varps) {
|
|
const auto it = m_mTaskAffinity.find(varp);
|
|
const MTaskIdVec& key = it == m_mTaskAffinity.end() ? emptyVec : it->second;
|
|
m2v[key].push_back(varp);
|
|
}
|
|
|
|
// Create a TSP sort state for each unique MTaskIdSet, except for the empty set
|
|
V3TSP::StateVec states;
|
|
for (const auto& pair : m2v) {
|
|
const MTaskIdVec& vec = pair.first;
|
|
const bool empty = std::find(vec.begin(), vec.end(), true) == vec.end();
|
|
if (!empty) states.push_back(new VarTspSorter{vec});
|
|
}
|
|
|
|
// Do the TSP sort
|
|
V3TSP::StateVec sortedStates;
|
|
V3TSP::tspSort(states, &sortedStates);
|
|
|
|
varps.clear();
|
|
|
|
// Helper function to sort given vector, then append to 'varps'
|
|
const auto sortAndAppend = [this, &varps](std::vector<AstVar*>& subVarps) {
|
|
simpleSortVars(subVarps);
|
|
for (AstVar* const varp : subVarps) varps.push_back(varp);
|
|
};
|
|
|
|
// Enumerate by sorted MTaskIdSet, sort within the set separately
|
|
for (const V3TSP::TspStateBase* const stateBasep : sortedStates) {
|
|
const VarTspSorter* const statep = dynamic_cast<const VarTspSorter*>(stateBasep);
|
|
sortAndAppend(m2v[statep->mTaskIds()]);
|
|
VL_DO_DANGLING(delete statep, statep);
|
|
}
|
|
|
|
// Finally add the variables with no known MTask affinity
|
|
sortAndAppend(m2v[emptyVec]);
|
|
}
|
|
|
|
void orderModuleVars(AstNodeModule* modp) {
|
|
// Unlink all module variables from the module, compute attributes
|
|
for (AstNode *nodep = modp->stmtsp(), *nextp; nodep; nodep = nextp) {
|
|
nextp = nodep->nextp();
|
|
if (AstVar* const varp = VN_CAST(nodep, Var)) {
|
|
m_varps.push_back(varp);
|
|
|
|
// Compute attributes up front
|
|
// Stratum
|
|
const int sigbytes = varp->dtypeSkipRefp()->widthAlignBytes();
|
|
const uint8_t stratum = (v3Global.opt.hierChild() && varp->isPrimaryIO()) ? 0
|
|
: (varp->isUsedClock() && varp->widthMin() == 1) ? 1
|
|
: VN_IS(varp->dtypeSkipRefp(), UnpackArrayDType) ? 9
|
|
: (varp->basicp() && varp->basicp()->isOpaque()) ? 8
|
|
: (varp->isScBv() || varp->isScBigUint()) ? 7
|
|
: (sigbytes == 8) ? 6
|
|
: (sigbytes == 4) ? 5
|
|
: (sigbytes == 2) ? 3
|
|
: (sigbytes == 1) ? 2
|
|
: 10;
|
|
m_attributes.emplace(varp, VarAttributes{stratum, EmitCBase::isAnonOk(varp)});
|
|
}
|
|
}
|
|
|
|
if (!m_varps.empty()) {
|
|
if (!v3Global.opt.mtasks()) {
|
|
simpleSortVars(m_varps);
|
|
} else {
|
|
tspSortVars(m_varps);
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
static void processModule(AstNodeModule* modp, const MTaskAffinityMap& mTaskAffinity,
|
|
std::vector<AstVar*>& varps) VL_MT_STABLE {
|
|
VariableOrder{modp, mTaskAffinity, varps};
|
|
}
|
|
};
|
|
|
|
//######################################################################
|
|
// V3VariableOrder static functions
|
|
|
|
void V3VariableOrder::orderAll(AstNetlist* netlistp) {
|
|
UINFO(2, __FUNCTION__ << ": " << endl);
|
|
|
|
MTaskAffinityMap mTaskAffinity;
|
|
|
|
// Gather MTask affinities
|
|
if (v3Global.opt.mtasks()) {
|
|
netlistp->topModulep()->foreach([&](AstExecGraph* execGraphp) {
|
|
for (const V3GraphVertex& vtx : execGraphp->depGraphp()->vertices()) {
|
|
GatherMTaskAffinity::apply(vtx.as<const ExecMTask>(), mTaskAffinity);
|
|
}
|
|
});
|
|
}
|
|
if (v3Global.opt.stats()) V3Stats::statsStage("variableorder-gather");
|
|
|
|
// Sort variables for each module
|
|
std::unordered_map<AstNodeModule*, std::vector<AstVar*>> sortedVars;
|
|
{
|
|
V3ThreadScope threadScope;
|
|
|
|
for (AstNodeModule* modp = v3Global.rootp()->modulesp(); modp;
|
|
modp = VN_AS(modp->nextp(), NodeModule)) {
|
|
std::vector<AstVar*>& varps = sortedVars[modp];
|
|
threadScope.enqueue([modp, mTaskAffinity, &varps]() {
|
|
VariableOrder::processModule(modp, mTaskAffinity, varps);
|
|
});
|
|
}
|
|
}
|
|
if (v3Global.opt.stats()) V3Stats::statsStage("variableorder-sort");
|
|
|
|
// Insert them back under the module, in the new order, but at
|
|
// the front of the list so they come out first in dumps/XML.
|
|
for (AstNodeModule* modp = v3Global.rootp()->modulesp(); modp;
|
|
modp = VN_AS(modp->nextp(), NodeModule)) {
|
|
const std::vector<AstVar*>& varps = sortedVars[modp];
|
|
|
|
if (!varps.empty()) {
|
|
auto it = varps.cbegin();
|
|
AstVar* const firstp = *it++;
|
|
firstp->unlinkFrBack();
|
|
for (; it != varps.cend(); ++it) {
|
|
AstVar* const varp = *it;
|
|
varp->unlinkFrBack();
|
|
firstp->addNext(varp);
|
|
}
|
|
if (AstNode* const stmtsp = modp->stmtsp()) {
|
|
stmtsp->unlinkFrBackWithNext();
|
|
AstNode::addNext<AstNode, AstNode>(firstp, stmtsp);
|
|
}
|
|
modp->addStmtsp(firstp);
|
|
}
|
|
}
|
|
|
|
// Done
|
|
V3Global::dumpCheckGlobalTree("variableorder", 0, dumpTreeEitherLevel() >= 3);
|
|
}
|