Separate tracing of const values from non-const values

Some values emitted to the trace files are constant (e.g.: actual
parameter values), and never change. Previously we used to trace these
in the 'full' dumps, which also included all other truly variable
signals. This patch introduces a new generated trace function 'const',
to complement the 'full' and 'chg' flavour, and 'const' now only
contains the constant signals, while 'full' and 'chg' contain only the
truly variable signals. The generate 'full' and 'chg' trace functions
now have exactly the same shape. Note that 'const' signals are still
traced using the 'full*' dump methods of the trace buffers, so there is
no need for a third flavour of those.
This commit is contained in:
Geza Lore 2023-10-23 11:36:24 +01:00
parent 774c10396c
commit 165a2ef1b8
8 changed files with 188 additions and 126 deletions

View File

@ -104,6 +104,7 @@ void VerilatedFst::open(const char* filename) VL_MT_SAFE_EXCLUDES(m_mutex) {
fstWriterSetPackType(m_fst, FST_WR_PT_LZ4); fstWriterSetPackType(m_fst, FST_WR_PT_LZ4);
fstWriterSetTimescaleFromString(m_fst, timeResStr().c_str()); // lintok-begin-on-ref fstWriterSetTimescaleFromString(m_fst, timeResStr().c_str()); // lintok-begin-on-ref
if (m_useFstWriterThread) fstWriterSetParallelMode(m_fst, 1); if (m_useFstWriterThread) fstWriterSetParallelMode(m_fst, 1);
constDump(true); // First dump must contain the const signals
fullDump(true); // First dump must be full for fst fullDump(true); // First dump must be full for fst
m_curScope.clear(); m_curScope.clear();

View File

@ -212,11 +212,14 @@ protected:
private: private:
std::vector<bool> m_sigs_enabledVec; // Staging for m_sigs_enabledp std::vector<bool> m_sigs_enabledVec; // Staging for m_sigs_enabledp
std::vector<CallbackRecord> m_initCbs; // Routines to initialize tracing std::vector<CallbackRecord> m_initCbs; // Routines to initialize tracing
std::vector<CallbackRecord> m_constCbs; // Routines to perform const dump
std::vector<CallbackRecord> m_constOffloadCbs; // Routines to perform offloaded const dump
std::vector<CallbackRecord> m_fullCbs; // Routines to perform full dump std::vector<CallbackRecord> m_fullCbs; // Routines to perform full dump
std::vector<CallbackRecord> m_fullOffloadCbs; // Routines to perform offloaded full dump std::vector<CallbackRecord> m_fullOffloadCbs; // Routines to perform offloaded full dump
std::vector<CallbackRecord> m_chgCbs; // Routines to perform incremental dump std::vector<CallbackRecord> m_chgCbs; // Routines to perform incremental dump
std::vector<CallbackRecord> m_chgOffloadCbs; // Routines to perform offloaded incremental dump std::vector<CallbackRecord> m_chgOffloadCbs; // Routines to perform offloaded incremental dump
std::vector<CallbackRecord> m_cleanupCbs; // Routines to call at the end of dump std::vector<CallbackRecord> m_cleanupCbs; // Routines to call at the end of dump
bool m_constDump = true; // Whether a const dump is required on the next call to 'dump'
bool m_fullDump = true; // Whether a full dump is required on the next call to 'dump' bool m_fullDump = true; // Whether a full dump is required on the next call to 'dump'
uint32_t m_nextCode = 0; // Next code number to assign uint32_t m_nextCode = 0; // Next code number to assign
uint32_t m_numSignals = 0; // Number of distinct signals uint32_t m_numSignals = 0; // Number of distinct signals
@ -289,6 +292,7 @@ protected:
uint32_t nextCode() const { return m_nextCode; } uint32_t nextCode() const { return m_nextCode; }
uint32_t numSignals() const { return m_numSignals; } uint32_t numSignals() const { return m_numSignals; }
uint32_t maxBits() const { return m_maxBits; } uint32_t maxBits() const { return m_maxBits; }
void constDump(bool value) { m_constDump = value; }
void fullDump(bool value) { m_fullDump = value; } void fullDump(bool value) { m_fullDump = value; }
double timeRes() const { return m_timeRes; } double timeRes() const { return m_timeRes; }
@ -359,6 +363,8 @@ public:
void addModel(VerilatedModel*) VL_MT_SAFE_EXCLUDES(m_mutex); void addModel(VerilatedModel*) VL_MT_SAFE_EXCLUDES(m_mutex);
void addInitCb(initCb_t cb, void* userp) VL_MT_SAFE; void addInitCb(initCb_t cb, void* userp) VL_MT_SAFE;
void addConstCb(dumpCb_t cb, void* userp) VL_MT_SAFE;
void addConstCb(dumpOffloadCb_t cb, void* userp) VL_MT_SAFE;
void addFullCb(dumpCb_t cb, void* userp) VL_MT_SAFE; void addFullCb(dumpCb_t cb, void* userp) VL_MT_SAFE;
void addFullCb(dumpOffloadCb_t cb, void* userp) VL_MT_SAFE; void addFullCb(dumpOffloadCb_t cb, void* userp) VL_MT_SAFE;
void addChgCb(dumpCb_t cb, void* userp) VL_MT_SAFE; void addChgCb(dumpCb_t cb, void* userp) VL_MT_SAFE;

View File

@ -610,6 +610,15 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::dump(uint64_t timeui) VL_MT_SAFE_EXCLUD
} }
} }
if (VL_UNLIKELY(m_constDump)) {
m_constDump = false;
if (offload()) {
runOffloadedCallbacks(m_constOffloadCbs);
} else {
runCallbacks(m_constCbs);
}
}
for (uint32_t i = 0; i < m_cleanupCbs.size(); ++i) { for (uint32_t i = 0; i < m_cleanupCbs.size(); ++i) {
const CallbackRecord& cbr = m_cleanupCbs[i]; const CallbackRecord& cbr = m_cleanupCbs[i];
cbr.m_cleanupCb(cbr.m_userp, self()); cbr.m_cleanupCb(cbr.m_userp, self());
@ -695,6 +704,14 @@ void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addInitCb(initCb_t cb, void* userp) VL_
addCallbackRecord(m_initCbs, CallbackRecord{cb, userp}); addCallbackRecord(m_initCbs, CallbackRecord{cb, userp});
} }
template <> template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addConstCb(dumpCb_t cb, void* userp) VL_MT_SAFE {
addCallbackRecord(m_constCbs, CallbackRecord{cb, userp});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addConstCb(dumpOffloadCb_t cb, void* userp) VL_MT_SAFE {
addCallbackRecord(m_constOffloadCbs, CallbackRecord{cb, userp});
}
template <>
void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addFullCb(dumpCb_t cb, void* userp) VL_MT_SAFE { void VerilatedTrace<VL_SUB_T, VL_BUF_T>::addFullCb(dumpCb_t cb, void* userp) VL_MT_SAFE {
addCallbackRecord(m_fullCbs, CallbackRecord{cb, userp}); addCallbackRecord(m_fullCbs, CallbackRecord{cb, userp});
} }

View File

@ -164,6 +164,7 @@ void VerilatedVcd::openNextImp(bool incFilename) {
} }
} }
m_isOpen = true; m_isOpen = true;
constDump(true); // First dump must containt the const signals
fullDump(true); // First dump must be full fullDump(true); // First dump must be full
m_wroteBytes = 0; m_wroteBytes = 0;
} }

View File

@ -1249,6 +1249,38 @@ inline std::ostream& operator<<(std::ostream& os, const VUseType& rhs) {
//###################################################################### //######################################################################
class VTraceType final {
public:
enum en : uint8_t {
CONSTANT, // Constant value dump (once at the beginning)
FULL, // Full value dump (always emitted)
CHANGE // Incremental value dump (emitted only if the value changed)
};
enum en m_e;
VTraceType()
: m_e{CONSTANT} {}
// cppcheck-suppress noExplicitConstructor
constexpr VTraceType(en _e)
: m_e{_e} {}
explicit VTraceType(int _e)
: m_e(static_cast<en>(_e)) {} // Need () or GCC 4.8 false warning
constexpr operator en() const { return m_e; }
const char* ascii() const {
static const char* const names[] = {"CONSTANT", "FULL", "CHANGE"};
return names[m_e];
}
};
constexpr bool operator==(const VTraceType& lhs, const VTraceType& rhs) {
return lhs.m_e == rhs.m_e;
}
constexpr bool operator==(const VTraceType& lhs, VTraceType::en rhs) { return lhs.m_e == rhs; }
constexpr bool operator==(VTraceType::en lhs, const VTraceType& rhs) { return lhs == rhs.m_e; }
inline std::ostream& operator<<(std::ostream& os, const VTraceType& rhs) {
return os << rhs.ascii();
}
// ######################################################################
class VCastable final { class VCastable final {
public: public:
enum en : uint8_t { enum en : uint8_t {
@ -2656,10 +2688,16 @@ public:
template <typename U> template <typename U>
// cppcheck-suppress noExplicitConstructor // cppcheck-suppress noExplicitConstructor
VNRef(U&& x) VNRef(U&& x)
: std::reference_wrapper<T_Node>{x} {} : std::reference_wrapper<T_Node> {
x
}
{}
// cppcheck-suppress noExplicitConstructor // cppcheck-suppress noExplicitConstructor
VNRef(const std::reference_wrapper<T_Node>& other) VNRef(const std::reference_wrapper<T_Node>& other)
: std::reference_wrapper<T_Node>{other} {} : std::reference_wrapper<T_Node> {
other
}
{}
}; };
static_assert(sizeof(VNRef<AstNode>) == sizeof(std::reference_wrapper<AstNode>), static_assert(sizeof(VNRef<AstNode>) == sizeof(std::reference_wrapper<AstNode>),

View File

@ -3243,14 +3243,14 @@ class AstTraceInc final : public AstNodeStmt {
private: private:
AstTraceDecl* m_declp; // Pointer to declaration AstTraceDecl* m_declp; // Pointer to declaration
const uint32_t m_baseCode; // Trace code base value in function containing this AstTraceInc const uint32_t m_baseCode; // Trace code base value in function containing this AstTraceInc
const bool m_full; // Is this a full vs incremental dump const VTraceType m_traceType; // Is this a const/full/incremental dump
public: public:
AstTraceInc(FileLine* fl, AstTraceDecl* declp, bool full, uint32_t baseCode = 0) AstTraceInc(FileLine* fl, AstTraceDecl* declp, VTraceType traceType, uint32_t baseCode = 0)
: ASTGEN_SUPER_TraceInc(fl) : ASTGEN_SUPER_TraceInc(fl)
, m_declp{declp} , m_declp{declp}
, m_baseCode{baseCode} , m_baseCode{baseCode}
, m_full{full} { , m_traceType{traceType} {
dtypeFrom(declp); dtypeFrom(declp);
this->valuep( this->valuep(
declp->valuep()->cloneTree(true)); // TODO: maybe use reference to TraceDecl instead? declp->valuep()->cloneTree(true)); // TODO: maybe use reference to TraceDecl instead?
@ -3274,7 +3274,7 @@ public:
bool isOutputter() override { return true; } bool isOutputter() override { return true; }
// but isPure() true // but isPure() true
AstTraceDecl* declp() const { return m_declp; } AstTraceDecl* declp() const { return m_declp; }
bool full() const { return m_full; } VTraceType traceType() const { return m_traceType; }
uint32_t baseCode() const { return m_baseCode; } uint32_t baseCode() const { return m_baseCode; }
}; };
class AstTracePopNamePrefix final : public AstNodeStmt { class AstTracePopNamePrefix final : public AstNodeStmt {

View File

@ -769,7 +769,8 @@ class EmitCTrace final : EmitCFunc {
void emitTraceChangeOne(AstTraceInc* nodep, int arrayindex) { void emitTraceChangeOne(AstTraceInc* nodep, int arrayindex) {
iterateAndNextConstNull(nodep->precondsp()); iterateAndNextConstNull(nodep->precondsp());
const string func = nodep->full() ? "full" : "chg"; // Note: Both VTraceType::CHANGE and VTraceType::FULL use the 'full' methods
const std::string func = nodep->traceType() == VTraceType::CHANGE ? "chg" : "full";
bool emitWidth = true; bool emitWidth = true;
if (nodep->dtypep()->basicp()->isDouble()) { if (nodep->dtypep()->basicp()->isDouble()) {
puts("bufp->" + func + "Double"); puts("bufp->" + func + "Double");
@ -794,7 +795,10 @@ class EmitCTrace final : EmitCFunc {
const uint32_t offset = (arrayindex < 0) ? 0 : (arrayindex * nodep->declp()->widthWords()); const uint32_t offset = (arrayindex < 0) ? 0 : (arrayindex * nodep->declp()->widthWords());
const uint32_t code = nodep->declp()->code() + offset; const uint32_t code = nodep->declp()->code() + offset;
puts(v3Global.opt.useTraceOffload() && !nodep->full() ? "(base+" : "(oldp+"); // Note: Both VTraceType::CHANGE and VTraceType::FULL use the 'full' methods
puts(v3Global.opt.useTraceOffload() && nodep->traceType() == VTraceType::CHANGE
? "(base+"
: "(oldp+");
puts(cvtToStr(code - nodep->baseCode())); puts(cvtToStr(code - nodep->baseCode()));
puts(","); puts(",");
emitTraceValue(nodep, arrayindex); emitTraceValue(nodep, arrayindex);

View File

@ -30,7 +30,7 @@
// Finally: // Finally:
// Process graph to determine when traced variables can change, allocate // Process graph to determine when traced variables can change, allocate
// activity flags, insert nodes to set activity flags, allocate signal // activity flags, insert nodes to set activity flags, allocate signal
// numbers (codes), and construct the full and incremental trace // numbers (codes), and construct the const, full and incremental trace
// functions, together with all other trace support functions. // functions, together with all other trace support functions.
// //
//************************************************************************* //*************************************************************************
@ -319,11 +319,10 @@ private:
return activityNumber; return activityNumber;
} }
void sortTraces(TraceVec& traces, uint32_t& nFullCodes, uint32_t& nChgCodes) { void sortTraces(TraceVec& traces, uint32_t& nNonConstCodes) {
// Populate sort structure // Populate sort structure
traces.clear(); traces.clear();
nFullCodes = 0; nNonConstCodes = 0;
nChgCodes = 0;
for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) { for (V3GraphVertex* itp = m_graph.verticesBeginp(); itp; itp = itp->verticesNextp()) {
if (TraceTraceVertex* const vtxp = itp->cast<TraceTraceVertex>()) { if (TraceTraceVertex* const vtxp = itp->cast<TraceTraceVertex>()) {
ActCodeSet actSet; ActCodeSet actSet;
@ -347,11 +346,8 @@ private:
|| actSet.size() == 1, || actSet.size() == 1,
vtxp->nodep(), "Always active trace has further triggers"); vtxp->nodep(), "Always active trace has further triggers");
// Count nodes // Count nodes
if (!vtxp->duplicatep()) { if (!vtxp->duplicatep() && !actSet.empty())
const uint32_t inc = vtxp->nodep()->codeInc(); nNonConstCodes += vtxp->nodep()->codeInc();
nFullCodes += inc;
if (!actSet.empty()) nChgCodes += inc;
}
if (actSet.empty()) { if (actSet.empty()) {
// If a trace doesn't have activity, it's constant, and we // If a trace doesn't have activity, it's constant, and we
// don't need to track changes on it. // don't need to track changes on it.
@ -374,8 +370,7 @@ private:
// Sort the traces by activity sets // Sort the traces by activity sets
TraceVec traces; TraceVec traces;
uint32_t unused1; uint32_t unused1;
uint32_t unused2; sortTraces(traces, unused1);
sortTraces(traces, unused1, unused2);
// For each activity set with only a small number of signals, make those // 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 // signals always traced, as it's cheaper to check a few value changes
@ -482,54 +477,62 @@ private:
} }
} }
AstCFunc* newCFunc(bool full, AstCFunc* topFuncp, int& funcNump, uint32_t baseCode = 0) { AstCFunc* newCFunc(VTraceType traceType, AstCFunc* topFuncp, unsigned funcNum,
uint32_t baseCode = 0) {
// Create new function // Create new function
const bool isTopFunc = topFuncp == nullptr; const bool isTopFunc = topFuncp == nullptr;
const string baseName = full && isTopFunc ? "trace_full_top_" std::string baseName = "trace_";
: full ? "trace_full_sub_" if (traceType == VTraceType::CONSTANT) {
: isTopFunc ? "trace_chg_top_" baseName += "const_";
: "trace_chg_sub_"; } else if (traceType == VTraceType::FULL) {
baseName += "full_";
} else {
baseName += "chg_";
}
baseName += isTopFunc ? "top_" : "sub_";
FileLine* const flp = m_topScopep->fileline(); FileLine* const flp = m_topScopep->fileline();
AstCFunc* const funcp = new AstCFunc{flp, baseName + cvtToStr(funcNump++), m_topScopep}; AstCFunc* const funcp = new AstCFunc{flp, baseName + cvtToStr(funcNum), m_topScopep};
funcp->isTrace(true); funcp->isTrace(true);
funcp->dontCombine(true); funcp->dontCombine(true);
funcp->isLoose(true); funcp->isLoose(true);
funcp->slow(full); funcp->slow(traceType != VTraceType::CHANGE);
funcp->isStatic(isTopFunc); funcp->isStatic(isTopFunc);
// Add it to top scope // Add it to top scope
m_topScopep->addBlocksp(funcp); m_topScopep->addBlocksp(funcp);
const auto addInitStr = [funcp, flp](const string& str) -> void { const auto addInitStr = [funcp, flp](const string& str) -> void {
funcp->addInitsp(new AstCStmt{flp, str}); funcp->addInitsp(new AstCStmt{flp, str});
}; };
const std::string bufArg
= v3Global.opt.traceClassBase()
+ "::" + (v3Global.opt.useTraceOffload() ? "OffloadBuffer" : "Buffer") + "* bufp";
if (isTopFunc) { if (isTopFunc) {
// Top functions // Top functions
funcp->argTypes("void* voidSelf, " + v3Global.opt.traceClassBase() funcp->argTypes("void* voidSelf, " + bufArg);
+ "::" + (v3Global.opt.useTraceOffload() ? "OffloadBuffer" : "Buffer")
+ "* bufp");
addInitStr(EmitCBase::voidSelfAssign(m_topModp)); addInitStr(EmitCBase::voidSelfAssign(m_topModp));
addInitStr(EmitCBase::symClassAssign()); addInitStr(EmitCBase::symClassAssign());
// Add global activity check to change dump functions // Add global activity check to change dump functions
if (!full) { // if (traceType == VTraceType::CHANGE) { //
addInitStr("if (VL_UNLIKELY(!vlSymsp->__Vm_activity)) return;\n"); addInitStr("if (VL_UNLIKELY(!vlSymsp->__Vm_activity)) return;\n");
} }
// Register function // Register function
if (full) { std::string str;
m_regFuncp->addStmtsp(new AstText{flp, "tracep->addFullCb(", true}); if (traceType == VTraceType::CONSTANT) {
str = "tracep->addConstCb(";
} else if (traceType == VTraceType::FULL) {
str = "tracep->addFullCb(";
} else { } else {
m_regFuncp->addStmtsp(new AstText{flp, "tracep->addChgCb(", true}); str = "tracep->addChgCb(";
} }
m_regFuncp->addStmtsp(new AstText{flp, str, true});
m_regFuncp->addStmtsp(new AstAddrOfCFunc{flp, funcp}); m_regFuncp->addStmtsp(new AstAddrOfCFunc{flp, funcp});
m_regFuncp->addStmtsp(new AstText{flp, ", vlSelf", true}); m_regFuncp->addStmtsp(new AstText{flp, ", vlSelf);\n", true});
m_regFuncp->addStmtsp(new AstText{flp, ");\n", true});
} else { } else {
// Sub functions // Sub functions
funcp->argTypes(v3Global.opt.traceClassBase() funcp->argTypes(bufArg);
+ "::" + +(v3Global.opt.useTraceOffload() ? "OffloadBuffer" : "Buffer")
+ "* bufp");
// Setup base references. Note in rare occasions we can end up with an empty trace // Setup base references. Note in rare occasions we can end up with an empty trace
// sub function, hence the VL_ATTR_UNUSED attributes. // sub function, hence the VL_ATTR_UNUSED attributes.
if (full) { if (traceType != VTraceType::CHANGE) {
// Full dump sub function // Full dump sub function
addInitStr("uint32_t* const oldp VL_ATTR_UNUSED = " addInitStr("uint32_t* const oldp VL_ATTR_UNUSED = "
"bufp->oldp(vlSymsp->__Vm_baseCode);\n"); "bufp->oldp(vlSymsp->__Vm_baseCode);\n");
@ -557,21 +560,15 @@ private:
return funcp; return funcp;
} }
void createFullTraceFunction(const TraceVec& traces, uint32_t nAllCodes, void createConstTraceFunctions(const TraceVec& traces) {
uint32_t parallelism) {
const int splitLimit = v3Global.opt.outputSplitCTrace() ? v3Global.opt.outputSplitCTrace() const int splitLimit = v3Global.opt.outputSplitCTrace() ? v3Global.opt.outputSplitCTrace()
: std::numeric_limits<int>::max(); : std::numeric_limits<int>::max();
int topFuncNum = 0; AstCFunc* const topFuncp = newCFunc(VTraceType::CONSTANT, nullptr, 0);
int subFuncNum = 0; unsigned subFuncNum = 0;
auto it = traces.cbegin();
while (it != traces.cend()) {
AstCFunc* topFuncp = nullptr;
AstCFunc* subFuncp = nullptr; AstCFunc* subFuncp = nullptr;
int subStmts = 0; int subStmts = 0;
const uint32_t maxCodes = (nAllCodes + parallelism - 1) / parallelism; for (auto it = traces.cbegin(); it != traces.end(); ++it) {
uint32_t nCodes = 0;
for (; nCodes < maxCodes && it != traces.end(); ++it) {
const TraceTraceVertex* const vtxp = it->second; const TraceTraceVertex* const vtxp = it->second;
AstTraceDecl* const declp = vtxp->nodep(); AstTraceDecl* const declp = vtxp->nodep();
if (const TraceTraceVertex* const canonVtxp = vtxp->duplicatep()) { if (const TraceTraceVertex* const canonVtxp = vtxp->duplicatep()) {
@ -579,14 +576,14 @@ private:
// number to the canonical node, and emit this as an alias, so // number to the canonical node, and emit this as an alias, so
// no need to create a TraceInc node. // no need to create a TraceInc node.
const AstTraceDecl* const canonDeclp = canonVtxp->nodep(); const AstTraceDecl* const canonDeclp = canonVtxp->nodep();
UASSERT_OBJ(!canonVtxp->duplicatep(), canonDeclp, UASSERT_OBJ(!canonVtxp->duplicatep(), canonDeclp, "Canonical node is a duplicate");
"Canonical node is a duplicate");
UASSERT_OBJ(canonDeclp->code() != 0, canonDeclp, UASSERT_OBJ(canonDeclp->code() != 0, canonDeclp,
"Canonical node should have code assigned already"); "Canonical node should have code assigned already");
declp->code(canonDeclp->code()); declp->code(canonDeclp->code());
} else { continue;
// This is a canonical trace node. Assign signal number and }
// add a TraceInc node to the full dump function.
// This is a canonical trace node. Assign trace code (signal number).
UASSERT_OBJ(declp->code() == 0, declp, UASSERT_OBJ(declp->code() == 0, declp,
"Canonical node should not have code assigned yet"); "Canonical node should not have code assigned yet");
declp->code(m_code); declp->code(m_code);
@ -594,67 +591,65 @@ private:
m_statUniqCodes += declp->codeInc(); m_statUniqCodes += declp->codeInc();
++m_statUniqSigs; ++m_statUniqSigs;
// Create top function if not yet created // If this is a const signal, add the AstTraceInc
if (!topFuncp) { topFuncp = newCFunc(/* full: */ true, nullptr, topFuncNum); } const ActCodeSet& actSet = it->first;
if (actSet.count(TraceActivityVertex::ACTIVITY_NEVER)) {
// Crate new sub function if required // Crate new sub function if required
if (!subFuncp || subStmts > splitLimit) { if (!subFuncp || subStmts > splitLimit) {
subStmts = 0; subStmts = 0;
subFuncp = newCFunc(/* full: */ true, topFuncp, subFuncNum); subFuncp = newCFunc(VTraceType::CONSTANT, topFuncp, subFuncNum);
++subFuncNum;
} }
FileLine* const flp = declp->fileline();
// Add TraceInc node AstTraceInc* const incp = new AstTraceInc{flp, declp, VTraceType::CONSTANT};
AstTraceInc* const incp
= new AstTraceInc{declp->fileline(), declp, /* full: */ true};
subFuncp->addStmtsp(incp); subFuncp->addStmtsp(incp);
subStmts += incp->nodeCount(); 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, void createNonConstTraceFunctions(const TraceVec& traces, uint32_t nAllCodes,
uint32_t parallelism) { uint32_t parallelism) {
const int splitLimit = v3Global.opt.outputSplitCTrace() ? v3Global.opt.outputSplitCTrace() const int splitLimit = v3Global.opt.outputSplitCTrace() ? v3Global.opt.outputSplitCTrace()
: std::numeric_limits<int>::max(); : std::numeric_limits<int>::max();
int topFuncNum = 0; unsigned topFuncNum = 0;
int subFuncNum = 0; unsigned subFuncNum = 0;
TraceVec::const_iterator it = traces.begin(); TraceVec::const_iterator it = traces.begin();
while (it != traces.end()) { while (it != traces.end()) {
AstCFunc* topFuncp = nullptr; AstCFunc* topFulFuncp = nullptr;
AstCFunc* subFuncp = nullptr; AstCFunc* topChgFuncp = nullptr;
AstCFunc* subFulFuncp = nullptr;
AstCFunc* subChgFuncp = nullptr;
int subStmts = 0; int subStmts = 0;
uint32_t maxCodes = (nAllCodes + parallelism - 1) / parallelism; const uint32_t maxCodes = std::max((nAllCodes + parallelism - 1) / parallelism, 1U);
if (maxCodes < 1) maxCodes = 1;
uint32_t nCodes = 0; uint32_t nCodes = 0;
const ActCodeSet* prevActSet = nullptr; const ActCodeSet* prevActSet = nullptr;
AstIf* ifp = nullptr; AstIf* ifp = nullptr;
uint32_t baseCode = 0; uint32_t baseCode = 0;
for (; nCodes < maxCodes && it != traces.end(); ++it) { for (; nCodes < maxCodes && it != traces.end(); ++it) {
const TraceTraceVertex* const vtxp = it->second; const TraceTraceVertex* const vtxp = it->second;
// This is a duplicate decl, no need to add it to incremental dump // This is a duplicate decl, no need to add it
if (vtxp->duplicatep()) continue; if (vtxp->duplicatep()) continue;
const ActCodeSet& actSet = it->first; const ActCodeSet& actSet = it->first;
// Traced value never changes, no need to add it to incremental dump // Traced value never changes, no need to add it
if (actSet.count(TraceActivityVertex::ACTIVITY_NEVER)) continue; if (actSet.count(TraceActivityVertex::ACTIVITY_NEVER)) continue;
AstTraceDecl* const declp = vtxp->nodep(); AstTraceDecl* const declp = vtxp->nodep();
// Create top function if not yet created // Create top function if not yet created
if (!topFuncp) { topFuncp = newCFunc(/* full: */ false, nullptr, topFuncNum); } if (!topFulFuncp) {
topFulFuncp = newCFunc(VTraceType::FULL, nullptr, topFuncNum);
topChgFuncp = newCFunc(VTraceType::CHANGE, nullptr, topFuncNum);
++topFuncNum;
}
// Create new sub function if required // Create new sub function if required
if (!subFuncp || subStmts > splitLimit) { if (!subFulFuncp || subStmts > splitLimit) {
baseCode = declp->code(); baseCode = declp->code();
subStmts = 0; subStmts = 0;
subFuncp = newCFunc(/* full: */ false, topFuncp, subFuncNum, baseCode); subFulFuncp = newCFunc(VTraceType::FULL, topFulFuncp, subFuncNum, baseCode);
subChgFuncp = newCFunc(VTraceType::CHANGE, topChgFuncp, subFuncNum, baseCode);
++subFuncNum;
prevActSet = nullptr; prevActSet = nullptr;
ifp = nullptr; ifp = nullptr;
} }
@ -674,24 +669,27 @@ private:
} }
ifp = new AstIf{flp, condp}; ifp = new AstIf{flp, condp};
if (!always) ifp->branchPred(VBranchPred::BP_UNLIKELY); if (!always) ifp->branchPred(VBranchPred::BP_UNLIKELY);
subFuncp->addStmtsp(ifp); subChgFuncp->addStmtsp(ifp);
subStmts += ifp->nodeCount(); subStmts += ifp->nodeCount();
prevActSet = &actSet; prevActSet = &actSet;
} }
// Add TraceInc node // Add TraceInc nodes
AstTraceInc* const incp FileLine* const flp = declp->fileline();
= new AstTraceInc{declp->fileline(), declp, /* full: */ false, baseCode}; AstTraceInc* const incFulp = new AstTraceInc{flp, declp, VTraceType::FULL};
ifp->addThensp(incp); subFulFuncp->addStmtsp(incFulp);
subStmts += incp->nodeCount(); AstTraceInc* const incChgp
= new AstTraceInc{flp, declp, VTraceType::CHANGE, baseCode};
ifp->addThensp(incChgp);
// Track splitting due to size
UASSERT_OBJ(incFulp->nodeCount() == incChgp->nodeCount(), declp,
"Should have equal cost");
subStmts += incChgp->nodeCount();
// Track partitioning // Track partitioning
nCodes += declp->codeInc(); 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);
}
} }
} }
@ -743,11 +741,8 @@ private:
TraceVec traces; // The sorted traces TraceVec traces; // The sorted traces
// We will split functions such that each have to dump roughly the same amount of data // 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. // 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 nNonConstCodes = 0;
uint32_t nChgCodes = 0; // Number of non-constant codes (need to go in to chg* dump) sortTraces(traces, nNonConstCodes);
sortTraces(traces, nFullCodes, nChgCodes);
UINFO(5, "nFullCodes: " << nFullCodes << " nChgCodes: " << nChgCodes << endl);
// Our keys are now sorted to have same activity number adjacent, then // Our keys are now sorted to have same activity number adjacent, then
// by trace order. (Better would be execution order for cache // by trace order. (Better would be execution order for cache
@ -763,11 +758,11 @@ private:
m_regFuncp->isLoose(true); m_regFuncp->isLoose(true);
m_topScopep->addBlocksp(m_regFuncp); m_topScopep->addBlocksp(m_regFuncp);
// Create the full dump functions, also allocates signal numbers // Create the const dump functions. Also allocates trace codes.
createFullTraceFunction(traces, nFullCodes, m_parallelism); createConstTraceFunctions(traces);
// Create the incremental dump functions // Create the full and incremental dump functions
createChgTraceFunctions(traces, nChgCodes, m_parallelism); createNonConstTraceFunctions(traces, nNonConstCodes, m_parallelism);
// Remove refs to traced values from TraceDecl nodes, these have now moved under // Remove refs to traced values from TraceDecl nodes, these have now moved under
// TraceInc // TraceInc