verilator/src/V3Trace.cpp
Krzysztof Bieganski d012563ab1
Fix tracing with awaits at end of block (#4075) (#4076)
Given an await at the end of a block, e.g. at the end of a loop body, a trace
activity setter was not inserted, as there were no following statements. This
patch makes the activity update unconditional.
2023-03-31 13:51:31 -04:00

929 lines
42 KiB
C++

// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Waves tracing
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2003-2023 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
//
//*************************************************************************
// V3Trace's Transformations:
//
// Examine whole design and build a graph describing which function call
// may result in a write to a traced variable. This is done in 2 passes:
//
// Pass 1:
// Add vertices for TraceDecl, CFunc, CCall and VarRef nodes, add
// edges from CCall -> CFunc, VarRef -> TraceDecl, also add edges
// for public entry points to CFuncs (these are like a spontaneous
// call)
//
// Pass 2:
// Add edges from CFunc -> VarRef being written
//
// Finally:
// Process graph to determine when traced variables can change, allocate
// activity flags, insert nodes to set activity flags, allocate signal
// numbers (codes), and construct the full and incremental trace
// functions, together with all other trace support functions.
//
//*************************************************************************
#include "config_build.h"
#include "verilatedos.h"
#include "V3Trace.h"
#include "V3DupFinder.h"
#include "V3EmitCBase.h"
#include "V3Global.h"
#include "V3Graph.h"
#include "V3Stats.h"
#include <limits>
#include <map>
#include <set>
VL_DEFINE_DEBUG_FUNCTIONS;
//######################################################################
// Graph vertexes
class TraceActivityVertex final : public V3GraphVertex {
AstNode* const m_insertp;
int32_t m_activityCode;
bool m_slow; // If always slow, we can use the same code
public:
enum { ACTIVITY_NEVER = ((1UL << 31) - 1) };
enum { ACTIVITY_ALWAYS = ((1UL << 31) - 2) };
enum { ACTIVITY_SLOW = 0 };
TraceActivityVertex(V3Graph* graphp, AstNode* nodep, bool slow)
: V3GraphVertex{graphp}
, m_insertp{nodep} {
m_activityCode = 0;
m_slow = slow;
}
TraceActivityVertex(V3Graph* graphp, int32_t code)
: V3GraphVertex{graphp}
, m_insertp{nullptr} {
m_activityCode = code;
m_slow = false;
}
~TraceActivityVertex() override = default;
// ACCESSORS
AstNode* insertp() const {
if (!m_insertp) v3fatalSrc("Null insertp; probably called on a special always/slow.");
return m_insertp;
}
string name() const override {
if (activityAlways()) {
return "*ALWAYS*";
} else {
return std::string{slow() ? "*SLOW* " : ""} + insertp()->name();
}
}
string dotColor() const override { return slow() ? "yellowGreen" : "green"; }
int32_t activityCode() const { return m_activityCode; }
bool activityAlways() const { return activityCode() == ACTIVITY_ALWAYS; }
bool activitySlow() const { return activityCode() == ACTIVITY_SLOW; }
void activityCode(int32_t code) { m_activityCode = code; }
bool slow() const { return m_slow; }
void slow(bool flag) {
if (!flag) m_slow = false;
}
};
class TraceCFuncVertex final : public V3GraphVertex {
AstCFunc* const m_nodep;
public:
TraceCFuncVertex(V3Graph* graphp, AstCFunc* nodep)
: V3GraphVertex{graphp}
, m_nodep{nodep} {}
~TraceCFuncVertex() override = default;
// ACCESSORS
AstCFunc* nodep() const { return m_nodep; }
string name() const override { return nodep()->name(); }
string dotColor() const override { return "yellow"; }
FileLine* fileline() const override { return nodep()->fileline(); }
};
class TraceTraceVertex final : public V3GraphVertex {
AstTraceDecl* const m_nodep; // TRACEINC this represents
// nullptr, or other vertex with the real code() that duplicates this one
TraceTraceVertex* m_duplicatep = nullptr;
public:
TraceTraceVertex(V3Graph* graphp, AstTraceDecl* nodep)
: V3GraphVertex{graphp}
, m_nodep{nodep} {}
~TraceTraceVertex() override = default;
// ACCESSORS
AstTraceDecl* nodep() const { return m_nodep; }
string name() const override { return nodep()->name(); }
string dotColor() const override { return "red"; }
FileLine* fileline() const override { return nodep()->fileline(); }
TraceTraceVertex* duplicatep() const { return m_duplicatep; }
void duplicatep(TraceTraceVertex* dupp) {
UASSERT_OBJ(!duplicatep(), nodep(), "Assigning duplicatep() to already duplicated node");
m_duplicatep = dupp;
}
};
class TraceVarVertex final : public V3GraphVertex {
AstVarScope* const m_nodep;
public:
TraceVarVertex(V3Graph* graphp, AstVarScope* nodep)
: V3GraphVertex{graphp}
, m_nodep{nodep} {}
~TraceVarVertex() override = default;
// ACCESSORS
AstVarScope* nodep() const { return m_nodep; }
string name() const override { return nodep()->name(); }
string dotColor() const override { return "skyblue"; }
FileLine* fileline() const override { return nodep()->fileline(); }
};
//######################################################################
// Trace state, as a visitor of each AstNode
class TraceVisitor final : public VNVisitor {
private:
// NODE STATE
// V3Hasher in V3DupFinder
// Ast*::user4() // V3Hasher calculation
// Cleared entire netlist
// AstCFunc::user1() // V3GraphVertex* for this node
// AstTraceDecl::user1() // V3GraphVertex* for this node
// AstVarScope::user1() // V3GraphVertex* for this node
// AstStmtExpr::user2() // bool; walked next list for other ccalls
// Ast*::user3() // TraceActivityVertex* for this node
const VNUser1InUse m_inuser1;
const VNUser2InUse m_inuser2;
const VNUser3InUse m_inuser3;
// VNUser4InUse In V3Hasher via V3DupFinder
// STATE
AstNodeModule* m_topModp = nullptr; // Module to add variables to
AstScope* const m_topScopep = v3Global.rootp()->topScopep()->scopep(); // The top AstScope
AstCFunc* m_cfuncp = nullptr; // C function adding to graph
AstCFunc* m_regFuncp = nullptr; // Trace registration function
AstTraceDecl* m_tracep = nullptr; // Trace function adding to graph
AstVarScope* m_activityVscp = nullptr; // Activity variable
uint32_t m_activityNumber = 0; // Count of fields in activity variable
uint32_t m_code = 0; // Trace ident code# being assigned
V3Graph m_graph; // Var/CFunc tracking
TraceActivityVertex* const m_alwaysVtxp; // "Always trace" vertex
bool m_finding = false; // Pass one of algorithm?
// Trace parallelism. Only VCD tracing can be parallelized at this time.
const uint32_t m_parallelism
= v3Global.opt.useTraceParallel() ? static_cast<uint32_t>(v3Global.opt.threads()) : 1;
VDouble0 m_statUniqSigs; // Statistic tracking
VDouble0 m_statUniqCodes; // Statistic tracking
// All activity numbers applying to a given trace
using ActCodeSet = std::set<uint32_t>;
// For activity set, what traces apply
using TraceVec = std::multimap<ActCodeSet, TraceTraceVertex*>;
// METHODS
void detectDuplicates() {
UINFO(9, "Finding duplicates\n");
// Note uses user4
V3DupFinder dupFinder; // Duplicate code detection
// Hash all of the values the traceIncs need
for (const V3GraphVertex* itp = m_graph.verticesBeginp(); itp;
itp = itp->verticesNextp()) {
if (const TraceTraceVertex* const vvertexp
= dynamic_cast<const TraceTraceVertex*>(itp)) {
const AstTraceDecl* const nodep = vvertexp->nodep();
if (nodep->valuep()) {
UASSERT_OBJ(nodep->valuep()->backp() == nodep, nodep,
"Trace duplicate back needs consistency,"
" so we can map duplicates back to TRACEINCs");
// Just keep one node in the map and point all duplicates to this node
if (dupFinder.findDuplicate(nodep->valuep()) == dupFinder.end()) {
dupFinder.insert(nodep->valuep());
}
}
}
}
// Find if there are any duplicates
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
if (TraceTraceVertex* const vvertexp = dynamic_cast<TraceTraceVertex*>(itp)) {
const AstTraceDecl* const nodep = vvertexp->nodep();
if (nodep->valuep() && !vvertexp->duplicatep()) {
const auto dupit = dupFinder.findDuplicate(nodep->valuep());
if (dupit != dupFinder.end()) {
const AstTraceDecl* const dupDeclp
= VN_AS(dupit->second->backp(), TraceDecl);
UASSERT_OBJ(dupDeclp, nodep, "Trace duplicate of wrong type");
TraceTraceVertex* const dupvertexp
= dynamic_cast<TraceTraceVertex*>(dupDeclp->user1u().toGraphVertex());
UINFO(8, " Orig " << nodep << endl);
UINFO(8, " dup " << dupDeclp << endl);
// Mark the hashed node as the original and our
// iterating node as duplicated
vvertexp->duplicatep(dupvertexp);
}
}
}
}
}
void graphSimplify(bool initial) {
if (initial) {
// Remove all variable nodes
for (V3GraphVertex *nextp, *itp = m_graph.verticesBeginp(); itp; itp = nextp) {
nextp = itp->verticesNextp();
if (TraceVarVertex* const vvertexp = dynamic_cast<TraceVarVertex*>(itp)) {
vvertexp->rerouteEdges(&m_graph);
vvertexp->unlinkDelete(&m_graph);
}
}
// Remove multiple variables connecting funcs to traces
// We do this twice, as then we have fewer edges to multiply out in the below
// expansion.
m_graph.removeRedundantEdges(&V3GraphEdge::followAlwaysTrue);
// Remove all Cfunc nodes
for (V3GraphVertex *nextp, *itp = m_graph.verticesBeginp(); itp; itp = nextp) {
nextp = itp->verticesNextp();
if (TraceCFuncVertex* const vvertexp = dynamic_cast<TraceCFuncVertex*>(itp)) {
vvertexp->rerouteEdges(&m_graph);
vvertexp->unlinkDelete(&m_graph);
}
}
}
// Remove multiple variables connecting funcs to traces
m_graph.removeRedundantEdges(&V3GraphEdge::followAlwaysTrue);
// If there are any edges from a always, keep only the always
for (const V3GraphVertex* itp = m_graph.verticesBeginp(); itp;
itp = itp->verticesNextp()) {
if (const TraceTraceVertex* const vvertexp
= dynamic_cast<const TraceTraceVertex*>(itp)) {
// Search for the incoming always edge
const V3GraphEdge* alwaysEdgep = nullptr;
for (const V3GraphEdge* edgep = vvertexp->inBeginp(); edgep;
edgep = edgep->inNextp()) {
const TraceActivityVertex* const actVtxp
= dynamic_cast<const TraceActivityVertex*>(edgep->fromp());
UASSERT_OBJ(actVtxp, vvertexp->nodep(),
"Tracing a node with FROM non activity");
if (actVtxp->activityAlways()) {
alwaysEdgep = edgep;
break;
}
}
// If always edge exists, remove all other edges
if (alwaysEdgep) {
for (V3GraphEdge *nextp, *edgep = vvertexp->inBeginp(); edgep; edgep = nextp) {
nextp = edgep->inNextp();
if (edgep != alwaysEdgep) edgep->unlinkDelete();
}
}
}
}
// Activity points with no outputs can be removed
for (V3GraphVertex *nextp, *itp = m_graph.verticesBeginp(); itp; itp = nextp) {
nextp = itp->verticesNextp();
if (TraceActivityVertex* const vtxp = dynamic_cast<TraceActivityVertex*>(itp)) {
// Leave in the always vertex for later use.
if (vtxp != m_alwaysVtxp && !vtxp->outBeginp()) vtxp->unlinkDelete(&m_graph);
}
}
}
uint32_t assignactivityNumbers() {
uint32_t activityNumber = 1; // Note 0 indicates "slow" only
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
if (TraceActivityVertex* const vvertexp = dynamic_cast<TraceActivityVertex*>(itp)) {
if (vvertexp != m_alwaysVtxp) {
if (vvertexp->slow()) {
vvertexp->activityCode(TraceActivityVertex::ACTIVITY_SLOW);
} else {
vvertexp->activityCode(activityNumber++);
}
}
}
}
return activityNumber;
}
void sortTraces(TraceVec& traces, uint32_t& nFullCodes, uint32_t& nChgCodes) {
// Populate sort structure
traces.clear();
nFullCodes = 0;
nChgCodes = 0;
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
if (TraceTraceVertex* const vtxp = dynamic_cast<TraceTraceVertex*>(itp)) {
ActCodeSet actSet;
UINFO(9, " Add to sort: " << vtxp << endl);
if (debug() >= 9) vtxp->nodep()->dumpTree("- trnode: ");
for (const V3GraphEdge* edgep = vtxp->inBeginp(); edgep;
edgep = edgep->inNextp()) {
const TraceActivityVertex* const cfvertexp
= dynamic_cast<const TraceActivityVertex*>(edgep->fromp());
UASSERT_OBJ(cfvertexp, vtxp->nodep(),
"Should have been function pointing to this trace");
UINFO(9, " Activity: " << cfvertexp << endl);
if (cfvertexp->activityAlways()) {
// If code 0, we always trace; ignore other codes
actSet.insert(TraceActivityVertex::ACTIVITY_ALWAYS);
} else {
actSet.insert(cfvertexp->activityCode());
}
}
UASSERT_OBJ(actSet.count(TraceActivityVertex::ACTIVITY_ALWAYS) == 0
|| actSet.size() == 1,
vtxp->nodep(), "Always active trace has further triggers");
// Count nodes
if (!vtxp->duplicatep()) {
const uint32_t inc = vtxp->nodep()->codeInc();
nFullCodes += inc;
if (!actSet.empty()) nChgCodes += inc;
}
if (actSet.empty()) {
// If a trace doesn't have activity, it's constant, and we
// don't need to track changes on it.
actSet.insert(TraceActivityVertex::ACTIVITY_NEVER);
} else if (actSet.count(TraceActivityVertex::ACTIVITY_SLOW) && actSet.size() > 1) {
// If a trace depends on the slow flag as well as other
// flags, remove the dependency on the slow flag. We will
// make slow routines set all activity flags.
actSet.erase(TraceActivityVertex::ACTIVITY_SLOW);
}
traces.emplace(actSet, vtxp);
}
}
}
void graphOptimize() {
// Assign initial activity numbers to activity vertices
assignactivityNumbers();
// Sort the traces by activity sets
TraceVec traces;
uint32_t unused1;
uint32_t unused2;
sortTraces(traces, unused1, unused2);
// For each activity set with only a small number of signals, make those
// signals always traced, as it's cheaper to check a few value changes
// than to test a lot of activity flags
auto it = traces.begin();
const auto end = traces.end();
while (it != end) {
auto head = it;
// Approximate the complexity of the value change check
uint32_t complexity = 0;
const ActCodeSet& actSet = it->first;
for (; it != end && it->first == actSet; ++it) {
if (!it->second->duplicatep()) {
uint32_t cost = 0;
const AstTraceDecl* const declp = it->second->nodep();
// The number of comparisons required by bufp->chg*
cost += declp->isWide() ? declp->codeInc() : 1;
// Arrays are traced by element
cost *= declp->arrayRange().ranged() ? declp->arrayRange().elements() : 1;
// Note: Experiments factoring in the size of declp->valuep()
// showed no benefit in tracing speed, even for large trees,
// so we will leave those out for now.
complexity += cost;
}
}
// Leave alone always changing, never changing and signals only set in slow code
if (actSet.count(TraceActivityVertex::ACTIVITY_ALWAYS)) continue;
if (actSet.count(TraceActivityVertex::ACTIVITY_NEVER)) continue;
if (actSet.count(TraceActivityVertex::ACTIVITY_SLOW)) continue;
// If the value comparisons are cheaper to perform than checking the
// activity flags make the signals always traced. Note this cost
// equation is heuristic.
if (complexity <= actSet.size() * 2) {
for (; head != it; ++head) {
new V3GraphEdge{&m_graph, m_alwaysVtxp, head->second, 1};
}
}
}
graphSimplify(false);
}
AstNodeExpr* selectActivity(FileLine* flp, uint32_t acode, const VAccess& access) {
return new AstArraySel(flp, new AstVarRef{flp, m_activityVscp, access}, acode);
}
void addActivitySetter(AstNode* insertp, uint32_t code) {
FileLine* const fl = insertp->fileline();
AstAssign* const setterp = new AstAssign{fl, selectActivity(fl, code, VAccess::WRITE),
new AstConst{fl, AstConst::BitTrue{}}};
if (AstStmtExpr* const stmtp = VN_CAST(insertp, StmtExpr)) {
stmtp->addNextHere(setterp);
} else if (AstCFunc* const funcp = VN_CAST(insertp, CFunc)) {
// If there are awaits, insert the setter after each await
if (funcp->isCoroutine() && funcp->stmtsp()) {
funcp->stmtsp()->foreachAndNext([&](AstCAwait* awaitp) {
AstNode* stmtp = awaitp->backp();
while (VN_IS(stmtp, NodeExpr)) stmtp = stmtp->backp();
stmtp->addNextHere(setterp->cloneTree(false));
});
}
funcp->addStmtsp(setterp);
} else {
insertp->v3fatalSrc("Bad trace activity vertex");
}
}
void createActivityFlags() {
// Assign final activity numbers
m_activityNumber = assignactivityNumbers();
// Create an array of bytes, not a bit vector, as they can be set
// atomically by mtasks, and are cheaper to set (no need for
// read-modify-write on the C type), and the speed of the tracing code
// is the same on largish designs.
FileLine* const flp = m_topScopep->fileline();
AstNodeDType* const newScalarDtp = new AstBasicDType{flp, VFlagBitPacked{}, 1};
v3Global.rootp()->typeTablep()->addTypesp(newScalarDtp);
AstRange* const newArange
= new AstRange{flp, VNumRange{static_cast<int>(m_activityNumber) - 1, 0}};
AstNodeDType* const newArrDtp = new AstUnpackArrayDType{flp, newScalarDtp, newArange};
v3Global.rootp()->typeTablep()->addTypesp(newArrDtp);
AstVar* const newvarp
= new AstVar{flp, VVarType::MODULETEMP, "__Vm_traceActivity", newArrDtp};
m_topModp->addStmtsp(newvarp);
AstVarScope* const newvscp = new AstVarScope{flp, m_topScopep, newvarp};
m_topScopep->addVarsp(newvscp);
m_activityVscp = newvscp;
// Insert activity setters
for (const V3GraphVertex* itp = m_graph.verticesBeginp(); itp;
itp = itp->verticesNextp()) {
if (const TraceActivityVertex* const vtxp
= dynamic_cast<const TraceActivityVertex*>(itp)) {
if (vtxp->activitySlow()) {
// Just set all flags in slow code as it should be rare.
// This will be rolled up into a loop by V3Reloop.
for (uint32_t code = 0; code < m_activityNumber; ++code) {
addActivitySetter(vtxp->insertp(), code);
}
} else if (!vtxp->activityAlways()) {
addActivitySetter(vtxp->insertp(), vtxp->activityCode());
}
}
}
}
AstCFunc* newCFunc(bool full, AstCFunc* topFuncp, int& funcNump, uint32_t baseCode = 0) {
// Create new function
const bool isTopFunc = topFuncp == nullptr;
const string baseName = full && isTopFunc ? "trace_full_top_"
: full ? "trace_full_sub_"
: isTopFunc ? "trace_chg_top_"
: "trace_chg_sub_";
FileLine* const flp = m_topScopep->fileline();
AstCFunc* const funcp = new AstCFunc{flp, baseName + cvtToStr(funcNump++), m_topScopep};
funcp->isTrace(true);
funcp->dontCombine(true);
funcp->isLoose(true);
funcp->slow(full);
funcp->isStatic(isTopFunc);
// Add it to top scope
m_topScopep->addBlocksp(funcp);
const auto addInitStr = [funcp, flp](const string& str) -> void {
funcp->addInitsp(new AstCStmt{flp, str});
};
if (isTopFunc) {
// Top functions
funcp->argTypes("void* voidSelf, " + v3Global.opt.traceClassBase()
+ "::" + (v3Global.opt.useTraceOffload() ? "OffloadBuffer" : "Buffer")
+ "* bufp");
addInitStr(EmitCBase::voidSelfAssign(m_topModp));
addInitStr(EmitCBase::symClassAssign());
// Add global activity check to change dump functions
if (!full) { //
addInitStr("if (VL_UNLIKELY(!vlSymsp->__Vm_activity)) return;\n");
}
// Register function
if (full) {
m_regFuncp->addStmtsp(new AstText{flp, "tracep->addFullCb(", true});
} else {
m_regFuncp->addStmtsp(new AstText{flp, "tracep->addChgCb(", true});
}
m_regFuncp->addStmtsp(new AstAddrOfCFunc{flp, funcp});
m_regFuncp->addStmtsp(new AstText{flp, ", vlSelf", true});
m_regFuncp->addStmtsp(new AstText{flp, ");\n", true});
} else {
// Sub functions
funcp->argTypes(v3Global.opt.traceClassBase()
+ "::" + +(v3Global.opt.useTraceOffload() ? "OffloadBuffer" : "Buffer")
+ "* bufp");
// Setup base references. Note in rare occasions we can end up with an empty trace
// sub function, hence the VL_ATTR_UNUSED attributes.
if (full) {
// Full dump sub function
addInitStr("uint32_t* const oldp VL_ATTR_UNUSED = "
"bufp->oldp(vlSymsp->__Vm_baseCode);\n");
} else {
// Change dump sub function
if (v3Global.opt.useTraceOffload()) {
addInitStr("const uint32_t base VL_ATTR_UNUSED = "
"vlSymsp->__Vm_baseCode + "
+ cvtToStr(baseCode) + ";\n");
addInitStr("if (false && bufp) {} // Prevent unused\n");
} else {
addInitStr("uint32_t* const oldp VL_ATTR_UNUSED = "
"bufp->oldp(vlSymsp->__Vm_baseCode + "
+ cvtToStr(baseCode) + ");\n");
}
}
// Add call to top function
AstCCall* const callp = new AstCCall{funcp->fileline(), funcp};
callp->dtypeSetVoid();
callp->argTypes("bufp");
topFuncp->addStmtsp(callp->makeStmt());
}
// Done
UINFO(5, " newCFunc " << funcp << endl);
return funcp;
}
void createFullTraceFunction(const TraceVec& traces, uint32_t nAllCodes,
uint32_t parallelism) {
const int splitLimit = v3Global.opt.outputSplitCTrace() ? v3Global.opt.outputSplitCTrace()
: std::numeric_limits<int>::max();
int topFuncNum = 0;
int subFuncNum = 0;
auto it = traces.cbegin();
while (it != traces.cend()) {
AstCFunc* topFuncp = nullptr;
AstCFunc* subFuncp = nullptr;
int subStmts = 0;
const uint32_t maxCodes = (nAllCodes + parallelism - 1) / parallelism;
uint32_t nCodes = 0;
for (; nCodes < maxCodes && it != traces.end(); ++it) {
const TraceTraceVertex* const vtxp = it->second;
AstTraceDecl* const declp = vtxp->nodep();
if (const TraceTraceVertex* const canonVtxp = vtxp->duplicatep()) {
// This is a duplicate trace node. We will assign the signal
// number to the canonical node, and emit this as an alias, so
// no need to create a TraceInc node.
const AstTraceDecl* const canonDeclp = canonVtxp->nodep();
UASSERT_OBJ(!canonVtxp->duplicatep(), canonDeclp,
"Canonical node is a duplicate");
UASSERT_OBJ(canonDeclp->code() != 0, canonDeclp,
"Canonical node should have code assigned already");
declp->code(canonDeclp->code());
} else {
// This is a canonical trace node. Assign signal number and
// add a TraceInc node to the full dump function.
UASSERT_OBJ(declp->code() == 0, declp,
"Canonical node should not have code assigned yet");
declp->code(m_code);
m_code += declp->codeInc();
m_statUniqCodes += declp->codeInc();
++m_statUniqSigs;
// Create top function if not yet created
if (!topFuncp) { topFuncp = newCFunc(/* full: */ true, nullptr, topFuncNum); }
// Crate new sub function if required
if (!subFuncp || subStmts > splitLimit) {
subStmts = 0;
subFuncp = newCFunc(/* full: */ true, topFuncp, subFuncNum);
}
// Add TraceInc node
AstTraceInc* const incp
= new AstTraceInc{declp->fileline(), declp, /* full: */ true};
subFuncp->addStmtsp(incp);
subStmts += incp->nodeCount();
// Track partitioning
nCodes += declp->codeInc();
}
}
if (topFuncp) { // might be nullptr if all trailing entries were duplicates
UINFO(5, "trace_full_top" << topFuncNum - 1 << " codes: " << nCodes << "/"
<< maxCodes << endl);
}
}
}
void createChgTraceFunctions(const TraceVec& traces, uint32_t nAllCodes,
uint32_t parallelism) {
const int splitLimit = v3Global.opt.outputSplitCTrace() ? v3Global.opt.outputSplitCTrace()
: std::numeric_limits<int>::max();
int topFuncNum = 0;
int subFuncNum = 0;
TraceVec::const_iterator it = traces.begin();
while (it != traces.end()) {
AstCFunc* topFuncp = nullptr;
AstCFunc* subFuncp = nullptr;
int subStmts = 0;
uint32_t maxCodes = (nAllCodes + parallelism - 1) / parallelism;
if (maxCodes < 1) maxCodes = 1;
uint32_t nCodes = 0;
const ActCodeSet* prevActSet = nullptr;
AstIf* ifp = nullptr;
uint32_t baseCode = 0;
for (; nCodes < maxCodes && it != traces.end(); ++it) {
const TraceTraceVertex* const vtxp = it->second;
// This is a duplicate decl, no need to add it to incremental dump
if (vtxp->duplicatep()) continue;
const ActCodeSet& actSet = it->first;
// Traced value never changes, no need to add it to incremental dump
if (actSet.count(TraceActivityVertex::ACTIVITY_NEVER)) continue;
AstTraceDecl* const declp = vtxp->nodep();
// Create top function if not yet created
if (!topFuncp) { topFuncp = newCFunc(/* full: */ false, nullptr, topFuncNum); }
// Create new sub function if required
if (!subFuncp || subStmts > splitLimit) {
baseCode = declp->code();
subStmts = 0;
subFuncp = newCFunc(/* full: */ false, topFuncp, subFuncNum, baseCode);
prevActSet = nullptr;
ifp = nullptr;
}
// If required, create the conditional node checking the activity flags
if (!prevActSet || actSet != *prevActSet) {
FileLine* const flp = m_topScopep->fileline();
const bool always = actSet.count(TraceActivityVertex::ACTIVITY_ALWAYS) != 0;
AstNodeExpr* condp = nullptr;
if (always) {
condp = new AstConst{flp, 1}; // Always true, will be folded later
} else {
for (const uint32_t actCode : actSet) {
AstNodeExpr* const selp = selectActivity(flp, actCode, VAccess::READ);
condp = condp ? new AstOr{flp, condp, selp} : selp;
}
}
ifp = new AstIf{flp, condp};
if (!always) ifp->branchPred(VBranchPred::BP_UNLIKELY);
subFuncp->addStmtsp(ifp);
subStmts += ifp->nodeCount();
prevActSet = &actSet;
}
// Add TraceInc node
AstTraceInc* const incp
= new AstTraceInc{declp->fileline(), declp, /* full: */ false, baseCode};
ifp->addThensp(incp);
subStmts += incp->nodeCount();
// Track partitioning
nCodes += declp->codeInc();
}
if (topFuncp) { // might be nullptr if all trailing entries were duplicates/constants
UINFO(5, "trace_chg_top" << topFuncNum - 1 << " codes: " << nCodes << "/"
<< maxCodes << endl);
}
}
}
void createCleanupFunction() {
FileLine* const fl = m_topScopep->fileline();
AstCFunc* const cleanupFuncp = new AstCFunc{fl, "trace_cleanup", m_topScopep};
cleanupFuncp->argTypes("void* voidSelf, " + v3Global.opt.traceClassBase()
+ "* /*unused*/");
cleanupFuncp->isTrace(true);
cleanupFuncp->slow(false);
cleanupFuncp->isStatic(true);
cleanupFuncp->isLoose(true);
m_topScopep->addBlocksp(cleanupFuncp);
cleanupFuncp->addInitsp(new AstCStmt{fl, EmitCBase::voidSelfAssign(m_topModp)});
cleanupFuncp->addInitsp(new AstCStmt{fl, EmitCBase::symClassAssign()});
// Register it
m_regFuncp->addStmtsp(new AstText{fl, "tracep->addCleanupCb(", true});
m_regFuncp->addStmtsp(new AstAddrOfCFunc{fl, cleanupFuncp});
m_regFuncp->addStmtsp(new AstText{fl, ", vlSelf);\n", true});
// Clear global activity flag
cleanupFuncp->addStmtsp(new AstCStmt{m_topScopep->fileline(),
std::string{"vlSymsp->__Vm_activity = false;\n"}});
// Clear fine grained activity flags
for (uint32_t i = 0; i < m_activityNumber; ++i) {
AstNode* const clrp = new AstAssign{fl, selectActivity(fl, i, VAccess::WRITE),
new AstConst{fl, AstConst::BitFalse{}}};
cleanupFuncp->addStmtsp(clrp);
}
}
void createTraceFunctions() {
// Detect and remove duplicate values
detectDuplicates();
// Simplify & optimize the graph
if (dumpGraph() >= 6) m_graph.dumpDotFilePrefixed("trace_pre");
graphSimplify(true);
if (dumpGraph() >= 6) m_graph.dumpDotFilePrefixed("trace_simplified");
graphOptimize();
if (dumpGraph() >= 6) m_graph.dumpDotFilePrefixed("trace_optimized");
// Create the fine grained activity flags
createActivityFlags();
// Form a sorted list of the traces we are interested in
TraceVec traces; // The sorted traces
// We will split functions such that each have to dump roughly the same amount of data
// for this we need to keep tack of the number of codes used by the trace functions.
uint32_t nFullCodes = 0; // Number of non-duplicate codes (need to go into full* dump)
uint32_t nChgCodes = 0; // Number of non-constant codes (need to go in to chg* dump)
sortTraces(traces, nFullCodes, nChgCodes);
UINFO(5, "nFullCodes: " << nFullCodes << " nChgCodes: " << nChgCodes << endl);
// Our keys are now sorted to have same activity number adjacent, then
// by trace order. (Better would be execution order for cache
// efficiency....) Last are constants and non-changers, as then the
// last value vector is more compact
// Create the trace registration function
m_regFuncp = new AstCFunc{m_topScopep->fileline(), "trace_register", m_topScopep};
m_regFuncp->argTypes(v3Global.opt.traceClassBase() + "* tracep");
m_regFuncp->isTrace(true);
m_regFuncp->slow(true);
m_regFuncp->isStatic(false);
m_regFuncp->isLoose(true);
m_topScopep->addBlocksp(m_regFuncp);
// Create the full dump functions, also allocates signal numbers
createFullTraceFunction(traces, nFullCodes, m_parallelism);
// Create the incremental dump functions
createChgTraceFunctions(traces, nChgCodes, m_parallelism);
// Remove refs to traced values from TraceDecl nodes, these have now moved under
// TraceInc
for (const auto& i : traces) {
AstNode* const valuep = i.second->nodep()->valuep();
valuep->unlinkFrBack();
valuep->deleteTree();
}
// Create the trace cleanup function clearing the activity flags
createCleanupFunction();
}
TraceCFuncVertex* getCFuncVertexp(AstCFunc* nodep) {
TraceCFuncVertex* vertexp
= dynamic_cast<TraceCFuncVertex*>(nodep->user1u().toGraphVertex());
if (!vertexp) {
vertexp = new TraceCFuncVertex{&m_graph, nodep};
nodep->user1p(vertexp);
}
return vertexp;
}
TraceActivityVertex* getActivityVertexp(AstNode* nodep, bool slow) {
TraceActivityVertex* vertexp
= dynamic_cast<TraceActivityVertex*>(nodep->user3u().toGraphVertex());
if (!vertexp) {
vertexp = new TraceActivityVertex{&m_graph, nodep, slow};
nodep->user3p(vertexp);
}
vertexp->slow(slow);
return vertexp;
}
// VISITORS
void visit(AstNetlist* nodep) override {
m_code = 1; // Multiple TopScopes will require fixing how code#s
// are assigned as duplicate varscopes must result in the same tracing code#.
// Add vertexes for all TraceDecl, and edges from VARs each trace looks at
m_finding = false;
iterateChildren(nodep);
// Add vertexes for all CFUNCs, and edges to VARs the func sets
m_finding = true;
iterateChildren(nodep);
// Create the trace functions and insert them into the tree
createTraceFunctions();
}
void visit(AstNodeModule* nodep) override {
if (nodep->isTop()) m_topModp = nodep;
iterateChildren(nodep);
}
void visit(AstStmtExpr* nodep) override {
if (!m_finding && !nodep->user2()) {
if (AstCCall* const callp = VN_CAST(nodep->exprp(), CCall)) {
UINFO(8, " CCALL " << callp << endl);
// See if there are other calls in same statement list;
// If so, all funcs might share the same activity code
TraceActivityVertex* const activityVtxp
= getActivityVertexp(nodep, callp->funcp()->slow());
for (AstNode* nextp = nodep; nextp; nextp = nextp->nextp()) {
if (AstStmtExpr* const stmtp = VN_CAST(nextp, StmtExpr)) {
if (AstCCall* const ccallp = VN_CAST(stmtp->exprp(), CCall)) {
stmtp->user2(true); // Processed
UINFO(8, " SubCCALL " << ccallp << endl);
V3GraphVertex* const ccallFuncVtxp = getCFuncVertexp(ccallp->funcp());
activityVtxp->slow(ccallp->funcp()->slow());
new V3GraphEdge{&m_graph, activityVtxp, ccallFuncVtxp, 1};
}
}
}
}
}
iterateChildren(nodep);
}
void visit(AstCFunc* nodep) override {
UINFO(8, " CFUNC " << nodep << endl);
V3GraphVertex* const funcVtxp = getCFuncVertexp(nodep);
if (!m_finding) { // If public, we need a unique activity code to allow for sets
// directly in this func
if (nodep->funcPublic() || nodep->dpiExportImpl() || nodep == v3Global.rootp()->evalp()
|| nodep->isCoroutine()) {
// Cannot treat a coroutine as slow, it may be resumed later
const bool slow = nodep->slow() && !nodep->isCoroutine();
V3GraphVertex* const activityVtxp = getActivityVertexp(nodep, slow);
new V3GraphEdge{&m_graph, activityVtxp, funcVtxp, 1};
}
}
VL_RESTORER(m_cfuncp);
{
m_cfuncp = nodep;
iterateChildren(nodep);
}
}
void visit(AstTraceDecl* nodep) override {
UINFO(8, " TRACE " << nodep << endl);
if (!m_finding) {
V3GraphVertex* const vertexp = new TraceTraceVertex{&m_graph, nodep};
nodep->user1p(vertexp);
UASSERT_OBJ(m_cfuncp, nodep, "Trace not under func");
m_tracep = nodep;
iterateChildren(nodep);
m_tracep = nullptr;
}
}
void visit(AstVarRef* nodep) override {
if (m_tracep) {
UASSERT_OBJ(nodep->varScopep(), nodep, "No var scope?");
UASSERT_OBJ(nodep->access().isReadOnly(), nodep, "Lvalue in trace? Should be const.");
V3GraphVertex* varVtxp = nodep->varScopep()->user1u().toGraphVertex();
if (!varVtxp) {
varVtxp = new TraceVarVertex{&m_graph, nodep->varScopep()};
nodep->varScopep()->user1p(varVtxp);
}
V3GraphVertex* const traceVtxp = m_tracep->user1u().toGraphVertex();
new V3GraphEdge{&m_graph, varVtxp, traceVtxp, 1};
if (nodep->varp()->isPrimaryInish() // Always need to trace primary inputs
|| nodep->varp()->isSigPublic()) { // Or ones user can change
new V3GraphEdge{&m_graph, m_alwaysVtxp, traceVtxp, 1};
}
} else if (m_cfuncp && m_finding && nodep->access().isWriteOrRW()) {
UASSERT_OBJ(nodep->varScopep(), nodep, "No var scope?");
V3GraphVertex* const funcVtxp = getCFuncVertexp(m_cfuncp);
V3GraphVertex* const varVtxp = nodep->varScopep()->user1u().toGraphVertex();
if (varVtxp) { // else we're not tracing this signal
new V3GraphEdge{&m_graph, funcVtxp, varVtxp, 1};
}
}
}
//--------------------
void visit(AstNode* nodep) override { iterateChildren(nodep); }
public:
// CONSTRUCTORS
explicit TraceVisitor(AstNetlist* nodep)
: m_alwaysVtxp{new TraceActivityVertex{&m_graph, TraceActivityVertex::ACTIVITY_ALWAYS}} {
iterate(nodep);
}
~TraceVisitor() override {
V3Stats::addStat("Tracing, Unique traced signals", m_statUniqSigs);
V3Stats::addStat("Tracing, Unique trace codes", m_statUniqCodes);
}
};
//######################################################################
// Trace class functions
void V3Trace::traceAll(AstNetlist* nodep) {
UINFO(2, __FUNCTION__ << ": " << endl);
{ TraceVisitor{nodep}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("trace", 0, dumpTree() >= 3);
}