// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Implementation of User defined primitives
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// 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-FileCopyrightText: 2003-2026 Wilson Snyder
// 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 <vector>

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<AstVar*> m_inputVars;  // All the input vars in the AstPrimitive
    std::vector<AstVar*> 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 trigger 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}};
                } else if (valName == "x" || valName == "X") {
                    // No x inputs supported yet, so this whole table line
                    // can never match. Drop the whole thing.
                    if (edgetrigp) pushDeletep(edgetrigp);
                    if (logandp) pushDeletep(logandp);
                    return;
                }
            }
            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}};
            } else if (oNodep->name() == "x" || oNodep->name() == "X") {
                // No x inputs supported yet, so this whole table line
                // can never match. Drop the whole thing.
                if (edgetrigp) pushDeletep(edgetrigp);
                if (logandp) pushDeletep(logandp);
                return;
            }
        }

        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.
    static bool isEdgeTrig(std::string& valName) {
        if (valName == "x" || valName == "X") return false;
        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] == 'x' || valName[0] == 'X' || valName[1] == 'x' || valName[1] == 'X')
                valName = "x";
            else 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;
    }
    static bool isCombOutputSig(const std::string& valName) {
        return (valName == "0" || valName == "1" || valName == "x" || valName == "X");
    }
    static bool isSequentOutputSig(const std::string& valName) {
        return (valName == "0" || valName == "1" || valName == "x" || valName == "X"
                || valName == "-");
    }
    static 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);
}
