// -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* // DESCRIPTION: Verilator: Implementation of User defined primitives // // 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 // //************************************************************************* // V3Udp's Transformations: // // For every table line create an always block containing if statements // with condition depending on a combination of the input fields: // // 0 1 0 on a, b, c turns into !a&b&~c // //************************************************************************* #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT #include "V3Udp.h" #include "V3Error.h" #include VL_DEFINE_DEBUG_FUNCTIONS; class UdpVisitor final : public VNVisitor { bool m_inInitial = false; // Is inside of an initial block AstVar* m_oFieldVarp = nullptr; // Output filed var of table line std::vector m_inputVars; // All the input vars in the AstPrimitive std::vector m_outputVars; // All the output vars in the AstPrimitive AstPrimitive* m_primp = nullptr; // The current primitive bool m_isFirstOutput = false; // Whether the first IO port is output AstVarRef* m_outputInitVerfp = nullptr; // Initial output value for sequential UDP AstAlways* m_alwaysBlockp = nullptr; // Main Always block in UDP transform void visit(AstInitial* nodep) override { VL_RESTORER(m_inInitial); if (m_primp) m_inInitial = true; iterateChildren(nodep); } void visit(AstAssign* nodep) override { if (m_inInitial) { m_outputInitVerfp = VN_CAST(nodep->lhsp(), VarRef); } iterateChildren(nodep); } void visit(AstPrimitive* nodep) override { UASSERT_OBJ(!m_primp, nodep, "Primitives cannot nest"); VL_RESTORER(m_primp); VL_RESTORER(m_outputInitVerfp); VL_RESTORER(m_isFirstOutput); VL_RESTORER(m_inputVars); VL_RESTORER(m_outputVars); m_outputInitVerfp = nullptr; m_primp = nodep; m_isFirstOutput = false; iterateChildren(nodep); m_inputVars.clear(); m_outputVars.clear(); } void visit(AstVar* nodep) override { // Push the input and output vars for primitive. if (m_primp) { if (nodep->isIO()) { if (nodep->isInput()) { m_inputVars.push_back(nodep); } else { m_outputVars.push_back(nodep); } if ((m_inputVars.size() == 0) && (m_outputVars.size() == 1)) { m_isFirstOutput = true; } } } iterateChildren(nodep); } void visit(AstUdpTable* nodep) override { FileLine* const fl = nodep->fileline(); if (m_outputVars.size() != 1) { m_outputVars.back()->v3error( m_outputVars.size() << " output ports for UDP table, there must be one output port"); } if (!m_isFirstOutput && m_outputVars.size()) { m_inputVars[0]->v3error("First UDP port must be the output port"); } m_oFieldVarp = m_outputVars[0]; m_alwaysBlockp = new AstAlways{fl, VAlwaysKwd::ALWAYS, nullptr, nullptr}; fl->warnOff(V3ErrorCode::LATCH, true); iterateChildren(nodep); nodep->replaceWith(m_alwaysBlockp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } void visit(AstUdpTableLine* nodep) override { FileLine* const fl = nodep->fileline(); if (!nodep->udpIsCombo() && !m_oFieldVarp->isBitLogic()) { m_oFieldVarp->v3error("For sequential UDP, the output must be of 'reg' data type"); } if (nodep->udpIsCombo() && m_oFieldVarp->isBitLogic()) { m_oFieldVarp->v3error( "For combinational UDP, the output must not be a 'reg' data type"); } AstNode* iNodep = nodep->iFieldsp(); AstNode* oNodep = nodep->oFieldsp(); AstSenTree* edgetrigp = nullptr; AstLogAnd* logandp = new AstLogAnd{fl, new AstConst{fl, AstConst::BitTrue{}}, new AstConst{fl, AstConst::BitTrue{}}}; for (AstVar* const varp : m_inputVars) { if (!iNodep) break; if (AstUdpTableLineVal* linevalp = VN_CAST(iNodep, UdpTableLineVal)) { string valName = linevalp->name(); if (isEdgeTrig(valName)) { if (nodep->udpIsCombo()) { linevalp->v3error( "There should not be a edge trigger for combinational UDP table line"); } if (edgetrigp) { linevalp->v3error("There can be only one edge tigger signal"); VL_DO_DANGLING(pushDeletep(edgetrigp), edgetrigp); } edgetrigp = new AstSenTree{ fl, new AstSenItem{fl, VEdgeType::ET_BOTHEDGE, new AstVarRef{fl, varp, VAccess::READ}}}; } if (valName == "0" || valName == "f") { logandp = new AstLogAnd{ fl, logandp, new AstLogNot{fl, new AstVarRef{fl, varp, VAccess::READ}}}; } else if (valName == "1" || valName == "r") { logandp = new AstLogAnd{fl, logandp, new AstVarRef{fl, varp, VAccess::READ}}; } } iNodep = iNodep->nextp(); } uint32_t inputvars = 0; for (const AstNode* icountp = nodep->iFieldsp(); icountp; icountp = icountp->nextp()) ++inputvars; if (inputvars != m_inputVars.size()) { nodep->v3error("Incorrect number of input values, expected " << m_inputVars.size() << ", got " << inputvars); } string const oValName = nodep->udpIsCombo() ? oNodep->name() : oNodep->nextp()->name(); if (oValName == "-") { if (edgetrigp) pushDeletep(edgetrigp); if (logandp) pushDeletep(logandp); return; } if (!nodep->udpIsCombo()) { if (oNodep->name() == "0") { logandp = new AstLogAnd{ fl, logandp, new AstLogNot{fl, new AstVarRef{fl, m_oFieldVarp, VAccess::READ}}}; } else if (oNodep->name() == "1") { logandp = new AstLogAnd{fl, logandp, new AstVarRef{fl, m_oFieldVarp, VAccess::READ}}; } } fl->warnOff(V3ErrorCode::LATCH, true); AstIf* const ifp = new AstIf{fl, logandp, new AstAssign{fl, new AstVarRef{fl, m_oFieldVarp, VAccess::WRITE}, new AstConst{fl, getOutputNum(nodep, oValName)}}}; if (nodep->udpIsCombo()) { if (!isCombOutputSig(oValName)) { oNodep->v3error("Illegal value for combinational UDP line output"); } m_alwaysBlockp->addStmtsp(ifp); if (edgetrigp) pushDeletep(edgetrigp); return; } if (!isSequentOutputSig(oValName)) { oNodep->nextp()->v3error("Illegal value for sequential UDP line output"); } m_alwaysBlockp->addNext(new AstAlways{fl, VAlwaysKwd::ALWAYS, edgetrigp, ifp}); } void visit(AstNode* nodep) override { iterateChildren(nodep); } void visit(AstLogAnd* nodep) override { iterateChildren(nodep); } void visit(AstLogNot* nodep) override { iterateChildren(nodep); } // For logic processing. bool isEdgeTrig(std::string& valName) { if (valName == "*") return true; if (valName == "01" || valName == "p" || valName == "P" || valName == "r" || valName == "R") { valName = "r"; return true; } if (valName == "10" || valName == "n" || valName == "N" || valName == "f" || valName == "F") { valName = "f"; return true; } if (valName.size() == 2) { if (valName[0] == '1' || valName[1] == '0') valName = "f"; else if (valName[0] == '0' || valName[1] == '1') valName = "r"; return true; } if (valName[0] != '0' && valName[0] != '1') { valName = "?"; } return false; } bool isCombOutputSig(const std::string& valName) { return (valName == "0" || valName == "1" || valName == "x" || valName == "X"); } bool isSequentOutputSig(const std::string& valName) { return (valName == "0" || valName == "1" || valName == "x" || valName == "X" || valName == "-"); } V3Number getOutputNum(AstNode* nodep, const std::string& fieldNames) { V3Number outputNum{nodep, 1}; if (fieldNames == "0") { outputNum.setBit(0, 0); } else if (fieldNames == "1") { outputNum.setBit(0, 1); } else { outputNum.setBit(0, 'x'); } return outputNum; } public: // CONSTRUCTORS explicit UdpVisitor(AstNetlist* nodep) { iterate(nodep); } ~UdpVisitor() override = default; }; void V3Udp::udpResolve(AstNetlist* rootp) { UINFO(4, __FUNCTION__ << ": "); { const UdpVisitor visitor{rootp}; } // Destruct before checking V3Global::dumpCheckGlobalTree("udp", 0, dumpTreeEitherLevel() >= 3); }