// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Waves tracing // // 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 // //************************************************************************* // V3TraceDecl's Transformations: // Create trace init CFunc // For each VarScope // If appropriate type of signal, create a TraceDecl // //************************************************************************* #include "config_build.h" #include "verilatedos.h" #include "V3Global.h" #include "V3TraceDecl.h" #include "V3EmitCBase.h" #include "V3Stats.h" //###################################################################### // TraceDecl state, as a visitor of each AstNode class TraceDeclVisitor final : public EmitCBaseVisitor { private: // NODE STATE // STATE AstScope* m_topScopep = nullptr; // Current top scope AstCFunc* m_initFuncp = nullptr; // Trace function being built AstCFunc* m_initSubFuncp = nullptr; // Trace function being built (under m_init) int m_initSubStmts = 0; // Number of statements in function int m_funcNum = 0; // Function number being built AstVarScope* m_traVscp = nullptr; // Signal being trace constructed AstNode* m_traValuep = nullptr; // Signal being traced's value to trace in it string m_traShowname; // Signal being traced's component name bool m_interface = false; // Currently tracing an interface VDouble0 m_statSigs; // Statistic tracking VDouble0 m_statIgnSigs; // Statistic tracking // METHODS VL_DEBUG_FUNC; // Declare debug() const char* vscIgnoreTrace(const AstVarScope* nodep) { // Return true if this shouldn't be traced // See also similar rule in V3Coverage::varIgnoreToggle const AstVar* const varp = nodep->varp(); if (!varp->isTrace()) { return "Verilator trace_off"; } else if (!nodep->isTrace()) { return "Verilator cell trace_off"; } else if (!v3Global.opt.traceUnderscore()) { const string prettyName = varp->prettyName(); if (!prettyName.empty() && prettyName[0] == '_') return "Leading underscore"; if (prettyName.find("._") != string::npos) return "Inlined leading underscore"; } return nullptr; } AstCFunc* newCFunc(AstCFuncType type, const string& name) { FileLine* const flp = m_topScopep->fileline(); AstCFunc* const funcp = new AstCFunc(flp, name, m_topScopep); string argTypes("void* userp, " + v3Global.opt.traceClassBase() + "* tracep"); if (m_interface) argTypes += ", const char* scopep"; funcp->argTypes(argTypes); funcp->funcType(type); funcp->symProlog(true); funcp->slow(true); funcp->declPrivate(true); m_topScopep->addActivep(funcp); UINFO(5, " Newfunc " << funcp << endl); return funcp; } void callCFuncSub(AstCFunc* basep, AstCFunc* funcp, AstIntfRef* irp) { AstCCall* callp = new AstCCall(funcp->fileline(), funcp); callp->argTypes("userp, tracep"); if (irp) callp->addArgsp(irp->unlinkFrBack()); basep->addStmtsp(callp); } AstCFunc* newCFuncSub(AstCFunc* basep) { const string name = "traceInitSub" + cvtToStr(m_funcNum++); AstCFunc* const funcp = newCFunc(AstCFuncType::TRACE_INIT_SUB, name); if (!m_interface) callCFuncSub(basep, funcp, nullptr); return funcp; } void addTraceDecl(const VNumRange& arrayRange, int widthOverride) { // If !=0, is packed struct/array where basicp size // misreflects one element VNumRange bitRange; if (widthOverride) { bitRange = VNumRange(widthOverride - 1, 0, false); } else if (const AstBasicDType* const bdtypep = m_traValuep->dtypep()->basicp()) { bitRange = bdtypep->nrange(); } AstTraceDecl* const declp = new AstTraceDecl(m_traVscp->fileline(), m_traShowname, m_traVscp->varp(), m_traValuep->cloneTree(true), bitRange, arrayRange, m_interface); UINFO(9, "Decl " << declp << endl); if (!m_interface && v3Global.opt.outputSplitCTrace() && m_initSubStmts > v3Global.opt.outputSplitCTrace()) { m_initSubFuncp = newCFuncSub(m_initFuncp); m_initSubStmts = 0; } m_initSubFuncp->addStmtsp(declp); m_initSubStmts += EmitCBaseCounterVisitor(declp).count(); } void addIgnore(const char* why) { ++m_statIgnSigs; m_initSubFuncp->addStmtsp(new AstComment( m_traVscp->fileline(), "Tracing: " + m_traShowname + " // Ignored: " + why, true)); } // VISITORS virtual void visit(AstTopScope* nodep) override { m_topScopep = nodep->scopep(); // Create the trace init function m_initFuncp = newCFunc(AstCFuncType::TRACE_INIT, "traceInitTop"); // Create initial sub function m_initSubFuncp = newCFuncSub(m_initFuncp); // And find variables iterateChildren(nodep); } virtual void visit(AstScope* nodep) override { AstCell* const cellp = VN_CAST(nodep->aboveCellp(), Cell); if (cellp && VN_IS(cellp->modp(), Iface)) { AstCFunc* const origSubFunc = m_initSubFuncp; int origSubStmts = m_initSubStmts; { m_interface = true; m_initSubFuncp = newCFuncSub(origSubFunc); string scopeName = nodep->prettyName(); const size_t lastDot = scopeName.find_last_of('.'); UASSERT_OBJ(lastDot != string::npos, nodep, "Expected an interface scope name to have at least one dot"); scopeName = scopeName.substr(0, lastDot + 1); const size_t scopeLen = scopeName.length(); AstIntfRef* nextIrp = cellp->intfRefp(); // While instead of for loop because interface references will // be unlinked as we go while (nextIrp) { AstIntfRef* const irp = nextIrp; nextIrp = VN_CAST(irp->nextp(), IntfRef); const string irpName = irp->prettyName(); if (scopeLen > irpName.length()) continue; string intfScopeName = irpName.substr(0, scopeLen); if (scopeName != intfScopeName) continue; callCFuncSub(origSubFunc, m_initSubFuncp, irp); ++origSubStmts; } iterateChildren(nodep); } m_initSubFuncp = origSubFunc; m_initSubStmts = origSubStmts; m_interface = false; } else { iterateChildren(nodep); } } virtual void visit(AstVarScope* nodep) override { iterateChildren(nodep); // Prefilter - things that get through this if will either get // traced or get a comment as to why not traced. // Generally this equation doesn't need updating, instead use // varp->isTrace() and/or vscIgnoreTrace. if ((!nodep->varp()->isTemp() || nodep->varp()->isTrace()) && !nodep->varp()->isClassMember() && !nodep->varp()->isFuncLocal()) { UINFO(5, " vsc " << nodep << endl); const AstVar* const varp = nodep->varp(); const AstScope* const scopep = nodep->scopep(); // Compute show name // This code assumes SPTRACEVCDC_VERSION >= 1330; // it uses spaces to separate hierarchy components. if (m_interface) { m_traShowname = AstNode::vcdName(varp->name()); } else { m_traShowname = AstNode::vcdName(scopep->name() + " " + varp->name()); if (m_traShowname.substr(0, 4) == "TOP ") m_traShowname.erase(0, 4); } UASSERT_OBJ(m_initSubFuncp, nodep, "nullptr"); m_traVscp = nodep; if (const char* const ignoreReasonp = vscIgnoreTrace(nodep)) { addIgnore(ignoreReasonp); } else { ++m_statSigs; if (nodep->valuep()) { m_traValuep = nodep->valuep()->cloneTree(true); } else { m_traValuep = new AstVarRef(nodep->fileline(), nodep, VAccess::READ); } // Recurse into data type of the signal; the visitors will call addTraceDecl() iterate(varp->dtypep()->skipRefToEnump()); // Cleanup if (m_traValuep) VL_DO_CLEAR(m_traValuep->deleteTree(), m_traValuep = nullptr); } m_traVscp = nullptr; m_traShowname = ""; } } // VISITORS - Data types when tracing virtual void visit(AstConstDType* nodep) override { if (m_traVscp) iterate(nodep->subDTypep()->skipRefToEnump()); } virtual void visit(AstRefDType* nodep) override { if (m_traVscp) iterate(nodep->subDTypep()->skipRefToEnump()); } virtual void visit(AstUnpackArrayDType* nodep) override { // Note more specific dtypes above if (m_traVscp) { if (static_cast(nodep->arrayUnpackedElements()) > v3Global.opt.traceMaxArray()) { addIgnore("Wide memory > --trace-max-array ents"); } else if (VN_IS(nodep->subDTypep()->skipRefToEnump(), BasicDType) // Nothing lower than this array && m_traVscp->dtypep()->skipRefToEnump() == nodep) { // Nothing above this array // Simple 1-D array, use existing V3EmitC runtime loop rather than unrolling // This will put "(index)" at end of signal name for us if (m_traVscp->dtypep()->skipRefToEnump()->isString()) { addIgnore("Unsupported: strings"); } else { addTraceDecl(nodep->declRange(), 0); } } else { // Unroll now, as have no other method to get right signal names AstNodeDType* const subtypep = nodep->subDTypep()->skipRefToEnump(); for (int i = nodep->lsb(); i <= nodep->msb(); ++i) { VL_RESTORER(m_traShowname); VL_RESTORER(m_traValuep); { m_traShowname += string("(") + cvtToStr(i) + string(")"); m_traValuep = new AstArraySel( nodep->fileline(), m_traValuep->cloneTree(true), i - nodep->lsb()); m_traValuep->dtypep(subtypep); iterate(subtypep); VL_DO_CLEAR(m_traValuep->deleteTree(), m_traValuep = nullptr); } } } } } virtual void visit(AstPackArrayDType* nodep) override { if (m_traVscp) { if (!v3Global.opt.traceStructs()) { // Everything downstream is packed, so deal with as one trace unit. // This may not be the nicest for user presentation, but is // a much faster way to trace addTraceDecl(VNumRange(), nodep->width()); } else { AstNodeDType* const subtypep = nodep->subDTypep()->skipRefToEnump(); for (int i = nodep->lsb(); i <= nodep->msb(); ++i) { VL_RESTORER(m_traShowname); VL_RESTORER(m_traValuep); { m_traShowname += string("(") + cvtToStr(i) + string(")"); m_traValuep = new AstSel(nodep->fileline(), m_traValuep->cloneTree(true), (i - nodep->lsb()) * subtypep->width(), subtypep->width()); m_traValuep->dtypep(subtypep); iterate(subtypep); VL_DO_CLEAR(m_traValuep->deleteTree(), m_traValuep = nullptr); } } } } } virtual void visit(AstNodeUOrStructDType* nodep) override { if (m_traVscp) { if (nodep->packed() && !v3Global.opt.traceStructs()) { // Everything downstream is packed, so deal with as one trace unit // This may not be the nicest for user presentation, but is // a much faster way to trace addTraceDecl(VNumRange(), nodep->width()); } else if (!nodep->packed()) { addIgnore("Unsupported: Unpacked struct/union"); } else { for (const AstMemberDType* itemp = nodep->membersp(); itemp; itemp = VN_CAST_CONST(itemp->nextp(), MemberDType)) { AstNodeDType* const subtypep = itemp->subDTypep()->skipRefToEnump(); VL_RESTORER(m_traShowname); VL_RESTORER(m_traValuep); { m_traShowname += string(" ") + itemp->prettyName(); if (VN_IS(nodep, StructDType)) { m_traValuep = new AstSel(nodep->fileline(), m_traValuep->cloneTree(true), itemp->lsb(), subtypep->width()); m_traValuep->dtypep(subtypep); iterate(subtypep); VL_DO_CLEAR(m_traValuep->deleteTree(), m_traValuep = nullptr); } else { // Else union, replicate fields iterate(subtypep); } } } } } } virtual void visit(AstBasicDType* nodep) override { if (m_traVscp) { if (nodep->isString()) { addIgnore("Unsupported: strings"); } else { addTraceDecl(VNumRange(), 0); } } } virtual void visit(AstEnumDType* nodep) override { iterate(nodep->skipRefp()); } virtual void visit(AstNodeDType*) override { // Note more specific dtypes above if (!m_traVscp) return; addIgnore("Unsupported: data type"); } //-------------------- virtual void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTORS explicit TraceDeclVisitor(AstNetlist* nodep) { iterate(nodep); } virtual ~TraceDeclVisitor() override { V3Stats::addStat("Tracing, Traced signals", m_statSigs); V3Stats::addStat("Tracing, Ignored signals", m_statIgnSigs); } }; //###################################################################### // Trace class functions void V3TraceDecl::traceDeclAll(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); { TraceDeclVisitor visitor(nodep); } // Destruct before checking V3Global::dumpCheckGlobalTree("tracedecl", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3); }