/**CFile****************************************************************

  FileName    [giaMfs.c]

  SystemName  [ABC: Logic synthesis and verification system.]

  PackageName [Scalable AIG package.]

  Synopsis    [Interface with the MFS package.]

  Author      [Alan Mishchenko]
  
  Affiliation [UC Berkeley]

  Date        [Ver. 1.0. Started - June 20, 2005.]

  Revision    [$Id: giaMfs.c,v 1.00 2005/06/20 00:00:00 alanmi Exp $]

***********************************************************************/

#include "gia.h"
#include "opt/sfm/sfm.h"
#include "misc/tim/tim.h"

ABC_NAMESPACE_IMPL_START

////////////////////////////////////////////////////////////////////////
///                        DECLARATIONS                              ///
////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////
///                     FUNCTION DEFINITIONS                         ///
////////////////////////////////////////////////////////////////////////

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Sfm_Ntk_t * Gia_ManExtractMfs( Gia_Man_t * p )
{
    word uTruth, uTruths6[6] = {
        ABC_CONST(0xAAAAAAAAAAAAAAAA),
        ABC_CONST(0xCCCCCCCCCCCCCCCC),
        ABC_CONST(0xF0F0F0F0F0F0F0F0),
        ABC_CONST(0xFF00FF00FF00FF00),
        ABC_CONST(0xFFFF0000FFFF0000),
        ABC_CONST(0xFFFFFFFF00000000)
    };
    Gia_Obj_t * pObj, * pObjExtra;
    Vec_Wec_t * vFanins; // mfs data
    Vec_Str_t * vFixed;  // mfs data 
    Vec_Str_t * vEmpty;  // mfs data
    Vec_Wrd_t * vTruths; // mfs data
    Vec_Int_t * vArray;
    Vec_Int_t * vLeaves;
    Tim_Man_t * pManTime = (Tim_Man_t *)p->pManTime;
    int nBoxes   = Gia_ManBoxNum(p);
    int nRealPis = nBoxes ? Tim_ManPiNum(pManTime) : Gia_ManPiNum(p);
    int nRealPos = nBoxes ? Tim_ManPoNum(pManTime) : Gia_ManPoNum(p);
    int i, j, k, curCi, curCo, nBoxIns, nBoxOuts;
    int Id, iFan, nMfsVars, nBbIns = 0, nBbOuts = 0, Counter = 0;
    assert( !p->pAigExtra || Gia_ManPiNum(p->pAigExtra) <= 6 );
    Tim_ManBlackBoxIoNum( pManTime, &nBbIns, &nBbOuts );
    // skip PIs due to box outputs
    Counter += nBbOuts;
    // prepare storage
    nMfsVars = Gia_ManCiNum(p) + 1 + Gia_ManLutNum(p) + Gia_ManCoNum(p) + nBbIns + nBbOuts;
    vFanins  = Vec_WecStart( nMfsVars );
    vFixed   = Vec_StrStart( nMfsVars );
    vEmpty   = Vec_StrStart( nMfsVars );
    vTruths  = Vec_WrdStart( nMfsVars );
    // set internal PIs
    Gia_ManCleanCopyArray( p );
    Gia_ManForEachCiId( p, Id, i )
        Gia_ObjSetCopyArray( p, Id, Counter++ );
    // set constant node
    Vec_StrWriteEntry( vFixed, Counter, (char)1 );
    Vec_WrdWriteEntry( vTruths, Counter, (word)0 );
    Gia_ObjSetCopyArray( p, 0, Counter++ );
    // set internal LUTs
    vLeaves = Vec_IntAlloc( 6 );
    Gia_ObjComputeTruthTableStart( p, 6 );
    Gia_ManForEachLut( p, Id )
    {
        Vec_IntClear( vLeaves );
        vArray = Vec_WecEntry( vFanins, Counter );
        Vec_IntGrow( vArray, Gia_ObjLutSize(p, Id) );
        Gia_LutForEachFanin( p, Id, iFan, k )
        {
            assert( Gia_ObjCopyArray(p, iFan) >= 0 );
            Vec_IntPush( vArray, Gia_ObjCopyArray(p, iFan) );
            Vec_IntPush( vLeaves, iFan );
        }
        assert( Vec_IntSize(vLeaves) <= 6 );
        assert( Vec_IntSize(vLeaves) == Gia_ObjLutSize(p, Id) );
        uTruth = *Gia_ObjComputeTruthTableCut( p, Gia_ManObj(p, Id), vLeaves );
        Vec_WrdWriteEntry( vTruths, Counter, uTruth );
        Gia_ObjSetCopyArray( p, Id, Counter++ );
    }
    Gia_ObjComputeTruthTableStop( p );
    // set all POs
    Gia_ManForEachCo( p, pObj, i )
    {
        iFan = Gia_ObjFaninId0p( p, pObj );
        assert( Gia_ObjCopyArray(p, iFan) >= 0 );
        vArray = Vec_WecEntry( vFanins, Counter );
        Vec_IntFill( vArray, 1, Gia_ObjCopyArray(p, iFan) );
        if ( i < Gia_ManCoNum(p) - nRealPos ) // internal PO
        {
            Vec_StrWriteEntry( vFixed, Counter, (char)1 );
            Vec_StrWriteEntry( vEmpty, Counter, (char)1 );
            uTruth = Gia_ObjFaninC0(pObj) ? ~uTruths6[0]: uTruths6[0];
            Vec_WrdWriteEntry( vTruths, Counter, uTruth );
        }
        Gia_ObjSetCopyArray( p, Gia_ObjId(p, pObj), Counter++ );
    }
    // skip POs due to box inputs
    Counter += nBbIns;
    assert( Counter == nMfsVars );
    // add functions of the boxes
    if ( p->pAigExtra )
    {
        int iBbIn = 0, iBbOut = 0;
        Gia_ObjComputeTruthTableStart( p->pAigExtra, 6 );
        curCi = nRealPis;
        curCo = 0;
        for ( k = 0; k < nBoxes; k++ )
        {
            nBoxIns = Tim_ManBoxInputNum( pManTime, k );
            nBoxOuts = Tim_ManBoxOutputNum( pManTime, k );
            // collect truth table leaves
            Vec_IntClear( vLeaves );
            for ( i = 0; i < nBoxIns; i++ )
                Vec_IntPush( vLeaves, Gia_ObjId(p->pAigExtra, Gia_ManCi(p->pAigExtra, i)) );
            // iterate through box outputs
            if ( !Tim_ManBoxIsBlack(pManTime, k) )
            {
                for ( j = 0; j < nBoxOuts; j++ )
                {
                    // CI corresponding to the box outputs
                    pObj = Gia_ManCi( p, curCi + j );
                    Counter = Gia_ObjCopyArray( p, Gia_ObjId(p, pObj) );
                    // box output in the extra manager
                    pObjExtra = Gia_ManCo( p->pAigExtra, curCi - nRealPis + j );
                    // compute truth table
                    if ( Gia_ObjFaninId0p(p->pAigExtra, pObjExtra) == 0 )
                        uTruth = 0;
                    else if ( Gia_ObjIsCi(Gia_ObjFanin0(pObjExtra)) )
                        uTruth = uTruths6[Gia_ObjCioId(Gia_ObjFanin0(pObjExtra))];
                    else
                        uTruth = *Gia_ObjComputeTruthTableCut( p->pAigExtra, Gia_ObjFanin0(pObjExtra), vLeaves );
                    uTruth = Gia_ObjFaninC0(pObjExtra) ? ~uTruth : uTruth;
                    Vec_WrdWriteEntry( vTruths, Counter, uTruth );
                    //Dau_DsdPrintFromTruth( &uTruth, Vec_IntSize(vLeaves) );
                    // add box inputs (POs of the AIG) as fanins
                    vArray = Vec_WecEntry( vFanins, Counter );
                    Vec_IntGrow( vArray, nBoxIns );
                    for ( i = 0; i < nBoxIns; i++ )
                    {
                        iFan = Gia_ObjId( p, Gia_ManCo(p, curCo + i) );
                        assert( Gia_ObjCopyArray(p, iFan) >= 0 );
                        Vec_IntPush( vArray, Gia_ObjCopyArray(p, iFan) );
                    }
                    Vec_StrWriteEntry( vFixed, Counter, (char)1 );
                }
            }
            else // create buffers for black box inputs and outputs
            {
                for ( j = 0; j < nBoxOuts; j++ )
                {
                    // CI corresponding to the box outputs
                    pObj = Gia_ManCi( p, curCi + j );
                    Counter = Gia_ObjCopyArray( p, Gia_ObjId(p, pObj) );
                    // connect it with the special primary input (iBbOut)
                    vArray = Vec_WecEntry( vFanins, Counter );
                    assert( Vec_IntSize(vArray) == 0 );
                    Vec_IntFill( vArray, 1, iBbOut++ );
                    Vec_StrWriteEntry( vFixed, Counter, (char)1 );
                    Vec_StrWriteEntry( vEmpty, Counter, (char)1 );
                    Vec_WrdWriteEntry( vTruths, Counter, uTruths6[0] );
                }
                for ( i = 0; i < nBoxIns; i++ )
                {
                    // CO corresponding to the box inputs
                    pObj = Gia_ManCo( p, curCo + i );
                    Counter = Gia_ObjCopyArray( p, Gia_ObjId(p, pObj) );
                    // connect it with the special primary output (iBbIn)
                    vArray = Vec_WecEntry( vFanins, nMfsVars - nBbIns + iBbIn++ );
                    assert( Vec_IntSize(vArray) == 0 );
                    Vec_IntFill( vArray, 1, Counter );
                }
            }
            // set internal POs pointing directly to internal PIs as no-delay
            for ( i = 0; i < nBoxIns; i++ )
            {
                pObj = Gia_ManCo( p, curCo + i );
                if ( !Gia_ObjIsCi( Gia_ObjFanin0(pObj) ) )
                    continue;
                Counter = Gia_ObjCopyArray( p, Gia_ObjFaninId0p(p, pObj) );
                Vec_StrWriteEntry( vEmpty, Counter, (char)1 );
            }
            curCo += nBoxIns;
            curCi += nBoxOuts;
        }
        curCo += nRealPos;
        Gia_ObjComputeTruthTableStop( p->pAigExtra );
        // verify counts
        assert( curCi == Gia_ManCiNum(p) );
        assert( curCo == Gia_ManCoNum(p) );
        assert( curCi - nRealPis == Gia_ManCoNum(p->pAigExtra) );
        assert( iBbIn  == nBbIns );
        assert( iBbOut == nBbOuts );
    }
    // finalize 
    Vec_IntFree( vLeaves );
    return Sfm_NtkConstruct( vFanins, nBbOuts + nRealPis, nRealPos + nBbIns, vFixed, vEmpty, vTruths );
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Gia_Man_t * Gia_ManInsertMfs( Gia_Man_t * p, Sfm_Ntk_t * pNtk )
{
    extern int Gia_ManFromIfLogicCreateLut( Gia_Man_t * pNew, word * pRes, Vec_Int_t * vLeaves, Vec_Int_t * vCover, Vec_Int_t * vMapping, Vec_Int_t * vMapping2 );
    Gia_Man_t * pNew;  Gia_Obj_t * pObj;
    Tim_Man_t * pManTime = (Tim_Man_t *)p->pManTime;
    int nBoxes   = Gia_ManBoxNum(p);
    int nRealPis = nBoxes ? Tim_ManPiNum(pManTime) : Gia_ManPiNum(p);
    int nRealPos = nBoxes ? Tim_ManPoNum(pManTime) : Gia_ManPoNum(p);
    int i, k, Id, curCi, curCo, nBoxIns, nBoxOuts, iLitNew, iMfsId, iGroup, Fanin;
    int nMfsNodes;
    word * pTruth, uTruthVar = ABC_CONST(0xAAAAAAAAAAAAAAAA);
    Vec_Wec_t * vGroups = Vec_WecStart( nBoxes );
    Vec_Int_t * vMfs2Gia;
    Vec_Int_t * vGroupMap;
    Vec_Int_t * vMfsTopo, * vCover, * vBoxesLeft;
    Vec_Int_t * vArray, * vLeaves;
    Vec_Int_t * vMapping, * vMapping2;
    int nBbIns = 0, nBbOuts = 0;
    Tim_ManBlackBoxIoNum( pManTime, &nBbIns, &nBbOuts );
    nMfsNodes = 1 + Gia_ManCiNum(p) + Gia_ManLutNum(p) + Gia_ManCoNum(p) + nBbIns + nBbOuts;
    vMfs2Gia  = Vec_IntStartFull( nMfsNodes );
    vGroupMap = Vec_IntStartFull( nMfsNodes );
    // collect nodes
    curCi = nRealPis;
    curCo = 0;
    for ( i = 0; i < nBoxes; i++ )
    {
        nBoxIns = Tim_ManBoxInputNum( pManTime, i );
        nBoxOuts = Tim_ManBoxOutputNum( pManTime, i );
        vArray = Vec_WecEntry( vGroups, i );
        for ( k = 0; k < nBoxIns; k++ )
        {
            pObj = Gia_ManCo( p, curCo + k );
            iMfsId = Gia_ObjCopyArray( p, Gia_ObjId(p, pObj) );
            assert( iMfsId > 0 );
            Vec_IntPush( vArray, iMfsId );
            Vec_IntWriteEntry( vGroupMap, iMfsId, Abc_Var2Lit(i,0) );
        }
        for ( k = 0; k < nBoxOuts; k++ )
        {
            pObj = Gia_ManCi( p, curCi + k );
            iMfsId = Gia_ObjCopyArray( p, Gia_ObjId(p, pObj) );
            assert( iMfsId > 0 );
            Vec_IntPush( vArray, iMfsId );
            Vec_IntWriteEntry( vGroupMap, iMfsId, Abc_Var2Lit(i,1) );
        }
        curCo += nBoxIns;
        curCi += nBoxOuts;
    }
    curCo += nRealPos;
    assert( curCi == Gia_ManCiNum(p) );
    assert( curCo == Gia_ManCoNum(p) );

    // collect nodes in the given order
    vBoxesLeft = Vec_IntAlloc( nBoxes );
    vMfsTopo = Sfm_NtkDfs( pNtk, vGroups, vGroupMap, vBoxesLeft );
    assert( Vec_IntSize(vBoxesLeft) <= nBoxes );
    assert( Vec_IntSize(vMfsTopo) > 0 );

    // start new GIA
    pNew = Gia_ManStart( Gia_ManObjNum(p) );
    pNew->pName = Abc_UtilStrsav( p->pName );
    pNew->pSpec = Abc_UtilStrsav( p->pSpec );

    // start mapping
    vMapping  = Vec_IntStart( Gia_ManObjNum(p) );
    vMapping2 = Vec_IntStart( 1 );
    // create const LUT
    Vec_IntWriteEntry( vMapping, 0, Vec_IntSize(vMapping2) );
    Vec_IntPush( vMapping2, 0 );
    Vec_IntPush( vMapping2, 0 );

    // map constant
    Vec_IntWriteEntry( vMfs2Gia, Gia_ObjCopyArray(p, 0), 0 );
    // map primary inputs
    Gia_ManForEachCiId( p, Id, i )
        if ( i < nRealPis )
            Vec_IntWriteEntry( vMfs2Gia, Gia_ObjCopyArray(p, Id), Gia_ManAppendCi(pNew) );
    // map internal nodes
    vLeaves = Vec_IntAlloc( 6 );
    vCover = Vec_IntAlloc( 1 << 16 );
    Vec_IntForEachEntry( vMfsTopo, iMfsId, i )
    {
        pTruth = Sfm_NodeReadTruth( pNtk, iMfsId );
        iGroup = Vec_IntEntry( vGroupMap, iMfsId );
        vArray = Sfm_NodeReadFanins( pNtk, iMfsId ); // belongs to pNtk
        if ( Vec_IntSize(vArray) == 1 && Vec_IntEntry(vArray,0) < nBbOuts ) // skip unreal inputs
        {
            // create CI for the output of black box
            assert( Abc_LitIsCompl(iGroup) );
            iLitNew = Gia_ManAppendCi( pNew );
            Vec_IntWriteEntry( vMfs2Gia, iMfsId, iLitNew );
            continue;
        }
        Vec_IntClear( vLeaves );
        Vec_IntForEachEntry( vArray, Fanin, k )
        {
            iLitNew = Vec_IntEntry( vMfs2Gia, Fanin );  assert( iLitNew >= 0 );
            Vec_IntPush( vLeaves, iLitNew );            
        }
        if ( iGroup == -1 ) // internal node
        {
            assert( Sfm_NodeReadUsed(pNtk, iMfsId) );
            iLitNew = Gia_ManFromIfLogicCreateLut( pNew, pTruth, vLeaves, vCover, vMapping, vMapping2 );
        }
        else if ( Abc_LitIsCompl(iGroup) ) // internal CI
        {
            //Dau_DsdPrintFromTruth( pTruth, Vec_IntSize(vLeaves) );
            iLitNew = Gia_ManAppendCi( pNew );
        }
        else // internal CO
        {
            iLitNew = Gia_ManAppendCo( pNew, Abc_LitNotCond(Vec_IntEntry(vLeaves, 0), pTruth[0] == ~uTruthVar) );
            //printf("Group = %d. po = %d\n", iGroup>>1, iMfsId );
        }
        Vec_IntWriteEntry( vMfs2Gia, iMfsId, iLitNew );
    }
    Vec_IntFree( vCover );
    Vec_IntFree( vLeaves );

    // map primary outputs
    Gia_ManForEachCo( p, pObj, i )
    {
        if ( i < Gia_ManCoNum(p) - nRealPos ) // internal COs
        {
            iMfsId = Gia_ObjCopyArray( p, Gia_ObjId(p, pObj) );
            iGroup = Vec_IntEntry( vGroupMap, iMfsId );
            if ( Vec_IntFind(vMfsTopo, iGroup) >= 0 )
            {
                iLitNew = Vec_IntEntry( vMfs2Gia, iMfsId );
                assert( iLitNew >= 0 );
            }
            continue;
        }
        iLitNew = Vec_IntEntry( vMfs2Gia, Gia_ObjCopyArray(p, Gia_ObjFaninId0p(p, pObj)) );
        assert( iLitNew >= 0 );
        Gia_ManAppendCo( pNew, Abc_LitNotCond(iLitNew, Gia_ObjFaninC0(pObj)) );
    }

    // finish mapping 
    if ( Vec_IntSize(vMapping) > Gia_ManObjNum(pNew) )
        Vec_IntShrink( vMapping, Gia_ManObjNum(pNew) );
    else
        Vec_IntFillExtra( vMapping, Gia_ManObjNum(pNew), 0 );
    assert( Vec_IntSize(vMapping) == Gia_ManObjNum(pNew) );
    Vec_IntForEachEntry( vMapping, iLitNew, i )
        if ( iLitNew > 0 )
            Vec_IntAddToEntry( vMapping, i, Gia_ManObjNum(pNew) );
    Vec_IntAppend( vMapping, vMapping2 );
    Vec_IntFree( vMapping2 );
    assert( pNew->vMapping == NULL );
    pNew->vMapping = vMapping;

    // create new timing manager and extra AIG
    if ( pManTime )
        pNew->pManTime = Gia_ManUpdateTimMan2( p, vBoxesLeft, 0 );
    // update extra STG
    if ( p->pAigExtra )
        pNew->pAigExtra = Gia_ManUpdateExtraAig2( p->pManTime, p->pAigExtra, vBoxesLeft );
    // duplicated flops
    if ( p->vRegClasses )
        pNew->vRegClasses = Vec_IntDup( p->vRegClasses );
    // duplicated initial state
    if ( p->vRegInits )
        pNew->vRegInits = Vec_IntDup( p->vRegInits );

    // cleanup
    Vec_WecFree( vGroups );
    Vec_IntFree( vMfsTopo );
    Vec_IntFree( vGroupMap );
    Vec_IntFree( vMfs2Gia );
    Vec_IntFree( vBoxesLeft );
    return pNew;
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Gia_Man_t * Gia_ManPerformMfs( Gia_Man_t * p, Sfm_Par_t * pPars )
{
    Sfm_Ntk_t * pNtk;
    Gia_Man_t * pNew;
    int nFaninMax, nNodes;
    assert( Gia_ManRegNum(p) == 0 );
    assert( p->vMapping != NULL );
    if ( p->pManTime != NULL && p->pAigExtra == NULL )
    {
        Abc_Print( 1, "Timing manager is given but there is no GIA of boxes.\n" );
        return NULL;
    }
    // count fanouts
    nFaninMax = Gia_ManLutSizeMax( p );
    if ( nFaninMax > 6 )
    {
        Abc_Print( 1, "Currently \"&mfs\" cannot process the network containing nodes with more than 6 fanins.\n" );
        return NULL;
    }
    // collect information
    pNtk = Gia_ManExtractMfs( p );
    // perform optimization
    nNodes = Sfm_NtkPerform( pNtk, pPars );
    if ( nNodes == 0 )
    {
        Abc_Print( 1, "The network is not changed by \"&mfs\".\n" );
        pNew = Gia_ManDup( p );
        pNew->vMapping = Vec_IntDup( p->vMapping );
        Gia_ManTransferTiming( pNew, p );
    }
    else
    {
        pNew = Gia_ManInsertMfs( p, pNtk );
        if( pPars->fVerbose )
            Abc_Print( 1, "The network has %d nodes changed by \"&mfs\".\n", nNodes );
        // check integrity
        //Gia_ManCheckIntegrityWithBoxes( pNew );
    }
    Sfm_NtkFree( pNtk );
    return pNew;
}

////////////////////////////////////////////////////////////////////////
///                       END OF FILE                                ///
////////////////////////////////////////////////////////////////////////

ABC_NAMESPACE_IMPL_END

