// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilator: Add temporaries, such as for changed nodes
//
// Code available from: http://www.veripool.org/verilator
//
//*************************************************************************
//
// Copyright 2003-2019 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.
//
// Verilator is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
//*************************************************************************
// V3Changed's Transformations:
//
// Each module:
//	Each combo block
//	    For each variable that comes from combo block and is generated AFTER a usage
//		Add __Vlast_{var} to local section, init to current value (just use array?)
//		Change = if any var != last.
//	    If a signal is used as a clock in this module or any
//	    module *below*, and it isn't a input to this module,
//	    we need to indicate a new clock has been created.
//
//*************************************************************************

#include "config_build.h"
#include "verilatedos.h"

#include "V3Global.h"
#include "V3Ast.h"
#include "V3Changed.h"
#include "V3EmitCBase.h"

#include <algorithm>
#include <cstdarg>
#include <set>

//######################################################################

class ChangedState {
public:
    // STATE
    AstNodeModule*	m_topModp;	// Top module
    AstScope*		m_scopetopp;	// Scope under TOPSCOPE
    AstCFunc*		m_chgFuncp;	// Change function we're building
    AstCFunc*		m_tlChgFuncp;	// Top level change function we're building
    int			m_numStmts;     // Number of statements added to m_chgFuncp
    int 		m_funcNum;	// Number of change functions emitted

    ChangedState() {
	m_topModp = NULL;
	m_chgFuncp = NULL;
	m_scopetopp = NULL;
	m_tlChgFuncp = NULL;
	m_numStmts = 0;
	m_funcNum = 0;
    }
    ~ChangedState() {}

    void maybeCreateChgFuncp() {
	// Don't create an extra function call if splitting is disabled
	if (!v3Global.opt.outputSplitCFuncs()) {
	    m_chgFuncp = m_tlChgFuncp;
	    return;
	}
	if (!m_chgFuncp || v3Global.opt.outputSplitCFuncs() < m_numStmts) {
	    m_chgFuncp = new AstCFunc(m_scopetopp->fileline(), "_change_request_" + cvtToStr(++m_funcNum), m_scopetopp, "QData");
	    m_chgFuncp->argTypes(EmitCBaseVisitor::symClassVar());
	    m_chgFuncp->symProlog(true);
	    m_chgFuncp->declPrivate(true);
	    m_scopetopp->addActivep(m_chgFuncp);

	    // Add a top call to it
	    AstCCall* callp = new AstCCall(m_scopetopp->fileline(), m_chgFuncp);
	    callp->argTypes("vlSymsp");

	    if (!m_tlChgFuncp->stmtsp()) {
		m_tlChgFuncp->addStmtsp(new AstCReturn(m_scopetopp->fileline(), callp));
	    } else {
                AstCReturn* returnp = VN_CAST(m_tlChgFuncp->stmtsp(), CReturn);
		if (!returnp) m_scopetopp->v3fatalSrc("Lost CReturn in top change function");
		// This is currently using AstLogOr which will shortcut the evaluation if
		// any function returns true. This is likely what we want and is similar to the logic already in use
		// inside V3EmitC, however, it also means that verbose logging may miss to print change detect variables.
		AstNode* newp = new AstCReturn(m_scopetopp->fileline(),
					       new AstLogOr(m_scopetopp->fileline(), callp,
							    returnp->lhsp()->unlinkFrBack()));
		returnp->replaceWith(newp);
		returnp->deleteTree(); VL_DANGLING(returnp);
	    }
	    m_numStmts = 0;
	}
    }
};

//######################################################################
// Utility visitor to find elements to be compared

class ChangedInsertVisitor : public AstNVisitor {
private:
    // STATE
    ChangedState*	m_statep;	// Shared state across visitors
    AstVarScope*	m_vscp;		// Original (non-change) variable we're change-detecting
    AstVarScope*	m_newvscp;	// New (change detect) variable we're change-detecting
    AstNode*		m_varEqnp;	// Original var's equation to get var value
    AstNode*		m_newLvEqnp;	// New var's equation to read value
    AstNode*		m_newRvEqnp;	// New var's equation to set value
    uint32_t		m_detects;	// # detects created

    // CONSTANTS
    enum MiscConsts {
	DETECTARRAY_MAX_INDEXES = 256	// How many indexes before error
	// Ok to increase this, but may result in much slower model
    };

    void newChangeDet() {
	if (++m_detects > DETECTARRAY_MAX_INDEXES) {
	    m_vscp->v3warn(E_DETECTARRAY, "Unsupported: Can't detect more than "<<cvtToStr(DETECTARRAY_MAX_INDEXES)
			   <<" array indexes (probably with UNOPTFLAT warning suppressed): "<<m_vscp->prettyName()<<endl
			   <<m_vscp->warnMore()
			   <<"... Could recompile with DETECTARRAY_MAX_INDEXES increased"<<endl);
	    return;
	}
	m_statep->maybeCreateChgFuncp();

        AstChangeDet* changep = new AstChangeDet(m_vscp->fileline(),
                                                 m_varEqnp->cloneTree(true),
                                                 m_newRvEqnp->cloneTree(true), false);
	m_statep->m_chgFuncp->addStmtsp(changep);
        AstAssign* initp = new AstAssign(m_vscp->fileline(),
                                         m_newLvEqnp->cloneTree(true),
                                         m_varEqnp->cloneTree(true));
	m_statep->m_chgFuncp->addFinalsp(initp);
	EmitCBaseCounterVisitor visitor(initp);
	m_statep->m_numStmts += visitor.count();
    }

    virtual void visit(AstBasicDType* nodep) {
	newChangeDet();
    }
    virtual void visit(AstPackArrayDType* nodep) {
	newChangeDet();
    }
    virtual void visit(AstUnpackArrayDType* nodep) {
	for (int index=0; index < nodep->elementsConst(); ++index) {
	    AstNode* origVEp = m_varEqnp;
	    AstNode* origNLEp = m_newLvEqnp;
	    AstNode* origNREp = m_newRvEqnp;

	    m_varEqnp   = new AstArraySel(nodep->fileline(), m_varEqnp->cloneTree(true), index);
	    m_newLvEqnp = new AstArraySel(nodep->fileline(), m_newLvEqnp->cloneTree(true), index);
	    m_newRvEqnp = new AstArraySel(nodep->fileline(), m_newRvEqnp->cloneTree(true), index);

            iterate(nodep->subDTypep()->skipRefp());

	    m_varEqnp->deleteTree();
	    m_newLvEqnp->deleteTree();
	    m_newRvEqnp->deleteTree();

	    m_varEqnp   = origVEp;
	    m_newLvEqnp = origNLEp;
	    m_newRvEqnp = origNREp;
	}
    }
    virtual void visit(AstNodeClassDType* nodep) {
	if (nodep->packedUnsup()) {
	    newChangeDet();
	} else {
	    if (debug()) nodep->dumpTree(cout,"-DETECTARRAY-class-");
            m_vscp->v3warn(E_DETECTARRAY, "Unsupported: Can't detect changes on complex variable"
                           " (probably with UNOPTFLAT warning suppressed): "
                           <<m_vscp->varp()->prettyName());
	}
    }
    virtual void visit(AstNode* nodep) {
        iterateChildren(nodep);
	if (debug()) nodep->dumpTree(cout,"-DETECTARRAY-general-");
        m_vscp->v3warn(E_DETECTARRAY, "Unsupported: Can't detect changes on complex variable"
                       " (probably with UNOPTFLAT warning suppressed): "
                       <<m_vscp->varp()->prettyName());
    }
public:
    // CONSTUCTORS
    ChangedInsertVisitor(AstVarScope* vscp, ChangedState* statep) {
	m_statep = statep;
	m_vscp = vscp;
	m_detects = 0;
	{
	    AstVar* varp = m_vscp->varp();
	    string newvarname = "__Vchglast__"+m_vscp->scopep()->nameDotless()+"__"+varp->shortName();
	    // Create:  VARREF(_last)
	    //          ASSIGN(VARREF(_last), VARREF(var))
	    //          ...
	    //          CHANGEDET(VARREF(_last), VARREF(var))
            AstVar* newvarp = new AstVar(varp->fileline(), AstVarType::MODULETEMP, newvarname, varp);
	    m_statep->m_topModp->addStmtp(newvarp);
	    m_newvscp = new AstVarScope(m_vscp->fileline(), m_statep->m_scopetopp, newvarp);
	    m_statep->m_scopetopp->addVarp(m_newvscp);

	    m_varEqnp   = new AstVarRef(m_vscp->fileline(), m_vscp, false);
	    m_newLvEqnp = new AstVarRef(m_vscp->fileline(), m_newvscp, true);
	    m_newRvEqnp = new AstVarRef(m_vscp->fileline(), m_newvscp, false);
	}
        iterate(vscp->dtypep()->skipRefp());
	m_varEqnp->deleteTree();
	m_newLvEqnp->deleteTree();
	m_newRvEqnp->deleteTree();
    }
    virtual ~ChangedInsertVisitor() {}
};

//######################################################################
// Changed state, as a visitor of each AstNode

class ChangedVisitor : public AstNVisitor {
private:
    // NODE STATE
    // Entire netlist:
    //  AstVarScope::user1()		-> bool.  True indicates processed
    AstUser1InUse	m_inuser1;

    // STATE
    ChangedState*	m_statep;	// Shared state across visitors

    // METHODS
    VL_DEBUG_FUNC;  // Declare debug()

    void genChangeDet(AstVarScope* vscp) {
	vscp->v3warn(IMPERFECTSCH,"Imperfect scheduling of variable: "<<vscp);
	ChangedInsertVisitor visitor (vscp, m_statep);
    }

    // VISITORS
    virtual void visit(AstNodeModule* nodep) {
	UINFO(4," MOD   "<<nodep<<endl);
	if (nodep->isTop()) {
	    m_statep->m_topModp = nodep;
	}
        iterateChildren(nodep);
    }

    virtual void visit(AstTopScope* nodep) {
	UINFO(4," TS "<<nodep<<endl);
	// Clearing
	AstNode::user1ClearTree();
	// Create the change detection function
	AstScope* scopep = nodep->scopep();
	if (!scopep) nodep->v3fatalSrc("No scope found on top level, perhaps you have no statements?");
	m_statep->m_scopetopp = scopep;

	// Create a wrapper change detection function that calls each change detection function
	m_statep->m_tlChgFuncp = new AstCFunc(nodep->fileline(), "_change_request", scopep, "QData");
	m_statep->m_tlChgFuncp->argTypes(EmitCBaseVisitor::symClassVar());
	m_statep->m_tlChgFuncp->symProlog(true);
	m_statep->m_tlChgFuncp->declPrivate(true);
	m_statep->m_scopetopp->addActivep(m_statep->m_tlChgFuncp);
	// Each change detection function needs at least one AstChangeDet
	// to ensure that V3EmitC outputs the necessary code.
	m_statep->maybeCreateChgFuncp();
	m_statep->m_chgFuncp->addStmtsp(new AstChangeDet(nodep->fileline(), NULL, NULL, false));

        iterateChildren(nodep);
    }
    virtual void visit(AstVarScope* nodep) {
	if (nodep->isCircular()) {
	    UINFO(8,"  CIRC "<<nodep<<endl);
	    if (!nodep->user1SetOnce()) {
		genChangeDet(nodep);
	    }
	}
    }
    virtual void visit(AstNodeMath* nodep) {
	// Short-circuit
    }
    //--------------------
    // Default: Just iterate
    virtual void visit(AstNode* nodep) {
        iterateChildren(nodep);
    }

public:
    // CONSTUCTORS
    ChangedVisitor(AstNetlist* nodep, ChangedState* statep) {
	m_statep = statep;
        iterate(nodep);
    }
    virtual ~ChangedVisitor() {}
};

//######################################################################
// Changed class functions

void V3Changed::changedAll(AstNetlist* nodep) {
    UINFO(2,__FUNCTION__<<": "<<endl);
    {
        ChangedState state;
        ChangedVisitor visitor (nodep, &state);
    }  // Destruct before checking
    V3Global::dumpCheckGlobalTree("changed", 0, v3Global.opt.dumpTreeLevel(__FILE__) >= 3);
}
