// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Build DPI protected C++ and SV // // Code available from: https://verilator.org // //************************************************************************* // // Copyright 2003-2025 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 // //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3ProtectLib.h" #include "V3Control.h" #include "V3Hasher.h" #include "V3InstrCount.h" #include "V3String.h" #include "V3Task.h" VL_DEFINE_DEBUG_FUNCTIONS; //###################################################################### // ProtectLib top-level visitor class ProtectVisitor final : public VNVisitor { AstVFile* m_vfilep = nullptr; // DPI-enabled Verilog wrapper AstCFile* m_cfilep = nullptr; // C implementation of DPI functions // Verilog text blocks AstTextBlock* m_modPortsp = nullptr; // Module port list AstTextBlock* m_comboPortsp = nullptr; // Combo function port list AstTextBlock* m_seqPortsp = nullptr; // Sequential function port list AstTextBlock* m_comboIgnorePortsp = nullptr; // Combo ignore function port list AstTextBlock* m_comboDeclsp = nullptr; // Combo signal declaration list AstTextBlock* m_seqDeclsp = nullptr; // Sequential signal declaration list AstTextBlock* m_tmpDeclsp = nullptr; // Temporary signal declaration list AstTextBlock* m_hashValuep = nullptr; // CPP hash value AstTextBlock* m_comboParamsp = nullptr; // Combo function parameter list AstTextBlock* m_clkSensp = nullptr; // Clock sensitivity list AstTextBlock* m_comboIgnoreParamsp = nullptr; // Combo ignore parameter list AstTextBlock* m_seqParamsp = nullptr; // Sequential parameter list AstTextBlock* m_nbAssignsp = nullptr; // Non-blocking assignment list AstTextBlock* m_seqAssignsp = nullptr; // Sequential assignment list AstTextBlock* m_comboAssignsp = nullptr; // Combo assignment list // C text blocks AstTextBlock* m_cHashValuep = nullptr; // CPP hash value AstTextBlock* m_cComboParamsp = nullptr; // Combo function parameter list AstTextBlock* m_cComboInsp = nullptr; // Combo input copy list AstTextBlock* m_cComboOutsp = nullptr; // Combo output copy list AstTextBlock* m_cSeqParamsp = nullptr; // Sequential parameter list AstTextBlock* m_cSeqClksp = nullptr; // Sequential clock copy list AstTextBlock* m_cSeqOutsp = nullptr; // Sequential output copy list AstTextBlock* m_cIgnoreParamsp = nullptr; // Combo ignore parameter list const string m_libName; const string m_topName; bool m_foundTop = false; // Have seen the top module bool m_hasClk = false; // True if the top module has sequential logic // VISITORS void visit(AstNetlist* nodep) override { m_vfilep = new AstVFile{nodep->fileline(), v3Global.opt.makeDir() + "/" + m_libName + ".sv"}; nodep->addFilesp(m_vfilep); m_cfilep = new AstCFile{nodep->fileline(), v3Global.opt.makeDir() + "/" + m_libName + ".cpp"}; nodep->addFilesp(m_cfilep); iterateChildren(nodep); } void visit(AstNodeModule* nodep) override { if (!nodep->isTop()) { return; } else { UASSERT_OBJ(!m_foundTop, nodep, "Multiple root modules"); } FileLine* const fl = nodep->fileline(); // Need to know the existence of clk before createSvFile() m_hasClk = checkIfClockExists(nodep); createSvFile(fl, nodep); createCppFile(fl); iterateChildren(nodep); // cppcheck-suppress unreadVariable const V3Hash hash = V3Hasher::uncachedHash(m_cfilep); m_hashValuep->add(cvtToStr(hash.value()) + ";\n"); m_cHashValuep->add(cvtToStr(hash.value()) + "U;\n"); m_foundTop = true; } void addComment(AstTextBlock* txtp, FileLine* fl, const string& comment) { txtp->add(new AstComment{fl, comment}); } void configSection(AstNodeModule* modp, AstTextBlock* txtp, FileLine* fl) { txtp->add("\n`ifdef VERILATOR\n"); txtp->add("`verilator_config\n"); txtp->add("profile_data -hier-dpi \"" + m_libName + "_protectlib_combo_update\" -cost 64'd" + std::to_string(v3Global.currentHierBlockCost()) + "\n"); txtp->add("profile_data -hier-dpi \"" + m_libName + "_protectlib_seq_update\" -cost 64'd" + std::to_string(v3Global.currentHierBlockCost()) + "\n"); // Mark remaining NBA protectlib wrapper DPIs as non-hazardous by deliberately forwarding // them with non-zero cost. // Also, specify hierarchical workers for those tasks for scheduling. txtp->add("profile_data -hier-dpi \"" + m_libName + "_protectlib_combo_ignore\" -cost 64'd1\n"); txtp->add("hier_workers -hier-dpi \"" + m_libName + "_protectlib_combo_update\" -workers 16'd" + std::to_string(V3Control::getHierWorkers(m_libName)) + "\n"); txtp->add("hier_workers -hier-dpi \"" + m_libName + "_protectlib_seq_update\" -workers 16'd" + std::to_string(V3Control::getHierWorkers(m_libName)) + "\n"); // No workers for combo_ignore txtp->add("`verilog\n"); txtp->add("`endif\n"); } void hashComment(AstTextBlock* txtp, FileLine* fl) { addComment(txtp, fl, "Checks to make sure the .sv wrapper and library agree"); } void initialComment(AstTextBlock* txtp, FileLine* fl) { addComment(txtp, fl, "Creates an instance of the library module at initial-time"); addComment(txtp, fl, "(one for each instance in the user's design) also evaluates"); addComment(txtp, fl, "the library module's initial process"); } void comboComment(AstTextBlock* txtp, FileLine* fl) { addComment(txtp, fl, "Updates all non-clock inputs and retrieves the results"); } void seqComment(AstTextBlock* txtp, FileLine* fl) { addComment(txtp, fl, "Updates all clocks and retrieves the results"); } void comboIgnoreComment(AstTextBlock* txtp, FileLine* fl) { addComment(txtp, fl, "Need to convince some simulators that the input to the module"); addComment(txtp, fl, "must be evaluated before evaluating the clock edge"); } void finalComment(AstTextBlock* txtp, FileLine* fl) { addComment(txtp, fl, "Evaluates the library module's final process"); } void createSvFile(FileLine* fl, AstNodeModule* modp) { // Comments AstTextBlock* const txtp = new AstTextBlock{fl}; addComment(txtp, fl, "Wrapper module for DPI protected library"); addComment(txtp, fl, "This module requires lib" + m_libName + ".a or lib" + m_libName + ".so to work"); addComment(txtp, fl, "See instructions in your simulator for how" " to use DPI libraries\n"); // Module declaration m_modPortsp = new AstTextBlock{fl, "module " + m_libName + " (\n", ", ", ");\n\n"}; txtp->add(m_modPortsp); // Timescale if (v3Global.opt.hierChild() && v3Global.rootp()->timescaleSpecified()) { // Emit timescale for hierarchical Verilation if input HDL specifies timespec txtp->add("timeunit "s + modp->timeunit().ascii() + ";\n"); txtp->add("timeprecision "s + +v3Global.rootp()->timeprecision().ascii() + ";\n"); } else { addComment(txtp, fl, "Precision of submodule" " (commented out to avoid requiring timescale on all modules)"); addComment(txtp, fl, "timeunit "s + v3Global.rootp()->timeunit().ascii() + ";"); addComment(txtp, fl, "timeprecision "s + v3Global.rootp()->timeprecision().ascii() + ";\n"); } // DPI declarations hashComment(txtp, fl); txtp->add("import \"DPI-C\" function void " + m_libName + "_protectlib_check_hash(int protectlib_hash__V);\n\n"); initialComment(txtp, fl); txtp->add("import \"DPI-C\" function chandle " + m_libName + "_protectlib_create(string scope__V);\n\n"); comboComment(txtp, fl); m_comboPortsp = new AstTextBlock{fl, // "import \"DPI-C\" function longint " + m_libName + "_protectlib_combo_update(\n", // ", ", // ");\n\n"}; m_comboPortsp->add("chandle handle__V\n"); txtp->add(m_comboPortsp); seqComment(txtp, fl); if (m_hasClk) { m_seqPortsp = new AstTextBlock{fl, // "import \"DPI-C\" function longint " + m_libName + "_protectlib_seq_update(\n", // ", ", // ");\n\n"}; m_seqPortsp->add("chandle handle__V\n"); txtp->add(m_seqPortsp); } comboIgnoreComment(txtp, fl); m_comboIgnorePortsp = new AstTextBlock{fl, // "import \"DPI-C\" function void " + m_libName + "_protectlib_combo_ignore(\n", // ", ", // ");\n\n"}; m_comboIgnorePortsp->add("chandle handle__V\n"); txtp->add(m_comboIgnorePortsp); finalComment(txtp, fl); txtp->add("import \"DPI-C\" function void " + m_libName + "_protectlib_final(chandle handle__V);\n\n"); // Local variables // Avoid tracing handle, as it is not a stable value, so breaks vcddiff // Likewise other internals aren't interesting to the user txtp->add("// verilator tracing_off\n"); txtp->add("chandle handle__V;\n"); txtp->add("time last_combo_seqnum__V;\n"); if (m_hasClk) txtp->add("time last_seq_seqnum__V;\n"); txtp->add("\n"); m_comboDeclsp = new AstTextBlock{fl}; txtp->add(m_comboDeclsp); m_seqDeclsp = new AstTextBlock{fl}; txtp->add(m_seqDeclsp); m_tmpDeclsp = new AstTextBlock{fl}; txtp->add(m_tmpDeclsp); // CPP hash value addComment(txtp, fl, "Hash value to make sure this file and the corresponding"); addComment(txtp, fl, "library agree"); m_hashValuep = new AstTextBlock{fl, "localparam int protectlib_hash__V = 32'd", "", "\n"}; txtp->add(m_hashValuep); // Initial txtp->add("initial begin\n"); txtp->add(m_libName + "_protectlib_check_hash(protectlib_hash__V);\n"); txtp->add("handle__V = " + m_libName + "_protectlib_create" "($sformatf(\"%m\"));\n"); txtp->add("end\n\n"); // Combinatorial process addComment(txtp, fl, "Combinatorially evaluate changes to inputs"); txtp->add("always_comb begin\n"); m_comboParamsp = new AstTextBlock{fl, // "last_combo_seqnum__V = " + m_libName + "_protectlib_combo_update(\n", // ",\n", // "\n);\n"}; m_comboParamsp->add("handle__V"); txtp->add(m_comboParamsp); txtp->add("end\n\n"); // Sequential process if (m_hasClk) { addComment(txtp, fl, "Evaluate clock edges"); m_clkSensp = new AstTextBlock{fl, "always @(", " or ", ")"}; txtp->add(m_clkSensp); txtp->add(" begin\n"); m_comboIgnoreParamsp = new AstTextBlock{fl, // m_libName + "_protectlib_combo_ignore(\n", ",\n", "\n);\n"}; m_comboIgnoreParamsp->add("handle__V"); txtp->add(m_comboIgnoreParamsp); m_seqParamsp = new AstTextBlock{fl, // "last_seq_seqnum__V <= " + m_libName + "_protectlib_seq_update(\n", // ",\n", // "\n);\n"}; m_seqParamsp->add("handle__V"); txtp->add(m_seqParamsp); m_nbAssignsp = new AstTextBlock{fl}; txtp->add(m_nbAssignsp); txtp->add("end\n\n"); } // Select between combinatorial and sequential results addComment(txtp, fl, "Select between combinatorial and sequential results"); txtp->add("always_comb begin\n"); if (m_hasClk) { txtp->add("if (last_seq_seqnum__V > last_combo_seqnum__V) begin\n"); m_seqAssignsp = new AstTextBlock{fl}; txtp->add(m_seqAssignsp); txtp->add("end else begin\n"); m_comboAssignsp = new AstTextBlock{fl}; txtp->add(m_comboAssignsp); txtp->add("end\n"); } else { m_comboAssignsp = new AstTextBlock{fl}; txtp->add(m_comboAssignsp); } txtp->add("end\n\n"); // Final txtp->add("final " + m_libName + "_protectlib_final(handle__V);\n\n"); txtp->add("endmodule\n"); configSection(modp, txtp, fl); m_vfilep->tblockp(txtp); } void castPtr(FileLine* fl, AstTextBlock* txtp) { txtp->add(m_topName + "_container* const handlep__V = " // LCOV_EXCL_LINE // lcov bug "static_cast<" + m_topName + "_container*>(vhandlep__V);\n"); } void createCppFile(FileLine* fl) { // Comments AstTextBlock* const txtp = new AstTextBlock{fl}; addComment(txtp, fl, "Wrapper functions for DPI protected library\n"); // Includes txtp->add("#include \"" + m_topName + ".h\"\n"); txtp->add("#include \"verilated_dpi.h\"\n\n"); txtp->add("#include \n"); txtp->add("#include \n\n"); // Verilated module plus sequence number addComment(txtp, fl, "Container class to house verilated object and sequence number"); txtp->add("class " + m_topName + "_container: public " + m_topName + " {\n"); txtp->add("public:\n"); txtp->add("long long m_seqnum;\n"); txtp->add(m_topName + "_container(const char* scopep__V):\n"); txtp->add(m_topName + "(scopep__V) {}\n"); txtp->add("};\n\n"); // Extern C txtp->add("extern \"C\" {\n\n"); // Hash check hashComment(txtp, fl); txtp->add("void " + m_libName + "_protectlib_check_hash" "(int protectlib_hash__V) {\n"); m_cHashValuep = new AstTextBlock{fl, "const int expected_hash__V = ", "", ""}; txtp->add(m_cHashValuep); txtp->add(/**/ "if (protectlib_hash__V != expected_hash__V) {\n"); txtp->add(/****/ "fprintf(stderr, \"%%Error: cannot use " + m_libName + " library, " "Verliog (%u) and library (%u) hash values do not " "agree\\n\", protectlib_hash__V, expected_hash__V);\n"); txtp->add(/****/ "std::exit(EXIT_FAILURE);\n"); txtp->add(/**/ "}\n"); txtp->add("}\n\n"); // Initial initialComment(txtp, fl); txtp->add("void* " + m_libName + "_protectlib_create(const char* scopep__V) {\n"); txtp->add(/**/ m_topName + "_container* const handlep__V = new " + m_topName + "_container{scopep__V};\n"); txtp->add(/**/ "return handlep__V;\n"); txtp->add("}\n\n"); // Updates comboComment(txtp, fl); m_cComboParamsp = new AstTextBlock{ fl, "long long " + m_libName + "_protectlib_combo_update(\n", ",\n", "\n) {\n"}; m_cComboParamsp->add("void* vhandlep__V"); txtp->add(m_cComboParamsp); m_cComboInsp = new AstTextBlock{fl}; castPtr(fl, m_cComboInsp); txtp->add(m_cComboInsp); txtp->add("handlep__V->eval();\n"); m_cComboOutsp = new AstTextBlock{fl}; txtp->add(m_cComboOutsp); txtp->add("return handlep__V->m_seqnum++;\n"); txtp->add("}\n\n"); if (m_hasClk) { seqComment(txtp, fl); m_cSeqParamsp = new AstTextBlock{ fl, "long long " + m_libName + "_protectlib_seq_update(\n", ",\n", "\n) {\n"}; m_cSeqParamsp->add("void* vhandlep__V"); txtp->add(m_cSeqParamsp); m_cSeqClksp = new AstTextBlock{fl}; castPtr(fl, m_cSeqClksp); txtp->add(m_cSeqClksp); txtp->add("handlep__V->eval();\n"); m_cSeqOutsp = new AstTextBlock{fl}; txtp->add(m_cSeqOutsp); txtp->add("return handlep__V->m_seqnum++;\n"); txtp->add("}\n\n"); } comboIgnoreComment(txtp, fl); m_cIgnoreParamsp = new AstTextBlock{ fl, "void " + m_libName + "_protectlib_combo_ignore(\n", ",\n", "\n)\n{ }\n\n"}; m_cIgnoreParamsp->add("void* vhandlep__V"); txtp->add(m_cIgnoreParamsp); // Final finalComment(txtp, fl); txtp->add("void " + m_libName + "_protectlib_final(void* vhandlep__V) {\n"); castPtr(fl, txtp); txtp->add(/**/ "handlep__V->final();\n"); txtp->add(/**/ "delete handlep__V;\n"); txtp->add("}\n\n"); txtp->add("}\n"); m_cfilep->tblockp(txtp); } void visit(AstVar* nodep) override { if (!nodep->isIO()) return; if (nodep->direction() == VDirection::INPUT) { if (nodep->isPrimaryClock()) { UASSERT_OBJ(m_hasClk, nodep, "checkIfClockExists() didn't find this clock"); handleClock(nodep); } else { handleDataInput(nodep); } } else if (nodep->direction() == VDirection::OUTPUT) { handleOutput(nodep); } else { nodep->v3warn(E_UNSUPPORTED, "Unsupported: --lib-create port direction: " << nodep->direction().ascii()); } } void visit(AstNode*) override {} string cInputConnection(AstVar* varp) { return V3Task::assignDpiToInternal("handlep__V->" + varp->name(), varp); } void handleClock(AstVar* varp) { handleInput(varp); m_seqPortsp->add(varp->cloneTree(false)); if (m_hasClk) { const std::string pname = varp->prettyName(); m_seqParamsp->add(pname); m_clkSensp->add(pname); } m_cSeqParamsp->add(varp->dpiArgType(true, false)); m_cSeqClksp->add(cInputConnection(varp)); } void handleDataInput(AstVar* varp) { handleInput(varp); m_comboPortsp->add(varp->cloneTree(false)); m_comboParamsp->add(varp->prettyName()); m_comboIgnorePortsp->add(varp->cloneTree(false)); if (m_hasClk) m_comboIgnoreParamsp->add(varp->prettyName()); m_cComboParamsp->add(varp->dpiArgType(true, false)); m_cComboInsp->add(cInputConnection(varp)); m_cIgnoreParamsp->add(varp->dpiArgType(true, false)); } void handleInput(AstVar* varp) { m_modPortsp->add(varp->cloneTree(false)); } static void addLocalVariable(AstTextBlock* textp, const AstVar* varp, const char* suffix) { AstVar* const newVarp = new AstVar{varp->fileline(), VVarType::VAR, varp->name() + suffix, varp->dtypep()}; textp->add(newVarp); } void handleOutput(AstVar* const varp) { const std::string pname = varp->prettyName(); m_modPortsp->add(varp->cloneTree(false)); m_comboPortsp->add(varp->cloneTree(false)); m_comboParamsp->add(pname + "_combo__V"); if (m_hasClk) { m_seqPortsp->add(varp->cloneTree(false)); m_seqParamsp->add(pname + "_tmp__V"); } addLocalVariable(m_comboDeclsp, varp, "_combo__V"); if (m_hasClk) { addLocalVariable(m_seqDeclsp, varp, "_seq__V"); addLocalVariable(m_tmpDeclsp, varp, "_tmp__V"); m_nbAssignsp->add(pname + "_seq__V <= " + pname + "_tmp__V;\n"); m_seqAssignsp->add(pname + " = " + pname + "_seq__V;\n"); } m_comboAssignsp->add(pname + " = " + pname + "_combo__V;\n"); m_cComboParamsp->add(varp->dpiArgType(true, false)); m_cComboOutsp->add(V3Task::assignInternalToDpi(varp, true, "", "", "handlep__V->") + "\n"); if (m_hasClk) { m_cSeqParamsp->add(varp->dpiArgType(true, false)); m_cSeqOutsp->add(V3Task::assignInternalToDpi(varp, true, "", "", "handlep__V->") + "\n"); } } static bool checkIfClockExists(const AstNodeModule* modp) { for (const AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { if (const AstVar* const varp = VN_CAST(stmtp, Var)) { if (varp->direction() == VDirection::INPUT && varp->isPrimaryClock()) { return true; } } } return false; } public: explicit ProtectVisitor(AstNode* nodep) : m_libName{v3Global.opt.libCreate()} , m_topName{v3Global.opt.prefix()} { iterate(nodep); } }; //###################################################################### // ProtectLib class functions void V3ProtectLib::protect() { UINFO(2, __FUNCTION__ << ":"); ProtectVisitor{v3Global.rootp()}; }